summaryrefslogtreecommitdiffstats
path: root/widget
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--widget/BasicEvents.h1394
-rw-r--r--widget/CommandList.h169
-rw-r--r--widget/CompositorWidget.cpp65
-rw-r--r--widget/CompositorWidget.h287
-rw-r--r--widget/ContentCache.cpp1703
-rw-r--r--widget/ContentCache.h531
-rw-r--r--widget/ContentEvents.h302
-rw-r--r--widget/EventClassList.h58
-rw-r--r--widget/EventForwards.h472
-rw-r--r--widget/EventMessageList.h476
-rw-r--r--widget/FontRange.h24
-rw-r--r--widget/GfxDriverInfo.cpp1032
-rw-r--r--widget/GfxDriverInfo.h519
-rw-r--r--widget/GfxInfoBase.cpp1840
-rw-r--r--widget/GfxInfoBase.h179
-rw-r--r--widget/GfxInfoCollector.cpp43
-rw-r--r--widget/GfxInfoCollector.h88
-rw-r--r--widget/GfxInfoX11.cpp1153
-rw-r--r--widget/GfxInfoX11.h121
-rw-r--r--widget/IMEData.cpp343
-rw-r--r--widget/IMEData.h987
-rw-r--r--widget/IconLoader.cpp181
-rw-r--r--widget/IconLoader.h81
-rw-r--r--widget/InProcessCompositorWidget.cpp115
-rw-r--r--widget/InProcessCompositorWidget.h49
-rw-r--r--widget/InputData.cpp855
-rw-r--r--widget/InputData.h767
-rw-r--r--widget/LSBUtils.cpp67
-rw-r--r--widget/LSBUtils.h25
-rw-r--r--widget/LookAndFeel.h602
-rw-r--r--widget/LookAndFeelTypes.ipdlh94
-rw-r--r--widget/MediaKeysEventSourceFactory.h24
-rw-r--r--widget/MiscEvents.h142
-rw-r--r--widget/MouseEvents.h703
-rw-r--r--widget/NativeKeyToDOMCodeName.h821
-rw-r--r--widget/NativeKeyToDOMKeyName.h1288
-rw-r--r--widget/PluginWidgetProxy.cpp173
-rw-r--r--widget/PluginWidgetProxy.h80
-rw-r--r--widget/PrintBackgroundTask.h120
-rw-r--r--widget/PuppetBidiKeyboard.cpp45
-rw-r--r--widget/PuppetBidiKeyboard.h35
-rw-r--r--widget/PuppetWidget.cpp1291
-rw-r--r--widget/PuppetWidget.h427
-rw-r--r--widget/RemoteLookAndFeel.cpp265
-rw-r--r--widget/RemoteLookAndFeel.h71
-rw-r--r--widget/Screen.cpp123
-rw-r--r--widget/Screen.h54
-rw-r--r--widget/ScreenManager.cpp245
-rw-r--r--widget/ScreenManager.h61
-rw-r--r--widget/ScrollbarDrawingMac.cpp327
-rw-r--r--widget/ScrollbarDrawingMac.h53
-rw-r--r--widget/SharedWidgetUtils.cpp273
-rw-r--r--widget/SystemTimeConverter.h235
-rw-r--r--widget/TextEventDispatcher.cpp925
-rw-r--r--widget/TextEventDispatcher.h534
-rw-r--r--widget/TextEventDispatcherListener.h95
-rw-r--r--widget/TextEvents.h1390
-rw-r--r--widget/TextRange.h287
-rw-r--r--widget/ThemeChangeKind.h35
-rw-r--r--widget/TouchEvents.h194
-rw-r--r--widget/TouchResampler.cpp381
-rw-r--r--widget/TouchResampler.h191
-rw-r--r--widget/VsyncDispatcher.cpp196
-rw-r--r--widget/VsyncDispatcher.h111
-rw-r--r--widget/WidgetEventImpl.cpp1862
-rw-r--r--widget/WidgetMessageUtils.h60
-rw-r--r--widget/WidgetTraceEvent.h27
-rw-r--r--widget/WidgetUtils.cpp149
-rw-r--r--widget/WidgetUtils.h101
-rw-r--r--widget/WindowSurface.h38
-rw-r--r--widget/WindowSurfaceX11SHM.cpp27
-rw-r--r--widget/WindowSurfaceX11SHM.h36
-rw-r--r--widget/android/AndroidAlerts.cpp146
-rw-r--r--widget/android/AndroidAlerts.h47
-rw-r--r--widget/android/AndroidBridge.cpp741
-rw-r--r--widget/android/AndroidBridge.h362
-rw-r--r--widget/android/AndroidBridgeUtilities.h19
-rw-r--r--widget/android/AndroidColors.h28
-rw-r--r--widget/android/AndroidCompositorWidget.cpp30
-rw-r--r--widget/android/AndroidCompositorWidget.h40
-rw-r--r--widget/android/AndroidContentController.cpp68
-rw-r--r--widget/android/AndroidContentController.h51
-rw-r--r--widget/android/AndroidUiThread.cpp342
-rw-r--r--widget/android/AndroidUiThread.h25
-rw-r--r--widget/android/AndroidView.h35
-rw-r--r--widget/android/AndroidVsync.cpp140
-rw-r--r--widget/android/AndroidVsync.h75
-rw-r--r--widget/android/Base64UtilsSupport.h52
-rw-r--r--widget/android/EventDispatcher.cpp1026
-rw-r--r--widget/android/EventDispatcher.h104
-rw-r--r--widget/android/GeckoBatteryManager.h28
-rw-r--r--widget/android/GeckoEditableSupport.cpp1630
-rw-r--r--widget/android/GeckoEditableSupport.h287
-rw-r--r--widget/android/GeckoNetworkManager.h45
-rw-r--r--widget/android/GeckoProcessManager.cpp77
-rw-r--r--widget/android/GeckoProcessManager.h84
-rw-r--r--widget/android/GeckoScreenOrientation.h52
-rw-r--r--widget/android/GeckoSystemStateListener.h31
-rw-r--r--widget/android/GeckoTelemetryDelegate.h101
-rw-r--r--widget/android/GeckoVRManager.h24
-rw-r--r--widget/android/GeckoViewSupport.h109
-rw-r--r--widget/android/GfxInfo.cpp800
-rw-r--r--widget/android/GfxInfo.h117
-rw-r--r--widget/android/ImageDecoderSupport.cpp176
-rw-r--r--widget/android/ImageDecoderSupport.h30
-rw-r--r--widget/android/MediaKeysEventSourceFactory.cpp17
-rw-r--r--widget/android/PrefsHelper.h306
-rw-r--r--widget/android/ScreenHelperAndroid.cpp129
-rw-r--r--widget/android/ScreenHelperAndroid.h40
-rw-r--r--widget/android/Telemetry.h86
-rw-r--r--widget/android/WebAuthnTokenManager.h79
-rw-r--r--widget/android/WebExecutorSupport.cpp460
-rw-r--r--widget/android/WebExecutorSupport.h32
-rw-r--r--widget/android/WindowEvent.h57
-rw-r--r--widget/android/bindings/AccessibilityEvent-classes.txt3
-rw-r--r--widget/android/bindings/AndroidBuild-classes.txt5
-rw-r--r--widget/android/bindings/AndroidGraphics-classes.txt10
-rw-r--r--widget/android/bindings/AndroidInputType-classes.txt3
-rw-r--r--widget/android/bindings/AndroidRect-classes.txt2
-rw-r--r--widget/android/bindings/InetAddress-classes.txt6
-rw-r--r--widget/android/bindings/JavaBuiltins-classes.txt25
-rw-r--r--widget/android/bindings/JavaExceptions-classes.txt5
-rw-r--r--widget/android/bindings/KeyEvent-classes.txt3
-rw-r--r--widget/android/bindings/MediaCodec-classes.txt9
-rw-r--r--widget/android/bindings/MotionEvent-classes.txt3
-rw-r--r--widget/android/bindings/SurfaceTexture-classes.txt2
-rw-r--r--widget/android/bindings/ViewConfiguration-classes.txt1
-rw-r--r--widget/android/bindings/moz.build53
-rw-r--r--widget/android/components.conf114
-rw-r--r--widget/android/jni/Accessors.h251
-rw-r--r--widget/android/jni/Conversions.cpp107
-rw-r--r--widget/android/jni/Conversions.h23
-rw-r--r--widget/android/jni/GeckoBundleUtils.h41
-rw-r--r--widget/android/jni/GeckoResultUtils.h54
-rw-r--r--widget/android/jni/Natives.h1615
-rw-r--r--widget/android/jni/Refs.h1016
-rw-r--r--widget/android/jni/Types.h160
-rw-r--r--widget/android/jni/Utils.cpp341
-rw-r--r--widget/android/jni/Utils.h148
-rw-r--r--widget/android/jni/moz.build33
-rw-r--r--widget/android/moz.build198
-rw-r--r--widget/android/nsAndroidProtocolHandler.cpp140
-rw-r--r--widget/android/nsAndroidProtocolHandler.h33
-rw-r--r--widget/android/nsAppShell.cpp781
-rw-r--r--widget/android/nsAppShell.h250
-rw-r--r--widget/android/nsClipboard.cpp163
-rw-r--r--widget/android/nsClipboard.h22
-rw-r--r--widget/android/nsDeviceContextAndroid.cpp79
-rw-r--r--widget/android/nsDeviceContextAndroid.h33
-rw-r--r--widget/android/nsIAndroidBridge.idl87
-rw-r--r--widget/android/nsLookAndFeel.cpp531
-rw-r--r--widget/android/nsLookAndFeel.h49
-rw-r--r--widget/android/nsNativeBasicThemeAndroid.cpp15
-rw-r--r--widget/android/nsNativeBasicThemeAndroid.h20
-rw-r--r--widget/android/nsNativeThemeAndroid.cpp926
-rw-r--r--widget/android/nsNativeThemeAndroid.h66
-rw-r--r--widget/android/nsPrintSettingsServiceAndroid.cpp33
-rw-r--r--widget/android/nsPrintSettingsServiceAndroid.h18
-rw-r--r--widget/android/nsUserIdleServiceAndroid.cpp14
-rw-r--r--widget/android/nsUserIdleServiceAndroid.h35
-rw-r--r--widget/android/nsWidgetFactory.cpp21
-rw-r--r--widget/android/nsWidgetFactory.h22
-rw-r--r--widget/android/nsWindow.cpp2619
-rw-r--r--widget/android/nsWindow.h279
-rw-r--r--widget/cocoa/CFTypeRefPtr.h194
-rw-r--r--widget/cocoa/CustomCocoaEvents.h18
-rw-r--r--widget/cocoa/DesktopBackgroundImage.h19
-rw-r--r--widget/cocoa/DesktopBackgroundImage.mm68
-rw-r--r--widget/cocoa/GfxInfo.h102
-rw-r--r--widget/cocoa/GfxInfo.mm560
-rw-r--r--widget/cocoa/IconLoaderHelperCocoa.h72
-rw-r--r--widget/cocoa/IconLoaderHelperCocoa.mm141
-rw-r--r--widget/cocoa/MediaHardwareKeysEventSourceMac.h47
-rw-r--r--widget/cocoa/MediaHardwareKeysEventSourceMac.mm183
-rw-r--r--widget/cocoa/MediaHardwareKeysEventSourceMacMediaCenter.h60
-rw-r--r--widget/cocoa/MediaHardwareKeysEventSourceMacMediaCenter.mm172
-rw-r--r--widget/cocoa/MediaKeysEventSourceFactory.cpp23
-rw-r--r--widget/cocoa/NativeKeyBindings.h45
-rw-r--r--widget/cocoa/NativeKeyBindings.mm261
-rw-r--r--widget/cocoa/OSXNotificationCenter.h60
-rw-r--r--widget/cocoa/OSXNotificationCenter.mm572
-rw-r--r--widget/cocoa/ScreenHelperCocoa.h34
-rw-r--r--widget/cocoa/ScreenHelperCocoa.mm156
-rw-r--r--widget/cocoa/SwipeTracker.h100
-rw-r--r--widget/cocoa/SwipeTracker.mm201
-rw-r--r--widget/cocoa/TextInputHandler.h1287
-rw-r--r--widget/cocoa/TextInputHandler.mm5061
-rw-r--r--widget/cocoa/VibrancyManager.h96
-rw-r--r--widget/cocoa/VibrancyManager.mm152
-rw-r--r--widget/cocoa/ViewRegion.h54
-rw-r--r--widget/cocoa/ViewRegion.mm66
-rw-r--r--widget/cocoa/WidgetTraceEvent.mm79
-rw-r--r--widget/cocoa/components.conf17
-rw-r--r--widget/cocoa/crashtests/373122-1-inner.html39
-rw-r--r--widget/cocoa/crashtests/373122-1.html9
-rw-r--r--widget/cocoa/crashtests/397209-1.html7
-rw-r--r--widget/cocoa/crashtests/403296-1.xhtml10
-rw-r--r--widget/cocoa/crashtests/419737-1.html8
-rw-r--r--widget/cocoa/crashtests/435223-1.html8
-rw-r--r--widget/cocoa/crashtests/444260-1.xhtml3
-rw-r--r--widget/cocoa/crashtests/444864-1.html6
-rw-r--r--widget/cocoa/crashtests/449111-1.html4
-rw-r--r--widget/cocoa/crashtests/460349-1.xhtml4
-rw-r--r--widget/cocoa/crashtests/460387-1.html2
-rw-r--r--widget/cocoa/crashtests/464589-1.html20
-rw-r--r--widget/cocoa/crashtests/crashtests.list11
-rw-r--r--widget/cocoa/cursors/arrowN.pngbin0 -> 253 bytes
-rw-r--r--widget/cocoa/cursors/arrowN@2x.pngbin0 -> 614 bytes
-rw-r--r--widget/cocoa/cursors/arrowS.pngbin0 -> 250 bytes
-rw-r--r--widget/cocoa/cursors/arrowS@2x.pngbin0 -> 609 bytes
-rw-r--r--widget/cocoa/cursors/cell.pngbin0 -> 264 bytes
-rw-r--r--widget/cocoa/cursors/cell@2x.pngbin0 -> 639 bytes
-rw-r--r--widget/cocoa/cursors/colResize.pngbin0 -> 320 bytes
-rw-r--r--widget/cocoa/cursors/colResize@2x.pngbin0 -> 825 bytes
-rw-r--r--widget/cocoa/cursors/help.pngbin0 -> 713 bytes
-rw-r--r--widget/cocoa/cursors/help@2x.pngbin0 -> 1679 bytes
-rw-r--r--widget/cocoa/cursors/move.pngbin0 -> 281 bytes
-rw-r--r--widget/cocoa/cursors/move@2x.pngbin0 -> 619 bytes
-rw-r--r--widget/cocoa/cursors/rowResize.pngbin0 -> 329 bytes
-rw-r--r--widget/cocoa/cursors/rowResize@2x.pngbin0 -> 843 bytes
-rw-r--r--widget/cocoa/cursors/sizeNE.pngbin0 -> 274 bytes
-rw-r--r--widget/cocoa/cursors/sizeNE@2x.pngbin0 -> 775 bytes
-rw-r--r--widget/cocoa/cursors/sizeNESW.pngbin0 -> 295 bytes
-rw-r--r--widget/cocoa/cursors/sizeNESW@2x.pngbin0 -> 948 bytes
-rw-r--r--widget/cocoa/cursors/sizeNS.pngbin0 -> 279 bytes
-rw-r--r--widget/cocoa/cursors/sizeNS@2x.pngbin0 -> 658 bytes
-rw-r--r--widget/cocoa/cursors/sizeNW.pngbin0 -> 274 bytes
-rw-r--r--widget/cocoa/cursors/sizeNW@2x.pngbin0 -> 771 bytes
-rw-r--r--widget/cocoa/cursors/sizeNWSE.pngbin0 -> 288 bytes
-rw-r--r--widget/cocoa/cursors/sizeNWSE@2x.pngbin0 -> 947 bytes
-rw-r--r--widget/cocoa/cursors/sizeSE.pngbin0 -> 264 bytes
-rw-r--r--widget/cocoa/cursors/sizeSE@2x.pngbin0 -> 783 bytes
-rw-r--r--widget/cocoa/cursors/sizeSW.pngbin0 -> 268 bytes
-rw-r--r--widget/cocoa/cursors/sizeSW@2x.pngbin0 -> 783 bytes
-rw-r--r--widget/cocoa/cursors/vtIBeam.pngbin0 -> 104 bytes
-rw-r--r--widget/cocoa/cursors/vtIBeam@2x.pngbin0 -> 331 bytes
-rw-r--r--widget/cocoa/cursors/zoomIn.pngbin0 -> 648 bytes
-rw-r--r--widget/cocoa/cursors/zoomIn@2x.pngbin0 -> 1702 bytes
-rw-r--r--widget/cocoa/cursors/zoomOut.pngbin0 -> 641 bytes
-rw-r--r--widget/cocoa/cursors/zoomOut@2x.pngbin0 -> 1693 bytes
-rw-r--r--widget/cocoa/docs/index.md11
-rw-r--r--widget/cocoa/docs/macos-apis.md187
-rw-r--r--widget/cocoa/docs/sdks.md240
-rw-r--r--widget/cocoa/moz.build179
-rw-r--r--widget/cocoa/mozView.h62
-rw-r--r--widget/cocoa/nsAppShell.h68
-rw-r--r--widget/cocoa/nsAppShell.mm908
-rw-r--r--widget/cocoa/nsBidiKeyboard.h23
-rw-r--r--widget/cocoa/nsBidiKeyboard.mm38
-rw-r--r--widget/cocoa/nsChangeObserver.h53
-rw-r--r--widget/cocoa/nsChildView.h599
-rw-r--r--widget/cocoa/nsChildView.mm5118
-rw-r--r--widget/cocoa/nsClipboard.h57
-rw-r--r--widget/cocoa/nsClipboard.mm768
-rw-r--r--widget/cocoa/nsCocoaDebugUtils.h115
-rw-r--r--widget/cocoa/nsCocoaDebugUtils.mm236
-rw-r--r--widget/cocoa/nsCocoaFeatures.h55
-rw-r--r--widget/cocoa/nsCocoaFeatures.mm196
-rw-r--r--widget/cocoa/nsCocoaUtils.h492
-rw-r--r--widget/cocoa/nsCocoaUtils.mm1456
-rw-r--r--widget/cocoa/nsCocoaWindow.h422
-rw-r--r--widget/cocoa/nsCocoaWindow.mm3994
-rw-r--r--widget/cocoa/nsColorPicker.h44
-rw-r--r--widget/cocoa/nsColorPicker.mm156
-rw-r--r--widget/cocoa/nsCursorManager.h68
-rw-r--r--widget/cocoa/nsCursorManager.mm318
-rw-r--r--widget/cocoa/nsDeviceContextSpecX.h50
-rw-r--r--widget/cocoa/nsDeviceContextSpecX.mm303
-rw-r--r--widget/cocoa/nsDragService.h70
-rw-r--r--widget/cocoa/nsDragService.mm655
-rw-r--r--widget/cocoa/nsFilePicker.h72
-rw-r--r--widget/cocoa/nsFilePicker.mm638
-rw-r--r--widget/cocoa/nsLookAndFeel.h93
-rw-r--r--widget/cocoa/nsLookAndFeel.mm777
-rw-r--r--widget/cocoa/nsMacCursor.h128
-rw-r--r--widget/cocoa/nsMacCursor.mm367
-rw-r--r--widget/cocoa/nsMacDockSupport.h35
-rw-r--r--widget/cocoa/nsMacDockSupport.mm198
-rw-r--r--widget/cocoa/nsMacFinderProgress.h24
-rw-r--r--widget/cocoa/nsMacFinderProgress.mm87
-rw-r--r--widget/cocoa/nsMacSharingService.h22
-rw-r--r--widget/cocoa/nsMacSharingService.mm202
-rw-r--r--widget/cocoa/nsMacWebAppUtils.h22
-rw-r--r--widget/cocoa/nsMacWebAppUtils.mm90
-rw-r--r--widget/cocoa/nsMenuBarX.h148
-rw-r--r--widget/cocoa/nsMenuBarX.mm1007
-rw-r--r--widget/cocoa/nsMenuBaseX.h76
-rw-r--r--widget/cocoa/nsMenuGroupOwnerX.h59
-rw-r--r--widget/cocoa/nsMenuGroupOwnerX.mm191
-rw-r--r--widget/cocoa/nsMenuItemIconX.h63
-rw-r--r--widget/cocoa/nsMenuItemIconX.mm228
-rw-r--r--widget/cocoa/nsMenuItemX.h82
-rw-r--r--widget/cocoa/nsMenuItemX.mm360
-rw-r--r--widget/cocoa/nsMenuUtilsX.h30
-rw-r--r--widget/cocoa/nsMenuUtilsX.mm217
-rw-r--r--widget/cocoa/nsMenuX.h98
-rw-r--r--widget/cocoa/nsMenuX.mm933
-rw-r--r--widget/cocoa/nsNativeBasicThemeCocoa.cpp109
-rw-r--r--widget/cocoa/nsNativeBasicThemeCocoa.h51
-rw-r--r--widget/cocoa/nsNativeThemeCocoa.h490
-rw-r--r--widget/cocoa/nsNativeThemeCocoa.mm3941
-rw-r--r--widget/cocoa/nsNativeThemeColors.h66
-rw-r--r--widget/cocoa/nsPIWidgetCocoa.idl37
-rw-r--r--widget/cocoa/nsPrintDialogX.h61
-rw-r--r--widget/cocoa/nsPrintDialogX.mm546
-rw-r--r--widget/cocoa/nsPrintSettingsServiceX.h33
-rw-r--r--widget/cocoa/nsPrintSettingsServiceX.mm78
-rw-r--r--widget/cocoa/nsPrintSettingsX.h102
-rw-r--r--widget/cocoa/nsPrintSettingsX.mm354
-rw-r--r--widget/cocoa/nsSandboxViolationSink.h36
-rw-r--r--widget/cocoa/nsSandboxViolationSink.mm107
-rw-r--r--widget/cocoa/nsSound.h25
-rw-r--r--widget/cocoa/nsSound.mm69
-rw-r--r--widget/cocoa/nsStandaloneNativeMenu.h44
-rw-r--r--widget/cocoa/nsStandaloneNativeMenu.mm191
-rw-r--r--widget/cocoa/nsSystemStatusBarCocoa.h38
-rw-r--r--widget/cocoa/nsSystemStatusBarCocoa.mm72
-rw-r--r--widget/cocoa/nsToolkit.h49
-rw-r--r--widget/cocoa/nsToolkit.mm250
-rw-r--r--widget/cocoa/nsTouchBar.h136
-rw-r--r--widget/cocoa/nsTouchBar.mm604
-rw-r--r--widget/cocoa/nsTouchBarInput.h90
-rw-r--r--widget/cocoa/nsTouchBarInput.mm245
-rw-r--r--widget/cocoa/nsTouchBarInputIcon.h71
-rw-r--r--widget/cocoa/nsTouchBarInputIcon.mm138
-rw-r--r--widget/cocoa/nsTouchBarNativeAPIDefines.h80
-rw-r--r--widget/cocoa/nsTouchBarUpdater.h23
-rw-r--r--widget/cocoa/nsTouchBarUpdater.mm116
-rw-r--r--widget/cocoa/nsUserIdleServiceX.h31
-rw-r--r--widget/cocoa/nsUserIdleServiceX.mm60
-rw-r--r--widget/cocoa/nsWidgetFactory.mm213
-rw-r--r--widget/cocoa/nsWindowMap.h60
-rw-r--r--widget/cocoa/nsWindowMap.mm280
-rw-r--r--widget/cocoa/resources/MainMenu.nib/classes.nib4
-rw-r--r--widget/cocoa/resources/MainMenu.nib/info.nib21
-rw-r--r--widget/cocoa/resources/MainMenu.nib/keyedobjects.nibbin0 -> 1877 bytes
-rw-r--r--widget/crashtests/1128214.html19
-rw-r--r--widget/crashtests/303901-1.html29
-rw-r--r--widget/crashtests/303901-2.html20
-rw-r--r--widget/crashtests/380359-1.xhtml8
-rw-r--r--widget/crashtests/crashtests.list4
-rw-r--r--widget/generic/PCompositorWidget.ipdl28
-rw-r--r--widget/generic/PlatformWidgetTypes.ipdlh21
-rw-r--r--widget/gtk/CompositorWidgetChild.cpp42
-rw-r--r--widget/gtk/CompositorWidgetChild.h39
-rw-r--r--widget/gtk/CompositorWidgetParent.cpp44
-rw-r--r--widget/gtk/CompositorWidgetParent.h38
-rw-r--r--widget/gtk/DMABufLibWrapper.cpp300
-rw-r--r--widget/gtk/DMABufLibWrapper.h168
-rw-r--r--widget/gtk/DMABufSurface.cpp1002
-rw-r--r--widget/gtk/DMABufSurface.h290
-rw-r--r--widget/gtk/GRefPtr.h32
-rw-r--r--widget/gtk/GtkCompositorWidget.cpp147
-rw-r--r--widget/gtk/GtkCompositorWidget.h103
-rw-r--r--widget/gtk/IMContextWrapper.cpp3169
-rw-r--r--widget/gtk/IMContextWrapper.h685
-rw-r--r--widget/gtk/InProcessGtkCompositorWidget.cpp45
-rw-r--r--widget/gtk/InProcessGtkCompositorWidget.h30
-rw-r--r--widget/gtk/MPRISInterfaceDescription.h91
-rw-r--r--widget/gtk/MPRISServiceHandler.cpp851
-rw-r--r--widget/gtk/MPRISServiceHandler.h188
-rw-r--r--widget/gtk/MediaKeysEventSourceFactory.cpp14
-rw-r--r--widget/gtk/MozContainer.cpp376
-rw-r--r--widget/gtk/MozContainer.h90
-rw-r--r--widget/gtk/MozContainerWayland.cpp514
-rw-r--r--widget/gtk/MozContainerWayland.h83
-rw-r--r--widget/gtk/NativeKeyBindings.cpp346
-rw-r--r--widget/gtk/NativeKeyBindings.h44
-rw-r--r--widget/gtk/PCompositorWidget.ipdl30
-rw-r--r--widget/gtk/PlatformWidgetTypes.ipdlh31
-rw-r--r--widget/gtk/ScreenHelperGTK.cpp195
-rw-r--r--widget/gtk/ScreenHelperGTK.h45
-rw-r--r--widget/gtk/TaskbarProgress.cpp108
-rw-r--r--widget/gtk/TaskbarProgress.h33
-rw-r--r--widget/gtk/WakeLockListener.cpp500
-rw-r--r--widget/gtk/WakeLockListener.h55
-rw-r--r--widget/gtk/WaylandVsyncSource.cpp214
-rw-r--r--widget/gtk/WaylandVsyncSource.h96
-rw-r--r--widget/gtk/WidgetStyleCache.cpp1464
-rw-r--r--widget/gtk/WidgetStyleCache.h59
-rw-r--r--widget/gtk/WidgetTraceEvent.cpp68
-rw-r--r--widget/gtk/WidgetUtilsGtk.cpp52
-rw-r--r--widget/gtk/WidgetUtilsGtk.h26
-rw-r--r--widget/gtk/WindowSurfaceProvider.cpp131
-rw-r--r--widget/gtk/WindowSurfaceProvider.h81
-rw-r--r--widget/gtk/WindowSurfaceWayland.cpp1116
-rw-r--r--widget/gtk/WindowSurfaceWayland.h269
-rw-r--r--widget/gtk/WindowSurfaceX11.cpp50
-rw-r--r--widget/gtk/WindowSurfaceX11.h40
-rw-r--r--widget/gtk/WindowSurfaceX11Image.cpp263
-rw-r--r--widget/gtk/WindowSurfaceX11Image.h48
-rw-r--r--widget/gtk/WindowSurfaceXRender.cpp75
-rw-r--r--widget/gtk/WindowSurfaceXRender.h37
-rw-r--r--widget/gtk/compat-gtk3/gdk/gdkversionmacros.h32
-rw-r--r--widget/gtk/compat/gdk/gdkdnd.h29
-rw-r--r--widget/gtk/compat/gdk/gdkkeysyms.h266
-rw-r--r--widget/gtk/compat/gdk/gdkvisual.h15
-rw-r--r--widget/gtk/compat/gdk/gdkwindow.h27
-rw-r--r--widget/gtk/compat/gdk/gdkx.h46
-rw-r--r--widget/gtk/compat/glib/gmem.h48
-rw-r--r--widget/gtk/compat/gtk/gtkwidget.h43
-rw-r--r--widget/gtk/compat/gtk/gtkwindow.h26
-rw-r--r--widget/gtk/components.conf166
-rw-r--r--widget/gtk/crashtests/540078-1.xhtml1
-rw-r--r--widget/gtk/crashtests/673390-1.html1
-rw-r--r--widget/gtk/crashtests/crashtests.list2
-rw-r--r--widget/gtk/gtk3drawing.cpp3214
-rw-r--r--widget/gtk/gtkdrawing.h634
-rw-r--r--widget/gtk/maiRedundantObjectFactory.c81
-rw-r--r--widget/gtk/maiRedundantObjectFactory.h30
-rw-r--r--widget/gtk/moz.build178
-rw-r--r--widget/gtk/mozgtk/gtk2/moz.build40
-rw-r--r--widget/gtk/mozgtk/gtk3/moz.build38
-rw-r--r--widget/gtk/mozgtk/moz.build7
-rw-r--r--widget/gtk/mozgtk/mozgtk.c676
-rw-r--r--widget/gtk/mozgtk/stub/moz.build16
-rw-r--r--widget/gtk/mozwayland/moz.build16
-rw-r--r--widget/gtk/mozwayland/mozwayland.c201
-rw-r--r--widget/gtk/mozwayland/mozwayland.h134
-rw-r--r--widget/gtk/nsAppShell.cpp253
-rw-r--r--widget/gtk/nsAppShell.h34
-rw-r--r--widget/gtk/nsApplicationChooser.cpp131
-rw-r--r--widget/gtk/nsApplicationChooser.h32
-rw-r--r--widget/gtk/nsBidiKeyboard.cpp52
-rw-r--r--widget/gtk/nsBidiKeyboard.h25
-rw-r--r--widget/gtk/nsClipboard.cpp822
-rw-r--r--widget/gtk/nsClipboard.h90
-rw-r--r--widget/gtk/nsClipboardWayland.cpp915
-rw-r--r--widget/gtk/nsClipboardWayland.h162
-rw-r--r--widget/gtk/nsClipboardX11.cpp340
-rw-r--r--widget/gtk/nsClipboardX11.h73
-rw-r--r--widget/gtk/nsColorPicker.cpp242
-rw-r--r--widget/gtk/nsColorPicker.h72
-rw-r--r--widget/gtk/nsDeviceContextSpecG.cpp341
-rw-r--r--widget/gtk/nsDeviceContextSpecG.h62
-rw-r--r--widget/gtk/nsDragService.cpp2122
-rw-r--r--widget/gtk/nsDragService.h210
-rw-r--r--widget/gtk/nsFilePicker.cpp646
-rw-r--r--widget/gtk/nsFilePicker.h87
-rw-r--r--widget/gtk/nsGTKToolkit.h53
-rw-r--r--widget/gtk/nsGtkCursors.h416
-rw-r--r--widget/gtk/nsGtkKeyUtils.cpp2377
-rw-r--r--widget/gtk/nsGtkKeyUtils.h467
-rw-r--r--widget/gtk/nsGtkUtils.h23
-rw-r--r--widget/gtk/nsIImageToPixbuf.h38
-rw-r--r--widget/gtk/nsImageToPixbuf.cpp108
-rw-r--r--widget/gtk/nsImageToPixbuf.h47
-rw-r--r--widget/gtk/nsLookAndFeel.cpp1536
-rw-r--r--widget/gtk/nsLookAndFeel.h131
-rw-r--r--widget/gtk/nsNativeBasicThemeGTK.cpp125
-rw-r--r--widget/gtk/nsNativeBasicThemeGTK.h44
-rw-r--r--widget/gtk/nsNativeThemeGTK.cpp2019
-rw-r--r--widget/gtk/nsNativeThemeGTK.h116
-rw-r--r--widget/gtk/nsPrintDialogGTK.cpp1054
-rw-r--r--widget/gtk/nsPrintDialogGTK.h37
-rw-r--r--widget/gtk/nsPrintSettingsGTK.cpp685
-rw-r--r--widget/gtk/nsPrintSettingsGTK.h145
-rw-r--r--widget/gtk/nsPrintSettingsServiceGTK.cpp78
-rw-r--r--widget/gtk/nsPrintSettingsServiceGTK.h33
-rw-r--r--widget/gtk/nsSound.cpp398
-rw-r--r--widget/gtk/nsSound.h33
-rw-r--r--widget/gtk/nsToolkit.cpp31
-rw-r--r--widget/gtk/nsUserIdleServiceGTK.cpp115
-rw-r--r--widget/gtk/nsUserIdleServiceGTK.h50
-rw-r--r--widget/gtk/nsWaylandDisplay.cpp309
-rw-r--r--widget/gtk/nsWaylandDisplay.h130
-rw-r--r--widget/gtk/nsWidgetFactory.cpp74
-rw-r--r--widget/gtk/nsWidgetFactory.h22
-rw-r--r--widget/gtk/nsWindow.cpp8420
-rw-r--r--widget/gtk/nsWindow.h719
-rw-r--r--widget/gtk/wayland/gbm.h480
-rw-r--r--widget/gtk/wayland/gtk-primary-selection-client-protocol.h580
-rw-r--r--widget/gtk/wayland/gtk-primary-selection-protocol.c115
-rw-r--r--widget/gtk/wayland/idle-inhibit-unstable-v1-client-protocol.h228
-rw-r--r--widget/gtk/wayland/idle-inhibit-unstable-v1-protocol.c60
-rw-r--r--widget/gtk/wayland/linux-dmabuf-unstable-v1-client-protocol.h650
-rw-r--r--widget/gtk/wayland/linux-dmabuf-unstable-v1-protocol.c81
-rw-r--r--widget/gtk/wayland/moz.build35
-rw-r--r--widget/gtk/wayland/primary-selection-unstable-v1-client-protocol.h578
-rw-r--r--widget/gtk/wayland/primary-selection-unstable-v1-protocol.c115
-rw-r--r--widget/gtk/wayland/va_drmcommon.h156
-rw-r--r--widget/gtk/wayland/xdg-output-unstable-v1-client-protocol.h392
-rw-r--r--widget/gtk/wayland/xdg-output-unstable-v1-protocol.c74
-rw-r--r--widget/headless/HeadlessClipboard.cpp112
-rw-r--r--widget/headless/HeadlessClipboard.h33
-rw-r--r--widget/headless/HeadlessClipboardData.cpp19
-rw-r--r--widget/headless/HeadlessClipboardData.h34
-rw-r--r--widget/headless/HeadlessCompositorWidget.cpp43
-rw-r--r--widget/headless/HeadlessCompositorWidget.h53
-rw-r--r--widget/headless/HeadlessKeyBindings.cpp34
-rw-r--r--widget/headless/HeadlessKeyBindings.h35
-rw-r--r--widget/headless/HeadlessKeyBindingsCocoa.mm47
-rw-r--r--widget/headless/HeadlessLookAndFeel.h65
-rw-r--r--widget/headless/HeadlessLookAndFeelGTK.cpp359
-rw-r--r--widget/headless/HeadlessScreenHelper.cpp44
-rw-r--r--widget/headless/HeadlessScreenHelper.h27
-rw-r--r--widget/headless/HeadlessSound.cpp36
-rw-r--r--widget/headless/HeadlessSound.h31
-rw-r--r--widget/headless/HeadlessThemeGTK.cpp417
-rw-r--r--widget/headless/HeadlessThemeGTK.h61
-rw-r--r--widget/headless/HeadlessWidget.cpp503
-rw-r--r--widget/headless/HeadlessWidget.h187
-rw-r--r--widget/headless/HeadlessWidgetTypes.ipdlh18
-rw-r--r--widget/headless/moz.build49
-rw-r--r--widget/headless/tests/.eslintrc.js5
-rw-r--r--widget/headless/tests/headless.html6
-rw-r--r--widget/headless/tests/headless_button.html6
-rw-r--r--widget/headless/tests/moz.build7
-rw-r--r--widget/headless/tests/test_headless.js215
-rw-r--r--widget/headless/tests/test_headless_clipboard.js46
-rw-r--r--widget/headless/tests/xpcshell.ini10
-rw-r--r--widget/moz.build363
-rw-r--r--widget/nsAppShellSingleton.h62
-rw-r--r--widget/nsAutoRollup.cpp50
-rw-r--r--widget/nsAutoRollup.h54
-rw-r--r--widget/nsBaseAppShell.cpp305
-rw-r--r--widget/nsBaseAppShell.h136
-rw-r--r--widget/nsBaseClipboard.cpp117
-rw-r--r--widget/nsBaseClipboard.h44
-rw-r--r--widget/nsBaseDragService.cpp951
-rw-r--r--widget/nsBaseDragService.h216
-rw-r--r--widget/nsBaseFilePicker.cpp408
-rw-r--r--widget/nsBaseFilePicker.h68
-rw-r--r--widget/nsBaseScreen.cpp46
-rw-r--r--widget/nsBaseScreen.h41
-rw-r--r--widget/nsBaseWidget.cpp3290
-rw-r--r--widget/nsBaseWidget.h744
-rw-r--r--widget/nsCUPSShim.cpp64
-rw-r--r--widget/nsCUPSShim.h85
-rw-r--r--widget/nsClipboardHelper.cpp124
-rw-r--r--widget/nsClipboardHelper.h30
-rw-r--r--widget/nsClipboardProxy.cpp140
-rw-r--r--widget/nsClipboardProxy.h48
-rw-r--r--widget/nsColorPickerProxy.cpp53
-rw-r--r--widget/nsColorPickerProxy.h33
-rw-r--r--widget/nsContentProcessWidgetFactory.cpp65
-rw-r--r--widget/nsDeviceContextSpecProxy.cpp182
-rw-r--r--widget/nsDeviceContextSpecProxy.h65
-rw-r--r--widget/nsDragServiceProxy.cpp95
-rw-r--r--widget/nsDragServiceProxy.h27
-rw-r--r--widget/nsFilePickerProxy.cpp276
-rw-r--r--widget/nsFilePickerProxy.h84
-rw-r--r--widget/nsGUIEventIPC.h1455
-rw-r--r--widget/nsHTMLFormatConverter.cpp183
-rw-r--r--widget/nsHTMLFormatConverter.h32
-rw-r--r--widget/nsIAppShell.idl76
-rw-r--r--widget/nsIApplicationChooser.idl40
-rw-r--r--widget/nsIBaseWindow.idl218
-rw-r--r--widget/nsIBidiKeyboard.idl32
-rw-r--r--widget/nsIClipboard.idl90
-rw-r--r--widget/nsIClipboardHelper.idl36
-rw-r--r--widget/nsIClipboardOwner.idl29
-rw-r--r--widget/nsIColorPicker.idl72
-rw-r--r--widget/nsIDeviceContextSpec.h93
-rw-r--r--widget/nsIDisplayInfo.idl14
-rw-r--r--widget/nsIDragService.idl200
-rw-r--r--widget/nsIDragSession.idl125
-rw-r--r--widget/nsIFilePicker.idl215
-rw-r--r--widget/nsIFormatConverter.idl50
-rw-r--r--widget/nsIGfxInfo.idl314
-rw-r--r--widget/nsIGfxInfoDebug.idl24
-rw-r--r--widget/nsIGtkTaskbarProgress.idl22
-rw-r--r--widget/nsIJumpListBuilder.idl159
-rw-r--r--widget/nsIJumpListItem.idl137
-rw-r--r--widget/nsIKeyEventInPluginCallback.h42
-rw-r--r--widget/nsIMacDockSupport.idl39
-rw-r--r--widget/nsIMacFinderProgress.idl43
-rw-r--r--widget/nsIMacSharingService.idl30
-rw-r--r--widget/nsIMacWebAppUtils.idl35
-rw-r--r--widget/nsINativeMenuService.h39
-rw-r--r--widget/nsIPaper.idl42
-rw-r--r--widget/nsIPaperMargin.idl16
-rw-r--r--widget/nsIPluginWidget.h44
-rw-r--r--widget/nsIPrintDialogService.h70
-rw-r--r--widget/nsIPrintSession.idl39
-rw-r--r--widget/nsIPrintSettings.idl317
-rw-r--r--widget/nsIPrintSettingsService.idl148
-rw-r--r--widget/nsIPrintSettingsWin.idl56
-rw-r--r--widget/nsIPrinter.idl74
-rw-r--r--widget/nsIPrinterList.idl61
-rw-r--r--widget/nsIRollupListener.h72
-rw-r--r--widget/nsIScreen.idl66
-rw-r--r--widget/nsIScreenManager.idl27
-rw-r--r--widget/nsISharePicker.idl32
-rw-r--r--widget/nsISound.idl40
-rw-r--r--widget/nsIStandaloneNativeMenu.idl56
-rw-r--r--widget/nsISystemStatusBar.idl36
-rw-r--r--widget/nsITaskbarOverlayIconController.idl39
-rw-r--r--widget/nsITaskbarPreview.idl71
-rw-r--r--widget/nsITaskbarPreviewButton.idl63
-rw-r--r--widget/nsITaskbarPreviewController.idl103
-rw-r--r--widget/nsITaskbarProgress.idl58
-rw-r--r--widget/nsITaskbarTabPreview.idl63
-rw-r--r--widget/nsITaskbarWindowPreview.idl70
-rw-r--r--widget/nsITouchBarHelper.idl55
-rw-r--r--widget/nsITouchBarInput.idl78
-rw-r--r--widget/nsITouchBarUpdater.idl42
-rw-r--r--widget/nsITransferable.idl204
-rw-r--r--widget/nsIUserIdleService.idl86
-rw-r--r--widget/nsIUserIdleServiceInternal.idl18
-rw-r--r--widget/nsIWidget.h2157
-rw-r--r--widget/nsIWidgetListener.cpp90
-rw-r--r--widget/nsIWidgetListener.h196
-rw-r--r--widget/nsIWinTaskbar.idl178
-rw-r--r--widget/nsIWindowsUIUtils.idl35
-rw-r--r--widget/nsNativeBasicTheme.cpp1599
-rw-r--r--widget/nsNativeBasicTheme.h334
-rw-r--r--widget/nsNativeTheme.cpp677
-rw-r--r--widget/nsNativeTheme.h199
-rw-r--r--widget/nsPaper.cpp87
-rw-r--r--widget/nsPaper.h128
-rw-r--r--widget/nsPaperMargin.cpp32
-rw-r--r--widget/nsPaperMargin.h29
-rw-r--r--widget/nsPrimitiveHelpers.cpp197
-rw-r--r--widget/nsPrimitiveHelpers.h54
-rw-r--r--widget/nsPrintSession.cpp33
-rw-r--r--widget/nsPrintSession.h39
-rw-r--r--widget/nsPrintSettingsImpl.cpp854
-rw-r--r--widget/nsPrintSettingsImpl.h123
-rw-r--r--widget/nsPrintSettingsService.cpp1021
-rw-r--r--widget/nsPrintSettingsService.h90
-rw-r--r--widget/nsPrinterBase.cpp212
-rw-r--r--widget/nsPrinterBase.h112
-rw-r--r--widget/nsPrinterCUPS.cpp375
-rw-r--r--widget/nsPrinterCUPS.h146
-rw-r--r--widget/nsPrinterListBase.cpp160
-rw-r--r--widget/nsPrinterListBase.h92
-rw-r--r--widget/nsPrinterListCUPS.cpp201
-rw-r--r--widget/nsPrinterListCUPS.h32
-rw-r--r--widget/nsShmImage.cpp326
-rw-r--r--widget/nsShmImage.h75
-rw-r--r--widget/nsSoundProxy.cpp49
-rw-r--r--widget/nsSoundProxy.h22
-rw-r--r--widget/nsTransferable.cpp531
-rw-r--r--widget/nsTransferable.h89
-rw-r--r--widget/nsUserIdleService.cpp883
-rw-r--r--widget/nsUserIdleService.h227
-rw-r--r--widget/nsWidgetInitData.h149
-rw-r--r--widget/nsWidgetsCID.h339
-rw-r--r--widget/nsXPLookAndFeel.cpp1127
-rw-r--r--widget/nsXPLookAndFeel.h131
-rw-r--r--widget/reftests/664925.xhtml1
-rw-r--r--widget/reftests/meter-fallback-default-style-ref.html57
-rw-r--r--widget/reftests/meter-fallback-default-style.html20
-rw-r--r--widget/reftests/meter-native-style-ref.html19
-rw-r--r--widget/reftests/meter-native-style.html18
-rw-r--r--widget/reftests/meter-vertical-native-style-ref.html14
-rw-r--r--widget/reftests/meter-vertical-native-style.html13
-rw-r--r--widget/reftests/progressbar-fallback-default-style-ref.html33
-rw-r--r--widget/reftests/progressbar-fallback-default-style.html20
-rw-r--r--widget/reftests/reftest.list9
-rw-r--r--widget/reftests/scaled-scrollbar.html6
-rw-r--r--widget/reftests/scrollbar-buttons.html4
-rw-r--r--widget/tests/.eslintrc.js5
-rw-r--r--widget/tests/TestChromeMargin.cpp130
-rw-r--r--widget/tests/browser/browser.ini2
-rw-r--r--widget/tests/browser/browser_test_clipboardcache.js145
-rw-r--r--widget/tests/bug586713_window.xhtml50
-rw-r--r--widget/tests/chrome.ini107
-rw-r--r--widget/tests/empty_window.xhtml4
-rw-r--r--widget/tests/file_bug596600.html4
-rw-r--r--widget/tests/file_input_events_on_deactive_window.html5
-rw-r--r--widget/tests/file_secure_input.html1
-rw-r--r--widget/tests/gtest/TestTimeConverter.cpp265
-rw-r--r--widget/tests/gtest/TestTouchResampler.cpp941
-rw-r--r--widget/tests/gtest/moz.build16
-rw-r--r--widget/tests/mochitest.ini18
-rw-r--r--widget/tests/moz.build125
-rw-r--r--widget/tests/native_menus_window.xhtml282
-rw-r--r--widget/tests/native_mouse_mac_window.xhtml770
-rw-r--r--widget/tests/standalone_native_menu_window.xhtml332
-rw-r--r--widget/tests/system_font_changes.xhtml63
-rw-r--r--widget/tests/taskbar_previews.xhtml118
-rw-r--r--widget/tests/test_AltGr_key_events_in_web_content_on_windows.html106
-rw-r--r--widget/tests/test_actionhint.html85
-rw-r--r--widget/tests/test_alwaysontop_focus.xhtml39
-rw-r--r--widget/tests/test_assign_event_data.html759
-rw-r--r--widget/tests/test_autocapitalize.html65
-rw-r--r--widget/tests/test_bug1123480.xhtml147
-rw-r--r--widget/tests/test_bug343416.xhtml191
-rw-r--r--widget/tests/test_bug413277.html35
-rw-r--r--widget/tests/test_bug428405.xhtml167
-rw-r--r--widget/tests/test_bug429954.xhtml43
-rw-r--r--widget/tests/test_bug444800.xhtml98
-rw-r--r--widget/tests/test_bug466599.xhtml103
-rw-r--r--widget/tests/test_bug478536.xhtml33
-rw-r--r--widget/tests/test_bug485118.xhtml72
-rw-r--r--widget/tests/test_bug517396.xhtml54
-rw-r--r--widget/tests/test_bug522217.xhtml35
-rw-r--r--widget/tests/test_bug538242.xhtml55
-rw-r--r--widget/tests/test_bug565392.html64
-rw-r--r--widget/tests/test_bug586713.xhtml29
-rw-r--r--widget/tests/test_bug593307.xhtml40
-rw-r--r--widget/tests/test_bug596600.xhtml168
-rw-r--r--widget/tests/test_bug673301.xhtml35
-rw-r--r--widget/tests/test_bug760802.xhtml85
-rw-r--r--widget/tests/test_clipboard.xhtml72
-rw-r--r--widget/tests/test_composition_text_querycontent.xhtml35
-rw-r--r--widget/tests/test_imestate.html1458
-rw-r--r--widget/tests/test_input_events_on_deactive_window.xhtml235
-rw-r--r--widget/tests/test_key_event_counts.xhtml90
-rw-r--r--widget/tests/test_keycodes.xhtml5628
-rw-r--r--widget/tests/test_keypress_event_with_alt_on_mac.html109
-rw-r--r--widget/tests/test_mouse_event_with_control_on_mac.html114
-rw-r--r--widget/tests/test_mouse_scroll.xhtml35
-rw-r--r--widget/tests/test_native_key_bindings_mac.html336
-rw-r--r--widget/tests/test_native_menus.xhtml29
-rw-r--r--widget/tests/test_native_mouse_mac.xhtml29
-rw-r--r--widget/tests/test_panel_mouse_coords.xhtml78
-rw-r--r--widget/tests/test_picker_no_crash.html36
-rw-r--r--widget/tests/test_platform_colors.xhtml106
-rw-r--r--widget/tests/test_plugin_scroll_consistency.html59
-rw-r--r--widget/tests/test_position_on_resize.xhtml90
-rw-r--r--widget/tests/test_scrollbar_colors.html120
-rw-r--r--widget/tests/test_secure_input.html141
-rw-r--r--widget/tests/test_sizemode_events.xhtml105
-rw-r--r--widget/tests/test_standalone_native_menu.xhtml29
-rw-r--r--widget/tests/test_system_font_changes.xhtml28
-rw-r--r--widget/tests/test_system_status_bar.xhtml53
-rw-r--r--widget/tests/test_taskbar_progress.xhtml119
-rw-r--r--widget/tests/test_transferable_overflow.xhtml152
-rw-r--r--widget/tests/test_wheeltransaction.xhtml27
-rw-r--r--widget/tests/unit/test_macsharingservice.js29
-rw-r--r--widget/tests/unit/test_macwebapputils.js34
-rw-r--r--widget/tests/unit/test_taskbar_jumplistitems.js328
-rw-r--r--widget/tests/unit/xpcshell.ini9
-rw-r--r--widget/tests/utils.js27
-rw-r--r--widget/tests/window_bug429954.xhtml44
-rw-r--r--widget/tests/window_bug478536.xhtml211
-rw-r--r--widget/tests/window_bug522217.xhtml80
-rw-r--r--widget/tests/window_bug538242.xhtml3
-rw-r--r--widget/tests/window_bug593307_centerscreen.xhtml26
-rw-r--r--widget/tests/window_bug593307_offscreen.xhtml33
-rw-r--r--widget/tests/window_composition_text_querycontent.xhtml9632
-rw-r--r--widget/tests/window_imestate_iframes.html360
-rw-r--r--widget/tests/window_mouse_scroll_win.html1518
-rw-r--r--widget/tests/window_mouse_scroll_win_2.html6
-rw-r--r--widget/tests/window_picker_no_crash_child.html10
-rw-r--r--widget/tests/window_state_windows.xhtml82
-rw-r--r--widget/tests/window_wheeltransaction.xhtml1569
-rw-r--r--widget/uikit/GfxInfo.cpp174
-rw-r--r--widget/uikit/GfxInfo.h75
-rw-r--r--widget/uikit/moz.build22
-rw-r--r--widget/uikit/nsAppShell.h56
-rw-r--r--widget/uikit/nsAppShell.mm241
-rw-r--r--widget/uikit/nsLookAndFeel.h39
-rw-r--r--widget/uikit/nsLookAndFeel.mm414
-rw-r--r--widget/uikit/nsScreenManager.h60
-rw-r--r--widget/uikit/nsScreenManager.mm98
-rw-r--r--widget/uikit/nsWidgetFactory.mm49
-rw-r--r--widget/uikit/nsWindow.h109
-rw-r--r--widget/uikit/nsWindow.mm754
-rw-r--r--widget/windows/AudioSession.cpp443
-rw-r--r--widget/windows/AudioSession.h28
-rw-r--r--widget/windows/CompositorWidgetChild.cpp109
-rw-r--r--widget/windows/CompositorWidgetChild.h60
-rw-r--r--widget/windows/CompositorWidgetParent.cpp222
-rw-r--r--widget/windows/CompositorWidgetParent.h84
-rw-r--r--widget/windows/DirectManipulationOwner.cpp718
-rw-r--r--widget/windows/DirectManipulationOwner.h50
-rw-r--r--widget/windows/GfxInfo.cpp2058
-rw-r--r--widget/windows/GfxInfo.h114
-rw-r--r--widget/windows/IEnumFE.cpp139
-rw-r--r--widget/windows/IEnumFE.h88
-rw-r--r--widget/windows/IMMHandler.cpp2384
-rw-r--r--widget/windows/IMMHandler.h436
-rw-r--r--widget/windows/IconLoaderHelperWin.cpp79
-rw-r--r--widget/windows/IconLoaderHelperWin.h70
-rw-r--r--widget/windows/InProcessWinCompositorWidget.cpp356
-rw-r--r--widget/windows/InProcessWinCompositorWidget.h101
-rw-r--r--widget/windows/InkCollector.cpp245
-rw-r--r--widget/windows/InkCollector.h101
-rw-r--r--widget/windows/InputDeviceUtils.cpp61
-rw-r--r--widget/windows/InputDeviceUtils.h26
-rw-r--r--widget/windows/JumpListBuilder.cpp635
-rw-r--r--widget/windows/JumpListBuilder.h69
-rw-r--r--widget/windows/JumpListItem.cpp575
-rw-r--r--widget/windows/JumpListItem.h131
-rw-r--r--widget/windows/KeyboardLayout.cpp5418
-rw-r--r--widget/windows/KeyboardLayout.h1125
-rw-r--r--widget/windows/LSPAnnotator.cpp135
-rw-r--r--widget/windows/MediaKeysEventSourceFactory.cpp20
-rw-r--r--widget/windows/OSKInputPaneManager.cpp101
-rw-r--r--widget/windows/OSKInputPaneManager.h24
-rw-r--r--widget/windows/PCompositorWidget.ipdl45
-rw-r--r--widget/windows/PlatformWidgetTypes.ipdlh34
-rw-r--r--widget/windows/RemoteBackbuffer.cpp675
-rw-r--r--widget/windows/RemoteBackbuffer.h92
-rw-r--r--widget/windows/ScreenHelperWin.cpp89
-rw-r--r--widget/windows/ScreenHelperWin.h26
-rw-r--r--widget/windows/ScrollbarUtil.cpp217
-rw-r--r--widget/windows/ScrollbarUtil.h41
-rw-r--r--widget/windows/ShellHeaderOnlyUtils.h178
-rw-r--r--widget/windows/SystemStatusBar.cpp302
-rw-r--r--widget/windows/SystemStatusBar.h32
-rw-r--r--widget/windows/TSFTextStore.cpp7398
-rw-r--r--widget/windows/TSFTextStore.h1053
-rw-r--r--widget/windows/TaskbarPreview.cpp409
-rw-r--r--widget/windows/TaskbarPreview.h132
-rw-r--r--widget/windows/TaskbarPreviewButton.cpp137
-rw-r--r--widget/windows/TaskbarPreviewButton.h47
-rw-r--r--widget/windows/TaskbarTabPreview.cpp345
-rw-r--r--widget/windows/TaskbarTabPreview.h70
-rw-r--r--widget/windows/TaskbarWindowPreview.cpp323
-rw-r--r--widget/windows/TaskbarWindowPreview.h85
-rw-r--r--widget/windows/ToastNotification.cpp204
-rw-r--r--widget/windows/ToastNotification.h50
-rw-r--r--widget/windows/ToastNotificationHandler.cpp622
-rw-r--r--widget/windows/ToastNotificationHandler.h108
-rw-r--r--widget/windows/UrlmonHeaderOnlyUtils.h76
-rw-r--r--widget/windows/WidgetTraceEvent.cpp119
-rw-r--r--widget/windows/WinCompositorWidget.cpp105
-rw-r--r--widget/windows/WinCompositorWidget.h99
-rw-r--r--widget/windows/WinCompositorWindowThread.cpp212
-rw-r--r--widget/windows/WinCompositorWindowThread.h66
-rw-r--r--widget/windows/WinContentSystemParameters.cpp213
-rw-r--r--widget/windows/WinContentSystemParameters.h72
-rw-r--r--widget/windows/WinHeaderOnlyUtils.h742
-rw-r--r--widget/windows/WinIMEHandler.cpp1180
-rw-r--r--widget/windows/WinIMEHandler.h245
-rw-r--r--widget/windows/WinMessages.h104
-rw-r--r--widget/windows/WinModifierKeyState.h59
-rw-r--r--widget/windows/WinMouseScrollHandler.cpp1702
-rw-r--r--widget/windows/WinMouseScrollHandler.h576
-rw-r--r--widget/windows/WinNativeEventData.h50
-rw-r--r--widget/windows/WinPointerEvents.cpp190
-rw-r--r--widget/windows/WinPointerEvents.h174
-rw-r--r--widget/windows/WinTaskbar.cpp437
-rw-r--r--widget/windows/WinTaskbar.h45
-rw-r--r--widget/windows/WinTextEventDispatcherListener.cpp68
-rw-r--r--widget/windows/WinTextEventDispatcherListener.h50
-rw-r--r--widget/windows/WinUtils.cpp2272
-rw-r--r--widget/windows/WinUtils.h657
-rw-r--r--widget/windows/WindowHook.cpp113
-rw-r--r--widget/windows/WindowHook.h76
-rw-r--r--widget/windows/WindowsConsole.cpp51
-rw-r--r--widget/windows/WindowsConsole.h16
-rw-r--r--widget/windows/WindowsEMF.cpp94
-rw-r--r--widget/windows/WindowsEMF.h106
-rw-r--r--widget/windows/WindowsSMTCProvider.cpp731
-rw-r--r--widget/windows/WindowsSMTCProvider.h129
-rw-r--r--widget/windows/WindowsUIUtils.cpp461
-rw-r--r--widget/windows/WindowsUIUtils.h34
-rw-r--r--widget/windows/components.conf219
-rw-r--r--widget/windows/moz.build192
-rw-r--r--widget/windows/nsAppShell.cpp781
-rw-r--r--widget/windows/nsAppShell.h61
-rw-r--r--widget/windows/nsBidiKeyboard.cpp169
-rw-r--r--widget/windows/nsBidiKeyboard.h34
-rw-r--r--widget/windows/nsClipboard.cpp1071
-rw-r--r--widget/windows/nsClipboard.h96
-rw-r--r--widget/windows/nsColorPicker.cpp208
-rw-r--r--widget/windows/nsColorPicker.h54
-rw-r--r--widget/windows/nsDataObj.cpp2231
-rw-r--r--widget/windows/nsDataObj.h308
-rw-r--r--widget/windows/nsDataObjCollection.cpp370
-rw-r--r--widget/windows/nsDataObjCollection.h92
-rw-r--r--widget/windows/nsDeviceContextSpecWin.cpp656
-rw-r--r--widget/windows/nsDeviceContextSpecWin.h109
-rw-r--r--widget/windows/nsDragService.cpp613
-rw-r--r--widget/windows/nsDragService.h67
-rw-r--r--widget/windows/nsFilePicker.cpp622
-rw-r--r--widget/windows/nsFilePicker.h109
-rw-r--r--widget/windows/nsLookAndFeel.cpp976
-rw-r--r--widget/windows/nsLookAndFeel.h138
-rw-r--r--widget/windows/nsNativeBasicThemeWin.cpp299
-rw-r--r--widget/windows/nsNativeBasicThemeWin.h58
-rw-r--r--widget/windows/nsNativeDragSource.cpp98
-rw-r--r--widget/windows/nsNativeDragSource.h68
-rw-r--r--widget/windows/nsNativeDragTarget.cpp479
-rw-r--r--widget/windows/nsNativeDragTarget.h103
-rw-r--r--widget/windows/nsNativeThemeWin.cpp4013
-rw-r--r--widget/windows/nsNativeThemeWin.h164
-rw-r--r--widget/windows/nsPrintDialogUtil.cpp371
-rw-r--r--widget/windows/nsPrintDialogUtil.h10
-rw-r--r--widget/windows/nsPrintDialogWin.cpp180
-rw-r--r--widget/windows/nsPrintDialogWin.h41
-rw-r--r--widget/windows/nsPrintSettingsServiceWin.cpp127
-rw-r--r--widget/windows/nsPrintSettingsServiceWin.h30
-rw-r--r--widget/windows/nsPrintSettingsWin.cpp536
-rw-r--r--widget/windows/nsPrintSettingsWin.h53
-rw-r--r--widget/windows/nsPrinterWin.cpp371
-rw-r--r--widget/windows/nsPrinterWin.h42
-rw-r--r--widget/windows/nsSharePicker.cpp81
-rw-r--r--widget/windows/nsSharePicker.h29
-rw-r--r--widget/windows/nsSound.cpp330
-rw-r--r--widget/windows/nsSound.h47
-rw-r--r--widget/windows/nsToolkit.cpp69
-rw-r--r--widget/windows/nsToolkit.h47
-rw-r--r--widget/windows/nsUXThemeConstants.h255
-rw-r--r--widget/windows/nsUXThemeData.cpp410
-rw-r--r--widget/windows/nsUXThemeData.h133
-rw-r--r--widget/windows/nsUserIdleServiceWin.cpp22
-rw-r--r--widget/windows/nsUserIdleServiceWin.h43
-rw-r--r--widget/windows/nsWidgetFactory.cpp60
-rw-r--r--widget/windows/nsWidgetFactory.h22
-rw-r--r--widget/windows/nsWinGesture.cpp388
-rw-r--r--widget/windows/nsWinGesture.h91
-rw-r--r--widget/windows/nsWindow.cpp8663
-rw-r--r--widget/windows/nsWindow.h747
-rw-r--r--widget/windows/nsWindowBase.cpp224
-rw-r--r--widget/windows/nsWindowBase.h139
-rw-r--r--widget/windows/nsWindowDbg.cpp60
-rw-r--r--widget/windows/nsWindowDbg.h62
-rw-r--r--widget/windows/nsWindowDefs.h137
-rw-r--r--widget/windows/nsWindowGfx.cpp693
-rw-r--r--widget/windows/nsWindowGfx.h35
-rw-r--r--widget/windows/nsdefs.h47
-rw-r--r--widget/windows/res/aliasb.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/cell.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/col_resize.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/copy.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/grab.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/grabbing.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/none.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/row_resize.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/select.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/vertical_text.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/zoom_in.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/zoom_out.curbin0 -> 326 bytes
-rw-r--r--widget/windows/resource.h16
-rw-r--r--widget/windows/tests/TestUriValidation.cpp135
-rw-r--r--widget/windows/tests/TestUrisToValidate.h471
-rw-r--r--widget/windows/tests/TestWinDND.cpp696
-rw-r--r--widget/windows/tests/moz.build28
-rw-r--r--widget/windows/touchinjection_sdk80.h117
-rw-r--r--widget/windows/widget.rc30
-rw-r--r--widget/x11/keysym2ucs.c866
-rw-r--r--widget/x11/keysym2ucs.h28
-rw-r--r--widget/x11/moz.build16
930 files changed, 272250 insertions, 0 deletions
diff --git a/widget/BasicEvents.h b/widget/BasicEvents.h
new file mode 100644
index 0000000000..eb66579e10
--- /dev/null
+++ b/widget/BasicEvents.h
@@ -0,0 +1,1394 @@
+/* -*- 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_BasicEvents_h__
+#define mozilla_BasicEvents_h__
+
+#include <stdint.h>
+#include <type_traits>
+
+#include "mozilla/EventForwards.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "nsCOMPtr.h"
+#include "nsAtom.h"
+#include "nsISupportsImpl.h"
+#include "nsIWidget.h"
+#include "nsString.h"
+#include "Units.h"
+
+#ifdef DEBUG
+# include "nsXULAppAPI.h"
+#endif // #ifdef DEBUG
+
+class nsIPrincipal;
+
+namespace IPC {
+template <typename T>
+struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+
+class EventTargetChainItem;
+
+enum class CrossProcessForwarding {
+ // eStop prevents the event to be sent to remote process.
+ eStop,
+ // eAllow keeps current state of the event whether it's sent to remote
+ // process. In other words, eAllow does NOT mean that making the event
+ // sent to remote process when IsCrossProcessForwardingStopped() returns
+ // true.
+ eAllow,
+};
+
+/******************************************************************************
+ * mozilla::BaseEventFlags
+ *
+ * BaseEventFlags must be a POD struct for safe to use memcpy (including
+ * in ParamTraits<BaseEventFlags>). So don't make virtual methods, constructor,
+ * destructor and operators.
+ * This is necessary for VC which is NOT C++0x compiler.
+ ******************************************************************************/
+
+struct BaseEventFlags {
+ public:
+ // If mIsTrusted is true, the event is a trusted event. Otherwise, it's
+ // an untrusted event.
+ bool mIsTrusted : 1;
+ // If mInBubblingPhase is true, the event is in bubbling phase or target
+ // phase.
+ bool mInBubblingPhase : 1;
+ // If mInCapturePhase is true, the event is in capture phase or target phase.
+ bool mInCapturePhase : 1;
+ // If mInSystemGroup is true, the event is being dispatched in system group.
+ bool mInSystemGroup : 1;
+ // If mCancelable is true, the event can be consumed. I.e., calling
+ // dom::Event::PreventDefault() can prevent the default action.
+ bool mCancelable : 1;
+ // If mBubbles is true, the event can bubble. Otherwise, cannot be handled
+ // in bubbling phase.
+ bool mBubbles : 1;
+ // If mPropagationStopped is true, dom::Event::StopPropagation() or
+ // dom::Event::StopImmediatePropagation() has been called.
+ bool mPropagationStopped : 1;
+ // If mImmediatePropagationStopped is true,
+ // dom::Event::StopImmediatePropagation() has been called.
+ // Note that mPropagationStopped must be true when this is true.
+ bool mImmediatePropagationStopped : 1;
+ // If mDefaultPrevented is true, the event has been consumed.
+ // E.g., dom::Event::PreventDefault() has been called or
+ // the default action has been performed.
+ bool mDefaultPrevented : 1;
+ // If mDefaultPreventedByContent is true, the event has been
+ // consumed by content.
+ // Note that mDefaultPrevented must be true when this is true.
+ bool mDefaultPreventedByContent : 1;
+ // If mDefaultPreventedByChrome is true, the event has been
+ // consumed by chrome.
+ // Note that mDefaultPrevented must be true when this is true.
+ bool mDefaultPreventedByChrome : 1;
+ // mMultipleActionsPrevented may be used when default handling don't want to
+ // be prevented, but only one of the event targets should handle the event.
+ // For example, when a <label> element is in another <label> element and
+ // the first <label> element is clicked, that one may set this true.
+ // Then, the second <label> element won't handle the event.
+ bool mMultipleActionsPrevented : 1;
+ // Similar to above but expected to be used during PreHandleEvent phase.
+ bool mMultiplePreActionsPrevented : 1;
+ // If mIsBeingDispatched is true, the DOM event created from the event is
+ // dispatching into the DOM tree and not completed.
+ bool mIsBeingDispatched : 1;
+ // If mDispatchedAtLeastOnce is true, the event has been dispatched
+ // as a DOM event and the dispatch has been completed in the process.
+ // So, this is false even if the event has already been dispatched
+ // in another process.
+ bool mDispatchedAtLeastOnce : 1;
+ // If mIsSynthesizedForTests is true, the event has been synthesized for
+ // automated tests or something hacky approach of an add-on.
+ bool mIsSynthesizedForTests : 1;
+ // If mExceptionWasRaised is true, one of the event handlers has raised an
+ // exception.
+ bool mExceptionWasRaised : 1;
+ // If mRetargetToNonNativeAnonymous is true and the target is in a non-native
+ // native anonymous subtree, the event target is set to mOriginalTarget.
+ bool mRetargetToNonNativeAnonymous : 1;
+ // If mNoContentDispatch is true, the event is never dispatched to the
+ // event handlers which are added to the contents, onfoo attributes and
+ // properties. Note that this flag is ignored when
+ // EventChainPreVisitor::mForceContentDispatch is set true. For exapmle,
+ // window and document object sets it true. Therefore, web applications
+ // can handle the event if they add event listeners to the window or the
+ // document.
+ // XXX This is an ancient and broken feature, don't use this for new bug
+ // as far as possible.
+ bool mNoContentDispatch : 1;
+ // If mOnlyChromeDispatch is true, the event is dispatched to only chrome.
+ bool mOnlyChromeDispatch : 1;
+ // Indicates if the key combination is reserved by chrome. This is set by
+ // MarkAsReservedByChrome().
+ bool mIsReservedByChrome : 1;
+ // If mOnlySystemGroupDispatchInContent is true, event listeners added to
+ // the default group for non-chrome EventTarget won't be called.
+ // Be aware, if this is true, EventDispatcher needs to check if each event
+ // listener is added to chrome node, so, don't set this to true for the
+ // events which are fired a lot of times like eMouseMove.
+ bool mOnlySystemGroupDispatchInContent : 1;
+ // If mOnlySystemGroupDispatch is true, the event will be dispatched only to
+ // event listeners added in the system group.
+ bool mOnlySystemGroupDispatch : 1;
+ // The event's action will be handled by APZ. The main thread should not
+ // perform its associated action.
+ bool mHandledByAPZ : 1;
+ // True if the event is currently being handled by an event listener that
+ // was registered as a passive listener.
+ bool mInPassiveListener : 1;
+ // If mComposed is true, the event fired by nodes in shadow DOM can cross the
+ // boundary of shadow DOM and light DOM.
+ bool mComposed : 1;
+ // Similar to mComposed. Set it to true to allow events cross the boundary
+ // between native non-anonymous content and native anonymouse content
+ bool mComposedInNativeAnonymousContent : 1;
+ // Set to true for events which are suppressed or delayed so that later a
+ // DelayedEvent of it is dispatched. This is used when parent side process
+ // the key event after content side, and may drop the event if the event
+ // was suppressed or delayed in contents side.
+ // It is also set to true for the events (in a DelayedInputEvent), which will
+ // be dispatched afterwards.
+ bool mIsSuppressedOrDelayed : 1;
+ // Certain mouse events can be marked as positionless to return 0 from
+ // coordinate related getters.
+ bool mIsPositionless : 1;
+
+ // Flags managing state of propagation between processes.
+ // Note the the following flags shouldn't be referred directly. Use utility
+ // methods instead.
+
+ // If mNoRemoteProcessDispatch is true, the event is not allowed to be sent
+ // to remote process.
+ bool mNoRemoteProcessDispatch : 1;
+ // If mWantReplyFromContentProcess is true, the event will be redispatched
+ // in the parent process after the content process has handled it. Useful
+ // for when the parent process need the know first how the event was used
+ // by content before handling it itself.
+ bool mWantReplyFromContentProcess : 1;
+ // If mPostedToRemoteProcess is true, the event has been posted to the
+ // remote process (but it's not handled yet if it's not a duplicated event
+ // instance).
+ bool mPostedToRemoteProcess : 1;
+ // If mCameFromAnotherProcess is true, the event came from another process.
+ bool mCameFromAnotherProcess : 1;
+
+ // At lease one of the event in the event path had non privileged click
+ // listener.
+ bool mHadNonPrivilegedClickListeners : 1;
+
+ // If the event is being handled in target phase, returns true.
+ inline bool InTargetPhase() const {
+ return (mInBubblingPhase && mInCapturePhase);
+ }
+
+ /**
+ * Helper methods for methods of DOM Event.
+ */
+ inline void StopPropagation() { mPropagationStopped = true; }
+ inline void StopImmediatePropagation() {
+ StopPropagation();
+ mImmediatePropagationStopped = true;
+ }
+ inline void PreventDefault(bool aCalledByDefaultHandler = true) {
+ if (!mCancelable) {
+ return;
+ }
+ mDefaultPrevented = true;
+ // Note that even if preventDefault() has already been called by chrome,
+ // a call of preventDefault() by content needs to overwrite
+ // mDefaultPreventedByContent to true because in such case, defaultPrevented
+ // must be true when web apps check it after they call preventDefault().
+ if (aCalledByDefaultHandler) {
+ StopCrossProcessForwarding();
+ mDefaultPreventedByChrome = true;
+ } else {
+ mDefaultPreventedByContent = true;
+ }
+ }
+ // This should be used only before dispatching events into the DOM tree.
+ inline void PreventDefaultBeforeDispatch(
+ CrossProcessForwarding aCrossProcessForwarding) {
+ if (!mCancelable) {
+ return;
+ }
+ mDefaultPrevented = true;
+ if (aCrossProcessForwarding == CrossProcessForwarding::eStop) {
+ StopCrossProcessForwarding();
+ }
+ }
+ inline bool DefaultPrevented() const { return mDefaultPrevented; }
+ inline bool DefaultPreventedByContent() const {
+ MOZ_ASSERT(!mDefaultPreventedByContent || DefaultPrevented());
+ return mDefaultPreventedByContent;
+ }
+ inline bool IsTrusted() const { return mIsTrusted; }
+ inline bool PropagationStopped() const { return mPropagationStopped; }
+
+ // Helper methods to access flags managing state of propagation between
+ // processes.
+
+ /**
+ * Prevent to be dispatched to remote process.
+ */
+ inline void StopCrossProcessForwarding() {
+ MOZ_ASSERT(!mPostedToRemoteProcess);
+ mNoRemoteProcessDispatch = true;
+ mWantReplyFromContentProcess = false;
+ }
+ /**
+ * Return true if the event shouldn't be dispatched to remote process.
+ */
+ inline bool IsCrossProcessForwardingStopped() const {
+ return mNoRemoteProcessDispatch;
+ }
+ /**
+ * Mark the event as waiting reply from remote process.
+ * If the caller needs to win other keyboard event handlers in chrome,
+ * the caller should call StopPropagation() too.
+ * Otherwise, if the caller just needs to know if the event is consumed by
+ * either content or chrome, it should just call this because the event
+ * may be reserved by chrome and it needs to be dispatched into the DOM
+ * tree in chrome for checking if it's reserved before being sent to any
+ * remote processes.
+ */
+ inline void MarkAsWaitingReplyFromRemoteProcess() {
+ MOZ_ASSERT(!mPostedToRemoteProcess);
+ mNoRemoteProcessDispatch = false;
+ mWantReplyFromContentProcess = true;
+ }
+ /**
+ * Reset "waiting reply from remote process" state. This is useful when
+ * you dispatch a copy of an event coming from different process.
+ */
+ inline void ResetWaitingReplyFromRemoteProcessState() {
+ if (IsWaitingReplyFromRemoteProcess()) {
+ // FYI: mWantReplyFromContentProcess is also used for indicating
+ // "handled in remote process" state. Therefore, only when
+ // IsWaitingReplyFromRemoteProcess() returns true, this should
+ // reset the flag.
+ mWantReplyFromContentProcess = false;
+ }
+ }
+ /**
+ * Return true if the event handler should wait reply event. I.e., if this
+ * returns true, any event handler should do nothing with the event.
+ */
+ inline bool IsWaitingReplyFromRemoteProcess() const {
+ return !mNoRemoteProcessDispatch && mWantReplyFromContentProcess;
+ }
+ /**
+ * Mark the event as already handled in the remote process. This should be
+ * called when initializing reply events.
+ */
+ inline void MarkAsHandledInRemoteProcess() {
+ mNoRemoteProcessDispatch = true;
+ mWantReplyFromContentProcess = true;
+ mPostedToRemoteProcess = false;
+ }
+ /**
+ * Return true if the event has already been handled in the remote process.
+ */
+ inline bool IsHandledInRemoteProcess() const {
+ return mNoRemoteProcessDispatch && mWantReplyFromContentProcess;
+ }
+ /**
+ * Return true if the event should be sent back to its parent process.
+ */
+ inline bool WantReplyFromContentProcess() const {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ return IsWaitingReplyFromRemoteProcess();
+ }
+ /**
+ * Mark the event has already posted to a remote process.
+ */
+ inline void MarkAsPostedToRemoteProcess() {
+ MOZ_ASSERT(!IsCrossProcessForwardingStopped());
+ mPostedToRemoteProcess = true;
+ }
+ /**
+ * Reset the cross process dispatching state. This should be used when a
+ * process receives the event because the state is in the sender.
+ */
+ inline void ResetCrossProcessDispatchingState() {
+ MOZ_ASSERT(!IsCrossProcessForwardingStopped());
+ mPostedToRemoteProcess = false;
+ // Ignore propagation state in the different process if it's marked as
+ // "waiting reply from remote process" because the process needs to
+ // stop propagation in the process until receiving a reply event.
+ if (IsWaitingReplyFromRemoteProcess()) {
+ mPropagationStopped = mImmediatePropagationStopped = false;
+ }
+ // mDispatchedAtLeastOnce indicates the state in current process.
+ mDispatchedAtLeastOnce = false;
+ }
+ /**
+ * Return true if the event has been posted to a remote process.
+ * Note that MarkAsPostedToRemoteProcess() is called by
+ * ParamTraits<mozilla::WidgetEvent>. Therefore, it *might* be possible
+ * that posting the event failed even if this returns true. But that must
+ * really rare. If that'd be problem for you, you should unmark this in
+ * BrowserParent or somewhere.
+ */
+ inline bool HasBeenPostedToRemoteProcess() const {
+ return mPostedToRemoteProcess;
+ }
+ /**
+ * Return true if the event came from another process.
+ */
+ inline bool CameFromAnotherProcess() const { return mCameFromAnotherProcess; }
+ /**
+ * Mark the event as coming from another process.
+ */
+ inline void MarkAsComingFromAnotherProcess() {
+ mCameFromAnotherProcess = true;
+ }
+ /**
+ * Mark the event is reserved by chrome. I.e., shouldn't be dispatched to
+ * content because it shouldn't be cancelable.
+ */
+ inline void MarkAsReservedByChrome() {
+ MOZ_ASSERT(!mPostedToRemoteProcess);
+ mIsReservedByChrome = true;
+ // For reserved commands (such as Open New Tab), we don't need to wait for
+ // the content to answer, neither to give a chance for content to override
+ // its behavior.
+ StopCrossProcessForwarding();
+ // If the event is reserved by chrome, we shouldn't expose the event to
+ // web contents because such events shouldn't be cancelable. So, it's not
+ // good behavior to fire such events but to ignore the defaultPrevented
+ // attribute value in chrome.
+ mOnlySystemGroupDispatchInContent = true;
+ }
+ /**
+ * Return true if the event is reserved by chrome.
+ */
+ inline bool IsReservedByChrome() const {
+ MOZ_ASSERT(!mIsReservedByChrome || (IsCrossProcessForwardingStopped() &&
+ mOnlySystemGroupDispatchInContent));
+ return mIsReservedByChrome;
+ }
+
+ inline void Clear() { SetRawFlags(0); }
+ // Get if either the instance's bit or the aOther's bit is true, the
+ // instance's bit becomes true. In other words, this works like:
+ // eventFlags |= aOther;
+ inline void Union(const BaseEventFlags& aOther) {
+ RawFlags rawFlags = GetRawFlags() | aOther.GetRawFlags();
+ SetRawFlags(rawFlags);
+ }
+
+ private:
+ typedef uint64_t RawFlags;
+
+ inline void SetRawFlags(RawFlags aRawFlags) {
+ static_assert(sizeof(BaseEventFlags) <= sizeof(RawFlags),
+ "mozilla::EventFlags must not be bigger than the RawFlags");
+ memcpy(this, &aRawFlags, sizeof(BaseEventFlags));
+ }
+ inline RawFlags GetRawFlags() const {
+ RawFlags result = 0;
+ memcpy(&result, this, sizeof(BaseEventFlags));
+ return result;
+ }
+};
+
+/******************************************************************************
+ * mozilla::EventFlags
+ ******************************************************************************/
+
+struct EventFlags : public BaseEventFlags {
+ EventFlags() { Clear(); }
+};
+
+/******************************************************************************
+ * mozilla::WidgetEventTime
+ ******************************************************************************/
+
+class WidgetEventTime {
+ public:
+ // Elapsed time, in milliseconds, from a platform-specific zero time
+ // to the time the message was created
+ uint64_t mTime;
+ // Timestamp when the message was created. Set in parallel to 'time' until we
+ // determine if it is safe to drop 'time' (see bug 77992).
+ TimeStamp mTimeStamp;
+
+ WidgetEventTime() : mTime(0), mTimeStamp(TimeStamp::Now()) {}
+
+ WidgetEventTime(uint64_t aTime, TimeStamp aTimeStamp)
+ : mTime(aTime), mTimeStamp(aTimeStamp) {}
+
+ void AssignEventTime(const WidgetEventTime& aOther) {
+ mTime = aOther.mTime;
+ mTimeStamp = aOther.mTimeStamp;
+ }
+};
+
+/******************************************************************************
+ * mozilla::WidgetEvent
+ ******************************************************************************/
+
+class WidgetEvent : public WidgetEventTime {
+ private:
+ void SetDefaultCancelableAndBubbles() {
+ switch (mClass) {
+ case eEditorInputEventClass:
+ mFlags.mCancelable = false;
+ mFlags.mBubbles = mFlags.mIsTrusted;
+ break;
+ case eMouseEventClass:
+ mFlags.mCancelable =
+ (mMessage != eMouseEnter && mMessage != eMouseLeave);
+ mFlags.mBubbles = (mMessage != eMouseEnter && mMessage != eMouseLeave);
+ break;
+ case ePointerEventClass:
+ mFlags.mCancelable =
+ (mMessage != ePointerEnter && mMessage != ePointerLeave &&
+ mMessage != ePointerCancel && mMessage != ePointerGotCapture &&
+ mMessage != ePointerLostCapture);
+ mFlags.mBubbles =
+ (mMessage != ePointerEnter && mMessage != ePointerLeave);
+ break;
+ case eDragEventClass:
+ mFlags.mCancelable = (mMessage != eDragExit && mMessage != eDragLeave &&
+ mMessage != eDragEnd);
+ mFlags.mBubbles = true;
+ break;
+ case eSMILTimeEventClass:
+ mFlags.mCancelable = false;
+ mFlags.mBubbles = false;
+ break;
+ case eTransitionEventClass:
+ case eAnimationEventClass:
+ mFlags.mCancelable = false;
+ mFlags.mBubbles = true;
+ break;
+ case eCompositionEventClass:
+ // XXX compositionstart is cancelable in draft of DOM3 Events.
+ // However, it doesn't make sense for us, we cannot cancel
+ // composition when we send compositionstart event.
+ mFlags.mCancelable = false;
+ mFlags.mBubbles = true;
+ break;
+ default:
+ if (mMessage == eResize || mMessage == eMozVisualResize ||
+ mMessage == eMozVisualScroll || mMessage == eEditorInput ||
+ mMessage == eFormSelect) {
+ mFlags.mCancelable = false;
+ } else {
+ mFlags.mCancelable = true;
+ }
+ mFlags.mBubbles = true;
+ break;
+ }
+ }
+
+ protected:
+ WidgetEvent(bool aIsTrusted, EventMessage aMessage,
+ EventClassID aEventClassID)
+ : WidgetEventTime(),
+ mClass(aEventClassID),
+ mMessage(aMessage),
+ mRefPoint(0, 0),
+ mLastRefPoint(0, 0),
+ mFocusSequenceNumber(0),
+ mSpecifiedEventType(nullptr),
+ mPath(nullptr),
+ mLayersId(layers::LayersId{0}) {
+ MOZ_COUNT_CTOR(WidgetEvent);
+ mFlags.Clear();
+ mFlags.mIsTrusted = aIsTrusted;
+ SetDefaultCancelableAndBubbles();
+ SetDefaultComposed();
+ SetDefaultComposedInNativeAnonymousContent();
+ }
+
+ WidgetEvent() : WidgetEventTime(), mPath(nullptr) {
+ MOZ_COUNT_CTOR(WidgetEvent);
+ }
+
+ public:
+ WidgetEvent(bool aIsTrusted, EventMessage aMessage)
+ : WidgetEvent(aIsTrusted, aMessage, eBasicEventClass) {}
+
+ MOZ_COUNTED_DTOR_VIRTUAL(WidgetEvent)
+
+ WidgetEvent(const WidgetEvent& aOther) : WidgetEventTime() {
+ MOZ_COUNT_CTOR(WidgetEvent);
+ *this = aOther;
+ }
+ WidgetEvent& operator=(const WidgetEvent& aOther) = default;
+
+ WidgetEvent(WidgetEvent&& aOther)
+ : WidgetEventTime(std::move(aOther)),
+ mClass(aOther.mClass),
+ mMessage(aOther.mMessage),
+ mRefPoint(std::move(aOther.mRefPoint)),
+ mLastRefPoint(std::move(aOther.mLastRefPoint)),
+ mFocusSequenceNumber(aOther.mFocusSequenceNumber),
+ mFlags(std::move(aOther.mFlags)),
+ mSpecifiedEventType(std::move(aOther.mSpecifiedEventType)),
+ mSpecifiedEventTypeString(std::move(aOther.mSpecifiedEventTypeString)),
+ mTarget(std::move(aOther.mTarget)),
+ mCurrentTarget(std::move(aOther.mCurrentTarget)),
+ mOriginalTarget(std::move(aOther.mOriginalTarget)),
+ mRelatedTarget(std::move(aOther.mRelatedTarget)),
+ mOriginalRelatedTarget(std::move(aOther.mOriginalRelatedTarget)),
+ mPath(std::move(aOther.mPath)) {
+ MOZ_COUNT_CTOR(WidgetEvent);
+ }
+ WidgetEvent& operator=(WidgetEvent&& aOther) = default;
+
+ virtual WidgetEvent* Duplicate() const {
+ MOZ_ASSERT(mClass == eBasicEventClass,
+ "Duplicate() must be overridden by sub class");
+ WidgetEvent* result = new WidgetEvent(false, mMessage);
+ result->AssignEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ EventClassID mClass;
+ EventMessage mMessage;
+ // Relative to the widget of the event, or if there is no widget then it is
+ // in screen coordinates. Not modified by layout code.
+ // This is in visual coordinates, i.e. the correct RelativeTo value that
+ // expresses what this is relative to is `{viewportFrame, Visual}`, where
+ // `viewportFrame` is the viewport frame of the widget's root document.
+ LayoutDeviceIntPoint mRefPoint;
+ // The previous mRefPoint, if known, used to calculate mouse movement deltas.
+ LayoutDeviceIntPoint mLastRefPoint;
+ // The sequence number of the last potentially focus changing event handled
+ // by APZ. This is used to track when that event has been processed by
+ // content, and focus can be reconfirmed for async keyboard scrolling.
+ uint64_t mFocusSequenceNumber;
+ // See BaseEventFlags definition for the detail.
+ BaseEventFlags mFlags;
+
+ // If JS creates an event with unknown event type or known event type but
+ // for different event interface, the event type is stored to this.
+ // NOTE: This is always used if the instance is a WidgetCommandEvent instance
+ // or "input" event is dispatched with dom::Event class.
+ RefPtr<nsAtom> mSpecifiedEventType;
+
+ // nsAtom isn't available on non-main thread due to unsafe. Therefore,
+ // mSpecifiedEventTypeString is used instead of mSpecifiedEventType if
+ // the event is created in non-main thread.
+ nsString mSpecifiedEventTypeString;
+
+ // Event targets, needed by DOM Events
+ // Note that when you need event target for DOM event, you should use
+ // Get*DOMEventTarget() instead of accessing these members directly.
+ nsCOMPtr<dom::EventTarget> mTarget;
+ nsCOMPtr<dom::EventTarget> mCurrentTarget;
+ nsCOMPtr<dom::EventTarget> mOriginalTarget;
+
+ /// The possible related target
+ nsCOMPtr<dom::EventTarget> mRelatedTarget;
+ nsCOMPtr<dom::EventTarget> mOriginalRelatedTarget;
+
+ nsTArray<EventTargetChainItem>* mPath;
+
+ // The LayersId of the content process that this event should be
+ // dispatched to. This field is only used in the chrome process
+ // and doesn't get remoted to child processes.
+ layers::LayersId mLayersId;
+
+ dom::EventTarget* GetDOMEventTarget() const;
+ dom::EventTarget* GetCurrentDOMEventTarget() const;
+ dom::EventTarget* GetOriginalDOMEventTarget() const;
+
+ void AssignEventData(const WidgetEvent& aEvent, bool aCopyTargets) {
+ // mClass should be initialized with the constructor.
+ // mMessage should be initialized with the constructor.
+ mRefPoint = aEvent.mRefPoint;
+ // mLastRefPoint doesn't need to be copied.
+ mFocusSequenceNumber = aEvent.mFocusSequenceNumber;
+ // mLayersId intentionally not copied, since it's not used within content
+ AssignEventTime(aEvent);
+ // mFlags should be copied manually if it's necessary.
+ mSpecifiedEventType = aEvent.mSpecifiedEventType;
+ // mSpecifiedEventTypeString should be copied manually if it's necessary.
+ mTarget = aCopyTargets ? aEvent.mTarget : nullptr;
+ mCurrentTarget = aCopyTargets ? aEvent.mCurrentTarget : nullptr;
+ mOriginalTarget = aCopyTargets ? aEvent.mOriginalTarget : nullptr;
+ mRelatedTarget = aCopyTargets ? aEvent.mRelatedTarget : nullptr;
+ mOriginalRelatedTarget =
+ aCopyTargets ? aEvent.mOriginalRelatedTarget : nullptr;
+ }
+
+ /**
+ * Helper methods for methods of DOM Event.
+ */
+ void StopPropagation() { mFlags.StopPropagation(); }
+ void StopImmediatePropagation() { mFlags.StopImmediatePropagation(); }
+ void PreventDefault(bool aCalledByDefaultHandler = true,
+ nsIPrincipal* aPrincipal = nullptr);
+
+ void PreventDefaultBeforeDispatch(
+ CrossProcessForwarding aCrossProcessForwarding) {
+ mFlags.PreventDefaultBeforeDispatch(aCrossProcessForwarding);
+ }
+ bool DefaultPrevented() const { return mFlags.DefaultPrevented(); }
+ bool DefaultPreventedByContent() const {
+ return mFlags.DefaultPreventedByContent();
+ }
+ bool IsTrusted() const { return mFlags.IsTrusted(); }
+ bool PropagationStopped() const { return mFlags.PropagationStopped(); }
+
+ /**
+ * Prevent to be dispatched to remote process.
+ */
+ inline void StopCrossProcessForwarding() {
+ mFlags.StopCrossProcessForwarding();
+ }
+ /**
+ * Return true if the event shouldn't be dispatched to remote process.
+ */
+ inline bool IsCrossProcessForwardingStopped() const {
+ return mFlags.IsCrossProcessForwardingStopped();
+ }
+ /**
+ * Mark the event as waiting reply from remote process.
+ * Note that this also stops immediate propagation in current process.
+ */
+ inline void MarkAsWaitingReplyFromRemoteProcess() {
+ mFlags.MarkAsWaitingReplyFromRemoteProcess();
+ }
+ /**
+ * Reset "waiting reply from remote process" state. This is useful when
+ * you dispatch a copy of an event coming from different process.
+ */
+ inline void ResetWaitingReplyFromRemoteProcessState() {
+ mFlags.ResetWaitingReplyFromRemoteProcessState();
+ }
+ /**
+ * Return true if the event handler should wait reply event. I.e., if this
+ * returns true, any event handler should do nothing with the event.
+ */
+ inline bool IsWaitingReplyFromRemoteProcess() const {
+ return mFlags.IsWaitingReplyFromRemoteProcess();
+ }
+ /**
+ * Mark the event as already handled in the remote process. This should be
+ * called when initializing reply events.
+ */
+ inline void MarkAsHandledInRemoteProcess() {
+ mFlags.MarkAsHandledInRemoteProcess();
+ }
+ /**
+ * Return true if the event has already been handled in the remote process.
+ * I.e., if this returns true, the event is a reply event.
+ */
+ inline bool IsHandledInRemoteProcess() const {
+ return mFlags.IsHandledInRemoteProcess();
+ }
+ /**
+ * Return true if the event should be sent back to its parent process.
+ * So, usual event handlers shouldn't call this.
+ */
+ inline bool WantReplyFromContentProcess() const {
+ return mFlags.WantReplyFromContentProcess();
+ }
+ /**
+ * Mark the event has already posted to a remote process.
+ */
+ inline void MarkAsPostedToRemoteProcess() {
+ mFlags.MarkAsPostedToRemoteProcess();
+ }
+ /**
+ * Reset the cross process dispatching state. This should be used when a
+ * process receives the event because the state is in the sender.
+ */
+ inline void ResetCrossProcessDispatchingState() {
+ mFlags.ResetCrossProcessDispatchingState();
+ }
+ /**
+ * Return true if the event has been posted to a remote process.
+ */
+ inline bool HasBeenPostedToRemoteProcess() const {
+ return mFlags.HasBeenPostedToRemoteProcess();
+ }
+ /**
+ * Return true if the event came from another process.
+ */
+ inline bool CameFromAnotherProcess() const {
+ return mFlags.CameFromAnotherProcess();
+ }
+ /**
+ * Mark the event as coming from another process.
+ */
+ inline void MarkAsComingFromAnotherProcess() {
+ mFlags.MarkAsComingFromAnotherProcess();
+ }
+ /**
+ * Mark the event is reserved by chrome. I.e., shouldn't be dispatched to
+ * content because it shouldn't be cancelable.
+ */
+ inline void MarkAsReservedByChrome() { mFlags.MarkAsReservedByChrome(); }
+ /**
+ * Return true if the event is reserved by chrome.
+ */
+ inline bool IsReservedByChrome() const { return mFlags.IsReservedByChrome(); }
+
+ /**
+ * Utils for checking event types
+ */
+
+ /**
+ * As*Event() returns the pointer of the instance only when the instance is
+ * the class or one of its derived class.
+ */
+#define NS_ROOT_EVENT_CLASS(aPrefix, aName)
+#define NS_EVENT_CLASS(aPrefix, aName) \
+ virtual aPrefix##aName* As##aName(); \
+ const aPrefix##aName* As##aName() const;
+
+#include "mozilla/EventClassList.h"
+
+#undef NS_EVENT_CLASS
+#undef NS_ROOT_EVENT_CLASS
+
+ /**
+ * Returns true if the event is a query content event.
+ */
+ bool IsQueryContentEvent() const;
+ /**
+ * Returns true if the event is a selection event.
+ */
+ bool IsSelectionEvent() const;
+ /**
+ * Returns true if the event is a content command event.
+ */
+ bool IsContentCommandEvent() const;
+
+ /**
+ * Returns true if the event mMessage is one of mouse events.
+ */
+ bool HasMouseEventMessage() const;
+ /**
+ * Returns true if the event mMessage is one of drag events.
+ */
+ bool HasDragEventMessage() const;
+ /**
+ * Returns true if aMessage or mMessage is one of key events.
+ */
+ static bool IsKeyEventMessage(EventMessage aMessage);
+ bool HasKeyEventMessage() const { return IsKeyEventMessage(mMessage); }
+ /**
+ * Returns true if the event mMessage is one of composition events or text
+ * event.
+ */
+ bool HasIMEEventMessage() const;
+ /**
+ * Returns true if the event mMessage is one of plugin activation events.
+ */
+ bool HasPluginActivationEventMessage() const;
+
+ /**
+ * Returns true if the event can be sent to remote process.
+ */
+ bool CanBeSentToRemoteProcess() const;
+ /**
+ * Returns true if the original target is a remote process and the event
+ * will be posted to the remote process later.
+ */
+ bool WillBeSentToRemoteProcess() const;
+ /**
+ * Returns true if the event is related to IME handling. It includes
+ * IME events, query content events and selection events.
+ * Be careful when you use this.
+ */
+ bool IsIMERelatedEvent() const;
+
+ /**
+ * Whether the event should be handled by the frame of the mouse cursor
+ * position or not. When it should be handled there (e.g., the mouse events),
+ * this returns true.
+ */
+ bool IsUsingCoordinates() const;
+ /**
+ * Whether the event should be handled by the focused DOM window in the
+ * same top level window's or not. E.g., key events, IME related events
+ * (including the query content events, they are used in IME transaction)
+ * should be handled by the (last) focused window rather than the dispatched
+ * window.
+ *
+ * NOTE: Even if this returns true, the event isn't going to be handled by the
+ * application level active DOM window which is on another top level window.
+ * So, when the event is fired on a deactive window, the event is going to be
+ * handled by the last focused DOM window in the last focused window.
+ */
+ bool IsTargetedAtFocusedWindow() const;
+ /**
+ * Whether the event should be handled by the focused content or not. E.g.,
+ * key events, IME related events and other input events which are not handled
+ * by the frame of the mouse cursor position.
+ *
+ * NOTE: Even if this returns true, the event isn't going to be handled by the
+ * application level active DOM window which is on another top level window.
+ * So, when the event is fired on a deactive window, the event is going to be
+ * handled by the last focused DOM element of the last focused DOM window in
+ * the last focused window.
+ */
+ bool IsTargetedAtFocusedContent() const;
+ /**
+ * Whether the event should cause a DOM event.
+ */
+ bool IsAllowedToDispatchDOMEvent() const;
+ /**
+ * Whether the event should be dispatched in system group.
+ */
+ bool IsAllowedToDispatchInSystemGroup() const;
+ /**
+ * Whether the event should be blocked for fingerprinting resistance.
+ */
+ bool IsBlockedForFingerprintingResistance() const;
+ /**
+ * Initialize mComposed
+ */
+ void SetDefaultComposed() {
+ switch (mClass) {
+ case eCompositionEventClass:
+ mFlags.mComposed =
+ mMessage == eCompositionStart || mMessage == eCompositionUpdate ||
+ mMessage == eCompositionChange || mMessage == eCompositionEnd;
+ break;
+ case eDragEventClass:
+ // All drag & drop events are composed
+ mFlags.mComposed = mMessage == eDrag || mMessage == eDragEnd ||
+ mMessage == eDragEnter || mMessage == eDragExit ||
+ mMessage == eDragLeave || mMessage == eDragOver ||
+ mMessage == eDragStart || mMessage == eDrop;
+ break;
+ case eEditorInputEventClass:
+ mFlags.mComposed =
+ mMessage == eEditorInput || mMessage == eEditorBeforeInput;
+ break;
+ case eFocusEventClass:
+ mFlags.mComposed = mMessage == eBlur || mMessage == eFocus ||
+ mMessage == eFocusOut || mMessage == eFocusIn;
+ break;
+ case eKeyboardEventClass:
+ mFlags.mComposed =
+ mMessage == eKeyDown || mMessage == eKeyUp || mMessage == eKeyPress;
+ break;
+ case eMouseEventClass:
+ mFlags.mComposed =
+ mMessage == eMouseClick || mMessage == eMouseDoubleClick ||
+ mMessage == eMouseAuxClick || mMessage == eMouseDown ||
+ mMessage == eMouseUp || mMessage == eMouseOver ||
+ mMessage == eMouseOut || mMessage == eMouseMove ||
+ mMessage == eContextMenu || mMessage == eXULPopupShowing ||
+ mMessage == eXULPopupHiding || mMessage == eXULPopupShown ||
+ mMessage == eXULPopupHidden;
+ break;
+ case ePointerEventClass:
+ // All pointer events are composed
+ mFlags.mComposed =
+ mMessage == ePointerDown || mMessage == ePointerMove ||
+ mMessage == ePointerUp || mMessage == ePointerCancel ||
+ mMessage == ePointerOver || mMessage == ePointerOut ||
+ mMessage == ePointerGotCapture || mMessage == ePointerLostCapture;
+ break;
+ case eTouchEventClass:
+ // All touch events are composed
+ mFlags.mComposed = mMessage == eTouchStart || mMessage == eTouchEnd ||
+ mMessage == eTouchMove || mMessage == eTouchCancel;
+ break;
+ case eUIEventClass:
+ mFlags.mComposed = mMessage == eLegacyDOMFocusIn ||
+ mMessage == eLegacyDOMFocusOut ||
+ mMessage == eLegacyDOMActivate;
+ break;
+ case eWheelEventClass:
+ // All wheel events are composed
+ mFlags.mComposed = mMessage == eWheel;
+ break;
+ case eMouseScrollEventClass:
+ // Legacy mouse scroll events are composed too, for consistency with
+ // wheel.
+ mFlags.mComposed = mMessage == eLegacyMouseLineOrPageScroll ||
+ mMessage == eLegacyMousePixelScroll;
+ break;
+ default:
+ mFlags.mComposed = false;
+ break;
+ }
+ }
+
+ void SetComposed(const nsAString& aEventTypeArg) {
+ mFlags.mComposed = // composition events
+ aEventTypeArg.EqualsLiteral("compositionstart") ||
+ aEventTypeArg.EqualsLiteral("compositionupdate") ||
+ aEventTypeArg.EqualsLiteral("compositionend") ||
+ aEventTypeArg.EqualsLiteral("text") ||
+ // drag and drop events
+ aEventTypeArg.EqualsLiteral("dragstart") ||
+ aEventTypeArg.EqualsLiteral("drag") ||
+ aEventTypeArg.EqualsLiteral("dragenter") ||
+ aEventTypeArg.EqualsLiteral("dragexit") ||
+ aEventTypeArg.EqualsLiteral("dragleave") ||
+ aEventTypeArg.EqualsLiteral("dragover") ||
+ aEventTypeArg.EqualsLiteral("drop") ||
+ aEventTypeArg.EqualsLiteral("dropend") ||
+ // editor input events
+ aEventTypeArg.EqualsLiteral("input") ||
+ aEventTypeArg.EqualsLiteral("beforeinput") ||
+ // focus events
+ aEventTypeArg.EqualsLiteral("blur") ||
+ aEventTypeArg.EqualsLiteral("focus") ||
+ aEventTypeArg.EqualsLiteral("focusin") ||
+ aEventTypeArg.EqualsLiteral("focusout") ||
+ // keyboard events
+ aEventTypeArg.EqualsLiteral("keydown") ||
+ aEventTypeArg.EqualsLiteral("keyup") ||
+ aEventTypeArg.EqualsLiteral("keypress") ||
+ // mouse events
+ aEventTypeArg.EqualsLiteral("click") ||
+ aEventTypeArg.EqualsLiteral("dblclick") ||
+ aEventTypeArg.EqualsLiteral("mousedown") ||
+ aEventTypeArg.EqualsLiteral("mouseup") ||
+ aEventTypeArg.EqualsLiteral("mouseenter") ||
+ aEventTypeArg.EqualsLiteral("mouseleave") ||
+ aEventTypeArg.EqualsLiteral("mouseover") ||
+ aEventTypeArg.EqualsLiteral("mouseout") ||
+ aEventTypeArg.EqualsLiteral("mousemove") ||
+ aEventTypeArg.EqualsLiteral("contextmenu") ||
+ // pointer events
+ aEventTypeArg.EqualsLiteral("pointerdown") ||
+ aEventTypeArg.EqualsLiteral("pointermove") ||
+ aEventTypeArg.EqualsLiteral("pointerup") ||
+ aEventTypeArg.EqualsLiteral("pointercancel") ||
+ aEventTypeArg.EqualsLiteral("pointerover") ||
+ aEventTypeArg.EqualsLiteral("pointerout") ||
+ aEventTypeArg.EqualsLiteral("pointerenter") ||
+ aEventTypeArg.EqualsLiteral("pointerleave") ||
+ aEventTypeArg.EqualsLiteral("gotpointercapture") ||
+ aEventTypeArg.EqualsLiteral("lostpointercapture") ||
+ // touch events
+ aEventTypeArg.EqualsLiteral("touchstart") ||
+ aEventTypeArg.EqualsLiteral("touchend") ||
+ aEventTypeArg.EqualsLiteral("touchmove") ||
+ aEventTypeArg.EqualsLiteral("touchcancel") ||
+ // UI legacy events
+ aEventTypeArg.EqualsLiteral("DOMFocusIn") ||
+ aEventTypeArg.EqualsLiteral("DOMFocusOut") ||
+ aEventTypeArg.EqualsLiteral("DOMActivate") ||
+ // wheel events
+ aEventTypeArg.EqualsLiteral("wheel");
+ }
+
+ void SetComposed(bool aComposed) { mFlags.mComposed = aComposed; }
+
+ void SetDefaultComposedInNativeAnonymousContent() {
+ // For compatibility concerns, we set mComposedInNativeAnonymousContent to
+ // false for those events we want to stop propagation.
+ //
+ // nsVideoFrame may create anonymous image element which fires eLoad,
+ // eLoadStart, eLoadEnd, eLoadError. We don't want these events cross
+ // the boundary of NAC
+ mFlags.mComposedInNativeAnonymousContent =
+ mMessage != eLoad && mMessage != eLoadStart && mMessage != eLoadEnd &&
+ mMessage != eLoadError;
+ }
+
+ bool IsUserAction() const;
+};
+
+/******************************************************************************
+ * mozilla::NativeEventData
+ *
+ * WidgetGUIEvent's mPluginEvent member used to be a void* pointer,
+ * used to reference external, OS-specific data structures.
+ *
+ * That void* pointer wasn't serializable by itself, causing
+ * certain plugin events not to function in e10s. See bug 586656.
+ *
+ * To make this serializable, we changed this void* pointer into
+ * a proper buffer, and copy these external data structures into this
+ * buffer.
+ *
+ * That buffer is NativeEventData::mBuffer below.
+ *
+ * We wrap this in that NativeEventData class providing operators to
+ * be compatible with existing code that was written around
+ * the old void* field.
+ ******************************************************************************/
+
+class NativeEventData final {
+ CopyableTArray<uint8_t> mBuffer;
+
+ friend struct IPC::ParamTraits<mozilla::NativeEventData>;
+
+ public:
+ explicit operator bool() const { return !mBuffer.IsEmpty(); }
+
+ template <typename T>
+ explicit operator const T*() const {
+ return mBuffer.IsEmpty() ? nullptr
+ : reinterpret_cast<const T*>(mBuffer.Elements());
+ }
+
+ template <typename T>
+ void Copy(const T& other) {
+ static_assert(!std::is_pointer_v<T>, "Don't want a pointer!");
+ mBuffer.SetLength(sizeof(T));
+ memcpy(mBuffer.Elements(), &other, mBuffer.Length());
+ }
+
+ void Clear() { mBuffer.Clear(); }
+};
+
+/******************************************************************************
+ * mozilla::WidgetGUIEvent
+ ******************************************************************************/
+
+class WidgetGUIEvent : public WidgetEvent {
+ protected:
+ WidgetGUIEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget,
+ EventClassID aEventClassID)
+ : WidgetEvent(aIsTrusted, aMessage, aEventClassID), mWidget(aWidget) {}
+
+ WidgetGUIEvent() = default;
+
+ public:
+ virtual WidgetGUIEvent* AsGUIEvent() override { return this; }
+
+ WidgetGUIEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget)
+ : WidgetEvent(aIsTrusted, aMessage, eGUIEventClass), mWidget(aWidget) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eGUIEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ WidgetGUIEvent* result = new WidgetGUIEvent(false, mMessage, nullptr);
+ result->AssignGUIEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ // Originator of the event
+ nsCOMPtr<nsIWidget> mWidget;
+
+ /*
+ * Ideally though, we wouldn't allow arbitrary reinterpret_cast'ing here;
+ * instead, we would at least store type information here so that
+ * this class can't be used to reinterpret one structure type into another.
+ * We can also wonder if it would be possible to properly extend
+ * WidgetGUIEvent and other Event classes to remove the need for this
+ * mPluginEvent field.
+ */
+ typedef NativeEventData PluginEvent;
+
+ // Event for NPAPI plugin
+ PluginEvent mPluginEvent;
+
+ void AssignGUIEventData(const WidgetGUIEvent& aEvent, bool aCopyTargets) {
+ AssignEventData(aEvent, aCopyTargets);
+
+ // widget should be initialized with the constructor.
+
+ mPluginEvent = aEvent.mPluginEvent;
+ }
+};
+
+/******************************************************************************
+ * mozilla::Modifier
+ *
+ * All modifier keys should be defined here. This is used for managing
+ * modifier states for DOM Level 3 or later.
+ ******************************************************************************/
+
+enum Modifier {
+ MODIFIER_NONE = 0x0000,
+ MODIFIER_ALT = 0x0001,
+ MODIFIER_ALTGRAPH = 0x0002,
+ MODIFIER_CAPSLOCK = 0x0004,
+ MODIFIER_CONTROL = 0x0008,
+ MODIFIER_FN = 0x0010,
+ MODIFIER_FNLOCK = 0x0020,
+ MODIFIER_META = 0x0040,
+ MODIFIER_NUMLOCK = 0x0080,
+ MODIFIER_SCROLLLOCK = 0x0100,
+ MODIFIER_SHIFT = 0x0200,
+ MODIFIER_SYMBOL = 0x0400,
+ MODIFIER_SYMBOLLOCK = 0x0800,
+ MODIFIER_OS = 0x1000
+};
+
+/******************************************************************************
+ * Modifier key names.
+ ******************************************************************************/
+
+#define NS_DOM_KEYNAME_ALT "Alt"
+#define NS_DOM_KEYNAME_ALTGRAPH "AltGraph"
+#define NS_DOM_KEYNAME_CAPSLOCK "CapsLock"
+#define NS_DOM_KEYNAME_CONTROL "Control"
+#define NS_DOM_KEYNAME_FN "Fn"
+#define NS_DOM_KEYNAME_FNLOCK "FnLock"
+#define NS_DOM_KEYNAME_META "Meta"
+#define NS_DOM_KEYNAME_NUMLOCK "NumLock"
+#define NS_DOM_KEYNAME_SCROLLLOCK "ScrollLock"
+#define NS_DOM_KEYNAME_SHIFT "Shift"
+#define NS_DOM_KEYNAME_SYMBOL "Symbol"
+#define NS_DOM_KEYNAME_SYMBOLLOCK "SymbolLock"
+#define NS_DOM_KEYNAME_OS "OS"
+
+/******************************************************************************
+ * mozilla::Modifiers
+ ******************************************************************************/
+
+typedef uint16_t Modifiers;
+
+class MOZ_STACK_CLASS GetModifiersName final : public nsAutoCString {
+ public:
+ explicit GetModifiersName(Modifiers aModifiers) {
+ if (aModifiers & MODIFIER_ALT) {
+ AssignLiteral(NS_DOM_KEYNAME_ALT);
+ }
+ if (aModifiers & MODIFIER_ALTGRAPH) {
+ MaybeAppendSeparator();
+ AppendLiteral(NS_DOM_KEYNAME_ALTGRAPH);
+ }
+ if (aModifiers & MODIFIER_CAPSLOCK) {
+ MaybeAppendSeparator();
+ AppendLiteral(NS_DOM_KEYNAME_CAPSLOCK);
+ }
+ if (aModifiers & MODIFIER_CONTROL) {
+ MaybeAppendSeparator();
+ AppendLiteral(NS_DOM_KEYNAME_CONTROL);
+ }
+ if (aModifiers & MODIFIER_FN) {
+ MaybeAppendSeparator();
+ AppendLiteral(NS_DOM_KEYNAME_FN);
+ }
+ if (aModifiers & MODIFIER_FNLOCK) {
+ MaybeAppendSeparator();
+ AppendLiteral(NS_DOM_KEYNAME_FNLOCK);
+ }
+ if (aModifiers & MODIFIER_META) {
+ MaybeAppendSeparator();
+ AppendLiteral(NS_DOM_KEYNAME_META);
+ }
+ if (aModifiers & MODIFIER_NUMLOCK) {
+ MaybeAppendSeparator();
+ AppendLiteral(NS_DOM_KEYNAME_NUMLOCK);
+ }
+ if (aModifiers & MODIFIER_SCROLLLOCK) {
+ MaybeAppendSeparator();
+ AppendLiteral(NS_DOM_KEYNAME_SCROLLLOCK);
+ }
+ if (aModifiers & MODIFIER_SHIFT) {
+ MaybeAppendSeparator();
+ AppendLiteral(NS_DOM_KEYNAME_SHIFT);
+ }
+ if (aModifiers & MODIFIER_SYMBOL) {
+ MaybeAppendSeparator();
+ AppendLiteral(NS_DOM_KEYNAME_SYMBOL);
+ }
+ if (aModifiers & MODIFIER_SYMBOLLOCK) {
+ MaybeAppendSeparator();
+ AppendLiteral(NS_DOM_KEYNAME_SYMBOLLOCK);
+ }
+ if (aModifiers & MODIFIER_OS) {
+ MaybeAppendSeparator();
+ AppendLiteral(NS_DOM_KEYNAME_OS);
+ }
+ if (IsEmpty()) {
+ AssignLiteral("none");
+ }
+ }
+
+ private:
+ void MaybeAppendSeparator() {
+ if (!IsEmpty()) {
+ AppendLiteral(" | ");
+ }
+ }
+};
+
+/******************************************************************************
+ * mozilla::WidgetInputEvent
+ ******************************************************************************/
+
+class WidgetInputEvent : public WidgetGUIEvent {
+ protected:
+ WidgetInputEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget,
+ EventClassID aEventClassID)
+ : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, aEventClassID),
+ mModifiers(0) {}
+
+ WidgetInputEvent() : mModifiers(0) {}
+
+ public:
+ virtual WidgetInputEvent* AsInputEvent() override { return this; }
+
+ WidgetInputEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget)
+ : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, eInputEventClass),
+ mModifiers(0) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eInputEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ WidgetInputEvent* result = new WidgetInputEvent(false, mMessage, nullptr);
+ result->AssignInputEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ /**
+ * Returns a modifier of "Accel" virtual modifier which is used for shortcut
+ * key.
+ */
+ static Modifier AccelModifier();
+
+ /**
+ * GetModifier() returns a modifier flag which is activated by aDOMKeyName.
+ */
+ static Modifier GetModifier(const nsAString& aDOMKeyName);
+
+ // true indicates the accel key on the environment is down
+ bool IsAccel() const { return ((mModifiers & AccelModifier()) != 0); }
+
+ // true indicates the shift key is down
+ bool IsShift() const { return ((mModifiers & MODIFIER_SHIFT) != 0); }
+ // true indicates the control key is down
+ bool IsControl() const { return ((mModifiers & MODIFIER_CONTROL) != 0); }
+ // true indicates the alt key is down
+ bool IsAlt() const { return ((mModifiers & MODIFIER_ALT) != 0); }
+ // true indicates the meta key is down (or, on Mac, the Command key)
+ bool IsMeta() const { return ((mModifiers & MODIFIER_META) != 0); }
+ // true indicates the win key is down on Windows. Or the Super or Hyper key
+ // is down on Linux.
+ bool IsOS() const { return ((mModifiers & MODIFIER_OS) != 0); }
+ // true indicates the alt graph key is down
+ // NOTE: on Mac, the option key press causes both IsAlt() and IsAltGrpah()
+ // return true.
+ bool IsAltGraph() const { return ((mModifiers & MODIFIER_ALTGRAPH) != 0); }
+ // true indicates the CapLock LED is turn on.
+ bool IsCapsLocked() const { return ((mModifiers & MODIFIER_CAPSLOCK) != 0); }
+ // true indicates the NumLock LED is turn on.
+ bool IsNumLocked() const { return ((mModifiers & MODIFIER_NUMLOCK) != 0); }
+ // true indicates the ScrollLock LED is turn on.
+ bool IsScrollLocked() const {
+ return ((mModifiers & MODIFIER_SCROLLLOCK) != 0);
+ }
+
+ // true indicates the Fn key is down, but this is not supported by native
+ // key event on any platform.
+ bool IsFn() const { return ((mModifiers & MODIFIER_FN) != 0); }
+ // true indicates the FnLock LED is turn on, but we don't know such
+ // keyboards nor platforms.
+ bool IsFnLocked() const { return ((mModifiers & MODIFIER_FNLOCK) != 0); }
+ // true indicates the Symbol is down, but this is not supported by native
+ // key event on any platforms.
+ bool IsSymbol() const { return ((mModifiers & MODIFIER_SYMBOL) != 0); }
+ // true indicates the SymbolLock LED is turn on, but we don't know such
+ // keyboards nor platforms.
+ bool IsSymbolLocked() const {
+ return ((mModifiers & MODIFIER_SYMBOLLOCK) != 0);
+ }
+
+ void InitBasicModifiers(bool aCtrlKey, bool aAltKey, bool aShiftKey,
+ bool aMetaKey) {
+ mModifiers = 0;
+ if (aCtrlKey) {
+ mModifiers |= MODIFIER_CONTROL;
+ }
+ if (aAltKey) {
+ mModifiers |= MODIFIER_ALT;
+ }
+ if (aShiftKey) {
+ mModifiers |= MODIFIER_SHIFT;
+ }
+ if (aMetaKey) {
+ mModifiers |= MODIFIER_META;
+ }
+ }
+
+ Modifiers mModifiers;
+
+ void AssignInputEventData(const WidgetInputEvent& aEvent, bool aCopyTargets) {
+ AssignGUIEventData(aEvent, aCopyTargets);
+
+ mModifiers = aEvent.mModifiers;
+ }
+};
+
+/******************************************************************************
+ * mozilla::InternalUIEvent
+ *
+ * XXX Why this inherits WidgetGUIEvent rather than WidgetEvent?
+ ******************************************************************************/
+
+class InternalUIEvent : public WidgetGUIEvent {
+ protected:
+ InternalUIEvent() : mDetail(0), mCausedByUntrustedEvent(false) {}
+
+ InternalUIEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget,
+ EventClassID aEventClassID)
+ : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, aEventClassID),
+ mDetail(0),
+ mCausedByUntrustedEvent(false) {}
+
+ InternalUIEvent(bool aIsTrusted, EventMessage aMessage,
+ EventClassID aEventClassID)
+ : WidgetGUIEvent(aIsTrusted, aMessage, nullptr, aEventClassID),
+ mDetail(0),
+ mCausedByUntrustedEvent(false) {}
+
+ public:
+ virtual InternalUIEvent* AsUIEvent() override { return this; }
+
+ /**
+ * If the UIEvent is caused by another event (e.g., click event),
+ * aEventCausesThisEvent should be the event. If there is no such event,
+ * this should be nullptr.
+ */
+ InternalUIEvent(bool aIsTrusted, EventMessage aMessage,
+ const WidgetEvent* aEventCausesThisEvent)
+ : WidgetGUIEvent(aIsTrusted, aMessage, nullptr, eUIEventClass),
+ mDetail(0),
+ mCausedByUntrustedEvent(aEventCausesThisEvent &&
+ !aEventCausesThisEvent->IsTrusted()) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eUIEventClass,
+ "Duplicate() must be overridden by sub class");
+ InternalUIEvent* result = new InternalUIEvent(false, mMessage, nullptr);
+ result->AssignUIEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ int32_t mDetail;
+ // mCausedByUntrustedEvent is true if the event is caused by untrusted event.
+ bool mCausedByUntrustedEvent;
+
+ // If you check the event is a trusted event and NOT caused by an untrusted
+ // event, IsTrustable() returns what you expected.
+ bool IsTrustable() const { return IsTrusted() && !mCausedByUntrustedEvent; }
+
+ void AssignUIEventData(const InternalUIEvent& aEvent, bool aCopyTargets) {
+ AssignGUIEventData(aEvent, aCopyTargets);
+
+ mDetail = aEvent.mDetail;
+ mCausedByUntrustedEvent = aEvent.mCausedByUntrustedEvent;
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_BasicEvents_h__
diff --git a/widget/CommandList.h b/widget/CommandList.h
new file mode 100644
index 0000000000..c28369122e
--- /dev/null
+++ b/widget/CommandList.h
@@ -0,0 +1,169 @@
+/* -*- 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/. */
+
+/**
+ * Define NS_DEFINE_COMMAND(aName, aCommandStr) before including this.
+ * @param aName The name useful in C++ of the command.
+ * @param aCommandStr The command string in JS.
+ *
+ * Define NS_DEFINE_COMMAND_WITH_PARAM(aName, aCommandStr, aParam) before
+ * including this.
+ * @param aName The name useful in C++ of the command.
+ * @param aCommandStr The command string in JS, but this may be shared with
+ * other aName values. I.e., cannot map aName and
+ * aCommandStr 1:1.
+ * @param aParam Additional param value. When aCommandStr is executed,
+ * this value is also specified. I.e., aName becomes
+ * unique when you look for with both aCommandStr and
+ * aParam.
+ *
+ * Define NS_DEFINE_COMMAND_NO_EXEC_COMMAND(aName) before including this.
+ * @param aName The name useful in C++ of the command.
+ */
+
+// Mapped from commands of some platforms
+NS_DEFINE_COMMAND(BeginLine, cmd_beginLine)
+NS_DEFINE_COMMAND(CharNext, cmd_charNext)
+NS_DEFINE_COMMAND(CharPrevious, cmd_charPrevious)
+NS_DEFINE_COMMAND(Copy, cmd_copy)
+NS_DEFINE_COMMAND(Cut, cmd_cut)
+NS_DEFINE_COMMAND(Delete, cmd_delete)
+NS_DEFINE_COMMAND(DeleteCharBackward, cmd_deleteCharBackward)
+NS_DEFINE_COMMAND(DeleteCharForward, cmd_deleteCharForward)
+NS_DEFINE_COMMAND(DeleteToBeginningOfLine, cmd_deleteToBeginningOfLine)
+NS_DEFINE_COMMAND(DeleteToEndOfLine, cmd_deleteToEndOfLine)
+NS_DEFINE_COMMAND(DeleteWordBackward, cmd_deleteWordBackward)
+NS_DEFINE_COMMAND(DeleteWordForward, cmd_deleteWordForward)
+NS_DEFINE_COMMAND(EndLine, cmd_endLine)
+NS_DEFINE_COMMAND(InsertParagraph, cmd_insertParagraph)
+NS_DEFINE_COMMAND(InsertLineBreak, cmd_insertLineBreak)
+NS_DEFINE_COMMAND(LineNext, cmd_lineNext)
+NS_DEFINE_COMMAND(LinePrevious, cmd_linePrevious)
+NS_DEFINE_COMMAND(MoveBottom, cmd_moveBottom)
+NS_DEFINE_COMMAND(MovePageDown, cmd_movePageDown)
+NS_DEFINE_COMMAND(MovePageUp, cmd_movePageUp)
+NS_DEFINE_COMMAND(MoveTop, cmd_moveTop)
+NS_DEFINE_COMMAND(Paste, cmd_paste)
+NS_DEFINE_COMMAND(ScrollBottom, cmd_scrollBottom)
+NS_DEFINE_COMMAND(ScrollLeft, cmd_scrollLeft)
+NS_DEFINE_COMMAND(ScrollLineDown, cmd_scrollLineDown)
+NS_DEFINE_COMMAND(ScrollLineUp, cmd_scrollLineUp)
+NS_DEFINE_COMMAND(ScrollPageDown, cmd_scrollPageDown)
+NS_DEFINE_COMMAND(ScrollPageUp, cmd_scrollPageUp)
+NS_DEFINE_COMMAND(ScrollRight, cmd_scrollRight)
+NS_DEFINE_COMMAND(ScrollTop, cmd_scrollTop)
+NS_DEFINE_COMMAND(SelectAll, cmd_selectAll)
+NS_DEFINE_COMMAND(SelectBeginLine, cmd_selectBeginLine)
+NS_DEFINE_COMMAND(SelectBottom, cmd_selectBottom)
+NS_DEFINE_COMMAND(SelectCharNext, cmd_selectCharNext)
+NS_DEFINE_COMMAND(SelectCharPrevious, cmd_selectCharPrevious)
+NS_DEFINE_COMMAND(SelectEndLine, cmd_selectEndLine)
+NS_DEFINE_COMMAND(SelectLineNext, cmd_selectLineNext)
+NS_DEFINE_COMMAND(SelectLinePrevious, cmd_selectLinePrevious)
+NS_DEFINE_COMMAND(SelectPageDown, cmd_selectPageDown)
+NS_DEFINE_COMMAND(SelectPageUp, cmd_selectPageUp)
+NS_DEFINE_COMMAND(SelectTop, cmd_selectTop)
+NS_DEFINE_COMMAND(SelectWordNext, cmd_selectWordNext)
+NS_DEFINE_COMMAND(SelectWordPrevious, cmd_selectWordPrevious)
+NS_DEFINE_COMMAND(WordNext, cmd_wordNext)
+NS_DEFINE_COMMAND(WordPrevious, cmd_wordPrevious)
+
+// We don't have corresponding commands for them, but some platforms have them.
+NS_DEFINE_COMMAND_NO_EXEC_COMMAND(CancelOperation)
+NS_DEFINE_COMMAND_NO_EXEC_COMMAND(Complete)
+NS_DEFINE_COMMAND_NO_EXEC_COMMAND(InsertBacktab)
+NS_DEFINE_COMMAND_NO_EXEC_COMMAND(InsertTab)
+
+// Commands mapped from HTMLDocument.execCommand()
+NS_DEFINE_COMMAND(FormatBold, cmd_bold)
+NS_DEFINE_COMMAND(FormatItalic, cmd_italic)
+NS_DEFINE_COMMAND(FormatUnderline, cmd_underline)
+NS_DEFINE_COMMAND(FormatStrikeThrough, cmd_strikethrough)
+NS_DEFINE_COMMAND(FormatSubscript, cmd_subscript)
+NS_DEFINE_COMMAND(FormatSuperscript, cmd_superscript)
+NS_DEFINE_COMMAND(HistoryUndo, cmd_undo)
+NS_DEFINE_COMMAND(HistoryRedo, cmd_redo)
+NS_DEFINE_COMMAND(FormatBlock, cmd_paragraphState)
+NS_DEFINE_COMMAND(FormatIndent, cmd_indent)
+NS_DEFINE_COMMAND(FormatOutdent, cmd_outdent)
+NS_DEFINE_COMMAND_WITH_PARAM(FormatJustifyLeft, cmd_align, left)
+NS_DEFINE_COMMAND_WITH_PARAM(FormatJustifyRight, cmd_align, right)
+NS_DEFINE_COMMAND_WITH_PARAM(FormatJustifyCenter, cmd_align, center)
+NS_DEFINE_COMMAND_WITH_PARAM(FormatJustifyFull, cmd_align, justify)
+NS_DEFINE_COMMAND(FormatBackColor, cmd_highlight)
+NS_DEFINE_COMMAND(FormatFontColor, cmd_fontColor)
+NS_DEFINE_COMMAND(FormatFontName, cmd_fontFace)
+NS_DEFINE_COMMAND(FormatFontSize, cmd_fontSize)
+NS_DEFINE_COMMAND(FormatIncreaseFontSize, cmd_increaseFont)
+NS_DEFINE_COMMAND(FormatDecreaseFontSize, cmd_decreaseFont)
+NS_DEFINE_COMMAND(InsertHorizontalRule, cmd_insertHR)
+NS_DEFINE_COMMAND(InsertLink, cmd_insertLinkNoUI)
+NS_DEFINE_COMMAND(InsertImage, cmd_insertImageNoUI)
+NS_DEFINE_COMMAND(InsertHTML, cmd_insertHTML)
+NS_DEFINE_COMMAND(InsertText, cmd_insertText)
+NS_DEFINE_COMMAND(InsertOrderedList, cmd_ol)
+NS_DEFINE_COMMAND(InsertUnorderedList, cmd_ul)
+NS_DEFINE_COMMAND(GetHTML, cmd_getContents)
+NS_DEFINE_COMMAND(FormatRemove, cmd_removeStyles)
+NS_DEFINE_COMMAND(FormatRemoveLink, cmd_removeLinks)
+NS_DEFINE_COMMAND(SetDocumentUseCSS, cmd_setDocumentUseCSS)
+NS_DEFINE_COMMAND(SetDocumentReadOnly, cmd_setDocumentReadOnly)
+NS_DEFINE_COMMAND(SetDocumentInsertBROnEnterKeyPress, cmd_insertBrOnReturn)
+NS_DEFINE_COMMAND(SetDocumentDefaultParagraphSeparator,
+ cmd_defaultParagraphSeparator)
+NS_DEFINE_COMMAND(ToggleObjectResizers, cmd_enableObjectResizing)
+NS_DEFINE_COMMAND(ToggleInlineTableEditor, cmd_enableInlineTableEditing)
+NS_DEFINE_COMMAND(ToggleAbsolutePositionEditor,
+ cmd_enableAbsolutePositionEditing)
+
+// Commands not mapped from HTMLDocument.execCommand() but available with
+// command dispatcher and handled in editor.
+NS_DEFINE_COMMAND(CutOrDelete, cmd_cutOrDelete)
+NS_DEFINE_COMMAND(CopyOrDelete, cmd_copyOrDelete)
+NS_DEFINE_COMMAND(EditorObserverDocumentCreated, obs_documentCreated)
+NS_DEFINE_COMMAND(EditorObserverDocumentLocationChanged,
+ obs_documentLocationChanged)
+NS_DEFINE_COMMAND(EditorObserverDocumentWillBeDestroyed,
+ obs_documentWillBeDestroyed)
+NS_DEFINE_COMMAND(FormatAbbreviation, cmd_abbr)
+NS_DEFINE_COMMAND(FormatAbsolutePosition, cmd_absPos)
+NS_DEFINE_COMMAND(FormatAcronym, cmd_acronym)
+NS_DEFINE_COMMAND(FormatCitation, cmd_cite)
+NS_DEFINE_COMMAND(FormatCode, cmd_code)
+NS_DEFINE_COMMAND(FormatDecreaseZIndex, cmd_decreaseZIndex)
+NS_DEFINE_COMMAND(FormatDocumentBackgroundColor, cmd_backgroundColor)
+NS_DEFINE_COMMAND(FormatEmphasis, cmd_em)
+NS_DEFINE_COMMAND(FormatIncreaseZIndex, cmd_increaseZIndex)
+NS_DEFINE_COMMAND(FormatJustify, cmd_align) // Only for getting enabled/state
+NS_DEFINE_COMMAND(FormatJustifyNone, cmd_align) // with empty string or params
+NS_DEFINE_COMMAND(FormatNoBreak, cmd_nobreak)
+NS_DEFINE_COMMAND(FormatRemoveList, cmd_removeList)
+NS_DEFINE_COMMAND(FormatSample, cmd_samp)
+NS_DEFINE_COMMAND(FormatSetBlockTextDirection, cmd_switchTextDirection)
+NS_DEFINE_COMMAND(FormatStrong, cmd_strong)
+NS_DEFINE_COMMAND(FormatTeletypeText, cmd_tt)
+NS_DEFINE_COMMAND(FormatVariable, cmd_var)
+NS_DEFINE_COMMAND(InsertDefinitionDetails, cmd_dd)
+NS_DEFINE_COMMAND(InsertDefinitionTerm, cmd_dt)
+NS_DEFINE_COMMAND(MoveDown, cmd_moveDown)
+NS_DEFINE_COMMAND(MoveDown2, cmd_moveDown2)
+NS_DEFINE_COMMAND(MoveLeft, cmd_moveLeft)
+NS_DEFINE_COMMAND(MoveLeft2, cmd_moveLeft2)
+NS_DEFINE_COMMAND(MoveRight, cmd_moveRight)
+NS_DEFINE_COMMAND(MoveRight2, cmd_moveRight2)
+NS_DEFINE_COMMAND(MoveUp, cmd_moveUp)
+NS_DEFINE_COMMAND(MoveUp2, cmd_moveUp2)
+NS_DEFINE_COMMAND(PasteAsQuotation, cmd_pasteQuote)
+NS_DEFINE_COMMAND(PasteTransferable, cmd_pasteTransferable)
+NS_DEFINE_COMMAND(PasteWithoutFormat, cmd_pasteNoFormatting)
+NS_DEFINE_COMMAND(SelectDown, cmd_selectDown)
+NS_DEFINE_COMMAND(SelectDown2, cmd_selectDown2)
+NS_DEFINE_COMMAND(SelectLeft, cmd_selectLeft)
+NS_DEFINE_COMMAND(SelectLeft2, cmd_selectLeft2)
+NS_DEFINE_COMMAND(SelectRight, cmd_selectRight)
+NS_DEFINE_COMMAND(SelectRight2, cmd_selectRight2)
+NS_DEFINE_COMMAND(SelectUp, cmd_selectUp)
+NS_DEFINE_COMMAND(SelectUp2, cmd_selectUp2)
+NS_DEFINE_COMMAND(SetDocumentModified, cmd_setDocumentModified)
diff --git a/widget/CompositorWidget.cpp b/widget/CompositorWidget.cpp
new file mode 100644
index 0000000000..f4785699de
--- /dev/null
+++ b/widget/CompositorWidget.cpp
@@ -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/. */
+
+#include "CompositorWidget.h"
+#include "GLConsts.h"
+#include "nsBaseWidget.h"
+#include "VsyncDispatcher.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidget::CompositorWidget(const layers::CompositorOptions& aOptions)
+ : mOptions(aOptions) {}
+
+CompositorWidget::~CompositorWidget() = default;
+
+already_AddRefed<gfx::DrawTarget> CompositorWidget::StartRemoteDrawing() {
+ return nullptr;
+}
+
+void CompositorWidget::CleanupRemoteDrawing() { mLastBackBuffer = nullptr; }
+
+already_AddRefed<gfx::DrawTarget> CompositorWidget::GetBackBufferDrawTarget(
+ gfx::DrawTarget* aScreenTarget, const gfx::IntRect& aRect,
+ bool* aOutIsCleared) {
+ MOZ_ASSERT(aScreenTarget);
+ gfx::SurfaceFormat format =
+ aScreenTarget->GetFormat() == gfx::SurfaceFormat::B8G8R8X8
+ ? gfx::SurfaceFormat::B8G8R8X8
+ : gfx::SurfaceFormat::B8G8R8A8;
+ gfx::IntSize size = aRect.Size();
+ gfx::IntSize clientSize = Max(size, GetClientSize().ToUnknownSize());
+
+ *aOutIsCleared = false;
+ // Re-use back buffer if possible
+ if (!mLastBackBuffer ||
+ mLastBackBuffer->GetBackendType() != aScreenTarget->GetBackendType() ||
+ mLastBackBuffer->GetFormat() != format ||
+ mLastBackBuffer->GetSize() != clientSize) {
+ mLastBackBuffer =
+ aScreenTarget->CreateSimilarDrawTarget(clientSize, format);
+ *aOutIsCleared = true;
+ }
+ return do_AddRef(mLastBackBuffer);
+}
+
+already_AddRefed<gfx::SourceSurface> CompositorWidget::EndBackBufferDrawing() {
+ RefPtr<gfx::SourceSurface> surface =
+ mLastBackBuffer ? mLastBackBuffer->Snapshot() : nullptr;
+ return surface.forget();
+}
+
+uint32_t CompositorWidget::GetGLFrameBufferFormat() { return LOCAL_GL_RGBA; }
+
+RefPtr<VsyncObserver> CompositorWidget::GetVsyncObserver() const {
+ // This should only used when the widget is in the GPU process, and should be
+ // implemented by IPDL-enabled CompositorWidgets.
+ // GPU process does not have a CompositorVsyncDispatcher.
+ MOZ_ASSERT_UNREACHABLE("Must be implemented by derived class");
+ return nullptr;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/CompositorWidget.h b/widget/CompositorWidget.h
new file mode 100644
index 0000000000..1ae80029c7
--- /dev/null
+++ b/widget/CompositorWidget.h
@@ -0,0 +1,287 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_CompositorWidget_h__
+#define mozilla_widget_CompositorWidget_h__
+
+#include "nsISupports.h"
+#include "mozilla/RefPtr.h"
+#include "Units.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/layers/CompositorOptions.h"
+#include "mozilla/layers/LayersTypes.h"
+
+#ifdef MOZ_IS_GCC
+# include "mozilla/layers/NativeLayer.h"
+#endif
+
+class nsIWidget;
+class nsBaseWidget;
+
+namespace mozilla {
+class VsyncObserver;
+namespace gl {
+class GLContext;
+} // namespace gl
+namespace layers {
+class Compositor;
+class LayerManager;
+class LayerManagerComposite;
+class NativeLayerRoot;
+} // namespace layers
+namespace gfx {
+class DrawTarget;
+class SourceSurface;
+} // namespace gfx
+namespace widget {
+
+class WinCompositorWidget;
+class GtkCompositorWidget;
+class AndroidCompositorWidget;
+class CompositorWidgetInitData;
+
+// Gecko widgets usually need to communicate with the CompositorWidget with
+// platform-specific messages (for example to update the window size or
+// transparency). This functionality is controlled through a "host". Since
+// this functionality is platform-dependent, it is only forward declared
+// here.
+class PlatformCompositorWidgetDelegate;
+
+// Headless mode uses its own, singular CompositorWidget implementation.
+class HeadlessCompositorWidget;
+
+class CompositorWidgetDelegate {
+ public:
+ virtual PlatformCompositorWidgetDelegate* AsPlatformSpecificDelegate() {
+ return nullptr;
+ }
+
+ virtual HeadlessCompositorWidget* AsHeadlessCompositorWidget() {
+ return nullptr;
+ }
+};
+
+// Platforms that support out-of-process widgets.
+#if defined(XP_WIN) || defined(MOZ_X11)
+// CompositorWidgetParent should implement CompositorWidget and
+// PCompositorWidgetParent.
+class CompositorWidgetParent;
+
+// CompositorWidgetChild should implement CompositorWidgetDelegate and
+// PCompositorWidgetChild.
+class CompositorWidgetChild;
+
+# define MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING
+#endif
+
+class WidgetRenderingContext {
+ public:
+#if defined(XP_MACOSX)
+ gl::GLContext* mGL = nullptr;
+#endif
+};
+
+/**
+ * Access to a widget from the compositor is restricted to these methods.
+ */
+class CompositorWidget {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::widget::CompositorWidget)
+
+ /**
+ * Create an in-process compositor widget. aWidget may be ignored if the
+ * platform does not require it.
+ */
+ static RefPtr<CompositorWidget> CreateLocal(
+ const CompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions, nsIWidget* aWidget);
+
+ /**
+ * Called before rendering using OMTC. Returns false when the widget is
+ * not ready to be rendered (for example while the window is closed).
+ *
+ * Always called from the compositing thread, which may be the main-thread if
+ * OMTC is not enabled.
+ */
+ virtual bool PreRender(WidgetRenderingContext* aContext) { return true; }
+
+ /**
+ * Called after rendering using OMTC. Not called when rendering was
+ * cancelled by a negative return value from PreRender.
+ *
+ * Always called from the compositing thread, which may be the main-thread if
+ * OMTC is not enabled.
+ */
+ virtual void PostRender(WidgetRenderingContext* aContext) {}
+
+ /**
+ * Called before the first composite. If the result is non-null, one or more
+ * native layers will be placed on the window and used for compositing.
+ * When native layers are used, StartRemoteDrawing(InRegion) and
+ * EndRemoteDrawing(InRegion) will not be called.
+ */
+ virtual RefPtr<layers::NativeLayerRoot> GetNativeLayerRoot() {
+ return nullptr;
+ }
+
+ /**
+ * Return a DrawTarget for the window which can be composited into.
+ *
+ * Only called if GetNativeLayerRoot() returns nullptr.
+ * Called by BasicCompositor on the compositor thread for OMTC drawing
+ * before each composition (unless there's a native layer root).
+ *
+ * The window may specify its buffer mode. If unspecified, it is assumed
+ * to require double-buffering.
+ */
+ virtual already_AddRefed<gfx::DrawTarget> StartRemoteDrawing();
+ virtual already_AddRefed<gfx::DrawTarget> StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion, layers::BufferMode* aBufferMode) {
+ return StartRemoteDrawing();
+ }
+
+ /**
+ * Ensure that what was painted into the DrawTarget returned from
+ * StartRemoteDrawing reaches the screen.
+ *
+ * Called by BasicCompositor on the compositor thread for OMTC drawing
+ * after each composition for which StartRemoteDrawing(InRegion) was called.
+ */
+ virtual void EndRemoteDrawing() {}
+ virtual void EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget,
+ const LayoutDeviceIntRegion& aInvalidRegion) {
+ EndRemoteDrawing();
+ }
+
+ /**
+ * Return true when it is better to defer EndRemoteDrawing().
+ *
+ * Called by BasicCompositor on the compositor thread for OMTC drawing
+ * after each composition.
+ */
+ virtual bool NeedsToDeferEndRemoteDrawing() { return false; }
+
+ /**
+ * Some widgets (namely Gtk) may need clean up underlying surface
+ * before painting to draw transparent objects correctly.
+ */
+ virtual void ClearBeforePaint(RefPtr<gfx::DrawTarget> aTarget,
+ const LayoutDeviceIntRegion& aRegion) {}
+
+ /**
+ * Called when shutting down the LayerManager to clean-up any cached
+ * resources.
+ *
+ * Always called from the compositing thread.
+ */
+ virtual void CleanupWindowEffects() {}
+
+ /**
+ * A hook for the widget to prepare a Compositor, during the latter's
+ * initialization.
+ *
+ * If this method returns true, it means that the widget will be able to
+ * present frames from the compoositor.
+ *
+ * Returning false will cause the compositor's initialization to fail, and
+ * a different compositor backend will be used (if any).
+ */
+ virtual bool InitCompositor(layers::Compositor* aCompositor) { return true; }
+
+ /**
+ * Return the size of the drawable area of the widget.
+ */
+ virtual LayoutDeviceIntSize GetClientSize() = 0;
+
+ /**
+ * Return the internal format of the default framebuffer for this
+ * widget.
+ */
+ virtual uint32_t GetGLFrameBufferFormat();
+
+ /*
+ * Access the underlying nsIWidget. This method will be removed when the
+ * compositor no longer depends on nsIWidget on any platform.
+ */
+ virtual nsIWidget* RealWidget() = 0;
+
+ /**
+ * Clean up any resources used by Start/EndRemoteDrawing.
+ *
+ * Called by BasicCompositor on the compositor thread for OMTC drawing
+ * when the compositor is destroyed.
+ */
+ virtual void CleanupRemoteDrawing();
+
+ /**
+ * Return a key that can represent the widget object round-trip across the
+ * CompositorBridge channel. This only needs to be implemented on GTK and
+ * Windows.
+ *
+ * The key must be the nsIWidget pointer cast to a uintptr_t. See
+ * CompositorBridgeChild::RecvHideAllPlugins and
+ * CompositorBridgeParent::SendHideAllPlugins.
+ */
+ virtual uintptr_t GetWidgetKey() { return 0; }
+
+ /**
+ * Create a backbuffer for the software compositor.
+ */
+ virtual already_AddRefed<gfx::DrawTarget> GetBackBufferDrawTarget(
+ gfx::DrawTarget* aScreenTarget, const gfx::IntRect& aRect,
+ bool* aOutIsCleared);
+
+ /**
+ * Ensure end of composition to back buffer.
+ *
+ * Called by BasicCompositor on the compositor thread for OMTC drawing
+ * after each composition to back buffer.
+ */
+ virtual already_AddRefed<gfx::SourceSurface> EndBackBufferDrawing();
+
+ /**
+ * Observe or unobserve vsync.
+ */
+ virtual void ObserveVsync(VsyncObserver* aObserver) = 0;
+
+ /**
+ * Get the compositor options for the compositor associated with this
+ * CompositorWidget.
+ */
+ const layers::CompositorOptions& GetCompositorOptions() { return mOptions; }
+
+ /**
+ * Return true if the window is hidden and should not be composited.
+ */
+ virtual bool IsHidden() const { return false; }
+
+ /**
+ * This is only used by out-of-process compositors.
+ */
+ virtual RefPtr<VsyncObserver> GetVsyncObserver() const;
+
+ virtual WinCompositorWidget* AsWindows() { return nullptr; }
+ virtual GtkCompositorWidget* AsX11() { return nullptr; }
+ virtual AndroidCompositorWidget* AsAndroid() { return nullptr; }
+
+ /**
+ * Return the platform-specific delegate for the widget, if any.
+ */
+ virtual CompositorWidgetDelegate* AsDelegate() { return nullptr; }
+
+ protected:
+ explicit CompositorWidget(const layers::CompositorOptions& aOptions);
+ virtual ~CompositorWidget();
+
+ // Back buffer of BasicCompositor
+ RefPtr<gfx::DrawTarget> mLastBackBuffer;
+
+ layers::CompositorOptions mOptions;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/ContentCache.cpp b/widget/ContentCache.cpp
new file mode 100644
index 0000000000..99f757bc35
--- /dev/null
+++ b/widget/ContentCache.cpp
@@ -0,0 +1,1703 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ContentCache.h"
+
+#include <utility>
+
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Logging.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TextComposition.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "nsExceptionHandler.h"
+#include "nsIWidget.h"
+
+namespace mozilla {
+
+using namespace dom;
+using namespace widget;
+
+static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
+
+static const char* GetNotificationName(const IMENotification* aNotification) {
+ if (!aNotification) {
+ return "Not notification";
+ }
+ return ToChar(aNotification->mMessage);
+}
+
+/*****************************************************************************
+ * mozilla::ContentCache
+ *****************************************************************************/
+
+LazyLogModule sContentCacheLog("ContentCacheWidgets");
+
+/*****************************************************************************
+ * mozilla::ContentCacheInChild
+ *****************************************************************************/
+
+void ContentCacheInChild::Clear() {
+ MOZ_LOG(sContentCacheLog, LogLevel::Info, ("0x%p Clear()", this));
+
+ mCompositionStart.reset();
+ mLastCommit.reset();
+ mText.Truncate();
+ mSelection.reset();
+ mFirstCharRect.SetEmpty();
+ mCaret.reset();
+ mTextRectArray.reset();
+ mLastCommitStringTextRectArray.reset();
+ mEditorRect.SetEmpty();
+}
+
+void ContentCacheInChild::OnCompositionEvent(
+ const WidgetCompositionEvent& aCompositionEvent) {
+ if (aCompositionEvent.CausesDOMCompositionEndEvent()) {
+ RefPtr<TextComposition> composition =
+ IMEStateManager::GetTextCompositionFor(aCompositionEvent.mWidget);
+ if (composition) {
+ nsAutoString lastCommitString;
+ if (aCompositionEvent.mMessage == eCompositionCommitAsIs) {
+ lastCommitString = composition->CommitStringIfCommittedAsIs();
+ } else {
+ lastCommitString = aCompositionEvent.mData;
+ }
+ // We don't need to store canceling information because this is required
+ // by undoing of last commit (Kakutei-Undo of Japanese IME).
+ if (!lastCommitString.IsEmpty()) {
+ mLastCommit = Some(OffsetAndData<uint32_t>(
+ composition->NativeOffsetOfStartComposition(), lastCommitString));
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Debug,
+ ("0x%p OnCompositionEvent(), stored last composition string data "
+ "(aCompositionEvent={ mMessage=%s, mData=\"%s\"}, mLastCommit=%s)",
+ this, ToChar(aCompositionEvent.mMessage),
+ PrintStringDetail(
+ aCompositionEvent.mData,
+ PrintStringDetail::kMaxLengthForCompositionString)
+ .get(),
+ ToString(mLastCommit).c_str()));
+ return;
+ }
+ }
+ }
+ if (mLastCommit.isSome()) {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Debug,
+ ("0x%p OnCompositionEvent(), resetting the last composition string "
+ "data (aCompositionEvent={ mMessage=%s, mData=\"%s\"}, "
+ "mLastCommit=%s)",
+ this, ToChar(aCompositionEvent.mMessage),
+ PrintStringDetail(aCompositionEvent.mData,
+ PrintStringDetail::kMaxLengthForCompositionString)
+ .get(),
+ ToString(mLastCommit).c_str()));
+ mLastCommit.reset();
+ }
+}
+
+bool ContentCacheInChild::CacheAll(nsIWidget* aWidget,
+ const IMENotification* aNotification) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p CacheAll(aWidget=0x%p, aNotification=%s)", this, aWidget,
+ GetNotificationName(aNotification)));
+
+ if (NS_WARN_IF(!CacheText(aWidget, aNotification)) ||
+ NS_WARN_IF(!CacheEditorRect(aWidget, aNotification))) {
+ return false;
+ }
+ return true;
+}
+
+bool ContentCacheInChild::CacheSelection(nsIWidget* aWidget,
+ const IMENotification* aNotification) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p CacheSelection(aWidget=0x%p, aNotification=%s)", this, aWidget,
+ GetNotificationName(aNotification)));
+
+ mCaret.reset();
+ mSelection.reset();
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ aWidget);
+ aWidget->DispatchEvent(&querySelectedTextEvent, status);
+ if (NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection())) {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheSelection(), FAILED, couldn't retrieve the selected text",
+ this));
+ return false;
+ }
+ MOZ_ASSERT(querySelectedTextEvent.mReply->mOffsetAndData.isSome());
+ if (querySelectedTextEvent.mReply->mReversed) {
+ mSelection.emplace(querySelectedTextEvent.mReply->EndOffset(),
+ querySelectedTextEvent.mReply->StartOffset(),
+ querySelectedTextEvent.mReply->WritingModeRef());
+ } else {
+ mSelection.emplace(querySelectedTextEvent.mReply->StartOffset(),
+ querySelectedTextEvent.mReply->EndOffset(),
+ querySelectedTextEvent.mReply->WritingModeRef());
+ }
+
+ return CacheCaret(aWidget, aNotification) &&
+ CacheTextRects(aWidget, aNotification);
+}
+
+bool ContentCacheInChild::CacheCaret(nsIWidget* aWidget,
+ const IMENotification* aNotification) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p CacheCaret(aWidget=0x%p, aNotification=%s)", this, aWidget,
+ GetNotificationName(aNotification)));
+
+ mCaret.reset();
+
+ if (NS_WARN_IF(mSelection.isNothing())) {
+ return false;
+ }
+
+ // XXX Should be mSelection.mFocus?
+ uint32_t offset = mSelection->StartOffset();
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetQueryContentEvent queryCaretRectEvet(true, eQueryCaretRect, aWidget);
+ queryCaretRectEvet.InitForQueryCaretRect(offset);
+ aWidget->DispatchEvent(&queryCaretRectEvet, status);
+ if (NS_WARN_IF(queryCaretRectEvet.Failed())) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheCaret(), FAILED, couldn't retrieve the caret rect at "
+ "offset=%u",
+ this, offset));
+ return false;
+ }
+ mCaret.emplace(offset, queryCaretRectEvet.mReply->mRect);
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p CacheCaret(), Succeeded, "
+ "mSelection=%s, mCaret=%s",
+ this, ToString(mSelection).c_str(), ToString(mCaret).c_str()));
+ return true;
+}
+
+bool ContentCacheInChild::CacheEditorRect(
+ nsIWidget* aWidget, const IMENotification* aNotification) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p CacheEditorRect(aWidget=0x%p, aNotification=%s)", this,
+ aWidget, GetNotificationName(aNotification)));
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, aWidget);
+ aWidget->DispatchEvent(&queryEditorRectEvent, status);
+ if (NS_WARN_IF(queryEditorRectEvent.Failed())) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheEditorRect(), FAILED, "
+ "couldn't retrieve the editor rect",
+ this));
+ return false;
+ }
+ mEditorRect = queryEditorRectEvent.mReply->mRect;
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p CacheEditorRect(), Succeeded, "
+ "mEditorRect=%s",
+ this, ToString(mEditorRect).c_str()));
+ return true;
+}
+
+bool ContentCacheInChild::CacheText(nsIWidget* aWidget,
+ const IMENotification* aNotification) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p CacheText(aWidget=0x%p, aNotification=%s)", this, aWidget,
+ GetNotificationName(aNotification)));
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
+ aWidget);
+ queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
+ aWidget->DispatchEvent(&queryTextContentEvent, status);
+ if (NS_WARN_IF(queryTextContentEvent.Failed())) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheText(), FAILED, couldn't retrieve whole text", this));
+ mText.Truncate();
+ return false;
+ }
+ mText = queryTextContentEvent.mReply->DataRef();
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Info,
+ ("0x%p CacheText(), Succeeded, mText.Length()=%u", this, mText.Length()));
+
+ // Forget last commit range if string in the range is different from the
+ // last commit string.
+ if (mLastCommit.isSome() &&
+ nsDependentSubstring(mText, mLastCommit->StartOffset(),
+ mLastCommit->Length()) != mLastCommit->DataRef()) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Debug,
+ ("0x%p CacheText(), resetting the last composition string data "
+ "(mLastCommit=%s, current string=\"%s\")",
+ this, ToString(mLastCommit).c_str(),
+ PrintStringDetail(
+ nsDependentSubstring(mText, mLastCommit->StartOffset(),
+ mLastCommit->Length()),
+ PrintStringDetail::kMaxLengthForCompositionString)
+ .get()));
+ mLastCommit.reset();
+ }
+
+ return CacheSelection(aWidget, aNotification);
+}
+
+bool ContentCacheInChild::QueryCharRect(nsIWidget* aWidget, uint32_t aOffset,
+ LayoutDeviceIntRect& aCharRect) const {
+ aCharRect.SetEmpty();
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, aWidget);
+ queryTextRectEvent.InitForQueryTextRect(aOffset, 1);
+ aWidget->DispatchEvent(&queryTextRectEvent, status);
+ if (NS_WARN_IF(queryTextRectEvent.Failed())) {
+ return false;
+ }
+ aCharRect = queryTextRectEvent.mReply->mRect;
+
+ // Guarantee the rect is not empty.
+ if (NS_WARN_IF(!aCharRect.Height())) {
+ aCharRect.SetHeight(1);
+ }
+ if (NS_WARN_IF(!aCharRect.Width())) {
+ aCharRect.SetWidth(1);
+ }
+ return true;
+}
+
+bool ContentCacheInChild::QueryCharRectArray(nsIWidget* aWidget,
+ uint32_t aOffset, uint32_t aLength,
+ RectArray& aCharRectArray) const {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetQueryContentEvent queryTextRectsEvent(true, eQueryTextRectArray,
+ aWidget);
+ queryTextRectsEvent.InitForQueryTextRectArray(aOffset, aLength);
+ aWidget->DispatchEvent(&queryTextRectsEvent, status);
+ if (NS_WARN_IF(queryTextRectsEvent.Failed())) {
+ aCharRectArray.Clear();
+ return false;
+ }
+ aCharRectArray = std::move(queryTextRectsEvent.mReply->mRectArray);
+ return true;
+}
+
+bool ContentCacheInChild::CacheTextRects(nsIWidget* aWidget,
+ const IMENotification* aNotification) {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Info,
+ ("0x%p CacheTextRects(aWidget=0x%p, aNotification=%s), mCaret=%s", this,
+ aWidget, GetNotificationName(aNotification), ToString(mCaret).c_str()));
+
+ mCompositionStart.reset();
+ mTextRectArray.reset();
+ mLastCommitStringTextRectArray.reset();
+ mFirstCharRect.SetEmpty();
+
+ if (NS_WARN_IF(mSelection.isNothing())) {
+ return false;
+ }
+
+ mSelection->ClearRects();
+
+ // Retrieve text rects in composition string if there is.
+ RefPtr<TextComposition> textComposition =
+ IMEStateManager::GetTextCompositionFor(aWidget);
+ if (textComposition) {
+ // mCompositionStart may be updated by some composition event handlers.
+ // So, let's update it with the latest information.
+ mCompositionStart = Some(textComposition->NativeOffsetOfStartComposition());
+ // Note that TextComposition::String() may not be modified here because
+ // it's modified after all edit action listeners are performed but this
+ // is called while some of them are performed.
+ // FYI: For supporting IME which commits composition and restart new
+ // composition immediately, we should cache next character of current
+ // composition too.
+ uint32_t length = textComposition->LastData().Length() + 1;
+ mTextRectArray.emplace(mCompositionStart.value());
+ if (NS_WARN_IF(!QueryCharRectArray(aWidget, mTextRectArray->mStart, length,
+ mTextRectArray->mRects))) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheTextRects(), FAILED, "
+ "couldn't retrieve text rect array of the composition string",
+ this));
+ mTextRectArray.reset();
+ }
+ }
+
+ if (mTextRectArray.isSome() &&
+ mTextRectArray->IsOffsetInRange(mSelection->mAnchor) &&
+ (!mSelection->mAnchor ||
+ mTextRectArray->IsOffsetInRange(mSelection->mAnchor - 1))) {
+ mSelection->mAnchorCharRects[eNextCharRect] =
+ mTextRectArray->GetRect(mSelection->mAnchor);
+ if (mSelection->mAnchor) {
+ mSelection->mAnchorCharRects[ePrevCharRect] =
+ mTextRectArray->GetRect(mSelection->mAnchor - 1);
+ }
+ } else {
+ RectArray rects;
+ uint32_t startOffset = mSelection->mAnchor ? mSelection->mAnchor - 1 : 0;
+ uint32_t length = mSelection->mAnchor ? 2 : 1;
+ if (NS_WARN_IF(!QueryCharRectArray(aWidget, startOffset, length, rects))) {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheTextRects(), FAILED, "
+ "couldn't retrieve text rect array around the selection anchor (%u)",
+ this, mSelection->mAnchor));
+ MOZ_ASSERT(mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty());
+ MOZ_ASSERT(mSelection->mAnchorCharRects[eNextCharRect].IsEmpty());
+ } else {
+ if (rects.Length() > 1) {
+ mSelection->mAnchorCharRects[ePrevCharRect] = rects[0];
+ mSelection->mAnchorCharRects[eNextCharRect] = rects[1];
+ } else if (rects.Length()) {
+ mSelection->mAnchorCharRects[eNextCharRect] = rects[0];
+ MOZ_ASSERT(mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty());
+ }
+ }
+ }
+
+ if (mSelection->Collapsed()) {
+ mSelection->mFocusCharRects[0] = mSelection->mAnchorCharRects[0];
+ mSelection->mFocusCharRects[1] = mSelection->mAnchorCharRects[1];
+ } else if (mTextRectArray.isSome() &&
+ mTextRectArray->IsOffsetInRange(mSelection->mFocus) &&
+ (!mSelection->mFocus ||
+ mTextRectArray->IsOffsetInRange(mSelection->mFocus - 1))) {
+ mSelection->mFocusCharRects[eNextCharRect] =
+ mTextRectArray->GetRect(mSelection->mFocus);
+ if (mSelection->mFocus) {
+ mSelection->mFocusCharRects[ePrevCharRect] =
+ mTextRectArray->GetRect(mSelection->mFocus - 1);
+ }
+ } else {
+ RectArray rects;
+ uint32_t startOffset = mSelection->mFocus ? mSelection->mFocus - 1 : 0;
+ uint32_t length = mSelection->mFocus ? 2 : 1;
+ if (NS_WARN_IF(!QueryCharRectArray(aWidget, startOffset, length, rects))) {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheTextRects(), FAILED, "
+ "couldn't retrieve text rect array around the selection focus (%u)",
+ this, mSelection->mFocus));
+ MOZ_ASSERT(mSelection->mFocusCharRects[ePrevCharRect].IsEmpty());
+ MOZ_ASSERT(mSelection->mFocusCharRects[eNextCharRect].IsEmpty());
+ } else {
+ if (rects.Length() > 1) {
+ mSelection->mFocusCharRects[ePrevCharRect] = rects[0];
+ mSelection->mFocusCharRects[eNextCharRect] = rects[1];
+ } else if (rects.Length()) {
+ mSelection->mFocusCharRects[eNextCharRect] = rects[0];
+ MOZ_ASSERT(mSelection->mFocusCharRects[ePrevCharRect].IsEmpty());
+ }
+ }
+ }
+
+ if (!mSelection->Collapsed()) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, aWidget);
+ queryTextRectEvent.InitForQueryTextRect(mSelection->StartOffset(),
+ mSelection->Length());
+ aWidget->DispatchEvent(&queryTextRectEvent, status);
+ if (NS_WARN_IF(queryTextRectEvent.Failed())) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheTextRects(), FAILED, "
+ "couldn't retrieve text rect of whole selected text",
+ this));
+ } else {
+ mSelection->mRect = queryTextRectEvent.mReply->mRect;
+ }
+ }
+
+ if (!mSelection->mFocus) {
+ mFirstCharRect = mSelection->mFocusCharRects[eNextCharRect];
+ } else if (mSelection->mFocus == 1) {
+ mFirstCharRect = mSelection->mFocusCharRects[ePrevCharRect];
+ } else if (!mSelection->mAnchor) {
+ mFirstCharRect = mSelection->mAnchorCharRects[eNextCharRect];
+ } else if (mSelection->mAnchor == 1) {
+ mFirstCharRect = mSelection->mFocusCharRects[ePrevCharRect];
+ } else if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(0)) {
+ mFirstCharRect = mTextRectArray->GetRect(0);
+ } else {
+ LayoutDeviceIntRect charRect;
+ if (NS_WARN_IF(!QueryCharRect(aWidget, 0, charRect))) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheTextRects(), FAILED, "
+ "couldn't retrieve first char rect",
+ this));
+ } else {
+ mFirstCharRect = charRect;
+ }
+ }
+
+ if (mLastCommit.isSome()) {
+ mLastCommitStringTextRectArray.emplace(mLastCommit->StartOffset());
+ if (mLastCommit->Length() == 1) {
+ MOZ_ASSERT(mSelection->Collapsed());
+ MOZ_ASSERT(mSelection->mAnchor - 1 == mLastCommit->StartOffset());
+ mLastCommitStringTextRectArray->mRects.AppendElement(
+ mSelection->mAnchorCharRects[ePrevCharRect]);
+ } else if (NS_WARN_IF(!QueryCharRectArray(
+ aWidget, mLastCommit->StartOffset(), mLastCommit->Length(),
+ mLastCommitStringTextRectArray->mRects))) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheTextRects(), FAILED, "
+ "couldn't retrieve text rect array of the last commit string",
+ this));
+ mLastCommitStringTextRectArray.reset();
+ mLastCommit.reset();
+ }
+ MOZ_ASSERT((mLastCommitStringTextRectArray.isSome()
+ ? mLastCommitStringTextRectArray->mRects.Length()
+ : 0) == (mLastCommit.isSome() ? mLastCommit->Length() : 0));
+ }
+
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p CacheTextRects(), Succeeded, "
+ "mText.Length()=%x, mTextRectArray=%s, mSelection=%s, "
+ "mFirstCharRect=%s, mLastCommitStringTextRectArray=%s",
+ this, mText.Length(), ToString(mTextRectArray).c_str(),
+ ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str(),
+ ToString(mLastCommitStringTextRectArray).c_str()));
+ return true;
+}
+
+void ContentCacheInChild::SetSelection(nsIWidget* aWidget,
+ uint32_t aStartOffset, uint32_t aLength,
+ bool aReversed,
+ const WritingMode& aWritingMode) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p SetSelection(aStartOffset=%u, "
+ "aLength=%u, aReversed=%s, aWritingMode=%s), mText.Length()=%u",
+ this, aStartOffset, aLength, GetBoolName(aReversed),
+ ToString(aWritingMode).c_str(), mText.Length()));
+
+ mSelection = Some(Selection(
+ !aReversed ? aStartOffset : aStartOffset + aLength,
+ !aReversed ? aStartOffset + aLength : aStartOffset, aWritingMode));
+
+ if (mLastCommit.isSome()) {
+ // Forget last commit string range if selection is not collapsed
+ // at end of the last commit string.
+ if (!mSelection->Collapsed() ||
+ mSelection->mAnchor != mLastCommit->EndOffset()) {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Debug,
+ ("0x%p SetSelection(), forgetting last commit composition data "
+ "(mSelection=%s, mLastCommit=%s)",
+ this, ToString(mSelection).c_str(), ToString(mLastCommit).c_str()));
+ mLastCommit.reset();
+ }
+ }
+
+ if (NS_WARN_IF(!CacheCaret(aWidget))) {
+ return;
+ }
+ Unused << NS_WARN_IF(!CacheTextRects(aWidget));
+}
+
+/*****************************************************************************
+ * mozilla::ContentCacheInParent
+ *****************************************************************************/
+
+ContentCacheInParent::ContentCacheInParent(BrowserParent& aBrowserParent)
+ : ContentCache(),
+ mBrowserParent(aBrowserParent),
+ mCommitStringByRequest(nullptr),
+ mPendingEventsNeedingAck(0),
+ mPendingCommitLength(0),
+ mPendingCompositionCount(0),
+ mPendingCommitCount(0),
+ mWidgetHasComposition(false),
+ mIsChildIgnoringCompositionEvents(false) {}
+
+void ContentCacheInParent::AssignContent(const ContentCache& aOther,
+ nsIWidget* aWidget,
+ const IMENotification* aNotification) {
+ mText = aOther.mText;
+ mSelection = aOther.mSelection;
+ mFirstCharRect = aOther.mFirstCharRect;
+ mCaret = aOther.mCaret;
+ mTextRectArray = aOther.mTextRectArray;
+ mLastCommitStringTextRectArray = aOther.mLastCommitStringTextRectArray;
+ mEditorRect = aOther.mEditorRect;
+
+ // Only when there is one composition, the TextComposition instance in this
+ // process is managing the composition in the remote process. Therefore,
+ // we shouldn't update composition start offset of TextComposition with
+ // old composition which is still being handled by the child process.
+ if (mWidgetHasComposition && mPendingCompositionCount == 1 &&
+ mCompositionStart.isSome()) {
+ IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget,
+ mCompositionStart.value());
+ }
+
+ // When this instance allows to query content relative to composition string,
+ // we should modify mCompositionStart with the latest information in the
+ // remote process because now we have the information around the composition
+ // string.
+ mCompositionStartInChild = aOther.mCompositionStart;
+ if (mWidgetHasComposition || mPendingCommitCount) {
+ if (mCompositionStartInChild.isSome()) {
+ if (mCompositionStart.valueOr(UINT32_MAX) !=
+ mCompositionStartInChild.value()) {
+ mCompositionStart = mCompositionStartInChild;
+ mPendingCommitLength = 0;
+ }
+ } else if (mCompositionStart.isSome() && mSelection.isSome() &&
+ mCompositionStart.value() != mSelection->StartOffset()) {
+ mCompositionStart = Some(mSelection->StartOffset());
+ mPendingCommitLength = 0;
+ }
+ }
+
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p AssignContent(aNotification=%s), "
+ "Succeeded, mText.Length()=%u, mSelection=%s, mFirstCharRect=%s, "
+ "mCaret=%s, mTextRectArray=%s, mWidgetHasComposition=%s, "
+ "mPendingCompositionCount=%u, mCompositionStart=%s, "
+ "mPendingCommitLength=%u, mEditorRect=%s, "
+ "mLastCommitStringTextRectArray=%s",
+ this, GetNotificationName(aNotification), mText.Length(),
+ ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str(),
+ ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
+ GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
+ ToString(mCompositionStart).c_str(), mPendingCommitLength,
+ ToString(mEditorRect).c_str(),
+ ToString(mLastCommitStringTextRectArray).c_str()));
+}
+
+bool ContentCacheInParent::HandleQueryContentEvent(
+ WidgetQueryContentEvent& aEvent, nsIWidget* aWidget) const {
+ MOZ_ASSERT(aWidget);
+
+ // ContentCache doesn't store offset of its start with XP linebreaks.
+ // So, we don't support to query contents relative to composition start
+ // offset with XP linebreaks.
+ if (NS_WARN_IF(!aEvent.mUseNativeLineBreak)) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED due to query with XP "
+ "linebreaks",
+ this));
+ return false;
+ }
+
+ if (NS_WARN_IF(!aEvent.mInput.IsValidOffset())) {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED due to invalid offset", this));
+ return false;
+ }
+
+ if (NS_WARN_IF(!aEvent.mInput.IsValidEventMessage(aEvent.mMessage))) {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED due to invalid event message",
+ this));
+ return false;
+ }
+
+ bool isRelativeToInsertionPoint = aEvent.mInput.mRelativeToInsertionPoint;
+ if (isRelativeToInsertionPoint) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Debug,
+ ("0x%p HandleQueryContentEvent(), "
+ "making offset absolute... aEvent={ mMessage=%s, mInput={ "
+ "mOffset=%" PRId64 ", mLength=%" PRIu32 " } }, "
+ "mWidgetHasComposition=%s, mPendingCommitCount=%" PRIu8
+ ", mCompositionStart=%" PRIu32 ", "
+ "mPendingCommitLength=%" PRIu32 ", mSelection=%s",
+ this, ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
+ aEvent.mInput.mLength, GetBoolName(mWidgetHasComposition),
+ mPendingCommitCount, mCompositionStart.valueOr(UINT32_MAX),
+ mPendingCommitLength, ToString(mSelection).c_str()));
+ if (mWidgetHasComposition || mPendingCommitCount) {
+ if (NS_WARN_IF(mCompositionStart.isNothing()) ||
+ NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
+ mCompositionStart.value() + mPendingCommitLength))) {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED due to "
+ "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart + "
+ "mPendingCommitLength) failure, "
+ "mCompositionStart=%" PRIu32 ", mPendingCommitLength=%" PRIu32 ", "
+ "aEvent={ mMessage=%s, mInput={ mOffset=%" PRId64
+ ", mLength=%" PRIu32 " } }",
+ this, mCompositionStart.valueOr(UINT32_MAX), mPendingCommitLength,
+ ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
+ aEvent.mInput.mLength));
+ return false;
+ }
+ } else if (NS_WARN_IF(mSelection.isNothing())) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is "
+ "not set",
+ this));
+ return false;
+ } else if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
+ mSelection->StartOffset() + mPendingCommitLength))) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED due to "
+ "aEvent.mInput.MakeOffsetAbsolute(mSelection.StartOffset() + "
+ "mPendingCommitLength) failure, mSelection=%s, "
+ "mPendingCommitLength=%" PRIu32 ", aEvent={ mMessage=%s, "
+ "mInput={ mOffset=%" PRId64 ", mLength=%" PRIu32 " } }",
+ this, ToString(mSelection).c_str(), mPendingCommitLength,
+ ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
+ aEvent.mInput.mLength));
+ return false;
+ }
+ }
+
+ switch (aEvent.mMessage) {
+ case eQuerySelectedText:
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent(aEvent={ "
+ "mMessage=eQuerySelectedText }, aWidget=0x%p)",
+ this, aWidget));
+ if (NS_WARN_IF(!IsSelectionValid())) {
+ // If content cache hasn't been initialized properly, make the query
+ // failed.
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED because mSelection is "
+ "not valid",
+ this));
+ return false;
+ }
+ if (!mSelection->Collapsed() &&
+ NS_WARN_IF(mSelection->EndOffset() > mText.Length())) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED because "
+ "mSelection->EndOffset()=%u is larger than mText.Length()=%u",
+ this, mSelection->EndOffset(), mText.Length()));
+ return false;
+ }
+ aEvent.EmplaceReply();
+ aEvent.mReply->mFocusedWidget = aWidget;
+ aEvent.mReply->mOffsetAndData.emplace(
+ mSelection->StartOffset(),
+ Substring(mText, mSelection->StartOffset(), mSelection->Length()),
+ OffsetAndDataFor::SelectedString);
+ aEvent.mReply->mReversed = mSelection->Reversed();
+ aEvent.mReply->mHasSelection = true;
+ aEvent.mReply->mWritingMode = mSelection->mWritingMode;
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent(), "
+ "Succeeded, aEvent={ mMessage=eQuerySelectedText, mReply=%s }",
+ this, ToString(aEvent.mReply).c_str()));
+ return true;
+ case eQueryTextContent: {
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent("
+ "aEvent={ mMessage=eQueryTextContent, mInput={ mOffset=%" PRId64
+ ", mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
+ this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
+ mText.Length()));
+ uint32_t inputOffset = aEvent.mInput.mOffset;
+ uint32_t inputEndOffset =
+ std::min(aEvent.mInput.EndOffset(), mText.Length());
+ if (NS_WARN_IF(inputEndOffset < inputOffset)) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED because "
+ "inputOffset=%u is larger than inputEndOffset=%u",
+ this, inputOffset, inputEndOffset));
+ return false;
+ }
+ aEvent.EmplaceReply();
+ aEvent.mReply->mFocusedWidget = aWidget;
+ aEvent.mReply->mOffsetAndData.emplace(
+ inputOffset,
+ Substring(mText, inputOffset, inputEndOffset - inputOffset),
+ OffsetAndDataFor::EditorString);
+ // TODO: Support font ranges
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
+ "mMessage=eQueryTextContent, mReply=%s }",
+ this, ToString(aEvent.mReply).c_str()));
+ return true;
+ }
+ case eQueryTextRect: {
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent("
+ "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%" PRId64
+ ", mLength=%u } }, aWidget=0x%p), mText.Length()=%u",
+ this, aEvent.mInput.mOffset, aEvent.mInput.mLength, aWidget,
+ mText.Length()));
+ if (NS_WARN_IF(!IsSelectionValid())) {
+ // If content cache hasn't been initialized properly, make the query
+ // failed.
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED because mSelection is "
+ "not valid",
+ this));
+ return false;
+ }
+ // Note that if the query is relative to insertion point, the query was
+ // probably requested by native IME. In such case, we should return
+ // non-empty rect since returning failure causes IME showing its window
+ // at odd position.
+ LayoutDeviceIntRect textRect;
+ if (aEvent.mInput.mLength) {
+ if (NS_WARN_IF(
+ !GetUnionTextRects(aEvent.mInput.mOffset, aEvent.mInput.mLength,
+ isRelativeToInsertionPoint, textRect))) {
+ // XXX We don't have cache for this request.
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED to get union rect",
+ this));
+ return false;
+ }
+ } else {
+ // If the length is 0, we should return caret rect instead.
+ if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
+ isRelativeToInsertionPoint, textRect))) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED to get caret rect",
+ this));
+ return false;
+ }
+ }
+ aEvent.EmplaceReply();
+ aEvent.mReply->mFocusedWidget = aWidget;
+ aEvent.mReply->mRect = textRect;
+ aEvent.mReply->mOffsetAndData.emplace(
+ aEvent.mInput.mOffset,
+ aEvent.mInput.mOffset < mText.Length()
+ ? static_cast<const nsAString&>(
+ Substring(mText, aEvent.mInput.mOffset,
+ mText.Length() >= aEvent.mInput.EndOffset()
+ ? aEvent.mInput.mLength
+ : UINT32_MAX))
+ : static_cast<const nsAString&>(EmptyString()),
+ OffsetAndDataFor::EditorString);
+ // XXX This may be wrong if storing range isn't in the selection range.
+ aEvent.mReply->mWritingMode = mSelection->mWritingMode;
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
+ "mMessage=eQueryTextRect mReply=%s }",
+ this, ToString(aEvent.mReply).c_str()));
+ return true;
+ }
+ case eQueryCaretRect: {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent(aEvent={ mMessage=eQueryCaretRect, "
+ "mInput={ mOffset=%" PRId64 " } }, aWidget=0x%p), mText.Length()=%u",
+ this, aEvent.mInput.mOffset, aWidget, mText.Length()));
+ if (NS_WARN_IF(!IsSelectionValid())) {
+ // If content cache hasn't been initialized properly, make the query
+ // failed.
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED because mSelection is "
+ "not valid",
+ this));
+ return false;
+ }
+ // Note that if the query is relative to insertion point, the query was
+ // probably requested by native IME. In such case, we should return
+ // non-empty rect since returning failure causes IME showing its window
+ // at odd position.
+ LayoutDeviceIntRect caretRect;
+ if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
+ isRelativeToInsertionPoint, caretRect))) {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(),FAILED to get caret rect", this));
+ return false;
+ }
+ aEvent.EmplaceReply();
+ aEvent.mReply->mFocusedWidget = aWidget;
+ aEvent.mReply->mRect = caretRect;
+ aEvent.mReply->mOffsetAndData.emplace(aEvent.mInput.mOffset,
+ EmptyString(),
+ OffsetAndDataFor::SelectedString);
+ // TODO: Set mWritingMode here
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
+ "mMessage=eQueryCaretRect, mReply=%s }",
+ this, ToString(aEvent.mReply).c_str()));
+ return true;
+ }
+ case eQueryEditorRect:
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent(aEvent={ "
+ "mMessage=eQueryEditorRect }, aWidget=0x%p)",
+ this, aWidget));
+ aEvent.EmplaceReply();
+ aEvent.mReply->mFocusedWidget = aWidget;
+ aEvent.mReply->mRect = mEditorRect;
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
+ "mMessage=eQueryEditorRect, mReply=%s }",
+ this, ToString(aEvent.mReply).c_str()));
+ return true;
+ default:
+ aEvent.EmplaceReply();
+ aEvent.mReply->mFocusedWidget = aWidget;
+ if (NS_WARN_IF(aEvent.Failed())) {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED due to not set enough "
+ "data, aEvent={ mMessage=%s, mReply=%s }",
+ this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
+ return false;
+ }
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent(), Succeeded, aEvent={ "
+ "mMessage=%s, mReply=%s }",
+ this, ToChar(aEvent.mMessage), ToString(aEvent.mReply).c_str()));
+ return true;
+ }
+}
+
+bool ContentCacheInParent::GetTextRect(uint32_t aOffset,
+ bool aRoundToExistingOffset,
+ LayoutDeviceIntRect& aTextRect) const {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Info,
+ ("0x%p GetTextRect(aOffset=%u, aRoundToExistingOffset=%s), "
+ "mTextRectArray=%s, mSelection=%s, mLastCommitStringTextRectArray=%s",
+ this, aOffset, GetBoolName(aRoundToExistingOffset),
+ ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
+ ToString(mLastCommitStringTextRectArray).c_str()));
+
+ if (!aOffset) {
+ NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
+ aTextRect = mFirstCharRect;
+ return !aTextRect.IsEmpty();
+ }
+ if (mSelection.isSome()) {
+ if (aOffset == mSelection->mAnchor) {
+ NS_WARNING_ASSERTION(
+ !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(), "empty rect");
+ aTextRect = mSelection->mAnchorCharRects[eNextCharRect];
+ return !aTextRect.IsEmpty();
+ }
+ if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
+ NS_WARNING_ASSERTION(
+ !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(), "empty rect");
+ aTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
+ return !aTextRect.IsEmpty();
+ }
+ if (aOffset == mSelection->mFocus) {
+ NS_WARNING_ASSERTION(
+ !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(), "empty rect");
+ aTextRect = mSelection->mFocusCharRects[eNextCharRect];
+ return !aTextRect.IsEmpty();
+ }
+ if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
+ NS_WARNING_ASSERTION(
+ !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(), "empty rect");
+ aTextRect = mSelection->mFocusCharRects[ePrevCharRect];
+ return !aTextRect.IsEmpty();
+ }
+ }
+
+ if (mTextRectArray.isSome() && mTextRectArray->IsOffsetInRange(aOffset)) {
+ aTextRect = mTextRectArray->GetRect(aOffset);
+ return !aTextRect.IsEmpty();
+ }
+
+ if (mLastCommitStringTextRectArray.isSome() &&
+ mLastCommitStringTextRectArray->IsOffsetInRange(aOffset)) {
+ aTextRect = mLastCommitStringTextRectArray->GetRect(aOffset);
+ return !aTextRect.IsEmpty();
+ }
+
+ if (!aRoundToExistingOffset) {
+ aTextRect.SetEmpty();
+ return false;
+ }
+
+ if (mTextRectArray.isNothing() || !mTextRectArray->HasRects()) {
+ // If there are no rects in mTextRectArray, we should refer the start of
+ // the selection because IME must query a char rect around it if there is
+ // no composition.
+ aTextRect = mSelection->StartCharRect();
+ return !aTextRect.IsEmpty();
+ }
+
+ // Although we may have mLastCommitStringTextRectArray here and it must have
+ // previous character rects at selection. However, we should stop using it
+ // because it's stored really short time after commiting a composition.
+ // So, multiple query may return different rect and it may cause flickerling
+ // the IME UI.
+ uint32_t offset = aOffset;
+ if (offset < mTextRectArray->StartOffset()) {
+ offset = mTextRectArray->StartOffset();
+ } else {
+ offset = mTextRectArray->EndOffset() - 1;
+ }
+ aTextRect = mTextRectArray->GetRect(offset);
+ return !aTextRect.IsEmpty();
+}
+
+bool ContentCacheInParent::GetUnionTextRects(
+ uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset,
+ LayoutDeviceIntRect& aUnionTextRect) const {
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p GetUnionTextRects(aOffset=%u, "
+ "aLength=%u, aRoundToExistingOffset=%s), mTextRectArray=%s, "
+ "mSelection=%s, mLastCommitStringTextRectArray=%s",
+ this, aOffset, aLength, GetBoolName(aRoundToExistingOffset),
+ ToString(mTextRectArray).c_str(), ToString(mSelection).c_str(),
+ ToString(mLastCommitStringTextRectArray).c_str()));
+
+ CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
+ if (!endOffset.isValid()) {
+ return false;
+ }
+
+ if (mSelection.isSome() && !mSelection->Collapsed() &&
+ aOffset == mSelection->StartOffset() && aLength == mSelection->Length()) {
+ NS_WARNING_ASSERTION(!mSelection->mRect.IsEmpty(), "empty rect");
+ aUnionTextRect = mSelection->mRect;
+ return !aUnionTextRect.IsEmpty();
+ }
+
+ if (aLength == 1) {
+ if (!aOffset) {
+ NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
+ aUnionTextRect = mFirstCharRect;
+ return !aUnionTextRect.IsEmpty();
+ }
+ if (mSelection.isSome()) {
+ if (aOffset == mSelection->mAnchor) {
+ NS_WARNING_ASSERTION(
+ !mSelection->mAnchorCharRects[eNextCharRect].IsEmpty(),
+ "empty rect");
+ aUnionTextRect = mSelection->mAnchorCharRects[eNextCharRect];
+ return !aUnionTextRect.IsEmpty();
+ }
+ if (mSelection->mAnchor && aOffset == mSelection->mAnchor - 1) {
+ NS_WARNING_ASSERTION(
+ !mSelection->mAnchorCharRects[ePrevCharRect].IsEmpty(),
+ "empty rect");
+ aUnionTextRect = mSelection->mAnchorCharRects[ePrevCharRect];
+ return !aUnionTextRect.IsEmpty();
+ }
+ if (aOffset == mSelection->mFocus) {
+ NS_WARNING_ASSERTION(
+ !mSelection->mFocusCharRects[eNextCharRect].IsEmpty(),
+ "empty rect");
+ aUnionTextRect = mSelection->mFocusCharRects[eNextCharRect];
+ return !aUnionTextRect.IsEmpty();
+ }
+ if (mSelection->mFocus && aOffset == mSelection->mFocus - 1) {
+ NS_WARNING_ASSERTION(
+ !mSelection->mFocusCharRects[ePrevCharRect].IsEmpty(),
+ "empty rect");
+ aUnionTextRect = mSelection->mFocusCharRects[ePrevCharRect];
+ return !aUnionTextRect.IsEmpty();
+ }
+ }
+ }
+
+ // Even if some text rects are not cached of the queried range,
+ // we should return union rect when the first character's rect is cached
+ // since the first character rect is important and the others are not so
+ // in most cases.
+
+ if (!aOffset && mSelection.isSome() && aOffset != mSelection->mAnchor &&
+ aOffset != mSelection->mFocus &&
+ (mTextRectArray.isNothing() ||
+ !mTextRectArray->IsOffsetInRange(aOffset)) &&
+ (mLastCommitStringTextRectArray.isNothing() ||
+ !mLastCommitStringTextRectArray->IsOffsetInRange(aOffset))) {
+ // The first character rect isn't cached.
+ return false;
+ }
+
+ // Use mLastCommitStringTextRectArray only when it overlaps with aOffset
+ // even if aROundToExistingOffset is true for avoiding flickerling IME UI.
+ // See the last comment in GetTextRect() for the detail.
+ if (mLastCommitStringTextRectArray.isSome() &&
+ mLastCommitStringTextRectArray->IsOverlappingWith(aOffset, aLength)) {
+ aUnionTextRect =
+ mLastCommitStringTextRectArray->GetUnionRectAsFarAsPossible(
+ aOffset, aLength, aRoundToExistingOffset);
+ } else {
+ aUnionTextRect.SetEmpty();
+ }
+
+ if (mTextRectArray.isSome() &&
+ ((aRoundToExistingOffset && mTextRectArray->HasRects()) ||
+ mTextRectArray->IsOverlappingWith(aOffset, aLength))) {
+ aUnionTextRect =
+ aUnionTextRect.Union(mTextRectArray->GetUnionRectAsFarAsPossible(
+ aOffset, aLength, aRoundToExistingOffset));
+ }
+
+ if (!aOffset) {
+ aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
+ }
+ if (mSelection.isSome()) {
+ if (aOffset <= mSelection->mAnchor &&
+ mSelection->mAnchor < endOffset.value()) {
+ aUnionTextRect =
+ aUnionTextRect.Union(mSelection->mAnchorCharRects[eNextCharRect]);
+ }
+ if (mSelection->mAnchor && aOffset <= mSelection->mAnchor - 1 &&
+ mSelection->mAnchor - 1 < endOffset.value()) {
+ aUnionTextRect =
+ aUnionTextRect.Union(mSelection->mAnchorCharRects[ePrevCharRect]);
+ }
+ if (aOffset <= mSelection->mFocus &&
+ mSelection->mFocus < endOffset.value()) {
+ aUnionTextRect =
+ aUnionTextRect.Union(mSelection->mFocusCharRects[eNextCharRect]);
+ }
+ if (mSelection->mFocus && aOffset <= mSelection->mFocus - 1 &&
+ mSelection->mFocus - 1 < endOffset.value()) {
+ aUnionTextRect =
+ aUnionTextRect.Union(mSelection->mFocusCharRects[ePrevCharRect]);
+ }
+ }
+
+ return !aUnionTextRect.IsEmpty();
+}
+
+bool ContentCacheInParent::GetCaretRect(uint32_t aOffset,
+ bool aRoundToExistingOffset,
+ LayoutDeviceIntRect& aCaretRect) const {
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p GetCaretRect(aOffset=%u, aRoundToExistingOffset=%s), "
+ "mCaret=%s, mTextRectArray=%s, mSelection=%s, mFirstCharRect=%s",
+ this, aOffset, GetBoolName(aRoundToExistingOffset),
+ ToString(mCaret).c_str(), ToString(mTextRectArray).c_str(),
+ ToString(mSelection).c_str(), ToString(mFirstCharRect).c_str()));
+
+ if (mCaret.isSome() && mCaret->mOffset == aOffset) {
+ aCaretRect = mCaret->mRect;
+ return true;
+ }
+
+ // Guess caret rect from the text rect if it's stored.
+ if (!GetTextRect(aOffset, aRoundToExistingOffset, aCaretRect)) {
+ // There might be previous character rect in the cache. If so, we can
+ // guess the caret rect with it.
+ if (!aOffset ||
+ !GetTextRect(aOffset - 1, aRoundToExistingOffset, aCaretRect)) {
+ aCaretRect.SetEmpty();
+ return false;
+ }
+
+ if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
+ aCaretRect.MoveToY(aCaretRect.YMost());
+ } else {
+ // XXX bidi-unaware.
+ aCaretRect.MoveToX(aCaretRect.XMost());
+ }
+ }
+
+ // XXX This is not bidi aware because we don't cache each character's
+ // direction. However, this is usually used by IME, so, assuming the
+ // character is in LRT context must not cause any problem.
+ if (mSelection.isSome() && mSelection->mWritingMode.IsVertical()) {
+ aCaretRect.SetHeight(mCaret.isSome() ? mCaret->mRect.Height() : 1);
+ } else {
+ aCaretRect.SetWidth(mCaret.isSome() ? mCaret->mRect.Width() : 1);
+ }
+ return true;
+}
+
+bool ContentCacheInParent::OnCompositionEvent(
+ const WidgetCompositionEvent& aEvent) {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Info,
+ ("0x%p OnCompositionEvent(aEvent={ "
+ "mMessage=%s, mData=\"%s\" (Length()=%u), mRanges->Length()=%zu }), "
+ "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
+ "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
+ "mIsChildIgnoringCompositionEvents=%s, mCommitStringByRequest=0x%p",
+ this, ToChar(aEvent.mMessage),
+ PrintStringDetail(aEvent.mData,
+ PrintStringDetail::kMaxLengthForCompositionString)
+ .get(),
+ aEvent.mData.Length(), aEvent.mRanges ? aEvent.mRanges->Length() : 0,
+ mPendingEventsNeedingAck, GetBoolName(mWidgetHasComposition),
+ mPendingCompositionCount, mPendingCommitCount,
+ GetBoolName(mIsChildIgnoringCompositionEvents), mCommitStringByRequest));
+
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mDispatchedEventMessages.AppendElement(aEvent.mMessage);
+#endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+
+ // We must be able to simulate the selection because
+ // we might not receive selection updates in time
+ if (!mWidgetHasComposition) {
+ if (mCompositionStartInChild.isSome()) {
+ // If there is pending composition in the remote process, let's use
+ // its start offset temporarily because this stores a lot of information
+ // around it and the user must look around there, so, showing some UI
+ // around it must make sense.
+ mCompositionStart = mCompositionStartInChild;
+ } else {
+ mCompositionStart =
+ Some(mSelection.isSome() ? mSelection->StartOffset() : 0);
+ }
+ MOZ_ASSERT(aEvent.mMessage == eCompositionStart);
+ MOZ_RELEASE_ASSERT(mPendingCompositionCount < UINT8_MAX);
+ mPendingCompositionCount++;
+ }
+
+ mWidgetHasComposition = !aEvent.CausesDOMCompositionEndEvent();
+
+ if (!mWidgetHasComposition) {
+ // mCompositionStart will be reset when commit event is completely handled
+ // in the remote process.
+ if (mPendingCompositionCount == 1) {
+ mPendingCommitLength = aEvent.mData.Length();
+ }
+ mPendingCommitCount++;
+ } else if (aEvent.mMessage != eCompositionStart) {
+ mCompositionString = aEvent.mData;
+ }
+
+ // During REQUEST_TO_COMMIT_COMPOSITION or REQUEST_TO_CANCEL_COMPOSITION,
+ // widget usually sends a eCompositionChange and/or eCompositionCommit event
+ // to finalize or clear the composition, respectively. In this time,
+ // we need to intercept all composition events here and pass the commit
+ // string for returning to the remote process as a result of
+ // RequestIMEToCommitComposition(). Then, eCommitComposition event will
+ // be dispatched with the committed string in the remote process internally.
+ if (mCommitStringByRequest) {
+ if (aEvent.mMessage == eCompositionCommitAsIs) {
+ *mCommitStringByRequest = mCompositionString;
+ } else {
+ MOZ_ASSERT(aEvent.mMessage == eCompositionChange ||
+ aEvent.mMessage == eCompositionCommit);
+ *mCommitStringByRequest = aEvent.mData;
+ }
+ // We need to wait eCompositionCommitRequestHandled from the remote process
+ // in this case. Therefore, mPendingEventsNeedingAck needs to be
+ // incremented here. Additionally, we stop sending eCompositionCommit(AsIs)
+ // event. Therefore, we need to decrement mPendingCommitCount which has
+ // been incremented above.
+ if (!mWidgetHasComposition) {
+ mPendingEventsNeedingAck++;
+ MOZ_DIAGNOSTIC_ASSERT(mPendingCommitCount);
+ if (mPendingCommitCount) {
+ mPendingCommitCount--;
+ }
+ }
+ return false;
+ }
+
+ mPendingEventsNeedingAck++;
+ return true;
+}
+
+void ContentCacheInParent::OnSelectionEvent(
+ const WidgetSelectionEvent& aSelectionEvent) {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Info,
+ ("0x%p OnSelectionEvent(aEvent={ "
+ "mMessage=%s, mOffset=%u, mLength=%u, mReversed=%s, "
+ "mExpandToClusterBoundary=%s, mUseNativeLineBreak=%s }), "
+ "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
+ "mPendingCompositionCount=%" PRIu8 ", mPendingCommitCount=%" PRIu8 ", "
+ "mIsChildIgnoringCompositionEvents=%s",
+ this, ToChar(aSelectionEvent.mMessage), aSelectionEvent.mOffset,
+ aSelectionEvent.mLength, GetBoolName(aSelectionEvent.mReversed),
+ GetBoolName(aSelectionEvent.mExpandToClusterBoundary),
+ GetBoolName(aSelectionEvent.mUseNativeLineBreak),
+ mPendingEventsNeedingAck, GetBoolName(mWidgetHasComposition),
+ mPendingCompositionCount, mPendingCommitCount,
+ GetBoolName(mIsChildIgnoringCompositionEvents)));
+
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mDispatchedEventMessages.AppendElement(aSelectionEvent.mMessage);
+#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
+
+ mPendingEventsNeedingAck++;
+}
+
+void ContentCacheInParent::OnEventNeedingAckHandled(nsIWidget* aWidget,
+ EventMessage aMessage) {
+ // This is called when the child process receives WidgetCompositionEvent or
+ // WidgetSelectionEvent.
+
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Info,
+ ("0x%p OnEventNeedingAckHandled(aWidget=0x%p, "
+ "aMessage=%s), mPendingEventsNeedingAck=%u, "
+ "mWidgetHasComposition=%s, mPendingCompositionCount=%" PRIu8 ", "
+ "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s",
+ this, aWidget, ToChar(aMessage), mPendingEventsNeedingAck,
+ GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
+ mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents)));
+
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mReceivedEventMessages.AppendElement(aMessage);
+#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+
+ bool isCommittedInChild =
+ // Commit requester in the remote process has committed the composition.
+ aMessage == eCompositionCommitRequestHandled ||
+ // The commit event has been handled normally in the remote process.
+ (!mIsChildIgnoringCompositionEvents &&
+ WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage));
+
+ if (isCommittedInChild) {
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ if (mPendingCompositionCount == 1) {
+ RemoveUnnecessaryEventMessageLog();
+ }
+#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+
+ if (NS_WARN_IF(!mPendingCompositionCount)) {
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ nsPrintfCString info(
+ "\nThere is no pending composition but received %s "
+ "message from the remote child\n\n",
+ ToChar(aMessage));
+ AppendEventMessageLog(info);
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ MOZ_DIAGNOSTIC_ASSERT(
+ false, "No pending composition but received unexpected commit event");
+#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+
+ // Prevent odd behavior in release channel.
+ mPendingCompositionCount = 1;
+ }
+
+ mPendingCompositionCount--;
+
+ // Forget composition string only when the latest composition string is
+ // handled in the remote process because if there is 2 or more pending
+ // composition, this value shouldn't be referred.
+ if (!mPendingCompositionCount) {
+ mCompositionString.Truncate();
+ }
+
+ // Forget pending commit string length if it's handled in the remote
+ // process. Note that this doesn't care too old composition's commit
+ // string because in such case, we cannot return proper information
+ // to IME synchornously.
+ mPendingCommitLength = 0;
+ }
+
+ if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage)) {
+ // After the remote process receives eCompositionCommit(AsIs) event,
+ // it'll restart to handle composition events.
+ mIsChildIgnoringCompositionEvents = false;
+
+ if (NS_WARN_IF(!mPendingCommitCount)) {
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ nsPrintfCString info(
+ "\nThere is no pending comment events but received "
+ "%s message from the remote child\n\n",
+ ToChar(aMessage));
+ AppendEventMessageLog(info);
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ MOZ_DIAGNOSTIC_ASSERT(
+ false,
+ "No pending commit events but received unexpected commit event");
+#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+
+ // Prevent odd behavior in release channel.
+ mPendingCommitCount = 1;
+ }
+
+ mPendingCommitCount--;
+ } else if (aMessage == eCompositionCommitRequestHandled &&
+ mPendingCommitCount) {
+ // If the remote process commits composition synchronously after
+ // requesting commit composition and we've already sent commit composition,
+ // it starts to ignore following composition events until receiving
+ // eCompositionStart event.
+ mIsChildIgnoringCompositionEvents = true;
+ }
+
+ // If neither widget (i.e., IME) nor the remote process has composition,
+ // now, we can forget composition string informations.
+ if (!mWidgetHasComposition && !mPendingCompositionCount &&
+ !mPendingCommitCount) {
+ mCompositionStart.reset();
+ }
+
+ if (NS_WARN_IF(!mPendingEventsNeedingAck)) {
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ nsPrintfCString info(
+ "\nThere is no pending events but received %s "
+ "message from the remote child\n\n",
+ ToChar(aMessage));
+ AppendEventMessageLog(info);
+ CrashReporter::AppendAppNotesToCrashReport(info);
+#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ MOZ_DIAGNOSTIC_ASSERT(
+ false, "No pending event message but received unexpected event");
+ mPendingEventsNeedingAck = 1;
+ }
+ if (--mPendingEventsNeedingAck) {
+ return;
+ }
+
+ FlushPendingNotifications(aWidget);
+}
+
+bool ContentCacheInParent::RequestIMEToCommitComposition(
+ nsIWidget* aWidget, bool aCancel, nsAString& aCommittedString) {
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Info,
+ ("0x%p RequestToCommitComposition(aWidget=%p, "
+ "aCancel=%s), mPendingCompositionCount=%" PRIu8 ", "
+ "mPendingCommitCount=%" PRIu8 ", mIsChildIgnoringCompositionEvents=%s, "
+ "IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)=%s, "
+ "mWidgetHasComposition=%s, mCommitStringByRequest=%p",
+ this, aWidget, GetBoolName(aCancel), mPendingCompositionCount,
+ mPendingCommitCount, GetBoolName(mIsChildIgnoringCompositionEvents),
+ GetBoolName(
+ IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)),
+ GetBoolName(mWidgetHasComposition), mCommitStringByRequest));
+
+ MOZ_ASSERT(!mCommitStringByRequest);
+
+ // If there are 2 or more pending compositions, we already sent
+ // eCompositionCommit(AsIs) to the remote process. So, this request is
+ // too late for IME. The remote process should wait following
+ // composition events for cleaning up TextComposition and handle the
+ // request as it's handled asynchronously.
+ if (mPendingCompositionCount > 1) {
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mRequestIMEToCommitCompositionResults.AppendElement(
+ RequestIMEToCommitCompositionResult::eToOldCompositionReceived);
+#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ return false;
+ }
+
+ // If there is no pending composition, we may have already sent
+ // eCompositionCommit(AsIs) event for the active composition. If so, the
+ // remote process will receive composition events which causes cleaning up
+ // TextComposition. So, this shouldn't do nothing and TextComposition
+ // should handle the request as it's handled asynchronously.
+ // XXX Perhaps, this is wrong because TextComposition in child process
+ // may commit the composition with current composition string in the
+ // remote process. I.e., it may be different from actual commit string
+ // which user typed. So, perhaps, we should return true and the commit
+ // string.
+ if (mPendingCommitCount) {
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mRequestIMEToCommitCompositionResults.AppendElement(
+ RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived);
+#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ return false;
+ }
+
+ // If BrowserParent which has IME focus was already changed to different one,
+ // the request shouldn't be sent to IME because it's too late.
+ if (!IMEStateManager::DoesBrowserParentHaveIMEFocus(&mBrowserParent)) {
+ // Use the latest composition string which may not be handled in the
+ // remote process for avoiding data loss.
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mRequestIMEToCommitCompositionResults.AppendElement(
+ RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur);
+#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ aCommittedString = mCompositionString;
+ // After we return true from here, i.e., without actually requesting IME
+ // to commit composition, we will receive eCompositionCommitRequestHandled
+ // pseudo event message from the remote process. So, we need to increment
+ // mPendingEventsNeedingAck here.
+ mPendingEventsNeedingAck++;
+ return true;
+ }
+
+ RefPtr<TextComposition> composition =
+ IMEStateManager::GetTextCompositionFor(aWidget);
+ if (NS_WARN_IF(!composition)) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Warning,
+ (" 0x%p RequestToCommitComposition(), "
+ "does nothing due to no composition",
+ this));
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mRequestIMEToCommitCompositionResults.AppendElement(
+ RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition);
+#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ return false;
+ }
+
+ mCommitStringByRequest = &aCommittedString;
+
+ // Request commit or cancel composition with TextComposition because we may
+ // have already requested to commit or cancel the composition or we may
+ // have already received eCompositionCommit(AsIs) event. Those status are
+ // managed by composition. So, if we don't request commit composition,
+ // we should do nothing with native IME here.
+ composition->RequestToCommit(aWidget, aCancel);
+
+ mCommitStringByRequest = nullptr;
+
+ MOZ_LOG(
+ sContentCacheLog, LogLevel::Info,
+ (" 0x%p RequestToCommitComposition(), "
+ "mWidgetHasComposition=%s, the composition %s committed synchronously",
+ this, GetBoolName(mWidgetHasComposition),
+ composition->Destroyed() ? "WAS" : "has NOT been"));
+
+ if (!composition->Destroyed()) {
+ // When the composition isn't committed synchronously, the remote process's
+ // TextComposition instance will synthesize commit events and wait to
+ // receive delayed composition events. When TextComposition instances both
+ // in this process and the remote process will be destroyed when delayed
+ // composition events received. TextComposition instance in the parent
+ // process will dispatch following composition events and be destroyed
+ // normally. On the other hand, TextComposition instance in the remote
+ // process won't dispatch following composition events and will be
+ // destroyed by IMEStateManager::DispatchCompositionEvent().
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mRequestIMEToCommitCompositionResults.AppendElement(
+ RequestIMEToCommitCompositionResult::eHandledAsynchronously);
+#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ return false;
+ }
+
+ // When the composition is committed synchronously, the commit string will be
+ // returned to the remote process. Then, PuppetWidget will dispatch
+ // eCompositionCommit event with the returned commit string (i.e., the value
+ // is aCommittedString of this method) and that causes destroying
+ // TextComposition instance in the remote process (Note that TextComposition
+ // instance in this process was already destroyed).
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mRequestIMEToCommitCompositionResults.AppendElement(
+ RequestIMEToCommitCompositionResult::eHandledSynchronously);
+#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ return true;
+}
+
+void ContentCacheInParent::MaybeNotifyIME(
+ nsIWidget* aWidget, const IMENotification& aNotification) {
+ if (!mPendingEventsNeedingAck) {
+ IMEStateManager::NotifyIME(aNotification, aWidget, &mBrowserParent);
+ return;
+ }
+
+ switch (aNotification.mMessage) {
+ case NOTIFY_IME_OF_SELECTION_CHANGE:
+ mPendingSelectionChange.MergeWith(aNotification);
+ break;
+ case NOTIFY_IME_OF_TEXT_CHANGE:
+ mPendingTextChange.MergeWith(aNotification);
+ break;
+ case NOTIFY_IME_OF_POSITION_CHANGE:
+ mPendingLayoutChange.MergeWith(aNotification);
+ break;
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
+ mPendingCompositionUpdate.MergeWith(aNotification);
+ break;
+ default:
+ MOZ_CRASH("Unsupported notification");
+ break;
+ }
+}
+
+void ContentCacheInParent::FlushPendingNotifications(nsIWidget* aWidget) {
+ MOZ_ASSERT(!mPendingEventsNeedingAck);
+
+ // If the BrowserParent's widget has already gone, this can do nothing since
+ // widget is necessary to notify IME of something.
+ if (!aWidget) {
+ return;
+ }
+
+ // New notifications which are notified during flushing pending notifications
+ // should be merged again.
+ mPendingEventsNeedingAck++;
+
+ nsCOMPtr<nsIWidget> widget = aWidget;
+
+ // First, text change notification should be sent because selection change
+ // notification notifies IME of current selection range in the latest content.
+ // So, IME may need the latest content before that.
+ if (mPendingTextChange.HasNotification()) {
+ IMENotification notification(mPendingTextChange);
+ if (!widget->Destroyed()) {
+ mPendingTextChange.Clear();
+ IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
+ }
+ }
+
+ if (mPendingSelectionChange.HasNotification()) {
+ IMENotification notification(mPendingSelectionChange);
+ if (!widget->Destroyed()) {
+ mPendingSelectionChange.Clear();
+ IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
+ }
+ }
+
+ // Layout change notification should be notified after selection change
+ // notification because IME may want to query position of new caret position.
+ if (mPendingLayoutChange.HasNotification()) {
+ IMENotification notification(mPendingLayoutChange);
+ if (!widget->Destroyed()) {
+ mPendingLayoutChange.Clear();
+ IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
+ }
+ }
+
+ // Finally, send composition update notification because it notifies IME of
+ // finishing handling whole sending events.
+ if (mPendingCompositionUpdate.HasNotification()) {
+ IMENotification notification(mPendingCompositionUpdate);
+ if (!widget->Destroyed()) {
+ mPendingCompositionUpdate.Clear();
+ IMEStateManager::NotifyIME(notification, widget, &mBrowserParent);
+ }
+ }
+
+ if (!--mPendingEventsNeedingAck && !widget->Destroyed() &&
+ (mPendingTextChange.HasNotification() ||
+ mPendingSelectionChange.HasNotification() ||
+ mPendingLayoutChange.HasNotification() ||
+ mPendingCompositionUpdate.HasNotification())) {
+ FlushPendingNotifications(widget);
+ }
+}
+
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+
+void ContentCacheInParent::RemoveUnnecessaryEventMessageLog() {
+ bool foundLastCompositionStart = false;
+ for (size_t i = mDispatchedEventMessages.Length(); i > 1; i--) {
+ if (mDispatchedEventMessages[i - 1] != eCompositionStart) {
+ continue;
+ }
+ if (!foundLastCompositionStart) {
+ // Find previous eCompositionStart of the latest eCompositionStart.
+ foundLastCompositionStart = true;
+ continue;
+ }
+ // Remove the messages before the last 2 sets of composition events.
+ mDispatchedEventMessages.RemoveElementsAt(0, i - 1);
+ break;
+ }
+ uint32_t numberOfCompositionCommitRequestHandled = 0;
+ foundLastCompositionStart = false;
+ for (size_t i = mReceivedEventMessages.Length(); i > 1; i--) {
+ if (mReceivedEventMessages[i - 1] == eCompositionCommitRequestHandled) {
+ numberOfCompositionCommitRequestHandled++;
+ }
+ if (mReceivedEventMessages[i - 1] != eCompositionStart) {
+ continue;
+ }
+ if (!foundLastCompositionStart) {
+ // Find previous eCompositionStart of the latest eCompositionStart.
+ foundLastCompositionStart = true;
+ continue;
+ }
+ // Remove the messages before the last 2 sets of composition events.
+ mReceivedEventMessages.RemoveElementsAt(0, i - 1);
+ break;
+ }
+
+ if (!numberOfCompositionCommitRequestHandled) {
+ // If there is no eCompositionCommitRequestHandled in
+ // mReceivedEventMessages, we don't need to store log of
+ // RequestIMEToCommmitComposition().
+ mRequestIMEToCommitCompositionResults.Clear();
+ } else {
+ // We need to keep all reason of eCompositionCommitRequestHandled, which
+ // is sent when mRequestIMEToCommitComposition() returns true.
+ // So, we can discard older log than the first
+ // eCompositionCommitRequestHandled in mReceivedEventMessages.
+ for (size_t i = mRequestIMEToCommitCompositionResults.Length(); i > 1;
+ i--) {
+ if (mRequestIMEToCommitCompositionResults[i - 1] ==
+ RequestIMEToCommitCompositionResult::
+ eReceivedAfterBrowserParentBlur ||
+ mRequestIMEToCommitCompositionResults[i - 1] ==
+ RequestIMEToCommitCompositionResult::eHandledSynchronously) {
+ --numberOfCompositionCommitRequestHandled;
+ if (!numberOfCompositionCommitRequestHandled) {
+ mRequestIMEToCommitCompositionResults.RemoveElementsAt(0, i - 1);
+ break;
+ }
+ }
+ }
+ }
+}
+
+void ContentCacheInParent::AppendEventMessageLog(nsACString& aLog) const {
+ aLog.AppendLiteral("Dispatched Event Message Log:\n");
+ for (EventMessage message : mDispatchedEventMessages) {
+ aLog.AppendLiteral(" ");
+ aLog.Append(ToChar(message));
+ aLog.AppendLiteral("\n");
+ }
+ aLog.AppendLiteral("\nReceived Event Message Log:\n");
+ for (EventMessage message : mReceivedEventMessages) {
+ aLog.AppendLiteral(" ");
+ aLog.Append(ToChar(message));
+ aLog.AppendLiteral("\n");
+ }
+ aLog.AppendLiteral("\nResult of RequestIMEToCommitComposition():\n");
+ for (RequestIMEToCommitCompositionResult result :
+ mRequestIMEToCommitCompositionResults) {
+ aLog.AppendLiteral(" ");
+ aLog.Append(ToReadableText(result));
+ aLog.AppendLiteral("\n");
+ }
+ aLog.AppendLiteral("\n");
+}
+
+#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+
+/*****************************************************************************
+ * mozilla::ContentCache::TextRectArray
+ *****************************************************************************/
+
+LayoutDeviceIntRect ContentCache::TextRectArray::GetRect(
+ uint32_t aOffset) const {
+ LayoutDeviceIntRect rect;
+ if (IsOffsetInRange(aOffset)) {
+ rect = mRects[aOffset - mStart];
+ }
+ return rect;
+}
+
+LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRect(
+ uint32_t aOffset, uint32_t aLength) const {
+ LayoutDeviceIntRect rect;
+ if (!IsRangeCompletelyInRange(aOffset, aLength)) {
+ return rect;
+ }
+ for (uint32_t i = 0; i < aLength; i++) {
+ rect = rect.Union(mRects[aOffset - mStart + i]);
+ }
+ return rect;
+}
+
+LayoutDeviceIntRect ContentCache::TextRectArray::GetUnionRectAsFarAsPossible(
+ uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset) const {
+ LayoutDeviceIntRect rect;
+ if (!HasRects() ||
+ (!aRoundToExistingOffset && !IsOverlappingWith(aOffset, aLength))) {
+ return rect;
+ }
+ uint32_t startOffset = std::max(aOffset, mStart);
+ if (aRoundToExistingOffset && startOffset >= EndOffset()) {
+ startOffset = EndOffset() - 1;
+ }
+ uint32_t endOffset = std::min(aOffset + aLength, EndOffset());
+ if (aRoundToExistingOffset && endOffset < mStart + 1) {
+ endOffset = mStart + 1;
+ }
+ if (NS_WARN_IF(endOffset < startOffset)) {
+ return rect;
+ }
+ for (uint32_t i = 0; i < endOffset - startOffset; i++) {
+ rect = rect.Union(mRects[startOffset - mStart + i]);
+ }
+ return rect;
+}
+
+} // namespace mozilla
diff --git a/widget/ContentCache.h b/widget/ContentCache.h
new file mode 100644
index 0000000000..1347cb9942
--- /dev/null
+++ b/widget/ContentCache.h
@@ -0,0 +1,531 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ContentCache_h
+#define mozilla_ContentCache_h
+
+#include <stdint.h>
+
+#include "mozilla/widget/IMEData.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ToString.h"
+#include "mozilla/WritingModes.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "Units.h"
+
+class nsIWidget;
+
+namespace mozilla {
+
+class ContentCacheInParent;
+
+namespace dom {
+class BrowserParent;
+} // namespace dom
+
+/**
+ * ContentCache stores various information of the child content.
+ * This class has members which are necessary both in parent process and
+ * content process.
+ */
+
+class ContentCache {
+ public:
+ typedef CopyableTArray<LayoutDeviceIntRect> RectArray;
+ typedef widget::IMENotification IMENotification;
+
+ ContentCache() = default;
+
+ protected:
+ // Whole text in the target
+ nsString mText;
+
+ // Start offset of the composition string.
+ Maybe<uint32_t> mCompositionStart;
+
+ enum { ePrevCharRect = 1, eNextCharRect = 0 };
+
+ struct Selection final {
+ // Following values are offset in "flat text".
+ uint32_t mAnchor;
+ uint32_t mFocus;
+
+ WritingMode mWritingMode;
+
+ // Character rects at previous and next character of mAnchor and mFocus.
+ // The reason why ContentCache needs to store each previous character of
+ // them is IME may query character rect of the last character of a line
+ // when caret is at the end of the line.
+ // Note that use ePrevCharRect and eNextCharRect for accessing each item.
+ LayoutDeviceIntRect mAnchorCharRects[2];
+ LayoutDeviceIntRect mFocusCharRects[2];
+
+ // Whole rect of selected text. This is empty if the selection is collapsed.
+ LayoutDeviceIntRect mRect;
+
+ explicit Selection(uint32_t aAnchorOffset, uint32_t aFocusOffset,
+ const WritingMode& aWritingMode)
+ : mAnchor(aAnchorOffset),
+ mFocus(aFocusOffset),
+ mWritingMode(aWritingMode) {}
+
+ void ClearRects() {
+ for (auto& rect : mAnchorCharRects) {
+ rect.SetEmpty();
+ }
+ for (auto& rect : mFocusCharRects) {
+ rect.SetEmpty();
+ }
+ mRect.SetEmpty();
+ }
+ bool HasRects() const {
+ for (auto& rect : mAnchorCharRects) {
+ if (!rect.IsEmpty()) {
+ return true;
+ }
+ }
+ for (auto& rect : mFocusCharRects) {
+ if (!rect.IsEmpty()) {
+ return true;
+ }
+ }
+ return !mRect.IsEmpty();
+ }
+
+ bool Collapsed() const { return mFocus == mAnchor; }
+ bool Reversed() const { return mFocus < mAnchor; }
+ uint32_t StartOffset() const { return Reversed() ? mFocus : mAnchor; }
+ uint32_t EndOffset() const { return Reversed() ? mAnchor : mFocus; }
+ uint32_t Length() const {
+ return Reversed() ? mAnchor - mFocus : mFocus - mAnchor;
+ }
+ LayoutDeviceIntRect StartCharRect() const {
+ return Reversed() ? mFocusCharRects[eNextCharRect]
+ : mAnchorCharRects[eNextCharRect];
+ }
+ LayoutDeviceIntRect EndCharRect() const {
+ return Reversed() ? mAnchorCharRects[eNextCharRect]
+ : mFocusCharRects[eNextCharRect];
+ }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const Selection& aSelection) {
+ aStream << "{ mAnchor=" << aSelection.mAnchor
+ << ", mFocus=" << aSelection.mFocus
+ << ", mWritingMode=" << ToString(aSelection.mWritingMode).c_str();
+ if (aSelection.HasRects()) {
+ if (aSelection.mAnchor > 0) {
+ aStream << ", mAnchorCharRects[ePrevCharRect]="
+ << aSelection.mAnchorCharRects[ContentCache::ePrevCharRect];
+ }
+ aStream << ", mAnchorCharRects[eNextCharRect]="
+ << aSelection.mAnchorCharRects[ContentCache::eNextCharRect];
+ if (aSelection.mFocus > 0) {
+ aStream << ", mFocusCharRects[ePrevCharRect]="
+ << aSelection.mFocusCharRects[ContentCache::ePrevCharRect];
+ }
+ aStream << ", mFocusCharRects[eNextCharRect]="
+ << aSelection.mFocusCharRects[ContentCache::eNextCharRect]
+ << ", mRect=" << aSelection.mRect;
+ }
+ aStream << ", Reversed()=" << (aSelection.Reversed() ? "true" : "false")
+ << ", StartOffset()=" << aSelection.StartOffset()
+ << ", EndOffset()=" << aSelection.EndOffset()
+ << ", Collapsed()=" << (aSelection.Collapsed() ? "true" : "false")
+ << ", Length()=" << aSelection.Length() << " }";
+ return aStream;
+ }
+
+ private:
+ Selection() = default;
+
+ friend struct IPC::ParamTraits<ContentCache::Selection>;
+ friend struct IPC::ParamTraits<Maybe<ContentCache::Selection>>;
+ };
+ Maybe<Selection> mSelection;
+
+ bool IsSelectionValid() const {
+ return mSelection.isSome() && mSelection->EndOffset() <= mText.Length();
+ }
+
+ // Stores first char rect because Yosemite's Japanese IME sometimes tries
+ // to query it. If there is no text, this is caret rect.
+ LayoutDeviceIntRect mFirstCharRect;
+
+ struct Caret final {
+ uint32_t mOffset;
+ LayoutDeviceIntRect mRect;
+
+ explicit Caret(uint32_t aOffset, LayoutDeviceIntRect aCaretRect)
+ : mOffset(aOffset), mRect(aCaretRect) {}
+
+ uint32_t Offset() const { return mOffset; }
+ bool HasRect() const { return !mRect.IsEmpty(); }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const Caret& aCaret) {
+ aStream << "{ mOffset=" << aCaret.mOffset;
+ if (aCaret.HasRect()) {
+ aStream << ", mRect=" << aCaret.mRect;
+ }
+ return aStream << " }";
+ }
+
+ private:
+ Caret() = default;
+
+ friend struct IPC::ParamTraits<ContentCache::Caret>;
+ friend struct IPC::ParamTraits<Maybe<ContentCache::Caret>>;
+ };
+ Maybe<Caret> mCaret;
+
+ struct TextRectArray final {
+ uint32_t mStart;
+ RectArray mRects;
+
+ explicit TextRectArray(uint32_t aStartOffset) : mStart(aStartOffset) {}
+
+ bool HasRects() const { return Length() > 0; }
+ uint32_t StartOffset() const { return mStart; }
+ uint32_t EndOffset() const {
+ CheckedInt<uint32_t> endOffset =
+ CheckedInt<uint32_t>(mStart) + mRects.Length();
+ return endOffset.isValid() ? endOffset.value() : UINT32_MAX;
+ }
+ uint32_t Length() const { return EndOffset() - mStart; }
+ bool IsOffsetInRange(uint32_t aOffset) const {
+ return StartOffset() <= aOffset && aOffset < EndOffset();
+ }
+ bool IsRangeCompletelyInRange(uint32_t aOffset, uint32_t aLength) const {
+ CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
+ if (NS_WARN_IF(!endOffset.isValid())) {
+ return false;
+ }
+ return IsOffsetInRange(aOffset) && aOffset + aLength <= EndOffset();
+ }
+ bool IsOverlappingWith(uint32_t aOffset, uint32_t aLength) const {
+ if (!HasRects() || aOffset == UINT32_MAX || !aLength) {
+ return false;
+ }
+ CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(aOffset) + aLength;
+ if (NS_WARN_IF(!endOffset.isValid())) {
+ return false;
+ }
+ return aOffset < EndOffset() && endOffset.value() > mStart;
+ }
+ LayoutDeviceIntRect GetRect(uint32_t aOffset) const;
+ LayoutDeviceIntRect GetUnionRect(uint32_t aOffset, uint32_t aLength) const;
+ LayoutDeviceIntRect GetUnionRectAsFarAsPossible(
+ uint32_t aOffset, uint32_t aLength, bool aRoundToExistingOffset) const;
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const TextRectArray& aTextRectArray) {
+ aStream << "{ mStart=" << aTextRectArray.mStart
+ << ", mRects={ Length()=" << aTextRectArray.Length();
+ if (aTextRectArray.HasRects()) {
+ aStream << ", Elements()=[ ";
+ static constexpr uint32_t kMaxPrintRects = 4;
+ const uint32_t kFirstHalf = aTextRectArray.Length() <= kMaxPrintRects
+ ? UINT32_MAX
+ : (kMaxPrintRects + 1) / 2;
+ const uint32_t kSecondHalf =
+ aTextRectArray.Length() <= kMaxPrintRects ? 0 : kMaxPrintRects / 2;
+ for (uint32_t i = 0; i < aTextRectArray.Length(); i++) {
+ if (i > 0) {
+ aStream << ", ";
+ }
+ aStream << ToString(aTextRectArray.mRects[i]).c_str();
+ if (i + 1 == kFirstHalf) {
+ aStream << " ...";
+ i = aTextRectArray.Length() - kSecondHalf - 1;
+ }
+ }
+ }
+ return aStream << " ] } }";
+ }
+
+ private:
+ TextRectArray() = default;
+
+ friend struct IPC::ParamTraits<ContentCache::TextRectArray>;
+ friend struct IPC::ParamTraits<Maybe<ContentCache::TextRectArray>>;
+ };
+ Maybe<TextRectArray> mTextRectArray;
+ Maybe<TextRectArray> mLastCommitStringTextRectArray;
+
+ LayoutDeviceIntRect mEditorRect;
+
+ friend class ContentCacheInParent;
+ friend struct IPC::ParamTraits<ContentCache>;
+ friend struct IPC::ParamTraits<ContentCache::Selection>;
+ friend struct IPC::ParamTraits<ContentCache::Caret>;
+ friend struct IPC::ParamTraits<ContentCache::TextRectArray>;
+ friend std::ostream& operator<<(
+ std::ostream& aStream,
+ const Selection& aSelection); // For e(Prev|Next)CharRect
+};
+
+class ContentCacheInChild final : public ContentCache {
+ public:
+ ContentCacheInChild() = default;
+
+ /**
+ * Called when composition event will be dispatched in this process from
+ * PuppetWidget.
+ */
+ void OnCompositionEvent(const WidgetCompositionEvent& aCompositionEvent);
+
+ /**
+ * When IME loses focus, this should be called and making this forget the
+ * content for reducing footprint.
+ */
+ void Clear();
+
+ /**
+ * Cache*() retrieves the latest content information and store them.
+ * Be aware, CacheSelection() calls CacheTextRects(), and also CacheText()
+ * calls CacheSelection(). So, related data is also retrieved automatically.
+ */
+ bool CacheEditorRect(nsIWidget* aWidget,
+ const IMENotification* aNotification = nullptr);
+ bool CacheSelection(nsIWidget* aWidget,
+ const IMENotification* aNotification = nullptr);
+ bool CacheText(nsIWidget* aWidget,
+ const IMENotification* aNotification = nullptr);
+
+ bool CacheAll(nsIWidget* aWidget,
+ const IMENotification* aNotification = nullptr);
+
+ /**
+ * SetSelection() modifies selection with specified raw data. And also this
+ * tries to retrieve text rects too.
+ */
+ void SetSelection(nsIWidget* aWidget, uint32_t aStartOffset, uint32_t aLength,
+ bool aReversed, const WritingMode& aWritingMode);
+
+ private:
+ bool QueryCharRect(nsIWidget* aWidget, uint32_t aOffset,
+ LayoutDeviceIntRect& aCharRect) const;
+ bool QueryCharRectArray(nsIWidget* aWidget, uint32_t aOffset,
+ uint32_t aLength, RectArray& aCharRectArray) const;
+ bool CacheCaret(nsIWidget* aWidget,
+ const IMENotification* aNotification = nullptr);
+ bool CacheTextRects(nsIWidget* aWidget,
+ const IMENotification* aNotification = nullptr);
+
+ // Once composition is committed, all of the commit string may be composed
+ // again by Kakutei-Undo of Japanese IME. Therefore, we need to keep
+ // storing the last composition start to cache all character rects of the
+ // last commit string.
+ Maybe<OffsetAndData<uint32_t>> mLastCommit;
+};
+
+class ContentCacheInParent final : public ContentCache {
+ public:
+ explicit ContentCacheInParent(dom::BrowserParent& aBrowserParent);
+
+ /**
+ * AssignContent() is called when BrowserParent receives ContentCache from
+ * the content process. This doesn't copy composition information because
+ * it's managed by BrowserParent itself.
+ */
+ void AssignContent(const ContentCache& aOther, nsIWidget* aWidget,
+ const IMENotification* aNotification = nullptr);
+
+ /**
+ * HandleQueryContentEvent() sets content data to aEvent.mReply.
+ *
+ * For eQuerySelectedText, fail if the cache doesn't contain the whole
+ * selected range. (This shouldn't happen because PuppetWidget should have
+ * already sent the whole selection.)
+ *
+ * For eQueryTextContent, fail only if the cache doesn't overlap with
+ * the queried range. Note the difference from above. We use
+ * this behavior because a normal eQueryTextContent event is allowed to
+ * have out-of-bounds offsets, so that widget can request content without
+ * knowing the exact length of text. It's up to widget to handle cases when
+ * the returned offset/length are different from the queried offset/length.
+ *
+ * For eQueryTextRect, fail if cached offset/length aren't equals to input.
+ * Cocoa widget always queries selected offset, so it works on it.
+ *
+ * For eQueryCaretRect, fail if cached offset isn't equals to input
+ *
+ * For eQueryEditorRect, always success
+ */
+ bool HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
+ nsIWidget* aWidget) const;
+
+ /**
+ * OnCompositionEvent() should be called before sending composition string.
+ * This returns true if the event should be sent. Otherwise, false.
+ */
+ bool OnCompositionEvent(const WidgetCompositionEvent& aCompositionEvent);
+
+ /**
+ * OnSelectionEvent() should be called before sending selection event.
+ */
+ void OnSelectionEvent(const WidgetSelectionEvent& aSelectionEvent);
+
+ /**
+ * OnEventNeedingAckHandled() should be called after the child process
+ * handles a sent event which needs acknowledging.
+ *
+ * WARNING: This may send notifications to IME. That might cause destroying
+ * BrowserParent or aWidget. Therefore, the caller must not destroy
+ * this instance during a call of this method.
+ */
+ void OnEventNeedingAckHandled(nsIWidget* aWidget, EventMessage aMessage);
+
+ /**
+ * RequestIMEToCommitComposition() requests aWidget to commit or cancel
+ * composition. If it's handled synchronously, this returns true.
+ *
+ * @param aWidget The widget to be requested to commit or cancel
+ * the composition.
+ * @param aCancel When the caller tries to cancel the composition, true.
+ * Otherwise, i.e., tries to commit the composition, false.
+ * @param aCommittedString The committed string (i.e., the last data of
+ * dispatched composition events during requesting
+ * IME to commit composition.
+ * @return Whether the composition is actually committed
+ * synchronously.
+ */
+ bool RequestIMEToCommitComposition(nsIWidget* aWidget, bool aCancel,
+ nsAString& aCommittedString);
+
+ /**
+ * MaybeNotifyIME() may notify IME of the notification. If child process
+ * hasn't been handled all sending events yet, this stores the notification
+ * and flush it later.
+ */
+ void MaybeNotifyIME(nsIWidget* aWidget, const IMENotification& aNotification);
+
+ private:
+ IMENotification mPendingSelectionChange;
+ IMENotification mPendingTextChange;
+ IMENotification mPendingLayoutChange;
+ IMENotification mPendingCompositionUpdate;
+
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ // Log of event messages to be output to crash report.
+ nsTArray<EventMessage> mDispatchedEventMessages;
+ nsTArray<EventMessage> mReceivedEventMessages;
+ // Log of RequestIMEToCommitComposition() in the last 2 compositions.
+ enum class RequestIMEToCommitCompositionResult : uint8_t {
+ eToOldCompositionReceived,
+ eToCommittedCompositionReceived,
+ eReceivedAfterBrowserParentBlur,
+ eReceivedButNoTextComposition,
+ eHandledAsynchronously,
+ eHandledSynchronously,
+ };
+ const char* ToReadableText(
+ RequestIMEToCommitCompositionResult aResult) const {
+ switch (aResult) {
+ case RequestIMEToCommitCompositionResult::eToOldCompositionReceived:
+ return "Commit request is not handled because it's for "
+ "older composition";
+ case RequestIMEToCommitCompositionResult::eToCommittedCompositionReceived:
+ return "Commit request is not handled because BrowserParent has "
+ "already "
+ "sent commit event for the composition";
+ case RequestIMEToCommitCompositionResult::eReceivedAfterBrowserParentBlur:
+ return "Commit request is handled with stored composition string "
+ "because BrowserParent has already lost focus";
+ case RequestIMEToCommitCompositionResult::eReceivedButNoTextComposition:
+ return "Commit request is not handled because there is no "
+ "TextCompsition instance";
+ case RequestIMEToCommitCompositionResult::eHandledAsynchronously:
+ return "Commit request is handled but IME doesn't commit current "
+ "composition synchronously";
+ case RequestIMEToCommitCompositionResult::eHandledSynchronously:
+ return "Commit reqeust is handled synchronously";
+ default:
+ return "Unknown reason";
+ }
+ }
+ nsTArray<RequestIMEToCommitCompositionResult>
+ mRequestIMEToCommitCompositionResults;
+#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
+
+ // mBrowserParent is owner of the instance.
+ dom::BrowserParent& MOZ_NON_OWNING_REF mBrowserParent;
+ // mCompositionString is composition string which were sent to the remote
+ // process but not yet committed in the remote process.
+ nsString mCompositionString;
+ // This is not nullptr only while the instance is requesting IME to
+ // composition. Then, data value of dispatched composition events should
+ // be stored into the instance.
+ nsAString* mCommitStringByRequest;
+ // mPendingEventsNeedingAck is increased before sending a composition event or
+ // a selection event and decreased after they are received in the child
+ // process.
+ uint32_t mPendingEventsNeedingAck;
+ // mCompositionStartInChild stores current composition start offset in the
+ // remote process.
+ Maybe<uint32_t> mCompositionStartInChild;
+ // mPendingCommitLength is commit string length of the first pending
+ // composition. This is used by relative offset query events when querying
+ // new composition start offset.
+ // Note that when mPendingCompositionCount is not 0, i.e., there are 2 or
+ // more pending compositions, this cache won't be used because in such case,
+ // anyway ContentCacheInParent cannot return proper character rect.
+ uint32_t mPendingCommitLength;
+ // mPendingCompositionCount is number of compositions which started in widget
+ // but not yet handled in the child process.
+ uint8_t mPendingCompositionCount;
+ // mPendingCommitCount is number of eCompositionCommit(AsIs) events which
+ // were sent to the child process but not yet handled in it.
+ uint8_t mPendingCommitCount;
+ // mWidgetHasComposition is true when the widget in this process thinks that
+ // IME has composition. So, this is set to true when eCompositionStart is
+ // dispatched and set to false when eCompositionCommit(AsIs) is dispatched.
+ bool mWidgetHasComposition;
+ // mIsChildIgnoringCompositionEvents is set to true if the child process
+ // requests commit composition whose commit has already been sent to it.
+ // Then, set to false when the child process ignores the commit event.
+ bool mIsChildIgnoringCompositionEvents;
+
+ ContentCacheInParent() = delete;
+
+ /**
+ * When following methods' aRoundToExistingOffset is true, even if specified
+ * offset or range is out of bounds, the result is computed with the existing
+ * cache forcibly.
+ */
+ bool GetCaretRect(uint32_t aOffset, bool aRoundToExistingOffset,
+ LayoutDeviceIntRect& aCaretRect) const;
+ bool GetTextRect(uint32_t aOffset, bool aRoundToExistingOffset,
+ LayoutDeviceIntRect& aTextRect) const;
+ bool GetUnionTextRects(uint32_t aOffset, uint32_t aLength,
+ bool aRoundToExistingOffset,
+ LayoutDeviceIntRect& aUnionTextRect) const;
+
+ void FlushPendingNotifications(nsIWidget* aWidget);
+
+#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ /**
+ * Remove unnecessary messages from mDispatchedEventMessages and
+ * mReceivedEventMessages.
+ */
+ void RemoveUnnecessaryEventMessageLog();
+
+ /**
+ * Append event message log to aLog.
+ */
+ void AppendEventMessageLog(nsACString& aLog) const;
+#endif // #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ContentCache_h
diff --git a/widget/ContentEvents.h b/widget/ContentEvents.h
new file mode 100644
index 0000000000..aee229c823
--- /dev/null
+++ b/widget/ContentEvents.h
@@ -0,0 +1,302 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ContentEvents_h__
+#define mozilla_ContentEvents_h__
+
+#include <stdint.h>
+
+#include "mozilla/BasicEvents.h"
+#include "mozilla/dom/DataTransfer.h"
+#include "mozilla/dom/EventTarget.h"
+#include "nsCOMPtr.h"
+#include "nsRect.h"
+#include "nsString.h"
+
+class nsIContent;
+
+namespace mozilla {
+
+/******************************************************************************
+ * mozilla::InternalScrollPortEvent
+ ******************************************************************************/
+
+class InternalScrollPortEvent : public WidgetGUIEvent {
+ public:
+ virtual InternalScrollPortEvent* AsScrollPortEvent() override { return this; }
+
+ enum OrientType { eVertical, eHorizontal, eBoth };
+
+ InternalScrollPortEvent(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget)
+ : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, eScrollPortEventClass),
+ mOrient(eVertical) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eScrollPortEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ InternalScrollPortEvent* result =
+ new InternalScrollPortEvent(false, mMessage, nullptr);
+ result->AssignScrollPortEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ OrientType mOrient;
+
+ void AssignScrollPortEventData(const InternalScrollPortEvent& aEvent,
+ bool aCopyTargets) {
+ AssignGUIEventData(aEvent, aCopyTargets);
+
+ mOrient = aEvent.mOrient;
+ }
+};
+
+/******************************************************************************
+ * mozilla::InternalScrollPortEvent
+ ******************************************************************************/
+
+class InternalScrollAreaEvent : public WidgetGUIEvent {
+ public:
+ virtual InternalScrollAreaEvent* AsScrollAreaEvent() override { return this; }
+
+ InternalScrollAreaEvent(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget)
+ : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, eScrollAreaEventClass) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eScrollAreaEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ InternalScrollAreaEvent* result =
+ new InternalScrollAreaEvent(false, mMessage, nullptr);
+ result->AssignScrollAreaEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ nsRect mArea;
+
+ void AssignScrollAreaEventData(const InternalScrollAreaEvent& aEvent,
+ bool aCopyTargets) {
+ AssignGUIEventData(aEvent, aCopyTargets);
+
+ mArea = aEvent.mArea;
+ }
+};
+
+/******************************************************************************
+ * mozilla::InternalFormEvent
+ *
+ * We hold the originating form control for form submit and reset events.
+ * mOriginator is a weak pointer (does not hold a strong reference).
+ ******************************************************************************/
+
+class InternalFormEvent : public WidgetEvent {
+ public:
+ virtual InternalFormEvent* AsFormEvent() override { return this; }
+
+ InternalFormEvent(bool aIsTrusted, EventMessage aMessage)
+ : WidgetEvent(aIsTrusted, aMessage, eFormEventClass),
+ mOriginator(nullptr) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eFormEventClass,
+ "Duplicate() must be overridden by sub class");
+ InternalFormEvent* result = new InternalFormEvent(false, mMessage);
+ result->AssignFormEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ nsIContent* mOriginator;
+
+ void AssignFormEventData(const InternalFormEvent& aEvent, bool aCopyTargets) {
+ AssignEventData(aEvent, aCopyTargets);
+
+ // Don't copy mOriginator due to a weak pointer.
+ }
+};
+
+/******************************************************************************
+ * mozilla::InternalClipboardEvent
+ ******************************************************************************/
+
+class InternalClipboardEvent : public WidgetEvent {
+ public:
+ virtual InternalClipboardEvent* AsClipboardEvent() override { return this; }
+
+ InternalClipboardEvent(bool aIsTrusted, EventMessage aMessage)
+ : WidgetEvent(aIsTrusted, aMessage, eClipboardEventClass) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eClipboardEventClass,
+ "Duplicate() must be overridden by sub class");
+ InternalClipboardEvent* result =
+ new InternalClipboardEvent(false, mMessage);
+ result->AssignClipboardEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ nsCOMPtr<dom::DataTransfer> mClipboardData;
+
+ void AssignClipboardEventData(const InternalClipboardEvent& aEvent,
+ bool aCopyTargets) {
+ AssignEventData(aEvent, aCopyTargets);
+
+ mClipboardData = aEvent.mClipboardData;
+ }
+};
+
+/******************************************************************************
+ * mozilla::InternalFocusEvent
+ ******************************************************************************/
+
+class InternalFocusEvent : public InternalUIEvent {
+ public:
+ virtual InternalFocusEvent* AsFocusEvent() override { return this; }
+
+ InternalFocusEvent(bool aIsTrusted, EventMessage aMessage)
+ : InternalUIEvent(aIsTrusted, aMessage, eFocusEventClass),
+ mFromRaise(false),
+ mIsRefocus(false) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eFocusEventClass,
+ "Duplicate() must be overridden by sub class");
+ InternalFocusEvent* result = new InternalFocusEvent(false, mMessage);
+ result->AssignFocusEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ bool mFromRaise;
+ bool mIsRefocus;
+
+ void AssignFocusEventData(const InternalFocusEvent& aEvent,
+ bool aCopyTargets) {
+ AssignUIEventData(aEvent, aCopyTargets);
+
+ mFromRaise = aEvent.mFromRaise;
+ mIsRefocus = aEvent.mIsRefocus;
+ }
+};
+
+/******************************************************************************
+ * mozilla::InternalTransitionEvent
+ ******************************************************************************/
+
+class InternalTransitionEvent : public WidgetEvent {
+ public:
+ virtual InternalTransitionEvent* AsTransitionEvent() override { return this; }
+
+ InternalTransitionEvent(bool aIsTrusted, EventMessage aMessage)
+ : WidgetEvent(aIsTrusted, aMessage, eTransitionEventClass),
+ mElapsedTime(0.0) {}
+
+ InternalTransitionEvent(const InternalTransitionEvent& aOther) = delete;
+ InternalTransitionEvent& operator=(const InternalTransitionEvent& aOther) =
+ delete;
+ InternalTransitionEvent(InternalTransitionEvent&& aOther) = default;
+ InternalTransitionEvent& operator=(InternalTransitionEvent&& aOther) =
+ default;
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eTransitionEventClass,
+ "Duplicate() must be overridden by sub class");
+ InternalTransitionEvent* result =
+ new InternalTransitionEvent(false, mMessage);
+ result->AssignTransitionEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ nsString mPropertyName;
+ nsString mPseudoElement;
+ float mElapsedTime;
+
+ void AssignTransitionEventData(const InternalTransitionEvent& aEvent,
+ bool aCopyTargets) {
+ AssignEventData(aEvent, aCopyTargets);
+
+ mPropertyName = aEvent.mPropertyName;
+ mElapsedTime = aEvent.mElapsedTime;
+ mPseudoElement = aEvent.mPseudoElement;
+ }
+};
+
+/******************************************************************************
+ * mozilla::InternalAnimationEvent
+ ******************************************************************************/
+
+class InternalAnimationEvent : public WidgetEvent {
+ public:
+ virtual InternalAnimationEvent* AsAnimationEvent() override { return this; }
+
+ InternalAnimationEvent(bool aIsTrusted, EventMessage aMessage)
+ : WidgetEvent(aIsTrusted, aMessage, eAnimationEventClass),
+ mElapsedTime(0.0) {}
+
+ InternalAnimationEvent(const InternalAnimationEvent& aOther) = delete;
+ InternalAnimationEvent& operator=(const InternalAnimationEvent& aOther) =
+ delete;
+ InternalAnimationEvent(InternalAnimationEvent&& aOther) = default;
+ InternalAnimationEvent& operator=(InternalAnimationEvent&& aOther) = default;
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eAnimationEventClass,
+ "Duplicate() must be overridden by sub class");
+ InternalAnimationEvent* result =
+ new InternalAnimationEvent(false, mMessage);
+ result->AssignAnimationEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ nsString mAnimationName;
+ nsString mPseudoElement;
+ float mElapsedTime;
+
+ void AssignAnimationEventData(const InternalAnimationEvent& aEvent,
+ bool aCopyTargets) {
+ AssignEventData(aEvent, aCopyTargets);
+
+ mAnimationName = aEvent.mAnimationName;
+ mElapsedTime = aEvent.mElapsedTime;
+ mPseudoElement = aEvent.mPseudoElement;
+ }
+};
+
+/******************************************************************************
+ * mozilla::InternalSMILTimeEvent
+ ******************************************************************************/
+
+class InternalSMILTimeEvent : public InternalUIEvent {
+ public:
+ virtual InternalSMILTimeEvent* AsSMILTimeEvent() override { return this; }
+
+ InternalSMILTimeEvent(bool aIsTrusted, EventMessage aMessage)
+ : InternalUIEvent(aIsTrusted, aMessage, eSMILTimeEventClass) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eSMILTimeEventClass,
+ "Duplicate() must be overridden by sub class");
+ InternalSMILTimeEvent* result = new InternalSMILTimeEvent(false, mMessage);
+ result->AssignSMILTimeEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ void AssignSMILTimeEventData(const InternalSMILTimeEvent& aEvent,
+ bool aCopyTargets) {
+ AssignUIEventData(aEvent, aCopyTargets);
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ContentEvents_h__
diff --git a/widget/EventClassList.h b/widget/EventClassList.h
new file mode 100644
index 0000000000..c8870d84e5
--- /dev/null
+++ b/widget/EventClassList.h
@@ -0,0 +1,58 @@
+/* -*- 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/. */
+
+/**
+ * This header file lists up all event classes and related structs.
+ * Define NS_EVENT_CLASS(aPrefix, aName) and NS_ROOT_EVENT_CLASS(aPrefix, aName)
+ * before including this.
+ * If an event name is WidgetInputEvent, aPrefix is "Widget" and aName is
+ * "InputEvent". NS_ROOT_EVENT_CLASS() is only used for WidgetEvent for
+ * allowing special handling for it. If you don't need such special handling,
+ * you can define it as:
+ * #define NS_ROOT_EVENT_CLASS(aPrefix, aName) NS_EVENT_CLASS(aPrefix, aName)
+ */
+
+// BasicEvents.h
+NS_ROOT_EVENT_CLASS(Widget, Event)
+NS_EVENT_CLASS(Widget, GUIEvent)
+NS_EVENT_CLASS(Widget, InputEvent)
+NS_EVENT_CLASS(Internal, UIEvent)
+
+// TextEvents.h
+NS_EVENT_CLASS(Widget, KeyboardEvent)
+NS_EVENT_CLASS(Widget, CompositionEvent)
+NS_EVENT_CLASS(Widget, QueryContentEvent)
+NS_EVENT_CLASS(Widget, SelectionEvent)
+NS_EVENT_CLASS(Internal, EditorInputEvent)
+
+// MouseEvents.h
+NS_EVENT_CLASS(Widget, MouseEventBase)
+NS_EVENT_CLASS(Widget, MouseEvent)
+NS_EVENT_CLASS(Widget, DragEvent)
+NS_EVENT_CLASS(Widget, MouseScrollEvent)
+NS_EVENT_CLASS(Widget, WheelEvent)
+NS_EVENT_CLASS(Widget, PointerEvent)
+
+// TouchEvents.h
+NS_EVENT_CLASS(Widget, GestureNotifyEvent)
+NS_EVENT_CLASS(Widget, SimpleGestureEvent)
+NS_EVENT_CLASS(Widget, TouchEvent)
+
+// ContentEvents.h
+NS_EVENT_CLASS(Internal, ScrollPortEvent)
+NS_EVENT_CLASS(Internal, ScrollAreaEvent)
+NS_EVENT_CLASS(Internal, FormEvent)
+NS_EVENT_CLASS(Internal, ClipboardEvent)
+NS_EVENT_CLASS(Internal, FocusEvent)
+NS_EVENT_CLASS(Internal, TransitionEvent)
+NS_EVENT_CLASS(Internal, AnimationEvent)
+NS_EVENT_CLASS(Internal, SMILTimeEvent)
+
+// MiscEvents.h
+NS_EVENT_CLASS(Widget, CommandEvent)
+NS_EVENT_CLASS(Widget, ContentCommandEvent)
+
+// InternalMutationEvent.h (dom/events)
+NS_EVENT_CLASS(Internal, MutationEvent)
diff --git a/widget/EventForwards.h b/widget/EventForwards.h
new file mode 100644
index 0000000000..cfb90940ef
--- /dev/null
+++ b/widget/EventForwards.h
@@ -0,0 +1,472 @@
+/* -*- 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_EventForwards_h__
+#define mozilla_EventForwards_h__
+
+#include <stdint.h>
+
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+
+#ifdef DEBUG
+# include "mozilla/StaticPrefs_dom.h"
+#endif // #ifdef DEBUG
+
+class nsCommandParams;
+
+/**
+ * XXX Following enums should be in BasicEvents.h. However, currently, it's
+ * impossible to use foward delearation for enum.
+ */
+
+/**
+ * Return status for event processors.
+ */
+enum nsEventStatus {
+ // The event is ignored, do default processing
+ nsEventStatus_eIgnore,
+ // The event is consumed, don't do default processing
+ nsEventStatus_eConsumeNoDefault,
+ // The event is consumed, but do default processing
+ nsEventStatus_eConsumeDoDefault,
+ // Value is not for use, only for serialization
+ nsEventStatus_eSentinel
+};
+
+namespace mozilla {
+
+enum class CanBubble { eYes, eNo };
+
+enum class Cancelable { eYes, eNo };
+
+enum class ChromeOnlyDispatch { eYes, eNo };
+
+enum class Trusted { eYes, eNo };
+
+enum class Composed { eYes, eNo, eDefault };
+
+/**
+ * Event messages
+ */
+
+typedef uint16_t EventMessageType;
+
+enum EventMessage : EventMessageType {
+
+#define NS_EVENT_MESSAGE(aMessage) aMessage,
+#define NS_EVENT_MESSAGE_FIRST_LAST(aMessage, aFirst, aLast) \
+ aMessage##First = aFirst, aMessage##Last = aLast,
+
+#include "mozilla/EventMessageList.h"
+
+#undef NS_EVENT_MESSAGE
+#undef NS_EVENT_MESSAGE_FIRST_LAST
+
+ // For preventing bustage due to "," after the last item.
+ eEventMessage_MaxValue
+};
+
+const char* ToChar(EventMessage aEventMessage);
+
+/**
+ * Event class IDs
+ */
+
+typedef uint8_t EventClassIDType;
+
+enum EventClassID : EventClassIDType {
+// The event class name will be:
+// eBasicEventClass for WidgetEvent
+// eFooEventClass for WidgetFooEvent or InternalFooEvent
+#define NS_ROOT_EVENT_CLASS(aPrefix, aName) eBasic##aName##Class
+#define NS_EVENT_CLASS(aPrefix, aName) , e##aName##Class
+
+#include "mozilla/EventClassList.h"
+
+#undef NS_EVENT_CLASS
+#undef NS_ROOT_EVENT_CLASS
+};
+
+const char* ToChar(EventClassID aEventClassID);
+
+typedef uint16_t Modifiers;
+
+#define NS_DEFINE_KEYNAME(aCPPName, aDOMKeyName) KEY_NAME_INDEX_##aCPPName,
+
+typedef uint16_t KeyNameIndexType;
+enum KeyNameIndex : KeyNameIndexType {
+#include "mozilla/KeyNameList.h"
+ // If a DOM keyboard event is synthesized by script, this is used. Then,
+ // specified key name should be stored and use it as .key value.
+ KEY_NAME_INDEX_USE_STRING
+};
+
+#undef NS_DEFINE_KEYNAME
+
+const nsCString ToString(KeyNameIndex aKeyNameIndex);
+
+#define NS_DEFINE_PHYSICAL_KEY_CODE_NAME(aCPPName, aDOMCodeName) \
+ CODE_NAME_INDEX_##aCPPName,
+
+typedef uint8_t CodeNameIndexType;
+enum CodeNameIndex : CodeNameIndexType {
+#include "mozilla/PhysicalKeyCodeNameList.h"
+ // If a DOM keyboard event is synthesized by script, this is used. Then,
+ // specified code name should be stored and use it as .code value.
+ CODE_NAME_INDEX_USE_STRING
+};
+
+#undef NS_DEFINE_PHYSICAL_KEY_CODE_NAME
+
+const nsCString ToString(CodeNameIndex aCodeNameIndex);
+
+#define NS_DEFINE_INPUTTYPE(aCPPName, aDOMName) e##aCPPName,
+
+typedef uint8_t EditorInputTypeType;
+enum class EditorInputType : EditorInputTypeType {
+#include "mozilla/InputTypeList.h"
+ // If a DOM input event is synthesized by script, this is used. Then,
+ // specified input type should be stored as string and use it as .inputType
+ // value.
+ eUnknown,
+};
+
+#undef NS_DEFINE_INPUTTYPE
+
+inline bool ExposesClipboardDataOrDataTransfer(EditorInputType aInputType) {
+ switch (aInputType) {
+ case EditorInputType::eInsertFromPaste:
+ case EditorInputType::eInsertFromPasteAsQuotation:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * IsDataAvailableOnTextEditor() returns true if aInputType on TextEditor
+ * should have non-null InputEvent.data value.
+ */
+inline bool IsDataAvailableOnTextEditor(EditorInputType aInputType) {
+ switch (aInputType) {
+ case EditorInputType::eInsertText:
+ case EditorInputType::eInsertCompositionText:
+ case EditorInputType::eInsertFromComposition: // Only level 2
+ case EditorInputType::eInsertFromPaste:
+ case EditorInputType::eInsertFromPasteAsQuotation:
+ case EditorInputType::eInsertTranspose:
+ case EditorInputType::eInsertFromDrop:
+ case EditorInputType::eInsertReplacementText:
+ case EditorInputType::eInsertFromYank:
+ case EditorInputType::eFormatSetBlockTextDirection:
+ case EditorInputType::eFormatSetInlineTextDirection:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * IsDataAvailableOnHTMLEditor() returns true if aInputType on HTMLEditor
+ * should have non-null InputEvent.data value.
+ */
+inline bool IsDataAvailableOnHTMLEditor(EditorInputType aInputType) {
+ switch (aInputType) {
+ case EditorInputType::eInsertText:
+ case EditorInputType::eInsertCompositionText:
+ case EditorInputType::eInsertFromComposition: // Only level 2
+ case EditorInputType::eFormatSetBlockTextDirection:
+ case EditorInputType::eFormatSetInlineTextDirection:
+ case EditorInputType::eInsertLink:
+ case EditorInputType::eFormatBackColor:
+ case EditorInputType::eFormatFontColor:
+ case EditorInputType::eFormatFontName:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * IsDataTransferAvailableOnHTMLEditor() returns true if aInputType on
+ * HTMLEditor should have non-null InputEvent.dataTransfer value.
+ */
+inline bool IsDataTransferAvailableOnHTMLEditor(EditorInputType aInputType) {
+ switch (aInputType) {
+ case EditorInputType::eInsertFromPaste:
+ case EditorInputType::eInsertFromPasteAsQuotation:
+ case EditorInputType::eInsertFromDrop:
+ case EditorInputType::eInsertTranspose:
+ case EditorInputType::eInsertReplacementText:
+ case EditorInputType::eInsertFromYank:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * MayHaveTargetRangesOnHTMLEditor() returns true if "beforeinput" event whose
+ * whose inputType is aInputType on HTMLEditor may return non-empty static
+ * range array from getTargetRanges().
+ * Note that TextEditor always sets empty array. Therefore, there is no
+ * method for TextEditor.
+ */
+inline bool MayHaveTargetRangesOnHTMLEditor(EditorInputType aInputType) {
+ switch (aInputType) {
+ // Explicitly documented by the specs.
+ case EditorInputType::eHistoryRedo:
+ case EditorInputType::eHistoryUndo:
+ // Not documented, but other browsers use empty array.
+ case EditorInputType::eFormatSetBlockTextDirection:
+ return false;
+ default:
+ return true;
+ }
+}
+
+/**
+ * IsCancelableBeforeInputEvent() returns true if `beforeinput` event for
+ * aInputType should be cancelable.
+ *
+ * Input Events Level 1:
+ * https://rawgit.com/w3c/input-events/v1/index.html#x5-1-2-attributes
+ * Input Events Level 2:
+ * https://w3c.github.io/input-events/#interface-InputEvent-Attributes
+ */
+inline bool IsCancelableBeforeInputEvent(EditorInputType aInputType) {
+ switch (aInputType) {
+ case EditorInputType::eInsertText:
+ return true; // In Level 1, undefined.
+ case EditorInputType::eInsertReplacementText:
+ return true; // In Level 1, undefined.
+ case EditorInputType::eInsertLineBreak:
+ return true; // In Level 1, undefined.
+ case EditorInputType::eInsertParagraph:
+ return true; // In Level 1, undefined.
+ case EditorInputType::eInsertOrderedList:
+ return true;
+ case EditorInputType::eInsertUnorderedList:
+ return true;
+ case EditorInputType::eInsertHorizontalRule:
+ return true;
+ case EditorInputType::eInsertFromYank:
+ return true;
+ case EditorInputType::eInsertFromDrop:
+ return true;
+ case EditorInputType::eInsertFromPaste:
+ return true;
+ case EditorInputType::eInsertFromPasteAsQuotation:
+ return true;
+ case EditorInputType::eInsertTranspose:
+ return true;
+ case EditorInputType::eInsertCompositionText:
+ return false;
+ case EditorInputType::eInsertFromComposition:
+ MOZ_ASSERT(!StaticPrefs::dom_input_events_conform_to_level_1());
+ return true;
+ case EditorInputType::eInsertLink:
+ return true;
+ case EditorInputType::eDeleteByComposition:
+ MOZ_ASSERT(!StaticPrefs::dom_input_events_conform_to_level_1());
+ return true;
+ case EditorInputType::eDeleteCompositionText:
+ MOZ_ASSERT(!StaticPrefs::dom_input_events_conform_to_level_1());
+ return false;
+ case EditorInputType::eDeleteWordBackward:
+ return true; // In Level 1, undefined.
+ case EditorInputType::eDeleteWordForward:
+ return true; // In Level 1, undefined.
+ case EditorInputType::eDeleteSoftLineBackward:
+ return true; // In Level 1, undefined.
+ case EditorInputType::eDeleteSoftLineForward:
+ return true; // In Level 1, undefined.
+ case EditorInputType::eDeleteEntireSoftLine:
+ return true; // In Level 1, undefined.
+ case EditorInputType::eDeleteHardLineBackward:
+ return true; // In Level 1, undefined.
+ case EditorInputType::eDeleteHardLineForward:
+ return true; // In Level 1, undefined.
+ case EditorInputType::eDeleteByDrag:
+ return true;
+ case EditorInputType::eDeleteByCut:
+ return true;
+ case EditorInputType::eDeleteContent:
+ return true; // In Level 1, undefined.
+ case EditorInputType::eDeleteContentBackward:
+ return true; // In Level 1, undefined.
+ case EditorInputType::eDeleteContentForward:
+ return true; // In Level 1, undefined.
+ case EditorInputType::eHistoryUndo:
+ return true;
+ case EditorInputType::eHistoryRedo:
+ return true;
+ case EditorInputType::eFormatBold:
+ return true;
+ case EditorInputType::eFormatItalic:
+ return true;
+ case EditorInputType::eFormatUnderline:
+ return true;
+ case EditorInputType::eFormatStrikeThrough:
+ return true;
+ case EditorInputType::eFormatSuperscript:
+ return true;
+ case EditorInputType::eFormatSubscript:
+ return true;
+ case EditorInputType::eFormatJustifyFull:
+ return true;
+ case EditorInputType::eFormatJustifyCenter:
+ return true;
+ case EditorInputType::eFormatJustifyRight:
+ return true;
+ case EditorInputType::eFormatJustifyLeft:
+ return true;
+ case EditorInputType::eFormatIndent:
+ return true;
+ case EditorInputType::eFormatOutdent:
+ return true;
+ case EditorInputType::eFormatRemove:
+ return true;
+ case EditorInputType::eFormatSetBlockTextDirection:
+ return true;
+ case EditorInputType::eFormatSetInlineTextDirection:
+ return true;
+ case EditorInputType::eFormatBackColor:
+ return true;
+ case EditorInputType::eFormatFontColor:
+ return true;
+ case EditorInputType::eFormatFontName:
+ return true;
+ case EditorInputType::eUnknown:
+ // This is not declared by Input Events, but it does not make sense to
+ // allow web apps to cancel default action without inputType value check.
+ // If some our specific edit actions should be cancelable, new inputType
+ // value for them should be declared by the spec.
+ return false;
+ default:
+ MOZ_ASSERT_UNREACHABLE("The new input type is not handled");
+ return false;
+ }
+}
+
+#define NS_DEFINE_COMMAND(aName, aCommandStr) , aName
+#define NS_DEFINE_COMMAND_WITH_PARAM(aName, aCommandStr, aParam) , aName
+#define NS_DEFINE_COMMAND_NO_EXEC_COMMAND(aName) , aName
+
+typedef uint8_t CommandInt;
+enum class Command : CommandInt {
+ DoNothing
+
+#include "mozilla/CommandList.h"
+};
+#undef NS_DEFINE_COMMAND
+#undef NS_DEFINE_COMMAND_WITH_PARAM
+#undef NS_DEFINE_COMMAND_NO_EXEC_COMMAND
+
+const char* ToChar(Command aCommand);
+
+/**
+ * Return a command value for aCommandName.
+ * XXX: Is there a better place to put `Command` related methods instead of
+ * global scope in `mozilla` namespace?
+ *
+ * @param aCommandName Should be a XUL command name like "cmd_bold"
+ * (case sensitive).
+ * @param aCommandparams Additional parameter value of aCommandName.
+ * Can be nullptr, but if aCommandName requires
+ * additional parameter and sets this to nullptr,
+ * will return Command::DoNothing with warning.
+ */
+Command GetInternalCommand(const char* aCommandName,
+ const nsCommandParams* aCommandParams = nullptr);
+
+} // namespace mozilla
+
+/**
+ * All header files should include this header instead of *Events.h.
+ */
+
+namespace mozilla {
+
+template <class T>
+class OwningNonNull;
+
+namespace dom {
+class StaticRange;
+}
+
+#define NS_EVENT_CLASS(aPrefix, aName) class aPrefix##aName;
+#define NS_ROOT_EVENT_CLASS(aPrefix, aName) NS_EVENT_CLASS(aPrefix, aName)
+
+#include "mozilla/EventClassList.h"
+
+#undef NS_EVENT_CLASS
+#undef NS_ROOT_EVENT_CLASS
+
+// BasicEvents.h
+struct BaseEventFlags;
+struct EventFlags;
+
+class WidgetEventTime;
+
+class NativeEventData;
+
+// TextEvents.h
+enum class AccessKeyType;
+
+struct AlternativeCharCode;
+struct ShortcutKeyCandidate;
+
+typedef nsTArray<ShortcutKeyCandidate> ShortcutKeyCandidateArray;
+typedef AutoTArray<ShortcutKeyCandidate, 10> AutoShortcutKeyCandidateArray;
+
+// TextRange.h
+typedef uint8_t RawTextRangeType;
+enum class TextRangeType : RawTextRangeType;
+
+struct TextRangeStyle;
+struct TextRange;
+
+class EditCommands;
+class TextRangeArray;
+
+typedef nsTArray<OwningNonNull<dom::StaticRange>> OwningNonNullStaticRangeArray;
+
+// FontRange.h
+struct FontRange;
+
+enum MouseButton {
+ eNotPressed = -1,
+ ePrimary = 0,
+ eMiddle = 1,
+ eSecondary = 2
+};
+
+enum MouseButtonsFlag {
+ eNoButtons = 0x00,
+ ePrimaryFlag = 0x01,
+ eSecondaryFlag = 0x02,
+ eMiddleFlag = 0x04,
+ // typicall, "back" button being left side of 5-button
+ // mice, see "buttons" attribute document of DOM3 Events.
+ e4thFlag = 0x08,
+ // typicall, "forward" button being right side of 5-button
+ // mice, see "buttons" attribute document of DOM3 Events.
+ e5thFlag = 0x10
+};
+
+enum class TextRangeType : RawTextRangeType;
+
+// IMEData.h
+
+template <typename IntType>
+class StartAndEndOffsets;
+template <typename IntType>
+class OffsetAndData;
+
+} // namespace mozilla
+
+#endif // mozilla_EventForwards_h__
diff --git a/widget/EventMessageList.h b/widget/EventMessageList.h
new file mode 100644
index 0000000000..96d7125d47
--- /dev/null
+++ b/widget/EventMessageList.h
@@ -0,0 +1,476 @@
+/* -*- 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/. */
+
+/**
+ * This header file lists up all event messages.
+ * Before including this header file, you should define:
+ * NS_EVENT_MESSAGE(aMessage)
+ *
+ * Additionally, you can specify following macro for e*First and e*Last.
+ * NS_EVENT_MESSAGE_FIRST_LAST(aMessage, aFirst, aLast)
+ * This is optional, if you need only actual event messages, you don't need
+ * to define this macro.
+ *
+ * Naming rules of the event messages:
+ * 0. Starting with "e" prefix and use camelcase.
+ * 1. Basically, use same name as the DOM name which is fired at dispatching
+ * the event.
+ * 2. If the event message name becomes too generic, e.g., "eInvalid", that may
+ * conflict with another enum's item name, append something after the "e"
+ * prefix, e.g., "eFormInvalid".
+ */
+
+#ifndef NS_EVENT_MESSAGE_FIRST_LAST
+# define UNDEF_NS_EVENT_MESSAGE_FIRST_LAST 1
+# define NS_EVENT_MESSAGE_FIRST_LAST(aMessage, aFirst, aLast)
+#endif
+
+NS_EVENT_MESSAGE(eVoidEvent)
+
+// This is a dummy event message for all event listener implementation in
+// EventListenerManager.
+NS_EVENT_MESSAGE(eAllEvents)
+
+// Widget may be destroyed
+NS_EVENT_MESSAGE(eWindowClose)
+
+NS_EVENT_MESSAGE(eKeyPress)
+NS_EVENT_MESSAGE(eKeyUp)
+NS_EVENT_MESSAGE(eKeyDown)
+
+// These messages are dispatched when PluginInstaceChild receives native
+// keyboard events directly and it posts the information to the widget.
+// These messages shouldn't be handled by content and non-reserved chrome
+// event handlers.
+NS_EVENT_MESSAGE(eKeyDownOnPlugin)
+NS_EVENT_MESSAGE(eKeyUpOnPlugin)
+
+// This message is sent after a content process handles a key event or accesskey
+// to indicate that an potential accesskey was not found. The parent process may
+// then respond by, for example, opening menus and processing other shortcuts.
+// It inherits its properties from a keypress event.
+NS_EVENT_MESSAGE(eAccessKeyNotFound)
+
+NS_EVENT_MESSAGE(eResize)
+NS_EVENT_MESSAGE(eScroll)
+NS_EVENT_MESSAGE(eMozVisualResize)
+NS_EVENT_MESSAGE(eMozVisualScroll)
+
+// Application installation
+NS_EVENT_MESSAGE(eInstall)
+NS_EVENT_MESSAGE(eAppInstalled)
+
+// A plugin was clicked or otherwise focused. ePluginActivate should be
+// used when the window is not active. ePluginFocus should be used when
+// the window is active. In the latter case, the dispatcher of the event
+// is expected to ensure that the plugin's widget is focused beforehand.
+NS_EVENT_MESSAGE(ePluginActivate)
+NS_EVENT_MESSAGE(ePluginFocus)
+
+NS_EVENT_MESSAGE(eOffline)
+NS_EVENT_MESSAGE(eOnline)
+
+NS_EVENT_MESSAGE(eLanguageChange)
+
+NS_EVENT_MESSAGE(eMouseMove)
+NS_EVENT_MESSAGE(eMouseUp)
+NS_EVENT_MESSAGE(eMouseDown)
+NS_EVENT_MESSAGE(eMouseEnterIntoWidget)
+NS_EVENT_MESSAGE(eMouseExitFromWidget)
+NS_EVENT_MESSAGE(eMouseDoubleClick)
+NS_EVENT_MESSAGE(eMouseClick)
+NS_EVENT_MESSAGE(eMouseAuxClick)
+// eMouseActivate is fired when the widget is activated by a click.
+NS_EVENT_MESSAGE(eMouseActivate)
+NS_EVENT_MESSAGE(eMouseOver)
+NS_EVENT_MESSAGE(eMouseOut)
+NS_EVENT_MESSAGE(eMouseHitTest)
+NS_EVENT_MESSAGE(eMouseEnter)
+NS_EVENT_MESSAGE(eMouseLeave)
+NS_EVENT_MESSAGE(eMouseTouchDrag)
+NS_EVENT_MESSAGE(eMouseLongTap)
+NS_EVENT_MESSAGE_FIRST_LAST(eMouseEvent, eMouseMove, eMouseLongTap)
+
+// Pointer spec events
+NS_EVENT_MESSAGE(ePointerMove)
+NS_EVENT_MESSAGE(ePointerUp)
+NS_EVENT_MESSAGE(ePointerDown)
+NS_EVENT_MESSAGE(ePointerOver)
+NS_EVENT_MESSAGE(ePointerOut)
+NS_EVENT_MESSAGE(ePointerEnter)
+NS_EVENT_MESSAGE(ePointerLeave)
+NS_EVENT_MESSAGE(ePointerCancel)
+NS_EVENT_MESSAGE(ePointerGotCapture)
+NS_EVENT_MESSAGE(ePointerLostCapture)
+NS_EVENT_MESSAGE_FIRST_LAST(ePointerEvent, ePointerMove, ePointerLostCapture)
+
+NS_EVENT_MESSAGE(eContextMenu)
+
+NS_EVENT_MESSAGE(eCueChange)
+
+NS_EVENT_MESSAGE(eLoad)
+NS_EVENT_MESSAGE(eUnload)
+NS_EVENT_MESSAGE(eHashChange)
+NS_EVENT_MESSAGE(eImageAbort)
+NS_EVENT_MESSAGE(eLoadError)
+NS_EVENT_MESSAGE(eLoadEnd)
+NS_EVENT_MESSAGE(ePopState)
+NS_EVENT_MESSAGE(eRejectionHandled)
+NS_EVENT_MESSAGE(eStorage)
+NS_EVENT_MESSAGE(eUnhandledRejection)
+NS_EVENT_MESSAGE(eBeforeUnload)
+NS_EVENT_MESSAGE(eReadyStateChange)
+
+NS_EVENT_MESSAGE(eFormSubmit)
+NS_EVENT_MESSAGE(eFormReset)
+NS_EVENT_MESSAGE(eFormChange)
+NS_EVENT_MESSAGE(eFormSelect)
+NS_EVENT_MESSAGE(eFormInvalid)
+NS_EVENT_MESSAGE(eFormCheckboxStateChange)
+NS_EVENT_MESSAGE(eFormRadioStateChange)
+NS_EVENT_MESSAGE(eFormData)
+
+// Need separate focus/blur notifications for non-native widgets
+NS_EVENT_MESSAGE(eFocus)
+NS_EVENT_MESSAGE(eBlur)
+NS_EVENT_MESSAGE(eFocusIn)
+NS_EVENT_MESSAGE(eFocusOut)
+
+NS_EVENT_MESSAGE(eDragEnter)
+NS_EVENT_MESSAGE(eDragOver)
+NS_EVENT_MESSAGE(eDragExit)
+NS_EVENT_MESSAGE(eDrag)
+NS_EVENT_MESSAGE(eDragEnd)
+NS_EVENT_MESSAGE(eDragStart)
+NS_EVENT_MESSAGE(eDrop)
+NS_EVENT_MESSAGE(eDragLeave)
+NS_EVENT_MESSAGE_FIRST_LAST(eDragDropEvent, eDragEnter, eDragLeave)
+
+// XUL specific events
+NS_EVENT_MESSAGE(eXULPopupShowing)
+NS_EVENT_MESSAGE(eXULPopupShown)
+NS_EVENT_MESSAGE(eXULPopupPositioned)
+NS_EVENT_MESSAGE(eXULPopupHiding)
+NS_EVENT_MESSAGE(eXULPopupHidden)
+NS_EVENT_MESSAGE(eXULBroadcast)
+NS_EVENT_MESSAGE(eXULCommandUpdate)
+
+// Legacy mouse scroll (wheel) events
+NS_EVENT_MESSAGE(eLegacyMouseLineOrPageScroll)
+NS_EVENT_MESSAGE(eLegacyMousePixelScroll)
+
+NS_EVENT_MESSAGE(eScrollPortUnderflow)
+NS_EVENT_MESSAGE(eScrollPortOverflow)
+
+NS_EVENT_MESSAGE(eLegacySubtreeModified)
+NS_EVENT_MESSAGE(eLegacyNodeInserted)
+NS_EVENT_MESSAGE(eLegacyNodeRemoved)
+NS_EVENT_MESSAGE(eLegacyNodeRemovedFromDocument)
+NS_EVENT_MESSAGE(eLegacyNodeInsertedIntoDocument)
+NS_EVENT_MESSAGE(eLegacyAttrModified)
+NS_EVENT_MESSAGE(eLegacyCharacterDataModified)
+NS_EVENT_MESSAGE_FIRST_LAST(eLegacyMutationEvent, eLegacySubtreeModified,
+ eLegacyCharacterDataModified)
+
+NS_EVENT_MESSAGE(eUnidentifiedEvent)
+
+// composition events
+NS_EVENT_MESSAGE(eCompositionStart)
+// eCompositionEnd is the message for DOM compositionend event.
+// This event should NOT be dispatched from widget if eCompositionCommit
+// is available.
+NS_EVENT_MESSAGE(eCompositionEnd)
+// eCompositionUpdate is the message for DOM compositionupdate event.
+// This event should NOT be dispatched from widget since it will be dispatched
+// by mozilla::TextComposition automatically if eCompositionChange event
+// will change composition string.
+NS_EVENT_MESSAGE(eCompositionUpdate)
+// eCompositionChange is the message for representing a change of
+// composition string. This should be dispatched from widget even if
+// composition string isn't changed but the ranges are changed. This causes
+// a DOM "text" event which is a non-standard DOM event.
+NS_EVENT_MESSAGE(eCompositionChange)
+// eCompositionCommitAsIs is the message for representing a commit of
+// composition string. TextComposition will commit composition with the
+// last data. TextComposition will dispatch this event to the DOM tree as
+// eCompositionChange without clause information. After that,
+// eCompositionEnd will be dispatched automatically.
+// Its mData and mRanges should be empty and nullptr.
+NS_EVENT_MESSAGE(eCompositionCommitAsIs)
+// eCompositionCommit is the message for representing a commit of
+// composition string with its mData value. TextComposition will dispatch this
+// event to the DOM tree as eCompositionChange without clause information.
+// After that, eCompositionEnd will be dispatched automatically.
+// Its mRanges should be nullptr.
+NS_EVENT_MESSAGE(eCompositionCommit)
+// eCompositionCommitRequestHandled is NOT used with any Widget*Event.
+// This is used only by PBrowser.OnEventNeedingAckHandled(). If active IME
+// commits composition synchronously, BrowserParent returns the commit string
+// to the remote process synchronously. Then, BrowserChild dispatches
+// eCompositionCommit in the remote process. Finally, this message is sent
+// to BrowserParent. (If IME commits composition asynchronously, this message
+// is not used.)
+NS_EVENT_MESSAGE(eCompositionCommitRequestHandled)
+
+// Following events are defined for deprecated DOM events which are using
+// InternalUIEvent class.
+// DOMActivate (mapped with the DOM event and used internally)
+NS_EVENT_MESSAGE(eLegacyDOMActivate)
+// DOMFocusIn (only mapped with the DOM event)
+NS_EVENT_MESSAGE(eLegacyDOMFocusIn)
+// DOMFocusOut (only mapped with the DOM event)
+NS_EVENT_MESSAGE(eLegacyDOMFocusOut)
+
+// pagetransition events
+NS_EVENT_MESSAGE(ePageShow)
+NS_EVENT_MESSAGE(ePageHide)
+
+// SVG events
+NS_EVENT_MESSAGE(eSVGLoad)
+NS_EVENT_MESSAGE(eSVGScroll)
+
+// XUL command events
+NS_EVENT_MESSAGE(eXULCommand)
+
+// Cut, copy, paste events
+NS_EVENT_MESSAGE(eCopy)
+NS_EVENT_MESSAGE(eCut)
+NS_EVENT_MESSAGE(ePaste)
+NS_EVENT_MESSAGE(ePasteNoFormatting)
+
+// Query for the selected text information, it return the selection offset,
+// selection length and selected text.
+NS_EVENT_MESSAGE(eQuerySelectedText)
+// Query for the text content of specified range, it returns actual lengh (if
+// the specified range is too long) and the text of the specified range.
+// Returns the entire text if requested length > actual length.
+NS_EVENT_MESSAGE(eQueryTextContent)
+// Query for the caret rect of nth insertion point. The offset of the result is
+// relative position from the top level widget.
+NS_EVENT_MESSAGE(eQueryCaretRect)
+// Query for the bounding rect of a range of characters. This works on any
+// valid character range given offset and length. Result is relative to top
+// level widget coordinates
+NS_EVENT_MESSAGE(eQueryTextRect)
+// Query for the bounding rect array of a range of characters.
+// Thiis similar event of eQueryTextRect.
+NS_EVENT_MESSAGE(eQueryTextRectArray)
+// Query for the bounding rect of the current focused frame. Result is relative
+// to top level widget coordinates
+NS_EVENT_MESSAGE(eQueryEditorRect)
+// Query for the current state of the content. The particular members of
+// mReply that are set for each query content event will be valid on success.
+NS_EVENT_MESSAGE(eQueryContentState)
+// Query for the selection in the form of a nsITransferable.
+NS_EVENT_MESSAGE(eQuerySelectionAsTransferable)
+// Query for character at a point. This returns the character offset, its
+// rect and also tentative caret point if the point is clicked. The point is
+// specified by Event::mRefPoint.
+NS_EVENT_MESSAGE(eQueryCharacterAtPoint)
+// Query if the DOM element under Event::mRefPoint belongs to our widget
+// or not.
+NS_EVENT_MESSAGE(eQueryDOMWidgetHittest)
+
+// Video events
+NS_EVENT_MESSAGE(eLoadStart)
+NS_EVENT_MESSAGE(eProgress)
+NS_EVENT_MESSAGE(eSuspend)
+NS_EVENT_MESSAGE(eEmptied)
+NS_EVENT_MESSAGE(eStalled)
+NS_EVENT_MESSAGE(ePlay)
+NS_EVENT_MESSAGE(ePause)
+NS_EVENT_MESSAGE(eLoadedMetaData)
+NS_EVENT_MESSAGE(eLoadedData)
+NS_EVENT_MESSAGE(eWaiting)
+NS_EVENT_MESSAGE(ePlaying)
+NS_EVENT_MESSAGE(eCanPlay)
+NS_EVENT_MESSAGE(eCanPlayThrough)
+NS_EVENT_MESSAGE(eSeeking)
+NS_EVENT_MESSAGE(eSeeked)
+NS_EVENT_MESSAGE(eTimeUpdate)
+NS_EVENT_MESSAGE(eEnded)
+NS_EVENT_MESSAGE(eRateChange)
+NS_EVENT_MESSAGE(eDurationChange)
+NS_EVENT_MESSAGE(eVolumeChange)
+
+// paint notification events
+NS_EVENT_MESSAGE(eAfterPaint)
+
+// Simple gesture events
+NS_EVENT_MESSAGE(eSwipeGestureMayStart)
+NS_EVENT_MESSAGE(eSwipeGestureStart)
+NS_EVENT_MESSAGE(eSwipeGestureUpdate)
+NS_EVENT_MESSAGE(eSwipeGestureEnd)
+NS_EVENT_MESSAGE(eSwipeGesture)
+NS_EVENT_MESSAGE(eMagnifyGestureStart)
+NS_EVENT_MESSAGE(eMagnifyGestureUpdate)
+NS_EVENT_MESSAGE(eMagnifyGesture)
+NS_EVENT_MESSAGE(eRotateGestureStart)
+NS_EVENT_MESSAGE(eRotateGestureUpdate)
+NS_EVENT_MESSAGE(eRotateGesture)
+NS_EVENT_MESSAGE(eTapGesture)
+NS_EVENT_MESSAGE(ePressTapGesture)
+NS_EVENT_MESSAGE(eEdgeUIStarted)
+NS_EVENT_MESSAGE(eEdgeUICanceled)
+NS_EVENT_MESSAGE(eEdgeUICompleted)
+
+// Events to manipulate selection (WidgetSelectionEvent)
+// Clear any previous selection and set the given range as the selection
+NS_EVENT_MESSAGE(eSetSelection)
+
+// Events of commands for the contents
+NS_EVENT_MESSAGE(eContentCommandCut)
+NS_EVENT_MESSAGE(eContentCommandCopy)
+NS_EVENT_MESSAGE(eContentCommandPaste)
+NS_EVENT_MESSAGE(eContentCommandDelete)
+NS_EVENT_MESSAGE(eContentCommandUndo)
+NS_EVENT_MESSAGE(eContentCommandRedo)
+NS_EVENT_MESSAGE(eContentCommandPasteTransferable)
+NS_EVENT_MESSAGE(eContentCommandLookUpDictionary)
+// eContentCommandScroll scrolls the nearest scrollable element to the
+// currently focused content or latest DOM selection. This would normally be
+// the same element scrolled by keyboard scroll commands, except that this event
+// will scroll an element scrollable in either direction. I.e., if the nearest
+// scrollable ancestor element can only be scrolled vertically, and horizontal
+// scrolling is requested using this event, no scrolling will occur.
+NS_EVENT_MESSAGE(eContentCommandScroll)
+
+// Event to gesture notification
+NS_EVENT_MESSAGE(eGestureNotify)
+
+NS_EVENT_MESSAGE(eScrolledAreaChanged)
+
+// CSS Transition & Animation events:
+NS_EVENT_MESSAGE(eTransitionStart)
+NS_EVENT_MESSAGE(eTransitionRun)
+NS_EVENT_MESSAGE(eTransitionEnd)
+NS_EVENT_MESSAGE(eTransitionCancel)
+NS_EVENT_MESSAGE(eAnimationStart)
+NS_EVENT_MESSAGE(eAnimationEnd)
+NS_EVENT_MESSAGE(eAnimationIteration)
+NS_EVENT_MESSAGE(eAnimationCancel)
+
+// Webkit-prefixed versions of Transition & Animation events, for web compat:
+NS_EVENT_MESSAGE(eWebkitTransitionEnd)
+NS_EVENT_MESSAGE(eWebkitAnimationStart)
+NS_EVENT_MESSAGE(eWebkitAnimationEnd)
+NS_EVENT_MESSAGE(eWebkitAnimationIteration)
+
+NS_EVENT_MESSAGE(eSMILBeginEvent)
+NS_EVENT_MESSAGE(eSMILEndEvent)
+NS_EVENT_MESSAGE(eSMILRepeatEvent)
+
+NS_EVENT_MESSAGE(eAudioProcess)
+NS_EVENT_MESSAGE(eAudioComplete)
+
+// script notification events
+NS_EVENT_MESSAGE(eBeforeScriptExecute)
+NS_EVENT_MESSAGE(eAfterScriptExecute)
+
+NS_EVENT_MESSAGE(eBeforePrint)
+NS_EVENT_MESSAGE(eAfterPrint)
+
+NS_EVENT_MESSAGE(eMessage)
+NS_EVENT_MESSAGE(eMessageError)
+
+// Menu open event
+NS_EVENT_MESSAGE(eOpen)
+
+// Device motion and orientation
+NS_EVENT_MESSAGE(eDeviceOrientation)
+NS_EVENT_MESSAGE(eAbsoluteDeviceOrientation)
+NS_EVENT_MESSAGE(eDeviceMotion)
+NS_EVENT_MESSAGE(eDeviceProximity)
+NS_EVENT_MESSAGE(eUserProximity)
+NS_EVENT_MESSAGE(eDeviceLight)
+#if defined(MOZ_WIDGET_ANDROID)
+NS_EVENT_MESSAGE(eOrientationChange)
+#endif
+
+// WebVR events
+NS_EVENT_MESSAGE(eVRDisplayActivate)
+NS_EVENT_MESSAGE(eVRDisplayDeactivate)
+NS_EVENT_MESSAGE(eVRDisplayConnect)
+NS_EVENT_MESSAGE(eVRDisplayDisconnect)
+NS_EVENT_MESSAGE(eVRDisplayPresentChange)
+
+NS_EVENT_MESSAGE(eShow)
+
+// Fullscreen DOM API
+NS_EVENT_MESSAGE(eFullscreenChange)
+NS_EVENT_MESSAGE(eFullscreenError)
+NS_EVENT_MESSAGE(eMozFullscreenChange)
+NS_EVENT_MESSAGE(eMozFullscreenError)
+
+NS_EVENT_MESSAGE(eTouchStart)
+NS_EVENT_MESSAGE(eTouchMove)
+NS_EVENT_MESSAGE(eTouchEnd)
+NS_EVENT_MESSAGE(eTouchCancel)
+NS_EVENT_MESSAGE(eTouchPointerCancel)
+
+// Pointerlock DOM API
+NS_EVENT_MESSAGE(ePointerLockChange)
+NS_EVENT_MESSAGE(ePointerLockError)
+NS_EVENT_MESSAGE(eMozPointerLockChange)
+NS_EVENT_MESSAGE(eMozPointerLockError)
+
+// eWheel is the event message of DOM wheel event.
+NS_EVENT_MESSAGE(eWheel)
+// eWheelOperationStart may be dispatched when user starts to operate mouse
+// wheel. This won't be fired on some platforms which don't have corresponding
+// native event.
+NS_EVENT_MESSAGE(eWheelOperationStart)
+// eWheelOperationEnd may be dispatched when user ends or cancels operating
+// mouse wheel. This won't be fired on some platforms which don't have
+// corresponding native event.
+NS_EVENT_MESSAGE(eWheelOperationEnd)
+
+// System time is changed
+NS_EVENT_MESSAGE(eTimeChange)
+
+// Network packet events.
+NS_EVENT_MESSAGE(eNetworkUpload)
+NS_EVENT_MESSAGE(eNetworkDownload)
+
+// MediaRecorder events.
+NS_EVENT_MESSAGE(eMediaRecorderDataAvailable)
+NS_EVENT_MESSAGE(eMediaRecorderWarning)
+NS_EVENT_MESSAGE(eMediaRecorderStop)
+
+// Gamepad input events
+NS_EVENT_MESSAGE(eGamepadButtonDown)
+NS_EVENT_MESSAGE(eGamepadButtonUp)
+NS_EVENT_MESSAGE(eGamepadAxisMove)
+NS_EVENT_MESSAGE(eGamepadConnected)
+NS_EVENT_MESSAGE(eGamepadDisconnected)
+NS_EVENT_MESSAGE_FIRST_LAST(eGamepadEvent, eGamepadButtonDown,
+ eGamepadDisconnected)
+
+// input and beforeinput events.
+NS_EVENT_MESSAGE(eEditorInput)
+NS_EVENT_MESSAGE(eEditorBeforeInput)
+
+// selection events
+NS_EVENT_MESSAGE(eSelectStart)
+NS_EVENT_MESSAGE(eSelectionChange)
+
+// visibility change
+NS_EVENT_MESSAGE(eVisibilityChange)
+
+// Details element events.
+NS_EVENT_MESSAGE(eToggle)
+
+// Dialog element events.
+NS_EVENT_MESSAGE(eClose)
+
+// Marquee element events.
+NS_EVENT_MESSAGE(eMarqueeBounce)
+NS_EVENT_MESSAGE(eMarqueeStart)
+NS_EVENT_MESSAGE(eMarqueeFinish)
+
+#ifdef UNDEF_NS_EVENT_MESSAGE_FIRST_LAST
+# undef UNDEF_NS_EVENT_MESSAGE_FIRST_LAST
+# undef NS_EVENT_MESSAGE_FIRST_LAST
+#endif
diff --git a/widget/FontRange.h b/widget/FontRange.h
new file mode 100644
index 0000000000..e5ee079a62
--- /dev/null
+++ b/widget/FontRange.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_FontRange_h_
+#define mozilla_FontRange_h_
+
+#include "gfxTypes.h" // for gfxFloat
+
+namespace mozilla {
+
+struct FontRange {
+ FontRange() : mStartOffset(0), mFontSize(0) {}
+
+ int32_t mStartOffset;
+ nsString mFontName;
+ gfxFloat mFontSize; // in device pixels
+};
+
+} // namespace mozilla
+
+#endif // mozilla_FontRange_h_
diff --git a/widget/GfxDriverInfo.cpp b/widget/GfxDriverInfo.cpp
new file mode 100644
index 0000000000..09cf307c21
--- /dev/null
+++ b/widget/GfxDriverInfo.cpp
@@ -0,0 +1,1032 @@
+/* -*- 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 "GfxDriverInfo.h"
+
+#include "nsIGfxInfo.h"
+#include "nsTArray.h"
+#include "nsUnicharUtils.h"
+
+using namespace mozilla::widget;
+
+int32_t GfxDriverInfo::allFeatures = 0;
+uint64_t GfxDriverInfo::allDriverVersions = ~(uint64_t(0));
+
+GfxDeviceFamily*
+ GfxDriverInfo::sDeviceFamilies[static_cast<size_t>(DeviceFamily::Max)];
+nsAString* GfxDriverInfo::sDesktopEnvironment[static_cast<size_t>(
+ DesktopEnvironment::Max)];
+nsAString*
+ GfxDriverInfo::sWindowProtocol[static_cast<size_t>(WindowProtocol::Max)];
+nsAString*
+ GfxDriverInfo::sDeviceVendors[static_cast<size_t>(DeviceVendor::Max)];
+nsAString*
+ GfxDriverInfo::sDriverVendors[static_cast<size_t>(DriverVendor::Max)];
+
+GfxDriverInfo::GfxDriverInfo()
+ : mOperatingSystem(OperatingSystem::Unknown),
+ mOperatingSystemVersion(0),
+ mScreen(ScreenSizeStatus::All),
+ mBattery(BatteryStatus::All),
+ mDesktopEnvironment(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::All)),
+ mWindowProtocol(GfxDriverInfo::GetWindowProtocol(WindowProtocol::All)),
+ mAdapterVendor(GfxDriverInfo::GetDeviceVendor(DeviceFamily::All)),
+ mDriverVendor(GfxDriverInfo::GetDriverVendor(DriverVendor::All)),
+ mDevices(GfxDriverInfo::GetDeviceFamily(DeviceFamily::All)),
+ mDeleteDevices(false),
+ mFeature(allFeatures),
+ mFeatureStatus(nsIGfxInfo::FEATURE_STATUS_OK),
+ mComparisonOp(DRIVER_COMPARISON_IGNORED),
+ mDriverVersion(0),
+ mDriverVersionMax(0),
+ mSuggestedVersion(nullptr),
+ mRuleId(nullptr),
+ mGpu2(false) {}
+
+GfxDriverInfo::GfxDriverInfo(
+ OperatingSystem os, ScreenSizeStatus screen, BatteryStatus battery,
+ const nsAString& desktopEnv, const nsAString& windowProtocol,
+ const nsAString& vendor, const nsAString& driverVendor,
+ GfxDeviceFamily* devices, int32_t feature, int32_t featureStatus,
+ VersionComparisonOp op, uint64_t driverVersion, const char* ruleId,
+ const char* suggestedVersion /* = nullptr */, bool ownDevices /* = false */,
+ bool gpu2 /* = false */)
+ : mOperatingSystem(os),
+ mOperatingSystemVersion(0),
+ mScreen(screen),
+ mBattery(battery),
+ mDesktopEnvironment(desktopEnv),
+ mWindowProtocol(windowProtocol),
+ mAdapterVendor(vendor),
+ mDriverVendor(driverVendor),
+ mDevices(devices),
+ mDeleteDevices(ownDevices),
+ mFeature(feature),
+ mFeatureStatus(featureStatus),
+ mComparisonOp(op),
+ mDriverVersion(driverVersion),
+ mDriverVersionMax(0),
+ mSuggestedVersion(suggestedVersion),
+ mRuleId(ruleId),
+ mGpu2(gpu2) {}
+
+GfxDriverInfo::GfxDriverInfo(const GfxDriverInfo& aOrig)
+ : mOperatingSystem(aOrig.mOperatingSystem),
+ mOperatingSystemVersion(aOrig.mOperatingSystemVersion),
+ mScreen(aOrig.mScreen),
+ mBattery(aOrig.mBattery),
+ mDesktopEnvironment(aOrig.mDesktopEnvironment),
+ mWindowProtocol(aOrig.mWindowProtocol),
+ mAdapterVendor(aOrig.mAdapterVendor),
+ mDriverVendor(aOrig.mDriverVendor),
+ mFeature(aOrig.mFeature),
+ mFeatureStatus(aOrig.mFeatureStatus),
+ mComparisonOp(aOrig.mComparisonOp),
+ mDriverVersion(aOrig.mDriverVersion),
+ mDriverVersionMax(aOrig.mDriverVersionMax),
+ mSuggestedVersion(aOrig.mSuggestedVersion),
+ mRuleId(aOrig.mRuleId),
+ mGpu2(aOrig.mGpu2) {
+ // If we're managing the lifetime of the device family, we have to make a
+ // copy of the original's device family.
+ if (aOrig.mDeleteDevices && aOrig.mDevices) {
+ GfxDeviceFamily* devices = new GfxDeviceFamily;
+ *devices = *aOrig.mDevices;
+ mDevices = devices;
+ } else {
+ mDevices = aOrig.mDevices;
+ }
+
+ mDeleteDevices = aOrig.mDeleteDevices;
+}
+
+GfxDriverInfo::~GfxDriverInfo() {
+ if (mDeleteDevices) {
+ delete mDevices;
+ }
+}
+
+void GfxDeviceFamily::Append(const nsAString& aDeviceId) {
+ mIds.AppendElement(aDeviceId);
+}
+
+void GfxDeviceFamily::AppendRange(int32_t aBeginDeviceId,
+ int32_t aEndDeviceId) {
+ mRanges.AppendElement(
+ GfxDeviceFamily::DeviceRange{aBeginDeviceId, aEndDeviceId});
+}
+
+nsresult GfxDeviceFamily::Contains(nsAString& aDeviceId) const {
+ for (const auto& id : mIds) {
+ if (id.Equals(aDeviceId, nsCaseInsensitiveStringComparator)) {
+ return NS_OK;
+ }
+ }
+
+ if (mRanges.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult valid = NS_OK;
+ int32_t deviceId = aDeviceId.ToInteger(&valid, 16);
+ if (valid != NS_OK) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ for (const auto& range : mRanges) {
+ if (deviceId >= range.mBegin && deviceId <= range.mEnd) {
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+// Macros for appending a device to the DeviceFamily.
+#define APPEND_DEVICE(device) APPEND_DEVICE2(#device)
+#define APPEND_DEVICE2(device) \
+ deviceFamily->Append(NS_LITERAL_STRING_FROM_CSTRING(device))
+#define APPEND_RANGE(start, end) deviceFamily->AppendRange(start, end)
+
+const GfxDeviceFamily* GfxDriverInfo::GetDeviceFamily(DeviceFamily id) {
+ if (id >= DeviceFamily::Max) {
+ MOZ_ASSERT_UNREACHABLE("DeviceFamily id is out of range");
+ return nullptr;
+ }
+
+ // All of these have no specific device ID filtering.
+ switch (id) {
+ case DeviceFamily::All:
+ case DeviceFamily::IntelAll:
+ case DeviceFamily::NvidiaAll:
+ case DeviceFamily::AtiAll:
+ case DeviceFamily::MicrosoftAll:
+ case DeviceFamily::ParallelsAll:
+ case DeviceFamily::QualcommAll:
+ case DeviceFamily::AppleAll:
+ case DeviceFamily::AmazonAll:
+ return nullptr;
+ default:
+ break;
+ }
+
+ // If it already exists, we must have processed it once, so return it now.
+ auto idx = static_cast<size_t>(id);
+ if (sDeviceFamilies[idx]) {
+ return sDeviceFamilies[idx];
+ }
+
+ sDeviceFamilies[idx] = new GfxDeviceFamily;
+ GfxDeviceFamily* deviceFamily = sDeviceFamilies[idx];
+
+ switch (id) {
+ case DeviceFamily::IntelGMA500:
+ APPEND_DEVICE(0x8108); /* IntelGMA500_1 */
+ APPEND_DEVICE(0x8109); /* IntelGMA500_2 */
+ break;
+ case DeviceFamily::IntelGMA900:
+ APPEND_DEVICE(0x2582); /* IntelGMA900_1 */
+ APPEND_DEVICE(0x2782); /* IntelGMA900_2 */
+ APPEND_DEVICE(0x2592); /* IntelGMA900_3 */
+ APPEND_DEVICE(0x2792); /* IntelGMA900_4 */
+ break;
+ case DeviceFamily::IntelGMA950:
+ APPEND_DEVICE(0x2772); /* Intel945G_1 */
+ APPEND_DEVICE(0x2776); /* Intel945G_2 */
+ APPEND_DEVICE(0x27a2); /* Intel945_1 */
+ APPEND_DEVICE(0x27a6); /* Intel945_2 */
+ APPEND_DEVICE(0x27ae); /* Intel945_3 */
+ break;
+ case DeviceFamily::IntelGMA3150:
+ APPEND_DEVICE(0xa001); /* IntelGMA3150_Nettop_1 */
+ APPEND_DEVICE(0xa002); /* IntelGMA3150_Nettop_2 */
+ APPEND_DEVICE(0xa011); /* IntelGMA3150_Netbook_1 */
+ APPEND_DEVICE(0xa012); /* IntelGMA3150_Netbook_2 */
+ break;
+ case DeviceFamily::IntelGMAX3000:
+ APPEND_DEVICE(0x2972); /* Intel946GZ_1 */
+ APPEND_DEVICE(0x2973); /* Intel946GZ_2 */
+ APPEND_DEVICE(0x2982); /* IntelG35_1 */
+ APPEND_DEVICE(0x2983); /* IntelG35_2 */
+ APPEND_DEVICE(0x2992); /* IntelQ965_1 */
+ APPEND_DEVICE(0x2993); /* IntelQ965_2 */
+ APPEND_DEVICE(0x29a2); /* IntelG965_1 */
+ APPEND_DEVICE(0x29a3); /* IntelG965_2 */
+ APPEND_DEVICE(0x29b2); /* IntelQ35_1 */
+ APPEND_DEVICE(0x29b3); /* IntelQ35_2 */
+ APPEND_DEVICE(0x29c2); /* IntelG33_1 */
+ APPEND_DEVICE(0x29c3); /* IntelG33_2 */
+ APPEND_DEVICE(0x29d2); /* IntelQ33_1 */
+ APPEND_DEVICE(0x29d3); /* IntelQ33_2 */
+ APPEND_DEVICE(0x2a02); /* IntelGL960_1 */
+ APPEND_DEVICE(0x2a03); /* IntelGL960_2 */
+ APPEND_DEVICE(0x2a12); /* IntelGM965_1 */
+ APPEND_DEVICE(0x2a13); /* IntelGM965_2 */
+ break;
+ case DeviceFamily::IntelGMAX4500HD:
+ APPEND_DEVICE(0x2a42); /* IntelGMA4500MHD_1 */
+ APPEND_DEVICE(0x2a43); /* IntelGMA4500MHD_2 */
+ APPEND_DEVICE(0x2e42); /* IntelB43_1 */
+ APPEND_DEVICE(0x2e43); /* IntelB43_2 */
+ APPEND_DEVICE(0x2e92); /* IntelB43_3 */
+ APPEND_DEVICE(0x2e93); /* IntelB43_4 */
+ APPEND_DEVICE(0x2e32); /* IntelG41_1 */
+ APPEND_DEVICE(0x2e33); /* IntelG41_2 */
+ APPEND_DEVICE(0x2e22); /* IntelG45_1 */
+ APPEND_DEVICE(0x2e23); /* IntelG45_2 */
+ APPEND_DEVICE(0x2e12); /* IntelQ45_1 */
+ APPEND_DEVICE(0x2e13); /* IntelQ45_2 */
+ break;
+ case DeviceFamily::IntelHDGraphicsToIvyBridge:
+ APPEND_DEVICE(0x015A); /* IntelIvyBridge_GT1_1 (HD Graphics) */
+ // clang-format off
+ APPEND_DEVICE(0x0152); /* IntelIvyBridge_GT1_2 (HD Graphics 2500, desktop) */
+ APPEND_DEVICE(0x0162); /* IntelIvyBridge_GT2_1 (HD Graphics 4000, desktop) */
+ APPEND_DEVICE(0x0166); /* IntelIvyBridge_GT2_2 (HD Graphics 4000, mobile) */
+ APPEND_DEVICE(0x016A); /* IntelIvyBridge_GT2_3 (HD Graphics P4000, workstation) */
+ // clang-format on
+ [[fallthrough]];
+ case DeviceFamily::IntelHDGraphicsToSandyBridge:
+ APPEND_DEVICE(0x0042); /* IntelHDGraphics */
+ APPEND_DEVICE(0x0046); /* IntelMobileHDGraphics */
+ APPEND_DEVICE(0x0102); /* IntelSandyBridge_1 */
+ APPEND_DEVICE(0x0106); /* IntelSandyBridge_2 */
+ APPEND_DEVICE(0x0112); /* IntelSandyBridge_3 */
+ APPEND_DEVICE(0x0116); /* IntelSandyBridge_4 */
+ APPEND_DEVICE(0x0122); /* IntelSandyBridge_5 */
+ APPEND_DEVICE(0x0126); /* IntelSandyBridge_6 */
+ APPEND_DEVICE(0x010a); /* IntelSandyBridge_7 */
+ break;
+ case DeviceFamily::IntelHaswell:
+ APPEND_DEVICE(0x0402); /* IntelHaswell_GT1_1 */
+ APPEND_DEVICE(0x0406); /* IntelHaswell_GT1_2 */
+ APPEND_DEVICE(0x040A); /* IntelHaswell_GT1_3 */
+ APPEND_DEVICE(0x040B); /* IntelHaswell_GT1_4 */
+ APPEND_DEVICE(0x040E); /* IntelHaswell_GT1_5 */
+ APPEND_DEVICE(0x0A02); /* IntelHaswell_GT1_6 */
+ APPEND_DEVICE(0x0A06); /* IntelHaswell_GT1_7 */
+ APPEND_DEVICE(0x0A0A); /* IntelHaswell_GT1_8 */
+ APPEND_DEVICE(0x0A0B); /* IntelHaswell_GT1_9 */
+ APPEND_DEVICE(0x0A0E); /* IntelHaswell_GT1_10 */
+ APPEND_DEVICE(0x0412); /* IntelHaswell_GT2_1 */
+ APPEND_DEVICE(0x0416); /* IntelHaswell_GT2_2 */
+ APPEND_DEVICE(0x041A); /* IntelHaswell_GT2_3 */
+ APPEND_DEVICE(0x041B); /* IntelHaswell_GT2_4 */
+ APPEND_DEVICE(0x041E); /* IntelHaswell_GT2_5 */
+ APPEND_DEVICE(0x0A12); /* IntelHaswell_GT2_6 */
+ APPEND_DEVICE(0x0A16); /* IntelHaswell_GT2_7 */
+ APPEND_DEVICE(0x0A1A); /* IntelHaswell_GT2_8 */
+ APPEND_DEVICE(0x0A1B); /* IntelHaswell_GT2_9 */
+ APPEND_DEVICE(0x0A1E); /* IntelHaswell_GT2_10 */
+ APPEND_DEVICE(0x0422); /* IntelHaswell_GT3_1 */
+ APPEND_DEVICE(0x0426); /* IntelHaswell_GT3_2 */
+ APPEND_DEVICE(0x042A); /* IntelHaswell_GT3_3 */
+ APPEND_DEVICE(0x042B); /* IntelHaswell_GT3_4 */
+ APPEND_DEVICE(0x042E); /* IntelHaswell_GT3_5 */
+ APPEND_DEVICE(0x0A22); /* IntelHaswell_GT3_6 */
+ APPEND_DEVICE(0x0A26); /* IntelHaswell_GT3_7 */
+ APPEND_DEVICE(0x0A2A); /* IntelHaswell_GT3_8 */
+ APPEND_DEVICE(0x0A2B); /* IntelHaswell_GT3_9 */
+ APPEND_DEVICE(0x0A2E); /* IntelHaswell_GT3_10 */
+ APPEND_DEVICE(0x0D22); /* IntelHaswell_GT3e_1 */
+ APPEND_DEVICE(0x0D26); /* IntelHaswell_GT3e_2 */
+ APPEND_DEVICE(0x0D2A); /* IntelHaswell_GT3e_3 */
+ APPEND_DEVICE(0x0D2B); /* IntelHaswell_GT3e_4 */
+ APPEND_DEVICE(0x0D2E); /* IntelHaswell_GT3e_5 */
+ break;
+ case DeviceFamily::IntelSandyBridge:
+ APPEND_DEVICE(0x0102);
+ APPEND_DEVICE(0x0106);
+ APPEND_DEVICE(0x010a);
+ APPEND_DEVICE(0x0112);
+ APPEND_DEVICE(0x0116);
+ APPEND_DEVICE(0x0122);
+ APPEND_DEVICE(0x0126);
+ break;
+ case DeviceFamily::IntelHD520:
+ APPEND_DEVICE(0x1916);
+ break;
+ case DeviceFamily::IntelMobileHDGraphics:
+ APPEND_DEVICE(0x0046); /* IntelMobileHDGraphics */
+ break;
+ case DeviceFamily::NvidiaBlockD3D9Layers:
+ // Glitches whilst scrolling (see bugs 612007, 644787, 645872)
+ APPEND_DEVICE(0x00f3); /* NV43 [GeForce 6200 (TM)] */
+ APPEND_DEVICE(0x0146); /* NV43 [Geforce Go 6600TE/6200TE (TM)] */
+ APPEND_DEVICE(0x014f); /* NV43 [GeForce 6200 (TM)] */
+ APPEND_DEVICE(0x0161); /* NV44 [GeForce 6200 TurboCache (TM)] */
+ APPEND_DEVICE(0x0162); /* NV44 [GeForce 6200SE TurboCache (TM)] */
+ APPEND_DEVICE(0x0163); /* NV44 [GeForce 6200 LE (TM)] */
+ APPEND_DEVICE(0x0164); /* NV44 [GeForce Go 6200 (TM)] */
+ APPEND_DEVICE(0x0167); /* NV43 [GeForce Go 6200/6400 (TM)] */
+ APPEND_DEVICE(0x0168); /* NV43 [GeForce Go 6200/6400 (TM)] */
+ APPEND_DEVICE(0x0169); /* NV44 [GeForce 6250 (TM)] */
+ APPEND_DEVICE(0x0222); /* NV44 [GeForce 6200 A-LE (TM)] */
+ APPEND_DEVICE(0x0240); /* C51PV [GeForce 6150 (TM)] */
+ APPEND_DEVICE(0x0241); /* C51 [GeForce 6150 LE (TM)] */
+ APPEND_DEVICE(0x0244); /* C51 [Geforce Go 6150 (TM)] */
+ APPEND_DEVICE(0x0245); /* C51 [Quadro NVS 210S/GeForce 6150LE (TM)] */
+ APPEND_DEVICE(0x0247); /* C51 [GeForce Go 6100 (TM)] */
+ APPEND_DEVICE(0x03d0); /* C61 [GeForce 6150SE nForce 430 (TM)] */
+ APPEND_DEVICE(0x03d1); /* C61 [GeForce 6100 nForce 405 (TM)] */
+ APPEND_DEVICE(0x03d2); /* C61 [GeForce 6100 nForce 400 (TM)] */
+ APPEND_DEVICE(0x03d5); /* C61 [GeForce 6100 nForce 420 (TM)] */
+ break;
+ case DeviceFamily::RadeonX1000:
+ // This list is from the ATIRadeonX1000.kext Info.plist
+ APPEND_DEVICE(0x7187);
+ APPEND_DEVICE(0x7210);
+ APPEND_DEVICE(0x71de);
+ APPEND_DEVICE(0x7146);
+ APPEND_DEVICE(0x7142);
+ APPEND_DEVICE(0x7109);
+ APPEND_DEVICE(0x71c5);
+ APPEND_DEVICE(0x71c0);
+ APPEND_DEVICE(0x7240);
+ APPEND_DEVICE(0x7249);
+ APPEND_DEVICE(0x7291);
+ break;
+ case DeviceFamily::RadeonCaicos:
+ APPEND_DEVICE(0x6766);
+ APPEND_DEVICE(0x6767);
+ APPEND_DEVICE(0x6768);
+ APPEND_DEVICE(0x6770);
+ APPEND_DEVICE(0x6771);
+ APPEND_DEVICE(0x6772);
+ APPEND_DEVICE(0x6778);
+ APPEND_DEVICE(0x6779);
+ APPEND_DEVICE(0x677b);
+ break;
+ case DeviceFamily::Geforce7300GT:
+ APPEND_DEVICE(0x0393);
+ break;
+ case DeviceFamily::Nvidia310M:
+ APPEND_DEVICE(0x0A70);
+ break;
+ case DeviceFamily::Nvidia8800GTS:
+ APPEND_DEVICE(0x0193);
+ break;
+ case DeviceFamily::Bug1137716:
+ APPEND_DEVICE(0x0a29);
+ APPEND_DEVICE(0x0a2b);
+ APPEND_DEVICE(0x0a2d);
+ APPEND_DEVICE(0x0a35);
+ APPEND_DEVICE(0x0a6c);
+ APPEND_DEVICE(0x0a70);
+ APPEND_DEVICE(0x0a72);
+ APPEND_DEVICE(0x0a7a);
+ APPEND_DEVICE(0x0caf);
+ APPEND_DEVICE(0x0dd2);
+ APPEND_DEVICE(0x0dd3);
+ // GF180M ids
+ APPEND_DEVICE(0x0de3);
+ APPEND_DEVICE(0x0de8);
+ APPEND_DEVICE(0x0de9);
+ APPEND_DEVICE(0x0dea);
+ APPEND_DEVICE(0x0deb);
+ APPEND_DEVICE(0x0dec);
+ APPEND_DEVICE(0x0ded);
+ APPEND_DEVICE(0x0dee);
+ APPEND_DEVICE(0x0def);
+ APPEND_DEVICE(0x0df0);
+ APPEND_DEVICE(0x0df1);
+ APPEND_DEVICE(0x0df2);
+ APPEND_DEVICE(0x0df3);
+ APPEND_DEVICE(0x0df4);
+ APPEND_DEVICE(0x0df5);
+ APPEND_DEVICE(0x0df6);
+ APPEND_DEVICE(0x0df7);
+ APPEND_DEVICE(0x1050);
+ APPEND_DEVICE(0x1051);
+ APPEND_DEVICE(0x1052);
+ APPEND_DEVICE(0x1054);
+ APPEND_DEVICE(0x1055);
+ break;
+ case DeviceFamily::Bug1116812:
+ APPEND_DEVICE(0x2e32);
+ APPEND_DEVICE(0x2a02);
+ break;
+ case DeviceFamily::Bug1155608:
+ APPEND_DEVICE(0x2e22); /* IntelG45_1 */
+ break;
+ case DeviceFamily::Bug1447141:
+ APPEND_DEVICE(0x9991);
+ APPEND_DEVICE(0x9993);
+ APPEND_DEVICE(0x9996);
+ APPEND_DEVICE(0x9998);
+ APPEND_DEVICE(0x9901);
+ APPEND_DEVICE(0x990b);
+ break;
+ case DeviceFamily::Bug1207665:
+ APPEND_DEVICE(0xa001); /* Intel Media Accelerator 3150 */
+ APPEND_DEVICE(0xa002);
+ APPEND_DEVICE(0xa011);
+ APPEND_DEVICE(0xa012);
+ break;
+ case DeviceFamily::AmdR600:
+ // AMD R600 generation GPUs
+ // R600
+ APPEND_RANGE(0x9400, 0x9403);
+ APPEND_DEVICE(0x9405);
+ APPEND_RANGE(0x940a, 0x940b);
+ APPEND_DEVICE(0x940f);
+ // RV610
+ APPEND_RANGE(0x94c0, 0x94c1);
+ APPEND_RANGE(0x94c3, 0x94c9);
+ APPEND_RANGE(0x94cb, 0x94cd);
+ // RV630
+ APPEND_RANGE(0x9580, 0x9581);
+ APPEND_DEVICE(0x9583);
+ APPEND_RANGE(0x9586, 0x958f);
+ // RV670
+ APPEND_RANGE(0x9500, 0x9501);
+ APPEND_RANGE(0x9504, 0x9509);
+ APPEND_DEVICE(0x950f);
+ APPEND_DEVICE(0x9511);
+ APPEND_DEVICE(0x9515);
+ APPEND_DEVICE(0x9517);
+ APPEND_DEVICE(0x9519);
+ // RV620
+ APPEND_DEVICE(0x95c0);
+ APPEND_DEVICE(0x95c2);
+ APPEND_RANGE(0x95c4, 0x95c7);
+ APPEND_DEVICE(0x95c9);
+ APPEND_RANGE(0x95cc, 0x95cf);
+ // RV635
+ APPEND_RANGE(0x9590, 0x9591);
+ APPEND_DEVICE(0x9593);
+ APPEND_RANGE(0x9595, 0x9599);
+ APPEND_DEVICE(0x959b);
+ // RS780
+ APPEND_RANGE(0x9610, 0x9616);
+ // RS880
+ APPEND_RANGE(0x9710, 0x9715);
+ break;
+ case DeviceFamily::NvidiaBlockWebRender:
+ /* GT218 */
+ APPEND_DEVICE(0x0a60);
+ APPEND_DEVICE(0x0a62);
+ APPEND_DEVICE(0x0a63);
+ APPEND_DEVICE(0x0a64);
+ APPEND_DEVICE(0x0a65);
+ APPEND_DEVICE(0x0a66);
+ APPEND_DEVICE(0x0a67);
+ APPEND_DEVICE(0x0a7b);
+ APPEND_DEVICE(0x10c0);
+ APPEND_DEVICE(0x10c3);
+ APPEND_DEVICE(0x10c5);
+ APPEND_DEVICE(0x10d8);
+ /* GT218M */
+ APPEND_DEVICE(0x0a68);
+ APPEND_DEVICE(0x0a69);
+ APPEND_DEVICE(0x0a6a);
+ APPEND_DEVICE(0x0a6c);
+ APPEND_DEVICE(0x0a6e);
+ APPEND_DEVICE(0x0a6f);
+ APPEND_DEVICE(0x0a70);
+ APPEND_DEVICE(0x0a71);
+ APPEND_DEVICE(0x0a72);
+ APPEND_DEVICE(0x0a73);
+ APPEND_DEVICE(0x0a74);
+ APPEND_DEVICE(0x0a75);
+ APPEND_DEVICE(0x0a76);
+ APPEND_DEVICE(0x0a7a);
+ /* GT218GL */
+ APPEND_DEVICE(0x0a78);
+ /* GT218GLM */
+ APPEND_DEVICE(0x0a7c);
+ break;
+ case DeviceFamily::NvidiaRolloutWebRender:
+ APPEND_RANGE(0x0400, 0x04ff);
+ APPEND_RANGE(0x05e0, 0x05ff);
+ APPEND_RANGE(0x0600, INT32_MAX);
+ APPEND_RANGE(0x06c0, INT32_MAX);
+ break;
+ case DeviceFamily::IntelRolloutWebRender:
+#ifdef EARLY_BETA_OR_EARLIER
+ // gen5 (ironlake)
+ APPEND_DEVICE(0x0042);
+ APPEND_DEVICE(0x0046);
+
+ // cherryview
+ APPEND_DEVICE(0x22b0);
+ APPEND_DEVICE(0x22b1);
+ APPEND_DEVICE(0x22b2);
+ APPEND_DEVICE(0x22b3);
+#endif
+
+ [[fallthrough]];
+ case DeviceFamily::IntelModernRolloutWebRender:
+ // sandybridge gen6 gt1
+ APPEND_DEVICE(0x0102);
+ APPEND_DEVICE(0x0106);
+ APPEND_DEVICE(0x010a);
+
+ // sandybridge gen6 gt2
+ APPEND_DEVICE(0x0112);
+ APPEND_DEVICE(0x0116);
+ APPEND_DEVICE(0x0122);
+ APPEND_DEVICE(0x0126);
+
+#ifdef EARLY_BETA_OR_EARLIER
+ // ivybridge gen7 baytrail
+ APPEND_DEVICE(0x0f30);
+ APPEND_DEVICE(0x0f31);
+ APPEND_DEVICE(0x0f33);
+ APPEND_DEVICE(0x0155);
+ APPEND_DEVICE(0x0157);
+#endif
+
+ // ivybridge gen7 gt1
+ APPEND_DEVICE(0x0152);
+ APPEND_DEVICE(0x0156);
+ APPEND_DEVICE(0x015a);
+
+ // ivybridge gen7 gt2
+ APPEND_DEVICE(0x0162);
+ APPEND_DEVICE(0x0166);
+ APPEND_DEVICE(0x016a);
+
+ // haswell gen7.5 gt1
+ APPEND_DEVICE(0x0402);
+ APPEND_DEVICE(0x0406);
+ APPEND_DEVICE(0x040a);
+ APPEND_DEVICE(0x040b);
+ APPEND_DEVICE(0x040e);
+ APPEND_DEVICE(0x0a02);
+ APPEND_DEVICE(0x0a06);
+ APPEND_DEVICE(0x0a0a);
+ APPEND_DEVICE(0x0a0b);
+ APPEND_DEVICE(0x0a0e);
+ APPEND_DEVICE(0x0c02);
+ APPEND_DEVICE(0x0c06);
+ APPEND_DEVICE(0x0c0c);
+ APPEND_DEVICE(0x0c0b);
+ APPEND_DEVICE(0x0c0e);
+ APPEND_DEVICE(0x0d02);
+ APPEND_DEVICE(0x0d06);
+ APPEND_DEVICE(0x0d0a);
+ APPEND_DEVICE(0x0d0b);
+ APPEND_DEVICE(0x0d0e);
+
+ // haswell gen7.5 gt2
+ APPEND_DEVICE(0x0412);
+ APPEND_DEVICE(0x0416);
+ APPEND_DEVICE(0x041a);
+ APPEND_DEVICE(0x041b);
+ APPEND_DEVICE(0x041e);
+ APPEND_DEVICE(0x0a12);
+ APPEND_DEVICE(0x0a16);
+ APPEND_DEVICE(0x0a1a);
+ APPEND_DEVICE(0x0a1b);
+ APPEND_DEVICE(0x0a1e);
+
+ // haswell gen7.5 gt3
+ APPEND_DEVICE(0x0422);
+ APPEND_DEVICE(0x0426);
+ APPEND_DEVICE(0x042a);
+ APPEND_DEVICE(0x042b);
+ APPEND_DEVICE(0x042e);
+ APPEND_DEVICE(0x0a22);
+ APPEND_DEVICE(0x0a26);
+ APPEND_DEVICE(0x0a0a);
+ APPEND_DEVICE(0x0a1a);
+ APPEND_DEVICE(0x0a2a);
+ APPEND_DEVICE(0x0a2b);
+ APPEND_DEVICE(0x0a2e);
+ APPEND_DEVICE(0x0c22);
+ APPEND_DEVICE(0x0c26);
+ APPEND_DEVICE(0x0c2c);
+ APPEND_DEVICE(0x0c2b);
+ APPEND_DEVICE(0x0c2e);
+ APPEND_DEVICE(0x0d22);
+ APPEND_DEVICE(0x0d26);
+ APPEND_DEVICE(0x0d2b);
+ APPEND_DEVICE(0x0d2e);
+
+#ifdef EARLY_BETA_OR_EARLIER
+ // broxton (apollolake)
+ APPEND_DEVICE(0x0a84);
+ APPEND_DEVICE(0x1a84);
+ APPEND_DEVICE(0x1a85);
+ APPEND_DEVICE(0x5a84);
+ APPEND_DEVICE(0x5a85);
+
+ // geminilake
+ APPEND_DEVICE(0x3184);
+ APPEND_DEVICE(0x3185);
+#endif
+ // broadwell gt1 (gen8)
+ APPEND_DEVICE(0x1602);
+ APPEND_DEVICE(0x1606);
+ APPEND_DEVICE(0x160a);
+ APPEND_DEVICE(0x160b);
+ APPEND_DEVICE(0x160d);
+ APPEND_DEVICE(0x160e);
+
+ // broadwell gt2+ (gen8)
+ APPEND_DEVICE(0x1612);
+ APPEND_DEVICE(0x1616);
+ APPEND_DEVICE(0x161a);
+ APPEND_DEVICE(0x161b);
+ APPEND_DEVICE(0x161d);
+ APPEND_DEVICE(0x161e);
+ APPEND_DEVICE(0x1622);
+ APPEND_DEVICE(0x1626);
+ APPEND_DEVICE(0x162a);
+ APPEND_DEVICE(0x162b);
+ APPEND_DEVICE(0x162d);
+ APPEND_DEVICE(0x162e);
+ APPEND_DEVICE(0x1632);
+ APPEND_DEVICE(0x1636);
+ APPEND_DEVICE(0x163a);
+ APPEND_DEVICE(0x163b);
+ APPEND_DEVICE(0x163d);
+ APPEND_DEVICE(0x163e);
+
+ // skylake gt1
+ APPEND_DEVICE(0x1902);
+ APPEND_DEVICE(0x1906);
+ APPEND_DEVICE(0x190a);
+ APPEND_DEVICE(0x190e);
+
+ // skylake gt2
+ APPEND_DEVICE(0x1912);
+ APPEND_DEVICE(0x1913);
+ APPEND_DEVICE(0x1915);
+ APPEND_DEVICE(0x1916);
+ APPEND_DEVICE(0x1917);
+ APPEND_DEVICE(0x191a);
+ APPEND_DEVICE(0x191b);
+ APPEND_DEVICE(0x191d);
+ APPEND_DEVICE(0x191e);
+ APPEND_DEVICE(0x1921);
+
+ // skylake gt3
+ APPEND_DEVICE(0x1923);
+ APPEND_DEVICE(0x1926);
+ APPEND_DEVICE(0x1927);
+ APPEND_DEVICE(0x192b);
+
+ // skylake gt4
+ APPEND_DEVICE(0x1932);
+ APPEND_DEVICE(0x193a);
+ APPEND_DEVICE(0x193b);
+ APPEND_DEVICE(0x193d);
+
+ // kabylake gt1
+ APPEND_DEVICE(0x5902);
+ APPEND_DEVICE(0x5906);
+ APPEND_DEVICE(0x5908);
+ APPEND_DEVICE(0x590a);
+ APPEND_DEVICE(0x590b);
+ APPEND_DEVICE(0x590e);
+
+ // kabylake gt1.5
+ APPEND_DEVICE(0x5913);
+ APPEND_DEVICE(0x5915);
+ APPEND_DEVICE(0x5917);
+
+ // kabylake gt2
+ APPEND_DEVICE(0x5912);
+ APPEND_DEVICE(0x5916);
+ APPEND_DEVICE(0x591a);
+ APPEND_DEVICE(0x591b);
+ APPEND_DEVICE(0x591c);
+ APPEND_DEVICE(0x591d);
+ APPEND_DEVICE(0x591e);
+ APPEND_DEVICE(0x5921);
+ APPEND_DEVICE(0x5926);
+ APPEND_DEVICE(0x5923);
+ APPEND_DEVICE(0x5927);
+ APPEND_DEVICE(0x593b);
+ APPEND_DEVICE(0x87c0);
+
+ // coffeelake gt1
+ APPEND_DEVICE(0x3e90);
+ APPEND_DEVICE(0x3e93);
+ APPEND_DEVICE(0x3e99);
+ APPEND_DEVICE(0x3e9c);
+ APPEND_DEVICE(0x3ea1);
+ APPEND_DEVICE(0x3ea4);
+ APPEND_DEVICE(0x9b21);
+ APPEND_DEVICE(0x9ba0);
+ APPEND_DEVICE(0x9ba2);
+ APPEND_DEVICE(0x9ba4);
+ APPEND_DEVICE(0x9ba5);
+ APPEND_DEVICE(0x9ba8);
+ APPEND_DEVICE(0x9baa);
+ APPEND_DEVICE(0x9bab);
+ APPEND_DEVICE(0x9bac);
+
+ // coffeelake gt2+
+ APPEND_RANGE(0x3e91, 0x3e92);
+ APPEND_DEVICE(0x3e94);
+ APPEND_DEVICE(0x3e96);
+ APPEND_DEVICE(0x3e98);
+ APPEND_RANGE(0x3e9a, 0x3e9b);
+ APPEND_DEVICE(0x3ea0);
+ APPEND_DEVICE(0x3ea2);
+ APPEND_RANGE(0x3ea5, 0x3ea9);
+ APPEND_DEVICE(0x87ca);
+ APPEND_DEVICE(0x9b41);
+ APPEND_DEVICE(0x9bc0);
+ APPEND_DEVICE(0x9bc2);
+ APPEND_RANGE(0x9bc4, 0x9bc5);
+ APPEND_DEVICE(0x9bc8);
+ APPEND_RANGE(0x9bca, 0x9bcc);
+
+ // icelake gt1,gt1.5,gt2
+ APPEND_RANGE(0x8a50, 0x8a5d);
+
+ // rocketlake
+ APPEND_RANGE(0x4c8a, 0x4c9a);
+
+ // tigerlake
+ APPEND_RANGE(0x9a40, 0x9af8);
+ break;
+ case DeviceFamily::AtiRolloutWebRender:
+ APPEND_RANGE(0x6600, 0x66af);
+ APPEND_RANGE(0x6700, 0x671f);
+ APPEND_RANGE(0x6780, 0x683f);
+ APPEND_RANGE(0x6860, 0x687f);
+ APPEND_RANGE(0x6900, 0x69ff);
+ APPEND_DEVICE(0x6fdf);
+ APPEND_DEVICE(0x7300);
+ APPEND_RANGE(0x7310, 0x738e);
+ APPEND_RANGE(0x9830, 0x986f);
+ APPEND_RANGE(0x9900, 0x99ff);
+ // Raven
+ APPEND_DEVICE(0x15dd);
+ APPEND_DEVICE(0x15d8);
+ // Renoir
+ APPEND_DEVICE(0x1636);
+
+ // Evergreen
+ APPEND_RANGE(0x6840, 0x684b);
+ APPEND_RANGE(0x6850, 0x685f);
+ APPEND_RANGE(0x6880, 0x68ff);
+ APPEND_RANGE(0x9800, 0x980a);
+ APPEND_RANGE(0x9640, 0x964f);
+ APPEND_RANGE(0x6720, 0x677f);
+
+ // Stoney
+ APPEND_DEVICE(0x98e4);
+
+ // Carrizo
+ APPEND_RANGE(0x9870, 0x9877);
+
+ // Kaveri
+ APPEND_RANGE(0x1304, 0x131d);
+
+ // R700
+ APPEND_RANGE(0x9440, 0x949f);
+ APPEND_RANGE(0x94a0, 0x94b9);
+ APPEND_RANGE(0x9540, 0x955f);
+
+ break;
+ // This should never happen, but we get a warning if we don't handle this.
+ case DeviceFamily::Max:
+ case DeviceFamily::All:
+ case DeviceFamily::IntelAll:
+ case DeviceFamily::NvidiaAll:
+ case DeviceFamily::AtiAll:
+ case DeviceFamily::MicrosoftAll:
+ case DeviceFamily::ParallelsAll:
+ case DeviceFamily::QualcommAll:
+ case DeviceFamily::AppleAll:
+ case DeviceFamily::AmazonAll:
+ NS_WARNING("Invalid DeviceFamily id");
+ break;
+ }
+
+ return deviceFamily;
+}
+
+// Macro for assigning a desktop environment to a string.
+#define DECLARE_DESKTOP_ENVIRONMENT_ID(name, desktopEnvId) \
+ case DesktopEnvironment::name: \
+ sDesktopEnvironment[idx]->AssignLiteral(desktopEnvId); \
+ break;
+
+const nsAString& GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment id) {
+ if (id >= DesktopEnvironment::Max) {
+ MOZ_ASSERT_UNREACHABLE("DesktopEnvironment id is out of range");
+ id = DesktopEnvironment::All;
+ }
+
+ auto idx = static_cast<size_t>(id);
+ if (sDesktopEnvironment[idx]) {
+ return *sDesktopEnvironment[idx];
+ }
+
+ sDesktopEnvironment[idx] = new nsString();
+
+ switch (id) {
+ DECLARE_DESKTOP_ENVIRONMENT_ID(GNOME, "gnome");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(KDE, "kde");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(XFCE, "xfce");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(Cinnamon, "cinnamon");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(Enlightenment, "enlightment");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(LXDE, "lxde");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(Openbox, "openbox");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(i3, "i3");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(Mate, "mate");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(Unity, "unity");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(Pantheon, "pantheon");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(LXQT, "lxqt");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(Deepin, "deepin");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(Dwm, "dwm");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(Budgie, "budgie");
+ DECLARE_DESKTOP_ENVIRONMENT_ID(Unknown, "unknown");
+ case DesktopEnvironment::Max: // Suppress a warning.
+ DECLARE_DESKTOP_ENVIRONMENT_ID(All, "");
+ }
+
+ return *sDesktopEnvironment[idx];
+}
+
+// Macro for assigning a window protocol id to a string.
+#define DECLARE_WINDOW_PROTOCOL_ID(name, windowProtocolId) \
+ case WindowProtocol::name: \
+ sWindowProtocol[idx]->AssignLiteral(windowProtocolId); \
+ break;
+
+const nsAString& GfxDriverInfo::GetWindowProtocol(WindowProtocol id) {
+ if (id >= WindowProtocol::Max) {
+ MOZ_ASSERT_UNREACHABLE("WindowProtocol id is out of range");
+ id = WindowProtocol::All;
+ }
+
+ auto idx = static_cast<size_t>(id);
+ if (sWindowProtocol[idx]) {
+ return *sWindowProtocol[idx];
+ }
+
+ sWindowProtocol[idx] = new nsString();
+
+ switch (id) {
+ DECLARE_WINDOW_PROTOCOL_ID(X11, "x11");
+ DECLARE_WINDOW_PROTOCOL_ID(XWayland, "xwayland");
+ DECLARE_WINDOW_PROTOCOL_ID(Wayland, "wayland");
+ DECLARE_WINDOW_PROTOCOL_ID(WaylandDRM, "wayland/drm");
+ DECLARE_WINDOW_PROTOCOL_ID(WaylandAll, "wayland/all");
+ DECLARE_WINDOW_PROTOCOL_ID(X11All, "x11/all");
+ case WindowProtocol::Max: // Suppress a warning.
+ DECLARE_WINDOW_PROTOCOL_ID(All, "");
+ }
+
+ return *sWindowProtocol[idx];
+}
+
+// Macro for assigning a device vendor id to a string.
+#define DECLARE_VENDOR_ID(name, deviceId) \
+ case DeviceVendor::name: \
+ sDeviceVendors[idx]->AssignLiteral(deviceId); \
+ break;
+
+const nsAString& GfxDriverInfo::GetDeviceVendor(DeviceFamily id) {
+ if (id >= DeviceFamily::Max) {
+ MOZ_ASSERT_UNREACHABLE("DeviceVendor id is out of range");
+ id = DeviceFamily::All;
+ }
+
+ DeviceVendor vendor = DeviceVendor::All;
+ switch (id) {
+ case DeviceFamily::IntelAll:
+ case DeviceFamily::IntelGMA500:
+ case DeviceFamily::IntelGMA900:
+ case DeviceFamily::IntelGMA950:
+ case DeviceFamily::IntelGMA3150:
+ case DeviceFamily::IntelGMAX3000:
+ case DeviceFamily::IntelGMAX4500HD:
+ case DeviceFamily::IntelHDGraphicsToIvyBridge:
+ case DeviceFamily::IntelHDGraphicsToSandyBridge:
+ case DeviceFamily::IntelHaswell:
+ case DeviceFamily::IntelSandyBridge:
+ case DeviceFamily::IntelHD520:
+ case DeviceFamily::IntelMobileHDGraphics:
+ case DeviceFamily::IntelRolloutWebRender:
+ case DeviceFamily::IntelModernRolloutWebRender:
+ case DeviceFamily::Bug1116812:
+ case DeviceFamily::Bug1155608:
+ case DeviceFamily::Bug1207665:
+ vendor = DeviceVendor::Intel;
+ break;
+ case DeviceFamily::NvidiaAll:
+ case DeviceFamily::NvidiaBlockD3D9Layers:
+ case DeviceFamily::NvidiaBlockWebRender:
+ case DeviceFamily::NvidiaRolloutWebRender:
+ case DeviceFamily::Geforce7300GT:
+ case DeviceFamily::Nvidia310M:
+ case DeviceFamily::Nvidia8800GTS:
+ case DeviceFamily::Bug1137716:
+ vendor = DeviceVendor::NVIDIA;
+ break;
+ case DeviceFamily::AtiAll:
+ case DeviceFamily::RadeonCaicos:
+ case DeviceFamily::RadeonX1000:
+ case DeviceFamily::Bug1447141:
+ case DeviceFamily::AmdR600:
+ case DeviceFamily::AtiRolloutWebRender:
+ vendor = DeviceVendor::ATI;
+ break;
+ case DeviceFamily::MicrosoftAll:
+ vendor = DeviceVendor::Microsoft;
+ break;
+ case DeviceFamily::ParallelsAll:
+ vendor = DeviceVendor::Parallels;
+ break;
+ case DeviceFamily::AppleAll:
+ vendor = DeviceVendor::Apple;
+ break;
+ case DeviceFamily::AmazonAll:
+ vendor = DeviceVendor::Amazon;
+ break;
+ case DeviceFamily::QualcommAll:
+ // Choose an arbitrary Qualcomm PCI VENdor ID for now.
+ // TODO: This should be "QCOM" when Windows device ID parsing is reworked.
+ vendor = DeviceVendor::Qualcomm;
+ break;
+ case DeviceFamily::All:
+ case DeviceFamily::Max:
+ break;
+ }
+
+ return GetDeviceVendor(vendor);
+}
+
+const nsAString& GfxDriverInfo::GetDeviceVendor(DeviceVendor id) {
+ if (id >= DeviceVendor::Max) {
+ MOZ_ASSERT_UNREACHABLE("DeviceVendor id is out of range");
+ id = DeviceVendor::All;
+ }
+
+ auto idx = static_cast<size_t>(id);
+ if (sDeviceVendors[idx]) {
+ return *sDeviceVendors[idx];
+ }
+
+ sDeviceVendors[idx] = new nsString();
+
+ switch (id) {
+ DECLARE_VENDOR_ID(Intel, "0x8086");
+ DECLARE_VENDOR_ID(NVIDIA, "0x10de");
+ DECLARE_VENDOR_ID(ATI, "0x1002");
+ // AMD has 0x1022 but continues to release GPU hardware under ATI.
+ DECLARE_VENDOR_ID(Microsoft, "0x1414");
+ DECLARE_VENDOR_ID(MicrosoftBasic, "0x00ba");
+ DECLARE_VENDOR_ID(MicrosoftHyperV, "0x000b");
+ DECLARE_VENDOR_ID(Parallels, "0x1ab8");
+ DECLARE_VENDOR_ID(VMWare, "0x15ad");
+ DECLARE_VENDOR_ID(VirtualBox, "0x80ee");
+ DECLARE_VENDOR_ID(Apple, "0x106b");
+ DECLARE_VENDOR_ID(Amazon, "0x1d0f");
+ // Choose an arbitrary Qualcomm PCI VENdor ID for now.
+ // TODO: This should be "QCOM" when Windows device ID parsing is reworked.
+ DECLARE_VENDOR_ID(Qualcomm, "0x5143");
+ case DeviceVendor::Max: // Suppress a warning.
+ DECLARE_VENDOR_ID(All, "");
+ }
+
+ return *sDeviceVendors[idx];
+}
+
+// Macro for assigning a driver vendor id to a string.
+#define DECLARE_DRIVER_VENDOR_ID(name, driverVendorId) \
+ case DriverVendor::name: \
+ sDriverVendors[idx]->AssignLiteral(driverVendorId); \
+ break;
+
+const nsAString& GfxDriverInfo::GetDriverVendor(DriverVendor id) {
+ if (id >= DriverVendor::Max) {
+ MOZ_ASSERT_UNREACHABLE("DriverVendor id is out of range");
+ id = DriverVendor::All;
+ }
+
+ auto idx = static_cast<size_t>(id);
+ if (sDriverVendors[idx]) {
+ return *sDriverVendors[idx];
+ }
+
+ sDriverVendors[idx] = new nsString();
+
+ switch (id) {
+ DECLARE_DRIVER_VENDOR_ID(MesaAll, "mesa/all");
+ DECLARE_DRIVER_VENDOR_ID(MesaLLVMPipe, "mesa/llvmpipe");
+ DECLARE_DRIVER_VENDOR_ID(MesaSoftPipe, "mesa/softpipe");
+ DECLARE_DRIVER_VENDOR_ID(MesaSWRast, "mesa/swrast");
+ DECLARE_DRIVER_VENDOR_ID(MesaUnknown, "mesa/unknown");
+ DECLARE_DRIVER_VENDOR_ID(MesaNouveau, "mesa/nouveau");
+ DECLARE_DRIVER_VENDOR_ID(NonMesaAll, "non-mesa/all");
+ DECLARE_DRIVER_VENDOR_ID(HardwareMesaAll, "mesa/hw-all");
+ DECLARE_DRIVER_VENDOR_ID(SoftwareMesaAll, "mesa/sw-all");
+ case DriverVendor::Max: // Suppress a warning.
+ DECLARE_DRIVER_VENDOR_ID(All, "");
+ }
+
+ return *sDriverVendors[idx];
+}
diff --git a/widget/GfxDriverInfo.h b/widget/GfxDriverInfo.h
new file mode 100644
index 0000000000..a69cb1d853
--- /dev/null
+++ b/widget/GfxDriverInfo.h
@@ -0,0 +1,519 @@
+/* -*- 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_GfxDriverInfo_h__
+#define __mozilla_widget_GfxDriverInfo_h__
+
+#include "nsString.h"
+#include "nsTArray.h"
+
+// Macros for adding a blocklist item to the static list. _EXT variants
+// allow one to specify all available parameters, including those available
+// only on specific platforms (e.g. desktop environment and driver vendor
+// for Linux.)
+
+#define APPEND_TO_DRIVER_BLOCKLIST_EXT( \
+ os, screen, battery, desktopEnv, windowProtocol, driverVendor, devices, \
+ feature, featureStatus, driverComparator, driverVersion, ruleId, \
+ suggestedVersion) \
+ sDriverInfo->AppendElement(GfxDriverInfo( \
+ os, screen, battery, \
+ (nsAString&)GfxDriverInfo::GetDesktopEnvironment(desktopEnv), \
+ (nsAString&)GfxDriverInfo::GetWindowProtocol(windowProtocol), \
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(devices), \
+ (nsAString&)GfxDriverInfo::GetDriverVendor(driverVendor), \
+ (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(devices), feature, \
+ featureStatus, driverComparator, driverVersion, ruleId, \
+ suggestedVersion))
+
+#define APPEND_TO_DRIVER_BLOCKLIST(os, devices, feature, featureStatus, \
+ driverComparator, driverVersion, ruleId, \
+ suggestedVersion) \
+ APPEND_TO_DRIVER_BLOCKLIST_EXT( \
+ os, ScreenSizeStatus::All, BatteryStatus::All, DesktopEnvironment::All, \
+ WindowProtocol::All, DriverVendor::All, devices, feature, featureStatus, \
+ driverComparator, driverVersion, ruleId, suggestedVersion)
+
+#define APPEND_TO_DRIVER_BLOCKLIST2_EXT( \
+ os, screen, battery, desktopEnv, windowProtocol, driverVendor, devices, \
+ feature, featureStatus, driverComparator, driverVersion, ruleId) \
+ sDriverInfo->AppendElement(GfxDriverInfo( \
+ os, screen, battery, \
+ (nsAString&)GfxDriverInfo::GetDesktopEnvironment(desktopEnv), \
+ (nsAString&)GfxDriverInfo::GetWindowProtocol(windowProtocol), \
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(devices), \
+ (nsAString&)GfxDriverInfo::GetDriverVendor(driverVendor), \
+ (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(devices), feature, \
+ featureStatus, driverComparator, driverVersion, ruleId))
+
+#define APPEND_TO_DRIVER_BLOCKLIST2(os, devices, feature, featureStatus, \
+ driverComparator, driverVersion, ruleId) \
+ APPEND_TO_DRIVER_BLOCKLIST2_EXT( \
+ os, ScreenSizeStatus::All, BatteryStatus::All, DesktopEnvironment::All, \
+ WindowProtocol::All, DriverVendor::All, devices, feature, featureStatus, \
+ driverComparator, driverVersion, ruleId)
+
+#define APPEND_TO_DRIVER_BLOCKLIST_RANGE_EXT( \
+ os, screen, battery, desktopEnv, windowProtocol, driverVendor, devices, \
+ feature, featureStatus, driverComparator, driverVersion, driverVersionMax, \
+ ruleId, suggestedVersion) \
+ do { \
+ MOZ_ASSERT((driverComparator) == DRIVER_BETWEEN_EXCLUSIVE || \
+ (driverComparator) == DRIVER_BETWEEN_INCLUSIVE || \
+ (driverComparator) == DRIVER_BETWEEN_INCLUSIVE_START); \
+ GfxDriverInfo info( \
+ os, screen, battery, \
+ (nsAString&)GfxDriverInfo::GetDesktopEnvironment(desktopEnv), \
+ (nsAString&)GfxDriverInfo::GetWindowProtocol(windowProtocol), \
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(devices), \
+ (nsAString&)GfxDriverInfo::GetDriverVendor(driverVendor), \
+ (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(devices), feature, \
+ featureStatus, driverComparator, driverVersion, ruleId, \
+ suggestedVersion); \
+ info.mDriverVersionMax = driverVersionMax; \
+ sDriverInfo->AppendElement(info); \
+ } while (false)
+
+#define APPEND_TO_DRIVER_BLOCKLIST_RANGE( \
+ os, devices, feature, featureStatus, driverComparator, driverVersion, \
+ driverVersionMax, ruleId, suggestedVersion) \
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE_EXT( \
+ os, ScreenSizeStatus::All, BatteryStatus::All, DesktopEnvironment::All, \
+ WindowProtocol::All, DriverVendor::All, devices, feature, featureStatus, \
+ driverComparator, driverVersion, driverVersionMax, ruleId, \
+ suggestedVersion)
+
+#define APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2_EXT( \
+ os, screen, battery, desktopEnv, windowProtocol, driverVendor, devices, \
+ feature, featureStatus, driverComparator, driverVersion, driverVersionMax, \
+ ruleId, suggestedVersion) \
+ do { \
+ MOZ_ASSERT((driverComparator) == DRIVER_BETWEEN_EXCLUSIVE || \
+ (driverComparator) == DRIVER_BETWEEN_INCLUSIVE || \
+ (driverComparator) == DRIVER_BETWEEN_INCLUSIVE_START); \
+ GfxDriverInfo info( \
+ os, screen, battery, \
+ (nsAString&)GfxDriverInfo::GetDesktopEnvironment(desktopEnv), \
+ (nsAString&)GfxDriverInfo::GetWindowProtocol(windowProtocol), \
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(devices), \
+ (nsAString&)GfxDriverInfo::GetDriverVendor(driverVendor), \
+ (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(devices), feature, \
+ featureStatus, driverComparator, driverVersion, ruleId, \
+ suggestedVersion, false, true); \
+ info.mDriverVersionMax = driverVersionMax; \
+ sDriverInfo->AppendElement(info); \
+ } while (false)
+
+#define APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2( \
+ os, devices, feature, featureStatus, driverComparator, driverVersion, \
+ driverVersionMax, ruleId, suggestedVersion) \
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2_EXT( \
+ os, ScreenSizeStatus::All, BatteryStatus::All, DesktopEnvironment::All, \
+ WindowProtocol::All, DriverVendor::All, devices, feature, featureStatus, \
+ driverComparator, driverVersion, driverVersionMax, ruleId, \
+ suggestedVersion)
+
+namespace mozilla {
+namespace widget {
+
+enum class OperatingSystem : uint8_t {
+ Unknown,
+ Windows,
+ WindowsXP,
+ WindowsServer2003,
+ WindowsVista,
+ Windows7,
+ Windows8,
+ Windows8_1,
+ Windows10,
+ RecentWindows10,
+ NotRecentWindows10,
+ Linux,
+ OSX,
+ OSX10_5,
+ OSX10_6,
+ OSX10_7,
+ OSX10_8,
+ OSX10_9,
+ OSX10_10,
+ OSX10_11,
+ OSX10_12,
+ OSX10_13,
+ OSX10_14,
+ OSX10_15,
+ OSX11_0,
+ Android,
+ Ios
+};
+
+enum VersionComparisonOp {
+ DRIVER_LESS_THAN, // driver < version
+ DRIVER_BUILD_ID_LESS_THAN, // driver build id < version
+ DRIVER_LESS_THAN_OR_EQUAL, // driver <= version
+ DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, // driver build id <= version
+ DRIVER_GREATER_THAN, // driver > version
+ DRIVER_GREATER_THAN_OR_EQUAL, // driver >= version
+ DRIVER_EQUAL, // driver == version
+ DRIVER_NOT_EQUAL, // driver != version
+ DRIVER_BETWEEN_EXCLUSIVE, // driver > version && driver < versionMax
+ DRIVER_BETWEEN_INCLUSIVE, // driver >= version && driver <= versionMax
+ DRIVER_BETWEEN_INCLUSIVE_START, // driver >= version && driver < versionMax
+ DRIVER_COMPARISON_IGNORED
+};
+
+enum class DeviceFamily : uint8_t {
+ All,
+ IntelAll,
+ NvidiaAll,
+ AtiAll,
+ MicrosoftAll,
+ ParallelsAll,
+ QualcommAll,
+ AppleAll,
+ AmazonAll,
+ IntelGMA500,
+ IntelGMA900,
+ IntelGMA950,
+ IntelGMA3150,
+ IntelGMAX3000,
+ IntelGMAX4500HD,
+ IntelHDGraphicsToIvyBridge,
+ IntelHDGraphicsToSandyBridge,
+ IntelHaswell,
+ IntelSandyBridge,
+ IntelHD520,
+ IntelMobileHDGraphics,
+ NvidiaBlockD3D9Layers,
+ RadeonX1000,
+ RadeonCaicos,
+ Geforce7300GT,
+ Nvidia310M,
+ Nvidia8800GTS,
+ Bug1137716,
+ Bug1116812,
+ Bug1155608,
+ Bug1207665,
+ Bug1447141,
+ AmdR600,
+ NvidiaBlockWebRender,
+ NvidiaRolloutWebRender,
+ IntelRolloutWebRender,
+ IntelModernRolloutWebRender,
+ AtiRolloutWebRender,
+
+ Max
+};
+
+enum class DeviceVendor : uint8_t {
+ All, // There is an assumption that this is the first enum
+ Intel,
+ NVIDIA,
+ ATI,
+ Microsoft,
+ Parallels,
+ VMWare,
+ VirtualBox,
+ Qualcomm,
+ MicrosoftBasic,
+ MicrosoftHyperV,
+ Apple,
+ Amazon,
+
+ Max
+};
+
+enum DriverVendor : uint8_t {
+ All, // There is an assumption that this is the first enum
+ // Wildcard for all Mesa drivers.
+ MesaAll,
+ // Note that the following list of Mesa drivers is not comprehensive; we pull
+ // the DRI driver at runtime. These drivers are provided for convenience when
+ // populating the local blocklist.
+ MesaLLVMPipe,
+ MesaSoftPipe,
+ MesaSWRast,
+ // Nouveau: Open-source nvidia
+ MesaNouveau,
+ // A generic ID to be provided when we can't determine the DRI driver on Mesa.
+ MesaUnknown,
+ // Wildcard for all non-Mesa drivers.
+ NonMesaAll,
+ // Wildcard for all hardware Mesa drivers.
+ HardwareMesaAll,
+ // Wildcard for all software Mesa drivers.
+ SoftwareMesaAll,
+
+ Max
+};
+
+enum class DesktopEnvironment : uint8_t {
+ All, // There is an assumption that this is the first enum
+ GNOME,
+ KDE,
+ XFCE,
+ Cinnamon,
+ Enlightenment,
+ LXDE,
+ Openbox,
+ i3,
+ Mate,
+ Unity,
+ Pantheon,
+ LXQT,
+ Deepin,
+ Dwm,
+ Budgie,
+ Unknown,
+ Max
+};
+
+enum class WindowProtocol : uint8_t {
+ All, // There is an assumption that this is the first enum
+ X11,
+ XWayland,
+ Wayland,
+ WaylandDRM,
+ // Wildcard for all Wayland variants, excluding XWayland.
+ WaylandAll,
+ // Wildcard for all X11 variants, including XWayland.
+ X11All,
+ Max
+};
+
+enum class BatteryStatus : uint8_t { All, Present, None };
+
+enum class ScreenSizeStatus : uint8_t {
+ All,
+ Small, // <= 1900x1200
+ SmallAndMedium, // <= 3440x1440
+ Medium, // <= 3440x1440 && > 1900x1200
+ MediumAndLarge, // >1900x1200
+ Large // > 3440x1440
+};
+
+/* Array of devices to match, or an empty array for all devices */
+class GfxDeviceFamily final {
+ public:
+ GfxDeviceFamily() = default;
+
+ void Append(const nsAString& aDeviceId);
+ void AppendRange(int32_t aBeginDeviceId, int32_t aEndDeviceId);
+
+ bool IsEmpty() const { return mIds.IsEmpty() && mRanges.IsEmpty(); }
+
+ nsresult Contains(nsAString& aDeviceId) const;
+
+ private:
+ struct DeviceRange {
+ int32_t mBegin;
+ int32_t mEnd;
+ };
+
+ CopyableTArray<nsString> mIds;
+ CopyableTArray<DeviceRange> mRanges;
+};
+
+struct GfxDriverInfo {
+ // If |ownDevices| is true, you are transferring ownership of the devices
+ // array, and it will be deleted when this GfxDriverInfo is destroyed.
+ GfxDriverInfo(OperatingSystem os, ScreenSizeStatus aScreen,
+ BatteryStatus aBattery, const nsAString& desktopEnv,
+ const nsAString& windowProtocol, const nsAString& vendor,
+ const nsAString& driverVendor, GfxDeviceFamily* devices,
+ int32_t feature, int32_t featureStatus, VersionComparisonOp op,
+ uint64_t driverVersion, const char* ruleId,
+ const char* suggestedVersion = nullptr, bool ownDevices = false,
+ bool gpu2 = false);
+
+ GfxDriverInfo();
+ GfxDriverInfo(const GfxDriverInfo&);
+ ~GfxDriverInfo();
+
+ OperatingSystem mOperatingSystem;
+ uint32_t mOperatingSystemVersion;
+ ScreenSizeStatus mScreen;
+ BatteryStatus mBattery;
+ nsString mDesktopEnvironment;
+ nsString mWindowProtocol;
+
+ nsString mAdapterVendor;
+ nsString mDriverVendor;
+
+ const GfxDeviceFamily* mDevices;
+
+ // Whether the mDevices array should be deleted when this structure is
+ // deallocated. False by default.
+ bool mDeleteDevices;
+
+ /* A feature from nsIGfxInfo, or all features */
+ int32_t mFeature;
+ static int32_t allFeatures;
+
+ /* A feature status from nsIGfxInfo */
+ int32_t mFeatureStatus;
+
+ VersionComparisonOp mComparisonOp;
+
+ /* versions are assumed to be A.B.C.D packed as 0xAAAABBBBCCCCDDDD */
+ uint64_t mDriverVersion;
+ uint64_t mDriverVersionMax;
+ static uint64_t allDriverVersions;
+
+ const char* mSuggestedVersion;
+ nsCString mRuleId;
+
+ static const GfxDeviceFamily* GetDeviceFamily(DeviceFamily id);
+ static GfxDeviceFamily*
+ sDeviceFamilies[static_cast<size_t>(DeviceFamily::Max)];
+
+ static const nsAString& GetDesktopEnvironment(DesktopEnvironment id);
+ static nsAString*
+ sDesktopEnvironment[static_cast<size_t>(DesktopEnvironment::Max)];
+
+ static const nsAString& GetWindowProtocol(WindowProtocol id);
+ static nsAString* sWindowProtocol[static_cast<size_t>(WindowProtocol::Max)];
+
+ static const nsAString& GetDeviceVendor(DeviceVendor id);
+ static const nsAString& GetDeviceVendor(DeviceFamily id);
+ static nsAString* sDeviceVendors[static_cast<size_t>(DeviceVendor::Max)];
+
+ static const nsAString& GetDriverVendor(DriverVendor id);
+ static nsAString* sDriverVendors[static_cast<size_t>(DriverVendor::Max)];
+
+ nsString mModel, mHardware, mProduct, mManufacturer;
+
+ bool mGpu2;
+};
+
+#define GFX_DRIVER_VERSION(a, b, c, d) \
+ ((uint64_t(a) << 48) | (uint64_t(b) << 32) | (uint64_t(c) << 16) | \
+ uint64_t(d))
+
+inline uint64_t V(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
+ // We make sure every driver number is padded by 0s, this will allow us the
+ // easiest 'compare as if decimals' approach. See ParseDriverVersion for a
+ // more extensive explanation of this approach.
+ while (b > 0 && b < 1000) {
+ b *= 10;
+ }
+ while (c > 0 && c < 1000) {
+ c *= 10;
+ }
+ while (d > 0 && d < 1000) {
+ d *= 10;
+ }
+ return GFX_DRIVER_VERSION(a, b, c, d);
+}
+
+// All destination string storage needs to have at least 5 bytes available.
+inline bool SplitDriverVersion(const char* aSource, char* aAStr, char* aBStr,
+ char* aCStr, char* aDStr) {
+ // sscanf doesn't do what we want here to we parse this manually.
+ int len = strlen(aSource);
+
+ // This "4" is hardcoded in a few places, including once as a 3.
+ char* dest[4] = {aAStr, aBStr, aCStr, aDStr};
+ unsigned destIdx = 0;
+ unsigned destPos = 0;
+
+ for (int i = 0; i < len; i++) {
+ if (destIdx >= 4) {
+ // Invalid format found. Ensure we don't access dest beyond bounds.
+ return false;
+ }
+
+ if (aSource[i] == '.') {
+ MOZ_ASSERT(destIdx < 4 && destPos <= 4);
+ dest[destIdx++][destPos] = 0;
+ destPos = 0;
+ continue;
+ }
+
+ if (destPos > 3) {
+ // Ignore more than 4 chars. Ensure we never access dest[destIdx]
+ // beyond its bounds.
+ continue;
+ }
+
+ MOZ_ASSERT(destIdx < 4 && destPos < 4);
+ dest[destIdx][destPos++] = aSource[i];
+ }
+
+ // Take care of the trailing period
+ if (destIdx >= 4) {
+ return false;
+ }
+
+ // Add last terminator.
+ MOZ_ASSERT(destIdx < 4 && destPos <= 4);
+ dest[destIdx][destPos] = 0;
+
+ if (destIdx != 3) {
+ return false;
+ }
+ return true;
+}
+
+// This allows us to pad driver version 'substrings' with 0s, this
+// effectively allows us to treat the version numbers as 'decimals'. This is
+// a little strange but this method seems to do the right thing for all
+// different vendor's driver strings. i.e. .98 will become 9800, which is
+// larger than .978 which would become 9780.
+inline void PadDriverDecimal(char* aString) {
+ for (int i = 0; i < 4; i++) {
+ if (!aString[i]) {
+ for (int c = i; c < 4; c++) {
+ aString[c] = '0';
+ }
+ break;
+ }
+ }
+ aString[4] = 0;
+}
+
+inline bool ParseDriverVersion(const nsAString& aVersion,
+ uint64_t* aNumericVersion) {
+ *aNumericVersion = 0;
+
+#if defined(XP_WIN) || defined(MOZ_X11)
+ int a, b, c, d;
+ char aStr[8], bStr[8], cStr[8], dStr[8];
+ /* honestly, why do I even bother */
+ if (!SplitDriverVersion(NS_LossyConvertUTF16toASCII(aVersion).get(), aStr,
+ bStr, cStr, dStr))
+ return false;
+
+ PadDriverDecimal(bStr);
+ PadDriverDecimal(cStr);
+ PadDriverDecimal(dStr);
+
+ a = atoi(aStr);
+ b = atoi(bStr);
+ c = atoi(cStr);
+ d = atoi(dStr);
+
+ if (a < 0 || a > 0xffff) return false;
+ if (b < 0 || b > 0xffff) return false;
+ if (c < 0 || c > 0xffff) return false;
+ if (d < 0 || d > 0xffff) return false;
+
+ *aNumericVersion = GFX_DRIVER_VERSION(a, b, c, d);
+ MOZ_ASSERT(*aNumericVersion != GfxDriverInfo::allDriverVersions);
+ return true;
+#elif defined(ANDROID)
+ // Can't use aVersion.ToInteger() because that's not compiled into our code
+ // unless we have XPCOM_GLUE_AVOID_NSPR disabled.
+ *aNumericVersion = atoi(NS_LossyConvertUTF16toASCII(aVersion).get());
+ MOZ_ASSERT(*aNumericVersion != GfxDriverInfo::allDriverVersions);
+ return true;
+#else
+ return false;
+#endif
+}
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /*__mozilla_widget_GfxDriverInfo_h__ */
diff --git a/widget/GfxInfoBase.cpp b/widget/GfxInfoBase.cpp
new file mode 100644
index 0000000000..6b88fc08b6
--- /dev/null
+++ b/widget/GfxInfoBase.cpp
@@ -0,0 +1,1840 @@
+/* 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/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "GfxInfoBase.h"
+
+#include <mutex> // std::call_once
+
+#include "GfxDriverInfo.h"
+#include "js/Array.h" // JS::GetArrayLength, JS::NewArrayObject
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsVersionComparator.h"
+#include "mozilla/Services.h"
+#include "mozilla/Observer.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIScreenManager.h"
+#include "nsTArray.h"
+#include "nsXULAppAPI.h"
+#include "nsIXULAppInfo.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/PaintThread.h"
+
+#include "gfxPlatform.h"
+#include "gfxConfig.h"
+#include "DriverCrashGuard.h"
+
+using namespace mozilla::widget;
+using namespace mozilla;
+using mozilla::MutexAutoLock;
+
+nsTArray<GfxDriverInfo>* GfxInfoBase::sDriverInfo;
+StaticAutoPtr<nsTArray<gfx::GfxInfoFeatureStatus>> GfxInfoBase::sFeatureStatus;
+bool GfxInfoBase::sDriverInfoObserverInitialized;
+bool GfxInfoBase::sShutdownOccurred;
+
+// Call this when setting sFeatureStatus to a non-null pointer to
+// ensure destruction even if the GfxInfo component is never instantiated.
+static void InitFeatureStatus(nsTArray<gfx::GfxInfoFeatureStatus>* aPtr) {
+ static std::once_flag sOnce;
+ std::call_once(sOnce, [] { ClearOnShutdown(&GfxInfoBase::sFeatureStatus); });
+ GfxInfoBase::sFeatureStatus = aPtr;
+}
+
+// Observes for shutdown so that the child GfxDriverInfo list is freed.
+class ShutdownObserver : public nsIObserver {
+ virtual ~ShutdownObserver() = default;
+
+ public:
+ ShutdownObserver() = default;
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Observe(nsISupports* subject, const char* aTopic,
+ const char16_t* aData) override {
+ MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
+
+ delete GfxInfoBase::sDriverInfo;
+ GfxInfoBase::sDriverInfo = nullptr;
+
+ for (auto& deviceFamily : GfxDriverInfo::sDeviceFamilies) {
+ delete deviceFamily;
+ deviceFamily = nullptr;
+ }
+
+ for (auto& desktop : GfxDriverInfo::sDesktopEnvironment) {
+ delete desktop;
+ desktop = nullptr;
+ }
+
+ for (auto& windowProtocol : GfxDriverInfo::sWindowProtocol) {
+ delete windowProtocol;
+ windowProtocol = nullptr;
+ }
+
+ for (auto& deviceVendor : GfxDriverInfo::sDeviceVendors) {
+ delete deviceVendor;
+ deviceVendor = nullptr;
+ }
+
+ for (auto& driverVendor : GfxDriverInfo::sDriverVendors) {
+ delete driverVendor;
+ driverVendor = nullptr;
+ }
+
+ GfxInfoBase::sShutdownOccurred = true;
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(ShutdownObserver, nsIObserver)
+
+static void InitGfxDriverInfoShutdownObserver() {
+ if (GfxInfoBase::sDriverInfoObserverInitialized) return;
+
+ GfxInfoBase::sDriverInfoObserverInitialized = true;
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (!observerService) {
+ NS_WARNING("Could not get observer service!");
+ return;
+ }
+
+ ShutdownObserver* obs = new ShutdownObserver();
+ observerService->AddObserver(obs, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+}
+
+using namespace mozilla::widget;
+using namespace mozilla::gfx;
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(GfxInfoBase, nsIGfxInfo, nsIObserver,
+ nsISupportsWeakReference)
+
+#define BLOCKLIST_PREF_BRANCH "gfx.blacklist."
+#define SUGGESTED_VERSION_PREF BLOCKLIST_PREF_BRANCH "suggested-driver-version"
+
+static const char* GetPrefNameForFeature(int32_t aFeature) {
+ const char* name = nullptr;
+ switch (aFeature) {
+ case nsIGfxInfo::FEATURE_DIRECT2D:
+ name = BLOCKLIST_PREF_BRANCH "direct2d";
+ break;
+ case nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS:
+ name = BLOCKLIST_PREF_BRANCH "layers.direct3d9";
+ break;
+ case nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS:
+ name = BLOCKLIST_PREF_BRANCH "layers.direct3d10";
+ break;
+ case nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS:
+ name = BLOCKLIST_PREF_BRANCH "layers.direct3d10-1";
+ break;
+ case nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS:
+ name = BLOCKLIST_PREF_BRANCH "layers.direct3d11";
+ break;
+ case nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE:
+ name = BLOCKLIST_PREF_BRANCH "direct3d11angle";
+ break;
+ case nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING:
+ name = BLOCKLIST_PREF_BRANCH "hardwarevideodecoding";
+ break;
+ case nsIGfxInfo::FEATURE_OPENGL_LAYERS:
+ name = BLOCKLIST_PREF_BRANCH "layers.opengl";
+ break;
+ case nsIGfxInfo::FEATURE_WEBGL_OPENGL:
+ name = BLOCKLIST_PREF_BRANCH "webgl.opengl";
+ break;
+ case nsIGfxInfo::FEATURE_WEBGL_ANGLE:
+ name = BLOCKLIST_PREF_BRANCH "webgl.angle";
+ break;
+ case nsIGfxInfo::UNUSED_FEATURE_WEBGL_MSAA:
+ name = BLOCKLIST_PREF_BRANCH "webgl.msaa";
+ break;
+ case nsIGfxInfo::FEATURE_STAGEFRIGHT:
+ name = BLOCKLIST_PREF_BRANCH "stagefright";
+ break;
+ case nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_H264:
+ name = BLOCKLIST_PREF_BRANCH "webrtc.hw.acceleration.h264";
+ break;
+ case nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_ENCODE:
+ name = BLOCKLIST_PREF_BRANCH "webrtc.hw.acceleration.encode";
+ break;
+ case nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_DECODE:
+ name = BLOCKLIST_PREF_BRANCH "webrtc.hw.acceleration.decode";
+ break;
+ case nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION:
+ name = BLOCKLIST_PREF_BRANCH "canvas2d.acceleration";
+ break;
+ case nsIGfxInfo::FEATURE_DX_INTEROP2:
+ name = BLOCKLIST_PREF_BRANCH "dx.interop2";
+ break;
+ case nsIGfxInfo::FEATURE_GPU_PROCESS:
+ name = BLOCKLIST_PREF_BRANCH "gpu.process";
+ break;
+ case nsIGfxInfo::FEATURE_WEBGL2:
+ name = BLOCKLIST_PREF_BRANCH "webgl2";
+ break;
+ case nsIGfxInfo::FEATURE_ADVANCED_LAYERS:
+ name = BLOCKLIST_PREF_BRANCH "layers.advanced";
+ break;
+ case nsIGfxInfo::FEATURE_D3D11_KEYED_MUTEX:
+ name = BLOCKLIST_PREF_BRANCH "d3d11.keyed.mutex";
+ break;
+ case nsIGfxInfo::FEATURE_WEBRENDER:
+ name = BLOCKLIST_PREF_BRANCH "webrender";
+ break;
+ case nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR:
+ name = BLOCKLIST_PREF_BRANCH "webrender.compositor";
+ break;
+ case nsIGfxInfo::FEATURE_DX_NV12:
+ name = BLOCKLIST_PREF_BRANCH "dx.nv12";
+ break;
+ case nsIGfxInfo::FEATURE_DX_P010:
+ name = BLOCKLIST_PREF_BRANCH "dx.p010";
+ break;
+ case nsIGfxInfo::FEATURE_DX_P016:
+ name = BLOCKLIST_PREF_BRANCH "dx.p016";
+ break;
+ case nsIGfxInfo::FEATURE_VP8_HW_DECODE:
+ case nsIGfxInfo::FEATURE_VP9_HW_DECODE:
+ // We don't provide prefs for these features as these are
+ // not handling downloadable blocklist.
+ break;
+ case nsIGfxInfo::FEATURE_GL_SWIZZLE:
+ name = BLOCKLIST_PREF_BRANCH "gl.swizzle";
+ break;
+ case nsIGfxInfo::FEATURE_WEBRENDER_SCISSORED_CACHE_CLEARS:
+ name = BLOCKLIST_PREF_BRANCH "webrender.scissored_cache_clears";
+ break;
+ case nsIGfxInfo::FEATURE_ALLOW_WEBGL_OUT_OF_PROCESS:
+ name = BLOCKLIST_PREF_BRANCH "webgl.allow-oop";
+ break;
+ case nsIGfxInfo::FEATURE_THREADSAFE_GL:
+ name = BLOCKLIST_PREF_BRANCH "gl.threadsafe";
+ break;
+ case nsIGfxInfo::FEATURE_WEBRENDER_SOFTWARE:
+ name = BLOCKLIST_PREF_BRANCH "webrender.software";
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected nsIGfxInfo feature?!");
+ break;
+ }
+
+ return name;
+}
+
+// Returns the value of the pref for the relevant feature in aValue.
+// If the pref doesn't exist, aValue is not touched, and returns false.
+static bool GetPrefValueForFeature(int32_t aFeature, int32_t& aValue,
+ nsACString& aFailureId) {
+ const char* prefname = GetPrefNameForFeature(aFeature);
+ if (!prefname) return false;
+
+ aValue = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ if (!NS_SUCCEEDED(Preferences::GetInt(prefname, &aValue))) {
+ return false;
+ }
+
+ nsCString failureprefname(prefname);
+ failureprefname += ".failureid";
+ nsAutoCString failureValue;
+ nsresult rv = Preferences::GetCString(failureprefname.get(), failureValue);
+ if (NS_SUCCEEDED(rv)) {
+ aFailureId = failureValue.get();
+ } else {
+ aFailureId = "FEATURE_FAILURE_BLOCKLIST_PREF";
+ }
+
+ return true;
+}
+
+static void SetPrefValueForFeature(int32_t aFeature, int32_t aValue,
+ const nsACString& aFailureId) {
+ const char* prefname = GetPrefNameForFeature(aFeature);
+ if (!prefname) return;
+ if (XRE_IsParentProcess()) {
+ GfxInfoBase::sFeatureStatus = nullptr;
+ }
+
+ Preferences::SetInt(prefname, aValue);
+ if (!aFailureId.IsEmpty()) {
+ nsCString failureprefname(prefname);
+ failureprefname += ".failureid";
+ Preferences::SetCString(failureprefname.get(), aFailureId);
+ }
+}
+
+static void RemovePrefForFeature(int32_t aFeature) {
+ const char* prefname = GetPrefNameForFeature(aFeature);
+ if (!prefname) return;
+
+ if (XRE_IsParentProcess()) {
+ GfxInfoBase::sFeatureStatus = nullptr;
+ }
+ Preferences::ClearUser(prefname);
+}
+
+static bool GetPrefValueForDriverVersion(nsCString& aVersion) {
+ return NS_SUCCEEDED(
+ Preferences::GetCString(SUGGESTED_VERSION_PREF, aVersion));
+}
+
+static void SetPrefValueForDriverVersion(const nsAString& aVersion) {
+ Preferences::SetString(SUGGESTED_VERSION_PREF, aVersion);
+}
+
+static void RemovePrefForDriverVersion() {
+ Preferences::ClearUser(SUGGESTED_VERSION_PREF);
+}
+
+static OperatingSystem BlocklistOSToOperatingSystem(const nsAString& os) {
+ if (os.EqualsLiteral("WINNT 6.1")) {
+ return OperatingSystem::Windows7;
+ } else if (os.EqualsLiteral("WINNT 6.2")) {
+ return OperatingSystem::Windows8;
+ } else if (os.EqualsLiteral("WINNT 6.3")) {
+ return OperatingSystem::Windows8_1;
+ } else if (os.EqualsLiteral("WINNT 10.0")) {
+ return OperatingSystem::Windows10;
+ } else if (os.EqualsLiteral("Linux")) {
+ return OperatingSystem::Linux;
+ } else if (os.EqualsLiteral("Darwin 9")) {
+ return OperatingSystem::OSX10_5;
+ } else if (os.EqualsLiteral("Darwin 10")) {
+ return OperatingSystem::OSX10_6;
+ } else if (os.EqualsLiteral("Darwin 11")) {
+ return OperatingSystem::OSX10_7;
+ } else if (os.EqualsLiteral("Darwin 12")) {
+ return OperatingSystem::OSX10_8;
+ } else if (os.EqualsLiteral("Darwin 13")) {
+ return OperatingSystem::OSX10_9;
+ } else if (os.EqualsLiteral("Darwin 14")) {
+ return OperatingSystem::OSX10_10;
+ } else if (os.EqualsLiteral("Darwin 15")) {
+ return OperatingSystem::OSX10_11;
+ } else if (os.EqualsLiteral("Darwin 16")) {
+ return OperatingSystem::OSX10_12;
+ } else if (os.EqualsLiteral("Darwin 17")) {
+ return OperatingSystem::OSX10_13;
+ } else if (os.EqualsLiteral("Darwin 18")) {
+ return OperatingSystem::OSX10_14;
+ } else if (os.EqualsLiteral("Darwin 19")) {
+ return OperatingSystem::OSX10_15;
+ } else if (os.EqualsLiteral("Darwin 20")) {
+ return OperatingSystem::OSX11_0;
+ } else if (os.EqualsLiteral("Android")) {
+ return OperatingSystem::Android;
+ // For historical reasons, "All" in blocklist means "All Windows"
+ } else if (os.EqualsLiteral("All")) {
+ return OperatingSystem::Windows;
+ } else if (os.EqualsLiteral("Darwin")) {
+ return OperatingSystem::OSX;
+ }
+
+ return OperatingSystem::Unknown;
+}
+
+static GfxDeviceFamily* BlocklistDevicesToDeviceFamily(
+ nsTArray<nsCString>& devices) {
+ if (devices.Length() == 0) return nullptr;
+
+ // For each device, get its device ID, and return a freshly-allocated
+ // GfxDeviceFamily with the contents of that array.
+ GfxDeviceFamily* deviceIds = new GfxDeviceFamily;
+
+ for (uint32_t i = 0; i < devices.Length(); ++i) {
+ // We make sure we don't add any "empty" device entries to the array, so
+ // we don't need to check if devices[i] is empty.
+ deviceIds->Append(NS_ConvertUTF8toUTF16(devices[i]));
+ }
+
+ return deviceIds;
+}
+
+static int32_t BlocklistFeatureToGfxFeature(const nsAString& aFeature) {
+ MOZ_ASSERT(!aFeature.IsEmpty());
+ if (aFeature.EqualsLiteral("DIRECT2D"))
+ return nsIGfxInfo::FEATURE_DIRECT2D;
+ else if (aFeature.EqualsLiteral("DIRECT3D_9_LAYERS"))
+ return nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS;
+ else if (aFeature.EqualsLiteral("DIRECT3D_10_LAYERS"))
+ return nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS;
+ else if (aFeature.EqualsLiteral("DIRECT3D_10_1_LAYERS"))
+ return nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS;
+ else if (aFeature.EqualsLiteral("DIRECT3D_11_LAYERS"))
+ return nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS;
+ else if (aFeature.EqualsLiteral("DIRECT3D_11_ANGLE"))
+ return nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE;
+ else if (aFeature.EqualsLiteral("HARDWARE_VIDEO_DECODING"))
+ return nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING;
+ else if (aFeature.EqualsLiteral("OPENGL_LAYERS"))
+ return nsIGfxInfo::FEATURE_OPENGL_LAYERS;
+ else if (aFeature.EqualsLiteral("WEBGL_OPENGL"))
+ return nsIGfxInfo::FEATURE_WEBGL_OPENGL;
+ else if (aFeature.EqualsLiteral("WEBGL_ANGLE"))
+ return nsIGfxInfo::FEATURE_WEBGL_ANGLE;
+ else if (aFeature.EqualsLiteral("WEBGL_MSAA"))
+ return nsIGfxInfo::UNUSED_FEATURE_WEBGL_MSAA;
+ else if (aFeature.EqualsLiteral("STAGEFRIGHT"))
+ return nsIGfxInfo::FEATURE_STAGEFRIGHT;
+ else if (aFeature.EqualsLiteral("WEBRTC_HW_ACCELERATION_ENCODE"))
+ return nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_ENCODE;
+ else if (aFeature.EqualsLiteral("WEBRTC_HW_ACCELERATION_DECODE"))
+ return nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_DECODE;
+ else if (aFeature.EqualsLiteral("WEBRTC_HW_ACCELERATION_H264"))
+ return nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_H264;
+ else if (aFeature.EqualsLiteral("CANVAS2D_ACCELERATION"))
+ return nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION;
+ else if (aFeature.EqualsLiteral("DX_INTEROP2"))
+ return nsIGfxInfo::FEATURE_DX_INTEROP2;
+ else if (aFeature.EqualsLiteral("GPU_PROCESS"))
+ return nsIGfxInfo::FEATURE_GPU_PROCESS;
+ else if (aFeature.EqualsLiteral("WEBGL2"))
+ return nsIGfxInfo::FEATURE_WEBGL2;
+ else if (aFeature.EqualsLiteral("ADVANCED_LAYERS"))
+ return nsIGfxInfo::FEATURE_ADVANCED_LAYERS;
+ else if (aFeature.EqualsLiteral("D3D11_KEYED_MUTEX"))
+ return nsIGfxInfo::FEATURE_D3D11_KEYED_MUTEX;
+ else if (aFeature.EqualsLiteral("WEBRENDER"))
+ return nsIGfxInfo::FEATURE_WEBRENDER;
+ else if (aFeature.EqualsLiteral("WEBRENDER_COMPOSITOR"))
+ return nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR;
+ else if (aFeature.EqualsLiteral("DX_NV12"))
+ return nsIGfxInfo::FEATURE_DX_NV12;
+ // We do not support FEATURE_VP8_HW_DECODE and FEATURE_VP9_HW_DECODE
+ // in downloadable blocklist.
+ else if (aFeature.EqualsLiteral("GL_SWIZZLE"))
+ return nsIGfxInfo::FEATURE_GL_SWIZZLE;
+ else if (aFeature.EqualsLiteral("WEBRENDER_SCISSORED_CACHE_CLEARS"))
+ return nsIGfxInfo::FEATURE_WEBRENDER_SCISSORED_CACHE_CLEARS;
+ else if (aFeature.EqualsLiteral("ALLOW_WEBGL_OUT_OF_PROCESS"))
+ return nsIGfxInfo::FEATURE_ALLOW_WEBGL_OUT_OF_PROCESS;
+ else if (aFeature.EqualsLiteral("THREADSAFE_GL"))
+ return nsIGfxInfo::FEATURE_THREADSAFE_GL;
+ else if (aFeature.EqualsLiteral("WEBRENDER_SOFTWARE"))
+ return nsIGfxInfo::FEATURE_WEBRENDER_SOFTWARE;
+
+ // If we don't recognize the feature, it may be new, and something
+ // this version doesn't understand. So, nothing to do. This is
+ // different from feature not being specified at all, in which case
+ // this method should not get called and we should continue with the
+ // "all features" blocklisting.
+ return -1;
+}
+
+static int32_t BlocklistFeatureStatusToGfxFeatureStatus(
+ const nsAString& aStatus) {
+ if (aStatus.EqualsLiteral("STATUS_OK"))
+ return nsIGfxInfo::FEATURE_STATUS_OK;
+ else if (aStatus.EqualsLiteral("BLOCKED_DRIVER_VERSION"))
+ return nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
+ else if (aStatus.EqualsLiteral("BLOCKED_DEVICE"))
+ return nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ else if (aStatus.EqualsLiteral("DISCOURAGED"))
+ return nsIGfxInfo::FEATURE_DISCOURAGED;
+ else if (aStatus.EqualsLiteral("BLOCKED_OS_VERSION"))
+ return nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION;
+ else if (aStatus.EqualsLiteral("DENIED"))
+ return nsIGfxInfo::FEATURE_DENIED;
+ else if (aStatus.EqualsLiteral("ALLOW_QUALIFIED"))
+ return nsIGfxInfo::FEATURE_ALLOW_QUALIFIED;
+ else if (aStatus.EqualsLiteral("ALLOW_ALWAYS"))
+ return nsIGfxInfo::FEATURE_ALLOW_ALWAYS;
+
+ // Do not allow it to set STATUS_UNKNOWN. Also, we are not
+ // expecting the "mismatch" status showing up here.
+
+ return nsIGfxInfo::FEATURE_STATUS_OK;
+}
+
+static VersionComparisonOp BlocklistComparatorToComparisonOp(
+ const nsAString& op) {
+ if (op.EqualsLiteral("LESS_THAN"))
+ return DRIVER_LESS_THAN;
+ else if (op.EqualsLiteral("BUILD_ID_LESS_THAN"))
+ return DRIVER_BUILD_ID_LESS_THAN;
+ else if (op.EqualsLiteral("LESS_THAN_OR_EQUAL"))
+ return DRIVER_LESS_THAN_OR_EQUAL;
+ else if (op.EqualsLiteral("BUILD_ID_LESS_THAN_OR_EQUAL"))
+ return DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL;
+ else if (op.EqualsLiteral("GREATER_THAN"))
+ return DRIVER_GREATER_THAN;
+ else if (op.EqualsLiteral("GREATER_THAN_OR_EQUAL"))
+ return DRIVER_GREATER_THAN_OR_EQUAL;
+ else if (op.EqualsLiteral("EQUAL"))
+ return DRIVER_EQUAL;
+ else if (op.EqualsLiteral("NOT_EQUAL"))
+ return DRIVER_NOT_EQUAL;
+ else if (op.EqualsLiteral("BETWEEN_EXCLUSIVE"))
+ return DRIVER_BETWEEN_EXCLUSIVE;
+ else if (op.EqualsLiteral("BETWEEN_INCLUSIVE"))
+ return DRIVER_BETWEEN_INCLUSIVE;
+ else if (op.EqualsLiteral("BETWEEN_INCLUSIVE_START"))
+ return DRIVER_BETWEEN_INCLUSIVE_START;
+
+ return DRIVER_COMPARISON_IGNORED;
+}
+
+/*
+ Deserialize Blocklist entries from string.
+ e.g:
+ os:WINNT 6.0\tvendor:0x8086\tdevices:0x2582,0x2782\tfeature:DIRECT3D_10_LAYERS\tfeatureStatus:BLOCKED_DRIVER_VERSION\tdriverVersion:8.52.322.2202\tdriverVersionComparator:LESS_THAN_OR_EQUAL
+*/
+static bool BlocklistEntryToDriverInfo(const nsACString& aBlocklistEntry,
+ GfxDriverInfo& aDriverInfo) {
+ // If we get an application version to be zero, something is not working
+ // and we are not going to bother checking the blocklist versions.
+ // See TestGfxWidgets.cpp for how version comparison works.
+ // <versionRange minVersion="42.0a1" maxVersion="45.0"></versionRange>
+ static mozilla::Version zeroV("0");
+ static mozilla::Version appV(GfxInfoBase::GetApplicationVersion().get());
+ if (appV <= zeroV) {
+ gfxCriticalErrorOnce(gfxCriticalError::DefaultOptions(false))
+ << "Invalid application version "
+ << GfxInfoBase::GetApplicationVersion().get();
+ }
+
+ aDriverInfo.mRuleId = "FEATURE_FAILURE_DL_BLOCKLIST_NO_ID"_ns;
+
+ for (const auto& keyValue : aBlocklistEntry.Split('\t')) {
+ nsTArray<nsCString> splitted;
+ ParseString(keyValue, ':', splitted);
+ if (splitted.Length() != 2) {
+ // If we don't recognize the input data, we do not want to proceed.
+ gfxCriticalErrorOnce(CriticalLog::DefaultOptions(false))
+ << "Unrecognized data " << nsCString(keyValue).get();
+ return false;
+ }
+ const nsCString& key = splitted[0];
+ const nsCString& value = splitted[1];
+ NS_ConvertUTF8toUTF16 dataValue(value);
+
+ if (value.Length() == 0) {
+ // Safety check for empty values.
+ gfxCriticalErrorOnce(CriticalLog::DefaultOptions(false))
+ << "Empty value for " << key.get();
+ return false;
+ }
+
+ if (key.EqualsLiteral("blockID")) {
+ nsCString blockIdStr = "FEATURE_FAILURE_DL_BLOCKLIST_"_ns + value;
+ aDriverInfo.mRuleId = blockIdStr.get();
+ } else if (key.EqualsLiteral("os")) {
+ aDriverInfo.mOperatingSystem = BlocklistOSToOperatingSystem(dataValue);
+ } else if (key.EqualsLiteral("osversion")) {
+ aDriverInfo.mOperatingSystemVersion = strtoul(value.get(), nullptr, 10);
+ } else if (key.EqualsLiteral("desktopEnvironment")) {
+ aDriverInfo.mDesktopEnvironment = dataValue;
+ } else if (key.EqualsLiteral("windowProtocol")) {
+ aDriverInfo.mWindowProtocol = dataValue;
+ } else if (key.EqualsLiteral("vendor")) {
+ aDriverInfo.mAdapterVendor = dataValue;
+ } else if (key.EqualsLiteral("driverVendor")) {
+ aDriverInfo.mDriverVendor = dataValue;
+ } else if (key.EqualsLiteral("feature")) {
+ aDriverInfo.mFeature = BlocklistFeatureToGfxFeature(dataValue);
+ if (aDriverInfo.mFeature < 0) {
+ // If we don't recognize the feature, we do not want to proceed.
+ gfxCriticalErrorOnce(CriticalLog::DefaultOptions(false))
+ << "Unrecognized feature " << value.get();
+ return false;
+ }
+ } else if (key.EqualsLiteral("featureStatus")) {
+ aDriverInfo.mFeatureStatus =
+ BlocklistFeatureStatusToGfxFeatureStatus(dataValue);
+ } else if (key.EqualsLiteral("driverVersion")) {
+ uint64_t version;
+ if (ParseDriverVersion(dataValue, &version))
+ aDriverInfo.mDriverVersion = version;
+ } else if (key.EqualsLiteral("driverVersionMax")) {
+ uint64_t version;
+ if (ParseDriverVersion(dataValue, &version))
+ aDriverInfo.mDriverVersionMax = version;
+ } else if (key.EqualsLiteral("driverVersionComparator")) {
+ aDriverInfo.mComparisonOp = BlocklistComparatorToComparisonOp(dataValue);
+ } else if (key.EqualsLiteral("model")) {
+ aDriverInfo.mModel = dataValue;
+ } else if (key.EqualsLiteral("product")) {
+ aDriverInfo.mProduct = dataValue;
+ } else if (key.EqualsLiteral("manufacturer")) {
+ aDriverInfo.mManufacturer = dataValue;
+ } else if (key.EqualsLiteral("hardware")) {
+ aDriverInfo.mHardware = dataValue;
+ } else if (key.EqualsLiteral("versionRange")) {
+ nsTArray<nsCString> versionRange;
+ ParseString(value, ',', versionRange);
+ if (versionRange.Length() != 2) {
+ gfxCriticalErrorOnce(CriticalLog::DefaultOptions(false))
+ << "Unrecognized versionRange " << value.get();
+ return false;
+ }
+ const nsCString& minValue = versionRange[0];
+ const nsCString& maxValue = versionRange[1];
+
+ mozilla::Version minV(minValue.get());
+ mozilla::Version maxV(maxValue.get());
+
+ if (minV > zeroV && !(appV >= minV)) {
+ // The version of the application is less than the minimal version
+ // this blocklist entry applies to, so we can just ignore it by
+ // returning false and letting the caller deal with it.
+ return false;
+ }
+ if (maxV > zeroV && !(appV <= maxV)) {
+ // The version of the application is more than the maximal version
+ // this blocklist entry applies to, so we can just ignore it by
+ // returning false and letting the caller deal with it.
+ return false;
+ }
+ } else if (key.EqualsLiteral("devices")) {
+ nsTArray<nsCString> devices;
+ ParseString(value, ',', devices);
+ GfxDeviceFamily* deviceIds = BlocklistDevicesToDeviceFamily(devices);
+ if (deviceIds) {
+ // Get GfxDriverInfo to adopt the devices array we created.
+ aDriverInfo.mDeleteDevices = true;
+ aDriverInfo.mDevices = deviceIds;
+ }
+ }
+ // We explicitly ignore unknown elements.
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+GfxInfoBase::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (strcmp(aTopic, "blocklist-data-gfxItems") == 0) {
+ nsTArray<GfxDriverInfo> driverInfo;
+ NS_ConvertUTF16toUTF8 utf8Data(aData);
+
+ for (const auto& blocklistEntry : utf8Data.Split('\n')) {
+ GfxDriverInfo di;
+ if (BlocklistEntryToDriverInfo(blocklistEntry, di)) {
+ // XXX Changing this to driverInfo.AppendElement(di) causes leaks.
+ // Probably some non-standard semantics of the copy/move operations?
+ *driverInfo.AppendElement() = di;
+ // Prevent di falling out of scope from destroying the devices.
+ di.mDeleteDevices = false;
+ } else {
+ driverInfo.AppendElement();
+ }
+ }
+
+ EvaluateDownloadedBlocklist(driverInfo);
+ }
+
+ return NS_OK;
+}
+
+GfxInfoBase::GfxInfoBase() : mScreenPixels(INT64_MAX), mMutex("GfxInfoBase") {}
+
+GfxInfoBase::~GfxInfoBase() = default;
+
+nsresult GfxInfoBase::Init() {
+ InitGfxDriverInfoShutdownObserver();
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->AddObserver(this, "blocklist-data-gfxItems", true);
+ }
+
+ return NS_OK;
+}
+
+void GfxInfoBase::GetData() {
+ if (mScreenPixels != INT64_MAX) {
+ // Already initialized.
+ return;
+ }
+
+ nsCOMPtr<nsIScreenManager> manager =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (!manager) {
+ MOZ_ASSERT_UNREACHABLE("failed to get nsIScreenManager");
+ return;
+ }
+
+ manager->GetTotalScreenPixels(&mScreenPixels);
+}
+
+NS_IMETHODIMP
+GfxInfoBase::GetFeatureStatus(int32_t aFeature, nsACString& aFailureId,
+ int32_t* aStatus) {
+ // Ignore the gfx.blocklist.all pref on release and beta.
+#if defined(RELEASE_OR_BETA)
+ int32_t blocklistAll = 0;
+#else
+ int32_t blocklistAll = StaticPrefs::gfx_blocklist_all_AtStartup();
+#endif
+ if (blocklistAll > 0) {
+ gfxCriticalErrorOnce(gfxCriticalError::DefaultOptions(false))
+ << "Forcing blocklisting all features";
+ *aStatus = FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_BLOCK_ALL";
+ return NS_OK;
+ } else if (blocklistAll < 0) {
+ gfxCriticalErrorOnce(gfxCriticalError::DefaultOptions(false))
+ << "Ignoring any feature blocklisting.";
+ *aStatus = FEATURE_STATUS_OK;
+ return NS_OK;
+ }
+
+ if (GetPrefValueForFeature(aFeature, *aStatus, aFailureId)) {
+ return NS_OK;
+ }
+
+ if (XRE_IsContentProcess() || XRE_IsGPUProcess()) {
+ // Use the cached data received from the parent process.
+ MOZ_ASSERT(sFeatureStatus);
+ bool success = false;
+ for (const auto& fs : *sFeatureStatus) {
+ if (fs.feature() == aFeature) {
+ aFailureId = fs.failureId();
+ *aStatus = fs.status();
+ success = true;
+ break;
+ }
+ }
+ return success ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+ nsString version;
+ nsTArray<GfxDriverInfo> driverInfo;
+ nsresult rv =
+ GetFeatureStatusImpl(aFeature, aStatus, version, driverInfo, aFailureId);
+ return rv;
+}
+
+nsTArray<gfx::GfxInfoFeatureStatus> GfxInfoBase::GetAllFeatures() {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ if (!sFeatureStatus) {
+ InitFeatureStatus(new nsTArray<gfx::GfxInfoFeatureStatus>());
+ for (int32_t i = 1; i <= nsIGfxInfo::FEATURE_MAX_VALUE; ++i) {
+ int32_t status = 0;
+ nsAutoCString failureId;
+ GetFeatureStatus(i, failureId, &status);
+ gfx::GfxInfoFeatureStatus gfxFeatureStatus;
+ gfxFeatureStatus.feature() = i;
+ gfxFeatureStatus.status() = status;
+ gfxFeatureStatus.failureId() = failureId;
+ sFeatureStatus->AppendElement(gfxFeatureStatus);
+ }
+ }
+
+ nsTArray<gfx::GfxInfoFeatureStatus> features;
+ for (const auto& status : *sFeatureStatus) {
+ gfx::GfxInfoFeatureStatus copy = status;
+ features.AppendElement(copy);
+ }
+ return features;
+}
+
+inline bool MatchingAllowStatus(int32_t aStatus) {
+ switch (aStatus) {
+ case nsIGfxInfo::FEATURE_ALLOW_ALWAYS:
+ case nsIGfxInfo::FEATURE_ALLOW_QUALIFIED:
+ return true;
+ default:
+ return false;
+ }
+}
+
+// Matching OS go somewhat beyond the simple equality check because of the
+// "All Windows" and "All OS X" variations.
+//
+// aBlockedOS is describing the system(s) we are trying to block.
+// aSystemOS is describing the system we are running on.
+//
+// aSystemOS should not be "Windows" or "OSX" - it should be set to
+// a particular version instead.
+// However, it is valid for aBlockedOS to be one of those generic values,
+// as we could be blocking all of the versions.
+inline bool MatchingOperatingSystems(OperatingSystem aBlockedOS,
+ OperatingSystem aSystemOS,
+ uint32_t aSystemOSBuild) {
+ MOZ_ASSERT(aSystemOS != OperatingSystem::Windows &&
+ aSystemOS != OperatingSystem::OSX);
+
+ // If the block entry OS is unknown, it doesn't match
+ if (aBlockedOS == OperatingSystem::Unknown) {
+ return false;
+ }
+
+#if defined(XP_WIN)
+ if (aBlockedOS == OperatingSystem::Windows) {
+ // We do want even "unknown" aSystemOS to fall under "all windows"
+ return true;
+ }
+
+ constexpr uint32_t kMinWin10BuildNumber = 18362;
+ if (aBlockedOS == OperatingSystem::RecentWindows10 &&
+ aSystemOS == OperatingSystem::Windows10) {
+ // For allowlist purposes, we sometimes want to restrict to only recent
+ // versions of Windows 10. This is a bit of a kludge but easier than adding
+ // complicated blocklist infrastructure for build ID comparisons like driver
+ // versions.
+ return aSystemOSBuild >= kMinWin10BuildNumber;
+ }
+
+ if (aBlockedOS == OperatingSystem::NotRecentWindows10) {
+ if (aSystemOS == OperatingSystem::Windows10) {
+ return aSystemOSBuild < kMinWin10BuildNumber;
+ } else {
+ return true;
+ }
+ }
+#endif
+
+#if defined(XP_MACOSX)
+ if (aBlockedOS == OperatingSystem::OSX) {
+ // We do want even "unknown" aSystemOS to fall under "all OS X"
+ return true;
+ }
+#endif
+
+ return aSystemOS == aBlockedOS;
+}
+
+inline bool MatchingBattery(BatteryStatus aBatteryStatus, bool aHasBattery) {
+ switch (aBatteryStatus) {
+ case BatteryStatus::All:
+ return true;
+ case BatteryStatus::None:
+ return !aHasBattery;
+ case BatteryStatus::Present:
+ return aHasBattery;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("bad battery status");
+ return false;
+}
+
+inline bool MatchingScreenSize(ScreenSizeStatus aScreenStatus,
+ int64_t aScreenPixels) {
+ constexpr int64_t kMaxSmallPixels = 2304000; // 1920x1200
+ constexpr int64_t kMaxMediumPixels = 4953600; // 3440x1440
+
+ switch (aScreenStatus) {
+ case ScreenSizeStatus::All:
+ return true;
+ case ScreenSizeStatus::Small:
+ return aScreenPixels <= kMaxSmallPixels;
+ case ScreenSizeStatus::SmallAndMedium:
+ return aScreenPixels <= kMaxMediumPixels;
+ case ScreenSizeStatus::Medium:
+ return aScreenPixels > kMaxSmallPixels &&
+ aScreenPixels <= kMaxMediumPixels;
+ case ScreenSizeStatus::MediumAndLarge:
+ return aScreenPixels > kMaxSmallPixels;
+ case ScreenSizeStatus::Large:
+ return aScreenPixels > kMaxMediumPixels;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("bad screen status");
+ return false;
+}
+
+int32_t GfxInfoBase::FindBlocklistedDeviceInList(
+ const nsTArray<GfxDriverInfo>& info, nsAString& aSuggestedVersion,
+ int32_t aFeature, nsACString& aFailureId, OperatingSystem os,
+ bool aForAllowing) {
+ int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+
+ // Some properties are not available on all platforms.
+ nsAutoString desktopEnvironment;
+ nsresult rv = GetDesktopEnvironment(desktopEnvironment);
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
+ return 0;
+ }
+
+ nsAutoString windowProtocol;
+ rv = GetWindowProtocol(windowProtocol);
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
+ return 0;
+ }
+
+ bool hasBattery = false;
+ rv = GetHasBattery(&hasBattery);
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
+ return 0;
+ }
+
+ uint32_t osBuild = OperatingSystemBuild();
+
+ // Get the adapters once then reuse below
+ nsAutoString adapterVendorID[2];
+ nsAutoString adapterDeviceID[2];
+ nsAutoString adapterDriverVendor[2];
+ nsAutoString adapterDriverVersionString[2];
+ bool adapterInfoFailed[2];
+
+ adapterInfoFailed[0] =
+ (NS_FAILED(GetAdapterVendorID(adapterVendorID[0])) ||
+ NS_FAILED(GetAdapterDeviceID(adapterDeviceID[0])) ||
+ NS_FAILED(GetAdapterDriverVendor(adapterDriverVendor[0])) ||
+ NS_FAILED(GetAdapterDriverVersion(adapterDriverVersionString[0])));
+ adapterInfoFailed[1] =
+ (NS_FAILED(GetAdapterVendorID2(adapterVendorID[1])) ||
+ NS_FAILED(GetAdapterDeviceID2(adapterDeviceID[1])) ||
+ NS_FAILED(GetAdapterDriverVendor2(adapterDriverVendor[1])) ||
+ NS_FAILED(GetAdapterDriverVersion2(adapterDriverVersionString[1])));
+ // No point in going on if we don't have adapter info
+ if (adapterInfoFailed[0] && adapterInfoFailed[1]) {
+ return 0;
+ }
+
+#if defined(XP_WIN) || defined(ANDROID) || defined(MOZ_X11)
+ uint64_t driverVersion[2] = {0, 0};
+ if (!adapterInfoFailed[0]) {
+ ParseDriverVersion(adapterDriverVersionString[0], &driverVersion[0]);
+ }
+ if (!adapterInfoFailed[1]) {
+ ParseDriverVersion(adapterDriverVersionString[1], &driverVersion[1]);
+ }
+#endif
+
+ uint32_t i = 0;
+ for (; i < info.Length(); i++) {
+ // If the status is FEATURE_ALLOW_*, then it is for the allowlist, not
+ // blocklisting. Only consider entries for our search mode.
+ if (MatchingAllowStatus(info[i].mFeatureStatus) != aForAllowing) {
+ continue;
+ }
+
+ // If we don't have the info for this GPU, no need to check further.
+ // It is unclear that we would ever have a mixture of 1st and 2nd
+ // GPU, but leaving the code in for that possibility for now.
+ // (Actually, currently mGpu2 will never be true, so this can
+ // be optimized out.)
+ uint32_t infoIndex = info[i].mGpu2 ? 1 : 0;
+ if (adapterInfoFailed[infoIndex]) {
+ continue;
+ }
+
+ // Do the operating system check first, no point in getting the driver
+ // info if we won't need to use it.
+ if (!MatchingOperatingSystems(info[i].mOperatingSystem, os, osBuild)) {
+ continue;
+ }
+
+ if (info[i].mOperatingSystemVersion &&
+ info[i].mOperatingSystemVersion != OperatingSystemVersion()) {
+ continue;
+ }
+
+ if (!MatchingBattery(info[i].mBattery, hasBattery)) {
+ continue;
+ }
+
+ if (!MatchingScreenSize(info[i].mScreen, mScreenPixels)) {
+ continue;
+ }
+
+ if (!DoesDesktopEnvironmentMatch(info[i].mDesktopEnvironment,
+ desktopEnvironment)) {
+ continue;
+ }
+
+ if (!DoesWindowProtocolMatch(info[i].mWindowProtocol, windowProtocol)) {
+ continue;
+ }
+
+ if (!DoesVendorMatch(info[i].mAdapterVendor, adapterVendorID[infoIndex])) {
+ continue;
+ }
+
+ if (!DoesDriverVendorMatch(info[i].mDriverVendor,
+ adapterDriverVendor[infoIndex])) {
+ continue;
+ }
+
+ if (info[i].mDevices && !info[i].mDevices->IsEmpty()) {
+ nsresult rv = info[i].mDevices->Contains(adapterDeviceID[infoIndex]);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ // Not found
+ continue;
+ }
+ if (rv != NS_OK) {
+ // Failed to search, allowlist should not match, blocklist should match
+ // for safety reasons
+ if (aForAllowing) {
+ continue;
+ }
+ break;
+ }
+ }
+
+ bool match = false;
+
+ if (!info[i].mHardware.IsEmpty() && !info[i].mHardware.Equals(Hardware())) {
+ continue;
+ }
+ if (!info[i].mModel.IsEmpty() && !info[i].mModel.Equals(Model())) {
+ continue;
+ }
+ if (!info[i].mProduct.IsEmpty() && !info[i].mProduct.Equals(Product())) {
+ continue;
+ }
+ if (!info[i].mManufacturer.IsEmpty() &&
+ !info[i].mManufacturer.Equals(Manufacturer())) {
+ continue;
+ }
+
+#if defined(XP_WIN) || defined(ANDROID) || defined(MOZ_X11)
+ switch (info[i].mComparisonOp) {
+ case DRIVER_LESS_THAN:
+ match = driverVersion[infoIndex] < info[i].mDriverVersion;
+ break;
+ case DRIVER_BUILD_ID_LESS_THAN:
+ match = (driverVersion[infoIndex] & 0xFFFF) < info[i].mDriverVersion;
+ break;
+ case DRIVER_LESS_THAN_OR_EQUAL:
+ match = driverVersion[infoIndex] <= info[i].mDriverVersion;
+ break;
+ case DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL:
+ match = (driverVersion[infoIndex] & 0xFFFF) <= info[i].mDriverVersion;
+ break;
+ case DRIVER_GREATER_THAN:
+ match = driverVersion[infoIndex] > info[i].mDriverVersion;
+ break;
+ case DRIVER_GREATER_THAN_OR_EQUAL:
+ match = driverVersion[infoIndex] >= info[i].mDriverVersion;
+ break;
+ case DRIVER_EQUAL:
+ match = driverVersion[infoIndex] == info[i].mDriverVersion;
+ break;
+ case DRIVER_NOT_EQUAL:
+ match = driverVersion[infoIndex] != info[i].mDriverVersion;
+ break;
+ case DRIVER_BETWEEN_EXCLUSIVE:
+ match = driverVersion[infoIndex] > info[i].mDriverVersion &&
+ driverVersion[infoIndex] < info[i].mDriverVersionMax;
+ break;
+ case DRIVER_BETWEEN_INCLUSIVE:
+ match = driverVersion[infoIndex] >= info[i].mDriverVersion &&
+ driverVersion[infoIndex] <= info[i].mDriverVersionMax;
+ break;
+ case DRIVER_BETWEEN_INCLUSIVE_START:
+ match = driverVersion[infoIndex] >= info[i].mDriverVersion &&
+ driverVersion[infoIndex] < info[i].mDriverVersionMax;
+ break;
+ case DRIVER_COMPARISON_IGNORED:
+ // We don't have a comparison op, so we match everything.
+ match = true;
+ break;
+ default:
+ NS_WARNING("Bogus op in GfxDriverInfo");
+ break;
+ }
+#else
+ // We don't care what driver version it was. We only check OS version and if
+ // the device matches.
+ match = true;
+#endif
+
+ if (match || info[i].mDriverVersion == GfxDriverInfo::allDriverVersions) {
+ if ((info[i].mFeature == GfxDriverInfo::allFeatures &&
+ aFeature != nsIGfxInfo::FEATURE_WEBRENDER_SOFTWARE) ||
+ info[i].mFeature == aFeature) {
+ status = info[i].mFeatureStatus;
+ if (!info[i].mRuleId.IsEmpty()) {
+ aFailureId = info[i].mRuleId.get();
+ } else {
+ aFailureId = "FEATURE_FAILURE_DL_BLOCKLIST_NO_ID";
+ }
+ break;
+ }
+ }
+ }
+
+#if defined(XP_WIN)
+ // As a very special case, we block D2D on machines with an NVidia 310M GPU
+ // as either the primary or secondary adapter. D2D is also blocked when the
+ // NV 310M is the primary adapter (using the standard blocklisting mechanism).
+ // If the primary GPU already matched something in the blocklist then we
+ // ignore this special rule. See bug 1008759.
+ if (status == nsIGfxInfo::FEATURE_STATUS_UNKNOWN &&
+ (aFeature == nsIGfxInfo::FEATURE_DIRECT2D)) {
+ if (!adapterInfoFailed[1]) {
+ nsAString& nvVendorID =
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(DeviceVendor::NVIDIA);
+ const nsString nv310mDeviceId = u"0x0A70"_ns;
+ if (nvVendorID.Equals(adapterVendorID[1],
+ nsCaseInsensitiveStringComparator) &&
+ nv310mDeviceId.Equals(adapterDeviceID[1],
+ nsCaseInsensitiveStringComparator)) {
+ status = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_D2D_NV310M_BLOCK";
+ }
+ }
+ }
+
+ // Depends on Windows driver versioning. We don't pass a GfxDriverInfo object
+ // back to the Windows handler, so we must handle this here.
+ if (status == FEATURE_BLOCKED_DRIVER_VERSION) {
+ if (info[i].mSuggestedVersion) {
+ aSuggestedVersion.AppendPrintf("%s", info[i].mSuggestedVersion);
+ } else if (info[i].mComparisonOp == DRIVER_LESS_THAN &&
+ info[i].mDriverVersion != GfxDriverInfo::allDriverVersions) {
+ aSuggestedVersion.AppendPrintf(
+ "%lld.%lld.%lld.%lld",
+ (info[i].mDriverVersion & 0xffff000000000000) >> 48,
+ (info[i].mDriverVersion & 0x0000ffff00000000) >> 32,
+ (info[i].mDriverVersion & 0x00000000ffff0000) >> 16,
+ (info[i].mDriverVersion & 0x000000000000ffff));
+ }
+ }
+#endif
+
+ return status;
+}
+
+void GfxInfoBase::SetFeatureStatus(nsTArray<gfx::GfxInfoFeatureStatus>&& aFS) {
+ MOZ_ASSERT(!sFeatureStatus);
+ InitFeatureStatus(new nsTArray<gfx::GfxInfoFeatureStatus>(std::move(aFS)));
+}
+
+bool GfxInfoBase::DoesDesktopEnvironmentMatch(
+ const nsAString& aBlocklistDesktop, const nsAString& aDesktopEnv) {
+ return aBlocklistDesktop.Equals(aDesktopEnv,
+ nsCaseInsensitiveStringComparator) ||
+ aBlocklistDesktop.Equals(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::All),
+ nsCaseInsensitiveStringComparator);
+}
+
+bool GfxInfoBase::DoesWindowProtocolMatch(
+ const nsAString& aBlocklistWindowProtocol,
+ const nsAString& aWindowProtocol) {
+ return aBlocklistWindowProtocol.Equals(aWindowProtocol,
+ nsCaseInsensitiveStringComparator) ||
+ aBlocklistWindowProtocol.Equals(
+ GfxDriverInfo::GetWindowProtocol(WindowProtocol::All),
+ nsCaseInsensitiveStringComparator);
+}
+
+bool GfxInfoBase::DoesVendorMatch(const nsAString& aBlocklistVendor,
+ const nsAString& aAdapterVendor) {
+ return aBlocklistVendor.Equals(aAdapterVendor,
+ nsCaseInsensitiveStringComparator) ||
+ aBlocklistVendor.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::All),
+ nsCaseInsensitiveStringComparator);
+}
+
+bool GfxInfoBase::DoesDriverVendorMatch(const nsAString& aBlocklistVendor,
+ const nsAString& aDriverVendor) {
+ return aBlocklistVendor.Equals(aDriverVendor,
+ nsCaseInsensitiveStringComparator) ||
+ aBlocklistVendor.Equals(
+ GfxDriverInfo::GetDriverVendor(DriverVendor::All),
+ nsCaseInsensitiveStringComparator);
+}
+
+bool GfxInfoBase::IsFeatureAllowlisted(int32_t aFeature) const {
+ return aFeature == nsIGfxInfo::FEATURE_WEBRENDER ||
+ aFeature == nsIGfxInfo::FEATURE_WEBRENDER_SOFTWARE;
+}
+
+nsresult GfxInfoBase::GetFeatureStatusImpl(
+ int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
+ OperatingSystem* aOS /* = nullptr */) {
+ if (aFeature <= 0) {
+ gfxWarning() << "Invalid feature <= 0";
+ return NS_OK;
+ }
+
+ if (*aStatus != nsIGfxInfo::FEATURE_STATUS_UNKNOWN) {
+ // Terminate now with the status determined by the derived type (OS-specific
+ // code).
+ return NS_OK;
+ }
+
+ if (sShutdownOccurred) {
+ // This is futile; we've already commenced shutdown and our blocklists have
+ // been deleted. We may want to look into resurrecting the blocklist instead
+ // but for now, just don't even go there.
+ return NS_OK;
+ }
+
+ // Ensure any additional initialization required is complete.
+ GetData();
+
+ // If an operating system was provided by the derived GetFeatureStatusImpl,
+ // grab it here. Otherwise, the OS is unknown.
+ OperatingSystem os = (aOS ? *aOS : OperatingSystem::Unknown);
+
+ nsAutoString adapterVendorID;
+ nsAutoString adapterDeviceID;
+ nsAutoString adapterDriverVersionString;
+ if (NS_FAILED(GetAdapterVendorID(adapterVendorID)) ||
+ NS_FAILED(GetAdapterDeviceID(adapterDeviceID)) ||
+ NS_FAILED(GetAdapterDriverVersion(adapterDriverVersionString))) {
+ aFailureId = "FEATURE_FAILURE_CANT_RESOLVE_ADAPTER";
+ *aStatus = FEATURE_BLOCKED_DEVICE;
+ return NS_OK;
+ }
+
+ // Check if the device is blocked from the downloaded blocklist. If not, check
+ // the static list after that. This order is used so that we can later escape
+ // out of static blocks (i.e. if we were wrong or something was patched, we
+ // can back out our static block without doing a release).
+ int32_t status;
+ if (aDriverInfo.Length()) {
+ status =
+ FindBlocklistedDeviceInList(aDriverInfo, aSuggestedVersion, aFeature,
+ aFailureId, os, /* aForAllowing */ false);
+ } else {
+ if (!sDriverInfo) {
+ sDriverInfo = new nsTArray<GfxDriverInfo>();
+ }
+ status = FindBlocklistedDeviceInList(GetGfxDriverInfo(), aSuggestedVersion,
+ aFeature, aFailureId, os,
+ /* aForAllowing */ false);
+ }
+
+ if (status == nsIGfxInfo::FEATURE_STATUS_UNKNOWN) {
+ if (IsFeatureAllowlisted(aFeature)) {
+ // This feature is actually using the allowlist; that means after we pass
+ // the blocklist to prevent us explicitly from getting the feature, we now
+ // need to check the allowlist to ensure we are allowed to get it in the
+ // first place.
+ if (aDriverInfo.Length()) {
+ status = FindBlocklistedDeviceInList(aDriverInfo, aSuggestedVersion,
+ aFeature, aFailureId, os,
+ /* aForAllowing */ true);
+ } else {
+ status = FindBlocklistedDeviceInList(
+ GetGfxDriverInfo(), aSuggestedVersion, aFeature, aFailureId, os,
+ /* aForAllowing */ true);
+ }
+
+ if (status == nsIGfxInfo::FEATURE_STATUS_UNKNOWN) {
+ status = nsIGfxInfo::FEATURE_DENIED;
+ }
+ } else {
+ // It's now done being processed. It's safe to set the status to
+ // STATUS_OK.
+ status = nsIGfxInfo::FEATURE_STATUS_OK;
+ }
+ }
+
+ *aStatus = status;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfoBase::GetFeatureSuggestedDriverVersion(int32_t aFeature,
+ nsAString& aVersion) {
+ nsCString version;
+ if (GetPrefValueForDriverVersion(version)) {
+ aVersion = NS_ConvertASCIItoUTF16(version);
+ return NS_OK;
+ }
+
+ int32_t status;
+ nsCString discardFailureId;
+ nsTArray<GfxDriverInfo> driverInfo;
+ return GetFeatureStatusImpl(aFeature, &status, aVersion, driverInfo,
+ discardFailureId);
+}
+
+void GfxInfoBase::EvaluateDownloadedBlocklist(
+ nsTArray<GfxDriverInfo>& aDriverInfo) {
+ int32_t features[] = {nsIGfxInfo::FEATURE_DIRECT2D,
+ nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS,
+ nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS,
+ nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS,
+ nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
+ nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS,
+ nsIGfxInfo::FEATURE_WEBGL_OPENGL,
+ nsIGfxInfo::FEATURE_WEBGL_ANGLE,
+ nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_ENCODE,
+ nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_DECODE,
+ nsIGfxInfo::UNUSED_FEATURE_WEBGL_MSAA,
+ nsIGfxInfo::FEATURE_STAGEFRIGHT,
+ nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_H264,
+ nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION,
+ nsIGfxInfo::FEATURE_VP8_HW_DECODE,
+ nsIGfxInfo::FEATURE_VP9_HW_DECODE,
+ nsIGfxInfo::FEATURE_DX_INTEROP2,
+ nsIGfxInfo::FEATURE_GPU_PROCESS,
+ nsIGfxInfo::FEATURE_WEBGL2,
+ nsIGfxInfo::FEATURE_ADVANCED_LAYERS,
+ nsIGfxInfo::FEATURE_D3D11_KEYED_MUTEX,
+ nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR,
+ nsIGfxInfo::FEATURE_WEBRENDER_SOFTWARE,
+ nsIGfxInfo::FEATURE_DX_NV12,
+ nsIGfxInfo::FEATURE_DX_P010,
+ nsIGfxInfo::FEATURE_DX_P016,
+ nsIGfxInfo::FEATURE_GL_SWIZZLE,
+ nsIGfxInfo::FEATURE_ALLOW_WEBGL_OUT_OF_PROCESS,
+ 0};
+
+ // For every feature we know about, we evaluate whether this blocklist has a
+ // non-STATUS_OK status. If it does, we set the pref we evaluate in
+ // GetFeatureStatus above, so we don't need to hold on to this blocklist
+ // anywhere permanent.
+ int i = 0;
+ while (features[i]) {
+ int32_t status;
+ nsCString failureId;
+ nsAutoString suggestedVersion;
+ if (NS_SUCCEEDED(GetFeatureStatusImpl(
+ features[i], &status, suggestedVersion, aDriverInfo, failureId))) {
+ switch (status) {
+ default:
+ case nsIGfxInfo::FEATURE_STATUS_OK:
+ RemovePrefForFeature(features[i]);
+ break;
+
+ case nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION:
+ if (!suggestedVersion.IsEmpty()) {
+ SetPrefValueForDriverVersion(suggestedVersion);
+ } else {
+ RemovePrefForDriverVersion();
+ }
+ [[fallthrough]];
+
+ case nsIGfxInfo::FEATURE_BLOCKED_MISMATCHED_VERSION:
+ case nsIGfxInfo::FEATURE_BLOCKED_DEVICE:
+ case nsIGfxInfo::FEATURE_DISCOURAGED:
+ case nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION:
+ SetPrefValueForFeature(features[i], status, failureId);
+ break;
+ }
+ }
+
+ ++i;
+ }
+}
+
+NS_IMETHODIMP_(void)
+GfxInfoBase::LogFailure(const nsACString& failure) {
+ // gfxCriticalError has a mutex lock of its own, so we may not actually
+ // need this lock. ::GetFailures() accesses the data but the LogForwarder
+ // will not return the copy of the logs unless it can get the same lock
+ // that gfxCriticalError uses. Still, that is so much of an implementation
+ // detail that it's nicer to just add an extra lock here and in
+ // ::GetFailures()
+ MutexAutoLock lock(mMutex);
+
+ // By default, gfxCriticalError asserts; make it not assert in this case.
+ gfxCriticalError(CriticalLog::DefaultOptions(false))
+ << "(LF) " << failure.BeginReading();
+}
+
+NS_IMETHODIMP GfxInfoBase::GetFailures(nsTArray<int32_t>& indices,
+ nsTArray<nsCString>& failures) {
+ MutexAutoLock lock(mMutex);
+
+ LogForwarder* logForwarder = Factory::GetLogForwarder();
+ if (!logForwarder) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // There are two string copies in this method, starting with this one. We are
+ // assuming this is not a big deal, as the size of the array should be small
+ // and the strings in it should be small as well (the error messages in the
+ // code.) The second copy happens with the AppendElement() calls.
+ // Technically, we don't need the mutex lock after the StringVectorCopy()
+ // call.
+ LoggingRecord loggedStrings = logForwarder->LoggingRecordCopy();
+ LoggingRecord::const_iterator it;
+ for (it = loggedStrings.begin(); it != loggedStrings.end(); ++it) {
+ failures.AppendElement(
+ nsDependentCSubstring(Get<1>(*it).c_str(), Get<1>(*it).size()));
+ indices.AppendElement(Get<0>(*it));
+ }
+
+ return NS_OK;
+}
+
+nsTArray<GfxInfoCollectorBase*>* sCollectors;
+
+static void InitCollectors() {
+ if (!sCollectors) sCollectors = new nsTArray<GfxInfoCollectorBase*>;
+}
+
+nsresult GfxInfoBase::GetInfo(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aResult) {
+ InitCollectors();
+ InfoObject obj(aCx);
+
+ for (uint32_t i = 0; i < sCollectors->Length(); i++) {
+ (*sCollectors)[i]->GetInfo(obj);
+ }
+
+ // Some example property definitions
+ // obj.DefineProperty("wordCacheSize", gfxTextRunWordCache::Count());
+ // obj.DefineProperty("renderer", mRendererIDsString);
+ // obj.DefineProperty("five", 5);
+
+ if (!obj.mOk) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aResult.setObject(*obj.mObj);
+ return NS_OK;
+}
+
+nsAutoCString gBaseAppVersion;
+
+const nsCString& GfxInfoBase::GetApplicationVersion() {
+ static bool versionInitialized = false;
+ if (!versionInitialized) {
+ // If we fail to get the version, we will not try again.
+ versionInitialized = true;
+
+ // Get the version from xpcom/system/nsIXULAppInfo.idl
+ nsCOMPtr<nsIXULAppInfo> app = do_GetService("@mozilla.org/xre/app-info;1");
+ if (app) {
+ app->GetVersion(gBaseAppVersion);
+ }
+ }
+ return gBaseAppVersion;
+}
+
+void GfxInfoBase::AddCollector(GfxInfoCollectorBase* collector) {
+ InitCollectors();
+ sCollectors->AppendElement(collector);
+}
+
+void GfxInfoBase::RemoveCollector(GfxInfoCollectorBase* collector) {
+ InitCollectors();
+ for (uint32_t i = 0; i < sCollectors->Length(); i++) {
+ if ((*sCollectors)[i] == collector) {
+ sCollectors->RemoveElementAt(i);
+ break;
+ }
+ }
+ if (sCollectors->IsEmpty()) {
+ delete sCollectors;
+ sCollectors = nullptr;
+ }
+}
+
+nsresult GfxInfoBase::FindMonitors(JSContext* aCx, JS::HandleObject aOutArray) {
+ // If we have no platform specific implementation for detecting monitors, we
+ // can just get the screen size from gfxPlatform as the best guess.
+ if (!gfxPlatform::Initialized()) {
+ return NS_OK;
+ }
+
+ // If the screen size is empty, we are probably in xpcshell.
+ gfx::IntSize screenSize = gfxPlatform::GetPlatform()->GetScreenSize();
+
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+
+ JS::Rooted<JS::Value> screenWidth(aCx, JS::Int32Value(screenSize.width));
+ JS_SetProperty(aCx, obj, "screenWidth", screenWidth);
+
+ JS::Rooted<JS::Value> screenHeight(aCx, JS::Int32Value(screenSize.height));
+ JS_SetProperty(aCx, obj, "screenHeight", screenHeight);
+
+ JS::Rooted<JS::Value> element(aCx, JS::ObjectValue(*obj));
+ JS_SetElement(aCx, aOutArray, 0, element);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfoBase::GetMonitors(JSContext* aCx, JS::MutableHandleValue aResult) {
+ JS::Rooted<JSObject*> array(aCx, JS::NewArrayObject(aCx, 0));
+
+ nsresult rv = FindMonitors(aCx, array);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ aResult.setObject(*array);
+ return NS_OK;
+}
+
+static inline bool SetJSPropertyString(JSContext* aCx,
+ JS::Handle<JSObject*> aObj,
+ const char* aProp, const char* aString) {
+ JS::Rooted<JSString*> str(aCx, JS_NewStringCopyZ(aCx, aString));
+ if (!str) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> val(aCx, JS::StringValue(str));
+ return JS_SetProperty(aCx, aObj, aProp, val);
+}
+
+template <typename T>
+static inline bool AppendJSElement(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ const T& aValue) {
+ uint32_t index;
+ if (!JS::GetArrayLength(aCx, aObj, &index)) {
+ return false;
+ }
+ return JS_SetElement(aCx, aObj, index, aValue);
+}
+
+nsresult GfxInfoBase::GetFeatures(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aOut) {
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+ if (!obj) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ aOut.setObject(*obj);
+
+ layers::LayersBackend backend =
+ gfxPlatform::Initialized()
+ ? gfxPlatform::GetPlatform()->GetCompositorBackend()
+ : layers::LayersBackend::LAYERS_NONE;
+ const char* backendName = layers::GetLayersBackendName(backend);
+ SetJSPropertyString(aCx, obj, "compositor", backendName);
+
+ // If graphics isn't initialized yet, just stop now.
+ if (!gfxPlatform::Initialized()) {
+ return NS_OK;
+ }
+
+ DescribeFeatures(aCx, obj);
+ return NS_OK;
+}
+
+nsresult GfxInfoBase::GetFeatureLog(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aOut) {
+ JS::Rooted<JSObject*> containerObj(aCx, JS_NewPlainObject(aCx));
+ if (!containerObj) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ aOut.setObject(*containerObj);
+
+ JS::Rooted<JSObject*> featureArray(aCx, JS::NewArrayObject(aCx, 0));
+ if (!featureArray) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Collect features.
+ gfxConfig::ForEachFeature([&](const char* aName, const char* aDescription,
+ FeatureState& aFeature) -> void {
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+ if (!obj) {
+ return;
+ }
+ if (!SetJSPropertyString(aCx, obj, "name", aName) ||
+ !SetJSPropertyString(aCx, obj, "description", aDescription) ||
+ !SetJSPropertyString(aCx, obj, "status",
+ FeatureStatusToString(aFeature.GetValue()))) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> log(aCx);
+ if (!BuildFeatureStateLog(aCx, aFeature, &log)) {
+ return;
+ }
+ if (!JS_SetProperty(aCx, obj, "log", log)) {
+ return;
+ }
+
+ if (!AppendJSElement(aCx, featureArray, obj)) {
+ return;
+ }
+ });
+
+ JS::Rooted<JSObject*> fallbackArray(aCx, JS::NewArrayObject(aCx, 0));
+ if (!fallbackArray) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Collect fallbacks.
+ gfxConfig::ForEachFallback(
+ [&](const char* aName, const char* aMessage) -> void {
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+ if (!obj) {
+ return;
+ }
+
+ if (!SetJSPropertyString(aCx, obj, "name", aName) ||
+ !SetJSPropertyString(aCx, obj, "message", aMessage)) {
+ return;
+ }
+
+ if (!AppendJSElement(aCx, fallbackArray, obj)) {
+ return;
+ }
+ });
+
+ JS::Rooted<JS::Value> val(aCx);
+
+ val = JS::ObjectValue(*featureArray);
+ JS_SetProperty(aCx, containerObj, "features", val);
+
+ val = JS::ObjectValue(*fallbackArray);
+ JS_SetProperty(aCx, containerObj, "fallbacks", val);
+
+ return NS_OK;
+}
+
+bool GfxInfoBase::BuildFeatureStateLog(JSContext* aCx,
+ const FeatureState& aFeature,
+ JS::MutableHandle<JS::Value> aOut) {
+ JS::Rooted<JSObject*> log(aCx, JS::NewArrayObject(aCx, 0));
+ if (!log) {
+ return false;
+ }
+ aOut.setObject(*log);
+
+ aFeature.ForEachStatusChange([&](const char* aType, FeatureStatus aStatus,
+ const char* aMessage,
+ const nsCString& aFailureId) -> void {
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+ if (!obj) {
+ return;
+ }
+
+ if (!SetJSPropertyString(aCx, obj, "type", aType) ||
+ !SetJSPropertyString(aCx, obj, "status",
+ FeatureStatusToString(aStatus)) ||
+ (aMessage && !SetJSPropertyString(aCx, obj, "message", aMessage))) {
+ return;
+ }
+
+ if (!AppendJSElement(aCx, log, obj)) {
+ return;
+ }
+ });
+
+ return true;
+}
+
+void GfxInfoBase::DescribeFeatures(JSContext* aCx, JS::Handle<JSObject*> aObj) {
+ JS::Rooted<JSObject*> obj(aCx);
+
+ gfx::FeatureState& hwCompositing =
+ gfxConfig::GetFeature(gfx::Feature::HW_COMPOSITING);
+ InitFeatureObject(aCx, aObj, "hwCompositing", hwCompositing, &obj);
+
+ gfx::FeatureState& gpuProcess =
+ gfxConfig::GetFeature(gfx::Feature::GPU_PROCESS);
+ InitFeatureObject(aCx, aObj, "gpuProcess", gpuProcess, &obj);
+
+ gfx::FeatureState& wrQualified =
+ gfxConfig::GetFeature(gfx::Feature::WEBRENDER_QUALIFIED);
+ InitFeatureObject(aCx, aObj, "wrQualified", wrQualified, &obj);
+
+ gfx::FeatureState& webrender = gfxConfig::GetFeature(gfx::Feature::WEBRENDER);
+ InitFeatureObject(aCx, aObj, "webrender", webrender, &obj);
+
+ gfx::FeatureState& wrCompositor =
+ gfxConfig::GetFeature(gfx::Feature::WEBRENDER_COMPOSITOR);
+ InitFeatureObject(aCx, aObj, "wrCompositor", wrCompositor, &obj);
+
+ gfx::FeatureState& wrSoftware =
+ gfxConfig::GetFeature(gfx::Feature::WEBRENDER_SOFTWARE);
+ InitFeatureObject(aCx, aObj, "wrSoftware", wrSoftware, &obj);
+
+ gfx::FeatureState& openglCompositing =
+ gfxConfig::GetFeature(gfx::Feature::OPENGL_COMPOSITING);
+ InitFeatureObject(aCx, aObj, "openglCompositing", openglCompositing, &obj);
+
+ gfx::FeatureState& omtp = gfxConfig::GetFeature(gfx::Feature::OMTP);
+ InitFeatureObject(aCx, aObj, "omtp", omtp, &obj);
+
+ // Only include AL if the platform attempted to use it.
+ gfx::FeatureState& advancedLayers =
+ gfxConfig::GetFeature(gfx::Feature::ADVANCED_LAYERS);
+ if (advancedLayers.GetValue() != FeatureStatus::Unused) {
+ InitFeatureObject(aCx, aObj, "advancedLayers", advancedLayers, &obj);
+
+ if (gfxConfig::UseFallback(Fallback::NO_CONSTANT_BUFFER_OFFSETTING)) {
+ JS::Rooted<JS::Value> trueVal(aCx, JS::BooleanValue(true));
+ JS_SetProperty(aCx, obj, "noConstantBufferOffsetting", trueVal);
+ }
+ }
+}
+
+bool GfxInfoBase::InitFeatureObject(JSContext* aCx,
+ JS::Handle<JSObject*> aContainer,
+ const char* aName,
+ mozilla::gfx::FeatureState& aFeatureState,
+ JS::MutableHandle<JSObject*> aOutObj) {
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+ if (!obj) {
+ return false;
+ }
+
+ nsCString status = aFeatureState.GetStatusAndFailureIdString();
+
+ JS::Rooted<JSString*> str(aCx, JS_NewStringCopyZ(aCx, status.get()));
+ JS::Rooted<JS::Value> val(aCx, JS::StringValue(str));
+ JS_SetProperty(aCx, obj, "status", val);
+
+ // Add the feature object to the container.
+ {
+ JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*obj));
+ JS_SetProperty(aCx, aContainer, aName, val);
+ }
+
+ aOutObj.set(obj);
+ return true;
+}
+
+nsresult GfxInfoBase::GetActiveCrashGuards(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aOut) {
+ JS::Rooted<JSObject*> array(aCx, JS::NewArrayObject(aCx, 0));
+ if (!array) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ aOut.setObject(*array);
+
+ DriverCrashGuard::ForEachActiveCrashGuard(
+ [&](const char* aName, const char* aPrefName) -> void {
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+ if (!obj) {
+ return;
+ }
+ if (!SetJSPropertyString(aCx, obj, "type", aName)) {
+ return;
+ }
+ if (!SetJSPropertyString(aCx, obj, "prefName", aPrefName)) {
+ return;
+ }
+ if (!AppendJSElement(aCx, array, obj)) {
+ return;
+ }
+ });
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfoBase::GetWebRenderEnabled(bool* aWebRenderEnabled) {
+ *aWebRenderEnabled = gfxVars::UseWebRender();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfoBase::GetUsesTiling(bool* aUsesTiling) {
+ *aUsesTiling = gfxPlatform::GetPlatform()->UsesTiling();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfoBase::GetContentUsesTiling(bool* aUsesTiling) {
+ *aUsesTiling = gfxPlatform::GetPlatform()->ContentUsesTiling();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfoBase::GetOffMainThreadPaintEnabled(bool* aOffMainThreadPaintEnabled) {
+ *aOffMainThreadPaintEnabled = gfxConfig::IsEnabled(gfx::Feature::OMTP);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfoBase::GetOffMainThreadPaintWorkerCount(
+ int32_t* aOffMainThreadPaintWorkerCount) {
+ if (gfxConfig::IsEnabled(gfx::Feature::OMTP)) {
+ *aOffMainThreadPaintWorkerCount =
+ layers::PaintThread::CalculatePaintWorkerCount();
+ } else {
+ *aOffMainThreadPaintWorkerCount = 0;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfoBase::GetTargetFrameRate(uint32_t* aTargetFrameRate) {
+ *aTargetFrameRate = gfxPlatform::TargetFrameRate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfoBase::GetIsHeadless(bool* aIsHeadless) {
+ *aIsHeadless = gfxPlatform::IsHeadless();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfoBase::GetContentBackend(nsAString& aContentBackend) {
+ BackendType backend = gfxPlatform::GetPlatform()->GetDefaultContentBackend();
+ nsString outStr;
+
+ switch (backend) {
+ case BackendType::DIRECT2D1_1: {
+ outStr.AppendPrintf("Direct2D 1.1");
+ break;
+ }
+ case BackendType::SKIA: {
+ outStr.AppendPrintf("Skia");
+ break;
+ }
+ case BackendType::CAIRO: {
+ outStr.AppendPrintf("Cairo");
+ break;
+ }
+ default:
+ return NS_ERROR_FAILURE;
+ }
+
+ aContentBackend.Assign(outStr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfoBase::GetUsingGPUProcess(bool* aOutValue) {
+ GPUProcessManager* gpu = GPUProcessManager::Get();
+ if (!gpu) {
+ // Not supported in content processes.
+ return NS_ERROR_FAILURE;
+ }
+
+ *aOutValue = !!gpu->GetGPUChild();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfoBase::ControlGPUProcessForXPCShell(bool aEnable, bool* _retval) {
+ gfxPlatform::GetPlatform();
+
+ GPUProcessManager* gpm = GPUProcessManager::Get();
+ if (aEnable) {
+ if (!gfxConfig::IsEnabled(gfx::Feature::GPU_PROCESS)) {
+ gfxConfig::UserForceEnable(gfx::Feature::GPU_PROCESS, "xpcshell-test");
+ }
+ gpm->LaunchGPUProcess();
+ gpm->EnsureGPUReady();
+ } else {
+ gfxConfig::UserDisable(gfx::Feature::GPU_PROCESS, "xpcshell-test");
+ gpm->KillProcess();
+ }
+
+ *_retval = true;
+ return NS_OK;
+}
+
+GfxInfoCollectorBase::GfxInfoCollectorBase() {
+ GfxInfoBase::AddCollector(this);
+}
+
+GfxInfoCollectorBase::~GfxInfoCollectorBase() {
+ GfxInfoBase::RemoveCollector(this);
+}
diff --git a/widget/GfxInfoBase.h b/widget/GfxInfoBase.h
new file mode 100644
index 0000000000..2b299cd2c8
--- /dev/null
+++ b/widget/GfxInfoBase.h
@@ -0,0 +1,179 @@
+/* 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_GfxInfoBase_h__
+#define __mozilla_widget_GfxInfoBase_h__
+
+#include "GfxDriverInfo.h"
+#include "GfxInfoCollector.h"
+#include "gfxFeature.h"
+#include "gfxTelemetry.h"
+#include "js/Value.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/gfx/GraphicsMessages.h"
+#include "nsCOMPtr.h"
+#include "nsIGfxInfo.h"
+#include "nsIGfxInfoDebug.h"
+#include "nsIObserver.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace widget {
+
+class GfxInfoBase : public nsIGfxInfo,
+ public nsIObserver,
+ public nsSupportsWeakReference
+#ifdef DEBUG
+ ,
+ public nsIGfxInfoDebug
+#endif
+{
+ public:
+ GfxInfoBase();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ // We only declare a subset of the nsIGfxInfo interface. It's up to derived
+ // classes to implement the rest of the interface.
+ // Derived classes need to use
+ // using GfxInfoBase::GetFeatureStatus;
+ // using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+ // to import the relevant methods into their namespace.
+ NS_IMETHOD GetFeatureStatus(int32_t aFeature, nsACString& aFailureId,
+ int32_t* _retval) override;
+ NS_IMETHOD GetFeatureSuggestedDriverVersion(int32_t aFeature,
+ nsAString& _retval) override;
+
+ NS_IMETHOD GetMonitors(JSContext* cx,
+ JS::MutableHandleValue _retval) override;
+ NS_IMETHOD GetFailures(nsTArray<int32_t>& indices,
+ nsTArray<nsCString>& failures) override;
+ NS_IMETHOD_(void) LogFailure(const nsACString& failure) override;
+ NS_IMETHOD GetInfo(JSContext*, JS::MutableHandle<JS::Value>) override;
+ NS_IMETHOD GetFeatures(JSContext*, JS::MutableHandle<JS::Value>) override;
+ NS_IMETHOD GetFeatureLog(JSContext*, JS::MutableHandle<JS::Value>) override;
+ NS_IMETHOD GetActiveCrashGuards(JSContext*,
+ JS::MutableHandle<JS::Value>) override;
+ NS_IMETHOD GetContentBackend(nsAString& aContentBackend) override;
+ NS_IMETHOD GetUsingGPUProcess(bool* aOutValue) override;
+ NS_IMETHOD GetWebRenderEnabled(bool* aWebRenderEnabled) override;
+ NS_IMETHOD GetIsHeadless(bool* aIsHeadless) override;
+ NS_IMETHOD GetUsesTiling(bool* aUsesTiling) override;
+ NS_IMETHOD GetContentUsesTiling(bool* aUsesTiling) override;
+ NS_IMETHOD GetOffMainThreadPaintEnabled(
+ bool* aOffMainThreadPaintEnabled) override;
+ NS_IMETHOD GetOffMainThreadPaintWorkerCount(
+ int32_t* aOffMainThreadPaintWorkerCount) override;
+ NS_IMETHOD GetTargetFrameRate(uint32_t* aTargetFrameRate) override;
+
+ // Non-XPCOM method to get IPC data:
+ nsTArray<mozilla::gfx::GfxInfoFeatureStatus> GetAllFeatures();
+
+ // Initialization function. If you override this, you must call this class's
+ // version of Init first.
+ // We need Init to be called separately from the constructor so we can
+ // register as an observer after all derived classes have been constructed
+ // and we know we have a non-zero refcount.
+ // Ideally, Init() would be void-return, but the rules of
+ // NS_GENERIC_FACTORY_CONSTRUCTOR_INIT require it be nsresult return.
+ virtual nsresult Init();
+
+ NS_IMETHOD_(void) GetData() override;
+ NS_IMETHOD_(int32_t) GetMaxRefreshRate(bool* aMixed) override {
+ if (aMixed) {
+ *aMixed = false;
+ }
+ return -1;
+ }
+
+ static void AddCollector(GfxInfoCollectorBase* collector);
+ static void RemoveCollector(GfxInfoCollectorBase* collector);
+
+ static nsTArray<GfxDriverInfo>* sDriverInfo;
+ static StaticAutoPtr<nsTArray<mozilla::gfx::GfxInfoFeatureStatus>>
+ sFeatureStatus;
+ static bool sDriverInfoObserverInitialized;
+ static bool sShutdownOccurred;
+
+ virtual nsString Model() { return u""_ns; }
+ virtual nsString Hardware() { return u""_ns; }
+ virtual nsString Product() { return u""_ns; }
+ virtual nsString Manufacturer() { return u""_ns; }
+ virtual uint32_t OperatingSystemVersion() { return 0; }
+ virtual uint32_t OperatingSystemBuild() { return 0; }
+
+ // Convenience to get the application version
+ static const nsCString& GetApplicationVersion();
+
+ virtual nsresult FindMonitors(JSContext* cx, JS::HandleObject array);
+
+ static void SetFeatureStatus(
+ nsTArray<mozilla::gfx::GfxInfoFeatureStatus>&& aFS);
+
+ protected:
+ virtual ~GfxInfoBase();
+
+ virtual nsresult GetFeatureStatusImpl(
+ int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
+ OperatingSystem* aOS = nullptr);
+
+ // Gets the driver info table. Used by GfxInfoBase to check for general cases
+ // (while subclasses check for more specific ones).
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() = 0;
+
+ virtual void DescribeFeatures(JSContext* aCx, JS::Handle<JSObject*> obj);
+
+ bool DoesDesktopEnvironmentMatch(const nsAString& aBlocklistDesktop,
+ const nsAString& aDesktopEnv);
+
+ virtual bool DoesWindowProtocolMatch(
+ const nsAString& aBlocklistWindowProtocol,
+ const nsAString& aWindowProtocol);
+
+ bool DoesVendorMatch(const nsAString& aBlocklistVendor,
+ const nsAString& aAdapterVendor);
+
+ virtual bool DoesDriverVendorMatch(const nsAString& aBlocklistVendor,
+ const nsAString& aDriverVendor);
+
+ bool InitFeatureObject(JSContext* aCx, JS::Handle<JSObject*> aContainer,
+ const char* aName,
+ mozilla::gfx::FeatureState& aFeatureState,
+ JS::MutableHandle<JSObject*> aOutObj);
+
+ NS_IMETHOD ControlGPUProcessForXPCShell(bool aEnable, bool* _retval) override;
+
+ // Total number of pixels for all detected screens at startup.
+ int64_t mScreenPixels;
+
+ private:
+ virtual int32_t FindBlocklistedDeviceInList(
+ const nsTArray<GfxDriverInfo>& aDriverInfo, nsAString& aSuggestedVersion,
+ int32_t aFeature, nsACString& aFailureId, OperatingSystem os,
+ bool aForAllowing);
+
+ bool IsFeatureAllowlisted(int32_t aFeature) const;
+
+ void EvaluateDownloadedBlocklist(nsTArray<GfxDriverInfo>& aDriverInfo);
+
+ bool BuildFeatureStateLog(JSContext* aCx, const gfx::FeatureState& aFeature,
+ JS::MutableHandle<JS::Value> aOut);
+
+ Mutex mMutex;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_GfxInfoBase_h__ */
diff --git a/widget/GfxInfoCollector.cpp b/widget/GfxInfoCollector.cpp
new file mode 100644
index 0000000000..262b2206cd
--- /dev/null
+++ b/widget/GfxInfoCollector.cpp
@@ -0,0 +1,43 @@
+/* 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/. */
+
+#include "GfxInfoCollector.h"
+#include "jsapi.h"
+#include "nsString.h"
+
+using namespace mozilla;
+using namespace widget;
+
+void InfoObject::DefineProperty(const char* name, int value) {
+ if (!mOk) return;
+
+ mOk = JS_DefineProperty(mCx, mObj, name, value, JSPROP_ENUMERATE);
+}
+
+void InfoObject::DefineProperty(const char* name, nsAString& value) {
+ if (!mOk) return;
+
+ const nsString& flat = PromiseFlatString(value);
+ JS::Rooted<JSString*> string(
+ mCx, JS_NewUCStringCopyN(mCx, static_cast<const char16_t*>(flat.get()),
+ flat.Length()));
+ if (!string) mOk = false;
+
+ if (!mOk) return;
+
+ mOk = JS_DefineProperty(mCx, mObj, name, string, JSPROP_ENUMERATE);
+}
+
+void InfoObject::DefineProperty(const char* name, const char* value) {
+ nsAutoString string = NS_ConvertASCIItoUTF16(value);
+ DefineProperty(name, string);
+}
+
+InfoObject::InfoObject(JSContext* aCx) : mCx(aCx), mObj(aCx), mOk(true) {
+ mObj = JS_NewPlainObject(aCx);
+ if (!mObj) mOk = false;
+}
diff --git a/widget/GfxInfoCollector.h b/widget/GfxInfoCollector.h
new file mode 100644
index 0000000000..dd5cd2b4b1
--- /dev/null
+++ b/widget/GfxInfoCollector.h
@@ -0,0 +1,88 @@
+/* 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_GfxInfoCollector_h__
+#define __mozilla_widget_GfxInfoCollector_h__
+
+#include "mozilla/Attributes.h"
+#include "nsStringFwd.h"
+#include "js/RootingAPI.h"
+
+namespace mozilla {
+namespace widget {
+
+/* this is handy wrapper around JSAPI to make it more pleasant to use.
+ * We collect the JSAPI errors and so that callers don't need to */
+class MOZ_STACK_CLASS InfoObject {
+ friend class GfxInfoBase;
+
+ public:
+ void DefineProperty(const char* name, int value);
+ void DefineProperty(const char* name, nsAString& value);
+ void DefineProperty(const char* name, const char* value);
+
+ private:
+ // We need to ensure that this object lives on the stack so that GC sees it
+ // properly
+ explicit InfoObject(JSContext* aCx);
+ InfoObject(InfoObject&);
+
+ JSContext* mCx;
+ JS::Rooted<JSObject*> mObj;
+ bool mOk;
+};
+
+/*
+
+ Here's an example usage:
+
+ class Foo {
+ Foo::Foo() : mInfoCollector(this, &Foo::GetAweseomeness) {}
+
+ void GetAwesomeness(InfoObject &obj) {
+ obj.DefineProperty("awesome", mAwesome);
+ }
+
+ int mAwesome;
+
+ GfxInfoCollector<Foo> mInfoCollector;
+ }
+
+ This will define a property on the object
+ returned from calling getInfo() on a
+ GfxInfo object. e.g.
+
+ gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+ info = gfxInfo.getInfo();
+ if (info.awesome)
+ alert(info.awesome);
+
+*/
+
+class GfxInfoCollectorBase {
+ public:
+ GfxInfoCollectorBase();
+ virtual void GetInfo(InfoObject& obj) = 0;
+ virtual ~GfxInfoCollectorBase();
+};
+
+template <class T>
+class GfxInfoCollector : public GfxInfoCollectorBase {
+ public:
+ GfxInfoCollector(T* aPointer, void (T::*aFunc)(InfoObject& obj))
+ : mPointer(aPointer), mFunc(aFunc) {}
+ virtual void GetInfo(InfoObject& obj) override { (mPointer->*mFunc)(obj); }
+
+ protected:
+ T* mPointer;
+ void (T::*mFunc)(InfoObject& obj);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/GfxInfoX11.cpp b/widget/GfxInfoX11.cpp
new file mode 100644
index 0000000000..6443e1b51c
--- /dev/null
+++ b/widget/GfxInfoX11.cpp
@@ -0,0 +1,1153 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <sys/utsname.h>
+#include <string>
+#include <cctype>
+#include "nsCRTGlue.h"
+#include "nsExceptionHandler.h"
+#include "nsUnicharUtils.h"
+#include "prenv.h"
+#include "nsPrintfCString.h"
+#include "nsWhitespaceTokenizer.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/gfx/Logging.h"
+
+#include "GfxInfoX11.h"
+
+#include <gdk/gdkx.h>
+#ifdef MOZ_WAYLAND
+# include "mozilla/widget/nsWaylandDisplay.h"
+# include "mozilla/widget/DMABufLibWrapper.h"
+#endif
+
+#define EXIT_STATUS_BUFFER_TOO_SMALL 2
+#ifdef DEBUG
+bool fire_glxtest_process();
+#endif
+
+namespace mozilla::widget {
+
+#ifdef DEBUG
+NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug)
+#endif
+
+// these global variables will be set when firing the glxtest process
+int glxtest_pipe = -1;
+pid_t glxtest_pid = 0;
+
+nsresult GfxInfo::Init() {
+ mGLMajorVersion = 0;
+ mGLMinorVersion = 0;
+ mHasTextureFromPixmap = false;
+ mIsMesa = false;
+ mIsAccelerated = true;
+ mIsWayland = false;
+ mIsWaylandDRM = false;
+ mIsXWayland = false;
+ mHasMultipleGPUs = false;
+ mGlxTestError = false;
+ return GfxInfoBase::Init();
+}
+
+void GfxInfo::AddCrashReportAnnotations() {
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterVendorID,
+ mVendorId);
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterDeviceID,
+ mDeviceId);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::AdapterDriverVendor, mDriverVendor);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::AdapterDriverVersion, mDriverVersion);
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::IsWayland,
+ mIsWayland);
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::IsWaylandDRM,
+ mIsWaylandDRM);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::DesktopEnvironment, mDesktopEnvironment);
+
+ if (mHasMultipleGPUs) {
+ nsAutoCString note;
+ note.AppendLiteral("Has dual GPUs.");
+ if (!mSecondaryVendorId.IsEmpty()) {
+ note.AppendLiteral(" GPU #2: AdapterVendorID2: ");
+ note.Append(mSecondaryVendorId);
+ note.AppendLiteral(", AdapterDeviceID2: ");
+ note.Append(mSecondaryDeviceId);
+ }
+ CrashReporter::AppendAppNotesToCrashReport(note);
+ }
+}
+
+void GfxInfo::GetData() {
+ GfxInfoBase::GetData();
+
+ // to understand this function, see bug 639842. We retrieve the OpenGL driver
+ // information in a separate process to protect against bad drivers.
+
+ // if glxtest_pipe == -1, that means that we already read the information
+ if (glxtest_pipe == -1) return;
+
+ enum { buf_size = 2048 };
+ char buf[buf_size];
+ ssize_t bytesread = read(glxtest_pipe, &buf,
+ buf_size - 1); // -1 because we'll append a zero
+ close(glxtest_pipe);
+ glxtest_pipe = -1;
+
+ // bytesread < 0 would mean that the above read() call failed.
+ // This should never happen. If it did, the outcome would be to blocklist
+ // anyway.
+ if (bytesread < 0) {
+ bytesread = 0;
+ } else if (bytesread == buf_size - 1) {
+ gfxCriticalNote << "glxtest: read from pipe exceeded buffer size";
+ }
+
+ // let buf be a zero-terminated string
+ buf[bytesread] = 0;
+
+ // Wait for the glxtest process to finish. This serves 2 purposes:
+ // * avoid having a zombie glxtest process laying around
+ // * get the glxtest process status info.
+ int glxtest_status = 0;
+ bool wait_for_glxtest_process = true;
+ bool waiting_for_glxtest_process_failed = false;
+ int waitpid_errno = 0;
+ while (wait_for_glxtest_process) {
+ wait_for_glxtest_process = false;
+ if (waitpid(glxtest_pid, &glxtest_status, 0) == -1) {
+ waitpid_errno = errno;
+ if (waitpid_errno == EINTR) {
+ wait_for_glxtest_process = true;
+ } else {
+ // Bug 718629
+ // ECHILD happens when the glxtest process got reaped got reaped after a
+ // PR_CreateProcess as per bug 227246. This shouldn't matter, as we
+ // still seem to get the data from the pipe, and if we didn't, the
+ // outcome would be to blocklist anyway.
+ waiting_for_glxtest_process_failed = (waitpid_errno != ECHILD);
+ }
+ }
+ }
+
+ int exit_code = EXIT_FAILURE;
+ bool exited_with_error_code = false;
+ if (!waiting_for_glxtest_process_failed && WIFEXITED(glxtest_status)) {
+ exit_code = WEXITSTATUS(glxtest_status);
+ exited_with_error_code = exit_code != EXIT_SUCCESS;
+ }
+
+ bool received_signal =
+ !waiting_for_glxtest_process_failed && WIFSIGNALED(glxtest_status);
+
+ bool error = waiting_for_glxtest_process_failed || exited_with_error_code ||
+ received_signal;
+ bool errorLog = false;
+
+ nsCString glVendor;
+ nsCString glRenderer;
+ nsCString glVersion;
+ nsCString textureFromPixmap;
+
+ // Available if GLX_MESA_query_renderer is supported.
+ nsCString mesaVendor;
+ nsCString mesaDevice;
+ nsCString mesaAccelerated;
+ // Available if using a DRI-based libGL stack.
+ nsCString driDriver;
+ nsCString screenInfo;
+ nsCString adapterRam;
+
+ nsCString drmRenderDevice;
+
+ AutoTArray<nsCString, 2> pciVendors;
+ AutoTArray<nsCString, 2> pciDevices;
+
+ nsCString* stringToFill = nullptr;
+ bool logString = false;
+
+ char* bufptr = buf;
+ while (true) {
+ char* line = NS_strtok("\n", &bufptr);
+ if (!line) break;
+ if (stringToFill) {
+ stringToFill->Assign(line);
+ stringToFill = nullptr;
+ } else if (logString) {
+ gfxCriticalNote << "glxtest: " << line;
+ logString = false;
+ } else if (!strcmp(line, "VENDOR")) {
+ stringToFill = &glVendor;
+ } else if (!strcmp(line, "RENDERER")) {
+ stringToFill = &glRenderer;
+ } else if (!strcmp(line, "VERSION")) {
+ stringToFill = &glVersion;
+ } else if (!strcmp(line, "TFP")) {
+ stringToFill = &textureFromPixmap;
+ } else if (!strcmp(line, "MESA_VENDOR_ID")) {
+ stringToFill = &mesaVendor;
+ } else if (!strcmp(line, "MESA_DEVICE_ID")) {
+ stringToFill = &mesaDevice;
+ } else if (!strcmp(line, "MESA_ACCELERATED")) {
+ stringToFill = &mesaAccelerated;
+ } else if (!strcmp(line, "MESA_VRAM")) {
+ stringToFill = &adapterRam;
+ } else if (!strcmp(line, "DRI_DRIVER")) {
+ stringToFill = &driDriver;
+ } else if (!strcmp(line, "SCREEN_INFO")) {
+ stringToFill = &screenInfo;
+ } else if (!strcmp(line, "PCI_VENDOR_ID")) {
+ stringToFill = pciVendors.AppendElement();
+ } else if (!strcmp(line, "PCI_DEVICE_ID")) {
+ stringToFill = pciDevices.AppendElement();
+ } else if (!strcmp(line, "DRM_RENDERDEVICE")) {
+ stringToFill = &drmRenderDevice;
+ } else if (!strcmp(line, "WARNING")) {
+ logString = true;
+ } else if (!strcmp(line, "ERROR")) {
+ logString = true;
+ errorLog = true;
+ }
+ }
+
+ MOZ_ASSERT(pciDevices.Length() == pciVendors.Length(),
+ "Missing PCI vendors/devices");
+
+ size_t pciLen = std::min(pciVendors.Length(), pciDevices.Length());
+ mHasMultipleGPUs = pciLen > 1;
+
+ if (!strcmp(textureFromPixmap.get(), "TRUE")) mHasTextureFromPixmap = true;
+
+ // only useful for Linux kernel version check for FGLRX driver.
+ // assumes X client == X server, which is sad.
+ struct utsname unameobj;
+ if (uname(&unameobj) >= 0) {
+ mOS.Assign(unameobj.sysname);
+ mOSRelease.Assign(unameobj.release);
+ }
+
+ const char* spoofedVendor = PR_GetEnv("MOZ_GFX_SPOOF_GL_VENDOR");
+ if (spoofedVendor) glVendor.Assign(spoofedVendor);
+ const char* spoofedRenderer = PR_GetEnv("MOZ_GFX_SPOOF_GL_RENDERER");
+ if (spoofedRenderer) glRenderer.Assign(spoofedRenderer);
+ const char* spoofedVersion = PR_GetEnv("MOZ_GFX_SPOOF_GL_VERSION");
+ if (spoofedVersion) glVersion.Assign(spoofedVersion);
+ const char* spoofedOS = PR_GetEnv("MOZ_GFX_SPOOF_OS");
+ if (spoofedOS) mOS.Assign(spoofedOS);
+ const char* spoofedOSRelease = PR_GetEnv("MOZ_GFX_SPOOF_OS_RELEASE");
+ if (spoofedOSRelease) mOSRelease.Assign(spoofedOSRelease);
+
+ // Scan the GL_VERSION string for the GL and driver versions.
+ nsCWhitespaceTokenizer tokenizer(glVersion);
+ while (tokenizer.hasMoreTokens()) {
+ nsCString token(tokenizer.nextToken());
+ unsigned int major = 0, minor = 0, revision = 0, patch = 0;
+ if (sscanf(token.get(), "%u.%u.%u.%u", &major, &minor, &revision, &patch) >=
+ 2) {
+ // A survey of GL_VENDOR strings indicates that the first version is
+ // always the GL version, the second is usually the driver version.
+ if (mGLMajorVersion == 0) {
+ mGLMajorVersion = major;
+ mGLMinorVersion = minor;
+ } else if (mDriverVersion.IsEmpty()) { // Not already spoofed.
+ mDriverVersion =
+ nsPrintfCString("%u.%u.%u.%u", major, minor, revision, patch);
+ }
+ }
+ }
+
+ if (mGLMajorVersion == 0) {
+ NS_WARNING("Failed to parse GL version!");
+ }
+
+ mDrmRenderDevice = std::move(drmRenderDevice);
+
+ // Mesa always exposes itself in the GL_VERSION string, but not always the
+ // GL_VENDOR string.
+ mIsMesa = glVersion.Find("Mesa") != -1;
+
+ // We need to use custom driver vendor IDs for mesa so we can treat them
+ // differently than the proprietary drivers.
+ if (mIsMesa) {
+ mIsAccelerated = !mesaAccelerated.Equals("FALSE");
+ // Process software rasterizers before the DRI driver string; we may be
+ // forcing software rasterization on a DRI-accelerated X server by using
+ // LIBGL_ALWAYS_SOFTWARE or a similar restriction.
+ if (strcasestr(glRenderer.get(), "llvmpipe")) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDriverVendor(DriverVendor::MesaLLVMPipe),
+ mDriverVendor);
+ mIsAccelerated = false;
+ } else if (strcasestr(glRenderer.get(), "softpipe")) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDriverVendor(DriverVendor::MesaSoftPipe),
+ mDriverVendor);
+ mIsAccelerated = false;
+ } else if (strcasestr(glRenderer.get(), "software rasterizer") ||
+ !mIsAccelerated) {
+ // Fallback to reporting swrast if GLX_MESA_query_renderer tells us
+ // we're using an unaccelerated context.
+ CopyUTF16toUTF8(GfxDriverInfo::GetDriverVendor(DriverVendor::MesaSWRast),
+ mDriverVendor);
+ mIsAccelerated = false;
+ } else if (!driDriver.IsEmpty()) {
+ mDriverVendor = nsPrintfCString("mesa/%s", driDriver.get());
+ } else {
+ // Some other mesa configuration where we couldn't get enough info.
+ NS_WARNING("Failed to detect Mesa driver being used!");
+ CopyUTF16toUTF8(GfxDriverInfo::GetDriverVendor(DriverVendor::MesaUnknown),
+ mDriverVendor);
+ }
+
+ if (!mesaVendor.IsEmpty()) {
+ mVendorId = mesaVendor;
+ }
+
+ if (!mesaDevice.IsEmpty()) {
+ mDeviceId = mesaDevice;
+ }
+ } else if (glVendor.EqualsLiteral("NVIDIA Corporation")) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(DeviceVendor::NVIDIA),
+ mVendorId);
+ mDriverVendor.AssignLiteral("nvidia/unknown");
+ // TODO: Use NV-CONTROL X11 extension to query Device ID and VRAM.
+ } else if (glVendor.EqualsLiteral("ATI Technologies Inc.")) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(DeviceVendor::ATI),
+ mVendorId);
+ mDriverVendor.AssignLiteral("ati/unknown");
+ // TODO: Look into ways to find the device ID on FGLRX.
+ } else {
+ NS_WARNING("Failed to detect GL vendor!");
+ }
+
+ if (!screenInfo.IsEmpty()) {
+ PRInt32 start = 0;
+ PRInt32 loc = screenInfo.Find(";", PR_FALSE, start);
+ while (loc != kNotFound) {
+ int isDefault = 0;
+ nsCString line(screenInfo.get() + start, loc - start);
+ ScreenInfo info;
+ if (sscanf(line.get(), "%ux%u:%u", &info.mWidth, &info.mHeight,
+ &isDefault) == 3) {
+ info.mIsDefault = isDefault != 0;
+ mScreenInfo.AppendElement(info);
+ }
+
+ start = loc + 1;
+ loc = screenInfo.Find(";", PR_FALSE, start);
+ }
+ }
+
+ if (!adapterRam.IsEmpty()) {
+ mAdapterRAM = (uint32_t)atoi(adapterRam.get());
+ }
+
+ // If we have the DRI driver, we can derive the vendor ID from that if needed.
+ if (mVendorId.IsEmpty() && !driDriver.IsEmpty()) {
+ const char* nvidiaDrivers[] = {"nouveau", "tegra", nullptr};
+ for (size_t i = 0; nvidiaDrivers[i]; ++i) {
+ if (driDriver.Equals(nvidiaDrivers[i])) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(DeviceVendor::NVIDIA),
+ mVendorId);
+ break;
+ }
+ }
+
+ if (mVendorId.IsEmpty()) {
+ const char* intelDrivers[] = {"iris", "i915", "i965",
+ "i810", "intel", nullptr};
+ for (size_t i = 0; intelDrivers[i]; ++i) {
+ if (driDriver.Equals(intelDrivers[i])) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(DeviceVendor::Intel),
+ mVendorId);
+ break;
+ }
+ }
+ }
+
+ if (mVendorId.IsEmpty()) {
+ const char* amdDrivers[] = {"r600", "r200", "r100",
+ "radeon", "radeonsi", nullptr};
+ for (size_t i = 0; amdDrivers[i]; ++i) {
+ if (driDriver.Equals(amdDrivers[i])) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(DeviceVendor::ATI),
+ mVendorId);
+ break;
+ }
+ }
+ }
+
+ if (mVendorId.IsEmpty()) {
+ if (driDriver.EqualsLiteral("freedreno")) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(DeviceVendor::Qualcomm),
+ mVendorId);
+ }
+ }
+ }
+
+ // If we still don't have a vendor ID, we can try the PCI vendor list.
+ if (mVendorId.IsEmpty()) {
+ if (pciVendors.Length() == 1) {
+ mVendorId = pciVendors[0];
+ } else if (pciVendors.IsEmpty()) {
+ gfxCriticalNote << "No GPUs detected via PCI";
+ } else {
+ gfxCriticalNote
+ << "More than 1 GPU detected via PCI, cannot deduce vendor";
+ }
+ }
+
+ // If we know the vendor ID, but didn't get a device ID, we can guess from the
+ // PCI device list.
+ if (mDeviceId.IsEmpty() && !mVendorId.IsEmpty()) {
+ for (size_t i = 0; i < pciLen; ++i) {
+ if (mVendorId.Equals(pciVendors[i])) {
+ if (mDeviceId.IsEmpty()) {
+ mDeviceId = pciDevices[i];
+ } else {
+ gfxCriticalNote << "More than 1 GPU from same vendor detected via "
+ "PCI, cannot deduce device";
+ mDeviceId.Truncate();
+ break;
+ }
+ }
+ }
+ }
+
+ // Assuming we know the vendor, we should check for a secondary card.
+ if (!mVendorId.IsEmpty()) {
+ if (pciLen > 2) {
+ gfxCriticalNote
+ << "More than 2 GPUs detected via PCI, secondary GPU is arbitrary";
+ }
+ for (size_t i = 0; i < pciLen; ++i) {
+ if (!mVendorId.Equals(pciVendors[i]) ||
+ (!mDeviceId.IsEmpty() && !mDeviceId.Equals(pciDevices[i]))) {
+ mSecondaryVendorId = pciVendors[i];
+ mSecondaryDeviceId = pciDevices[i];
+ break;
+ }
+ }
+ }
+
+ // If we couldn't choose, log them.
+ if (mVendorId.IsEmpty()) {
+ for (size_t i = 0; i < pciLen; ++i) {
+ gfxCriticalNote << "PCI candidate " << pciVendors[i].get() << "/"
+ << pciDevices[i].get();
+ }
+ }
+
+ // Fallback to GL_VENDOR and GL_RENDERER.
+ if (mVendorId.IsEmpty()) {
+ mVendorId.Assign(glVendor.get());
+ }
+ if (mDeviceId.IsEmpty()) {
+ mDeviceId.Assign(glRenderer.get());
+ }
+
+ mAdapterDescription.Assign(glRenderer);
+#ifdef MOZ_WAYLAND
+ mIsWayland = gdk_display_get_default() &&
+ !GDK_IS_X11_DISPLAY(gdk_display_get_default());
+ if (mIsWayland) {
+ mIsWaylandDRM = GetDMABufDevice()->IsDMABufVAAPIEnabled() ||
+ GetDMABufDevice()->IsDMABufWebGLEnabled() ||
+ GetDMABufDevice()->IsDMABufTexturesEnabled();
+ }
+#endif
+
+ // Make a best effort guess at whether or not we are using the XWayland compat
+ // layer. For all intents and purposes, we should otherwise believe we are
+ // using X11.
+ const char* windowEnv = getenv("XDG_SESSION_TYPE");
+ mIsXWayland = windowEnv && strcmp(windowEnv, "wayland") == 0;
+
+ // Make a best effort guess at the desktop environment in use. Sadly there
+ // does not appear to be a standard way to do this, so we check a few
+ // different environment variables and search for relevant keywords.
+ //
+ // Note that some users manually change these values. Some applications check
+ // the environment variable like we are here, and either not work or restrict
+ // functionality. There may be some heroics we could go through to determine
+ // the truth, but for the moment, this is the best we can do. This is
+ // something to keep in mind when updating the blocklist.
+ const char* desktopEnv = getenv("XDG_CURRENT_DESKTOP");
+ if (!desktopEnv) {
+ desktopEnv = getenv("DESKTOP_SESSION");
+ }
+
+ if (desktopEnv) {
+ std::string currentDesktop(desktopEnv);
+ for (auto& c : currentDesktop) {
+ c = std::tolower(c);
+ }
+
+ if (currentDesktop.find("budgie") != std::string::npos) {
+ // We need to check for Budgie first, because it might incorporate GNOME
+ // into the environment variable value.
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::Budgie),
+ mDesktopEnvironment);
+ } else if (currentDesktop.find("gnome") != std::string::npos) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::GNOME),
+ mDesktopEnvironment);
+ } else if (currentDesktop.find("kde") != std::string::npos) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::KDE),
+ mDesktopEnvironment);
+ } else if (currentDesktop.find("xfce") != std::string::npos) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::XFCE),
+ mDesktopEnvironment);
+ } else if (currentDesktop.find("cinnamon") != std::string::npos) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::Cinnamon),
+ mDesktopEnvironment);
+ } else if (currentDesktop.find("enlightenment") != std::string::npos) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDesktopEnvironment(
+ DesktopEnvironment::Enlightenment),
+ mDesktopEnvironment);
+ } else if (currentDesktop.find("lxde") != std::string::npos ||
+ currentDesktop.find("lubuntu") != std::string::npos) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::LXDE),
+ mDesktopEnvironment);
+ } else if (currentDesktop.find("openbox") != std::string::npos) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::Openbox),
+ mDesktopEnvironment);
+ } else if (currentDesktop.find("i3") != std::string::npos) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::i3),
+ mDesktopEnvironment);
+ } else if (currentDesktop.find("mate") != std::string::npos) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::Mate),
+ mDesktopEnvironment);
+ } else if (currentDesktop.find("unity") != std::string::npos) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::Unity),
+ mDesktopEnvironment);
+ } else if (currentDesktop.find("pantheon") != std::string::npos) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::Pantheon),
+ mDesktopEnvironment);
+ } else if (currentDesktop.find("lxqt") != std::string::npos) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::LXQT),
+ mDesktopEnvironment);
+ } else if (currentDesktop.find("deepin") != std::string::npos) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::Deepin),
+ mDesktopEnvironment);
+ } else if (currentDesktop.find("dwm") != std::string::npos) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::Dwm),
+ mDesktopEnvironment);
+ }
+ }
+
+ if (mDesktopEnvironment.IsEmpty()) {
+ if (getenv("GNOME_DESKTOP_SESSION_ID")) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::GNOME),
+ mDesktopEnvironment);
+ } else if (getenv("KDE_FULL_SESSION")) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::KDE),
+ mDesktopEnvironment);
+ } else if (getenv("MATE_DESKTOP_SESSION_ID")) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::Mate),
+ mDesktopEnvironment);
+ } else if (getenv("LXQT_SESSION_CONFIG")) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::LXQT),
+ mDesktopEnvironment);
+ } else {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::Unknown),
+ mDesktopEnvironment);
+ }
+ }
+
+ if (error || errorLog) {
+ if (!mAdapterDescription.IsEmpty()) {
+ mAdapterDescription.AppendLiteral(" (See failure log)");
+ } else {
+ mAdapterDescription.AppendLiteral("See failure log");
+ }
+
+ mGlxTestError = true;
+ }
+
+ if (error) {
+ nsAutoCString msg("glxtest: process failed");
+ if (waiting_for_glxtest_process_failed) {
+ msg.AppendPrintf(" (waitpid failed with errno=%d for pid %d)",
+ waitpid_errno, glxtest_pid);
+ }
+
+ if (exited_with_error_code) {
+ if (exit_code == EXIT_STATUS_BUFFER_TOO_SMALL) {
+ msg.AppendLiteral(" (buffer too small)");
+ } else {
+ msg.AppendPrintf(" (exited with status %d)",
+ WEXITSTATUS(glxtest_status));
+ }
+ }
+ if (received_signal) {
+ msg.AppendPrintf(" (received signal %d)", WTERMSIG(glxtest_status));
+ }
+
+ gfxCriticalNote << msg.get();
+ }
+
+ AddCrashReportAnnotations();
+}
+
+const nsTArray<GfxDriverInfo>& GfxInfo::GetGfxDriverInfo() {
+ if (!sDriverInfo->Length()) {
+ // Mesa 10.0 provides the GLX_MESA_query_renderer extension, which allows us
+ // to query device IDs backing a GL context for blocklisting.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::MesaAll,
+ DeviceFamily::All, GfxDriverInfo::allFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(10, 0, 0, 0), "FEATURE_FAILURE_OLD_MESA", "Mesa 10.0");
+
+ // NVIDIA baseline (ported from old blocklist)
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::NonMesaAll,
+ DeviceFamily::NvidiaAll, GfxDriverInfo::allFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(257, 21, 0, 0), "FEATURE_FAILURE_OLD_NVIDIA", "NVIDIA 257.21");
+
+ // fglrx baseline (chosen arbitrarily as 2013-07-22 release).
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Linux, DeviceFamily::AtiAll,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN, V(13, 15, 100, 1), "FEATURE_FAILURE_OLD_FGLRX",
+ "fglrx 13.15.100.1");
+
+ ////////////////////////////////////
+ // FEATURE_WEBRENDER
+
+ // Intel Mesa baseline, chosen arbitrarily.
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Linux, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(18, 0, 0, 0), "FEATURE_FAILURE_WEBRENDER_OLD_MESA", "Mesa 18.0.0.0");
+
+ // Nvidia Mesa baseline, see bug 1563859.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::MesaAll,
+ DeviceFamily::NvidiaAll, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(18, 2, 0, 0), "FEATURE_FAILURE_WEBRENDER_OLD_MESA", "Mesa 18.2.0.0");
+
+ // Disable on all Nvidia devices not using Mesa for now.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::NonMesaAll,
+ DeviceFamily::NvidiaAll, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_FAILURE_WEBRENDER_NO_LINUX_NVIDIA", "");
+
+ // ATI Mesa baseline, chosen arbitrarily.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::MesaAll,
+ DeviceFamily::AtiAll, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(18, 0, 0, 0), "FEATURE_FAILURE_WEBRENDER_OLD_MESA", "Mesa 18.0.0.0");
+
+ // Disable on all ATI devices not using Mesa for now.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::NonMesaAll,
+ DeviceFamily::AtiAll, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_FAILURE_WEBRENDER_NO_LINUX_ATI", "");
+
+ // Bug 1673939 - Garbled text on RS880 GPUs with Mesa drivers.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::MesaAll,
+ DeviceFamily::AmdR600, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_FAILURE_WEBRENDER_BUG_1673939", "");
+
+ ////////////////////////////////////
+ // FEATURE_WEBRENDER - ALLOWLIST
+
+ // Intel Mesa baseline, chosen arbitrarily.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::GNOME, WindowProtocol::X11, DriverVendor::MesaAll,
+ DeviceFamily::IntelRolloutWebRender, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_GREATER_THAN_OR_EQUAL,
+ V(18, 0, 0, 0), "FEATURE_ROLLOUT_INTEL_GNOME_X11_MESA",
+ "Mesa 18.0.0.0");
+
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::GNOME, WindowProtocol::Wayland,
+ DriverVendor::MesaAll, DeviceFamily::IntelRolloutWebRender,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_ALLOW_ALWAYS,
+ DRIVER_GREATER_THAN_OR_EQUAL, V(18, 0, 0, 0),
+ "FEATURE_ROLLOUT_INTEL_GNOME_WAYLAND_MESA", "Mesa 18.0.0.0");
+
+ // ATI Mesa baseline, chosen arbitrarily.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::GNOME, WindowProtocol::X11, DriverVendor::MesaAll,
+ DeviceFamily::AtiRolloutWebRender, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_GREATER_THAN_OR_EQUAL,
+ V(18, 0, 0, 0), "FEATURE_ROLLOUT_ATI_GNOME_X11_MESA", "Mesa 18.0.0.0");
+
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::GNOME, WindowProtocol::Wayland,
+ DriverVendor::MesaAll, DeviceFamily::AtiRolloutWebRender,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_ALLOW_ALWAYS,
+ DRIVER_GREATER_THAN_OR_EQUAL, V(18, 0, 0, 0),
+ "FEATURE_ROLLOUT_ATI_GNOME_WAYLAND_MESA", "Mesa 18.0.0.0");
+
+#ifdef EARLY_BETA_OR_EARLIER
+ // Intel Mesa baseline, chosen arbitrarily.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::MesaAll,
+ DeviceFamily::IntelRolloutWebRender, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_GREATER_THAN_OR_EQUAL,
+ V(18, 0, 0, 0), "FEATURE_ROLLOUT_EARLY_BETA_INTEL_MESA",
+ "Mesa 18.0.0.0");
+
+ // ATI Mesa baseline, chosen arbitrarily.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::MesaAll,
+ DeviceFamily::AtiRolloutWebRender, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_GREATER_THAN_OR_EQUAL,
+ V(18, 0, 0, 0), "FEATURE_ROLLOUT_EARLY_BETA_ATI_MESA", "Mesa 18.0.0.0");
+#endif
+
+#ifdef NIGHTLY_BUILD
+ // Intel Mesa baseline, chosen arbitrarily.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::MesaAll,
+ DeviceFamily::IntelRolloutWebRender, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_ALLOW_QUALIFIED, DRIVER_GREATER_THAN_OR_EQUAL,
+ V(18, 0, 0, 0), "FEATURE_ROLLOUT_NIGHTLY_INTEL_MESA", "Mesa 18.0.0.0");
+
+ // Nvidia Mesa baseline, see bug 1563859.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::MesaAll,
+ DeviceFamily::NvidiaRolloutWebRender, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_ALLOW_QUALIFIED, DRIVER_GREATER_THAN_OR_EQUAL,
+ V(18, 2, 0, 0), "FEATURE_ROLLOUT_NIGHTLY_NVIDIA_MESA", "Mesa 18.2.0.0");
+
+ // ATI Mesa baseline, chosen arbitrarily.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::MesaAll,
+ DeviceFamily::AtiRolloutWebRender, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_ALLOW_QUALIFIED, DRIVER_GREATER_THAN_OR_EQUAL,
+ V(18, 0, 0, 0), "FEATURE_ROLLOUT_NIGHTLY_ATI_MESA", "Mesa 18.0.0.0");
+#endif
+
+ ////////////////////////////////////
+ // FEATURE_WEBRENDER_SOFTWARE - ALLOWLIST
+#ifdef EARLY_BETA_OR_EARLIER
+# if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || \
+ defined(__i386) || defined(__amd64__)
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::SmallAndMedium,
+ BatteryStatus::All, DesktopEnvironment::All, WindowProtocol::All,
+ DriverVendor::NonMesaAll, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_WEBRENDER_SOFTWARE,
+ nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_ROLLOUT_NIGHTLY_SOFTWARE_WR_NON_MESA_S_M_SCRN",
+ "");
+
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::SmallAndMedium,
+ BatteryStatus::All, DesktopEnvironment::All, WindowProtocol::All,
+ DriverVendor::HardwareMesaAll, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_WEBRENDER_SOFTWARE,
+ nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_ROLLOUT_NIGHTLY_SOFTWARE_WR_HW_MESA_S_M_SCRN",
+ "");
+# endif
+#endif
+
+ ////////////////////////////////////
+
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::MesaNouveau,
+ DeviceFamily::All, nsIGfxInfo::FEATURE_THREADSAFE_GL,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_FAILURE_THREADSAFE_GL", "");
+ }
+ return *sDriverInfo;
+}
+
+bool GfxInfo::DoesWindowProtocolMatch(const nsAString& aBlocklistWindowProtocol,
+ const nsAString& aWindowProtocol) {
+ if (mIsWayland &&
+ aBlocklistWindowProtocol.Equals(
+ GfxDriverInfo::GetWindowProtocol(WindowProtocol::WaylandAll),
+ nsCaseInsensitiveStringComparator)) {
+ return true;
+ }
+ if (!mIsWayland &&
+ aBlocklistWindowProtocol.Equals(
+ GfxDriverInfo::GetWindowProtocol(WindowProtocol::X11All),
+ nsCaseInsensitiveStringComparator)) {
+ return true;
+ }
+ return GfxInfoBase::DoesWindowProtocolMatch(aBlocklistWindowProtocol,
+ aWindowProtocol);
+}
+
+bool GfxInfo::DoesDriverVendorMatch(const nsAString& aBlocklistVendor,
+ const nsAString& aDriverVendor) {
+ if (mIsMesa) {
+ if (aBlocklistVendor.Equals(
+ GfxDriverInfo::GetDriverVendor(DriverVendor::MesaAll),
+ nsCaseInsensitiveStringComparator)) {
+ return true;
+ }
+ if (mIsAccelerated &&
+ aBlocklistVendor.Equals(
+ GfxDriverInfo::GetDriverVendor(DriverVendor::HardwareMesaAll),
+ nsCaseInsensitiveStringComparator)) {
+ return true;
+ }
+ if (!mIsAccelerated &&
+ aBlocklistVendor.Equals(
+ GfxDriverInfo::GetDriverVendor(DriverVendor::SoftwareMesaAll),
+ nsCaseInsensitiveStringComparator)) {
+ return true;
+ }
+ }
+ if (!mIsMesa && aBlocklistVendor.Equals(
+ GfxDriverInfo::GetDriverVendor(DriverVendor::NonMesaAll),
+ nsCaseInsensitiveStringComparator)) {
+ return true;
+ }
+ return GfxInfoBase::DoesDriverVendorMatch(aBlocklistVendor, aDriverVendor);
+}
+
+nsresult GfxInfo::GetFeatureStatusImpl(
+ int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
+ OperatingSystem* aOS /* = nullptr */)
+
+{
+ NS_ENSURE_ARG_POINTER(aStatus);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ aSuggestedDriverVersion.SetIsVoid(true);
+ OperatingSystem os = OperatingSystem::Linux;
+ if (aOS) *aOS = os;
+
+ if (sShutdownOccurred) {
+ return NS_OK;
+ }
+
+ GetData();
+
+ if (mGlxTestError) {
+ // If glxtest failed, block all features by default.
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_GLXTEST_FAILED";
+ return NS_OK;
+ }
+
+ if (mGLMajorVersion == 1) {
+ // We're on OpenGL 1. In most cases that indicates really old hardware.
+ // We better block them, rather than rely on them to fail gracefully,
+ // because they don't! see bug 696636
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_OPENGL_1";
+ return NS_OK;
+ }
+
+ // Blocklist software GL implementations from using layers acceleration.
+ // On the test infrastructure, we'll force-enable layers acceleration.
+ if (aFeature == nsIGfxInfo::FEATURE_OPENGL_LAYERS && !mIsAccelerated &&
+ !PR_GetEnv("MOZ_LAYERS_ALLOW_SOFTWARE_GL")) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_SOFTWARE_GL";
+ return NS_OK;
+ }
+
+ return GfxInfoBase::GetFeatureStatusImpl(
+ aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, aFailureId, &os);
+}
+
+NS_IMETHODIMP
+GfxInfo::GetD2DEnabled(bool* aEnabled) { return NS_ERROR_FAILURE; }
+
+NS_IMETHODIMP
+GfxInfo::GetDWriteEnabled(bool* aEnabled) { return NS_ERROR_FAILURE; }
+
+NS_IMETHODIMP
+GfxInfo::GetDWriteVersion(nsAString& aDwriteVersion) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP GfxInfo::GetHasBattery(bool* aHasBattery) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetEmbeddedInFirefoxReality(bool* aEmbeddedInFirefoxReality) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetCleartypeParameters(nsAString& aCleartypeParams) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetWindowProtocol(nsAString& aWindowProtocol) {
+ GetData();
+ if (mIsWayland) {
+ if (mIsWaylandDRM) {
+ aWindowProtocol =
+ GfxDriverInfo::GetWindowProtocol(WindowProtocol::WaylandDRM);
+ } else {
+ aWindowProtocol =
+ GfxDriverInfo::GetWindowProtocol(WindowProtocol::Wayland);
+ }
+ } else if (mIsXWayland) {
+ aWindowProtocol =
+ GfxDriverInfo::GetWindowProtocol(WindowProtocol::XWayland);
+ } else {
+ aWindowProtocol = GfxDriverInfo::GetWindowProtocol(WindowProtocol::X11);
+ }
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_LINUX_WINDOW_PROTOCOL,
+ aWindowProtocol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDesktopEnvironment(nsAString& aDesktopEnvironment) {
+ GetData();
+ AppendASCIItoUTF16(mDesktopEnvironment, aDesktopEnvironment);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription(nsAString& aAdapterDescription) {
+ GetData();
+ AppendASCIItoUTF16(mAdapterDescription, aAdapterDescription);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription2(nsAString& aAdapterDescription) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM(uint32_t* aAdapterRAM) {
+ GetData();
+ *aAdapterRAM = mAdapterRAM;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(uint32_t* aAdapterRAM) { return NS_ERROR_FAILURE; }
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver(nsAString& aAdapterDriver) {
+ aAdapterDriver.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver2(nsAString& aAdapterDriver) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) {
+ GetData();
+ CopyASCIItoUTF16(mDriverVendor, aAdapterDriverVendor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) {
+ GetData();
+ CopyASCIItoUTF16(mDriverVersion, aAdapterDriverVersion);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion2(nsAString& aAdapterDriverVersion) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate(nsAString& aAdapterDriverDate) {
+ aAdapterDriverDate.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate2(nsAString& aAdapterDriverDate) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID(nsAString& aAdapterVendorID) {
+ GetData();
+ CopyUTF8toUTF16(mVendorId, aAdapterVendorID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString& aAdapterVendorID) {
+ GetData();
+ CopyUTF8toUTF16(mSecondaryVendorId, aAdapterVendorID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString& aAdapterDeviceID) {
+ GetData();
+ CopyUTF8toUTF16(mDeviceId, aAdapterDeviceID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID2(nsAString& aAdapterDeviceID) {
+ GetData();
+ CopyUTF8toUTF16(mSecondaryDeviceId, aAdapterDeviceID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID(nsAString& aAdapterSubsysID) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID2(nsAString& aAdapterSubsysID) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDisplayInfo(nsTArray<nsString>& aDisplayInfo) {
+ GetData();
+
+ for (auto screenInfo : mScreenInfo) {
+ nsString infoString;
+ infoString.AppendPrintf("%dx%d %s", screenInfo.mWidth, screenInfo.mHeight,
+ screenInfo.mIsDefault ? "default" : "");
+ aDisplayInfo.AppendElement(infoString);
+ }
+
+ return aDisplayInfo.IsEmpty() ? NS_ERROR_FAILURE : NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDisplayWidth(nsTArray<uint32_t>& aDisplayWidth) {
+ for (auto screenInfo : mScreenInfo) {
+ aDisplayWidth.AppendElement((uint32_t)screenInfo.mWidth);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDisplayHeight(nsTArray<uint32_t>& aDisplayHeight) {
+ for (auto screenInfo : mScreenInfo) {
+ aDisplayHeight.AppendElement((uint32_t)screenInfo.mHeight);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active) {
+ // This is never the case, as the active GPU should be the primary GPU.
+ *aIsGPU2Active = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDrmRenderDevice(nsACString& aDrmRenderDevice) {
+ GetData();
+ aDrmRenderDevice.Assign(mDrmRenderDevice);
+ return NS_OK;
+}
+
+#ifdef DEBUG
+
+// Implement nsIGfxInfoDebug
+// We don't support spoofing anything on Linux
+
+NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString& aVendorID) {
+ GetData();
+ CopyUTF16toUTF8(aVendorID, mVendorId);
+ mIsAccelerated = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString& aDeviceID) {
+ GetData();
+ CopyUTF16toUTF8(aDeviceID, mDeviceId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString& aDriverVersion) {
+ GetData();
+ CopyUTF16toUTF8(aDriverVersion, mDriverVersion);
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion) {
+ // We don't support OS versioning on Linux. There's just "Linux".
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::FireTestProcess() {
+ // If the pid is zero, then we have never run the test process to query for
+ // driver information. This would normally be run on startup, but we need to
+ // manually invoke it for XPC shell tests.
+ if (glxtest_pid == 0) {
+ fire_glxtest_process();
+ }
+ return NS_OK;
+}
+
+#endif
+
+} // namespace mozilla::widget
diff --git a/widget/GfxInfoX11.h b/widget/GfxInfoX11.h
new file mode 100644
index 0000000000..c52f1eb56e
--- /dev/null
+++ b/widget/GfxInfoX11.h
@@ -0,0 +1,121 @@
+/* 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 __GfxInfoX11_h__
+#define __GfxInfoX11_h__
+
+#include "GfxInfoBase.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace widget {
+
+class GfxInfo final : public GfxInfoBase {
+ public:
+ // We only declare the subset of nsIGfxInfo that we actually implement. The
+ // rest is brought forward from GfxInfoBase.
+ NS_IMETHOD GetD2DEnabled(bool* aD2DEnabled) override;
+ NS_IMETHOD GetDWriteEnabled(bool* aDWriteEnabled) override;
+ NS_IMETHOD GetDWriteVersion(nsAString& aDwriteVersion) override;
+ NS_IMETHOD GetEmbeddedInFirefoxReality(
+ bool* aEmbeddedInFirefoxReality) override;
+ NS_IMETHOD GetHasBattery(bool* aHasBattery) override;
+ NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override;
+ NS_IMETHOD GetWindowProtocol(nsAString& aWindowProtocol) override;
+ NS_IMETHOD GetDesktopEnvironment(nsAString& aDesktopEnvironment) override;
+ NS_IMETHOD GetAdapterDescription(nsAString& aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver(nsAString& aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID(nsAString& aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID(nsAString& aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID(nsAString& aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM(uint32_t* aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) override;
+ NS_IMETHOD GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate(nsAString& aAdapterDriverDate) override;
+ NS_IMETHOD GetAdapterDescription2(nsAString& aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver2(nsAString& aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID2(nsAString& aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID2(nsAString& aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID2(nsAString& aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM2(uint32_t* aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) override;
+ NS_IMETHOD GetAdapterDriverVersion2(
+ nsAString& aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate2(nsAString& aAdapterDriverDate) override;
+ NS_IMETHOD GetIsGPU2Active(bool* aIsGPU2Active) override;
+ NS_IMETHOD GetDisplayInfo(nsTArray<nsString>& aDisplayInfo) override;
+ NS_IMETHOD GetDisplayWidth(nsTArray<uint32_t>& aDisplayWidth) override;
+ NS_IMETHOD GetDisplayHeight(nsTArray<uint32_t>& aDisplayHeight) override;
+ NS_IMETHOD GetDrmRenderDevice(nsACString& aDrmRenderDevice) override;
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+
+ virtual nsresult Init() override;
+
+ NS_IMETHOD_(void) GetData() override;
+
+#ifdef DEBUG
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+ protected:
+ ~GfxInfo() = default;
+
+ virtual nsresult GetFeatureStatusImpl(
+ int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
+ OperatingSystem* aOS = nullptr) override;
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() override;
+
+ virtual bool DoesWindowProtocolMatch(
+ const nsAString& aBlocklistWindowProtocol,
+ const nsAString& aWindowProtocol) override;
+
+ virtual bool DoesDriverVendorMatch(const nsAString& aBlocklistVendor,
+ const nsAString& aDriverVendor) override;
+
+ private:
+ nsCString mVendorId;
+ nsCString mDeviceId;
+ nsCString mDriverVendor;
+ nsCString mDriverVersion;
+ nsCString mAdapterDescription;
+ uint32_t mAdapterRAM;
+ nsCString mOS;
+ nsCString mOSRelease;
+ nsAutoCStringN<16> mDesktopEnvironment;
+
+ nsCString mSecondaryVendorId;
+ nsCString mSecondaryDeviceId;
+
+ nsCString mDrmRenderDevice;
+
+ struct ScreenInfo {
+ uint32_t mWidth;
+ uint32_t mHeight;
+ bool mIsDefault;
+ };
+
+ nsTArray<ScreenInfo> mScreenInfo;
+ bool mHasTextureFromPixmap;
+ unsigned int mGLMajorVersion, mGLMinorVersion;
+ bool mIsMesa;
+ bool mIsAccelerated;
+ bool mIsWayland;
+ bool mIsWaylandDRM;
+ bool mIsXWayland;
+ bool mHasMultipleGPUs;
+ bool mGlxTestError;
+
+ void AddCrashReportAnnotations();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __GfxInfoX11_h__ */
diff --git a/widget/IMEData.cpp b/widget/IMEData.cpp
new file mode 100644
index 0000000000..12ee9cb63c
--- /dev/null
+++ b/widget/IMEData.cpp
@@ -0,0 +1,343 @@
+/* -*- Mode: C++; tab-width: 40; 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 "IMEData.h"
+#include <sstream>
+
+#include "gfxFontUtils.h"
+
+#include "mozilla/WritingModes.h"
+
+#include "nsPrintfCString.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+PrintStringDetail::PrintStringDetail(const nsAString& aString,
+ uint32_t aMaxLength /* = UINT32_MAX */) {
+ Assign("\"");
+ const uint32_t kFirstHalf =
+ aString.Length() <= aMaxLength ? UINT32_MAX : (aMaxLength + 1) / 2;
+ const uint32_t kSecondHalf =
+ aString.Length() <= aMaxLength ? 0 : aMaxLength / 2;
+ for (uint32_t i = 0; i < aString.Length(); i++) {
+ if (i > 0) {
+ Append(" ");
+ }
+ char32_t ch = aString.CharAt(i);
+ if (NS_IS_HIGH_SURROGATE(ch) && i + 1 < aString.Length() &&
+ NS_IS_LOW_SURROGATE(aString.CharAt(i + 1))) {
+ ch = SURROGATE_TO_UCS4(ch, aString.CharAt(i + 1));
+ }
+ Append(PrintCharData(ch));
+ if (i + 1 == kFirstHalf) {
+ Append(" ...");
+ i = aString.Length() - kSecondHalf - 1;
+ if (NS_IS_LOW_SURROGATE(aString.CharAt(i)) &&
+ NS_IS_HIGH_SURROGATE(aString.CharAt(i - 1))) {
+ if (i - 1 <= kFirstHalf) {
+ i++;
+ } else {
+ i--;
+ }
+ }
+ } else if (!IS_IN_BMP(ch)) {
+ i++;
+ }
+ }
+ Append("\"");
+}
+
+// static
+nsCString PrintStringDetail::PrintCharData(char32_t aChar) {
+ switch (aChar) {
+ case 0x0000:
+ return "NULL (0x0000)"_ns;
+ case 0x0008:
+ return "BACKSPACE (0x0008)"_ns;
+ case 0x0009:
+ return "CHARACTER TABULATION (0x0009)"_ns;
+ case 0x000A:
+ return "LINE FEED (0x000A)"_ns;
+ case 0x000B:
+ return "LINE TABULATION (0x000B)"_ns;
+ case 0x000C:
+ return "FORM FEED (0x000C)"_ns;
+ case 0x000D:
+ return "CARRIAGE RETURN (0x000D)"_ns;
+ case 0x0018:
+ return "CANCEL (0x0018)"_ns;
+ case 0x001B:
+ return "ESCAPE (0x001B)"_ns;
+ case 0x0020:
+ return "SPACE (0x0020)"_ns;
+ case 0x007F:
+ return "DELETE (0x007F)"_ns;
+ case 0x00A0:
+ return "NO-BREAK SPACE (0x00A0)"_ns;
+ case 0x00AD:
+ return "SOFT HYPHEN (0x00AD)"_ns;
+ case 0x2000:
+ return "EN QUAD (0x2000)"_ns;
+ case 0x2001:
+ return "EM QUAD (0x2001)"_ns;
+ case 0x2002:
+ return "EN SPACE (0x2002)"_ns;
+ case 0x2003:
+ return "EM SPACE (0x2003)"_ns;
+ case 0x2004:
+ return "THREE-PER-EM SPACE (0x2004)"_ns;
+ case 0x2005:
+ return "FOUR-PER-EM SPACE (0x2005)"_ns;
+ case 0x2006:
+ return "SIX-PER-EM SPACE (0x2006)"_ns;
+ case 0x2007:
+ return "FIGURE SPACE (0x2007)"_ns;
+ case 0x2008:
+ return "PUNCTUATION SPACE (0x2008)"_ns;
+ case 0x2009:
+ return "THIN SPACE (0x2009)"_ns;
+ case 0x200A:
+ return "HAIR SPACE (0x200A)"_ns;
+ case 0x200B:
+ return "ZERO WIDTH SPACE (0x200B)"_ns;
+ case 0x200C:
+ return "ZERO WIDTH NON-JOINER (0x200C)"_ns;
+ case 0x200D:
+ return "ZERO WIDTH JOINER (0x200D)"_ns;
+ case 0x200E:
+ return "LEFT-TO-RIGHT MARK (0x200E)"_ns;
+ case 0x200F:
+ return "RIGHT-TO-LEFT MARK (0x200F)"_ns;
+ case 0x2029:
+ return "PARAGRAPH SEPARATOR (0x2029)"_ns;
+ case 0x202A:
+ return "LEFT-TO-RIGHT EMBEDDING (0x202A)"_ns;
+ case 0x202B:
+ return "RIGHT-TO-LEFT EMBEDDING (0x202B)"_ns;
+ case 0x202D:
+ return "LEFT-TO-RIGHT OVERRIDE (0x202D)"_ns;
+ case 0x202E:
+ return "RIGHT-TO-LEFT OVERRIDE (0x202E)"_ns;
+ case 0x202F:
+ return "NARROW NO-BREAK SPACE (0x202F)"_ns;
+ case 0x205F:
+ return "MEDIUM MATHEMATICAL SPACE (0x205F)"_ns;
+ case 0x2060:
+ return "WORD JOINER (0x2060)"_ns;
+ case 0x2066:
+ return "LEFT-TO-RIGHT ISOLATE (0x2066)"_ns;
+ case 0x2067:
+ return "RIGHT-TO-LEFT ISOLATE (0x2067)"_ns;
+ case 0x3000:
+ return "IDEOGRAPHIC SPACE (0x3000)"_ns;
+ case 0xFEFF:
+ return "ZERO WIDTH NO-BREAK SPACE (0xFEFF)"_ns;
+ default: {
+ if (aChar < ' ' || (aChar >= 0x80 && aChar < 0xA0)) {
+ return nsPrintfCString("Control (0x%04X)", aChar);
+ }
+ if (NS_IS_HIGH_SURROGATE(aChar)) {
+ return nsPrintfCString("High Surrogate (0x%04X)", aChar);
+ }
+ if (NS_IS_LOW_SURROGATE(aChar)) {
+ return nsPrintfCString("Low Surrogate (0x%04X)", aChar);
+ }
+ if (gfxFontUtils::IsVarSelector(aChar)) {
+ return IS_IN_BMP(aChar)
+ ? nsPrintfCString("Variant Selector (0x%04X)", aChar)
+ : nsPrintfCString("Variant Selector (0x%08X)", aChar);
+ }
+ nsAutoString utf16Str;
+ AppendUCS4ToUTF16(aChar, utf16Str);
+ return IS_IN_BMP(aChar)
+ ? nsPrintfCString("'%s' (0x%04X)",
+ NS_ConvertUTF16toUTF8(utf16Str).get(), aChar)
+ : nsPrintfCString("'%s' (0x%08X)",
+ NS_ConvertUTF16toUTF8(utf16Str).get(),
+ aChar);
+ }
+ }
+}
+
+namespace widget {
+
+std::ostream& operator<<(std::ostream& aStream, const IMEEnabled& aEnabled) {
+ switch (aEnabled) {
+ case IMEEnabled::Disabled:
+ return aStream << "DISABLED";
+ case IMEEnabled::Enabled:
+ return aStream << "ENABLED";
+ case IMEEnabled::Password:
+ return aStream << "PASSWORD";
+ case IMEEnabled::Unknown:
+ return aStream << "illegal value";
+ }
+ MOZ_ASSERT_UNREACHABLE("Add a case to handle your new IMEEnabled value");
+ return aStream;
+}
+
+std::ostream& operator<<(std::ostream& aStream, const IMEState::Open& aOpen) {
+ switch (aOpen) {
+ case IMEState::DONT_CHANGE_OPEN_STATE:
+ aStream << "DONT_CHANGE_OPEN_STATE";
+ break;
+ case IMEState::OPEN:
+ aStream << "OPEN";
+ break;
+ case IMEState::CLOSED:
+ aStream << "CLOSED";
+ break;
+ default:
+ aStream << "illegal value";
+ break;
+ }
+ return aStream;
+}
+
+std::ostream& operator<<(std::ostream& aStream, const IMEState& aState) {
+ aStream << "{ mEnabled=" << aState.mEnabled << ", mOpen=" << aState.mOpen
+ << " }";
+ return aStream;
+}
+
+std::ostream& operator<<(std::ostream& aStream,
+ const InputContext::Origin& aOrigin) {
+ switch (aOrigin) {
+ case InputContext::ORIGIN_MAIN:
+ aStream << "ORIGIN_MAIN";
+ break;
+ case InputContext::ORIGIN_CONTENT:
+ aStream << "ORIGIN_CONTENT";
+ break;
+ default:
+ aStream << "illegal value";
+ break;
+ }
+ return aStream;
+}
+
+std::ostream& operator<<(std::ostream& aStream, const InputContext& aContext) {
+ aStream << "{ mIMEState=" << aContext.mIMEState
+ << ", mOrigin=" << aContext.mOrigin << ", mHTMLInputType=\""
+ << aContext.mHTMLInputType << "\", mHTMLInputInputmode=\""
+ << aContext.mHTMLInputInputmode << "\", mActionHint=\""
+ << aContext.mActionHint << "\", mAutocapitalize=\""
+ << aContext.mAutocapitalize << "\", mMayBeIMEUnaware="
+ << (aContext.mMayBeIMEUnaware ? "true" : "false")
+ << ", mIsPrivateBrowsing="
+ << (aContext.mInPrivateBrowsing ? "true" : "false") << " }";
+ return aStream;
+}
+
+std::ostream& operator<<(std::ostream& aStream,
+ const InputContextAction::Cause& aCause) {
+ switch (aCause) {
+ case InputContextAction::CAUSE_UNKNOWN:
+ aStream << "CAUSE_UNKNOWN";
+ break;
+ case InputContextAction::CAUSE_UNKNOWN_CHROME:
+ aStream << "CAUSE_UNKNOWN_CHROME";
+ break;
+ case InputContextAction::CAUSE_KEY:
+ aStream << "CAUSE_KEY";
+ break;
+ case InputContextAction::CAUSE_MOUSE:
+ aStream << "CAUSE_MOUSE";
+ break;
+ case InputContextAction::CAUSE_TOUCH:
+ aStream << "CAUSE_TOUCH";
+ break;
+ case InputContextAction::CAUSE_LONGPRESS:
+ aStream << "CAUSE_LONGPRESS";
+ break;
+ case InputContextAction::CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT:
+ aStream << "CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT";
+ break;
+ case InputContextAction::CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT:
+ aStream << "CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT";
+ break;
+ default:
+ aStream << "illegal value";
+ break;
+ }
+ return aStream;
+}
+
+std::ostream& operator<<(std::ostream& aStream,
+ const InputContextAction::FocusChange& aFocusChange) {
+ switch (aFocusChange) {
+ case InputContextAction::FOCUS_NOT_CHANGED:
+ aStream << "FOCUS_NOT_CHANGED";
+ break;
+ case InputContextAction::GOT_FOCUS:
+ aStream << "GOT_FOCUS";
+ break;
+ case InputContextAction::LOST_FOCUS:
+ aStream << "LOST_FOCUS";
+ break;
+ case InputContextAction::MENU_GOT_PSEUDO_FOCUS:
+ aStream << "MENU_GOT_PSEUDO_FOCUS";
+ break;
+ case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
+ aStream << "MENU_LOST_PSEUDO_FOCUS";
+ break;
+ case InputContextAction::WIDGET_CREATED:
+ aStream << "WIDGET_CREATED";
+ break;
+ default:
+ aStream << "illegal value";
+ break;
+ }
+ return aStream;
+}
+
+std::ostream& operator<<(
+ std::ostream& aStream,
+ const IMENotification::SelectionChangeDataBase& aData) {
+ if (!aData.IsValid()) {
+ aStream << "{ IsValid()=false }";
+ return aStream;
+ }
+ aStream << "{ mOffset=" << aData.mOffset;
+ if (aData.mString->Length() > 20) {
+ aStream << ", mString.Length()=" << aData.mString->Length();
+ } else {
+ aStream << ", mString=\"" << NS_ConvertUTF16toUTF8(*aData.mString)
+ << "\" (Length()=" << aData.mString->Length() << ")";
+ }
+
+ aStream << ", GetWritingMode()=" << aData.GetWritingMode()
+ << ", mReversed=" << (aData.mReversed ? "true" : "false")
+ << ", mCausedByComposition="
+ << (aData.mCausedByComposition ? "true" : "false")
+ << ", mCausedBySelectionEvent="
+ << (aData.mCausedBySelectionEvent ? "true" : "false")
+ << ", mOccurredDuringComposition="
+ << (aData.mOccurredDuringComposition ? "true" : "false") << " }";
+ return aStream;
+}
+
+std::ostream& operator<<(std::ostream& aStream,
+ const IMENotification::TextChangeDataBase& aData) {
+ if (!aData.IsValid()) {
+ aStream << "{ IsValid()=false }";
+ return aStream;
+ }
+ aStream << "{ mStartOffset=" << aData.mStartOffset
+ << ", mRemoveEndOffset=" << aData.mRemovedEndOffset
+ << ", mAddedEndOffset=" << aData.mAddedEndOffset
+ << ", mCausedOnlyByComposition="
+ << (aData.mCausedOnlyByComposition ? "true" : "false")
+ << ", mIncludingChangesDuringComposition="
+ << (aData.mIncludingChangesDuringComposition ? "true" : "false")
+ << ", mIncludingChangesWithoutComposition="
+ << (aData.mIncludingChangesWithoutComposition ? "true" : "false")
+ << " }";
+ return aStream;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/IMEData.h b/widget/IMEData.h
new file mode 100644
index 0000000000..c98ca3c217
--- /dev/null
+++ b/widget/IMEData.h
@@ -0,0 +1,987 @@
+/* -*- Mode: C++; tab-width: 40; 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_IMEData_h_
+#define mozilla_widget_IMEData_h_
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/ToString.h"
+
+#include "nsPoint.h"
+#include "nsRect.h"
+#include "nsString.h"
+#include "nsXULAppAPI.h"
+#include "Units.h"
+
+class nsIWidget;
+
+namespace mozilla {
+
+class WritingMode;
+
+// Helper class to logging string which may contain various Unicode characters
+// and/or may be too long string for logging.
+class MOZ_STACK_CLASS PrintStringDetail : public nsAutoCString {
+ public:
+ static constexpr uint32_t kMaxLengthForCompositionString = 8;
+ static constexpr uint32_t kMaxLengthForSelectedString = 12;
+ static constexpr uint32_t kMaxLengthForEditor = 20;
+
+ PrintStringDetail() = delete;
+ explicit PrintStringDetail(const nsAString& aString,
+ uint32_t aMaxLength = UINT32_MAX);
+
+ private:
+ static nsCString PrintCharData(char32_t aChar);
+};
+
+// StartAndEndOffsets represents a range in flat-text.
+template <typename IntType>
+class StartAndEndOffsets {
+ protected:
+ static IntType MaxOffset() { return std::numeric_limits<IntType>::max(); }
+
+ public:
+ StartAndEndOffsets() = delete;
+ explicit StartAndEndOffsets(IntType aStartOffset, IntType aEndOffset)
+ : mStartOffset(aStartOffset),
+ mEndOffset(aStartOffset <= aEndOffset ? aEndOffset : aStartOffset) {
+ MOZ_ASSERT(aStartOffset <= mEndOffset);
+ }
+
+ IntType StartOffset() const { return mStartOffset; }
+ IntType Length() const { return mEndOffset - mStartOffset; }
+ IntType EndOffset() const { return mEndOffset; }
+
+ bool IsOffsetInRange(IntType aOffset) const {
+ return aOffset >= mStartOffset && aOffset < mEndOffset;
+ }
+ bool IsOffsetInRangeOrEndOffset(IntType aOffset) const {
+ return aOffset >= mStartOffset && aOffset <= mEndOffset;
+ }
+
+ void MoveTo(IntType aNewStartOffset) {
+ auto delta = static_cast<int64_t>(mStartOffset) - aNewStartOffset;
+ mStartOffset += delta;
+ mEndOffset += delta;
+ }
+ void SetOffsetAndLength(IntType aNewOffset, IntType aNewLength) {
+ mStartOffset = aNewOffset;
+ CheckedInt<IntType> endOffset(aNewOffset + aNewLength);
+ mEndOffset = endOffset.isValid() ? endOffset.value() : MaxOffset();
+ }
+ void SetEndOffset(IntType aEndOffset) {
+ MOZ_ASSERT(mStartOffset <= aEndOffset);
+ mEndOffset = std::max(aEndOffset, mStartOffset);
+ }
+ void SetStartAndEndOffsets(IntType aStartOffset, IntType aEndOffset) {
+ MOZ_ASSERT(aStartOffset <= aEndOffset);
+ mStartOffset = aStartOffset;
+ mEndOffset = aStartOffset <= aEndOffset ? aEndOffset : aStartOffset;
+ }
+ void SetLength(IntType aNewLength) {
+ CheckedInt<IntType> endOffset(mStartOffset + aNewLength);
+ mEndOffset = endOffset.isValid() ? endOffset.value() : MaxOffset();
+ }
+
+ friend std::ostream& operator<<(
+ std::ostream& aStream,
+ const StartAndEndOffsets<IntType>& aStartAndEndOffsets) {
+ aStream << "{ mStartOffset=" << aStartAndEndOffsets.mStartOffset
+ << ", mEndOffset=" << aStartAndEndOffsets.mEndOffset
+ << ", Length()=" << aStartAndEndOffsets.Length() << " }";
+ return aStream;
+ }
+
+ private:
+ IntType mStartOffset;
+ IntType mEndOffset;
+};
+
+// OffsetAndData class is designed for storing composition string and its
+// start offset. Length() and EndOffset() return only valid length or
+// offset. I.e., if the string is too long for inserting at the offset,
+// the length is shrunken. However, the string itself is not shrunken.
+// Therefore, moving it to where all of the string can be contained,
+// they will return longer/bigger value.
+enum class OffsetAndDataFor {
+ CompositionString,
+ SelectedString,
+ EditorString,
+};
+template <typename IntType>
+class OffsetAndData {
+ protected:
+ static IntType MaxOffset() { return std::numeric_limits<IntType>::max(); }
+
+ public:
+ OffsetAndData() = delete;
+ explicit OffsetAndData(
+ IntType aStartOffset, const nsAString& aData,
+ OffsetAndDataFor aFor = OffsetAndDataFor::CompositionString)
+ : mData(aData), mOffset(aStartOffset), mFor(aFor) {}
+
+ IntType StartOffset() const { return mOffset; }
+ IntType Length() const {
+ CheckedInt<IntType> endOffset(mOffset + mData.Length());
+ return endOffset.isValid() ? mData.Length() : MaxOffset() - mOffset;
+ }
+ IntType EndOffset() const { return mOffset + Length(); }
+ StartAndEndOffsets<IntType> CreateStartAndEndOffsets() const {
+ return StartAndEndOffsets<IntType>(StartOffset(), EndOffset());
+ }
+ const nsString& DataRef() const {
+ // In strictly speaking, we should return substring which may be shrunken
+ // for rounding to the max offset. However, it's unrealistic edge case,
+ // and creating new string is not so cheap job in a hot path. Therefore,
+ // this just returns the data as-is.
+ return mData;
+ }
+ bool IsDataEmpty() const { return mData.IsEmpty(); }
+
+ bool IsOffsetInRange(IntType aOffset) const {
+ return aOffset >= mOffset && aOffset < EndOffset();
+ }
+ bool IsOffsetInRangeOrEndOffset(IntType aOffset) const {
+ return aOffset >= mOffset && aOffset <= EndOffset();
+ }
+
+ void MoveTo(IntType aNewOffset) { mOffset = aNewOffset; }
+ void SetOffsetAndData(IntType aStartOffset, const nsAString& aData) {
+ mOffset = aStartOffset;
+ mData = aData;
+ }
+ void SetData(const nsAString& aData) { mData = aData; }
+ void TruncateData(uint32_t aLength = 0) { mData.Truncate(aLength); }
+ void ReplaceData(nsAString::size_type aCutStart,
+ nsAString::size_type aCutLength,
+ const nsAString& aNewString) {
+ mData.Replace(aCutStart, aCutLength, aNewString);
+ }
+
+ friend std::ostream& operator<<(
+ std::ostream& aStream, const OffsetAndData<IntType>& aOffsetAndData) {
+ const auto maxDataLength =
+ aOffsetAndData.mFor == OffsetAndDataFor::CompositionString
+ ? PrintStringDetail::kMaxLengthForCompositionString
+ : (aOffsetAndData.mFor == OffsetAndDataFor::SelectedString
+ ? PrintStringDetail::kMaxLengthForSelectedString
+ : PrintStringDetail::kMaxLengthForEditor);
+ aStream << "{ mOffset=" << aOffsetAndData.mOffset << ", mData="
+ << PrintStringDetail(aOffsetAndData.mData, maxDataLength).get()
+ << ", Length()=" << aOffsetAndData.Length()
+ << ", EndOffset()=" << aOffsetAndData.EndOffset() << " }";
+ return aStream;
+ }
+
+ private:
+ nsString mData;
+ IntType mOffset;
+ OffsetAndDataFor mFor;
+};
+
+namespace widget {
+
+/**
+ * Preference for receiving IME updates
+ *
+ * If mWantUpdates is not NOTIFY_NOTHING, nsTextStateManager will observe text
+ * change and/or selection change and call nsIWidget::NotifyIME() with
+ * NOTIFY_IME_OF_SELECTION_CHANGE and/or NOTIFY_IME_OF_TEXT_CHANGE.
+ * Please note that the text change observing cost is very expensive especially
+ * on an HTML editor has focus.
+ * If the IME implementation on a particular platform doesn't care about
+ * NOTIFY_IME_OF_SELECTION_CHANGE and/or NOTIFY_IME_OF_TEXT_CHANGE,
+ * they should set mWantUpdates to NOTIFY_NOTHING to avoid the cost.
+ * If the IME implementation needs notifications even while our process is
+ * deactive, it should also set NOTIFY_DURING_DEACTIVE.
+ */
+struct IMENotificationRequests final {
+ typedef uint8_t Notifications;
+
+ enum : Notifications {
+ NOTIFY_NOTHING = 0,
+ NOTIFY_TEXT_CHANGE = 1 << 1,
+ NOTIFY_POSITION_CHANGE = 1 << 2,
+ // NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR is used when mouse button is pressed
+ // or released on a character in the focused editor. The notification is
+ // notified to IME as a mouse event. If it's consumed by IME, NotifyIME()
+ // returns NS_SUCCESS_EVENT_CONSUMED. Otherwise, it returns NS_OK if it's
+ // handled without any error.
+ NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR = 1 << 3,
+ // NOTE: NOTIFY_DURING_DEACTIVE isn't supported in environments where two
+ // or more compositions are possible. E.g., Mac and Linux (GTK).
+ NOTIFY_DURING_DEACTIVE = 1 << 7,
+
+ NOTIFY_ALL = NOTIFY_TEXT_CHANGE | NOTIFY_POSITION_CHANGE |
+ NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR,
+ };
+
+ IMENotificationRequests() : mWantUpdates(NOTIFY_NOTHING) {}
+
+ explicit IMENotificationRequests(Notifications aWantUpdates)
+ : mWantUpdates(aWantUpdates) {}
+
+ IMENotificationRequests operator|(
+ const IMENotificationRequests& aOther) const {
+ return IMENotificationRequests(aOther.mWantUpdates | mWantUpdates);
+ }
+ IMENotificationRequests& operator|=(const IMENotificationRequests& aOther) {
+ mWantUpdates |= aOther.mWantUpdates;
+ return *this;
+ }
+ bool operator==(const IMENotificationRequests& aOther) const {
+ return mWantUpdates == aOther.mWantUpdates;
+ }
+
+ bool WantTextChange() const { return !!(mWantUpdates & NOTIFY_TEXT_CHANGE); }
+
+ bool WantPositionChanged() const {
+ return !!(mWantUpdates & NOTIFY_POSITION_CHANGE);
+ }
+
+ bool WantChanges() const { return WantTextChange(); }
+
+ bool WantMouseButtonEventOnChar() const {
+ return !!(mWantUpdates & NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR);
+ }
+
+ bool WantDuringDeactive() const {
+ return !!(mWantUpdates & NOTIFY_DURING_DEACTIVE);
+ }
+
+ Notifications mWantUpdates;
+};
+
+/**
+ * IME enabled states.
+ *
+ * WARNING: If you change these values, you also need to edit:
+ * nsIDOMWindowUtils.idl
+ */
+enum class IMEEnabled {
+ /**
+ * 'Disabled' means the user cannot use IME. So, the IME open state should
+ * be 'closed' during 'disabled'.
+ */
+ Disabled,
+ /**
+ * 'Enabled' means the user can use IME.
+ */
+ Enabled,
+ /**
+ * 'Password' state is a special case for the password editors.
+ * E.g., on mac, the password editors should disable the non-Roman
+ * keyboard layouts at getting focus. Thus, the password editor may have
+ * special rules on some platforms.
+ */
+ Password,
+ /**
+ * 'Unknown' is useful when you cache this enum. So, this shouldn't be
+ * used with nsIWidget::SetInputContext().
+ */
+ Unknown,
+};
+
+/**
+ * Contains IMEStatus plus information about the current
+ * input context that the IME can use as hints if desired.
+ */
+
+struct IMEState final {
+ IMEEnabled mEnabled;
+
+ /**
+ * IME open states the mOpen value of SetInputContext() should be one value of
+ * OPEN, CLOSE or DONT_CHANGE_OPEN_STATE. GetInputContext() should return
+ * OPEN, CLOSE or OPEN_STATE_NOT_SUPPORTED.
+ */
+ enum Open {
+ /**
+ * 'Unsupported' means the platform cannot return actual IME open state.
+ * This value is used only by GetInputContext().
+ */
+ OPEN_STATE_NOT_SUPPORTED,
+ /**
+ * 'Don't change' means the widget shouldn't change IME open state when
+ * SetInputContext() is called.
+ */
+ DONT_CHANGE_OPEN_STATE = OPEN_STATE_NOT_SUPPORTED,
+ /**
+ * 'Open' means that IME should compose in its primary language (or latest
+ * input mode except direct ASCII character input mode). Even if IME is
+ * opened by this value, users should be able to close IME by theirselves.
+ * Web contents can specify this value by |ime-mode: active;|.
+ */
+ OPEN,
+ /**
+ * 'Closed' means that IME shouldn't handle key events (or should handle
+ * as ASCII character inputs on mobile device). Even if IME is closed by
+ * this value, users should be able to open IME by theirselves.
+ * Web contents can specify this value by |ime-mode: inactive;|.
+ */
+ CLOSED
+ };
+ Open mOpen;
+
+ IMEState() : mEnabled(IMEEnabled::Enabled), mOpen(DONT_CHANGE_OPEN_STATE) {}
+
+ explicit IMEState(IMEEnabled aEnabled, Open aOpen = DONT_CHANGE_OPEN_STATE)
+ : mEnabled(aEnabled), mOpen(aOpen) {}
+
+ // Returns true if the user can input characters.
+ // This means that a plain text editor, an HTML editor, a password editor or
+ // a plain text editor whose ime-mode is "disabled".
+ bool IsEditable() const {
+ return mEnabled == IMEEnabled::Enabled || mEnabled == IMEEnabled::Password;
+ }
+};
+
+// NS_ONLY_ONE_NATIVE_IME_CONTEXT is a special value of native IME context.
+// If there can be only one IME composition in a process, this can be used.
+#define NS_ONLY_ONE_NATIVE_IME_CONTEXT \
+ (reinterpret_cast<void*>(static_cast<intptr_t>(-1)))
+
+struct NativeIMEContext final {
+ // Pointer to native IME context. Typically this is the result of
+ // nsIWidget::GetNativeData(NS_RAW_NATIVE_IME_CONTEXT) in the parent process.
+ // See also NS_ONLY_ONE_NATIVE_IME_CONTEXT.
+ uintptr_t mRawNativeIMEContext;
+ // Process ID of the origin of mNativeIMEContext.
+ uint64_t mOriginProcessID;
+
+ NativeIMEContext() : mRawNativeIMEContext(0), mOriginProcessID(0) {
+ Init(nullptr);
+ }
+
+ explicit NativeIMEContext(nsIWidget* aWidget)
+ : mRawNativeIMEContext(0), mOriginProcessID(0) {
+ Init(aWidget);
+ }
+
+ bool IsValid() const {
+ return mRawNativeIMEContext &&
+ mOriginProcessID != static_cast<uintptr_t>(-1);
+ }
+
+ void Init(nsIWidget* aWidget);
+ void InitWithRawNativeIMEContext(const void* aRawNativeIMEContext) {
+ InitWithRawNativeIMEContext(const_cast<void*>(aRawNativeIMEContext));
+ }
+ void InitWithRawNativeIMEContext(void* aRawNativeIMEContext);
+
+ bool operator==(const NativeIMEContext& aOther) const {
+ return mRawNativeIMEContext == aOther.mRawNativeIMEContext &&
+ mOriginProcessID == aOther.mOriginProcessID;
+ }
+ bool operator!=(const NativeIMEContext& aOther) const {
+ return !(*this == aOther);
+ }
+};
+
+struct InputContext final {
+ InputContext()
+ : mOrigin(XRE_IsParentProcess() ? ORIGIN_MAIN : ORIGIN_CONTENT),
+ mMayBeIMEUnaware(false),
+ mHasHandledUserInput(false),
+ mInPrivateBrowsing(false) {}
+
+ // If InputContext instance is a static variable, any heap allocated stuff
+ // of its members need to be deleted at XPCOM shutdown. Otherwise, it's
+ // detected as memory leak.
+ void ShutDown() {
+ mHTMLInputType.Truncate();
+ mHTMLInputInputmode.Truncate();
+ mActionHint.Truncate();
+ mAutocapitalize.Truncate();
+ }
+
+ bool IsPasswordEditor() const {
+ return mHTMLInputType.LowerCaseEqualsLiteral("password");
+ }
+
+ // https://html.spec.whatwg.org/dev/interaction.html#autocapitalization
+ bool IsAutocapitalizeSupported() const {
+ return !mHTMLInputType.EqualsLiteral("password") &&
+ !mHTMLInputType.EqualsLiteral("url") &&
+ !mHTMLInputType.EqualsLiteral("email");
+ }
+
+ IMEState mIMEState;
+
+ /* The type of the input if the input is a html input field */
+ nsString mHTMLInputType;
+
+ /* The type of the inputmode */
+ nsString mHTMLInputInputmode;
+
+ /* A hint for the action that is performed when the input is submitted */
+ nsString mActionHint;
+
+ /* A hint for autocapitalize */
+ nsString mAutocapitalize;
+
+ /**
+ * mOrigin indicates whether this focus event refers to main or remote
+ * content.
+ */
+ enum Origin {
+ // Adjusting focus of content on the main process
+ ORIGIN_MAIN,
+ // Adjusting focus of content in a remote process
+ ORIGIN_CONTENT
+ };
+ Origin mOrigin;
+
+ /* True if the webapp may be unaware of IME events such as input event or
+ * composiion events. This enables a key-events-only mode on Android for
+ * compatibility with webapps relying on key listeners. */
+ bool mMayBeIMEUnaware;
+
+ /**
+ * True if the document has ever received user input
+ */
+ bool mHasHandledUserInput;
+
+ /* Whether the owning document of the input element has been loaded
+ * in private browsing mode. */
+ bool mInPrivateBrowsing;
+
+ bool IsOriginMainProcess() const { return mOrigin == ORIGIN_MAIN; }
+
+ bool IsOriginContentProcess() const { return mOrigin == ORIGIN_CONTENT; }
+
+ bool IsOriginCurrentProcess() const {
+ if (XRE_IsParentProcess()) {
+ return IsOriginMainProcess();
+ }
+ return IsOriginContentProcess();
+ }
+};
+
+// FYI: Implemented in nsBaseWidget.cpp
+const char* ToChar(InputContext::Origin aOrigin);
+
+struct InputContextAction final {
+ /**
+ * mCause indicates what action causes calling nsIWidget::SetInputContext().
+ * It must be one of following values.
+ */
+ enum Cause {
+ // The cause is unknown but originated from content. Focus might have been
+ // changed by content script.
+ CAUSE_UNKNOWN,
+ // The cause is unknown but originated from chrome. Focus might have been
+ // changed by chrome script.
+ CAUSE_UNKNOWN_CHROME,
+ // The cause is user's keyboard operation.
+ CAUSE_KEY,
+ // The cause is user's mouse operation.
+ CAUSE_MOUSE,
+ // The cause is user's touch operation (implies mouse)
+ CAUSE_TOUCH,
+ // The cause is users' long press operation.
+ CAUSE_LONGPRESS,
+ // The cause is unknown but it occurs during user input except keyboard
+ // input. E.g., an event handler of a user input event moves focus.
+ CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT,
+ // The cause is unknown but it occurs during keyboard input.
+ CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT,
+ };
+ Cause mCause;
+
+ /**
+ * mFocusChange indicates what happened for focus.
+ */
+ enum FocusChange {
+ FOCUS_NOT_CHANGED,
+ // A content got focus.
+ GOT_FOCUS,
+ // Focused content lost focus.
+ LOST_FOCUS,
+ // Menu got pseudo focus that means focused content isn't changed but
+ // keyboard events will be handled by menu.
+ MENU_GOT_PSEUDO_FOCUS,
+ // Menu lost pseudo focus that means focused content will handle keyboard
+ // events.
+ MENU_LOST_PSEUDO_FOCUS,
+ // The widget is created. When a widget is crated, it may need to notify
+ // IME module to initialize its native IME context. In such case, this is
+ // used. I.e., this isn't used by IMEStateManager.
+ WIDGET_CREATED
+ };
+ FocusChange mFocusChange;
+
+ bool ContentGotFocusByTrustedCause() const {
+ return (mFocusChange == GOT_FOCUS && mCause != CAUSE_UNKNOWN);
+ }
+
+ bool UserMightRequestOpenVKB() const {
+ // If focus is changed, user must not request to open VKB.
+ if (mFocusChange != FOCUS_NOT_CHANGED) {
+ return false;
+ }
+ switch (mCause) {
+ // If user clicks or touches focused editor, user must request to open
+ // VKB.
+ case CAUSE_MOUSE:
+ case CAUSE_TOUCH:
+ // If script does something during a user input and that causes changing
+ // input context, user might request to open VKB. E.g., user clicks
+ // dummy editor and JS moves focus to an actual editable node. However,
+ // this should return false if the user input is a keyboard event since
+ // physical keyboard operation shouldn't cause opening VKB.
+ case CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * IsHandlingUserInput() returns true if it's caused by a user action directly
+ * or it's caused by script or something but it occurred while we're handling
+ * a user action. E.g., when it's caused by Element.focus() in an event
+ * handler of a user input, this returns true.
+ */
+ static bool IsHandlingUserInput(Cause aCause) {
+ switch (aCause) {
+ case CAUSE_KEY:
+ case CAUSE_MOUSE:
+ case CAUSE_TOUCH:
+ case CAUSE_LONGPRESS:
+ case CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT:
+ case CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ bool IsHandlingUserInput() const { return IsHandlingUserInput(mCause); }
+
+ InputContextAction()
+ : mCause(CAUSE_UNKNOWN), mFocusChange(FOCUS_NOT_CHANGED) {}
+
+ explicit InputContextAction(Cause aCause,
+ FocusChange aFocusChange = FOCUS_NOT_CHANGED)
+ : mCause(aCause), mFocusChange(aFocusChange) {}
+};
+
+// IMEMessage is shared by IMEStateManager and TextComposition.
+// Update values in GeckoEditable.java if you make changes here.
+// XXX Negative values are used in Android...
+typedef int8_t IMEMessageType;
+enum IMEMessage : IMEMessageType {
+ // This is used by IMENotification internally. This means that the instance
+ // hasn't been initialized yet.
+ NOTIFY_IME_OF_NOTHING,
+ // An editable content is getting focus
+ NOTIFY_IME_OF_FOCUS,
+ // An editable content is losing focus
+ NOTIFY_IME_OF_BLUR,
+ // Selection in the focused editable content is changed
+ NOTIFY_IME_OF_SELECTION_CHANGE,
+ // Text in the focused editable content is changed
+ NOTIFY_IME_OF_TEXT_CHANGE,
+ // Notified when a dispatched composition event is handled by the
+ // contents. This must be notified after the other notifications.
+ // Note that if a remote process has focus, this is notified only once when
+ // all dispatched events are handled completely. So, the receiver shouldn't
+ // count number of received this notification for comparing with the number
+ // of dispatched events.
+ // NOTE: If a composition event causes moving focus from the focused editor,
+ // this notification may not be notified as usual. Even in such case,
+ // NOTIFY_IME_OF_BLUR is always sent. So, notification listeners
+ // should tread the blur notification as including this if there is
+ // pending composition events.
+ NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED,
+ // Position or size of focused element may be changed.
+ NOTIFY_IME_OF_POSITION_CHANGE,
+ // Mouse button event is fired on a character in focused editor
+ NOTIFY_IME_OF_MOUSE_BUTTON_EVENT,
+ // Request to commit current composition to IME
+ // (some platforms may not support)
+ REQUEST_TO_COMMIT_COMPOSITION,
+ // Request to cancel current composition to IME
+ // (some platforms may not support)
+ REQUEST_TO_CANCEL_COMPOSITION
+};
+
+// FYI: Implemented in nsBaseWidget.cpp
+const char* ToChar(IMEMessage aIMEMessage);
+
+struct IMENotification final {
+ IMENotification() : mMessage(NOTIFY_IME_OF_NOTHING), mSelectionChangeData() {}
+
+ IMENotification(const IMENotification& aOther)
+ : mMessage(NOTIFY_IME_OF_NOTHING) {
+ Assign(aOther);
+ }
+
+ ~IMENotification() { Clear(); }
+
+ MOZ_IMPLICIT IMENotification(IMEMessage aMessage)
+ : mMessage(aMessage), mSelectionChangeData() {
+ switch (aMessage) {
+ case NOTIFY_IME_OF_SELECTION_CHANGE:
+ mSelectionChangeData.mString = new nsString();
+ mSelectionChangeData.Clear();
+ break;
+ case NOTIFY_IME_OF_TEXT_CHANGE:
+ mTextChangeData.Clear();
+ break;
+ case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
+ mMouseButtonEventData.mEventMessage = eVoidEvent;
+ mMouseButtonEventData.mOffset = UINT32_MAX;
+ mMouseButtonEventData.mCursorPos.Set(nsIntPoint(0, 0));
+ mMouseButtonEventData.mCharRect.Set(nsIntRect(0, 0, 0, 0));
+ mMouseButtonEventData.mButton = -1;
+ mMouseButtonEventData.mButtons = 0;
+ mMouseButtonEventData.mModifiers = 0;
+ break;
+ default:
+ break;
+ }
+ }
+
+ void Assign(const IMENotification& aOther) {
+ bool changingMessage = mMessage != aOther.mMessage;
+ if (changingMessage) {
+ Clear();
+ mMessage = aOther.mMessage;
+ }
+ switch (mMessage) {
+ case NOTIFY_IME_OF_SELECTION_CHANGE:
+ if (changingMessage) {
+ mSelectionChangeData.mString = new nsString();
+ }
+ mSelectionChangeData.Assign(aOther.mSelectionChangeData);
+ break;
+ case NOTIFY_IME_OF_TEXT_CHANGE:
+ mTextChangeData = aOther.mTextChangeData;
+ break;
+ case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
+ mMouseButtonEventData = aOther.mMouseButtonEventData;
+ break;
+ default:
+ break;
+ }
+ }
+
+ IMENotification& operator=(const IMENotification& aOther) {
+ Assign(aOther);
+ return *this;
+ }
+
+ void Clear() {
+ if (mMessage == NOTIFY_IME_OF_SELECTION_CHANGE) {
+ MOZ_ASSERT(mSelectionChangeData.mString);
+ delete mSelectionChangeData.mString;
+ mSelectionChangeData.mString = nullptr;
+ }
+ mMessage = NOTIFY_IME_OF_NOTHING;
+ }
+
+ bool HasNotification() const { return mMessage != NOTIFY_IME_OF_NOTHING; }
+
+ void MergeWith(const IMENotification& aNotification) {
+ switch (mMessage) {
+ case NOTIFY_IME_OF_NOTHING:
+ MOZ_ASSERT(aNotification.mMessage != NOTIFY_IME_OF_NOTHING);
+ Assign(aNotification);
+ break;
+ case NOTIFY_IME_OF_SELECTION_CHANGE:
+ MOZ_ASSERT(aNotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE);
+ mSelectionChangeData.Assign(aNotification.mSelectionChangeData);
+ break;
+ case NOTIFY_IME_OF_TEXT_CHANGE:
+ MOZ_ASSERT(aNotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE);
+ mTextChangeData += aNotification.mTextChangeData;
+ break;
+ case NOTIFY_IME_OF_POSITION_CHANGE:
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
+ MOZ_ASSERT(aNotification.mMessage == mMessage);
+ break;
+ default:
+ MOZ_CRASH("Merging notification isn't supported");
+ break;
+ }
+ }
+
+ IMEMessage mMessage;
+
+ struct Point {
+ int32_t mX;
+ int32_t mY;
+
+ void Set(const nsIntPoint& aPoint) {
+ mX = aPoint.x;
+ mY = aPoint.y;
+ }
+ nsIntPoint AsIntPoint() const { return nsIntPoint(mX, mY); }
+ };
+
+ struct Rect {
+ int32_t mX;
+ int32_t mY;
+ int32_t mWidth;
+ int32_t mHeight;
+
+ void Set(const nsIntRect& aRect) {
+ aRect.GetRect(&mX, &mY, &mWidth, &mHeight);
+ }
+ nsIntRect AsIntRect() const { return nsIntRect(mX, mY, mWidth, mHeight); }
+ };
+
+ // NOTIFY_IME_OF_SELECTION_CHANGE specific data
+ struct SelectionChangeDataBase {
+ // Selection range.
+ uint32_t mOffset;
+
+ // Selected string
+ nsString* mString;
+
+ // Writing mode at the selection.
+ uint8_t mWritingMode;
+
+ bool mReversed;
+ bool mCausedByComposition;
+ bool mCausedBySelectionEvent;
+ bool mOccurredDuringComposition;
+
+ void SetWritingMode(const WritingMode& aWritingMode);
+ WritingMode GetWritingMode() const;
+
+ uint32_t StartOffset() const {
+ return mOffset + (mReversed ? Length() : 0);
+ }
+ uint32_t EndOffset() const { return mOffset + (mReversed ? 0 : Length()); }
+ const nsString& String() const { return *mString; }
+ uint32_t Length() const { return mString->Length(); }
+ bool IsInInt32Range() const { return mOffset + Length() <= INT32_MAX; }
+ bool IsCollapsed() const { return mString->IsEmpty(); }
+ void ClearSelectionData() {
+ mOffset = UINT32_MAX;
+ mString->Truncate();
+ mWritingMode = 0;
+ mReversed = false;
+ }
+ void Clear() {
+ ClearSelectionData();
+ mCausedByComposition = false;
+ mCausedBySelectionEvent = false;
+ mOccurredDuringComposition = false;
+ }
+ bool IsValid() const { return mOffset != UINT32_MAX; }
+ void Assign(const SelectionChangeDataBase& aOther) {
+ mOffset = aOther.mOffset;
+ *mString = aOther.String();
+ mWritingMode = aOther.mWritingMode;
+ mReversed = aOther.mReversed;
+ AssignReason(aOther.mCausedByComposition, aOther.mCausedBySelectionEvent,
+ aOther.mOccurredDuringComposition);
+ }
+ void AssignReason(bool aCausedByComposition, bool aCausedBySelectionEvent,
+ bool aOccurredDuringComposition) {
+ mCausedByComposition = aCausedByComposition;
+ mCausedBySelectionEvent = aCausedBySelectionEvent;
+ mOccurredDuringComposition = aOccurredDuringComposition;
+ }
+ };
+
+ // SelectionChangeDataBase cannot have constructors because it's used in
+ // the union. Therefore, SelectionChangeData should only implement
+ // constructors. In other words, add other members to
+ // SelectionChangeDataBase.
+ struct SelectionChangeData final : public SelectionChangeDataBase {
+ SelectionChangeData() {
+ mString = &mStringInstance;
+ Clear();
+ }
+ explicit SelectionChangeData(const SelectionChangeDataBase& aOther) {
+ mString = &mStringInstance;
+ Assign(aOther);
+ }
+ SelectionChangeData(const SelectionChangeData& aOther) {
+ mString = &mStringInstance;
+ Assign(aOther);
+ }
+ SelectionChangeData& operator=(const SelectionChangeDataBase& aOther) {
+ mString = &mStringInstance;
+ Assign(aOther);
+ return *this;
+ }
+ SelectionChangeData& operator=(const SelectionChangeData& aOther) {
+ mString = &mStringInstance;
+ Assign(aOther);
+ return *this;
+ }
+
+ private:
+ // When SelectionChangeData is used outside of union, it shouldn't create
+ // nsString instance in the heap as far as possible.
+ nsString mStringInstance;
+ };
+
+ struct TextChangeDataBase {
+ // mStartOffset is the start offset of modified or removed text in
+ // original content and inserted text in new content.
+ uint32_t mStartOffset;
+ // mRemovalEndOffset is the end offset of modified or removed text in
+ // original content. If the value is same as mStartOffset, no text hasn't
+ // been removed yet.
+ uint32_t mRemovedEndOffset;
+ // mAddedEndOffset is the end offset of inserted text or same as
+ // mStartOffset if just removed. The vlaue is offset in the new content.
+ uint32_t mAddedEndOffset;
+
+ // Note that TextChangeDataBase may be the result of merging two or more
+ // changes especially in e10s mode.
+
+ // mCausedOnlyByComposition is true only when *all* merged changes are
+ // caused by composition.
+ bool mCausedOnlyByComposition;
+ // mIncludingChangesDuringComposition is true if at least one change which
+ // is not caused by composition occurred during the last composition.
+ // Note that if after the last composition is finished and there are some
+ // changes not caused by composition, this is set to false.
+ bool mIncludingChangesDuringComposition;
+ // mIncludingChangesWithoutComposition is true if there is at least one
+ // change which did occur when there wasn't a composition ongoing.
+ bool mIncludingChangesWithoutComposition;
+
+ uint32_t OldLength() const {
+ MOZ_ASSERT(IsValid());
+ return mRemovedEndOffset - mStartOffset;
+ }
+ uint32_t NewLength() const {
+ MOZ_ASSERT(IsValid());
+ return mAddedEndOffset - mStartOffset;
+ }
+
+ // Positive if text is added. Negative if text is removed.
+ int64_t Difference() const { return mAddedEndOffset - mRemovedEndOffset; }
+
+ bool IsInInt32Range() const {
+ MOZ_ASSERT(IsValid());
+ return mStartOffset <= INT32_MAX && mRemovedEndOffset <= INT32_MAX &&
+ mAddedEndOffset <= INT32_MAX;
+ }
+
+ bool IsValid() const {
+ return !(mStartOffset == UINT32_MAX && !mRemovedEndOffset &&
+ !mAddedEndOffset);
+ }
+
+ void Clear() {
+ mStartOffset = UINT32_MAX;
+ mRemovedEndOffset = mAddedEndOffset = 0;
+ }
+
+ void MergeWith(const TextChangeDataBase& aOther);
+ TextChangeDataBase& operator+=(const TextChangeDataBase& aOther) {
+ MergeWith(aOther);
+ return *this;
+ }
+
+#ifdef DEBUG
+ void Test();
+#endif // #ifdef DEBUG
+ };
+
+ // TextChangeDataBase cannot have constructors because they are used in union.
+ // Therefore, TextChangeData should only implement constructor. In other
+ // words, add other members to TextChangeDataBase.
+ struct TextChangeData : public TextChangeDataBase {
+ TextChangeData() { Clear(); }
+
+ TextChangeData(uint32_t aStartOffset, uint32_t aRemovedEndOffset,
+ uint32_t aAddedEndOffset, bool aCausedByComposition,
+ bool aOccurredDuringComposition) {
+ MOZ_ASSERT(aRemovedEndOffset >= aStartOffset,
+ "removed end offset must not be smaller than start offset");
+ MOZ_ASSERT(aAddedEndOffset >= aStartOffset,
+ "added end offset must not be smaller than start offset");
+ mStartOffset = aStartOffset;
+ mRemovedEndOffset = aRemovedEndOffset;
+ mAddedEndOffset = aAddedEndOffset;
+ mCausedOnlyByComposition = aCausedByComposition;
+ mIncludingChangesDuringComposition =
+ !aCausedByComposition && aOccurredDuringComposition;
+ mIncludingChangesWithoutComposition =
+ !aCausedByComposition && !aOccurredDuringComposition;
+ }
+ };
+
+ struct MouseButtonEventData {
+ // The value of WidgetEvent::mMessage
+ EventMessage mEventMessage;
+ // Character offset from the start of the focused editor under the cursor
+ uint32_t mOffset;
+ // Cursor position in pixels relative to the widget
+ Point mCursorPos;
+ // Character rect in pixels under the cursor relative to the widget
+ Rect mCharRect;
+ // The value of WidgetMouseEventBase::button and buttons
+ int16_t mButton;
+ int16_t mButtons;
+ // The value of WidgetInputEvent::modifiers
+ Modifiers mModifiers;
+ };
+
+ union {
+ // NOTIFY_IME_OF_SELECTION_CHANGE specific data
+ SelectionChangeDataBase mSelectionChangeData;
+
+ // NOTIFY_IME_OF_TEXT_CHANGE specific data
+ TextChangeDataBase mTextChangeData;
+
+ // NOTIFY_IME_OF_MOUSE_BUTTON_EVENT specific data
+ MouseButtonEventData mMouseButtonEventData;
+ };
+
+ void SetData(const SelectionChangeDataBase& aSelectionChangeData) {
+ MOZ_RELEASE_ASSERT(mMessage == NOTIFY_IME_OF_SELECTION_CHANGE);
+ mSelectionChangeData.Assign(aSelectionChangeData);
+ }
+
+ void SetData(const TextChangeDataBase& aTextChangeData) {
+ MOZ_RELEASE_ASSERT(mMessage == NOTIFY_IME_OF_TEXT_CHANGE);
+ mTextChangeData = aTextChangeData;
+ }
+};
+
+struct CandidateWindowPosition {
+ // Upper left corner of the candidate window if mExcludeRect is false.
+ // Otherwise, the position currently interested. E.g., caret position.
+ LayoutDeviceIntPoint mPoint;
+ // Rect which shouldn't be overlapped with the candidate window.
+ // This is valid only when mExcludeRect is true.
+ LayoutDeviceIntRect mRect;
+ // See explanation of mPoint and mRect.
+ bool mExcludeRect;
+};
+
+std::ostream& operator<<(std::ostream& aStream, const IMEEnabled& aEnabled);
+std::ostream& operator<<(std::ostream& aStream, const IMEState::Open& aOpen);
+std::ostream& operator<<(std::ostream& aStream, const IMEState& aState);
+std::ostream& operator<<(std::ostream& aStream,
+ const InputContext::Origin& aOrigin);
+std::ostream& operator<<(std::ostream& aStream, const InputContext& aContext);
+std::ostream& operator<<(std::ostream& aStream,
+ const InputContextAction::Cause& aCause);
+std::ostream& operator<<(std::ostream& aStream,
+ const InputContextAction::FocusChange& aFocusChange);
+std::ostream& operator<<(std::ostream& aStream,
+ const IMENotification::SelectionChangeDataBase& aData);
+std::ostream& operator<<(std::ostream& aStream,
+ const IMENotification::TextChangeDataBase& aData);
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef mozilla_widget_IMEData_h_
diff --git a/widget/IconLoader.cpp b/widget/IconLoader.cpp
new file mode 100644
index 0000000000..6da82caa70
--- /dev/null
+++ b/widget/IconLoader.cpp
@@ -0,0 +1,181 @@
+/* -*- 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/widget/IconLoader.h"
+#include "gfxPlatform.h"
+#include "imgIContainer.h"
+#include "imgLoader.h"
+#include "mozilla/dom/Document.h"
+#include "nsContentUtils.h"
+#include "nsIContent.h"
+
+using namespace mozilla;
+
+using mozilla::gfx::SourceSurface;
+
+namespace mozilla::widget {
+
+NS_IMPL_CYCLE_COLLECTION(mozilla::widget::IconLoader, mContent, mHelper)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(mozilla::widget::IconLoader)
+ NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::widget::IconLoader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::widget::IconLoader)
+
+IconLoader::IconLoader(Helper* aHelper, nsINode* aContent,
+ const nsIntRect& aImageRegionRect)
+ : mContent(aContent),
+ mContentType(nsIContentPolicy::TYPE_INTERNAL_IMAGE),
+ mImageRegionRect(aImageRegionRect),
+ mLoadedIcon(false),
+ mHelper(aHelper) {}
+
+IconLoader::~IconLoader() { Destroy(); }
+
+void IconLoader::Destroy() {
+ if (mIconRequest) {
+ mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ }
+ if (mHelper) {
+ mHelper = nullptr;
+ }
+}
+
+nsresult IconLoader::LoadIcon(nsIURI* aIconURI, bool aIsInternalIcon) {
+ 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;
+ }
+
+ RefPtr<mozilla::dom::Document> document = mContent->OwnerDoc();
+
+ nsCOMPtr<nsILoadGroup> loadGroup = document->GetDocumentLoadGroup();
+ if (!loadGroup) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<imgLoader> loader = nsContentUtils::GetImgLoaderForDocument(document);
+ if (!loader) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ if (aIsInternalIcon) {
+ rv = loader->LoadImage(
+ aIconURI, nullptr, nullptr, nullptr, 0, loadGroup, this, nullptr,
+ nullptr, nsIRequest::LOAD_NORMAL, nullptr, mContentType, u""_ns,
+ /* aUseUrgentStartForChannel */ false, /* aLinkPreload */ false,
+ getter_AddRefs(mIconRequest));
+ } else {
+ rv = loader->LoadImage(
+ aIconURI, nullptr, nullptr, mContent->NodePrincipal(), 0, loadGroup,
+ this, mContent, document, nsIRequest::LOAD_NORMAL, nullptr,
+ mContentType, u""_ns, /* aUseUrgentStartForChannel */ false,
+ /* aLinkPreload */ false, getter_AddRefs(mIconRequest));
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+//
+// imgINotificationObserver
+//
+
+void IconLoader::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;
+ }
+
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ MOZ_ASSERT(image);
+
+ // Ask the image to decode at its intrinsic size.
+ int32_t width = 0, height = 0;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+ image->RequestDecodeForSize(nsIntSize(width, height),
+ imgIContainer::FLAG_HIGH_QUALITY_SCALING);
+ }
+
+ if (aType == imgINotificationObserver::FRAME_COMPLETE) {
+ nsresult rv = OnFrameComplete(aRequest);
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ MOZ_ASSERT(image);
+
+ mHelper->OnComplete(image, mImageRegionRect);
+ return;
+ }
+
+ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+ if (mIconRequest && mIconRequest == aRequest) {
+ mIconRequest->Cancel(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ }
+ }
+}
+
+nsresult IconLoader::OnFrameComplete(imgIRequest* aRequest) {
+ if (aRequest != mIconRequest) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Only support one frame.
+ if (mLoadedIcon) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<imgIContainer> imageContainer;
+ aRequest->GetImage(getter_AddRefs(imageContainer));
+ if (!imageContainer) {
+ 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)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mImageRegionRect.IsEmpty()) {
+ mImageRegionRect.SetRect(0, 0, origWidth, origHeight);
+ }
+
+ mLoadedIcon = true;
+
+ return NS_OK;
+}
+
+} // namespace mozilla::widget
diff --git a/widget/IconLoader.h b/widget/IconLoader.h
new file mode 100644
index 0000000000..9d21478b2b
--- /dev/null
+++ b/widget/IconLoader.h
@@ -0,0 +1,81 @@
+/* -*- 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_IconLoader_h_
+#define mozilla_widget_IconLoader_h_
+
+#include "imgINotificationObserver.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIContentPolicy.h"
+#include "nsISupports.h"
+
+class nsIURI;
+class nsINode;
+class imgRequestProxy;
+class imgIContainer;
+
+namespace mozilla::widget {
+
+/**
+ * IconLoader is a utility for loading icons from either the disk or over
+ * the network, and then using them in native OS widgetry like the macOS
+ * global menu bar or the Windows notification area.
+ */
+
+class IconLoader : public imgINotificationObserver {
+ public:
+ /**
+ * IconLoader itself is cross-platform, but each platform needs to supply
+ * it with a Helper that does the platform-specific conversion work. The
+ * Helper needs to implement the OnComplete method in order to handle the
+ * imgIContainer of the loaded icon.
+ */
+ class Helper : public nsISupports {
+ public:
+ // Helper needs to implement nsISupports in order for its subclasses to
+ // participate in cycle collection
+ virtual nsresult OnComplete(imgIContainer* aContainer,
+ const nsIntRect& aRect) = 0;
+
+ protected:
+ virtual ~Helper() = default;
+ };
+
+ IconLoader(Helper* aHelper, nsINode* aContent,
+ const nsIntRect& aImageRegionRect);
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+ NS_DECL_CYCLE_COLLECTION_CLASS(IconLoader)
+
+ // LoadIcon will start a load request for the icon.
+ // The request may not complete until after LoadIcon returns.
+ // If aIsInternalIcon is true, the document and principal will not be
+ // used when loading.
+ nsresult LoadIcon(nsIURI* aIconURI, bool aIsInternalIcon = false);
+
+ void ReleaseJSObjects() { mContent = nullptr; }
+
+ void Destroy();
+
+ protected:
+ virtual ~IconLoader();
+
+ private:
+ nsresult OnFrameComplete(imgIRequest* aRequest);
+
+ nsCOMPtr<nsINode> mContent;
+ nsContentPolicyType mContentType;
+ RefPtr<imgRequestProxy> mIconRequest;
+ nsIntRect mImageRegionRect;
+ bool mLoadedIcon;
+ RefPtr<Helper> mHelper;
+};
+
+} // namespace mozilla::widget
+#endif // mozilla_widget_IconLoader_h_
diff --git a/widget/InProcessCompositorWidget.cpp b/widget/InProcessCompositorWidget.cpp
new file mode 100644
index 0000000000..2083825cec
--- /dev/null
+++ b/widget/InProcessCompositorWidget.cpp
@@ -0,0 +1,115 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "InProcessCompositorWidget.h"
+
+#include "mozilla/VsyncDispatcher.h"
+#include "nsBaseWidget.h"
+
+#if defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING)
+# include "mozilla/widget/AndroidCompositorWidget.h"
+#endif
+
+namespace mozilla {
+namespace widget {
+
+// Platforms with no OOP compositor process support use
+// InProcessCompositorWidget by default.
+#if !defined(MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING)
+/* static */
+RefPtr<CompositorWidget> CompositorWidget::CreateLocal(
+ const CompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions, nsIWidget* aWidget) {
+ MOZ_ASSERT(aWidget);
+# ifdef MOZ_WIDGET_ANDROID
+ return new AndroidCompositorWidget(aOptions,
+ static_cast<nsBaseWidget*>(aWidget));
+# else
+ return new InProcessCompositorWidget(aOptions,
+ static_cast<nsBaseWidget*>(aWidget));
+# endif
+}
+#endif
+
+InProcessCompositorWidget::InProcessCompositorWidget(
+ const layers::CompositorOptions& aOptions, nsBaseWidget* aWidget)
+ : CompositorWidget(aOptions), mWidget(aWidget) {
+ // The only method of construction that is used outside of unit tests is
+ // ::CreateLocal, above. That method of construction asserts that mWidget
+ // is not assigned a NULL value. And yet mWidget is NULL in some crash
+ // reports that involve other class methods. Adding a release assert here
+ // will give us the earliest possible notification that we're headed for
+ // a crash.
+ MOZ_RELEASE_ASSERT(mWidget);
+}
+
+bool InProcessCompositorWidget::PreRender(WidgetRenderingContext* aContext) {
+ return mWidget->PreRender(aContext);
+}
+
+void InProcessCompositorWidget::PostRender(WidgetRenderingContext* aContext) {
+ mWidget->PostRender(aContext);
+}
+
+RefPtr<layers::NativeLayerRoot>
+InProcessCompositorWidget::GetNativeLayerRoot() {
+ return mWidget->GetNativeLayerRoot();
+}
+
+already_AddRefed<gfx::DrawTarget>
+InProcessCompositorWidget::StartRemoteDrawing() {
+ return mWidget->StartRemoteDrawing();
+}
+
+already_AddRefed<gfx::DrawTarget>
+InProcessCompositorWidget::StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion, layers::BufferMode* aBufferMode) {
+ return mWidget->StartRemoteDrawingInRegion(aInvalidRegion, aBufferMode);
+}
+
+void InProcessCompositorWidget::EndRemoteDrawing() {
+ mWidget->EndRemoteDrawing();
+}
+
+void InProcessCompositorWidget::EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
+ mWidget->EndRemoteDrawingInRegion(aDrawTarget, aInvalidRegion);
+}
+
+void InProcessCompositorWidget::CleanupRemoteDrawing() {
+ mWidget->CleanupRemoteDrawing();
+}
+
+void InProcessCompositorWidget::CleanupWindowEffects() {
+ mWidget->CleanupWindowEffects();
+}
+
+bool InProcessCompositorWidget::InitCompositor(
+ layers::Compositor* aCompositor) {
+ return mWidget->InitCompositor(aCompositor);
+}
+
+LayoutDeviceIntSize InProcessCompositorWidget::GetClientSize() {
+ return mWidget->GetClientSize();
+}
+
+uint32_t InProcessCompositorWidget::GetGLFrameBufferFormat() {
+ return mWidget->GetGLFrameBufferFormat();
+}
+
+uintptr_t InProcessCompositorWidget::GetWidgetKey() {
+ return reinterpret_cast<uintptr_t>(mWidget);
+}
+
+nsIWidget* InProcessCompositorWidget::RealWidget() { return mWidget; }
+
+void InProcessCompositorWidget::ObserveVsync(VsyncObserver* aObserver) {
+ if (RefPtr<CompositorVsyncDispatcher> cvd =
+ mWidget->GetCompositorVsyncDispatcher()) {
+ cvd->SetCompositorVsyncObserver(aObserver);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/InProcessCompositorWidget.h b/widget/InProcessCompositorWidget.h
new file mode 100644
index 0000000000..d40db7bed5
--- /dev/null
+++ b/widget/InProcessCompositorWidget.h
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_InProcessCompositorWidget_h__
+#define mozilla_widget_InProcessCompositorWidget_h__
+
+#include "CompositorWidget.h"
+
+namespace mozilla {
+namespace widget {
+
+// This version of CompositorWidget implements a wrapper around
+// nsBaseWidget.
+class InProcessCompositorWidget : public CompositorWidget {
+ public:
+ explicit InProcessCompositorWidget(const layers::CompositorOptions& aOptions,
+ nsBaseWidget* aWidget);
+
+ virtual bool PreRender(WidgetRenderingContext* aManager) override;
+ virtual void PostRender(WidgetRenderingContext* aManager) override;
+ virtual RefPtr<layers::NativeLayerRoot> GetNativeLayerRoot() override;
+ virtual already_AddRefed<gfx::DrawTarget> StartRemoteDrawing() override;
+ virtual already_AddRefed<gfx::DrawTarget> StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode) override;
+ virtual void EndRemoteDrawing() override;
+ virtual void EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget,
+ const LayoutDeviceIntRegion& aInvalidRegion) override;
+ virtual void CleanupRemoteDrawing() override;
+ virtual void CleanupWindowEffects() override;
+ virtual bool InitCompositor(layers::Compositor* aCompositor) override;
+ virtual LayoutDeviceIntSize GetClientSize() override;
+ virtual uint32_t GetGLFrameBufferFormat() override;
+ virtual void ObserveVsync(VsyncObserver* aObserver) override;
+ virtual uintptr_t GetWidgetKey() override;
+
+ // If you can override this method, inherit from CompositorWidget instead.
+ nsIWidget* RealWidget() override;
+
+ protected:
+ nsBaseWidget* mWidget;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/InputData.cpp b/widget/InputData.cpp
new file mode 100644
index 0000000000..ab966d6765
--- /dev/null
+++ b/widget/InputData.cpp
@@ -0,0 +1,855 @@
+/* -*- 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 "InputData.h"
+
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/dom/WheelEventBinding.h"
+#include "mozilla/TextEvents.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsThreadUtils.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "UnitTransforms.h"
+
+namespace mozilla {
+
+using namespace dom;
+
+InputData::~InputData() = default;
+
+InputData::InputData(InputType aInputType)
+ : mInputType(aInputType),
+ mTime(0),
+ mFocusSequenceNumber(0),
+ mLayersId{0},
+ modifiers(0) {}
+
+InputData::InputData(InputType aInputType, uint32_t aTime, TimeStamp aTimeStamp,
+ Modifiers aModifiers)
+ : mInputType(aInputType),
+ mTime(aTime),
+ mTimeStamp(aTimeStamp),
+ mFocusSequenceNumber(0),
+ mLayersId{0},
+ modifiers(aModifiers) {}
+
+SingleTouchData::SingleTouchData(int32_t aIdentifier,
+ ScreenIntPoint aScreenPoint,
+ ScreenSize aRadius, float aRotationAngle,
+ float aForce)
+ : mIdentifier(aIdentifier),
+ mScreenPoint(aScreenPoint),
+ mRadius(aRadius),
+ mRotationAngle(aRotationAngle),
+ mForce(aForce) {}
+
+SingleTouchData::SingleTouchData(int32_t aIdentifier,
+ ParentLayerPoint aLocalScreenPoint,
+ ScreenSize aRadius, float aRotationAngle,
+ float aForce)
+ : mIdentifier(aIdentifier),
+ mLocalScreenPoint(aLocalScreenPoint),
+ mRadius(aRadius),
+ mRotationAngle(aRotationAngle),
+ mForce(aForce) {}
+
+SingleTouchData::SingleTouchData()
+ : mIdentifier(0), mRotationAngle(0.0), mForce(0.0) {}
+
+already_AddRefed<Touch> SingleTouchData::ToNewDOMTouch() const {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can only create dom::Touch instances on main thread");
+ RefPtr<Touch> touch =
+ new Touch(mIdentifier,
+ LayoutDeviceIntPoint::Truncate(mScreenPoint.x, mScreenPoint.y),
+ LayoutDeviceIntPoint::Truncate(mRadius.width, mRadius.height),
+ mRotationAngle, mForce);
+ return touch.forget();
+}
+
+MultiTouchInput::MultiTouchInput(MultiTouchType aType, uint32_t aTime,
+ TimeStamp aTimeStamp, Modifiers aModifiers)
+ : InputData(MULTITOUCH_INPUT, aTime, aTimeStamp, aModifiers),
+ mType(aType),
+ mHandledByAPZ(false) {}
+
+MultiTouchInput::MultiTouchInput()
+ : InputData(MULTITOUCH_INPUT),
+ mType(MULTITOUCH_START),
+ mHandledByAPZ(false) {}
+
+MultiTouchInput::MultiTouchInput(const MultiTouchInput& aOther)
+ : InputData(MULTITOUCH_INPUT, aOther.mTime, aOther.mTimeStamp,
+ aOther.modifiers),
+ mType(aOther.mType),
+ mScreenOffset(aOther.mScreenOffset),
+ mHandledByAPZ(aOther.mHandledByAPZ) {
+ mTouches.AppendElements(aOther.mTouches);
+}
+
+MultiTouchInput::MultiTouchInput(const WidgetTouchEvent& aTouchEvent)
+ : InputData(MULTITOUCH_INPUT, aTouchEvent.mTime, aTouchEvent.mTimeStamp,
+ aTouchEvent.mModifiers),
+ mHandledByAPZ(aTouchEvent.mFlags.mHandledByAPZ) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can only copy from WidgetTouchEvent on main thread");
+
+ switch (aTouchEvent.mMessage) {
+ case eTouchStart:
+ mType = MULTITOUCH_START;
+ break;
+ case eTouchMove:
+ mType = MULTITOUCH_MOVE;
+ break;
+ case eTouchEnd:
+ mType = MULTITOUCH_END;
+ break;
+ case eTouchCancel:
+ mType = MULTITOUCH_CANCEL;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Did not assign a type to a MultiTouchInput");
+ break;
+ }
+
+ mScreenOffset = ViewAs<ExternalPixel>(
+ aTouchEvent.mWidget->WidgetToScreenOffset(),
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+
+ for (size_t i = 0; i < aTouchEvent.mTouches.Length(); i++) {
+ const Touch* domTouch = aTouchEvent.mTouches[i];
+
+ // Extract data from weird interfaces.
+ int32_t identifier = domTouch->Identifier();
+ int32_t radiusX = domTouch->RadiusX(CallerType::System);
+ int32_t radiusY = domTouch->RadiusY(CallerType::System);
+ float rotationAngle = domTouch->RotationAngle(CallerType::System);
+ float force = domTouch->Force(CallerType::System);
+
+ SingleTouchData data(
+ identifier,
+ ViewAs<ScreenPixel>(
+ domTouch->mRefPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent),
+ ScreenSize(radiusX, radiusY), rotationAngle, force);
+
+ mTouches.AppendElement(data);
+ }
+}
+
+void MultiTouchInput::Translate(const ScreenPoint& aTranslation) {
+ const int32_t xTranslation = (int32_t)(aTranslation.x + 0.5f);
+ const int32_t yTranslation = (int32_t)(aTranslation.y + 0.5f);
+
+ for (auto& touchData : mTouches) {
+ for (auto& historicalData : touchData.mHistoricalData) {
+ historicalData.mScreenPoint.MoveBy(xTranslation, yTranslation);
+ }
+ touchData.mScreenPoint.MoveBy(xTranslation, yTranslation);
+ }
+}
+
+WidgetTouchEvent MultiTouchInput::ToWidgetTouchEvent(nsIWidget* aWidget) const {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can only convert To WidgetTouchEvent on main thread");
+
+ EventMessage touchEventMessage = eVoidEvent;
+ switch (mType) {
+ case MULTITOUCH_START:
+ touchEventMessage = eTouchStart;
+ break;
+ case MULTITOUCH_MOVE:
+ touchEventMessage = eTouchMove;
+ break;
+ case MULTITOUCH_END:
+ touchEventMessage = eTouchEnd;
+ break;
+ case MULTITOUCH_CANCEL:
+ touchEventMessage = eTouchCancel;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "Did not assign a type to WidgetTouchEvent in MultiTouchInput");
+ break;
+ }
+
+ WidgetTouchEvent event(true, touchEventMessage, aWidget);
+ if (touchEventMessage == eVoidEvent) {
+ return event;
+ }
+
+ event.mModifiers = this->modifiers;
+ event.mTime = this->mTime;
+ event.mTimeStamp = this->mTimeStamp;
+ event.mFlags.mHandledByAPZ = mHandledByAPZ;
+ event.mFocusSequenceNumber = mFocusSequenceNumber;
+ event.mLayersId = mLayersId;
+
+ for (size_t i = 0; i < mTouches.Length(); i++) {
+ *event.mTouches.AppendElement() = mTouches[i].ToNewDOMTouch();
+ }
+
+ return event;
+}
+
+int32_t MultiTouchInput::IndexOfTouch(int32_t aTouchIdentifier) {
+ for (size_t i = 0; i < mTouches.Length(); i++) {
+ if (mTouches[i].mIdentifier == aTouchIdentifier) {
+ return (int32_t)i;
+ }
+ }
+ return -1;
+}
+
+bool MultiTouchInput::TransformToLocal(
+ const ScreenToParentLayerMatrix4x4& aTransform) {
+ for (auto& touchData : mTouches) {
+ for (auto& historicalData : touchData.mHistoricalData) {
+ Maybe<ParentLayerIntPoint> historicalPoint =
+ UntransformBy(aTransform, historicalData.mScreenPoint);
+ if (!historicalPoint) {
+ return false;
+ }
+ historicalData.mLocalScreenPoint = *historicalPoint;
+ }
+ Maybe<ParentLayerIntPoint> point =
+ UntransformBy(aTransform, touchData.mScreenPoint);
+ if (!point) {
+ return false;
+ }
+ touchData.mLocalScreenPoint = *point;
+ }
+ return true;
+}
+
+MouseInput::MouseInput()
+ : InputData(MOUSE_INPUT),
+ mType(MOUSE_NONE),
+ mButtonType(NONE),
+ mInputSource(0),
+ mButtons(0),
+ mHandledByAPZ(false) {}
+
+MouseInput::MouseInput(MouseType aType, ButtonType aButtonType,
+ uint16_t aInputSource, int16_t aButtons,
+ const ScreenPoint& aPoint, uint32_t aTime,
+ TimeStamp aTimeStamp, Modifiers aModifiers)
+ : InputData(MOUSE_INPUT, aTime, aTimeStamp, aModifiers),
+ mType(aType),
+ mButtonType(aButtonType),
+ mInputSource(aInputSource),
+ mButtons(aButtons),
+ mOrigin(aPoint),
+ mHandledByAPZ(false) {}
+
+MouseInput::MouseInput(const WidgetMouseEventBase& aMouseEvent)
+ : InputData(MOUSE_INPUT, aMouseEvent.mTime, aMouseEvent.mTimeStamp,
+ aMouseEvent.mModifiers),
+ mType(MOUSE_NONE),
+ mButtonType(NONE),
+ mInputSource(aMouseEvent.mInputSource),
+ mButtons(aMouseEvent.mButtons),
+ mHandledByAPZ(aMouseEvent.mFlags.mHandledByAPZ) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can only copy from WidgetTouchEvent on main thread");
+
+ mButtonType = NONE;
+
+ switch (aMouseEvent.mButton) {
+ case MouseButton::ePrimary:
+ mButtonType = MouseInput::PRIMARY_BUTTON;
+ break;
+ case MouseButton::eMiddle:
+ mButtonType = MouseInput::MIDDLE_BUTTON;
+ break;
+ case MouseButton::eSecondary:
+ mButtonType = MouseInput::SECONDARY_BUTTON;
+ break;
+ }
+
+ switch (aMouseEvent.mMessage) {
+ case eMouseMove:
+ mType = MOUSE_MOVE;
+ break;
+ case eMouseUp:
+ mType = MOUSE_UP;
+ break;
+ case eMouseDown:
+ mType = MOUSE_DOWN;
+ break;
+ case eDragStart:
+ mType = MOUSE_DRAG_START;
+ break;
+ case eDragEnd:
+ mType = MOUSE_DRAG_END;
+ break;
+ case eMouseEnterIntoWidget:
+ mType = MOUSE_WIDGET_ENTER;
+ break;
+ case eMouseExitFromWidget:
+ mType = MOUSE_WIDGET_EXIT;
+ break;
+ case eMouseHitTest:
+ mType = MOUSE_HITTEST;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Mouse event type not supported");
+ break;
+ }
+
+ mOrigin = ScreenPoint(ViewAs<ScreenPixel>(
+ aMouseEvent.mRefPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
+}
+
+bool MouseInput::IsLeftButton() const { return mButtonType == PRIMARY_BUTTON; }
+
+bool MouseInput::TransformToLocal(
+ const ScreenToParentLayerMatrix4x4& aTransform) {
+ Maybe<ParentLayerPoint> point = UntransformBy(aTransform, mOrigin);
+ if (!point) {
+ return false;
+ }
+ mLocalOrigin = *point;
+
+ return true;
+}
+
+WidgetMouseEvent MouseInput::ToWidgetEvent(nsIWidget* aWidget) const {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can only convert To WidgetTouchEvent on main thread");
+
+ EventMessage msg = eVoidEvent;
+ uint32_t clickCount = 0;
+ Maybe<WidgetMouseEvent::ExitFrom> exitFrom;
+ switch (mType) {
+ case MOUSE_MOVE:
+ msg = eMouseMove;
+ break;
+ case MOUSE_UP:
+ msg = eMouseUp;
+ clickCount = 1;
+ break;
+ case MOUSE_DOWN:
+ msg = eMouseDown;
+ clickCount = 1;
+ break;
+ case MOUSE_DRAG_START:
+ msg = eDragStart;
+ break;
+ case MOUSE_DRAG_END:
+ msg = eDragEnd;
+ break;
+ case MOUSE_WIDGET_ENTER:
+ msg = eMouseEnterIntoWidget;
+ break;
+ case MOUSE_WIDGET_EXIT:
+ msg = eMouseExitFromWidget;
+ exitFrom = Some(WidgetMouseEvent::ePlatformChild);
+ break;
+ case MOUSE_HITTEST:
+ msg = eMouseHitTest;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "Did not assign a type to WidgetMouseEvent in MouseInput");
+ break;
+ }
+
+ WidgetMouseEvent event(true, msg, aWidget, WidgetMouseEvent::eReal,
+ WidgetMouseEvent::eNormal);
+
+ if (msg == eVoidEvent) {
+ return event;
+ }
+
+ switch (mButtonType) {
+ case MouseInput::PRIMARY_BUTTON:
+ event.mButton = MouseButton::ePrimary;
+ break;
+ case MouseInput::MIDDLE_BUTTON:
+ event.mButton = MouseButton::eMiddle;
+ break;
+ case MouseInput::SECONDARY_BUTTON:
+ event.mButton = MouseButton::eSecondary;
+ break;
+ case MouseInput::NONE:
+ default:
+ break;
+ }
+
+ event.mButtons = mButtons;
+ event.mModifiers = modifiers;
+ event.mTime = mTime;
+ event.mTimeStamp = mTimeStamp;
+ event.mLayersId = mLayersId;
+ event.mFlags.mHandledByAPZ = mHandledByAPZ;
+ event.mRefPoint = RoundedToInt(ViewAs<LayoutDevicePixel>(
+ mOrigin,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
+ event.mClickCount = clickCount;
+ event.mInputSource = mInputSource;
+ event.mFocusSequenceNumber = mFocusSequenceNumber;
+ event.mExitFrom = exitFrom;
+
+ return event;
+}
+
+PanGestureInput::PanGestureInput()
+ : InputData(PANGESTURE_INPUT),
+ mType(PANGESTURE_MAYSTART),
+ mLineOrPageDeltaX(0),
+ mLineOrPageDeltaY(0),
+ mUserDeltaMultiplierX(1.0),
+ mUserDeltaMultiplierY(1.0),
+ mHandledByAPZ(false),
+ mFollowedByMomentum(false),
+ mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection(false),
+ mOverscrollBehaviorAllowsSwipe(false),
+ mSimulateMomentum(false) {}
+
+PanGestureInput::PanGestureInput(PanGestureType aType, uint32_t aTime,
+ TimeStamp aTimeStamp,
+ const ScreenPoint& aPanStartPoint,
+ const ScreenPoint& aPanDisplacement,
+ Modifiers aModifiers)
+ : InputData(PANGESTURE_INPUT, aTime, aTimeStamp, aModifiers),
+ mType(aType),
+ mPanStartPoint(aPanStartPoint),
+ mPanDisplacement(aPanDisplacement),
+ mLineOrPageDeltaX(0),
+ mLineOrPageDeltaY(0),
+ mUserDeltaMultiplierX(1.0),
+ mUserDeltaMultiplierY(1.0),
+ mHandledByAPZ(false),
+ mFollowedByMomentum(false),
+ mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection(false),
+ mOverscrollBehaviorAllowsSwipe(false),
+ mSimulateMomentum(false) {}
+
+bool PanGestureInput::IsMomentum() const {
+ switch (mType) {
+ case PanGestureInput::PANGESTURE_MOMENTUMSTART:
+ case PanGestureInput::PANGESTURE_MOMENTUMPAN:
+ case PanGestureInput::PANGESTURE_MOMENTUMEND:
+ return true;
+ default:
+ return false;
+ }
+}
+
+WidgetWheelEvent PanGestureInput::ToWidgetEvent(nsIWidget* aWidget) const {
+ WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
+ wheelEvent.mModifiers = this->modifiers;
+ wheelEvent.mTime = mTime;
+ wheelEvent.mTimeStamp = mTimeStamp;
+ wheelEvent.mLayersId = mLayersId;
+ wheelEvent.mRefPoint = RoundedToInt(ViewAs<LayoutDevicePixel>(
+ mPanStartPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
+ wheelEvent.mButtons = 0;
+ wheelEvent.mMayHaveMomentum = true; // pan inputs may have momentum
+ wheelEvent.mIsMomentum = IsMomentum();
+ wheelEvent.mLineOrPageDeltaX = mLineOrPageDeltaX;
+ wheelEvent.mLineOrPageDeltaY = mLineOrPageDeltaY;
+ wheelEvent.mDeltaX = mPanDisplacement.x;
+ wheelEvent.mDeltaY = mPanDisplacement.y;
+ wheelEvent.mFlags.mHandledByAPZ = mHandledByAPZ;
+ wheelEvent.mFocusSequenceNumber = mFocusSequenceNumber;
+ if (mDeltaType == PanGestureInput::PANDELTA_PAGE) {
+ // Emulate legacy widget/gtk behavior
+ wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_LINE;
+ wheelEvent.mIsNoLineOrPageDelta = true;
+ wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY;
+ wheelEvent.mDeltaX *= 3;
+ wheelEvent.mDeltaY *= 3;
+ } else {
+ wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_PIXEL;
+ }
+ return wheelEvent;
+}
+
+bool PanGestureInput::TransformToLocal(
+ const ScreenToParentLayerMatrix4x4& aTransform) {
+ Maybe<ParentLayerPoint> panStartPoint =
+ UntransformBy(aTransform, mPanStartPoint);
+ if (!panStartPoint) {
+ return false;
+ }
+ mLocalPanStartPoint = *panStartPoint;
+
+ if (mDeltaType == PanGestureInput::PANDELTA_PAGE) {
+ // Skip transforming the pan displacement because we want
+ // raw page proportion counts.
+ mLocalPanDisplacement.x = mPanDisplacement.x;
+ mLocalPanDisplacement.y = mPanDisplacement.y;
+ return true;
+ }
+
+ Maybe<ParentLayerPoint> panDisplacement =
+ UntransformVector(aTransform, mPanDisplacement, mPanStartPoint);
+ if (!panDisplacement) {
+ return false;
+ }
+ mLocalPanDisplacement = *panDisplacement;
+ return true;
+}
+
+ScreenPoint PanGestureInput::UserMultipliedPanDisplacement() const {
+ return ScreenPoint(mPanDisplacement.x * mUserDeltaMultiplierX,
+ mPanDisplacement.y * mUserDeltaMultiplierY);
+}
+
+ParentLayerPoint PanGestureInput::UserMultipliedLocalPanDisplacement() const {
+ return ParentLayerPoint(mLocalPanDisplacement.x * mUserDeltaMultiplierX,
+ mLocalPanDisplacement.y * mUserDeltaMultiplierY);
+}
+
+static int32_t TakeLargestInt(gfx::Float* aFloat) {
+ int32_t result(*aFloat); // truncate towards zero
+ *aFloat -= result;
+ return result;
+}
+
+/* static */ gfx::IntPoint PanGestureInput::GetIntegerDeltaForEvent(
+ bool aIsStart, float x, float y) {
+ static gfx::Point sAccumulator(0.0f, 0.0f);
+ if (aIsStart) {
+ sAccumulator = gfx::Point(0.0f, 0.0f);
+ }
+ sAccumulator.x += x;
+ sAccumulator.y += y;
+ return gfx::IntPoint(TakeLargestInt(&sAccumulator.x),
+ TakeLargestInt(&sAccumulator.y));
+}
+
+PinchGestureInput::PinchGestureInput()
+ : InputData(PINCHGESTURE_INPUT),
+ mType(PINCHGESTURE_START),
+ mSource(UNKNOWN),
+ mHandledByAPZ(false) {}
+
+PinchGestureInput::PinchGestureInput(
+ PinchGestureType aType, PinchGestureSource aSource, uint32_t aTime,
+ TimeStamp aTimeStamp, const ExternalPoint& aScreenOffset,
+ const ScreenPoint& aFocusPoint, ScreenCoord aCurrentSpan,
+ ScreenCoord aPreviousSpan, Modifiers aModifiers)
+ : InputData(PINCHGESTURE_INPUT, aTime, aTimeStamp, aModifiers),
+ mType(aType),
+ mSource(aSource),
+ mFocusPoint(aFocusPoint),
+ mScreenOffset(aScreenOffset),
+ mCurrentSpan(aCurrentSpan),
+ mPreviousSpan(aPreviousSpan),
+ mLineOrPageDeltaY(0),
+ mHandledByAPZ(false) {}
+
+bool PinchGestureInput::TransformToLocal(
+ const ScreenToParentLayerMatrix4x4& aTransform) {
+ Maybe<ParentLayerPoint> point = UntransformBy(aTransform, mFocusPoint);
+ if (!point) {
+ return false;
+ }
+ mLocalFocusPoint = *point;
+ return true;
+}
+
+WidgetWheelEvent PinchGestureInput::ToWidgetEvent(nsIWidget* aWidget) const {
+ WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
+ wheelEvent.mModifiers = this->modifiers | MODIFIER_CONTROL;
+ wheelEvent.mTime = mTime;
+ wheelEvent.mTimeStamp = mTimeStamp;
+ wheelEvent.mLayersId = mLayersId;
+ wheelEvent.mRefPoint = RoundedToInt(ViewAs<LayoutDevicePixel>(
+ mFocusPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
+ wheelEvent.mButtons = 0;
+ wheelEvent.mFlags.mHandledByAPZ = mHandledByAPZ;
+ wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_PIXEL;
+
+ wheelEvent.mDeltaY = ComputeDeltaY(aWidget);
+
+ wheelEvent.mLineOrPageDeltaY = mLineOrPageDeltaY;
+
+ MOZ_ASSERT(mType == PINCHGESTURE_END || wheelEvent.mDeltaY != 0.0);
+
+ return wheelEvent;
+}
+
+double PinchGestureInput::ComputeDeltaY(nsIWidget* aWidget) const {
+#if defined(OS_MACOSX)
+ // This converts the pinch gesture value to a fake wheel event that has the
+ // control key pressed so that pages can implement custom pinch gesture
+ // handling. It may seem strange that this doesn't use a wheel event with
+ // the deltaZ property set, but this matches Chrome's behavior as described
+ // at https://code.google.com/p/chromium/issues/detail?id=289887
+ //
+ // The intent of the formula below is to produce numbers similar to Chrome's
+ // implementation of this feature. Chrome implements deltaY using the formula
+ // "-100 * log(1 + [event magnification])" which is unfortunately incorrect.
+ // All deltas for a single pinch gesture should sum to 0 if the start and end
+ // of a pinch gesture end up in the same place. This doesn't happen in Chrome
+ // because they followed Apple's misleading documentation, which implies that
+ // "1 + [event magnification]" is the scale factor. The scale factor is
+ // instead "pow(ratio, [event magnification])" so "[event magnification]" is
+ // already in log space.
+ //
+ // The multiplication by the backing scale factor below counteracts the
+ // division by the backing scale factor in WheelEvent.
+
+ // We want to set deltaY to |-100.0 * M * GetDefaultScaleInternal()| where M
+ // is [event magnification] but [event magnification] is only available in the
+ // macOS widget code so we have to reverse engineer from mCurrentSpan and
+ // mPreviousSpan (which are derived from [event magnification]) to get it.
+ // Specifically, we know |mCurrentSpan == 100.0| and |mPreviousSpan == 100.0 *
+ // (1.0 - M)|. We can calculate deltaY by solving the mPreviousSpan equation
+ // for M in terms of mPreviousSpan and plugging that into to the formula for
+ // deltaY.
+ return (mPreviousSpan - 100.0) *
+ (aWidget ? aWidget->GetDefaultScaleInternal() : 1.f);
+#else
+ // This calculation is based on what the Windows widget code does.
+ // Specifically, it creates a PinchGestureInput with |mCurrentSpan == 100.0 *
+ // currentScale| and |mPreviousSpan == 100.0 * lastScale| where currentScale
+ // is the scale from the current OS event and lastScale is the scale when the
+ // previous OS event happened. On macOS [event magnification] is a relative
+ // change in scale factor, ie if the scale factor changed from 1 to 1.1 it
+ // will be 0.1, similarly if it changed from 1 to 0.9 it will be -0.1. To
+ // calculate the relative scale change on Windows we would calculate |M =
+ // currentScale - lastScale = (mCurrentSpan-mPreviousSpan)/100| and use the
+ // same formula as the macOS code
+ // (|-100.0 * M * GetDefaultScaleInternal()|).
+
+ // XXX When we write the code for other platforms to do the same we'll need to
+ // make sure this calculation is reasonable.
+
+ return (mPreviousSpan - mCurrentSpan) *
+ (aWidget ? aWidget->GetDefaultScaleInternal() : 1.f);
+#endif
+}
+
+/* static */ gfx::IntPoint PinchGestureInput::GetIntegerDeltaForEvent(
+ bool aIsStart, float x, float y) {
+ static gfx::Point sAccumulator(0.0f, 0.0f);
+ if (aIsStart) {
+ sAccumulator = gfx::Point(0.0f, 0.0f);
+ }
+ sAccumulator.x += x;
+ sAccumulator.y += y;
+ return gfx::IntPoint(TakeLargestInt(&sAccumulator.x),
+ TakeLargestInt(&sAccumulator.y));
+}
+
+TapGestureInput::TapGestureInput()
+ : InputData(TAPGESTURE_INPUT), mType(TAPGESTURE_LONG) {}
+
+TapGestureInput::TapGestureInput(TapGestureType aType, uint32_t aTime,
+ TimeStamp aTimeStamp,
+ const ScreenIntPoint& aPoint,
+ Modifiers aModifiers)
+ : InputData(TAPGESTURE_INPUT, aTime, aTimeStamp, aModifiers),
+ mType(aType),
+ mPoint(aPoint) {}
+
+TapGestureInput::TapGestureInput(TapGestureType aType, uint32_t aTime,
+ TimeStamp aTimeStamp,
+ const ParentLayerPoint& aLocalPoint,
+ Modifiers aModifiers)
+ : InputData(TAPGESTURE_INPUT, aTime, aTimeStamp, aModifiers),
+ mType(aType),
+ mLocalPoint(aLocalPoint) {}
+
+bool TapGestureInput::TransformToLocal(
+ const ScreenToParentLayerMatrix4x4& aTransform) {
+ Maybe<ParentLayerIntPoint> point = UntransformBy(aTransform, mPoint);
+ if (!point) {
+ return false;
+ }
+ mLocalPoint = *point;
+ return true;
+}
+
+ScrollWheelInput::ScrollWheelInput()
+ : InputData(SCROLLWHEEL_INPUT),
+ mDeltaType(SCROLLDELTA_LINE),
+ mScrollMode(SCROLLMODE_INSTANT),
+ mHandledByAPZ(false),
+ mDeltaX(0.0),
+ mDeltaY(0.0),
+ mLineOrPageDeltaX(0),
+ mLineOrPageDeltaY(0),
+ mScrollSeriesNumber(0),
+ mUserDeltaMultiplierX(1.0),
+ mUserDeltaMultiplierY(1.0),
+ mMayHaveMomentum(false),
+ mIsMomentum(false),
+ mAPZAction(APZWheelAction::Scroll) {}
+
+ScrollWheelInput::ScrollWheelInput(
+ uint32_t aTime, TimeStamp aTimeStamp, Modifiers aModifiers,
+ ScrollMode aScrollMode, ScrollDeltaType aDeltaType,
+ const ScreenPoint& aOrigin, double aDeltaX, double aDeltaY,
+ bool aAllowToOverrideSystemScrollSpeed,
+ WheelDeltaAdjustmentStrategy aWheelDeltaAdjustmentStrategy)
+ : InputData(SCROLLWHEEL_INPUT, aTime, aTimeStamp, aModifiers),
+ mDeltaType(aDeltaType),
+ mScrollMode(aScrollMode),
+ mOrigin(aOrigin),
+ mHandledByAPZ(false),
+ mDeltaX(aDeltaX),
+ mDeltaY(aDeltaY),
+ mLineOrPageDeltaX(0),
+ mLineOrPageDeltaY(0),
+ mScrollSeriesNumber(0),
+ mUserDeltaMultiplierX(1.0),
+ mUserDeltaMultiplierY(1.0),
+ mMayHaveMomentum(false),
+ mIsMomentum(false),
+ mAllowToOverrideSystemScrollSpeed(aAllowToOverrideSystemScrollSpeed),
+ mWheelDeltaAdjustmentStrategy(aWheelDeltaAdjustmentStrategy),
+ mAPZAction(APZWheelAction::Scroll) {}
+
+ScrollWheelInput::ScrollWheelInput(const WidgetWheelEvent& aWheelEvent)
+ : InputData(SCROLLWHEEL_INPUT, aWheelEvent.mTime, aWheelEvent.mTimeStamp,
+ aWheelEvent.mModifiers),
+ mDeltaType(DeltaTypeForDeltaMode(aWheelEvent.mDeltaMode)),
+ mScrollMode(SCROLLMODE_INSTANT),
+ mHandledByAPZ(aWheelEvent.mFlags.mHandledByAPZ),
+ mDeltaX(aWheelEvent.mDeltaX),
+ mDeltaY(aWheelEvent.mDeltaY),
+ mLineOrPageDeltaX(aWheelEvent.mLineOrPageDeltaX),
+ mLineOrPageDeltaY(aWheelEvent.mLineOrPageDeltaY),
+ mScrollSeriesNumber(0),
+ mUserDeltaMultiplierX(1.0),
+ mUserDeltaMultiplierY(1.0),
+ mMayHaveMomentum(aWheelEvent.mMayHaveMomentum),
+ mIsMomentum(aWheelEvent.mIsMomentum),
+ mAllowToOverrideSystemScrollSpeed(
+ aWheelEvent.mAllowToOverrideSystemScrollSpeed),
+ mWheelDeltaAdjustmentStrategy(WheelDeltaAdjustmentStrategy::eNone),
+ mAPZAction(APZWheelAction::Scroll) {
+ mOrigin = ScreenPoint(ViewAs<ScreenPixel>(
+ aWheelEvent.mRefPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
+}
+
+ScrollWheelInput::ScrollDeltaType ScrollWheelInput::DeltaTypeForDeltaMode(
+ uint32_t aDeltaMode) {
+ switch (aDeltaMode) {
+ case WheelEvent_Binding::DOM_DELTA_LINE:
+ return SCROLLDELTA_LINE;
+ case WheelEvent_Binding::DOM_DELTA_PAGE:
+ return SCROLLDELTA_PAGE;
+ case WheelEvent_Binding::DOM_DELTA_PIXEL:
+ return SCROLLDELTA_PIXEL;
+ default:
+ MOZ_CRASH();
+ }
+ return SCROLLDELTA_LINE;
+}
+
+uint32_t ScrollWheelInput::DeltaModeForDeltaType(ScrollDeltaType aDeltaType) {
+ switch (aDeltaType) {
+ case ScrollWheelInput::SCROLLDELTA_LINE:
+ return WheelEvent_Binding::DOM_DELTA_LINE;
+ case ScrollWheelInput::SCROLLDELTA_PAGE:
+ return WheelEvent_Binding::DOM_DELTA_PAGE;
+ case ScrollWheelInput::SCROLLDELTA_PIXEL:
+ default:
+ return WheelEvent_Binding::DOM_DELTA_PIXEL;
+ }
+}
+
+ScrollUnit ScrollWheelInput::ScrollUnitForDeltaType(
+ ScrollDeltaType aDeltaType) {
+ switch (aDeltaType) {
+ case SCROLLDELTA_LINE:
+ return ScrollUnit::LINES;
+ case SCROLLDELTA_PAGE:
+ return ScrollUnit::PAGES;
+ case SCROLLDELTA_PIXEL:
+ return ScrollUnit::DEVICE_PIXELS;
+ default:
+ MOZ_CRASH();
+ }
+ return ScrollUnit::LINES;
+}
+
+WidgetWheelEvent ScrollWheelInput::ToWidgetEvent(nsIWidget* aWidget) const {
+ WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
+ wheelEvent.mModifiers = this->modifiers;
+ wheelEvent.mTime = mTime;
+ wheelEvent.mTimeStamp = mTimeStamp;
+ wheelEvent.mLayersId = mLayersId;
+ wheelEvent.mRefPoint = RoundedToInt(ViewAs<LayoutDevicePixel>(
+ mOrigin,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
+ wheelEvent.mButtons = 0;
+ wheelEvent.mDeltaMode = DeltaModeForDeltaType(mDeltaType);
+ wheelEvent.mMayHaveMomentum = mMayHaveMomentum;
+ wheelEvent.mIsMomentum = mIsMomentum;
+ wheelEvent.mDeltaX = mDeltaX;
+ wheelEvent.mDeltaY = mDeltaY;
+ wheelEvent.mLineOrPageDeltaX = mLineOrPageDeltaX;
+ wheelEvent.mLineOrPageDeltaY = mLineOrPageDeltaY;
+ wheelEvent.mAllowToOverrideSystemScrollSpeed =
+ mAllowToOverrideSystemScrollSpeed;
+ wheelEvent.mFlags.mHandledByAPZ = mHandledByAPZ;
+ wheelEvent.mFocusSequenceNumber = mFocusSequenceNumber;
+ return wheelEvent;
+}
+
+bool ScrollWheelInput::TransformToLocal(
+ const ScreenToParentLayerMatrix4x4& aTransform) {
+ Maybe<ParentLayerPoint> point = UntransformBy(aTransform, mOrigin);
+ if (!point) {
+ return false;
+ }
+ mLocalOrigin = *point;
+ return true;
+}
+
+bool ScrollWheelInput::IsCustomizedByUserPrefs() const {
+ return mUserDeltaMultiplierX != 1.0 || mUserDeltaMultiplierY != 1.0;
+}
+
+KeyboardInput::KeyboardInput(const WidgetKeyboardEvent& aEvent)
+ : InputData(KEYBOARD_INPUT, aEvent.mTime, aEvent.mTimeStamp,
+ aEvent.mModifiers),
+ mKeyCode(aEvent.mKeyCode),
+ mCharCode(aEvent.mCharCode),
+ mHandledByAPZ(false) {
+ switch (aEvent.mMessage) {
+ case eKeyPress: {
+ mType = KeyboardInput::KEY_PRESS;
+ break;
+ }
+ case eKeyUp: {
+ mType = KeyboardInput::KEY_UP;
+ break;
+ }
+ case eKeyDown: {
+ mType = KeyboardInput::KEY_DOWN;
+ break;
+ }
+ default:
+ mType = KeyboardInput::KEY_OTHER;
+ break;
+ }
+
+ aEvent.GetShortcutKeyCandidates(mShortcutCandidates);
+}
+
+KeyboardInput::KeyboardInput()
+ : InputData(KEYBOARD_INPUT),
+ mType(KEY_DOWN),
+ mKeyCode(0),
+ mCharCode(0),
+ mHandledByAPZ(false) {}
+
+} // namespace mozilla
diff --git a/widget/InputData.h b/widget/InputData.h
new file mode 100644
index 0000000000..e70d152d8f
--- /dev/null
+++ b/widget/InputData.h
@@ -0,0 +1,767 @@
+/* -*- 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 InputData_h__
+#define InputData_h__
+
+#include "nsDebug.h"
+#include "nsPoint.h"
+#include "nsTArray.h"
+#include "Units.h"
+#include "mozilla/ScrollTypes.h"
+#include "mozilla/DefineEnum.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/WheelHandlingHelper.h" // for WheelDeltaAdjustmentStrategy
+#include "mozilla/gfx/MatrixFwd.h"
+#include "mozilla/layers/APZPublicUtils.h"
+#include "mozilla/layers/KeyboardScrollAction.h"
+#include "mozilla/TextEvents.h"
+
+template <class E>
+struct already_AddRefed;
+class nsIWidget;
+
+namespace mozilla {
+
+namespace layers {
+class APZInputBridgeChild;
+class PAPZInputBridgeParent;
+} // namespace layers
+
+namespace dom {
+class Touch;
+} // namespace dom
+
+// clang-format off
+MOZ_DEFINE_ENUM(
+ InputType, (
+ MULTITOUCH_INPUT,
+ MOUSE_INPUT,
+ PANGESTURE_INPUT,
+ PINCHGESTURE_INPUT,
+ TAPGESTURE_INPUT,
+ SCROLLWHEEL_INPUT,
+ KEYBOARD_INPUT
+));
+// clang-format on
+
+class MultiTouchInput;
+class MouseInput;
+class PanGestureInput;
+class PinchGestureInput;
+class TapGestureInput;
+class ScrollWheelInput;
+class KeyboardInput;
+
+// This looks unnecessary now, but as we add more and more classes that derive
+// from InputType (eventually probably almost as many as *Events.h has), it
+// will be more and more clear what's going on with a macro that shortens the
+// definition of the RTTI functions.
+#define INPUTDATA_AS_CHILD_TYPE(type, enumID) \
+ const type& As##type() const { \
+ MOZ_ASSERT(mInputType == enumID, "Invalid cast of InputData."); \
+ return (const type&)*this; \
+ } \
+ type& As##type() { \
+ MOZ_ASSERT(mInputType == enumID, "Invalid cast of InputData."); \
+ return (type&)*this; \
+ }
+
+/** Base input data class. Should never be instantiated. */
+class InputData {
+ public:
+ // Warning, this class is serialized and sent over IPC. Any change to its
+ // fields must be reflected in its ParamTraits<>, in nsGUIEventIPC.h
+ InputType mInputType;
+ // Time in milliseconds that this data is relevant to. This only really
+ // matters when this data is used as an event. We use uint32_t instead of
+ // TimeStamp because it is easier to convert from WidgetInputEvent. The time
+ // is platform-specific but it in the case of B2G and Fennec it is since
+ // startup.
+ uint32_t mTime;
+ // Set in parallel to mTime until we determine it is safe to drop
+ // platform-specific event times (see bug 77992).
+ TimeStamp mTimeStamp;
+ // The sequence number of the last potentially focus changing event handled
+ // by APZ. This is used to track when that event has been processed by
+ // content, and focus can be reconfirmed for async keyboard scrolling.
+ uint64_t mFocusSequenceNumber;
+
+ // The LayersId of the content process that the corresponding WidgetEvent
+ // should be dispatched to.
+ layers::LayersId mLayersId;
+
+ Modifiers modifiers;
+
+ INPUTDATA_AS_CHILD_TYPE(MultiTouchInput, MULTITOUCH_INPUT)
+ INPUTDATA_AS_CHILD_TYPE(MouseInput, MOUSE_INPUT)
+ INPUTDATA_AS_CHILD_TYPE(PanGestureInput, PANGESTURE_INPUT)
+ INPUTDATA_AS_CHILD_TYPE(PinchGestureInput, PINCHGESTURE_INPUT)
+ INPUTDATA_AS_CHILD_TYPE(TapGestureInput, TAPGESTURE_INPUT)
+ INPUTDATA_AS_CHILD_TYPE(ScrollWheelInput, SCROLLWHEEL_INPUT)
+ INPUTDATA_AS_CHILD_TYPE(KeyboardInput, KEYBOARD_INPUT)
+
+ virtual ~InputData();
+ explicit InputData(InputType aInputType);
+
+ protected:
+ InputData(InputType aInputType, uint32_t aTime, TimeStamp aTimeStamp,
+ Modifiers aModifiers);
+};
+
+/**
+ * Data container for a single touch input. Similar to dom::Touch, but used in
+ * off-main-thread situations. This is more for just storing touch data, whereas
+ * dom::Touch is more useful for dispatching through the DOM (which can only
+ * happen on the main thread). dom::Touch also bears the problem of storing
+ * pointers to nsIWidget instances which can only be used on the main thread,
+ * so if instead we used dom::Touch and ever set these pointers
+ * off-main-thread, Bad Things Can Happen(tm).
+ *
+ * Note that this doesn't inherit from InputData because this itself is not an
+ * event. It is only a container/struct that should have any number of instances
+ * within a MultiTouchInput.
+ *
+ * fixme/bug 775746: Make dom::Touch inherit from this class.
+ */
+class SingleTouchData {
+ public:
+ // Construct a SingleTouchData from a Screen point.
+ // mLocalScreenPoint remains (0,0) unless it's set later.
+ SingleTouchData(int32_t aIdentifier, ScreenIntPoint aScreenPoint,
+ ScreenSize aRadius, float aRotationAngle, float aForce);
+
+ // Construct a SingleTouchData from a ParentLayer point.
+ // mScreenPoint remains (0,0) unless it's set later.
+ // Note: if APZ starts using the radius for anything, we should add a local
+ // version of that too, and have this constructor take it as a
+ // ParentLayerSize.
+ SingleTouchData(int32_t aIdentifier, ParentLayerPoint aLocalScreenPoint,
+ ScreenSize aRadius, float aRotationAngle, float aForce);
+
+ SingleTouchData();
+
+ already_AddRefed<dom::Touch> ToNewDOMTouch() const;
+
+ // Warning, this class is serialized and sent over IPC. Any change to its
+ // fields must be reflected in its ParamTraits<>, in nsGUIEventIPC.h
+
+ // Historical data of this touch, which was coalesced into this event.
+ // Touch event coalescing can happen at the system level when the touch
+ // screen's sampling frequency is higher than the vsync rate, or when the
+ // UI thread is busy. When multiple "samples" of touch data are coalesced into
+ // one touch event, the touch event's regular position information is the
+ // information from the last sample. And the previous, "coalesced-away"
+ // samples are stored in mHistoricalData.
+
+ struct HistoricalTouchData {
+ // The timestamp at which the information in this "sample" was originally
+ // sampled.
+ TimeStamp mTimeStamp;
+
+ // The touch data of this historical sample.
+ ScreenIntPoint mScreenPoint;
+ ParentLayerPoint mLocalScreenPoint;
+ ScreenSize mRadius;
+ float mRotationAngle = 0.0f;
+ float mForce = 0.0f;
+ };
+ CopyableTArray<HistoricalTouchData> mHistoricalData;
+
+ // A unique number assigned to each SingleTouchData within a MultiTouchInput
+ // so that they can be easily distinguished when handling a touch
+ // start/move/end.
+ int32_t mIdentifier;
+
+ // Point on the screen that the touch hit, in device pixels. They are
+ // coordinates on the screen.
+ ScreenIntPoint mScreenPoint;
+
+ // |mScreenPoint| transformed to the local coordinates of the APZC targeted
+ // by the hit. This is set and used by APZ.
+ ParentLayerPoint mLocalScreenPoint;
+
+ // Radius that the touch covers, i.e. if you're using your thumb it will
+ // probably be larger than using your pinky, even with the same force.
+ // Radius can be different along x and y. For example, if you press down with
+ // your entire finger vertically, the y radius will be much larger than the x
+ // radius.
+ ScreenSize mRadius;
+
+ float mRotationAngle;
+
+ // How hard the screen is being pressed.
+ float mForce;
+};
+
+/**
+ * Similar to WidgetTouchEvent, but for use off-main-thread. Also only stores a
+ * screen touch point instead of the many different coordinate spaces
+ * WidgetTouchEvent stores its touch point in. This includes a way to initialize
+ * itself from a WidgetTouchEvent by copying all relevant data over. Note that
+ * this copying from WidgetTouchEvent functionality can only be used on the main
+ * thread.
+ *
+ * Stores an array of SingleTouchData.
+ */
+class MultiTouchInput : public InputData {
+ public:
+ // clang-format off
+ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(
+ MultiTouchType, (
+ MULTITOUCH_START,
+ MULTITOUCH_MOVE,
+ MULTITOUCH_END,
+ MULTITOUCH_CANCEL
+ ));
+ // clang-format on
+
+ MultiTouchInput(MultiTouchType aType, uint32_t aTime, TimeStamp aTimeStamp,
+ Modifiers aModifiers);
+ MultiTouchInput();
+ MultiTouchInput(MultiTouchInput&&) = default;
+ MultiTouchInput(const MultiTouchInput& aOther);
+ explicit MultiTouchInput(const WidgetTouchEvent& aTouchEvent);
+
+ MultiTouchInput& operator=(MultiTouchInput&&) = default;
+ MultiTouchInput& operator=(const MultiTouchInput&) = default;
+
+ void Translate(const ScreenPoint& aTranslation);
+
+ WidgetTouchEvent ToWidgetTouchEvent(nsIWidget* aWidget) const;
+
+ // Return the index into mTouches of the SingleTouchData with the given
+ // identifier, or -1 if there is no such SingleTouchData.
+ int32_t IndexOfTouch(int32_t aTouchIdentifier);
+
+ bool TransformToLocal(const ScreenToParentLayerMatrix4x4& aTransform);
+
+ // Warning, this class is serialized and sent over IPC. Any change to its
+ // fields must be reflected in its ParamTraits<>, in nsGUIEventIPC.h
+ MultiTouchType mType;
+ CopyableTArray<SingleTouchData> mTouches;
+ // The screen offset of the root widget. This can be changing along with
+ // the touch interaction, so we sstore it in the event.
+ ExternalPoint mScreenOffset;
+ bool mHandledByAPZ;
+};
+
+class MouseInput : public InputData {
+ protected:
+ friend mozilla::layers::APZInputBridgeChild;
+ friend mozilla::layers::PAPZInputBridgeParent;
+
+ MouseInput();
+
+ public:
+ // clang-format off
+ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(
+ MouseType, (
+ MOUSE_NONE,
+ MOUSE_MOVE,
+ MOUSE_DOWN,
+ MOUSE_UP,
+ MOUSE_DRAG_START,
+ MOUSE_DRAG_END,
+ MOUSE_WIDGET_ENTER,
+ MOUSE_WIDGET_EXIT,
+ MOUSE_HITTEST
+ ));
+
+ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(
+ ButtonType, (
+ PRIMARY_BUTTON,
+ MIDDLE_BUTTON,
+ SECONDARY_BUTTON,
+ NONE
+ ));
+ // clang-format on
+
+ MouseInput(MouseType aType, ButtonType aButtonType, uint16_t aInputSource,
+ int16_t aButtons, const ScreenPoint& aPoint, uint32_t aTime,
+ TimeStamp aTimeStamp, Modifiers aModifiers);
+ explicit MouseInput(const WidgetMouseEventBase& aMouseEvent);
+
+ bool IsLeftButton() const;
+
+ bool TransformToLocal(const ScreenToParentLayerMatrix4x4& aTransform);
+ WidgetMouseEvent ToWidgetEvent(nsIWidget* aWidget) const;
+
+ // Warning, this class is serialized and sent over IPC. Any change to its
+ // fields must be reflected in its ParamTraits<>, in nsGUIEventIPC.h
+ MouseType mType;
+ ButtonType mButtonType;
+ uint16_t mInputSource;
+ int16_t mButtons;
+ ScreenPoint mOrigin;
+ ParentLayerPoint mLocalOrigin;
+ bool mHandledByAPZ;
+};
+
+/**
+ * Encapsulation class for pan events, can be used off-main-thread.
+ * These events are currently only used for scrolling on desktop.
+ */
+class PanGestureInput : public InputData {
+ protected:
+ friend mozilla::layers::APZInputBridgeChild;
+ friend mozilla::layers::PAPZInputBridgeParent;
+
+ PanGestureInput();
+
+ public:
+ // clang-format off
+ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(
+ PanGestureType, (
+ // MayStart: Dispatched before any actual panning has occurred but when a
+ // pan gesture is probably about to start, for example when the user
+ // starts touching the touchpad. Should interrupt any ongoing APZ
+ // animation and can be used to trigger scrollability indicators (e.g.
+ // flashing overlay scrollbars).
+ PANGESTURE_MAYSTART,
+
+ // Cancelled: Dispatched after MayStart when no pan gesture is going to
+ // happen after all, for example when the user lifts their fingers from a
+ // touchpad without having done any scrolling.
+ PANGESTURE_CANCELLED,
+
+ // Start: A pan gesture is starting.
+ // For devices that do not support the MayStart event type, this event can
+ // be used to interrupt ongoing APZ animations.
+ PANGESTURE_START,
+
+ // Pan: The actual pan motion by mPanDisplacement.
+ PANGESTURE_PAN,
+
+ // End: The pan gesture has ended, for example because the user has lifted
+ // their fingers from a touchpad after scrolling.
+ // Any potential momentum events fire after this event.
+ PANGESTURE_END,
+
+ // The following momentum event types are used in order to control the pan
+ // momentum animation. Using these instead of our own animation ensures
+ // that the animation curve is OS native and that the animation stops
+ // reliably if it is cancelled by the user.
+
+ // MomentumStart: Dispatched between the End event of the actual
+ // user-controlled pan, and the first MomentumPan event of the momentum
+ // animation.
+ PANGESTURE_MOMENTUMSTART,
+
+ // MomentumPan: The actual momentum motion by mPanDisplacement.
+ PANGESTURE_MOMENTUMPAN,
+
+ // MomentumEnd: The momentum animation has ended, for example because the
+ // momentum velocity has gone below the stopping threshold, or because the
+ // user has stopped the animation by putting their fingers on a touchpad.
+ PANGESTURE_MOMENTUMEND
+ ));
+
+ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(
+ PanDeltaType, (
+ // There are three kinds of scroll delta modes in Gecko: "page", "line"
+ // and "pixel". Touchpad pan gestures only support "page" and "pixel".
+ //
+ // NOTE: PANDELTA_PAGE currently replicates Gtk behavior
+ // (see AsyncPanZoomController::OnPan).
+ PANDELTA_PAGE,
+ PANDELTA_PIXEL
+ ));
+ // clang-format on
+
+ PanGestureInput(PanGestureType aType, uint32_t aTime, TimeStamp aTimeStamp,
+ const ScreenPoint& aPanStartPoint,
+ const ScreenPoint& aPanDisplacement, Modifiers aModifiers);
+
+ bool IsMomentum() const;
+
+ WidgetWheelEvent ToWidgetEvent(nsIWidget* aWidget) const;
+
+ bool TransformToLocal(const ScreenToParentLayerMatrix4x4& aTransform);
+
+ ScreenPoint UserMultipliedPanDisplacement() const;
+ ParentLayerPoint UserMultipliedLocalPanDisplacement() const;
+
+ static gfx::IntPoint GetIntegerDeltaForEvent(bool aIsStart, float x, float y);
+
+ // Warning, this class is serialized and sent over IPC. Any change to its
+ // fields must be reflected in its ParamTraits<>, in nsGUIEventIPC.h
+ PanGestureType mType;
+ ScreenPoint mPanStartPoint;
+
+ // The delta. This can be non-zero on any type of event.
+ ScreenPoint mPanDisplacement;
+
+ // Versions of |mPanStartPoint| and |mPanDisplacement| in the local
+ // coordinates of the APZC receiving the pan. These are set and used by APZ.
+ ParentLayerPoint mLocalPanStartPoint;
+ ParentLayerPoint mLocalPanDisplacement;
+
+ // See lineOrPageDeltaX/Y on WidgetWheelEvent.
+ int32_t mLineOrPageDeltaX;
+ int32_t mLineOrPageDeltaY;
+
+ // User-set delta multipliers.
+ double mUserDeltaMultiplierX;
+ double mUserDeltaMultiplierY;
+
+ PanDeltaType mDeltaType = PANDELTA_PIXEL;
+
+ bool mHandledByAPZ : 1;
+
+ // true if this is a PANGESTURE_END event that will be followed by a
+ // PANGESTURE_MOMENTUMSTART event.
+ bool mFollowedByMomentum : 1;
+
+ // If this is true, and this event started a new input block that couldn't
+ // find a scrollable target which is scrollable in the horizontal component
+ // of the scroll start direction, then this input block needs to be put on
+ // hold until a content response has arrived, even if the block has a
+ // confirmed target.
+ // This is used by events that can result in a swipe instead of a scroll.
+ bool mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection : 1;
+
+ // This is used by APZ to communicate to the macOS widget code whether
+ // the overscroll-behavior of the scroll frame handling this swipe allows
+ // non-local overscroll behaviors in the horizontal direction (such as
+ // swipe navigation).
+ bool mOverscrollBehaviorAllowsSwipe : 1;
+
+ // true if APZ should do a fling animation after this pan ends, like
+ // it would with touchscreens. (For platforms that don't emit momentum
+ // events.)
+ bool mSimulateMomentum : 1;
+
+ void SetHandledByAPZ(bool aHandled) { mHandledByAPZ = aHandled; }
+ void SetFollowedByMomentum(bool aFollowed) {
+ mFollowedByMomentum = aFollowed;
+ }
+ void SetRequiresContentResponseIfCannotScrollHorizontallyInStartDirection(
+ bool aRequires) {
+ mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection =
+ aRequires;
+ }
+ void SetOverscrollBehaviorAllowsSwipe(bool aAllows) {
+ mOverscrollBehaviorAllowsSwipe = aAllows;
+ }
+ void SetSimulateMomentum(bool aSimulate) { mSimulateMomentum = aSimulate; }
+};
+
+/**
+ * Encapsulation class for pinch events. In general, these will be generated by
+ * a gesture listener by looking at SingleTouchData/MultiTouchInput instances
+ * and determining whether or not the user was trying to do a gesture.
+ */
+class PinchGestureInput : public InputData {
+ protected:
+ friend mozilla::layers::APZInputBridgeChild;
+ friend mozilla::layers::PAPZInputBridgeParent;
+
+ PinchGestureInput();
+
+ public:
+ // clang-format off
+ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(
+ PinchGestureType, (
+ PINCHGESTURE_START,
+ PINCHGESTURE_SCALE,
+ // The FINGERLIFTED state is used when a touch-based pinch gesture is
+ // terminated by lifting one of the two fingers. The position of the
+ // finger that's still down is populated as the focus point.
+ PINCHGESTURE_FINGERLIFTED,
+ // The END state is used when the pinch gesture is completely terminated.
+ // In this state, the focus point should not be relied upon for having
+ // meaningful data.
+ PINCHGESTURE_END
+ ));
+
+ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(
+ PinchGestureSource, (
+ UNKNOWN, // Default initialization value. Should never actually be used.
+ TOUCH, // From two-finger pinch gesture
+ ONE_TOUCH, // From one-finger pinch gesture
+ TRACKPAD, // From trackpad pinch gesture
+ MOUSEWHEEL // Synthesized from modifier+mousewheel
+
+ // If adding more items here, increase n_values for the
+ // APZ_ZOOM_PINCHSOURCE Telemetry metric.
+ ));
+ // clang-format on
+
+ // Construct a pinch gesture from a Screen point.
+ PinchGestureInput(PinchGestureType aType, PinchGestureSource aSource,
+ uint32_t aTime, TimeStamp aTimeStamp,
+ const ExternalPoint& aScreenOffset,
+ const ScreenPoint& aFocusPoint, ScreenCoord aCurrentSpan,
+ ScreenCoord aPreviousSpan, Modifiers aModifiers);
+
+ bool TransformToLocal(const ScreenToParentLayerMatrix4x4& aTransform);
+
+ WidgetWheelEvent ToWidgetEvent(nsIWidget* aWidget) const;
+
+ double ComputeDeltaY(nsIWidget* aWidget) const;
+
+ static gfx::IntPoint GetIntegerDeltaForEvent(bool aIsStart, float x, float y);
+
+ // Warning, this class is serialized and sent over IPC. Any change to its
+ // fields must be reflected in its ParamTraits<>, in nsGUIEventIPC.h
+ PinchGestureType mType;
+
+ // Some indication of the input device that generated this pinch gesture.
+ PinchGestureSource mSource;
+
+ // Center point of the pinch gesture. That is, if there are two fingers on the
+ // screen, it is their midpoint. In the case of more than two fingers, the
+ // point is implementation-specific, but can for example be the midpoint
+ // between the very first and very last touch. This is in device pixels and
+ // are the coordinates on the screen of this midpoint.
+ // For PINCHGESTURE_END events, this may hold the last known focus point or
+ // just be empty; in any case for END events it should not be relied upon.
+ // For PINCHGESTURE_FINGERLIFTED events, this holds the point of the finger
+ // that is still down.
+ ScreenPoint mFocusPoint;
+
+ // The screen offset of the root widget. This can be changing along with
+ // the touch interaction, so we sstore it in the event.
+ ExternalPoint mScreenOffset;
+
+ // |mFocusPoint| transformed to the local coordinates of the APZC targeted
+ // by the hit. This is set and used by APZ.
+ ParentLayerPoint mLocalFocusPoint;
+
+ // The distance between the touches responsible for the pinch gesture.
+ ScreenCoord mCurrentSpan;
+
+ // The previous |mCurrentSpan| in the PinchGestureInput preceding this one.
+ // This is only really relevant during a PINCHGESTURE_SCALE because when it is
+ // of this type then there must have been a history of spans.
+ ScreenCoord mPreviousSpan;
+
+ // We accumulate (via GetIntegerDeltaForEvent) the deltaY that would be
+ // computed by ToWidgetEvent, and then whenever we get a whole integer
+ // value we put it in mLineOrPageDeltaY. Since we only ever use deltaY we
+ // don't need a mLineOrPageDeltaX. This field is used to dispatch legacy mouse
+ // events which are only dispatched when the corresponding field on
+ // WidgetWheelEvent is non-zero.
+ int32_t mLineOrPageDeltaY;
+
+ bool mHandledByAPZ;
+};
+
+/**
+ * Encapsulation class for tap events. In general, these will be generated by
+ * a gesture listener by looking at SingleTouchData/MultiTouchInput instances
+ * and determining whether or not the user was trying to do a gesture.
+ */
+class TapGestureInput : public InputData {
+ protected:
+ friend mozilla::layers::APZInputBridgeChild;
+ friend mozilla::layers::PAPZInputBridgeParent;
+
+ TapGestureInput();
+
+ public:
+ // clang-format off
+ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(
+ TapGestureType, (
+ TAPGESTURE_LONG,
+ TAPGESTURE_LONG_UP,
+ TAPGESTURE_UP,
+ TAPGESTURE_CONFIRMED,
+ TAPGESTURE_DOUBLE,
+ TAPGESTURE_SECOND, // See GeckoContentController::TapType::eSecondTap
+ TAPGESTURE_CANCEL
+ ));
+ // clang-format on
+
+ // Construct a tap gesture from a Screen point.
+ // mLocalPoint remains (0,0) unless it's set later.
+ TapGestureInput(TapGestureType aType, uint32_t aTime, TimeStamp aTimeStamp,
+ const ScreenIntPoint& aPoint, Modifiers aModifiers);
+
+ // Construct a tap gesture from a ParentLayer point.
+ // mPoint remains (0,0) unless it's set later.
+ TapGestureInput(TapGestureType aType, uint32_t aTime, TimeStamp aTimeStamp,
+ const ParentLayerPoint& aLocalPoint, Modifiers aModifiers);
+
+ bool TransformToLocal(const ScreenToParentLayerMatrix4x4& aTransform);
+
+ // Warning, this class is serialized and sent over IPC. Any change to its
+ // fields must be reflected in its ParamTraits<>, in nsGUIEventIPC.h
+ TapGestureType mType;
+
+ // The location of the tap in screen pixels.
+ ScreenIntPoint mPoint;
+
+ // The location of the tap in the local coordinates of the APZC receiving it.
+ // This is set and used by APZ.
+ ParentLayerPoint mLocalPoint;
+};
+
+// Encapsulation class for scroll-wheel events. These are generated by mice
+// with physical scroll wheels, and on Windows by most touchpads when using
+// scroll gestures.
+class ScrollWheelInput : public InputData {
+ protected:
+ friend mozilla::layers::APZInputBridgeChild;
+ friend mozilla::layers::PAPZInputBridgeParent;
+
+ typedef mozilla::layers::APZWheelAction APZWheelAction;
+
+ ScrollWheelInput();
+
+ public:
+ // clang-format off
+ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(
+ ScrollDeltaType, (
+ // There are three kinds of scroll delta modes in Gecko: "page", "line"
+ // and "pixel".
+ SCROLLDELTA_LINE,
+ SCROLLDELTA_PAGE,
+ SCROLLDELTA_PIXEL
+ ));
+
+ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(
+ ScrollMode, (
+ SCROLLMODE_INSTANT,
+ SCROLLMODE_SMOOTH
+ )
+ );
+ // clang-format on
+
+ ScrollWheelInput(uint32_t aTime, TimeStamp aTimeStamp, Modifiers aModifiers,
+ ScrollMode aScrollMode, ScrollDeltaType aDeltaType,
+ const ScreenPoint& aOrigin, double aDeltaX, double aDeltaY,
+ bool aAllowToOverrideSystemScrollSpeed,
+ WheelDeltaAdjustmentStrategy aWheelDeltaAdjustmentStrategy);
+ explicit ScrollWheelInput(const WidgetWheelEvent& aEvent);
+
+ static ScrollDeltaType DeltaTypeForDeltaMode(uint32_t aDeltaMode);
+ static uint32_t DeltaModeForDeltaType(ScrollDeltaType aDeltaType);
+ static mozilla::ScrollUnit ScrollUnitForDeltaType(ScrollDeltaType aDeltaType);
+
+ WidgetWheelEvent ToWidgetEvent(nsIWidget* aWidget) const;
+ bool TransformToLocal(const ScreenToParentLayerMatrix4x4& aTransform);
+
+ bool IsCustomizedByUserPrefs() const;
+
+ // The following two functions are for auto-dir scrolling. For detailed
+ // information on auto-dir, @see mozilla::WheelDeltaAdjustmentStrategy
+ bool IsAutoDir() const {
+ switch (mWheelDeltaAdjustmentStrategy) {
+ case WheelDeltaAdjustmentStrategy::eAutoDir:
+ case WheelDeltaAdjustmentStrategy::eAutoDirWithRootHonour:
+ return true;
+ default:
+ // Prevent compilation errors generated by -Werror=switch
+ break;
+ }
+ return false;
+ }
+ // Indicates which element this scroll honours if it's an auto-dir scroll.
+ // If true, honour the root element; otherwise, honour the currently scrolling
+ // target.
+ // Note that if IsAutoDir() returns false, then this function also returns
+ // false, but false in this case is meaningless as IsAutoDir() indicates it's
+ // not an auto-dir scroll.
+ // For detailed information on auto-dir,
+ // @see mozilla::WheelDeltaAdjustmentStrategy
+ bool HonoursRoot() const {
+ return WheelDeltaAdjustmentStrategy::eAutoDirWithRootHonour ==
+ mWheelDeltaAdjustmentStrategy;
+ }
+
+ // Warning, this class is serialized and sent over IPC. Any change to its
+ // fields must be reflected in its ParamTraits<>, in nsGUIEventIPC.h
+ ScrollDeltaType mDeltaType;
+ ScrollMode mScrollMode;
+ ScreenPoint mOrigin;
+
+ bool mHandledByAPZ;
+
+ // Deltas are in units corresponding to the delta type. For line deltas, they
+ // are the number of line units to scroll. The number of device pixels for a
+ // horizontal and vertical line unit are in FrameMetrics::mLineScrollAmount.
+ // For pixel deltas, these values are in ScreenCoords.
+ //
+ // The horizontal (X) delta is > 0 for scrolling right and < 0 for scrolling
+ // left. The vertical (Y) delta is < 0 for scrolling up and > 0 for
+ // scrolling down.
+ double mDeltaX;
+ double mDeltaY;
+
+ // The location of the scroll in local coordinates. This is set and used by
+ // APZ.
+ ParentLayerPoint mLocalOrigin;
+
+ // See lineOrPageDeltaX/Y on WidgetWheelEvent.
+ int32_t mLineOrPageDeltaX;
+ int32_t mLineOrPageDeltaY;
+
+ // Indicates the order in which this event was added to a transaction. The
+ // first event is 1; if not a member of a transaction, this is 0.
+ uint32_t mScrollSeriesNumber;
+
+ // User-set delta multipliers.
+ double mUserDeltaMultiplierX;
+ double mUserDeltaMultiplierY;
+
+ bool mMayHaveMomentum;
+ bool mIsMomentum;
+ bool mAllowToOverrideSystemScrollSpeed;
+
+ // Sometimes a wheel event input's wheel delta should be adjusted. This member
+ // specifies how to adjust the wheel delta.
+ WheelDeltaAdjustmentStrategy mWheelDeltaAdjustmentStrategy;
+
+ APZWheelAction mAPZAction;
+};
+
+class KeyboardInput : public InputData {
+ public:
+ typedef mozilla::layers::KeyboardScrollAction KeyboardScrollAction;
+
+ // Note that if you change the first member in this enum(I.e. KEY_DOWN) to one
+ // other member, don't forget to update the minimum value in
+ // ContiguousEnumSerializer for KeyboardEventType in widget/nsGUIEventIPC
+ // accordingly.
+ enum KeyboardEventType {
+ KEY_DOWN,
+ KEY_PRESS,
+ KEY_UP,
+ // Any other key event such as eKeyDownOnPlugin
+ KEY_OTHER,
+
+ // Used as an upper bound for ContiguousEnumSerializer
+ KEY_SENTINEL,
+ };
+
+ explicit KeyboardInput(const WidgetKeyboardEvent& aEvent);
+
+ // Warning, this class is serialized and sent over IPC. Any change to its
+ // fields must be reflected in its ParamTraits<>, in nsGUIEventIPC.h
+
+ KeyboardEventType mType;
+ uint32_t mKeyCode;
+ uint32_t mCharCode;
+ CopyableTArray<ShortcutKeyCandidate> mShortcutCandidates;
+
+ bool mHandledByAPZ;
+
+ // The scroll action to perform on a layer for this keyboard input. This is
+ // only used in APZ and is NOT serialized over IPC.
+ KeyboardScrollAction mAction;
+
+ protected:
+ friend mozilla::layers::APZInputBridgeChild;
+ friend mozilla::layers::PAPZInputBridgeParent;
+
+ KeyboardInput();
+};
+
+} // namespace mozilla
+
+#endif // InputData_h__
diff --git a/widget/LSBUtils.cpp b/widget/LSBUtils.cpp
new file mode 100644
index 0000000000..6cd3044420
--- /dev/null
+++ b/widget/LSBUtils.cpp
@@ -0,0 +1,67 @@
+/* -*- 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 "LSBUtils.h"
+
+#include <unistd.h>
+#include "base/process_util.h"
+#include "mozilla/FileUtils.h"
+
+namespace mozilla::widget::lsb {
+
+static const char* gLsbReleasePath = "/usr/bin/lsb_release";
+
+bool GetLSBRelease(nsACString& aDistributor, nsACString& aDescription,
+ nsACString& aRelease, nsACString& aCodename) {
+ if (access(gLsbReleasePath, R_OK) != 0) return false;
+
+ int pipefd[2];
+ if (pipe(pipefd) == -1) {
+ NS_WARNING("pipe() failed!");
+ return false;
+ }
+
+ std::vector<std::string> argv = {gLsbReleasePath, "-idrc"};
+
+ base::LaunchOptions options;
+ options.fds_to_remap.push_back({pipefd[1], STDOUT_FILENO});
+ options.wait = true;
+
+ base::ProcessHandle process;
+ bool ok = base::LaunchApp(argv, options, &process);
+ close(pipefd[1]);
+ if (!ok) {
+ NS_WARNING("Failed to spawn lsb_release!");
+ close(pipefd[0]);
+ return false;
+ }
+
+ ScopedCloseFile stream(fdopen(pipefd[0], "r"));
+ if (!stream) {
+ NS_WARNING("Could not wrap fd!");
+ close(pipefd[0]);
+ return false;
+ }
+
+ char dist[256], desc[256], release[256], codename[256];
+ if (fscanf(stream,
+ "Distributor ID:\t%255[^\n]\n"
+ "Description:\t%255[^\n]\n"
+ "Release:\t%255[^\n]\n"
+ "Codename:\t%255[^\n]\n",
+ dist, desc, release, codename) != 4) {
+ NS_WARNING("Failed to parse lsb_release!");
+ return false;
+ }
+
+ aDistributor.Assign(dist);
+ aDescription.Assign(desc);
+ aRelease.Assign(release);
+ aCodename.Assign(codename);
+ return true;
+}
+
+} // namespace mozilla::widget::lsb
diff --git a/widget/LSBUtils.h b/widget/LSBUtils.h
new file mode 100644
index 0000000000..37d4c400ad
--- /dev/null
+++ b/widget/LSBUtils.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_WIDGET_LSB_UTILS_H
+#define _MOZILLA_WIDGET_LSB_UTILS_H
+
+#include "nsString.h"
+
+namespace mozilla {
+namespace widget {
+namespace lsb {
+
+// Fetches the LSB release data by parsing the lsb_release command.
+// Returns false if the lsb_release command was not found, or parsing failed.
+bool GetLSBRelease(nsACString& aDistributor, nsACString& aDescription,
+ nsACString& aRelease, nsACString& aCodename);
+
+} // namespace lsb
+} // namespace widget
+} // namespace mozilla
+
+#endif // _MOZILLA_WIDGET_LSB_UTILS_H
diff --git a/widget/LookAndFeel.h b/widget/LookAndFeel.h
new file mode 100644
index 0000000000..aff5e4009c
--- /dev/null
+++ b/widget/LookAndFeel.h
@@ -0,0 +1,602 @@
+/* -*- 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 __LookAndFeel
+#define __LookAndFeel
+
+#ifndef MOZILLA_INTERNAL_API
+# error "This header is only usable from within libxul (MOZILLA_INTERNAL_API)."
+#endif
+
+#include "nsDebug.h"
+#include "nsColor.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/widget/ThemeChangeKind.h"
+
+struct gfxFontStyle;
+
+namespace mozilla {
+
+namespace widget {
+class FullLookAndFeel;
+class LookAndFeelCache;
+} // namespace widget
+
+enum class StyleSystemColor : uint8_t;
+
+class LookAndFeel {
+ public:
+ using ColorID = StyleSystemColor;
+
+ // When modifying this list, also modify nsXPLookAndFeel::sIntPrefs
+ // in widget/xpwidgts/nsXPLookAndFeel.cpp.
+ enum class IntID {
+ // default, may be overriden by OS
+ CaretBlinkTime,
+ // pixel width of caret
+ CaretWidth,
+ // show the caret when text is selected?
+ ShowCaretDuringSelection,
+ // select textfields when focused via tab/accesskey?
+ SelectTextfieldsOnKeyFocus,
+ // delay before submenus open
+ SubmenuDelay,
+ // can popups overlap menu/task bar?
+ MenusCanOverlapOSBar,
+ // should overlay scrollbars be used?
+ UseOverlayScrollbars,
+ // allow H and V overlay scrollbars to overlap?
+ AllowOverlayScrollbarsOverlap,
+ // show/hide scrollbars based on activity
+ ShowHideScrollbars,
+ // skip navigating to disabled menu item?
+ SkipNavigatingDisabledMenuItem,
+ // begin a drag if the mouse is moved further than the threshold while the
+ // button is down
+ DragThresholdX,
+ DragThresholdY,
+ // Accessibility theme being used?
+ UseAccessibilityTheme,
+
+ // position of scroll arrows in a scrollbar
+ ScrollArrowStyle,
+ // is scroll thumb proportional or fixed?
+ ScrollSliderStyle,
+
+ // each button can take one of four values:
+ ScrollButtonLeftMouseButtonAction,
+ // 0 - scrolls one line, 1 - scrolls one page
+ ScrollButtonMiddleMouseButtonAction,
+ // 2 - scrolls to end, 3 - button ignored
+ ScrollButtonRightMouseButtonAction,
+
+ // delay for opening spring loaded folders
+ TreeOpenDelay,
+ // delay for closing spring loaded folders
+ TreeCloseDelay,
+ // delay for triggering the tree scrolling
+ TreeLazyScrollDelay,
+ // delay for scrolling the tree
+ TreeScrollDelay,
+ // the maximum number of lines to be scrolled at ones
+ TreeScrollLinesMax,
+ // What type of tab-order to use
+ TabFocusModel,
+ // Should menu items blink when they're chosen?
+ ChosenMenuItemsShouldBlink,
+
+ /*
+ * A Boolean value to determine whether the Windows accent color
+ * should be applied to the title bar.
+ *
+ * The value of this metric is not used on other platforms. These platforms
+ * should return NS_ERROR_NOT_IMPLEMENTED when queried for this metric.
+ */
+ WindowsAccentColorInTitlebar,
+
+ /*
+ * A Boolean value to determine whether the Windows default theme is
+ * being used.
+ *
+ * The value of this metric is not used on other platforms. These platforms
+ * should return NS_ERROR_NOT_IMPLEMENTED when queried for this metric.
+ */
+ WindowsDefaultTheme,
+
+ /*
+ * A Boolean value to determine whether the DWM compositor is being used
+ *
+ * This metric is not used on non-Windows platforms. These platforms
+ * should return NS_ERROR_NOT_IMPLEMENTED when queried for this metric.
+ */
+ DWMCompositor,
+
+ /*
+ * A Boolean value to determine whether Windows is themed (Classic vs.
+ * uxtheme)
+ *
+ * This is Windows-specific and is not implemented on other platforms
+ * (will return the default of NS_ERROR_FAILURE).
+ */
+ WindowsClassic,
+
+ /*
+ * A Boolean value to determine whether the current Windows desktop theme
+ * supports Aero Glass.
+ *
+ * This is Windows-specific and is not implemented on other platforms
+ * (will return the default of NS_ERROR_FAILURE).
+ */
+ WindowsGlass,
+
+ /*
+ * A Boolean value to determine whether the device is a touch enabled
+ * device. Currently this is only supported by the Windows 7 Touch API.
+ *
+ * Platforms that do not support this metric should return
+ * NS_ERROR_NOT_IMPLEMENTED when queried for this metric.
+ */
+ TouchEnabled,
+
+ /*
+ * A Boolean value to determine whether the Mac graphite theme is
+ * being used.
+ *
+ * The value of this metric is not used on other platforms. These platforms
+ * should return NS_ERROR_NOT_IMPLEMENTED when queried for this metric.
+ */
+ MacGraphiteTheme,
+
+ /*
+ * A Boolean value to determine whether the macOS Big Sur-specific
+ * theming should be used.
+ *
+ * The value of this metric is not used on non-Mac platforms. These
+ * platforms should return NS_ERROR_NOT_IMPLEMENTED when queried for this
+ * metric.
+ */
+ MacBigSurTheme,
+
+ /*
+ * AlertNotificationOrigin indicates from which corner of the
+ * screen alerts slide in, and from which direction (horizontal/vertical).
+ * 0, the default, represents bottom right, sliding vertically.
+ * Use any bitwise combination of the following constants:
+ * NS_ALERT_HORIZONTAL (1), NS_ALERT_LEFT (2), NS_ALERT_TOP (4).
+ *
+ * 6 4
+ * +-----------+
+ * 7| |5
+ * | |
+ * 3| |1
+ * +-----------+
+ * 2 0
+ */
+ AlertNotificationOrigin,
+
+ /**
+ * If true, clicking on a scrollbar (not as in dragging the thumb) defaults
+ * to scrolling the view corresponding to the clicked point. Otherwise, we
+ * only do so if the scrollbar is clicked using the middle mouse button or
+ * if shift is pressed when the scrollbar is clicked.
+ */
+ ScrollToClick,
+
+ /**
+ * IME and spell checker underline styles, the values should be
+ * NS_DECORATION_LINE_STYLE_*. They are defined below.
+ */
+ IMERawInputUnderlineStyle,
+ IMESelectedRawTextUnderlineStyle,
+ IMEConvertedTextUnderlineStyle,
+ IMESelectedConvertedTextUnderline,
+ SpellCheckerUnderlineStyle,
+
+ /**
+ * If this metric != 0, support window dragging on the menubar.
+ */
+ MenuBarDrag,
+ /**
+ * Return the appropriate WindowsThemeIdentifier for the current theme.
+ */
+ WindowsThemeIdentifier,
+ /**
+ * Return an appropriate os version identifier.
+ */
+ OperatingSystemVersionIdentifier,
+ /**
+ * 0: scrollbar button repeats to scroll only when cursor is on the button.
+ * 1: scrollbar button repeats to scroll even if cursor is outside of it.
+ */
+ ScrollbarButtonAutoRepeatBehavior,
+ /**
+ * Delay before showing a tooltip.
+ */
+ TooltipDelay,
+ /*
+ * A Boolean value to determine whether Mac OS X Lion style swipe animations
+ * should be used.
+ */
+ SwipeAnimationEnabled,
+
+ /*
+ * Controls whether overlay scrollbars display when the user moves
+ * the mouse in a scrollable frame.
+ */
+ ScrollbarDisplayOnMouseMove,
+
+ /*
+ * Overlay scrollbar animation constants.
+ */
+ ScrollbarFadeBeginDelay,
+ ScrollbarFadeDuration,
+
+ /**
+ * Distance in pixels to offset the context menu from the cursor
+ * on open.
+ */
+ ContextMenuOffsetVertical,
+ ContextMenuOffsetHorizontal,
+
+ /*
+ * A boolean value indicating whether client-side decorations are
+ * supported by the user's GTK version.
+ */
+ GTKCSDAvailable,
+
+ /*
+ * A boolean value indicating whether GTK+ system titlebar should be
+ * disabled by default.
+ */
+ GTKCSDHideTitlebarByDefault,
+
+ /*
+ * A boolean value indicating whether client-side decorations should
+ * have transparent background.
+ */
+ GTKCSDTransparentBackground,
+
+ /*
+ * A boolean value indicating whether client-side decorations should
+ * contain a minimize button.
+ */
+ GTKCSDMinimizeButton,
+
+ /*
+ * A boolean value indicating whether client-side decorations should
+ * contain a maximize button.
+ */
+ GTKCSDMaximizeButton,
+
+ /*
+ * A boolean value indicating whether client-side decorations should
+ * contain a close button.
+ */
+ GTKCSDCloseButton,
+
+ /*
+ * A boolean value indicating whether titlebar buttons are located
+ * in left titlebar corner.
+ */
+ GTKCSDReversedPlacement,
+
+ /*
+ * A boolean value indicating whether or not the OS is using a dark theme,
+ * which we may want to switch to as well if not overridden by the user.
+ */
+ SystemUsesDarkTheme,
+
+ /**
+ * Corresponding to prefers-reduced-motion.
+ * https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion
+ * 0: no-preference
+ * 1: reduce
+ */
+
+ PrefersReducedMotion,
+ /**
+ * Corresponding to PointerCapabilities in ServoTypes.h
+ * 0: None
+ * 1: Coarse
+ * 2: Fine
+ * 4: Hover
+ */
+ PrimaryPointerCapabilities,
+ /**
+ * Corresponding to union of PointerCapabilities values in ServoTypes.h
+ * E.g. if there is a mouse and a digitizer, the value will be
+ * 'Coarse | Fine | Hover'.
+ */
+ AllPointerCapabilities,
+ /**
+ * An Integer value that will represent the position of the Close button
+ * in GTK Client side decoration header. Its value will be between 0 and 2
+ * if it is on the left side of the tabbar, otherwise it will be between
+ * 3 and 5.
+ */
+ GTKCSDCloseButtonPosition,
+
+ /**
+ * An Integer value that will represent the position of the Minimize button
+ * in GTK Client side decoration header. Its value will be between 0 and 2
+ * if it is on the left side of the tabbar, otherwise it will be between
+ * 3 and 5.
+ */
+ GTKCSDMinimizeButtonPosition,
+
+ /**
+ * An Integer value that will represent the position of the Maximize button
+ * in GTK Client side decoration header. Its value will be between 0 and 2
+ * if it is on the left side of the tabbar, otherwise it will be between
+ * 3 and 5.
+ */
+ GTKCSDMaximizeButtonPosition,
+
+ /*
+ * Not an ID; used to define the range of valid IDs. Must be last.
+ */
+ End,
+ };
+
+ /**
+ * Windows themes we currently detect.
+ */
+ enum WindowsTheme {
+ eWindowsTheme_Generic = 0, // unrecognized theme
+ eWindowsTheme_Classic,
+ eWindowsTheme_Aero,
+ eWindowsTheme_LunaBlue,
+ eWindowsTheme_LunaOlive,
+ eWindowsTheme_LunaSilver,
+ eWindowsTheme_Royale,
+ eWindowsTheme_Zune,
+ eWindowsTheme_AeroLite
+ };
+
+ /**
+ * Operating system versions.
+ */
+ enum class OperatingSystemVersion {
+ Windows7 = 2,
+ Windows8,
+ Windows10,
+ Unknown
+ };
+
+ enum {
+ eScrollArrow_None = 0,
+ eScrollArrow_StartBackward = 0x1000,
+ eScrollArrow_StartForward = 0x0100,
+ eScrollArrow_EndBackward = 0x0010,
+ eScrollArrow_EndForward = 0x0001
+ };
+
+ enum {
+ // single arrow at each end
+ eScrollArrowStyle_Single =
+ eScrollArrow_StartBackward | eScrollArrow_EndForward,
+ // both arrows at bottom/right, none at top/left
+ eScrollArrowStyle_BothAtBottom =
+ eScrollArrow_EndBackward | eScrollArrow_EndForward,
+ // both arrows at both ends
+ eScrollArrowStyle_BothAtEachEnd =
+ eScrollArrow_EndBackward | eScrollArrow_EndForward |
+ eScrollArrow_StartBackward | eScrollArrow_StartForward,
+ // both arrows at top/left, none at bottom/right
+ eScrollArrowStyle_BothAtTop =
+ eScrollArrow_StartBackward | eScrollArrow_StartForward
+ };
+
+ enum { eScrollThumbStyle_Normal, eScrollThumbStyle_Proportional };
+
+ // When modifying this list, also modify nsXPLookAndFeel::sFloatPrefs
+ // in widget/nsXPLookAndFeel.cpp.
+ enum class FloatID {
+ IMEUnderlineRelativeSize,
+ SpellCheckerUnderlineRelativeSize,
+
+ // The width/height ratio of the cursor. If used, the CaretWidth int metric
+ // should be added to the calculated caret width.
+ CaretAspectRatio,
+
+ // Not an ID; used to define the range of valid IDs. Must be last.
+ End,
+ };
+
+ // These constants must be kept in 1:1 correspondence with the
+ // NS_STYLE_FONT_* system font constants.
+ enum class FontID {
+ Caption = 1, // css2
+ MINIMUM = Caption,
+ Icon,
+ Menu,
+ MessageBox,
+ SmallCaption,
+ StatusBar,
+
+ Window, // css3
+ Document,
+ Workspace,
+ Desktop,
+ Info,
+ Dialog,
+ Button,
+ PullDownMenu,
+ List,
+ Field,
+
+ Tooltips, // moz
+ Widget,
+ MAXIMUM = Widget,
+ };
+
+ /**
+ * GetColor() return a native color value (might be overwritten by prefs) for
+ * aID. Some platforms don't return an error even if the index doesn't
+ * match any system colors. And also some platforms may initialize the
+ * return value even when it returns an error. Therefore, if you want to
+ * use a color for the default value, you should use the other GetColor()
+ * which returns nscolor directly.
+ *
+ * NOTE:
+ * ColorID::TextSelectForeground might return NS_DONT_CHANGE_COLOR.
+ * ColorID::IME* might return NS_TRANSPARENT, NS_SAME_AS_FOREGROUND_COLOR or
+ * NS_40PERCENT_FOREGROUND_COLOR.
+ * These values have particular meaning. Then, they are not an actual
+ * color value.
+ */
+ static nsresult GetColor(ColorID aID, nscolor* aResult);
+
+ /**
+ * This variant of GetColor() takes an extra Boolean parameter that allows
+ * the caller to ask that hard-coded color values be substituted for
+ * native colors (used when it is desireable to hide system colors to
+ * avoid system fingerprinting).
+ */
+ static nsresult GetColor(ColorID aID, bool aUseStandinsForNativeColors,
+ nscolor* aResult);
+
+ /**
+ * GetInt() and GetFloat() return a int or float value for aID. The result
+ * might be distance, time, some flags or a int value which has particular
+ * meaning. See each document at definition of each ID for the detail.
+ * The result is always 0 when they return error. Therefore, if you want to
+ * use a value for the default value, you should use the other method which
+ * returns int or float directly.
+ */
+ static nsresult GetInt(IntID aID, int32_t* aResult);
+ static nsresult GetFloat(FloatID aID, float* aResult);
+
+ static nscolor GetColor(ColorID aID, nscolor aDefault = NS_RGB(0, 0, 0)) {
+ nscolor result = NS_RGB(0, 0, 0);
+ if (NS_FAILED(GetColor(aID, &result))) {
+ return aDefault;
+ }
+ return result;
+ }
+
+ static nscolor GetColorUsingStandins(ColorID aID,
+ nscolor aDefault = NS_RGB(0, 0, 0)) {
+ nscolor result = NS_RGB(0, 0, 0);
+ if (NS_FAILED(GetColor(aID,
+ true, // aUseStandinsForNativeColors
+ &result))) {
+ return aDefault;
+ }
+ return result;
+ }
+
+ static int32_t GetInt(IntID aID, int32_t aDefault = 0) {
+ int32_t result;
+ if (NS_FAILED(GetInt(aID, &result))) {
+ return aDefault;
+ }
+ return result;
+ }
+
+ static float GetFloat(FloatID aID, float aDefault = 0.0f) {
+ float result;
+ if (NS_FAILED(GetFloat(aID, &result))) {
+ return aDefault;
+ }
+ return result;
+ }
+
+ /**
+ * Retrieve the name and style of a system-theme font. Returns true
+ * if the system theme specifies this font, false if a default should
+ * be used. In the latter case neither aName nor aStyle is modified.
+ *
+ * Size of the font should be in CSS pixels, not device pixels.
+ *
+ * @param aID Which system-theme font is wanted.
+ * @param aName The name of the font to use.
+ * @param aStyle Styling to apply to the font.
+ */
+ static bool GetFont(FontID aID, nsString& aName, gfxFontStyle& aStyle);
+
+ /**
+ * GetPasswordCharacter() returns a unicode character which should be used
+ * for a masked character in password editor. E.g., '*'.
+ */
+ static char16_t GetPasswordCharacter();
+
+ /**
+ * If the latest character in password field shouldn't be hidden by the
+ * result of GetPasswordCharacter(), GetEchoPassword() returns TRUE.
+ * Otherwise, FALSE.
+ */
+ static bool GetEchoPassword();
+
+ /**
+ * The millisecond to mask password value.
+ * This value is only valid when GetEchoPassword() returns true.
+ */
+ static uint32_t GetPasswordMaskDelay();
+
+ /**
+ * When system look and feel is changed, Refresh() must be called. Then,
+ * cached data would be released.
+ */
+ static void Refresh();
+
+ /**
+ * GTK's initialization code can't be run off main thread, call this
+ * if you plan on using LookAndFeel off main thread later.
+ *
+ * This initialized state may get reset due to theme changes, so it
+ * must be called prior to each potential off-main-thread LookAndFeel
+ * call, not just once.
+ */
+ static void NativeInit();
+
+ /**
+ * If the implementation is caching values, these accessors allow the
+ * cache to be exported and imported.
+ */
+ static widget::LookAndFeelCache GetCache();
+ static void SetCache(const widget::LookAndFeelCache& aCache);
+ static void SetData(widget::FullLookAndFeel&& aTables);
+ static void NotifyChangedAllWindows(widget::ThemeChangeKind);
+};
+
+} // namespace mozilla
+
+// On the Mac, GetColor(ColorID::TextSelectForeground, color) returns this
+// constant to specify that the foreground color should not be changed
+// (ie. a colored text keeps its colors when selected).
+// Of course if other plaforms work like the Mac, they can use it too.
+#define NS_DONT_CHANGE_COLOR NS_RGB(0x01, 0x01, 0x01)
+
+// Similar with NS_DONT_CHANGE_COLOR, except NS_DONT_CHANGE_COLOR would returns
+// complementary color if fg color is same as bg color.
+// NS_CHANGE_COLOR_IF_SAME_AS_BG would returns
+// ColorID::TextSelectForegroundCustom if fg and bg color are the same.
+#define NS_CHANGE_COLOR_IF_SAME_AS_BG NS_RGB(0x02, 0x02, 0x02)
+
+// ---------------------------------------------------------------------
+// Special colors for ColorID::IME* and ColorID::SpellCheckerUnderline
+// ---------------------------------------------------------------------
+
+// For background color only.
+#define NS_TRANSPARENT NS_RGBA(0x01, 0x00, 0x00, 0x00)
+// For foreground color only.
+#define NS_SAME_AS_FOREGROUND_COLOR NS_RGBA(0x02, 0x00, 0x00, 0x00)
+#define NS_40PERCENT_FOREGROUND_COLOR NS_RGBA(0x03, 0x00, 0x00, 0x00)
+
+#define NS_IS_SELECTION_SPECIAL_COLOR(c) \
+ ((c) == NS_TRANSPARENT || (c) == NS_SAME_AS_FOREGROUND_COLOR || \
+ (c) == NS_40PERCENT_FOREGROUND_COLOR)
+
+// ------------------------------------------
+// Bits for IntID::AlertNotificationOrigin
+// ------------------------------------------
+
+#define NS_ALERT_HORIZONTAL 1
+#define NS_ALERT_LEFT 2
+#define NS_ALERT_TOP 4
+
+#endif /* __LookAndFeel */
diff --git a/widget/LookAndFeelTypes.ipdlh b/widget/LookAndFeelTypes.ipdlh
new file mode 100644
index 0000000000..dad93e0cf3
--- /dev/null
+++ b/widget/LookAndFeelTypes.ipdlh
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+using mozilla::LookAndFeel::IntID from "mozilla/widget/WidgetMessageUtils.h";
+using mozilla::LookAndFeel::ColorID from "mozilla/widget/WidgetMessageUtils.h";
+using nscolor from "nsColor.h";
+
+namespace mozilla {
+namespace widget {
+
+struct LookAndFeelInt {
+ IntID id;
+ int32_t value;
+};
+
+comparable struct LookAndFeelFont {
+ bool haveFont;
+ nsString name;
+ float size;
+ float weight;
+ bool italic;
+};
+
+struct LookAndFeelColor {
+ ColorID id;
+ nscolor color;
+};
+
+struct LookAndFeelCache {
+ LookAndFeelInt[] mInts;
+ LookAndFeelFont[] mFonts;
+ LookAndFeelColor[] mColors;
+};
+
+/**
+ * The format allows for some compression compared with having fixed
+ * length arrays for each value type and some indication of whether
+ * a value is present. This is because not all values are present on
+ * a given platform, and because there is also substantial repetition
+ * of specific values.
+ *
+ * Each of ints, floats, colors, and fonts is an array that stores the
+ * unique values that occur in the LookAndFeel. intMap, floatMap,
+ * colorMap, and fontMap map from value IDs (LookAndFeel::IntID, etc.)
+ * to indexes into the value arrays. The map arrays are of fixed
+ * length, determined by the maximum ID value. If a value for a
+ * particular ID is not present, the entry in the map is set to -1.
+ *
+ * (Note that fontMap is different from the others since it maps from a
+ * LookAndFeel::FontID value minus 1, as 1 is the minimum value of that
+ * enum.)
+ */
+struct LookAndFeelTables {
+ int32_t[] ints;
+ float[] floats;
+ nscolor[] colors;
+ LookAndFeelFont[] fonts;
+
+ uint8_t[] intMap;
+ uint8_t[] floatMap;
+ uint8_t[] colorMap;
+ uint8_t[] fontMap;
+
+ uint16_t passwordChar;
+ bool passwordEcho;
+};
+
+struct LookAndFeelTheme {
+#ifdef MOZ_WIDGET_GTK
+ nsCString themeName;
+ bool preferDarkTheme;
+#endif
+};
+
+/**
+ * Stores the entirety of a LookAndFeel's data.
+ */
+struct FullLookAndFeel {
+ LookAndFeelTables tables;
+#ifdef MOZ_WIDGET_GTK
+ LookAndFeelTheme theme;
+#endif
+};
+
+union LookAndFeelData {
+ LookAndFeelCache;
+ FullLookAndFeel;
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/MediaKeysEventSourceFactory.h b/widget/MediaKeysEventSourceFactory.h
new file mode 100644
index 0000000000..6e913dde22
--- /dev/null
+++ b/widget/MediaKeysEventSourceFactory.h
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 WIDGET_MEDIAKEYSEVENTSOURCEFACTORY_H_
+#define WIDGET_MEDIAKEYSEVENTSOURCEFACTORY_H_
+
+namespace mozilla {
+namespace dom {
+class MediaControlKeySource;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+namespace widget {
+
+// This function declaration is used to create a media keys event source on
+// different platforms, each platform should have their own implementation.
+extern mozilla::dom::MediaControlKeySource* CreateMediaControlKeySource();
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/MiscEvents.h b/widget/MiscEvents.h
new file mode 100644
index 0000000000..38c270756e
--- /dev/null
+++ b/widget/MiscEvents.h
@@ -0,0 +1,142 @@
+/* -*- 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_MiscEvents_h__
+#define mozilla_MiscEvents_h__
+
+#include <stdint.h>
+
+#include "mozilla/BasicEvents.h"
+#include "nsCOMPtr.h"
+#include "nsAtom.h"
+#include "nsGkAtoms.h"
+#include "nsITransferable.h"
+
+namespace mozilla {
+
+namespace dom {
+class PBrowserParent;
+class PBrowserChild;
+} // namespace dom
+
+/******************************************************************************
+ * mozilla::WidgetContentCommandEvent
+ ******************************************************************************/
+
+class WidgetContentCommandEvent : public WidgetGUIEvent {
+ public:
+ virtual WidgetContentCommandEvent* AsContentCommandEvent() override {
+ return this;
+ }
+
+ WidgetContentCommandEvent(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget, bool aOnlyEnabledCheck = false)
+ : WidgetGUIEvent(aIsTrusted, aMessage, aWidget,
+ eContentCommandEventClass),
+ mOnlyEnabledCheck(aOnlyEnabledCheck),
+ mSucceeded(false),
+ mIsEnabled(false) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ // This event isn't an internal event of any DOM event.
+ NS_ASSERTION(!IsAllowedToDispatchDOMEvent(),
+ "WidgetQueryContentEvent needs to support Duplicate()");
+ MOZ_CRASH("WidgetQueryContentEvent doesn't support Duplicate()");
+ return nullptr;
+ }
+
+ // eContentCommandPasteTransferable
+ nsCOMPtr<nsITransferable> mTransferable; // [in]
+
+ // eContentCommandScroll
+ // for mScroll.mUnit
+ enum { eCmdScrollUnit_Line, eCmdScrollUnit_Page, eCmdScrollUnit_Whole };
+
+ struct ScrollInfo {
+ ScrollInfo()
+ : mAmount(0), mUnit(eCmdScrollUnit_Line), mIsHorizontal(false) {}
+
+ int32_t mAmount; // [in]
+ uint8_t mUnit; // [in]
+ bool mIsHorizontal; // [in]
+ } mScroll;
+
+ bool mOnlyEnabledCheck; // [in]
+
+ bool mSucceeded; // [out]
+ bool mIsEnabled; // [out]
+
+ void AssignContentCommandEventData(const WidgetContentCommandEvent& aEvent,
+ bool aCopyTargets) {
+ AssignGUIEventData(aEvent, aCopyTargets);
+
+ mScroll = aEvent.mScroll;
+ mOnlyEnabledCheck = aEvent.mOnlyEnabledCheck;
+ mSucceeded = aEvent.mSucceeded;
+ mIsEnabled = aEvent.mIsEnabled;
+ }
+};
+
+/******************************************************************************
+ * mozilla::WidgetCommandEvent
+ *
+ * This sends a command to chrome. If you want to request what is performed
+ * in focused content, you should use WidgetContentCommandEvent instead.
+ *
+ * XXX Should be |WidgetChromeCommandEvent|?
+ ******************************************************************************/
+
+class WidgetCommandEvent : public WidgetGUIEvent {
+ public:
+ virtual WidgetCommandEvent* AsCommandEvent() override { return this; }
+
+ protected:
+ WidgetCommandEvent(bool aIsTrusted, nsAtom* aEventType, nsAtom* aCommand,
+ nsIWidget* aWidget)
+ : WidgetGUIEvent(aIsTrusted, eUnidentifiedEvent, aWidget,
+ eCommandEventClass),
+ mCommand(aCommand) {
+ mSpecifiedEventType = aEventType;
+ }
+
+ public:
+ /**
+ * Constructor to initialize an app command. This is the only case to
+ * initialize this class as a command in C++ stack.
+ */
+ WidgetCommandEvent(bool aIsTrusted, nsAtom* aCommand, nsIWidget* aWidget)
+ : WidgetCommandEvent(aIsTrusted, nsGkAtoms::onAppCommand, aCommand,
+ aWidget) {}
+
+ /**
+ * Constructor to initialize as internal event of dom::CommandEvent.
+ */
+ WidgetCommandEvent() : WidgetCommandEvent(false, nullptr, nullptr, nullptr) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eCommandEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ WidgetCommandEvent* result =
+ new WidgetCommandEvent(false, mSpecifiedEventType, mCommand, nullptr);
+ result->AssignCommandEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ RefPtr<nsAtom> mCommand;
+
+ // XXX Not tested by test_assign_event_data.html
+ void AssignCommandEventData(const WidgetCommandEvent& aEvent,
+ bool aCopyTargets) {
+ AssignGUIEventData(aEvent, aCopyTargets);
+
+ // mCommand must have been initialized with the constructor.
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_MiscEvents_h__
diff --git a/widget/MouseEvents.h b/widget/MouseEvents.h
new file mode 100644
index 0000000000..3b07cce480
--- /dev/null
+++ b/widget/MouseEvents.h
@@ -0,0 +1,703 @@
+/* -*- 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_MouseEvents_h__
+#define mozilla_MouseEvents_h__
+
+#include <stdint.h>
+
+#include "mozilla/BasicEvents.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/dom/DataTransfer.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+
+namespace dom {
+class PBrowserParent;
+class PBrowserChild;
+class PBrowserBridgeParent;
+} // namespace dom
+
+class WidgetPointerEvent;
+} // namespace mozilla
+
+namespace mozilla {
+class WidgetPointerEventHolder final {
+ public:
+ nsTArray<WidgetPointerEvent> mEvents;
+ NS_INLINE_DECL_REFCOUNTING(WidgetPointerEventHolder)
+
+ private:
+ virtual ~WidgetPointerEventHolder() = default;
+};
+
+/******************************************************************************
+ * mozilla::WidgetPointerHelper
+ ******************************************************************************/
+
+class WidgetPointerHelper {
+ public:
+ uint32_t pointerId;
+ uint32_t tiltX;
+ uint32_t tiltY;
+ uint32_t twist;
+ float tangentialPressure;
+ bool convertToPointer;
+ RefPtr<WidgetPointerEventHolder> mCoalescedWidgetEvents;
+
+ WidgetPointerHelper()
+ : pointerId(0),
+ tiltX(0),
+ tiltY(0),
+ twist(0),
+ tangentialPressure(0),
+ convertToPointer(true) {}
+
+ WidgetPointerHelper(uint32_t aPointerId, uint32_t aTiltX, uint32_t aTiltY,
+ uint32_t aTwist = 0, float aTangentialPressure = 0)
+ : pointerId(aPointerId),
+ tiltX(aTiltX),
+ tiltY(aTiltY),
+ twist(aTwist),
+ tangentialPressure(aTangentialPressure),
+ convertToPointer(true) {}
+
+ explicit WidgetPointerHelper(const WidgetPointerHelper& aHelper) = default;
+
+ void AssignPointerHelperData(const WidgetPointerHelper& aEvent,
+ bool aCopyCoalescedEvents = false) {
+ pointerId = aEvent.pointerId;
+ tiltX = aEvent.tiltX;
+ tiltY = aEvent.tiltY;
+ twist = aEvent.twist;
+ tangentialPressure = aEvent.tangentialPressure;
+ convertToPointer = aEvent.convertToPointer;
+ if (aCopyCoalescedEvents) {
+ mCoalescedWidgetEvents = aEvent.mCoalescedWidgetEvents;
+ }
+ }
+};
+
+/******************************************************************************
+ * mozilla::WidgetMouseEventBase
+ ******************************************************************************/
+
+class WidgetMouseEventBase : public WidgetInputEvent {
+ private:
+ friend class dom::PBrowserParent;
+ friend class dom::PBrowserChild;
+ friend class dom::PBrowserBridgeParent;
+
+ protected:
+ WidgetMouseEventBase()
+ : mPressure(0),
+ mButton(0),
+ mButtons(0),
+ mInputSource(/* MouseEvent_Binding::MOZ_SOURCE_MOUSE = */ 1) {}
+ // Including MouseEventBinding.h here leads to an include loop, so
+ // we have to hardcode MouseEvent_Binding::MOZ_SOURCE_MOUSE.
+
+ WidgetMouseEventBase(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget, EventClassID aEventClassID)
+ : WidgetInputEvent(aIsTrusted, aMessage, aWidget, aEventClassID),
+ mPressure(0),
+ mButton(0),
+ mButtons(0),
+ mInputSource(/* MouseEvent_Binding::MOZ_SOURCE_MOUSE = */ 1) {}
+ // Including MouseEventBinding.h here leads to an include loop, so
+ // we have to hardcode MouseEvent_Binding::MOZ_SOURCE_MOUSE.
+
+ public:
+ virtual WidgetMouseEventBase* AsMouseEventBase() override { return this; }
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_CRASH("WidgetMouseEventBase must not be most-subclass");
+ }
+
+ // ID of the canvas HitRegion
+ nsString mRegion;
+
+ // Finger or touch pressure of event. It ranges between 0.0 and 1.0.
+ float mPressure;
+
+ // Pressed button ID of mousedown or mouseup event.
+ // This is set only when pressing a button causes the event.
+ int16_t mButton;
+
+ // Flags of all pressed buttons at the event fired.
+ // This is set at any mouse event, don't be confused with |mButton|.
+ int16_t mButtons;
+
+ // Possible values a in MouseEvent
+ uint16_t mInputSource;
+
+ bool IsLeftButtonPressed() const {
+ return !!(mButtons & MouseButtonsFlag::ePrimaryFlag);
+ }
+ bool IsRightButtonPressed() const {
+ return !!(mButtons & MouseButtonsFlag::eSecondaryFlag);
+ }
+ bool IsMiddleButtonPressed() const {
+ return !!(mButtons & MouseButtonsFlag::eMiddleFlag);
+ }
+ bool Is4thButtonPressed() const {
+ return !!(mButtons & MouseButtonsFlag::e4thFlag);
+ }
+ bool Is5thButtonPressed() const {
+ return !!(mButtons & MouseButtonsFlag::e5thFlag);
+ }
+
+ void AssignMouseEventBaseData(const WidgetMouseEventBase& aEvent,
+ bool aCopyTargets) {
+ AssignInputEventData(aEvent, aCopyTargets);
+
+ mButton = aEvent.mButton;
+ mButtons = aEvent.mButtons;
+ mPressure = aEvent.mPressure;
+ mInputSource = aEvent.mInputSource;
+ }
+
+ /**
+ * Returns true if left click event.
+ */
+ bool IsLeftClickEvent() const {
+ return mMessage == eMouseClick && mButton == MouseButton::ePrimary;
+ }
+};
+
+/******************************************************************************
+ * mozilla::WidgetMouseEvent
+ ******************************************************************************/
+
+class WidgetMouseEvent : public WidgetMouseEventBase,
+ public WidgetPointerHelper {
+ private:
+ friend class dom::PBrowserParent;
+ friend class dom::PBrowserChild;
+ friend class dom::PBrowserBridgeParent;
+
+ public:
+ typedef bool ReasonType;
+ enum Reason : ReasonType { eReal, eSynthesized };
+
+ typedef uint8_t ContextMenuTriggerType;
+ enum ContextMenuTrigger : ContextMenuTriggerType {
+ eNormal,
+ eContextMenuKey,
+ eControlClick
+ };
+
+ typedef uint8_t ExitFromType;
+ enum ExitFrom : ExitFromType {
+ ePlatformChild,
+ ePlatformTopLevel,
+ ePuppet,
+ ePuppetParentToPuppetChild
+ };
+
+ protected:
+ WidgetMouseEvent()
+ : mReason(eReal),
+ mContextMenuTrigger(eNormal),
+ mIgnoreRootScrollFrame(false),
+ mClickCount(0),
+ mUseLegacyNonPrimaryDispatch(false) {}
+
+ WidgetMouseEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget,
+ EventClassID aEventClassID, Reason aReason)
+ : WidgetMouseEventBase(aIsTrusted, aMessage, aWidget, aEventClassID),
+ mReason(aReason),
+ mContextMenuTrigger(eNormal),
+ mIgnoreRootScrollFrame(false),
+ mClickCount(0),
+ mUseLegacyNonPrimaryDispatch(false) {}
+
+ public:
+ virtual WidgetMouseEvent* AsMouseEvent() override { return this; }
+
+ WidgetMouseEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget,
+ Reason aReason,
+ ContextMenuTrigger aContextMenuTrigger = eNormal)
+ : WidgetMouseEventBase(aIsTrusted, aMessage, aWidget, eMouseEventClass),
+ mReason(aReason),
+ mContextMenuTrigger(aContextMenuTrigger),
+ mIgnoreRootScrollFrame(false),
+ mClickCount(0),
+ mUseLegacyNonPrimaryDispatch(false) {
+ if (aMessage == eContextMenu) {
+ mButton = (mContextMenuTrigger == eNormal) ? MouseButton::eSecondary
+ : MouseButton::ePrimary;
+ }
+ }
+
+#ifdef DEBUG
+ virtual ~WidgetMouseEvent() {
+ NS_WARNING_ASSERTION(
+ mMessage != eContextMenu ||
+ (mButton == ((mContextMenuTrigger == eNormal)
+ ? MouseButton::eSecondary
+ : MouseButton::ePrimary) &&
+ (mContextMenuTrigger != eControlClick || IsControl())),
+ "Wrong button set to eContextMenu event?");
+ }
+#endif
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eMouseEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ WidgetMouseEvent* result = new WidgetMouseEvent(
+ false, mMessage, nullptr, mReason, mContextMenuTrigger);
+ result->AssignMouseEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ // If during mouseup handling we detect that click event might need to be
+ // dispatched, this is setup to be the target of the click event.
+ nsCOMPtr<dom::EventTarget> mClickTarget;
+
+ // mReason indicates the reason why the event is fired:
+ // - Representing mouse operation.
+ // - Synthesized for emulating mousemove event when the content under the
+ // mouse cursor is scrolled.
+ Reason mReason;
+
+ // mContextMenuTrigger is valid only when mMessage is eContextMenu.
+ // This indicates if the context menu event is caused by context menu key or
+ // other reasons (typically, a click of right mouse button).
+ ContextMenuTrigger mContextMenuTrigger;
+
+ // mExitFrom contains a value only when mMessage is eMouseExitFromWidget.
+ // This indicates if the mouse cursor exits from a top level platform widget,
+ // a child widget or a puppet widget.
+ Maybe<ExitFrom> mExitFrom;
+
+ // Whether the event should ignore scroll frame bounds during dispatch.
+ bool mIgnoreRootScrollFrame;
+
+ // mClickCount may be non-zero value when mMessage is eMouseDown, eMouseUp,
+ // eMouseClick or eMouseDoubleClick. The number is count of mouse clicks.
+ // Otherwise, this must be 0.
+ uint32_t mClickCount;
+
+ // Indicates whether the event should dispatch click events for non-primary
+ // mouse buttons on window and document.
+ bool mUseLegacyNonPrimaryDispatch;
+
+ void AssignMouseEventData(const WidgetMouseEvent& aEvent, bool aCopyTargets) {
+ AssignMouseEventBaseData(aEvent, aCopyTargets);
+ AssignPointerHelperData(aEvent, /* aCopyCoalescedEvents */ true);
+
+ mExitFrom = aEvent.mExitFrom;
+ mIgnoreRootScrollFrame = aEvent.mIgnoreRootScrollFrame;
+ mClickCount = aEvent.mClickCount;
+ mUseLegacyNonPrimaryDispatch = aEvent.mUseLegacyNonPrimaryDispatch;
+ }
+
+ /**
+ * Returns true if the event is a context menu event caused by key.
+ */
+ bool IsContextMenuKeyEvent() const {
+ return mMessage == eContextMenu && mContextMenuTrigger == eContextMenuKey;
+ }
+
+ /**
+ * Returns true if the event is a real mouse event. Otherwise, i.e., it's
+ * a synthesized event by scroll or something, returns false.
+ */
+ bool IsReal() const { return mReason == eReal; }
+
+ /**
+ * Returns true if middle click paste is enabled.
+ */
+ static bool IsMiddleClickPasteEnabled();
+};
+
+/******************************************************************************
+ * mozilla::WidgetDragEvent
+ ******************************************************************************/
+
+class WidgetDragEvent : public WidgetMouseEvent {
+ private:
+ friend class mozilla::dom::PBrowserParent;
+ friend class mozilla::dom::PBrowserChild;
+
+ protected:
+ WidgetDragEvent()
+ : mUserCancelled(false), mDefaultPreventedOnContent(false) {}
+
+ public:
+ virtual WidgetDragEvent* AsDragEvent() override { return this; }
+
+ WidgetDragEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget)
+ : WidgetMouseEvent(aIsTrusted, aMessage, aWidget, eDragEventClass, eReal),
+ mUserCancelled(false),
+ mDefaultPreventedOnContent(false) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eDragEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ WidgetDragEvent* result = new WidgetDragEvent(false, mMessage, nullptr);
+ result->AssignDragEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ // The dragging data.
+ nsCOMPtr<dom::DataTransfer> mDataTransfer;
+
+ // If this is true, user has cancelled the drag operation.
+ bool mUserCancelled;
+ // If this is true, the drag event's preventDefault() is called on content.
+ bool mDefaultPreventedOnContent;
+
+ // XXX Not tested by test_assign_event_data.html
+ void AssignDragEventData(const WidgetDragEvent& aEvent, bool aCopyTargets) {
+ AssignMouseEventData(aEvent, aCopyTargets);
+
+ mDataTransfer = aEvent.mDataTransfer;
+ // XXX mUserCancelled isn't copied, is this intentionally?
+ mUserCancelled = false;
+ mDefaultPreventedOnContent = aEvent.mDefaultPreventedOnContent;
+ }
+
+ /**
+ * Should be called before dispatching the DOM tree if this event is
+ * synthesized for tests because drop effect is initialized before
+ * dispatching from widget if it's not synthesized event, but synthesized
+ * events are not initialized in the path.
+ */
+ void InitDropEffectForTests();
+};
+
+/******************************************************************************
+ * mozilla::WidgetMouseScrollEvent
+ *
+ * This is used for legacy DOM mouse scroll events, i.e.,
+ * DOMMouseScroll and MozMousePixelScroll event. These events are NOT hanbled
+ * by ESM even if widget dispatches them. Use new WidgetWheelEvent instead.
+ ******************************************************************************/
+
+class WidgetMouseScrollEvent : public WidgetMouseEventBase {
+ private:
+ WidgetMouseScrollEvent() : mDelta(0), mIsHorizontal(false) {}
+
+ public:
+ virtual WidgetMouseScrollEvent* AsMouseScrollEvent() override { return this; }
+
+ WidgetMouseScrollEvent(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget)
+ : WidgetMouseEventBase(aIsTrusted, aMessage, aWidget,
+ eMouseScrollEventClass),
+ mDelta(0),
+ mIsHorizontal(false) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eMouseScrollEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ WidgetMouseScrollEvent* result =
+ new WidgetMouseScrollEvent(false, mMessage, nullptr);
+ result->AssignMouseScrollEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ // The delta value of mouse scroll event.
+ // If the event message is eLegacyMouseLineOrPageScroll, the value indicates
+ // scroll amount in lines. However, if the value is
+ // UIEvent::SCROLL_PAGE_UP or UIEvent::SCROLL_PAGE_DOWN, the
+ // value inducates one page scroll. If the event message is
+ // eLegacyMousePixelScroll, the value indicates scroll amount in pixels.
+ int32_t mDelta;
+
+ // If this is true, it may cause to scroll horizontally.
+ // Otherwise, vertically.
+ bool mIsHorizontal;
+
+ void AssignMouseScrollEventData(const WidgetMouseScrollEvent& aEvent,
+ bool aCopyTargets) {
+ AssignMouseEventBaseData(aEvent, aCopyTargets);
+
+ mDelta = aEvent.mDelta;
+ mIsHorizontal = aEvent.mIsHorizontal;
+ }
+};
+
+/******************************************************************************
+ * mozilla::WidgetWheelEvent
+ ******************************************************************************/
+
+class WidgetWheelEvent : public WidgetMouseEventBase {
+ private:
+ friend class mozilla::dom::PBrowserParent;
+ friend class mozilla::dom::PBrowserChild;
+
+ WidgetWheelEvent()
+ : mDeltaX(0.0),
+ mDeltaY(0.0),
+ mDeltaZ(0.0),
+ mOverflowDeltaX(0.0),
+ mOverflowDeltaY(0.0)
+ // Including WheelEventBinding.h here leads to an include loop, so
+ // we have to hardcode WheelEvent_Binding::DOM_DELTA_PIXEL.
+ ,
+ mDeltaMode(/* WheelEvent_Binding::DOM_DELTA_PIXEL = */ 0),
+ mLineOrPageDeltaX(0),
+ mLineOrPageDeltaY(0),
+ mScrollType(SCROLL_DEFAULT),
+ mCustomizedByUserPrefs(false),
+ mMayHaveMomentum(false),
+ mIsMomentum(false),
+ mIsNoLineOrPageDelta(false),
+ mViewPortIsOverscrolled(false),
+ mCanTriggerSwipe(false),
+ mAllowToOverrideSystemScrollSpeed(false),
+ mDeltaValuesHorizontalizedForDefaultHandler(false) {}
+
+ public:
+ virtual WidgetWheelEvent* AsWheelEvent() override { return this; }
+
+ WidgetWheelEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget)
+ : WidgetMouseEventBase(aIsTrusted, aMessage, aWidget, eWheelEventClass),
+ mDeltaX(0.0),
+ mDeltaY(0.0),
+ mDeltaZ(0.0),
+ mOverflowDeltaX(0.0),
+ mOverflowDeltaY(0.0)
+ // Including WheelEventBinding.h here leads to an include loop, so
+ // we have to hardcode WheelEvent_Binding::DOM_DELTA_PIXEL.
+ ,
+ mDeltaMode(/* WheelEvent_Binding::DOM_DELTA_PIXEL = */ 0),
+ mLineOrPageDeltaX(0),
+ mLineOrPageDeltaY(0),
+ mScrollType(SCROLL_DEFAULT),
+ mCustomizedByUserPrefs(false),
+ mMayHaveMomentum(false),
+ mIsMomentum(false),
+ mIsNoLineOrPageDelta(false),
+ mViewPortIsOverscrolled(false),
+ mCanTriggerSwipe(false),
+ mAllowToOverrideSystemScrollSpeed(true),
+ mDeltaValuesHorizontalizedForDefaultHandler(false) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eWheelEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ WidgetWheelEvent* result = new WidgetWheelEvent(false, mMessage, nullptr);
+ result->AssignWheelEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ // On OS X, scroll gestures that start at the edge of the scrollable range
+ // can result in a swipe gesture. For the first wheel event of such a
+ // gesture, call TriggersSwipe() after the event has been processed
+ // in order to find out whether a swipe should be started.
+ bool TriggersSwipe() const {
+ return mCanTriggerSwipe && mViewPortIsOverscrolled &&
+ this->mOverflowDeltaX != 0.0;
+ }
+
+ // NOTE: mDeltaX, mDeltaY and mDeltaZ may be customized by
+ // mousewheel.*.delta_multiplier_* prefs which are applied by
+ // EventStateManager. So, after widget dispatches this event,
+ // these delta values may have different values than before.
+ double mDeltaX;
+ double mDeltaY;
+ double mDeltaZ;
+
+ // overflowed delta values for scroll, these values are set by
+ // EventStateManger. If the default action of the wheel event isn't scroll,
+ // these values are always zero. Otherwise, remaining delta values which are
+ // not used by scroll are set.
+ // NOTE: mDeltaX, mDeltaY and mDeltaZ may be modified by EventStateManager.
+ // However, mOverflowDeltaX and mOverflowDeltaY indicate unused original
+ // delta values which are not applied the delta_multiplier prefs.
+ // So, if widget wanted to know the actual direction to be scrolled,
+ // it would need to check the mDeltaX and mDeltaY.
+ double mOverflowDeltaX;
+ double mOverflowDeltaY;
+
+ // Should be one of WheelEvent_Binding::DOM_DELTA_*
+ uint32_t mDeltaMode;
+
+ // If widget sets mLineOrPageDelta, EventStateManager will dispatch
+ // eLegacyMouseLineOrPageScroll event for compatibility. Note that the delta
+ // value means pages if the mDeltaMode is DOM_DELTA_PAGE, otherwise, lines.
+ int32_t mLineOrPageDeltaX;
+ int32_t mLineOrPageDeltaY;
+
+ // When the default action for an wheel event is moving history or zooming,
+ // need to chose a delta value for doing it.
+ int32_t GetPreferredIntDelta() {
+ if (!mLineOrPageDeltaX && !mLineOrPageDeltaY) {
+ return 0;
+ }
+ if (mLineOrPageDeltaY && !mLineOrPageDeltaX) {
+ return mLineOrPageDeltaY;
+ }
+ if (mLineOrPageDeltaX && !mLineOrPageDeltaY) {
+ return mLineOrPageDeltaX;
+ }
+ if ((mLineOrPageDeltaX < 0 && mLineOrPageDeltaY > 0) ||
+ (mLineOrPageDeltaX > 0 && mLineOrPageDeltaY < 0)) {
+ return 0; // We cannot guess the answer in this case.
+ }
+ return (Abs(mLineOrPageDeltaX) > Abs(mLineOrPageDeltaY))
+ ? mLineOrPageDeltaX
+ : mLineOrPageDeltaY;
+ }
+
+ // Scroll type
+ // The default value is SCROLL_DEFAULT, which means EventStateManager will
+ // select preferred scroll type automatically.
+ enum ScrollType : uint8_t {
+ SCROLL_DEFAULT,
+ SCROLL_SYNCHRONOUSLY,
+ SCROLL_ASYNCHRONOUSELY,
+ SCROLL_SMOOTHLY
+ };
+ ScrollType mScrollType;
+
+ // If the delta values are computed from prefs, this value is true.
+ // Otherwise, i.e., they are computed from native events, false.
+ bool mCustomizedByUserPrefs;
+
+ // true if the momentum events directly tied to this event may follow it.
+ bool mMayHaveMomentum;
+ // true if the event is caused by momentum.
+ bool mIsMomentum;
+
+ // If device event handlers don't know when they should set mLineOrPageDeltaX
+ // and mLineOrPageDeltaY, this is true. Otherwise, false.
+ // If mIsNoLineOrPageDelta is true, ESM will generate
+ // eLegacyMouseLineOrPageScroll events when accumulated delta values reach
+ // a line height.
+ bool mIsNoLineOrPageDelta;
+
+ // Whether or not the parent of the currently overscrolled frame is the
+ // ViewPort. This is false in situations when an element on the page is being
+ // overscrolled (such as a text field), but true when the 'page' is being
+ // overscrolled.
+ bool mViewPortIsOverscrolled;
+
+ // The wheel event can trigger a swipe to start if it's overscrolling the
+ // viewport.
+ bool mCanTriggerSwipe;
+
+ // If mAllowToOverrideSystemScrollSpeed is true, the scroll speed may be
+ // overridden. Otherwise, the scroll speed won't be overridden even if
+ // it's enabled by the pref.
+ bool mAllowToOverrideSystemScrollSpeed;
+
+ // After the event's default action handler has adjusted its delta's values
+ // for horizontalizing a vertical wheel scroll, this variable will be set to
+ // true.
+ bool mDeltaValuesHorizontalizedForDefaultHandler;
+
+ void AssignWheelEventData(const WidgetWheelEvent& aEvent, bool aCopyTargets) {
+ AssignMouseEventBaseData(aEvent, aCopyTargets);
+
+ mDeltaX = aEvent.mDeltaX;
+ mDeltaY = aEvent.mDeltaY;
+ mDeltaZ = aEvent.mDeltaZ;
+ mDeltaMode = aEvent.mDeltaMode;
+ mCustomizedByUserPrefs = aEvent.mCustomizedByUserPrefs;
+ mMayHaveMomentum = aEvent.mMayHaveMomentum;
+ mIsMomentum = aEvent.mIsMomentum;
+ mIsNoLineOrPageDelta = aEvent.mIsNoLineOrPageDelta;
+ mLineOrPageDeltaX = aEvent.mLineOrPageDeltaX;
+ mLineOrPageDeltaY = aEvent.mLineOrPageDeltaY;
+ mScrollType = aEvent.mScrollType;
+ mOverflowDeltaX = aEvent.mOverflowDeltaX;
+ mOverflowDeltaY = aEvent.mOverflowDeltaY;
+ mViewPortIsOverscrolled = aEvent.mViewPortIsOverscrolled;
+ mCanTriggerSwipe = aEvent.mCanTriggerSwipe;
+ mAllowToOverrideSystemScrollSpeed =
+ aEvent.mAllowToOverrideSystemScrollSpeed;
+ mDeltaValuesHorizontalizedForDefaultHandler =
+ aEvent.mDeltaValuesHorizontalizedForDefaultHandler;
+ }
+
+ // System scroll speed settings may be too slow at using Gecko. In such
+ // case, we should override the scroll speed computed with system settings.
+ // Following methods return preferred delta values which are multiplied by
+ // factors specified by prefs. If system scroll speed shouldn't be
+ // overridden (e.g., this feature is disabled by pref), they return raw
+ // delta values.
+ double OverriddenDeltaX() const;
+ double OverriddenDeltaY() const;
+
+ // Compute the overridden delta value. This may be useful for suppressing
+ // too fast scroll by system scroll speed overriding when widget sets
+ // mAllowToOverrideSystemScrollSpeed.
+ static double ComputeOverriddenDelta(double aDelta, bool aIsForVertical);
+
+ private:
+ static bool sInitialized;
+ static bool sIsSystemScrollSpeedOverrideEnabled;
+ static int32_t sOverrideFactorX;
+ static int32_t sOverrideFactorY;
+ static void Initialize();
+};
+
+/******************************************************************************
+ * mozilla::WidgetPointerEvent
+ ******************************************************************************/
+
+class WidgetPointerEvent : public WidgetMouseEvent {
+ friend class mozilla::dom::PBrowserParent;
+ friend class mozilla::dom::PBrowserChild;
+
+ WidgetPointerEvent() : mWidth(1), mHeight(1), mIsPrimary(true) {}
+
+ public:
+ virtual WidgetPointerEvent* AsPointerEvent() override { return this; }
+
+ WidgetPointerEvent(bool aIsTrusted, EventMessage aMsg, nsIWidget* w)
+ : WidgetMouseEvent(aIsTrusted, aMsg, w, ePointerEventClass, eReal),
+ mWidth(1),
+ mHeight(1),
+ mIsPrimary(true) {}
+
+ explicit WidgetPointerEvent(const WidgetMouseEvent& aEvent)
+ : WidgetMouseEvent(aEvent), mWidth(1), mHeight(1), mIsPrimary(true) {
+ mClass = ePointerEventClass;
+ }
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == ePointerEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ WidgetPointerEvent* result =
+ new WidgetPointerEvent(false, mMessage, nullptr);
+ result->AssignPointerEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ uint32_t mWidth;
+ uint32_t mHeight;
+ bool mIsPrimary;
+
+ // XXX Not tested by test_assign_event_data.html
+ void AssignPointerEventData(const WidgetPointerEvent& aEvent,
+ bool aCopyTargets) {
+ AssignMouseEventData(aEvent, aCopyTargets);
+
+ mWidth = aEvent.mWidth;
+ mHeight = aEvent.mHeight;
+ mIsPrimary = aEvent.mIsPrimary;
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_MouseEvents_h__
diff --git a/widget/NativeKeyToDOMCodeName.h b/widget/NativeKeyToDOMCodeName.h
new file mode 100644
index 0000000000..80f89d414b
--- /dev/null
+++ b/widget/NativeKeyToDOMCodeName.h
@@ -0,0 +1,821 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 header file defines simple code mapping between native scancode or
+ * something and DOM code name index.
+ * You must define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX macro before include
+ * this.
+ *
+ * It must have two arguments, (aNativeKey, aCodeNameIndex).
+ * aNativeKey is a scancode value or something (depends on the platform).
+ * aCodeNameIndex is the widget::CodeNameIndex value.
+ */
+
+// Windows
+#define CODE_MAP_WIN(aCPPCodeName, aNativeKey)
+// Mac OS X
+#define CODE_MAP_MAC(aCPPCodeName, aNativeKey)
+// GTK and Qt on Linux
+#define CODE_MAP_X11(aCPPCodeName, aNativeKey)
+// Android
+#define CODE_MAP_ANDROID(aCPPCodeName, aNativeKey)
+
+#if defined(XP_WIN)
+# undef CODE_MAP_WIN
+// aNativeKey is scan code
+# define CODE_MAP_WIN(aCPPCodeName, aNativeKey) \
+ NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, \
+ CODE_NAME_INDEX_##aCPPCodeName)
+#elif defined(XP_MACOSX)
+# undef CODE_MAP_MAC
+// aNativeKey is key code starting with kVK_.
+# define CODE_MAP_MAC(aCPPCodeName, aNativeKey) \
+ NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, \
+ CODE_NAME_INDEX_##aCPPCodeName)
+#elif defined(MOZ_WIDGET_GTK)
+# undef CODE_MAP_X11
+// aNativeKey is hardware_keycode of GDKEvent or nativeScanCode of QKeyEvent.
+# define CODE_MAP_X11(aCPPCodeName, aNativeKey) \
+ NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, \
+ CODE_NAME_INDEX_##aCPPCodeName)
+#elif defined(ANDROID)
+# undef CODE_MAP_ANDROID
+// aNativeKey is scan code
+# define CODE_MAP_ANDROID(aCPPCodeName, aNativeKey) \
+ NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, \
+ CODE_NAME_INDEX_##aCPPCodeName)
+#endif
+
+// Writing system keys
+CODE_MAP_WIN(Backquote, 0x0029)
+CODE_MAP_MAC(Backquote, kVK_ANSI_Grave)
+CODE_MAP_X11(Backquote, 0x0031)
+CODE_MAP_ANDROID(Backquote, 0x0029)
+
+CODE_MAP_WIN(Backslash, 0x002B)
+CODE_MAP_MAC(Backslash, kVK_ANSI_Backslash)
+CODE_MAP_X11(Backslash, 0x0033)
+CODE_MAP_ANDROID(Backslash, 0x002B)
+
+CODE_MAP_WIN(Backspace, 0x000E)
+CODE_MAP_MAC(Backspace, kVK_Delete)
+CODE_MAP_X11(Backspace, 0x0016)
+CODE_MAP_ANDROID(Backspace, 0x000E)
+
+CODE_MAP_WIN(BracketLeft, 0x001A)
+CODE_MAP_MAC(BracketLeft, kVK_ANSI_LeftBracket)
+CODE_MAP_X11(BracketLeft, 0x0022)
+CODE_MAP_ANDROID(BracketLeft, 0x001A)
+
+CODE_MAP_WIN(BracketRight, 0x001B)
+CODE_MAP_MAC(BracketRight, kVK_ANSI_RightBracket)
+CODE_MAP_X11(BracketRight, 0x0023)
+CODE_MAP_ANDROID(BracketRight, 0x001B)
+
+CODE_MAP_WIN(Comma, 0x0033)
+CODE_MAP_MAC(Comma, kVK_ANSI_Comma)
+CODE_MAP_X11(Comma, 0x003B)
+CODE_MAP_ANDROID(Comma, 0x00033)
+
+CODE_MAP_WIN(Digit0, 0x000B)
+CODE_MAP_MAC(Digit0, kVK_ANSI_0)
+CODE_MAP_X11(Digit0, 0x0013)
+CODE_MAP_ANDROID(Digit0, 0x000B)
+
+CODE_MAP_WIN(Digit1, 0x0002)
+CODE_MAP_MAC(Digit1, kVK_ANSI_1)
+CODE_MAP_X11(Digit1, 0x000A)
+CODE_MAP_ANDROID(Digit1, 0x0002)
+
+CODE_MAP_WIN(Digit2, 0x0003)
+CODE_MAP_MAC(Digit2, kVK_ANSI_2)
+CODE_MAP_X11(Digit2, 0x000B)
+CODE_MAP_ANDROID(Digit2, 0x0003)
+
+CODE_MAP_WIN(Digit3, 0x0004)
+CODE_MAP_MAC(Digit3, kVK_ANSI_3)
+CODE_MAP_X11(Digit3, 0x000C)
+CODE_MAP_ANDROID(Digit3, 0x0004)
+
+CODE_MAP_WIN(Digit4, 0x0005)
+CODE_MAP_MAC(Digit4, kVK_ANSI_4)
+CODE_MAP_X11(Digit4, 0x000D)
+CODE_MAP_ANDROID(Digit4, 0x0005)
+
+CODE_MAP_WIN(Digit5, 0x0006)
+CODE_MAP_MAC(Digit5, kVK_ANSI_5)
+CODE_MAP_X11(Digit5, 0x000E)
+CODE_MAP_ANDROID(Digit5, 0x0006)
+
+CODE_MAP_WIN(Digit6, 0x0007)
+CODE_MAP_MAC(Digit6, kVK_ANSI_6)
+CODE_MAP_X11(Digit6, 0x000F)
+CODE_MAP_ANDROID(Digit6, 0x0007)
+
+CODE_MAP_WIN(Digit7, 0x0008)
+CODE_MAP_MAC(Digit7, kVK_ANSI_7)
+CODE_MAP_X11(Digit7, 0x0010)
+CODE_MAP_ANDROID(Digit7, 0x0008)
+
+CODE_MAP_WIN(Digit8, 0x0009)
+CODE_MAP_MAC(Digit8, kVK_ANSI_8)
+CODE_MAP_X11(Digit8, 0x0011)
+CODE_MAP_ANDROID(Digit8, 0x0009)
+
+CODE_MAP_WIN(Digit9, 0x000A)
+CODE_MAP_MAC(Digit9, kVK_ANSI_9)
+CODE_MAP_X11(Digit9, 0x0012)
+CODE_MAP_ANDROID(Digit9, 0x000A)
+
+CODE_MAP_WIN(Equal, 0x000D)
+CODE_MAP_MAC(Equal, kVK_ANSI_Equal)
+CODE_MAP_X11(Equal, 0x0015)
+CODE_MAP_ANDROID(Equal, 0x000D)
+
+CODE_MAP_WIN(IntlBackslash, 0x0056)
+CODE_MAP_MAC(IntlBackslash, kVK_ISO_Section)
+CODE_MAP_X11(IntlBackslash, 0x005E)
+CODE_MAP_ANDROID(IntlBackslash, 0x0056)
+
+// Win: IntlHash's scan code is shared with "Backslash" key.
+// Mac: IntlHash's virtual key code is shared with "Backslash" key.
+// X11: IntlHash's scan code is shared with "Backslash" key.
+// Android: IntlHash's scan code is shared with "Backslash" key.
+
+CODE_MAP_WIN(IntlRo, 0x0073)
+CODE_MAP_MAC(IntlRo, kVK_JIS_Underscore)
+CODE_MAP_X11(IntlRo, 0x0061)
+CODE_MAP_ANDROID(IntlRo, 0x0059)
+
+CODE_MAP_WIN(IntlYen, 0x007D)
+CODE_MAP_MAC(IntlYen, kVK_JIS_Yen)
+CODE_MAP_X11(IntlYen, 0x0084)
+CODE_MAP_ANDROID(IntlYen, 0x007C)
+
+CODE_MAP_WIN(KeyA, 0x001E)
+CODE_MAP_MAC(KeyA, kVK_ANSI_A)
+CODE_MAP_X11(KeyA, 0x0026)
+CODE_MAP_ANDROID(KeyA, 0x001E)
+
+CODE_MAP_WIN(KeyB, 0x0030)
+CODE_MAP_MAC(KeyB, kVK_ANSI_B)
+CODE_MAP_X11(KeyB, 0x0038)
+CODE_MAP_ANDROID(KeyB, 0x0030)
+
+CODE_MAP_WIN(KeyC, 0x002E)
+CODE_MAP_MAC(KeyC, kVK_ANSI_C)
+CODE_MAP_X11(KeyC, 0x0036)
+CODE_MAP_ANDROID(KeyC, 0x002E)
+
+CODE_MAP_WIN(KeyD, 0x0020)
+CODE_MAP_MAC(KeyD, kVK_ANSI_D)
+CODE_MAP_X11(KeyD, 0x0028)
+CODE_MAP_ANDROID(KeyD, 0x0020)
+
+CODE_MAP_WIN(KeyE, 0x0012)
+CODE_MAP_MAC(KeyE, kVK_ANSI_E)
+CODE_MAP_X11(KeyE, 0x001A)
+CODE_MAP_ANDROID(KeyE, 0x0012)
+
+CODE_MAP_WIN(KeyF, 0x0021)
+CODE_MAP_MAC(KeyF, kVK_ANSI_F)
+CODE_MAP_X11(KeyF, 0x0029)
+CODE_MAP_ANDROID(KeyF, 0x0021)
+
+CODE_MAP_WIN(KeyG, 0x0022)
+CODE_MAP_MAC(KeyG, kVK_ANSI_G)
+CODE_MAP_X11(KeyG, 0x002A)
+CODE_MAP_ANDROID(KeyG, 0x0022)
+
+CODE_MAP_WIN(KeyH, 0x0023)
+CODE_MAP_MAC(KeyH, kVK_ANSI_H)
+CODE_MAP_X11(KeyH, 0x002B)
+CODE_MAP_ANDROID(KeyH, 0x0023)
+
+CODE_MAP_WIN(KeyI, 0x0017)
+CODE_MAP_MAC(KeyI, kVK_ANSI_I)
+CODE_MAP_X11(KeyI, 0x001F)
+CODE_MAP_ANDROID(KeyI, 0x0017)
+
+CODE_MAP_WIN(KeyJ, 0x0024)
+CODE_MAP_MAC(KeyJ, kVK_ANSI_J)
+CODE_MAP_X11(KeyJ, 0x002C)
+CODE_MAP_ANDROID(KeyJ, 0x0024)
+
+CODE_MAP_WIN(KeyK, 0x0025)
+CODE_MAP_MAC(KeyK, kVK_ANSI_K)
+CODE_MAP_X11(KeyK, 0x002D)
+CODE_MAP_ANDROID(KeyK, 0x0025)
+
+CODE_MAP_WIN(KeyL, 0x0026)
+CODE_MAP_MAC(KeyL, kVK_ANSI_L)
+CODE_MAP_X11(KeyL, 0x002E)
+CODE_MAP_ANDROID(KeyL, 0x0026)
+
+CODE_MAP_WIN(KeyM, 0x0032)
+CODE_MAP_MAC(KeyM, kVK_ANSI_M)
+CODE_MAP_X11(KeyM, 0x003A)
+CODE_MAP_ANDROID(KeyM, 0x0032)
+
+CODE_MAP_WIN(KeyN, 0x0031)
+CODE_MAP_MAC(KeyN, kVK_ANSI_N)
+CODE_MAP_X11(KeyN, 0x0039)
+CODE_MAP_ANDROID(KeyN, 0x0031)
+
+CODE_MAP_WIN(KeyO, 0x0018)
+CODE_MAP_MAC(KeyO, kVK_ANSI_O)
+CODE_MAP_X11(KeyO, 0x0020)
+CODE_MAP_ANDROID(KeyO, 0x0018)
+
+CODE_MAP_WIN(KeyP, 0x0019)
+CODE_MAP_MAC(KeyP, kVK_ANSI_P)
+CODE_MAP_X11(KeyP, 0x0021)
+CODE_MAP_ANDROID(KeyP, 0x0019)
+
+CODE_MAP_WIN(KeyQ, 0x0010)
+CODE_MAP_MAC(KeyQ, kVK_ANSI_Q)
+CODE_MAP_X11(KeyQ, 0x0018)
+CODE_MAP_ANDROID(KeyQ, 0x0010)
+
+CODE_MAP_WIN(KeyR, 0x0013)
+CODE_MAP_MAC(KeyR, kVK_ANSI_R)
+CODE_MAP_X11(KeyR, 0x001B)
+CODE_MAP_ANDROID(KeyR, 0x0013)
+
+CODE_MAP_WIN(KeyS, 0x001F)
+CODE_MAP_MAC(KeyS, kVK_ANSI_S)
+CODE_MAP_X11(KeyS, 0x0027)
+CODE_MAP_ANDROID(KeyS, 0x001F)
+
+CODE_MAP_WIN(KeyT, 0x0014)
+CODE_MAP_MAC(KeyT, kVK_ANSI_T)
+CODE_MAP_X11(KeyT, 0x001C)
+CODE_MAP_ANDROID(KeyT, 0x0014)
+
+CODE_MAP_WIN(KeyU, 0x0016)
+CODE_MAP_MAC(KeyU, kVK_ANSI_U)
+CODE_MAP_X11(KeyU, 0x001E)
+CODE_MAP_ANDROID(KeyU, 0x0016)
+
+CODE_MAP_WIN(KeyV, 0x002F)
+CODE_MAP_MAC(KeyV, kVK_ANSI_V)
+CODE_MAP_X11(KeyV, 0x0037)
+CODE_MAP_ANDROID(KeyV, 0x002F)
+
+CODE_MAP_WIN(KeyW, 0x0011)
+CODE_MAP_MAC(KeyW, kVK_ANSI_W)
+CODE_MAP_X11(KeyW, 0x0019)
+CODE_MAP_ANDROID(KeyW, 0x0011)
+
+CODE_MAP_WIN(KeyX, 0x002D)
+CODE_MAP_MAC(KeyX, kVK_ANSI_X)
+CODE_MAP_X11(KeyX, 0x0035)
+CODE_MAP_ANDROID(KeyX, 0x002D)
+
+CODE_MAP_WIN(KeyY, 0x0015)
+CODE_MAP_MAC(KeyY, kVK_ANSI_Y)
+CODE_MAP_X11(KeyY, 0x001D)
+CODE_MAP_ANDROID(KeyY, 0x0015)
+
+CODE_MAP_WIN(KeyZ, 0x002C)
+CODE_MAP_MAC(KeyZ, kVK_ANSI_Z)
+CODE_MAP_X11(KeyZ, 0x0034)
+CODE_MAP_ANDROID(KeyZ, 0x002C)
+
+CODE_MAP_WIN(Minus, 0x000C)
+CODE_MAP_MAC(Minus, kVK_ANSI_Minus)
+CODE_MAP_X11(Minus, 0x0014)
+CODE_MAP_ANDROID(Minus, 0x000C)
+
+CODE_MAP_WIN(Period, 0x0034)
+CODE_MAP_MAC(Period, kVK_ANSI_Period)
+CODE_MAP_X11(Period, 0x003C)
+CODE_MAP_ANDROID(Period, 0x0034)
+
+CODE_MAP_WIN(Quote, 0x0028)
+CODE_MAP_MAC(Quote, kVK_ANSI_Quote)
+CODE_MAP_X11(Quote, 0x0030)
+CODE_MAP_ANDROID(Quote, 0x0028)
+
+CODE_MAP_WIN(Semicolon, 0x0027)
+CODE_MAP_MAC(Semicolon, kVK_ANSI_Semicolon)
+CODE_MAP_X11(Semicolon, 0x002F)
+CODE_MAP_ANDROID(Semicolon, 0x0027)
+
+CODE_MAP_WIN(Slash, 0x0035)
+CODE_MAP_MAC(Slash, kVK_ANSI_Slash)
+CODE_MAP_X11(Slash, 0x003D)
+CODE_MAP_ANDROID(Slash, 0x0035)
+
+// Functional keys
+CODE_MAP_WIN(AltLeft, 0x0038)
+CODE_MAP_MAC(AltLeft, kVK_Option)
+CODE_MAP_X11(AltLeft, 0x0040)
+CODE_MAP_ANDROID(AltLeft, 0x0038)
+
+CODE_MAP_WIN(AltRight, 0xE038)
+CODE_MAP_MAC(AltRight, kVK_RightOption)
+CODE_MAP_X11(AltRight, 0x006C)
+CODE_MAP_ANDROID(AltRight, 0x0064)
+
+CODE_MAP_WIN(CapsLock, 0x003A)
+CODE_MAP_MAC(CapsLock, kVK_CapsLock)
+CODE_MAP_X11(CapsLock, 0x0042)
+CODE_MAP_ANDROID(CapsLock, 0x003A)
+
+CODE_MAP_WIN(ContextMenu, 0xE05D)
+CODE_MAP_MAC(ContextMenu, kVK_PC_ContextMenu)
+CODE_MAP_X11(ContextMenu, 0x0087)
+CODE_MAP_ANDROID(ContextMenu, 0x007F)
+
+CODE_MAP_WIN(ControlLeft, 0x001D)
+CODE_MAP_MAC(ControlLeft, kVK_Control)
+CODE_MAP_X11(ControlLeft, 0x0025)
+CODE_MAP_ANDROID(ControlLeft, 0x001D)
+
+CODE_MAP_WIN(ControlRight, 0xE01D)
+CODE_MAP_MAC(ControlRight, kVK_RightControl)
+CODE_MAP_X11(ControlRight, 0x0069)
+CODE_MAP_ANDROID(ControlRight, 0x0061)
+
+CODE_MAP_WIN(Enter, 0x001C)
+CODE_MAP_MAC(Enter, kVK_Return)
+CODE_MAP_X11(Enter, 0x0024)
+CODE_MAP_ANDROID(Enter, 0x001C)
+
+CODE_MAP_WIN(OSLeft, 0xE05B)
+CODE_MAP_MAC(OSLeft, kVK_Command)
+CODE_MAP_X11(OSLeft, 0x0085)
+CODE_MAP_ANDROID(OSLeft, 0x007D)
+
+CODE_MAP_WIN(OSRight, 0xE05C)
+CODE_MAP_MAC(OSRight, kVK_RightCommand)
+CODE_MAP_X11(OSRight, 0x0086)
+CODE_MAP_ANDROID(OSRight, 0x007E)
+
+CODE_MAP_WIN(ShiftLeft, 0x002A)
+CODE_MAP_MAC(ShiftLeft, kVK_Shift)
+CODE_MAP_X11(ShiftLeft, 0x0032)
+CODE_MAP_ANDROID(ShiftLeft, 0x002A)
+
+CODE_MAP_WIN(ShiftRight, 0x0036)
+CODE_MAP_MAC(ShiftRight, kVK_RightShift)
+CODE_MAP_X11(ShiftRight, 0x003E)
+CODE_MAP_ANDROID(ShiftRight, 0x0036)
+
+CODE_MAP_WIN(Space, 0x0039)
+CODE_MAP_MAC(Space, kVK_Space)
+CODE_MAP_X11(Space, 0x0041)
+CODE_MAP_ANDROID(Space, 0x0039)
+
+CODE_MAP_WIN(Tab, 0x000F)
+CODE_MAP_MAC(Tab, kVK_Tab)
+CODE_MAP_X11(Tab, 0x0017)
+CODE_MAP_ANDROID(Tab, 0x000F)
+
+// IME keys
+CODE_MAP_WIN(Convert, 0x0079)
+CODE_MAP_X11(Convert, 0x0064)
+CODE_MAP_ANDROID(Convert, 0x005C)
+
+CODE_MAP_WIN(Lang1, 0x0072) // for non-Korean layout
+CODE_MAP_WIN(Lang1, 0xE0F2) // for Korean layout
+CODE_MAP_MAC(Lang1, kVK_JIS_Kana)
+CODE_MAP_X11(Lang1, 0x0082)
+CODE_MAP_ANDROID(Lang1, 0x007A)
+
+CODE_MAP_WIN(Lang2, 0x0071) // for non-Korean layout
+CODE_MAP_WIN(Lang2, 0xE0F1) // for Korean layout
+CODE_MAP_MAC(Lang2, kVK_JIS_Eisu)
+CODE_MAP_X11(Lang2, 0x0083)
+CODE_MAP_ANDROID(Lang2, 0x007B)
+
+CODE_MAP_WIN(KanaMode, 0x0070)
+CODE_MAP_X11(KanaMode, 0x0065)
+CODE_MAP_ANDROID(KanaMode, 0x005D)
+
+CODE_MAP_WIN(NonConvert, 0x007B)
+CODE_MAP_X11(NonConvert, 0x0066)
+CODE_MAP_ANDROID(NonConvert, 0x005E)
+
+// Control pad section
+CODE_MAP_WIN(Delete, 0xE053)
+CODE_MAP_MAC(Delete, kVK_ForwardDelete)
+CODE_MAP_X11(Delete, 0x0077)
+CODE_MAP_ANDROID(Delete, 0x006F)
+
+CODE_MAP_WIN(End, 0xE04F)
+CODE_MAP_MAC(End, kVK_End)
+CODE_MAP_X11(End, 0x0073)
+CODE_MAP_ANDROID(End, 0x006B)
+
+CODE_MAP_MAC(Help, kVK_Help) // Insert key on PC keyboard
+CODE_MAP_X11(Help, 0x0092) // Help key on Sun keyboard
+CODE_MAP_ANDROID(Help, 0x008A) // Help key on Sun keyboard
+
+CODE_MAP_WIN(Home, 0xE047)
+CODE_MAP_MAC(Home, kVK_Home)
+CODE_MAP_X11(Home, 0x006E)
+CODE_MAP_ANDROID(Home, 0x0066)
+
+CODE_MAP_WIN(Insert, 0xE052)
+CODE_MAP_X11(Insert, 0x0076)
+CODE_MAP_ANDROID(Insert, 0x006E)
+
+CODE_MAP_WIN(PageDown, 0xE051)
+CODE_MAP_MAC(PageDown, kVK_PageDown)
+CODE_MAP_X11(PageDown, 0x0075)
+CODE_MAP_ANDROID(PageDown, 0x006D)
+
+CODE_MAP_WIN(PageUp, 0xE049)
+CODE_MAP_MAC(PageUp, kVK_PageUp)
+CODE_MAP_X11(PageUp, 0x0070)
+CODE_MAP_ANDROID(PageUp, 0x0068)
+
+// Arrow pad section
+CODE_MAP_WIN(ArrowDown, 0xE050)
+CODE_MAP_MAC(ArrowDown, kVK_DownArrow)
+CODE_MAP_X11(ArrowDown, 0x0074)
+CODE_MAP_ANDROID(ArrowDown, 0x006C)
+
+CODE_MAP_WIN(ArrowLeft, 0xE04B)
+CODE_MAP_MAC(ArrowLeft, kVK_LeftArrow)
+CODE_MAP_X11(ArrowLeft, 0x0071)
+CODE_MAP_ANDROID(ArrowLeft, 0x0069)
+
+CODE_MAP_WIN(ArrowRight, 0xE04D)
+CODE_MAP_MAC(ArrowRight, kVK_RightArrow)
+CODE_MAP_X11(ArrowRight, 0x0072)
+CODE_MAP_ANDROID(ArrowRight, 0x006A)
+
+CODE_MAP_WIN(ArrowUp, 0xE048)
+CODE_MAP_MAC(ArrowUp, kVK_UpArrow)
+CODE_MAP_X11(ArrowUp, 0x006F)
+CODE_MAP_ANDROID(ArrowUp, 0x0067)
+
+// Numpad section
+CODE_MAP_WIN(NumLock, 0xE045) // MSDN says 0x0045, though...
+CODE_MAP_MAC(NumLock, kVK_ANSI_KeypadClear)
+CODE_MAP_X11(NumLock, 0x004D)
+CODE_MAP_ANDROID(NumLock, 0x0045)
+
+CODE_MAP_WIN(Numpad0, 0x0052)
+CODE_MAP_MAC(Numpad0, kVK_ANSI_Keypad0)
+CODE_MAP_X11(Numpad0, 0x005A)
+CODE_MAP_ANDROID(Numpad0, 0x0052)
+
+CODE_MAP_WIN(Numpad1, 0x004F)
+CODE_MAP_MAC(Numpad1, kVK_ANSI_Keypad1)
+CODE_MAP_X11(Numpad1, 0x0057)
+CODE_MAP_ANDROID(Numpad1, 0x004F)
+
+CODE_MAP_WIN(Numpad2, 0x0050)
+CODE_MAP_MAC(Numpad2, kVK_ANSI_Keypad2)
+CODE_MAP_X11(Numpad2, 0x0058)
+CODE_MAP_ANDROID(Numpad2, 0x0050)
+
+CODE_MAP_WIN(Numpad3, 0x0051)
+CODE_MAP_MAC(Numpad3, kVK_ANSI_Keypad3)
+CODE_MAP_X11(Numpad3, 0x0059)
+CODE_MAP_ANDROID(Numpad3, 0x0051)
+
+CODE_MAP_WIN(Numpad4, 0x004B)
+CODE_MAP_MAC(Numpad4, kVK_ANSI_Keypad4)
+CODE_MAP_X11(Numpad4, 0x0053)
+CODE_MAP_ANDROID(Numpad4, 0x004B)
+
+CODE_MAP_WIN(Numpad5, 0x004C)
+CODE_MAP_MAC(Numpad5, kVK_ANSI_Keypad5)
+CODE_MAP_X11(Numpad5, 0x0054)
+CODE_MAP_ANDROID(Numpad5, 0x004C)
+
+CODE_MAP_WIN(Numpad6, 0x004D)
+CODE_MAP_MAC(Numpad6, kVK_ANSI_Keypad6)
+CODE_MAP_X11(Numpad6, 0x0055)
+CODE_MAP_ANDROID(Numpad6, 0x004D)
+
+CODE_MAP_WIN(Numpad7, 0x0047)
+CODE_MAP_MAC(Numpad7, kVK_ANSI_Keypad7)
+CODE_MAP_X11(Numpad7, 0x004F)
+CODE_MAP_ANDROID(Numpad7, 0x0047)
+
+CODE_MAP_WIN(Numpad8, 0x0048)
+CODE_MAP_MAC(Numpad8, kVK_ANSI_Keypad8)
+CODE_MAP_X11(Numpad8, 0x0050)
+CODE_MAP_ANDROID(Numpad8, 0x0048)
+
+CODE_MAP_WIN(Numpad9, 0x0049)
+CODE_MAP_MAC(Numpad9, kVK_ANSI_Keypad9)
+CODE_MAP_X11(Numpad9, 0x0051)
+CODE_MAP_ANDROID(Numpad9, 0x0049)
+
+CODE_MAP_WIN(NumpadAdd, 0x004E)
+CODE_MAP_MAC(NumpadAdd, kVK_ANSI_KeypadPlus)
+CODE_MAP_X11(NumpadAdd, 0x0056)
+CODE_MAP_ANDROID(NumpadAdd, 0x004E)
+
+CODE_MAP_WIN(NumpadComma, 0x007E)
+CODE_MAP_MAC(NumpadComma, kVK_JIS_KeypadComma)
+CODE_MAP_X11(NumpadComma, 0x0081)
+CODE_MAP_ANDROID(NumpadComma, 0x0079)
+
+CODE_MAP_WIN(NumpadDecimal, 0x0053)
+CODE_MAP_MAC(NumpadDecimal, kVK_ANSI_KeypadDecimal)
+CODE_MAP_X11(NumpadDecimal, 0x005B)
+CODE_MAP_ANDROID(NumpadDecimal, 0x0053)
+
+CODE_MAP_WIN(NumpadDivide, 0xE035)
+CODE_MAP_MAC(NumpadDivide, kVK_ANSI_KeypadDivide)
+CODE_MAP_X11(NumpadDivide, 0x006A)
+CODE_MAP_ANDROID(NumpadDivide, 0x0062)
+
+CODE_MAP_WIN(NumpadEnter, 0xE01C)
+CODE_MAP_MAC(NumpadEnter, kVK_ANSI_KeypadEnter)
+CODE_MAP_MAC(NumpadEnter, kVK_Powerbook_KeypadEnter)
+CODE_MAP_X11(NumpadEnter, 0x0068)
+CODE_MAP_ANDROID(NumpadEnter, 0x0060)
+
+CODE_MAP_WIN(NumpadEqual, 0x0059)
+CODE_MAP_MAC(NumpadEqual, kVK_ANSI_KeypadEquals)
+CODE_MAP_X11(NumpadEqual, 0x007D)
+CODE_MAP_ANDROID(NumpadEqual, 0x0075)
+
+CODE_MAP_WIN(NumpadMultiply, 0x0037)
+CODE_MAP_MAC(NumpadMultiply, kVK_ANSI_KeypadMultiply)
+CODE_MAP_X11(NumpadMultiply, 0x003F)
+CODE_MAP_ANDROID(NumpadMultiply, 0x0037)
+
+CODE_MAP_WIN(NumpadSubtract, 0x004A)
+CODE_MAP_MAC(NumpadSubtract, kVK_ANSI_KeypadMinus)
+CODE_MAP_X11(NumpadSubtract, 0x0052)
+CODE_MAP_ANDROID(NumpadSubtract, 0x004A)
+
+// Function section
+CODE_MAP_WIN(Escape, 0x0001)
+CODE_MAP_MAC(Escape, kVK_Escape)
+CODE_MAP_X11(Escape, 0x0009)
+CODE_MAP_ANDROID(Escape, 0x0001)
+
+CODE_MAP_WIN(F1, 0x003B)
+CODE_MAP_MAC(F1, kVK_F1)
+CODE_MAP_X11(F1, 0x0043)
+CODE_MAP_ANDROID(F1, 0x003B)
+
+CODE_MAP_WIN(F2, 0x003C)
+CODE_MAP_MAC(F2, kVK_F2)
+CODE_MAP_X11(F2, 0x0044)
+CODE_MAP_ANDROID(F2, 0x003C)
+
+CODE_MAP_WIN(F3, 0x003D)
+CODE_MAP_MAC(F3, kVK_F3)
+CODE_MAP_X11(F3, 0x0045)
+CODE_MAP_ANDROID(F3, 0x003D)
+
+CODE_MAP_WIN(F4, 0x003E)
+CODE_MAP_MAC(F4, kVK_F4)
+CODE_MAP_X11(F4, 0x0046)
+CODE_MAP_ANDROID(F4, 0x003E)
+
+CODE_MAP_WIN(F5, 0x003F)
+CODE_MAP_MAC(F5, kVK_F5)
+CODE_MAP_X11(F5, 0x0047)
+CODE_MAP_ANDROID(F5, 0x003F)
+
+CODE_MAP_WIN(F6, 0x0040)
+CODE_MAP_MAC(F6, kVK_F6)
+CODE_MAP_X11(F6, 0x0048)
+CODE_MAP_ANDROID(F6, 0x0040)
+
+CODE_MAP_WIN(F7, 0x0041)
+CODE_MAP_MAC(F7, kVK_F7)
+CODE_MAP_X11(F7, 0x0049)
+CODE_MAP_ANDROID(F7, 0x0041)
+
+CODE_MAP_WIN(F8, 0x0042)
+CODE_MAP_MAC(F8, kVK_F8)
+CODE_MAP_X11(F8, 0x004A)
+CODE_MAP_ANDROID(F8, 0x0042)
+
+CODE_MAP_WIN(F9, 0x0043)
+CODE_MAP_MAC(F9, kVK_F9)
+CODE_MAP_X11(F9, 0x004B)
+CODE_MAP_ANDROID(F9, 0x0043)
+
+CODE_MAP_WIN(F10, 0x0044)
+CODE_MAP_MAC(F10, kVK_F10)
+CODE_MAP_X11(F10, 0x004C)
+CODE_MAP_ANDROID(F10, 0x0044)
+
+CODE_MAP_WIN(F11, 0x0057)
+CODE_MAP_MAC(F11, kVK_F11)
+CODE_MAP_X11(F11, 0x005F)
+CODE_MAP_ANDROID(F11, 0x0057)
+
+CODE_MAP_WIN(F12, 0x0058)
+CODE_MAP_MAC(F12, kVK_F12)
+CODE_MAP_X11(F12, 0x0060)
+CODE_MAP_ANDROID(F12, 0x0058)
+
+CODE_MAP_WIN(F13, 0x0064)
+CODE_MAP_MAC(F13, kVK_F13) // PrintScreen on PC keyboard
+CODE_MAP_X11(F13, 0x00BF)
+CODE_MAP_ANDROID(F13, 0x00B7)
+
+CODE_MAP_WIN(F14, 0x0065)
+CODE_MAP_MAC(F14, kVK_F14) // ScrollLock on PC keyboard
+CODE_MAP_X11(F14, 0x00C0)
+CODE_MAP_ANDROID(F14, 0x00B8)
+
+CODE_MAP_WIN(F15, 0x0066)
+CODE_MAP_MAC(F15, kVK_F15) // Pause on PC keyboard
+CODE_MAP_X11(F15, 0x00C1)
+CODE_MAP_ANDROID(F15, 0x00B9)
+
+CODE_MAP_WIN(F16, 0x0067)
+CODE_MAP_MAC(F16, kVK_F16)
+CODE_MAP_X11(F16, 0x00C2)
+CODE_MAP_ANDROID(F16, 0x00BA)
+
+CODE_MAP_WIN(F17, 0x0068)
+CODE_MAP_MAC(F17, kVK_F17)
+CODE_MAP_X11(F17, 0x00C3)
+CODE_MAP_ANDROID(F17, 0x00BB)
+
+CODE_MAP_WIN(F18, 0x0069)
+CODE_MAP_MAC(F18, kVK_F18)
+CODE_MAP_X11(F18, 0x00C4)
+CODE_MAP_ANDROID(F18, 0x00BC)
+
+CODE_MAP_WIN(F19, 0x006A)
+CODE_MAP_MAC(F19, kVK_F19)
+CODE_MAP_X11(F19, 0x00C5)
+CODE_MAP_ANDROID(F19, 0x00BD)
+
+CODE_MAP_WIN(F20, 0x006B)
+CODE_MAP_MAC(F20, kVK_F20)
+CODE_MAP_X11(F20, 0x00C6)
+CODE_MAP_ANDROID(F20, 0x00BE)
+
+CODE_MAP_WIN(F21, 0x006C)
+CODE_MAP_X11(F21, 0x00C7)
+CODE_MAP_ANDROID(F21, 0x00BF)
+
+CODE_MAP_WIN(F22, 0x006D)
+CODE_MAP_X11(F22, 0x00C8)
+CODE_MAP_ANDROID(F22, 0x00C0)
+
+CODE_MAP_WIN(F23, 0x006E)
+CODE_MAP_X11(F23, 0x00C9)
+CODE_MAP_ANDROID(F23, 0x00C1)
+
+CODE_MAP_WIN(F24, 0x0076)
+CODE_MAP_X11(F24, 0x00CA)
+CODE_MAP_ANDROID(F24, 0x00C2)
+
+CODE_MAP_MAC(Fn, kVK_Function) // not available?
+CODE_MAP_ANDROID(Fn, 0x01D0)
+
+CODE_MAP_WIN(PrintScreen, 0xE037)
+CODE_MAP_WIN(PrintScreen, 0x0054) // Alt + PrintScreen
+CODE_MAP_X11(PrintScreen, 0x006B)
+CODE_MAP_ANDROID(PrintScreen, 0x0063)
+
+CODE_MAP_WIN(ScrollLock, 0x0046)
+CODE_MAP_X11(ScrollLock, 0x004E)
+CODE_MAP_ANDROID(ScrollLock, 0x0046)
+
+CODE_MAP_WIN(Pause, 0x0045)
+CODE_MAP_WIN(Pause, 0xE046) // Ctrl + Pause
+CODE_MAP_X11(Pause, 0x007F)
+CODE_MAP_ANDROID(Pause, 0x0077)
+
+// Media keys
+CODE_MAP_WIN(BrowserBack, 0xE06A)
+CODE_MAP_X11(BrowserBack, 0x00A6)
+CODE_MAP_ANDROID(BrowserBack, 0x009E)
+
+CODE_MAP_WIN(BrowserFavorites, 0xE066)
+CODE_MAP_X11(BrowserFavorites, 0x00A4)
+CODE_MAP_ANDROID(BrowserFavorites, 0x009C)
+
+CODE_MAP_WIN(BrowserForward, 0xE069)
+CODE_MAP_X11(BrowserForward, 0x00A7)
+CODE_MAP_ANDROID(BrowserForward, 0x009F)
+
+CODE_MAP_WIN(BrowserHome, 0xE032)
+CODE_MAP_X11(BrowserHome, 0x00B4)
+// CODE_MAP_ANDROID(BrowserHome) // not available? works as Home key.
+
+CODE_MAP_WIN(BrowserRefresh, 0xE067)
+CODE_MAP_X11(BrowserRefresh, 0x00B5)
+CODE_MAP_ANDROID(BrowserRefresh, 0x00AD)
+
+CODE_MAP_WIN(BrowserSearch, 0xE065)
+CODE_MAP_X11(BrowserSearch, 0x00E1)
+CODE_MAP_ANDROID(BrowserSearch, 0x00D9)
+
+CODE_MAP_WIN(BrowserStop, 0xE068)
+CODE_MAP_X11(BrowserStop, 0x0088)
+CODE_MAP_ANDROID(BrowserStop, 0x0080)
+
+// CODE_MAP_WIN(Eject) // not available?
+// CODE_MAP_MAC(Eject) // not available?
+CODE_MAP_X11(Eject, 0x00A9)
+CODE_MAP_ANDROID(Eject, 0x00A1)
+
+CODE_MAP_WIN(LaunchApp1, 0xE06B)
+CODE_MAP_X11(LaunchApp1, 0x0098)
+CODE_MAP_ANDROID(LaunchApp1, 0x0090)
+
+CODE_MAP_WIN(LaunchApp2, 0xE021)
+CODE_MAP_X11(LaunchApp2, 0x0094)
+// CODE_MAP_ANDROID(LaunchApp2) // not available?
+
+CODE_MAP_WIN(LaunchMail, 0xE06C)
+CODE_MAP_X11(LaunchMail, 0x00A3)
+// CODE_MAP_ANDROID(LaunchMail) // not available?
+
+CODE_MAP_WIN(MediaPlayPause, 0xE022)
+CODE_MAP_X11(MediaPlayPause, 0x00AC)
+CODE_MAP_ANDROID(MediaPlayPause, 0x00A4)
+
+CODE_MAP_WIN(MediaSelect, 0xE06D)
+CODE_MAP_X11(MediaSelect, 0x00B3)
+// CODE_MAP_ANDROID(MediaSelect) // not available?
+
+CODE_MAP_WIN(MediaStop, 0xE024)
+CODE_MAP_X11(MediaStop, 0x00AE)
+CODE_MAP_ANDROID(MediaStop, 0x00A6)
+
+CODE_MAP_WIN(MediaTrackNext, 0xE019)
+CODE_MAP_X11(MediaTrackNext, 0x00AB)
+CODE_MAP_ANDROID(MediaTrackNext, 0x00A3)
+
+CODE_MAP_WIN(MediaTrackPrevious, 0xE010)
+CODE_MAP_X11(MediaTrackPrevious, 0x00AD)
+CODE_MAP_ANDROID(MediaTrackPrevious, 0x00A5)
+
+CODE_MAP_WIN(Power, 0xE05E)
+CODE_MAP_MAC(Power, 0x007F) // On 10.7 and 10.8 only
+// CODE_MAP_X11(Power) // not available?
+CODE_MAP_ANDROID(Power, 0x0074)
+
+// CODE_MAP_WIN(Sleep) // not available?
+// CODE_MAP_X11(Sleep) // not available?
+CODE_MAP_ANDROID(Sleep, 0x008E)
+
+CODE_MAP_WIN(VolumeDown, 0xE02E)
+CODE_MAP_MAC(VolumeDown, kVK_VolumeDown) // not available?
+CODE_MAP_X11(VolumeDown, 0x007A)
+CODE_MAP_ANDROID(VolumeDown, 0x0072)
+
+CODE_MAP_WIN(VolumeMute, 0xE020)
+CODE_MAP_MAC(VolumeMute, kVK_Mute) // not available?
+CODE_MAP_X11(VolumeMute, 0x0079)
+CODE_MAP_ANDROID(VolumeMute, 0x0071)
+
+CODE_MAP_WIN(VolumeUp, 0xE030)
+CODE_MAP_MAC(VolumeUp, kVK_VolumeUp) // not available?
+CODE_MAP_X11(VolumeUp, 0x007B)
+CODE_MAP_ANDROID(VolumeUp, 0x0073) // side of body, not on keyboard
+
+// CODE_MAP_WIN(WakeUp) // not available?
+CODE_MAP_X11(WakeUp, 0x0097)
+CODE_MAP_ANDROID(WakeUp, 0x008F)
+
+// Legacy editing keys
+CODE_MAP_X11(Again, 0x0089) // Again key on Sun keyboard
+CODE_MAP_ANDROID(Again, 0x0081) // Again key on Sun keyboard
+
+CODE_MAP_X11(Copy, 0x008D) // Copy key on Sun keyboard
+CODE_MAP_ANDROID(Copy, 0x0085) // Copy key on Sun keyboard
+
+CODE_MAP_X11(Cut, 0x0091) // Cut key on Sun keyboard
+CODE_MAP_ANDROID(Cut, 0x0089) // Cut key on Sun keyboard
+
+CODE_MAP_X11(Find, 0x0090) // Find key on Sun keyboard
+CODE_MAP_ANDROID(Find, 0x0088) // Find key on Sun keyboard
+
+CODE_MAP_X11(Open, 0x008E) // Open key on Sun keyboard
+CODE_MAP_ANDROID(Open, 0x0086) // Open key on Sun keyboard
+
+CODE_MAP_X11(Paste, 0x008F) // Paste key on Sun keyboard
+CODE_MAP_ANDROID(Paste, 0x0087) // Paste key on Sun keyboard
+
+CODE_MAP_X11(Props, 0x008A) // Props key on Sun keyboard
+CODE_MAP_ANDROID(Props, 0x0082) // Props key on Sun keyboard
+
+CODE_MAP_X11(Select, 0x008C) // Front key on Sun keyboard
+CODE_MAP_ANDROID(Select, 0x0084) // Front key on Sun keyboard
+
+CODE_MAP_X11(Undo, 0x008B) // Undo key on Sun keyboard
+CODE_MAP_ANDROID(Undo, 0x0083) // Undo key on Sun keyboard
+
+#undef CODE_MAP_WIN
+#undef CODE_MAP_MAC
+#undef CODE_MAP_X11
+#undef CODE_MAP_ANDROID
diff --git a/widget/NativeKeyToDOMKeyName.h b/widget/NativeKeyToDOMKeyName.h
new file mode 100644
index 0000000000..c9c1623217
--- /dev/null
+++ b/widget/NativeKeyToDOMKeyName.h
@@ -0,0 +1,1288 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 header file defines simple key mapping between native keycode value and
+ * DOM key name index.
+ * You must define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX macro before include
+ * this.
+ *
+ * It must have two arguments, (aNativeKey, aKeyNameIndex).
+ * aNativeKey is a native keycode value.
+ * aKeyNameIndex is the widget::KeyNameIndex value.
+ */
+
+// Windows
+#define KEY_MAP_WIN(aCPPKeyName, aNativeKey)
+#define KEY_MAP_WIN_JPN(aCPPKeyName, aNativeKey)
+#define KEY_MAP_WIN_KOR(aCPPKeyName, aNativeKey)
+#define KEY_MAP_WIN_OTH(aCPPKeyName, aNativeKey)
+#define KEY_MAP_WIN_CMD(aCPPKeyName, aAppCommand)
+// Mac OS X
+#define KEY_MAP_COCOA(aCPPKeyName, aNativeKey)
+// GTK
+#define KEY_MAP_GTK(aCPPKeyName, aNativeKey)
+// Only for Android
+#define KEY_MAP_ANDROID(aCPPKeyName, aNativeKey)
+
+#if defined(XP_WIN)
+# if defined(NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX)
+// KEY_MAP_WIN() defines the mapping not depending on keyboard layout.
+# undef KEY_MAP_WIN
+# define KEY_MAP_WIN(aCPPKeyName, aNativeKey) \
+ NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, \
+ KEY_NAME_INDEX_##aCPPKeyName)
+# elif defined(NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX)
+// KEY_MAP_WIN_JPN() defines the mapping which is valid only with Japanese
+// keyboard layout.
+# undef KEY_MAP_WIN_JPN
+# define KEY_MAP_WIN_JPN(aCPPKeyName, aNativeKey) \
+ NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX( \
+ aNativeKey, KEY_NAME_INDEX_##aCPPKeyName)
+# elif defined(NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX)
+// KEY_MAP_WIN_KOR() defines the mapping which is valid only with Korean
+// keyboard layout.
+# undef KEY_MAP_WIN_KOR
+# define KEY_MAP_WIN_KOR(aCPPKeyName, aNativeKey) \
+ NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, \
+ KEY_NAME_INDEX_##aCPPKeyName)
+# elif defined(NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX)
+// KEY_MAP_WIN_OTH() defines the mapping which is valid with neither
+// Japanese keyboard layout nor Korean keyboard layout.
+# undef KEY_MAP_WIN_OTH
+# define KEY_MAP_WIN_OTH(aCPPKeyName, aNativeKey) \
+ NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, \
+ KEY_NAME_INDEX_##aCPPKeyName)
+# elif defined(NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX)
+// KEY_MAP_WIN_CMD() defines the mapping from APPCOMMAND_* of WM_APPCOMMAND.
+# undef KEY_MAP_WIN_CMD
+# define KEY_MAP_WIN_CMD(aCPPKeyName, aAppCommand) \
+ NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX(aAppCommand, \
+ KEY_NAME_INDEX_##aCPPKeyName)
+# else
+# error Any NS_*_TO_DOM_KEY_NAME_INDEX() is not defined.
+# endif // #if defined(NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX) ...
+#elif defined(XP_MACOSX)
+# undef KEY_MAP_COCOA
+# define KEY_MAP_COCOA(aCPPKeyName, aNativeKey) \
+ NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, \
+ KEY_NAME_INDEX_##aCPPKeyName)
+#elif defined(MOZ_WIDGET_GTK)
+# undef KEY_MAP_GTK
+# define KEY_MAP_GTK(aCPPKeyName, aNativeKey) \
+ NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, \
+ KEY_NAME_INDEX_##aCPPKeyName)
+#elif defined(ANDROID)
+# undef KEY_MAP_ANDROID
+# define KEY_MAP_ANDROID(aCPPKeyName, aNativeKey) \
+ NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, \
+ KEY_NAME_INDEX_##aCPPKeyName)
+#endif
+
+/******************************************************************************
+ * Modifier Keys
+ ******************************************************************************/
+// Alt
+KEY_MAP_WIN(Alt, VK_MENU)
+KEY_MAP_WIN(Alt, VK_LMENU)
+KEY_MAP_WIN(Alt, VK_RMENU) // This is ignored if active keyboard layout
+ // has AltGr. In such case, AltGraph is mapped.
+KEY_MAP_COCOA(Alt, kVK_Option)
+KEY_MAP_COCOA(Alt, kVK_RightOption)
+KEY_MAP_GTK(Alt, GDK_Alt_L)
+KEY_MAP_GTK(Alt, GDK_Alt_R)
+KEY_MAP_ANDROID(Alt, AKEYCODE_ALT_LEFT)
+KEY_MAP_ANDROID(Alt, AKEYCODE_ALT_RIGHT)
+
+// AltGraph
+KEY_MAP_GTK (AltGraph, GDK_Mode_switch /* same as GDK_kana_switch,
+ GDK_ISO_Group_Shift and
+ GDK_script_switch */)
+// Let's treat both Level 3 shift and Level 5 shift as AltGr.
+// And also, let's treat Latch key and Lock key as AltGr key too like
+// GDK_Shift_Lock.
+KEY_MAP_GTK(AltGraph, GDK_ISO_Level3_Shift)
+KEY_MAP_GTK(AltGraph, GDK_ISO_Level3_Latch)
+KEY_MAP_GTK(AltGraph, GDK_ISO_Level3_Lock)
+KEY_MAP_GTK(AltGraph, GDK_ISO_Level5_Shift)
+KEY_MAP_GTK(AltGraph, GDK_ISO_Level5_Latch)
+KEY_MAP_GTK(AltGraph, GDK_ISO_Level5_Lock)
+
+// CapsLock
+KEY_MAP_WIN(CapsLock, VK_CAPITAL)
+KEY_MAP_COCOA(CapsLock, kVK_CapsLock)
+KEY_MAP_GTK(CapsLock, GDK_Caps_Lock)
+KEY_MAP_ANDROID(CapsLock, AKEYCODE_CAPS_LOCK)
+
+// Control
+KEY_MAP_WIN(Control, VK_CONTROL)
+KEY_MAP_WIN(Control, VK_LCONTROL)
+KEY_MAP_WIN(Control, VK_RCONTROL)
+KEY_MAP_COCOA(Control, kVK_Control)
+KEY_MAP_COCOA(Control, kVK_RightControl)
+KEY_MAP_GTK(Control, GDK_Control_L)
+KEY_MAP_GTK(Control, GDK_Control_R)
+KEY_MAP_ANDROID(Control, AKEYCODE_CTRL_LEFT)
+KEY_MAP_ANDROID(Control, AKEYCODE_CTRL_RIGHT)
+
+// Fn
+KEY_MAP_COCOA(Fn, kVK_Function)
+KEY_MAP_ANDROID(Fn, AKEYCODE_FUNCTION)
+
+// Meta
+KEY_MAP_COCOA(Meta, kVK_Command)
+KEY_MAP_COCOA(Meta, kVK_RightCommand)
+KEY_MAP_GTK(Meta, GDK_Meta_L)
+KEY_MAP_GTK(Meta, GDK_Meta_R)
+KEY_MAP_ANDROID(Meta, AKEYCODE_META_LEFT)
+KEY_MAP_ANDROID(Meta, AKEYCODE_META_RIGHT)
+
+// NumLock
+KEY_MAP_WIN(NumLock, VK_NUMLOCK)
+KEY_MAP_GTK(NumLock, GDK_Num_Lock)
+KEY_MAP_ANDROID(NumLock, AKEYCODE_NUM_LOCK)
+
+// OS
+KEY_MAP_WIN(OS, VK_LWIN)
+KEY_MAP_WIN(OS, VK_RWIN)
+KEY_MAP_GTK(OS, GDK_Super_L)
+KEY_MAP_GTK(OS, GDK_Super_R)
+KEY_MAP_GTK(OS, GDK_Hyper_L)
+KEY_MAP_GTK(OS, GDK_Hyper_R)
+
+// ScrollLock
+KEY_MAP_WIN(ScrollLock, VK_SCROLL)
+KEY_MAP_GTK(ScrollLock, GDK_Scroll_Lock)
+KEY_MAP_ANDROID(ScrollLock, AKEYCODE_SCROLL_LOCK)
+
+// Shift
+KEY_MAP_WIN(Shift, VK_SHIFT)
+KEY_MAP_WIN(Shift, VK_LSHIFT)
+KEY_MAP_WIN(Shift, VK_RSHIFT)
+KEY_MAP_COCOA(Shift, kVK_Shift)
+KEY_MAP_COCOA(Shift, kVK_RightShift)
+KEY_MAP_GTK(Shift, GDK_Shift_L)
+KEY_MAP_GTK(Shift, GDK_Shift_R)
+KEY_MAP_GTK(Shift, GDK_Shift_Lock) // Let's treat as Shift key (bug 769159)
+KEY_MAP_ANDROID(Shift, AKEYCODE_SHIFT_LEFT)
+KEY_MAP_ANDROID(Shift, AKEYCODE_SHIFT_RIGHT)
+
+// Symbol
+KEY_MAP_ANDROID(Symbol, AKEYCODE_SYM)
+
+/******************************************************************************
+ * Whitespace Keys
+ ******************************************************************************/
+// Enter
+KEY_MAP_WIN(Enter, VK_RETURN)
+KEY_MAP_COCOA(Enter, kVK_Return)
+KEY_MAP_COCOA(Enter, kVK_ANSI_KeypadEnter)
+KEY_MAP_COCOA(Enter, kVK_Powerbook_KeypadEnter)
+KEY_MAP_GTK(Enter, GDK_Return)
+KEY_MAP_GTK(Enter, GDK_KP_Enter)
+KEY_MAP_GTK(Enter, GDK_ISO_Enter)
+KEY_MAP_GTK(Enter, GDK_3270_Enter)
+KEY_MAP_ANDROID(Enter, AKEYCODE_DPAD_CENTER)
+KEY_MAP_ANDROID(Enter, AKEYCODE_ENTER)
+KEY_MAP_ANDROID(Enter, AKEYCODE_NUMPAD_ENTER)
+
+// Tab
+KEY_MAP_WIN(Tab, VK_TAB)
+KEY_MAP_COCOA(Tab, kVK_Tab)
+KEY_MAP_GTK(Tab, GDK_Tab)
+KEY_MAP_GTK(Tab, GDK_ISO_Left_Tab) // Shift+Tab
+KEY_MAP_GTK(Tab, GDK_KP_Tab)
+KEY_MAP_ANDROID(Tab, AKEYCODE_TAB)
+
+/******************************************************************************
+ * Navigation Keys
+ ******************************************************************************/
+// ArrowDown
+KEY_MAP_WIN(ArrowDown, VK_DOWN)
+KEY_MAP_COCOA(ArrowDown, kVK_DownArrow)
+KEY_MAP_GTK(ArrowDown, GDK_Down)
+KEY_MAP_GTK(ArrowDown, GDK_KP_Down)
+KEY_MAP_ANDROID(ArrowDown, AKEYCODE_DPAD_DOWN)
+
+// ArrowLeft
+KEY_MAP_WIN(ArrowLeft, VK_LEFT)
+KEY_MAP_COCOA(ArrowLeft, kVK_LeftArrow)
+KEY_MAP_GTK(ArrowLeft, GDK_Left)
+KEY_MAP_GTK(ArrowLeft, GDK_KP_Left)
+KEY_MAP_ANDROID(ArrowLeft, AKEYCODE_DPAD_LEFT)
+
+// ArrowRight
+KEY_MAP_WIN(ArrowRight, VK_RIGHT)
+KEY_MAP_COCOA(ArrowRight, kVK_RightArrow)
+KEY_MAP_GTK(ArrowRight, GDK_Right)
+KEY_MAP_GTK(ArrowRight, GDK_KP_Right)
+KEY_MAP_ANDROID(ArrowRight, AKEYCODE_DPAD_RIGHT)
+
+// ArrowUp
+KEY_MAP_WIN(ArrowUp, VK_UP)
+KEY_MAP_COCOA(ArrowUp, kVK_UpArrow)
+KEY_MAP_GTK(ArrowUp, GDK_Up)
+KEY_MAP_GTK(ArrowUp, GDK_KP_Up)
+KEY_MAP_ANDROID(ArrowUp, AKEYCODE_DPAD_UP)
+
+// End
+KEY_MAP_WIN(End, VK_END)
+KEY_MAP_COCOA(End, kVK_End)
+KEY_MAP_GTK(End, GDK_End)
+KEY_MAP_GTK(End, GDK_KP_End)
+KEY_MAP_ANDROID(End, AKEYCODE_MOVE_END)
+
+// Home
+KEY_MAP_WIN(Home, VK_HOME)
+KEY_MAP_COCOA(Home, kVK_Home)
+KEY_MAP_GTK(Home, GDK_Home)
+KEY_MAP_GTK(Home, GDK_KP_Home)
+KEY_MAP_ANDROID(Home, AKEYCODE_MOVE_HOME)
+
+// PageDown
+KEY_MAP_WIN(PageDown, VK_NEXT)
+KEY_MAP_COCOA(PageDown, kVK_PageDown)
+KEY_MAP_GTK(PageDown, GDK_Page_Down /* same as GDK_Next */)
+KEY_MAP_GTK(PageDown, GDK_KP_Page_Down /* same as GDK_KP_Next */)
+KEY_MAP_ANDROID(PageDown, AKEYCODE_PAGE_DOWN)
+
+// PageUp
+KEY_MAP_WIN(PageUp, VK_PRIOR)
+KEY_MAP_COCOA(PageUp, kVK_PageUp)
+KEY_MAP_GTK(PageUp, GDK_Page_Up /* same as GDK_Prior */)
+KEY_MAP_GTK(PageUp, GDK_KP_Page_Up /* same as GDK_KP_Prior */)
+KEY_MAP_ANDROID(PageUp, AKEYCODE_PAGE_UP)
+
+/******************************************************************************
+ * Editing Keys
+ ******************************************************************************/
+// Backspace
+KEY_MAP_WIN(Backspace, VK_BACK)
+KEY_MAP_COCOA(Backspace, kVK_PC_Backspace)
+KEY_MAP_GTK(Backspace, GDK_BackSpace)
+KEY_MAP_ANDROID(Backspace, AKEYCODE_DEL)
+
+// Clear
+KEY_MAP_WIN(Clear, VK_CLEAR)
+KEY_MAP_WIN(Clear, VK_OEM_CLEAR)
+KEY_MAP_COCOA(Clear, kVK_ANSI_KeypadClear)
+KEY_MAP_GTK(Clear, GDK_Clear)
+KEY_MAP_ANDROID(Clear, AKEYCODE_CLEAR)
+
+// Copy
+KEY_MAP_WIN_CMD(Copy, APPCOMMAND_COPY)
+KEY_MAP_GTK(Copy, GDK_Copy)
+KEY_MAP_ANDROID(Copy, AKEYCODE_COPY)
+
+// CrSel
+KEY_MAP_WIN(CrSel, VK_CRSEL)
+KEY_MAP_GTK(CrSel, GDK_3270_CursorSelect) // legacy IBM keyboard layout
+
+// Cut
+KEY_MAP_WIN_CMD(Cut, APPCOMMAND_CUT)
+KEY_MAP_GTK(Cut, GDK_Cut)
+KEY_MAP_ANDROID(Cut, AKEYCODE_CUT)
+
+// Delete
+KEY_MAP_WIN(Delete, VK_DELETE)
+KEY_MAP_COCOA(Delete, kVK_PC_Delete)
+KEY_MAP_GTK(Delete, GDK_Delete)
+KEY_MAP_GTK(Delete, GDK_KP_Delete)
+KEY_MAP_ANDROID(Delete, AKEYCODE_FORWARD_DEL)
+
+// EraseEof
+KEY_MAP_WIN(EraseEof, VK_EREOF)
+KEY_MAP_GTK(EraseEof, GDK_3270_EraseEOF) // legacy IBM keyboard layout
+
+// ExSel
+KEY_MAP_WIN(ExSel, VK_EXSEL)
+KEY_MAP_GTK(ExSel, GDK_3270_ExSelect) // legacy IBM keyboard layout
+
+// Insert
+KEY_MAP_WIN(Insert, VK_INSERT)
+KEY_MAP_GTK(Insert, GDK_Insert)
+KEY_MAP_GTK(Insert, GDK_KP_Insert)
+KEY_MAP_ANDROID(Insert, AKEYCODE_INSERT)
+
+// Paste
+KEY_MAP_WIN_CMD(Paste, APPCOMMAND_PASTE)
+KEY_MAP_GTK(Paste, GDK_Paste)
+KEY_MAP_ANDROID(Paste, AKEYCODE_PASTE)
+
+// Redo
+KEY_MAP_WIN_CMD(Redo, APPCOMMAND_REDO)
+KEY_MAP_GTK(Redo, GDK_Redo)
+
+// Undo
+KEY_MAP_WIN_CMD(Undo, APPCOMMAND_UNDO)
+KEY_MAP_GTK(Undo, GDK_Undo)
+
+/******************************************************************************
+ * UI Keys
+ ******************************************************************************/
+// Accept
+KEY_MAP_WIN(Accept, VK_ACCEPT)
+
+// Attn
+KEY_MAP_WIN_OTH(Attn, VK_ATTN) // not valid with Japanese keyboard layout
+KEY_MAP_GTK(Attn, GDK_3270_Attn) // legacy IBM keyboard layout
+
+// Cancel
+KEY_MAP_WIN(Cancel, VK_CANCEL)
+KEY_MAP_GTK(Cancel, GDK_Cancel)
+
+// ContextMenu
+KEY_MAP_WIN(ContextMenu, VK_APPS)
+KEY_MAP_COCOA(ContextMenu, kVK_PC_ContextMenu)
+KEY_MAP_GTK(ContextMenu, GDK_Menu)
+KEY_MAP_ANDROID(ContextMenu, AKEYCODE_MENU)
+
+// Escape
+KEY_MAP_WIN(Escape, VK_ESCAPE)
+KEY_MAP_COCOA(Escape, kVK_Escape)
+KEY_MAP_GTK(Escape, GDK_Escape)
+KEY_MAP_ANDROID(Escape, AKEYCODE_ESCAPE)
+
+// Execute
+KEY_MAP_WIN(Execute, VK_EXECUTE)
+KEY_MAP_GTK(Execute, GDK_Execute)
+
+// Find
+KEY_MAP_WIN_CMD(Find, APPCOMMAND_FIND)
+KEY_MAP_GTK(Find, GDK_Find)
+
+// Help
+KEY_MAP_WIN(Help, VK_HELP)
+KEY_MAP_WIN_CMD(Help, APPCOMMAND_HELP)
+KEY_MAP_COCOA(Help, kVK_Help)
+KEY_MAP_GTK(Help, GDK_Help)
+KEY_MAP_ANDROID(Help, AKEYCODE_HELP)
+
+// Pause
+KEY_MAP_WIN(Pause, VK_PAUSE)
+KEY_MAP_GTK(Pause, GDK_Pause)
+// Break is typically mapped to Alt+Pause or Ctrl+Pause on GTK.
+KEY_MAP_GTK(Pause, GDK_Break)
+KEY_MAP_ANDROID(Pause, AKEYCODE_BREAK)
+
+// Play
+KEY_MAP_WIN(Play, VK_PLAY)
+KEY_MAP_GTK(Play, GDK_3270_Play) // legacy IBM keyboard layout
+
+// Select
+KEY_MAP_WIN(Select, VK_SELECT)
+KEY_MAP_GTK(Select, GDK_Select)
+
+// ZoomIn
+KEY_MAP_GTK(ZoomIn, GDK_ZoomIn)
+KEY_MAP_ANDROID(ZoomIn, AKEYCODE_ZOOM_IN)
+
+// ZoomOut
+KEY_MAP_GTK(ZoomOut, GDK_ZoomOut)
+KEY_MAP_ANDROID(ZoomOut, AKEYCODE_ZOOM_OUT)
+
+/******************************************************************************
+ * Device Keys
+ ******************************************************************************/
+// BrightnessDown
+KEY_MAP_GTK(BrightnessDown, GDK_MonBrightnessDown)
+KEY_MAP_ANDROID(BrightnessDown, AKEYCODE_BRIGHTNESS_DOWN)
+
+// BrightnessUp
+KEY_MAP_GTK(BrightnessUp, GDK_MonBrightnessUp)
+KEY_MAP_ANDROID(BrightnessUp, AKEYCODE_BRIGHTNESS_UP)
+
+// Eject
+KEY_MAP_GTK(Eject, GDK_Eject)
+KEY_MAP_ANDROID(Eject, AKEYCODE_MEDIA_EJECT)
+
+// LogOff
+KEY_MAP_GTK(LogOff, GDK_LogOff)
+
+// Power
+KEY_MAP_ANDROID(Power, AKEYCODE_POWER)
+
+// PowerOff
+KEY_MAP_GTK(PowerOff, GDK_PowerDown)
+KEY_MAP_GTK(PowerOff, GDK_PowerOff)
+
+// PrintScreen
+KEY_MAP_WIN(PrintScreen, VK_SNAPSHOT)
+KEY_MAP_GTK(PrintScreen, GDK_3270_PrintScreen)
+KEY_MAP_GTK(PrintScreen, GDK_Print)
+KEY_MAP_GTK(PrintScreen, GDK_Sys_Req)
+KEY_MAP_ANDROID(PrintScreen, AKEYCODE_SYSRQ)
+
+// Hibernate
+KEY_MAP_GTK(Hibernate, GDK_Hibernate)
+
+// Standby
+KEY_MAP_WIN(Standby, VK_SLEEP)
+KEY_MAP_GTK(Standby, GDK_Standby)
+KEY_MAP_GTK(Standby, GDK_Suspend)
+KEY_MAP_GTK(Standby, GDK_Sleep)
+KEY_MAP_ANDROID(Standby, AKEYCODE_SLEEP)
+
+// WakeUp
+KEY_MAP_GTK(WakeUp, GDK_WakeUp)
+KEY_MAP_ANDROID(WakeUp, AKEYCODE_WAKEUP)
+
+/******************************************************************************
+ * IME and Composition Keys
+ ******************************************************************************/
+// AllCandidates
+KEY_MAP_GTK(AllCandidates, GDK_MultipleCandidate) // OADG 109, Zen Koho
+
+// Alphanumeric
+KEY_MAP_WIN_JPN(Alphanumeric, VK_OEM_ATTN)
+KEY_MAP_GTK(Alphanumeric, GDK_Eisu_Shift)
+KEY_MAP_GTK(Alphanumeric, GDK_Eisu_toggle)
+
+// CodeInput
+KEY_MAP_GTK(CodeInput, GDK_Codeinput) // OADG 109, Kanji Bangou
+
+// Compose
+KEY_MAP_GTK(Compose, GDK_Multi_key) // "Multi Key" is "Compose key" on X
+
+// Convert
+KEY_MAP_WIN(Convert, VK_CONVERT)
+KEY_MAP_GTK(Convert, GDK_Henkan)
+KEY_MAP_ANDROID(Convert, AKEYCODE_HENKAN)
+
+// Dead
+KEY_MAP_GTK(Dead, GDK_dead_grave)
+KEY_MAP_GTK(Dead, GDK_dead_acute)
+KEY_MAP_GTK(Dead, GDK_dead_circumflex)
+KEY_MAP_GTK(Dead, GDK_dead_tilde) // Same as GDK_dead_perispomeni
+KEY_MAP_GTK(Dead, GDK_dead_macron)
+KEY_MAP_GTK(Dead, GDK_dead_breve)
+KEY_MAP_GTK(Dead, GDK_dead_abovedot)
+KEY_MAP_GTK(Dead, GDK_dead_diaeresis)
+KEY_MAP_GTK(Dead, GDK_dead_abovering)
+KEY_MAP_GTK(Dead, GDK_dead_doubleacute)
+KEY_MAP_GTK(Dead, GDK_dead_caron)
+KEY_MAP_GTK(Dead, GDK_dead_cedilla)
+KEY_MAP_GTK(Dead, GDK_dead_ogonek)
+KEY_MAP_GTK(Dead, GDK_dead_iota)
+KEY_MAP_GTK(Dead, GDK_dead_voiced_sound)
+KEY_MAP_GTK(Dead, GDK_dead_semivoiced_sound)
+KEY_MAP_GTK(Dead, GDK_dead_belowdot)
+KEY_MAP_GTK(Dead, GDK_dead_hook)
+KEY_MAP_GTK(Dead, GDK_dead_horn)
+KEY_MAP_GTK(Dead, GDK_dead_stroke)
+KEY_MAP_GTK(Dead, GDK_dead_abovecomma) // Same as GDK_dead_psili
+KEY_MAP_GTK(Dead, GDK_dead_abovereversedcomma) // Same as GDK_dead_dasia
+KEY_MAP_GTK(Dead, GDK_dead_doublegrave)
+KEY_MAP_GTK(Dead, GDK_dead_belowring)
+KEY_MAP_GTK(Dead, GDK_dead_belowmacron)
+KEY_MAP_GTK(Dead, GDK_dead_belowcircumflex)
+KEY_MAP_GTK(Dead, GDK_dead_belowtilde)
+KEY_MAP_GTK(Dead, GDK_dead_belowbreve)
+KEY_MAP_GTK(Dead, GDK_dead_belowdiaeresis)
+KEY_MAP_GTK(Dead, GDK_dead_invertedbreve)
+KEY_MAP_GTK(Dead, GDK_dead_belowcomma)
+KEY_MAP_GTK(Dead, GDK_dead_currency)
+KEY_MAP_GTK(Dead, GDK_dead_a)
+KEY_MAP_GTK(Dead, GDK_dead_A)
+KEY_MAP_GTK(Dead, GDK_dead_e)
+KEY_MAP_GTK(Dead, GDK_dead_E)
+KEY_MAP_GTK(Dead, GDK_dead_i)
+KEY_MAP_GTK(Dead, GDK_dead_I)
+KEY_MAP_GTK(Dead, GDK_dead_o)
+KEY_MAP_GTK(Dead, GDK_dead_O)
+KEY_MAP_GTK(Dead, GDK_dead_u)
+KEY_MAP_GTK(Dead, GDK_dead_U)
+KEY_MAP_GTK(Dead, GDK_dead_small_schwa)
+KEY_MAP_GTK(Dead, GDK_dead_capital_schwa)
+KEY_MAP_GTK(Dead, GDK_dead_greek)
+
+// FinalMode
+KEY_MAP_WIN(FinalMode, VK_FINAL)
+
+// GroupFirst
+KEY_MAP_GTK(GroupFirst, GDK_ISO_First_Group)
+
+// GroupLast
+KEY_MAP_GTK(GroupLast, GDK_ISO_Last_Group)
+
+// GroupNext
+KEY_MAP_GTK(GroupNext, GDK_ISO_Next_Group)
+KEY_MAP_ANDROID(GroupNext, AKEYCODE_LANGUAGE_SWITCH)
+
+// GroupPrevious
+KEY_MAP_GTK(GroupPrevious, GDK_ISO_Prev_Group)
+
+// ModeChange
+KEY_MAP_WIN(ModeChange, VK_MODECHANGE)
+KEY_MAP_ANDROID(ModeChange, AKEYCODE_SWITCH_CHARSET)
+
+// NonConvert
+KEY_MAP_WIN(NonConvert, VK_NONCONVERT)
+KEY_MAP_GTK(NonConvert, GDK_Muhenkan)
+KEY_MAP_ANDROID(NonConvert, AKEYCODE_MUHENKAN)
+
+// PreviousCandidate
+KEY_MAP_GTK(PreviousCandidate, GDK_PreviousCandidate) // OADG 109, Mae Koho
+
+// Process
+KEY_MAP_WIN(Process, VK_PROCESSKEY)
+
+// SingleCandidate
+KEY_MAP_GTK(SingleCandidate, GDK_SingleCandidate)
+
+/******************************************************************************
+ * Keys specific to Korean keyboards
+ ******************************************************************************/
+// HangulMode
+KEY_MAP_WIN_KOR(HangulMode, VK_HANGUL /* same as VK_KANA */)
+
+// HanjaMode
+KEY_MAP_WIN_KOR(HanjaMode, VK_HANJA /* same as VK_KANJI */)
+
+// JunjaMode
+KEY_MAP_WIN(JunjaMode, VK_JUNJA)
+
+/******************************************************************************
+ * Keys specific to Japanese keyboards
+ ******************************************************************************/
+// Eisu
+KEY_MAP_COCOA(Eisu, kVK_JIS_Eisu)
+KEY_MAP_ANDROID(Eisu, AKEYCODE_EISU)
+
+// Hankaku
+KEY_MAP_WIN_JPN(Hankaku, VK_OEM_AUTO)
+KEY_MAP_GTK(Hankaku, GDK_Hankaku)
+
+// Hiragana
+KEY_MAP_WIN_JPN(Hiragana, VK_OEM_COPY)
+KEY_MAP_GTK(Hiragana, GDK_Hiragana)
+
+// HiraganaKatakana
+KEY_MAP_GTK(HiraganaKatakana, GDK_Hiragana_Katakana)
+KEY_MAP_ANDROID(HiraganaKatakana, AKEYCODE_KATAKANA_HIRAGANA)
+
+// KanaMode
+// VK_KANA is never used with modern Japanese keyboard, however, IE maps it to
+// KanaMode, therefore, we should use same map for it.
+KEY_MAP_WIN_JPN(KanaMode, VK_KANA /* same as VK_HANGUL */)
+KEY_MAP_WIN_JPN(KanaMode, VK_ATTN)
+KEY_MAP_GTK(KanaMode, GDK_Kana_Lock)
+KEY_MAP_GTK(KanaMode, GDK_Kana_Shift)
+
+// KanjiMode
+KEY_MAP_WIN_JPN(KanjiMode, VK_KANJI /* same as VK_HANJA */)
+KEY_MAP_COCOA(KanjiMode, kVK_JIS_Kana) // Kana key opens IME
+KEY_MAP_GTK(KanjiMode, GDK_Kanji) // Typically, Alt + Hankaku/Zenkaku key
+// Assuming that KANA key of Android is the Kana key on Mac keyboard.
+KEY_MAP_ANDROID(KanjiMode, AKEYCODE_KANA)
+
+// Katakana
+KEY_MAP_WIN_JPN(Katakana, VK_OEM_FINISH)
+KEY_MAP_GTK(Katakana, GDK_Katakana)
+
+// Romaji
+KEY_MAP_WIN_JPN(Romaji, VK_OEM_BACKTAB)
+KEY_MAP_GTK(Romaji, GDK_Romaji)
+
+// Zenkaku
+KEY_MAP_WIN_JPN(Zenkaku, VK_OEM_ENLW)
+KEY_MAP_GTK(Zenkaku, GDK_Zenkaku)
+
+// ZenkakuHankaku
+KEY_MAP_GTK(ZenkakuHankaku, GDK_Zenkaku_Hankaku)
+KEY_MAP_ANDROID(ZenkakuHankaku, AKEYCODE_ZENKAKU_HANKAKU)
+
+/******************************************************************************
+ * General-Purpose Function Keys
+ ******************************************************************************/
+// F1
+KEY_MAP_WIN(F1, VK_F1)
+KEY_MAP_COCOA(F1, kVK_F1)
+KEY_MAP_GTK(F1, GDK_F1)
+KEY_MAP_GTK(F1, GDK_KP_F1)
+KEY_MAP_ANDROID(F1, AKEYCODE_F1)
+
+// F2
+KEY_MAP_WIN(F2, VK_F2)
+KEY_MAP_COCOA(F2, kVK_F2)
+KEY_MAP_GTK(F2, GDK_F2)
+KEY_MAP_GTK(F2, GDK_KP_F2)
+KEY_MAP_ANDROID(F2, AKEYCODE_F2)
+
+// F3
+KEY_MAP_WIN(F3, VK_F3)
+KEY_MAP_COCOA(F3, kVK_F3)
+KEY_MAP_GTK(F3, GDK_F3)
+KEY_MAP_GTK(F3, GDK_KP_F3)
+KEY_MAP_ANDROID(F3, AKEYCODE_F3)
+
+// F4
+KEY_MAP_WIN(F4, VK_F4)
+KEY_MAP_COCOA(F4, kVK_F4)
+KEY_MAP_GTK(F4, GDK_F4)
+KEY_MAP_GTK(F4, GDK_KP_F4)
+KEY_MAP_ANDROID(F4, AKEYCODE_F4)
+
+// F5
+KEY_MAP_WIN(F5, VK_F5)
+KEY_MAP_COCOA(F5, kVK_F5)
+KEY_MAP_GTK(F5, GDK_F5)
+KEY_MAP_ANDROID(F5, AKEYCODE_F5)
+
+// F6
+KEY_MAP_WIN(F6, VK_F6)
+KEY_MAP_COCOA(F6, kVK_F6)
+KEY_MAP_GTK(F6, GDK_F6)
+KEY_MAP_ANDROID(F6, AKEYCODE_F6)
+
+// F7
+KEY_MAP_WIN(F7, VK_F7)
+KEY_MAP_COCOA(F7, kVK_F7)
+KEY_MAP_GTK(F7, GDK_F7)
+KEY_MAP_ANDROID(F7, AKEYCODE_F7)
+
+// F8
+KEY_MAP_WIN(F8, VK_F8)
+KEY_MAP_COCOA(F8, kVK_F8)
+KEY_MAP_GTK(F8, GDK_F8)
+KEY_MAP_ANDROID(F8, AKEYCODE_F8)
+
+// F9
+KEY_MAP_WIN(F9, VK_F9)
+KEY_MAP_COCOA(F9, kVK_F9)
+KEY_MAP_GTK(F9, GDK_F9)
+KEY_MAP_ANDROID(F9, AKEYCODE_F9)
+
+// F10
+KEY_MAP_WIN(F10, VK_F10)
+KEY_MAP_COCOA(F10, kVK_F10)
+KEY_MAP_GTK(F10, GDK_F10)
+KEY_MAP_ANDROID(F10, AKEYCODE_F10)
+
+// F11
+KEY_MAP_WIN(F11, VK_F11)
+KEY_MAP_COCOA(F11, kVK_F11)
+KEY_MAP_GTK(F11, GDK_F11 /* same as GDK_L1 */)
+KEY_MAP_ANDROID(F11, AKEYCODE_F11)
+
+// F12
+KEY_MAP_WIN(F12, VK_F12)
+KEY_MAP_COCOA(F12, kVK_F12)
+KEY_MAP_GTK(F12, GDK_F12 /* same as GDK_L2 */)
+KEY_MAP_ANDROID(F12, AKEYCODE_F12)
+
+// F13
+KEY_MAP_WIN(F13, VK_F13)
+KEY_MAP_COCOA(F13, kVK_F13)
+KEY_MAP_GTK(F13, GDK_F13 /* same as GDK_L3 */)
+
+// F14
+KEY_MAP_WIN(F14, VK_F14)
+KEY_MAP_COCOA(F14, kVK_F14)
+KEY_MAP_GTK(F14, GDK_F14 /* same as GDK_L4 */)
+
+// F15
+KEY_MAP_WIN(F15, VK_F15)
+KEY_MAP_COCOA(F15, kVK_F15)
+KEY_MAP_GTK(F15, GDK_F15 /* same as GDK_L5 */)
+
+// F16
+KEY_MAP_WIN(F16, VK_F16)
+KEY_MAP_COCOA(F16, kVK_F16)
+KEY_MAP_GTK(F16, GDK_F16 /* same as GDK_L6 */)
+
+// F17
+KEY_MAP_WIN(F17, VK_F17)
+KEY_MAP_COCOA(F17, kVK_F17)
+KEY_MAP_GTK(F17, GDK_F17 /* same as GDK_L7 */)
+
+// F18
+KEY_MAP_WIN(F18, VK_F18)
+KEY_MAP_COCOA(F18, kVK_F18)
+KEY_MAP_GTK(F18, GDK_F18 /* same as GDK_L8 */)
+
+// F19
+KEY_MAP_WIN(F19, VK_F19)
+KEY_MAP_COCOA(F19, kVK_F19)
+KEY_MAP_GTK(F19, GDK_F19 /* same as GDK_L9 */)
+
+// F20
+KEY_MAP_WIN(F20, VK_F20)
+KEY_MAP_GTK(F20, GDK_F20 /* same as GDK_L10 */)
+
+// F21
+KEY_MAP_WIN(F21, VK_F21)
+KEY_MAP_GTK(F21, GDK_F21 /* same as GDK_R1 */)
+
+// F22
+KEY_MAP_WIN(F22, VK_F22)
+KEY_MAP_GTK(F22, GDK_F22 /* same as GDK_R2 */)
+
+// F23
+KEY_MAP_WIN(F23, VK_F23)
+KEY_MAP_GTK(F23, GDK_F23 /* same as GDK_R3 */)
+
+// F24
+KEY_MAP_WIN(F24, VK_F24)
+KEY_MAP_GTK(F24, GDK_F24 /* same as GDK_R4 */)
+
+// F25
+KEY_MAP_GTK(F25, GDK_F25 /* same as GDK_R5 */)
+
+// F26
+KEY_MAP_GTK(F26, GDK_F26 /* same as GDK_R6 */)
+
+// F27
+KEY_MAP_GTK(F27, GDK_F27 /* same as GDK_R7 */)
+
+// F28
+KEY_MAP_GTK(F28, GDK_F28 /* same as GDK_R8 */)
+
+// F29
+KEY_MAP_GTK(F29, GDK_F29 /* same as GDK_R9 */)
+
+// F30
+KEY_MAP_GTK(F30, GDK_F30 /* same as GDK_R10 */)
+
+// F31
+KEY_MAP_GTK(F31, GDK_F31 /* same as GDK_R11 */)
+
+// F32
+KEY_MAP_GTK(F32, GDK_F32 /* same as GDK_R12 */)
+
+// F33
+KEY_MAP_GTK(F33, GDK_F33 /* same as GDK_R13 */)
+
+// F34
+KEY_MAP_GTK(F34, GDK_F34 /* same as GDK_R14 */)
+
+// F35
+KEY_MAP_GTK(F35, GDK_F35 /* same as GDK_R15 */)
+
+/******************************************************************************
+ * Multimedia Keys
+ ******************************************************************************/
+// ChannelDown
+KEY_MAP_WIN_CMD(ChannelDown, APPCOMMAND_MEDIA_CHANNEL_DOWN)
+KEY_MAP_ANDROID(ChannelDown, AKEYCODE_CHANNEL_DOWN)
+
+// ChannelUp
+KEY_MAP_WIN_CMD(ChannelUp, APPCOMMAND_MEDIA_CHANNEL_UP)
+KEY_MAP_ANDROID(ChannelUp, AKEYCODE_CHANNEL_UP)
+
+// Close
+// NOTE: This is not a key to close disk tray, this is a key to close document
+// or window.
+KEY_MAP_WIN_CMD(Close, APPCOMMAND_CLOSE)
+KEY_MAP_GTK(Close, GDK_Close)
+
+// MailForward
+KEY_MAP_WIN_CMD(MailForward, APPCOMMAND_FORWARD_MAIL)
+KEY_MAP_GTK(MailForward, GDK_MailForward)
+
+// MailReply
+KEY_MAP_WIN_CMD(MailReply, APPCOMMAND_REPLY_TO_MAIL)
+KEY_MAP_GTK(MailReply, GDK_Reply)
+
+// MailSend
+KEY_MAP_WIN_CMD(MailSend, APPCOMMAND_SEND_MAIL)
+KEY_MAP_GTK(MailSend, GDK_Send)
+
+// MediaFastForward
+KEY_MAP_WIN_CMD(MediaFastForward, APPCOMMAND_MEDIA_FAST_FORWARD)
+KEY_MAP_GTK(MediaFastForward, GDK_AudioForward)
+KEY_MAP_ANDROID(MediaFastForward, AKEYCODE_MEDIA_FAST_FORWARD)
+
+// MediaPause
+KEY_MAP_WIN_CMD(MediaPause, APPCOMMAND_MEDIA_PAUSE)
+KEY_MAP_GTK(MediaPause, GDK_AudioPause)
+KEY_MAP_ANDROID(MediaPause, AKEYCODE_MEDIA_PAUSE)
+
+// MediaPlay
+KEY_MAP_WIN_CMD(MediaPlay, APPCOMMAND_MEDIA_PLAY)
+KEY_MAP_GTK(MediaPlay, GDK_AudioPlay)
+KEY_MAP_ANDROID(MediaPlay, AKEYCODE_MEDIA_PLAY)
+
+// MediaPlayPause
+KEY_MAP_WIN(MediaPlayPause, VK_MEDIA_PLAY_PAUSE)
+KEY_MAP_WIN_CMD(MediaPlayPause, APPCOMMAND_MEDIA_PLAY_PAUSE)
+KEY_MAP_ANDROID(MediaPlayPause, AKEYCODE_MEDIA_PLAY_PAUSE)
+
+// MediaRecord
+KEY_MAP_WIN_CMD(MediaRecord, APPCOMMAND_MEDIA_RECORD)
+KEY_MAP_GTK(MediaRecord, GDK_AudioRecord)
+KEY_MAP_ANDROID(MediaRecord, AKEYCODE_MEDIA_RECORD)
+
+// MediaRewind
+KEY_MAP_WIN_CMD(MediaRewind, APPCOMMAND_MEDIA_REWIND)
+KEY_MAP_GTK(MediaRewind, GDK_AudioRewind)
+KEY_MAP_ANDROID(MediaRewind, AKEYCODE_MEDIA_REWIND)
+
+// MediaStop
+KEY_MAP_WIN(MediaStop, VK_MEDIA_STOP)
+KEY_MAP_WIN_CMD(MediaStop, APPCOMMAND_MEDIA_STOP)
+KEY_MAP_GTK(MediaStop, GDK_AudioStop)
+KEY_MAP_ANDROID(MediaStop, AKEYCODE_MEDIA_STOP)
+
+// MediaTrackNext
+KEY_MAP_WIN(MediaTrackNext, VK_MEDIA_NEXT_TRACK)
+KEY_MAP_WIN_CMD(MediaTrackNext, APPCOMMAND_MEDIA_NEXTTRACK)
+KEY_MAP_GTK(MediaTrackNext, GDK_AudioNext)
+KEY_MAP_ANDROID(MediaTrackNext, AKEYCODE_MEDIA_NEXT)
+
+// MediaTrackPrevious
+KEY_MAP_WIN(MediaTrackPrevious, VK_MEDIA_PREV_TRACK)
+KEY_MAP_WIN_CMD(MediaTrackPrevious, APPCOMMAND_MEDIA_PREVIOUSTRACK)
+KEY_MAP_GTK(MediaTrackPrevious, GDK_AudioPrev)
+KEY_MAP_ANDROID(MediaTrackPrevious, AKEYCODE_MEDIA_PREVIOUS)
+
+// New
+KEY_MAP_WIN_CMD(New, APPCOMMAND_NEW)
+KEY_MAP_GTK(New, GDK_New)
+
+// Open
+KEY_MAP_WIN_CMD(Open, APPCOMMAND_OPEN)
+KEY_MAP_GTK(Open, GDK_Open)
+
+// Print
+KEY_MAP_WIN_CMD(Print, APPCOMMAND_PRINT)
+
+// Save
+KEY_MAP_WIN_CMD(Save, APPCOMMAND_SAVE)
+KEY_MAP_GTK(Save, GDK_Save)
+
+// SpellCheck
+KEY_MAP_WIN_CMD(SpellCheck, APPCOMMAND_SPELL_CHECK)
+KEY_MAP_GTK(SpellCheck, GDK_Spell)
+
+/******************************************************************************
+ * Audio Keys
+ *****************************************************************************/
+// AudioBassBoostDown
+KEY_MAP_WIN_CMD(AudioBassBoostDown, APPCOMMAND_BASS_DOWN)
+
+// AudioBassBoostUp
+KEY_MAP_WIN_CMD(AudioBassBoostUp, APPCOMMAND_BASS_UP)
+
+// AudioVolumeDown
+KEY_MAP_WIN(AudioVolumeDown, VK_VOLUME_DOWN)
+KEY_MAP_WIN_CMD(AudioVolumeDown, APPCOMMAND_VOLUME_DOWN)
+KEY_MAP_COCOA(AudioVolumeDown, kVK_VolumeDown)
+KEY_MAP_GTK(AudioVolumeDown, GDK_AudioLowerVolume)
+KEY_MAP_ANDROID(AudioVolumeDown, AKEYCODE_VOLUME_DOWN)
+
+// AudioVolumeUp
+KEY_MAP_WIN(AudioVolumeUp, VK_VOLUME_UP)
+KEY_MAP_WIN_CMD(AudioVolumeUp, APPCOMMAND_VOLUME_UP)
+KEY_MAP_COCOA(AudioVolumeUp, kVK_VolumeUp)
+KEY_MAP_GTK(AudioVolumeUp, GDK_AudioRaiseVolume)
+KEY_MAP_ANDROID(AudioVolumeUp, AKEYCODE_VOLUME_UP)
+
+// AudioVolumeMute
+KEY_MAP_WIN(AudioVolumeMute, VK_VOLUME_MUTE)
+KEY_MAP_WIN_CMD(AudioVolumeMute, APPCOMMAND_VOLUME_MUTE)
+KEY_MAP_COCOA(AudioVolumeMute, kVK_Mute)
+KEY_MAP_GTK(AudioVolumeMute, GDK_AudioMute)
+KEY_MAP_ANDROID(AudioVolumeMute, AKEYCODE_VOLUME_MUTE)
+
+// MicrophoneVolumeMute
+KEY_MAP_ANDROID(MicrophoneVolumeMute, AKEYCODE_MUTE)
+
+/******************************************************************************
+ * Application Keys
+ ******************************************************************************/
+// LaunchCalculator
+KEY_MAP_GTK(LaunchCalculator, GDK_Calculator)
+KEY_MAP_ANDROID(LaunchCalculator, AKEYCODE_CALCULATOR)
+
+// LaunchCalendar
+KEY_MAP_GTK(LaunchCalendar, GDK_Calendar)
+KEY_MAP_ANDROID(LaunchCalendar, AKEYCODE_CALENDAR)
+
+// LaunchContacts
+KEY_MAP_ANDROID(LaunchContacts, AKEYCODE_CONTACTS)
+
+// LaunchMail
+KEY_MAP_WIN(LaunchMail, VK_LAUNCH_MAIL)
+KEY_MAP_WIN_CMD(LaunchMail, APPCOMMAND_LAUNCH_MAIL)
+KEY_MAP_GTK(LaunchMail, GDK_Mail)
+KEY_MAP_ANDROID(LaunchMail, AKEYCODE_ENVELOPE)
+
+// LaunchMediaPlayer
+KEY_MAP_WIN(LaunchMediaPlayer, VK_LAUNCH_MEDIA_SELECT)
+KEY_MAP_WIN_CMD(LaunchMediaPlayer, APPCOMMAND_LAUNCH_MEDIA_SELECT)
+// GDK_CD is defined as "Launch CD/DVD player" in XF86keysym.h.
+// Therefore, let's map it to media player rather than music player.
+KEY_MAP_GTK(LaunchMediaPlayer, GDK_CD)
+KEY_MAP_GTK(LaunchMediaPlayer, GDK_Video)
+KEY_MAP_GTK(LaunchMediaPlayer, GDK_AudioMedia)
+
+// LaunchMusicPlayer
+KEY_MAP_GTK(LaunchMusicPlayer, GDK_Music)
+KEY_MAP_ANDROID(LaunchMusicPlayer, AKEYCODE_MUSIC)
+
+// LaunchMyComputer
+KEY_MAP_GTK(LaunchMyComputer, GDK_MyComputer)
+KEY_MAP_GTK(LaunchMyComputer, GDK_Explorer)
+
+// LaunchScreenSaver
+KEY_MAP_GTK(LaunchScreenSaver, GDK_ScreenSaver)
+
+// LaunchSpreadsheet
+KEY_MAP_GTK(LaunchSpreadsheet, GDK_Excel)
+
+// LaunchWebBrowser
+KEY_MAP_GTK(LaunchWebBrowser, GDK_WWW)
+KEY_MAP_ANDROID(LaunchWebBrowser, AKEYCODE_EXPLORER)
+
+// LaunchWebCam
+KEY_MAP_GTK(LaunchWebCam, GDK_WebCam)
+
+// LaunchWordProcessor
+KEY_MAP_GTK(LaunchWordProcessor, GDK_Word)
+
+// LaunchApplication1
+KEY_MAP_WIN(LaunchApplication1, VK_LAUNCH_APP1)
+KEY_MAP_WIN_CMD(LaunchApplication1, APPCOMMAND_LAUNCH_APP1)
+KEY_MAP_GTK(LaunchApplication1, GDK_Launch0)
+
+// LaunchApplication2
+KEY_MAP_WIN(LaunchApplication2, VK_LAUNCH_APP2)
+KEY_MAP_WIN_CMD(LaunchApplication2, APPCOMMAND_LAUNCH_APP2)
+KEY_MAP_GTK(LaunchApplication2, GDK_Launch1)
+
+// LaunchApplication3
+KEY_MAP_GTK(LaunchApplication3, GDK_Launch2)
+
+// LaunchApplication4
+KEY_MAP_GTK(LaunchApplication4, GDK_Launch3)
+
+// LaunchApplication5
+KEY_MAP_GTK(LaunchApplication5, GDK_Launch4)
+
+// LaunchApplication6
+KEY_MAP_GTK(LaunchApplication6, GDK_Launch5)
+
+// LaunchApplication7
+KEY_MAP_GTK(LaunchApplication7, GDK_Launch6)
+
+// LaunchApplication8
+KEY_MAP_GTK(LaunchApplication8, GDK_Launch7)
+
+// LaunchApplication9
+KEY_MAP_GTK(LaunchApplication9, GDK_Launch8)
+
+// LaunchApplication10
+KEY_MAP_GTK(LaunchApplication10, GDK_Launch9)
+
+// LaunchApplication11
+KEY_MAP_GTK(LaunchApplication11, GDK_LaunchA)
+
+// LaunchApplication12
+KEY_MAP_GTK(LaunchApplication12, GDK_LaunchB)
+
+// LaunchApplication13
+KEY_MAP_GTK(LaunchApplication13, GDK_LaunchC)
+
+// LaunchApplication14
+KEY_MAP_GTK(LaunchApplication14, GDK_LaunchD)
+
+// LaunchApplication15
+KEY_MAP_GTK(LaunchApplication15, GDK_LaunchE)
+
+// LaunchApplication16
+KEY_MAP_GTK(LaunchApplication16, GDK_LaunchF)
+
+// LaunchApplication17
+
+// LaunchApplication18
+
+/******************************************************************************
+ * Browser Keys
+ ******************************************************************************/
+// BrowserBack
+KEY_MAP_WIN(BrowserBack, VK_BROWSER_BACK)
+KEY_MAP_WIN_CMD(BrowserBack, APPCOMMAND_BROWSER_BACKWARD)
+KEY_MAP_GTK(BrowserBack, GDK_Back)
+
+// BrowserFavorites
+KEY_MAP_WIN(BrowserFavorites, VK_BROWSER_FAVORITES)
+KEY_MAP_WIN_CMD(BrowserFavorites, APPCOMMAND_BROWSER_FAVORITES)
+KEY_MAP_ANDROID(BrowserFavorites, AKEYCODE_BOOKMARK)
+
+// BrowserForward
+KEY_MAP_WIN(BrowserForward, VK_BROWSER_FORWARD)
+KEY_MAP_WIN_CMD(BrowserForward, APPCOMMAND_BROWSER_FORWARD)
+KEY_MAP_GTK(BrowserForward, GDK_Forward)
+KEY_MAP_ANDROID(BrowserForward, AKEYCODE_FORWARD)
+
+// BrowserHome
+KEY_MAP_WIN(BrowserHome, VK_BROWSER_HOME)
+KEY_MAP_WIN_CMD(BrowserHome, APPCOMMAND_BROWSER_HOME)
+KEY_MAP_GTK(BrowserHome, GDK_HomePage)
+
+// BrowserRefresh
+KEY_MAP_WIN(BrowserRefresh, VK_BROWSER_REFRESH)
+KEY_MAP_WIN_CMD(BrowserRefresh, APPCOMMAND_BROWSER_REFRESH)
+KEY_MAP_GTK(BrowserRefresh, GDK_Refresh)
+KEY_MAP_GTK(BrowserRefresh, GDK_Reload)
+
+// BrowserSearch
+KEY_MAP_WIN(BrowserSearch, VK_BROWSER_SEARCH)
+KEY_MAP_WIN_CMD(BrowserSearch, APPCOMMAND_BROWSER_SEARCH)
+KEY_MAP_GTK(BrowserSearch, GDK_Search)
+KEY_MAP_ANDROID(BrowserSearch, AKEYCODE_SEARCH)
+
+// BrowserStop
+KEY_MAP_WIN(BrowserStop, VK_BROWSER_STOP)
+KEY_MAP_WIN_CMD(BrowserStop, APPCOMMAND_BROWSER_STOP)
+KEY_MAP_GTK(BrowserStop, GDK_Stop)
+
+/******************************************************************************
+ * Mobile Phone Keys
+ ******************************************************************************/
+// AppSwitch
+KEY_MAP_ANDROID(AppSwitch, AKEYCODE_APP_SWITCH)
+
+// Call
+KEY_MAP_ANDROID(Call, AKEYCODE_CALL)
+
+// Camera
+KEY_MAP_ANDROID(Camera, AKEYCODE_CAMERA)
+
+// CameraFocus
+KEY_MAP_ANDROID(CameraFocus, AKEYCODE_FOCUS)
+
+// EndCall
+KEY_MAP_ANDROID(EndCall, AKEYCODE_ENDCALL)
+
+// GoBack
+KEY_MAP_ANDROID(GoBack, AKEYCODE_BACK)
+
+// GoHome
+KEY_MAP_ANDROID(GoHome, AKEYCODE_HOME)
+
+// HeadsetHook
+KEY_MAP_ANDROID(HeadsetHook, AKEYCODE_HEADSETHOOK)
+
+// Notification
+KEY_MAP_ANDROID(Notification, AKEYCODE_NOTIFICATION)
+
+// MannerMode
+KEY_MAP_ANDROID(MannerMode, AKEYCODE_MANNER_MODE)
+
+/******************************************************************************
+ * TV Keys
+ ******************************************************************************/
+// TV
+KEY_MAP_ANDROID(TV, AKEYCODE_TV)
+
+// TV3DMode
+KEY_MAP_ANDROID(TV3DMode, AKEYCODE_3D_MODE)
+
+// TVAntennaCable
+KEY_MAP_ANDROID(TVAntennaCable, AKEYCODE_TV_ANTENNA_CABLE)
+
+// TVAudioDescription
+KEY_MAP_ANDROID(TVAudioDescription, AKEYCODE_TV_AUDIO_DESCRIPTION)
+
+// TVAudioDescriptionMixDown
+KEY_MAP_ANDROID(TVAudioDescriptionMixDown,
+ AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN)
+
+// TVAudioDescriptionMixUp
+KEY_MAP_ANDROID(TVAudioDescriptionMixUp, AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP)
+
+// TVContentsMenu
+KEY_MAP_ANDROID(TVContentsMenu, AKEYCODE_TV_CONTENTS_MENU)
+
+// TVDataService
+KEY_MAP_ANDROID(TVDataService, AKEYCODE_TV_DATA_SERVICE)
+
+// TVInput
+KEY_MAP_ANDROID(TVInput, AKEYCODE_TV_INPUT)
+
+// TVInputComponent1
+KEY_MAP_ANDROID(TVInputComponent1, AKEYCODE_TV_INPUT_COMPONENT_1)
+
+// TVInputComponent2
+KEY_MAP_ANDROID(TVInputComponent2, AKEYCODE_TV_INPUT_COMPONENT_2)
+
+// TVInputComposite1
+KEY_MAP_ANDROID(TVInputComposite1, AKEYCODE_TV_INPUT_COMPOSITE_1)
+
+// TVInputComposite2
+KEY_MAP_ANDROID(TVInputComposite2, AKEYCODE_TV_INPUT_COMPOSITE_2)
+
+// TVInputHDMI1
+KEY_MAP_ANDROID(TVInputHDMI1, AKEYCODE_TV_INPUT_HDMI_1)
+
+// TVInputHDMI2
+KEY_MAP_ANDROID(TVInputHDMI2, AKEYCODE_TV_INPUT_HDMI_2)
+
+// TVInputHDMI3
+KEY_MAP_ANDROID(TVInputHDMI3, AKEYCODE_TV_INPUT_HDMI_3)
+
+// TVInputHDMI4
+KEY_MAP_ANDROID(TVInputHDMI4, AKEYCODE_TV_INPUT_HDMI_4)
+
+// TVInputVGA1
+KEY_MAP_ANDROID(TVInputVGA1, AKEYCODE_TV_INPUT_VGA_1)
+
+// TVNetwork
+KEY_MAP_ANDROID(TVNetwork, AKEYCODE_TV_NETWORK)
+
+// TVNumberEntry
+KEY_MAP_ANDROID(TVNumberEntry, AKEYCODE_TV_NUMBER_ENTRY)
+
+// TVPower
+KEY_MAP_ANDROID(TVPower, AKEYCODE_TV_POWER)
+
+// TVRadioService
+KEY_MAP_ANDROID(TVRadioService, AKEYCODE_TV_RADIO_SERVICE)
+
+// TVSatellite
+KEY_MAP_ANDROID(TVSatellite, AKEYCODE_TV_SATELLITE)
+
+// TVSatelliteBS
+KEY_MAP_ANDROID(TVSatelliteBS, AKEYCODE_TV_SATELLITE_BS)
+
+// TVSatelliteCS
+KEY_MAP_ANDROID(TVSatelliteCS, AKEYCODE_TV_SATELLITE_CS)
+
+// TVSatelliteToggle
+KEY_MAP_ANDROID(TVSatelliteToggle, AKEYCODE_TV_SATELLITE_SERVICE)
+
+// TVTerrestrialAnalog
+KEY_MAP_ANDROID(TVTerrestrialAnalog, AKEYCODE_TV_TERRESTRIAL_ANALOG)
+
+// TVTerrestrialDigital
+KEY_MAP_ANDROID(TVTerrestrialDigital, AKEYCODE_TV_TERRESTRIAL_DIGITAL)
+
+// TVTimer
+KEY_MAP_ANDROID(TVTimer, AKEYCODE_TV_TIMER_PROGRAMMING)
+
+/******************************************************************************
+ * Media Controller Keys
+ ******************************************************************************/
+// AVRInput
+KEY_MAP_ANDROID(AVRInput, AKEYCODE_AVR_INPUT)
+
+// AVRPower
+KEY_MAP_ANDROID(AVRPower, AKEYCODE_AVR_POWER)
+
+// ColorF0Red
+KEY_MAP_GTK(ColorF0Red, GDK_Red)
+KEY_MAP_ANDROID(ColorF0Red, AKEYCODE_PROG_RED)
+
+// ColorF1Green
+KEY_MAP_GTK(ColorF1Green, GDK_Green)
+KEY_MAP_ANDROID(ColorF1Green, AKEYCODE_PROG_GREEN)
+
+// ColorF2Yellow
+KEY_MAP_GTK(ColorF2Yellow, GDK_Yellow)
+KEY_MAP_ANDROID(ColorF2Yellow, AKEYCODE_PROG_YELLOW)
+
+// ColorF3Blue
+KEY_MAP_GTK(ColorF3Blue, GDK_Blue)
+KEY_MAP_ANDROID(ColorF3Blue, AKEYCODE_PROG_BLUE)
+
+// ClosedCaptionToggle
+KEY_MAP_ANDROID(ClosedCaptionToggle, AKEYCODE_CAPTIONS)
+
+// Dimmer
+KEY_MAP_GTK(Dimmer, GDK_BrightnessAdjust)
+
+// DVR
+KEY_MAP_ANDROID(DVR, AKEYCODE_DVR)
+
+// Guide
+KEY_MAP_ANDROID(Guide, AKEYCODE_GUIDE)
+
+// Info
+KEY_MAP_ANDROID(Info, AKEYCODE_INFO)
+
+// MediaAudioTrack
+KEY_MAP_ANDROID(MediaAudioTrack, AKEYCODE_MEDIA_AUDIO_TRACK)
+
+// MediaLast
+KEY_MAP_ANDROID(MediaLast, AKEYCODE_LAST_CHANNEL)
+
+// MediaTopMenu
+KEY_MAP_ANDROID(MediaTopMenu, AKEYCODE_MEDIA_TOP_MENU)
+
+// MediaSkipBackward
+KEY_MAP_ANDROID(MediaSkipBackward, AKEYCODE_MEDIA_SKIP_BACKWARD)
+
+// MediaSkipForward
+KEY_MAP_ANDROID(MediaSkipForward, AKEYCODE_MEDIA_SKIP_FORWARD)
+
+// MediaStepBackward
+KEY_MAP_ANDROID(MediaStepBackward, AKEYCODE_MEDIA_STEP_BACKWARD)
+
+// MediaStepForward
+KEY_MAP_ANDROID(MediaStepForward, AKEYCODE_MEDIA_STEP_FORWARD)
+
+// NavigateIn
+KEY_MAP_ANDROID(NavigateIn, AKEYCODE_NAVIGATE_IN)
+
+// NavigateNext
+KEY_MAP_ANDROID(NavigateNext, AKEYCODE_NAVIGATE_NEXT)
+
+// NavigateOut
+KEY_MAP_ANDROID(NavigateOut, AKEYCODE_NAVIGATE_OUT)
+
+// NavigatePrevious
+KEY_MAP_ANDROID(NavigatePrevious, AKEYCODE_NAVIGATE_PREVIOUS)
+
+// Pairing
+KEY_MAP_ANDROID(Pairing, AKEYCODE_PAIRING)
+
+// PinPToggle
+KEY_MAP_ANDROID(PinPToggle, AKEYCODE_WINDOW)
+
+// RandomToggle
+KEY_MAP_GTK(RandomToggle, GDK_AudioRandomPlay)
+
+// Settings
+KEY_MAP_ANDROID(Settings, AKEYCODE_SETTINGS)
+
+// STBInput
+KEY_MAP_ANDROID(STBInput, AKEYCODE_STB_INPUT)
+
+// STBPower
+KEY_MAP_ANDROID(STBPower, AKEYCODE_STB_POWER)
+
+// Subtitle
+KEY_MAP_GTK(Subtitle, GDK_Subtitle)
+
+// Teletext
+KEY_MAP_ANDROID(Teletext, AKEYCODE_TV_TELETEXT)
+
+// VideoModeNext
+KEY_MAP_GTK(VideoModeNext, GDK_Next_VMode)
+
+// ZoomToggle
+KEY_MAP_WIN(ZoomToggle, VK_ZOOM)
+KEY_MAP_ANDROID(ZoomToggle, AKEYCODE_TV_ZOOM_MODE)
+
+/******************************************************************************
+ * Keys not defined by any standards
+ ******************************************************************************/
+// SoftLeft
+KEY_MAP_ANDROID(SoftLeft, AKEYCODE_SOFT_LEFT)
+
+// SoftRight
+KEY_MAP_ANDROID(SoftRight, AKEYCODE_SOFT_RIGHT)
+
+#undef KEY_MAP_WIN
+#undef KEY_MAP_WIN_JPN
+#undef KEY_MAP_WIN_KOR
+#undef KEY_MAP_WIN_OTH
+#undef KEY_MAP_WIN_CMD
+#undef KEY_MAP_COCOA
+#undef KEY_MAP_GTK
+#undef KEY_MAP_ANDROID
diff --git a/widget/PluginWidgetProxy.cpp b/widget/PluginWidgetProxy.cpp
new file mode 100644
index 0000000000..a12cc164e3
--- /dev/null
+++ b/widget/PluginWidgetProxy.cpp
@@ -0,0 +1,173 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "PluginWidgetProxy.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/plugins/PluginWidgetChild.h"
+#include "mozilla/plugins/PluginInstanceParent.h"
+#include "nsDebug.h"
+
+#define PWLOG(...)
+// #define PWLOG(...) printf_stderr(__VA_ARGS__)
+
+/* static */
+already_AddRefed<nsIWidget> nsIWidget::CreatePluginProxyWidget(
+ BrowserChild* aBrowserChild, mozilla::plugins::PluginWidgetChild* aActor) {
+ nsCOMPtr<nsIWidget> widget =
+ new mozilla::widget::PluginWidgetProxy(aBrowserChild, aActor);
+ return widget.forget();
+}
+
+namespace mozilla {
+namespace widget {
+
+using mozilla::plugins::PluginInstanceParent;
+
+NS_IMPL_ISUPPORTS_INHERITED(PluginWidgetProxy, PuppetWidget, nsIWidget)
+
+#define ENSURE_CHANNEL \
+ do { \
+ if (!mActor) { \
+ NS_WARNING("called on an invalid channel."); \
+ return NS_ERROR_FAILURE; \
+ } \
+ } while (0)
+
+PluginWidgetProxy::PluginWidgetProxy(
+ dom::BrowserChild* aBrowserChild,
+ mozilla::plugins::PluginWidgetChild* aActor)
+ : PuppetWidget(aBrowserChild), mActor(aActor), mCachedPluginPort(0) {
+ // See ChannelDestroyed() in the header
+ mActor->SetWidget(this);
+}
+
+PluginWidgetProxy::~PluginWidgetProxy() {
+ PWLOG("PluginWidgetProxy::~PluginWidgetProxy()\n");
+}
+
+nsresult PluginWidgetProxy::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) {
+ ENSURE_CHANNEL;
+ PWLOG("PluginWidgetProxy::Create()\n");
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ uint64_t scrollCaptureId;
+ uintptr_t pluginInstanceId;
+ mActor->SendCreate(&rv, &scrollCaptureId, &pluginInstanceId);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to create chrome widget, plugins won't paint.");
+ return rv;
+ }
+
+ BaseCreate(aParent, aInitData);
+ mParent = aParent;
+
+ mBounds = aRect;
+ mEnabled = true;
+ mVisible = true;
+
+ PluginInstanceParent* instance =
+ PluginInstanceParent::LookupPluginInstanceByID(pluginInstanceId);
+ if (instance) {
+ Unused << NS_WARN_IF(
+ NS_FAILED(instance->SetScrollCaptureId(scrollCaptureId)));
+ }
+
+ return NS_OK;
+}
+
+void PluginWidgetProxy::SetParent(nsIWidget* aNewParent) {
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+ nsIWidget* parent = GetParent();
+ if (parent) {
+ parent->RemoveChild(this);
+ }
+ if (aNewParent) {
+ aNewParent->AddChild(this);
+ }
+ mParent = aNewParent;
+}
+
+nsIWidget* PluginWidgetProxy::GetParent(void) { return mParent.get(); }
+
+void PluginWidgetProxy::Destroy() {
+ PWLOG("PluginWidgetProxy::Destroy()\n");
+
+ if (mActor) {
+ // Communicate that the layout widget has been torn down before the sub
+ // protocol.
+ mActor->ProxyShutdown();
+ mActor = nullptr;
+ }
+
+ PuppetWidget::Destroy();
+}
+
+void PluginWidgetProxy::GetWindowClipRegion(
+ nsTArray<LayoutDeviceIntRect>* aRects) {
+ if (mClipRects && mClipRectCount) {
+ aRects->AppendElements(mClipRects.get(), mClipRectCount);
+ }
+}
+
+void* PluginWidgetProxy::GetNativeData(uint32_t aDataType) {
+ if (!mActor) {
+ return nullptr;
+ }
+ auto tab = static_cast<mozilla::dom::BrowserChild*>(mActor->Manager());
+ if (tab && tab->IsDestroyed()) {
+ return nullptr;
+ }
+ switch (aDataType) {
+ case NS_NATIVE_PLUGIN_PORT:
+ case NS_NATIVE_WINDOW:
+ case NS_NATIVE_SHAREABLE_WINDOW:
+ break;
+ default:
+ NS_WARNING(
+ "PluginWidgetProxy::GetNativeData received request for unsupported "
+ "data type.");
+ return nullptr;
+ }
+ // The parent side window handle or xid never changes so we can
+ // cache this for our lifetime.
+ if (mCachedPluginPort) {
+ return (void*)mCachedPluginPort;
+ }
+ mActor->SendGetNativePluginPort(&mCachedPluginPort);
+ PWLOG("PluginWidgetProxy::GetNativeData %p\n", (void*)mCachedPluginPort);
+ return (void*)mCachedPluginPort;
+}
+
+void PluginWidgetProxy::SetNativeData(uint32_t aDataType, uintptr_t aVal) {
+ if (!mActor) {
+ return;
+ }
+
+ auto tab = static_cast<mozilla::dom::BrowserChild*>(mActor->Manager());
+ if (tab && tab->IsDestroyed()) {
+ return;
+ }
+
+ switch (aDataType) {
+ case NS_NATIVE_CHILD_WINDOW:
+ mActor->SendSetNativeChildWindow(aVal);
+ break;
+ default:
+ NS_ERROR("SetNativeData called with unsupported data type.");
+ }
+}
+
+void PluginWidgetProxy::SetFocus(Raise aRaise,
+ mozilla::dom::CallerType aCallerType) {
+ if (mActor) {
+ PWLOG("PluginWidgetProxy::SetFocus(%d)\n", aRaise == Raise::Yes);
+ mActor->SendSetFocus(aRaise == Raise::Yes, aCallerType);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/PluginWidgetProxy.h b/widget/PluginWidgetProxy.h
new file mode 100644
index 0000000000..dde703c095
--- /dev/null
+++ b/widget/PluginWidgetProxy.h
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_RemotePlugin_h__
+#define mozilla_widget_RemotePlugin_h__
+
+#ifndef XP_WIN
+# error "Plugin widgets are Windows-only."
+#endif
+
+#include "PuppetWidget.h"
+#include "mozilla/dom/BrowserChild.h"
+
+/*
+ * PluginWidgetProxy is a nsIWidget wrapper we hand around in plugin and layout
+ * code. It wraps a native widget it creates in the chrome process. Since this
+ * is for plugins, only a limited set of the widget apis need to be overridden,
+ * the rest of the implementation is in PuppetWidget or nsBaseWidget.
+ */
+
+namespace mozilla {
+namespace plugins {
+class PluginWidgetChild;
+} // namespace plugins
+namespace widget {
+
+class PluginWidgetProxy final : public PuppetWidget {
+ public:
+ explicit PluginWidgetProxy(dom::BrowserChild* aBrowserChild,
+ mozilla::plugins::PluginWidgetChild* aChannel);
+
+ protected:
+ virtual ~PluginWidgetProxy();
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIWidget
+ using PuppetWidget::Create; // for Create signature not overridden here
+ [[nodiscard]] virtual nsresult Create(
+ nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr) override;
+ virtual void Destroy() override;
+ virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override;
+ virtual void SetParent(nsIWidget* aNewParent) override;
+
+ virtual nsIWidget* GetParent(void) override;
+ virtual void* GetNativeData(uint32_t aDataType) override;
+ void SetNativeData(uint32_t aDataType, uintptr_t aVal) override;
+ virtual nsTransparencyMode GetTransparencyMode() override {
+ return eTransparencyOpaque;
+ }
+ virtual void GetWindowClipRegion(
+ nsTArray<LayoutDeviceIntRect>* aRects) override;
+
+ public:
+ /**
+ * When tabs are closed PPluginWidget can terminate before plugin code is
+ * finished tearing us down. When this happens plugin calls over mActor
+ * fail triggering an abort in the content process. To protect against this
+ * the connection tells us when it is torn down here so we can avoid making
+ * calls while content finishes tearing us down.
+ */
+ void ChannelDestroyed() { mActor = nullptr; }
+
+ private:
+ // Our connection with the chrome widget, created on PBrowser.
+ mozilla::plugins::PluginWidgetChild* mActor;
+ // PuppetWidget does not implement parent apis, but we need
+ // them for plugin widgets.
+ nsCOMPtr<nsIWidget> mParent;
+ uintptr_t mCachedPluginPort;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/PrintBackgroundTask.h b/widget/PrintBackgroundTask.h
new file mode 100644
index 0000000000..d8b4d8a4e5
--- /dev/null
+++ b/widget/PrintBackgroundTask.h
@@ -0,0 +1,120 @@
+/* -*- 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 mozilla_PrintBackgroundTask_h_
+#define mozilla_PrintBackgroundTask_h_
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Telemetry.h"
+#include <utility>
+#include <tuple>
+
+// A helper to resolve a DOM Promise with the result of a const method, executed
+// in another thread.
+//
+// Once in the main thread, the caller can turn the result of the method into a
+// JSValue by specializing ResolveOrReject.
+namespace mozilla {
+
+template <typename T, typename Result>
+void ResolveOrReject(dom::Promise& aPromise, T&, Result& aResult) {
+ aPromise.MaybeResolve(std::forward<Result>(aResult));
+}
+
+template <typename T, typename Result, typename... Args>
+using PrintBackgroundTask = Result (T::*)(Args...) const;
+
+template <typename T, typename Result, typename... Args>
+void SpawnPrintBackgroundTask(
+ T& aReceiver, dom::Promise& aPromise, const nsCString& aTelemetryKey,
+ PrintBackgroundTask<T, Result, Args...> aBackgroundTask, Args... aArgs) {
+ auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>(
+ "nsPrinterBase::SpawnBackgroundTaskPromise", &aPromise);
+ // We actually want to allow to access the printer data from the callback, so
+ // disable strict checking. They should of course only access immutable
+ // members.
+ auto holder = MakeRefPtr<nsMainThreadPtrHolder<T>>(
+ "nsPrinterBase::SpawnBackgroundTaskPrinter", &aReceiver,
+ /* strict = */ false);
+ // See
+ // https://stackoverflow.com/questions/47496358/c-lambdas-how-to-capture-variadic-parameter-pack-from-the-upper-scope
+ // about the tuple shenanigans. It could be improved with C++20
+ NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction(
+ "SpawnPrintBackgroundTask",
+ [holder = std::move(holder), promiseHolder = std::move(promiseHolder),
+ aTelemetryKey, startRoundTrip = TimeStamp::Now(),
+ backgroundTask = aBackgroundTask,
+ aArgs = std::make_tuple(std::forward<Args>(aArgs)...)] {
+ auto start = TimeStamp::Now();
+ Result result = std::apply(
+ [&](auto&&... args) {
+ return (holder->get()->*backgroundTask)(args...);
+ },
+ std::move(aArgs));
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PRINT_BACKGROUND_TASK_TIME_MS, aTelemetryKey, start);
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "SpawnPrintBackgroundTaskResolution",
+ [holder = std::move(holder),
+ promiseHolder = std::move(promiseHolder),
+ telemetryKey = std::move(aTelemetryKey), startRoundTrip,
+ result = std::move(result)] {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PRINT_BACKGROUND_TASK_ROUND_TRIP_TIME_MS,
+ telemetryKey, startRoundTrip);
+ ResolveOrReject(*promiseHolder->get(), *holder->get(),
+ result);
+ }));
+ }),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+}
+
+// Gets a fresh promise into aResultPromise, that resolves whenever the print
+// background task finishes.
+template <typename T, typename Result, typename... Args>
+nsresult PrintBackgroundTaskPromise(
+ T& aReceiver, JSContext* aCx, dom::Promise** aResultPromise,
+ const nsCString& aTelemetryKey,
+ PrintBackgroundTask<T, Result, Args...> aTask, Args... aArgs) {
+ ErrorResult rv;
+ RefPtr<dom::Promise> promise =
+ dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv);
+ if (MOZ_UNLIKELY(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ SpawnPrintBackgroundTask(aReceiver, *promise, aTelemetryKey, aTask,
+ std::forward<Args>(aArgs)...);
+
+ promise.forget(aResultPromise);
+ return NS_OK;
+}
+
+// Resolves an async attribute via a background task, creating and storing a
+// promise as needed in aPromiseSlot.
+template <typename T, typename Result, typename... Args>
+nsresult AsyncPromiseAttributeGetter(
+ T& aReceiver, RefPtr<dom::Promise>& aPromiseSlot, JSContext* aCx,
+ dom::Promise** aResultPromise, const nsCString& aTelemetryKey,
+ PrintBackgroundTask<T, Result, Args...> aTask, Args... aArgs) {
+ if (RefPtr<dom::Promise> existing = aPromiseSlot) {
+ existing.forget(aResultPromise);
+ return NS_OK;
+ }
+
+ nsresult rv =
+ PrintBackgroundTaskPromise(aReceiver, aCx, aResultPromise, aTelemetryKey,
+ aTask, std::forward<Args>(aArgs)...);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aPromiseSlot = *aResultPromise;
+ return NS_OK;
+}
+
+} // namespace mozilla
+
+#endif
diff --git a/widget/PuppetBidiKeyboard.cpp b/widget/PuppetBidiKeyboard.cpp
new file mode 100644
index 0000000000..99ea6f6ca9
--- /dev/null
+++ b/widget/PuppetBidiKeyboard.cpp
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PuppetBidiKeyboard.h"
+#include "nsIWidget.h"
+
+using namespace mozilla::widget;
+
+NS_IMPL_ISUPPORTS(PuppetBidiKeyboard, nsIBidiKeyboard)
+
+PuppetBidiKeyboard::PuppetBidiKeyboard()
+ : nsIBidiKeyboard(), mIsLangRTL(false), mHaveBidiKeyboards(false) {}
+
+PuppetBidiKeyboard::~PuppetBidiKeyboard() = default;
+
+NS_IMETHODIMP
+PuppetBidiKeyboard::Reset() { return NS_OK; }
+
+NS_IMETHODIMP
+PuppetBidiKeyboard::IsLangRTL(bool* aIsRTL) {
+ *aIsRTL = mIsLangRTL;
+ return NS_OK;
+}
+
+void PuppetBidiKeyboard::SetBidiKeyboardInfo(bool aIsLangRTL,
+ bool aHaveBidiKeyboards) {
+ mIsLangRTL = aIsLangRTL;
+ mHaveBidiKeyboards = aHaveBidiKeyboards;
+}
+
+NS_IMETHODIMP
+PuppetBidiKeyboard::GetHaveBidiKeyboards(bool* aResult) {
+ *aResult = mHaveBidiKeyboards;
+ return NS_OK;
+}
+
+// static
+already_AddRefed<nsIBidiKeyboard>
+nsIWidget::CreateBidiKeyboardContentProcess() {
+ return do_AddRef(new PuppetBidiKeyboard());
+}
diff --git a/widget/PuppetBidiKeyboard.h b/widget/PuppetBidiKeyboard.h
new file mode 100644
index 0000000000..82250cc36e
--- /dev/null
+++ b/widget/PuppetBidiKeyboard.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_PuppetBidiKeyboard_h_
+#define mozilla_widget_PuppetBidiKeyboard_h_
+
+#include "nsIBidiKeyboard.h"
+
+namespace mozilla {
+namespace widget {
+
+class PuppetBidiKeyboard final : public nsIBidiKeyboard {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIBIDIKEYBOARD
+
+ PuppetBidiKeyboard();
+
+ void SetBidiKeyboardInfo(bool aIsLangRTL, bool aHaveBidiKeyboards);
+
+ private:
+ ~PuppetBidiKeyboard();
+
+ bool mIsLangRTL;
+ bool mHaveBidiKeyboards;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_PuppetBidiKeyboard_h_
diff --git a/widget/PuppetWidget.cpp b/widget/PuppetWidget.cpp
new file mode 100644
index 0000000000..159096a08b
--- /dev/null
+++ b/widget/PuppetWidget.cpp
@@ -0,0 +1,1291 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+
+#include "ClientLayerManager.h"
+#include "gfxPlatform.h"
+#include "nsRefreshDriver.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/Hal.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/layers/APZChild.h"
+#include "mozilla/layers/PLayerTransactionChild.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/TextComposition.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/Unused.h"
+#include "BasicLayers.h"
+#include "PuppetWidget.h"
+#include "nsContentUtils.h"
+#include "nsIWidgetListener.h"
+#include "imgIContainer.h"
+#include "nsView.h"
+#include "nsXPLookAndFeel.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::hal;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+
+static void InvalidateRegion(nsIWidget* aWidget,
+ const LayoutDeviceIntRegion& aRegion) {
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ aWidget->Invalidate(iter.Get());
+ }
+}
+
+/*static*/
+already_AddRefed<nsIWidget> nsIWidget::CreatePuppetWidget(
+ BrowserChild* aBrowserChild) {
+ MOZ_ASSERT(!aBrowserChild || nsIWidget::UsePuppetWidgets(),
+ "PuppetWidgets not allowed in this configuration");
+
+ nsCOMPtr<nsIWidget> widget = new PuppetWidget(aBrowserChild);
+ return widget.forget();
+}
+
+namespace mozilla {
+namespace widget {
+
+static bool IsPopup(const nsWidgetInitData* aInitData) {
+ return aInitData && aInitData->mWindowType == eWindowType_popup;
+}
+
+static bool MightNeedIMEFocus(const nsWidgetInitData* aInitData) {
+ // In the puppet-widget world, popup widgets are just dummies and
+ // shouldn't try to mess with IME state.
+#ifdef MOZ_CROSS_PROCESS_IME
+ return !IsPopup(aInitData);
+#else
+ return false;
+#endif
+}
+
+// Arbitrary, fungible.
+const size_t PuppetWidget::kMaxDimension = 4000;
+
+NS_IMPL_ISUPPORTS_INHERITED(PuppetWidget, nsBaseWidget,
+ TextEventDispatcherListener)
+
+PuppetWidget::PuppetWidget(BrowserChild* aBrowserChild)
+ : mBrowserChild(aBrowserChild),
+ mMemoryPressureObserver(nullptr),
+ mDPI(-1),
+ mRounding(1),
+ mDefaultScale(-1),
+ mCursorHotspotX(0),
+ mCursorHotspotY(0),
+ mEnabled(false),
+ mVisible(false),
+ mNeedIMEStateInit(false),
+ mIgnoreCompositionEvents(false) {
+ // Setting 'Unknown' means "not yet cached".
+ mInputContext.mIMEState.mEnabled = IMEEnabled::Unknown;
+}
+
+PuppetWidget::~PuppetWidget() { Destroy(); }
+
+void PuppetWidget::InfallibleCreate(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) {
+ MOZ_ASSERT(!aNativeParent, "got a non-Puppet native parent");
+
+ BaseCreate(nullptr, aInitData);
+
+ mBounds = aRect;
+ mEnabled = true;
+ mVisible = true;
+
+ mDrawTarget = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ IntSize(1, 1), SurfaceFormat::B8G8R8A8);
+
+ mNeedIMEStateInit = MightNeedIMEFocus(aInitData);
+
+ PuppetWidget* parent = static_cast<PuppetWidget*>(aParent);
+ if (parent) {
+ parent->SetChild(this);
+ mLayerManager = parent->GetLayerManager();
+ } else {
+ Resize(mBounds.X(), mBounds.Y(), mBounds.Width(), mBounds.Height(), false);
+ }
+ mMemoryPressureObserver = MemoryPressureObserver::Create(this);
+}
+
+nsresult PuppetWidget::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) {
+ InfallibleCreate(aParent, aNativeParent, aRect, aInitData);
+ return NS_OK;
+}
+
+void PuppetWidget::InitIMEState() {
+ MOZ_ASSERT(mBrowserChild);
+ if (mNeedIMEStateInit) {
+ mContentCache.Clear();
+ mBrowserChild->SendUpdateContentCache(mContentCache);
+ mIMENotificationRequestsOfParent = IMENotificationRequests();
+ mNeedIMEStateInit = false;
+ }
+}
+
+already_AddRefed<nsIWidget> PuppetWidget::CreateChild(
+ const LayoutDeviceIntRect& aRect, nsWidgetInitData* aInitData,
+ bool aForceUseIWidgetParent) {
+ bool isPopup = IsPopup(aInitData);
+ nsCOMPtr<nsIWidget> widget = nsIWidget::CreatePuppetWidget(mBrowserChild);
+ return ((widget && NS_SUCCEEDED(widget->Create(isPopup ? nullptr : this,
+ nullptr, aRect, aInitData)))
+ ? widget.forget()
+ : nullptr);
+}
+
+void PuppetWidget::Destroy() {
+ if (mOnDestroyCalled) {
+ return;
+ }
+ mOnDestroyCalled = true;
+
+ Base::OnDestroy();
+ Base::Destroy();
+ if (mMemoryPressureObserver) {
+ mMemoryPressureObserver->Unregister();
+ mMemoryPressureObserver = nullptr;
+ }
+ mChild = nullptr;
+ if (mLayerManager) {
+ mLayerManager->Destroy();
+ }
+ mLayerManager = nullptr;
+ mBrowserChild = nullptr;
+}
+
+void PuppetWidget::Show(bool aState) {
+ NS_ASSERTION(mEnabled,
+ "does it make sense to Show()/Hide() a disabled widget?");
+
+ bool wasVisible = mVisible;
+ mVisible = aState;
+
+ if (mChild) {
+ mChild->mVisible = aState;
+ }
+
+ if (!wasVisible && mVisible) {
+ // The previously attached widget listener is handy if
+ // we're transitioning from page to page without dropping
+ // layers (since we'll continue to show the old layers
+ // associated with that old widget listener). If the
+ // PuppetWidget was hidden, those layers are dropped,
+ // so the previously attached widget listener is really
+ // of no use anymore (and is actually actively harmful - see
+ // bug 1323586).
+ mPreviouslyAttachedWidgetListener = nullptr;
+ Resize(mBounds.Width(), mBounds.Height(), false);
+ Invalidate(mBounds);
+ }
+}
+
+void PuppetWidget::Resize(double aWidth, double aHeight, bool aRepaint) {
+ LayoutDeviceIntRect oldBounds = mBounds;
+ mBounds.SizeTo(
+ LayoutDeviceIntSize(NSToIntRound(aWidth), NSToIntRound(aHeight)));
+
+ if (mChild) {
+ mChild->Resize(aWidth, aHeight, aRepaint);
+ return;
+ }
+
+ // XXX: roc says that |aRepaint| dictates whether or not to
+ // invalidate the expanded area
+ if (oldBounds.Size() < mBounds.Size() && aRepaint) {
+ LayoutDeviceIntRegion dirty(mBounds);
+ dirty.Sub(dirty, oldBounds);
+ InvalidateRegion(this, dirty);
+ }
+
+ // call WindowResized() on both the current listener, and possibly
+ // also the previous one if we're in a state where we're drawing that one
+ // because the current one is paint suppressed
+ if (!oldBounds.IsEqualEdges(mBounds) && mAttachedWidgetListener) {
+ if (GetCurrentWidgetListener() &&
+ GetCurrentWidgetListener() != mAttachedWidgetListener) {
+ GetCurrentWidgetListener()->WindowResized(this, mBounds.Width(),
+ mBounds.Height());
+ }
+ mAttachedWidgetListener->WindowResized(this, mBounds.Width(),
+ mBounds.Height());
+ }
+}
+
+nsresult PuppetWidget::ConfigureChildren(
+ const nsTArray<Configuration>& aConfigurations) {
+ for (uint32_t i = 0; i < aConfigurations.Length(); ++i) {
+ const Configuration& configuration = aConfigurations[i];
+ PuppetWidget* w = static_cast<PuppetWidget*>(configuration.mChild.get());
+ NS_ASSERTION(w->GetParent() == this, "Configured widget is not a child");
+ w->SetWindowClipRegion(configuration.mClipRegion, true);
+ LayoutDeviceIntRect bounds = w->GetBounds();
+ if (bounds.Size() != configuration.mBounds.Size()) {
+ w->Resize(configuration.mBounds.X(), configuration.mBounds.Y(),
+ configuration.mBounds.Width(), configuration.mBounds.Height(),
+ true);
+ } else if (bounds.TopLeft() != configuration.mBounds.TopLeft()) {
+ w->Move(configuration.mBounds.X(), configuration.mBounds.Y());
+ }
+ w->SetWindowClipRegion(configuration.mClipRegion, false);
+ }
+ return NS_OK;
+}
+
+void PuppetWidget::SetFocus(Raise aRaise, CallerType aCallerType) {
+ if (aRaise == Raise::Yes && mBrowserChild) {
+ mBrowserChild->SendRequestFocus(true, aCallerType);
+ }
+}
+
+void PuppetWidget::Invalidate(const LayoutDeviceIntRect& aRect) {
+#ifdef DEBUG
+ debug_DumpInvalidate(stderr, this, &aRect, "PuppetWidget", 0);
+#endif
+
+ if (mChild) {
+ mChild->Invalidate(aRect);
+ return;
+ }
+
+ if (mBrowserChild && !aRect.IsEmpty()) {
+ if (RefPtr<nsRefreshDriver> refreshDriver = GetTopLevelRefreshDriver()) {
+ refreshDriver->ScheduleViewManagerFlush();
+ }
+ }
+}
+
+mozilla::LayoutDeviceToLayoutDeviceMatrix4x4
+PuppetWidget::WidgetToTopLevelWidgetTransform() {
+ if (!GetOwningBrowserChild()) {
+ NS_WARNING("PuppetWidget without Tab does not have transform information.");
+ return mozilla::LayoutDeviceToLayoutDeviceMatrix4x4();
+ }
+ return GetOwningBrowserChild()->GetChildToParentConversionMatrix();
+}
+
+void PuppetWidget::InitEvent(WidgetGUIEvent& aEvent,
+ LayoutDeviceIntPoint* aPoint) {
+ if (nullptr == aPoint) {
+ aEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ } else {
+ // use the point override if provided
+ aEvent.mRefPoint = *aPoint;
+ }
+ aEvent.mTime = PR_Now() / 1000;
+}
+
+nsresult PuppetWidget::DispatchEvent(WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) {
+#ifdef DEBUG
+ debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "PuppetWidget", 0);
+#endif
+
+ MOZ_ASSERT(!mChild || mChild->mWindowType == eWindowType_popup,
+ "Unexpected event dispatch!");
+
+ MOZ_ASSERT(!aEvent->AsKeyboardEvent() ||
+ aEvent->mFlags.mIsSynthesizedForTests ||
+ aEvent->AsKeyboardEvent()->AreAllEditCommandsInitialized(),
+ "Non-sysnthesized keyboard events should have edit commands for "
+ "all types "
+ "before dispatched");
+
+ if (aEvent->mClass == eCompositionEventClass) {
+ // If we've already requested to commit/cancel the latest composition,
+ // TextComposition for the old composition has been destroyed. Then,
+ // the DOM tree needs to listen to next eCompositionStart and its
+ // following events. So, until we meet new eCompositionStart, let's
+ // discard all unnecessary composition events here.
+ if (mIgnoreCompositionEvents) {
+ if (aEvent->mMessage != eCompositionStart) {
+ aStatus = nsEventStatus_eIgnore;
+ return NS_OK;
+ }
+ // Now, we receive new eCompositionStart. Let's restart to handle
+ // composition in this process.
+ mIgnoreCompositionEvents = false;
+ }
+ // Store the latest native IME context of parent process's widget or
+ // TextEventDispatcher if it's in this process.
+ WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
+#ifdef DEBUG
+ if (mNativeIMEContext.IsValid() &&
+ mNativeIMEContext != compositionEvent->mNativeIMEContext) {
+ RefPtr<TextComposition> composition =
+ IMEStateManager::GetTextCompositionFor(this);
+ MOZ_ASSERT(
+ !composition,
+ "When there is composition caused by old native IME context, "
+ "composition events caused by different native IME context are not "
+ "allowed");
+ }
+#endif // #ifdef DEBUG
+ mNativeIMEContext = compositionEvent->mNativeIMEContext;
+ mContentCache.OnCompositionEvent(*compositionEvent);
+ }
+
+ // If the event is a composition event or a keyboard event, it should be
+ // dispatched with TextEventDispatcher if we could do that with current
+ // design. However, we cannot do that without big changes and the behavior
+ // is not so complicated for now. Therefore, we should just notify it
+ // of dispatching events and TextEventDispatcher should emulate the state
+ // with events here.
+ if (aEvent->mClass == eCompositionEventClass ||
+ aEvent->mClass == eKeyboardEventClass) {
+ TextEventDispatcher* dispatcher = GetTextEventDispatcher();
+ // However, if the event is being dispatched by the text event dispatcher
+ // or, there is native text event dispatcher listener, that means that
+ // native text input event handler is in this process like on Android,
+ // and the event is not synthesized for tests, the event is coming from
+ // the TextEventDispatcher. In these cases, we shouldn't notify
+ // TextEventDispatcher of dispatching the event.
+ if (!dispatcher->IsDispatchingEvent() &&
+ !(mNativeTextEventDispatcherListener &&
+ !aEvent->mFlags.mIsSynthesizedForTests)) {
+ DebugOnly<nsresult> rv =
+ dispatcher->BeginInputTransactionFor(aEvent, this);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "The text event dispatcher should always succeed to start input "
+ "transaction for the event");
+ }
+ }
+
+ aStatus = nsEventStatus_eIgnore;
+
+ if (GetCurrentWidgetListener()) {
+ aStatus =
+ GetCurrentWidgetListener()->HandleEvent(aEvent, mUseAttachedEvents);
+ }
+
+ return NS_OK;
+}
+
+nsEventStatus PuppetWidget::DispatchInputEvent(WidgetInputEvent* aEvent) {
+ if (!AsyncPanZoomEnabled()) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ DispatchEvent(aEvent, status);
+ return status;
+ }
+
+ if (!mBrowserChild) {
+ return nsEventStatus_eIgnore;
+ }
+
+ switch (aEvent->mClass) {
+ case eWheelEventClass:
+ Unused << mBrowserChild->SendDispatchWheelEvent(*aEvent->AsWheelEvent());
+ break;
+ case eMouseEventClass:
+ Unused << mBrowserChild->SendDispatchMouseEvent(*aEvent->AsMouseEvent());
+ break;
+ case eKeyboardEventClass:
+ Unused << mBrowserChild->SendDispatchKeyboardEvent(
+ *aEvent->AsKeyboardEvent());
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unsupported event type");
+ }
+
+ return nsEventStatus_eIgnore;
+}
+
+nsresult PuppetWidget::SynthesizeNativeKeyEvent(
+ int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode,
+ uint32_t aModifierFlags, const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "keyevent");
+ if (!mBrowserChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mBrowserChild->SendSynthesizeNativeKeyEvent(
+ aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags,
+ nsString(aCharacters), nsString(aUnmodifiedCharacters),
+ notifier.SaveObserver());
+ return NS_OK;
+}
+
+nsresult PuppetWidget::SynthesizeNativeMouseEvent(
+ mozilla::LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage,
+ uint32_t aModifierFlags, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+ if (!mBrowserChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mBrowserChild->SendSynthesizeNativeMouseEvent(
+ aPoint, aNativeMessage, aModifierFlags, notifier.SaveObserver());
+ return NS_OK;
+}
+
+nsresult PuppetWidget::SynthesizeNativeMouseMove(
+ mozilla::LayoutDeviceIntPoint aPoint, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mousemove");
+ if (!mBrowserChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mBrowserChild->SendSynthesizeNativeMouseMove(aPoint, notifier.SaveObserver());
+ return NS_OK;
+}
+
+nsresult PuppetWidget::SynthesizeNativeMouseScrollEvent(
+ mozilla::LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage,
+ double aDeltaX, double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mousescrollevent");
+ if (!mBrowserChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mBrowserChild->SendSynthesizeNativeMouseScrollEvent(
+ aPoint, aNativeMessage, aDeltaX, aDeltaY, aDeltaZ, aModifierFlags,
+ aAdditionalFlags, notifier.SaveObserver());
+ return NS_OK;
+}
+
+nsresult PuppetWidget::SynthesizeNativeTouchPoint(
+ uint32_t aPointerId, TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint, double aPointerPressure,
+ uint32_t aPointerOrientation, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+ if (!mBrowserChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mBrowserChild->SendSynthesizeNativeTouchPoint(
+ aPointerId, aPointerState, aPoint, aPointerPressure, aPointerOrientation,
+ notifier.SaveObserver());
+ return NS_OK;
+}
+
+nsresult PuppetWidget::SynthesizeNativeTouchTap(LayoutDeviceIntPoint aPoint,
+ bool aLongTap,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "touchtap");
+ if (!mBrowserChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mBrowserChild->SendSynthesizeNativeTouchTap(aPoint, aLongTap,
+ notifier.SaveObserver());
+ return NS_OK;
+}
+
+nsresult PuppetWidget::ClearNativeTouchSequence(nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "cleartouch");
+ if (!mBrowserChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mBrowserChild->SendClearNativeTouchSequence(notifier.SaveObserver());
+ return NS_OK;
+}
+
+void PuppetWidget::SetConfirmedTargetAPZC(
+ uint64_t aInputBlockId,
+ const nsTArray<ScrollableLayerGuid>& aTargets) const {
+ if (mBrowserChild) {
+ mBrowserChild->SetTargetAPZC(aInputBlockId, aTargets);
+ }
+}
+
+void PuppetWidget::UpdateZoomConstraints(
+ const uint32_t& aPresShellId, const ScrollableLayerGuid::ViewID& aViewId,
+ const Maybe<ZoomConstraints>& aConstraints) {
+ if (mBrowserChild) {
+ mBrowserChild->DoUpdateZoomConstraints(aPresShellId, aViewId, aConstraints);
+ }
+}
+
+bool PuppetWidget::AsyncPanZoomEnabled() const {
+ return mBrowserChild && mBrowserChild->AsyncPanZoomEnabled();
+}
+
+bool PuppetWidget::GetEditCommands(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands) {
+ // Validate the arguments.
+ if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
+ return false;
+ }
+ if (NS_WARN_IF(!mBrowserChild)) {
+ return false;
+ }
+ mBrowserChild->RequestEditCommands(aType, aEvent, aCommands);
+ return true;
+}
+
+LayerManager* PuppetWidget::GetLayerManager(
+ PLayerTransactionChild* aShadowManager, LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence) {
+ if (!mLayerManager) {
+ if (XRE_IsParentProcess()) {
+ // On the parent process there is no CompositorBridgeChild which confuses
+ // some layers code, so we use basic layers instead. Note that we create
+ // a non-retaining layer manager since we don't care about performance.
+ mLayerManager = new BasicLayerManager(BasicLayerManager::BLM_OFFSCREEN);
+ return mLayerManager;
+ }
+
+ // If we know for sure that the parent side of this BrowserChild is not
+ // connected to the compositor, we don't want to use a "remote" layer
+ // manager like WebRender or Client. Instead we use a Basic one which
+ // can do drawing in this process.
+ MOZ_ASSERT(!mBrowserChild ||
+ mBrowserChild->IsLayersConnected() != Some(true));
+ mLayerManager = new BasicLayerManager(this);
+ }
+
+ return mLayerManager;
+}
+
+bool PuppetWidget::CreateRemoteLayerManager(
+ const std::function<bool(LayerManager*)>& aInitializeFunc) {
+ RefPtr<LayerManager> lm;
+ MOZ_ASSERT(mBrowserChild);
+ if (mBrowserChild->GetCompositorOptions().UseWebRender()) {
+ lm = new WebRenderLayerManager(this);
+ } else {
+ lm = new ClientLayerManager(this);
+ }
+
+ if (!aInitializeFunc(lm)) {
+ return false;
+ }
+
+ // Force the old LM to self destruct, otherwise if the reference dangles we
+ // could fail to revoke the most recent transaction. We only want to replace
+ // it if we successfully create its successor because a partially initialized
+ // layer manager is worse than a fully initialized but shutdown layer manager.
+ DestroyLayerManager();
+ mLayerManager = std::move(lm);
+ return true;
+}
+
+nsresult PuppetWidget::RequestIMEToCommitComposition(bool aCancel) {
+ if (!mBrowserChild) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(!Destroyed());
+
+ // There must not be composition which is caused by the PuppetWidget instance.
+ if (NS_WARN_IF(!mNativeIMEContext.IsValid())) {
+ return NS_OK;
+ }
+
+ // We've already requested to commit/cancel composition.
+ if (NS_WARN_IF(mIgnoreCompositionEvents)) {
+#ifdef DEBUG
+ RefPtr<TextComposition> composition =
+ IMEStateManager::GetTextCompositionFor(this);
+ MOZ_ASSERT(!composition);
+#endif // #ifdef DEBUG
+ return NS_OK;
+ }
+
+ RefPtr<TextComposition> composition =
+ IMEStateManager::GetTextCompositionFor(this);
+ // This method shouldn't be called when there is no text composition instance.
+ if (NS_WARN_IF(!composition)) {
+ return NS_OK;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ composition->IsRequestingCommitOrCancelComposition(),
+ "Requesting commit or cancel composition should be requested via "
+ "TextComposition instance");
+
+ bool isCommitted = false;
+ nsAutoString committedString;
+ if (NS_WARN_IF(!mBrowserChild->SendRequestIMEToCommitComposition(
+ aCancel, &isCommitted, &committedString))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If the composition wasn't committed synchronously, we need to wait async
+ // composition events for destroying the TextComposition instance.
+ if (!isCommitted) {
+ return NS_OK;
+ }
+
+ // Dispatch eCompositionCommit event.
+ WidgetCompositionEvent compositionCommitEvent(true, eCompositionCommit, this);
+ InitEvent(compositionCommitEvent, nullptr);
+ compositionCommitEvent.mData = committedString;
+ nsEventStatus status = nsEventStatus_eIgnore;
+ DispatchEvent(&compositionCommitEvent, status);
+
+#ifdef DEBUG
+ RefPtr<TextComposition> currentComposition =
+ IMEStateManager::GetTextCompositionFor(this);
+ MOZ_ASSERT(!currentComposition);
+#endif // #ifdef DEBUG
+
+ // Ignore the following composition events until we receive new
+ // eCompositionStart event.
+ mIgnoreCompositionEvents = true;
+
+ Unused << mBrowserChild->SendOnEventNeedingAckHandled(
+ eCompositionCommitRequestHandled);
+
+ // NOTE: PuppetWidget might be destroyed already.
+ return NS_OK;
+}
+
+// When this widget caches input context and currently managed by
+// IMEStateManager, the cache is valid.
+bool PuppetWidget::HaveValidInputContextCache() const {
+ return (mInputContext.mIMEState.mEnabled != IMEEnabled::Unknown &&
+ IMEStateManager::GetWidgetForActiveInputContext() == this);
+}
+
+nsRefreshDriver* PuppetWidget::GetTopLevelRefreshDriver() const {
+ if (!mBrowserChild) {
+ return nullptr;
+ }
+
+ if (PresShell* presShell = mBrowserChild->GetTopLevelPresShell()) {
+ return presShell->GetRefreshDriver();
+ }
+
+ return nullptr;
+}
+
+void PuppetWidget::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) {
+ mInputContext = aContext;
+ // Any widget instances cannot cache IME open state because IME open state
+ // can be changed by user but native IME may not notify us of changing the
+ // open state on some platforms.
+ mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
+ if (!mBrowserChild) {
+ return;
+ }
+ mBrowserChild->SendSetInputContext(aContext, aAction);
+}
+
+InputContext PuppetWidget::GetInputContext() {
+ // XXX Currently, we don't support retrieving IME open state from child
+ // process.
+
+ // If the cache of input context is valid, we can avoid to use synchronous
+ // IPC.
+ if (HaveValidInputContextCache()) {
+ return mInputContext;
+ }
+
+ NS_WARNING("PuppetWidget::GetInputContext() needs to retrieve it with IPC");
+
+ // Don't cache InputContext here because this process isn't managing IME
+ // state of the chrome widget. So, we cannot modify mInputContext when
+ // chrome widget is set to new context.
+ InputContext context;
+ if (mBrowserChild) {
+ mBrowserChild->SendGetInputContext(&context.mIMEState);
+ }
+ return context;
+}
+
+NativeIMEContext PuppetWidget::GetNativeIMEContext() {
+ return mNativeIMEContext;
+}
+
+nsresult PuppetWidget::NotifyIMEOfFocusChange(
+ const IMENotification& aIMENotification) {
+ MOZ_ASSERT(IMEStateManager::CanSendNotificationToWidget());
+
+ if (!mBrowserChild) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool gotFocus = aIMENotification.mMessage == NOTIFY_IME_OF_FOCUS;
+ if (gotFocus) {
+ // When IME gets focus, we should initialize all information of the
+ // content.
+ if (NS_WARN_IF(!mContentCache.CacheAll(this, &aIMENotification))) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ // When IME loses focus, we don't need to store anything.
+ mContentCache.Clear();
+ }
+
+ mIMENotificationRequestsOfParent =
+ IMENotificationRequests(IMENotificationRequests::NOTIFY_ALL);
+ RefPtr<PuppetWidget> self = this;
+ mBrowserChild->SendNotifyIMEFocus(mContentCache, aIMENotification)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self](IMENotificationRequests&& aRequests) {
+ self->mIMENotificationRequestsOfParent = aRequests;
+ if (TextEventDispatcher* dispatcher =
+ self->GetTextEventDispatcher()) {
+ dispatcher->OnWidgetChangeIMENotificationRequests(self);
+ }
+ },
+ [self](mozilla::ipc::ResponseRejectReason&& aReason) {
+ NS_WARNING("SendNotifyIMEFocus got rejected.");
+ });
+
+ return NS_OK;
+}
+
+nsresult PuppetWidget::NotifyIMEOfCompositionUpdate(
+ const IMENotification& aIMENotification) {
+ MOZ_ASSERT(IMEStateManager::CanSendNotificationToWidget());
+
+ if (NS_WARN_IF(!mBrowserChild)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(!mContentCache.CacheSelection(this, &aIMENotification))) {
+ return NS_ERROR_FAILURE;
+ }
+ mBrowserChild->SendNotifyIMECompositionUpdate(mContentCache,
+ aIMENotification);
+ return NS_OK;
+}
+
+nsresult PuppetWidget::NotifyIMEOfTextChange(
+ const IMENotification& aIMENotification) {
+ MOZ_ASSERT(IMEStateManager::CanSendNotificationToWidget());
+ MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE,
+ "Passed wrong notification");
+
+ if (!mBrowserChild) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // FYI: text change notification is the first notification after
+ // a user operation changes the content. So, we need to modify
+ // the cache as far as possible here.
+
+ if (NS_WARN_IF(!mContentCache.CacheText(this, &aIMENotification))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // BrowserParent doesn't this this to cache. we don't send the notification
+ // if parent process doesn't request NOTIFY_TEXT_CHANGE.
+ if (mIMENotificationRequestsOfParent.WantTextChange()) {
+ mBrowserChild->SendNotifyIMETextChange(mContentCache, aIMENotification);
+ } else {
+ mBrowserChild->SendUpdateContentCache(mContentCache);
+ }
+ return NS_OK;
+}
+
+nsresult PuppetWidget::NotifyIMEOfSelectionChange(
+ const IMENotification& aIMENotification) {
+ MOZ_ASSERT(IMEStateManager::CanSendNotificationToWidget());
+ MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE,
+ "Passed wrong notification");
+ if (!mBrowserChild) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Note that selection change must be notified after text change if it occurs.
+ // Therefore, we don't need to query text content again here.
+ mContentCache.SetSelection(
+ this, aIMENotification.mSelectionChangeData.mOffset,
+ aIMENotification.mSelectionChangeData.Length(),
+ aIMENotification.mSelectionChangeData.mReversed,
+ aIMENotification.mSelectionChangeData.GetWritingMode());
+
+ mBrowserChild->SendNotifyIMESelection(mContentCache, aIMENotification);
+
+ return NS_OK;
+}
+
+nsresult PuppetWidget::NotifyIMEOfMouseButtonEvent(
+ const IMENotification& aIMENotification) {
+ MOZ_ASSERT(IMEStateManager::CanSendNotificationToWidget());
+ if (!mBrowserChild) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool consumedByIME = false;
+ if (!mBrowserChild->SendNotifyIMEMouseButtonEvent(aIMENotification,
+ &consumedByIME)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return consumedByIME ? NS_SUCCESS_EVENT_CONSUMED : NS_OK;
+}
+
+nsresult PuppetWidget::NotifyIMEOfPositionChange(
+ const IMENotification& aIMENotification) {
+ MOZ_ASSERT(IMEStateManager::CanSendNotificationToWidget());
+ if (NS_WARN_IF(!mBrowserChild)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(!mContentCache.CacheEditorRect(this, &aIMENotification))) {
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_WARN_IF(!mContentCache.CacheSelection(this, &aIMENotification))) {
+ return NS_ERROR_FAILURE;
+ }
+ if (mIMENotificationRequestsOfParent.WantPositionChanged()) {
+ mBrowserChild->SendNotifyIMEPositionChange(mContentCache, aIMENotification);
+ } else {
+ mBrowserChild->SendUpdateContentCache(mContentCache);
+ }
+ return NS_OK;
+}
+
+struct CursorSurface {
+ UniquePtr<char[]> mData;
+ IntSize mSize;
+};
+
+void PuppetWidget::SetCursor(nsCursor aCursor, imgIContainer* aCursorImage,
+ uint32_t aHotspotX, uint32_t aHotspotY) {
+ if (!mBrowserChild) {
+ return;
+ }
+
+ // Don't cache on windows, Windowless flash breaks this via async cursor
+ // updates.
+#if !defined(XP_WIN)
+ if (!mUpdateCursor && mCursor == aCursor && mCustomCursor == aCursorImage &&
+ (!aCursorImage ||
+ (mCursorHotspotX == aHotspotX && mCursorHotspotY == aHotspotY))) {
+ return;
+ }
+#endif
+
+ bool hasCustomCursor = false;
+ UniquePtr<char[]> customCursorData;
+ size_t length = 0;
+ IntSize customCursorSize;
+ int32_t stride = 0;
+ auto format = SurfaceFormat::B8G8R8A8;
+ bool force = mUpdateCursor;
+
+ if (aCursorImage) {
+ RefPtr<SourceSurface> surface = aCursorImage->GetFrame(
+ imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ if (surface) {
+ if (RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface()) {
+ hasCustomCursor = true;
+ customCursorData = nsContentUtils::GetSurfaceData(
+ WrapNotNull(dataSurface), &length, &stride);
+ customCursorSize = dataSurface->GetSize();
+ format = dataSurface->GetFormat();
+ }
+ }
+ }
+
+ mCustomCursor = nullptr;
+
+ nsDependentCString cursorData(customCursorData ? customCursorData.get() : "",
+ length);
+ if (!mBrowserChild->SendSetCursor(aCursor, hasCustomCursor, cursorData,
+ customCursorSize.width,
+ customCursorSize.height, stride, format,
+ aHotspotX, aHotspotY, force)) {
+ return;
+ }
+
+ mCursor = aCursor;
+ mCustomCursor = aCursorImage;
+ mCursorHotspotX = aHotspotX;
+ mCursorHotspotY = aHotspotY;
+ mUpdateCursor = false;
+}
+
+void PuppetWidget::ClearCachedCursor() {
+ nsBaseWidget::ClearCachedCursor();
+ mCustomCursor = nullptr;
+}
+
+void PuppetWidget::SetChild(PuppetWidget* aChild) {
+ MOZ_ASSERT(this != aChild, "can't parent a widget to itself");
+ MOZ_ASSERT(!aChild->mChild,
+ "fake widget 'hierarchy' only expected to have one level");
+
+ mChild = aChild;
+}
+
+void PuppetWidget::PaintNowIfNeeded() {
+ if (IsVisible()) {
+ if (RefPtr<nsRefreshDriver> refreshDriver = GetTopLevelRefreshDriver()) {
+ refreshDriver->DoTick();
+ }
+ }
+}
+
+void PuppetWidget::OnMemoryPressure(layers::MemoryPressureReason aWhy) {
+ if (aWhy != MemoryPressureReason::LOW_MEMORY_ONGOING && !mVisible &&
+ mLayerManager && XRE_IsContentProcess()) {
+ mLayerManager->ClearCachedResources();
+ }
+}
+
+bool PuppetWidget::NeedsPaint() {
+ // e10s popups are handled by the parent process, so never should be painted
+ // here
+ if (XRE_IsContentProcess() &&
+ StaticPrefs::browser_tabs_remote_desktopbehavior() &&
+ mWindowType == eWindowType_popup) {
+ NS_WARNING("Trying to paint an e10s popup in the child process!");
+ return false;
+ }
+
+ return mVisible;
+}
+
+float PuppetWidget::GetDPI() { return mDPI; }
+
+double PuppetWidget::GetDefaultScaleInternal() { return mDefaultScale; }
+
+int32_t PuppetWidget::RoundsWidgetCoordinatesTo() { return mRounding; }
+
+void* PuppetWidget::GetNativeData(uint32_t aDataType) {
+ switch (aDataType) {
+ case NS_NATIVE_SHAREABLE_WINDOW: {
+ // NOTE: We can not have a tab child in some situations, such as when
+ // we're rendering to a fake widget for thumbnails.
+ if (!mBrowserChild) {
+ NS_WARNING("Need BrowserChild to get the nativeWindow from!");
+ }
+ mozilla::WindowsHandle nativeData = 0;
+ if (mBrowserChild) {
+ nativeData = mBrowserChild->WidgetNativeData();
+ }
+ return (void*)nativeData;
+ }
+ case NS_NATIVE_WINDOW:
+ case NS_NATIVE_WIDGET:
+ case NS_NATIVE_DISPLAY:
+ // These types are ignored (see bug 1183828, bug 1240891).
+ break;
+ case NS_RAW_NATIVE_IME_CONTEXT:
+ MOZ_CRASH("You need to call GetNativeIMEContext() instead");
+ case NS_NATIVE_PLUGIN_PORT:
+ case NS_NATIVE_GRAPHIC:
+ case NS_NATIVE_SHELLWIDGET:
+ default:
+ NS_WARNING("nsWindow::GetNativeData called with bad value");
+ break;
+ }
+ return nullptr;
+}
+
+#if defined(XP_WIN)
+void PuppetWidget::SetNativeData(uint32_t aDataType, uintptr_t aVal) {
+ switch (aDataType) {
+ case NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW:
+ MOZ_ASSERT(mBrowserChild, "Need BrowserChild to send the message.");
+ if (mBrowserChild) {
+ mBrowserChild->SendSetNativeChildOfShareableWindow(aVal);
+ }
+ break;
+ default:
+ NS_WARNING("SetNativeData called with unsupported data type.");
+ }
+}
+#endif
+
+LayoutDeviceIntPoint PuppetWidget::GetChromeOffset() {
+ if (!GetOwningBrowserChild()) {
+ NS_WARNING("PuppetWidget without Tab does not have chrome information.");
+ return LayoutDeviceIntPoint();
+ }
+ return GetOwningBrowserChild()->GetChromeOffset();
+}
+
+LayoutDeviceIntPoint PuppetWidget::WidgetToScreenOffset() {
+ auto positionRalativeToWindow =
+ WidgetToTopLevelWidgetTransform().TransformPoint(LayoutDevicePoint());
+
+ return GetWindowPosition() +
+ LayoutDeviceIntPoint::Round(positionRalativeToWindow);
+}
+
+LayoutDeviceIntPoint PuppetWidget::GetWindowPosition() {
+ if (!GetOwningBrowserChild()) {
+ return LayoutDeviceIntPoint();
+ }
+
+ int32_t winX, winY, winW, winH;
+ NS_ENSURE_SUCCESS(
+ GetOwningBrowserChild()->GetDimensions(0, &winX, &winY, &winW, &winH),
+ LayoutDeviceIntPoint());
+ return LayoutDeviceIntPoint(winX, winY) +
+ GetOwningBrowserChild()->GetClientOffset();
+}
+
+LayoutDeviceIntRect PuppetWidget::GetScreenBounds() {
+ return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size());
+}
+
+uint32_t PuppetWidget::GetMaxTouchPoints() const {
+ return mBrowserChild ? mBrowserChild->MaxTouchPoints() : 0;
+}
+
+void PuppetWidget::StartAsyncScrollbarDrag(
+ const AsyncDragMetrics& aDragMetrics) {
+ mBrowserChild->StartScrollbarDrag(aDragMetrics);
+}
+
+PuppetScreen::PuppetScreen(void* nativeScreen) {}
+
+PuppetScreen::~PuppetScreen() = default;
+
+static ScreenConfiguration ScreenConfig() {
+ ScreenConfiguration config;
+ hal::GetCurrentScreenConfiguration(&config);
+ return config;
+}
+
+nsIntSize PuppetWidget::GetScreenDimensions() {
+ nsIntRect r = ScreenConfig().rect();
+ return nsIntSize(r.Width(), r.Height());
+}
+
+NS_IMETHODIMP
+PuppetScreen::GetRect(int32_t* outLeft, int32_t* outTop, int32_t* outWidth,
+ int32_t* outHeight) {
+ nsIntRect r = ScreenConfig().rect();
+ r.GetRect(outLeft, outTop, outWidth, outHeight);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PuppetScreen::GetAvailRect(int32_t* outLeft, int32_t* outTop, int32_t* outWidth,
+ int32_t* outHeight) {
+ return GetRect(outLeft, outTop, outWidth, outHeight);
+}
+
+NS_IMETHODIMP
+PuppetScreen::GetPixelDepth(int32_t* aPixelDepth) {
+ *aPixelDepth = ScreenConfig().pixelDepth();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PuppetScreen::GetColorDepth(int32_t* aColorDepth) {
+ *aColorDepth = ScreenConfig().colorDepth();
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(PuppetScreenManager, nsIScreenManager)
+
+PuppetScreenManager::PuppetScreenManager() {
+ mOneScreen = new PuppetScreen(nullptr);
+}
+
+PuppetScreenManager::~PuppetScreenManager() = default;
+
+NS_IMETHODIMP
+PuppetScreenManager::GetPrimaryScreen(nsIScreen** outScreen) {
+ NS_IF_ADDREF(*outScreen = mOneScreen.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PuppetScreenManager::GetTotalScreenPixels(int64_t* aTotalScreenPixels) {
+ MOZ_ASSERT(aTotalScreenPixels);
+ if (mOneScreen) {
+ int32_t x, y, width, height;
+ x = y = width = height = 0;
+ mOneScreen->GetRect(&x, &y, &width, &height);
+ *aTotalScreenPixels = width * height;
+ } else {
+ *aTotalScreenPixels = 0;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PuppetScreenManager::ScreenForRect(int32_t inLeft, int32_t inTop,
+ int32_t inWidth, int32_t inHeight,
+ nsIScreen** outScreen) {
+ return GetPrimaryScreen(outScreen);
+}
+
+ScreenIntMargin PuppetWidget::GetSafeAreaInsets() const {
+ return mSafeAreaInsets;
+}
+
+void PuppetWidget::UpdateSafeAreaInsets(
+ const ScreenIntMargin& aSafeAreaInsets) {
+ mSafeAreaInsets = aSafeAreaInsets;
+}
+
+nsIWidgetListener* PuppetWidget::GetCurrentWidgetListener() {
+ if (!mPreviouslyAttachedWidgetListener || !mAttachedWidgetListener) {
+ return mAttachedWidgetListener;
+ }
+
+ if (mAttachedWidgetListener->GetView()->IsPrimaryFramePaintSuppressed()) {
+ return mPreviouslyAttachedWidgetListener;
+ }
+
+ return mAttachedWidgetListener;
+}
+
+void PuppetWidget::ZoomToRect(const uint32_t& aPresShellId,
+ const ScrollableLayerGuid::ViewID& aViewId,
+ const CSSRect& aRect, const uint32_t& aFlags) {
+ if (!mBrowserChild) {
+ return;
+ }
+
+ mBrowserChild->ZoomToRect(aPresShellId, aViewId, aRect, aFlags);
+}
+
+void PuppetWidget::LookUpDictionary(
+ const nsAString& aText, const nsTArray<mozilla::FontRange>& aFontRangeArray,
+ const bool aIsVertical, const LayoutDeviceIntPoint& aPoint) {
+ if (!mBrowserChild) {
+ return;
+ }
+
+ mBrowserChild->SendLookUpDictionary(nsString(aText), aFontRangeArray,
+ aIsVertical, aPoint);
+}
+
+bool PuppetWidget::HasPendingInputEvent() {
+ if (!mBrowserChild) {
+ return false;
+ }
+
+ bool ret = false;
+
+ mBrowserChild->GetIPCChannel()->PeekMessages(
+ [&ret](const IPC::Message& aMsg) -> bool {
+ if (nsContentUtils::IsMessageInputEvent(aMsg)) {
+ ret = true;
+ return false; // Stop peeking.
+ }
+ return true;
+ });
+
+ return ret;
+}
+
+void PuppetWidget::HandledWindowedPluginKeyEvent(
+ const NativeEventData& aKeyEventData, bool aIsConsumed) {
+ if (NS_WARN_IF(mKeyEventInPluginCallbacks.IsEmpty())) {
+ return;
+ }
+ nsCOMPtr<nsIKeyEventInPluginCallback> callback =
+ mKeyEventInPluginCallbacks[0];
+ MOZ_ASSERT(callback);
+ mKeyEventInPluginCallbacks.RemoveElementAt(0);
+ callback->HandledWindowedPluginKeyEvent(aKeyEventData, aIsConsumed);
+}
+
+nsresult PuppetWidget::OnWindowedPluginKeyEvent(
+ const NativeEventData& aKeyEventData,
+ nsIKeyEventInPluginCallback* aCallback) {
+ if (NS_WARN_IF(!mBrowserChild)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_WARN_IF(!mBrowserChild->SendOnWindowedPluginKeyEvent(aKeyEventData))) {
+ return NS_ERROR_FAILURE;
+ }
+ mKeyEventInPluginCallbacks.AppendElement(aCallback);
+ return NS_SUCCESS_EVENT_HANDLED_ASYNCHRONOUSLY;
+}
+
+// TextEventDispatcherListener
+
+NS_IMETHODIMP
+PuppetWidget::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aIMENotification) {
+ MOZ_ASSERT(aTextEventDispatcher == mTextEventDispatcher);
+
+ // If there is different text event dispatcher listener for handling
+ // text event dispatcher, that means that native keyboard events and
+ // IME events are handled in this process. Therefore, we don't need
+ // to send any requests and notifications to the parent process.
+ if (mNativeTextEventDispatcherListener) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ switch (aIMENotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ return RequestIMEToCommitComposition(false);
+ case REQUEST_TO_CANCEL_COMPOSITION:
+ return RequestIMEToCommitComposition(true);
+ case NOTIFY_IME_OF_FOCUS:
+ case NOTIFY_IME_OF_BLUR:
+ return NotifyIMEOfFocusChange(aIMENotification);
+ case NOTIFY_IME_OF_SELECTION_CHANGE:
+ return NotifyIMEOfSelectionChange(aIMENotification);
+ case NOTIFY_IME_OF_TEXT_CHANGE:
+ return NotifyIMEOfTextChange(aIMENotification);
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
+ return NotifyIMEOfCompositionUpdate(aIMENotification);
+ case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
+ return NotifyIMEOfMouseButtonEvent(aIMENotification);
+ case NOTIFY_IME_OF_POSITION_CHANGE:
+ return NotifyIMEOfPositionChange(aIMENotification);
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP_(IMENotificationRequests)
+PuppetWidget::GetIMENotificationRequests() {
+ return IMENotificationRequests(
+ mIMENotificationRequestsOfParent.mWantUpdates |
+ IMENotificationRequests::NOTIFY_TEXT_CHANGE |
+ IMENotificationRequests::NOTIFY_POSITION_CHANGE);
+}
+
+NS_IMETHODIMP_(void)
+PuppetWidget::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) {
+ MOZ_ASSERT(aTextEventDispatcher == mTextEventDispatcher);
+}
+
+NS_IMETHODIMP_(void)
+PuppetWidget::WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
+ void* aData) {
+ MOZ_ASSERT(aTextEventDispatcher == mTextEventDispatcher);
+}
+
+nsresult PuppetWidget::SetSystemFont(const nsCString& aFontName) {
+ if (!mBrowserChild) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mBrowserChild->SendSetSystemFont(aFontName);
+ return NS_OK;
+}
+
+nsresult PuppetWidget::GetSystemFont(nsCString& aFontName) {
+ if (!mBrowserChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mBrowserChild->SendGetSystemFont(&aFontName);
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/PuppetWidget.h b/widget/PuppetWidget.h
new file mode 100644
index 0000000000..b82d63260d
--- /dev/null
+++ b/widget/PuppetWidget.h
@@ -0,0 +1,427 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This "puppet widget" isn't really a platform widget. It's intended
+ * to be used in widgetless rendering contexts, such as sandboxed
+ * content processes. If any "real" widgetry is needed, the request
+ * is forwarded to and/or data received from elsewhere.
+ */
+
+#ifndef mozilla_widget_PuppetWidget_h__
+#define mozilla_widget_PuppetWidget_h__
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
+#include "nsBaseScreen.h"
+#include "nsBaseWidget.h"
+#include "nsCOMArray.h"
+#include "nsIKeyEventInPluginCallback.h"
+#include "nsIScreenManager.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ContentCache.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "mozilla/layers/MemoryPressureObserver.h"
+
+namespace mozilla {
+
+namespace dom {
+class BrowserChild;
+} // namespace dom
+
+namespace widget {
+
+struct AutoCacheNativeKeyCommands;
+
+class PuppetWidget : public nsBaseWidget,
+ public TextEventDispatcherListener,
+ public layers::MemoryPressureListener {
+ typedef mozilla::CSSRect CSSRect;
+ typedef mozilla::dom::BrowserChild BrowserChild;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ // Avoiding to make compiler confused between mozilla::widget and nsIWidget.
+ typedef mozilla::widget::TextEventDispatcher TextEventDispatcher;
+ typedef mozilla::widget::TextEventDispatcherListener
+ TextEventDispatcherListener;
+
+ typedef nsBaseWidget Base;
+
+ // The width and height of the "widget" are clamped to this.
+ static const size_t kMaxDimension;
+
+ public:
+ explicit PuppetWidget(BrowserChild* aBrowserChild);
+
+ protected:
+ virtual ~PuppetWidget();
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // PuppetWidget creation is infallible, hence InfallibleCreate(), which
+ // Create() calls.
+ using nsBaseWidget::Create; // for Create signature not overridden here
+ virtual nsresult Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr) override;
+ void InfallibleCreate(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr);
+
+ void InitIMEState();
+
+ virtual already_AddRefed<nsIWidget> CreateChild(
+ const LayoutDeviceIntRect& aRect, nsWidgetInitData* aInitData = nullptr,
+ bool aForceUseIWidgetParent = false) override;
+
+ virtual void Destroy() override;
+
+ virtual void Show(bool aState) override;
+
+ virtual bool IsVisible() const override { return mVisible; }
+
+ virtual void ConstrainPosition(bool /*ignored aAllowSlop*/, int32_t* aX,
+ int32_t* aY) override {
+ *aX = kMaxDimension;
+ *aY = kMaxDimension;
+ }
+
+ // Widget position is controlled by the parent process via BrowserChild.
+ virtual void Move(double aX, double aY) override {}
+
+ virtual void Resize(double aWidth, double aHeight, bool aRepaint) override;
+ virtual void Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) override {
+ if (!mBounds.IsEqualXY(aX, aY)) {
+ NotifyWindowMoved(aX, aY);
+ }
+ mBounds.MoveTo(aX, aY);
+ return Resize(aWidth, aHeight, aRepaint);
+ }
+
+ // XXX/cjones: copying gtk behavior here; unclear what disabling a
+ // widget is supposed to entail
+ virtual void Enable(bool aState) override { mEnabled = aState; }
+ virtual bool IsEnabled() const override { return mEnabled; }
+
+ virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override;
+
+ virtual nsresult ConfigureChildren(
+ const nsTArray<Configuration>& aConfigurations) override;
+
+ virtual void Invalidate(const LayoutDeviceIntRect& aRect) override;
+
+ // PuppetWidgets don't have native data, as they're purely nonnative.
+ virtual void* GetNativeData(uint32_t aDataType) override;
+#if defined(XP_WIN)
+ void SetNativeData(uint32_t aDataType, uintptr_t aVal) override;
+#endif
+
+ // PuppetWidgets don't have any concept of titles.
+ virtual nsresult SetTitle(const nsAString& aTitle) override {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ virtual mozilla::LayoutDeviceToLayoutDeviceMatrix4x4
+ WidgetToTopLevelWidgetTransform() override;
+
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+
+ virtual LayoutDeviceIntPoint TopLevelWidgetToScreenOffset() override {
+ return GetWindowPosition();
+ }
+
+ int32_t RoundsWidgetCoordinatesTo() override;
+
+ void InitEvent(WidgetGUIEvent& aEvent,
+ LayoutDeviceIntPoint* aPoint = nullptr);
+
+ virtual nsresult DispatchEvent(WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+ nsEventStatus DispatchInputEvent(WidgetInputEvent* aEvent) override;
+ void SetConfirmedTargetAPZC(
+ uint64_t aInputBlockId,
+ const nsTArray<ScrollableLayerGuid>& aTargets) const override;
+ void UpdateZoomConstraints(
+ const uint32_t& aPresShellId, const ScrollableLayerGuid::ViewID& aViewId,
+ const mozilla::Maybe<ZoomConstraints>& aConstraints) override;
+ bool AsyncPanZoomEnabled() const override;
+
+ virtual bool GetEditCommands(
+ NativeKeyBindingsType aType, const mozilla::WidgetKeyboardEvent& aEvent,
+ nsTArray<mozilla::CommandInt>& aCommands) override;
+
+ friend struct AutoCacheNativeKeyCommands;
+
+ //
+ // nsBaseWidget methods we override
+ //
+
+ // Documents loaded in child processes are always subdocuments of
+ // other docs in an ancestor process. To ensure that the
+ // backgrounds of those documents are painted like those of
+ // same-process subdocuments, we force the widget here to be
+ // transparent, which in turn will cause layout to use a transparent
+ // backstop background color.
+ virtual nsTransparencyMode GetTransparencyMode() override {
+ return eTransparencyTransparent;
+ }
+
+ virtual LayerManager* GetLayerManager(
+ PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
+
+ // This is used for creating remote layer managers and for re-creating
+ // them after a compositor reset. The lambda aInitializeFunc is used to
+ // perform any caller-required initialization for the newly created layer
+ // manager; in the event of a failure, return false and it will destroy the
+ // new layer manager without changing the state of the widget.
+ bool CreateRemoteLayerManager(
+ const std::function<bool(LayerManager*)>& aInitializeFunc);
+
+ bool HasLayerManager() { return !!mLayerManager; }
+
+ virtual void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ virtual InputContext GetInputContext() override;
+ virtual NativeIMEContext GetNativeIMEContext() override;
+ TextEventDispatcherListener* GetNativeTextEventDispatcherListener() override {
+ return mNativeTextEventDispatcherListener
+ ? mNativeTextEventDispatcherListener.get()
+ : this;
+ }
+ void SetNativeTextEventDispatcherListener(
+ TextEventDispatcherListener* aListener) {
+ mNativeTextEventDispatcherListener = aListener;
+ }
+
+ virtual void SetCursor(nsCursor aDefaultCursor, imgIContainer* aCustomCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY) override;
+
+ virtual void ClearCachedCursor() override;
+
+ // Gets the DPI of the screen corresponding to this widget.
+ // Contacts the parent process which gets the DPI from the
+ // proper widget there. TODO: Handle DPI changes that happen
+ // later on.
+ virtual float GetDPI() override;
+ virtual double GetDefaultScaleInternal() override;
+
+ virtual bool NeedsPaint() override;
+
+ // Paint the widget immediately if any paints are queued up.
+ void PaintNowIfNeeded();
+
+ virtual BrowserChild* GetOwningBrowserChild() override {
+ return mBrowserChild;
+ }
+
+ void UpdateBackingScaleCache(float aDpi, int32_t aRounding, double aScale) {
+ mDPI = aDpi;
+ mRounding = aRounding;
+ mDefaultScale = aScale;
+ }
+
+ nsIntSize GetScreenDimensions();
+
+ // safe area insets support
+ virtual ScreenIntMargin GetSafeAreaInsets() const override;
+ void UpdateSafeAreaInsets(const ScreenIntMargin& aSafeAreaInsets);
+
+ // Get the offset to the chrome of the window that this tab belongs to.
+ //
+ // NOTE: In OOP iframes this value is zero. You should use
+ // WidgetToTopLevelWidgetTransform instead which is already including the
+ // chrome offset.
+ LayoutDeviceIntPoint GetChromeOffset();
+
+ // Get the screen position of the application window.
+ LayoutDeviceIntPoint GetWindowPosition();
+
+ virtual LayoutDeviceIntRect GetScreenBounds() 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;
+ 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;
+ virtual nsresult SynthesizeNativeTouchTap(LayoutDeviceIntPoint aPoint,
+ bool aLongTap,
+ nsIObserver* aObserver) override;
+ virtual nsresult ClearNativeTouchSequence(nsIObserver* aObserver) override;
+ virtual uint32_t GetMaxTouchPoints() const override;
+
+ virtual void StartAsyncScrollbarDrag(
+ const AsyncDragMetrics& aDragMetrics) override;
+
+ virtual void ZoomToRect(const uint32_t& aPresShellId,
+ const ScrollableLayerGuid::ViewID& aViewId,
+ const CSSRect& aRect,
+ const uint32_t& aFlags) override;
+
+ virtual bool HasPendingInputEvent() override;
+
+ void HandledWindowedPluginKeyEvent(const NativeEventData& aKeyEventData,
+ bool aIsConsumed);
+
+ virtual nsresult OnWindowedPluginKeyEvent(
+ const NativeEventData& aKeyEventData,
+ nsIKeyEventInPluginCallback* aCallback) override;
+
+ virtual void LookUpDictionary(
+ const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRangeArray,
+ const bool aIsVertical, const LayoutDeviceIntPoint& aPoint) override;
+
+ nsresult SetSystemFont(const nsCString& aFontName) override;
+ nsresult GetSystemFont(nsCString& aFontName) override;
+
+ // TextEventDispatcherListener
+ using nsBaseWidget::NotifyIME;
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+ NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;
+ NS_IMETHOD_(void)
+ OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override;
+ NS_IMETHOD_(void)
+ WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress, void* aData) override;
+
+ virtual void OnMemoryPressure(layers::MemoryPressureReason aWhy) override;
+
+ private:
+ void SetChild(PuppetWidget* aChild);
+
+ nsresult RequestIMEToCommitComposition(bool aCancel);
+ nsresult NotifyIMEOfFocusChange(const IMENotification& aIMENotification);
+ nsresult NotifyIMEOfSelectionChange(const IMENotification& aIMENotification);
+ nsresult NotifyIMEOfCompositionUpdate(
+ const IMENotification& aIMENotification);
+ nsresult NotifyIMEOfTextChange(const IMENotification& aIMENotification);
+ nsresult NotifyIMEOfMouseButtonEvent(const IMENotification& aIMENotification);
+ nsresult NotifyIMEOfPositionChange(const IMENotification& aIMENotification);
+
+ bool CacheEditorRect();
+ bool CacheCompositionRects(uint32_t& aStartOffset,
+ nsTArray<LayoutDeviceIntRect>& aRectArray,
+ uint32_t& aTargetCauseOffset);
+ bool GetCaretRect(LayoutDeviceIntRect& aCaretRect, uint32_t aCaretOffset);
+ uint32_t GetCaretOffset();
+
+ nsIWidgetListener* GetCurrentWidgetListener();
+
+ // When this widget caches input context and currently managed by
+ // IMEStateManager, the cache is valid.
+ bool HaveValidInputContextCache() const;
+
+ nsRefreshDriver* GetTopLevelRefreshDriver() const;
+
+ // BrowserChild normally holds a strong reference to this PuppetWidget
+ // or its root ancestor, but each PuppetWidget also needs a
+ // reference back to BrowserChild (e.g. to delegate nsIWidget IME calls
+ // to chrome) So we hold a weak reference to BrowserChild here. Since
+ // it's possible for BrowserChild to outlive the PuppetWidget, we clear
+ // this weak reference in Destroy()
+ BrowserChild* mBrowserChild;
+ // The "widget" to which we delegate events if we don't have an
+ // event handler.
+ RefPtr<PuppetWidget> mChild;
+ RefPtr<layers::MemoryPressureObserver> mMemoryPressureObserver;
+ // XXX/cjones: keeping this around until we teach LayerManager to do
+ // retained-content-only transactions
+ RefPtr<DrawTarget> mDrawTarget;
+ // IME
+ IMENotificationRequests mIMENotificationRequestsOfParent;
+ InputContext mInputContext;
+ // mNativeIMEContext is initialized when this dispatches every composition
+ // event both from parent process's widget and TextEventDispatcher in same
+ // process. If it hasn't been started composition yet, this isn't necessary
+ // for XP code since there is no TextComposition instance which is caused by
+ // the PuppetWidget instance.
+ NativeIMEContext mNativeIMEContext;
+ ContentCacheInChild mContentCache;
+
+ // The DPI of the screen corresponding to this widget
+ float mDPI;
+ int32_t mRounding;
+ double mDefaultScale;
+
+ nsCOMPtr<imgIContainer> mCustomCursor;
+ uint32_t mCursorHotspotX, mCursorHotspotY;
+
+ ScreenIntMargin mSafeAreaInsets;
+
+ nsCOMArray<nsIKeyEventInPluginCallback> mKeyEventInPluginCallbacks;
+
+ RefPtr<TextEventDispatcherListener> mNativeTextEventDispatcherListener;
+
+ protected:
+ bool mEnabled;
+ bool mVisible;
+
+ private:
+ bool mNeedIMEStateInit;
+ // When remote process requests to commit/cancel a composition, the
+ // composition may have already been committed in the main process. In such
+ // case, this will receive remaining composition events for the old
+ // composition even after requesting to commit/cancel the old composition
+ // but the TextComposition for the old composition has already been destroyed.
+ // So, until this meets new eCompositionStart, following composition events
+ // should be ignored if this is set to true.
+ bool mIgnoreCompositionEvents;
+};
+
+class PuppetScreen : public nsBaseScreen {
+ public:
+ explicit PuppetScreen(void* nativeScreen);
+ ~PuppetScreen();
+
+ NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth,
+ int32_t* aHeight) override;
+ NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth,
+ int32_t* aHeight) override;
+ NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth) override;
+ NS_IMETHOD GetColorDepth(int32_t* aColorDepth) override;
+};
+
+class PuppetScreenManager final : public nsIScreenManager {
+ ~PuppetScreenManager();
+
+ public:
+ PuppetScreenManager();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCREENMANAGER
+
+ protected:
+ nsCOMPtr<nsIScreen> mOneScreen;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_PuppetWidget_h__
diff --git a/widget/RemoteLookAndFeel.cpp b/widget/RemoteLookAndFeel.cpp
new file mode 100644
index 0000000000..8ca87d9631
--- /dev/null
+++ b/widget/RemoteLookAndFeel.cpp
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RemoteLookAndFeel.h"
+
+#include "gfxFont.h"
+#include "MainThreadUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "nsLookAndFeel.h"
+#include "nsXULAppAPI.h"
+
+#include <limits>
+#include <type_traits>
+#include <utility>
+
+namespace mozilla::widget {
+
+RemoteLookAndFeel::RemoteLookAndFeel(FullLookAndFeel&& aData)
+ : mTables(std::move(aData.tables())) {
+ MOZ_ASSERT(XRE_IsContentProcess(),
+ "Only content processes should be using a RemoteLookAndFeel");
+
+#ifdef MOZ_WIDGET_GTK
+ if (!StaticPrefs::widget_disable_native_theme_for_content()) {
+ // Configure the theme in this content process with the Gtk theme that was
+ // chosen by WithThemeConfiguredForContent in the parent process.
+ nsLookAndFeel::ConfigureTheme(aData.theme());
+ }
+#endif
+}
+
+RemoteLookAndFeel::~RemoteLookAndFeel() = default;
+
+void RemoteLookAndFeel::SetDataImpl(FullLookAndFeel&& aData) {
+ MOZ_ASSERT(XRE_IsContentProcess(),
+ "Only content processes should be using a RemoteLookAndFeel");
+ MOZ_ASSERT(NS_IsMainThread());
+ mTables = std::move(aData.tables());
+
+#ifdef MOZ_WIDGET_GTK
+ if (!StaticPrefs::widget_disable_native_theme_for_content()) {
+ // Configure the theme in this content process with the Gtk theme that was
+ // chosen by WithThemeConfiguredForContent in the parent process.
+ nsLookAndFeel::ConfigureTheme(aData.theme());
+ }
+#endif
+}
+
+namespace {
+
+template <typename Item, typename UInt, typename ID>
+Result<const Item*, nsresult> MapLookup(const nsTArray<Item>& aItems,
+ const nsTArray<UInt>& aMap, ID aID,
+ ID aMinimum = ID(0)) {
+ UInt mapped = aMap[static_cast<size_t>(aID) - static_cast<size_t>(aMinimum)];
+
+ if (mapped == std::numeric_limits<UInt>::max()) {
+ return Err(NS_ERROR_NOT_IMPLEMENTED);
+ }
+
+ return &aItems[static_cast<size_t>(mapped)];
+}
+
+template <typename Item, typename UInt>
+void AddToMap(nsTArray<Item>* aItems, nsTArray<UInt>* aMap,
+ Maybe<Item>&& aNewItem) {
+ if (aNewItem.isNothing()) {
+ aMap->AppendElement(std::numeric_limits<UInt>::max());
+ return;
+ }
+
+ size_t newIndex = aItems->Length();
+ MOZ_ASSERT(newIndex < std::numeric_limits<UInt>::max());
+
+ // Check if there is an existing value in aItems that we can point to.
+ //
+ // The arrays should be small enough and contain few enough unique
+ // values that sequential search here is reasonable.
+ for (size_t i = 0; i < newIndex; ++i) {
+ if ((*aItems)[i] == aNewItem.ref()) {
+ aMap->AppendElement(static_cast<UInt>(i));
+ return;
+ }
+ }
+
+ aItems->AppendElement(aNewItem.extract());
+ aMap->AppendElement(static_cast<UInt>(newIndex));
+}
+
+} // namespace
+
+nsresult RemoteLookAndFeel::NativeGetColor(ColorID aID, nscolor& aResult) {
+ const nscolor* result;
+ MOZ_TRY_VAR(result, MapLookup(mTables.colors(), mTables.colorMap(), aID));
+ aResult = *result;
+ return NS_OK;
+}
+
+nsresult RemoteLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
+ const int32_t* result;
+ MOZ_TRY_VAR(result, MapLookup(mTables.ints(), mTables.intMap(), aID));
+ aResult = *result;
+ return NS_OK;
+}
+
+nsresult RemoteLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
+ const float* result;
+ MOZ_TRY_VAR(result, MapLookup(mTables.floats(), mTables.floatMap(), aID));
+ aResult = *result;
+ return NS_OK;
+}
+
+bool RemoteLookAndFeel::NativeGetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) {
+ auto result =
+ MapLookup(mTables.fonts(), mTables.fontMap(), aID, FontID::MINIMUM);
+ if (result.isErr()) {
+ return false;
+ }
+
+ const LookAndFeelFont& font = *result.unwrap();
+ MOZ_ASSERT(font.haveFont());
+ aFontName = font.name();
+ aFontStyle = gfxFontStyle();
+ aFontStyle.size = font.size();
+ aFontStyle.weight = FontWeight(font.weight());
+ aFontStyle.style =
+ font.italic() ? FontSlantStyle::Italic() : FontSlantStyle::Normal();
+
+ return true;
+}
+
+char16_t RemoteLookAndFeel::GetPasswordCharacterImpl() {
+ return static_cast<char16_t>(mTables.passwordChar());
+}
+
+bool RemoteLookAndFeel::GetEchoPasswordImpl() { return mTables.passwordEcho(); }
+
+// static
+const FullLookAndFeel* RemoteLookAndFeel::ExtractData() {
+ MOZ_ASSERT(XRE_IsParentProcess(),
+ "Only parent processes should be extracting LookAndFeel data");
+
+ if (sCachedLookAndFeelData) {
+ return sCachedLookAndFeelData;
+ }
+
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ sInitialized = true;
+ ClearOnShutdown(&sCachedLookAndFeelData);
+ }
+
+ FullLookAndFeel* lf = new FullLookAndFeel{};
+ nsXPLookAndFeel* impl = nsXPLookAndFeel::GetInstance();
+
+ int32_t darkTheme = 0;
+ int32_t accessibilityTheme = 0;
+ impl->NativeGetInt(IntID::SystemUsesDarkTheme, darkTheme);
+ impl->NativeGetInt(IntID::UseAccessibilityTheme, accessibilityTheme);
+
+ impl->WithThemeConfiguredForContent([&](const LookAndFeelTheme& aTheme) {
+ for (auto id : MakeEnumeratedRange(IntID::End)) {
+ int32_t theInt;
+ nsresult rv;
+ // We want to take SystemUsesDarkTheme and UseAccessibilityTheme from
+ // the parent process theme rather than the content configured theme.
+ // This ensures that media queries like (prefers-color-scheme: dark) will
+ // match correctly in content processes.
+ //
+ // (When the RemoteLookAndFeel is not in use, the LookAndFeelCache
+ // ensures we get these values from the parent process theme.)
+ switch (id) {
+ case IntID::SystemUsesDarkTheme:
+ theInt = darkTheme;
+ rv = NS_OK;
+ break;
+ case IntID::UseAccessibilityTheme:
+ theInt = accessibilityTheme;
+ rv = NS_OK;
+ break;
+ default:
+ rv = impl->NativeGetInt(id, theInt);
+ break;
+ }
+ AddToMap(&lf->tables().ints(), &lf->tables().intMap(),
+ NS_SUCCEEDED(rv) ? Some(theInt) : Nothing{});
+ }
+
+ for (auto id : MakeEnumeratedRange(FloatID::End)) {
+ float theFloat;
+ nsresult rv = impl->NativeGetFloat(id, theFloat);
+ AddToMap(&lf->tables().floats(), &lf->tables().floatMap(),
+ NS_SUCCEEDED(rv) ? Some(theFloat) : Nothing{});
+ }
+
+ for (auto id : MakeEnumeratedRange(ColorID::End)) {
+ nscolor theColor;
+ nsresult rv = impl->NativeGetColor(id, theColor);
+ AddToMap(&lf->tables().colors(), &lf->tables().colorMap(),
+ NS_SUCCEEDED(rv) ? Some(theColor) : Nothing{});
+ }
+
+ for (auto id :
+ MakeInclusiveEnumeratedRange(FontID::MINIMUM, FontID::MAXIMUM)) {
+ LookAndFeelFont font{};
+ gfxFontStyle fontStyle{};
+
+ bool rv = impl->NativeGetFont(id, font.name(), fontStyle);
+ Maybe<LookAndFeelFont> maybeFont;
+ if (rv) {
+ font.haveFont() = true;
+ font.size() = fontStyle.size;
+ font.weight() = fontStyle.weight.ToFloat();
+ font.italic() = fontStyle.style.IsItalic();
+ MOZ_ASSERT(fontStyle.style.IsNormal() || fontStyle.style.IsItalic(),
+ "Cannot handle oblique font style");
+#ifdef DEBUG
+ {
+ // Assert that all the remaining font style properties have their
+ // default values.
+ gfxFontStyle candidate = fontStyle;
+ gfxFontStyle defaults{};
+ candidate.size = defaults.size;
+ candidate.weight = defaults.weight;
+ candidate.style = defaults.style;
+ MOZ_ASSERT(candidate.Equals(defaults),
+ "Some font style properties not supported");
+ }
+#endif
+ maybeFont = Some(std::move(font));
+ }
+ AddToMap(&lf->tables().fonts(), &lf->tables().fontMap(),
+ std::move(maybeFont));
+ }
+
+ lf->tables().passwordChar() = impl->GetPasswordCharacterImpl();
+ lf->tables().passwordEcho() = impl->GetEchoPasswordImpl();
+#ifdef MOZ_WIDGET_GTK
+ lf->theme() = aTheme;
+#endif
+ });
+
+ // This assignment to sCachedLookAndFeelData must be done after the
+ // WithThemeConfiguredForContent call, since it can end up calling RefreshImpl
+ // on the LookAndFeel, which will clear out sCachedTables.
+ sCachedLookAndFeelData = lf;
+ return sCachedLookAndFeelData;
+}
+
+void RemoteLookAndFeel::ClearCachedData() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ sCachedLookAndFeelData = nullptr;
+}
+
+StaticAutoPtr<FullLookAndFeel> RemoteLookAndFeel::sCachedLookAndFeelData;
+
+} // namespace mozilla::widget
diff --git a/widget/RemoteLookAndFeel.h b/widget/RemoteLookAndFeel.h
new file mode 100644
index 0000000000..aec2a3eb3b
--- /dev/null
+++ b/widget/RemoteLookAndFeel.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_RemoteLookAndFeel_h__
+#define mozilla_widget_RemoteLookAndFeel_h__
+
+#include "mozilla/widget/nsXPLookAndFeel.h"
+#include "mozilla/widget/LookAndFeelTypes.h"
+
+namespace mozilla::widget {
+
+/**
+ * A LookAndFeel implementation whose native values are provided by the
+ * parent process.
+ */
+class RemoteLookAndFeel final : public nsXPLookAndFeel {
+ public:
+ explicit RemoteLookAndFeel(FullLookAndFeel&& aTables);
+
+ virtual ~RemoteLookAndFeel();
+
+ void NativeInit() override {}
+
+ nsresult NativeGetInt(IntID aID, int32_t& aResult) override;
+ nsresult NativeGetFloat(FloatID aID, float& aResult) override;
+ nsresult NativeGetColor(ColorID aID, nscolor& aResult) override;
+ bool NativeGetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) override;
+
+ char16_t GetPasswordCharacterImpl() override;
+ bool GetEchoPasswordImpl() override;
+
+ // Sets the LookAndFeel data to be used by this content process' singleton
+ // RemoteLookAndFeel object.
+ void SetDataImpl(FullLookAndFeel&& aTables) override;
+
+ // Extracts the data from the platform's default LookAndFeel implementation.
+ //
+ // This is called in the parent process to obtain the data to send down to
+ // content processes when they are created (and when the OS theme changes).
+ //
+ // Note that the pointer returned from here is only valid until the next time
+ // ClearCachedData is called.
+ static const FullLookAndFeel* ExtractData();
+
+ // Clears any cached extracted data from the platform's default LookAndFeel
+ // implementation.
+ //
+ // This is called in the parent process when the default LookAndFeel is
+ // refreshed, to invalidate sCachedLookAndFeelData.
+ static void ClearCachedData();
+
+ private:
+ LookAndFeelTables mTables;
+
+ // A cached copy of the data extracted by ExtractData.
+ //
+ // Storing this lets us avoid doing most of the work of ExtractData each
+ // time we create a new content process.
+ //
+ // Only used in the parent process.
+ static StaticAutoPtr<FullLookAndFeel> sCachedLookAndFeelData;
+};
+
+} // namespace mozilla::widget
+
+#endif // mozilla_widget_RemoteLookAndFeel_h__
diff --git a/widget/Screen.cpp b/widget/Screen.cpp
new file mode 100644
index 0000000000..4fb196fe87
--- /dev/null
+++ b/widget/Screen.cpp
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "Screen.h"
+
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/StaticPrefs_layout.h"
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS(Screen, nsIScreen)
+
+Screen::Screen(LayoutDeviceIntRect aRect, LayoutDeviceIntRect aAvailRect,
+ uint32_t aPixelDepth, uint32_t aColorDepth,
+ DesktopToLayoutDeviceScale aContentsScale,
+ CSSToLayoutDeviceScale aDefaultCssScale, float aDPI)
+ : mRect(aRect),
+ mAvailRect(aAvailRect),
+ mRectDisplayPix(RoundedToInt(aRect / aContentsScale)),
+ mAvailRectDisplayPix(RoundedToInt(aAvailRect / aContentsScale)),
+ mPixelDepth(aPixelDepth),
+ mColorDepth(aColorDepth),
+ mContentsScale(aContentsScale),
+ mDefaultCssScale(aDefaultCssScale),
+ mDPI(aDPI) {}
+
+Screen::Screen(const mozilla::dom::ScreenDetails& aScreen)
+ : mRect(aScreen.rect()),
+ mAvailRect(aScreen.availRect()),
+ mRectDisplayPix(aScreen.rectDisplayPix()),
+ mAvailRectDisplayPix(aScreen.availRectDisplayPix()),
+ mPixelDepth(aScreen.pixelDepth()),
+ mColorDepth(aScreen.colorDepth()),
+ mContentsScale(aScreen.contentsScaleFactor()),
+ mDefaultCssScale(aScreen.defaultCSSScaleFactor()),
+ mDPI(aScreen.dpi()) {}
+
+Screen::Screen(const Screen& aOther)
+ : mRect(aOther.mRect),
+ mAvailRect(aOther.mAvailRect),
+ mRectDisplayPix(aOther.mRectDisplayPix),
+ mAvailRectDisplayPix(aOther.mAvailRectDisplayPix),
+ mPixelDepth(aOther.mPixelDepth),
+ mColorDepth(aOther.mColorDepth),
+ mContentsScale(aOther.mContentsScale),
+ mDefaultCssScale(aOther.mDefaultCssScale),
+ mDPI(aOther.mDPI) {}
+
+mozilla::dom::ScreenDetails Screen::ToScreenDetails() {
+ return mozilla::dom::ScreenDetails(
+ mRect, mRectDisplayPix, mAvailRect, mAvailRectDisplayPix, mPixelDepth,
+ mColorDepth, mContentsScale, mDefaultCssScale, mDPI);
+}
+
+NS_IMETHODIMP
+Screen::GetRect(int32_t* aOutLeft, int32_t* aOutTop, int32_t* aOutWidth,
+ int32_t* aOutHeight) {
+ mRect.GetRect(aOutLeft, aOutTop, aOutWidth, aOutHeight);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Screen::GetRectDisplayPix(int32_t* aOutLeft, int32_t* aOutTop,
+ int32_t* aOutWidth, int32_t* aOutHeight) {
+ mRectDisplayPix.GetRect(aOutLeft, aOutTop, aOutWidth, aOutHeight);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Screen::GetAvailRect(int32_t* aOutLeft, int32_t* aOutTop, int32_t* aOutWidth,
+ int32_t* aOutHeight) {
+ mAvailRect.GetRect(aOutLeft, aOutTop, aOutWidth, aOutHeight);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Screen::GetAvailRectDisplayPix(int32_t* aOutLeft, int32_t* aOutTop,
+ int32_t* aOutWidth, int32_t* aOutHeight) {
+ mAvailRectDisplayPix.GetRect(aOutLeft, aOutTop, aOutWidth, aOutHeight);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Screen::GetPixelDepth(int32_t* aPixelDepth) {
+ *aPixelDepth = mPixelDepth;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Screen::GetColorDepth(int32_t* aColorDepth) {
+ *aColorDepth = mColorDepth;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Screen::GetContentsScaleFactor(double* aOutScale) {
+ *aOutScale = mContentsScale.scale;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Screen::GetDefaultCSSScaleFactor(double* aOutScale) {
+ double scale = StaticPrefs::layout_css_devPixelsPerPx();
+ if (scale > 0.0) {
+ *aOutScale = scale;
+ } else {
+ *aOutScale = mDefaultCssScale.scale;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Screen::GetDpi(float* aDPI) {
+ *aDPI = mDPI;
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/Screen.h b/widget/Screen.h
new file mode 100644
index 0000000000..6f284b76c7
--- /dev/null
+++ b/widget/Screen.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_Screen_h
+#define mozilla_widget_Screen_h
+
+#include "nsIScreen.h"
+
+#include "Units.h"
+
+namespace mozilla {
+namespace dom {
+class ScreenDetails;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+namespace widget {
+
+class Screen final : public nsIScreen {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCREEN
+
+ Screen(LayoutDeviceIntRect aRect, LayoutDeviceIntRect aAvailRect,
+ uint32_t aPixelDepth, uint32_t aColorDepth,
+ DesktopToLayoutDeviceScale aContentsScale,
+ CSSToLayoutDeviceScale aDefaultCssScale, float dpi);
+ explicit Screen(const mozilla::dom::ScreenDetails& aScreenDetails);
+ Screen(const Screen& aOther);
+
+ mozilla::dom::ScreenDetails ToScreenDetails();
+
+ private:
+ virtual ~Screen() = default;
+
+ LayoutDeviceIntRect mRect;
+ LayoutDeviceIntRect mAvailRect;
+ DesktopIntRect mRectDisplayPix;
+ DesktopIntRect mAvailRectDisplayPix;
+ uint32_t mPixelDepth;
+ uint32_t mColorDepth;
+ DesktopToLayoutDeviceScale mContentsScale;
+ CSSToLayoutDeviceScale mDefaultCssScale;
+ float mDPI;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/ScreenManager.cpp b/widget/ScreenManager.cpp
new file mode 100644
index 0000000000..90324ebca4
--- /dev/null
+++ b/widget/ScreenManager.cpp
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "ScreenManager.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPtr.h"
+#ifdef MOZ_WAYLAND
+# include <gdk/gdk.h>
+# include <gdk/gdkx.h>
+# include <gdk/gdkwayland.h>
+#endif /* MOZ_WAYLAND */
+
+static mozilla::LazyLogModule sScreenLog("WidgetScreen");
+
+namespace mozilla::widget {
+
+NS_IMPL_ISUPPORTS(ScreenManager, nsIScreenManager)
+
+ScreenManager::ScreenManager() = default;
+
+ScreenManager::~ScreenManager() = default;
+
+static StaticRefPtr<ScreenManager> sSingleton;
+
+ScreenManager& ScreenManager::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new ScreenManager();
+ ClearOnShutdown(&sSingleton);
+ }
+ return *sSingleton;
+}
+
+already_AddRefed<ScreenManager> ScreenManager::GetAddRefedSingleton() {
+ RefPtr<ScreenManager> sm = &GetSingleton();
+ return sm.forget();
+}
+
+void ScreenManager::SetHelper(UniquePtr<Helper> aHelper) {
+ mHelper = std::move(aHelper);
+}
+
+void ScreenManager::Refresh(nsTArray<RefPtr<Screen>>&& aScreens) {
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refresh screens"));
+
+ mScreenList = std::move(aScreens);
+
+ CopyScreensToAllRemotesIfIsParent();
+}
+
+void ScreenManager::Refresh(nsTArray<mozilla::dom::ScreenDetails>&& aScreens) {
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refresh screens from IPC"));
+
+ mScreenList.Clear();
+ for (auto& screen : aScreens) {
+ mScreenList.AppendElement(new Screen(screen));
+ }
+
+ CopyScreensToAllRemotesIfIsParent();
+}
+
+template <class Range>
+void ScreenManager::CopyScreensToRemoteRange(Range aRemoteRange) {
+ AutoTArray<dom::ScreenDetails, 4> screens;
+ for (auto& screen : mScreenList) {
+ screens.AppendElement(screen->ToScreenDetails());
+ }
+ for (auto cp : aRemoteRange) {
+ MOZ_LOG(sScreenLog, LogLevel::Debug,
+ ("Send screens to [Pid %d]", cp->Pid()));
+ if (!cp->SendRefreshScreens(screens)) {
+ MOZ_LOG(sScreenLog, LogLevel::Error,
+ ("SendRefreshScreens to [Pid %d] failed", cp->Pid()));
+ }
+ }
+}
+
+void ScreenManager::CopyScreensToRemote(dom::ContentParent* aContentParent) {
+ MOZ_ASSERT(aContentParent);
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ auto range = {aContentParent};
+ CopyScreensToRemoteRange(range);
+}
+
+void ScreenManager::CopyScreensToAllRemotesIfIsParent() {
+ if (XRE_IsContentProcess()) {
+ return;
+ }
+
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing all ContentParents"));
+
+ CopyScreensToRemoteRange(
+ dom::ContentParent::AllProcesses(dom::ContentParent::eLive));
+}
+
+// Returns the screen that contains the rectangle. If the rect overlaps
+// multiple screens, it picks the screen with the greatest area of intersection.
+//
+// The coordinates are in desktop pixels.
+//
+NS_IMETHODIMP
+ScreenManager::ScreenForRect(int32_t aX, int32_t aY, int32_t aWidth,
+ int32_t aHeight, nsIScreen** aOutScreen) {
+#if defined(MOZ_WAYLAND) && defined(MOZ_LOGGING)
+ static bool inWayland = gdk_display_get_default() &&
+ !GDK_IS_X11_DISPLAY(gdk_display_get_default());
+ if (inWayland) {
+ MOZ_LOG(sScreenLog, LogLevel::Warning,
+ ("Getting screen in wayland, primary display will be returned."));
+ }
+#endif
+
+ if (mScreenList.IsEmpty()) {
+ MOZ_LOG(sScreenLog, LogLevel::Warning,
+ ("No screen available. This can happen in xpcshell."));
+ RefPtr<Screen> ret = new Screen(
+ LayoutDeviceIntRect(), LayoutDeviceIntRect(), 0, 0,
+ DesktopToLayoutDeviceScale(), CSSToLayoutDeviceScale(), 96 /* dpi */);
+ ret.forget(aOutScreen);
+ return NS_OK;
+ }
+
+ // Optimize for the common case. If the number of screens is only
+ // one then just return the primary screen.
+ if (mScreenList.Length() == 1) {
+ return GetPrimaryScreen(aOutScreen);
+ }
+
+ // which screen should we return?
+ Screen* which = mScreenList[0].get();
+
+ // walk the list of screens and find the one that has the most
+ // surface area.
+ uint32_t area = 0;
+ DesktopIntRect windowRect(aX, aY, aWidth, aHeight);
+ for (auto& screen : mScreenList) {
+ int32_t x, y, width, height;
+ x = y = width = height = 0;
+ screen->GetRectDisplayPix(&x, &y, &width, &height);
+ // calculate the surface area
+ DesktopIntRect screenRect(x, y, width, height);
+ screenRect.IntersectRect(screenRect, windowRect);
+ uint32_t tempArea = screenRect.Area();
+ if (tempArea > area) {
+ which = screen.get();
+ area = tempArea;
+ }
+ }
+
+ // If the rect intersects one or more screen,
+ // return the screen that has the largest intersection.
+ if (area > 0) {
+ RefPtr<Screen> ret = which;
+ ret.forget(aOutScreen);
+ return NS_OK;
+ }
+
+ // If the rect does not intersect a screen, find
+ // a screen that is nearest to the rect.
+ uint32_t distance = UINT32_MAX;
+ for (auto& screen : mScreenList) {
+ int32_t x, y, width, height;
+ x = y = width = height = 0;
+ screen->GetRectDisplayPix(&x, &y, &width, &height);
+
+ uint32_t distanceX = 0;
+ if (aX > (x + width)) {
+ distanceX = aX - (x + width);
+ } else if ((aX + aWidth) < x) {
+ distanceX = x - (aX + aWidth);
+ }
+
+ uint32_t distanceY = 0;
+ if (aY > (y + height)) {
+ distanceY = aY - (y + height);
+ } else if ((aY + aHeight) < y) {
+ distanceY = y - (aY + aHeight);
+ }
+
+ uint32_t tempDistance = distanceX * distanceX + distanceY * distanceY;
+ if (tempDistance < distance) {
+ which = screen.get();
+ distance = tempDistance;
+ if (distance == 0) {
+ break;
+ }
+ }
+ }
+
+ RefPtr<Screen> ret = which;
+ ret.forget(aOutScreen);
+ return NS_OK;
+}
+
+// The screen with the menubar/taskbar. This shouldn't be needed very
+// often.
+//
+NS_IMETHODIMP
+ScreenManager::GetPrimaryScreen(nsIScreen** aPrimaryScreen) {
+ if (mScreenList.IsEmpty()) {
+ MOZ_LOG(sScreenLog, LogLevel::Warning,
+ ("No screen available. This can happen in xpcshell."));
+ RefPtr<Screen> ret = new Screen(
+ LayoutDeviceIntRect(), LayoutDeviceIntRect(), 0, 0,
+ DesktopToLayoutDeviceScale(), CSSToLayoutDeviceScale(), 96 /* dpi */);
+ ret.forget(aPrimaryScreen);
+ return NS_OK;
+ }
+
+ RefPtr<Screen> ret = mScreenList[0];
+ ret.forget(aPrimaryScreen);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScreenManager::GetTotalScreenPixels(int64_t* aTotalScreenPixels) {
+ MOZ_ASSERT(aTotalScreenPixels);
+
+ if (mScreenList.IsEmpty()) {
+ MOZ_LOG(sScreenLog, LogLevel::Warning,
+ ("No screen available. This can happen in xpcshell."));
+ *aTotalScreenPixels = 0;
+ return NS_OK;
+ }
+
+ int64_t pixels = 0;
+ for (auto& screen : mScreenList) {
+ int32_t x, y, width, height;
+ x = y = width = height = 0;
+ screen->GetRect(&x, &y, &width, &height);
+ pixels += width * height;
+ }
+
+ *aTotalScreenPixels = pixels;
+ return NS_OK;
+}
+
+} // namespace mozilla::widget
diff --git a/widget/ScreenManager.h b/widget/ScreenManager.h
new file mode 100644
index 0000000000..de8eec3be2
--- /dev/null
+++ b/widget/ScreenManager.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_ScreenManager_h
+#define mozilla_widget_ScreenManager_h
+
+#include "nsIScreenManager.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/widget/Screen.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace dom {
+class ContentParent;
+class ScreenDetails;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+namespace widget {
+
+class ScreenManager final : public nsIScreenManager {
+ public:
+ class Helper {
+ public:
+ virtual ~Helper() = default;
+ };
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCREENMANAGER
+
+ static ScreenManager& GetSingleton();
+ static already_AddRefed<ScreenManager> GetAddRefedSingleton();
+
+ void SetHelper(UniquePtr<Helper> aHelper);
+ void Refresh(nsTArray<RefPtr<Screen>>&& aScreens);
+ void Refresh(nsTArray<mozilla::dom::ScreenDetails>&& aScreens);
+ void CopyScreensToRemote(mozilla::dom::ContentParent* aContentParent);
+
+ private:
+ ScreenManager();
+ virtual ~ScreenManager();
+
+ template <class Range>
+ void CopyScreensToRemoteRange(Range aRemoteRange);
+ void CopyScreensToAllRemotesIfIsParent();
+
+ AutoTArray<RefPtr<Screen>, 4> mScreenList;
+ UniquePtr<Helper> mHelper;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_ScreenManager_h
diff --git a/widget/ScrollbarDrawingMac.cpp b/widget/ScrollbarDrawingMac.cpp
new file mode 100644
index 0000000000..dcc352ccb6
--- /dev/null
+++ b/widget/ScrollbarDrawingMac.cpp
@@ -0,0 +1,327 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "ScrollbarDrawingMac.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/RelativeLuminanceUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsIFrame.h"
+#include "nsLookAndFeel.h"
+#include "nsContainerFrame.h"
+#include "nsNativeTheme.h"
+
+namespace mozilla {
+
+using namespace gfx;
+
+namespace widget {
+
+static nsIFrame* GetParentScrollbarFrame(nsIFrame* aFrame) {
+ // Walk our parents to find a scrollbar frame
+ nsIFrame* scrollbarFrame = aFrame;
+ do {
+ if (scrollbarFrame->IsScrollbarFrame()) {
+ break;
+ }
+ } while ((scrollbarFrame = scrollbarFrame->GetParent()));
+
+ // We return null if we can't find a parent scrollbar frame
+ return scrollbarFrame;
+}
+
+static bool IsParentScrollbarRolledOver(nsIFrame* aFrame) {
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ return nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0
+ ? nsNativeTheme::CheckBooleanAttr(scrollbarFrame, nsGkAtoms::hover)
+ : nsNativeTheme::GetContentState(scrollbarFrame,
+ StyleAppearance::None)
+ .HasState(NS_EVENT_STATE_HOVER);
+}
+
+LayoutDeviceIntSize ScrollbarDrawingMac::GetMinimumWidgetSize(
+ StyleAppearance aAppearance, nsIFrame* aFrame, float aDpiRatio) {
+ auto fn = [](StyleAppearance aAppearance, nsIFrame* aFrame) -> IntSize {
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ return IntSize{26, 0};
+ case StyleAppearance::ScrollbarthumbVertical:
+ return IntSize{0, 26};
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::ScrollbartrackHorizontal: {
+ ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
+ bool isSmall =
+ style->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin;
+ if (nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) !=
+ 0) {
+ if (isSmall) {
+ return IntSize{14, 14};
+ }
+ return IntSize{16, 16};
+ }
+ if (isSmall) {
+ return IntSize{11, 11};
+ }
+ return IntSize{15, 15};
+ }
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::ScrollbarNonDisappearing:
+ return IntSize{15, 15};
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ return IntSize{15, 16};
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ return IntSize{16, 15};
+ default:
+ return IntSize{};
+ }
+ };
+
+ IntSize minSize = fn(aAppearance, aFrame);
+ if (aDpiRatio >= 2.0f) {
+ return LayoutDeviceIntSize{minSize.width * 2, minSize.height * 2};
+ }
+ return LayoutDeviceIntSize{minSize.width, minSize.height};
+}
+
+ScrollbarParams ScrollbarDrawingMac::ComputeScrollbarParams(
+ nsIFrame* aFrame, const ComputedStyle& aStyle, bool aIsHorizontal) {
+ ScrollbarParams params;
+ params.overlay =
+ nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0;
+ params.rolledOver = IsParentScrollbarRolledOver(aFrame);
+ params.small =
+ aStyle.StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin;
+ params.rtl = nsNativeTheme::IsFrameRTL(aFrame);
+ params.horizontal = aIsHorizontal;
+ params.onDarkBackground = nsNativeTheme::IsDarkBackground(aFrame);
+ // Don't use custom scrollbars for overlay scrollbars since they are
+ // generally good enough for use cases of custom scrollbars.
+ if (!params.overlay) {
+ const nsStyleUI* ui = aStyle.StyleUI();
+ if (ui->HasCustomScrollbars()) {
+ const auto& colors = ui->mScrollbarColor.AsColors();
+ params.custom = true;
+ params.trackColor = colors.track.CalcColor(aStyle);
+ params.faceColor = colors.thumb.CalcColor(aStyle);
+ }
+ }
+
+ return params;
+}
+
+void ScrollbarDrawingMac::DrawScrollbarThumb(DrawTarget& aDT, const Rect& aRect,
+ const ScrollbarParams& aParams) {
+ // Compute the thumb thickness. This varies based on aParams.small,
+ // aParams.overlay and aParams.rolledOver. non-overlay: 6 / 8, overlay
+ // non-hovered: 5 / 7, overlay hovered: 9 / 11
+ float thickness = aParams.small ? 6.0f : 8.0f;
+ if (aParams.overlay) {
+ thickness -= 1.0f;
+ if (aParams.rolledOver) {
+ thickness += 4.0f;
+ }
+ }
+
+ // Compute the thumb rect.
+ float outerSpacing = (aParams.overlay || aParams.small) ? 1.0f : 2.0f;
+ Rect thumbRect = aRect;
+ thumbRect.Deflate(1.0f);
+ if (aParams.horizontal) {
+ float bottomEdge = thumbRect.YMost() - outerSpacing;
+ thumbRect.SetBoxY(bottomEdge - thickness, bottomEdge);
+ } else {
+ if (aParams.rtl) {
+ float leftEdge = thumbRect.X() + outerSpacing;
+ thumbRect.SetBoxX(leftEdge, leftEdge + thickness);
+ } else {
+ float rightEdge = thumbRect.XMost() - outerSpacing;
+ thumbRect.SetBoxX(rightEdge - thickness, rightEdge);
+ }
+ }
+
+ // Compute the thumb fill color.
+ nscolor faceColor;
+ if (aParams.custom) {
+ faceColor = aParams.faceColor;
+ } else {
+ if (aParams.overlay) {
+ faceColor = aParams.onDarkBackground ? NS_RGBA(255, 255, 255, 128)
+ : NS_RGBA(0, 0, 0, 128);
+ } else {
+ faceColor = aParams.rolledOver ? NS_RGBA(125, 125, 125, 255)
+ : NS_RGBA(194, 194, 194, 255);
+ }
+ }
+
+ // Fill the thumb shape with the color.
+ float cornerRadius =
+ (aParams.horizontal ? thumbRect.Height() : thumbRect.Width()) / 2.0f;
+ aDT.FillRoundedRect(RoundedRect(thumbRect, RectCornerRadii(cornerRadius)),
+ ColorPattern(ToDeviceColor(faceColor)));
+
+ // Overlay scrollbars have an additional stroke around the fill.
+ if (aParams.overlay) {
+ float strokeOutset = aParams.onDarkBackground ? 0.3f : 0.5f;
+ float strokeWidth = aParams.onDarkBackground ? 0.6f : 0.8f;
+ nscolor strokeColor = aParams.onDarkBackground ? NS_RGBA(0, 0, 0, 48)
+ : NS_RGBA(255, 255, 255, 48);
+ Rect thumbStrokeRect = thumbRect;
+ thumbStrokeRect.Inflate(strokeOutset);
+ float strokeRadius = (aParams.horizontal ? thumbStrokeRect.Height()
+ : thumbStrokeRect.Width()) /
+ 2.0f;
+
+ RefPtr<Path> path = MakePathForRoundedRect(aDT, thumbStrokeRect,
+ RectCornerRadii(strokeRadius));
+ aDT.Stroke(path, ColorPattern(ToDeviceColor(strokeColor)),
+ StrokeOptions(strokeWidth));
+ }
+}
+
+struct ScrollbarTrackDecorationColors {
+ nscolor mInnerColor = 0;
+ nscolor mShadowColor = 0;
+ nscolor mOuterColor = 0;
+};
+
+static ScrollbarTrackDecorationColors ComputeScrollbarTrackDecorationColors(
+ nscolor aTrackColor) {
+ ScrollbarTrackDecorationColors result;
+ float luminance = RelativeLuminanceUtils::Compute(aTrackColor);
+ if (luminance >= 0.5f) {
+ result.mInnerColor =
+ RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.836f);
+ result.mShadowColor =
+ RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.982f);
+ result.mOuterColor =
+ RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.886f);
+ } else {
+ result.mInnerColor =
+ RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.196f);
+ result.mShadowColor =
+ RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.018f);
+ result.mOuterColor =
+ RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.129f);
+ }
+ return result;
+}
+
+void ScrollbarDrawingMac::DrawScrollbarTrack(DrawTarget& aDT, const Rect& aRect,
+ const ScrollbarParams& aParams) {
+ if (aParams.overlay && !aParams.rolledOver) {
+ // Non-hovered overlay scrollbars don't have a track. Draw nothing.
+ return;
+ }
+
+ nscolor trackColor;
+ if (aParams.custom) {
+ trackColor = aParams.trackColor;
+ } else {
+ if (aParams.overlay) {
+ trackColor = aParams.onDarkBackground ? NS_RGBA(201, 201, 201, 38)
+ : NS_RGBA(250, 250, 250, 191);
+ } else {
+ trackColor = NS_RGBA(250, 250, 250, 255);
+ }
+ }
+
+ float thickness = aParams.horizontal ? aRect.height : aRect.width;
+
+ // The scrollbar track is drawn as multiple non-overlapping segments, which
+ // make up lines of different widths and with slightly different shading.
+ ScrollbarTrackDecorationColors colors =
+ ComputeScrollbarTrackDecorationColors(trackColor);
+ struct {
+ nscolor color;
+ float thickness;
+ } segments[] = {
+ {colors.mInnerColor, 1.0f},
+ {colors.mShadowColor, 1.0f},
+ {trackColor, thickness - 3.0f},
+ {colors.mOuterColor, 1.0f},
+ };
+
+ // Iterate over the segments "from inside to outside" and fill each segment.
+ // For horizontal scrollbars, iterate top to bottom.
+ // For vertical scrollbars, iterate left to right or right to left based on
+ // aParams.rtl.
+ float accumulatedThickness = 0.0f;
+ for (const auto& segment : segments) {
+ Rect segmentRect = aRect;
+ float startThickness = accumulatedThickness;
+ float endThickness = startThickness + segment.thickness;
+ if (aParams.horizontal) {
+ segmentRect.SetBoxY(aRect.Y() + startThickness, aRect.Y() + endThickness);
+ } else {
+ if (aParams.rtl) {
+ segmentRect.SetBoxX(aRect.XMost() - endThickness,
+ aRect.XMost() - startThickness);
+ } else {
+ segmentRect.SetBoxX(aRect.X() + startThickness,
+ aRect.X() + endThickness);
+ }
+ }
+ aDT.FillRect(segmentRect, ColorPattern(ToDeviceColor(segment.color)));
+ accumulatedThickness = endThickness;
+ }
+}
+
+void ScrollbarDrawingMac::DrawScrollCorner(DrawTarget& aDT, const Rect& aRect,
+ const ScrollbarParams& aParams) {
+ if (aParams.overlay && !aParams.rolledOver) {
+ // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
+ return;
+ }
+
+ // Draw the following scroll corner.
+ //
+ // Output: Rectangles:
+ // +---+---+----------+---+ +---+---+----------+---+
+ // | I | S | T ... T | O | | I | S | T ... T | O |
+ // +---+ | | | +---+---+ | |
+ // | S S | T ... T | | | S S | T ... T | . |
+ // +-------+ | . | +-------+----------+ . |
+ // | T ... T | . | | T ... T | . |
+ // | . . | . | | . . | |
+ // | T ... T | | | T ... T | O |
+ // +------------------+ | +------------------+---+
+ // | O ... O | | O ... O |
+ // +----------------------+ +----------------------+
+
+ float width = aRect.width;
+ float height = aRect.height;
+ nscolor trackColor =
+ aParams.custom ? aParams.trackColor : NS_RGBA(250, 250, 250, 255);
+ ScrollbarTrackDecorationColors colors =
+ ComputeScrollbarTrackDecorationColors(trackColor);
+ struct {
+ nscolor color;
+ Rect relativeRect;
+ } pieces[] = {
+ {colors.mInnerColor, {0.0f, 0.0f, 1.0f, 1.0f}},
+ {colors.mShadowColor, {1.0f, 0.0f, 1.0f, 1.0f}},
+ {colors.mShadowColor, {0.0f, 1.0f, 2.0f, 1.0f}},
+ {trackColor, {2.0f, 0.0f, width - 3.0f, 2.0f}},
+ {trackColor, {0.0f, 2.0f, width - 1.0f, height - 3.0f}},
+ {colors.mOuterColor, {width - 1.0f, 0.0f, 1.0f, height - 1.0f}},
+ {colors.mOuterColor, {0.0f, height - 1.0f, width, 1.0f}},
+ };
+
+ for (const auto& piece : pieces) {
+ Rect pieceRect = piece.relativeRect + aRect.TopLeft();
+ if (aParams.rtl) {
+ pieceRect.x = aRect.XMost() - piece.relativeRect.XMost();
+ }
+ aDT.FillRect(pieceRect, ColorPattern(ToDeviceColor(piece.color)));
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/ScrollbarDrawingMac.h b/widget/ScrollbarDrawingMac.h
new file mode 100644
index 0000000000..ef135337e7
--- /dev/null
+++ b/widget/ScrollbarDrawingMac.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_ScrollbarDrawing_h
+#define mozilla_widget_ScrollbarDrawing_h
+
+#include "nsColor.h"
+#include "nsITheme.h"
+#include "Units.h"
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+}
+
+namespace widget {
+
+struct ScrollbarParams {
+ bool overlay = false;
+ bool rolledOver = false;
+ bool small = false;
+ bool horizontal = false;
+ bool rtl = false;
+ bool onDarkBackground = false;
+ bool custom = false;
+ // Two colors only used when custom is true.
+ nscolor trackColor = NS_RGBA(0, 0, 0, 0);
+ nscolor faceColor = NS_RGBA(0, 0, 0, 0);
+};
+
+class ScrollbarDrawingMac final {
+ public:
+ static LayoutDeviceIntSize GetMinimumWidgetSize(StyleAppearance aAppearance,
+ nsIFrame* aFrame,
+ float aDpiRatio);
+ static ScrollbarParams ComputeScrollbarParams(nsIFrame* aFrame,
+ const ComputedStyle& aStyle,
+ bool aIsHorizontal);
+ static void DrawScrollbarThumb(gfx::DrawTarget& aDT, const gfx::Rect& aRect,
+ const ScrollbarParams& aParams);
+ static void DrawScrollbarTrack(gfx::DrawTarget& aDT, const gfx::Rect& aRect,
+ const ScrollbarParams& aParams);
+ static void DrawScrollCorner(gfx::DrawTarget& aDT, const gfx::Rect& aRect,
+ const ScrollbarParams& aParams);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/SharedWidgetUtils.cpp b/widget/SharedWidgetUtils.cpp
new file mode 100644
index 0000000000..f1c71b5c66
--- /dev/null
+++ b/widget/SharedWidgetUtils.cpp
@@ -0,0 +1,273 @@
+/* -*- 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 "WidgetUtils.h"
+
+#include "mozilla/TextEvents.h"
+
+#include "nsIBaseWindow.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace widget {
+
+// static
+void WidgetUtils::Shutdown() {
+ WidgetKeyboardEvent::Shutdown();
+ InternalEditorInputEvent::Shutdown();
+}
+
+// static
+already_AddRefed<nsIWidget> WidgetUtils::DOMWindowToWidget(
+ nsPIDOMWindowOuter* aDOMWindow) {
+ nsCOMPtr<nsIWidget> widget;
+ nsCOMPtr<nsPIDOMWindowOuter> window = aDOMWindow;
+
+ if (window) {
+ nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(window->GetDocShell()));
+
+ while (!widget && baseWin) {
+ baseWin->GetParentWidget(getter_AddRefs(widget));
+ if (!widget) {
+ nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(
+ do_QueryInterface(baseWin));
+ if (!docShellAsItem) return nullptr;
+
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ docShellAsItem->GetInProcessParent(getter_AddRefs(parent));
+
+ window = do_GetInterface(parent);
+ if (!window) return nullptr;
+
+ baseWin = do_QueryInterface(window->GetDocShell());
+ }
+ }
+ }
+
+ return widget.forget();
+}
+
+// static
+uint32_t WidgetUtils::ComputeKeyCodeFromChar(uint32_t aCharCode) {
+ if (aCharCode >= 'A' && aCharCode <= 'Z') {
+ return aCharCode - 'A' + NS_VK_A;
+ }
+ if (aCharCode >= 'a' && aCharCode <= 'z') {
+ return aCharCode - 'a' + NS_VK_A;
+ }
+ if (aCharCode >= '0' && aCharCode <= '9') {
+ return aCharCode - '0' + NS_VK_0;
+ }
+ switch (aCharCode) {
+ case ' ':
+ return NS_VK_SPACE;
+ case '\t':
+ return NS_VK_TAB;
+ case ':':
+ return NS_VK_COLON;
+ case ';':
+ return NS_VK_SEMICOLON;
+ case '<':
+ return NS_VK_LESS_THAN;
+ case '=':
+ return NS_VK_EQUALS;
+ case '>':
+ return NS_VK_GREATER_THAN;
+ case '?':
+ return NS_VK_QUESTION_MARK;
+ case '@':
+ return NS_VK_AT;
+ case '^':
+ return NS_VK_CIRCUMFLEX;
+ case '!':
+ return NS_VK_EXCLAMATION;
+ case '"':
+ return NS_VK_DOUBLE_QUOTE;
+ case '#':
+ return NS_VK_HASH;
+ case '$':
+ return NS_VK_DOLLAR;
+ case '%':
+ return NS_VK_PERCENT;
+ case '&':
+ return NS_VK_AMPERSAND;
+ case '_':
+ return NS_VK_UNDERSCORE;
+ case '(':
+ return NS_VK_OPEN_PAREN;
+ case ')':
+ return NS_VK_CLOSE_PAREN;
+ case '*':
+ return NS_VK_ASTERISK;
+ case '+':
+ return NS_VK_PLUS;
+ case '|':
+ return NS_VK_PIPE;
+ case '-':
+ return NS_VK_HYPHEN_MINUS;
+ case '{':
+ return NS_VK_OPEN_CURLY_BRACKET;
+ case '}':
+ return NS_VK_CLOSE_CURLY_BRACKET;
+ case '~':
+ return NS_VK_TILDE;
+ case ',':
+ return NS_VK_COMMA;
+ case '.':
+ return NS_VK_PERIOD;
+ case '/':
+ return NS_VK_SLASH;
+ case '`':
+ return NS_VK_BACK_QUOTE;
+ case '[':
+ return NS_VK_OPEN_BRACKET;
+ case '\\':
+ return NS_VK_BACK_SLASH;
+ case ']':
+ return NS_VK_CLOSE_BRACKET;
+ case '\'':
+ return NS_VK_QUOTE;
+ }
+ return 0;
+}
+
+// static
+void WidgetUtils::GetLatinCharCodeForKeyCode(uint32_t aKeyCode,
+ bool aIsCapsLock,
+ uint32_t* aUnshiftedCharCode,
+ uint32_t* aShiftedCharCode) {
+ MOZ_ASSERT(aUnshiftedCharCode && aShiftedCharCode,
+ "aUnshiftedCharCode and aShiftedCharCode must not be NULL");
+
+ if (aKeyCode >= NS_VK_A && aKeyCode <= NS_VK_Z) {
+ *aUnshiftedCharCode = *aShiftedCharCode = aKeyCode;
+ if (aIsCapsLock) {
+ *aShiftedCharCode += 0x20;
+ } else {
+ *aUnshiftedCharCode += 0x20;
+ }
+ return;
+ }
+
+ // aShiftedCharCode must be zero for non-alphabet keys.
+ *aShiftedCharCode = 0;
+
+ if (aKeyCode >= NS_VK_0 && aKeyCode <= NS_VK_9) {
+ *aUnshiftedCharCode = aKeyCode;
+ return;
+ }
+
+ switch (aKeyCode) {
+ case NS_VK_SPACE:
+ *aUnshiftedCharCode = ' ';
+ break;
+ case NS_VK_COLON:
+ *aUnshiftedCharCode = ':';
+ break;
+ case NS_VK_SEMICOLON:
+ *aUnshiftedCharCode = ';';
+ break;
+ case NS_VK_LESS_THAN:
+ *aUnshiftedCharCode = '<';
+ break;
+ case NS_VK_EQUALS:
+ *aUnshiftedCharCode = '=';
+ break;
+ case NS_VK_GREATER_THAN:
+ *aUnshiftedCharCode = '>';
+ break;
+ case NS_VK_QUESTION_MARK:
+ *aUnshiftedCharCode = '?';
+ break;
+ case NS_VK_AT:
+ *aUnshiftedCharCode = '@';
+ break;
+ case NS_VK_CIRCUMFLEX:
+ *aUnshiftedCharCode = '^';
+ break;
+ case NS_VK_EXCLAMATION:
+ *aUnshiftedCharCode = '!';
+ break;
+ case NS_VK_DOUBLE_QUOTE:
+ *aUnshiftedCharCode = '"';
+ break;
+ case NS_VK_HASH:
+ *aUnshiftedCharCode = '#';
+ break;
+ case NS_VK_DOLLAR:
+ *aUnshiftedCharCode = '$';
+ break;
+ case NS_VK_PERCENT:
+ *aUnshiftedCharCode = '%';
+ break;
+ case NS_VK_AMPERSAND:
+ *aUnshiftedCharCode = '&';
+ break;
+ case NS_VK_UNDERSCORE:
+ *aUnshiftedCharCode = '_';
+ break;
+ case NS_VK_OPEN_PAREN:
+ *aUnshiftedCharCode = '(';
+ break;
+ case NS_VK_CLOSE_PAREN:
+ *aUnshiftedCharCode = ')';
+ break;
+ case NS_VK_ASTERISK:
+ *aUnshiftedCharCode = '*';
+ break;
+ case NS_VK_PLUS:
+ *aUnshiftedCharCode = '+';
+ break;
+ case NS_VK_PIPE:
+ *aUnshiftedCharCode = '|';
+ break;
+ case NS_VK_HYPHEN_MINUS:
+ *aUnshiftedCharCode = '-';
+ break;
+ case NS_VK_OPEN_CURLY_BRACKET:
+ *aUnshiftedCharCode = '{';
+ break;
+ case NS_VK_CLOSE_CURLY_BRACKET:
+ *aUnshiftedCharCode = '}';
+ break;
+ case NS_VK_TILDE:
+ *aUnshiftedCharCode = '~';
+ break;
+ case NS_VK_COMMA:
+ *aUnshiftedCharCode = ',';
+ break;
+ case NS_VK_PERIOD:
+ *aUnshiftedCharCode = '.';
+ break;
+ case NS_VK_SLASH:
+ *aUnshiftedCharCode = '/';
+ break;
+ case NS_VK_BACK_QUOTE:
+ *aUnshiftedCharCode = '`';
+ break;
+ case NS_VK_OPEN_BRACKET:
+ *aUnshiftedCharCode = '[';
+ break;
+ case NS_VK_BACK_SLASH:
+ *aUnshiftedCharCode = '\\';
+ break;
+ case NS_VK_CLOSE_BRACKET:
+ *aUnshiftedCharCode = ']';
+ break;
+ case NS_VK_QUOTE:
+ *aUnshiftedCharCode = '\'';
+ break;
+ default:
+ *aUnshiftedCharCode = 0;
+ break;
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/SystemTimeConverter.h b/widget/SystemTimeConverter.h
new file mode 100644
index 0000000000..4a074d573f
--- /dev/null
+++ b/widget/SystemTimeConverter.h
@@ -0,0 +1,235 @@
+/* -*- 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 SystemTimeConverter_h
+#define SystemTimeConverter_h
+
+#include <limits>
+#include <type_traits>
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+
+// Utility class that converts time values represented as an unsigned integral
+// number of milliseconds from one time source (e.g. a native event time) to
+// corresponding mozilla::TimeStamp objects.
+//
+// This class handles wrapping of integer values and skew between the time
+// source and mozilla::TimeStamp values.
+//
+// It does this by using an historical reference time recorded in both time
+// scales (i.e. both as a numerical time value and as a TimeStamp).
+//
+// For performance reasons, this class is careful to minimize calls to the
+// native "current time" function (e.g. gdk_x11_server_get_time) since this can
+// be slow.
+template <typename Time, typename TimeStampNowProvider = TimeStamp>
+class SystemTimeConverter {
+ public:
+ SystemTimeConverter()
+ : mReferenceTime(Time(0)),
+ mReferenceTimeStamp() // Initializes to the null timestamp
+ ,
+ mLastBackwardsSkewCheck(Time(0)),
+ kTimeRange(std::numeric_limits<Time>::max()),
+ kTimeHalfRange(kTimeRange / 2),
+ kBackwardsSkewCheckInterval(Time(2000)) {
+ static_assert(!std::is_signed_v<Time>, "Expected Time to be unsigned");
+ }
+
+ template <typename CurrentTimeGetter>
+ mozilla::TimeStamp GetTimeStampFromSystemTime(
+ Time aTime, CurrentTimeGetter& aCurrentTimeGetter) {
+ TimeStamp roughlyNow = TimeStampNowProvider::Now();
+
+ // If the reference time is not set, use the current time value to fill
+ // it in.
+ if (mReferenceTimeStamp.IsNull()) {
+ // This sometimes happens when ::GetMessageTime returns 0 for the first
+ // message on Windows.
+ if (!aTime) return roughlyNow;
+ UpdateReferenceTime(aTime, aCurrentTimeGetter);
+ }
+
+ // Check for skew between the source of Time values and TimeStamp values.
+ // We do this by comparing two durations (both in ms):
+ //
+ // i. The duration from the reference time to the passed-in time.
+ // (timeDelta in the diagram below)
+ // ii. The duration from the reference timestamp to the current time
+ // based on TimeStamp::Now.
+ // (timeStampDelta in the diagram below)
+ //
+ // Normally, we'd expect (ii) to be slightly larger than (i) to account
+ // for the time taken between generating the event and processing it.
+ //
+ // If (ii) - (i) is negative then the source of Time values is getting
+ // "ahead" of TimeStamp. We call this "forwards" skew below.
+ //
+ // For the reverse case, if (ii) - (i) is positive (and greater than some
+ // tolerance factor), then we may have "backwards" skew. This is often
+ // the case when we have a backlog of events and by the time we process
+ // them, the time given by the system is comparatively "old".
+ //
+ // The IsNewerThanTimestamp function computes the equivalent of |aTime| in
+ // the TimeStamp scale and returns that in |timeAsTimeStamp|.
+ //
+ // Graphically:
+ //
+ // mReferenceTime aTime
+ // Time scale: ........+.......................*........
+ // |--------timeDelta------|
+ //
+ // mReferenceTimeStamp roughlyNow
+ // TimeStamp scale: ........+...........................*....
+ // |------timeStampDelta-------|
+ //
+ // |---|
+ // roughlyNow-timeAsTimeStamp
+ //
+ TimeStamp timeAsTimeStamp;
+ bool newer = IsTimeNewerThanTimestamp(aTime, roughlyNow, &timeAsTimeStamp);
+
+ // Tolerance when detecting clock skew.
+ static const TimeDuration kTolerance = TimeDuration::FromMilliseconds(30.0);
+
+ // Check for forwards skew
+ if (newer) {
+ // Make aTime correspond to roughlyNow
+ UpdateReferenceTime(aTime, roughlyNow);
+
+ // We didn't have backwards skew so don't bother checking for
+ // backwards skew again for a little while.
+ mLastBackwardsSkewCheck = aTime;
+
+ return roughlyNow;
+ }
+
+ if (roughlyNow - timeAsTimeStamp <= kTolerance) {
+ // If the time between event times and TimeStamp values is within
+ // the tolerance then assume we don't have clock skew so we can
+ // avoid checking for backwards skew for a while.
+ mLastBackwardsSkewCheck = aTime;
+ } else if (aTime - mLastBackwardsSkewCheck > kBackwardsSkewCheckInterval) {
+ aCurrentTimeGetter.GetTimeAsyncForPossibleBackwardsSkew(roughlyNow);
+ mLastBackwardsSkewCheck = aTime;
+ }
+
+ // Finally, calculate the timestamp
+ return timeAsTimeStamp;
+ }
+
+ void CompensateForBackwardsSkew(Time aReferenceTime,
+ const TimeStamp& aLowerBound) {
+ // Check if we actually have backwards skew. Backwards skew looks like
+ // the following:
+ //
+ // mReferenceTime
+ // Time: ..+...a...b...c..........................
+ //
+ // mReferenceTimeStamp
+ // TimeStamp: ..+.....a.....b.....c....................
+ //
+ // Converted
+ // time: ......a'..b'..c'.........................
+ //
+ // What we need to do is bring mReferenceTime "forwards".
+ //
+ // Suppose when we get (c), we detect possible backwards skew and trigger
+ // an async request for the current time (which is passed in here as
+ // aReferenceTime).
+ //
+ // We end up with something like the following:
+ //
+ // mReferenceTime aReferenceTime
+ // Time: ..+...a...b...c...v......................
+ //
+ // mReferenceTimeStamp
+ // TimeStamp: ..+.....a.....b.....c..........x.........
+ // ^ ^
+ // aLowerBound TimeStamp::Now()
+ //
+ // If the duration (aLowerBound - mReferenceTimeStamp) is greater than
+ // (aReferenceTime - mReferenceTime) then we know we have backwards skew.
+ //
+ // If that's not the case, then we probably just got caught behind
+ // temporarily.
+ if (IsTimeNewerThanTimestamp(aReferenceTime, aLowerBound, nullptr)) {
+ return;
+ }
+
+ // We have backwards skew; the equivalent TimeStamp for aReferenceTime lies
+ // somewhere between aLowerBound (which was the TimeStamp when we triggered
+ // the async request for the current time) and TimeStamp::Now().
+ //
+ // If aReferenceTime was waiting in the event queue for a long time, the
+ // equivalent TimeStamp might be much closer to aLowerBound than
+ // TimeStamp::Now() so for now we just set it to aLowerBound. That's
+ // guaranteed to be at least somewhat of an improvement.
+ UpdateReferenceTime(aReferenceTime, aLowerBound);
+ }
+
+ private:
+ template <typename CurrentTimeGetter>
+ void UpdateReferenceTime(Time aReferenceTime,
+ const CurrentTimeGetter& aCurrentTimeGetter) {
+ Time currentTime = aCurrentTimeGetter.GetCurrentTime();
+ TimeStamp currentTimeStamp = TimeStampNowProvider::Now();
+ Time timeSinceReference = currentTime - aReferenceTime;
+ TimeStamp referenceTimeStamp =
+ currentTimeStamp - TimeDuration::FromMilliseconds(timeSinceReference);
+ UpdateReferenceTime(aReferenceTime, referenceTimeStamp);
+ }
+
+ void UpdateReferenceTime(Time aReferenceTime,
+ const TimeStamp& aReferenceTimeStamp) {
+ mReferenceTime = aReferenceTime;
+ mReferenceTimeStamp = aReferenceTimeStamp;
+ }
+
+ bool IsTimeNewerThanTimestamp(Time aTime, TimeStamp aTimeStamp,
+ TimeStamp* aTimeAsTimeStamp) {
+ Time timeDelta = aTime - mReferenceTime;
+
+ // Cast the result to signed 64-bit integer first since that should be
+ // enough to hold the range of values returned by ToMilliseconds() and
+ // the result of converting from double to an integer-type when the value
+ // is outside the integer range is undefined.
+ // Then we do an implicit cast to Time (typically an unsigned 32-bit
+ // integer) which wraps times outside that range.
+ TimeDuration timeStampDelta = (aTimeStamp - mReferenceTimeStamp);
+ int64_t wholeMillis = static_cast<int64_t>(timeStampDelta.ToMilliseconds());
+ Time wrappedTimeStampDelta = wholeMillis; // truncate to unsigned
+
+ Time timeToTimeStamp = wrappedTimeStampDelta - timeDelta;
+ bool isNewer = false;
+ if (timeToTimeStamp == 0) {
+ // wholeMillis needs no adjustment
+ } else if (timeToTimeStamp < kTimeHalfRange) {
+ wholeMillis -= timeToTimeStamp;
+ } else {
+ isNewer = true;
+ wholeMillis += (-timeToTimeStamp);
+ }
+ if (aTimeAsTimeStamp) {
+ *aTimeAsTimeStamp =
+ mReferenceTimeStamp + TimeDuration::FromMilliseconds(wholeMillis);
+ }
+
+ return isNewer;
+ }
+
+ Time mReferenceTime;
+ TimeStamp mReferenceTimeStamp;
+ Time mLastBackwardsSkewCheck;
+
+ const Time kTimeRange;
+ const Time kTimeHalfRange;
+ const Time kBackwardsSkewCheckInterval;
+};
+
+} // namespace mozilla
+
+#endif /* SystemTimeConverter_h */
diff --git a/widget/TextEventDispatcher.cpp b/widget/TextEventDispatcher.cpp
new file mode 100644
index 0000000000..73ee6ba8d6
--- /dev/null
+++ b/widget/TextEventDispatcher.cpp
@@ -0,0 +1,925 @@
+/* -*- 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/Preferences.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "nsIFrame.h"
+#include "nsIWidget.h"
+#include "nsPIDOMWindow.h"
+#include "nsView.h"
+#include "PuppetWidget.h"
+
+namespace mozilla {
+namespace widget {
+
+/******************************************************************************
+ * TextEventDispatcher
+ *****************************************************************************/
+TextEventDispatcher::TextEventDispatcher(nsIWidget* aWidget)
+ : mWidget(aWidget),
+ mDispatchingEvent(0),
+ mInputTransactionType(eNoInputTransaction),
+ mIsComposing(false),
+ mIsHandlingComposition(false),
+ mHasFocus(false) {
+ MOZ_RELEASE_ASSERT(mWidget, "aWidget must not be nullptr");
+
+ ClearNotificationRequests();
+}
+
+nsresult TextEventDispatcher::BeginInputTransaction(
+ TextEventDispatcherListener* aListener) {
+ return BeginInputTransactionInternal(aListener,
+ eSameProcessSyncInputTransaction);
+}
+
+nsresult TextEventDispatcher::BeginTestInputTransaction(
+ TextEventDispatcherListener* aListener, bool aIsAPZAware) {
+ return BeginInputTransactionInternal(
+ aListener, aIsAPZAware ? eAsyncTestInputTransaction
+ : eSameProcessSyncTestInputTransaction);
+}
+
+nsresult TextEventDispatcher::BeginNativeInputTransaction() {
+ if (NS_WARN_IF(!mWidget)) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<TextEventDispatcherListener> listener =
+ mWidget->GetNativeTextEventDispatcherListener();
+ if (NS_WARN_IF(!listener)) {
+ return NS_ERROR_FAILURE;
+ }
+ return BeginInputTransactionInternal(listener, eNativeInputTransaction);
+}
+
+nsresult TextEventDispatcher::BeginInputTransactionInternal(
+ TextEventDispatcherListener* aListener, InputTransactionType aType) {
+ if (NS_WARN_IF(!aListener)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
+ if (listener) {
+ if (listener == aListener && mInputTransactionType == aType) {
+ UpdateNotificationRequests();
+ return NS_OK;
+ }
+ // If this has composition or is dispatching an event, any other listener
+ // can steal ownership. Especially, if the latter case is allowed,
+ // nobody cannot begin input transaction with this if a modal dialog is
+ // opened during dispatching an event.
+ if (IsComposing() || IsDispatchingEvent()) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+ }
+ mListener = do_GetWeakReference(aListener);
+ mInputTransactionType = aType;
+ if (listener && listener != aListener) {
+ listener->OnRemovedFrom(this);
+ }
+ UpdateNotificationRequests();
+ return NS_OK;
+}
+
+nsresult TextEventDispatcher::BeginInputTransactionFor(
+ const WidgetGUIEvent* aEvent, PuppetWidget* aPuppetWidget) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ MOZ_ASSERT(!IsDispatchingEvent());
+
+ switch (aEvent->mMessage) {
+ case eKeyDown:
+ case eKeyPress:
+ case eKeyUp:
+ MOZ_ASSERT(aEvent->mClass == eKeyboardEventClass);
+ break;
+ case eCompositionStart:
+ case eCompositionChange:
+ case eCompositionCommit:
+ case eCompositionCommitAsIs:
+ MOZ_ASSERT(aEvent->mClass == eCompositionEventClass);
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aEvent->mFlags.mIsSynthesizedForTests) {
+ // If the event is for an automated test and this instance dispatched
+ // an event to the parent process, we can assume that this is already
+ // initialized properly.
+ if (mInputTransactionType == eAsyncTestInputTransaction) {
+ return NS_OK;
+ }
+ // Even if the event coming from the parent process is synthesized for
+ // tests, this process should treat it as "sync" test here because
+ // it won't be go back to the parent process.
+ nsresult rv = BeginInputTransactionInternal(
+ static_cast<TextEventDispatcherListener*>(aPuppetWidget),
+ eSameProcessSyncTestInputTransaction);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ nsresult rv = BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // Emulate modifying members which indicate the state of composition.
+ // If we need to manage more states and/or more complexly, we should create
+ // internal methods which are called by both here and each event dispatcher
+ // method of this class.
+ switch (aEvent->mMessage) {
+ case eKeyDown:
+ case eKeyPress:
+ case eKeyUp:
+ return NS_OK;
+ case eCompositionStart:
+ MOZ_ASSERT(!mIsComposing);
+ mIsComposing = mIsHandlingComposition = true;
+ return NS_OK;
+ case eCompositionChange:
+ MOZ_ASSERT(mIsComposing);
+ MOZ_ASSERT(mIsHandlingComposition);
+ mIsComposing = mIsHandlingComposition = true;
+ return NS_OK;
+ case eCompositionCommit:
+ case eCompositionCommitAsIs:
+ MOZ_ASSERT(mIsComposing);
+ MOZ_ASSERT(mIsHandlingComposition);
+ mIsComposing = false;
+ mIsHandlingComposition = true;
+ return NS_OK;
+ default:
+ MOZ_ASSERT_UNREACHABLE("You forgot to handle the event");
+ return NS_ERROR_UNEXPECTED;
+ }
+}
+void TextEventDispatcher::EndInputTransaction(
+ TextEventDispatcherListener* aListener) {
+ if (NS_WARN_IF(IsComposing()) || NS_WARN_IF(IsDispatchingEvent())) {
+ return;
+ }
+
+ mInputTransactionType = eNoInputTransaction;
+
+ nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
+ if (NS_WARN_IF(!listener)) {
+ return;
+ }
+
+ if (NS_WARN_IF(listener != aListener)) {
+ return;
+ }
+
+ mListener = nullptr;
+ listener->OnRemovedFrom(this);
+ UpdateNotificationRequests();
+}
+
+void TextEventDispatcher::OnDestroyWidget() {
+ mWidget = nullptr;
+ mHasFocus = false;
+ ClearNotificationRequests();
+ mPendingComposition.Clear();
+ nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
+ mListener = nullptr;
+ mInputTransactionType = eNoInputTransaction;
+ if (listener) {
+ listener->OnRemovedFrom(this);
+ }
+}
+
+nsresult TextEventDispatcher::GetState() const {
+ nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
+ if (!listener) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ if (!mWidget || mWidget->Destroyed()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return NS_OK;
+}
+
+void TextEventDispatcher::InitEvent(WidgetGUIEvent& aEvent) const {
+ aEvent.mTime = PR_IntervalNow();
+ aEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ aEvent.mFlags.mIsSynthesizedForTests = IsForTests();
+ if (aEvent.mClass != eCompositionEventClass) {
+ return;
+ }
+ void* pseudoIMEContext = GetPseudoIMEContext();
+ if (pseudoIMEContext) {
+ aEvent.AsCompositionEvent()->mNativeIMEContext.InitWithRawNativeIMEContext(
+ pseudoIMEContext);
+ }
+#ifdef DEBUG
+ else {
+ MOZ_ASSERT(!XRE_IsContentProcess(),
+ "Why did the content process start native event transaction?");
+ MOZ_ASSERT(aEvent.AsCompositionEvent()->mNativeIMEContext.IsValid(),
+ "Native IME context shouldn't be invalid");
+ }
+#endif // #ifdef DEBUG
+}
+
+nsresult TextEventDispatcher::DispatchEvent(nsIWidget* aWidget,
+ WidgetGUIEvent& aEvent,
+ nsEventStatus& aStatus) {
+ MOZ_ASSERT(!aEvent.AsInputEvent(), "Use DispatchInputEvent()");
+
+ RefPtr<TextEventDispatcher> kungFuDeathGrip(this);
+ nsCOMPtr<nsIWidget> widget(aWidget);
+ mDispatchingEvent++;
+ nsresult rv = widget->DispatchEvent(&aEvent, aStatus);
+ mDispatchingEvent--;
+ return rv;
+}
+
+nsresult TextEventDispatcher::DispatchInputEvent(nsIWidget* aWidget,
+ WidgetInputEvent& aEvent,
+ nsEventStatus& aStatus) {
+ RefPtr<TextEventDispatcher> kungFuDeathGrip(this);
+ nsCOMPtr<nsIWidget> widget(aWidget);
+ mDispatchingEvent++;
+
+ // If the event is dispatched via nsIWidget::DispatchInputEvent(), it
+ // sends the event to the parent process first since APZ needs to handle it
+ // first. However, some callers (e.g., keyboard apps on B2G and tests
+ // expecting synchronous dispatch) don't want this to do that.
+ nsresult rv = NS_OK;
+ if (ShouldSendInputEventToAPZ()) {
+ aStatus = widget->DispatchInputEvent(&aEvent);
+ } else {
+ rv = widget->DispatchEvent(&aEvent, aStatus);
+ }
+
+ mDispatchingEvent--;
+ return rv;
+}
+
+nsresult TextEventDispatcher::StartComposition(
+ nsEventStatus& aStatus, const WidgetEventTime* aEventTime) {
+ aStatus = nsEventStatus_eIgnore;
+
+ nsresult rv = GetState();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(mIsComposing)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // When you change some members from here, you may need same change in
+ // BeginInputTransactionFor().
+ mIsComposing = mIsHandlingComposition = true;
+ WidgetCompositionEvent compositionStartEvent(true, eCompositionStart,
+ mWidget);
+ InitEvent(compositionStartEvent);
+ if (aEventTime) {
+ compositionStartEvent.AssignEventTime(*aEventTime);
+ }
+ rv = DispatchEvent(mWidget, compositionStartEvent, aStatus);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult TextEventDispatcher::StartCompositionAutomaticallyIfNecessary(
+ nsEventStatus& aStatus, const WidgetEventTime* aEventTime) {
+ if (IsComposing()) {
+ return NS_OK;
+ }
+
+ nsresult rv = StartComposition(aStatus, aEventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // If started composition has already been committed, we shouldn't dispatch
+ // the compositionchange event.
+ if (!IsComposing()) {
+ aStatus = nsEventStatus_eConsumeNoDefault;
+ return NS_OK;
+ }
+
+ // Note that the widget might be destroyed during a call of
+ // StartComposition(). In such case, we shouldn't keep dispatching next
+ // event.
+ rv = GetState();
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(rv != NS_ERROR_NOT_INITIALIZED,
+ "aDispatcher must still be initialized in this case");
+ aStatus = nsEventStatus_eConsumeNoDefault;
+ return NS_OK; // Don't throw exception in this case
+ }
+
+ aStatus = nsEventStatus_eIgnore;
+ return NS_OK;
+}
+
+nsresult TextEventDispatcher::CommitComposition(
+ nsEventStatus& aStatus, const nsAString* aCommitString,
+ const WidgetEventTime* aEventTime) {
+ aStatus = nsEventStatus_eIgnore;
+
+ nsresult rv = GetState();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // When there is no composition, caller shouldn't try to commit composition
+ // with non-existing composition string nor commit composition with empty
+ // string.
+ if (NS_WARN_IF(!IsComposing() &&
+ (!aCommitString || aCommitString->IsEmpty()))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIWidget> widget(mWidget);
+ rv = StartCompositionAutomaticallyIfNecessary(aStatus, aEventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (aStatus == nsEventStatus_eConsumeNoDefault) {
+ return NS_OK;
+ }
+
+ // When you change some members from here, you may need same change in
+ // BeginInputTransactionFor().
+
+ // End current composition and make this free for other IMEs.
+ mIsComposing = false;
+
+ EventMessage message =
+ aCommitString ? eCompositionCommit : eCompositionCommitAsIs;
+ WidgetCompositionEvent compositionCommitEvent(true, message, widget);
+ InitEvent(compositionCommitEvent);
+ if (aEventTime) {
+ compositionCommitEvent.AssignEventTime(*aEventTime);
+ }
+ if (message == eCompositionCommit) {
+ compositionCommitEvent.mData = *aCommitString;
+ // If aCommitString comes from TextInputProcessor, it may be void, but
+ // editor requires non-void string even when it's empty.
+ compositionCommitEvent.mData.SetIsVoid(false);
+ // Don't send CRLF nor CR, replace it with LF here.
+ compositionCommitEvent.mData.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
+ compositionCommitEvent.mData.ReplaceSubstring(u"\r"_ns, u"\n"_ns);
+ }
+ rv = DispatchEvent(widget, compositionCommitEvent, aStatus);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult TextEventDispatcher::NotifyIME(
+ const IMENotification& aIMENotification) {
+ nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
+
+ switch (aIMENotification.mMessage) {
+ case NOTIFY_IME_OF_BLUR:
+ mHasFocus = false;
+ ClearNotificationRequests();
+ break;
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
+ // If content handles composition events when native IME doesn't have
+ // composition, that means that we completely finished handling
+ // composition(s). Note that when focused content is in a remote
+ // process, this is sent when all dispatched composition events
+ // have been handled in the remote process.
+ if (!IsComposing()) {
+ mIsHandlingComposition = false;
+ }
+ break;
+ default:
+ break;
+ }
+
+ // First, send the notification to current input transaction's listener.
+ nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
+ if (listener) {
+ rv = listener->NotifyIME(this, aIMENotification);
+ }
+
+ if (!mWidget) {
+ return rv;
+ }
+
+ // If current input transaction isn't for native event handler, we should
+ // send the notification to the native text event dispatcher listener
+ // since native event handler may need to do something from
+ // TextEventDispatcherListener::NotifyIME() even before there is no
+ // input transaction yet. For example, native IME handler may need to
+ // create new context at receiving NOTIFY_IME_OF_FOCUS. In this case,
+ // mListener may not be initialized since input transaction should be
+ // initialized immediately before dispatching every WidgetKeyboardEvent
+ // and WidgetCompositionEvent (dispatching events always occurs after
+ // focus move).
+ nsCOMPtr<TextEventDispatcherListener> nativeListener =
+ mWidget->GetNativeTextEventDispatcherListener();
+ if (listener != nativeListener && nativeListener) {
+ switch (aIMENotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ case REQUEST_TO_CANCEL_COMPOSITION:
+ // It's not necessary to notify native IME of requests.
+ break;
+ default: {
+ // Even if current input transaction's listener returns NS_OK or
+ // something, we need to notify native IME of notifications because
+ // when user typing after TIP does something, the changed information
+ // is necessary for them.
+ nsresult rv2 = nativeListener->NotifyIME(this, aIMENotification);
+ // But return the result from current listener except when the
+ // notification isn't handled.
+ if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ rv = rv2;
+ }
+ break;
+ }
+ }
+ }
+
+ if (aIMENotification.mMessage == NOTIFY_IME_OF_FOCUS) {
+ mHasFocus = true;
+ UpdateNotificationRequests();
+ }
+
+ return rv;
+}
+
+void TextEventDispatcher::ClearNotificationRequests() {
+ mIMENotificationRequests = IMENotificationRequests();
+}
+
+void TextEventDispatcher::UpdateNotificationRequests() {
+ ClearNotificationRequests();
+
+ // If it doesn't has focus, no notifications are available.
+ if (!mHasFocus || !mWidget) {
+ return;
+ }
+
+ // If there is a listener, its requests are necessary.
+ nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
+ if (listener) {
+ mIMENotificationRequests = listener->GetIMENotificationRequests();
+ }
+
+ // Even if this is in non-native input transaction, native IME needs
+ // requests. So, add native IME requests too.
+ if (!IsInNativeInputTransaction()) {
+ nsCOMPtr<TextEventDispatcherListener> nativeListener =
+ mWidget->GetNativeTextEventDispatcherListener();
+ if (nativeListener) {
+ mIMENotificationRequests |= nativeListener->GetIMENotificationRequests();
+ }
+ }
+}
+
+bool TextEventDispatcher::DispatchKeyboardEvent(
+ EventMessage aMessage, const WidgetKeyboardEvent& aKeyboardEvent,
+ nsEventStatus& aStatus, void* aData) {
+ return DispatchKeyboardEventInternal(aMessage, aKeyboardEvent, aStatus,
+ aData);
+}
+
+bool TextEventDispatcher::DispatchKeyboardEventInternal(
+ EventMessage aMessage, const WidgetKeyboardEvent& aKeyboardEvent,
+ nsEventStatus& aStatus, void* aData, uint32_t aIndexOfKeypress,
+ bool aNeedsCallback) {
+ // Note that this method is also used for dispatching key events on a plugin
+ // because key events on a plugin should be dispatched same as normal key
+ // events. Then, only some handlers which need to intercept key events
+ // before the focused plugin (e.g., reserved shortcut key handlers) can
+ // consume the events.
+ MOZ_ASSERT(WidgetKeyboardEvent::IsKeyDownOrKeyDownOnPlugin(aMessage) ||
+ WidgetKeyboardEvent::IsKeyUpOrKeyUpOnPlugin(aMessage) ||
+ aMessage == eKeyPress,
+ "Invalid aMessage value");
+ nsresult rv = GetState();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ // If the key shouldn't cause keypress events, don't this patch them.
+ if (aMessage == eKeyPress && !aKeyboardEvent.ShouldCauseKeypressEvents()) {
+ return false;
+ }
+
+ // Basically, key events shouldn't be dispatched during composition.
+ // Note that plugin process has different IME context. Therefore, we don't
+ // need to check our composition state when the key event is fired on a
+ // plugin.
+ if (IsComposing() && !WidgetKeyboardEvent::IsKeyEventOnPlugin(aMessage)) {
+ // However, if we need to behave like other browsers, we need the keydown
+ // and keyup events. Note that this behavior is also allowed by D3E spec.
+ // FYI: keypress events must not be fired during composition.
+ if (!StaticPrefs::dom_keyboardevent_dispatch_during_composition() ||
+ aMessage == eKeyPress) {
+ return false;
+ }
+ // XXX If there was mOnlyContentDispatch for this case, it might be useful
+ // because our chrome doesn't assume that key events are fired during
+ // composition.
+ }
+
+ WidgetKeyboardEvent keyEvent(true, aMessage, mWidget);
+ InitEvent(keyEvent);
+ keyEvent.AssignKeyEventData(aKeyboardEvent, false);
+ // Command arrays are not duplicated by AssignKeyEventData() due to
+ // both performance and footprint reasons. So, when TextInputProcessor
+ // emulates real text input, the arrays may be initialized all commands
+ // already. If so, we need to duplicate the arrays here.
+ if (keyEvent.mIsSynthesizedByTIP) {
+ keyEvent.AssignCommands(aKeyboardEvent);
+ }
+
+ if (aStatus == nsEventStatus_eConsumeNoDefault) {
+ // If the key event should be dispatched as consumed event, marking it here.
+ // This is useful to prevent double action. This is intended to the system
+ // has already consumed the event but we need to dispatch the event for
+ // compatibility with older version and other browsers. So, we should not
+ // stop cross process forwarding of them.
+ keyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow);
+ }
+
+ // Corrects each member for the specific key event type.
+ if (keyEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING) {
+ MOZ_ASSERT(!aIndexOfKeypress,
+ "aIndexOfKeypress must be 0 for non-printable key");
+ // If the keyboard event isn't caused by printable key, its charCode should
+ // be 0.
+ keyEvent.SetCharCode(0);
+ } else {
+ if (WidgetKeyboardEvent::IsKeyDownOrKeyDownOnPlugin(aMessage) ||
+ WidgetKeyboardEvent::IsKeyUpOrKeyUpOnPlugin(aMessage)) {
+ MOZ_RELEASE_ASSERT(
+ !aIndexOfKeypress,
+ "aIndexOfKeypress must be 0 for either eKeyDown or eKeyUp");
+ } else {
+ MOZ_RELEASE_ASSERT(
+ !aIndexOfKeypress || aIndexOfKeypress < keyEvent.mKeyValue.Length(),
+ "aIndexOfKeypress must be 0 - mKeyValue.Length() - 1");
+ }
+ char16_t ch =
+ keyEvent.mKeyValue.IsEmpty() ? 0 : keyEvent.mKeyValue[aIndexOfKeypress];
+ keyEvent.SetCharCode(static_cast<uint32_t>(ch));
+ if (aMessage == eKeyPress) {
+ // keyCode of eKeyPress events of printable keys should be always 0.
+ keyEvent.mKeyCode = 0;
+ // eKeyPress events are dispatched for every character.
+ // So, each key value of eKeyPress events should be a character.
+ if (ch) {
+ keyEvent.mKeyValue.Assign(ch);
+ } else {
+ keyEvent.mKeyValue.Truncate();
+ }
+ }
+ }
+ if (WidgetKeyboardEvent::IsKeyUpOrKeyUpOnPlugin(aMessage)) {
+ // mIsRepeat of keyup event must be false.
+ keyEvent.mIsRepeat = false;
+ }
+ // mIsComposing should be initialized later.
+ keyEvent.mIsComposing = false;
+ if (mInputTransactionType == eNativeInputTransaction) {
+ // Copy mNativeKeyEvent here because for safety for other users of
+ // AssignKeyEventData(), it doesn't copy this.
+ keyEvent.mNativeKeyEvent = aKeyboardEvent.mNativeKeyEvent;
+ } else {
+ // If it's not a keyboard event for native key event, we should ensure that
+ // mNativeKeyEvent and mPluginEvent are null/empty.
+ keyEvent.mNativeKeyEvent = nullptr;
+ keyEvent.mPluginEvent.Clear();
+ }
+ // TODO: Manage mUniqueId here.
+
+ // Request the alternative char codes for the key event.
+ // eKeyDown also needs alternative char codes because nsXBLWindowKeyHandler
+ // needs to check if a following keypress event is reserved by chrome for
+ // stopping propagation of its preceding keydown event.
+ keyEvent.mAlternativeCharCodes.Clear();
+ if ((WidgetKeyboardEvent::IsKeyDownOrKeyDownOnPlugin(aMessage) ||
+ aMessage == eKeyPress) &&
+ (aNeedsCallback || keyEvent.IsControl() || keyEvent.IsAlt() ||
+ keyEvent.IsMeta() || keyEvent.IsOS())) {
+ nsCOMPtr<TextEventDispatcherListener> listener =
+ do_QueryReferent(mListener);
+ if (listener) {
+ DebugOnly<WidgetKeyboardEvent> original(keyEvent);
+ listener->WillDispatchKeyboardEvent(this, keyEvent, aIndexOfKeypress,
+ aData);
+ MOZ_ASSERT(keyEvent.mMessage ==
+ static_cast<WidgetKeyboardEvent&>(original).mMessage);
+ MOZ_ASSERT(keyEvent.mKeyCode ==
+ static_cast<WidgetKeyboardEvent&>(original).mKeyCode);
+ MOZ_ASSERT(keyEvent.mLocation ==
+ static_cast<WidgetKeyboardEvent&>(original).mLocation);
+ MOZ_ASSERT(keyEvent.mIsRepeat ==
+ static_cast<WidgetKeyboardEvent&>(original).mIsRepeat);
+ MOZ_ASSERT(keyEvent.mIsComposing ==
+ static_cast<WidgetKeyboardEvent&>(original).mIsComposing);
+ MOZ_ASSERT(keyEvent.mKeyNameIndex ==
+ static_cast<WidgetKeyboardEvent&>(original).mKeyNameIndex);
+ MOZ_ASSERT(keyEvent.mCodeNameIndex ==
+ static_cast<WidgetKeyboardEvent&>(original).mCodeNameIndex);
+ MOZ_ASSERT(keyEvent.mKeyValue ==
+ static_cast<WidgetKeyboardEvent&>(original).mKeyValue);
+ MOZ_ASSERT(keyEvent.mCodeValue ==
+ static_cast<WidgetKeyboardEvent&>(original).mCodeValue);
+ }
+ }
+
+ if (StaticPrefs::
+ dom_keyboardevent_keypress_dispatch_non_printable_keys_only_system_group_in_content() &&
+ keyEvent.mMessage == eKeyPress &&
+ !keyEvent.ShouldKeyPressEventBeFiredOnContent()) {
+ // Note that even if we set it to true, this may be overwritten by
+ // PresShell::DispatchEventToDOM().
+ keyEvent.mFlags.mOnlySystemGroupDispatchInContent = true;
+ }
+
+ DispatchInputEvent(mWidget, keyEvent, aStatus);
+ return true;
+}
+
+bool TextEventDispatcher::MaybeDispatchKeypressEvents(
+ const WidgetKeyboardEvent& aKeyboardEvent, nsEventStatus& aStatus,
+ void* aData, bool aNeedsCallback) {
+ // If the key event was consumed, keypress event shouldn't be fired.
+ if (aStatus == nsEventStatus_eConsumeNoDefault) {
+ return false;
+ }
+
+ // If the key shouldn't cause keypress events, don't fire them.
+ if (!aKeyboardEvent.ShouldCauseKeypressEvents()) {
+ return false;
+ }
+
+ // If the key isn't a printable key or just inputting one character or
+ // no character, we should dispatch only one keypress. Otherwise, i.e.,
+ // if the key is a printable key and inputs multiple characters, keypress
+ // event should be dispatched the count of inputting characters times.
+ size_t keypressCount =
+ aKeyboardEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING
+ ? 1
+ : std::max(static_cast<nsAString::size_type>(1),
+ aKeyboardEvent.mKeyValue.Length());
+ bool isDispatched = false;
+ bool consumed = false;
+ for (size_t i = 0; i < keypressCount; i++) {
+ aStatus = nsEventStatus_eIgnore;
+ if (!DispatchKeyboardEventInternal(eKeyPress, aKeyboardEvent, aStatus,
+ aData, i, aNeedsCallback)) {
+ // The widget must have been gone.
+ break;
+ }
+ isDispatched = true;
+ if (!consumed) {
+ consumed = (aStatus == nsEventStatus_eConsumeNoDefault);
+ }
+ }
+
+ // If one of the keypress event was consumed, return ConsumeNoDefault.
+ if (consumed) {
+ aStatus = nsEventStatus_eConsumeNoDefault;
+ }
+
+ return isDispatched;
+}
+
+/******************************************************************************
+ * TextEventDispatcher::PendingComposition
+ *****************************************************************************/
+
+TextEventDispatcher::PendingComposition::PendingComposition() { Clear(); }
+
+void TextEventDispatcher::PendingComposition::Clear() {
+ mString.Truncate();
+ mClauses = nullptr;
+ mCaret.mRangeType = TextRangeType::eUninitialized;
+ mReplacedNativeLineBreakers = false;
+}
+
+void TextEventDispatcher::PendingComposition::EnsureClauseArray() {
+ if (mClauses) {
+ return;
+ }
+ mClauses = new TextRangeArray();
+}
+
+nsresult TextEventDispatcher::PendingComposition::SetString(
+ const nsAString& aString) {
+ MOZ_ASSERT(!mReplacedNativeLineBreakers);
+ mString = aString;
+ return NS_OK;
+}
+
+nsresult TextEventDispatcher::PendingComposition::AppendClause(
+ uint32_t aLength, TextRangeType aTextRangeType) {
+ MOZ_ASSERT(!mReplacedNativeLineBreakers);
+
+ if (NS_WARN_IF(!aLength)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ switch (aTextRangeType) {
+ case TextRangeType::eRawClause:
+ case TextRangeType::eSelectedRawClause:
+ case TextRangeType::eConvertedClause:
+ case TextRangeType::eSelectedClause: {
+ EnsureClauseArray();
+ TextRange textRange;
+ textRange.mStartOffset =
+ mClauses->IsEmpty() ? 0 : mClauses->LastElement().mEndOffset;
+ textRange.mEndOffset = textRange.mStartOffset + aLength;
+ textRange.mRangeType = aTextRangeType;
+ mClauses->AppendElement(textRange);
+ return NS_OK;
+ }
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+}
+
+nsresult TextEventDispatcher::PendingComposition::SetCaret(uint32_t aOffset,
+ uint32_t aLength) {
+ MOZ_ASSERT(!mReplacedNativeLineBreakers);
+
+ mCaret.mStartOffset = aOffset;
+ mCaret.mEndOffset = mCaret.mStartOffset + aLength;
+ mCaret.mRangeType = TextRangeType::eCaret;
+ return NS_OK;
+}
+
+nsresult TextEventDispatcher::PendingComposition::Set(
+ const nsAString& aString, const TextRangeArray* aRanges) {
+ Clear();
+
+ nsresult rv = SetString(aString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!aRanges || aRanges->IsEmpty()) {
+ // Create dummy range if mString isn't empty.
+ if (!mString.IsEmpty()) {
+ rv = AppendClause(mString.Length(), TextRangeType::eRawClause);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ ReplaceNativeLineBreakers();
+ }
+ return NS_OK;
+ }
+
+ // Adjust offsets in the ranges for XP linefeed character (only \n).
+ for (uint32_t i = 0; i < aRanges->Length(); ++i) {
+ TextRange range = aRanges->ElementAt(i);
+ if (range.mRangeType == TextRangeType::eCaret) {
+ mCaret = range;
+ } else {
+ EnsureClauseArray();
+ mClauses->AppendElement(range);
+ }
+ }
+ ReplaceNativeLineBreakers();
+ return NS_OK;
+}
+
+void TextEventDispatcher::PendingComposition::ReplaceNativeLineBreakers() {
+ mReplacedNativeLineBreakers = true;
+
+ // If the composition string is empty, we don't need to do anything.
+ if (mString.IsEmpty()) {
+ return;
+ }
+
+ nsAutoString nativeString(mString);
+ // Don't expose CRLF nor CR to web contents, instead, use LF.
+ mString.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
+ mString.ReplaceSubstring(u"\r"_ns, u"\n"_ns);
+
+ // If the length isn't changed, we don't need to adjust any offset and length
+ // of mClauses nor mCaret.
+ if (nativeString.Length() == mString.Length()) {
+ return;
+ }
+
+ if (mClauses) {
+ for (TextRange& clause : *mClauses) {
+ AdjustRange(clause, nativeString);
+ }
+ }
+ if (mCaret.mRangeType == TextRangeType::eCaret) {
+ AdjustRange(mCaret, nativeString);
+ }
+}
+
+// static
+void TextEventDispatcher::PendingComposition::AdjustRange(
+ TextRange& aRange, const nsAString& aNativeString) {
+ TextRange nativeRange = aRange;
+ // XXX Following code wastes runtime cost because this causes computing
+ // mStartOffset for each clause from the start of composition string.
+ // If we'd make TextRange have only its length, we don't need to do
+ // this. However, this must not be so serious problem because
+ // composition string is usually short and separated as a few clauses.
+ if (nativeRange.mStartOffset > 0) {
+ nsAutoString preText(Substring(aNativeString, 0, nativeRange.mStartOffset));
+ preText.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
+ aRange.mStartOffset = preText.Length();
+ }
+ if (nativeRange.Length() == 0) {
+ aRange.mEndOffset = aRange.mStartOffset;
+ } else {
+ nsAutoString clause(Substring(aNativeString, nativeRange.mStartOffset,
+ nativeRange.Length()));
+ clause.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
+ aRange.mEndOffset = aRange.mStartOffset + clause.Length();
+ }
+}
+
+nsresult TextEventDispatcher::PendingComposition::Flush(
+ TextEventDispatcher* aDispatcher, nsEventStatus& aStatus,
+ const WidgetEventTime* aEventTime) {
+ aStatus = nsEventStatus_eIgnore;
+
+ nsresult rv = aDispatcher->GetState();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mClauses && !mClauses->IsEmpty() &&
+ mClauses->LastElement().mEndOffset != mString.Length()) {
+ NS_WARNING(
+ "Sum of length of the all clauses must be same as the string "
+ "length");
+ Clear();
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (mCaret.mRangeType == TextRangeType::eCaret) {
+ if (mCaret.mEndOffset > mString.Length()) {
+ NS_WARNING("Caret position is out of the composition string");
+ Clear();
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ EnsureClauseArray();
+ mClauses->AppendElement(mCaret);
+ }
+
+ // If the composition string is set without Set(), we need to replace native
+ // line breakers in the composition string with XP line breaker.
+ if (!mReplacedNativeLineBreakers) {
+ ReplaceNativeLineBreakers();
+ }
+
+ RefPtr<TextEventDispatcher> kungFuDeathGrip(aDispatcher);
+ nsCOMPtr<nsIWidget> widget(aDispatcher->mWidget);
+ WidgetCompositionEvent compChangeEvent(true, eCompositionChange, widget);
+ aDispatcher->InitEvent(compChangeEvent);
+ if (aEventTime) {
+ compChangeEvent.AssignEventTime(*aEventTime);
+ }
+ compChangeEvent.mData = mString;
+ // If mString comes from TextInputProcessor, it may be void, but editor
+ // requires non-void string even when it's empty.
+ compChangeEvent.mData.SetIsVoid(false);
+ if (mClauses) {
+ MOZ_ASSERT(!mClauses->IsEmpty(),
+ "mClauses must be non-empty array when it's not nullptr");
+ compChangeEvent.mRanges = mClauses;
+ }
+
+ // While this method dispatches a composition event, some other event handler
+ // cause more clauses to be added. So, we should clear pending composition
+ // before dispatching the event.
+ Clear();
+
+ rv = aDispatcher->StartCompositionAutomaticallyIfNecessary(aStatus,
+ aEventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (aStatus == nsEventStatus_eConsumeNoDefault) {
+ return NS_OK;
+ }
+ rv = aDispatcher->DispatchEvent(widget, compChangeEvent, aStatus);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/TextEventDispatcher.h b/widget/TextEventDispatcher.h
new file mode 100644
index 0000000000..557811fd1e
--- /dev/null
+++ b/widget/TextEventDispatcher.h
@@ -0,0 +1,534 @@
+/* -*- 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_textcompositionsynthesizer_h_
+#define mozilla_textcompositionsynthesizer_h_
+
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "mozilla/TextRange.h"
+#include "mozilla/widget/IMEData.h"
+
+class nsIWidget;
+
+namespace mozilla {
+namespace widget {
+
+class PuppetWidget;
+
+/**
+ * TextEventDispatcher is a helper class for dispatching widget events defined
+ * in TextEvents.h. Currently, this is a helper for dispatching
+ * WidgetCompositionEvent and WidgetKeyboardEvent. This manages the behavior
+ * of them for conforming to DOM Level 3 Events.
+ * An instance of this class is created by nsIWidget instance and owned by it.
+ * This is typically created only by the top level widgets because only they
+ * handle IME.
+ */
+
+class TextEventDispatcher final {
+ ~TextEventDispatcher() = default;
+
+ NS_INLINE_DECL_REFCOUNTING(TextEventDispatcher)
+
+ public:
+ explicit TextEventDispatcher(nsIWidget* aWidget);
+
+ /**
+ * Initializes the instance for IME or automated test. Either IME or tests
+ * need to call one of them before starting composition. If they return
+ * NS_ERROR_ALREADY_INITIALIZED, it means that the listener already listens
+ * notifications from TextEventDispatcher for same purpose (for IME or tests).
+ * If this returns another error, the caller shouldn't keep starting
+ * composition.
+ *
+ * @param aListener Specify the listener to listen notifications and
+ * requests. This must not be null.
+ * NOTE: aListener is stored as weak reference in
+ * TextEventDispatcher. See mListener
+ * definition below.
+ */
+ nsresult BeginInputTransaction(TextEventDispatcherListener* aListener);
+ nsresult BeginTestInputTransaction(TextEventDispatcherListener* aListener,
+ bool aIsAPZAware);
+ nsresult BeginNativeInputTransaction();
+
+ /**
+ * BeginInputTransactionFor() should be used when aPuppetWidget dispatches
+ * a composition or keyboard event coming from its parent process.
+ */
+ nsresult BeginInputTransactionFor(const WidgetGUIEvent* aEvent,
+ PuppetWidget* aPuppetWidget);
+
+ /**
+ * EndInputTransaction() should be called when the listener stops using
+ * the TextEventDispatcher.
+ *
+ * @param aListener The listener using the TextEventDispatcher instance.
+ */
+ void EndInputTransaction(TextEventDispatcherListener* aListener);
+
+ /**
+ * OnDestroyWidget() is called when mWidget is being destroyed.
+ */
+ void OnDestroyWidget();
+
+ nsIWidget* GetWidget() const { return mWidget; }
+
+ const IMENotificationRequests& IMENotificationRequestsRef() const {
+ return mIMENotificationRequests;
+ }
+
+ /**
+ * OnWidgetChangeIMENotificationRequests() is called when aWidget's
+ * IMENotificationRequest is maybe modified by unusual path. E.g.,
+ * modified in an async path.
+ */
+ void OnWidgetChangeIMENotificationRequests(nsIWidget* aWidget) {
+ MOZ_ASSERT(aWidget);
+ if (mWidget == aWidget) {
+ UpdateNotificationRequests();
+ }
+ }
+
+ /**
+ * GetState() returns current state of this class.
+ *
+ * @return NS_OK: Fine to compose text.
+ * NS_ERROR_NOT_INITIALIZED: BeginInputTransaction() or
+ * BeginInputTransactionForTests()
+ * should be called.
+ * NS_ERROR_NOT_AVAILABLE: The widget isn't available for
+ * composition.
+ */
+ nsresult GetState() const;
+
+ /**
+ * IsComposing() returns true after calling StartComposition() and before
+ * calling CommitComposition(). In other words, native IME has composition
+ * when this returns true.
+ */
+ bool IsComposing() const { return mIsComposing; }
+
+ /**
+ * IsHandlingComposition() returns true after calling StartComposition() and
+ * content has not handled eCompositionCommit(AsIs) event. In other words,
+ * our content has composition when this returns true.
+ */
+ bool IsHandlingComposition() const { return mIsHandlingComposition; }
+
+ /**
+ * IsInNativeInputTransaction() returns true if native IME handler began a
+ * transaction and it's not finished yet.
+ */
+ bool IsInNativeInputTransaction() const {
+ return mInputTransactionType == eNativeInputTransaction;
+ }
+
+ /**
+ * IsDispatchingEvent() returns true while this instance dispatching an event.
+ */
+ bool IsDispatchingEvent() const { return mDispatchingEvent > 0; }
+
+ /**
+ * GetPseudoIMEContext() returns pseudo native IME context if there is an
+ * input transaction whose type is not for native event handler.
+ * Otherwise, returns nullptr.
+ */
+ void* GetPseudoIMEContext() const {
+ if (mInputTransactionType == eNoInputTransaction ||
+ mInputTransactionType == eNativeInputTransaction) {
+ return nullptr;
+ }
+ return const_cast<TextEventDispatcher*>(this);
+ }
+
+ /**
+ * StartComposition() starts composition explicitly.
+ *
+ * @param aEventTime If this is not nullptr, WidgetCompositionEvent will
+ * be initialized with this. Otherwise, initialized
+ * with the time at initializing.
+ */
+ nsresult StartComposition(nsEventStatus& aStatus,
+ const WidgetEventTime* aEventTime = nullptr);
+
+ /**
+ * CommitComposition() commits composition.
+ *
+ * @param aCommitString If this is null, commits with the last composition
+ * string. Otherwise, commits the composition with
+ * this value.
+ * @param aEventTime If this is not nullptr, WidgetCompositionEvent will
+ * be initialized with this. Otherwise, initialized
+ * with the time at initializing.
+ */
+ nsresult CommitComposition(nsEventStatus& aStatus,
+ const nsAString* aCommitString = nullptr,
+ const WidgetEventTime* aEventTime = nullptr);
+
+ /**
+ * SetPendingCompositionString() sets new composition string which will be
+ * dispatched with eCompositionChange event by calling Flush().
+ *
+ * @param aString New composition string.
+ */
+ nsresult SetPendingCompositionString(const nsAString& aString) {
+ return mPendingComposition.SetString(aString);
+ }
+
+ /**
+ * AppendClauseToPendingComposition() appends a clause information to
+ * the pending composition string.
+ *
+ * @param aLength Length of the clause.
+ * @param aTextRangeType One of TextRangeType::eRawClause,
+ * TextRangeType::eSelectedRawClause,
+ * TextRangeType::eConvertedClause or
+ * TextRangeType::eSelectedClause.
+ */
+ nsresult AppendClauseToPendingComposition(uint32_t aLength,
+ TextRangeType aTextRangeType) {
+ return mPendingComposition.AppendClause(aLength, aTextRangeType);
+ }
+
+ /**
+ * SetCaretInPendingComposition() sets caret position in the pending
+ * composition string and its length. This is optional. If IME doesn't
+ * want to show caret, it shouldn't need to call this.
+ *
+ * @param aOffset Offset of the caret in the pending composition
+ * string. This should not be larger than the length
+ * of the pending composition string.
+ * @param aLength Caret width. If this is 0, caret will be collapsed.
+ * Note that Gecko doesn't supported wide caret yet,
+ * therefore, this is ignored for now.
+ */
+ nsresult SetCaretInPendingComposition(uint32_t aOffset, uint32_t aLength) {
+ return mPendingComposition.SetCaret(aOffset, aLength);
+ }
+
+ /**
+ * SetPendingComposition() is useful if native IME handler already creates
+ * array of clauses and/or caret information.
+ *
+ * @param aString Composition string. This may include native line
+ * breakers since they will be replaced with XP line
+ * breakers automatically.
+ * @param aRanges This should include the ranges of clauses and/or
+ * a range of caret. Note that this method allows
+ * some ranges overlap each other and the range order
+ * is not from start to end.
+ */
+ nsresult SetPendingComposition(const nsAString& aString,
+ const TextRangeArray* aRanges) {
+ return mPendingComposition.Set(aString, aRanges);
+ }
+
+ /**
+ * FlushPendingComposition() sends the pending composition string
+ * to the widget of the store DOM window. Before calling this, IME needs to
+ * set pending composition string with SetPendingCompositionString(),
+ * AppendClauseToPendingComposition() and/or
+ * SetCaretInPendingComposition().
+ *
+ * @param aEventTime If this is not nullptr, WidgetCompositionEvent will
+ * be initialized with this. Otherwise, initialized
+ * with the time at initializing.
+ */
+ nsresult FlushPendingComposition(
+ nsEventStatus& aStatus, const WidgetEventTime* aEventTime = nullptr) {
+ return mPendingComposition.Flush(this, aStatus, aEventTime);
+ }
+
+ /**
+ * ClearPendingComposition() makes this instance forget pending composition.
+ */
+ void ClearPendingComposition() { mPendingComposition.Clear(); }
+
+ /**
+ * GetPendingCompositionClauses() returns text ranges which was appended by
+ * AppendClauseToPendingComposition() or SetPendingComposition().
+ */
+ const TextRangeArray* GetPendingCompositionClauses() const {
+ return mPendingComposition.GetClauses();
+ }
+
+ /**
+ * @see nsIWidget::NotifyIME()
+ */
+ nsresult NotifyIME(const IMENotification& aIMENotification);
+
+ /**
+ * DispatchKeyboardEvent() maybe dispatches aKeyboardEvent.
+ *
+ * @param aMessage Must be eKeyDown or eKeyUp.
+ * Use MaybeDispatchKeypressEvents() for dispatching
+ * eKeyPress.
+ * @param aKeyboardEvent A keyboard event.
+ * @param aStatus If dispatching event should be marked as consumed,
+ * set nsEventStatus_eConsumeNoDefault. Otherwise,
+ * set nsEventStatus_eIgnore. After dispatching
+ * a event and it's consumed this returns
+ * nsEventStatus_eConsumeNoDefault.
+ * @param aData Calling this method may cause calling
+ * WillDispatchKeyboardEvent() of the listener.
+ * aData will be set to its argument.
+ * @return true if an event is dispatched. Otherwise, false.
+ */
+ bool DispatchKeyboardEvent(EventMessage aMessage,
+ const WidgetKeyboardEvent& aKeyboardEvent,
+ nsEventStatus& aStatus, void* aData = nullptr);
+
+ /**
+ * MaybeDispatchKeypressEvents() maybe dispatches a keypress event which is
+ * generated from aKeydownEvent.
+ *
+ * @param aKeyboardEvent A keyboard event.
+ * @param aStatus Sets the result when the caller dispatches
+ * aKeyboardEvent. Note that if the value is
+ * nsEventStatus_eConsumeNoDefault, this does NOT
+ * dispatch keypress events.
+ * When this method dispatches one or more keypress
+ * events and one of them is consumed, this returns
+ * nsEventStatus_eConsumeNoDefault.
+ * @param aData Calling this method may cause calling
+ * WillDispatchKeyboardEvent() of the listener.
+ * aData will be set to its argument.
+ * @param aNeedsCallback Set true when caller needs to initialize each
+ * eKeyPress event immediately before dispatch.
+ * Then, WillDispatchKeyboardEvent() is always called.
+ * @return true if one or more events are dispatched.
+ * Otherwise, false.
+ */
+ bool MaybeDispatchKeypressEvents(const WidgetKeyboardEvent& aKeyboardEvent,
+ nsEventStatus& aStatus,
+ void* aData = nullptr,
+ bool aNeedsCallback = false);
+
+ private:
+ // mWidget is owner of the instance. When this is created, this is set.
+ // And when mWidget is released, this is cleared by OnDestroyWidget().
+ // Note that mWidget may be destroyed already (i.e., mWidget->Destroyed() may
+ // return true).
+ nsIWidget* mWidget;
+ // mListener is a weak reference to TextEventDispatcherListener. That might
+ // be referred by JS. Therefore, the listener might be difficult to release
+ // itself if this is a strong reference. Additionally, it's difficult to
+ // check if a method to uninstall the listener is called by valid instance.
+ // So, using weak reference is the best way in this case.
+ nsWeakPtr mListener;
+ // mIMENotificationRequests should store current IME's notification requests.
+ // So, this may be invalid when IME doesn't have focus.
+ IMENotificationRequests mIMENotificationRequests;
+
+ // mPendingComposition stores new composition string temporarily.
+ // These values will be used for dispatching eCompositionChange event
+ // in Flush(). When Flush() is called, the members will be cleared
+ // automatically.
+ class PendingComposition {
+ public:
+ PendingComposition();
+ nsresult SetString(const nsAString& aString);
+ nsresult AppendClause(uint32_t aLength, TextRangeType aTextRangeType);
+ nsresult SetCaret(uint32_t aOffset, uint32_t aLength);
+ nsresult Set(const nsAString& aString, const TextRangeArray* aRanges);
+ nsresult Flush(TextEventDispatcher* aDispatcher, nsEventStatus& aStatus,
+ const WidgetEventTime* aEventTime);
+ const TextRangeArray* GetClauses() const { return mClauses; }
+ void Clear();
+
+ private:
+ nsString mString;
+ RefPtr<TextRangeArray> mClauses;
+ TextRange mCaret;
+ bool mReplacedNativeLineBreakers;
+
+ void EnsureClauseArray();
+
+ /**
+ * ReplaceNativeLineBreakers() replaces "\r\n" and "\r" to "\n" and adjust
+ * each clause information and the caret information.
+ */
+ void ReplaceNativeLineBreakers();
+
+ /**
+ * AdjustRange() adjusts aRange as in the string with XP line breakers.
+ *
+ * @param aRange The reference to a range in aNativeString.
+ * This will be modified.
+ * @param aNativeString The string with native line breakers.
+ * This may include "\r\n" and/or "\r".
+ */
+ static void AdjustRange(TextRange& aRange, const nsAString& aNativeString);
+ };
+ PendingComposition mPendingComposition;
+
+ // While dispatching an event, this is incremented.
+ uint16_t mDispatchingEvent;
+
+ enum InputTransactionType : uint8_t {
+ // No input transaction has been started.
+ eNoInputTransaction,
+ // Input transaction for native IME or keyboard event handler. Note that
+ // keyboard events may be dispatched via parent process if there is.
+ // In remote processes, this is also used when events come from the parent
+ // process and are not for tests because we cannot distinguish if
+ // TextEventDispatcher has which type of transaction when it dispatches
+ // (eNativeInputTransaction or eSameProcessSyncInputTransaction).
+ eNativeInputTransaction,
+ // Input transaction for automated tests which are APZ-aware. Note that
+ // keyboard events may be dispatched via parent process if there is.
+ eAsyncTestInputTransaction,
+ // Input transaction for automated tests which assume events are fired
+ // synchronously. I.e., keyboard events are always dispatched in the
+ // current process.
+ // In remote processes, this is also used when events come from the parent
+ // process and are not dispatched by the instance itself for APZ-aware
+ // tests because this instance won't dispatch the events via the parent
+ // process again.
+ eSameProcessSyncTestInputTransaction,
+ // Input transaction for others (currently, only FuzzingFunctions).
+ // Events are fired synchronously in the process.
+ // XXX Should we make this async for testing default action handlers in
+ // the main process?
+ eSameProcessSyncInputTransaction
+ };
+
+ InputTransactionType mInputTransactionType;
+
+ bool IsForTests() const {
+ return mInputTransactionType == eAsyncTestInputTransaction ||
+ mInputTransactionType == eSameProcessSyncTestInputTransaction;
+ }
+
+ // ShouldSendInputEventToAPZ() returns true when WidgetInputEvent should
+ // be dispatched via its parent process (if there is) for APZ. Otherwise,
+ // when the input transaction is for IME of B2G or automated tests which
+ // isn't APZ-aware, WidgetInputEvent should be dispatched form current
+ // process directly.
+ bool ShouldSendInputEventToAPZ() const {
+ switch (mInputTransactionType) {
+ case eNativeInputTransaction:
+ case eAsyncTestInputTransaction:
+ return true;
+ case eSameProcessSyncTestInputTransaction:
+ case eSameProcessSyncInputTransaction:
+ return false;
+ case eNoInputTransaction:
+ NS_WARNING(
+ "Why does the caller need to dispatch an event when "
+ "there is no input transaction?");
+ return true;
+ default:
+ MOZ_CRASH("Define the behavior of new InputTransactionType");
+ }
+ }
+
+ // See IsComposing().
+ bool mIsComposing;
+
+ // See IsHandlingComposition().
+ bool mIsHandlingComposition;
+
+ // true while NOTIFY_IME_OF_FOCUS is received but NOTIFY_IME_OF_BLUR has not
+ // received yet. Otherwise, false.
+ bool mHasFocus;
+
+ nsresult BeginInputTransactionInternal(TextEventDispatcherListener* aListener,
+ InputTransactionType aType);
+
+ /**
+ * InitEvent() initializes aEvent. This must be called before dispatching
+ * the event.
+ */
+ void InitEvent(WidgetGUIEvent& aEvent) const;
+
+ /**
+ * DispatchEvent() dispatches aEvent on aWidget.
+ */
+ nsresult DispatchEvent(nsIWidget* aWidget, WidgetGUIEvent& aEvent,
+ nsEventStatus& aStatus);
+
+ /**
+ * DispatchInputEvent() dispatches aEvent on aWidget.
+ */
+ nsresult DispatchInputEvent(nsIWidget* aWidget, WidgetInputEvent& aEvent,
+ nsEventStatus& aStatus);
+
+ /**
+ * StartCompositionAutomaticallyIfNecessary() starts composition if it hasn't
+ * been started it yet.
+ *
+ * @param aStatus If it succeeded to start composition normally, this
+ * returns nsEventStatus_eIgnore. Otherwise, e.g.,
+ * the composition is canceled during dispatching
+ * compositionstart event, this returns
+ * nsEventStatus_eConsumeNoDefault. In this case,
+ * the caller shouldn't keep doing its job.
+ * @param aEventTime If this is not nullptr, WidgetCompositionEvent will
+ * be initialized with this. Otherwise, initialized
+ * with the time at initializing.
+ * @return Only when something unexpected occurs, this returns
+ * an error. Otherwise, returns NS_OK even if aStatus
+ * is nsEventStatus_eConsumeNoDefault.
+ */
+ nsresult StartCompositionAutomaticallyIfNecessary(
+ nsEventStatus& aStatus, const WidgetEventTime* aEventTime);
+
+ /**
+ * DispatchKeyboardEventInternal() maybe dispatches aKeyboardEvent.
+ *
+ * @param aMessage Must be eKeyDown, eKeyUp or eKeyPress.
+ * @param aKeyboardEvent A keyboard event. If aMessage is eKeyPress and
+ * the event is for second or later character, its
+ * mKeyValue should be empty string.
+ * @param aStatus If dispatching event should be marked as consumed,
+ * set nsEventStatus_eConsumeNoDefault. Otherwise,
+ * set nsEventStatus_eIgnore. After dispatching
+ * a event and it's consumed this returns
+ * nsEventStatus_eConsumeNoDefault.
+ * @param aData Calling this method may cause calling
+ * WillDispatchKeyboardEvent() of the listener.
+ * aData will be set to its argument.
+ * @param aIndexOfKeypress This must be 0 if aMessage isn't eKeyPress or
+ * aKeyboard.mKeyNameIndex isn't
+ * KEY_NAME_INDEX_USE_STRING. Otherwise, i.e.,
+ * when an eKeyPress event causes inputting
+ * text, this must be between 0 and
+ * mKeyValue.Length() - 1 since keypress events
+ * sending only one character per event.
+ * @param aNeedsCallback Set true when caller needs to initialize each
+ * eKeyPress event immediately before dispatch.
+ * Then, WillDispatchKeyboardEvent() is always called.
+ * @return true if an event is dispatched. Otherwise, false.
+ */
+ bool DispatchKeyboardEventInternal(EventMessage aMessage,
+ const WidgetKeyboardEvent& aKeyboardEvent,
+ nsEventStatus& aStatus, void* aData,
+ uint32_t aIndexOfKeypress = 0,
+ bool aNeedsCallback = false);
+
+ /**
+ * ClearNotificationRequests() clears mIMENotificationRequests.
+ */
+ void ClearNotificationRequests();
+
+ /**
+ * UpdateNotificationRequests() updates mIMENotificationRequests with
+ * current state. If the instance doesn't have focus, this clears
+ * mIMENotificationRequests. Otherwise, updates it with both requests of
+ * current listener and native listener.
+ */
+ void UpdateNotificationRequests();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef mozilla_widget_textcompositionsynthesizer_h_
diff --git a/widget/TextEventDispatcherListener.h b/widget/TextEventDispatcherListener.h
new file mode 100644
index 0000000000..cd846c6d38
--- /dev/null
+++ b/widget/TextEventDispatcherListener.h
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_textinputdispatcherlistener_h_
+#define mozilla_textinputdispatcherlistener_h_
+
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace widget {
+
+class TextEventDispatcher;
+struct IMENotification;
+struct IMENotificationRequests;
+
+#define NS_TEXT_INPUT_PROXY_LISTENER_IID \
+ {0xf2226f55, \
+ 0x6ddb, \
+ 0x40d5, \
+ {0x8a, 0x24, 0xce, 0x4d, 0x5b, 0x38, 0x15, 0xf0}};
+
+class TextEventDispatcherListener : public nsSupportsWeakReference {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TEXT_INPUT_PROXY_LISTENER_IID)
+
+ /**
+ * NotifyIME() is called by TextEventDispatcher::NotifyIME(). This is a
+ * notification or request to IME. See document of nsIWidget::NotifyIME()
+ * for the detail.
+ */
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) = 0;
+
+ /**
+ * Returns preference for which IME notification are received by NotifyIME().
+ */
+ NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() = 0;
+
+ /**
+ * OnRemovedFrom() is called when the TextEventDispatcher stops working and
+ * is releasing the listener.
+ */
+ NS_IMETHOD_(void)
+ OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) = 0;
+
+ /**
+ * WillDispatchKeyboardEvent() may be called immediately before
+ * TextEventDispatcher dispatching a keyboard event. This is called only
+ * during calling TextEventDispatcher::DispatchKeyboardEvent() or
+ * TextEventDispatcher::MaybeDispatchKeypressEvents(). But this may not
+ * be called if TextEventDispatcher thinks that the keyboard event doesn't
+ * need alternative char codes.
+ *
+ * This method can overwrite any members of aKeyboardEvent which is already
+ * initialized by TextEventDispatcher. If it's necessary, this method should
+ * overwrite the charCode when Control key is pressed. TextEventDispatcher
+ * computes charCode from mKeyValue. However, when Control key is pressed,
+ * charCode should be an ASCII char. In such case, this method needs to
+ * overwrite it properly.
+ *
+ * @param aTextEventDispatcher Pointer to the caller.
+ * @param aKeyboardEvent The event trying to dispatch.
+ * This is already initialized, but if it's
+ * necessary, this method should overwrite the
+ * members and set alternative char codes.
+ * @param aIndexOfKeypress When aKeyboardEvent is eKeyPress event,
+ * it may be a sequence of keypress events
+ * if the key causes multiple characters.
+ * In such case, this indicates the index from
+ * first keypress event.
+ * If aKeyboardEvent is the first eKeyPress or
+ * other events, this value is 0.
+ * @param aData The pointer which was specified at calling
+ * the method of TextEventDispatcher.
+ * For example, if you do:
+ * |TextEventDispatcher->DispatchKeyboardEvent(
+ * eKeyDown, event, status, this);|
+ * Then, aData of this method becomes |this|.
+ * Finally, you can use it like:
+ * |static_cast<NativeEventHandler*>(aData)|
+ */
+ NS_IMETHOD_(void)
+ WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress, void* aData) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(TextEventDispatcherListener,
+ NS_TEXT_INPUT_PROXY_LISTENER_IID)
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef mozilla_textinputdispatcherlistener_h_
diff --git a/widget/TextEvents.h b/widget/TextEvents.h
new file mode 100644
index 0000000000..f4cffc1f6f
--- /dev/null
+++ b/widget/TextEvents.h
@@ -0,0 +1,1390 @@
+/* -*- 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_TextEvents_h__
+#define mozilla_TextEvents_h__
+
+#include <stdint.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/EventForwards.h" // for KeyNameIndex, temporarily
+#include "mozilla/FontRange.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/TextRange.h"
+#include "mozilla/WritingModes.h"
+#include "mozilla/dom/DataTransfer.h"
+#include "mozilla/dom/KeyboardEventBinding.h"
+#include "mozilla/dom/StaticRange.h"
+#include "mozilla/widget/IMEData.h"
+#include "nsCOMPtr.h"
+#include "nsISelectionListener.h"
+#include "nsITransferable.h"
+#include "nsRect.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsStringHashKey;
+template <class, class>
+class nsDataHashtable;
+
+/******************************************************************************
+ * virtual keycode values
+ ******************************************************************************/
+
+enum {
+#define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) NS_##aDOMKeyName = aDOMKeyCode,
+#include "mozilla/VirtualKeyCodeList.h"
+#undef NS_DEFINE_VK
+ NS_VK_UNKNOWN = 0xFF
+};
+
+namespace mozilla {
+
+enum : uint32_t {
+ eKeyLocationStandard = dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_STANDARD,
+ eKeyLocationLeft = dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_LEFT,
+ eKeyLocationRight = dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_RIGHT,
+ eKeyLocationNumpad = dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_NUMPAD
+};
+
+const nsCString GetDOMKeyCodeName(uint32_t aKeyCode);
+
+namespace dom {
+class PBrowserParent;
+class PBrowserChild;
+} // namespace dom
+namespace plugins {
+class PPluginInstanceChild;
+} // namespace plugins
+
+enum class AccessKeyType {
+ // Handle access key for chrome.
+ eChrome,
+ // Handle access key for content.
+ eContent,
+ // Don't handle access key.
+ eNone
+};
+
+/******************************************************************************
+ * mozilla::AlternativeCharCode
+ *
+ * This stores alternative charCode values of a key event with some modifiers.
+ * The stored values proper for testing shortcut key or access key.
+ ******************************************************************************/
+
+struct AlternativeCharCode {
+ AlternativeCharCode() : mUnshiftedCharCode(0), mShiftedCharCode(0) {}
+ AlternativeCharCode(uint32_t aUnshiftedCharCode, uint32_t aShiftedCharCode)
+ : mUnshiftedCharCode(aUnshiftedCharCode),
+ mShiftedCharCode(aShiftedCharCode) {}
+ uint32_t mUnshiftedCharCode;
+ uint32_t mShiftedCharCode;
+};
+
+/******************************************************************************
+ * mozilla::ShortcutKeyCandidate
+ *
+ * This stores a candidate of shortcut key combination.
+ ******************************************************************************/
+
+struct ShortcutKeyCandidate {
+ ShortcutKeyCandidate() : mCharCode(0), mIgnoreShift(0) {}
+ ShortcutKeyCandidate(uint32_t aCharCode, bool aIgnoreShift)
+ : mCharCode(aCharCode), mIgnoreShift(aIgnoreShift) {}
+ // The mCharCode value which must match keyboard shortcut definition.
+ uint32_t mCharCode;
+ // true if Shift state can be ignored. Otherwise, Shift key state must
+ // match keyboard shortcut definition.
+ bool mIgnoreShift;
+};
+
+/******************************************************************************
+ * mozilla::IgnoreModifierState
+ *
+ * This stores flags for modifiers that should be ignored when matching
+ * XBL handlers.
+ ******************************************************************************/
+
+struct IgnoreModifierState {
+ // When mShift is true, Shift key state will be ignored.
+ bool mShift;
+ // When mOS is true, OS key state will be ignored.
+ bool mOS;
+
+ IgnoreModifierState() : mShift(false), mOS(false) {}
+};
+
+/******************************************************************************
+ * mozilla::WidgetKeyboardEvent
+ ******************************************************************************/
+
+class WidgetKeyboardEvent : public WidgetInputEvent {
+ private:
+ friend class dom::PBrowserParent;
+ friend class dom::PBrowserChild;
+ friend struct IPC::ParamTraits<WidgetKeyboardEvent>;
+
+ protected:
+ WidgetKeyboardEvent()
+ : mNativeKeyEvent(nullptr),
+ mKeyCode(0),
+ mCharCode(0),
+ mPseudoCharCode(0),
+ mLocation(eKeyLocationStandard),
+ mUniqueId(0),
+ mKeyNameIndex(KEY_NAME_INDEX_Unidentified),
+ mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN),
+ mIsRepeat(false),
+ mIsComposing(false),
+ mIsSynthesizedByTIP(false),
+ mMaybeSkippableInRemoteProcess(true),
+ mUseLegacyKeyCodeAndCharCodeValues(false),
+ mEditCommandsForSingleLineEditorInitialized(false),
+ mEditCommandsForMultiLineEditorInitialized(false),
+ mEditCommandsForRichTextEditorInitialized(false) {}
+
+ public:
+ virtual WidgetKeyboardEvent* AsKeyboardEvent() override { return this; }
+
+ WidgetKeyboardEvent(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget,
+ EventClassID aEventClassID = eKeyboardEventClass)
+ : WidgetInputEvent(aIsTrusted, aMessage, aWidget, aEventClassID),
+ mNativeKeyEvent(nullptr),
+ mKeyCode(0),
+ mCharCode(0),
+ mPseudoCharCode(0),
+ mLocation(eKeyLocationStandard),
+ mUniqueId(0),
+ mKeyNameIndex(KEY_NAME_INDEX_Unidentified),
+ mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN),
+ mIsRepeat(false),
+ mIsComposing(false),
+ mIsSynthesizedByTIP(false),
+ mMaybeSkippableInRemoteProcess(true),
+ mUseLegacyKeyCodeAndCharCodeValues(false),
+ mEditCommandsForSingleLineEditorInitialized(false),
+ mEditCommandsForMultiLineEditorInitialized(false),
+ mEditCommandsForRichTextEditorInitialized(false) {
+ // If this is a keyboard event on a plugin, it shouldn't fired on content.
+ if (IsKeyEventOnPlugin()) {
+ mFlags.mOnlySystemGroupDispatchInContent = true;
+ StopCrossProcessForwarding();
+ }
+ }
+
+ static bool IsKeyDownOrKeyDownOnPlugin(EventMessage aMessage) {
+ return aMessage == eKeyDown || aMessage == eKeyDownOnPlugin;
+ }
+ bool IsKeyDownOrKeyDownOnPlugin() const {
+ return IsKeyDownOrKeyDownOnPlugin(mMessage);
+ }
+ static bool IsKeyUpOrKeyUpOnPlugin(EventMessage aMessage) {
+ return aMessage == eKeyUp || aMessage == eKeyUpOnPlugin;
+ }
+ bool IsKeyUpOrKeyUpOnPlugin() const {
+ return IsKeyUpOrKeyUpOnPlugin(mMessage);
+ }
+ static bool IsKeyEventOnPlugin(EventMessage aMessage) {
+ return aMessage == eKeyDownOnPlugin || aMessage == eKeyUpOnPlugin;
+ }
+ bool IsKeyEventOnPlugin() const { return IsKeyEventOnPlugin(mMessage); }
+
+ // IsInputtingText() and IsInputtingLineBreak() are used to check if
+ // it should cause eKeyPress events even on web content.
+ // UI Events defines that "keypress" event should be fired "if and only if
+ // that key normally produces a character value".
+ // <https://www.w3.org/TR/uievents/#event-type-keypress>
+ // Additionally, for backward compatiblity with all existing browsers,
+ // there is a spec issue for Enter key press.
+ // <https://github.com/w3c/uievents/issues/183>
+ bool IsInputtingText() const {
+ // NOTE: On some keyboard layout, some characters are inputted with Control
+ // key or Alt key, but at that time, widget clears the modifier flag
+ // from eKeyPress event because our TextEditor won't handle eKeyPress
+ // events as inputting text (bug 1346832).
+ // NOTE: There are some complicated issues of our traditional behavior.
+ // -- On Windows, KeyboardLayout::WillDispatchKeyboardEvent() clears
+ // MODIFIER_ALT and MODIFIER_CONTROL of eKeyPress event if it
+ // should be treated as inputting a character because AltGr is
+ // represented with both Alt key and Ctrl key are pressed, and
+ // some keyboard layouts may produces a character with Ctrl key.
+ // -- On Linux, KeymapWrapper doesn't have this hack since perhaps,
+ // we don't have any bug reports that user cannot input proper
+ // character with Alt and/or Ctrl key.
+ // -- On macOS, IMEInputHandler::WillDispatchKeyboardEvent() clears
+ // MODIFIER_ALT and MDOFIEIR_CONTROL of eKeyPress event only when
+ // TextInputHandler::InsertText() has been called for the event.
+ // I.e., they are cleared only when an editor has focus (even if IME
+ // is disabled in password field or by |ime-mode: disabled;|) because
+ // TextInputHandler::InsertText() is called while
+ // TextInputHandler::HandleKeyDownEvent() calls interpretKeyEvents:
+ // to notify text input processor of Cocoa (including IME). In other
+ // words, when we need to disable IME completey when no editor has
+ // focus, we cannot call interpretKeyEvents:. So,
+ // TextInputHandler::InsertText() won't be called when no editor has
+ // focus so that neither MODIFIER_ALT nor MODIFIER_CONTROL is
+ // cleared. So, fortunately, altKey and ctrlKey values of "keypress"
+ // events are same as the other browsers only when no editor has
+ // focus.
+ // NOTE: As mentioned above, for compatibility with the other browsers on
+ // macOS, we should keep MODIFIER_ALT and MODIFIER_CONTROL flags of
+ // eKeyPress events when no editor has focus. However, Alt key,
+ // labeled "option" on keyboard for Mac, is AltGraph key on the other
+ // platforms. So, even if MODIFIER_ALT is set, we need to dispatch
+ // eKeyPress event even on web content unless mCharCode is 0.
+ // Therefore, we need to ignore MODIFIER_ALT flag here only on macOS.
+ return mMessage == eKeyPress && mCharCode &&
+ !(mModifiers & (
+#ifndef XP_MACOSX
+ // So, ignore MODIFIER_ALT only on macOS since
+ // option key is used as AltGraph key on macOS.
+ MODIFIER_ALT |
+#endif // #ifndef XP_MAXOSX
+ MODIFIER_CONTROL | MODIFIER_META | MODIFIER_OS));
+ }
+
+ bool IsInputtingLineBreak() const {
+ return mMessage == eKeyPress && mKeyNameIndex == KEY_NAME_INDEX_Enter &&
+ !(mModifiers &
+ (MODIFIER_ALT | MODIFIER_CONTROL | MODIFIER_META | MODIFIER_OS));
+ }
+
+ /**
+ * ShouldKeyPressEventBeFiredOnContent() should be called only when the
+ * instance is eKeyPress event. This returns true when the eKeyPress
+ * event should be fired even on content in the default event group.
+ */
+ bool ShouldKeyPressEventBeFiredOnContent() const {
+ MOZ_DIAGNOSTIC_ASSERT(mMessage == eKeyPress);
+ if (IsInputtingText() || IsInputtingLineBreak()) {
+ return true;
+ }
+ // Ctrl + Enter won't cause actual input in our editor.
+ // However, the other browsers fire keypress event in any platforms.
+ // So, for compatibility with them, we should fire keypress event for
+ // Ctrl + Enter too.
+ return mMessage == eKeyPress && mKeyNameIndex == KEY_NAME_INDEX_Enter &&
+ !(mModifiers &
+ (MODIFIER_ALT | MODIFIER_META | MODIFIER_OS | MODIFIER_SHIFT));
+ }
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eKeyboardEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ WidgetKeyboardEvent* result =
+ new WidgetKeyboardEvent(false, mMessage, nullptr);
+ result->AssignKeyEventData(*this, true);
+ result->mEditCommandsForSingleLineEditor =
+ mEditCommandsForSingleLineEditor.Clone();
+ result->mEditCommandsForMultiLineEditor =
+ mEditCommandsForMultiLineEditor.Clone();
+ result->mEditCommandsForRichTextEditor =
+ mEditCommandsForRichTextEditor.Clone();
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ bool CanUserGestureActivateTarget() const {
+ // Printable keys, 'carriage return' and 'space' are supported user gestures
+ // for activating the document. However, if supported key is being pressed
+ // combining with other operation keys, such like alt, control ..etc., we
+ // won't activate the target for them because at that time user might
+ // interact with browser or window manager which doesn't necessarily
+ // demonstrate user's intent to play media.
+ const bool isCombiningWithOperationKeys = (IsControl() && !IsAltGraph()) ||
+ (IsAlt() && !IsAltGraph()) ||
+ IsMeta() || IsOS();
+ const bool isEnterOrSpaceKey =
+ mKeyNameIndex == KEY_NAME_INDEX_Enter || mKeyCode == NS_VK_SPACE;
+ return (PseudoCharCode() || isEnterOrSpaceKey) &&
+ (!isCombiningWithOperationKeys ||
+ // ctrl-c/ctrl-x/ctrl-v is quite common shortcut for clipboard
+ // operation.
+ // XXXedgar, we have to find a better way to handle browser keyboard
+ // shortcut for user activation, instead of just ignoring all
+ // combinations, see bug 1641171.
+ ((mKeyCode == dom::KeyboardEvent_Binding::DOM_VK_C ||
+ mKeyCode == dom::KeyboardEvent_Binding::DOM_VK_V ||
+ mKeyCode == dom::KeyboardEvent_Binding::DOM_VK_X) &&
+ IsAccel()));
+ }
+
+ /**
+ * CanTreatAsUserInput() returns true if the key is pressed for perhaps
+ * doing something on the web app or our UI. This means that when this
+ * returns false, e.g., when user presses a modifier key, user is probably
+ * displeased by opening popup, entering fullscreen mode, etc. Therefore,
+ * only when this returns true, such reactions should be allowed.
+ */
+ bool CanTreatAsUserInput() const {
+ if (!IsTrusted()) {
+ return false;
+ }
+ switch (mKeyNameIndex) {
+ case KEY_NAME_INDEX_Escape:
+ // modifier keys:
+ case KEY_NAME_INDEX_Alt:
+ case KEY_NAME_INDEX_AltGraph:
+ case KEY_NAME_INDEX_CapsLock:
+ case KEY_NAME_INDEX_Control:
+ case KEY_NAME_INDEX_Fn:
+ case KEY_NAME_INDEX_FnLock:
+ case KEY_NAME_INDEX_Meta:
+ case KEY_NAME_INDEX_NumLock:
+ case KEY_NAME_INDEX_ScrollLock:
+ case KEY_NAME_INDEX_Shift:
+ case KEY_NAME_INDEX_Symbol:
+ case KEY_NAME_INDEX_SymbolLock:
+ // legacy modifier keys:
+ case KEY_NAME_INDEX_Hyper:
+ case KEY_NAME_INDEX_Super:
+ // obsolete modifier key:
+ case KEY_NAME_INDEX_OS:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * ShouldInteractionTimeRecorded() returns true if the handling time of
+ * the event should be recorded with the telemetry.
+ */
+ bool ShouldInteractionTimeRecorded() const {
+ // Let's record only when we can treat the instance is a user input.
+ return CanTreatAsUserInput();
+ }
+
+ // OS translated Unicode chars which are used for accesskey and accelkey
+ // handling. The handlers will try from first character to last character.
+ CopyableTArray<AlternativeCharCode> mAlternativeCharCodes;
+ // DOM KeyboardEvent.key only when mKeyNameIndex is KEY_NAME_INDEX_USE_STRING.
+ nsString mKeyValue;
+ // DOM KeyboardEvent.code only when mCodeNameIndex is
+ // CODE_NAME_INDEX_USE_STRING.
+ nsString mCodeValue;
+
+ // OS-specific native event can optionally be preserved.
+ // This is used to retrieve editing shortcut keys in the environment.
+ void* mNativeKeyEvent;
+ // A DOM keyCode value or 0. If a keypress event whose mCharCode is 0, this
+ // should be 0.
+ uint32_t mKeyCode;
+ // If the instance is a keypress event of a printable key, this is a UTF-16
+ // value of the key. Otherwise, 0. This value must not be a control
+ // character when some modifiers are active. Then, this value should be an
+ // unmodified value except Shift and AltGr.
+ uint32_t mCharCode;
+ // mPseudoCharCode is valid only when mMessage is an eKeyDown event.
+ // This stores mCharCode value of keypress event which is fired with same
+ // key value and same modifier state.
+ uint32_t mPseudoCharCode;
+ // One of eKeyLocation*
+ uint32_t mLocation;
+ // Unique id associated with a keydown / keypress event. It's ok if this wraps
+ // over long periods.
+ uint32_t mUniqueId;
+
+ // DOM KeyboardEvent.key
+ KeyNameIndex mKeyNameIndex;
+ // DOM KeyboardEvent.code
+ CodeNameIndex mCodeNameIndex;
+
+ // Indicates whether the event is generated by auto repeat or not.
+ // if this is keyup event, always false.
+ bool mIsRepeat;
+ // Indicates whether the event is generated during IME (or deadkey)
+ // composition. This is initialized by EventStateManager. So, key event
+ // dispatchers don't need to initialize this.
+ bool mIsComposing;
+ // Indicates whether the event is synthesized from Text Input Processor
+ // or an actual event from nsAppShell.
+ bool mIsSynthesizedByTIP;
+ // Indicates whether the event is skippable in remote process.
+ // Don't refer this member directly when you need to check this.
+ // Use CanSkipInRemoteProcess() instead.
+ bool mMaybeSkippableInRemoteProcess;
+ // Indicates whether the event should return legacy keyCode value and
+ // charCode value to web apps (one of them is always 0) or not, when it's
+ // an eKeyPress event.
+ bool mUseLegacyKeyCodeAndCharCodeValues;
+
+ bool CanSkipInRemoteProcess() const {
+ // If this is a repeat event (i.e., generated by auto-repeat feature of
+ // the platform), remove process may skip to handle it because of
+ // performances reasons.. However, if it's caused by odd keyboard utils,
+ // we should not ignore any key events even marked as repeated since
+ // generated key sequence may be important to input proper text. E.g.,
+ // "SinhalaTamil IME" on Windows emulates dead key like input with
+ // generating WM_KEYDOWN for VK_PACKET (inputting any Unicode characters
+ // without keyboard layout information) and VK_BACK (Backspace) to remove
+ // previous character(s) and those messages may be marked as "repeat" by
+ // their bug.
+ return mIsRepeat && mMaybeSkippableInRemoteProcess;
+ }
+
+ /**
+ * Retrieves all edit commands from mWidget. This shouldn't be called when
+ * the instance is an untrusted event, doesn't have widget or in non-chrome
+ * process.
+ */
+ void InitAllEditCommands();
+
+ /**
+ * Retrieves edit commands from mWidget only for aType. This shouldn't be
+ * called when the instance is an untrusted event or doesn't have widget.
+ *
+ * @return false if some resource is not available to get
+ * commands unexpectedly. Otherwise, true even if
+ * retrieved command is nothing.
+ */
+ bool InitEditCommandsFor(nsIWidget::NativeKeyBindingsType aType);
+
+ /**
+ * PreventNativeKeyBindings() makes the instance to not cause any edit
+ * actions even if it matches with a native key binding.
+ */
+ void PreventNativeKeyBindings() {
+ mEditCommandsForSingleLineEditor.Clear();
+ mEditCommandsForMultiLineEditor.Clear();
+ mEditCommandsForRichTextEditor.Clear();
+ mEditCommandsForSingleLineEditorInitialized = true;
+ mEditCommandsForMultiLineEditorInitialized = true;
+ mEditCommandsForRichTextEditorInitialized = true;
+ }
+
+ /**
+ * EditCommandsConstRef() returns reference to edit commands for aType.
+ */
+ const nsTArray<CommandInt>& EditCommandsConstRef(
+ nsIWidget::NativeKeyBindingsType aType) const {
+ return const_cast<WidgetKeyboardEvent*>(this)->EditCommandsRef(aType);
+ }
+
+ /**
+ * IsEditCommandsInitialized() returns true if edit commands for aType
+ * was already initialized. Otherwise, false.
+ */
+ bool IsEditCommandsInitialized(nsIWidget::NativeKeyBindingsType aType) const {
+ return const_cast<WidgetKeyboardEvent*>(this)->IsEditCommandsInitializedRef(
+ aType);
+ }
+
+#ifdef DEBUG
+ /**
+ * AreAllEditCommandsInitialized() returns true if edit commands for all
+ * types were already initialized. Otherwise, false.
+ */
+ bool AreAllEditCommandsInitialized() const {
+ return mEditCommandsForSingleLineEditorInitialized &&
+ mEditCommandsForMultiLineEditorInitialized &&
+ mEditCommandsForRichTextEditorInitialized;
+ }
+#endif // #ifdef DEBUG
+
+ /**
+ * Execute edit commands for aType.
+ *
+ * @return true if the caller should do nothing anymore.
+ * false, otherwise.
+ */
+ typedef void (*DoCommandCallback)(Command, void*);
+ bool ExecuteEditCommands(nsIWidget::NativeKeyBindingsType aType,
+ DoCommandCallback aCallback, void* aCallbackData);
+
+ // If the key should cause keypress events, this returns true.
+ // Otherwise, false.
+ bool ShouldCauseKeypressEvents() const;
+
+ // mCharCode value of non-eKeyPress events is always 0. However, if
+ // non-eKeyPress event has one or more alternative char code values,
+ // its first item should be the mCharCode value of following eKeyPress event.
+ // PseudoCharCode() returns mCharCode value for eKeyPress event,
+ // the first alternative char code value of non-eKeyPress event or 0.
+ uint32_t PseudoCharCode() const {
+ return mMessage == eKeyPress ? mCharCode : mPseudoCharCode;
+ }
+ void SetCharCode(uint32_t aCharCode) {
+ if (mMessage == eKeyPress) {
+ mCharCode = aCharCode;
+ } else {
+ mPseudoCharCode = aCharCode;
+ }
+ }
+
+ void GetDOMKeyName(nsAString& aKeyName) {
+ if (mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
+ aKeyName = mKeyValue;
+ return;
+ }
+ GetDOMKeyName(mKeyNameIndex, aKeyName);
+ }
+ void GetDOMCodeName(nsAString& aCodeName) {
+ if (mCodeNameIndex == CODE_NAME_INDEX_USE_STRING) {
+ aCodeName = mCodeValue;
+ return;
+ }
+ GetDOMCodeName(mCodeNameIndex, aCodeName);
+ }
+
+ /**
+ * GetFallbackKeyCodeOfPunctuationKey() returns a DOM keyCode value for
+ * aCodeNameIndex. This is keyCode value of the key when active keyboard
+ * layout is ANSI (US), JIS or ABNT keyboard layout (the latter 2 layouts
+ * are used only when ANSI doesn't have the key). The result is useful
+ * if the key doesn't produce ASCII character with active keyboard layout
+ * nor with alternative ASCII capable keyboard layout.
+ */
+ static uint32_t GetFallbackKeyCodeOfPunctuationKey(
+ CodeNameIndex aCodeNameIndex);
+
+ bool IsModifierKeyEvent() const {
+ return GetModifierForKeyName(mKeyNameIndex) != MODIFIER_NONE;
+ }
+
+ /**
+ * Get the candidates for shortcut key.
+ *
+ * @param aCandidates [out] the candidate shortcut key combination list.
+ * the first item is most preferred.
+ */
+ void GetShortcutKeyCandidates(ShortcutKeyCandidateArray& aCandidates) const;
+
+ /**
+ * Get the candidates for access key.
+ *
+ * @param aCandidates [out] the candidate access key list.
+ * the first item is most preferred.
+ */
+ void GetAccessKeyCandidates(nsTArray<uint32_t>& aCandidates) const;
+
+ /**
+ * Check whether the modifiers match with chrome access key or
+ * content access key.
+ */
+ bool ModifiersMatchWithAccessKey(AccessKeyType aType) const;
+
+ /**
+ * Return active modifiers which may match with access key.
+ * For example, even if Alt is access key modifier, then, when Control,
+ * CapseLock and NumLock are active, this returns only MODIFIER_CONTROL.
+ */
+ Modifiers ModifiersForAccessKeyMatching() const;
+
+ /**
+ * Return access key modifiers.
+ */
+ static Modifiers AccessKeyModifiers(AccessKeyType aType);
+
+ static void Shutdown();
+
+ /**
+ * ComputeLocationFromCodeValue() returns one of .mLocation value
+ * (eKeyLocation*) which is the most preferred value for the specified code
+ * value.
+ */
+ static uint32_t ComputeLocationFromCodeValue(CodeNameIndex aCodeNameIndex);
+
+ /**
+ * ComputeKeyCodeFromKeyNameIndex() return a .mKeyCode value which can be
+ * mapped from the specified key value. Note that this returns 0 if the
+ * key name index is KEY_NAME_INDEX_Unidentified or KEY_NAME_INDEX_USE_STRING.
+ * This means that this method is useful only for non-printable keys.
+ */
+ static uint32_t ComputeKeyCodeFromKeyNameIndex(KeyNameIndex aKeyNameIndex);
+
+ /**
+ * ComputeCodeNameIndexFromKeyNameIndex() returns a code name index which
+ * is typically mapped to given key name index on the platform.
+ * Note that this returns CODE_NAME_INDEX_UNKNOWN if the key name index is
+ * KEY_NAME_INDEX_Unidentified or KEY_NAME_INDEX_USE_STRING.
+ * This means that this method is useful only for non-printable keys.
+ *
+ * @param aKeyNameIndex A non-printable key name index.
+ * @param aLocation Should be one of location value. This is
+ * important when aKeyNameIndex may exist in
+ * both Numpad or Standard, or in both Left or
+ * Right. If this is nothing, this method
+ * returns Left or Standard position's code
+ * value.
+ */
+ static CodeNameIndex ComputeCodeNameIndexFromKeyNameIndex(
+ KeyNameIndex aKeyNameIndex, const Maybe<uint32_t>& aLocation);
+
+ /**
+ * GetModifierForKeyName() returns a value of Modifier which is activated
+ * by the aKeyNameIndex.
+ */
+ static Modifier GetModifierForKeyName(KeyNameIndex aKeyNameIndex);
+
+ /**
+ * IsLeftOrRightModiferKeyNameIndex() returns true if aKeyNameIndex is a
+ * modifier key which may be in Left and Right location.
+ */
+ static bool IsLeftOrRightModiferKeyNameIndex(KeyNameIndex aKeyNameIndex) {
+ switch (aKeyNameIndex) {
+ case KEY_NAME_INDEX_Alt:
+ case KEY_NAME_INDEX_Control:
+ case KEY_NAME_INDEX_Meta:
+ case KEY_NAME_INDEX_OS:
+ case KEY_NAME_INDEX_Shift:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * IsLockableModifier() returns true if aKeyNameIndex is a lockable modifier
+ * key such as CapsLock and NumLock.
+ */
+ static bool IsLockableModifier(KeyNameIndex aKeyNameIndex);
+
+ static void GetDOMKeyName(KeyNameIndex aKeyNameIndex, nsAString& aKeyName);
+ static void GetDOMCodeName(CodeNameIndex aCodeNameIndex,
+ nsAString& aCodeName);
+
+ static KeyNameIndex GetKeyNameIndex(const nsAString& aKeyValue);
+ static CodeNameIndex GetCodeNameIndex(const nsAString& aCodeValue);
+
+ static const char* GetCommandStr(Command aCommand);
+
+ void AssignKeyEventData(const WidgetKeyboardEvent& aEvent,
+ bool aCopyTargets) {
+ AssignInputEventData(aEvent, aCopyTargets);
+
+ mKeyCode = aEvent.mKeyCode;
+ mCharCode = aEvent.mCharCode;
+ mPseudoCharCode = aEvent.mPseudoCharCode;
+ mLocation = aEvent.mLocation;
+ mAlternativeCharCodes = aEvent.mAlternativeCharCodes.Clone();
+ mIsRepeat = aEvent.mIsRepeat;
+ mIsComposing = aEvent.mIsComposing;
+ mKeyNameIndex = aEvent.mKeyNameIndex;
+ mCodeNameIndex = aEvent.mCodeNameIndex;
+ mKeyValue = aEvent.mKeyValue;
+ mCodeValue = aEvent.mCodeValue;
+ // Don't copy mNativeKeyEvent because it may be referred after its instance
+ // is destroyed.
+ mNativeKeyEvent = nullptr;
+ mUniqueId = aEvent.mUniqueId;
+ mIsSynthesizedByTIP = aEvent.mIsSynthesizedByTIP;
+ mMaybeSkippableInRemoteProcess = aEvent.mMaybeSkippableInRemoteProcess;
+ mUseLegacyKeyCodeAndCharCodeValues =
+ aEvent.mUseLegacyKeyCodeAndCharCodeValues;
+
+ // Don't copy mEditCommandsFor*Editor because it may require a lot of
+ // memory space. For example, if the event is dispatched but grabbed by
+ // a JS variable, they are not necessary anymore.
+
+ mEditCommandsForSingleLineEditorInitialized =
+ aEvent.mEditCommandsForSingleLineEditorInitialized;
+ mEditCommandsForMultiLineEditorInitialized =
+ aEvent.mEditCommandsForMultiLineEditorInitialized;
+ mEditCommandsForRichTextEditorInitialized =
+ aEvent.mEditCommandsForRichTextEditorInitialized;
+ }
+
+ void AssignCommands(const WidgetKeyboardEvent& aEvent) {
+ mEditCommandsForSingleLineEditorInitialized =
+ aEvent.mEditCommandsForSingleLineEditorInitialized;
+ if (mEditCommandsForSingleLineEditorInitialized) {
+ mEditCommandsForSingleLineEditor =
+ aEvent.mEditCommandsForSingleLineEditor.Clone();
+ } else {
+ mEditCommandsForSingleLineEditor.Clear();
+ }
+ mEditCommandsForMultiLineEditorInitialized =
+ aEvent.mEditCommandsForMultiLineEditorInitialized;
+ if (mEditCommandsForMultiLineEditorInitialized) {
+ mEditCommandsForMultiLineEditor =
+ aEvent.mEditCommandsForMultiLineEditor.Clone();
+ } else {
+ mEditCommandsForMultiLineEditor.Clear();
+ }
+ mEditCommandsForRichTextEditorInitialized =
+ aEvent.mEditCommandsForRichTextEditorInitialized;
+ if (mEditCommandsForRichTextEditorInitialized) {
+ mEditCommandsForRichTextEditor =
+ aEvent.mEditCommandsForRichTextEditor.Clone();
+ } else {
+ mEditCommandsForRichTextEditor.Clear();
+ }
+ }
+
+ private:
+ static const char16_t* const kKeyNames[];
+ static const char16_t* const kCodeNames[];
+ typedef nsDataHashtable<nsStringHashKey, KeyNameIndex> KeyNameIndexHashtable;
+ typedef nsDataHashtable<nsStringHashKey, CodeNameIndex>
+ CodeNameIndexHashtable;
+ static KeyNameIndexHashtable* sKeyNameIndexHashtable;
+ static CodeNameIndexHashtable* sCodeNameIndexHashtable;
+
+ // mEditCommandsFor*Editor store edit commands. This should be initialized
+ // with InitEditCommandsFor().
+ // XXX Ideally, this should be array of Command rather than CommandInt.
+ // However, ParamTraits isn't aware of enum array.
+ CopyableTArray<CommandInt> mEditCommandsForSingleLineEditor;
+ CopyableTArray<CommandInt> mEditCommandsForMultiLineEditor;
+ CopyableTArray<CommandInt> mEditCommandsForRichTextEditor;
+
+ nsTArray<CommandInt>& EditCommandsRef(
+ nsIWidget::NativeKeyBindingsType aType) {
+ switch (aType) {
+ case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+ return mEditCommandsForSingleLineEditor;
+ case nsIWidget::NativeKeyBindingsForMultiLineEditor:
+ return mEditCommandsForMultiLineEditor;
+ case nsIWidget::NativeKeyBindingsForRichTextEditor:
+ return mEditCommandsForRichTextEditor;
+ default:
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE(
+ "Invalid native key binding type");
+ }
+ }
+
+ // mEditCommandsFor*EditorInitialized are set to true when
+ // InitEditCommandsFor() initializes edit commands for the type.
+ bool mEditCommandsForSingleLineEditorInitialized;
+ bool mEditCommandsForMultiLineEditorInitialized;
+ bool mEditCommandsForRichTextEditorInitialized;
+
+ bool& IsEditCommandsInitializedRef(nsIWidget::NativeKeyBindingsType aType) {
+ switch (aType) {
+ case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+ return mEditCommandsForSingleLineEditorInitialized;
+ case nsIWidget::NativeKeyBindingsForMultiLineEditor:
+ return mEditCommandsForMultiLineEditorInitialized;
+ case nsIWidget::NativeKeyBindingsForRichTextEditor:
+ return mEditCommandsForRichTextEditorInitialized;
+ default:
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE(
+ "Invalid native key binding type");
+ }
+ }
+};
+
+/******************************************************************************
+ * mozilla::WidgetCompositionEvent
+ ******************************************************************************/
+
+class WidgetCompositionEvent : public WidgetGUIEvent {
+ private:
+ friend class mozilla::dom::PBrowserParent;
+ friend class mozilla::dom::PBrowserChild;
+
+ WidgetCompositionEvent() : mOriginalMessage(eVoidEvent) {}
+
+ public:
+ virtual WidgetCompositionEvent* AsCompositionEvent() override { return this; }
+
+ WidgetCompositionEvent(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget)
+ : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, eCompositionEventClass),
+ mNativeIMEContext(aWidget),
+ mOriginalMessage(eVoidEvent) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eCompositionEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ WidgetCompositionEvent* result =
+ new WidgetCompositionEvent(false, mMessage, nullptr);
+ result->AssignCompositionEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ // The composition string or the commit string. If the instance is a
+ // compositionstart event, this is initialized with selected text by
+ // TextComposition automatically.
+ nsString mData;
+
+ RefPtr<TextRangeArray> mRanges;
+
+ // mNativeIMEContext stores the native IME context which causes the
+ // composition event.
+ widget::NativeIMEContext mNativeIMEContext;
+
+ // If the instance is a clone of another event, mOriginalMessage stores
+ // the another event's mMessage.
+ EventMessage mOriginalMessage;
+
+ void AssignCompositionEventData(const WidgetCompositionEvent& aEvent,
+ bool aCopyTargets) {
+ AssignGUIEventData(aEvent, aCopyTargets);
+
+ mData = aEvent.mData;
+ mOriginalMessage = aEvent.mOriginalMessage;
+ mRanges = aEvent.mRanges;
+
+ // Currently, we don't need to copy the other members because they are
+ // for internal use only (not available from JS).
+ }
+
+ bool IsComposing() const { return mRanges && mRanges->IsComposing(); }
+
+ uint32_t TargetClauseOffset() const {
+ return mRanges ? mRanges->TargetClauseOffset() : 0;
+ }
+
+ uint32_t TargetClauseLength() const {
+ uint32_t length = UINT32_MAX;
+ if (mRanges) {
+ length = mRanges->TargetClauseLength();
+ }
+ return length == UINT32_MAX ? mData.Length() : length;
+ }
+
+ uint32_t RangeCount() const { return mRanges ? mRanges->Length() : 0; }
+
+ bool CausesDOMTextEvent() const {
+ return mMessage == eCompositionChange || mMessage == eCompositionCommit ||
+ mMessage == eCompositionCommitAsIs;
+ }
+
+ bool CausesDOMCompositionEndEvent() const {
+ return mMessage == eCompositionEnd || mMessage == eCompositionCommit ||
+ mMessage == eCompositionCommitAsIs;
+ }
+
+ bool IsFollowedByCompositionEnd() const {
+ return IsFollowedByCompositionEnd(mOriginalMessage);
+ }
+
+ static bool IsFollowedByCompositionEnd(EventMessage aEventMessage) {
+ return aEventMessage == eCompositionCommit ||
+ aEventMessage == eCompositionCommitAsIs;
+ }
+};
+
+/******************************************************************************
+ * mozilla::WidgetQueryContentEvent
+ ******************************************************************************/
+
+class WidgetQueryContentEvent : public WidgetGUIEvent {
+ private:
+ friend class dom::PBrowserParent;
+ friend class dom::PBrowserChild;
+
+ WidgetQueryContentEvent()
+ : mUseNativeLineBreak(true),
+ mWithFontRanges(false),
+ mNeedsToFlushLayout(true) {
+ MOZ_CRASH("WidgetQueryContentEvent is created without proper arguments");
+ }
+
+ public:
+ virtual WidgetQueryContentEvent* AsQueryContentEvent() override {
+ return this;
+ }
+
+ WidgetQueryContentEvent(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget)
+ : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, eQueryContentEventClass),
+ mUseNativeLineBreak(true),
+ mWithFontRanges(false),
+ mNeedsToFlushLayout(true) {}
+
+ WidgetQueryContentEvent(EventMessage aMessage,
+ const WidgetQueryContentEvent& aOtherEvent)
+ : WidgetGUIEvent(aOtherEvent.IsTrusted(), aMessage,
+ const_cast<nsIWidget*>(aOtherEvent.mWidget.get()),
+ eQueryContentEventClass),
+ mUseNativeLineBreak(aOtherEvent.mUseNativeLineBreak),
+ mWithFontRanges(false),
+ mNeedsToFlushLayout(aOtherEvent.mNeedsToFlushLayout) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ // This event isn't an internal event of any DOM event.
+ NS_ASSERTION(!IsAllowedToDispatchDOMEvent(),
+ "WidgetQueryContentEvent needs to support Duplicate()");
+ MOZ_CRASH("WidgetQueryContentEvent doesn't support Duplicate()");
+ }
+
+ struct Options final {
+ bool mUseNativeLineBreak;
+ bool mRelativeToInsertionPoint;
+
+ explicit Options()
+ : mUseNativeLineBreak(true), mRelativeToInsertionPoint(false) {}
+
+ explicit Options(const WidgetQueryContentEvent& aEvent)
+ : mUseNativeLineBreak(aEvent.mUseNativeLineBreak),
+ mRelativeToInsertionPoint(aEvent.mInput.mRelativeToInsertionPoint) {}
+ };
+
+ void Init(const Options& aOptions) {
+ mUseNativeLineBreak = aOptions.mUseNativeLineBreak;
+ mInput.mRelativeToInsertionPoint = aOptions.mRelativeToInsertionPoint;
+ MOZ_ASSERT(mInput.IsValidEventMessage(mMessage));
+ }
+
+ void InitForQueryTextContent(int64_t aOffset, uint32_t aLength,
+ const Options& aOptions = Options()) {
+ NS_ASSERTION(mMessage == eQueryTextContent, "wrong initializer is called");
+ mInput.mOffset = aOffset;
+ mInput.mLength = aLength;
+ Init(aOptions);
+ MOZ_ASSERT(mInput.IsValidOffset());
+ }
+
+ void InitForQueryCaretRect(int64_t aOffset,
+ const Options& aOptions = Options()) {
+ NS_ASSERTION(mMessage == eQueryCaretRect, "wrong initializer is called");
+ mInput.mOffset = aOffset;
+ Init(aOptions);
+ MOZ_ASSERT(mInput.IsValidOffset());
+ }
+
+ void InitForQueryTextRect(int64_t aOffset, uint32_t aLength,
+ const Options& aOptions = Options()) {
+ NS_ASSERTION(mMessage == eQueryTextRect, "wrong initializer is called");
+ mInput.mOffset = aOffset;
+ mInput.mLength = aLength;
+ Init(aOptions);
+ MOZ_ASSERT(mInput.IsValidOffset());
+ }
+
+ void InitForQuerySelectedText(SelectionType aSelectionType,
+ const Options& aOptions = Options()) {
+ MOZ_ASSERT(mMessage == eQuerySelectedText);
+ MOZ_ASSERT(aSelectionType != SelectionType::eNone);
+ mInput.mSelectionType = aSelectionType;
+ Init(aOptions);
+ }
+
+ void InitForQueryDOMWidgetHittest(
+ const mozilla::LayoutDeviceIntPoint& aPoint) {
+ NS_ASSERTION(mMessage == eQueryDOMWidgetHittest,
+ "wrong initializer is called");
+ mRefPoint = aPoint;
+ }
+
+ void InitForQueryTextRectArray(uint32_t aOffset, uint32_t aLength,
+ const Options& aOptions = Options()) {
+ NS_ASSERTION(mMessage == eQueryTextRectArray,
+ "wrong initializer is called");
+ mInput.mOffset = aOffset;
+ mInput.mLength = aLength;
+ Init(aOptions);
+ }
+
+ bool NeedsToFlushLayout() const { return mNeedsToFlushLayout; }
+
+ void RequestFontRanges() {
+ MOZ_ASSERT(mMessage == eQueryTextContent);
+ mWithFontRanges = true;
+ }
+
+ bool Succeeded() const {
+ if (mReply.isNothing()) {
+ return false;
+ }
+ switch (mMessage) {
+ case eQuerySelectedText:
+ return mReply->mOffsetAndData.isSome() ||
+ mInput.mSelectionType != SelectionType::eNormal;
+ case eQueryTextContent:
+ case eQueryTextRect:
+ case eQueryCaretRect:
+ return mReply->mOffsetAndData.isSome();
+ default:
+ return true;
+ }
+ }
+
+ bool Failed() const { return !Succeeded(); }
+
+ bool FoundSelection() const {
+ MOZ_ASSERT(mMessage == eQuerySelectedText);
+ return Succeeded() && mReply->mOffsetAndData.isSome();
+ }
+
+ bool FoundChar() const {
+ MOZ_ASSERT(mMessage == eQueryCharacterAtPoint);
+ return Succeeded() && mReply->mOffsetAndData.isSome();
+ }
+
+ bool FoundTentativeCaretOffset() const {
+ MOZ_ASSERT(mMessage == eQueryCharacterAtPoint);
+ return Succeeded() && mReply->mTentativeCaretOffset.isSome();
+ }
+
+ bool DidNotFindSelection() const {
+ MOZ_ASSERT(mMessage == eQuerySelectedText);
+ return Failed() || mReply->mOffsetAndData.isNothing();
+ }
+
+ bool DidNotFindChar() const {
+ MOZ_ASSERT(mMessage == eQueryCharacterAtPoint);
+ return Failed() || mReply->mOffsetAndData.isNothing();
+ }
+
+ bool DidNotFindTentativeCaretOffset() const {
+ MOZ_ASSERT(mMessage == eQueryCharacterAtPoint);
+ return Failed() || mReply->mTentativeCaretOffset.isNothing();
+ }
+
+ bool mUseNativeLineBreak;
+ bool mWithFontRanges;
+ bool mNeedsToFlushLayout;
+ struct Input final {
+ uint32_t EndOffset() const {
+ CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(mOffset) + mLength;
+ return NS_WARN_IF(!endOffset.isValid()) ? UINT32_MAX : endOffset.value();
+ }
+
+ int64_t mOffset;
+ uint32_t mLength;
+ SelectionType mSelectionType;
+ // If mOffset is true, mOffset is relative to the start offset of
+ // composition if there is, otherwise, the start of the first selection
+ // range.
+ bool mRelativeToInsertionPoint;
+
+ Input()
+ : mOffset(0),
+ mLength(0),
+ mSelectionType(SelectionType::eNormal),
+ mRelativeToInsertionPoint(false) {}
+
+ bool IsValidOffset() const {
+ return mRelativeToInsertionPoint || mOffset >= 0;
+ }
+ bool IsValidEventMessage(EventMessage aEventMessage) const {
+ if (!mRelativeToInsertionPoint) {
+ return true;
+ }
+ switch (aEventMessage) {
+ case eQueryTextContent:
+ case eQueryCaretRect:
+ case eQueryTextRect:
+ return true;
+ default:
+ return false;
+ }
+ }
+ bool MakeOffsetAbsolute(uint32_t aInsertionPointOffset) {
+ if (NS_WARN_IF(!mRelativeToInsertionPoint)) {
+ return true;
+ }
+ mRelativeToInsertionPoint = false;
+ // If mOffset + aInsertionPointOffset becomes negative value,
+ // we should assume the absolute offset is 0.
+ if (mOffset < 0 && -mOffset > aInsertionPointOffset) {
+ mOffset = 0;
+ return true;
+ }
+ // Otherwise, we don't allow too large offset.
+ CheckedInt<uint32_t> absOffset(mOffset + aInsertionPointOffset);
+ if (NS_WARN_IF(!absOffset.isValid())) {
+ mOffset = UINT32_MAX;
+ return false;
+ }
+ mOffset = absOffset.value();
+ return true;
+ }
+ } mInput;
+
+ struct Reply final {
+ EventMessage const mEventMessage;
+ void* mContentsRoot;
+ Maybe<OffsetAndData<uint32_t>> mOffsetAndData;
+ // mTentativeCaretOffset is used by only eQueryCharacterAtPoint.
+ // This is the offset where caret would be if user clicked at the mRefPoint.
+ Maybe<uint32_t> mTentativeCaretOffset;
+ // mRect is used by eQueryTextRect, eQueryCaretRect, eQueryCharacterAtPoint
+ // and eQueryEditorRect. The coordinates is system coordinates relative to
+ // the top level widget of mFocusedWidget. E.g., if a <xul:panel> which
+ // is owned by a window has focused editor, the offset of mRect is relative
+ // to the owner window, not the <xul:panel>.
+ mozilla::LayoutDeviceIntRect mRect;
+ // The return widget has the caret. This is set at all query events.
+ nsIWidget* mFocusedWidget;
+ // mozilla::WritingMode value at the end (focus) of the selection
+ mozilla::WritingMode mWritingMode;
+ // Used by eQuerySelectionAsTransferable
+ nsCOMPtr<nsITransferable> mTransferable;
+ // Used by eQueryTextContent with font ranges requested
+ CopyableAutoTArray<mozilla::FontRange, 1> mFontRanges;
+ // Used by eQueryTextRectArray
+ CopyableTArray<mozilla::LayoutDeviceIntRect> mRectArray;
+ // true if selection is reversed (end < start)
+ bool mReversed;
+ // true if the selection exists
+ bool mHasSelection;
+ // true if DOM element under mouse belongs to widget
+ bool mWidgetIsHit;
+
+ Reply() = delete;
+ explicit Reply(EventMessage aEventMessage)
+ : mEventMessage(aEventMessage),
+ mContentsRoot(nullptr),
+ mFocusedWidget(nullptr),
+ mReversed(false),
+ mHasSelection(false),
+ mWidgetIsHit(false) {}
+
+ // Don't allow to copy/move because of `mEventMessage`.
+ Reply(const Reply& aOther) = delete;
+ Reply(Reply&& aOther) = delete;
+ Reply& operator=(const Reply& aOther) = delete;
+ Reply& operator=(Reply&& aOther) = delete;
+
+ MOZ_NEVER_INLINE_DEBUG uint32_t StartOffset() const {
+ MOZ_ASSERT(mOffsetAndData.isSome());
+ return mOffsetAndData->StartOffset();
+ }
+ MOZ_NEVER_INLINE_DEBUG uint32_t EndOffset() const {
+ MOZ_ASSERT(mOffsetAndData.isSome());
+ return mOffsetAndData->EndOffset();
+ }
+ MOZ_NEVER_INLINE_DEBUG uint32_t DataLength() const {
+ MOZ_ASSERT(mOffsetAndData.isSome() ||
+ mEventMessage == eQuerySelectedText);
+ return mOffsetAndData.isSome() ? mOffsetAndData->Length() : 0;
+ }
+ MOZ_NEVER_INLINE_DEBUG uint32_t SelectionStartOffset() const {
+ MOZ_ASSERT(mEventMessage == eQuerySelectedText);
+ MOZ_ASSERT(mOffsetAndData.isSome());
+ return StartOffset() + (mReversed ? DataLength() : 0);
+ }
+
+ MOZ_NEVER_INLINE_DEBUG uint32_t SelectionEndOffset() const {
+ MOZ_ASSERT(mEventMessage == eQuerySelectedText);
+ MOZ_ASSERT(mOffsetAndData.isSome());
+ return StartOffset() + (mReversed ? 0 : DataLength());
+ }
+
+ const WritingMode& WritingModeRef() const {
+ MOZ_ASSERT(mEventMessage == eQuerySelectedText ||
+ mEventMessage == eQueryCaretRect ||
+ mEventMessage == eQueryTextRect);
+ MOZ_ASSERT(mOffsetAndData.isSome() ||
+ mEventMessage == eQuerySelectedText);
+ return mWritingMode;
+ }
+
+ MOZ_NEVER_INLINE_DEBUG const nsString& DataRef() const {
+ MOZ_ASSERT(mOffsetAndData.isSome() ||
+ mEventMessage == eQuerySelectedText);
+ return mOffsetAndData.isSome() ? mOffsetAndData->DataRef()
+ : EmptyString();
+ }
+ MOZ_NEVER_INLINE_DEBUG bool IsDataEmpty() const {
+ MOZ_ASSERT(mOffsetAndData.isSome() ||
+ mEventMessage == eQuerySelectedText);
+ return mOffsetAndData.isSome() ? mOffsetAndData->IsDataEmpty() : true;
+ }
+ MOZ_NEVER_INLINE_DEBUG bool IsOffsetInRange(uint32_t aOffset) const {
+ MOZ_ASSERT(mOffsetAndData.isSome() ||
+ mEventMessage == eQuerySelectedText);
+ return mOffsetAndData.isSome() ? mOffsetAndData->IsOffsetInRange(aOffset)
+ : false;
+ }
+ MOZ_NEVER_INLINE_DEBUG bool IsOffsetInRangeOrEndOffset(
+ uint32_t aOffset) const {
+ MOZ_ASSERT(mOffsetAndData.isSome() ||
+ mEventMessage == eQuerySelectedText);
+ return mOffsetAndData.isSome()
+ ? mOffsetAndData->IsOffsetInRangeOrEndOffset(aOffset)
+ : false;
+ }
+ MOZ_NEVER_INLINE_DEBUG void TruncateData(uint32_t aLength = 0) {
+ MOZ_ASSERT(mOffsetAndData.isSome());
+ mOffsetAndData->TruncateData(aLength);
+ }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const Reply& aReply) {
+ aStream << "{ ";
+ if (aReply.mEventMessage == eQuerySelectedText ||
+ aReply.mEventMessage == eQueryTextContent ||
+ aReply.mEventMessage == eQueryTextRect ||
+ aReply.mEventMessage == eQueryCaretRect ||
+ aReply.mEventMessage == eQueryCharacterAtPoint) {
+ aStream << "mOffsetAndData=" << ToString(aReply.mOffsetAndData).c_str()
+ << ", ";
+ if (aReply.mEventMessage == eQueryCharacterAtPoint) {
+ aStream << "mTentativeCaretOffset="
+ << ToString(aReply.mTentativeCaretOffset).c_str() << ", ";
+ }
+ }
+ aStream << "mHasSelection=" << (aReply.mHasSelection ? "true" : "false");
+ if (aReply.mHasSelection) {
+ if (aReply.mEventMessage == eQuerySelectedText) {
+ aStream << ", mReversed=" << (aReply.mReversed ? "true" : "false");
+ }
+ if (aReply.mEventMessage == eQuerySelectionAsTransferable) {
+ aStream << ", mTransferable=0x" << aReply.mTransferable;
+ }
+ }
+ if (aReply.mEventMessage == eQuerySelectedText ||
+ aReply.mEventMessage == eQueryTextRect ||
+ aReply.mEventMessage == eQueryCaretRect) {
+ aStream << ", mWritingMode=" << ToString(aReply.mWritingMode).c_str();
+ }
+ aStream << ", mContentsRoot=0x" << aReply.mContentsRoot
+ << ", mFocusedWidget=0x" << aReply.mFocusedWidget;
+ if (aReply.mEventMessage == eQueryTextContent) {
+ aStream << ", mFontRanges={ Length()=" << aReply.mFontRanges.Length()
+ << " }";
+ } else if (aReply.mEventMessage == eQueryTextRect ||
+ aReply.mEventMessage == eQueryCaretRect ||
+ aReply.mEventMessage == eQueryCharacterAtPoint) {
+ aStream << ", mRect=" << ToString(aReply.mRect).c_str();
+ } else if (aReply.mEventMessage == eQueryTextRectArray) {
+ aStream << ", mRectArray={ Length()=" << aReply.mRectArray.Length()
+ << " }";
+ } else if (aReply.mEventMessage == eQueryDOMWidgetHittest) {
+ aStream << ", mWidgetIsHit="
+ << (aReply.mWidgetIsHit ? "true" : "false");
+ }
+ return aStream << " }";
+ }
+ };
+
+ void EmplaceReply() { mReply.emplace(mMessage); }
+ Maybe<Reply> mReply;
+
+ // values of mComputedScrollAction
+ enum { SCROLL_ACTION_NONE, SCROLL_ACTION_LINE, SCROLL_ACTION_PAGE };
+};
+
+/******************************************************************************
+ * mozilla::WidgetSelectionEvent
+ ******************************************************************************/
+
+class WidgetSelectionEvent : public WidgetGUIEvent {
+ private:
+ friend class mozilla::dom::PBrowserParent;
+ friend class mozilla::dom::PBrowserChild;
+
+ WidgetSelectionEvent()
+ : mOffset(0),
+ mLength(0),
+ mReversed(false),
+ mExpandToClusterBoundary(true),
+ mSucceeded(false),
+ mUseNativeLineBreak(true),
+ mReason(nsISelectionListener::NO_REASON) {}
+
+ public:
+ virtual WidgetSelectionEvent* AsSelectionEvent() override { return this; }
+
+ WidgetSelectionEvent(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget)
+ : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, eSelectionEventClass),
+ mOffset(0),
+ mLength(0),
+ mReversed(false),
+ mExpandToClusterBoundary(true),
+ mSucceeded(false),
+ mUseNativeLineBreak(true),
+ mReason(nsISelectionListener::NO_REASON) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ // This event isn't an internal event of any DOM event.
+ NS_ASSERTION(!IsAllowedToDispatchDOMEvent(),
+ "WidgetSelectionEvent needs to support Duplicate()");
+ MOZ_CRASH("WidgetSelectionEvent doesn't support Duplicate()");
+ return nullptr;
+ }
+
+ // Start offset of selection
+ uint32_t mOffset;
+ // Length of selection
+ uint32_t mLength;
+ // Selection "anchor" should be in front
+ bool mReversed;
+ // Cluster-based or character-based
+ bool mExpandToClusterBoundary;
+ // true if setting selection succeeded.
+ bool mSucceeded;
+ // true if native line breaks are used for mOffset and mLength
+ bool mUseNativeLineBreak;
+ // Fennec provides eSetSelection reason codes for downstream
+ // use in AccessibleCaret visibility logic.
+ int16_t mReason;
+};
+
+/******************************************************************************
+ * mozilla::InternalEditorInputEvent
+ ******************************************************************************/
+
+class InternalEditorInputEvent : public InternalUIEvent {
+ private:
+ InternalEditorInputEvent()
+ : mData(VoidString()),
+ mInputType(EditorInputType::eUnknown),
+ mIsComposing(false) {}
+
+ public:
+ virtual InternalEditorInputEvent* AsEditorInputEvent() override {
+ return this;
+ }
+
+ InternalEditorInputEvent(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget = nullptr)
+ : InternalUIEvent(aIsTrusted, aMessage, aWidget, eEditorInputEventClass),
+ mData(VoidString()),
+ mInputType(EditorInputType::eUnknown) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eEditorInputEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ InternalEditorInputEvent* result =
+ new InternalEditorInputEvent(false, mMessage, nullptr);
+ result->AssignEditorInputEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ nsString mData;
+ RefPtr<dom::DataTransfer> mDataTransfer;
+ OwningNonNullStaticRangeArray mTargetRanges;
+
+ EditorInputType mInputType;
+
+ bool mIsComposing;
+
+ void AssignEditorInputEventData(const InternalEditorInputEvent& aEvent,
+ bool aCopyTargets) {
+ AssignUIEventData(aEvent, aCopyTargets);
+
+ mData = aEvent.mData;
+ mDataTransfer = aEvent.mDataTransfer;
+ mTargetRanges = aEvent.mTargetRanges.Clone();
+ mInputType = aEvent.mInputType;
+ mIsComposing = aEvent.mIsComposing;
+ }
+
+ void GetDOMInputTypeName(nsAString& aInputTypeName) {
+ GetDOMInputTypeName(mInputType, aInputTypeName);
+ }
+ static void GetDOMInputTypeName(EditorInputType aInputType,
+ nsAString& aInputTypeName);
+ static EditorInputType GetEditorInputType(const nsAString& aInputType);
+
+ static void Shutdown();
+
+ private:
+ static const char16_t* const kInputTypeNames[];
+ typedef nsDataHashtable<nsStringHashKey, EditorInputType> InputTypeHashtable;
+ static InputTypeHashtable* sInputTypeHashtable;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_TextEvents_h__
diff --git a/widget/TextRange.h b/widget/TextRange.h
new file mode 100644
index 0000000000..2ad41a6bc6
--- /dev/null
+++ b/widget/TextRange.h
@@ -0,0 +1,287 @@
+/* -*- 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_TextRage_h_
+#define mozilla_TextRage_h_
+
+#include <stdint.h>
+
+#include "mozilla/EventForwards.h"
+
+#include "nsColor.h"
+#include "nsISelectionController.h"
+#include "nsITextInputProcessor.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+/******************************************************************************
+ * mozilla::TextRangeStyle
+ ******************************************************************************/
+
+struct TextRangeStyle {
+ typedef uint8_t LineStyleType;
+ // FYI: Modify IME_RANGE_LINE_* too when you modify LineStyle.
+ enum class LineStyle : LineStyleType {
+ None,
+ Solid,
+ Dotted,
+ Dashed,
+ Double,
+ Wavy,
+ };
+ inline static LineStyle ToLineStyle(RawTextRangeType aRawLineStyle) {
+ switch (static_cast<LineStyle>(aRawLineStyle)) {
+ case LineStyle::None:
+ case LineStyle::Solid:
+ case LineStyle::Dotted:
+ case LineStyle::Dashed:
+ case LineStyle::Double:
+ case LineStyle::Wavy:
+ return static_cast<LineStyle>(aRawLineStyle);
+ }
+ MOZ_ASSERT_UNREACHABLE("aRawLineStyle value is invalid");
+ return LineStyle::None;
+ }
+
+ enum {
+ DEFINED_NONE = 0x00,
+ DEFINED_LINESTYLE = 0x01,
+ DEFINED_FOREGROUND_COLOR = 0x02,
+ DEFINED_BACKGROUND_COLOR = 0x04,
+ DEFINED_UNDERLINE_COLOR = 0x08
+ };
+
+ // Initialize all members, because TextRange instances may be compared by
+ // memcomp.
+ //
+ // FIXME(emilio): I don't think that'd be sound, as it has padding which the
+ // compiler is not guaranteed to initialize.
+ TextRangeStyle() { Clear(); }
+
+ void Clear() {
+ mDefinedStyles = DEFINED_NONE;
+ mLineStyle = LineStyle::None;
+ mIsBoldLine = false;
+ mForegroundColor = mBackgroundColor = mUnderlineColor = NS_RGBA(0, 0, 0, 0);
+ }
+
+ bool IsDefined() const { return mDefinedStyles != DEFINED_NONE; }
+
+ bool IsLineStyleDefined() const {
+ return (mDefinedStyles & DEFINED_LINESTYLE) != 0;
+ }
+
+ bool IsForegroundColorDefined() const {
+ return (mDefinedStyles & DEFINED_FOREGROUND_COLOR) != 0;
+ }
+
+ bool IsBackgroundColorDefined() const {
+ return (mDefinedStyles & DEFINED_BACKGROUND_COLOR) != 0;
+ }
+
+ bool IsUnderlineColorDefined() const {
+ return (mDefinedStyles & DEFINED_UNDERLINE_COLOR) != 0;
+ }
+
+ bool IsNoChangeStyle() const {
+ return !IsForegroundColorDefined() && !IsBackgroundColorDefined() &&
+ IsLineStyleDefined() && mLineStyle == LineStyle::None;
+ }
+
+ bool Equals(const TextRangeStyle& aOther) const {
+ if (mDefinedStyles != aOther.mDefinedStyles) return false;
+ if (IsLineStyleDefined() && (mLineStyle != aOther.mLineStyle ||
+ !mIsBoldLine != !aOther.mIsBoldLine))
+ return false;
+ if (IsForegroundColorDefined() &&
+ (mForegroundColor != aOther.mForegroundColor))
+ return false;
+ if (IsBackgroundColorDefined() &&
+ (mBackgroundColor != aOther.mBackgroundColor))
+ return false;
+ if (IsUnderlineColorDefined() &&
+ (mUnderlineColor != aOther.mUnderlineColor))
+ return false;
+ return true;
+ }
+
+ bool operator!=(const TextRangeStyle& aOther) const {
+ return !Equals(aOther);
+ }
+
+ bool operator==(const TextRangeStyle& aOther) const { return Equals(aOther); }
+
+ uint8_t mDefinedStyles;
+ LineStyle mLineStyle; // DEFINED_LINESTYLE
+
+ bool mIsBoldLine; // DEFINED_LINESTYLE
+
+ nscolor mForegroundColor; // DEFINED_FOREGROUND_COLOR
+ nscolor mBackgroundColor; // DEFINED_BACKGROUND_COLOR
+ nscolor mUnderlineColor; // DEFINED_UNDERLINE_COLOR
+};
+
+/******************************************************************************
+ * mozilla::TextRange
+ ******************************************************************************/
+
+enum class TextRangeType : RawTextRangeType {
+ eUninitialized = 0x00,
+ eCaret = 0x01,
+ eRawClause = nsITextInputProcessor::ATTR_RAW_CLAUSE,
+ eSelectedRawClause = nsITextInputProcessor::ATTR_SELECTED_RAW_CLAUSE,
+ eConvertedClause = nsITextInputProcessor::ATTR_CONVERTED_CLAUSE,
+ eSelectedClause = nsITextInputProcessor::ATTR_SELECTED_CLAUSE
+};
+
+bool IsValidRawTextRangeValue(RawTextRangeType aRawTextRangeValue);
+RawTextRangeType ToRawTextRangeType(TextRangeType aTextRangeType);
+TextRangeType ToTextRangeType(RawTextRangeType aRawTextRangeType);
+const char* ToChar(TextRangeType aTextRangeType);
+SelectionType ToSelectionType(TextRangeType aTextRangeType);
+
+struct TextRange {
+ TextRange()
+ : mStartOffset(0),
+ mEndOffset(0),
+ mRangeType(TextRangeType::eUninitialized) {}
+
+ uint32_t mStartOffset;
+ // XXX Storing end offset makes the initializing code very complicated.
+ // We should replace it with mLength.
+ uint32_t mEndOffset;
+
+ TextRangeStyle mRangeStyle;
+
+ TextRangeType mRangeType;
+
+ uint32_t Length() const { return mEndOffset - mStartOffset; }
+
+ bool IsClause() const { return mRangeType != TextRangeType::eCaret; }
+
+ bool Equals(const TextRange& aOther) const {
+ return mStartOffset == aOther.mStartOffset &&
+ mEndOffset == aOther.mEndOffset && mRangeType == aOther.mRangeType &&
+ mRangeStyle == aOther.mRangeStyle;
+ }
+
+ void RemoveCharacter(uint32_t aOffset) {
+ if (mStartOffset > aOffset) {
+ --mStartOffset;
+ --mEndOffset;
+ } else if (mEndOffset > aOffset) {
+ --mEndOffset;
+ }
+ }
+};
+
+/******************************************************************************
+ * mozilla::TextRangeArray
+ ******************************************************************************/
+class TextRangeArray final : public AutoTArray<TextRange, 10> {
+ friend class WidgetCompositionEvent;
+
+ ~TextRangeArray() = default;
+
+ NS_INLINE_DECL_REFCOUNTING(TextRangeArray)
+
+ const TextRange* GetTargetClause() const {
+ for (uint32_t i = 0; i < Length(); ++i) {
+ const TextRange& range = ElementAt(i);
+ if (range.mRangeType == TextRangeType::eSelectedRawClause ||
+ range.mRangeType == TextRangeType::eSelectedClause) {
+ return &range;
+ }
+ }
+ return nullptr;
+ }
+
+ // Returns target clause offset. If there are selected clauses, this returns
+ // the first selected clause offset. Otherwise, 0.
+ uint32_t TargetClauseOffset() const {
+ const TextRange* range = GetTargetClause();
+ return range ? range->mStartOffset : 0;
+ }
+
+ // Returns target clause length. If there are selected clauses, this returns
+ // the first selected clause length. Otherwise, UINT32_MAX.
+ uint32_t TargetClauseLength() const {
+ const TextRange* range = GetTargetClause();
+ return range ? range->Length() : UINT32_MAX;
+ }
+
+ public:
+ bool IsComposing() const {
+ for (uint32_t i = 0; i < Length(); ++i) {
+ if (ElementAt(i).IsClause()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool Equals(const TextRangeArray& aOther) const {
+ size_t len = Length();
+ if (len != aOther.Length()) {
+ return false;
+ }
+ for (size_t i = 0; i < len; i++) {
+ if (!ElementAt(i).Equals(aOther.ElementAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void RemoveCharacter(uint32_t aOffset) {
+ for (size_t i = 0, len = Length(); i < len; i++) {
+ ElementAt(i).RemoveCharacter(aOffset);
+ }
+ }
+
+ bool HasCaret() const {
+ for (const TextRange& range : *this) {
+ if (range.mRangeType == TextRangeType::eCaret) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool HasClauses() const {
+ for (const TextRange& range : *this) {
+ if (range.IsClause()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ uint32_t GetCaretPosition() const {
+ for (const TextRange& range : *this) {
+ if (range.mRangeType == TextRangeType::eCaret) {
+ return range.mStartOffset;
+ }
+ }
+ return UINT32_MAX;
+ }
+
+ const TextRange* GetFirstClause() const {
+ for (const TextRange& range : *this) {
+ // Look for the range of a clause whose start offset is 0 because the
+ // first clause's start offset is always 0.
+ if (range.IsClause() && !range.mStartOffset) {
+ return &range;
+ }
+ }
+ MOZ_ASSERT(!HasClauses());
+ return nullptr;
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_TextRage_h_
diff --git a/widget/ThemeChangeKind.h b/widget/ThemeChangeKind.h
new file mode 100644
index 0000000000..2300ea648b
--- /dev/null
+++ b/widget/ThemeChangeKind.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_ThemeChangeKind
+#define mozilla_widget_ThemeChangeKind
+
+#include "mozilla/TypedEnumBits.h"
+
+namespace mozilla::widget {
+
+enum class ThemeChangeKind : uint8_t {
+ // This is the cheapest change, no need to forcibly recompute style and/or
+ // layout.
+ MediaQueriesOnly = 0,
+ // Style needs to forcibly be recomputed because some of the stuff that may
+ // have changed, like system colors, are reflected in the computed style but
+ // not in the specified style.
+ Style = 1 << 0,
+ // Layout needs to forcibly be recomputed because some of the stuff that may
+ // have changed is layout-dependent, like system font.
+ Layout = 1 << 1,
+ // The union of the two flags above.
+ StyleAndLayout = Style | Layout,
+ // For IPC serialization purposes.
+ AllBits = Style | Layout,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ThemeChangeKind)
+
+} // namespace mozilla::widget
+
+#endif
diff --git a/widget/TouchEvents.h b/widget/TouchEvents.h
new file mode 100644
index 0000000000..d246171cc7
--- /dev/null
+++ b/widget/TouchEvents.h
@@ -0,0 +1,194 @@
+/* -*- 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_TouchEvents_h__
+#define mozilla_TouchEvents_h__
+
+#include <stdint.h>
+
+#include "mozilla/dom/Touch.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/RefPtr.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+/******************************************************************************
+ * mozilla::WidgetGestureNotifyEvent
+ *
+ * This event is the first event generated when the user touches
+ * the screen with a finger, and it's meant to decide what kind
+ * of action we'll use for that touch interaction.
+ *
+ * The event is dispatched to the layout and based on what is underneath
+ * the initial contact point it's then decided if we should pan
+ * (finger scrolling) or drag the target element.
+ ******************************************************************************/
+
+class WidgetGestureNotifyEvent : public WidgetGUIEvent {
+ public:
+ virtual WidgetGestureNotifyEvent* AsGestureNotifyEvent() override {
+ return this;
+ }
+
+ WidgetGestureNotifyEvent(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget)
+ : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, eGestureNotifyEventClass),
+ mPanDirection(ePanNone),
+ mDisplayPanFeedback(false) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ // XXX Looks like this event is handled only in PostHandleEvent() of
+ // EventStateManager. Therefore, it might be possible to handle this
+ // in PreHandleEvent() and not to dispatch as a DOM event into the DOM
+ // tree like ContentQueryEvent. Then, this event doesn't need to
+ // support Duplicate().
+ MOZ_ASSERT(mClass == eGestureNotifyEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ WidgetGestureNotifyEvent* result =
+ new WidgetGestureNotifyEvent(false, mMessage, nullptr);
+ result->AssignGestureNotifyEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ typedef int8_t PanDirectionType;
+ enum PanDirection : PanDirectionType {
+ ePanNone,
+ ePanVertical,
+ ePanHorizontal,
+ ePanBoth
+ };
+
+ PanDirection mPanDirection;
+ bool mDisplayPanFeedback;
+
+ void AssignGestureNotifyEventData(const WidgetGestureNotifyEvent& aEvent,
+ bool aCopyTargets) {
+ AssignGUIEventData(aEvent, aCopyTargets);
+
+ mPanDirection = aEvent.mPanDirection;
+ mDisplayPanFeedback = aEvent.mDisplayPanFeedback;
+ }
+};
+
+/******************************************************************************
+ * mozilla::WidgetSimpleGestureEvent
+ ******************************************************************************/
+
+class WidgetSimpleGestureEvent : public WidgetMouseEventBase {
+ public:
+ virtual WidgetSimpleGestureEvent* AsSimpleGestureEvent() override {
+ return this;
+ }
+
+ WidgetSimpleGestureEvent(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget)
+ : WidgetMouseEventBase(aIsTrusted, aMessage, aWidget,
+ eSimpleGestureEventClass),
+ mAllowedDirections(0),
+ mDirection(0),
+ mClickCount(0),
+ mDelta(0.0) {}
+
+ WidgetSimpleGestureEvent(const WidgetSimpleGestureEvent& aOther)
+ : WidgetMouseEventBase(aOther.IsTrusted(), aOther.mMessage,
+ aOther.mWidget, eSimpleGestureEventClass),
+ mAllowedDirections(aOther.mAllowedDirections),
+ mDirection(aOther.mDirection),
+ mClickCount(0),
+ mDelta(aOther.mDelta) {}
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eSimpleGestureEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ WidgetSimpleGestureEvent* result =
+ new WidgetSimpleGestureEvent(false, mMessage, nullptr);
+ result->AssignSimpleGestureEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ // See SimpleGestureEvent.webidl for values
+ uint32_t mAllowedDirections;
+ // See SimpleGestureEvent.webidl for values
+ uint32_t mDirection;
+ // The number of taps for tap events
+ uint32_t mClickCount;
+ // Delta for magnify and rotate events
+ double mDelta;
+
+ // XXX Not tested by test_assign_event_data.html
+ void AssignSimpleGestureEventData(const WidgetSimpleGestureEvent& aEvent,
+ bool aCopyTargets) {
+ AssignMouseEventBaseData(aEvent, aCopyTargets);
+
+ // mAllowedDirections isn't copied
+ mDirection = aEvent.mDirection;
+ mDelta = aEvent.mDelta;
+ mClickCount = aEvent.mClickCount;
+ }
+};
+
+/******************************************************************************
+ * mozilla::WidgetTouchEvent
+ ******************************************************************************/
+
+class WidgetTouchEvent : public WidgetInputEvent {
+ public:
+ typedef nsTArray<RefPtr<mozilla::dom::Touch>> TouchArray;
+ typedef AutoTArray<RefPtr<mozilla::dom::Touch>, 10> AutoTouchArray;
+ typedef AutoTouchArray::base_type TouchArrayBase;
+
+ virtual WidgetTouchEvent* AsTouchEvent() override { return this; }
+
+ MOZ_COUNTED_DEFAULT_CTOR(WidgetTouchEvent)
+
+ WidgetTouchEvent(const WidgetTouchEvent& aOther)
+ : WidgetInputEvent(aOther.IsTrusted(), aOther.mMessage, aOther.mWidget,
+ eTouchEventClass) {
+ MOZ_COUNT_CTOR(WidgetTouchEvent);
+ mModifiers = aOther.mModifiers;
+ mTime = aOther.mTime;
+ mTimeStamp = aOther.mTimeStamp;
+ mTouches.AppendElements(aOther.mTouches);
+ mFlags.mCancelable = mMessage != eTouchCancel;
+ mFlags.mHandledByAPZ = aOther.mFlags.mHandledByAPZ;
+ }
+
+ WidgetTouchEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget)
+ : WidgetInputEvent(aIsTrusted, aMessage, aWidget, eTouchEventClass) {
+ MOZ_COUNT_CTOR(WidgetTouchEvent);
+ mFlags.mCancelable = mMessage != eTouchCancel;
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(WidgetTouchEvent)
+
+ virtual WidgetEvent* Duplicate() const override {
+ MOZ_ASSERT(mClass == eTouchEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ WidgetTouchEvent* result = new WidgetTouchEvent(false, mMessage, nullptr);
+ result->AssignTouchEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ TouchArray mTouches;
+
+ void AssignTouchEventData(const WidgetTouchEvent& aEvent, bool aCopyTargets) {
+ AssignInputEventData(aEvent, aCopyTargets);
+
+ // Assign*EventData() assume that they're called only new instance.
+ MOZ_ASSERT(mTouches.IsEmpty());
+ mTouches.AppendElements(aEvent.mTouches);
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_TouchEvents_h__
diff --git a/widget/TouchResampler.cpp b/widget/TouchResampler.cpp
new file mode 100644
index 0000000000..51974a4405
--- /dev/null
+++ b/widget/TouchResampler.cpp
@@ -0,0 +1,381 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "TouchResampler.h"
+
+#include "nsAlgorithm.h"
+
+/**
+ * TouchResampler implementation
+ */
+
+namespace mozilla {
+namespace widget {
+
+// The values below have been tested and found to be acceptable on a device
+// with a display refresh rate of 60Hz and touch sampling rate of 100Hz.
+// While their "ideal" values are dependent on the exact rates of each device,
+// the values we've picked below should be somewhat robust across a variation of
+// different rates. They mostly aim to avoid making predictions that are too far
+// away (in terms of distance) from the finger, and to detect pauses in the
+// finger motion without too much delay.
+
+// Maximum time between two consecutive data points to consider resampling
+// between them.
+// Values between 1x and 5x of the touch sampling interval are reasonable.
+static const double kTouchResampleWindowSize = 40.0;
+
+// These next two values constrain the sampling timestamp.
+// Our caller will usually adjust frame timestamps to be slightly in the past,
+// for example by 5ms. This means that, during normal operation, we will
+// maximally need to predict by [touch sampling rate] minus 5ms.
+// So we would like kTouchResampleMaxPredictMs to satisfy the following:
+// kTouchResampleMaxPredictMs + [frame time adjust] > [touch sampling rate]
+static const double kTouchResampleMaxPredictMs = 8.0;
+// This one is a protection against very outdated frame timestamps.
+// Values larger than the touch sampling interval and less than 3x of the vsync
+// interval are reasonable.
+static const double kTouchResampleMaxBacksampleMs = 20.0;
+
+// The maximum age of the most recent data point to consider resampling.
+// Should be between 1x and 3x of the touch sampling interval.
+static const double kTouchResampleOldTouchThresholdMs = 17.0;
+
+uint64_t TouchResampler::ProcessEvent(MultiTouchInput&& aInput) {
+ mCurrentTouches.UpdateFromEvent(aInput);
+
+ uint64_t eventId = mNextEventId;
+ mNextEventId++;
+
+ if (aInput.mType == MultiTouchInput::MULTITOUCH_MOVE) {
+ // Touch move events are deferred until NotifyFrame.
+ mDeferredTouchMoveEvents.push({std::move(aInput), eventId});
+ } else {
+ // Non-move events are transferred to the outgoing queue unmodified.
+ // If there are pending touch move events, flush those out first, so that
+ // events are emitted in the right order.
+ FlushDeferredTouchMoveEventsUnresampled();
+ if (mInResampledState) {
+ // Return to a non-resampled state before emitting a non-move event.
+ ReturnToNonResampledState();
+ }
+ EmitEvent(std::move(aInput), eventId);
+ }
+
+ return eventId;
+}
+
+void TouchResampler::NotifyFrame(const TimeStamp& aTimeStamp) {
+ TimeStamp lastTouchTime = mCurrentTouches.LatestDataPointTime();
+ if (mDeferredTouchMoveEvents.empty() ||
+ (lastTouchTime &&
+ lastTouchTime < aTimeStamp - TimeDuration::FromMilliseconds(
+ kTouchResampleOldTouchThresholdMs))) {
+ // We haven't received a touch move event in a while, so the fingers must
+ // have stopped moving. Flush any old touch move events.
+ FlushDeferredTouchMoveEventsUnresampled();
+
+ if (mInResampledState) {
+ // Make sure we pause at the resting position that we actually observed,
+ // and not at a resampled position.
+ ReturnToNonResampledState();
+ }
+
+ // Clear touch location history so that we don't resample across a pause.
+ mCurrentTouches.ClearDataPoints();
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(lastTouchTime);
+ TimeStamp lowerBound = lastTouchTime - TimeDuration::FromMilliseconds(
+ kTouchResampleMaxBacksampleMs);
+ TimeStamp upperBound = lastTouchTime + TimeDuration::FromMilliseconds(
+ kTouchResampleMaxPredictMs);
+ TimeStamp sampleTime = clamped(aTimeStamp, lowerBound, upperBound);
+
+ if (mLastEmittedEventTime && sampleTime < mLastEmittedEventTime) {
+ // Keep emitted timestamps in order.
+ sampleTime = mLastEmittedEventTime;
+ }
+
+ // We have at least one pending touch move event. Pick one of the events from
+ // mDeferredTouchMoveEvents as the base event for the resampling adjustment.
+ // We want to produce an event stream whose timestamps are in the right order.
+ // As the base event, use the first event that's at or after sampleTime,
+ // unless there is no such event, in that case use the last one we have. We
+ // will set the timestamp on the resampled event to sampleTime later.
+ // Flush out any older events so that everything remains in the right order.
+ MultiTouchInput input;
+ uint64_t eventId;
+ while (true) {
+ MOZ_RELEASE_ASSERT(!mDeferredTouchMoveEvents.empty());
+
+ std::tie(input, eventId) = std::move(mDeferredTouchMoveEvents.front());
+ mDeferredTouchMoveEvents.pop();
+ if (mDeferredTouchMoveEvents.empty() || input.mTimeStamp >= sampleTime) {
+ break;
+ }
+
+ // Flush this event to the outgoing queue without resampling. What ends up
+ // on the screen will still be smooth because we will proceed to emit a
+ // resampled event before the paint for this frame starts.
+ PrependLeftoverHistoricalData(&input);
+ MOZ_RELEASE_ASSERT(input.mTimeStamp < sampleTime);
+ EmitEvent(std::move(input), eventId);
+ }
+
+ mOriginalOfResampledTouchMove = Nothing();
+
+ // Compute the resampled touch positions.
+ nsTArray<ScreenIntPoint> resampledPositions;
+ bool anyPositionDifferentFromOriginal = false;
+ for (const auto& touch : input.mTouches) {
+ ScreenIntPoint resampledPosition =
+ mCurrentTouches.ResampleTouchPositionAtTime(
+ touch.mIdentifier, touch.mScreenPoint, sampleTime);
+ if (resampledPosition != touch.mScreenPoint) {
+ anyPositionDifferentFromOriginal = true;
+ }
+ resampledPositions.AppendElement(resampledPosition);
+ }
+
+ if (anyPositionDifferentFromOriginal) {
+ // Store a copy of the original event, so that we can return to an
+ // non-resampled position later, if necessary.
+ mOriginalOfResampledTouchMove = Some(input);
+
+ // Add the original observed position to the historical data, as well as any
+ // leftover historical positions from the previous touch move event, and
+ // store the resampled values in the "final" position of the event.
+ PrependLeftoverHistoricalData(&input);
+ for (size_t i = 0; i < input.mTouches.Length(); i++) {
+ auto& touch = input.mTouches[i];
+ touch.mHistoricalData.AppendElement(SingleTouchData::HistoricalTouchData{
+ input.mTimeStamp,
+ touch.mScreenPoint,
+ touch.mLocalScreenPoint,
+ touch.mRadius,
+ touch.mRotationAngle,
+ touch.mForce,
+ });
+
+ // Remove any historical touch data that's in the future, compared to
+ // sampleTime. This data will be included by upcoming touch move
+ // events. This only happens if the frame timestamp can be older than the
+ // event timestamp, i.e. if interpolation occurs (rather than
+ // extrapolation).
+ auto futureDataStart = std::find_if(
+ touch.mHistoricalData.begin(), touch.mHistoricalData.end(),
+ [sampleTime](
+ const SingleTouchData::HistoricalTouchData& aHistoricalData) {
+ return aHistoricalData.mTimeStamp > sampleTime;
+ });
+ if (futureDataStart != touch.mHistoricalData.end()) {
+ nsTArray<SingleTouchData::HistoricalTouchData> futureData(
+ Span<SingleTouchData::HistoricalTouchData>(touch.mHistoricalData)
+ .From(futureDataStart.GetIndex()));
+ touch.mHistoricalData.TruncateLength(futureDataStart.GetIndex());
+ mRemainingTouchData.insert({touch.mIdentifier, std::move(futureData)});
+ }
+
+ touch.mScreenPoint = resampledPositions[i];
+ }
+ input.mTimeStamp = sampleTime;
+ }
+
+ EmitEvent(std::move(input), eventId);
+ mInResampledState = anyPositionDifferentFromOriginal;
+}
+
+void TouchResampler::PrependLeftoverHistoricalData(MultiTouchInput* aInput) {
+ for (auto& touch : aInput->mTouches) {
+ auto leftoverData = mRemainingTouchData.find(touch.mIdentifier);
+ if (leftoverData != mRemainingTouchData.end()) {
+ nsTArray<SingleTouchData::HistoricalTouchData> data =
+ std::move(leftoverData->second);
+ mRemainingTouchData.erase(leftoverData);
+ touch.mHistoricalData.InsertElementsAt(0, data);
+ }
+
+ if (TimeStamp cutoffTime = mLastEmittedEventTime) {
+ // If we received historical touch data that was further in the past than
+ // the last resampled event, discard that data so that the touch data
+ // points are emitted in order.
+ touch.mHistoricalData.RemoveElementsBy(
+ [cutoffTime](const SingleTouchData::HistoricalTouchData& aTouchData) {
+ return aTouchData.mTimeStamp < cutoffTime;
+ });
+ }
+ }
+ mRemainingTouchData.clear();
+}
+
+void TouchResampler::FlushDeferredTouchMoveEventsUnresampled() {
+ while (!mDeferredTouchMoveEvents.empty()) {
+ MultiTouchInput input;
+ uint64_t eventId;
+ std::tie(input, eventId) = std::move(mDeferredTouchMoveEvents.front());
+ mDeferredTouchMoveEvents.pop();
+ PrependLeftoverHistoricalData(&input);
+ EmitEvent(std::move(input), eventId);
+ mInResampledState = false;
+ mOriginalOfResampledTouchMove = Nothing();
+ }
+}
+
+void TouchResampler::ReturnToNonResampledState() {
+ MOZ_RELEASE_ASSERT(mInResampledState);
+ MOZ_RELEASE_ASSERT(mDeferredTouchMoveEvents.empty(),
+ "Don't call this if there is a deferred touch move event. "
+ "We can return to the non-resampled state by sending that "
+ "event, rather than a copy of a previous event.");
+
+ // The last outgoing event was a resampled touch move event.
+ // Return to the non-resampled state, by sending a touch move event to
+ // "overwrite" any resampled positions with the original observed positions.
+ MultiTouchInput input = std::move(*mOriginalOfResampledTouchMove);
+ mOriginalOfResampledTouchMove = Nothing();
+
+ // For the event's timestamp, we want to backdate the correction as far as we
+ // can, while still preserving timestamp ordering. But we also don't want to
+ // backdate it to be older than it was originally.
+ if (mLastEmittedEventTime > input.mTimeStamp) {
+ input.mTimeStamp = mLastEmittedEventTime;
+ }
+
+ // Assemble the correct historical touch data for this event.
+ // We don't want to include data points that we've already sent out with the
+ // resampled event. And from the leftover data points, we only want those that
+ // don't duplicate the final time + position of this event.
+ for (auto& touch : input.mTouches) {
+ touch.mHistoricalData.Clear();
+ }
+ PrependLeftoverHistoricalData(&input);
+ for (auto& touch : input.mTouches) {
+ touch.mHistoricalData.RemoveElementsBy([&](const auto& histData) {
+ return histData.mTimeStamp >= input.mTimeStamp;
+ });
+ }
+
+ EmitExtraEvent(std::move(input));
+ mInResampledState = false;
+}
+
+void TouchResampler::TouchInfo::Update(const SingleTouchData& aTouch,
+ const TimeStamp& aEventTime) {
+ for (const auto& historicalData : aTouch.mHistoricalData) {
+ mBaseDataPoint = mLatestDataPoint;
+ mLatestDataPoint =
+ Some(DataPoint{historicalData.mTimeStamp, historicalData.mScreenPoint});
+ }
+ mBaseDataPoint = mLatestDataPoint;
+ mLatestDataPoint = Some(DataPoint{aEventTime, aTouch.mScreenPoint});
+}
+
+ScreenIntPoint TouchResampler::TouchInfo::ResampleAtTime(
+ const ScreenIntPoint& aLastObservedPosition, const TimeStamp& aTimeStamp) {
+ TimeStamp cutoff =
+ aTimeStamp - TimeDuration::FromMilliseconds(kTouchResampleWindowSize);
+ if (!mBaseDataPoint || !mLatestDataPoint ||
+ !(mBaseDataPoint->mTimeStamp < mLatestDataPoint->mTimeStamp) ||
+ mBaseDataPoint->mTimeStamp < cutoff) {
+ return aLastObservedPosition;
+ }
+
+ // For the actual resampling, connect the last two data points with a line and
+ // sample along that line.
+ TimeStamp t1 = mBaseDataPoint->mTimeStamp;
+ TimeStamp t2 = mLatestDataPoint->mTimeStamp;
+ double t = (aTimeStamp - t1) / (t2 - t1);
+
+ double x1 = mBaseDataPoint->mPosition.x;
+ double x2 = mLatestDataPoint->mPosition.x;
+ double y1 = mBaseDataPoint->mPosition.y;
+ double y2 = mLatestDataPoint->mPosition.y;
+
+ int32_t resampledX = round(x1 + t * (x2 - x1));
+ int32_t resampledY = round(y1 + t * (y2 - y1));
+ return ScreenIntPoint(resampledX, resampledY);
+}
+
+void TouchResampler::CurrentTouches::UpdateFromEvent(
+ const MultiTouchInput& aInput) {
+ switch (aInput.mType) {
+ case MultiTouchInput::MULTITOUCH_START: {
+ // A new touch has been added; make sure mTouches reflects the current
+ // touches in the event.
+ nsTArray<TouchInfo> newTouches;
+ for (const auto& touch : aInput.mTouches) {
+ const auto touchInfo = TouchByIdentifier(touch.mIdentifier);
+ if (touchInfo != mTouches.end()) {
+ // This is one of the existing touches.
+ newTouches.AppendElement(std::move(*touchInfo));
+ mTouches.RemoveElementAt(touchInfo);
+ } else {
+ // This is the new touch.
+ newTouches.AppendElement(TouchInfo{
+ touch.mIdentifier, Nothing(),
+ Some(DataPoint{aInput.mTimeStamp, touch.mScreenPoint})});
+ }
+ }
+ MOZ_ASSERT(mTouches.IsEmpty(), "Missing touch end before touch start?");
+ mTouches = std::move(newTouches);
+ break;
+ }
+
+ case MultiTouchInput::MULTITOUCH_MOVE: {
+ // The touches have moved.
+ // Add position information to the history data points.
+ for (const auto& touch : aInput.mTouches) {
+ const auto touchInfo = TouchByIdentifier(touch.mIdentifier);
+ MOZ_ASSERT(touchInfo != mTouches.end());
+ if (touchInfo != mTouches.end()) {
+ touchInfo->Update(touch, aInput.mTimeStamp);
+ }
+ }
+ mLatestDataPointTime = aInput.mTimeStamp;
+ break;
+ }
+
+ case MultiTouchInput::MULTITOUCH_END: {
+ // A touch has been removed.
+ MOZ_RELEASE_ASSERT(aInput.mTouches.Length() == 1);
+ const auto touchInfo = TouchByIdentifier(aInput.mTouches[0].mIdentifier);
+ MOZ_ASSERT(touchInfo != mTouches.end());
+ if (touchInfo != mTouches.end()) {
+ mTouches.RemoveElementAt(touchInfo);
+ }
+ break;
+ }
+
+ case MultiTouchInput::MULTITOUCH_CANCEL:
+ // All touches are canceled.
+ mTouches.Clear();
+ break;
+ }
+}
+
+nsTArray<TouchResampler::TouchInfo>::iterator
+TouchResampler::CurrentTouches::TouchByIdentifier(int32_t aIdentifier) {
+ return std::find_if(mTouches.begin(), mTouches.end(),
+ [aIdentifier](const TouchInfo& info) {
+ return info.mIdentifier == aIdentifier;
+ });
+}
+
+ScreenIntPoint TouchResampler::CurrentTouches::ResampleTouchPositionAtTime(
+ int32_t aIdentifier, const ScreenIntPoint& aLastObservedPosition,
+ const TimeStamp& aTimeStamp) {
+ const auto touchInfo = TouchByIdentifier(aIdentifier);
+ MOZ_ASSERT(touchInfo != mTouches.end());
+ if (touchInfo != mTouches.end()) {
+ return touchInfo->ResampleAtTime(aLastObservedPosition, aTimeStamp);
+ }
+ return aLastObservedPosition;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/TouchResampler.h b/widget/TouchResampler.h
new file mode 100644
index 0000000000..69f544b557
--- /dev/null
+++ b/widget/TouchResampler.h
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_TouchResampler_h
+#define mozilla_widget_TouchResampler_h
+
+#include <queue>
+#include <unordered_map>
+
+#include "mozilla/Maybe.h"
+#include "mozilla/TimeStamp.h"
+#include "InputData.h"
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * De-jitters touch motions by resampling (interpolating or extrapolating) touch
+ * positions for the vsync timestamp.
+ *
+ * Touch resampling improves the touch panning experience on devices where touch
+ * positions are sampled at a rate that's not an integer multiple of the display
+ * refresh rate, for example 100Hz touch sampling on a 60Hz display: Without
+ * resampling, we would alternate between taking one touch sample or two touch
+ * samples into account each frame, creating a jittery motion ("small step, big
+ * step, small step, big step").
+ * Intended for use on Android, where both touch events and vsync notifications
+ * arrive on the same thread, the Java UI thread.
+ * This class is not thread safe.
+ *
+ * TouchResampler operates in the following way:
+ *
+ * Original events are fed into ProcessEvent().
+ * Outgoing events (potentially resampled for resampling) are added to a queue
+ * and can be consumed by calling ConsumeOutgoingEvents(). Touch events which
+ * are not touch move events are forwarded instantly and not resampled. Only
+ * touch move events are resampled. Whenever a touch move event is received, it
+ * gets delayed until NotifyFrame() is called, at which point it is resampled
+ * into a resampled version for the given frame timestamp, and added to the
+ * outgoing queue. If no touch move event is received between two consecutive
+ * frames, this is treated as a stop in the touch motion. If the last outgoing
+ * event was an resampled touch move event, we return back to the non-resampled
+ * state by emitting a copy of the last original touch move event, which has
+ * unmodified position data. Touch events which are not touch move events also
+ * force a return to the non-resampled state before they are moved to the
+ * outgoing queue.
+ */
+class TouchResampler final {
+ public:
+ // Feed a touch event into the interpolater. Returns an ID that can be used to
+ // match outgoing events to this incoming event, to track data associated with
+ // this event.
+ uint64_t ProcessEvent(MultiTouchInput&& aInput);
+
+ // Emit events, potentially resampled, for this timestamp. The events are put
+ // into the outgoing queue. May not emit any events if there's no update.
+ void NotifyFrame(const TimeStamp& aTimeStamp);
+
+ // Returns true between the start and the end of a touch gesture. During this
+ // time, the caller should keep itself registered with the system frame
+ // callback mechanism, so that NotifyFrame() can be called on every frame.
+ // (Otherwise, if we only registered the callback after receiving a touch move
+ // event, the frame callback might be delayed by a full frame.)
+ bool InTouchingState() const { return mCurrentTouches.HasTouch(); }
+
+ struct OutgoingEvent {
+ // The event, potentially modified from the original for resampling.
+ MultiTouchInput mEvent;
+
+ // Some(eventId) if this event is a modified version of an original event,
+ // Nothing() if this is an extra event.
+ Maybe<uint64_t> mEventId;
+ };
+
+ // Returns the outgoing events that were produced since the last call.
+ // No event IDs will be skipped. Returns at least one outgoing event for each
+ // incoming event (possibly after a delay), and potential extra events with
+ // no originating event ID.
+ // Outgoing events should be consumed after every call to ProcessEvent() and
+ // after every call to NotifyFrame().
+ std::queue<OutgoingEvent> ConsumeOutgoingEvents() {
+ return std::move(mOutgoingEvents);
+ }
+
+ private:
+ // Add the event to the outgoing queue.
+ void EmitEvent(MultiTouchInput&& aInput, uint64_t aEventId) {
+ mLastEmittedEventTime = aInput.mTimeStamp;
+ mOutgoingEvents.push(OutgoingEvent{std::move(aInput), Some(aEventId)});
+ }
+
+ // Emit an event that does not correspond to an incoming event.
+ void EmitExtraEvent(MultiTouchInput&& aInput) {
+ mLastEmittedEventTime = aInput.mTimeStamp;
+ mOutgoingEvents.push(OutgoingEvent{std::move(aInput), Nothing()});
+ }
+
+ // Move any touch move events that we deferred for resampling to the outgoing
+ // queue unmodified, leaving mDeferredTouchMoveEvents empty.
+ void FlushDeferredTouchMoveEventsUnresampled();
+
+ // Must only be called if mInResampledState is true and
+ // mDeferredTouchMoveEvents is empty. Emits mOriginalOfResampledTouchMove,
+ // with a potentially adjusted timestamp for correct ordering.
+ void ReturnToNonResampledState();
+
+ // Takes historical touch data from mRemainingTouchData and prepends it to the
+ // data in aInput.
+ void PrependLeftoverHistoricalData(MultiTouchInput* aInput);
+
+ struct DataPoint {
+ TimeStamp mTimeStamp;
+ ScreenIntPoint mPosition;
+ };
+
+ struct TouchInfo {
+ void Update(const SingleTouchData& aTouch, const TimeStamp& aEventTime);
+ ScreenIntPoint ResampleAtTime(const ScreenIntPoint& aLastObservedPosition,
+ const TimeStamp& aTimeStamp);
+
+ int32_t mIdentifier = 0;
+ Maybe<DataPoint> mBaseDataPoint;
+ Maybe<DataPoint> mLatestDataPoint;
+ };
+
+ struct CurrentTouches {
+ void UpdateFromEvent(const MultiTouchInput& aInput);
+ bool HasTouch() const { return !mTouches.IsEmpty(); }
+ TimeStamp LatestDataPointTime() { return mLatestDataPointTime; }
+
+ ScreenIntPoint ResampleTouchPositionAtTime(
+ int32_t aIdentifier, const ScreenIntPoint& aLastObservedPosition,
+ const TimeStamp& aTimeStamp);
+
+ void ClearDataPoints() {
+ for (auto& touch : mTouches) {
+ touch.mBaseDataPoint = Nothing();
+ touch.mLatestDataPoint = Nothing();
+ }
+ }
+
+ private:
+ nsTArray<TouchInfo>::iterator TouchByIdentifier(int32_t aIdentifier);
+
+ nsTArray<TouchInfo> mTouches;
+ TimeStamp mLatestDataPointTime;
+ };
+
+ // The current touch positions with historical data points. This data only
+ // contains original non-resampled positions from the incoming touch events.
+ CurrentTouches mCurrentTouches;
+
+ // Incoming touch move events are stored here until NotifyFrame is called.
+ std::queue<std::pair<MultiTouchInput, uint64_t>> mDeferredTouchMoveEvents;
+
+ // Stores any touch samples that were not included in the last emitted touch
+ // move event because they were in the future compared to the emitted event's
+ // timestamp. These data points should be prepended to the historical data of
+ // the next emitted touch move evnt.
+ // Can only be non-empty if mInResampledState is true.
+ std::unordered_map<int32_t, nsTArray<SingleTouchData::HistoricalTouchData>>
+ mRemainingTouchData;
+
+ // If we're in an resampled state, because the last outgoing event was a
+ // resampled touch move event, then this contains a copy of the unresampled,
+ // original touch move event.
+ // Some() iff mInResampledState is true.
+ Maybe<MultiTouchInput> mOriginalOfResampledTouchMove;
+
+ // The stream of outgoing events that can be consumed by our caller.
+ std::queue<OutgoingEvent> mOutgoingEvents;
+
+ // The timestamp of the event that was emitted most recently, or the null
+ // timestamp if no event has been emitted yet.
+ TimeStamp mLastEmittedEventTime;
+
+ uint64_t mNextEventId = 0;
+
+ // True if the last outgoing event was a touch move event with an resampled
+ // position. We only want to stay in this state as long as a continuous stream
+ // of touch move events is coming in.
+ bool mInResampledState = false;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_TouchResampler_h
diff --git a/widget/VsyncDispatcher.cpp b/widget/VsyncDispatcher.cpp
new file mode 100644
index 0000000000..ccb7151ee7
--- /dev/null
+++ b/widget/VsyncDispatcher.cpp
@@ -0,0 +1,196 @@
+/* -*- 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 "MainThreadUtils.h"
+#include "VsyncDispatcher.h"
+#include "VsyncSource.h"
+#include "gfxPlatform.h"
+#include "mozilla/layers/Compositor.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+
+using namespace mozilla::layers;
+
+namespace mozilla {
+
+CompositorVsyncDispatcher::CompositorVsyncDispatcher()
+ : mVsyncSource(gfxPlatform::GetPlatform()->GetHardwareVsync()),
+ mCompositorObserverLock("CompositorObserverLock"),
+ mDidShutdown(false) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mVsyncSource->RegisterCompositorVsyncDispatcher(this);
+}
+
+CompositorVsyncDispatcher::CompositorVsyncDispatcher(
+ RefPtr<gfx::VsyncSource> aVsyncSource)
+ : mVsyncSource(std::move(aVsyncSource)),
+ mCompositorObserverLock("CompositorObserverLock"),
+ mDidShutdown(false) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mVsyncSource->RegisterCompositorVsyncDispatcher(this);
+}
+
+CompositorVsyncDispatcher::~CompositorVsyncDispatcher() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // We auto remove this vsync dispatcher from the vsync source in the
+ // nsBaseWidget
+}
+
+void CompositorVsyncDispatcher::NotifyVsync(const VsyncEvent& aVsync) {
+ // In vsync thread
+ layers::CompositorBridgeParent::PostInsertVsyncProfilerMarker(aVsync.mTime);
+
+ MutexAutoLock lock(mCompositorObserverLock);
+ if (mCompositorVsyncObserver) {
+ mCompositorVsyncObserver->NotifyVsync(aVsync);
+ }
+}
+
+void CompositorVsyncDispatcher::MoveToSource(
+ const RefPtr<gfx::VsyncSource>& aVsyncSource) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(XRE_IsParentProcess());
+ mVsyncSource = aVsyncSource;
+}
+
+void CompositorVsyncDispatcher::ObserveVsync(bool aEnable) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (mDidShutdown) {
+ return;
+ }
+
+ if (aEnable) {
+ mVsyncSource->EnableCompositorVsyncDispatcher(this);
+ } else {
+ mVsyncSource->DisableCompositorVsyncDispatcher(this);
+ }
+}
+
+void CompositorVsyncDispatcher::SetCompositorVsyncObserver(
+ VsyncObserver* aVsyncObserver) {
+ // When remote compositing or running gtests, vsync observation is
+ // initiated on the main thread. Otherwise, it is initiated from the
+ // compositor thread.
+ MOZ_ASSERT(NS_IsMainThread() ||
+ CompositorThreadHolder::IsInCompositorThread());
+
+ { // scope lock
+ MutexAutoLock lock(mCompositorObserverLock);
+ mCompositorVsyncObserver = aVsyncObserver;
+ }
+
+ bool observeVsync = aVsyncObserver != nullptr;
+ nsCOMPtr<nsIRunnable> vsyncControl = NewRunnableMethod<bool>(
+ "CompositorVsyncDispatcher::ObserveVsync", this,
+ &CompositorVsyncDispatcher::ObserveVsync, observeVsync);
+ NS_DispatchToMainThread(vsyncControl);
+}
+
+void CompositorVsyncDispatcher::Shutdown() {
+ // Need to explicitly remove CompositorVsyncDispatcher when the nsBaseWidget
+ // shuts down. Otherwise, we would get dead vsync notifications between when
+ // the nsBaseWidget shuts down and the CompositorBridgeParent shuts down.
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mDidShutdown);
+ ObserveVsync(false);
+ mDidShutdown = true;
+ { // scope lock
+ MutexAutoLock lock(mCompositorObserverLock);
+ mCompositorVsyncObserver = nullptr;
+ }
+ mVsyncSource->DeregisterCompositorVsyncDispatcher(this);
+ mVsyncSource = nullptr;
+}
+
+RefreshTimerVsyncDispatcher::RefreshTimerVsyncDispatcher(
+ gfx::VsyncSource::Display* aDisplay)
+ : mDisplay(aDisplay), mRefreshTimersLock("RefreshTimers lock") {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+RefreshTimerVsyncDispatcher::~RefreshTimerVsyncDispatcher() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+void RefreshTimerVsyncDispatcher::MoveToDisplay(
+ gfx::VsyncSource::Display* aDisplay) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mDisplay = aDisplay;
+}
+
+void RefreshTimerVsyncDispatcher::NotifyVsync(const VsyncEvent& aVsync) {
+ MutexAutoLock lock(mRefreshTimersLock);
+
+ for (size_t i = 0; i < mChildRefreshTimers.Length(); i++) {
+ mChildRefreshTimers[i]->NotifyVsync(aVsync);
+ }
+
+ if (mParentRefreshTimer) {
+ mParentRefreshTimer->NotifyVsync(aVsync);
+ }
+}
+
+void RefreshTimerVsyncDispatcher::SetParentRefreshTimer(
+ VsyncObserver* aVsyncObserver) {
+ MOZ_ASSERT(NS_IsMainThread());
+ { // lock scope because UpdateVsyncStatus runs on main thread and will
+ // deadlock
+ MutexAutoLock lock(mRefreshTimersLock);
+ mParentRefreshTimer = aVsyncObserver;
+ }
+
+ UpdateVsyncStatus();
+}
+
+void RefreshTimerVsyncDispatcher::AddChildRefreshTimer(
+ VsyncObserver* aVsyncObserver) {
+ { // scope lock - called on pbackground thread
+ MutexAutoLock lock(mRefreshTimersLock);
+ MOZ_ASSERT(aVsyncObserver);
+ if (!mChildRefreshTimers.Contains(aVsyncObserver)) {
+ mChildRefreshTimers.AppendElement(aVsyncObserver);
+ }
+ }
+
+ UpdateVsyncStatus();
+}
+
+void RefreshTimerVsyncDispatcher::RemoveChildRefreshTimer(
+ VsyncObserver* aVsyncObserver) {
+ { // scope lock - called on pbackground thread
+ MutexAutoLock lock(mRefreshTimersLock);
+ MOZ_ASSERT(aVsyncObserver);
+ mChildRefreshTimers.RemoveElement(aVsyncObserver);
+ }
+
+ UpdateVsyncStatus();
+}
+
+void RefreshTimerVsyncDispatcher::UpdateVsyncStatus() {
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(NewRunnableMethod(
+ "RefreshTimerVsyncDispatcher::UpdateVsyncStatus", this,
+ &RefreshTimerVsyncDispatcher::UpdateVsyncStatus));
+ return;
+ }
+
+ mDisplay->NotifyRefreshTimerVsyncStatus(NeedsVsync());
+}
+
+bool RefreshTimerVsyncDispatcher::NeedsVsync() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MutexAutoLock lock(mRefreshTimersLock);
+ return (mParentRefreshTimer != nullptr) || !mChildRefreshTimers.IsEmpty();
+}
+
+} // namespace mozilla
diff --git a/widget/VsyncDispatcher.h b/widget/VsyncDispatcher.h
new file mode 100644
index 0000000000..bdaf11e699
--- /dev/null
+++ b/widget/VsyncDispatcher.h
@@ -0,0 +1,111 @@
+/* -*- 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_VsyncDispatcher_h
+#define mozilla_widget_VsyncDispatcher_h
+
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+#include "mozilla/RefPtr.h"
+#include "VsyncSource.h"
+
+namespace mozilla {
+
+class VsyncObserver {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncObserver)
+
+ public:
+ // The method called when a vsync occurs. Return true if some work was done.
+ // In general, this vsync notification will occur on the hardware vsync
+ // thread from VsyncSource. But it might also be called on PVsync ipc thread
+ // if this notification is cross process. Thus all observer should check the
+ // thread model before handling the real task.
+ virtual bool NotifyVsync(const VsyncEvent& aVsync) = 0;
+
+ protected:
+ VsyncObserver() = default;
+ virtual ~VsyncObserver() = default;
+}; // VsyncObserver
+
+// Used to dispatch vsync events in the parent process to compositors.
+//
+// When the compositor is in-process, CompositorWidgets own a
+// CompositorVsyncDispatcher, and directly attach the compositor's observer
+// to it.
+//
+// When the compositor is out-of-process, the CompositorWidgetDelegate owns
+// the vsync dispatcher instead. The widget receives vsync observer/unobserve
+// commands via IPDL, and uses this to attach a CompositorWidgetVsyncObserver.
+// This observer forwards vsync notifications (on the vsync thread) to a
+// dedicated vsync I/O thread, which then forwards the notification to the
+// compositor thread in the compositor process.
+class CompositorVsyncDispatcher final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorVsyncDispatcher)
+
+ public:
+ CompositorVsyncDispatcher();
+ explicit CompositorVsyncDispatcher(RefPtr<gfx::VsyncSource> aVsyncSource);
+
+ // Called on the vsync thread when a hardware vsync occurs
+ void NotifyVsync(const VsyncEvent& aVsync);
+
+ void MoveToSource(const RefPtr<gfx::VsyncSource>& aVsyncSource);
+
+ // Compositor vsync observers must be added/removed on the compositor thread
+ void SetCompositorVsyncObserver(VsyncObserver* aVsyncObserver);
+ void Shutdown();
+
+ private:
+ virtual ~CompositorVsyncDispatcher();
+ void ObserveVsync(bool aEnable);
+
+ RefPtr<gfx::VsyncSource> mVsyncSource;
+ Mutex mCompositorObserverLock;
+ RefPtr<VsyncObserver> mCompositorVsyncObserver;
+ bool mDidShutdown;
+};
+
+// Dispatch vsync event to ipc actor parent and chrome RefreshTimer.
+class RefreshTimerVsyncDispatcher final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefreshTimerVsyncDispatcher)
+
+ public:
+ explicit RefreshTimerVsyncDispatcher(gfx::VsyncSource::Display* aDisplay);
+
+ // Please check CompositorVsyncDispatcher::NotifyVsync().
+ void NotifyVsync(const VsyncEvent& aVsync);
+
+ void MoveToDisplay(gfx::VsyncSource::Display* aDisplay);
+
+ // Set chrome process's RefreshTimer to this dispatcher.
+ // This function can be called from any thread.
+ void SetParentRefreshTimer(VsyncObserver* aVsyncObserver);
+
+ // Add or remove the content process' RefreshTimer to this dispatcher. This
+ // will be a no-op for AddChildRefreshTimer() if the observer is already
+ // registered.
+ // These functions can be called from any thread.
+ void AddChildRefreshTimer(VsyncObserver* aVsyncObserver);
+ void RemoveChildRefreshTimer(VsyncObserver* aVsyncObserver);
+
+ private:
+ virtual ~RefreshTimerVsyncDispatcher();
+ void UpdateVsyncStatus();
+ bool NeedsVsync();
+
+ // We need to hold a weak ref to the display we belong to in order to notify
+ // it of our vsync requirement. The display holds a RefPtr to us, so we can't
+ // hold a RefPtr back without causing a cyclic dependency.
+ gfx::VsyncSource::Display* mDisplay;
+ Mutex mRefreshTimersLock;
+ RefPtr<VsyncObserver> mParentRefreshTimer;
+ nsTArray<RefPtr<VsyncObserver>> mChildRefreshTimers;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_widget_VsyncDispatcher_h
diff --git a/widget/WidgetEventImpl.cpp b/widget/WidgetEventImpl.cpp
new file mode 100644
index 0000000000..1a41d17c8d
--- /dev/null
+++ b/widget/WidgetEventImpl.cpp
@@ -0,0 +1,1862 @@
+/* -*- 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/BasicEvents.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_mousewheel.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/dom/KeyboardEventBinding.h"
+#include "nsCommandParams.h"
+#include "nsContentUtils.h"
+#include "nsIContent.h"
+#include "nsIDragSession.h"
+#include "nsPrintfCString.h"
+
+#if defined(XP_WIN)
+# include "npapi.h"
+# include "WinUtils.h"
+#endif
+
+namespace mozilla {
+
+/******************************************************************************
+ * Global helper methods
+ ******************************************************************************/
+
+const char* ToChar(EventMessage aEventMessage) {
+ switch (aEventMessage) {
+#define NS_EVENT_MESSAGE(aMessage) \
+ case aMessage: \
+ return #aMessage;
+
+#include "mozilla/EventMessageList.h"
+
+#undef NS_EVENT_MESSAGE
+ default:
+ return "illegal event message";
+ }
+}
+
+const char* ToChar(EventClassID aEventClassID) {
+ switch (aEventClassID) {
+#define NS_ROOT_EVENT_CLASS(aPrefix, aName) \
+ case eBasic##aName##Class: \
+ return "eBasic" #aName "Class";
+
+#define NS_EVENT_CLASS(aPrefix, aName) \
+ case e##aName##Class: \
+ return "e" #aName "Class";
+
+#include "mozilla/EventClassList.h"
+
+#undef NS_EVENT_CLASS
+#undef NS_ROOT_EVENT_CLASS
+ default:
+ return "illegal event class ID";
+ }
+}
+
+const nsCString ToString(KeyNameIndex aKeyNameIndex) {
+ if (aKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
+ return "USE_STRING"_ns;
+ }
+ nsAutoString keyName;
+ WidgetKeyboardEvent::GetDOMKeyName(aKeyNameIndex, keyName);
+ return NS_ConvertUTF16toUTF8(keyName);
+}
+
+const nsCString ToString(CodeNameIndex aCodeNameIndex) {
+ if (aCodeNameIndex == CODE_NAME_INDEX_USE_STRING) {
+ return "USE_STRING"_ns;
+ }
+ nsAutoString codeName;
+ WidgetKeyboardEvent::GetDOMCodeName(aCodeNameIndex, codeName);
+ return NS_ConvertUTF16toUTF8(codeName);
+}
+
+const char* ToChar(Command aCommand) {
+ if (aCommand == Command::DoNothing) {
+ return "CommandDoNothing";
+ }
+
+ switch (aCommand) {
+#define NS_DEFINE_COMMAND(aName, aCommandStr) \
+ case Command::aName: \
+ return "Command::" #aName;
+#define NS_DEFINE_COMMAND_WITH_PARAM(aName, aCommandStr, aParam) \
+ case Command::aName: \
+ return "Command::" #aName;
+#define NS_DEFINE_COMMAND_NO_EXEC_COMMAND(aName) \
+ case Command::aName: \
+ return "Command::" #aName;
+
+#include "mozilla/CommandList.h"
+
+#undef NS_DEFINE_COMMAND
+#undef NS_DEFINE_COMMAND_WITH_PARAM
+#undef NS_DEFINE_COMMAND_NO_EXEC_COMMAND
+
+ default:
+ return "illegal command value";
+ }
+}
+
+const nsCString GetDOMKeyCodeName(uint32_t aKeyCode) {
+ switch (aKeyCode) {
+#define NS_DISALLOW_SAME_KEYCODE
+#define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \
+ case aDOMKeyCode: \
+ return nsLiteralCString(#aDOMKeyName);
+
+#include "mozilla/VirtualKeyCodeList.h"
+
+#undef NS_DEFINE_VK
+#undef NS_DISALLOW_SAME_KEYCODE
+
+ default:
+ return nsPrintfCString("Invalid DOM keyCode (0x%08X)", aKeyCode);
+ }
+}
+
+bool IsValidRawTextRangeValue(RawTextRangeType aRawTextRangeType) {
+ switch (static_cast<TextRangeType>(aRawTextRangeType)) {
+ case TextRangeType::eUninitialized:
+ case TextRangeType::eCaret:
+ case TextRangeType::eRawClause:
+ case TextRangeType::eSelectedRawClause:
+ case TextRangeType::eConvertedClause:
+ case TextRangeType::eSelectedClause:
+ return true;
+ default:
+ return false;
+ }
+}
+
+RawTextRangeType ToRawTextRangeType(TextRangeType aTextRangeType) {
+ return static_cast<RawTextRangeType>(aTextRangeType);
+}
+
+TextRangeType ToTextRangeType(RawTextRangeType aRawTextRangeType) {
+ MOZ_ASSERT(IsValidRawTextRangeValue(aRawTextRangeType));
+ return static_cast<TextRangeType>(aRawTextRangeType);
+}
+
+const char* ToChar(TextRangeType aTextRangeType) {
+ switch (aTextRangeType) {
+ case TextRangeType::eUninitialized:
+ return "TextRangeType::eUninitialized";
+ case TextRangeType::eCaret:
+ return "TextRangeType::eCaret";
+ case TextRangeType::eRawClause:
+ return "TextRangeType::eRawClause";
+ case TextRangeType::eSelectedRawClause:
+ return "TextRangeType::eSelectedRawClause";
+ case TextRangeType::eConvertedClause:
+ return "TextRangeType::eConvertedClause";
+ case TextRangeType::eSelectedClause:
+ return "TextRangeType::eSelectedClause";
+ default:
+ return "Invalid TextRangeType";
+ }
+}
+
+SelectionType ToSelectionType(TextRangeType aTextRangeType) {
+ switch (aTextRangeType) {
+ case TextRangeType::eRawClause:
+ return SelectionType::eIMERawClause;
+ case TextRangeType::eSelectedRawClause:
+ return SelectionType::eIMESelectedRawClause;
+ case TextRangeType::eConvertedClause:
+ return SelectionType::eIMEConvertedClause;
+ case TextRangeType::eSelectedClause:
+ return SelectionType::eIMESelectedClause;
+ default:
+ MOZ_CRASH("TextRangeType is invalid");
+ return SelectionType::eNormal;
+ }
+}
+
+/******************************************************************************
+ * non class method implementation
+ ******************************************************************************/
+
+static nsDataHashtable<nsDepCharHashKey, Command>* sCommandHashtable = nullptr;
+
+Command GetInternalCommand(const char* aCommandName,
+ const nsCommandParams* aCommandParams) {
+ if (!aCommandName) {
+ return Command::DoNothing;
+ }
+
+ // Special cases for "cmd_align". It's mapped to multiple internal commands
+ // with additional param. Therefore, we cannot handle it with the hashtable.
+ if (!strcmp(aCommandName, "cmd_align")) {
+ if (!aCommandParams) {
+ // Note that if this is called by EditorCommand::IsCommandEnabled(),
+ // it cannot set aCommandParams. So, don't warn in this case even though
+ // this is illegal case for DoCommandParams().
+ return Command::FormatJustify;
+ }
+ nsAutoCString cValue;
+ nsresult rv = aCommandParams->GetCString("state_attribute", cValue);
+ if (NS_FAILED(rv)) {
+ nsString value; // Avoid copying the string buffer with using nsString.
+ rv = aCommandParams->GetString("state_attribute", value);
+ if (NS_FAILED(rv)) {
+ return Command::FormatJustifyNone;
+ }
+ CopyUTF16toUTF8(value, cValue);
+ }
+ if (cValue.LowerCaseEqualsASCII("left")) {
+ return Command::FormatJustifyLeft;
+ }
+ if (cValue.LowerCaseEqualsASCII("right")) {
+ return Command::FormatJustifyRight;
+ }
+ if (cValue.LowerCaseEqualsASCII("center")) {
+ return Command::FormatJustifyCenter;
+ }
+ if (cValue.LowerCaseEqualsASCII("justify")) {
+ return Command::FormatJustifyFull;
+ }
+ if (cValue.IsEmpty()) {
+ return Command::FormatJustifyNone;
+ }
+ return Command::DoNothing;
+ }
+
+ if (!sCommandHashtable) {
+ sCommandHashtable = new nsDataHashtable<nsDepCharHashKey, Command>();
+#define NS_DEFINE_COMMAND(aName, aCommandStr) \
+ sCommandHashtable->Put(#aCommandStr, Command::aName);
+
+#define NS_DEFINE_COMMAND_WITH_PARAM(aName, aCommandStr, aParam)
+
+#define NS_DEFINE_COMMAND_NO_EXEC_COMMAND(aName)
+
+#include "mozilla/CommandList.h"
+
+#undef NS_DEFINE_COMMAND
+#undef NS_DEFINE_COMMAND_WITH_PARAM
+#undef NS_DEFINE_COMMAND_NO_EXEC_COMMAND
+ }
+ Command command = Command::DoNothing;
+ if (!sCommandHashtable->Get(aCommandName, &command)) {
+ return Command::DoNothing;
+ }
+ return command;
+}
+
+/******************************************************************************
+ * As*Event() implementation
+ ******************************************************************************/
+
+#define NS_ROOT_EVENT_CLASS(aPrefix, aName)
+#define NS_EVENT_CLASS(aPrefix, aName) \
+ aPrefix##aName* WidgetEvent::As##aName() { return nullptr; } \
+ \
+ const aPrefix##aName* WidgetEvent::As##aName() const { \
+ return const_cast<WidgetEvent*>(this)->As##aName(); \
+ }
+
+#include "mozilla/EventClassList.h"
+
+#undef NS_EVENT_CLASS
+#undef NS_ROOT_EVENT_CLASS
+
+/******************************************************************************
+ * mozilla::WidgetEvent
+ *
+ * Event struct type checking methods.
+ ******************************************************************************/
+
+bool WidgetEvent::IsQueryContentEvent() const {
+ return mClass == eQueryContentEventClass;
+}
+
+bool WidgetEvent::IsSelectionEvent() const {
+ return mClass == eSelectionEventClass;
+}
+
+bool WidgetEvent::IsContentCommandEvent() const {
+ return mClass == eContentCommandEventClass;
+}
+
+/******************************************************************************
+ * mozilla::WidgetEvent
+ *
+ * Event message checking methods.
+ ******************************************************************************/
+
+bool WidgetEvent::HasMouseEventMessage() const {
+ switch (mMessage) {
+ case eMouseDown:
+ case eMouseUp:
+ case eMouseClick:
+ case eMouseDoubleClick:
+ case eMouseAuxClick:
+ case eMouseEnterIntoWidget:
+ case eMouseExitFromWidget:
+ case eMouseActivate:
+ case eMouseOver:
+ case eMouseOut:
+ case eMouseHitTest:
+ case eMouseMove:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool WidgetEvent::HasDragEventMessage() const {
+ switch (mMessage) {
+ case eDragEnter:
+ case eDragOver:
+ case eDragExit:
+ case eDrag:
+ case eDragEnd:
+ case eDragStart:
+ case eDrop:
+ case eDragLeave:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* static */
+bool WidgetEvent::IsKeyEventMessage(EventMessage aMessage) {
+ switch (aMessage) {
+ case eKeyDown:
+ case eKeyPress:
+ case eKeyUp:
+ case eKeyDownOnPlugin:
+ case eKeyUpOnPlugin:
+ case eAccessKeyNotFound:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool WidgetEvent::HasIMEEventMessage() const {
+ switch (mMessage) {
+ case eCompositionStart:
+ case eCompositionEnd:
+ case eCompositionUpdate:
+ case eCompositionChange:
+ case eCompositionCommitAsIs:
+ case eCompositionCommit:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool WidgetEvent::HasPluginActivationEventMessage() const {
+ return mMessage == ePluginActivate || mMessage == ePluginFocus;
+}
+
+/******************************************************************************
+ * mozilla::WidgetEvent
+ *
+ * Specific event checking methods.
+ ******************************************************************************/
+
+bool WidgetEvent::CanBeSentToRemoteProcess() const {
+ // If this event is explicitly marked as shouldn't be sent to remote process,
+ // just return false.
+ if (IsCrossProcessForwardingStopped()) {
+ return false;
+ }
+
+ if (mClass == eKeyboardEventClass || mClass == eWheelEventClass) {
+ return true;
+ }
+
+ switch (mMessage) {
+ case eMouseDown:
+ case eMouseUp:
+ case eMouseMove:
+ case eContextMenu:
+ case eMouseEnterIntoWidget:
+ case eMouseExitFromWidget:
+ case eMouseTouchDrag:
+ case eTouchStart:
+ case eTouchMove:
+ case eTouchEnd:
+ case eTouchCancel:
+ case eDragOver:
+ case eDragExit:
+ case eDrop:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool WidgetEvent::WillBeSentToRemoteProcess() const {
+ // This event won't be posted to remote process if it's already explicitly
+ // stopped.
+ if (IsCrossProcessForwardingStopped()) {
+ return false;
+ }
+
+ // When mOriginalTarget is nullptr, this method shouldn't be used.
+ if (NS_WARN_IF(!mOriginalTarget)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIContent> originalTarget = do_QueryInterface(mOriginalTarget);
+ return EventStateManager::IsTopLevelRemoteTarget(originalTarget);
+}
+
+bool WidgetEvent::IsIMERelatedEvent() const {
+ return HasIMEEventMessage() || IsQueryContentEvent() || IsSelectionEvent();
+}
+
+bool WidgetEvent::IsUsingCoordinates() const {
+ const WidgetMouseEvent* mouseEvent = AsMouseEvent();
+ if (mouseEvent) {
+ return !mouseEvent->IsContextMenuKeyEvent();
+ }
+ return !HasKeyEventMessage() && !IsIMERelatedEvent() &&
+ !HasPluginActivationEventMessage() && !IsContentCommandEvent();
+}
+
+bool WidgetEvent::IsTargetedAtFocusedWindow() const {
+ const WidgetMouseEvent* mouseEvent = AsMouseEvent();
+ if (mouseEvent) {
+ return mouseEvent->IsContextMenuKeyEvent();
+ }
+ return HasKeyEventMessage() || IsIMERelatedEvent() || IsContentCommandEvent();
+}
+
+bool WidgetEvent::IsTargetedAtFocusedContent() const {
+ const WidgetMouseEvent* mouseEvent = AsMouseEvent();
+ if (mouseEvent) {
+ return mouseEvent->IsContextMenuKeyEvent();
+ }
+ return HasKeyEventMessage() || IsIMERelatedEvent();
+}
+
+bool WidgetEvent::IsAllowedToDispatchDOMEvent() const {
+ switch (mClass) {
+ case eMouseEventClass:
+ if (mMessage == eMouseTouchDrag) {
+ return false;
+ }
+ [[fallthrough]];
+ case ePointerEventClass:
+ // We want synthesized mouse moves to cause mouseover and mouseout
+ // DOM events (EventStateManager::PreHandleEvent), but not mousemove
+ // DOM events.
+ // Synthesized button up events also do not cause DOM events because they
+ // do not have a reliable mRefPoint.
+ return AsMouseEvent()->mReason == WidgetMouseEvent::eReal;
+
+ case eWheelEventClass: {
+ // wheel event whose all delta values are zero by user pref applied, it
+ // shouldn't cause a DOM event.
+ const WidgetWheelEvent* wheelEvent = AsWheelEvent();
+ return wheelEvent->mDeltaX != 0.0 || wheelEvent->mDeltaY != 0.0 ||
+ wheelEvent->mDeltaZ != 0.0;
+ }
+ case eTouchEventClass:
+ return mMessage != eTouchPointerCancel;
+ // Following events are handled in EventStateManager, so, we don't need to
+ // dispatch DOM event for them into the DOM tree.
+ case eQueryContentEventClass:
+ case eSelectionEventClass:
+ case eContentCommandEventClass:
+ return false;
+
+ default:
+ return true;
+ }
+}
+
+bool WidgetEvent::IsAllowedToDispatchInSystemGroup() const {
+ // We don't expect to implement default behaviors with pointer events because
+ // if we do, prevent default on mouse events can't prevent default behaviors
+ // anymore.
+ return mClass != ePointerEventClass;
+}
+
+bool WidgetEvent::IsBlockedForFingerprintingResistance() const {
+ if (!nsContentUtils::ShouldResistFingerprinting()) {
+ return false;
+ }
+
+ switch (mClass) {
+ case eKeyboardEventClass: {
+ const WidgetKeyboardEvent* keyboardEvent = AsKeyboardEvent();
+
+ return (keyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_Alt ||
+ keyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_Shift ||
+ keyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_Control ||
+ keyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_AltGraph);
+ }
+ case ePointerEventClass: {
+ const WidgetPointerEvent* pointerEvent = AsPointerEvent();
+
+ // We suppress the pointer events if it is not primary for fingerprinting
+ // resistance. It is because of that we want to spoof any pointer event
+ // into a mouse pointer event and the mouse pointer event only has
+ // isPrimary as true.
+ return !pointerEvent->mIsPrimary;
+ }
+ default:
+ return false;
+ }
+}
+
+/******************************************************************************
+ * mozilla::WidgetEvent
+ *
+ * Misc methods.
+ ******************************************************************************/
+
+static dom::EventTarget* GetTargetForDOMEvent(dom::EventTarget* aTarget) {
+ return aTarget ? aTarget->GetTargetForDOMEvent() : nullptr;
+}
+
+dom::EventTarget* WidgetEvent::GetDOMEventTarget() const {
+ return GetTargetForDOMEvent(mTarget);
+}
+
+dom::EventTarget* WidgetEvent::GetCurrentDOMEventTarget() const {
+ return GetTargetForDOMEvent(mCurrentTarget);
+}
+
+dom::EventTarget* WidgetEvent::GetOriginalDOMEventTarget() const {
+ if (mOriginalTarget) {
+ return GetTargetForDOMEvent(mOriginalTarget);
+ }
+ return GetDOMEventTarget();
+}
+
+void WidgetEvent::PreventDefault(bool aCalledByDefaultHandler,
+ nsIPrincipal* aPrincipal) {
+ if (mMessage == ePointerDown) {
+ if (aCalledByDefaultHandler) {
+ // Shouldn't prevent default on pointerdown by default handlers to stop
+ // firing legacy mouse events. Use MOZ_ASSERT to catch incorrect usages
+ // in debug builds.
+ MOZ_ASSERT(false);
+ return;
+ }
+ if (aPrincipal) {
+ nsAutoString addonId;
+ Unused << NS_WARN_IF(NS_FAILED(aPrincipal->GetAddonId(addonId)));
+ if (!addonId.IsEmpty()) {
+ // Ignore the case that it's called by a web extension.
+ return;
+ }
+ }
+ }
+ mFlags.PreventDefault(aCalledByDefaultHandler);
+}
+
+bool WidgetEvent::IsUserAction() const {
+ if (!IsTrusted()) {
+ return false;
+ }
+ // FYI: eMouseScrollEventClass and ePointerEventClass represent
+ // user action but they are synthesized events.
+ switch (mClass) {
+ case eKeyboardEventClass:
+ case eCompositionEventClass:
+ case eMouseScrollEventClass:
+ case eWheelEventClass:
+ case eGestureNotifyEventClass:
+ case eSimpleGestureEventClass:
+ case eTouchEventClass:
+ case eCommandEventClass:
+ case eContentCommandEventClass:
+ return true;
+ case eMouseEventClass:
+ case eDragEventClass:
+ case ePointerEventClass:
+ return AsMouseEvent()->IsReal();
+ default:
+ return false;
+ }
+}
+
+/******************************************************************************
+ * mozilla::WidgetInputEvent
+ ******************************************************************************/
+
+/* static */
+Modifier WidgetInputEvent::GetModifier(const nsAString& aDOMKeyName) {
+ if (aDOMKeyName.EqualsLiteral("Accel")) {
+ return AccelModifier();
+ }
+ KeyNameIndex keyNameIndex = WidgetKeyboardEvent::GetKeyNameIndex(aDOMKeyName);
+ return WidgetKeyboardEvent::GetModifierForKeyName(keyNameIndex);
+}
+
+/* static */
+Modifier WidgetInputEvent::AccelModifier() {
+ static Modifier sAccelModifier = MODIFIER_NONE;
+ if (sAccelModifier == MODIFIER_NONE) {
+ switch (Preferences::GetInt("ui.key.accelKey", 0)) {
+ case dom::KeyboardEvent_Binding::DOM_VK_META:
+ sAccelModifier = MODIFIER_META;
+ break;
+ case dom::KeyboardEvent_Binding::DOM_VK_WIN:
+ sAccelModifier = MODIFIER_OS;
+ break;
+ case dom::KeyboardEvent_Binding::DOM_VK_ALT:
+ sAccelModifier = MODIFIER_ALT;
+ break;
+ case dom::KeyboardEvent_Binding::DOM_VK_CONTROL:
+ sAccelModifier = MODIFIER_CONTROL;
+ break;
+ default:
+#ifdef XP_MACOSX
+ sAccelModifier = MODIFIER_META;
+#else
+ sAccelModifier = MODIFIER_CONTROL;
+#endif
+ break;
+ }
+ }
+ return sAccelModifier;
+}
+
+/******************************************************************************
+ * mozilla::WidgetMouseEvent (MouseEvents.h)
+ ******************************************************************************/
+
+/* static */
+bool WidgetMouseEvent::IsMiddleClickPasteEnabled() {
+ return Preferences::GetBool("middlemouse.paste", false);
+}
+
+/******************************************************************************
+ * mozilla::WidgetDragEvent (MouseEvents.h)
+ ******************************************************************************/
+
+void WidgetDragEvent::InitDropEffectForTests() {
+ MOZ_ASSERT(mFlags.mIsSynthesizedForTests);
+
+ nsCOMPtr<nsIDragSession> session = nsContentUtils::GetDragSession();
+ if (NS_WARN_IF(!session)) {
+ return;
+ }
+
+ uint32_t effectAllowed = session->GetEffectAllowedForTests();
+ uint32_t desiredDropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
+#ifdef XP_MACOSX
+ if (IsAlt()) {
+ desiredDropEffect = IsMeta() ? nsIDragService::DRAGDROP_ACTION_LINK
+ : nsIDragService::DRAGDROP_ACTION_COPY;
+ }
+#else
+ // On Linux, we know user's intention from API, but we should use
+ // same modifiers as Windows for tests because GNOME on Ubuntu use
+ // them and that makes each test simpler.
+ if (IsControl()) {
+ desiredDropEffect = IsShift() ? nsIDragService::DRAGDROP_ACTION_LINK
+ : nsIDragService::DRAGDROP_ACTION_COPY;
+ } else if (IsShift()) {
+ desiredDropEffect = nsIDragService::DRAGDROP_ACTION_MOVE;
+ }
+#endif // #ifdef XP_MACOSX #else
+ // First, use modifier state for preferring action which is explicitly
+ // specified by the synthesizer.
+ if (!(desiredDropEffect &= effectAllowed)) {
+ // Otherwise, use an action which is allowed at starting the session.
+ desiredDropEffect = effectAllowed;
+ }
+ if (desiredDropEffect & nsIDragService::DRAGDROP_ACTION_MOVE) {
+ session->SetDragAction(nsIDragService::DRAGDROP_ACTION_MOVE);
+ } else if (desiredDropEffect & nsIDragService::DRAGDROP_ACTION_COPY) {
+ session->SetDragAction(nsIDragService::DRAGDROP_ACTION_COPY);
+ } else if (desiredDropEffect & nsIDragService::DRAGDROP_ACTION_LINK) {
+ session->SetDragAction(nsIDragService::DRAGDROP_ACTION_LINK);
+ } else {
+ session->SetDragAction(nsIDragService::DRAGDROP_ACTION_NONE);
+ }
+}
+
+/******************************************************************************
+ * mozilla::WidgetWheelEvent (MouseEvents.h)
+ ******************************************************************************/
+
+/* static */
+double WidgetWheelEvent::ComputeOverriddenDelta(double aDelta,
+ bool aIsForVertical) {
+ if (!StaticPrefs::
+ mousewheel_system_scroll_override_on_root_content_enabled()) {
+ return aDelta;
+ }
+ int32_t intFactor =
+ aIsForVertical
+ ? StaticPrefs::
+ mousewheel_system_scroll_override_on_root_content_vertical_factor()
+ : StaticPrefs::
+ mousewheel_system_scroll_override_on_root_content_horizontal_factor();
+ // Making the scroll speed slower doesn't make sense. So, ignore odd factor
+ // which is less than 1.0.
+ if (intFactor <= 100) {
+ return aDelta;
+ }
+ double factor = static_cast<double>(intFactor) / 100;
+ return aDelta * factor;
+}
+
+double WidgetWheelEvent::OverriddenDeltaX() const {
+ if (!mAllowToOverrideSystemScrollSpeed) {
+ return mDeltaX;
+ }
+ return ComputeOverriddenDelta(mDeltaX, false);
+}
+
+double WidgetWheelEvent::OverriddenDeltaY() const {
+ if (!mAllowToOverrideSystemScrollSpeed) {
+ return mDeltaY;
+ }
+ return ComputeOverriddenDelta(mDeltaY, true);
+}
+
+/******************************************************************************
+ * mozilla::WidgetKeyboardEvent (TextEvents.h)
+ ******************************************************************************/
+
+#define NS_DEFINE_KEYNAME(aCPPName, aDOMKeyName) (u"" aDOMKeyName),
+const char16_t* const WidgetKeyboardEvent::kKeyNames[] = {
+#include "mozilla/KeyNameList.h"
+};
+#undef NS_DEFINE_KEYNAME
+
+#define NS_DEFINE_PHYSICAL_KEY_CODE_NAME(aCPPName, aDOMCodeName) \
+ (u"" aDOMCodeName),
+const char16_t* const WidgetKeyboardEvent::kCodeNames[] = {
+#include "mozilla/PhysicalKeyCodeNameList.h"
+};
+#undef NS_DEFINE_PHYSICAL_KEY_CODE_NAME
+
+WidgetKeyboardEvent::KeyNameIndexHashtable*
+ WidgetKeyboardEvent::sKeyNameIndexHashtable = nullptr;
+WidgetKeyboardEvent::CodeNameIndexHashtable*
+ WidgetKeyboardEvent::sCodeNameIndexHashtable = nullptr;
+
+void WidgetKeyboardEvent::InitAllEditCommands() {
+ // If the event was created without widget, e.g., created event in chrome
+ // script, this shouldn't execute native key bindings.
+ if (NS_WARN_IF(!mWidget)) {
+ return;
+ }
+
+ // This event should be trusted event here and we shouldn't expose native
+ // key binding information to web contents with untrusted events.
+ if (NS_WARN_IF(!IsTrusted())) {
+ return;
+ }
+
+ MOZ_ASSERT(
+ XRE_IsParentProcess(),
+ "It's too expensive to retrieve all edit commands from remote process");
+ MOZ_ASSERT(!AreAllEditCommandsInitialized(),
+ "Shouldn't be called two or more times");
+
+ DebugOnly<bool> okIgnored =
+ InitEditCommandsFor(nsIWidget::NativeKeyBindingsForSingleLineEditor);
+ NS_WARNING_ASSERTION(
+ okIgnored,
+ "InitEditCommandsFor(nsIWidget::NativeKeyBindingsForSingleLineEditor) "
+ "failed, but ignored");
+ okIgnored =
+ InitEditCommandsFor(nsIWidget::NativeKeyBindingsForMultiLineEditor);
+ NS_WARNING_ASSERTION(
+ okIgnored,
+ "InitEditCommandsFor(nsIWidget::NativeKeyBindingsForMultiLineEditor) "
+ "failed, but ignored");
+ okIgnored =
+ InitEditCommandsFor(nsIWidget::NativeKeyBindingsForRichTextEditor);
+ NS_WARNING_ASSERTION(
+ okIgnored,
+ "InitEditCommandsFor(nsIWidget::NativeKeyBindingsForRichTextEditor) "
+ "failed, but ignored");
+}
+
+bool WidgetKeyboardEvent::InitEditCommandsFor(
+ nsIWidget::NativeKeyBindingsType aType) {
+ if (NS_WARN_IF(!mWidget) || NS_WARN_IF(!IsTrusted())) {
+ return false;
+ }
+
+ bool& initialized = IsEditCommandsInitializedRef(aType);
+ if (initialized) {
+ return true;
+ }
+ nsTArray<CommandInt>& commands = EditCommandsRef(aType);
+ initialized = mWidget->GetEditCommands(aType, *this, commands);
+ return initialized;
+}
+
+bool WidgetKeyboardEvent::ExecuteEditCommands(
+ nsIWidget::NativeKeyBindingsType aType, DoCommandCallback aCallback,
+ void* aCallbackData) {
+ // If the event was created without widget, e.g., created event in chrome
+ // script, this shouldn't execute native key bindings.
+ if (NS_WARN_IF(!mWidget)) {
+ return false;
+ }
+
+ // This event should be trusted event here and we shouldn't expose native
+ // key binding information to web contents with untrusted events.
+ if (NS_WARN_IF(!IsTrusted())) {
+ return false;
+ }
+
+ if (NS_WARN_IF(!InitEditCommandsFor(aType))) {
+ return false;
+ }
+
+ const nsTArray<CommandInt>& commands = EditCommandsRef(aType);
+ if (commands.IsEmpty()) {
+ return false;
+ }
+
+ for (CommandInt command : commands) {
+ aCallback(static_cast<Command>(command), aCallbackData);
+ }
+ return true;
+}
+
+bool WidgetKeyboardEvent::ShouldCauseKeypressEvents() const {
+ // Currently, we don't dispatch keypress events of modifier keys and
+ // dead keys.
+ switch (mKeyNameIndex) {
+ case KEY_NAME_INDEX_Alt:
+ case KEY_NAME_INDEX_AltGraph:
+ case KEY_NAME_INDEX_CapsLock:
+ case KEY_NAME_INDEX_Control:
+ case KEY_NAME_INDEX_Fn:
+ case KEY_NAME_INDEX_FnLock:
+ // case KEY_NAME_INDEX_Hyper:
+ case KEY_NAME_INDEX_Meta:
+ case KEY_NAME_INDEX_NumLock:
+ case KEY_NAME_INDEX_OS:
+ case KEY_NAME_INDEX_ScrollLock:
+ case KEY_NAME_INDEX_Shift:
+ // case KEY_NAME_INDEX_Super:
+ case KEY_NAME_INDEX_Symbol:
+ case KEY_NAME_INDEX_SymbolLock:
+ case KEY_NAME_INDEX_Dead:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static bool HasASCIIDigit(const ShortcutKeyCandidateArray& aCandidates) {
+ for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
+ uint32_t ch = aCandidates[i].mCharCode;
+ if (ch >= '0' && ch <= '9') return true;
+ }
+ return false;
+}
+
+static bool CharsCaseInsensitiveEqual(uint32_t aChar1, uint32_t aChar2) {
+ return aChar1 == aChar2 || (IS_IN_BMP(aChar1) && IS_IN_BMP(aChar2) &&
+ ToLowerCase(static_cast<char16_t>(aChar1)) ==
+ ToLowerCase(static_cast<char16_t>(aChar2)));
+}
+
+static bool IsCaseChangeableChar(uint32_t aChar) {
+ return IS_IN_BMP(aChar) && ToLowerCase(static_cast<char16_t>(aChar)) !=
+ ToUpperCase(static_cast<char16_t>(aChar));
+}
+
+void WidgetKeyboardEvent::GetShortcutKeyCandidates(
+ ShortcutKeyCandidateArray& aCandidates) const {
+ MOZ_ASSERT(aCandidates.IsEmpty(), "aCandidates must be empty");
+
+ // ShortcutKeyCandidate::mCharCode is a candidate charCode.
+ // ShortcutKeyCandidate::mIgnoreShift means the mCharCode should be tried to
+ // execute a command with/without shift key state. If this is TRUE, the
+ // shifted key state should be ignored. Otherwise, don't ignore the state.
+ // the priority of the charCodes are (shift key is not pressed):
+ // 0: PseudoCharCode()/false,
+ // 1: unshiftedCharCodes[0]/false, 2: unshiftedCharCodes[1]/false...
+ // the priority of the charCodes are (shift key is pressed):
+ // 0: PseudoCharCode()/false,
+ // 1: shiftedCharCodes[0]/false, 2: shiftedCharCodes[0]/true,
+ // 3: shiftedCharCodes[1]/false, 4: shiftedCharCodes[1]/true...
+ uint32_t pseudoCharCode = PseudoCharCode();
+ if (pseudoCharCode) {
+ ShortcutKeyCandidate key(pseudoCharCode, false);
+ aCandidates.AppendElement(key);
+ }
+
+ uint32_t len = mAlternativeCharCodes.Length();
+ if (!IsShift()) {
+ for (uint32_t i = 0; i < len; ++i) {
+ uint32_t ch = mAlternativeCharCodes[i].mUnshiftedCharCode;
+ if (!ch || ch == pseudoCharCode) {
+ continue;
+ }
+ ShortcutKeyCandidate key(ch, false);
+ aCandidates.AppendElement(key);
+ }
+ // If unshiftedCharCodes doesn't have numeric but shiftedCharCode has it,
+ // this keyboard layout is AZERTY or similar layout, probably.
+ // In this case, Accel+[0-9] should be accessible without shift key.
+ // However, the priority should be lowest.
+ if (!HasASCIIDigit(aCandidates)) {
+ for (uint32_t i = 0; i < len; ++i) {
+ uint32_t ch = mAlternativeCharCodes[i].mShiftedCharCode;
+ if (ch >= '0' && ch <= '9') {
+ ShortcutKeyCandidate key(ch, false);
+ aCandidates.AppendElement(key);
+ break;
+ }
+ }
+ }
+ } else {
+ for (uint32_t i = 0; i < len; ++i) {
+ uint32_t ch = mAlternativeCharCodes[i].mShiftedCharCode;
+ if (!ch) {
+ continue;
+ }
+
+ if (ch != pseudoCharCode) {
+ ShortcutKeyCandidate key(ch, false);
+ aCandidates.AppendElement(key);
+ }
+
+ // If the char is an alphabet, the shift key state should not be
+ // ignored. E.g., Ctrl+Shift+C should not execute Ctrl+C.
+
+ // And checking the charCode is same as unshiftedCharCode too.
+ // E.g., for Ctrl+Shift+(Plus of Numpad) should not run Ctrl+Plus.
+ uint32_t unshiftCh = mAlternativeCharCodes[i].mUnshiftedCharCode;
+ if (CharsCaseInsensitiveEqual(ch, unshiftCh)) {
+ continue;
+ }
+
+ // On the Hebrew keyboard layout on Windows, the unshifted char is a
+ // localized character but the shifted char is a Latin alphabet,
+ // then, we should not execute without the shift state. See bug 433192.
+ if (IsCaseChangeableChar(ch)) {
+ continue;
+ }
+
+ // Setting the alternative charCode candidates for retry without shift
+ // key state only when the shift key is pressed.
+ ShortcutKeyCandidate key(ch, true);
+ aCandidates.AppendElement(key);
+ }
+ }
+
+ // Special case for "Space" key. With some keyboard layouts, "Space" with
+ // or without Shift key causes non-ASCII space. For such keyboard layouts,
+ // we should guarantee that the key press works as an ASCII white space key
+ // press. However, if the space key is assigned to a function key, it
+ // shouldn't work as a space key.
+ if (mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
+ mCodeNameIndex == CODE_NAME_INDEX_Space && pseudoCharCode != ' ') {
+ ShortcutKeyCandidate spaceKey(' ', false);
+ aCandidates.AppendElement(spaceKey);
+ }
+}
+
+void WidgetKeyboardEvent::GetAccessKeyCandidates(
+ nsTArray<uint32_t>& aCandidates) const {
+ MOZ_ASSERT(aCandidates.IsEmpty(), "aCandidates must be empty");
+
+ // return the lower cased charCode candidates for access keys.
+ // the priority of the charCodes are:
+ // 0: charCode, 1: unshiftedCharCodes[0], 2: shiftedCharCodes[0]
+ // 3: unshiftedCharCodes[1], 4: shiftedCharCodes[1],...
+ uint32_t pseudoCharCode = PseudoCharCode();
+ if (pseudoCharCode) {
+ uint32_t ch = pseudoCharCode;
+ if (IS_IN_BMP(ch)) {
+ ch = ToLowerCase(static_cast<char16_t>(ch));
+ }
+ aCandidates.AppendElement(ch);
+ }
+ for (uint32_t i = 0; i < mAlternativeCharCodes.Length(); ++i) {
+ uint32_t ch[2] = {mAlternativeCharCodes[i].mUnshiftedCharCode,
+ mAlternativeCharCodes[i].mShiftedCharCode};
+ for (uint32_t j = 0; j < 2; ++j) {
+ if (!ch[j]) {
+ continue;
+ }
+ if (IS_IN_BMP(ch[j])) {
+ ch[j] = ToLowerCase(static_cast<char16_t>(ch[j]));
+ }
+ // Don't append the charcode that was already appended.
+ if (aCandidates.IndexOf(ch[j]) == aCandidates.NoIndex) {
+ aCandidates.AppendElement(ch[j]);
+ }
+ }
+ }
+ // Special case for "Space" key. With some keyboard layouts, "Space" with
+ // or without Shift key causes non-ASCII space. For such keyboard layouts,
+ // we should guarantee that the key press works as an ASCII white space key
+ // press. However, if the space key is assigned to a function key, it
+ // shouldn't work as a space key.
+ if (mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
+ mCodeNameIndex == CODE_NAME_INDEX_Space && pseudoCharCode != ' ') {
+ aCandidates.AppendElement(' ');
+ }
+}
+
+// mask values for ui.key.chromeAccess and ui.key.contentAccess
+#define NS_MODIFIER_SHIFT 1
+#define NS_MODIFIER_CONTROL 2
+#define NS_MODIFIER_ALT 4
+#define NS_MODIFIER_META 8
+#define NS_MODIFIER_OS 16
+
+static Modifiers PrefFlagsToModifiers(int32_t aPrefFlags) {
+ Modifiers result = 0;
+ if (aPrefFlags & NS_MODIFIER_SHIFT) {
+ result |= MODIFIER_SHIFT;
+ }
+ if (aPrefFlags & NS_MODIFIER_CONTROL) {
+ result |= MODIFIER_CONTROL;
+ }
+ if (aPrefFlags & NS_MODIFIER_ALT) {
+ result |= MODIFIER_ALT;
+ }
+ if (aPrefFlags & NS_MODIFIER_META) {
+ result |= MODIFIER_META;
+ }
+ if (aPrefFlags & NS_MODIFIER_OS) {
+ result |= MODIFIER_OS;
+ }
+ return result;
+}
+
+bool WidgetKeyboardEvent::ModifiersMatchWithAccessKey(
+ AccessKeyType aType) const {
+ if (!ModifiersForAccessKeyMatching()) {
+ return false;
+ }
+ return ModifiersForAccessKeyMatching() == AccessKeyModifiers(aType);
+}
+
+Modifiers WidgetKeyboardEvent::ModifiersForAccessKeyMatching() const {
+ static const Modifiers kModifierMask = MODIFIER_SHIFT | MODIFIER_CONTROL |
+ MODIFIER_ALT | MODIFIER_META |
+ MODIFIER_OS;
+ return mModifiers & kModifierMask;
+}
+
+/* static */
+Modifiers WidgetKeyboardEvent::AccessKeyModifiers(AccessKeyType aType) {
+ switch (StaticPrefs::ui_key_generalAccessKey()) {
+ case -1:
+ break; // use the individual prefs
+ case NS_VK_SHIFT:
+ return MODIFIER_SHIFT;
+ case NS_VK_CONTROL:
+ return MODIFIER_CONTROL;
+ case NS_VK_ALT:
+ return MODIFIER_ALT;
+ case NS_VK_META:
+ return MODIFIER_META;
+ case NS_VK_WIN:
+ return MODIFIER_OS;
+ default:
+ return MODIFIER_NONE;
+ }
+
+ switch (aType) {
+ case AccessKeyType::eChrome:
+ return PrefFlagsToModifiers(StaticPrefs::ui_key_chromeAccess());
+ case AccessKeyType::eContent:
+ return PrefFlagsToModifiers(StaticPrefs::ui_key_contentAccess());
+ default:
+ return MODIFIER_NONE;
+ }
+}
+
+/* static */
+void WidgetKeyboardEvent::Shutdown() {
+ delete sKeyNameIndexHashtable;
+ sKeyNameIndexHashtable = nullptr;
+ delete sCodeNameIndexHashtable;
+ sCodeNameIndexHashtable = nullptr;
+ // Although sCommandHashtable is not a member of WidgetKeyboardEvent, but
+ // let's delete it here since we need to do it at same time.
+ delete sCommandHashtable;
+ sCommandHashtable = nullptr;
+}
+
+/* static */
+void WidgetKeyboardEvent::GetDOMKeyName(KeyNameIndex aKeyNameIndex,
+ nsAString& aKeyName) {
+ if (aKeyNameIndex >= KEY_NAME_INDEX_USE_STRING) {
+ aKeyName.Truncate();
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(
+ static_cast<size_t>(aKeyNameIndex) < ArrayLength(kKeyNames),
+ "Illegal key enumeration value");
+ aKeyName = kKeyNames[aKeyNameIndex];
+}
+
+/* static */
+void WidgetKeyboardEvent::GetDOMCodeName(CodeNameIndex aCodeNameIndex,
+ nsAString& aCodeName) {
+ if (aCodeNameIndex >= CODE_NAME_INDEX_USE_STRING) {
+ aCodeName.Truncate();
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(
+ static_cast<size_t>(aCodeNameIndex) < ArrayLength(kCodeNames),
+ "Illegal physical code enumeration value");
+ aCodeName = kCodeNames[aCodeNameIndex];
+}
+
+/* static */
+KeyNameIndex WidgetKeyboardEvent::GetKeyNameIndex(const nsAString& aKeyValue) {
+ if (!sKeyNameIndexHashtable) {
+ sKeyNameIndexHashtable = new KeyNameIndexHashtable(ArrayLength(kKeyNames));
+ for (size_t i = 0; i < ArrayLength(kKeyNames); i++) {
+ sKeyNameIndexHashtable->Put(nsDependentString(kKeyNames[i]),
+ static_cast<KeyNameIndex>(i));
+ }
+ }
+ KeyNameIndex result = KEY_NAME_INDEX_USE_STRING;
+ sKeyNameIndexHashtable->Get(aKeyValue, &result);
+ return result;
+}
+
+/* static */
+CodeNameIndex WidgetKeyboardEvent::GetCodeNameIndex(
+ const nsAString& aCodeValue) {
+ if (!sCodeNameIndexHashtable) {
+ sCodeNameIndexHashtable =
+ new CodeNameIndexHashtable(ArrayLength(kCodeNames));
+ for (size_t i = 0; i < ArrayLength(kCodeNames); i++) {
+ sCodeNameIndexHashtable->Put(nsDependentString(kCodeNames[i]),
+ static_cast<CodeNameIndex>(i));
+ }
+ }
+ CodeNameIndex result = CODE_NAME_INDEX_USE_STRING;
+ sCodeNameIndexHashtable->Get(aCodeValue, &result);
+ return result;
+}
+
+/* static */
+uint32_t WidgetKeyboardEvent::GetFallbackKeyCodeOfPunctuationKey(
+ CodeNameIndex aCodeNameIndex) {
+ switch (aCodeNameIndex) {
+ case CODE_NAME_INDEX_Semicolon: // VK_OEM_1 on Windows
+ return dom::KeyboardEvent_Binding::DOM_VK_SEMICOLON;
+ case CODE_NAME_INDEX_Equal: // VK_OEM_PLUS on Windows
+ return dom::KeyboardEvent_Binding::DOM_VK_EQUALS;
+ case CODE_NAME_INDEX_Comma: // VK_OEM_COMMA on Windows
+ return dom::KeyboardEvent_Binding::DOM_VK_COMMA;
+ case CODE_NAME_INDEX_Minus: // VK_OEM_MINUS on Windows
+ return dom::KeyboardEvent_Binding::DOM_VK_HYPHEN_MINUS;
+ case CODE_NAME_INDEX_Period: // VK_OEM_PERIOD on Windows
+ return dom::KeyboardEvent_Binding::DOM_VK_PERIOD;
+ case CODE_NAME_INDEX_Slash: // VK_OEM_2 on Windows
+ return dom::KeyboardEvent_Binding::DOM_VK_SLASH;
+ case CODE_NAME_INDEX_Backquote: // VK_OEM_3 on Windows
+ return dom::KeyboardEvent_Binding::DOM_VK_BACK_QUOTE;
+ case CODE_NAME_INDEX_BracketLeft: // VK_OEM_4 on Windows
+ return dom::KeyboardEvent_Binding::DOM_VK_OPEN_BRACKET;
+ case CODE_NAME_INDEX_Backslash: // VK_OEM_5 on Windows
+ return dom::KeyboardEvent_Binding::DOM_VK_BACK_SLASH;
+ case CODE_NAME_INDEX_BracketRight: // VK_OEM_6 on Windows
+ return dom::KeyboardEvent_Binding::DOM_VK_CLOSE_BRACKET;
+ case CODE_NAME_INDEX_Quote: // VK_OEM_7 on Windows
+ return dom::KeyboardEvent_Binding::DOM_VK_QUOTE;
+ case CODE_NAME_INDEX_IntlBackslash: // VK_OEM_5 on Windows (ABNT, etc)
+ case CODE_NAME_INDEX_IntlYen: // VK_OEM_5 on Windows (JIS)
+ case CODE_NAME_INDEX_IntlRo: // VK_OEM_102 on Windows
+ return dom::KeyboardEvent_Binding::DOM_VK_BACK_SLASH;
+ default:
+ return 0;
+ }
+}
+
+/* static */ const char* WidgetKeyboardEvent::GetCommandStr(Command aCommand) {
+#define NS_DEFINE_COMMAND(aName, aCommandStr) , #aCommandStr
+#define NS_DEFINE_COMMAND_WITH_PARAM(aName, aCommandStr, aParam) , #aCommandStr
+#define NS_DEFINE_COMMAND_NO_EXEC_COMMAND(aName)
+ static const char* const kCommands[] = {
+ "" // DoNothing
+#include "mozilla/CommandList.h"
+ };
+#undef NS_DEFINE_COMMAND
+#undef NS_DEFINE_COMMAND_WITH_PARAM
+#undef NS_DEFINE_COMMAND_NO_EXEC_COMMAND
+
+ MOZ_RELEASE_ASSERT(static_cast<size_t>(aCommand) < ArrayLength(kCommands),
+ "Illegal command enumeration value");
+ return kCommands[static_cast<CommandInt>(aCommand)];
+}
+
+/* static */
+uint32_t WidgetKeyboardEvent::ComputeLocationFromCodeValue(
+ CodeNameIndex aCodeNameIndex) {
+ // Following commented out cases are not defined in PhysicalKeyCodeNameList.h
+ // but are defined by D3E spec. So, they should be uncommented when the
+ // code values are defined in the header.
+ switch (aCodeNameIndex) {
+ case CODE_NAME_INDEX_AltLeft:
+ case CODE_NAME_INDEX_ControlLeft:
+ case CODE_NAME_INDEX_OSLeft:
+ case CODE_NAME_INDEX_ShiftLeft:
+ return eKeyLocationLeft;
+ case CODE_NAME_INDEX_AltRight:
+ case CODE_NAME_INDEX_ControlRight:
+ case CODE_NAME_INDEX_OSRight:
+ case CODE_NAME_INDEX_ShiftRight:
+ return eKeyLocationRight;
+ case CODE_NAME_INDEX_Numpad0:
+ case CODE_NAME_INDEX_Numpad1:
+ case CODE_NAME_INDEX_Numpad2:
+ case CODE_NAME_INDEX_Numpad3:
+ case CODE_NAME_INDEX_Numpad4:
+ case CODE_NAME_INDEX_Numpad5:
+ case CODE_NAME_INDEX_Numpad6:
+ case CODE_NAME_INDEX_Numpad7:
+ case CODE_NAME_INDEX_Numpad8:
+ case CODE_NAME_INDEX_Numpad9:
+ case CODE_NAME_INDEX_NumpadAdd:
+ case CODE_NAME_INDEX_NumpadBackspace:
+ case CODE_NAME_INDEX_NumpadClear:
+ case CODE_NAME_INDEX_NumpadClearEntry:
+ case CODE_NAME_INDEX_NumpadComma:
+ case CODE_NAME_INDEX_NumpadDecimal:
+ case CODE_NAME_INDEX_NumpadDivide:
+ case CODE_NAME_INDEX_NumpadEnter:
+ case CODE_NAME_INDEX_NumpadEqual:
+ case CODE_NAME_INDEX_NumpadMemoryAdd:
+ case CODE_NAME_INDEX_NumpadMemoryClear:
+ case CODE_NAME_INDEX_NumpadMemoryRecall:
+ case CODE_NAME_INDEX_NumpadMemoryStore:
+ case CODE_NAME_INDEX_NumpadMemorySubtract:
+ case CODE_NAME_INDEX_NumpadMultiply:
+ case CODE_NAME_INDEX_NumpadParenLeft:
+ case CODE_NAME_INDEX_NumpadParenRight:
+ case CODE_NAME_INDEX_NumpadSubtract:
+ return eKeyLocationNumpad;
+ default:
+ return eKeyLocationStandard;
+ }
+}
+
+/* static */
+uint32_t WidgetKeyboardEvent::ComputeKeyCodeFromKeyNameIndex(
+ KeyNameIndex aKeyNameIndex) {
+ switch (aKeyNameIndex) {
+ case KEY_NAME_INDEX_Cancel:
+ return dom::KeyboardEvent_Binding::DOM_VK_CANCEL;
+ case KEY_NAME_INDEX_Help:
+ return dom::KeyboardEvent_Binding::DOM_VK_HELP;
+ case KEY_NAME_INDEX_Backspace:
+ return dom::KeyboardEvent_Binding::DOM_VK_BACK_SPACE;
+ case KEY_NAME_INDEX_Tab:
+ return dom::KeyboardEvent_Binding::DOM_VK_TAB;
+ case KEY_NAME_INDEX_Clear:
+ return dom::KeyboardEvent_Binding::DOM_VK_CLEAR;
+ case KEY_NAME_INDEX_Enter:
+ return dom::KeyboardEvent_Binding::DOM_VK_RETURN;
+ case KEY_NAME_INDEX_Shift:
+ return dom::KeyboardEvent_Binding::DOM_VK_SHIFT;
+ case KEY_NAME_INDEX_Control:
+ return dom::KeyboardEvent_Binding::DOM_VK_CONTROL;
+ case KEY_NAME_INDEX_Alt:
+ return dom::KeyboardEvent_Binding::DOM_VK_ALT;
+ case KEY_NAME_INDEX_Pause:
+ return dom::KeyboardEvent_Binding::DOM_VK_PAUSE;
+ case KEY_NAME_INDEX_CapsLock:
+ return dom::KeyboardEvent_Binding::DOM_VK_CAPS_LOCK;
+ case KEY_NAME_INDEX_Hiragana:
+ case KEY_NAME_INDEX_Katakana:
+ case KEY_NAME_INDEX_HiraganaKatakana:
+ case KEY_NAME_INDEX_KanaMode:
+ return dom::KeyboardEvent_Binding::DOM_VK_KANA;
+ case KEY_NAME_INDEX_HangulMode:
+ return dom::KeyboardEvent_Binding::DOM_VK_HANGUL;
+ case KEY_NAME_INDEX_Eisu:
+ return dom::KeyboardEvent_Binding::DOM_VK_EISU;
+ case KEY_NAME_INDEX_JunjaMode:
+ return dom::KeyboardEvent_Binding::DOM_VK_JUNJA;
+ case KEY_NAME_INDEX_FinalMode:
+ return dom::KeyboardEvent_Binding::DOM_VK_FINAL;
+ case KEY_NAME_INDEX_HanjaMode:
+ return dom::KeyboardEvent_Binding::DOM_VK_HANJA;
+ case KEY_NAME_INDEX_KanjiMode:
+ return dom::KeyboardEvent_Binding::DOM_VK_KANJI;
+ case KEY_NAME_INDEX_Escape:
+ return dom::KeyboardEvent_Binding::DOM_VK_ESCAPE;
+ case KEY_NAME_INDEX_Convert:
+ return dom::KeyboardEvent_Binding::DOM_VK_CONVERT;
+ case KEY_NAME_INDEX_NonConvert:
+ return dom::KeyboardEvent_Binding::DOM_VK_NONCONVERT;
+ case KEY_NAME_INDEX_Accept:
+ return dom::KeyboardEvent_Binding::DOM_VK_ACCEPT;
+ case KEY_NAME_INDEX_ModeChange:
+ return dom::KeyboardEvent_Binding::DOM_VK_MODECHANGE;
+ case KEY_NAME_INDEX_PageUp:
+ return dom::KeyboardEvent_Binding::DOM_VK_PAGE_UP;
+ case KEY_NAME_INDEX_PageDown:
+ return dom::KeyboardEvent_Binding::DOM_VK_PAGE_DOWN;
+ case KEY_NAME_INDEX_End:
+ return dom::KeyboardEvent_Binding::DOM_VK_END;
+ case KEY_NAME_INDEX_Home:
+ return dom::KeyboardEvent_Binding::DOM_VK_HOME;
+ case KEY_NAME_INDEX_ArrowLeft:
+ return dom::KeyboardEvent_Binding::DOM_VK_LEFT;
+ case KEY_NAME_INDEX_ArrowUp:
+ return dom::KeyboardEvent_Binding::DOM_VK_UP;
+ case KEY_NAME_INDEX_ArrowRight:
+ return dom::KeyboardEvent_Binding::DOM_VK_RIGHT;
+ case KEY_NAME_INDEX_ArrowDown:
+ return dom::KeyboardEvent_Binding::DOM_VK_DOWN;
+ case KEY_NAME_INDEX_Select:
+ return dom::KeyboardEvent_Binding::DOM_VK_SELECT;
+ case KEY_NAME_INDEX_Print:
+ return dom::KeyboardEvent_Binding::DOM_VK_PRINT;
+ case KEY_NAME_INDEX_Execute:
+ return dom::KeyboardEvent_Binding::DOM_VK_EXECUTE;
+ case KEY_NAME_INDEX_PrintScreen:
+ return dom::KeyboardEvent_Binding::DOM_VK_PRINTSCREEN;
+ case KEY_NAME_INDEX_Insert:
+ return dom::KeyboardEvent_Binding::DOM_VK_INSERT;
+ case KEY_NAME_INDEX_Delete:
+ return dom::KeyboardEvent_Binding::DOM_VK_DELETE;
+ case KEY_NAME_INDEX_OS:
+ // case KEY_NAME_INDEX_Super:
+ // case KEY_NAME_INDEX_Hyper:
+ return dom::KeyboardEvent_Binding::DOM_VK_WIN;
+ case KEY_NAME_INDEX_ContextMenu:
+ return dom::KeyboardEvent_Binding::DOM_VK_CONTEXT_MENU;
+ case KEY_NAME_INDEX_Standby:
+ return dom::KeyboardEvent_Binding::DOM_VK_SLEEP;
+ case KEY_NAME_INDEX_F1:
+ return dom::KeyboardEvent_Binding::DOM_VK_F1;
+ case KEY_NAME_INDEX_F2:
+ return dom::KeyboardEvent_Binding::DOM_VK_F2;
+ case KEY_NAME_INDEX_F3:
+ return dom::KeyboardEvent_Binding::DOM_VK_F3;
+ case KEY_NAME_INDEX_F4:
+ return dom::KeyboardEvent_Binding::DOM_VK_F4;
+ case KEY_NAME_INDEX_F5:
+ return dom::KeyboardEvent_Binding::DOM_VK_F5;
+ case KEY_NAME_INDEX_F6:
+ return dom::KeyboardEvent_Binding::DOM_VK_F6;
+ case KEY_NAME_INDEX_F7:
+ return dom::KeyboardEvent_Binding::DOM_VK_F7;
+ case KEY_NAME_INDEX_F8:
+ return dom::KeyboardEvent_Binding::DOM_VK_F8;
+ case KEY_NAME_INDEX_F9:
+ return dom::KeyboardEvent_Binding::DOM_VK_F9;
+ case KEY_NAME_INDEX_F10:
+ return dom::KeyboardEvent_Binding::DOM_VK_F10;
+ case KEY_NAME_INDEX_F11:
+ return dom::KeyboardEvent_Binding::DOM_VK_F11;
+ case KEY_NAME_INDEX_F12:
+ return dom::KeyboardEvent_Binding::DOM_VK_F12;
+ case KEY_NAME_INDEX_F13:
+ return dom::KeyboardEvent_Binding::DOM_VK_F13;
+ case KEY_NAME_INDEX_F14:
+ return dom::KeyboardEvent_Binding::DOM_VK_F14;
+ case KEY_NAME_INDEX_F15:
+ return dom::KeyboardEvent_Binding::DOM_VK_F15;
+ case KEY_NAME_INDEX_F16:
+ return dom::KeyboardEvent_Binding::DOM_VK_F16;
+ case KEY_NAME_INDEX_F17:
+ return dom::KeyboardEvent_Binding::DOM_VK_F17;
+ case KEY_NAME_INDEX_F18:
+ return dom::KeyboardEvent_Binding::DOM_VK_F18;
+ case KEY_NAME_INDEX_F19:
+ return dom::KeyboardEvent_Binding::DOM_VK_F19;
+ case KEY_NAME_INDEX_F20:
+ return dom::KeyboardEvent_Binding::DOM_VK_F20;
+ case KEY_NAME_INDEX_F21:
+ return dom::KeyboardEvent_Binding::DOM_VK_F21;
+ case KEY_NAME_INDEX_F22:
+ return dom::KeyboardEvent_Binding::DOM_VK_F22;
+ case KEY_NAME_INDEX_F23:
+ return dom::KeyboardEvent_Binding::DOM_VK_F23;
+ case KEY_NAME_INDEX_F24:
+ return dom::KeyboardEvent_Binding::DOM_VK_F24;
+ case KEY_NAME_INDEX_NumLock:
+ return dom::KeyboardEvent_Binding::DOM_VK_NUM_LOCK;
+ case KEY_NAME_INDEX_ScrollLock:
+ return dom::KeyboardEvent_Binding::DOM_VK_SCROLL_LOCK;
+ case KEY_NAME_INDEX_AudioVolumeMute:
+ return dom::KeyboardEvent_Binding::DOM_VK_VOLUME_MUTE;
+ case KEY_NAME_INDEX_AudioVolumeDown:
+ return dom::KeyboardEvent_Binding::DOM_VK_VOLUME_DOWN;
+ case KEY_NAME_INDEX_AudioVolumeUp:
+ return dom::KeyboardEvent_Binding::DOM_VK_VOLUME_UP;
+ case KEY_NAME_INDEX_Meta:
+ return dom::KeyboardEvent_Binding::DOM_VK_META;
+ case KEY_NAME_INDEX_AltGraph:
+ return dom::KeyboardEvent_Binding::DOM_VK_ALTGR;
+ case KEY_NAME_INDEX_Process:
+ return dom::KeyboardEvent_Binding::DOM_VK_PROCESSKEY;
+ case KEY_NAME_INDEX_Attn:
+ return dom::KeyboardEvent_Binding::DOM_VK_ATTN;
+ case KEY_NAME_INDEX_CrSel:
+ return dom::KeyboardEvent_Binding::DOM_VK_CRSEL;
+ case KEY_NAME_INDEX_ExSel:
+ return dom::KeyboardEvent_Binding::DOM_VK_EXSEL;
+ case KEY_NAME_INDEX_EraseEof:
+ return dom::KeyboardEvent_Binding::DOM_VK_EREOF;
+ case KEY_NAME_INDEX_Play:
+ return dom::KeyboardEvent_Binding::DOM_VK_PLAY;
+ case KEY_NAME_INDEX_ZoomToggle:
+ case KEY_NAME_INDEX_ZoomIn:
+ case KEY_NAME_INDEX_ZoomOut:
+ return dom::KeyboardEvent_Binding::DOM_VK_ZOOM;
+ default:
+ return 0;
+ }
+}
+
+/* static */
+CodeNameIndex WidgetKeyboardEvent::ComputeCodeNameIndexFromKeyNameIndex(
+ KeyNameIndex aKeyNameIndex, const Maybe<uint32_t>& aLocation) {
+ if (aLocation.isSome() &&
+ aLocation.value() ==
+ dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_NUMPAD) {
+ // On macOS, NumLock is not supported. Therefore, this handles
+ // control key values except "Enter" only on non-macOS platforms.
+ switch (aKeyNameIndex) {
+#ifndef XP_MACOSX
+ case KEY_NAME_INDEX_Insert:
+ return CODE_NAME_INDEX_Numpad0;
+ case KEY_NAME_INDEX_End:
+ return CODE_NAME_INDEX_Numpad1;
+ case KEY_NAME_INDEX_ArrowDown:
+ return CODE_NAME_INDEX_Numpad2;
+ case KEY_NAME_INDEX_PageDown:
+ return CODE_NAME_INDEX_Numpad3;
+ case KEY_NAME_INDEX_ArrowLeft:
+ return CODE_NAME_INDEX_Numpad4;
+ case KEY_NAME_INDEX_Clear:
+ // FYI: "Clear" on macOS should be DOM_KEY_LOCATION_STANDARD.
+ return CODE_NAME_INDEX_Numpad5;
+ case KEY_NAME_INDEX_ArrowRight:
+ return CODE_NAME_INDEX_Numpad6;
+ case KEY_NAME_INDEX_Home:
+ return CODE_NAME_INDEX_Numpad7;
+ case KEY_NAME_INDEX_ArrowUp:
+ return CODE_NAME_INDEX_Numpad8;
+ case KEY_NAME_INDEX_PageUp:
+ return CODE_NAME_INDEX_Numpad9;
+ case KEY_NAME_INDEX_Delete:
+ return CODE_NAME_INDEX_NumpadDecimal;
+#endif // #ifndef XP_MACOSX
+ case KEY_NAME_INDEX_Enter:
+ return CODE_NAME_INDEX_NumpadEnter;
+ default:
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+ }
+
+ if (WidgetKeyboardEvent::IsLeftOrRightModiferKeyNameIndex(aKeyNameIndex)) {
+ if (aLocation.isSome() &&
+ (aLocation.value() !=
+ dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_LEFT &&
+ aLocation.value() !=
+ dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_RIGHT)) {
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+ bool isRight =
+ aLocation.isSome() &&
+ aLocation.value() == dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_RIGHT;
+ switch (aKeyNameIndex) {
+ case KEY_NAME_INDEX_Alt:
+ return isRight ? CODE_NAME_INDEX_AltRight : CODE_NAME_INDEX_AltLeft;
+ case KEY_NAME_INDEX_Control:
+ return isRight ? CODE_NAME_INDEX_ControlRight
+ : CODE_NAME_INDEX_ControlLeft;
+ case KEY_NAME_INDEX_Shift:
+ return isRight ? CODE_NAME_INDEX_ShiftRight : CODE_NAME_INDEX_ShiftLeft;
+#if defined(XP_WIN)
+ case KEY_NAME_INDEX_Meta:
+ return CODE_NAME_INDEX_UNKNOWN;
+ case KEY_NAME_INDEX_OS: // win key.
+ return isRight ? CODE_NAME_INDEX_OSRight : CODE_NAME_INDEX_OSLeft;
+#elif defined(XP_MACOSX) || defined(ANDROID)
+ case KEY_NAME_INDEX_Meta: // command key.
+ return isRight ? CODE_NAME_INDEX_OSRight : CODE_NAME_INDEX_OSLeft;
+ case KEY_NAME_INDEX_OS:
+ return CODE_NAME_INDEX_UNKNOWN;
+#else
+ case KEY_NAME_INDEX_Meta: // Alt + Shift.
+ return isRight ? CODE_NAME_INDEX_AltRight : CODE_NAME_INDEX_AltLeft;
+ case KEY_NAME_INDEX_OS: // Super/Hyper key.
+ return isRight ? CODE_NAME_INDEX_OSRight : CODE_NAME_INDEX_OSLeft;
+#endif
+ default:
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+ }
+
+ if (aLocation.isSome() &&
+ aLocation.value() !=
+ dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_STANDARD) {
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+
+ switch (aKeyNameIndex) {
+ // Standard section:
+ case KEY_NAME_INDEX_Escape:
+ return CODE_NAME_INDEX_Escape;
+ case KEY_NAME_INDEX_Tab:
+ return CODE_NAME_INDEX_Tab;
+ case KEY_NAME_INDEX_CapsLock:
+ return CODE_NAME_INDEX_CapsLock;
+ case KEY_NAME_INDEX_ContextMenu:
+ return CODE_NAME_INDEX_ContextMenu;
+ case KEY_NAME_INDEX_Backspace:
+ return CODE_NAME_INDEX_Backspace;
+ case KEY_NAME_INDEX_Enter:
+ return CODE_NAME_INDEX_Enter;
+#ifdef XP_MACOSX
+ // Although, macOS does not fire native key event of "Fn" key, we support
+ // Fn key event if it's sent by other apps directly.
+ case KEY_NAME_INDEX_Fn:
+ return CODE_NAME_INDEX_Fn;
+#endif // #ifdef
+
+ // Arrow Pad section:
+ case KEY_NAME_INDEX_ArrowLeft:
+ return CODE_NAME_INDEX_ArrowLeft;
+ case KEY_NAME_INDEX_ArrowUp:
+ return CODE_NAME_INDEX_ArrowUp;
+ case KEY_NAME_INDEX_ArrowDown:
+ return CODE_NAME_INDEX_ArrowDown;
+ case KEY_NAME_INDEX_ArrowRight:
+ return CODE_NAME_INDEX_ArrowRight;
+
+ // Control Pad section:
+#ifndef XP_MACOSX
+ case KEY_NAME_INDEX_Insert:
+ return CODE_NAME_INDEX_Insert;
+#else
+ case KEY_NAME_INDEX_Help:
+ return CODE_NAME_INDEX_Help;
+#endif // #ifndef XP_MACOSX #else
+ case KEY_NAME_INDEX_Delete:
+ return CODE_NAME_INDEX_Delete;
+ case KEY_NAME_INDEX_Home:
+ return CODE_NAME_INDEX_Home;
+ case KEY_NAME_INDEX_End:
+ return CODE_NAME_INDEX_End;
+ case KEY_NAME_INDEX_PageUp:
+ return CODE_NAME_INDEX_PageUp;
+ case KEY_NAME_INDEX_PageDown:
+ return CODE_NAME_INDEX_PageDown;
+
+ // Function keys:
+ case KEY_NAME_INDEX_F1:
+ return CODE_NAME_INDEX_F1;
+ case KEY_NAME_INDEX_F2:
+ return CODE_NAME_INDEX_F2;
+ case KEY_NAME_INDEX_F3:
+ return CODE_NAME_INDEX_F3;
+ case KEY_NAME_INDEX_F4:
+ return CODE_NAME_INDEX_F4;
+ case KEY_NAME_INDEX_F5:
+ return CODE_NAME_INDEX_F5;
+ case KEY_NAME_INDEX_F6:
+ return CODE_NAME_INDEX_F6;
+ case KEY_NAME_INDEX_F7:
+ return CODE_NAME_INDEX_F7;
+ case KEY_NAME_INDEX_F8:
+ return CODE_NAME_INDEX_F8;
+ case KEY_NAME_INDEX_F9:
+ return CODE_NAME_INDEX_F9;
+ case KEY_NAME_INDEX_F10:
+ return CODE_NAME_INDEX_F10;
+ case KEY_NAME_INDEX_F11:
+ return CODE_NAME_INDEX_F11;
+ case KEY_NAME_INDEX_F12:
+ return CODE_NAME_INDEX_F12;
+ case KEY_NAME_INDEX_F13:
+ return CODE_NAME_INDEX_F13;
+ case KEY_NAME_INDEX_F14:
+ return CODE_NAME_INDEX_F14;
+ case KEY_NAME_INDEX_F15:
+ return CODE_NAME_INDEX_F15;
+ case KEY_NAME_INDEX_F16:
+ return CODE_NAME_INDEX_F16;
+ case KEY_NAME_INDEX_F17:
+ return CODE_NAME_INDEX_F17;
+ case KEY_NAME_INDEX_F18:
+ return CODE_NAME_INDEX_F18;
+ case KEY_NAME_INDEX_F19:
+ return CODE_NAME_INDEX_F19;
+ case KEY_NAME_INDEX_F20:
+ return CODE_NAME_INDEX_F20;
+#ifndef XP_MACOSX
+ case KEY_NAME_INDEX_F21:
+ return CODE_NAME_INDEX_F21;
+ case KEY_NAME_INDEX_F22:
+ return CODE_NAME_INDEX_F22;
+ case KEY_NAME_INDEX_F23:
+ return CODE_NAME_INDEX_F23;
+ case KEY_NAME_INDEX_F24:
+ return CODE_NAME_INDEX_F24;
+ case KEY_NAME_INDEX_Pause:
+ return CODE_NAME_INDEX_Pause;
+ case KEY_NAME_INDEX_PrintScreen:
+ return CODE_NAME_INDEX_PrintScreen;
+ case KEY_NAME_INDEX_ScrollLock:
+ return CODE_NAME_INDEX_ScrollLock;
+#endif // #ifndef XP_MACOSX
+
+ // NumLock key:
+#ifndef XP_MACOSX
+ case KEY_NAME_INDEX_NumLock:
+ return CODE_NAME_INDEX_NumLock;
+#else
+ case KEY_NAME_INDEX_Clear:
+ return CODE_NAME_INDEX_NumLock;
+#endif // #ifndef XP_MACOSX #else
+
+ // Media keys:
+ case KEY_NAME_INDEX_AudioVolumeDown:
+ return CODE_NAME_INDEX_VolumeDown;
+ case KEY_NAME_INDEX_AudioVolumeMute:
+ return CODE_NAME_INDEX_VolumeMute;
+ case KEY_NAME_INDEX_AudioVolumeUp:
+ return CODE_NAME_INDEX_VolumeUp;
+#ifndef XP_MACOSX
+ case KEY_NAME_INDEX_BrowserBack:
+ return CODE_NAME_INDEX_BrowserBack;
+ case KEY_NAME_INDEX_BrowserFavorites:
+ return CODE_NAME_INDEX_BrowserFavorites;
+ case KEY_NAME_INDEX_BrowserForward:
+ return CODE_NAME_INDEX_BrowserForward;
+ case KEY_NAME_INDEX_BrowserRefresh:
+ return CODE_NAME_INDEX_BrowserRefresh;
+ case KEY_NAME_INDEX_BrowserSearch:
+ return CODE_NAME_INDEX_BrowserSearch;
+ case KEY_NAME_INDEX_BrowserStop:
+ return CODE_NAME_INDEX_BrowserStop;
+ case KEY_NAME_INDEX_MediaPlayPause:
+ return CODE_NAME_INDEX_MediaPlayPause;
+ case KEY_NAME_INDEX_MediaStop:
+ return CODE_NAME_INDEX_MediaStop;
+ case KEY_NAME_INDEX_MediaTrackNext:
+ return CODE_NAME_INDEX_MediaTrackNext;
+ case KEY_NAME_INDEX_MediaTrackPrevious:
+ return CODE_NAME_INDEX_MediaTrackPrevious;
+ case KEY_NAME_INDEX_LaunchApplication1:
+ return CODE_NAME_INDEX_LaunchApp1;
+#endif // #ifndef XP_MACOSX
+
+ // Only Windows and GTK supports the following multimedia keys.
+#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+ case KEY_NAME_INDEX_BrowserHome:
+ return CODE_NAME_INDEX_BrowserHome;
+ case KEY_NAME_INDEX_LaunchApplication2:
+ return CODE_NAME_INDEX_LaunchApp2;
+#endif // #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+
+ // Only GTK and Android supports the following multimedia keys.
+#if defined(MOZ_WIDGET_GTK) || defined(ANDROID)
+ case KEY_NAME_INDEX_Eject:
+ return CODE_NAME_INDEX_Eject;
+ case KEY_NAME_INDEX_WakeUp:
+ return CODE_NAME_INDEX_WakeUp;
+#endif // #if defined(MOZ_WIDGET_GTK) || defined(ANDROID)
+
+ // Only Windows does not support Help key (and macOS handled above).
+#if !defined(XP_WIN) && !defined(XP_MACOSX)
+ case KEY_NAME_INDEX_Help:
+ return CODE_NAME_INDEX_Help;
+#endif // #if !defined(XP_WIN) && !defined(XP_MACOSX)
+
+ // IME specific keys:
+#ifdef XP_WIN
+ case KEY_NAME_INDEX_Convert:
+ return CODE_NAME_INDEX_Convert;
+ case KEY_NAME_INDEX_NonConvert:
+ return CODE_NAME_INDEX_NonConvert;
+ case KEY_NAME_INDEX_Alphanumeric:
+ return CODE_NAME_INDEX_CapsLock;
+ case KEY_NAME_INDEX_KanaMode:
+ case KEY_NAME_INDEX_Romaji:
+ case KEY_NAME_INDEX_Katakana:
+ case KEY_NAME_INDEX_Hiragana:
+ return CODE_NAME_INDEX_KanaMode;
+ case KEY_NAME_INDEX_Hankaku:
+ case KEY_NAME_INDEX_Zenkaku:
+ case KEY_NAME_INDEX_KanjiMode:
+ return CODE_NAME_INDEX_Backquote;
+ case KEY_NAME_INDEX_HanjaMode:
+ return CODE_NAME_INDEX_Lang2;
+ case KEY_NAME_INDEX_HangulMode:
+ return CODE_NAME_INDEX_Lang1;
+#endif // #ifdef XP_WIN
+
+#ifdef MOZ_WIDGET_GTK
+ case KEY_NAME_INDEX_Convert:
+ return CODE_NAME_INDEX_Convert;
+ case KEY_NAME_INDEX_NonConvert:
+ return CODE_NAME_INDEX_NonConvert;
+ case KEY_NAME_INDEX_Alphanumeric:
+ return CODE_NAME_INDEX_CapsLock;
+ case KEY_NAME_INDEX_HiraganaKatakana:
+ return CODE_NAME_INDEX_KanaMode;
+ case KEY_NAME_INDEX_ZenkakuHankaku:
+ return CODE_NAME_INDEX_Backquote;
+#endif // #ifdef MOZ_WIDGET_GTK
+
+#ifdef ANDROID
+ case KEY_NAME_INDEX_Convert:
+ return CODE_NAME_INDEX_Convert;
+ case KEY_NAME_INDEX_NonConvert:
+ return CODE_NAME_INDEX_NonConvert;
+ case KEY_NAME_INDEX_HiraganaKatakana:
+ return CODE_NAME_INDEX_KanaMode;
+ case KEY_NAME_INDEX_ZenkakuHankaku:
+ return CODE_NAME_INDEX_Backquote;
+ case KEY_NAME_INDEX_Eisu:
+ return CODE_NAME_INDEX_Lang2;
+ case KEY_NAME_INDEX_KanjiMode:
+ return CODE_NAME_INDEX_Lang1;
+#endif // #ifdef ANDROID
+
+#ifdef XP_MACOSX
+ case KEY_NAME_INDEX_Eisu:
+ return CODE_NAME_INDEX_Lang2;
+ case KEY_NAME_INDEX_KanjiMode:
+ return CODE_NAME_INDEX_Lang1;
+#endif // #ifdef XP_MACOSX
+
+ default:
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+}
+
+/* static */
+Modifier WidgetKeyboardEvent::GetModifierForKeyName(
+ KeyNameIndex aKeyNameIndex) {
+ switch (aKeyNameIndex) {
+ case KEY_NAME_INDEX_Alt:
+ return MODIFIER_ALT;
+ case KEY_NAME_INDEX_AltGraph:
+ return MODIFIER_ALTGRAPH;
+ case KEY_NAME_INDEX_CapsLock:
+ return MODIFIER_CAPSLOCK;
+ case KEY_NAME_INDEX_Control:
+ return MODIFIER_CONTROL;
+ case KEY_NAME_INDEX_Fn:
+ return MODIFIER_FN;
+ case KEY_NAME_INDEX_FnLock:
+ return MODIFIER_FNLOCK;
+ // case KEY_NAME_INDEX_Hyper:
+ case KEY_NAME_INDEX_Meta:
+ return MODIFIER_META;
+ case KEY_NAME_INDEX_NumLock:
+ return MODIFIER_NUMLOCK;
+ case KEY_NAME_INDEX_OS:
+ return MODIFIER_OS;
+ case KEY_NAME_INDEX_ScrollLock:
+ return MODIFIER_SCROLLLOCK;
+ case KEY_NAME_INDEX_Shift:
+ return MODIFIER_SHIFT;
+ // case KEY_NAME_INDEX_Super:
+ case KEY_NAME_INDEX_Symbol:
+ return MODIFIER_SYMBOL;
+ case KEY_NAME_INDEX_SymbolLock:
+ return MODIFIER_SYMBOLLOCK;
+ default:
+ return MODIFIER_NONE;
+ }
+}
+
+/* static */
+bool WidgetKeyboardEvent::IsLockableModifier(KeyNameIndex aKeyNameIndex) {
+ switch (aKeyNameIndex) {
+ case KEY_NAME_INDEX_CapsLock:
+ case KEY_NAME_INDEX_FnLock:
+ case KEY_NAME_INDEX_NumLock:
+ case KEY_NAME_INDEX_ScrollLock:
+ case KEY_NAME_INDEX_SymbolLock:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/******************************************************************************
+ * mozilla::InternalEditorInputEvent (TextEvents.h)
+ ******************************************************************************/
+
+#define NS_DEFINE_INPUTTYPE(aCPPName, aDOMName) (u"" aDOMName),
+const char16_t* const InternalEditorInputEvent::kInputTypeNames[] = {
+#include "mozilla/InputTypeList.h"
+};
+#undef NS_DEFINE_INPUTTYPE
+
+InternalEditorInputEvent::InputTypeHashtable*
+ InternalEditorInputEvent::sInputTypeHashtable = nullptr;
+
+/* static */
+void InternalEditorInputEvent::Shutdown() {
+ delete sInputTypeHashtable;
+ sInputTypeHashtable = nullptr;
+}
+
+/* static */
+void InternalEditorInputEvent::GetDOMInputTypeName(EditorInputType aInputType,
+ nsAString& aInputTypeName) {
+ if (static_cast<size_t>(aInputType) >=
+ static_cast<size_t>(EditorInputType::eUnknown)) {
+ aInputTypeName.Truncate();
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(
+ static_cast<size_t>(aInputType) < ArrayLength(kInputTypeNames),
+ "Illegal input type enumeration value");
+ aInputTypeName.Assign(kInputTypeNames[static_cast<size_t>(aInputType)]);
+}
+
+/* static */
+EditorInputType InternalEditorInputEvent::GetEditorInputType(
+ const nsAString& aInputType) {
+ if (aInputType.IsEmpty()) {
+ return EditorInputType::eUnknown;
+ }
+
+ if (!sInputTypeHashtable) {
+ sInputTypeHashtable = new InputTypeHashtable(ArrayLength(kInputTypeNames));
+ for (size_t i = 0; i < ArrayLength(kInputTypeNames); i++) {
+ sInputTypeHashtable->Put(nsDependentString(kInputTypeNames[i]),
+ static_cast<EditorInputType>(i));
+ }
+ }
+ EditorInputType result = EditorInputType::eUnknown;
+ sInputTypeHashtable->Get(aInputType, &result);
+ return result;
+}
+
+} // namespace mozilla
diff --git a/widget/WidgetMessageUtils.h b/widget/WidgetMessageUtils.h
new file mode 100644
index 0000000000..8aa2e01591
--- /dev/null
+++ b/widget/WidgetMessageUtils.h
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_WidgetMessageUtils_h
+#define mozilla_WidgetMessageUtils_h
+
+#include "ipc/EnumSerializer.h"
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/widget/ThemeChangeKind.h"
+#include "nsIWidget.h"
+#include "nsStyleConsts.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::widget::ThemeChangeKind>
+ : public BitFlagsEnumSerializer<mozilla::widget::ThemeChangeKind,
+ mozilla::widget::ThemeChangeKind::AllBits> {
+};
+
+template <>
+struct ParamTraits<mozilla::LookAndFeel::IntID>
+ : ContiguousEnumSerializer<mozilla::LookAndFeel::IntID,
+ mozilla::LookAndFeel::IntID::CaretBlinkTime,
+ mozilla::LookAndFeel::IntID::End> {
+ using IdType = std::underlying_type_t<mozilla::LookAndFeel::IntID>;
+ static_assert(static_cast<IdType>(
+ mozilla::LookAndFeel::IntID::CaretBlinkTime) == IdType(0));
+};
+
+template <>
+struct ParamTraits<mozilla::LookAndFeel::ColorID>
+ : ContiguousEnumSerializer<mozilla::LookAndFeel::ColorID,
+ mozilla::LookAndFeel::ColorID::WindowBackground,
+ mozilla::LookAndFeel::ColorID::End> {
+ using IdType = std::underlying_type_t<mozilla::LookAndFeel::ColorID>;
+ static_assert(
+ static_cast<IdType>(mozilla::LookAndFeel::ColorID::WindowBackground) ==
+ IdType(0));
+};
+
+template <>
+struct ParamTraits<nsTransparencyMode>
+ : ContiguousEnumSerializerInclusive<nsTransparencyMode, eTransparencyOpaque,
+ eTransparencyBorderlessGlass> {};
+
+template <>
+struct ParamTraits<nsCursor>
+ : ContiguousEnumSerializer<nsCursor, eCursor_standard, eCursorCount> {};
+
+template <>
+struct ParamTraits<nsIWidget::TouchPointerState>
+ : public BitFlagsEnumSerializer<nsIWidget::TouchPointerState,
+ nsIWidget::TouchPointerState::ALL_BITS> {};
+
+} // namespace IPC
+
+#endif // WidgetMessageUtils_h
diff --git a/widget/WidgetTraceEvent.h b/widget/WidgetTraceEvent.h
new file mode 100644
index 0000000000..7f85cc91c0
--- /dev/null
+++ b/widget/WidgetTraceEvent.h
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WIDGET_PUBLIC_WIDGETTRACEEVENT_H_
+#define WIDGET_PUBLIC_WIDGETTRACEEVENT_H_
+
+namespace mozilla {
+
+// Perform any required initialization in the widget backend for
+// event tracing. Return true if initialization was successful.
+bool InitWidgetTracing();
+
+// Perform any required cleanup in the widget backend for event tracing.
+void CleanUpWidgetTracing();
+
+// Fire a tracer event at the UI-thread event loop, and block until
+// the event is processed. This should only be called by
+// a thread that's not the UI thread.
+bool FireAndWaitForTracerEvent();
+
+// Signal that the event has been received by the event loop.
+void SignalTracerThread();
+
+} // namespace mozilla
+
+#endif // WIDGET_PUBLIC_WIDGETTRACEEVENT_H_
diff --git a/widget/WidgetUtils.cpp b/widget/WidgetUtils.cpp
new file mode 100644
index 0000000000..d3b47a5d30
--- /dev/null
+++ b/widget/WidgetUtils.cpp
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/WidgetUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+#include "nsIBidiKeyboard.h"
+#include "nsIStringBundle.h"
+#include "nsTArray.h"
+#ifdef XP_WIN
+# include "WinUtils.h"
+#endif
+#ifdef MOZ_WIDGET_GTK
+# include "mozilla/WidgetUtilsGtk.h"
+#endif
+
+namespace mozilla {
+
+gfx::Matrix ComputeTransformForRotation(const nsIntRect& aBounds,
+ ScreenRotation aRotation) {
+ gfx::Matrix transform;
+ static const gfx::Float floatPi = static_cast<gfx::Float>(M_PI);
+
+ switch (aRotation) {
+ case ROTATION_0:
+ break;
+ case ROTATION_90:
+ transform.PreTranslate(aBounds.Width(), 0);
+ transform.PreRotate(floatPi / 2);
+ break;
+ case ROTATION_180:
+ transform.PreTranslate(aBounds.Width(), aBounds.Height());
+ transform.PreRotate(floatPi);
+ break;
+ case ROTATION_270:
+ transform.PreTranslate(0, aBounds.Height());
+ transform.PreRotate(floatPi * 3 / 2);
+ break;
+ default:
+ MOZ_CRASH("Unknown rotation");
+ }
+ return transform;
+}
+
+gfx::Matrix ComputeTransformForUnRotation(const nsIntRect& aBounds,
+ ScreenRotation aRotation) {
+ gfx::Matrix transform;
+ static const gfx::Float floatPi = static_cast<gfx::Float>(M_PI);
+
+ switch (aRotation) {
+ case ROTATION_0:
+ break;
+ case ROTATION_90:
+ transform.PreTranslate(0, aBounds.Height());
+ transform.PreRotate(floatPi * 3 / 2);
+ break;
+ case ROTATION_180:
+ transform.PreTranslate(aBounds.Width(), aBounds.Height());
+ transform.PreRotate(floatPi);
+ break;
+ case ROTATION_270:
+ transform.PreTranslate(aBounds.Width(), 0);
+ transform.PreRotate(floatPi / 2);
+ break;
+ default:
+ MOZ_CRASH("Unknown rotation");
+ }
+ return transform;
+}
+
+nsIntRect RotateRect(nsIntRect aRect, const nsIntRect& aBounds,
+ ScreenRotation aRotation) {
+ switch (aRotation) {
+ case ROTATION_0:
+ return aRect;
+ case ROTATION_90:
+ return nsIntRect(aRect.Y(), aBounds.Width() - aRect.XMost(),
+ aRect.Height(), aRect.Width());
+ case ROTATION_180:
+ return nsIntRect(aBounds.Width() - aRect.XMost(),
+ aBounds.Height() - aRect.YMost(), aRect.Width(),
+ aRect.Height());
+ case ROTATION_270:
+ return nsIntRect(aBounds.Height() - aRect.YMost(), aRect.X(),
+ aRect.Height(), aRect.Width());
+ default:
+ MOZ_CRASH("Unknown rotation");
+ }
+}
+
+namespace widget {
+
+uint32_t WidgetUtils::IsTouchDeviceSupportPresent() {
+#ifdef XP_WIN
+ return WinUtils::IsTouchDeviceSupportPresent();
+#elif defined(MOZ_WIDGET_GTK)
+ return WidgetUtilsGTK::IsTouchDeviceSupportPresent();
+#else
+ return 0;
+#endif
+}
+
+// static
+void WidgetUtils::SendBidiKeyboardInfoToContent() {
+ nsCOMPtr<nsIBidiKeyboard> bidiKeyboard = nsContentUtils::GetBidiKeyboard();
+ if (!bidiKeyboard) {
+ return;
+ }
+
+ bool rtl;
+ if (NS_FAILED(bidiKeyboard->IsLangRTL(&rtl))) {
+ return;
+ }
+ bool bidiKeyboards = false;
+ bidiKeyboard->GetHaveBidiKeyboards(&bidiKeyboards);
+
+ nsTArray<dom::ContentParent*> children;
+ dom::ContentParent::GetAll(children);
+ for (uint32_t i = 0; i < children.Length(); i++) {
+ Unused << children[i]->SendBidiKeyboardNotify(rtl, bidiKeyboards);
+ }
+}
+
+// static
+void WidgetUtils::GetBrandShortName(nsAString& aBrandName) {
+ aBrandName.Truncate();
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ if (bundleService) {
+ bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(bundle));
+ }
+
+ if (bundle) {
+ bundle->GetStringFromName("brandShortName", aBrandName);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/WidgetUtils.h b/widget/WidgetUtils.h
new file mode 100644
index 0000000000..0a0435ddd6
--- /dev/null
+++ b/widget/WidgetUtils.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_WidgetUtils_h
+#define mozilla_WidgetUtils_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/gfx/Matrix.h"
+#include "nsRect.h"
+
+class nsIWidget;
+class nsPIDOMWindowOuter;
+
+namespace mozilla {
+
+// NB: these must match up with pseudo-enum in nsIScreen.idl.
+enum ScreenRotation {
+ ROTATION_0 = 0,
+ ROTATION_90,
+ ROTATION_180,
+ ROTATION_270,
+
+ ROTATION_COUNT
+};
+
+gfx::Matrix ComputeTransformForRotation(const nsIntRect& aBounds,
+ ScreenRotation aRotation);
+
+gfx::Matrix ComputeTransformForUnRotation(const nsIntRect& aBounds,
+ ScreenRotation aRotation);
+
+nsIntRect RotateRect(nsIntRect aRect, const nsIntRect& aBounds,
+ ScreenRotation aRotation);
+
+namespace widget {
+
+class WidgetUtils {
+ public:
+ /**
+ * Shutdown() is called when "xpcom-will-shutdown" is notified. This is
+ * useful when you need to observe the notification in XP level code under
+ * widget.
+ */
+ static void Shutdown();
+
+ /**
+ * Starting at the docshell item for the passed in DOM window this looks up
+ * the docshell tree until it finds a docshell item that has a widget.
+ */
+ static already_AddRefed<nsIWidget> DOMWindowToWidget(
+ nsPIDOMWindowOuter* aDOMWindow);
+
+ /**
+ * Compute our keyCode value (NS_VK_*) from an ASCII character.
+ */
+ static uint32_t ComputeKeyCodeFromChar(uint32_t aCharCode);
+
+ /**
+ * Get unshifted charCode and shifted charCode for aKeyCode if the keyboad
+ * layout is a Latin keyboard layout.
+ *
+ * @param aKeyCode Our keyCode (NS_VK_*).
+ * @param aIsCapsLock TRUE if CapsLock is Locked. Otherwise, FALSE.
+ * This is used only when aKeyCode is NS_VK_[0-9].
+ * @param aUnshiftedCharCode CharCode for aKeyCode without Shift key.
+ * This may be zero if aKeyCode key doesn't input
+ * a Latin character.
+ * Note that must not be nullptr.
+ * @param aShiftedCharCode CharCode for aKeyCOde with Shift key.
+ * This is always 0 when aKeyCode isn't
+ * NS_VK_[A-Z].
+ * Note that must not be nullptr.
+ */
+ static void GetLatinCharCodeForKeyCode(uint32_t aKeyCode, bool aIsCapsLock,
+ uint32_t* aUnshiftedCharCode,
+ uint32_t* aShiftedCharCode);
+
+ /**
+ * Does device have touch support
+ */
+ static uint32_t IsTouchDeviceSupportPresent();
+
+ /**
+ * Send bidi keyboard information to content process
+ */
+ static void SendBidiKeyboardInfoToContent();
+
+ /**
+ * Get branchShortName from string bundle
+ */
+ static void GetBrandShortName(nsAString& aBrandName);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_WidgetUtils_h
diff --git a/widget/WindowSurface.h b/widget/WindowSurface.h
new file mode 100644
index 0000000000..f852e83bce
--- /dev/null
+++ b/widget/WindowSurface.h
@@ -0,0 +1,38 @@
+/* -*- 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 _MOZILLA_WIDGET_WINDOW_SURFACE_H
+#define _MOZILLA_WIDGET_WINDOW_SURFACE_H
+
+#include "mozilla/gfx/2D.h"
+#include "Units.h"
+
+namespace mozilla {
+namespace widget {
+
+// A class for drawing to double-buffered windows.
+class WindowSurface {
+ public:
+ virtual ~WindowSurface() = default;
+
+ // Locks a region of the window for drawing, returning a draw target
+ // capturing the bounds of the provided region.
+ // Implementations must permit invocation from any thread.
+ virtual already_AddRefed<gfx::DrawTarget> Lock(
+ const LayoutDeviceIntRegion& aRegion) = 0;
+
+ // Swaps the provided invalid region from the back buffer to the window.
+ // Implementations must permit invocation from any thread.
+ virtual void Commit(const LayoutDeviceIntRegion& aInvalidRegion) = 0;
+
+ // Whether the window surface represents a fallback method.
+ virtual bool IsFallback() const { return false; }
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // _MOZILLA_WIDGET_WINDOW_SURFACE_H
diff --git a/widget/WindowSurfaceX11SHM.cpp b/widget/WindowSurfaceX11SHM.cpp
new file mode 100644
index 0000000000..889881d22d
--- /dev/null
+++ b/widget/WindowSurfaceX11SHM.cpp
@@ -0,0 +1,27 @@
+/* -*- 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 "WindowSurfaceX11SHM.h"
+
+namespace mozilla::widget {
+
+WindowSurfaceX11SHM::WindowSurfaceX11SHM(Display* aDisplay, Drawable aWindow,
+ Visual* aVisual, unsigned int aDepth) {
+ mFrontImage = new nsShmImage(aDisplay, aWindow, aVisual, aDepth);
+ mBackImage = new nsShmImage(aDisplay, aWindow, aVisual, aDepth);
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceX11SHM::Lock(
+ const LayoutDeviceIntRegion& aRegion) {
+ mBackImage.swap(mFrontImage);
+ return mBackImage->CreateDrawTarget(aRegion);
+}
+
+void WindowSurfaceX11SHM::Commit(const LayoutDeviceIntRegion& aInvalidRegion) {
+ mBackImage->Put(aInvalidRegion);
+}
+
+} // namespace mozilla::widget
diff --git a/widget/WindowSurfaceX11SHM.h b/widget/WindowSurfaceX11SHM.h
new file mode 100644
index 0000000000..5d12137f7b
--- /dev/null
+++ b/widget/WindowSurfaceX11SHM.h
@@ -0,0 +1,36 @@
+/* -*- 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 _MOZILLA_WIDGET_WINDOW_SURFACE_X11_SHM_H
+#define _MOZILLA_WIDGET_WINDOW_SURFACE_X11_SHM_H
+
+#ifdef MOZ_X11
+
+# include "mozilla/widget/WindowSurface.h"
+# include "nsShmImage.h"
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceX11SHM : public WindowSurface {
+ public:
+ WindowSurfaceX11SHM(Display* aDisplay, Drawable aWindow, Visual* aVisual,
+ unsigned int aDepth);
+
+ already_AddRefed<gfx::DrawTarget> Lock(
+ const LayoutDeviceIntRegion& aRegion) override;
+ void Commit(const LayoutDeviceIntRegion& aInvalidRegion) override;
+
+ private:
+ RefPtr<nsShmImage> mFrontImage;
+ RefPtr<nsShmImage> mBackImage;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_WINDOW_SURFACE_X11_SHM_H
diff --git a/widget/android/AndroidAlerts.cpp b/widget/android/AndroidAlerts.cpp
new file mode 100644
index 0000000000..33c779f23d
--- /dev/null
+++ b/widget/android/AndroidAlerts.cpp
@@ -0,0 +1,146 @@
+/* -*- 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 "AndroidAlerts.h"
+#include "mozilla/java/GeckoRuntimeWrappers.h"
+#include "mozilla/java/WebNotificationWrappers.h"
+#include "nsIPrincipal.h"
+#include "nsIURI.h"
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS(AndroidAlerts, nsIAlertsService)
+
+StaticAutoPtr<AndroidAlerts::ListenerMap> AndroidAlerts::sListenerMap;
+nsDataHashtable<nsStringHashKey, java::WebNotification::GlobalRef>
+ AndroidAlerts::mNotificationsMap;
+
+NS_IMETHODIMP
+AndroidAlerts::ShowAlertNotification(
+ const nsAString& aImageUrl, const nsAString& aAlertTitle,
+ const nsAString& aAlertText, bool aAlertTextClickable,
+ const nsAString& aAlertCookie, nsIObserver* aAlertListener,
+ const nsAString& aAlertName, const nsAString& aBidi, const nsAString& aLang,
+ const nsAString& aData, nsIPrincipal* aPrincipal, bool aInPrivateBrowsing,
+ bool aRequireInteraction) {
+ MOZ_ASSERT_UNREACHABLE("Should be implemented by nsAlertsService.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidAlerts::ShowAlert(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener) {
+ return ShowPersistentNotification(u""_ns, aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP
+AndroidAlerts::ShowPersistentNotification(const nsAString& aPersistentData,
+ nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener) {
+ // nsAlertsService disables our alerts backend if we ever return failure
+ // here. To keep the backend enabled, we always return NS_OK even if we
+ // encounter an error here.
+ nsresult rv;
+
+ nsAutoString imageUrl;
+ rv = aAlert->GetImageURL(imageUrl);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsAutoString title;
+ rv = aAlert->GetTitle(title);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsAutoString text;
+ rv = aAlert->GetText(text);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsAutoString cookie;
+ rv = aAlert->GetCookie(cookie);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsAutoString name;
+ rv = aAlert->GetName(name);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsAutoString lang;
+ rv = aAlert->GetLang(lang);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsAutoString dir;
+ rv = aAlert->GetDir(dir);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ bool requireInteraction;
+ rv = aAlert->GetRequireInteraction(&requireInteraction);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = aAlert->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsCString spec;
+ if (uri) {
+ rv = uri->GetDisplaySpec(spec);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ }
+
+ if (aPersistentData.IsEmpty() && aAlertListener) {
+ if (!sListenerMap) {
+ sListenerMap = new ListenerMap();
+ }
+ // This will remove any observers already registered for this name.
+ sListenerMap->Put(name, aAlertListener);
+ }
+
+ java::WebNotification::LocalRef notification = notification->New(
+ title, name, cookie, text, imageUrl, dir, lang, requireInteraction, spec);
+ java::GeckoRuntime::LocalRef runtime = java::GeckoRuntime::GetInstance();
+ if (runtime != NULL) {
+ runtime->NotifyOnShow(notification);
+ }
+ mNotificationsMap.Put(name, notification);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AndroidAlerts::CloseAlert(const nsAString& aAlertName) {
+ java::WebNotification::LocalRef notification =
+ mNotificationsMap.Get(aAlertName);
+ if (!notification) {
+ return NS_OK;
+ }
+
+ java::GeckoRuntime::LocalRef runtime = java::GeckoRuntime::GetInstance();
+ if (runtime != NULL) {
+ runtime->NotifyOnClose(notification);
+ }
+ mNotificationsMap.Remove(aAlertName);
+
+ return NS_OK;
+}
+
+void AndroidAlerts::NotifyListener(const nsAString& aName, const char* aTopic,
+ const char16_t* aCookie) {
+ if (!sListenerMap) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserver> listener = sListenerMap->Get(aName);
+ if (!listener) {
+ return;
+ }
+
+ listener->Observe(nullptr, aTopic, aCookie);
+
+ if ("alertfinished"_ns.Equals(aTopic)) {
+ sListenerMap->Remove(aName);
+ mNotificationsMap.Remove(aName);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/android/AndroidAlerts.h b/widget/android/AndroidAlerts.h
new file mode 100644
index 0000000000..bab955232a
--- /dev/null
+++ b/widget/android/AndroidAlerts.h
@@ -0,0 +1,47 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_AndroidAlerts_h__
+#define mozilla_widget_AndroidAlerts_h__
+
+#include "nsDataHashtable.h"
+#include "nsInterfaceHashtable.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsIAlertsService.h"
+#include "nsIObserver.h"
+
+#include "mozilla/java/WebNotificationWrappers.h"
+
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace widget {
+
+class AndroidAlerts : public nsIAlertsService {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIALERTSSERVICE
+
+ AndroidAlerts() {}
+
+ static void NotifyListener(const nsAString& aName, const char* aTopic,
+ const char16_t* aCookie);
+
+ static nsDataHashtable<nsStringHashKey,
+ mozilla::java::WebNotification::GlobalRef>
+ mNotificationsMap;
+
+ protected:
+ virtual ~AndroidAlerts() { sListenerMap = nullptr; }
+
+ using ListenerMap = nsInterfaceHashtable<nsStringHashKey, nsIObserver>;
+ static StaticAutoPtr<ListenerMap> sListenerMap;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // nsAndroidAlerts_h__
diff --git a/widget/android/AndroidBridge.cpp b/widget/android/AndroidBridge.cpp
new file mode 100644
index 0000000000..71215e41b0
--- /dev/null
+++ b/widget/android/AndroidBridge.cpp
@@ -0,0 +1,741 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 <android/log.h>
+#include <dlfcn.h>
+#include <math.h>
+#include <GLES2/gl2.h>
+
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+
+#include "mozilla/Hal.h"
+#include "nsXULAppAPI.h"
+#include <prthread.h>
+#include "AndroidBridge.h"
+#include "AndroidBridgeUtilities.h"
+#include "AndroidRect.h"
+#include "nsAlertsUtils.h"
+#include "nsAppShell.h"
+#include "nsOSHelperAppService.h"
+#include "nsWindow.h"
+#include "mozilla/Preferences.h"
+#include "nsThreadUtils.h"
+#include "gfxPlatform.h"
+#include "gfxContext.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxUtils.h"
+#include "nsPresContext.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsPrintfCString.h"
+#include "nsContentUtils.h"
+
+#include "EventDispatcher.h"
+#include "MediaCodec.h"
+#include "SurfaceTexture.h"
+#include "GLContextProvider.h"
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "WidgetUtils.h"
+
+#include "mozilla/java/EventDispatcherWrappers.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+#include "mozilla/java/GeckoThreadWrappers.h"
+#include "mozilla/java/HardwareCodecCapabilityUtilsWrappers.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+AndroidBridge* AndroidBridge::sBridge = nullptr;
+static jobject sGlobalContext = nullptr;
+nsDataHashtable<nsStringHashKey, nsString> AndroidBridge::sStoragePaths;
+
+jmethodID AndroidBridge::GetMethodID(JNIEnv* env, jclass jClass,
+ const char* methodName,
+ const char* methodType) {
+ jmethodID methodID = env->GetMethodID(jClass, methodName, methodType);
+ if (!methodID) {
+ ALOG(
+ ">>> FATAL JNI ERROR! GetMethodID(methodName=\"%s\", "
+ "methodType=\"%s\") failed. Did ProGuard optimize away something it "
+ "shouldn't have?",
+ methodName, methodType);
+ env->ExceptionDescribe();
+ MOZ_CRASH();
+ }
+ return methodID;
+}
+
+jmethodID AndroidBridge::GetStaticMethodID(JNIEnv* env, jclass jClass,
+ const char* methodName,
+ const char* methodType) {
+ jmethodID methodID = env->GetStaticMethodID(jClass, methodName, methodType);
+ if (!methodID) {
+ ALOG(
+ ">>> FATAL JNI ERROR! GetStaticMethodID(methodName=\"%s\", "
+ "methodType=\"%s\") failed. Did ProGuard optimize away something it "
+ "shouldn't have?",
+ methodName, methodType);
+ env->ExceptionDescribe();
+ MOZ_CRASH();
+ }
+ return methodID;
+}
+
+jfieldID AndroidBridge::GetFieldID(JNIEnv* env, jclass jClass,
+ const char* fieldName,
+ const char* fieldType) {
+ jfieldID fieldID = env->GetFieldID(jClass, fieldName, fieldType);
+ if (!fieldID) {
+ ALOG(
+ ">>> FATAL JNI ERROR! GetFieldID(fieldName=\"%s\", "
+ "fieldType=\"%s\") failed. Did ProGuard optimize away something it "
+ "shouldn't have?",
+ fieldName, fieldType);
+ env->ExceptionDescribe();
+ MOZ_CRASH();
+ }
+ return fieldID;
+}
+
+jfieldID AndroidBridge::GetStaticFieldID(JNIEnv* env, jclass jClass,
+ const char* fieldName,
+ const char* fieldType) {
+ jfieldID fieldID = env->GetStaticFieldID(jClass, fieldName, fieldType);
+ if (!fieldID) {
+ ALOG(
+ ">>> FATAL JNI ERROR! GetStaticFieldID(fieldName=\"%s\", "
+ "fieldType=\"%s\") failed. Did ProGuard optimize away something it "
+ "shouldn't have?",
+ fieldName, fieldType);
+ env->ExceptionDescribe();
+ MOZ_CRASH();
+ }
+ return fieldID;
+}
+
+void AndroidBridge::ConstructBridge() {
+ /* NSS hack -- bionic doesn't handle recursive unloads correctly,
+ * because library finalizer functions are called with the dynamic
+ * linker lock still held. This results in a deadlock when trying
+ * to call dlclose() while we're already inside dlclose().
+ * Conveniently, NSS has an env var that can prevent it from unloading.
+ */
+ putenv(const_cast<char*>("NSS_DISABLE_UNLOAD=1"));
+
+ MOZ_ASSERT(!sBridge);
+ sBridge = new AndroidBridge();
+}
+
+void AndroidBridge::DeconstructBridge() {
+ if (sBridge) {
+ delete sBridge;
+ // AndroidBridge destruction requires sBridge to still be valid,
+ // so we set sBridge to nullptr after deleting it.
+ sBridge = nullptr;
+ }
+}
+
+AndroidBridge::~AndroidBridge() {}
+
+AndroidBridge::AndroidBridge() {
+ ALOG_BRIDGE("AndroidBridge::Init");
+
+ JNIEnv* const jEnv = jni::GetGeckoThreadEnv();
+ AutoLocalJNIFrame jniFrame(jEnv);
+
+ mMessageQueue = java::GeckoThread::MsgQueue();
+ auto msgQueueClass = jni::Class::LocalRef::Adopt(
+ jEnv, jEnv->GetObjectClass(mMessageQueue.Get()));
+ // mMessageQueueNext must not be null
+ mMessageQueueNext =
+ GetMethodID(jEnv, msgQueueClass.Get(), "next", "()Landroid/os/Message;");
+ // mMessageQueueMessages may be null (e.g. due to proguard optimization)
+ mMessageQueueMessages = jEnv->GetFieldID(msgQueueClass.Get(), "mMessages",
+ "Landroid/os/Message;");
+
+ AutoJNIClass string(jEnv, "java/lang/String");
+ jStringClass = string.getGlobalRef();
+
+ mAPIVersion = jni::GetAPIVersion();
+
+ AutoJNIClass channels(jEnv, "java/nio/channels/Channels");
+ jChannels = channels.getGlobalRef();
+ jChannelCreate = channels.getStaticMethod(
+ "newChannel",
+ "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
+
+ AutoJNIClass readableByteChannel(jEnv,
+ "java/nio/channels/ReadableByteChannel");
+ jReadableByteChannel = readableByteChannel.getGlobalRef();
+ jByteBufferRead =
+ readableByteChannel.getMethod("read", "(Ljava/nio/ByteBuffer;)I");
+
+ AutoJNIClass inputStream(jEnv, "java/io/InputStream");
+ jInputStream = inputStream.getGlobalRef();
+ jClose = inputStream.getMethod("close", "()V");
+ jAvailable = inputStream.getMethod("available", "()I");
+}
+
+static void getHandlersFromStringArray(
+ JNIEnv* aJNIEnv, jni::ObjectArray::Param aArr, size_t aLen,
+ nsIMutableArray* aHandlersArray, nsIHandlerApp** aDefaultApp,
+ const nsAString& aAction = u""_ns, const nsACString& aMimeType = ""_ns) {
+ auto getNormalizedString = [](jni::Object::Param obj) -> nsString {
+ nsString out;
+ if (!obj) {
+ out.SetIsVoid(true);
+ } else {
+ out.Assign(jni::String::Ref::From(obj)->ToString());
+ }
+ return out;
+ };
+
+ for (size_t i = 0; i < aLen; i += 4) {
+ nsString name(getNormalizedString(aArr->GetElement(i)));
+ nsString isDefault(getNormalizedString(aArr->GetElement(i + 1)));
+ nsString packageName(getNormalizedString(aArr->GetElement(i + 2)));
+ nsString className(getNormalizedString(aArr->GetElement(i + 3)));
+
+ nsIHandlerApp* app = nsOSHelperAppService::CreateAndroidHandlerApp(
+ name, className, packageName, className, aMimeType, aAction);
+
+ aHandlersArray->AppendElement(app);
+ if (aDefaultApp && isDefault.Length() > 0) *aDefaultApp = app;
+ }
+}
+
+bool AndroidBridge::GetHandlersForMimeType(const nsAString& aMimeType,
+ nsIMutableArray* aHandlersArray,
+ nsIHandlerApp** aDefaultApp,
+ const nsAString& aAction) {
+ ALOG_BRIDGE("AndroidBridge::GetHandlersForMimeType");
+
+ auto arr = java::GeckoAppShell::GetHandlersForMimeType(aMimeType, aAction);
+ if (!arr) return false;
+
+ JNIEnv* const env = arr.Env();
+ size_t len = arr->Length();
+
+ if (!aHandlersArray) return len > 0;
+
+ getHandlersFromStringArray(env, arr, len, aHandlersArray, aDefaultApp,
+ aAction, NS_ConvertUTF16toUTF8(aMimeType));
+ return true;
+}
+
+bool AndroidBridge::HasHWVP8Encoder() {
+ ALOG_BRIDGE("AndroidBridge::HasHWVP8Encoder");
+
+ bool value = java::GeckoAppShell::HasHWVP8Encoder();
+
+ return value;
+}
+
+bool AndroidBridge::HasHWVP8Decoder() {
+ ALOG_BRIDGE("AndroidBridge::HasHWVP8Decoder");
+
+ bool value = java::GeckoAppShell::HasHWVP8Decoder();
+
+ return value;
+}
+
+bool AndroidBridge::HasHWH264() {
+ ALOG_BRIDGE("AndroidBridge::HasHWH264");
+
+ return java::HardwareCodecCapabilityUtils::HasHWH264();
+}
+
+bool AndroidBridge::GetHandlersForURL(const nsAString& aURL,
+ nsIMutableArray* aHandlersArray,
+ nsIHandlerApp** aDefaultApp,
+ const nsAString& aAction) {
+ ALOG_BRIDGE("AndroidBridge::GetHandlersForURL");
+
+ auto arr = java::GeckoAppShell::GetHandlersForURL(aURL, aAction);
+ if (!arr) return false;
+
+ JNIEnv* const env = arr.Env();
+ size_t len = arr->Length();
+
+ if (!aHandlersArray) return len > 0;
+
+ getHandlersFromStringArray(env, arr, len, aHandlersArray, aDefaultApp,
+ aAction);
+ return true;
+}
+
+void AndroidBridge::GetMimeTypeFromExtensions(const nsACString& aFileExt,
+ nsCString& aMimeType) {
+ ALOG_BRIDGE("AndroidBridge::GetMimeTypeFromExtensions");
+
+ auto jstrType = java::GeckoAppShell::GetMimeTypeFromExtensions(aFileExt);
+
+ if (jstrType) {
+ aMimeType = jstrType->ToCString();
+ }
+}
+
+void AndroidBridge::GetExtensionFromMimeType(const nsACString& aMimeType,
+ nsACString& aFileExt) {
+ ALOG_BRIDGE("AndroidBridge::GetExtensionFromMimeType");
+
+ auto jstrExt = java::GeckoAppShell::GetExtensionFromMimeType(aMimeType);
+
+ if (jstrExt) {
+ aFileExt = jstrExt->ToCString();
+ }
+}
+
+gfx::Rect AndroidBridge::getScreenSize() {
+ ALOG_BRIDGE("AndroidBridge::getScreenSize");
+
+ java::sdk::Rect::LocalRef screenrect = java::GeckoAppShell::GetScreenSize();
+ gfx::Rect screensize(screenrect->Left(), screenrect->Top(),
+ screenrect->Width(), screenrect->Height());
+
+ return screensize;
+}
+
+int AndroidBridge::GetScreenDepth() {
+ ALOG_BRIDGE("%s", __PRETTY_FUNCTION__);
+
+ static int sDepth = 0;
+ if (sDepth) return sDepth;
+
+ const int DEFAULT_DEPTH = 16;
+
+ if (jni::IsAvailable()) {
+ sDepth = java::GeckoAppShell::GetScreenDepth();
+ }
+ if (!sDepth) return DEFAULT_DEPTH;
+
+ return sDepth;
+}
+
+void AndroidBridge::Vibrate(const nsTArray<uint32_t>& aPattern) {
+ ALOG_BRIDGE("%s", __PRETTY_FUNCTION__);
+
+ uint32_t len = aPattern.Length();
+ if (!len) {
+ ALOG_BRIDGE(" invalid 0-length array");
+ return;
+ }
+
+ // It's clear if this worth special-casing, but it creates less
+ // java junk, so dodges the GC.
+ if (len == 1) {
+ jlong d = aPattern[0];
+ if (d < 0) {
+ ALOG_BRIDGE(" invalid vibration duration < 0");
+ return;
+ }
+ java::GeckoAppShell::Vibrate(d);
+ return;
+ }
+
+ // First element of the array vibrate() expects is how long to wait
+ // *before* vibrating. For us, this is always 0.
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ AutoLocalJNIFrame jniFrame(env, 1);
+
+ jlongArray array = env->NewLongArray(len + 1);
+ if (!array) {
+ ALOG_BRIDGE(" failed to allocate array");
+ return;
+ }
+
+ jlong* elts = env->GetLongArrayElements(array, nullptr);
+ elts[0] = 0;
+ for (uint32_t i = 0; i < aPattern.Length(); ++i) {
+ jlong d = aPattern[i];
+ if (d < 0) {
+ ALOG_BRIDGE(" invalid vibration duration < 0");
+ env->ReleaseLongArrayElements(array, elts, JNI_ABORT);
+ return;
+ }
+ elts[i + 1] = d;
+ }
+ env->ReleaseLongArrayElements(array, elts, 0);
+
+ java::GeckoAppShell::Vibrate(jni::LongArray::Ref::From(array),
+ -1 /* don't repeat */);
+}
+
+void AndroidBridge::GetIconForExtension(const nsACString& aFileExt,
+ uint32_t aIconSize,
+ uint8_t* const aBuf) {
+ ALOG_BRIDGE("AndroidBridge::GetIconForExtension");
+ NS_ASSERTION(aBuf != nullptr,
+ "AndroidBridge::GetIconForExtension: aBuf is null!");
+ if (!aBuf) return;
+
+ auto arr = java::GeckoAppShell::GetIconForExtension(
+ NS_ConvertUTF8toUTF16(aFileExt), aIconSize);
+
+ NS_ASSERTION(
+ arr != nullptr,
+ "AndroidBridge::GetIconForExtension: Returned pixels array is null!");
+ if (!arr) return;
+
+ JNIEnv* const env = arr.Env();
+ uint32_t len = static_cast<uint32_t>(env->GetArrayLength(arr.Get()));
+ jbyte* elements = env->GetByteArrayElements(arr.Get(), 0);
+
+ uint32_t bufSize = aIconSize * aIconSize * 4;
+ NS_ASSERTION(
+ len == bufSize,
+ "AndroidBridge::GetIconForExtension: Pixels array is incomplete!");
+ if (len == bufSize) memcpy(aBuf, elements, bufSize);
+
+ env->ReleaseByteArrayElements(arr.Get(), elements, 0);
+}
+
+bool AndroidBridge::GetStaticIntField(const char* className,
+ const char* fieldName, int32_t* aInt,
+ JNIEnv* jEnv /* = nullptr */) {
+ ALOG_BRIDGE("AndroidBridge::GetStaticIntField %s", fieldName);
+
+ if (!jEnv) {
+ if (!jni::IsAvailable()) {
+ return false;
+ }
+ jEnv = jni::GetGeckoThreadEnv();
+ }
+
+ AutoJNIClass cls(jEnv, className);
+ jfieldID field = cls.getStaticField(fieldName, "I");
+
+ if (!field) {
+ return false;
+ }
+
+ *aInt = static_cast<int32_t>(jEnv->GetStaticIntField(cls.getRawRef(), field));
+ return true;
+}
+
+bool AndroidBridge::GetStaticStringField(const char* className,
+ const char* fieldName,
+ nsAString& result,
+ JNIEnv* jEnv /* = nullptr */) {
+ ALOG_BRIDGE("AndroidBridge::GetStaticStringField %s", fieldName);
+
+ if (!jEnv) {
+ if (!jni::IsAvailable()) {
+ return false;
+ }
+ jEnv = jni::GetGeckoThreadEnv();
+ }
+
+ AutoLocalJNIFrame jniFrame(jEnv, 1);
+ AutoJNIClass cls(jEnv, className);
+ jfieldID field = cls.getStaticField(fieldName, "Ljava/lang/String;");
+
+ if (!field) {
+ return false;
+ }
+
+ jstring jstr = (jstring)jEnv->GetStaticObjectField(cls.getRawRef(), field);
+ if (!jstr) return false;
+
+ result.Assign(jni::String::Ref::From(jstr)->ToString());
+ return true;
+}
+
+namespace mozilla {
+class TracerRunnable : public Runnable {
+ public:
+ TracerRunnable() : Runnable("TracerRunnable") {
+ mTracerLock = new Mutex("TracerRunnable");
+ mTracerCondVar = new CondVar(*mTracerLock, "TracerRunnable");
+ mMainThread = do_GetMainThread();
+ }
+ ~TracerRunnable() {
+ delete mTracerCondVar;
+ delete mTracerLock;
+ mTracerLock = nullptr;
+ mTracerCondVar = nullptr;
+ }
+
+ virtual nsresult Run() {
+ MutexAutoLock lock(*mTracerLock);
+ if (!AndroidBridge::Bridge()) return NS_OK;
+
+ mHasRun = true;
+ mTracerCondVar->Notify();
+ return NS_OK;
+ }
+
+ bool Fire() {
+ if (!mTracerLock || !mTracerCondVar) return false;
+ MutexAutoLock lock(*mTracerLock);
+ mHasRun = false;
+ mMainThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ while (!mHasRun) mTracerCondVar->Wait();
+ return true;
+ }
+
+ void Signal() {
+ MutexAutoLock lock(*mTracerLock);
+ mHasRun = true;
+ mTracerCondVar->Notify();
+ }
+
+ private:
+ Mutex* mTracerLock;
+ CondVar* mTracerCondVar;
+ bool mHasRun;
+ nsCOMPtr<nsIThread> mMainThread;
+};
+StaticRefPtr<TracerRunnable> sTracerRunnable;
+
+bool InitWidgetTracing() {
+ if (!sTracerRunnable) sTracerRunnable = new TracerRunnable();
+ return true;
+}
+
+void CleanUpWidgetTracing() { sTracerRunnable = nullptr; }
+
+bool FireAndWaitForTracerEvent() {
+ if (sTracerRunnable) return sTracerRunnable->Fire();
+ return false;
+}
+
+void SignalTracerThread() {
+ if (sTracerRunnable) return sTracerRunnable->Signal();
+}
+
+} // namespace mozilla
+
+void AndroidBridge::GetCurrentBatteryInformation(
+ hal::BatteryInformation* aBatteryInfo) {
+ ALOG_BRIDGE("AndroidBridge::GetCurrentBatteryInformation");
+
+ // To prevent calling too many methods through JNI, the Java method returns
+ // an array of double even if we actually want a double and a boolean.
+ auto arr = java::GeckoAppShell::GetCurrentBatteryInformation();
+
+ JNIEnv* const env = arr.Env();
+ if (!arr || env->GetArrayLength(arr.Get()) != 3) {
+ return;
+ }
+
+ jdouble* info = env->GetDoubleArrayElements(arr.Get(), 0);
+
+ aBatteryInfo->level() = info[0];
+ aBatteryInfo->charging() = info[1] == 1.0f;
+ aBatteryInfo->remainingTime() = info[2];
+
+ env->ReleaseDoubleArrayElements(arr.Get(), info, 0);
+}
+
+void AndroidBridge::GetCurrentNetworkInformation(
+ hal::NetworkInformation* aNetworkInfo) {
+ ALOG_BRIDGE("AndroidBridge::GetCurrentNetworkInformation");
+
+ // To prevent calling too many methods through JNI, the Java method returns
+ // an array of double even if we actually want an integer, a boolean, and an
+ // integer.
+
+ auto arr = java::GeckoAppShell::GetCurrentNetworkInformation();
+
+ JNIEnv* const env = arr.Env();
+ if (!arr || env->GetArrayLength(arr.Get()) != 3) {
+ return;
+ }
+
+ jdouble* info = env->GetDoubleArrayElements(arr.Get(), 0);
+
+ aNetworkInfo->type() = info[0];
+ aNetworkInfo->isWifi() = info[1] == 1.0f;
+ aNetworkInfo->dhcpGateway() = info[2];
+
+ env->ReleaseDoubleArrayElements(arr.Get(), info, 0);
+}
+
+jobject AndroidBridge::GetGlobalContextRef() {
+ // The context object can change, so get a fresh copy every time.
+ auto context = java::GeckoAppShell::GetApplicationContext();
+ sGlobalContext = jni::Object::GlobalRef(context).Forget();
+ MOZ_ASSERT(sGlobalContext);
+ return sGlobalContext;
+}
+
+/* Implementation file */
+NS_IMPL_ISUPPORTS(nsAndroidBridge, nsIAndroidEventDispatcher, nsIAndroidBridge)
+
+nsAndroidBridge::nsAndroidBridge() {
+ if (jni::IsAvailable()) {
+ RefPtr<widget::EventDispatcher> dispatcher = new widget::EventDispatcher();
+ dispatcher->Attach(java::EventDispatcher::GetInstance(),
+ /* window */ nullptr);
+ mEventDispatcher = dispatcher;
+ }
+}
+
+NS_IMETHODIMP
+nsAndroidBridge::GetDispatcherByName(const char* aName,
+ nsIAndroidEventDispatcher** aResult) {
+ if (!jni::IsAvailable()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<widget::EventDispatcher> dispatcher = new widget::EventDispatcher();
+ dispatcher->Attach(java::EventDispatcher::ByName(aName),
+ /* window */ nullptr);
+ dispatcher.forget(aResult);
+ return NS_OK;
+}
+
+nsAndroidBridge::~nsAndroidBridge() {}
+
+NS_IMETHODIMP nsAndroidBridge::ContentDocumentChanged(
+ mozIDOMWindowProxy* aWindow) {
+ AndroidBridge::Bridge()->ContentDocumentChanged(aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAndroidBridge::IsContentDocumentDisplayed(
+ mozIDOMWindowProxy* aWindow, bool* aRet) {
+ *aRet = AndroidBridge::Bridge()->IsContentDocumentDisplayed(aWindow);
+ return NS_OK;
+}
+
+uint32_t AndroidBridge::GetScreenOrientation() {
+ ALOG_BRIDGE("AndroidBridge::GetScreenOrientation");
+
+ int16_t orientation = java::GeckoAppShell::GetScreenOrientation();
+
+ return static_cast<hal::ScreenOrientation>(orientation);
+}
+
+uint16_t AndroidBridge::GetScreenAngle() {
+ return java::GeckoAppShell::GetScreenAngle();
+}
+
+nsresult AndroidBridge::GetProxyForURI(const nsACString& aSpec,
+ const nsACString& aScheme,
+ const nsACString& aHost,
+ const int32_t aPort,
+ nsACString& aResult) {
+ if (!jni::IsAvailable()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto jstrRet =
+ java::GeckoAppShell::GetProxyForURI(aSpec, aScheme, aHost, aPort);
+
+ if (!jstrRet) return NS_ERROR_FAILURE;
+
+ aResult = jstrRet->ToCString();
+ return NS_OK;
+}
+
+bool AndroidBridge::PumpMessageLoop() {
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+
+ if (mMessageQueueMessages) {
+ auto msg = jni::Object::LocalRef::Adopt(
+ env, env->GetObjectField(mMessageQueue.Get(), mMessageQueueMessages));
+ // if queue.mMessages is null, queue.next() will block, which we don't
+ // want. It turns out to be an order of magnitude more performant to do
+ // this extra check here and block less vs. one fewer checks here and
+ // more blocking.
+ if (!msg) {
+ return false;
+ }
+ }
+
+ auto msg = jni::Object::LocalRef::Adopt(
+ env, env->CallObjectMethod(mMessageQueue.Get(), mMessageQueueNext));
+ if (!msg) {
+ return false;
+ }
+
+ return java::GeckoThread::PumpMessageLoop(msg);
+}
+
+NS_IMETHODIMP nsAndroidBridge::GetIsFennec(bool* aIsFennec) {
+ *aIsFennec = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAndroidBridge::GetBrowserApp(
+ nsIAndroidBrowserApp** aBrowserApp) {
+ nsAppShell* const appShell = nsAppShell::Get();
+ if (appShell) NS_IF_ADDREF(*aBrowserApp = appShell->GetBrowserApp());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAndroidBridge::SetBrowserApp(
+ nsIAndroidBrowserApp* aBrowserApp) {
+ nsAppShell* const appShell = nsAppShell::Get();
+ if (appShell) appShell->SetBrowserApp(aBrowserApp);
+ return NS_OK;
+}
+
+extern "C" __attribute__((visibility("default"))) jobject JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_allocateDirectBuffer(JNIEnv* env, jclass,
+ jlong size);
+
+void AndroidBridge::ContentDocumentChanged(mozIDOMWindowProxy* aWindow) {
+ if (RefPtr<nsWindow> widget =
+ nsWindow::From(nsPIDOMWindowOuter::From(aWindow))) {
+ widget->SetContentDocumentDisplayed(false);
+ }
+}
+
+bool AndroidBridge::IsContentDocumentDisplayed(mozIDOMWindowProxy* aWindow) {
+ if (RefPtr<nsWindow> widget =
+ nsWindow::From(nsPIDOMWindowOuter::From(aWindow))) {
+ return widget->IsContentDocumentDisplayed();
+ }
+ return false;
+}
+
+jni::Object::LocalRef AndroidBridge::ChannelCreate(jni::Object::Param stream) {
+ JNIEnv* const env = jni::GetEnvForThread();
+ auto rv = jni::Object::LocalRef::Adopt(
+ env, env->CallStaticObjectMethod(sBridge->jChannels,
+ sBridge->jChannelCreate, stream.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ return rv;
+}
+
+void AndroidBridge::InputStreamClose(jni::Object::Param obj) {
+ JNIEnv* const env = jni::GetEnvForThread();
+ env->CallVoidMethod(obj.Get(), sBridge->jClose);
+ MOZ_CATCH_JNI_EXCEPTION(env);
+}
+
+uint32_t AndroidBridge::InputStreamAvailable(jni::Object::Param obj) {
+ JNIEnv* const env = jni::GetEnvForThread();
+ auto rv = env->CallIntMethod(obj.Get(), sBridge->jAvailable);
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ return rv;
+}
+
+nsresult AndroidBridge::InputStreamRead(jni::Object::Param obj, char* aBuf,
+ uint32_t aCount, uint32_t* aRead) {
+ JNIEnv* const env = jni::GetEnvForThread();
+ auto arr = jni::ByteBuffer::New(aBuf, aCount);
+ jint read =
+ env->CallIntMethod(obj.Get(), sBridge->jByteBufferRead, arr.Get());
+
+ if (env->ExceptionCheck()) {
+ env->ExceptionClear();
+ return NS_ERROR_FAILURE;
+ }
+
+ if (read <= 0) {
+ *aRead = 0;
+ return NS_OK;
+ }
+ *aRead = read;
+ return NS_OK;
+}
diff --git a/widget/android/AndroidBridge.h b/widget/android/AndroidBridge.h
new file mode 100644
index 0000000000..3183de5ae6
--- /dev/null
+++ b/widget/android/AndroidBridge.h
@@ -0,0 +1,362 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 AndroidBridge_h__
+#define AndroidBridge_h__
+
+#include <jni.h>
+#include <android/log.h>
+#include <cstdlib>
+#include <unistd.h>
+
+#include "APKOpen.h"
+
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+
+#include "js/RootingAPI.h"
+#include "js/Value.h"
+#include "mozilla/jni/Refs.h"
+
+#include "nsIMutableArray.h"
+#include "nsIMIMEInfo.h"
+#include "nsColor.h"
+#include "gfxRect.h"
+
+#include "nsIAndroidBridge.h"
+
+#include "mozilla/Likely.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Types.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/jni/Utils.h"
+#include "nsDataHashtable.h"
+
+#include "Units.h"
+
+// Some debug #defines
+// #define DEBUG_ANDROID_EVENTS
+// #define DEBUG_ANDROID_WIDGET
+
+class nsPIDOMWindowOuter;
+
+typedef void* EGLSurface;
+class nsIRunnable;
+
+namespace mozilla {
+
+class AutoLocalJNIFrame;
+
+namespace hal {
+class BatteryInformation;
+class NetworkInformation;
+} // namespace hal
+
+// The order and number of the members in this structure must correspond
+// to the attrsAppearance array in GeckoAppShell.getSystemColors()
+typedef struct AndroidSystemColors {
+ nscolor textColorPrimary;
+ nscolor textColorPrimaryInverse;
+ nscolor textColorSecondary;
+ nscolor textColorSecondaryInverse;
+ nscolor textColorTertiary;
+ nscolor textColorTertiaryInverse;
+ nscolor textColorHighlight;
+ nscolor colorForeground;
+ nscolor colorBackground;
+ nscolor panelColorForeground;
+ nscolor panelColorBackground;
+} AndroidSystemColors;
+
+class AndroidBridge final {
+ public:
+ enum {
+ // Values for NotifyIME, in addition to values from the Gecko
+ // IMEMessage enum; use negative values here to prevent conflict
+ NOTIFY_IME_OPEN_VKB = -2,
+ NOTIFY_IME_REPLY_EVENT = -1,
+ };
+
+ enum {
+ LAYER_CLIENT_TYPE_NONE = 0,
+ LAYER_CLIENT_TYPE_GL = 2 // AndroidGeckoGLLayerClient
+ };
+
+ static bool IsJavaUiThread() {
+ return mozilla::jni::GetUIThreadId() == gettid();
+ }
+
+ static void ConstructBridge();
+ static void DeconstructBridge();
+
+ static AndroidBridge* Bridge() { return sBridge; }
+
+ void ContentDocumentChanged(mozIDOMWindowProxy* aDOMWindow);
+ bool IsContentDocumentDisplayed(mozIDOMWindowProxy* aDOMWindow);
+
+ bool GetHandlersForURL(const nsAString& aURL,
+ nsIMutableArray* handlersArray = nullptr,
+ nsIHandlerApp** aDefaultApp = nullptr,
+ const nsAString& aAction = u""_ns);
+
+ bool GetHandlersForMimeType(const nsAString& aMimeType,
+ nsIMutableArray* handlersArray = nullptr,
+ nsIHandlerApp** aDefaultApp = nullptr,
+ const nsAString& aAction = u""_ns);
+
+ bool HasHWVP8Encoder();
+ bool HasHWVP8Decoder();
+ bool HasHWH264();
+
+ void GetMimeTypeFromExtensions(const nsACString& aFileExt,
+ nsCString& aMimeType);
+ void GetExtensionFromMimeType(const nsACString& aMimeType,
+ nsACString& aFileExt);
+
+ gfx::Rect getScreenSize();
+ int GetScreenDepth();
+
+ void Vibrate(const nsTArray<uint32_t>& aPattern);
+
+ void GetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize,
+ uint8_t* const aBuf);
+
+ bool GetStaticStringField(const char* classID, const char* field,
+ nsAString& result, JNIEnv* env = nullptr);
+
+ bool GetStaticIntField(const char* className, const char* fieldName,
+ int32_t* aInt, JNIEnv* env = nullptr);
+
+ // Returns a global reference to the Context for Fennec's Activity. The
+ // caller is responsible for ensuring this doesn't leak by calling
+ // DeleteGlobalRef() when the context is no longer needed.
+ jobject GetGlobalContextRef(void);
+
+ void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo);
+
+ void GetCurrentNetworkInformation(hal::NetworkInformation* aNetworkInfo);
+
+ // These methods don't use a ScreenOrientation because it's an
+ // enum and that would require including the header which requires
+ // include IPC headers which requires including basictypes.h which
+ // requires a lot of changes...
+ uint32_t GetScreenOrientation();
+ uint16_t GetScreenAngle();
+
+ int GetAPIVersion() { return mAPIVersion; }
+
+ nsresult GetProxyForURI(const nsACString& aSpec, const nsACString& aScheme,
+ const nsACString& aHost, const int32_t aPort,
+ nsACString& aResult);
+
+ bool PumpMessageLoop();
+
+ // Utility methods.
+ static jfieldID GetFieldID(JNIEnv* env, jclass jClass, const char* fieldName,
+ const char* fieldType);
+ static jfieldID GetStaticFieldID(JNIEnv* env, jclass jClass,
+ const char* fieldName,
+ const char* fieldType);
+ static jmethodID GetMethodID(JNIEnv* env, jclass jClass,
+ const char* methodName, const char* methodType);
+ static jmethodID GetStaticMethodID(JNIEnv* env, jclass jClass,
+ const char* methodName,
+ const char* methodType);
+
+ static jni::Object::LocalRef ChannelCreate(jni::Object::Param);
+
+ static void InputStreamClose(jni::Object::Param obj);
+ static uint32_t InputStreamAvailable(jni::Object::Param obj);
+ static nsresult InputStreamRead(jni::Object::Param obj, char* aBuf,
+ uint32_t aCount, uint32_t* aRead);
+
+ protected:
+ static nsDataHashtable<nsStringHashKey, nsString> sStoragePaths;
+
+ static AndroidBridge* sBridge;
+
+ AndroidBridge();
+ ~AndroidBridge();
+
+ int mAPIVersion;
+
+ // intput stream
+ jclass jReadableByteChannel;
+ jclass jChannels;
+ jmethodID jChannelCreate;
+ jmethodID jByteBufferRead;
+
+ jclass jInputStream;
+ jmethodID jClose;
+ jmethodID jAvailable;
+
+ jmethodID jCalculateLength;
+
+ // some convinient types to have around
+ jclass jStringClass;
+
+ jni::Object::GlobalRef mMessageQueue;
+ jfieldID mMessageQueueMessages;
+ jmethodID mMessageQueueNext;
+};
+
+class AutoJNIClass {
+ private:
+ JNIEnv* const mEnv;
+ const jclass mClass;
+
+ public:
+ AutoJNIClass(JNIEnv* jEnv, const char* name)
+ : mEnv(jEnv), mClass(jni::GetClassRef(jEnv, name)) {}
+
+ ~AutoJNIClass() { mEnv->DeleteLocalRef(mClass); }
+
+ jclass getRawRef() const { return mClass; }
+
+ jclass getGlobalRef() const {
+ return static_cast<jclass>(mEnv->NewGlobalRef(mClass));
+ }
+
+ jfieldID getField(const char* name, const char* type) const {
+ return AndroidBridge::GetFieldID(mEnv, mClass, name, type);
+ }
+
+ jfieldID getStaticField(const char* name, const char* type) const {
+ return AndroidBridge::GetStaticFieldID(mEnv, mClass, name, type);
+ }
+
+ jmethodID getMethod(const char* name, const char* type) const {
+ return AndroidBridge::GetMethodID(mEnv, mClass, name, type);
+ }
+
+ jmethodID getStaticMethod(const char* name, const char* type) const {
+ return AndroidBridge::GetStaticMethodID(mEnv, mClass, name, type);
+ }
+};
+
+class AutoJObject {
+ public:
+ explicit AutoJObject(JNIEnv* aJNIEnv = nullptr) : mObject(nullptr) {
+ mJNIEnv = aJNIEnv ? aJNIEnv : jni::GetGeckoThreadEnv();
+ }
+
+ AutoJObject(JNIEnv* aJNIEnv, jobject aObject) {
+ mJNIEnv = aJNIEnv ? aJNIEnv : jni::GetGeckoThreadEnv();
+ mObject = aObject;
+ }
+
+ ~AutoJObject() {
+ if (mObject) mJNIEnv->DeleteLocalRef(mObject);
+ }
+
+ jobject operator=(jobject aObject) {
+ if (mObject) {
+ mJNIEnv->DeleteLocalRef(mObject);
+ }
+ return mObject = aObject;
+ }
+
+ operator jobject() { return mObject; }
+
+ private:
+ JNIEnv* mJNIEnv;
+ jobject mObject;
+};
+
+class AutoLocalJNIFrame {
+ public:
+ explicit AutoLocalJNIFrame(int nEntries = 15)
+ : mEntries(nEntries),
+ mJNIEnv(jni::GetGeckoThreadEnv()),
+ mHasFrameBeenPushed(false) {
+ MOZ_ASSERT(mJNIEnv);
+ Push();
+ }
+
+ explicit AutoLocalJNIFrame(JNIEnv* aJNIEnv, int nEntries = 15)
+ : mEntries(nEntries),
+ mJNIEnv(aJNIEnv ? aJNIEnv : jni::GetGeckoThreadEnv()),
+ mHasFrameBeenPushed(false) {
+ MOZ_ASSERT(mJNIEnv);
+ Push();
+ }
+
+ ~AutoLocalJNIFrame() {
+ if (mHasFrameBeenPushed) {
+ Pop();
+ }
+ }
+
+ JNIEnv* GetEnv() { return mJNIEnv; }
+
+ bool CheckForException() {
+ if (mJNIEnv->ExceptionCheck()) {
+ MOZ_CATCH_JNI_EXCEPTION(mJNIEnv);
+ return true;
+ }
+ return false;
+ }
+
+ // Note! Calling Purge makes all previous local refs created in
+ // the AutoLocalJNIFrame's scope INVALID; be sure that you locked down
+ // any local refs that you need to keep around in global refs!
+ void Purge() {
+ Pop();
+ Push();
+ }
+
+ template <typename ReturnType = jobject>
+ ReturnType Pop(ReturnType aResult = nullptr) {
+ MOZ_ASSERT(mHasFrameBeenPushed);
+ mHasFrameBeenPushed = false;
+ return static_cast<ReturnType>(
+ mJNIEnv->PopLocalFrame(static_cast<jobject>(aResult)));
+ }
+
+ private:
+ void Push() {
+ MOZ_ASSERT(!mHasFrameBeenPushed);
+ // Make sure there is enough space to store a local ref to the
+ // exception. I am not completely sure this is needed, but does
+ // not hurt.
+ if (mJNIEnv->PushLocalFrame(mEntries + 1) != 0) {
+ CheckForException();
+ return;
+ }
+ mHasFrameBeenPushed = true;
+ }
+
+ const int mEntries;
+ JNIEnv* const mJNIEnv;
+ bool mHasFrameBeenPushed;
+};
+
+} // namespace mozilla
+
+#define NS_ANDROIDBRIDGE_CID \
+ { \
+ 0x0FE2321D, 0xEBD9, 0x467D, { \
+ 0xA7, 0x43, 0x03, 0xA6, 0x8D, 0x40, 0x59, 0x9E \
+ } \
+ }
+
+class nsAndroidBridge final : public nsIAndroidBridge {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIANDROIDBRIDGE
+
+ NS_FORWARD_SAFE_NSIANDROIDEVENTDISPATCHER(mEventDispatcher)
+
+ nsAndroidBridge();
+
+ private:
+ ~nsAndroidBridge();
+
+ nsCOMPtr<nsIAndroidEventDispatcher> mEventDispatcher;
+
+ protected:
+};
+
+#endif /* AndroidBridge_h__ */
diff --git a/widget/android/AndroidBridgeUtilities.h b/widget/android/AndroidBridgeUtilities.h
new file mode 100644
index 0000000000..2d67c7ff46
--- /dev/null
+++ b/widget/android/AndroidBridgeUtilities.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 ALOG
+# if defined(DEBUG) || defined(FORCE_ALOG)
+# define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko", ##args)
+# else
+# define ALOG(args...) ((void)0)
+# endif
+#endif
+
+#ifdef DEBUG
+# define ALOG_BRIDGE(args...) ALOG(args)
+#else
+# define ALOG_BRIDGE(args...) ((void)0)
+#endif
diff --git a/widget/android/AndroidColors.h b/widget/android/AndroidColors.h
new file mode 100644
index 0000000000..1b594f6c3d
--- /dev/null
+++ b/widget/android/AndroidColors.h
@@ -0,0 +1,28 @@
+/* -*- 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 mozilla_widget_AndroidColors_h
+#define mozilla_widget_AndroidColors_h
+
+#include "mozilla/gfx/2D.h"
+
+namespace mozilla {
+namespace widget {
+
+static const gfx::sRGBColor sAndroidBackgroundColor(gfx::sRGBColor(1.0f, 1.0f,
+ 1.0f));
+static const gfx::sRGBColor sAndroidBorderColor(gfx::sRGBColor(0.73f, 0.73f,
+ 0.73f));
+static const gfx::sRGBColor sAndroidCheckColor(gfx::sRGBColor(0.19f, 0.21f,
+ 0.23f));
+static const gfx::sRGBColor sAndroidDisabledColor(gfx::sRGBColor(0.88f, 0.88f,
+ 0.88f));
+static const gfx::sRGBColor sAndroidActiveColor(gfx::sRGBColor(0.94f, 0.94f,
+ 0.94f));
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_AndroidColors_h
diff --git a/widget/android/AndroidCompositorWidget.cpp b/widget/android/AndroidCompositorWidget.cpp
new file mode 100644
index 0000000000..ec45ace6d4
--- /dev/null
+++ b/widget/android/AndroidCompositorWidget.cpp
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=2 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "AndroidCompositorWidget.h"
+#include "nsWindow.h"
+
+namespace mozilla {
+namespace widget {
+
+EGLNativeWindowType AndroidCompositorWidget::GetEGLNativeWindow() {
+ return (EGLNativeWindowType)mWidget->GetNativeData(NS_JAVA_SURFACE);
+}
+
+EGLNativeWindowType AndroidCompositorWidget::GetPresentationEGLSurface() {
+ return (EGLNativeWindowType)mWidget->GetNativeData(NS_PRESENTATION_SURFACE);
+}
+
+void AndroidCompositorWidget::SetPresentationEGLSurface(EGLSurface aVal) {
+ mWidget->SetNativeData(NS_PRESENTATION_SURFACE, (uintptr_t)aVal);
+}
+
+ANativeWindow* AndroidCompositorWidget::GetPresentationANativeWindow() {
+ return (ANativeWindow*)mWidget->GetNativeData(NS_PRESENTATION_WINDOW);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/android/AndroidCompositorWidget.h b/widget/android/AndroidCompositorWidget.h
new file mode 100644
index 0000000000..09cbaad593
--- /dev/null
+++ b/widget/android/AndroidCompositorWidget.h
@@ -0,0 +1,40 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 mozilla_widget_AndroidCompositorWidget_h
+#define mozilla_widget_AndroidCompositorWidget_h
+
+#include "GLDefs.h"
+#include "mozilla/widget/InProcessCompositorWidget.h"
+
+struct ANativeWindow;
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * AndroidCompositorWidget inherits from InProcessCompositorWidget because
+ * Android does not support OOP compositing yet. Once it does,
+ * AndroidCompositorWidget will be made to inherit from CompositorWidget
+ * instead.
+ */
+class AndroidCompositorWidget final : public InProcessCompositorWidget {
+ public:
+ using InProcessCompositorWidget::InProcessCompositorWidget;
+
+ AndroidCompositorWidget* AsAndroid() override { return this; }
+
+ EGLNativeWindowType GetEGLNativeWindow();
+
+ EGLSurface GetPresentationEGLSurface();
+ void SetPresentationEGLSurface(EGLSurface aVal);
+
+ ANativeWindow* GetPresentationANativeWindow();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_AndroidCompositorWidget_h
diff --git a/widget/android/AndroidContentController.cpp b/widget/android/AndroidContentController.cpp
new file mode 100644
index 0000000000..c42370e26c
--- /dev/null
+++ b/widget/android/AndroidContentController.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 "AndroidContentController.h"
+
+#include "AndroidBridge.h"
+#include "base/message_loop.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "nsIObserverService.h"
+#include "nsLayoutUtils.h"
+#include "nsWindow.h"
+
+using mozilla::layers::IAPZCTreeManager;
+
+namespace mozilla {
+namespace widget {
+
+void AndroidContentController::Destroy() {
+ mAndroidWindow = nullptr;
+ ChromeProcessController::Destroy();
+}
+
+void AndroidContentController::UpdateOverscrollVelocity(
+ const ScrollableLayerGuid& aGuid, const float aX, const float aY,
+ const bool aIsRootContent) {
+ if (aIsRootContent && mAndroidWindow) {
+ mAndroidWindow->UpdateOverscrollVelocity(aX, aY);
+ }
+}
+
+void AndroidContentController::UpdateOverscrollOffset(
+ const ScrollableLayerGuid& aGuid, const float aX, const float aY,
+ const bool aIsRootContent) {
+ if (aIsRootContent && mAndroidWindow) {
+ mAndroidWindow->UpdateOverscrollOffset(aX, aY);
+ }
+}
+
+void AndroidContentController::NotifyAPZStateChange(
+ const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg) {
+ // This function may get invoked twice, if the first invocation is not on
+ // the main thread then the ChromeProcessController version of this function
+ // will redispatch to the main thread. We want to make sure that our handling
+ // only happens on the main thread.
+ ChromeProcessController::NotifyAPZStateChange(aGuid, aChange, aArg);
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (aChange ==
+ layers::GeckoContentController::APZStateChange::eTransformEnd) {
+ // This is used by tests to determine when the APZ is done doing whatever
+ // it's doing. XXX generify this as needed when writing additional tests.
+ observerService->NotifyObservers(nullptr, "APZ:TransformEnd", nullptr);
+ observerService->NotifyObservers(nullptr, "PanZoom:StateChange",
+ u"NOTHING");
+ } else if (aChange == layers::GeckoContentController::APZStateChange::
+ eTransformBegin) {
+ observerService->NotifyObservers(nullptr, "PanZoom:StateChange",
+ u"PANNING");
+ }
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/android/AndroidContentController.h b/widget/android/AndroidContentController.h
new file mode 100644
index 0000000000..d3535c78cf
--- /dev/null
+++ b/widget/android/AndroidContentController.h
@@ -0,0 +1,51 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 AndroidContentController_h__
+#define AndroidContentController_h__
+
+#include "mozilla/layers/ChromeProcessController.h"
+#include "mozilla/EventForwards.h" // for Modifiers
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsTArray.h"
+#include "nsWindow.h"
+
+namespace mozilla {
+namespace layers {
+class APZEventState;
+class IAPZCTreeManager;
+} // namespace layers
+namespace widget {
+
+class AndroidContentController final
+ : public mozilla::layers::ChromeProcessController {
+ public:
+ AndroidContentController(nsWindow* aWindow,
+ mozilla::layers::APZEventState* aAPZEventState,
+ mozilla::layers::IAPZCTreeManager* aAPZCTreeManager)
+ : mozilla::layers::ChromeProcessController(aWindow, aAPZEventState,
+ aAPZCTreeManager),
+ mAndroidWindow(aWindow) {}
+
+ // ChromeProcessController methods
+ virtual void Destroy() override;
+ void UpdateOverscrollVelocity(const ScrollableLayerGuid& aGuid,
+ const float aX, const float aY,
+ const bool aIsRootContent) override;
+ void UpdateOverscrollOffset(const ScrollableLayerGuid& aGuid, const float aX,
+ const float aY,
+ const bool aIsRootContent) override;
+ void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
+ APZStateChange aChange, int aArg) override;
+
+ private:
+ nsWindow* mAndroidWindow;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/android/AndroidUiThread.cpp b/widget/android/AndroidUiThread.cpp
new file mode 100644
index 0000000000..a06ad4df17
--- /dev/null
+++ b/widget/android/AndroidUiThread.cpp
@@ -0,0 +1,342 @@
+/* -*- 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 "base/message_loop.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/java/GeckoThreadWrappers.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ThreadEventQueue.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "GeckoProfiler.h"
+#include "nsThread.h"
+#include "nsThreadManager.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+namespace {
+
+class AndroidUiThread;
+class AndroidUiTask;
+
+StaticAutoPtr<LinkedList<AndroidUiTask> > sTaskQueue;
+StaticAutoPtr<mozilla::Mutex> sTaskQueueLock;
+StaticRefPtr<AndroidUiThread> sThread;
+static bool sThreadDestroyed;
+static MessageLoop* sMessageLoop;
+static Atomic<Monitor*> sMessageLoopAccessMonitor;
+
+void EnqueueTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs);
+
+/*
+ * The AndroidUiThread is derived from nsThread so that nsIRunnable objects that
+ * get dispatched may be intercepted. Only nsIRunnable objects that need to be
+ * synchronously executed are passed into the nsThread to be queued. All other
+ * nsIRunnable object are immediately dispatched to the Android UI thread.
+ * AndroidUiThread is derived from nsThread instead of being an nsIEventTarget
+ * wrapper that contains an nsThread object because if nsIRunnable objects with
+ * a delay were dispatch directly to an nsThread object, such as obtained from
+ * nsThreadManager::GetCurrentThread(), the nsIRunnable could get stuck in the
+ * nsThread nsIRunnable queue. This is due to the fact that Android controls the
+ * event loop in the Android UI thread and has no knowledge of when the nsThread
+ * needs to be drained.
+ */
+
+class AndroidUiThread : public nsThread {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(AndroidUiThread, nsThread)
+ AndroidUiThread()
+ : nsThread(
+ MakeNotNull<ThreadEventQueue*>(MakeUnique<mozilla::EventQueue>()),
+ nsThread::NOT_MAIN_THREAD, 0) {}
+
+ nsresult Dispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aFlags) override;
+ nsresult DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDelayMs) override;
+
+ private:
+ ~AndroidUiThread() {}
+};
+
+NS_IMETHODIMP
+AndroidUiThread::Dispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aFlags) {
+ if (aFlags & NS_DISPATCH_SYNC) {
+ return nsThread::Dispatch(std::move(aEvent), aFlags);
+ } else {
+ EnqueueTask(std::move(aEvent), 0);
+ return NS_OK;
+ }
+}
+
+NS_IMETHODIMP
+AndroidUiThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDelayMs) {
+ EnqueueTask(std::move(aEvent), aDelayMs);
+ return NS_OK;
+}
+
+static void PumpEvents() { NS_ProcessPendingEvents(sThread.get()); }
+
+class ThreadObserver : public nsIThreadObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITHREADOBSERVER
+
+ ThreadObserver() {}
+
+ private:
+ virtual ~ThreadObserver() {}
+};
+
+NS_IMPL_ISUPPORTS(ThreadObserver, nsIThreadObserver)
+
+NS_IMETHODIMP
+ThreadObserver::OnDispatchedEvent() {
+ EnqueueTask(NS_NewRunnableFunction("PumpEvents", &PumpEvents), 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThreadObserver::OnProcessNextEvent(nsIThreadInternal* thread, bool mayWait) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThreadObserver::AfterProcessNextEvent(nsIThreadInternal* thread,
+ bool eventWasProcessed) {
+ return NS_OK;
+}
+
+class AndroidUiTask : public LinkedListElement<AndroidUiTask> {
+ using TimeStamp = mozilla::TimeStamp;
+ using TimeDuration = mozilla::TimeDuration;
+
+ public:
+ explicit AndroidUiTask(already_AddRefed<nsIRunnable> aTask)
+ : mTask(aTask),
+ mRunTime() // Null timestamp representing no delay.
+ {}
+
+ AndroidUiTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs)
+ : mTask(aTask),
+ mRunTime(TimeStamp::Now() + TimeDuration::FromMilliseconds(aDelayMs)) {}
+
+ bool IsEarlierThan(const AndroidUiTask& aOther) const {
+ if (mRunTime) {
+ return aOther.mRunTime ? mRunTime < aOther.mRunTime : false;
+ }
+ // In the case of no delay, we're earlier if aOther has a delay.
+ // Otherwise, we're not earlier, to maintain task order.
+ return !!aOther.mRunTime;
+ }
+
+ int64_t MillisecondsToRunTime() const {
+ if (mRunTime) {
+ return int64_t((mRunTime - TimeStamp::Now()).ToMilliseconds());
+ }
+ return 0;
+ }
+
+ already_AddRefed<nsIRunnable> TakeTask() { return mTask.forget(); }
+
+ private:
+ nsCOMPtr<nsIRunnable> mTask;
+ const TimeStamp mRunTime;
+};
+
+class CreateOnUiThread : public Runnable {
+ public:
+ CreateOnUiThread() : Runnable("CreateOnUiThread") {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(!sThreadDestroyed);
+ MOZ_ASSERT(sMessageLoopAccessMonitor);
+ MonitorAutoLock lock(*sMessageLoopAccessMonitor);
+ sThread = new AndroidUiThread();
+ sThread->InitCurrentThread();
+ sThread->SetObserver(new ThreadObserver());
+ PROFILER_REGISTER_THREAD("AndroidUI");
+ sMessageLoop =
+ new MessageLoop(MessageLoop::TYPE_MOZILLA_ANDROID_UI, sThread.get());
+ lock.NotifyAll();
+ return NS_OK;
+ }
+};
+
+class DestroyOnUiThread : public Runnable {
+ public:
+ DestroyOnUiThread() : Runnable("DestroyOnUiThread"), mDestroyed(false) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(!sThreadDestroyed);
+ MOZ_ASSERT(sMessageLoopAccessMonitor);
+ MOZ_ASSERT(sTaskQueue);
+ MonitorAutoLock lock(*sMessageLoopAccessMonitor);
+ sThreadDestroyed = true;
+
+ {
+ // Flush the queue
+ MutexAutoLock lock(*sTaskQueueLock);
+ while (AndroidUiTask* task = sTaskQueue->getFirst()) {
+ delete task;
+ }
+ }
+
+ delete sMessageLoop;
+ sMessageLoop = nullptr;
+ MOZ_ASSERT(sThread);
+ PROFILER_UNREGISTER_THREAD();
+ nsThreadManager::get().UnregisterCurrentThread(*sThread);
+ sThread = nullptr;
+ mDestroyed = true;
+ lock.NotifyAll();
+ return NS_OK;
+ }
+
+ void WaitForDestruction() {
+ MOZ_ASSERT(sMessageLoopAccessMonitor);
+ MonitorAutoLock lock(*sMessageLoopAccessMonitor);
+ while (!mDestroyed) {
+ lock.Wait();
+ }
+ }
+
+ private:
+ bool mDestroyed;
+};
+
+void EnqueueTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs) {
+ if (sThreadDestroyed) {
+ return;
+ }
+
+ // add the new task into the sTaskQueue, sorted with
+ // the earliest task first in the queue
+ AndroidUiTask* newTask =
+ (aDelayMs ? new AndroidUiTask(std::move(aTask), aDelayMs)
+ : new AndroidUiTask(std::move(aTask)));
+
+ bool headOfList = false;
+ {
+ MOZ_ASSERT(sTaskQueue);
+ MOZ_ASSERT(sTaskQueueLock);
+ MutexAutoLock lock(*sTaskQueueLock);
+
+ AndroidUiTask* task = sTaskQueue->getFirst();
+
+ while (task) {
+ if (newTask->IsEarlierThan(*task)) {
+ task->setPrevious(newTask);
+ break;
+ }
+ task = task->getNext();
+ }
+
+ if (!newTask->isInList()) {
+ sTaskQueue->insertBack(newTask);
+ }
+ headOfList = !newTask->getPrevious();
+ }
+
+ if (headOfList) {
+ // if we're inserting it at the head of the queue, notify Java because
+ // we need to get a callback at an earlier time than the last scheduled
+ // callback
+ java::GeckoThread::RequestUiThreadCallback(int64_t(aDelayMs));
+ }
+}
+
+} // namespace
+
+namespace mozilla {
+
+void CreateAndroidUiThread() {
+ MOZ_ASSERT(!sThread);
+ MOZ_ASSERT(!sMessageLoopAccessMonitor);
+ sTaskQueue = new LinkedList<AndroidUiTask>();
+ sTaskQueueLock = new Mutex("AndroidUiThreadTaskQueueLock");
+ sMessageLoopAccessMonitor =
+ new Monitor("AndroidUiThreadMessageLoopAccessMonitor");
+ sThreadDestroyed = false;
+ RefPtr<CreateOnUiThread> runnable = new CreateOnUiThread;
+ EnqueueTask(do_AddRef(runnable), 0);
+}
+
+void DestroyAndroidUiThread() {
+ MOZ_ASSERT(sThread);
+ RefPtr<DestroyOnUiThread> runnable = new DestroyOnUiThread;
+ EnqueueTask(do_AddRef(runnable), 0);
+ runnable->WaitForDestruction();
+ delete sMessageLoopAccessMonitor;
+ sMessageLoopAccessMonitor = nullptr;
+}
+
+MessageLoop* GetAndroidUiThreadMessageLoop() {
+ if (!sMessageLoopAccessMonitor) {
+ return nullptr;
+ }
+
+ MonitorAutoLock lock(*sMessageLoopAccessMonitor);
+ while (!sMessageLoop) {
+ lock.Wait();
+ }
+
+ return sMessageLoop;
+}
+
+RefPtr<nsThread> GetAndroidUiThread() {
+ if (!sMessageLoopAccessMonitor) {
+ return nullptr;
+ }
+
+ MonitorAutoLock lock(*sMessageLoopAccessMonitor);
+ while (!sThread) {
+ lock.Wait();
+ }
+
+ return sThread;
+}
+
+int64_t RunAndroidUiTasks() {
+ MutexAutoLock lock(*sTaskQueueLock);
+
+ if (sThreadDestroyed) {
+ return -1;
+ }
+
+ while (!sTaskQueue->isEmpty()) {
+ AndroidUiTask* task = sTaskQueue->getFirst();
+ const int64_t timeLeft = task->MillisecondsToRunTime();
+ if (timeLeft > 0) {
+ // this task (and therefore all remaining tasks)
+ // have not yet reached their runtime. return the
+ // time left until we should be called again
+ return timeLeft;
+ }
+
+ // Retrieve task before unlocking/running.
+ nsCOMPtr<nsIRunnable> runnable(task->TakeTask());
+ // LinkedListElements auto remove from list upon destruction
+ delete task;
+
+ // Unlock to allow posting new tasks reentrantly.
+ MutexAutoUnlock unlock(*sTaskQueueLock);
+ runnable->Run();
+ if (sThreadDestroyed) {
+ return -1;
+ }
+ }
+ return -1;
+}
+
+} // namespace mozilla
diff --git a/widget/android/AndroidUiThread.h b/widget/android/AndroidUiThread.h
new file mode 100644
index 0000000000..af722fb048
--- /dev/null
+++ b/widget/android/AndroidUiThread.h
@@ -0,0 +1,25 @@
+/* -*- 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 AndroidUiThread_h__
+#define AndroidUiThread_h__
+
+#include <mozilla/RefPtr.h>
+#include <nsThread.h>
+
+class MessageLoop;
+
+namespace mozilla {
+
+void CreateAndroidUiThread();
+void DestroyAndroidUiThread();
+int64_t RunAndroidUiTasks();
+
+MessageLoop* GetAndroidUiThreadMessageLoop();
+RefPtr<nsThread> GetAndroidUiThread();
+
+} // namespace mozilla
+
+#endif // AndroidUiThread_h__
diff --git a/widget/android/AndroidView.h b/widget/android/AndroidView.h
new file mode 100644
index 0000000000..173db8b5c1
--- /dev/null
+++ b/widget/android/AndroidView.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_AndroidView_h
+#define mozilla_widget_AndroidView_h
+
+#include "mozilla/widget/EventDispatcher.h"
+
+namespace mozilla {
+namespace widget {
+
+class AndroidView final : public nsIAndroidView {
+ virtual ~AndroidView() {}
+
+ public:
+ const RefPtr<mozilla::widget::EventDispatcher> mEventDispatcher{
+ new mozilla::widget::EventDispatcher()};
+
+ AndroidView() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIANDROIDVIEW
+
+ NS_FORWARD_NSIANDROIDEVENTDISPATCHER(mEventDispatcher->)
+
+ mozilla::java::GeckoBundle::GlobalRef mInitData;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_AndroidView_h
diff --git a/widget/android/AndroidVsync.cpp b/widget/android/AndroidVsync.cpp
new file mode 100644
index 0000000000..a25644ce80
--- /dev/null
+++ b/widget/android/AndroidVsync.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "AndroidVsync.h"
+
+#include "nsTArray.h"
+
+/**
+ * Implementation for the AndroidVsync class.
+ */
+
+namespace mozilla {
+namespace widget {
+
+StaticDataMutex<ThreadSafeWeakPtr<AndroidVsync>> AndroidVsync::sInstance(
+ "AndroidVsync::sInstance");
+
+/* static */ RefPtr<AndroidVsync> AndroidVsync::GetInstance() {
+ auto weakInstance = sInstance.Lock();
+ RefPtr<AndroidVsync> instance(*weakInstance);
+ if (!instance) {
+ instance = new AndroidVsync();
+ *weakInstance = instance;
+ }
+ return instance;
+}
+
+/**
+ * Owned by the Java AndroidVsync instance.
+ */
+class AndroidVsyncSupport final
+ : public java::AndroidVsync::Natives<AndroidVsyncSupport> {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AndroidVsyncSupport)
+
+ using Base = java::AndroidVsync::Natives<AndroidVsyncSupport>;
+ using Base::AttachNative;
+ using Base::DisposeNative;
+
+ explicit AndroidVsyncSupport(AndroidVsync* aAndroidVsync)
+ : mAndroidVsync(std::move(aAndroidVsync),
+ "AndroidVsyncSupport::mAndroidVsync") {}
+
+ // Called by Java
+ void NotifyVsync(const java::AndroidVsync::LocalRef& aInstance,
+ int64_t aFrameTimeNanos) {
+ auto androidVsync = mAndroidVsync.Lock();
+ if (*androidVsync) {
+ (*androidVsync)->NotifyVsync(aFrameTimeNanos);
+ }
+ }
+
+ // Called by the AndroidVsync destructor
+ void Unlink() {
+ auto androidVsync = mAndroidVsync.Lock();
+ *androidVsync = nullptr;
+ }
+
+ protected:
+ ~AndroidVsyncSupport() = default;
+
+ DataMutex<AndroidVsync*> mAndroidVsync;
+};
+
+AndroidVsync::AndroidVsync() : mImpl("AndroidVsync.mImpl") {
+ AndroidVsyncSupport::Init();
+
+ auto impl = mImpl.Lock();
+ impl->mSupport = new AndroidVsyncSupport(this);
+ impl->mSupportJava = java::AndroidVsync::New();
+ AndroidVsyncSupport::AttachNative(impl->mSupportJava, impl->mSupport);
+ float fps = impl->mSupportJava->GetRefreshRate();
+ impl->mVsyncDuration = TimeDuration::FromMilliseconds(1000.0 / fps);
+}
+
+AndroidVsync::~AndroidVsync() {
+ auto impl = mImpl.Lock();
+ impl->mInputObservers.Clear();
+ impl->mRenderObservers.Clear();
+ impl->UpdateObservingVsync();
+ impl->mSupport->Unlink();
+}
+
+TimeDuration AndroidVsync::GetVsyncRate() {
+ auto impl = mImpl.Lock();
+ return impl->mVsyncDuration;
+}
+
+void AndroidVsync::RegisterObserver(Observer* aObserver, ObserverType aType) {
+ auto impl = mImpl.Lock();
+ if (aType == AndroidVsync::INPUT) {
+ impl->mInputObservers.AppendElement(aObserver);
+ } else {
+ impl->mRenderObservers.AppendElement(aObserver);
+ }
+ impl->UpdateObservingVsync();
+}
+
+void AndroidVsync::UnregisterObserver(Observer* aObserver, ObserverType aType) {
+ auto impl = mImpl.Lock();
+ if (aType == AndroidVsync::INPUT) {
+ impl->mInputObservers.RemoveElement(aObserver);
+ } else {
+ impl->mRenderObservers.RemoveElement(aObserver);
+ }
+ impl->UpdateObservingVsync();
+}
+
+void AndroidVsync::Impl::UpdateObservingVsync() {
+ bool shouldObserve =
+ !mInputObservers.IsEmpty() || !mRenderObservers.IsEmpty();
+ if (shouldObserve != mObservingVsync) {
+ mObservingVsync = mSupportJava->ObserveVsync(shouldObserve);
+ }
+}
+
+// Always called on the Java UI thread.
+void AndroidVsync::NotifyVsync(int64_t aFrameTimeNanos) {
+ // Convert aFrameTimeNanos to a TimeStamp. The value converts trivially to
+ // the internal ticks representation of TimeStamp_posix; both use the
+ // monotonic clock and are in nanoseconds.
+ TimeStamp timeStamp = TimeStamp::FromSystemTime(aFrameTimeNanos);
+
+ // Do not keep the lock held while calling OnVsync.
+ nsTArray<Observer*> observers;
+ {
+ auto impl = mImpl.Lock();
+ observers.AppendElements(impl->mInputObservers);
+ observers.AppendElements(impl->mRenderObservers);
+ }
+ for (Observer* observer : observers) {
+ observer->OnVsync(timeStamp);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/android/AndroidVsync.h b/widget/android/AndroidVsync.h
new file mode 100644
index 0000000000..617beba017
--- /dev/null
+++ b/widget/android/AndroidVsync.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_AndroidVsync_h
+#define mozilla_widget_AndroidVsync_h
+
+#include "mozilla/DataMutex.h"
+#include "mozilla/java/AndroidVsyncNatives.h"
+#include "mozilla/ThreadSafeWeakPtr.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+namespace widget {
+
+class AndroidVsyncSupport;
+
+/**
+ * A thread-safe way to listen to vsync notifications on Android. All methods
+ * can be called on any thread.
+ * Observers must keep a strong reference to the AndroidVsync instance until
+ * they unregister themselves.
+ */
+class AndroidVsync final : public SupportsThreadSafeWeakPtr<AndroidVsync> {
+ public:
+ MOZ_DECLARE_THREADSAFEWEAKREFERENCE_TYPENAME(AndroidVsync)
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(AndroidVsync)
+
+ static RefPtr<AndroidVsync> GetInstance();
+
+ ~AndroidVsync();
+
+ class Observer {
+ public:
+ // Will be called on the Java UI thread.
+ virtual void OnVsync(const TimeStamp& aTimeStamp) = 0;
+ };
+
+ // INPUT observers are called before RENDER observers.
+ enum ObserverType { INPUT, RENDER };
+ void RegisterObserver(Observer* aObserver, ObserverType aType);
+ void UnregisterObserver(Observer* aObserver, ObserverType aType);
+
+ TimeDuration GetVsyncRate();
+
+ private:
+ friend class AndroidVsyncSupport;
+
+ AndroidVsync();
+
+ // Called by Java, via AndroidVsyncSupport
+ void NotifyVsync(int64_t aFrameTimeNanos);
+
+ struct Impl {
+ void UpdateObservingVsync();
+
+ TimeDuration mVsyncDuration;
+ nsTArray<Observer*> mInputObservers;
+ nsTArray<Observer*> mRenderObservers;
+ RefPtr<AndroidVsyncSupport> mSupport;
+ java::AndroidVsync::GlobalRef mSupportJava;
+ bool mObservingVsync = false;
+ };
+
+ DataMutex<Impl> mImpl;
+
+ static StaticDataMutex<ThreadSafeWeakPtr<AndroidVsync>> sInstance;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_AndroidVsync_h
diff --git a/widget/android/Base64UtilsSupport.h b/widget/android/Base64UtilsSupport.h
new file mode 100644
index 0000000000..8ac4347aaa
--- /dev/null
+++ b/widget/android/Base64UtilsSupport.h
@@ -0,0 +1,52 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 Base64UtilsSupport_h__
+#define Base64UtilsSupport_h__
+
+#include "mozilla/Base64.h"
+#include "mozilla/java/Base64UtilsNatives.h"
+
+namespace mozilla {
+namespace widget {
+
+class Base64UtilsSupport final
+ : public java::Base64Utils::Natives<Base64UtilsSupport> {
+ public:
+ static jni::ByteArray::LocalRef Decode(jni::String::Param data) {
+ if (!data) {
+ return nullptr;
+ }
+
+ FallibleTArray<uint8_t> bytes;
+ if (NS_FAILED(Base64URLDecode(
+ data->ToCString(), Base64URLDecodePaddingPolicy::Ignore, bytes))) {
+ return nullptr;
+ }
+
+ return jni::ByteArray::New((const signed char*)bytes.Elements(),
+ bytes.Length());
+ }
+
+ static jni::String::LocalRef Encode(jni::ByteArray::Param data) {
+ if (!data) {
+ return nullptr;
+ }
+
+ nsTArray<int8_t> bytes = data->GetElements();
+ nsCString result;
+ if (NS_FAILED(
+ Base64URLEncode(data->Length(), (const uint8_t*)bytes.Elements(),
+ Base64URLEncodePaddingPolicy::Omit, result))) {
+ return nullptr;
+ }
+ return jni::StringParam(result);
+ }
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // Base64UtilsSupport_h__
diff --git a/widget/android/EventDispatcher.cpp b/widget/android/EventDispatcher.cpp
new file mode 100644
index 0000000000..061bd64556
--- /dev/null
+++ b/widget/android/EventDispatcher.cpp
@@ -0,0 +1,1026 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * vim: set sw=2 ts=4 expandtab:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "EventDispatcher.h"
+
+#include "JavaBuiltins.h"
+#include "nsAppShell.h"
+#include "nsJSUtils.h"
+#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject
+#include "js/String.h" // JS::StringHasLatin1Chars
+#include "js/Warnings.h" // JS::WarnUTF8
+#include "xpcpublic.h"
+
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/java/EventCallbackWrappers.h"
+
+// Disable the C++ 2a warning. See bug #1509926
+#if defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wc++2a-compat"
+#endif
+
+namespace mozilla {
+namespace widget {
+
+namespace detail {
+
+bool CheckJS(JSContext* aCx, bool aResult) {
+ if (!aResult) {
+ JS_ClearPendingException(aCx);
+ }
+ return aResult;
+}
+
+nsresult BoxString(JSContext* aCx, JS::HandleValue aData,
+ jni::Object::LocalRef& aOut) {
+ if (aData.isNullOrUndefined()) {
+ aOut = nullptr;
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aData.isString());
+
+ JS::RootedString str(aCx, aData.toString());
+
+ if (JS::StringHasLatin1Chars(str)) {
+ nsAutoJSString autoStr;
+ NS_ENSURE_TRUE(CheckJS(aCx, autoStr.init(aCx, str)), NS_ERROR_FAILURE);
+
+ // StringParam can automatically convert a nsString to jstring.
+ aOut = jni::StringParam(autoStr, aOut.Env());
+ return NS_OK;
+ }
+
+ // Two-byte string
+ JNIEnv* const env = aOut.Env();
+ const char16_t* chars;
+ {
+ JS::AutoCheckCannotGC nogc;
+ size_t len = 0;
+ chars = JS_GetTwoByteStringCharsAndLength(aCx, nogc, str, &len);
+ if (chars) {
+ aOut = jni::String::LocalRef::Adopt(
+ env, env->NewString(reinterpret_cast<const jchar*>(chars), len));
+ }
+ }
+ if (NS_WARN_IF(!CheckJS(aCx, !!chars) || !aOut)) {
+ env->ExceptionClear();
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult BoxObject(JSContext* aCx, JS::HandleValue aData,
+ jni::Object::LocalRef& aOut);
+
+template <typename Type, bool (JS::Value::*IsType)() const,
+ Type (JS::Value::*ToType)() const, class ArrayType,
+ typename ArrayType::LocalRef (*NewArray)(const Type*, size_t)>
+nsresult BoxArrayPrimitive(JSContext* aCx, JS::HandleObject aData,
+ jni::Object::LocalRef& aOut, size_t aLength,
+ JS::HandleValue aElement) {
+ JS::RootedValue element(aCx);
+ auto data = MakeUnique<Type[]>(aLength);
+ data[0] = (aElement.get().*ToType)();
+
+ for (size_t i = 1; i < aLength; i++) {
+ NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, i, &element)),
+ NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE((element.get().*IsType)(), NS_ERROR_INVALID_ARG);
+
+ data[i] = (element.get().*ToType)();
+ }
+ aOut = (*NewArray)(data.get(), aLength);
+ return NS_OK;
+}
+
+template <class Type,
+ nsresult (*Box)(JSContext*, JS::HandleValue, jni::Object::LocalRef&),
+ typename IsType>
+nsresult BoxArrayObject(JSContext* aCx, JS::HandleObject aData,
+ jni::Object::LocalRef& aOut, size_t aLength,
+ JS::HandleValue aElement, IsType&& aIsType) {
+ auto out = jni::ObjectArray::New<Type>(aLength);
+ JS::RootedValue element(aCx);
+ jni::Object::LocalRef jniElement(aOut.Env());
+
+ nsresult rv = (*Box)(aCx, aElement, jniElement);
+ NS_ENSURE_SUCCESS(rv, rv);
+ out->SetElement(0, jniElement);
+
+ for (size_t i = 1; i < aLength; i++) {
+ NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, i, &element)),
+ NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(element.isNullOrUndefined() || aIsType(element),
+ NS_ERROR_INVALID_ARG);
+
+ rv = (*Box)(aCx, element, jniElement);
+ NS_ENSURE_SUCCESS(rv, rv);
+ out->SetElement(i, jniElement);
+ }
+ aOut = out;
+ return NS_OK;
+}
+
+nsresult BoxArray(JSContext* aCx, JS::HandleObject aData,
+ jni::Object::LocalRef& aOut) {
+ uint32_t length = 0;
+ NS_ENSURE_TRUE(CheckJS(aCx, JS::GetArrayLength(aCx, aData, &length)),
+ NS_ERROR_FAILURE);
+
+ if (!length) {
+ // Always represent empty arrays as an empty boolean array.
+ aOut = java::GeckoBundle::EMPTY_BOOLEAN_ARRAY();
+ return NS_OK;
+ }
+
+ // We only check the first element's type. If the array has mixed types,
+ // we'll throw an error during actual conversion.
+ JS::RootedValue element(aCx);
+ NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, 0, &element)),
+ NS_ERROR_FAILURE);
+
+ if (element.isBoolean()) {
+ return BoxArrayPrimitive<bool, &JS::Value::isBoolean, &JS::Value::toBoolean,
+ jni::BooleanArray, &jni::BooleanArray::New>(
+ aCx, aData, aOut, length, element);
+ }
+
+ if (element.isInt32()) {
+ nsresult rv =
+ BoxArrayPrimitive<int32_t, &JS::Value::isInt32, &JS::Value::toInt32,
+ jni::IntArray, &jni::IntArray::New>(aCx, aData, aOut,
+ length, element);
+ if (rv != NS_ERROR_INVALID_ARG) {
+ return rv;
+ }
+ // Not int32, but we can still try a double array.
+ }
+
+ if (element.isNumber()) {
+ return BoxArrayPrimitive<double, &JS::Value::isNumber, &JS::Value::toNumber,
+ jni::DoubleArray, &jni::DoubleArray::New>(
+ aCx, aData, aOut, length, element);
+ }
+
+ if (element.isNullOrUndefined() || element.isString()) {
+ const auto isString = [](JS::HandleValue val) -> bool {
+ return val.isString();
+ };
+ nsresult rv = BoxArrayObject<jni::String, &BoxString>(
+ aCx, aData, aOut, length, element, isString);
+ if (element.isString() || rv != NS_ERROR_INVALID_ARG) {
+ return rv;
+ }
+ // First element was null/undefined, so it may still be an object array.
+ }
+
+ const auto isObject = [aCx](JS::HandleValue val) -> bool {
+ if (!val.isObject()) {
+ return false;
+ }
+ bool array = false;
+ JS::RootedObject obj(aCx, &val.toObject());
+ // We don't support array of arrays.
+ return CheckJS(aCx, JS::IsArrayObject(aCx, obj, &array)) && !array;
+ };
+
+ if (element.isNullOrUndefined() || isObject(element)) {
+ return BoxArrayObject<java::GeckoBundle, &BoxObject>(
+ aCx, aData, aOut, length, element, isObject);
+ }
+
+ NS_WARNING("Unknown type");
+ return NS_ERROR_INVALID_ARG;
+}
+
+nsresult BoxValue(JSContext* aCx, JS::HandleValue aData,
+ jni::Object::LocalRef& aOut);
+
+nsresult BoxObject(JSContext* aCx, JS::HandleValue aData,
+ jni::Object::LocalRef& aOut) {
+ if (aData.isNullOrUndefined()) {
+ aOut = nullptr;
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aData.isObject());
+
+ JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
+ JS::RootedObject obj(aCx, &aData.toObject());
+
+ bool isArray = false;
+ if (CheckJS(aCx, JS::IsArrayObject(aCx, obj, &isArray)) && isArray) {
+ return BoxArray(aCx, obj, aOut);
+ }
+
+ NS_ENSURE_TRUE(CheckJS(aCx, JS_Enumerate(aCx, obj, &ids)), NS_ERROR_FAILURE);
+
+ const size_t length = ids.length();
+ auto keys = jni::ObjectArray::New<jni::String>(length);
+ auto values = jni::ObjectArray::New<jni::Object>(length);
+
+ // Iterate through each property of the JS object.
+ for (size_t i = 0; i < ids.length(); i++) {
+ const JS::RootedId id(aCx, ids[i]);
+ JS::RootedValue idVal(aCx);
+ JS::RootedValue val(aCx);
+ jni::Object::LocalRef key(aOut.Env());
+ jni::Object::LocalRef value(aOut.Env());
+
+ NS_ENSURE_TRUE(CheckJS(aCx, JS_IdToValue(aCx, id, &idVal)),
+ NS_ERROR_FAILURE);
+
+ JS::RootedString idStr(aCx, JS::ToString(aCx, idVal));
+ NS_ENSURE_TRUE(CheckJS(aCx, !!idStr), NS_ERROR_FAILURE);
+
+ idVal.setString(idStr);
+ NS_ENSURE_SUCCESS(BoxString(aCx, idVal, key), NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(CheckJS(aCx, JS_GetPropertyById(aCx, obj, id, &val)),
+ NS_ERROR_FAILURE);
+
+ nsresult rv = BoxValue(aCx, val, value);
+ if (rv == NS_ERROR_INVALID_ARG && !JS_IsExceptionPending(aCx)) {
+ nsAutoJSString autoStr;
+ if (CheckJS(aCx, autoStr.init(aCx, idVal.toString()))) {
+ JS_ReportErrorUTF8(aCx, u8"Invalid event data property %s",
+ NS_ConvertUTF16toUTF8(autoStr).get());
+ }
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ keys->SetElement(i, key);
+ values->SetElement(i, value);
+ }
+
+ aOut = java::GeckoBundle::New(keys, values);
+ return NS_OK;
+}
+
+nsresult BoxValue(JSContext* aCx, JS::HandleValue aData,
+ jni::Object::LocalRef& aOut) {
+ if (aData.isNullOrUndefined()) {
+ aOut = nullptr;
+ } else if (aData.isBoolean()) {
+ aOut = aData.toBoolean() ? java::sdk::Boolean::TRUE()
+ : java::sdk::Boolean::FALSE();
+ } else if (aData.isInt32()) {
+ aOut = java::sdk::Integer::ValueOf(aData.toInt32());
+ } else if (aData.isNumber()) {
+ aOut = java::sdk::Double::New(aData.toNumber());
+ } else if (aData.isString()) {
+ return BoxString(aCx, aData, aOut);
+ } else if (aData.isObject()) {
+ return BoxObject(aCx, aData, aOut);
+ } else {
+ NS_WARNING("Unknown type");
+ return NS_ERROR_INVALID_ARG;
+ }
+ return NS_OK;
+}
+
+nsresult BoxData(const nsAString& aEvent, JSContext* aCx, JS::HandleValue aData,
+ jni::Object::LocalRef& aOut, bool aObjectOnly) {
+ nsresult rv = NS_ERROR_INVALID_ARG;
+
+ if (!aObjectOnly) {
+ rv = BoxValue(aCx, aData, aOut);
+ } else if (aData.isObject() || aData.isNullOrUndefined()) {
+ rv = BoxObject(aCx, aData, aOut);
+ }
+ if (rv != NS_ERROR_INVALID_ARG) {
+ return rv;
+ }
+
+ NS_ConvertUTF16toUTF8 event(aEvent);
+ if (JS_IsExceptionPending(aCx)) {
+ JS::WarnUTF8(aCx, "Error dispatching %s", event.get());
+ } else {
+ JS_ReportErrorUTF8(aCx, "Invalid event data for %s", event.get());
+ }
+ return NS_ERROR_INVALID_ARG;
+}
+
+nsresult UnboxString(JSContext* aCx, const jni::Object::LocalRef& aData,
+ JS::MutableHandleValue aOut) {
+ if (!aData) {
+ aOut.setNull();
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aData.IsInstanceOf<jni::String>());
+
+ JNIEnv* const env = aData.Env();
+ const jstring jstr = jstring(aData.Get());
+ const size_t len = env->GetStringLength(jstr);
+ const jchar* const jchars = env->GetStringChars(jstr, nullptr);
+
+ if (NS_WARN_IF(!jchars)) {
+ env->ExceptionClear();
+ return NS_ERROR_FAILURE;
+ }
+
+ auto releaseStr = MakeScopeExit([env, jstr, jchars] {
+ env->ReleaseStringChars(jstr, jchars);
+ env->ExceptionClear();
+ });
+
+ JS::RootedString str(
+ aCx,
+ JS_NewUCStringCopyN(aCx, reinterpret_cast<const char16_t*>(jchars), len));
+ NS_ENSURE_TRUE(CheckJS(aCx, !!str), NS_ERROR_FAILURE);
+
+ aOut.setString(str);
+ return NS_OK;
+}
+
+nsresult UnboxValue(JSContext* aCx, const jni::Object::LocalRef& aData,
+ JS::MutableHandleValue aOut);
+
+nsresult UnboxBundle(JSContext* aCx, const jni::Object::LocalRef& aData,
+ JS::MutableHandleValue aOut) {
+ if (!aData) {
+ aOut.setNull();
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aData.IsInstanceOf<java::GeckoBundle>());
+
+ JNIEnv* const env = aData.Env();
+ const auto& bundle = java::GeckoBundle::Ref::From(aData);
+ jni::ObjectArray::LocalRef keys = bundle->Keys();
+ jni::ObjectArray::LocalRef values = bundle->Values();
+ const size_t len = keys->Length();
+ JS::RootedObject obj(aCx, JS_NewPlainObject(aCx));
+
+ NS_ENSURE_TRUE(CheckJS(aCx, !!obj), NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(values->Length() == len, NS_ERROR_FAILURE);
+
+ for (size_t i = 0; i < len; i++) {
+ jni::String::LocalRef key = keys->GetElement(i);
+ const size_t keyLen = env->GetStringLength(key.Get());
+ const jchar* const keyChars = env->GetStringChars(key.Get(), nullptr);
+ if (NS_WARN_IF(!keyChars)) {
+ env->ExceptionClear();
+ return NS_ERROR_FAILURE;
+ }
+
+ auto releaseKeyChars = MakeScopeExit([env, &key, keyChars] {
+ env->ReleaseStringChars(key.Get(), keyChars);
+ env->ExceptionClear();
+ });
+
+ JS::RootedValue value(aCx);
+ nsresult rv = UnboxValue(aCx, values->GetElement(i), &value);
+ if (rv == NS_ERROR_INVALID_ARG && !JS_IsExceptionPending(aCx)) {
+ JS_ReportErrorUTF8(
+ aCx, u8"Invalid event data property %s",
+ NS_ConvertUTF16toUTF8(
+ nsString(reinterpret_cast<const char16_t*>(keyChars), keyLen))
+ .get());
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(
+ CheckJS(aCx, JS_SetUCProperty(
+ aCx, obj, reinterpret_cast<const char16_t*>(keyChars),
+ keyLen, value)),
+ NS_ERROR_FAILURE);
+ }
+
+ aOut.setObject(*obj);
+ return NS_OK;
+}
+
+template <typename Type, typename JNIType, typename ArrayType,
+ JNIType* (JNIEnv::*GetElements)(ArrayType, jboolean*),
+ void (JNIEnv::*ReleaseElements)(ArrayType, JNIType*, jint),
+ JS::Value (*ToValue)(Type)>
+nsresult UnboxArrayPrimitive(JSContext* aCx, const jni::Object::LocalRef& aData,
+ JS::MutableHandleValue aOut) {
+ JNIEnv* const env = aData.Env();
+ const ArrayType jarray = ArrayType(aData.Get());
+ JNIType* const array = (env->*GetElements)(jarray, nullptr);
+ JS::RootedVector<JS::Value> elements(aCx);
+
+ if (NS_WARN_IF(!array)) {
+ env->ExceptionClear();
+ return NS_ERROR_FAILURE;
+ }
+
+ auto releaseArray = MakeScopeExit([env, jarray, array] {
+ (env->*ReleaseElements)(jarray, array, JNI_ABORT);
+ env->ExceptionClear();
+ });
+
+ const size_t len = env->GetArrayLength(jarray);
+ NS_ENSURE_TRUE(elements.initCapacity(len), NS_ERROR_FAILURE);
+
+ for (size_t i = 0; i < len; i++) {
+ NS_ENSURE_TRUE(elements.append((*ToValue)(Type(array[i]))),
+ NS_ERROR_FAILURE);
+ }
+
+ JS::RootedObject obj(aCx,
+ JS::NewArrayObject(aCx, JS::HandleValueArray(elements)));
+ NS_ENSURE_TRUE(CheckJS(aCx, !!obj), NS_ERROR_FAILURE);
+
+ aOut.setObject(*obj);
+ return NS_OK;
+}
+
+struct StringArray : jni::ObjectBase<StringArray> {
+ static const char name[];
+};
+
+struct GeckoBundleArray : jni::ObjectBase<GeckoBundleArray> {
+ static const char name[];
+};
+
+const char StringArray::name[] = "[Ljava/lang/String;";
+const char GeckoBundleArray::name[] = "[Lorg/mozilla/gecko/util/GeckoBundle;";
+
+template <nsresult (*Unbox)(JSContext*, const jni::Object::LocalRef&,
+ JS::MutableHandleValue)>
+nsresult UnboxArrayObject(JSContext* aCx, const jni::Object::LocalRef& aData,
+ JS::MutableHandleValue aOut) {
+ jni::ObjectArray::LocalRef array(aData.Env(),
+ jni::ObjectArray::Ref::From(aData));
+ const size_t len = array->Length();
+ JS::RootedObject obj(aCx, JS::NewArrayObject(aCx, len));
+ NS_ENSURE_TRUE(CheckJS(aCx, !!obj), NS_ERROR_FAILURE);
+
+ for (size_t i = 0; i < len; i++) {
+ jni::Object::LocalRef element = array->GetElement(i);
+ JS::RootedValue value(aCx);
+ nsresult rv = (*Unbox)(aCx, element, &value);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(CheckJS(aCx, JS_SetElement(aCx, obj, i, value)),
+ NS_ERROR_FAILURE);
+ }
+
+ aOut.setObject(*obj);
+ return NS_OK;
+}
+
+nsresult UnboxValue(JSContext* aCx, const jni::Object::LocalRef& aData,
+ JS::MutableHandleValue aOut) {
+ using jni::Java2Native;
+
+ if (!aData) {
+ aOut.setNull();
+ } else if (aData.IsInstanceOf<jni::Boolean>()) {
+ aOut.setBoolean(Java2Native<bool>(aData, aData.Env()));
+ } else if (aData.IsInstanceOf<jni::Integer>()) {
+ aOut.setInt32(Java2Native<int>(aData, aData.Env()));
+ } else if (aData.IsInstanceOf<jni::Byte>() ||
+ aData.IsInstanceOf<jni::Short>()) {
+ aOut.setInt32(java::sdk::Number::Ref::From(aData)->IntValue());
+ } else if (aData.IsInstanceOf<jni::Double>()) {
+ aOut.setNumber(Java2Native<double>(aData, aData.Env()));
+ } else if (aData.IsInstanceOf<jni::Float>() ||
+ aData.IsInstanceOf<jni::Long>()) {
+ aOut.setNumber(java::sdk::Number::Ref::From(aData)->DoubleValue());
+ } else if (aData.IsInstanceOf<jni::String>()) {
+ return UnboxString(aCx, aData, aOut);
+ } else if (aData.IsInstanceOf<jni::Character>()) {
+ return UnboxString(aCx, java::sdk::String::ValueOf(aData), aOut);
+ } else if (aData.IsInstanceOf<java::GeckoBundle>()) {
+ return UnboxBundle(aCx, aData, aOut);
+
+ } else if (aData.IsInstanceOf<jni::BooleanArray>()) {
+ return UnboxArrayPrimitive<
+ bool, jboolean, jbooleanArray, &JNIEnv::GetBooleanArrayElements,
+ &JNIEnv::ReleaseBooleanArrayElements, &JS::BooleanValue>(aCx, aData,
+ aOut);
+
+ } else if (aData.IsInstanceOf<jni::IntArray>()) {
+ return UnboxArrayPrimitive<
+ int32_t, jint, jintArray, &JNIEnv::GetIntArrayElements,
+ &JNIEnv::ReleaseIntArrayElements, &JS::Int32Value>(aCx, aData, aOut);
+
+ } else if (aData.IsInstanceOf<jni::DoubleArray>()) {
+ return UnboxArrayPrimitive<
+ double, jdouble, jdoubleArray, &JNIEnv::GetDoubleArrayElements,
+ &JNIEnv::ReleaseDoubleArrayElements, &JS::DoubleValue>(aCx, aData,
+ aOut);
+
+ } else if (aData.IsInstanceOf<StringArray>()) {
+ return UnboxArrayObject<&UnboxString>(aCx, aData, aOut);
+ } else if (aData.IsInstanceOf<GeckoBundleArray>()) {
+ return UnboxArrayObject<&UnboxBundle>(aCx, aData, aOut);
+ } else {
+ NS_WARNING("Invalid type");
+ return NS_ERROR_INVALID_ARG;
+ }
+ return NS_OK;
+}
+
+nsresult UnboxData(jni::String::Param aEvent, JSContext* aCx,
+ jni::Object::Param aData, JS::MutableHandleValue aOut,
+ bool aBundleOnly) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ jni::Object::LocalRef jniData(jni::GetGeckoThreadEnv(), aData);
+ nsresult rv = NS_ERROR_INVALID_ARG;
+
+ if (!aBundleOnly) {
+ rv = UnboxValue(aCx, jniData, aOut);
+ } else if (!jniData || jniData.IsInstanceOf<java::GeckoBundle>()) {
+ rv = UnboxBundle(aCx, jniData, aOut);
+ }
+ if (rv != NS_ERROR_INVALID_ARG || !aEvent) {
+ return rv;
+ }
+
+ nsCString event = aEvent->ToCString();
+ if (JS_IsExceptionPending(aCx)) {
+ JS::WarnUTF8(aCx, "Error dispatching %s", event.get());
+ } else {
+ JS_ReportErrorUTF8(aCx, "Invalid event data for %s", event.get());
+ }
+ return NS_ERROR_INVALID_ARG;
+}
+
+class JavaCallbackDelegate final : public nsIAndroidEventCallback {
+ const java::EventCallback::GlobalRef mCallback;
+
+ virtual ~JavaCallbackDelegate() {}
+
+ NS_IMETHOD Call(JSContext* aCx, JS::HandleValue aData,
+ void (java::EventCallback::*aCall)(jni::Object::Param)
+ const) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ jni::Object::LocalRef data(jni::GetGeckoThreadEnv());
+ nsresult rv = BoxData(u"callback"_ns, aCx, aData, data,
+ /* ObjectOnly */ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dom::AutoNoJSAPI nojsapi;
+
+ (java::EventCallback(*mCallback).*aCall)(data);
+ return NS_OK;
+ }
+
+ public:
+ explicit JavaCallbackDelegate(java::EventCallback::Param aCallback)
+ : mCallback(jni::GetGeckoThreadEnv(), aCallback) {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD OnSuccess(JS::HandleValue aData, JSContext* aCx) override {
+ return Call(aCx, aData, &java::EventCallback::SendSuccess);
+ }
+
+ NS_IMETHOD OnError(JS::HandleValue aData, JSContext* aCx) override {
+ return Call(aCx, aData, &java::EventCallback::SendError);
+ }
+};
+
+NS_IMPL_ISUPPORTS(JavaCallbackDelegate, nsIAndroidEventCallback)
+
+class NativeCallbackDelegateSupport final
+ : public java::EventDispatcher::NativeCallbackDelegate ::Natives<
+ NativeCallbackDelegateSupport> {
+ using CallbackDelegate = java::EventDispatcher::NativeCallbackDelegate;
+ using Base = CallbackDelegate::Natives<NativeCallbackDelegateSupport>;
+
+ const nsCOMPtr<nsIAndroidEventCallback> mCallback;
+ const nsCOMPtr<nsIAndroidEventFinalizer> mFinalizer;
+ const nsCOMPtr<nsIGlobalObject> mGlobalObject;
+
+ void Call(jni::Object::Param aData,
+ nsresult (nsIAndroidEventCallback::*aCall)(JS::HandleValue,
+ JSContext*)) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Use either the attached window's realm or a default realm.
+
+ dom::AutoJSAPI jsapi;
+ NS_ENSURE_TRUE_VOID(jsapi.Init(mGlobalObject));
+
+ JS::RootedValue data(jsapi.cx());
+ nsresult rv = UnboxData(u"callback"_ns, jsapi.cx(), aData, &data,
+ /* BundleOnly */ false);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = (mCallback->*aCall)(data, jsapi.cx());
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+
+ public:
+ using Base::AttachNative;
+
+ template <typename Functor>
+ static void OnNativeCall(Functor&& aCall) {
+ if (NS_IsMainThread()) {
+ // Invoke callbacks synchronously if we're already on Gecko thread.
+ return aCall();
+ }
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("OnNativeCall", std::move(aCall)));
+ }
+
+ static void Finalize(const CallbackDelegate::LocalRef& aInstance) {
+ DisposeNative(aInstance);
+ }
+
+ NativeCallbackDelegateSupport(nsIAndroidEventCallback* callback,
+ nsIAndroidEventFinalizer* finalizer,
+ nsIGlobalObject* globalObject)
+ : mCallback(callback),
+ mFinalizer(finalizer),
+ mGlobalObject(globalObject) {}
+
+ ~NativeCallbackDelegateSupport() {
+ if (mFinalizer) {
+ mFinalizer->OnFinalize();
+ }
+ }
+
+ void SendSuccess(jni::Object::Param aData) {
+ Call(aData, &nsIAndroidEventCallback::OnSuccess);
+ }
+
+ void SendError(jni::Object::Param aData) {
+ Call(aData, &nsIAndroidEventCallback::OnError);
+ }
+};
+
+class FinalizingCallbackDelegate final : public nsIAndroidEventCallback {
+ const nsCOMPtr<nsIAndroidEventCallback> mCallback;
+ const nsCOMPtr<nsIAndroidEventFinalizer> mFinalizer;
+
+ virtual ~FinalizingCallbackDelegate() {
+ if (mFinalizer) {
+ mFinalizer->OnFinalize();
+ }
+ }
+
+ public:
+ FinalizingCallbackDelegate(nsIAndroidEventCallback* aCallback,
+ nsIAndroidEventFinalizer* aFinalizer)
+ : mCallback(aCallback), mFinalizer(aFinalizer) {}
+
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSIANDROIDEVENTCALLBACK(mCallback->);
+};
+
+NS_IMPL_ISUPPORTS(FinalizingCallbackDelegate, nsIAndroidEventCallback)
+
+} // namespace detail
+
+using namespace detail;
+
+NS_IMPL_ISUPPORTS(EventDispatcher, nsIAndroidEventDispatcher)
+
+nsIGlobalObject* EventDispatcher::GetGlobalObject() {
+ if (mDOMWindow) {
+ return nsGlobalWindowInner::Cast(mDOMWindow->GetCurrentInnerWindow());
+ }
+ return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+nsresult EventDispatcher::DispatchOnGecko(ListenersList* list,
+ const nsAString& aEvent,
+ JS::HandleValue aData,
+ nsIAndroidEventCallback* aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+ dom::AutoNoJSAPI nojsapi;
+
+ list->lockCount++;
+
+ auto iteratingScope = MakeScopeExit([list] {
+ list->lockCount--;
+ if (list->lockCount || !list->unregistering) {
+ return;
+ }
+
+ list->unregistering = false;
+ for (ssize_t i = list->listeners.Count() - 1; i >= 0; i--) {
+ if (list->listeners[i]) {
+ continue;
+ }
+ list->listeners.RemoveObjectAt(i);
+ }
+ });
+
+ const size_t count = list->listeners.Count();
+ for (size_t i = 0; i < count; i++) {
+ if (!list->listeners[i]) {
+ // Unregistered.
+ continue;
+ }
+ const nsresult rv = list->listeners[i]->OnEvent(aEvent, aData, aCallback);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+ return NS_OK;
+}
+
+java::EventDispatcher::NativeCallbackDelegate::LocalRef
+EventDispatcher::WrapCallback(nsIAndroidEventCallback* aCallback,
+ nsIAndroidEventFinalizer* aFinalizer) {
+ if (!aCallback) {
+ return java::EventDispatcher::NativeCallbackDelegate::LocalRef(
+ jni::GetGeckoThreadEnv());
+ }
+
+ java::EventDispatcher::NativeCallbackDelegate::LocalRef callback =
+ java::EventDispatcher::NativeCallbackDelegate::New();
+ NativeCallbackDelegateSupport::AttachNative(
+ callback, MakeUnique<NativeCallbackDelegateSupport>(aCallback, aFinalizer,
+ GetGlobalObject()));
+ return callback;
+}
+
+bool EventDispatcher::HasListener(const char16_t* aEvent) {
+ java::EventDispatcher::LocalRef dispatcher(mDispatcher);
+ if (!dispatcher) {
+ return false;
+ }
+
+ nsDependentString event(aEvent);
+ return dispatcher->HasListener(event);
+}
+
+NS_IMETHODIMP
+EventDispatcher::Dispatch(JS::HandleValue aEvent, JS::HandleValue aData,
+ nsIAndroidEventCallback* aCallback,
+ nsIAndroidEventFinalizer* aFinalizer,
+ JSContext* aCx) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aEvent.isString()) {
+ NS_WARNING("Invalid event name");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoJSString event;
+ NS_ENSURE_TRUE(CheckJS(aCx, event.init(aCx, aEvent.toString())),
+ NS_ERROR_OUT_OF_MEMORY);
+
+ // Don't need to lock here because we're on the main thread, and we can't
+ // race against Register/UnregisterListener.
+
+ ListenersList* list = mListenersMap.Get(event);
+ if (list) {
+ if (!aCallback || !aFinalizer) {
+ return DispatchOnGecko(list, event, aData, aCallback);
+ }
+ nsCOMPtr<nsIAndroidEventCallback> callback(
+ new FinalizingCallbackDelegate(aCallback, aFinalizer));
+ return DispatchOnGecko(list, event, aData, callback);
+ }
+
+ java::EventDispatcher::LocalRef dispatcher(mDispatcher);
+ if (!dispatcher) {
+ return NS_OK;
+ }
+
+ jni::Object::LocalRef data(jni::GetGeckoThreadEnv());
+ nsresult rv = BoxData(event, aCx, aData, data, /* ObjectOnly */ true);
+ // Keep XPConnect from overriding the JSContext exception with one
+ // based on the nsresult.
+ //
+ // XXXbz Does xpconnect still do that? Needs to be checked/tested.
+ NS_ENSURE_SUCCESS(rv, JS_IsExceptionPending(aCx) ? NS_OK : rv);
+
+ dom::AutoNoJSAPI nojsapi;
+ dispatcher->DispatchToThreads(event, data,
+ WrapCallback(aCallback, aFinalizer));
+ return NS_OK;
+}
+
+nsresult EventDispatcher::Dispatch(const char16_t* aEvent,
+ java::GeckoBundle::Param aData,
+ nsIAndroidEventCallback* aCallback) {
+ nsDependentString event(aEvent);
+
+ ListenersList* list = mListenersMap.Get(event);
+ if (list) {
+ dom::AutoJSAPI jsapi;
+ NS_ENSURE_TRUE(jsapi.Init(GetGlobalObject()), NS_ERROR_FAILURE);
+ JS::RootedValue data(jsapi.cx());
+ nsresult rv = UnboxData(/* Event */ nullptr, jsapi.cx(), aData, &data,
+ /* BundleOnly */ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return DispatchOnGecko(list, event, data, aCallback);
+ }
+
+ java::EventDispatcher::LocalRef dispatcher(mDispatcher);
+ if (!dispatcher) {
+ return NS_OK;
+ }
+
+ dispatcher->DispatchToThreads(event, aData, WrapCallback(aCallback));
+ return NS_OK;
+}
+
+nsresult EventDispatcher::IterateEvents(JSContext* aCx, JS::HandleValue aEvents,
+ IterateEventsCallback aCallback,
+ nsIAndroidEventListener* aListener) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MutexAutoLock lock(mLock);
+
+ auto processEvent = [this, aCx, aCallback,
+ aListener](JS::HandleValue event) -> nsresult {
+ nsAutoJSString str;
+ NS_ENSURE_TRUE(CheckJS(aCx, str.init(aCx, event.toString())),
+ NS_ERROR_OUT_OF_MEMORY);
+ return (this->*aCallback)(str, aListener);
+ };
+
+ if (aEvents.isString()) {
+ return processEvent(aEvents);
+ }
+
+ bool isArray = false;
+ NS_ENSURE_TRUE(aEvents.isObject(), NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(CheckJS(aCx, JS::IsArrayObject(aCx, aEvents, &isArray)),
+ NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(isArray, NS_ERROR_INVALID_ARG);
+
+ JS::RootedObject events(aCx, &aEvents.toObject());
+ uint32_t length = 0;
+ NS_ENSURE_TRUE(CheckJS(aCx, JS::GetArrayLength(aCx, events, &length)),
+ NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(length, NS_ERROR_INVALID_ARG);
+
+ for (size_t i = 0; i < length; i++) {
+ JS::RootedValue event(aCx);
+ NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, events, i, &event)),
+ NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(event.isString(), NS_ERROR_INVALID_ARG);
+
+ const nsresult rv = processEvent(event);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+nsresult EventDispatcher::RegisterEventLocked(
+ const nsAString& aEvent, nsIAndroidEventListener* aListener) {
+ ListenersList* list = mListenersMap.Get(aEvent);
+ if (!list) {
+ list = new ListenersList();
+ mListenersMap.Put(aEvent, list);
+ }
+
+#ifdef DEBUG
+ for (ssize_t i = 0; i < list->listeners.Count(); i++) {
+ NS_ENSURE_TRUE(list->listeners[i] != aListener,
+ NS_ERROR_ALREADY_INITIALIZED);
+ }
+#endif
+
+ list->listeners.AppendObject(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EventDispatcher::RegisterListener(nsIAndroidEventListener* aListener,
+ JS::HandleValue aEvents, JSContext* aCx) {
+ return IterateEvents(aCx, aEvents, &EventDispatcher::RegisterEventLocked,
+ aListener);
+}
+
+nsresult EventDispatcher::UnregisterEventLocked(
+ const nsAString& aEvent, nsIAndroidEventListener* aListener) {
+ ListenersList* list = mListenersMap.Get(aEvent);
+#ifdef DEBUG
+ NS_ENSURE_TRUE(list, NS_ERROR_NOT_INITIALIZED);
+#else
+ NS_ENSURE_TRUE(list, NS_OK);
+#endif
+
+ DebugOnly<bool> found = false;
+ for (ssize_t i = list->listeners.Count() - 1; i >= 0; i--) {
+ if (list->listeners[i] != aListener) {
+ continue;
+ }
+ if (list->lockCount) {
+ // Only mark for removal when list is locked.
+ list->listeners.ReplaceObjectAt(nullptr, i);
+ list->unregistering = true;
+ } else {
+ list->listeners.RemoveObjectAt(i);
+ }
+ found = true;
+ }
+#ifdef DEBUG
+ return found ? NS_OK : NS_ERROR_NOT_INITIALIZED;
+#else
+ return NS_OK;
+#endif
+}
+
+NS_IMETHODIMP
+EventDispatcher::UnregisterListener(nsIAndroidEventListener* aListener,
+ JS::HandleValue aEvents, JSContext* aCx) {
+ return IterateEvents(aCx, aEvents, &EventDispatcher::UnregisterEventLocked,
+ aListener);
+}
+
+void EventDispatcher::Attach(java::EventDispatcher::Param aDispatcher,
+ nsPIDOMWindowOuter* aDOMWindow) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aDispatcher);
+
+ java::EventDispatcher::LocalRef dispatcher(mDispatcher);
+
+ if (dispatcher) {
+ if (dispatcher == aDispatcher) {
+ // Only need to update the window.
+ mDOMWindow = aDOMWindow;
+ return;
+ }
+ dispatcher->SetAttachedToGecko(java::EventDispatcher::REATTACHING);
+ }
+
+ dispatcher = java::EventDispatcher::LocalRef(aDispatcher);
+ NativesBase::AttachNative(dispatcher, this);
+ mDispatcher = dispatcher;
+ mDOMWindow = aDOMWindow;
+
+ dispatcher->SetAttachedToGecko(java::EventDispatcher::ATTACHED);
+}
+
+void EventDispatcher::Shutdown() {
+ mDispatcher = nullptr;
+ mDOMWindow = nullptr;
+}
+
+void EventDispatcher::Detach() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mDispatcher);
+
+ java::EventDispatcher::GlobalRef dispatcher(mDispatcher);
+
+ // SetAttachedToGecko will call disposeNative for us later on the Gecko
+ // thread to make sure all pending dispatchToGecko calls have completed.
+ if (dispatcher) {
+ dispatcher->SetAttachedToGecko(java::EventDispatcher::DETACHED);
+ }
+
+ Shutdown();
+}
+
+bool EventDispatcher::HasGeckoListener(jni::String::Param aEvent) {
+ // Can be called from any thread.
+ MutexAutoLock lock(mLock);
+ return !!mListenersMap.Get(aEvent->ToString());
+}
+
+void EventDispatcher::DispatchToGecko(jni::String::Param aEvent,
+ jni::Object::Param aData,
+ jni::Object::Param aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Don't need to lock here because we're on the main thread, and we can't
+ // race against Register/UnregisterListener.
+
+ nsString event = aEvent->ToString();
+ ListenersList* list = mListenersMap.Get(event);
+ if (!list || list->listeners.IsEmpty()) {
+ return;
+ }
+
+ // Use the same compartment as the attached window if possible, otherwise
+ // use a default compartment.
+ dom::AutoJSAPI jsapi;
+ NS_ENSURE_TRUE_VOID(jsapi.Init(GetGlobalObject()));
+
+ JS::RootedValue data(jsapi.cx());
+ nsresult rv = UnboxData(aEvent, jsapi.cx(), aData, &data,
+ /* BundleOnly */ true);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIAndroidEventCallback> callback;
+ if (aCallback) {
+ callback =
+ new JavaCallbackDelegate(java::EventCallback::Ref::From(aCallback));
+ }
+
+ DispatchOnGecko(list, event, data, callback);
+}
+
+/* static */
+nsresult EventDispatcher::UnboxBundle(JSContext* aCx, jni::Object::Param aData,
+ JS::MutableHandleValue aOut) {
+ return detail::UnboxBundle(aCx, aData, aOut);
+}
+
+} // namespace widget
+} // namespace mozilla
+
+#if defined(__clang__)
+# pragma clang diagnostic pop
+#endif
diff --git a/widget/android/EventDispatcher.h b/widget/android/EventDispatcher.h
new file mode 100644
index 0000000000..a17822e248
--- /dev/null
+++ b/widget/android/EventDispatcher.h
@@ -0,0 +1,104 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * vim: set sw=2 ts=4 expandtab:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_EventDispatcher_h
+#define mozilla_widget_EventDispatcher_h
+
+#include "jsapi.h"
+#include "nsClassHashtable.h"
+#include "nsCOMArray.h"
+#include "nsIAndroidBridge.h"
+#include "nsHashKeys.h"
+#include "nsPIDOMWindow.h"
+
+#include "mozilla/java/EventDispatcherNatives.h"
+#include "mozilla/java/GeckoBundleWrappers.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * EventDispatcher is the Gecko counterpart to the Java EventDispatcher class.
+ * Together, they make up a unified event bus. Events dispatched from the Java
+ * side may notify event listeners on the Gecko side, and vice versa.
+ */
+class EventDispatcher final
+ : public nsIAndroidEventDispatcher,
+ public java::EventDispatcher::Natives<EventDispatcher> {
+ using NativesBase = java::EventDispatcher::Natives<EventDispatcher>;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIANDROIDEVENTDISPATCHER
+
+ EventDispatcher() {}
+
+ void Attach(java::EventDispatcher::Param aDispatcher,
+ nsPIDOMWindowOuter* aDOMWindow);
+ void Detach();
+
+ nsresult Dispatch(const char16_t* aEvent,
+ java::GeckoBundle::Param aData = nullptr,
+ nsIAndroidEventCallback* aCallback = nullptr);
+
+ bool HasListener(const char16_t* aEvent);
+ bool HasGeckoListener(jni::String::Param aEvent);
+ void DispatchToGecko(jni::String::Param aEvent, jni::Object::Param aData,
+ jni::Object::Param aCallback);
+
+ static nsresult UnboxBundle(JSContext* aCx, jni::Object::Param aData,
+ JS::MutableHandleValue aOut);
+
+ nsIGlobalObject* GetGlobalObject();
+
+ using NativesBase::DisposeNative;
+
+ private:
+ friend class java::EventDispatcher::Natives<EventDispatcher>;
+
+ java::EventDispatcher::WeakRef mDispatcher;
+ nsCOMPtr<nsPIDOMWindowOuter> mDOMWindow;
+
+ virtual ~EventDispatcher() {}
+
+ void Shutdown();
+
+ struct ListenersList {
+ nsCOMArray<nsIAndroidEventListener> listeners{/* count */ 1};
+ // 0 if the list can be modified
+ uint32_t lockCount{0};
+ // true if this list has a listener that is being unregistered
+ bool unregistering{false};
+ };
+
+ using ListenersMap = nsClassHashtable<nsStringHashKey, ListenersList>;
+
+ Mutex mLock{"mozilla::widget::EventDispatcher"};
+ ListenersMap mListenersMap;
+
+ using IterateEventsCallback =
+ nsresult (EventDispatcher::*)(const nsAString&, nsIAndroidEventListener*);
+
+ nsresult IterateEvents(JSContext* aCx, JS::HandleValue aEvents,
+ IterateEventsCallback aCallback,
+ nsIAndroidEventListener* aListener);
+ nsresult RegisterEventLocked(const nsAString&, nsIAndroidEventListener*);
+ nsresult UnregisterEventLocked(const nsAString&, nsIAndroidEventListener*);
+
+ nsresult DispatchOnGecko(ListenersList* list, const nsAString& aEvent,
+ JS::HandleValue aData,
+ nsIAndroidEventCallback* aCallback);
+
+ java::EventDispatcher::NativeCallbackDelegate::LocalRef WrapCallback(
+ nsIAndroidEventCallback* aCallback,
+ nsIAndroidEventFinalizer* aFinalizer = nullptr);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_EventDispatcher_h
diff --git a/widget/android/GeckoBatteryManager.h b/widget/android/GeckoBatteryManager.h
new file mode 100644
index 0000000000..d9a171d0d1
--- /dev/null
+++ b/widget/android/GeckoBatteryManager.h
@@ -0,0 +1,28 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 GeckoBatteryManager_h
+#define GeckoBatteryManager_h
+
+#include "nsAppShell.h"
+
+#include "mozilla/Hal.h"
+#include "mozilla/java/GeckoBatteryManagerNatives.h"
+
+namespace mozilla {
+
+class GeckoBatteryManager final
+ : public java::GeckoBatteryManager::Natives<GeckoBatteryManager> {
+ public:
+ static void OnBatteryChange(double aLevel, bool aCharging,
+ double aRemainingTime) {
+ hal::NotifyBatteryChange(
+ hal::BatteryInformation(aLevel, aCharging, aRemainingTime));
+ }
+};
+
+} // namespace mozilla
+
+#endif // GeckoBatteryManager_h
diff --git a/widget/android/GeckoEditableSupport.cpp b/widget/android/GeckoEditableSupport.cpp
new file mode 100644
index 0000000000..4579fc6198
--- /dev/null
+++ b/widget/android/GeckoEditableSupport.cpp
@@ -0,0 +1,1630 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 4; indent-tabs-mode: nil; -*-
+ * vim: set sw=2 ts=4 expandtab:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "GeckoEditableSupport.h"
+
+#include "AndroidRect.h"
+#include "KeyEvent.h"
+#include "PuppetWidget.h"
+#include "nsIContent.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/java/GeckoEditableChildWrappers.h"
+#include "mozilla/java/GeckoServiceChildProcessWrappers.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_intl.h"
+#include "mozilla/TextComposition.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/ToString.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/widget/GeckoViewSupport.h"
+
+#include <android/api-level.h>
+#include <android/input.h>
+#include <android/log.h>
+
+#ifdef NIGHTLY_BUILD
+static mozilla::LazyLogModule sGeckoEditableSupportLog("GeckoEditableSupport");
+# define ALOGIME(...) \
+ MOZ_LOG(sGeckoEditableSupportLog, LogLevel::Debug, (__VA_ARGS__))
+#else
+# define ALOGIME(args...) \
+ do { \
+ } while (0)
+#endif
+
+static uint32_t ConvertAndroidKeyCodeToDOMKeyCode(int32_t androidKeyCode) {
+ // Special-case alphanumeric keycodes because they are most common.
+ if (androidKeyCode >= AKEYCODE_A && androidKeyCode <= AKEYCODE_Z) {
+ return androidKeyCode - AKEYCODE_A + NS_VK_A;
+ }
+
+ if (androidKeyCode >= AKEYCODE_0 && androidKeyCode <= AKEYCODE_9) {
+ return androidKeyCode - AKEYCODE_0 + NS_VK_0;
+ }
+
+ switch (androidKeyCode) {
+ // KEYCODE_UNKNOWN (0) ... KEYCODE_HOME (3)
+ case AKEYCODE_BACK:
+ return NS_VK_ESCAPE;
+ // KEYCODE_CALL (5) ... KEYCODE_POUND (18)
+ case AKEYCODE_DPAD_UP:
+ return NS_VK_UP;
+ case AKEYCODE_DPAD_DOWN:
+ return NS_VK_DOWN;
+ case AKEYCODE_DPAD_LEFT:
+ return NS_VK_LEFT;
+ case AKEYCODE_DPAD_RIGHT:
+ return NS_VK_RIGHT;
+ case AKEYCODE_DPAD_CENTER:
+ return NS_VK_RETURN;
+ case AKEYCODE_VOLUME_UP:
+ return NS_VK_VOLUME_UP;
+ case AKEYCODE_VOLUME_DOWN:
+ return NS_VK_VOLUME_DOWN;
+ // KEYCODE_VOLUME_POWER (26) ... KEYCODE_Z (54)
+ case AKEYCODE_COMMA:
+ return NS_VK_COMMA;
+ case AKEYCODE_PERIOD:
+ return NS_VK_PERIOD;
+ case AKEYCODE_ALT_LEFT:
+ return NS_VK_ALT;
+ case AKEYCODE_ALT_RIGHT:
+ return NS_VK_ALT;
+ case AKEYCODE_SHIFT_LEFT:
+ return NS_VK_SHIFT;
+ case AKEYCODE_SHIFT_RIGHT:
+ return NS_VK_SHIFT;
+ case AKEYCODE_TAB:
+ return NS_VK_TAB;
+ case AKEYCODE_SPACE:
+ return NS_VK_SPACE;
+ // KEYCODE_SYM (63) ... KEYCODE_ENVELOPE (65)
+ case AKEYCODE_ENTER:
+ return NS_VK_RETURN;
+ case AKEYCODE_DEL:
+ return NS_VK_BACK; // Backspace
+ case AKEYCODE_GRAVE:
+ return NS_VK_BACK_QUOTE;
+ // KEYCODE_MINUS (69)
+ case AKEYCODE_EQUALS:
+ return NS_VK_EQUALS;
+ case AKEYCODE_LEFT_BRACKET:
+ return NS_VK_OPEN_BRACKET;
+ case AKEYCODE_RIGHT_BRACKET:
+ return NS_VK_CLOSE_BRACKET;
+ case AKEYCODE_BACKSLASH:
+ return NS_VK_BACK_SLASH;
+ case AKEYCODE_SEMICOLON:
+ return NS_VK_SEMICOLON;
+ // KEYCODE_APOSTROPHE (75)
+ case AKEYCODE_SLASH:
+ return NS_VK_SLASH;
+ // KEYCODE_AT (77) ... KEYCODE_MEDIA_FAST_FORWARD (90)
+ case AKEYCODE_MUTE:
+ return NS_VK_VOLUME_MUTE;
+ case AKEYCODE_PAGE_UP:
+ return NS_VK_PAGE_UP;
+ case AKEYCODE_PAGE_DOWN:
+ return NS_VK_PAGE_DOWN;
+ // KEYCODE_PICTSYMBOLS (94) ... KEYCODE_BUTTON_MODE (110)
+ case AKEYCODE_ESCAPE:
+ return NS_VK_ESCAPE;
+ case AKEYCODE_FORWARD_DEL:
+ return NS_VK_DELETE;
+ case AKEYCODE_CTRL_LEFT:
+ return NS_VK_CONTROL;
+ case AKEYCODE_CTRL_RIGHT:
+ return NS_VK_CONTROL;
+ case AKEYCODE_CAPS_LOCK:
+ return NS_VK_CAPS_LOCK;
+ case AKEYCODE_SCROLL_LOCK:
+ return NS_VK_SCROLL_LOCK;
+ // KEYCODE_META_LEFT (117) ... KEYCODE_FUNCTION (119)
+ case AKEYCODE_SYSRQ:
+ return NS_VK_PRINTSCREEN;
+ case AKEYCODE_BREAK:
+ return NS_VK_PAUSE;
+ case AKEYCODE_MOVE_HOME:
+ return NS_VK_HOME;
+ case AKEYCODE_MOVE_END:
+ return NS_VK_END;
+ case AKEYCODE_INSERT:
+ return NS_VK_INSERT;
+ // KEYCODE_FORWARD (125) ... KEYCODE_MEDIA_RECORD (130)
+ case AKEYCODE_F1:
+ return NS_VK_F1;
+ case AKEYCODE_F2:
+ return NS_VK_F2;
+ case AKEYCODE_F3:
+ return NS_VK_F3;
+ case AKEYCODE_F4:
+ return NS_VK_F4;
+ case AKEYCODE_F5:
+ return NS_VK_F5;
+ case AKEYCODE_F6:
+ return NS_VK_F6;
+ case AKEYCODE_F7:
+ return NS_VK_F7;
+ case AKEYCODE_F8:
+ return NS_VK_F8;
+ case AKEYCODE_F9:
+ return NS_VK_F9;
+ case AKEYCODE_F10:
+ return NS_VK_F10;
+ case AKEYCODE_F11:
+ return NS_VK_F11;
+ case AKEYCODE_F12:
+ return NS_VK_F12;
+ case AKEYCODE_NUM_LOCK:
+ return NS_VK_NUM_LOCK;
+ case AKEYCODE_NUMPAD_0:
+ return NS_VK_NUMPAD0;
+ case AKEYCODE_NUMPAD_1:
+ return NS_VK_NUMPAD1;
+ case AKEYCODE_NUMPAD_2:
+ return NS_VK_NUMPAD2;
+ case AKEYCODE_NUMPAD_3:
+ return NS_VK_NUMPAD3;
+ case AKEYCODE_NUMPAD_4:
+ return NS_VK_NUMPAD4;
+ case AKEYCODE_NUMPAD_5:
+ return NS_VK_NUMPAD5;
+ case AKEYCODE_NUMPAD_6:
+ return NS_VK_NUMPAD6;
+ case AKEYCODE_NUMPAD_7:
+ return NS_VK_NUMPAD7;
+ case AKEYCODE_NUMPAD_8:
+ return NS_VK_NUMPAD8;
+ case AKEYCODE_NUMPAD_9:
+ return NS_VK_NUMPAD9;
+ case AKEYCODE_NUMPAD_DIVIDE:
+ return NS_VK_DIVIDE;
+ case AKEYCODE_NUMPAD_MULTIPLY:
+ return NS_VK_MULTIPLY;
+ case AKEYCODE_NUMPAD_SUBTRACT:
+ return NS_VK_SUBTRACT;
+ case AKEYCODE_NUMPAD_ADD:
+ return NS_VK_ADD;
+ case AKEYCODE_NUMPAD_DOT:
+ return NS_VK_DECIMAL;
+ case AKEYCODE_NUMPAD_COMMA:
+ return NS_VK_SEPARATOR;
+ case AKEYCODE_NUMPAD_ENTER:
+ return NS_VK_RETURN;
+ case AKEYCODE_NUMPAD_EQUALS:
+ return NS_VK_EQUALS;
+ // KEYCODE_NUMPAD_LEFT_PAREN (162) ... KEYCODE_CALCULATOR (210)
+
+ // Needs to confirm the behavior. If the key switches the open state
+ // of Japanese IME (or switches input character between Hiragana and
+ // Roman numeric characters), then, it might be better to use
+ // NS_VK_KANJI which is used for Alt+Zenkaku/Hankaku key on Windows.
+ case AKEYCODE_ZENKAKU_HANKAKU:
+ return 0;
+ case AKEYCODE_EISU:
+ return NS_VK_EISU;
+ case AKEYCODE_MUHENKAN:
+ return NS_VK_NONCONVERT;
+ case AKEYCODE_HENKAN:
+ return NS_VK_CONVERT;
+ case AKEYCODE_KATAKANA_HIRAGANA:
+ return 0;
+ case AKEYCODE_YEN:
+ return NS_VK_BACK_SLASH; // Same as other platforms.
+ case AKEYCODE_RO:
+ return NS_VK_BACK_SLASH; // Same as other platforms.
+ case AKEYCODE_KANA:
+ return NS_VK_KANA;
+ case AKEYCODE_ASSIST:
+ return NS_VK_HELP;
+
+ // the A key is the action key for gamepad devices.
+ case AKEYCODE_BUTTON_A:
+ return NS_VK_RETURN;
+
+ default:
+ ALOG(
+ "ConvertAndroidKeyCodeToDOMKeyCode: "
+ "No DOM keycode for Android keycode %d",
+ int(androidKeyCode));
+ return 0;
+ }
+}
+
+static KeyNameIndex ConvertAndroidKeyCodeToKeyNameIndex(
+ int32_t keyCode, int32_t action, int32_t domPrintableKeyValue) {
+ // Special-case alphanumeric keycodes because they are most common.
+ if (keyCode >= AKEYCODE_A && keyCode <= AKEYCODE_Z) {
+ return KEY_NAME_INDEX_USE_STRING;
+ }
+
+ if (keyCode >= AKEYCODE_0 && keyCode <= AKEYCODE_9) {
+ return KEY_NAME_INDEX_USE_STRING;
+ }
+
+ switch (keyCode) {
+#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: \
+ return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ // KEYCODE_0 (7) ... KEYCODE_9 (16)
+ case AKEYCODE_STAR: // '*' key
+ case AKEYCODE_POUND: // '#' key
+
+ // KEYCODE_A (29) ... KEYCODE_Z (54)
+
+ case AKEYCODE_COMMA: // ',' key
+ case AKEYCODE_PERIOD: // '.' key
+ case AKEYCODE_SPACE:
+ case AKEYCODE_GRAVE: // '`' key
+ case AKEYCODE_MINUS: // '-' key
+ case AKEYCODE_EQUALS: // '=' key
+ case AKEYCODE_LEFT_BRACKET: // '[' key
+ case AKEYCODE_RIGHT_BRACKET: // ']' key
+ case AKEYCODE_BACKSLASH: // '\' key
+ case AKEYCODE_SEMICOLON: // ';' key
+ case AKEYCODE_APOSTROPHE: // ''' key
+ case AKEYCODE_SLASH: // '/' key
+ case AKEYCODE_AT: // '@' key
+ case AKEYCODE_PLUS: // '+' key
+
+ case AKEYCODE_NUMPAD_0:
+ case AKEYCODE_NUMPAD_1:
+ case AKEYCODE_NUMPAD_2:
+ case AKEYCODE_NUMPAD_3:
+ case AKEYCODE_NUMPAD_4:
+ case AKEYCODE_NUMPAD_5:
+ case AKEYCODE_NUMPAD_6:
+ case AKEYCODE_NUMPAD_7:
+ case AKEYCODE_NUMPAD_8:
+ case AKEYCODE_NUMPAD_9:
+ case AKEYCODE_NUMPAD_DIVIDE:
+ case AKEYCODE_NUMPAD_MULTIPLY:
+ case AKEYCODE_NUMPAD_SUBTRACT:
+ case AKEYCODE_NUMPAD_ADD:
+ case AKEYCODE_NUMPAD_DOT:
+ case AKEYCODE_NUMPAD_COMMA:
+ case AKEYCODE_NUMPAD_EQUALS:
+ case AKEYCODE_NUMPAD_LEFT_PAREN:
+ case AKEYCODE_NUMPAD_RIGHT_PAREN:
+
+ case AKEYCODE_YEN: // yen sign key
+ case AKEYCODE_RO: // Japanese Ro key
+ return KEY_NAME_INDEX_USE_STRING;
+
+ case AKEYCODE_NUM: // XXX Not sure
+ case AKEYCODE_PICTSYMBOLS:
+
+ case AKEYCODE_BUTTON_A:
+ case AKEYCODE_BUTTON_B:
+ case AKEYCODE_BUTTON_C:
+ case AKEYCODE_BUTTON_X:
+ case AKEYCODE_BUTTON_Y:
+ case AKEYCODE_BUTTON_Z:
+ case AKEYCODE_BUTTON_L1:
+ case AKEYCODE_BUTTON_R1:
+ case AKEYCODE_BUTTON_L2:
+ case AKEYCODE_BUTTON_R2:
+ case AKEYCODE_BUTTON_THUMBL:
+ case AKEYCODE_BUTTON_THUMBR:
+ case AKEYCODE_BUTTON_START:
+ case AKEYCODE_BUTTON_SELECT:
+ case AKEYCODE_BUTTON_MODE:
+
+ case AKEYCODE_MEDIA_CLOSE:
+
+ case AKEYCODE_BUTTON_1:
+ case AKEYCODE_BUTTON_2:
+ case AKEYCODE_BUTTON_3:
+ case AKEYCODE_BUTTON_4:
+ case AKEYCODE_BUTTON_5:
+ case AKEYCODE_BUTTON_6:
+ case AKEYCODE_BUTTON_7:
+ case AKEYCODE_BUTTON_8:
+ case AKEYCODE_BUTTON_9:
+ case AKEYCODE_BUTTON_10:
+ case AKEYCODE_BUTTON_11:
+ case AKEYCODE_BUTTON_12:
+ case AKEYCODE_BUTTON_13:
+ case AKEYCODE_BUTTON_14:
+ case AKEYCODE_BUTTON_15:
+ case AKEYCODE_BUTTON_16:
+ return KEY_NAME_INDEX_Unidentified;
+
+ case AKEYCODE_UNKNOWN:
+ MOZ_ASSERT(action != AKEY_EVENT_ACTION_MULTIPLE,
+ "Don't call this when action is AKEY_EVENT_ACTION_MULTIPLE!");
+ // It's actually an unknown key if the action isn't ACTION_MULTIPLE.
+ // However, it might cause text input. So, let's check the value.
+ return domPrintableKeyValue ? KEY_NAME_INDEX_USE_STRING
+ : KEY_NAME_INDEX_Unidentified;
+
+ default:
+ ALOG(
+ "ConvertAndroidKeyCodeToKeyNameIndex: "
+ "No DOM key name index for Android keycode %d",
+ keyCode);
+ return KEY_NAME_INDEX_Unidentified;
+ }
+}
+
+static CodeNameIndex ConvertAndroidScanCodeToCodeNameIndex(int32_t scanCode) {
+ switch (scanCode) {
+#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
+ case aNativeKey: \
+ return aCodeNameIndex;
+
+#include "NativeKeyToDOMCodeName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
+
+ default:
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+}
+
+static void InitKeyEvent(WidgetKeyboardEvent& aEvent, int32_t aAction,
+ int32_t aKeyCode, int32_t aScanCode,
+ int32_t aMetaState, int64_t aTime,
+ int32_t aDomPrintableKeyValue, int32_t aRepeatCount,
+ int32_t aFlags) {
+ const uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(aKeyCode);
+
+ aEvent.mModifiers = nsWindow::GetModifiers(aMetaState);
+ aEvent.mKeyCode = domKeyCode;
+
+ aEvent.mIsRepeat =
+ (aEvent.mMessage == eKeyDown || aEvent.mMessage == eKeyPress) &&
+ ((aFlags & java::sdk::KeyEvent::FLAG_LONG_PRESS) || aRepeatCount);
+
+ aEvent.mKeyNameIndex = ConvertAndroidKeyCodeToKeyNameIndex(
+ aKeyCode, aAction, aDomPrintableKeyValue);
+ aEvent.mCodeNameIndex = ConvertAndroidScanCodeToCodeNameIndex(aScanCode);
+
+ if (aEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
+ aDomPrintableKeyValue) {
+ aEvent.mKeyValue = char16_t(aDomPrintableKeyValue);
+ }
+
+ aEvent.mLocation =
+ WidgetKeyboardEvent::ComputeLocationFromCodeValue(aEvent.mCodeNameIndex);
+ aEvent.mTime = aTime;
+ aEvent.mTimeStamp = nsWindow::GetEventTimeStamp(aTime);
+}
+
+static nscolor ConvertAndroidColor(uint32_t aArgb) {
+ return NS_RGBA((aArgb & 0x00ff0000) >> 16, (aArgb & 0x0000ff00) >> 8,
+ (aArgb & 0x000000ff), (aArgb & 0xff000000) >> 24);
+}
+
+static jni::ObjectArray::LocalRef ConvertRectArrayToJavaRectFArray(
+ const nsTArray<LayoutDeviceIntRect>& aRects,
+ const CSSToLayoutDeviceScale aScale) {
+ const size_t length = aRects.Length();
+ auto rects = jni::ObjectArray::New<java::sdk::RectF>(length);
+
+ for (size_t i = 0; i < length; i++) {
+ const LayoutDeviceIntRect& tmp = aRects[i];
+
+ // Character bounds in CSS units.
+ auto rect =
+ java::sdk::RectF::New(tmp.x / aScale.scale, tmp.y / aScale.scale,
+ (tmp.x + tmp.width) / aScale.scale,
+ (tmp.y + tmp.height) / aScale.scale);
+ rects->SetElement(i, rect);
+ }
+ return rects;
+}
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS(GeckoEditableSupport, TextEventDispatcherListener,
+ nsISupportsWeakReference)
+
+// This is the blocker helper class whether disposing GeckoEditableChild now.
+// During JNI call from GeckoEditableChild, we shouldn't dispose it.
+class MOZ_RAII AutoGeckoEditableBlocker final {
+ public:
+ explicit AutoGeckoEditableBlocker(GeckoEditableSupport* aGeckoEditableSupport)
+ : mGeckoEditable(aGeckoEditableSupport) {
+ mGeckoEditable->AddBlocker();
+ }
+ ~AutoGeckoEditableBlocker() { mGeckoEditable->ReleaseBlocker(); }
+
+ private:
+ RefPtr<GeckoEditableSupport> mGeckoEditable;
+};
+
+RefPtr<TextComposition> GeckoEditableSupport::GetComposition() const {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ return widget ? IMEStateManager::GetTextCompositionFor(widget) : nullptr;
+}
+
+bool GeckoEditableSupport::RemoveComposition(RemoveCompositionFlag aFlag) {
+ if (!mDispatcher || !mDispatcher->IsComposing()) {
+ return false;
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ NS_ENSURE_SUCCESS(BeginInputTransaction(mDispatcher), false);
+ mDispatcher->CommitComposition(
+ status, aFlag == CANCEL_IME_COMPOSITION ? &EmptyString() : nullptr);
+ return true;
+}
+
+void GeckoEditableSupport::OnKeyEvent(int32_t aAction, int32_t aKeyCode,
+ int32_t aScanCode, int32_t aMetaState,
+ int32_t aKeyPressMetaState, int64_t aTime,
+ int32_t aDomPrintableKeyValue,
+ int32_t aRepeatCount, int32_t aFlags,
+ bool aIsSynthesizedImeKey,
+ jni::Object::Param aOriginalEvent) {
+ AutoGeckoEditableBlocker blocker(this);
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ RefPtr<TextEventDispatcher> dispatcher =
+ mDispatcher ? mDispatcher.get()
+ : widget ? widget->GetTextEventDispatcher()
+ : nullptr;
+ NS_ENSURE_TRUE_VOID(dispatcher && widget);
+
+ if (!aIsSynthesizedImeKey) {
+ if (nsWindow* window = GetNsWindow()) {
+ window->UserActivity();
+ }
+ } else if (aIsSynthesizedImeKey && mIMEMaskEventsCount > 0) {
+ // Don't synthesize editor keys when not focused.
+ return;
+ }
+
+ EventMessage msg;
+ if (aAction == java::sdk::KeyEvent::ACTION_DOWN) {
+ msg = eKeyDown;
+ } else if (aAction == java::sdk::KeyEvent::ACTION_UP) {
+ msg = eKeyUp;
+ } else if (aAction == java::sdk::KeyEvent::ACTION_MULTIPLE) {
+ // Keys with multiple action are handled in Java,
+ // and we should never see one here
+ MOZ_CRASH("Cannot handle key with multiple action");
+ } else {
+ NS_WARNING("Unknown key action event");
+ return;
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetKeyboardEvent event(true, msg, widget);
+ InitKeyEvent(event, aAction, aKeyCode, aScanCode, aMetaState, aTime,
+ aDomPrintableKeyValue, aRepeatCount, aFlags);
+
+ if (nsIWidget::UsePuppetWidgets()) {
+ // Don't use native key bindings.
+ event.PreventNativeKeyBindings();
+ }
+
+ if (aIsSynthesizedImeKey) {
+ // Keys synthesized by Java IME code are saved in the mIMEKeyEvents
+ // array until the next IME_REPLACE_TEXT event, at which point
+ // these keys are dispatched in sequence.
+ mIMEKeyEvents.AppendElement(UniquePtr<WidgetEvent>(event.Duplicate()));
+ } else {
+ NS_ENSURE_SUCCESS_VOID(BeginInputTransaction(dispatcher));
+ dispatcher->DispatchKeyboardEvent(msg, event, status);
+ if (widget->Destroyed() || status == nsEventStatus_eConsumeNoDefault) {
+ // Skip default processing.
+ return;
+ }
+ mEditable->OnDefaultKeyEvent(aOriginalEvent);
+ }
+
+ // Only send keypress after keydown.
+ if (msg != eKeyDown) {
+ return;
+ }
+
+ WidgetKeyboardEvent pressEvent(true, eKeyPress, widget);
+ InitKeyEvent(pressEvent, aAction, aKeyCode, aScanCode, aKeyPressMetaState,
+ aTime, aDomPrintableKeyValue, aRepeatCount, aFlags);
+
+ if (nsIWidget::UsePuppetWidgets()) {
+ // Don't use native key bindings.
+ pressEvent.PreventNativeKeyBindings();
+ }
+
+ if (aIsSynthesizedImeKey) {
+ mIMEKeyEvents.AppendElement(UniquePtr<WidgetEvent>(pressEvent.Duplicate()));
+ } else {
+ dispatcher->MaybeDispatchKeypressEvents(pressEvent, status);
+ }
+}
+
+/*
+ * Send dummy key events for pages that are unaware of input events,
+ * to provide web compatibility for pages that depend on key events.
+ */
+void GeckoEditableSupport::SendIMEDummyKeyEvent(nsIWidget* aWidget,
+ EventMessage msg) {
+ AutoGeckoEditableBlocker blocker(this);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ MOZ_ASSERT(mDispatcher);
+
+ WidgetKeyboardEvent event(true, msg, aWidget);
+ event.mTime = PR_Now() / 1000;
+ // TODO: If we can know scan code of the key event which caused replacing
+ // composition string, we should set mCodeNameIndex here. Then,
+ // we should rename this method because it becomes not a "dummy"
+ // keyboard event.
+ event.mKeyCode = NS_VK_PROCESSKEY;
+ event.mKeyNameIndex = KEY_NAME_INDEX_Process;
+ // KeyboardEvents marked as "processed by IME" shouldn't cause any edit
+ // actions. So, we should set their native key binding to none before
+ // dispatch to avoid crash on PuppetWidget and avoid running redundant
+ // path to look for native key bindings.
+ event.PreventNativeKeyBindings();
+ NS_ENSURE_SUCCESS_VOID(BeginInputTransaction(mDispatcher));
+ mDispatcher->DispatchKeyboardEvent(msg, event, status);
+}
+
+void GeckoEditableSupport::AddIMETextChange(const IMETextChange& aChange) {
+ mIMETextChanges.AppendElement(aChange);
+
+ // We may not be in the middle of flushing,
+ // in which case this flag is meaningless.
+ mIMETextChangedDuringFlush = true;
+
+ // Now that we added a new range we need to go back and
+ // update all the ranges before that.
+ // Ranges that have offsets which follow this new range
+ // need to be updated to reflect new offsets
+ const int32_t delta = aChange.mNewEnd - aChange.mOldEnd;
+ for (int32_t i = mIMETextChanges.Length() - 2; i >= 0; i--) {
+ IMETextChange& previousChange = mIMETextChanges[i];
+ if (previousChange.mStart > aChange.mOldEnd) {
+ previousChange.mStart += delta;
+ previousChange.mOldEnd += delta;
+ previousChange.mNewEnd += delta;
+ }
+ }
+
+ // Now go through all ranges to merge any ranges that are connected
+ // srcIndex is the index of the range to merge from
+ // dstIndex is the index of the range to potentially merge into
+ int32_t srcIndex = mIMETextChanges.Length() - 1;
+ int32_t dstIndex = srcIndex;
+
+ while (--dstIndex >= 0) {
+ IMETextChange& src = mIMETextChanges[srcIndex];
+ IMETextChange& dst = mIMETextChanges[dstIndex];
+ // When merging a more recent change into an older
+ // change, we need to compare recent change's (start, oldEnd)
+ // range to the older change's (start, newEnd)
+ if (src.mOldEnd < dst.mStart || dst.mNewEnd < src.mStart) {
+ // No overlap between ranges
+ continue;
+ }
+
+ if (src.mStart == dst.mStart && src.mNewEnd == dst.mNewEnd) {
+ // Same range. Adjust old end offset.
+ dst.mOldEnd = std::min(src.mOldEnd, dst.mOldEnd);
+ } else {
+ // When merging two ranges, there are generally four posibilities:
+ // [----(----]----), (----[----]----),
+ // [----(----)----], (----[----)----]
+ // where [----] is the first range and (----) is the second range
+ // As seen above, the start of the merged range is always the lesser
+ // of the two start offsets. OldEnd and NewEnd then need to be
+ // adjusted separately depending on the case. In any case, the change
+ // in text length of the merged range should be the sum of text length
+ // changes of the two original ranges, i.e.,
+ // newNewEnd - newOldEnd == newEnd1 - oldEnd1 + newEnd2 - oldEnd2
+ dst.mStart = std::min(dst.mStart, src.mStart);
+ if (src.mOldEnd < dst.mNewEnd) {
+ // New range overlaps or is within previous range; merge
+ dst.mNewEnd += src.mNewEnd - src.mOldEnd;
+ } else { // src.mOldEnd >= dst.mNewEnd
+ // New range overlaps previous range; merge
+ dst.mOldEnd += src.mOldEnd - dst.mNewEnd;
+ dst.mNewEnd = src.mNewEnd;
+ }
+ }
+ // src merged to dst; delete src.
+ mIMETextChanges.RemoveElementAt(srcIndex);
+ // Any ranges that we skip over between src and dst are not mergeable
+ // so we can safely continue the merge starting at dst
+ srcIndex = dstIndex;
+ }
+}
+
+void GeckoEditableSupport::PostFlushIMEChanges() {
+ if (!mIMETextChanges.IsEmpty() || mIMESelectionChanged) {
+ // Already posted
+ return;
+ }
+
+ RefPtr<GeckoEditableSupport> self(this);
+
+ nsAppShell::PostEvent([this, self] {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (widget && !widget->Destroyed()) {
+ FlushIMEChanges();
+ }
+ });
+}
+
+void GeckoEditableSupport::FlushIMEChanges(FlushChangesFlag aFlags) {
+ // Only send change notifications if we are *not* masking events,
+ // i.e. if we have a focused editor,
+ NS_ENSURE_TRUE_VOID(!mIMEMaskEventsCount);
+
+ if (mIMEDelaySynchronizeReply && mIMEActiveCompositionCount > 0) {
+ // We are still expecting more composition events to be handled. Once
+ // that happens, FlushIMEChanges will be called again.
+ return;
+ }
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ NS_ENSURE_TRUE_VOID(widget);
+
+ struct TextRecord {
+ nsString text;
+ int32_t start;
+ int32_t oldEnd;
+ int32_t newEnd;
+ };
+ AutoTArray<TextRecord, 4> textTransaction;
+ textTransaction.SetCapacity(mIMETextChanges.Length());
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mIMETextChangedDuringFlush = false;
+
+ auto shouldAbort = [=](bool aForce) -> bool {
+ if (!aForce && !mIMETextChangedDuringFlush) {
+ return false;
+ }
+ // A query event could have triggered more text changes to come in, as
+ // indicated by our flag. If that happens, try flushing IME changes
+ // again.
+ if (aFlags == FLUSH_FLAG_NONE) {
+ FlushIMEChanges(FLUSH_FLAG_RETRY);
+ } else {
+ // Don't retry if already retrying, to avoid infinite loops.
+ __android_log_print(ANDROID_LOG_WARN, "GeckoEditableSupport",
+ "Already retrying IME flush");
+ }
+ return true;
+ };
+
+ for (const IMETextChange& change : mIMETextChanges) {
+ if (change.mStart == change.mOldEnd && change.mStart == change.mNewEnd) {
+ continue;
+ }
+
+ nsString insertedString;
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
+ widget);
+
+ if (change.mNewEnd != change.mStart) {
+ queryTextContentEvent.InitForQueryTextContent(
+ change.mStart, change.mNewEnd - change.mStart);
+ widget->DispatchEvent(&queryTextContentEvent, status);
+
+ if (shouldAbort(NS_WARN_IF(queryTextContentEvent.Failed()))) {
+ return;
+ }
+
+ insertedString = queryTextContentEvent.mReply->DataRef();
+ }
+
+ textTransaction.AppendElement(TextRecord{insertedString, change.mStart,
+ change.mOldEnd, change.mNewEnd});
+ }
+
+ int32_t selStart = -1;
+ int32_t selEnd = -1;
+
+ if (mIMESelectionChanged) {
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ widget);
+ widget->DispatchEvent(&querySelectedTextEvent, status);
+
+ if (shouldAbort(NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection()))) {
+ return;
+ }
+
+ selStart = static_cast<int32_t>(
+ querySelectedTextEvent.mReply->SelectionStartOffset());
+ selEnd = static_cast<int32_t>(
+ querySelectedTextEvent.mReply->SelectionEndOffset());
+
+ if (aFlags == FLUSH_FLAG_RECOVER) {
+ // Sometimes we get out-of-bounds selection during recovery.
+ // Limit the offsets so we don't crash.
+ for (const TextRecord& record : textTransaction) {
+ const int32_t end = record.start + record.text.Length();
+ selStart = std::min(selStart, end);
+ selEnd = std::min(selEnd, end);
+ }
+ }
+ }
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ auto flushOnException = [=]() -> bool {
+ if (!env->ExceptionCheck()) {
+ return false;
+ }
+ if (aFlags != FLUSH_FLAG_RECOVER) {
+ // First time seeing an exception; try flushing text.
+ env->ExceptionClear();
+ __android_log_print(ANDROID_LOG_WARN, "GeckoEditableSupport",
+ "Recovering from IME exception");
+ FlushIMEText(FLUSH_FLAG_RECOVER);
+ } else {
+ // Give up because we've already tried.
+#ifdef RELEASE_OR_BETA
+ env->ExceptionClear();
+#else
+ MOZ_CATCH_JNI_EXCEPTION(env);
+#endif
+ }
+ return true;
+ };
+
+ // Commit the text change and selection change transaction.
+ mIMETextChanges.Clear();
+
+ for (const TextRecord& record : textTransaction) {
+ mEditable->OnTextChange(record.text, record.start, record.oldEnd,
+ record.newEnd);
+ if (flushOnException()) {
+ return;
+ }
+ }
+
+ while (mIMEDelaySynchronizeReply && mIMEActiveSynchronizeCount) {
+ mIMEActiveSynchronizeCount--;
+ mEditable->NotifyIME(EditableListener::NOTIFY_IME_REPLY_EVENT);
+ }
+ mIMEDelaySynchronizeReply = false;
+ mIMEActiveSynchronizeCount = 0;
+ mIMEActiveCompositionCount = 0;
+
+ if (mIMESelectionChanged) {
+ mIMESelectionChanged = false;
+ mEditable->OnSelectionChange(selStart, selEnd);
+ flushOnException();
+ }
+}
+
+void GeckoEditableSupport::FlushIMEText(FlushChangesFlag aFlags) {
+ NS_WARNING_ASSERTION(
+ !mIMEDelaySynchronizeReply || !mIMEActiveCompositionCount,
+ "Cannot synchronize Java text with Gecko text");
+
+ // Notify Java of the newly focused content
+ mIMETextChanges.Clear();
+ mIMESelectionChanged = true;
+
+ // Use 'INT32_MAX / 2' here because subsequent text changes might combine
+ // with this text change, and overflow might occur if we just use
+ // INT32_MAX.
+ IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
+ notification.mTextChangeData.mStartOffset = 0;
+ notification.mTextChangeData.mRemovedEndOffset = INT32_MAX / 2;
+ notification.mTextChangeData.mAddedEndOffset = INT32_MAX / 2;
+ NotifyIME(mDispatcher, notification);
+
+ FlushIMEChanges(aFlags);
+}
+
+void GeckoEditableSupport::UpdateCompositionRects() {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ RefPtr<TextComposition> composition(GetComposition());
+ NS_ENSURE_TRUE_VOID(mDispatcher && widget);
+
+ if (!composition) {
+ return;
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ uint32_t offset = composition->NativeOffsetOfStartComposition();
+ WidgetQueryContentEvent queryTextRectsEvent(true, eQueryTextRectArray,
+ widget);
+ queryTextRectsEvent.InitForQueryTextRectArray(offset,
+ composition->String().Length());
+ widget->DispatchEvent(&queryTextRectsEvent, status);
+
+ auto rects = ConvertRectArrayToJavaRectFArray(
+ queryTextRectsEvent.Succeeded()
+ ? queryTextRectsEvent.mReply->mRectArray
+ : CopyableTArray<mozilla::LayoutDeviceIntRect>(),
+ widget->GetDefaultScale());
+
+ mEditable->UpdateCompositionRects(rects);
+}
+
+void GeckoEditableSupport::OnImeSynchronize() {
+ AutoGeckoEditableBlocker blocker(this);
+
+ if (mIMEDelaySynchronizeReply) {
+ // If we are waiting for other events to reply,
+ // queue this reply as well.
+ mIMEActiveSynchronizeCount++;
+ return;
+ }
+ if (!mIMEMaskEventsCount) {
+ FlushIMEChanges();
+ }
+ mEditable->NotifyIME(EditableListener::NOTIFY_IME_REPLY_EVENT);
+}
+
+void GeckoEditableSupport::OnImeReplaceText(int32_t aStart, int32_t aEnd,
+ jni::String::Param aText) {
+ AutoGeckoEditableBlocker blocker(this);
+
+ if (DoReplaceText(aStart, aEnd, aText)) {
+ mIMEDelaySynchronizeReply = true;
+ }
+
+ OnImeSynchronize();
+}
+
+bool GeckoEditableSupport::DoReplaceText(int32_t aStart, int32_t aEnd,
+ jni::String::Param aText) {
+ ALOGIME("IME: IME_REPLACE_TEXT: text=\"%s\"",
+ NS_ConvertUTF16toUTF8(aText->ToString()).get());
+
+ // Return true if processed and we should reply to the OnImeReplaceText
+ // event later. Return false if _not_ processed and we should reply to the
+ // OnImeReplaceText event now.
+
+ if (mIMEMaskEventsCount > 0) {
+ // Not focused; still reply to events, but don't do anything else.
+ return false;
+ }
+
+ if (nsWindow* window = GetNsWindow()) {
+ window->UserActivity();
+ }
+
+ /*
+ Replace text in Gecko thread from aStart to aEnd with the string text.
+ */
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ NS_ENSURE_TRUE(mDispatcher && widget, false);
+ NS_ENSURE_SUCCESS(BeginInputTransaction(mDispatcher), false);
+
+ RefPtr<TextComposition> composition(GetComposition());
+ MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
+
+ nsString string(aText->ToString());
+ const bool composing = !mIMERanges->IsEmpty();
+ nsEventStatus status = nsEventStatus_eIgnore;
+ bool textChanged = composing;
+ bool performDeletion = true;
+
+ if (!mIMEKeyEvents.IsEmpty() || !composition || !mDispatcher->IsComposing() ||
+ uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
+ uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
+ composition->String().Length()) {
+ // Only start a new composition if we have key events,
+ // if we don't have an existing composition, or
+ // the replaced text does not match our composition.
+ textChanged |= RemoveComposition();
+
+#ifdef NIGHTLY_BUILD
+ {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ widget);
+ widget->DispatchEvent(&querySelectedTextEvent, status);
+ if (querySelectedTextEvent.Succeeded()) {
+ ALOGIME(
+ "IME: Current selection: %s",
+ ToString(querySelectedTextEvent.mReply->mOffsetAndData).c_str());
+ }
+ }
+#endif
+
+ // If aStart or aEnd is negative value, we use current selection instead
+ // of updating the selection.
+ if (aStart >= 0 && aEnd >= 0) {
+ // Use text selection to set target position(s) for
+ // insert, or replace, of text.
+ WidgetSelectionEvent event(true, eSetSelection, widget);
+ event.mOffset = uint32_t(aStart);
+ event.mLength = uint32_t(aEnd - aStart);
+ event.mExpandToClusterBoundary = false;
+ event.mReason = nsISelectionListener::IME_REASON;
+ widget->DispatchEvent(&event, status);
+ }
+
+ if (!mIMEKeyEvents.IsEmpty()) {
+ bool ignoreNextKeyPress = false;
+ for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) {
+ const auto event = mIMEKeyEvents[i]->AsKeyboardEvent();
+ // widget for duplicated events is initially nullptr.
+ event->mWidget = widget;
+
+ status = nsEventStatus_eIgnore;
+ if (event->mMessage != eKeyPress) {
+ mDispatcher->DispatchKeyboardEvent(event->mMessage, *event, status);
+ // Skip default processing. It means that next key press shouldn't
+ // be dispatched.
+ ignoreNextKeyPress = event->mMessage == eKeyDown &&
+ status == nsEventStatus_eConsumeNoDefault;
+ } else {
+ if (ignoreNextKeyPress) {
+ // Don't dispatch key press since previous key down is consumed.
+ ignoreNextKeyPress = false;
+ continue;
+ }
+ mDispatcher->MaybeDispatchKeypressEvents(*event, status);
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ textChanged = true;
+ }
+ }
+ if (!mDispatcher || widget->Destroyed()) {
+ // Don't wait for any text change event.
+ textChanged = false;
+ break;
+ }
+ }
+ mIMEKeyEvents.Clear();
+ return textChanged;
+ }
+
+ if (aStart != aEnd) {
+ // Perform a deletion first.
+ performDeletion = true;
+ }
+ } else if (composition->String().Equals(string)) {
+ /* If the new text is the same as the existing composition text,
+ * the NS_COMPOSITION_CHANGE event does not generate a text
+ * change notification. However, the Java side still expects
+ * one, so we manually generate a notification. */
+ IMETextChange dummyChange;
+ dummyChange.mStart = aStart;
+ dummyChange.mOldEnd = dummyChange.mNewEnd = aEnd;
+ PostFlushIMEChanges();
+ mIMESelectionChanged = true;
+ AddIMETextChange(dummyChange);
+ textChanged = true;
+ }
+
+ if (StaticPrefs::
+ intl_ime_hack_on_any_apps_fire_key_events_for_composition() ||
+ mInputContext.mMayBeIMEUnaware) {
+ SendIMEDummyKeyEvent(widget, eKeyDown);
+ if (!mDispatcher || widget->Destroyed()) {
+ return false;
+ }
+ }
+
+ if (performDeletion) {
+ WidgetContentCommandEvent event(true, eContentCommandDelete, widget);
+ event.mTime = PR_Now() / 1000;
+ widget->DispatchEvent(&event, status);
+ if (!mDispatcher || widget->Destroyed()) {
+ return false;
+ }
+ textChanged = true;
+ }
+
+ if (composing) {
+ mDispatcher->SetPendingComposition(string, mIMERanges);
+ mDispatcher->FlushPendingComposition(status);
+ mIMEActiveCompositionCount++;
+ // Ensure IME ranges are empty.
+ mIMERanges->Clear();
+ } else if (!string.IsEmpty() || mDispatcher->IsComposing()) {
+ mDispatcher->CommitComposition(status, &string);
+ mIMEActiveCompositionCount++;
+ textChanged = true;
+ }
+ if (!mDispatcher || widget->Destroyed()) {
+ return false;
+ }
+
+ if (StaticPrefs::
+ intl_ime_hack_on_any_apps_fire_key_events_for_composition() ||
+ mInputContext.mMayBeIMEUnaware) {
+ SendIMEDummyKeyEvent(widget, eKeyUp);
+ // Widget may be destroyed after dispatching the above event.
+ }
+ return textChanged;
+}
+
+void GeckoEditableSupport::OnImeAddCompositionRange(
+ int32_t aStart, int32_t aEnd, int32_t aRangeType, int32_t aRangeStyle,
+ int32_t aRangeLineStyle, bool aRangeBoldLine, int32_t aRangeForeColor,
+ int32_t aRangeBackColor, int32_t aRangeLineColor) {
+ AutoGeckoEditableBlocker blocker(this);
+
+ if (mIMEMaskEventsCount > 0) {
+ // Not focused.
+ return;
+ }
+
+ TextRange range;
+ range.mStartOffset = aStart;
+ range.mEndOffset = aEnd;
+ range.mRangeType = ToTextRangeType(aRangeType);
+ range.mRangeStyle.mDefinedStyles = aRangeStyle;
+ range.mRangeStyle.mLineStyle = TextRangeStyle::ToLineStyle(aRangeLineStyle);
+ range.mRangeStyle.mIsBoldLine = aRangeBoldLine;
+ range.mRangeStyle.mForegroundColor =
+ ConvertAndroidColor(uint32_t(aRangeForeColor));
+ range.mRangeStyle.mBackgroundColor =
+ ConvertAndroidColor(uint32_t(aRangeBackColor));
+ range.mRangeStyle.mUnderlineColor =
+ ConvertAndroidColor(uint32_t(aRangeLineColor));
+ mIMERanges->AppendElement(range);
+}
+
+void GeckoEditableSupport::OnImeUpdateComposition(int32_t aStart, int32_t aEnd,
+ int32_t aFlags) {
+ AutoGeckoEditableBlocker blocker(this);
+
+ if (DoUpdateComposition(aStart, aEnd, aFlags)) {
+ mIMEDelaySynchronizeReply = true;
+ }
+}
+
+bool GeckoEditableSupport::DoUpdateComposition(int32_t aStart, int32_t aEnd,
+ int32_t aFlags) {
+ if (mIMEMaskEventsCount > 0) {
+ // Not focused.
+ return false;
+ }
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ nsEventStatus status = nsEventStatus_eIgnore;
+ NS_ENSURE_TRUE(mDispatcher && widget, false);
+
+ const bool keepCurrent =
+ !!(aFlags & java::GeckoEditableChild::FLAG_KEEP_CURRENT_COMPOSITION);
+
+ // A composition with no ranges means we want to set the selection.
+ if (mIMERanges->IsEmpty()) {
+ if (keepCurrent && mDispatcher->IsComposing()) {
+ // Don't set selection if we want to keep current composition.
+ return false;
+ }
+
+ MOZ_ASSERT(aStart >= 0 && aEnd >= 0);
+ const bool compositionChanged = RemoveComposition();
+
+ WidgetSelectionEvent selEvent(true, eSetSelection, widget);
+ selEvent.mOffset = std::min(aStart, aEnd);
+ selEvent.mLength = std::max(aStart, aEnd) - selEvent.mOffset;
+ selEvent.mReversed = aStart > aEnd;
+ selEvent.mExpandToClusterBoundary = false;
+ widget->DispatchEvent(&selEvent, status);
+ return compositionChanged;
+ }
+
+ /**
+ * Update the composition from aStart to aEnd using information from added
+ * ranges. This is only used for visual indication and does not affect the
+ * text content. Only the offsets are specified and not the text content
+ * to eliminate the possibility of this event altering the text content
+ * unintentionally.
+ */
+ nsString string;
+ RefPtr<TextComposition> composition(GetComposition());
+ MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
+
+ if (!composition || !mDispatcher->IsComposing() ||
+ uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
+ uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
+ composition->String().Length()) {
+ if (keepCurrent) {
+ // Don't start a new composition if we want to keep the current one.
+ mIMERanges->Clear();
+ return false;
+ }
+
+ // Only start new composition if we don't have an existing one,
+ // or if the existing composition doesn't match the new one.
+ RemoveComposition();
+
+ {
+ WidgetSelectionEvent event(true, eSetSelection, widget);
+ event.mOffset = uint32_t(aStart);
+ event.mLength = uint32_t(aEnd - aStart);
+ event.mExpandToClusterBoundary = false;
+ event.mReason = nsISelectionListener::IME_REASON;
+ widget->DispatchEvent(&event, status);
+ }
+
+ {
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ widget);
+ widget->DispatchEvent(&querySelectedTextEvent, status);
+ MOZ_ASSERT(querySelectedTextEvent.Succeeded());
+ if (querySelectedTextEvent.FoundSelection()) {
+ string = querySelectedTextEvent.mReply->DataRef();
+ }
+ }
+ } else {
+ // If the new composition matches the existing composition,
+ // reuse the old composition.
+ string = composition->String();
+ }
+
+ ALOGIME("IME: IME_SET_TEXT: text=\"%s\", length=%u, range=%zu",
+ NS_ConvertUTF16toUTF8(string).get(), string.Length(),
+ mIMERanges->Length());
+
+ if (NS_WARN_IF(NS_FAILED(BeginInputTransaction(mDispatcher)))) {
+ mIMERanges->Clear();
+ return false;
+ }
+ mDispatcher->SetPendingComposition(string, mIMERanges);
+ mDispatcher->FlushPendingComposition(status);
+ mIMEActiveCompositionCount++;
+ mIMERanges->Clear();
+ return true;
+}
+
+void GeckoEditableSupport::OnImeRequestCursorUpdates(int aRequestMode) {
+ AutoGeckoEditableBlocker blocker(this);
+
+ if (aRequestMode == EditableClient::ONE_SHOT) {
+ UpdateCompositionRects();
+ return;
+ }
+
+ mIMEMonitorCursor = (aRequestMode == EditableClient::START_MONITOR);
+}
+
+class MOZ_STACK_CLASS AutoSelectionRestore final {
+ public:
+ explicit AutoSelectionRestore(nsIWidget* widget,
+ TextEventDispatcher* dispatcher)
+ : mWidget(widget), mDispatcher(dispatcher) {
+ MOZ_ASSERT(widget);
+ if (!dispatcher || !dispatcher->IsComposing()) {
+ mOffset = UINT32_MAX;
+ mLength = UINT32_MAX;
+ return;
+ }
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ widget);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ widget->DispatchEvent(&querySelectedTextEvent, status);
+ if (querySelectedTextEvent.DidNotFindSelection()) {
+ mOffset = UINT32_MAX;
+ mLength = UINT32_MAX;
+ return;
+ }
+
+ mOffset = querySelectedTextEvent.mReply->StartOffset();
+ mLength = querySelectedTextEvent.mReply->DataLength();
+ }
+
+ ~AutoSelectionRestore() {
+ if (mWidget->Destroyed() || mOffset == UINT32_MAX) {
+ return;
+ }
+
+ WidgetSelectionEvent selection(true, eSetSelection, mWidget);
+ selection.mOffset = mOffset;
+ selection.mLength = mLength;
+ selection.mExpandToClusterBoundary = false;
+ selection.mReason = nsISelectionListener::IME_REASON;
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mWidget->DispatchEvent(&selection, status);
+ }
+
+ private:
+ nsCOMPtr<nsIWidget> mWidget;
+ RefPtr<TextEventDispatcher> mDispatcher;
+ uint32_t mOffset;
+ uint32_t mLength;
+};
+
+void GeckoEditableSupport::OnImeRequestCommit() {
+ AutoGeckoEditableBlocker blocker(this);
+
+ if (mIMEMaskEventsCount > 0) {
+ // Not focused.
+ return;
+ }
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (NS_WARN_IF(!widget)) {
+ return;
+ }
+
+ AutoSelectionRestore restore(widget, mDispatcher);
+
+ RemoveComposition(COMMIT_IME_COMPOSITION);
+}
+
+void GeckoEditableSupport::AsyncNotifyIME(int32_t aNotification) {
+ RefPtr<GeckoEditableSupport> self(this);
+
+ nsAppShell::PostEvent([this, self, aNotification] {
+ if (!mIMEMaskEventsCount) {
+ mEditable->NotifyIME(aNotification);
+ }
+ });
+}
+
+nsresult GeckoEditableSupport::NotifyIME(
+ TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) {
+ MOZ_ASSERT(mEditable);
+
+ switch (aNotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION: {
+ ALOGIME("IME: REQUEST_TO_COMMIT_COMPOSITION");
+
+ RemoveComposition(COMMIT_IME_COMPOSITION);
+ AsyncNotifyIME(EditableListener::NOTIFY_IME_TO_COMMIT_COMPOSITION);
+ break;
+ }
+
+ case REQUEST_TO_CANCEL_COMPOSITION: {
+ ALOGIME("IME: REQUEST_TO_CANCEL_COMPOSITION");
+
+ RemoveComposition(CANCEL_IME_COMPOSITION);
+ AsyncNotifyIME(EditableListener::NOTIFY_IME_TO_CANCEL_COMPOSITION);
+ break;
+ }
+
+ case NOTIFY_IME_OF_FOCUS: {
+ ALOGIME("IME: NOTIFY_IME_OF_FOCUS");
+
+ mIMEFocusCount++;
+
+ RefPtr<GeckoEditableSupport> self(this);
+ RefPtr<TextEventDispatcher> dispatcher = aTextEventDispatcher;
+
+ // Post an event because we have to flush the text before sending a
+ // focus event, and we may not be able to flush text during the
+ // NotifyIME call.
+ nsAppShell::PostEvent([this, self, dispatcher] {
+ nsCOMPtr<nsIWidget> widget = dispatcher->GetWidget();
+
+ --mIMEMaskEventsCount;
+ if (!mIMEFocusCount || !widget || widget->Destroyed()) {
+ return;
+ }
+
+ mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_TOKEN);
+
+ if (mIsRemote) {
+ if (!mEditableAttached) {
+ // Re-attach on focus; see OnRemovedFrom().
+ jni::NativeWeakPtrHolder<GeckoEditableSupport>::AttachExisting(
+ mEditable, do_AddRef(this));
+ mEditableAttached = true;
+ }
+ // Because GeckoEditableSupport in content process doesn't
+ // manage the active input context, we need to retrieve the
+ // input context from the widget, for use by
+ // OnImeReplaceText.
+ mInputContext = widget->GetInputContext();
+ }
+ mDispatcher = dispatcher;
+ mIMEKeyEvents.Clear();
+
+ mIMEDelaySynchronizeReply = false;
+ mIMEActiveCompositionCount = 0;
+ FlushIMEText();
+
+ // IME will call requestCursorUpdates after getting context.
+ // So reset cursor update mode before getting context.
+ mIMEMonitorCursor = false;
+
+ mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_FOCUS);
+ });
+ break;
+ }
+
+ case NOTIFY_IME_OF_BLUR: {
+ ALOGIME("IME: NOTIFY_IME_OF_BLUR");
+
+ mIMEFocusCount--;
+ MOZ_ASSERT(mIMEFocusCount >= 0);
+
+ RefPtr<GeckoEditableSupport> self(this);
+ nsAppShell::PostEvent([this, self] {
+ if (!mIMEFocusCount) {
+ mIMEDelaySynchronizeReply = false;
+ mIMEActiveSynchronizeCount = 0;
+ mIMEActiveCompositionCount = 0;
+ mInputContext.ShutDown();
+ mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_BLUR);
+ OnRemovedFrom(mDispatcher);
+ }
+ });
+
+ // Mask events because we lost focus. Unmask on the next focus.
+ mIMEMaskEventsCount++;
+ break;
+ }
+
+ case NOTIFY_IME_OF_SELECTION_CHANGE: {
+ ALOGIME("IME: NOTIFY_IME_OF_SELECTION_CHANGE: SelectionChangeData=%s",
+ ToString(aNotification.mSelectionChangeData).c_str());
+
+ PostFlushIMEChanges();
+ mIMESelectionChanged = true;
+ break;
+ }
+
+ case NOTIFY_IME_OF_TEXT_CHANGE: {
+ ALOGIME("IME: NOTIFY_IME_OF_TEXT_CHANGE: TextChangeData=%s",
+ ToString(aNotification.mTextChangeData).c_str());
+
+ /* Make sure Java's selection is up-to-date */
+ PostFlushIMEChanges();
+ mIMESelectionChanged = true;
+ AddIMETextChange(IMETextChange(aNotification));
+ break;
+ }
+
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: {
+ ALOGIME("IME: NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED");
+
+ // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED isn't sent per IME call.
+ // Receiving this event means that Gecko has already handled all IME
+ // composing events in queue.
+ //
+ if (mIsRemote) {
+ OnNotifyIMEOfCompositionEventHandled();
+ } else {
+ // Also, when receiving this event, mIMEDelaySynchronizeReply won't
+ // update yet on non-e10s case since IME event is posted before updating
+ // it. So we have to delay handling of this event.
+ RefPtr<GeckoEditableSupport> self(this);
+ nsAppShell::PostEvent(
+ [this, self] { OnNotifyIMEOfCompositionEventHandled(); });
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ return NS_OK;
+}
+
+void GeckoEditableSupport::OnNotifyIMEOfCompositionEventHandled() {
+ // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED may be merged with multiple events,
+ // so reset count.
+ mIMEActiveCompositionCount = 0;
+ if (mIMEDelaySynchronizeReply) {
+ FlushIMEChanges();
+ }
+
+ // Hardware keyboard support requires each string rect.
+ if (mIMEMonitorCursor) {
+ UpdateCompositionRects();
+ }
+}
+
+void GeckoEditableSupport::OnRemovedFrom(
+ TextEventDispatcher* aTextEventDispatcher) {
+ mDispatcher = nullptr;
+
+ if (mIsRemote && mEditable->HasEditableParent()) {
+ // When we're remote, detach every time.
+ OnWeakNonIntrusiveDetach(NS_NewRunnableFunction(
+ "GeckoEditableSupport::OnRemovedFrom",
+ [editable = java::GeckoEditableChild::GlobalRef(mEditable)] {
+ DisposeNative(editable);
+ }));
+ }
+}
+
+void GeckoEditableSupport::WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
+ void* aData) {}
+
+NS_IMETHODIMP_(IMENotificationRequests)
+GeckoEditableSupport::GetIMENotificationRequests() {
+ return IMENotificationRequests(IMENotificationRequests::NOTIFY_TEXT_CHANGE);
+}
+
+void GeckoEditableSupport::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) {
+ // SetInputContext is called from chrome process only
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(mEditable);
+
+ ALOGIME(
+ "IME: SetInputContext: aContext=%s, "
+ "aAction={mCause=%s, mFocusChange=%s}",
+ ToString(aContext).c_str(), ToString(aAction.mCause).c_str(),
+ ToString(aAction.mFocusChange).c_str());
+
+ mInputContext = aContext;
+
+ if (mInputContext.mIMEState.mEnabled != IMEEnabled::Disabled &&
+ !mInputContext.mHTMLInputInputmode.EqualsLiteral("none") &&
+ aAction.UserMightRequestOpenVKB()) {
+ // Don't reset keyboard when we should simply open the vkb
+ mEditable->NotifyIME(EditableListener::NOTIFY_IME_OPEN_VKB);
+ return;
+ }
+
+ // Post an event to keep calls in order relative to NotifyIME.
+ nsAppShell::PostEvent([this, self = RefPtr<GeckoEditableSupport>(this),
+ context = mInputContext, action = aAction] {
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+
+ if (!widget || widget->Destroyed()) {
+ return;
+ }
+ NotifyIMEContext(context, action);
+ });
+}
+
+void GeckoEditableSupport::NotifyIMEContext(const InputContext& aContext,
+ const InputContextAction& aAction) {
+ const bool inPrivateBrowsing = aContext.mInPrivateBrowsing;
+ // isUserAction is used whether opening virtual keyboard. But long press
+ // shouldn't open it.
+ const bool isUserAction =
+ aAction.mCause != InputContextAction::CAUSE_LONGPRESS &&
+ !(aAction.mCause == InputContextAction::CAUSE_UNKNOWN_CHROME &&
+ aContext.mIMEState.mEnabled == IMEEnabled::Enabled) &&
+ (aAction.IsHandlingUserInput() || aContext.mHasHandledUserInput);
+ const int32_t flags =
+ (inPrivateBrowsing ? EditableListener::IME_FLAG_PRIVATE_BROWSING : 0) |
+ (isUserAction ? EditableListener::IME_FLAG_USER_ACTION : 0);
+
+ mEditable->NotifyIMEContext(
+ static_cast<int32_t>(aContext.mIMEState.mEnabled),
+ aContext.mHTMLInputType, aContext.mHTMLInputInputmode,
+ aContext.mActionHint, aContext.mAutocapitalize, flags);
+}
+
+InputContext GeckoEditableSupport::GetInputContext() {
+ // GetInputContext is called from chrome process only
+ MOZ_ASSERT(XRE_IsParentProcess());
+ InputContext context = mInputContext;
+ context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
+ return context;
+}
+
+void GeckoEditableSupport::TransferParent(jni::Object::Param aEditableParent) {
+ AutoGeckoEditableBlocker blocker(this);
+
+ mEditable->SetParent(aEditableParent);
+
+ // If we are already focused, make sure the new parent has our token
+ // and focus information, so it can accept additional calls from us.
+ if (mIMEFocusCount > 0) {
+ mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_TOKEN);
+ if (mIsRemote) {
+ // GeckoEditableSupport::SetInputContext is called on chrome process
+ // only, so mInputContext may be still invalid since it is set after
+ // we have gotton focus.
+ RefPtr<GeckoEditableSupport> self(this);
+ nsAppShell::PostEvent([self = std::move(self)] {
+ NS_WARNING_ASSERTION(
+ self->mDispatcher,
+ "Text dispatcher is still null. Why don't we get focus yet?");
+ self->NotifyIMEContext(self->mInputContext, InputContextAction());
+ });
+ } else {
+ NotifyIMEContext(mInputContext, InputContextAction());
+ }
+ mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_FOCUS);
+ // We have focus, so don't destroy editable child.
+ return;
+ }
+
+ if (mIsRemote && !mDispatcher) {
+ // Detach now if we were only attached temporarily.
+ OnRemovedFrom(/* dispatcher */ nullptr);
+ }
+}
+
+void GeckoEditableSupport::SetOnBrowserChild(dom::BrowserChild* aBrowserChild) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ NS_ENSURE_TRUE_VOID(aBrowserChild);
+
+ const dom::ContentChild* const contentChild =
+ dom::ContentChild::GetSingleton();
+ RefPtr<widget::PuppetWidget> widget(aBrowserChild->WebWidget());
+ NS_ENSURE_TRUE_VOID(contentChild && widget);
+
+ // Get the content/tab ID in order to get the correct
+ // IGeckoEditableParent object, which GeckoEditableChild uses to
+ // communicate with the parent process.
+ const uint64_t contentId = contentChild->GetID();
+ const uint64_t tabId = aBrowserChild->GetTabId();
+ NS_ENSURE_TRUE_VOID(contentId && tabId);
+
+ RefPtr<widget::TextEventDispatcherListener> listener =
+ widget->GetNativeTextEventDispatcherListener();
+
+ if (!listener ||
+ listener.get() ==
+ static_cast<widget::TextEventDispatcherListener*>(widget)) {
+ // We need to set a new listener.
+ const auto editableChild = java::GeckoEditableChild::New(
+ /* parent */ nullptr, /* default */ false);
+
+ // Temporarily attach so we can receive the initial editable parent.
+ auto editableSupport =
+ jni::NativeWeakPtrHolder<GeckoEditableSupport>::Attach(editableChild,
+ editableChild);
+ auto accEditableSupport(editableSupport.Access());
+ MOZ_RELEASE_ASSERT(accEditableSupport);
+
+ // Tell PuppetWidget to use our listener for IME operations.
+ widget->SetNativeTextEventDispatcherListener(
+ accEditableSupport.AsRefPtr().get());
+
+ accEditableSupport->mEditableAttached = true;
+
+ // Connect the new child to a parent that corresponds to the BrowserChild.
+ java::GeckoServiceChildProcess::GetEditableParent(editableChild, contentId,
+ tabId);
+ return;
+ }
+
+ // We need to update the existing listener to use the new parent.
+
+ // We expect the existing TextEventDispatcherListener to be a
+ // GeckoEditableSupport object, so we perform a sanity check to make
+ // sure, by comparing their respective vtable pointers.
+ const RefPtr<widget::GeckoEditableSupport> dummy =
+ new widget::GeckoEditableSupport(/* child */ nullptr);
+ NS_ENSURE_TRUE_VOID(*reinterpret_cast<const uintptr_t*>(listener.get()) ==
+ *reinterpret_cast<const uintptr_t*>(dummy.get()));
+
+ const auto support =
+ static_cast<widget::GeckoEditableSupport*>(listener.get());
+ if (!support->mEditableAttached) {
+ // Temporarily attach so we can receive the initial editable parent.
+ jni::NativeWeakPtrHolder<GeckoEditableSupport>::AttachExisting(
+ support->GetJavaEditable(), do_AddRef(support));
+ support->mEditableAttached = true;
+ }
+
+ // Transfer to a new parent that corresponds to the BrowserChild.
+ java::GeckoServiceChildProcess::GetEditableParent(support->GetJavaEditable(),
+ contentId, tabId);
+}
+
+nsIWidget* GeckoEditableSupport::GetWidget() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mDispatcher ? mDispatcher->GetWidget() : GetNsWindow();
+}
+
+nsWindow* GeckoEditableSupport::GetNsWindow() const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto acc(mWindow.Access());
+ if (!acc) {
+ return nullptr;
+ }
+
+ return acc->GetNsWindow();
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/android/GeckoEditableSupport.h b/widget/android/GeckoEditableSupport.h
new file mode 100644
index 0000000000..5e8b445803
--- /dev/null
+++ b/widget/android/GeckoEditableSupport.h
@@ -0,0 +1,287 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 mozilla_widget_GeckoEditableSupport_h
+#define mozilla_widget_GeckoEditableSupport_h
+
+#include "nsAppShell.h"
+#include "nsIWidget.h"
+#include "nsTArray.h"
+
+#include "mozilla/java/GeckoEditableChildNatives.h"
+#include "mozilla/java/SessionTextInputWrappers.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "mozilla/UniquePtr.h"
+
+class nsWindow;
+
+namespace mozilla {
+
+class TextComposition;
+
+namespace dom {
+class BrowserChild;
+}
+
+namespace widget {
+
+class GeckoEditableSupport final
+ : public TextEventDispatcherListener,
+ public java::GeckoEditableChild::Natives<GeckoEditableSupport> {
+ /*
+ Rules for managing IME between Gecko and Java:
+
+ * Gecko controls the text content, and Java shadows the Gecko text
+ through text updates
+ * Gecko and Java maintain separate selections, and synchronize when
+ needed through selection updates and set-selection events
+ * Java controls the composition, and Gecko shadows the Java
+ composition through update composition events
+ */
+
+ using EditableBase = java::GeckoEditableChild::Natives<GeckoEditableSupport>;
+ using EditableClient = java::SessionTextInput::EditableClient;
+ using EditableListener = java::SessionTextInput::EditableListener;
+
+ struct IMETextChange final {
+ int32_t mStart, mOldEnd, mNewEnd;
+
+ IMETextChange() : mStart(-1), mOldEnd(-1), mNewEnd(-1) {}
+
+ explicit IMETextChange(const IMENotification& aIMENotification)
+ : mStart(aIMENotification.mTextChangeData.mStartOffset),
+ mOldEnd(aIMENotification.mTextChangeData.mRemovedEndOffset),
+ mNewEnd(aIMENotification.mTextChangeData.mAddedEndOffset) {
+ MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE,
+ "IMETextChange initialized with wrong notification");
+ MOZ_ASSERT(aIMENotification.mTextChangeData.IsValid(),
+ "The text change notification isn't initialized");
+ MOZ_ASSERT(aIMENotification.mTextChangeData.IsInInt32Range(),
+ "The text change notification is out of range");
+ }
+
+ bool IsEmpty() const { return mStart < 0; }
+ };
+
+ enum FlushChangesFlag {
+ // Not retrying.
+ FLUSH_FLAG_NONE,
+ // Retrying due to IME text changes during flush.
+ FLUSH_FLAG_RETRY,
+ // Retrying due to IME sync exceptions during flush.
+ FLUSH_FLAG_RECOVER
+ };
+
+ enum RemoveCompositionFlag { CANCEL_IME_COMPOSITION, COMMIT_IME_COMPOSITION };
+
+ const bool mIsRemote;
+ jni::NativeWeakPtr<GeckoViewSupport> mWindow; // Parent only
+ RefPtr<TextEventDispatcher> mDispatcher;
+ java::GeckoEditableChild::GlobalRef mEditable;
+ bool mEditableAttached;
+ InputContext mInputContext;
+ AutoTArray<UniquePtr<mozilla::WidgetEvent>, 4> mIMEKeyEvents;
+ AutoTArray<IMETextChange, 4> mIMETextChanges;
+ RefPtr<TextRangeArray> mIMERanges;
+ RefPtr<Runnable> mDisposeRunnable;
+ int32_t mIMEMaskEventsCount; // Mask events when > 0.
+ int32_t mIMEFocusCount; // We are focused when > 0.
+ bool mIMEDelaySynchronizeReply; // We reply asynchronously when true.
+ int32_t mIMEActiveSynchronizeCount; // The number of replies being delayed.
+ int32_t mIMEActiveCompositionCount; // The number of compositions expected.
+ uint32_t mDisposeBlockCount;
+ bool mIMESelectionChanged;
+ bool mIMETextChangedDuringFlush;
+ bool mIMEMonitorCursor;
+
+ nsIWidget* GetWidget() const;
+ nsWindow* GetNsWindow() const;
+
+ nsresult BeginInputTransaction(TextEventDispatcher* aDispatcher) {
+ if (mIsRemote) {
+ return aDispatcher->BeginInputTransaction(this);
+ } else {
+ return aDispatcher->BeginNativeInputTransaction();
+ }
+ }
+
+ virtual ~GeckoEditableSupport() {}
+
+ RefPtr<TextComposition> GetComposition() const;
+ bool RemoveComposition(RemoveCompositionFlag aFlag = COMMIT_IME_COMPOSITION);
+ void SendIMEDummyKeyEvent(nsIWidget* aWidget, EventMessage msg);
+ void AddIMETextChange(const IMETextChange& aChange);
+ void PostFlushIMEChanges();
+ void FlushIMEChanges(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
+ void FlushIMEText(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
+ void AsyncNotifyIME(int32_t aNotification);
+ void UpdateCompositionRects();
+ bool DoReplaceText(int32_t aStart, int32_t aEnd, jni::String::Param aText);
+ bool DoUpdateComposition(int32_t aStart, int32_t aEnd, int32_t aFlags);
+ void OnNotifyIMEOfCompositionEventHandled();
+ void NotifyIMEContext(const InputContext& aContext,
+ const InputContextAction& aAction);
+
+ public:
+ template <typename Functor>
+ static void OnNativeCall(Functor&& aCall) {
+ struct IMEEvent : nsAppShell::LambdaEvent<Functor> {
+ explicit IMEEvent(Functor&& l)
+ : nsAppShell::LambdaEvent<Functor>(std::move(l)) {}
+
+ bool IsUIEvent() const override {
+ using GES = GeckoEditableSupport;
+ if (this->lambda.IsTarget(&GES::OnKeyEvent) ||
+ this->lambda.IsTarget(&GES::OnImeReplaceText) ||
+ this->lambda.IsTarget(&GES::OnImeUpdateComposition)) {
+ return true;
+ }
+ return false;
+ }
+
+ void Run() override {
+ if (NS_WARN_IF(!this->lambda.GetNativeObject())) {
+ // Ignore stale calls after disposal.
+ jni::GetGeckoThreadEnv()->ExceptionClear();
+ return;
+ }
+ nsAppShell::LambdaEvent<Functor>::Run();
+ }
+ };
+ nsAppShell::PostEvent(mozilla::MakeUnique<IMEEvent>(std::move(aCall)));
+ }
+
+ static void SetOnBrowserChild(dom::BrowserChild* aBrowserChild);
+
+ // Constructor for main process GeckoEditableChild.
+ GeckoEditableSupport(jni::NativeWeakPtr<GeckoViewSupport> aWindow,
+ java::GeckoEditableChild::Param aEditableChild)
+ : mIsRemote(!aWindow.IsAttached()),
+ mWindow(aWindow),
+ mEditable(aEditableChild),
+ mEditableAttached(!mIsRemote),
+ mIMERanges(new TextRangeArray()),
+ mIMEMaskEventsCount(1), // Mask IME events since there's no focus yet
+ mIMEFocusCount(0),
+ mIMEDelaySynchronizeReply(false),
+ mIMEActiveSynchronizeCount(0),
+ mDisposeBlockCount(0),
+ mIMESelectionChanged(false),
+ mIMETextChangedDuringFlush(false),
+ mIMEMonitorCursor(false) {}
+
+ // Constructor for content process GeckoEditableChild.
+ explicit GeckoEditableSupport(java::GeckoEditableChild::Param aEditableChild)
+ : GeckoEditableSupport(nullptr, aEditableChild) {}
+
+ NS_DECL_ISUPPORTS
+
+ // TextEventDispatcherListener methods
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+
+ NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;
+
+ NS_IMETHOD_(void)
+ OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override;
+
+ NS_IMETHOD_(void)
+ WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress, void* aData) override;
+
+ void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction);
+
+ InputContext GetInputContext();
+
+ bool HasIMEFocus() const { return mIMEFocusCount != 0; }
+
+ void AddBlocker() { mDisposeBlockCount++; }
+
+ void ReleaseBlocker() {
+ mDisposeBlockCount--;
+
+ if (!mDisposeBlockCount && mDisposeRunnable) {
+ if (HasIMEFocus()) {
+ // If we have IME focus, GeckoEditableChild is already attached again.
+ // So disposer is unnecessary.
+ mDisposeRunnable = nullptr;
+ return;
+ }
+
+ RefPtr<GeckoEditableSupport> self(this);
+ RefPtr<Runnable> disposer = std::move(mDisposeRunnable);
+
+ nsAppShell::PostEvent(
+ [self = std::move(self), disposer = std::move(disposer)] {
+ self->mEditableAttached = false;
+ disposer->Run();
+ });
+ }
+ }
+
+ bool IsGeckoEditableUsed() const { return mDisposeBlockCount != 0; }
+
+ // GeckoEditableChild methods
+ using EditableBase::AttachNative;
+ using EditableBase::DisposeNative;
+
+ const java::GeckoEditableChild::Ref& GetJavaEditable() { return mEditable; }
+
+ void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer) {
+ RefPtr<GeckoEditableSupport> self(this);
+ nsAppShell::PostEvent(
+ [self = std::move(self), disposer = RefPtr<Runnable>(aDisposer)] {
+ if (self->IsGeckoEditableUsed()) {
+ // Current calling stack uses GeckoEditableChild, so we should
+ // not dispose it now.
+ self->mDisposeRunnable = disposer;
+ return;
+ }
+ self->mEditableAttached = false;
+ disposer->Run();
+ });
+ }
+
+ // Transfer to a new parent.
+ void TransferParent(jni::Object::Param aEditableParent);
+
+ // Handle an Android KeyEvent.
+ void OnKeyEvent(int32_t aAction, int32_t aKeyCode, int32_t aScanCode,
+ int32_t aMetaState, int32_t aKeyPressMetaState, int64_t aTime,
+ int32_t aDomPrintableKeyValue, int32_t aRepeatCount,
+ int32_t aFlags, bool aIsSynthesizedImeKey,
+ jni::Object::Param originalEvent);
+
+ // Synchronize Gecko thread with the InputConnection thread.
+ void OnImeSynchronize();
+
+ // Replace a range of text with new text.
+ void OnImeReplaceText(int32_t aStart, int32_t aEnd, jni::String::Param aText);
+
+ // Add styling for a range within the active composition.
+ void OnImeAddCompositionRange(int32_t aStart, int32_t aEnd,
+ int32_t aRangeType, int32_t aRangeStyle,
+ int32_t aRangeLineStyle, bool aRangeBoldLine,
+ int32_t aRangeForeColor,
+ int32_t aRangeBackColor,
+ int32_t aRangeLineColor);
+
+ // Update styling for the active composition using previous-added ranges.
+ void OnImeUpdateComposition(int32_t aStart, int32_t aEnd, int32_t aFlags);
+
+ // Set cursor mode whether IME requests
+ void OnImeRequestCursorUpdates(int aRequestMode);
+
+ // Commit current composition to sync Gecko text state with Java.
+ void OnImeRequestCommit();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_GeckoEditableSupport_h
diff --git a/widget/android/GeckoNetworkManager.h b/widget/android/GeckoNetworkManager.h
new file mode 100644
index 0000000000..7240821b9e
--- /dev/null
+++ b/widget/android/GeckoNetworkManager.h
@@ -0,0 +1,45 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 GeckoNetworkManager_h
+#define GeckoNetworkManager_h
+
+#include "nsAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsINetworkLinkService.h"
+
+#include "mozilla/java/GeckoNetworkManagerNatives.h"
+#include "mozilla/Services.h"
+
+namespace mozilla {
+
+class GeckoNetworkManager final
+ : public java::GeckoNetworkManager::Natives<GeckoNetworkManager> {
+ GeckoNetworkManager() = delete;
+
+ public:
+ static void OnConnectionChanged(int32_t aType, jni::String::Param aSubType,
+ bool aIsWifi, int32_t aGateway) {
+ hal::NotifyNetworkChange(hal::NetworkInformation(aType, aIsWifi, aGateway));
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(nullptr, NS_NETWORK_LINK_TYPE_TOPIC,
+ aSubType->ToString().get());
+ }
+ }
+
+ static void OnStatusChanged(jni::String::Param aStatus) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC,
+ aStatus->ToString().get());
+ }
+ }
+};
+
+} // namespace mozilla
+
+#endif // GeckoNetworkManager_h
diff --git a/widget/android/GeckoProcessManager.cpp b/widget/android/GeckoProcessManager.cpp
new file mode 100644
index 0000000000..274e92ed9b
--- /dev/null
+++ b/widget/android/GeckoProcessManager.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "GeckoProcessManager.h"
+
+#include "nsINetworkLinkService.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/Services.h"
+
+namespace mozilla {
+
+/* static */ void GeckoProcessManager::Init() {
+ BaseNatives::Init();
+ ConnectionManager::Init();
+}
+
+NS_IMPL_ISUPPORTS(GeckoProcessManager::ConnectionManager, nsIObserver)
+
+NS_IMETHODIMP GeckoProcessManager::ConnectionManager::Observe(
+ nsISupports* aSubject, const char* aTopic, const char16_t* aData) {
+ java::GeckoProcessManager::ConnectionManager::LocalRef connMgr(mJavaConnMgr);
+ if (!connMgr) {
+ return NS_OK;
+ }
+
+ if (!strcmp("application-foreground", aTopic)) {
+ connMgr->OnForeground();
+ return NS_OK;
+ }
+
+ if (!strcmp("application-background", aTopic)) {
+ connMgr->OnBackground();
+ return NS_OK;
+ }
+
+ if (!strcmp(NS_NETWORK_LINK_TOPIC, aTopic)) {
+ const nsDependentString state(aData);
+ // state can be up, down, or unknown. For the purposes of socket process
+ // prioritization, we treat unknown as being up.
+ const bool isUp = !state.EqualsLiteral(NS_NETWORK_LINK_DATA_DOWN);
+ connMgr->OnNetworkStateChange(isUp);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+/* static */ void GeckoProcessManager::ConnectionManager::AttachTo(
+ java::GeckoProcessManager::ConnectionManager::Param aInstance) {
+ RefPtr<ConnectionManager> native(new ConnectionManager());
+ BaseNatives::AttachNative(aInstance, native);
+
+ native->mJavaConnMgr = aInstance;
+
+ nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService());
+ obsServ->AddObserver(native, "application-background", false);
+ obsServ->AddObserver(native, "application-foreground", false);
+}
+
+void GeckoProcessManager::ConnectionManager::ObserveNetworkNotifications() {
+ nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService());
+ obsServ->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+
+ const bool isUp = java::GeckoAppShell::IsNetworkLinkUp();
+
+ java::GeckoProcessManager::ConnectionManager::LocalRef connMgr(mJavaConnMgr);
+ if (!connMgr) {
+ return;
+ }
+
+ connMgr->OnNetworkStateChange(isUp);
+}
+
+} // namespace mozilla
diff --git a/widget/android/GeckoProcessManager.h b/widget/android/GeckoProcessManager.h
new file mode 100644
index 0000000000..341cb5f5c5
--- /dev/null
+++ b/widget/android/GeckoProcessManager.h
@@ -0,0 +1,84 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 GeckoProcessManager_h
+#define GeckoProcessManager_h
+
+#include "WidgetUtils.h"
+#include "nsAppShell.h"
+#include "nsContentUtils.h"
+#include "nsIObserver.h"
+#include "nsWindow.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/ContentProcessManager.h"
+#include "mozilla/java/GeckoProcessManagerNatives.h"
+
+namespace mozilla {
+
+class GeckoProcessManager final
+ : public java::GeckoProcessManager::Natives<GeckoProcessManager> {
+ using BaseNatives = java::GeckoProcessManager::Natives<GeckoProcessManager>;
+
+ GeckoProcessManager() = delete;
+
+ static already_AddRefed<nsIWidget> GetWidget(int64_t aContentId,
+ int64_t aTabId) {
+ using namespace dom;
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ContentProcessManager* const cpm = ContentProcessManager::GetSingleton();
+ NS_ENSURE_TRUE(cpm, nullptr);
+
+ RefPtr<BrowserParent> tab = cpm->GetTopLevelBrowserParentByProcessAndTabId(
+ ContentParentId(aContentId), TabId(aTabId));
+ NS_ENSURE_TRUE(tab, nullptr);
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWin = tab->GetParentWindowOuter();
+ NS_ENSURE_TRUE(domWin, nullptr);
+
+ return widget::WidgetUtils::DOMWindowToWidget(domWin);
+ }
+
+ class ConnectionManager final
+ : public java::GeckoProcessManager::ConnectionManager::Natives<
+ ConnectionManager>,
+ public nsIObserver {
+ using BaseNatives = java::GeckoProcessManager::ConnectionManager::Natives<
+ ConnectionManager>;
+
+ virtual ~ConnectionManager() = default;
+
+ public:
+ ConnectionManager() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ static void AttachTo(
+ java::GeckoProcessManager::ConnectionManager::Param aInstance);
+ void ObserveNetworkNotifications();
+
+ private:
+ java::GeckoProcessManager::ConnectionManager::WeakRef mJavaConnMgr;
+ };
+
+ public:
+ static void Init();
+
+ static void GetEditableParent(jni::Object::Param aEditableChild,
+ int64_t aContentId, int64_t aTabId) {
+ nsCOMPtr<nsIWidget> widget = GetWidget(aContentId, aTabId);
+ if (RefPtr<nsWindow> window = nsWindow::From(widget)) {
+ java::GeckoProcessManager::SetEditableChildParent(
+ aEditableChild, window->GetEditableParent());
+ }
+ }
+};
+
+} // namespace mozilla
+
+#endif // GeckoProcessManager_h
diff --git a/widget/android/GeckoScreenOrientation.h b/widget/android/GeckoScreenOrientation.h
new file mode 100644
index 0000000000..bbfb3ae3cf
--- /dev/null
+++ b/widget/android/GeckoScreenOrientation.h
@@ -0,0 +1,52 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 GeckoScreenOrientation_h
+#define GeckoScreenOrientation_h
+
+#include "nsAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsIScreenManager.h"
+
+#include "mozilla/Hal.h"
+#include "mozilla/java/GeckoScreenOrientationNatives.h"
+
+namespace mozilla {
+
+class GeckoScreenOrientation final
+ : public java::GeckoScreenOrientation::Natives<GeckoScreenOrientation> {
+ GeckoScreenOrientation() = delete;
+
+ public:
+ static void OnOrientationChange(int16_t aOrientation, int16_t aAngle) {
+ nsCOMPtr<nsIScreenManager> screenMgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ nsCOMPtr<nsIScreen> screen;
+
+ if (!screenMgr ||
+ NS_FAILED(screenMgr->GetPrimaryScreen(getter_AddRefs(screen))) ||
+ !screen) {
+ return;
+ }
+
+ nsIntRect rect;
+ int32_t colorDepth, pixelDepth;
+
+ if (NS_FAILED(
+ screen->GetRect(&rect.x, &rect.y, &rect.width, &rect.height)) ||
+ NS_FAILED(screen->GetColorDepth(&colorDepth)) ||
+ NS_FAILED(screen->GetPixelDepth(&pixelDepth))) {
+ return;
+ }
+
+ hal::NotifyScreenConfigurationChange(hal::ScreenConfiguration(
+ rect, static_cast<hal::ScreenOrientation>(aOrientation), aAngle,
+ colorDepth, pixelDepth));
+ }
+};
+
+} // namespace mozilla
+
+#endif // GeckoScreenOrientation_h
diff --git a/widget/android/GeckoSystemStateListener.h b/widget/android/GeckoSystemStateListener.h
new file mode 100644
index 0000000000..75cb1cd9fe
--- /dev/null
+++ b/widget/android/GeckoSystemStateListener.h
@@ -0,0 +1,31 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 GeckoSystemStateListener_h
+#define GeckoSystemStateListener_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/java/GeckoSystemStateListenerNatives.h"
+
+namespace mozilla {
+
+class GeckoSystemStateListener final
+ : public java::GeckoSystemStateListener::Natives<GeckoSystemStateListener> {
+ GeckoSystemStateListener() = delete;
+
+ public:
+ static void OnDeviceChanged() {
+ MOZ_ASSERT(NS_IsMainThread());
+ // TODO(emilio, bug 1673318): This could become more granular and avoid work
+ // if we get whether these are layout/style-affecting from the caller.
+ mozilla::LookAndFeel::NotifyChangedAllWindows(
+ widget::ThemeChangeKind::StyleAndLayout);
+ }
+};
+
+} // namespace mozilla
+
+#endif // GeckoSystemStateListener_h
diff --git a/widget/android/GeckoTelemetryDelegate.h b/widget/android/GeckoTelemetryDelegate.h
new file mode 100644
index 0000000000..f391630a3d
--- /dev/null
+++ b/widget/android/GeckoTelemetryDelegate.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 GeckoTelemetryDelegate_h__
+#define GeckoTelemetryDelegate_h__
+
+#include "geckoview/streaming/GeckoViewStreamingTelemetry.h"
+
+#include <jni.h>
+
+#include "mozilla/java/RuntimeTelemetryNatives.h"
+#include "mozilla/jni/Natives.h"
+
+namespace mozilla {
+namespace widget {
+
+class GeckoTelemetryDelegate final
+ : public GeckoViewStreamingTelemetry::StreamingTelemetryDelegate,
+ public mozilla::java::RuntimeTelemetry::Proxy::Natives<
+ GeckoTelemetryDelegate> {
+ public:
+ // Implement Proxy native.
+ static void RegisterDelegateProxy(
+ mozilla::java::RuntimeTelemetry::Proxy::Param aProxy) {
+ MOZ_ASSERT(aProxy);
+
+ GeckoViewStreamingTelemetry::RegisterDelegate(
+ new GeckoTelemetryDelegate(aProxy));
+ }
+
+ explicit GeckoTelemetryDelegate(
+ mozilla::java::RuntimeTelemetry::Proxy::Param aProxy)
+ : mProxy(aProxy) {}
+
+ private:
+ void DispatchHistogram(bool aIsCategorical, const nsCString& aName,
+ const nsTArray<uint32_t>& aSamples) {
+ if (!mozilla::jni::IsAvailable() || !mProxy || aSamples.Length() < 1) {
+ return;
+ }
+
+ // Convert aSamples to an array of int64_t. We know |samples| required
+ // capacity needs to match |aSamples.Length()|.
+ nsTArray<int64_t> samples(aSamples.Length());
+ for (size_t i = 0, l = aSamples.Length(); i < l; ++i) {
+ samples.AppendElement(static_cast<int64_t>(aSamples[i]));
+ }
+
+ // LongArray::New *copies* the elements
+ mProxy->DispatchHistogram(
+ aIsCategorical, aName,
+ mozilla::jni::LongArray::New(samples.Elements(), samples.Length()));
+ }
+
+ // Implement StreamingTelemetryDelegate.
+ void ReceiveHistogramSamples(const nsCString& aName,
+ const nsTArray<uint32_t>& aSamples) override {
+ DispatchHistogram(/* isCategorical */ false, aName, aSamples);
+ }
+
+ void ReceiveCategoricalHistogramSamples(
+ const nsCString& aName, const nsTArray<uint32_t>& aSamples) override {
+ DispatchHistogram(/* isCategorical */ true, aName, aSamples);
+ }
+
+ void ReceiveBoolScalarValue(const nsCString& aName, bool aValue) override {
+ if (!mozilla::jni::IsAvailable() || !mProxy) {
+ return;
+ }
+
+ mProxy->DispatchBooleanScalar(aName, aValue);
+ }
+
+ void ReceiveStringScalarValue(const nsCString& aName,
+ const nsCString& aValue) override {
+ if (!mozilla::jni::IsAvailable() || !mProxy) {
+ return;
+ }
+
+ mProxy->DispatchStringScalar(aName, aValue);
+ }
+
+ void ReceiveUintScalarValue(const nsCString& aName,
+ uint32_t aValue) override {
+ if (!mozilla::jni::IsAvailable() || !mProxy) {
+ return;
+ }
+
+ mProxy->DispatchLongScalar(aName, static_cast<int64_t>(aValue));
+ }
+
+ mozilla::java::RuntimeTelemetry::Proxy::GlobalRef mProxy;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // GeckoTelemetryDelegate_h__
diff --git a/widget/android/GeckoVRManager.h b/widget/android/GeckoVRManager.h
new file mode 100644
index 0000000000..de28c402cd
--- /dev/null
+++ b/widget/android/GeckoVRManager.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_GeckoVRManager_h_
+#define mozilla_GeckoVRManager_h_
+
+#include "mozilla/java/GeckoVRManagerWrappers.h"
+#include "mozilla/jni/Utils.h"
+
+namespace mozilla {
+
+class GeckoVRManager {
+ public:
+ static void* GetExternalContext() {
+ return reinterpret_cast<void*>(
+ mozilla::java::GeckoVRManager::GetExternalContext());
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_GeckoVRManager_h_
diff --git a/widget/android/GeckoViewSupport.h b/widget/android/GeckoViewSupport.h
new file mode 100644
index 0000000000..e7019eaecd
--- /dev/null
+++ b/widget/android/GeckoViewSupport.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_GeckoViewSupport_h
+#define mozilla_widget_GeckoViewSupport_h
+
+#include "mozilla/java/GeckoResultWrappers.h"
+#include "mozilla/java/GeckoSessionNatives.h"
+#include "mozilla/java/WebResponseWrappers.h"
+#include "mozilla/widget/WindowEvent.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class GeckoViewSupport final
+ : public java::GeckoSession::Window::Natives<GeckoViewSupport> {
+ RefPtr<nsWindow> mWindow;
+
+ // We hold a WeakRef because we want to allow the
+ // GeckoSession.Window to be garbage collected.
+ // Callers need to create a LocalRef from this
+ // before calling methods.
+ java::GeckoSession::Window::WeakRef mGeckoViewWindow;
+
+ public:
+ typedef java::GeckoSession::Window::Natives<GeckoViewSupport> Base;
+
+ template <typename Functor>
+ static void OnNativeCall(Functor&& aCall) {
+ NS_DispatchToMainThread(new WindowEvent<Functor>(std::move(aCall)));
+ }
+
+ GeckoViewSupport(nsWindow* aWindow,
+ const java::GeckoSession::Window::LocalRef& aInstance,
+ nsPIDOMWindowOuter* aDOMWindow)
+ : mWindow(aWindow), mGeckoViewWindow(aInstance), mDOMWindow(aDOMWindow) {}
+
+ ~GeckoViewSupport();
+
+ nsWindow* GetNsWindow() const { return mWindow; }
+
+ using Base::DisposeNative;
+
+ /**
+ * GeckoView methods
+ */
+ private:
+ nsCOMPtr<nsPIDOMWindowOuter> mDOMWindow;
+ bool mIsReady{false};
+
+ public:
+ // Create and attach a window.
+ static void Open(const jni::Class::LocalRef& aCls,
+ java::GeckoSession::Window::Param aWindow,
+ jni::Object::Param aQueue, jni::Object::Param aCompositor,
+ jni::Object::Param aDispatcher,
+ jni::Object::Param aSessionAccessibility,
+ jni::Object::Param aInitData, jni::String::Param aId,
+ jni::String::Param aChromeURI, int32_t aScreenId,
+ bool aPrivateMode);
+
+ // Close and destroy the nsWindow.
+ void Close();
+
+ // Transfer this nsWindow to new GeckoSession objects.
+ void Transfer(const java::GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aQueue, jni::Object::Param aCompositor,
+ jni::Object::Param aDispatcher,
+ jni::Object::Param aSessionAccessibility,
+ jni::Object::Param aInitData);
+
+ void AttachEditable(const java::GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aEditableParent);
+
+ void AttachAccessibility(const java::GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aSessionAccessibility);
+
+ void OnReady(jni::Object::Param aQueue = nullptr);
+
+ auto OnLoadRequest(mozilla::jni::String::Param aUri, int32_t aWindowType,
+ int32_t aFlags, mozilla::jni::String::Param aTriggeringUri,
+ bool aHasUserGesture, bool aIsTopLevel) const
+ -> java::GeckoResult::LocalRef;
+
+ void PassExternalResponse(java::WebResponse::Param aResponse);
+
+ void AttachMediaSessionController(
+ const java::GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aController, const int64_t aId);
+
+ void DetachMediaSessionController(
+ const java::GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aController);
+
+ void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer) {
+ RefPtr<Runnable> disposer(aDisposer);
+ disposer->Run();
+ }
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_GeckoViewSupport_h
diff --git a/widget/android/GfxInfo.cpp b/widget/android/GfxInfo.cpp
new file mode 100644
index 0000000000..981c2c1741
--- /dev/null
+++ b/widget/android/GfxInfo.cpp
@@ -0,0 +1,800 @@
+/* -*- 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 "GLContext.h"
+#include "GLContextProvider.h"
+#include "nsUnicharUtils.h"
+#include "prenv.h"
+#include "nsExceptionHandler.h"
+#include "nsHashKeys.h"
+#include "nsVersionComparator.h"
+#include "AndroidBridge.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozilla/Preferences.h"
+
+#define NS_CRASHREPORTER_CONTRACTID "@mozilla.org/toolkit/crash-reporter;1"
+
+namespace mozilla {
+namespace widget {
+
+class GfxInfo::GLStrings {
+ nsCString mVendor;
+ nsCString mRenderer;
+ nsCString mVersion;
+ bool mReady;
+
+ public:
+ GLStrings() : mReady(false) {}
+
+ const nsCString& Vendor() {
+ EnsureInitialized();
+ return mVendor;
+ }
+
+ // This spoofed value wins, even if the environment variable
+ // MOZ_GFX_SPOOF_GL_VENDOR was set.
+ void SpoofVendor(const nsCString& s) { mVendor = s; }
+
+ const nsCString& Renderer() {
+ EnsureInitialized();
+ return mRenderer;
+ }
+
+ // This spoofed value wins, even if the environment variable
+ // MOZ_GFX_SPOOF_GL_RENDERER was set.
+ void SpoofRenderer(const nsCString& s) { mRenderer = s; }
+
+ const nsCString& Version() {
+ EnsureInitialized();
+ return mVersion;
+ }
+
+ // This spoofed value wins, even if the environment variable
+ // MOZ_GFX_SPOOF_GL_VERSION was set.
+ void SpoofVersion(const nsCString& s) { mVersion = s; }
+
+ void EnsureInitialized() {
+ if (mReady) {
+ return;
+ }
+
+ RefPtr<gl::GLContext> gl;
+ nsCString discardFailureId;
+ gl = gl::GLContextProvider::CreateHeadless(
+ {gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE}, &discardFailureId);
+
+ if (!gl) {
+ // Setting mReady to true here means that we won't retry. Everything will
+ // remain blocklisted forever. Ideally, we would like to update that once
+ // any GLContext is successfully created, like the compositor's GLContext.
+ mReady = true;
+ return;
+ }
+
+ gl->MakeCurrent();
+
+ if (mVendor.IsEmpty()) {
+ const char* spoofedVendor = PR_GetEnv("MOZ_GFX_SPOOF_GL_VENDOR");
+ if (spoofedVendor) {
+ mVendor.Assign(spoofedVendor);
+ } else {
+ mVendor.Assign((const char*)gl->fGetString(LOCAL_GL_VENDOR));
+ }
+ }
+
+ if (mRenderer.IsEmpty()) {
+ const char* spoofedRenderer = PR_GetEnv("MOZ_GFX_SPOOF_GL_RENDERER");
+ if (spoofedRenderer) {
+ mRenderer.Assign(spoofedRenderer);
+ } else {
+ mRenderer.Assign((const char*)gl->fGetString(LOCAL_GL_RENDERER));
+ }
+ }
+
+ if (mVersion.IsEmpty()) {
+ const char* spoofedVersion = PR_GetEnv("MOZ_GFX_SPOOF_GL_VERSION");
+ if (spoofedVersion) {
+ mVersion.Assign(spoofedVersion);
+ } else {
+ mVersion.Assign((const char*)gl->fGetString(LOCAL_GL_VERSION));
+ }
+ }
+
+ mReady = true;
+ }
+};
+
+#ifdef DEBUG
+NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug)
+#endif
+
+GfxInfo::GfxInfo()
+ : mInitialized(false),
+ mGLStrings(new GLStrings),
+ mOSVersionInteger(0),
+ mSDKVersion(0) {}
+
+GfxInfo::~GfxInfo() {}
+
+/* GetD2DEnabled and GetDwriteEnabled shouldn't be called until after
+ * gfxPlatform initialization has occurred because they depend on it for
+ * information. (See bug 591561) */
+nsresult GfxInfo::GetD2DEnabled(bool* aEnabled) { return NS_ERROR_FAILURE; }
+
+nsresult GfxInfo::GetDWriteEnabled(bool* aEnabled) { return NS_ERROR_FAILURE; }
+
+nsresult GfxInfo::GetHasBattery(bool* aHasBattery) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDWriteVersion(nsAString& aDwriteVersion) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetEmbeddedInFirefoxReality(bool* aEmbeddedInFirefoxReality) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetCleartypeParameters(nsAString& aCleartypeParams) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetWindowProtocol(nsAString& aWindowProtocol) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDesktopEnvironment(nsAString& aDesktopEnvironment) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void GfxInfo::EnsureInitialized() {
+ if (mInitialized) return;
+
+ if (!mozilla::AndroidBridge::Bridge()) {
+ gfxWarning() << "AndroidBridge missing during initialization";
+ return;
+ }
+
+ if (mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build",
+ "MODEL", mModel)) {
+ mAdapterDescription.AppendPrintf("Model: %s",
+ NS_LossyConvertUTF16toASCII(mModel).get());
+ }
+
+ if (mozilla::AndroidBridge::Bridge()->GetStaticStringField(
+ "android/os/Build", "PRODUCT", mProduct)) {
+ mAdapterDescription.AppendPrintf(
+ ", Product: %s", NS_LossyConvertUTF16toASCII(mProduct).get());
+ }
+
+ if (mozilla::AndroidBridge::Bridge()->GetStaticStringField(
+ "android/os/Build", "MANUFACTURER", mManufacturer)) {
+ mAdapterDescription.AppendPrintf(
+ ", Manufacturer: %s", NS_LossyConvertUTF16toASCII(mManufacturer).get());
+ }
+
+ if (mozilla::AndroidBridge::Bridge()->GetStaticIntField(
+ "android/os/Build$VERSION", "SDK_INT", &mSDKVersion)) {
+ // the HARDWARE field isn't available on Android SDK < 8, but we require 9+
+ // anyway.
+ MOZ_ASSERT(mSDKVersion >= 8);
+ if (mozilla::AndroidBridge::Bridge()->GetStaticStringField(
+ "android/os/Build", "HARDWARE", mHardware)) {
+ mAdapterDescription.AppendPrintf(
+ ", Hardware: %s", NS_LossyConvertUTF16toASCII(mHardware).get());
+ }
+ } else {
+ mSDKVersion = 0;
+ }
+
+ nsString release;
+ mozilla::AndroidBridge::Bridge()->GetStaticStringField(
+ "android/os/Build$VERSION", "RELEASE", release);
+ mOSVersion = NS_LossyConvertUTF16toASCII(release);
+
+ mOSVersionInteger = 0;
+ char a[5], b[5], c[5], d[5];
+ SplitDriverVersion(mOSVersion.get(), a, b, c, d);
+ uint8_t na = atoi(a);
+ uint8_t nb = atoi(b);
+ uint8_t nc = atoi(c);
+ uint8_t nd = atoi(d);
+
+ mOSVersionInteger = (uint32_t(na) << 24) | (uint32_t(nb) << 16) |
+ (uint32_t(nc) << 8) | uint32_t(nd);
+
+ mAdapterDescription.AppendPrintf(
+ ", OpenGL: %s -- %s -- %s", mGLStrings->Vendor().get(),
+ mGLStrings->Renderer().get(), mGLStrings->Version().get());
+
+ AddCrashReportAnnotations();
+
+ mScreenInfo.mScreenDimensions =
+ mozilla::AndroidBridge::Bridge()->getScreenSize();
+
+ mInitialized = true;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription(nsAString& aAdapterDescription) {
+ EnsureInitialized();
+ aAdapterDescription = NS_ConvertASCIItoUTF16(mAdapterDescription);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription2(nsAString& aAdapterDescription) {
+ EnsureInitialized();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM(uint32_t* aAdapterRAM) {
+ EnsureInitialized();
+ *aAdapterRAM = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(uint32_t* aAdapterRAM) {
+ EnsureInitialized();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver(nsAString& aAdapterDriver) {
+ EnsureInitialized();
+ aAdapterDriver.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver2(nsAString& aAdapterDriver) {
+ EnsureInitialized();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) {
+ EnsureInitialized();
+ aAdapterDriverVendor.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) {
+ EnsureInitialized();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) {
+ EnsureInitialized();
+ aAdapterDriverVersion = NS_ConvertASCIItoUTF16(mGLStrings->Version());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion2(nsAString& aAdapterDriverVersion) {
+ EnsureInitialized();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate(nsAString& aAdapterDriverDate) {
+ EnsureInitialized();
+ aAdapterDriverDate.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate2(nsAString& aAdapterDriverDate) {
+ EnsureInitialized();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID(nsAString& aAdapterVendorID) {
+ EnsureInitialized();
+ aAdapterVendorID = NS_ConvertASCIItoUTF16(mGLStrings->Vendor());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString& aAdapterVendorID) {
+ EnsureInitialized();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString& aAdapterDeviceID) {
+ EnsureInitialized();
+ aAdapterDeviceID = NS_ConvertASCIItoUTF16(mGLStrings->Renderer());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID2(nsAString& aAdapterDeviceID) {
+ EnsureInitialized();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID(nsAString& aAdapterSubsysID) {
+ EnsureInitialized();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID2(nsAString& aAdapterSubsysID) {
+ EnsureInitialized();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active) {
+ EnsureInitialized();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDisplayInfo(nsTArray<nsString>& aDisplayInfo) {
+ EnsureInitialized();
+ nsString displayInfo;
+ displayInfo.AppendPrintf("%dx%d",
+ (int32_t)mScreenInfo.mScreenDimensions.width,
+ (int32_t)mScreenInfo.mScreenDimensions.height);
+ aDisplayInfo.AppendElement(displayInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDisplayWidth(nsTArray<uint32_t>& aDisplayWidth) {
+ aDisplayWidth.AppendElement((uint32_t)mScreenInfo.mScreenDimensions.width);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDisplayHeight(nsTArray<uint32_t>& aDisplayHeight) {
+ aDisplayHeight.AppendElement((uint32_t)mScreenInfo.mScreenDimensions.height);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDrmRenderDevice(nsACString& aDrmRenderDevice) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void GfxInfo::AddCrashReportAnnotations() {
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterVendorID,
+ mGLStrings->Vendor());
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterDeviceID,
+ mGLStrings->Renderer());
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::AdapterDriverVersion, mGLStrings->Version());
+}
+
+const nsTArray<GfxDriverInfo>& GfxInfo::GetGfxDriverInfo() {
+ if (sDriverInfo->IsEmpty()) {
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Android, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_STATUS_OK,
+ DRIVER_COMPARISON_IGNORED, GfxDriverInfo::allDriverVersions,
+ "FEATURE_OK_FORCE_OPENGL");
+ }
+
+ return *sDriverInfo;
+}
+
+nsresult GfxInfo::GetFeatureStatusImpl(
+ int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
+ OperatingSystem* aOS /* = nullptr */) {
+ NS_ENSURE_ARG_POINTER(aStatus);
+ aSuggestedDriverVersion.SetIsVoid(true);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ OperatingSystem os = mOS;
+ if (aOS) *aOS = os;
+
+ if (sShutdownOccurred) {
+ return NS_OK;
+ }
+
+ // OpenGL layers are never blocklisted on Android.
+ // This early return is so we avoid potentially slow
+ // GLStrings initialization on startup when we initialize GL layers.
+ if (aFeature == nsIGfxInfo::FEATURE_OPENGL_LAYERS) {
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ return NS_OK;
+ }
+
+ EnsureInitialized();
+
+ if (mGLStrings->Vendor().IsEmpty() || mGLStrings->Renderer().IsEmpty()) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ return NS_OK;
+ }
+
+ // Don't evaluate special cases when evaluating the downloaded blocklist.
+ if (aDriverInfo.IsEmpty()) {
+ if (aFeature == nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION) {
+ if (mSDKVersion < 11) {
+ // It's slower than software due to not having a compositing fast path
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION;
+ aFailureId = "FEATURE_FAILURE_CANVAS_2D_SDK";
+ } else if (mGLStrings->Renderer().Find("Vivante GC1000") != -1) {
+ // Blocklist Vivante GC1000. See bug 1248183.
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILED_CANVAS_2D_HW";
+ } else {
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ }
+ return NS_OK;
+ }
+
+ if (aFeature == FEATURE_WEBGL_OPENGL) {
+ if (mGLStrings->Renderer().Find("Adreno 200") != -1 ||
+ mGLStrings->Renderer().Find("Adreno 205") != -1) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_ADRENO_20x";
+ return NS_OK;
+ }
+
+ if (mSDKVersion <= 17) {
+ if (mGLStrings->Renderer().Find("Adreno (TM) 3") != -1) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_ADRENO_3xx";
+ }
+ return NS_OK;
+ }
+
+ if (mHardware.EqualsLiteral("ville")) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_VILLE";
+ return NS_OK;
+ }
+ }
+
+ if (aFeature == FEATURE_STAGEFRIGHT) {
+ NS_LossyConvertUTF16toASCII cManufacturer(mManufacturer);
+ NS_LossyConvertUTF16toASCII cModel(mModel);
+ NS_LossyConvertUTF16toASCII cHardware(mHardware);
+
+ if (cHardware.EqualsLiteral("antares") ||
+ cHardware.EqualsLiteral("harmony") ||
+ cHardware.EqualsLiteral("picasso") ||
+ cHardware.EqualsLiteral("picasso_e") ||
+ cHardware.EqualsLiteral("ventana") ||
+ cHardware.EqualsLiteral("rk30board")) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_STAGE_HW";
+ return NS_OK;
+ }
+
+ if (CompareVersions(mOSVersion.get(), "4.1.0") < 0) {
+ // Whitelist:
+ // All Samsung ICS devices, except for:
+ // Samsung SGH-I717 (Bug 845729)
+ // Samsung SGH-I727 (Bug 845729)
+ // Samsung SGH-I757 (Bug 845729)
+ // All Galaxy nexus ICS devices
+ // Sony Xperia Ion (LT28) ICS devices
+ bool isWhitelisted =
+ cModel.Equals("LT28h", nsCaseInsensitiveCStringComparator) ||
+ cManufacturer.Equals("samsung",
+ nsCaseInsensitiveCStringComparator) ||
+ cModel.Equals(
+ "galaxy nexus",
+ nsCaseInsensitiveCStringComparator); // some Galaxy Nexus
+ // have
+ // manufacturer=amazon
+
+ if (cModel.Find("SGH-I717", true) != -1 ||
+ cModel.Find("SGH-I727", true) != -1 ||
+ cModel.Find("SGH-I757", true) != -1) {
+ isWhitelisted = false;
+ }
+
+ if (!isWhitelisted) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_4_1_HW";
+ return NS_OK;
+ }
+ } else if (CompareVersions(mOSVersion.get(), "4.2.0") < 0) {
+ // Whitelist:
+ // All JB phones except for those in blocklist below
+ // Blocklist:
+ // Samsung devices from bug 812881 and 853522.
+ // Motorola XT890 from bug 882342.
+ bool isBlocklisted = cModel.Find("GT-P3100", true) != -1 ||
+ cModel.Find("GT-P3110", true) != -1 ||
+ cModel.Find("GT-P3113", true) != -1 ||
+ cModel.Find("GT-P5100", true) != -1 ||
+ cModel.Find("GT-P5110", true) != -1 ||
+ cModel.Find("GT-P5113", true) != -1 ||
+ cModel.Find("XT890", true) != -1;
+
+ if (isBlocklisted) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_4_2_HW";
+ return NS_OK;
+ }
+ } else if (CompareVersions(mOSVersion.get(), "4.3.0") < 0) {
+ // Blocklist all Sony devices
+ if (cManufacturer.Find("Sony", true) != -1) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_4_3_SONY";
+ return NS_OK;
+ }
+ }
+ }
+
+ if (aFeature == FEATURE_WEBRTC_HW_ACCELERATION_ENCODE) {
+ if (mozilla::AndroidBridge::Bridge()) {
+ *aStatus = WebRtcHwVp8EncodeSupported();
+ aFailureId = "FEATURE_FAILURE_WEBRTC_ENCODE";
+ return NS_OK;
+ }
+ }
+ if (aFeature == FEATURE_WEBRTC_HW_ACCELERATION_DECODE) {
+ if (mozilla::AndroidBridge::Bridge()) {
+ *aStatus = WebRtcHwVp8DecodeSupported();
+ aFailureId = "FEATURE_FAILURE_WEBRTC_DECODE";
+ return NS_OK;
+ }
+ }
+ if (aFeature == FEATURE_WEBRTC_HW_ACCELERATION_H264) {
+ if (mozilla::AndroidBridge::Bridge()) {
+ *aStatus = WebRtcHwH264Supported();
+ aFailureId = "FEATURE_FAILURE_WEBRTC_H264";
+ return NS_OK;
+ }
+ }
+ if (aFeature == FEATURE_VP8_HW_DECODE ||
+ aFeature == FEATURE_VP9_HW_DECODE) {
+ NS_LossyConvertUTF16toASCII model(mModel);
+ bool isBlocked =
+ // GIFV crash, see bug 1232911.
+ model.Equals("GT-N8013", nsCaseInsensitiveCStringComparator);
+
+ if (isBlocked) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_VPx";
+ } else {
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ }
+ return NS_OK;
+ }
+
+ if (aFeature == FEATURE_WEBRENDER) {
+ bool isUnblocked = false;
+ const nsCString& gpu = mGLStrings->Renderer();
+ NS_LossyConvertUTF16toASCII model(mModel);
+
+#ifdef NIGHTLY_BUILD
+ // On Nightly enable Webrender on all Adreno 5xx GPUs
+ isUnblocked |= gpu.Find("Adreno (TM) 5", /*ignoreCase*/ true) >= 0;
+
+ // On Nightly enable Webrender on all Mali-Txxx GPUs
+ isUnblocked |= gpu.Find("Mali-T", /*ignoreCase*/ true) >= 0;
+#endif
+ // Enable Webrender on all Adreno 5xx GPUs, excluding 505 and 506.
+ isUnblocked |=
+ gpu.Find("Adreno (TM) 5", /*ignoreCase*/ true) >= 0 &&
+ gpu.Find("Adreno (TM) 505", /*ignoreCase*/ true) == kNotFound &&
+ gpu.Find("Adreno (TM) 506", /*ignoreCase*/ true) == kNotFound;
+
+ // Enable Webrender on all Adreno 6xx devices
+ isUnblocked |= gpu.Find("Adreno (TM) 6", /*ignoreCase*/ true) >= 0;
+
+ // Enable Webrender on all Mali-Gxx GPUs...
+ isUnblocked |= gpu.Find("Mali-G", /*ignoreCase*/ true) >= 0 &&
+ // Excluding G72 and G76 on Android 11, due to
+ // bugs 1688705 and 1688017.
+ !(mSDKVersion == 30 &&
+ (gpu.Find("Mali-G72", /*ignoreCase*/ true) >= 0 ||
+ gpu.Find("Mali-G76", /*ignoreCase*/ true) >= 0)) &&
+ // And excluding G31 due to bug 1689947.
+ gpu.Find("Mali-G31", /*ignoreCase*/ true) == kNotFound;
+
+ if (!isUnblocked) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_WEBRENDER_BLOCKED_DEVICE";
+ } else {
+ *aStatus = nsIGfxInfo::FEATURE_ALLOW_QUALIFIED;
+ }
+ return NS_OK;
+ }
+
+ if (aFeature == FEATURE_WEBRENDER_SCISSORED_CACHE_CLEARS) {
+ // Emulator with SwiftShader is buggy when attempting to clear picture
+ // cache textures with a scissor rect set.
+ const bool isEmulatorSwiftShader =
+ mGLStrings->Renderer().Find(
+ "Android Emulator OpenGL ES Translator (Google SwiftShader)") >=
+ 0;
+ if (isEmulatorSwiftShader) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_BUG_1603515";
+ } else {
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ }
+ return NS_OK;
+ }
+ }
+
+ return GfxInfoBase::GetFeatureStatusImpl(
+ aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, aFailureId, &os);
+}
+
+static nsCString FeatureCacheOsVerPrefName(int32_t aFeature) {
+ nsCString osPrefName;
+ osPrefName.AppendASCII("gfxinfo.cache.");
+ osPrefName.AppendInt(aFeature);
+ osPrefName.AppendASCII(".osver");
+ return osPrefName;
+}
+
+static nsCString FeatureCacheValuePrefName(int32_t aFeature) {
+ nsCString osPrefName;
+ osPrefName.AppendASCII("gfxinfo.cache.");
+ osPrefName.AppendInt(aFeature);
+ osPrefName.AppendASCII(".value");
+ return osPrefName;
+}
+
+static bool GetCachedFeatureVal(int32_t aFeature, uint32_t aExpectedOsVer,
+ int32_t& aOutStatus) {
+ uint32_t osVer = 0;
+ nsresult rv =
+ Preferences::GetUint(FeatureCacheOsVerPrefName(aFeature).get(), &osVer);
+ if (NS_FAILED(rv) || osVer != aExpectedOsVer) {
+ return false;
+ }
+ int32_t status = 0;
+ rv = Preferences::GetInt(FeatureCacheValuePrefName(aFeature).get(), &status);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ aOutStatus = status;
+ return true;
+}
+
+static void SetCachedFeatureVal(int32_t aFeature, uint32_t aOsVer,
+ int32_t aStatus) {
+ // Ignore failures; not much we can do anyway.
+ Preferences::SetUint(FeatureCacheOsVerPrefName(aFeature).get(), aOsVer);
+ Preferences::SetInt(FeatureCacheValuePrefName(aFeature).get(), aStatus);
+}
+
+int32_t GfxInfo::WebRtcHwVp8EncodeSupported() {
+ MOZ_ASSERT(mozilla::AndroidBridge::Bridge());
+
+ // The Android side of this calculation is very slow, so we cache the result
+ // in preferences, invalidating if the OS version changes.
+
+ int32_t status = 0;
+ if (GetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_ENCODE,
+ mOSVersionInteger, status)) {
+ return status;
+ }
+
+ status = mozilla::AndroidBridge::Bridge()->HasHWVP8Encoder()
+ ? nsIGfxInfo::FEATURE_STATUS_OK
+ : nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+
+ SetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_ENCODE, mOSVersionInteger,
+ status);
+
+ return status;
+}
+
+int32_t GfxInfo::WebRtcHwVp8DecodeSupported() {
+ MOZ_ASSERT(mozilla::AndroidBridge::Bridge());
+
+ // The Android side of this caclulation is very slow, so we cache the result
+ // in preferences, invalidating if the OS version changes.
+
+ int32_t status = 0;
+ if (GetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_DECODE,
+ mOSVersionInteger, status)) {
+ return status;
+ }
+
+ status = mozilla::AndroidBridge::Bridge()->HasHWVP8Decoder()
+ ? nsIGfxInfo::FEATURE_STATUS_OK
+ : nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+
+ SetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_DECODE, mOSVersionInteger,
+ status);
+
+ return status;
+}
+
+int32_t GfxInfo::WebRtcHwH264Supported() {
+ MOZ_ASSERT(mozilla::AndroidBridge::Bridge());
+
+ // The Android side of this calculation is very slow, so we cache the result
+ // in preferences, invalidating if the OS version changes.
+
+ int32_t status = 0;
+ if (GetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_H264,
+ mOSVersionInteger, status)) {
+ return status;
+ }
+
+ status = mozilla::AndroidBridge::Bridge()->HasHWH264()
+ ? nsIGfxInfo::FEATURE_STATUS_OK
+ : nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+
+ SetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_H264, mOSVersionInteger,
+ status);
+
+ return status;
+}
+
+#ifdef DEBUG
+
+// Implement nsIGfxInfoDebug
+
+NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString& aVendorID) {
+ mGLStrings->SpoofVendor(NS_LossyConvertUTF16toASCII(aVendorID));
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString& aDeviceID) {
+ mGLStrings->SpoofRenderer(NS_LossyConvertUTF16toASCII(aDeviceID));
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString& aDriverVersion) {
+ mGLStrings->SpoofVersion(NS_LossyConvertUTF16toASCII(aDriverVersion));
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion) {
+ EnsureInitialized();
+ mOSVersion = aVersion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::FireTestProcess() { return NS_OK; }
+
+#endif
+
+nsString GfxInfo::Model() {
+ EnsureInitialized();
+ return mModel;
+}
+
+nsString GfxInfo::Hardware() {
+ EnsureInitialized();
+ return mHardware;
+}
+
+nsString GfxInfo::Product() {
+ EnsureInitialized();
+ return mProduct;
+}
+
+nsString GfxInfo::Manufacturer() {
+ EnsureInitialized();
+ return mManufacturer;
+}
+
+uint32_t GfxInfo::OperatingSystemVersion() {
+ EnsureInitialized();
+ return mOSVersionInteger;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/android/GfxInfo.h b/widget/android/GfxInfo.h
new file mode 100644
index 0000000000..df14fe96c1
--- /dev/null
+++ b/widget/android/GfxInfo.h
@@ -0,0 +1,117 @@
+/* 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 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) override;
+ NS_IMETHOD GetDWriteEnabled(bool* aDWriteEnabled) override;
+ NS_IMETHOD GetDWriteVersion(nsAString& aDwriteVersion) override;
+ NS_IMETHOD GetEmbeddedInFirefoxReality(
+ bool* aEmbeddedInFirefoxReality) override;
+ NS_IMETHOD GetHasBattery(bool* aHasBattery) override;
+ NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override;
+ NS_IMETHOD GetWindowProtocol(nsAString& aWindowProtocol) override;
+ NS_IMETHOD GetDesktopEnvironment(nsAString& aDesktopEnvironment) override;
+ NS_IMETHOD GetAdapterDescription(nsAString& aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver(nsAString& aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID(nsAString& aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID(nsAString& aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID(nsAString& aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM(uint32_t* aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) override;
+ NS_IMETHOD GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate(nsAString& aAdapterDriverDate) override;
+ NS_IMETHOD GetAdapterDescription2(nsAString& aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver2(nsAString& aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID2(nsAString& aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID2(nsAString& aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID2(nsAString& aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM2(uint32_t* aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) override;
+ NS_IMETHOD GetAdapterDriverVersion2(
+ nsAString& aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate2(nsAString& aAdapterDriverDate) override;
+ NS_IMETHOD GetIsGPU2Active(bool* aIsGPU2Active) override;
+ NS_IMETHOD GetDisplayInfo(nsTArray<nsString>& aDisplayInfo) override;
+ NS_IMETHOD GetDisplayWidth(nsTArray<uint32_t>& aDisplayWidth) override;
+ NS_IMETHOD GetDisplayHeight(nsTArray<uint32_t>& aDisplayHeight) override;
+ NS_IMETHOD GetDrmRenderDevice(nsACString& aDrmRenderDevice) override;
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+
+ void EnsureInitialized();
+
+ virtual nsString Model() override;
+ virtual nsString Hardware() override;
+ virtual nsString Product() override;
+ virtual nsString Manufacturer() override;
+
+#ifdef DEBUG
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+ virtual uint32_t OperatingSystemVersion() override;
+
+ protected:
+ virtual nsresult GetFeatureStatusImpl(
+ int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
+ OperatingSystem* aOS = nullptr) override;
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() override;
+
+ private:
+ struct ScreenInfo {
+ gfx::Rect mScreenDimensions;
+ };
+
+ private:
+ void AddCrashReportAnnotations();
+ int32_t WebRtcHwVp8EncodeSupported();
+ int32_t WebRtcHwVp8DecodeSupported();
+ int32_t WebRtcHwH264Supported();
+
+ bool mInitialized;
+
+ class GLStrings;
+ UniquePtr<GLStrings> mGLStrings;
+
+ nsCString mAdapterDescription;
+
+ OperatingSystem mOS;
+
+ nsString mModel, mHardware, mManufacturer, mProduct;
+ nsCString mOSVersion;
+ uint32_t mOSVersionInteger;
+ int32_t mSDKVersion;
+ ScreenInfo mScreenInfo;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_GfxInfo_h__ */
diff --git a/widget/android/ImageDecoderSupport.cpp b/widget/android/ImageDecoderSupport.cpp
new file mode 100644
index 0000000000..9bebcc72a6
--- /dev/null
+++ b/widget/android/ImageDecoderSupport.cpp
@@ -0,0 +1,176 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "ImageDecoderSupport.h"
+
+#include "imgINotificationObserver.h"
+#include "imgITools.h"
+#include "imgINotificationObserver.h"
+#include "gfxUtils.h"
+#include "AndroidGraphics.h"
+#include "JavaExceptions.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Swizzle.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace widget {
+
+namespace {
+
+class ImageCallbackHelper;
+
+HashSet<RefPtr<ImageCallbackHelper>, PointerHasher<ImageCallbackHelper*>>
+ gDecodeRequests;
+
+class ImageCallbackHelper : public imgIContainerCallback,
+ public imgINotificationObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ void CompleteExceptionally(const char* aMessage) {
+ mResult->CompleteExceptionally(
+ java::sdk::IllegalArgumentException::New(aMessage)
+ .Cast<jni::Throwable>());
+ gDecodeRequests.remove(this);
+ }
+
+ void Complete(DataSourceSurface::ScopedMap& aSourceSurface, int32_t width,
+ int32_t height) {
+ auto pixels = mozilla::jni::ByteBuffer::New(
+ reinterpret_cast<int8_t*>(aSourceSurface.GetData()),
+ aSourceSurface.GetStride() * height);
+ auto bitmap = java::sdk::Bitmap::CreateBitmap(
+ width, height, java::sdk::Config::ARGB_8888());
+ bitmap->CopyPixelsFromBuffer(pixels);
+ mResult->Complete(bitmap);
+ gDecodeRequests.remove(this);
+ }
+
+ ImageCallbackHelper(java::GeckoResult::Param aResult, int32_t aDesiredLength)
+ : mResult(aResult), mDesiredLength(aDesiredLength), mImage(nullptr) {
+ MOZ_ASSERT(mResult);
+ }
+
+ NS_IMETHOD
+ OnImageReady(imgIContainer* aImage, nsresult aStatus) override {
+ // Let's make sure we are alive until the request completes
+ MOZ_ALWAYS_TRUE(gDecodeRequests.putNew(this));
+
+ if (NS_FAILED(aStatus)) {
+ CompleteExceptionally("Could not process image.");
+ return aStatus;
+ }
+
+ mImage = aImage;
+ return mImage->StartDecoding(
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY,
+ imgIContainer::FRAME_FIRST);
+ }
+
+ // This method assumes that the image is ready to be processed
+ nsresult SendBitmap() {
+ RefPtr<gfx::SourceSurface> surface;
+
+ if (mDesiredLength > 0) {
+ surface = mImage->GetFrameAtSize(
+ gfx::IntSize(mDesiredLength, mDesiredLength),
+ imgIContainer::FRAME_FIRST,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ } else {
+ surface = mImage->GetFrame(
+ imgIContainer::FRAME_FIRST,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ }
+
+ RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
+
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ int32_t width = dataSurface->GetSize().width;
+ int32_t height = dataSurface->GetSize().height;
+
+ DataSourceSurface::ScopedMap sourceMap(dataSurface,
+ DataSourceSurface::READ);
+
+ // Android's Bitmap only supports R8G8B8A8, so we need to convert the
+ // data to the right format
+ RefPtr<DataSourceSurface> destDataSurface =
+ Factory::CreateDataSourceSurfaceWithStride(dataSurface->GetSize(),
+ SurfaceFormat::R8G8B8A8,
+ sourceMap.GetStride());
+ NS_ENSURE_TRUE(destDataSurface, NS_ERROR_FAILURE);
+
+ DataSourceSurface::ScopedMap destMap(destDataSurface,
+ DataSourceSurface::READ_WRITE);
+
+ SwizzleData(sourceMap.GetData(), sourceMap.GetStride(),
+ surface->GetFormat(), destMap.GetData(), destMap.GetStride(),
+ SurfaceFormat::R8G8B8A8, destDataSurface->GetSize());
+
+ Complete(destMap, width, height);
+
+ return NS_OK;
+ }
+
+ void Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aData) override {
+ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+ SendBitmap();
+ // Breack the cyclic reference between `ImageDecoderListener` (which is a
+ // `imgIContainer`) and `ImageCallbackHelper`.
+ mImage = nullptr;
+ }
+ }
+
+ private:
+ const java::GeckoResult::GlobalRef mResult;
+ int32_t mDesiredLength;
+ nsCOMPtr<imgIContainer> mImage;
+ virtual ~ImageCallbackHelper() {}
+};
+
+NS_IMPL_ISUPPORTS(ImageCallbackHelper, imgIContainerCallback,
+ imgINotificationObserver)
+
+} // namespace
+
+/* static */ void ImageDecoderSupport::Decode(jni::String::Param aUri,
+ int32_t aDesiredLength,
+ jni::Object::Param aResult) {
+ auto result = java::GeckoResult::LocalRef(aResult);
+ RefPtr<ImageCallbackHelper> helper =
+ new ImageCallbackHelper(result, aDesiredLength);
+
+ nsresult rv = DecodeInternal(aUri->ToString(), helper, helper);
+ if (NS_FAILED(rv)) {
+ helper->OnImageReady(nullptr, rv);
+ }
+}
+
+/* static */ nsresult ImageDecoderSupport::DecodeInternal(
+ const nsAString& aUri, imgIContainerCallback* aCallback,
+ imgINotificationObserver* aObserver) {
+ nsCOMPtr<imgITools> imgTools = do_GetService("@mozilla.org/image/tools;1");
+ if (NS_WARN_IF(!imgTools)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aUri);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_IMAGE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return imgTools->DecodeImageFromChannelAsync(uri, channel, aCallback,
+ aObserver);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/android/ImageDecoderSupport.h b/widget/android/ImageDecoderSupport.h
new file mode 100644
index 0000000000..d38b3e7e1b
--- /dev/null
+++ b/widget/android/ImageDecoderSupport.h
@@ -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/. */
+
+#ifndef ImageDecoderSupport_h__
+#define ImageDecoderSupport_h__
+
+#include "mozilla/java/ImageDecoderNatives.h"
+
+class imgIContainerCallback;
+
+namespace mozilla {
+namespace widget {
+
+class ImageDecoderSupport final
+ : public java::ImageDecoder::Natives<ImageDecoderSupport> {
+ public:
+ static void Decode(jni::String::Param aUri, int32_t aDesiredLength,
+ jni::Object::Param aResult);
+
+ private:
+ static nsresult DecodeInternal(const nsAString& aUri,
+ imgIContainerCallback* aCallback,
+ imgINotificationObserver* aObserver);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // ImageDecoderSupport_h__
diff --git a/widget/android/MediaKeysEventSourceFactory.cpp b/widget/android/MediaKeysEventSourceFactory.cpp
new file mode 100644
index 0000000000..b52919d4cc
--- /dev/null
+++ b/widget/android/MediaKeysEventSourceFactory.cpp
@@ -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/. */
+
+#include "MediaKeysEventSourceFactory.h"
+
+namespace mozilla {
+namespace widget {
+
+mozilla::dom::MediaControlKeySource* CreateMediaControlKeySource() {
+ // GeckoView uses MediaController.webidl for media session events and control,
+ // see bug 1623715.
+ return nullptr;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/android/PrefsHelper.h b/widget/android/PrefsHelper.h
new file mode 100644
index 0000000000..0cc4ec4548
--- /dev/null
+++ b/widget/android/PrefsHelper.h
@@ -0,0 +1,306 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 PrefsHelper_h
+#define PrefsHelper_h
+
+#include "MainThreadUtils.h"
+#include "nsAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsVariant.h"
+
+#include "mozilla/java/PrefsHelperNatives.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+
+namespace mozilla {
+
+class PrefsHelper : public java::PrefsHelper::Natives<PrefsHelper> {
+ PrefsHelper() = delete;
+
+ static bool GetVariantPref(nsIObserverService* aObsServ,
+ nsIWritableVariant* aVariant,
+ jni::Object::Param aPrefHandler,
+ const jni::String::LocalRef& aPrefName) {
+ if (NS_FAILED(aObsServ->NotifyObservers(aVariant, "android-get-pref",
+ aPrefName->ToString().get()))) {
+ return false;
+ }
+
+ uint16_t varType = aVariant->GetDataType();
+
+ int32_t type = java::PrefsHelper::PREF_INVALID;
+ bool boolVal = false;
+ int32_t intVal = 0;
+ nsAutoString strVal;
+
+ switch (varType) {
+ case nsIDataType::VTYPE_BOOL:
+ type = java::PrefsHelper::PREF_BOOL;
+ if (NS_FAILED(aVariant->GetAsBool(&boolVal))) {
+ return false;
+ }
+ break;
+ case nsIDataType::VTYPE_INT32:
+ type = java::PrefsHelper::PREF_INT;
+ if (NS_FAILED(aVariant->GetAsInt32(&intVal))) {
+ return false;
+ }
+ break;
+ case nsIDataType::VTYPE_ASTRING:
+ type = java::PrefsHelper::PREF_STRING;
+ if (NS_FAILED(aVariant->GetAsAString(strVal))) {
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+
+ jni::StringParam jstrVal(type == java::PrefsHelper::PREF_STRING
+ ? jni::StringParam(strVal, aPrefName.Env())
+ : jni::StringParam(nullptr));
+
+ if (aPrefHandler) {
+ java::PrefsHelper::CallPrefHandler(aPrefHandler, type, aPrefName, boolVal,
+ intVal, jstrVal);
+ } else {
+ java::PrefsHelper::OnPrefChange(aPrefName, type, boolVal, intVal,
+ jstrVal);
+ }
+ return true;
+ }
+
+ static bool SetVariantPref(nsIObserverService* aObsServ,
+ nsIWritableVariant* aVariant,
+ jni::String::Param aPrefName, bool aFlush,
+ int32_t aType, bool aBoolVal, int32_t aIntVal,
+ jni::String::Param aStrVal) {
+ nsresult rv = NS_ERROR_FAILURE;
+
+ switch (aType) {
+ case java::PrefsHelper::PREF_BOOL:
+ rv = aVariant->SetAsBool(aBoolVal);
+ break;
+ case java::PrefsHelper::PREF_INT:
+ rv = aVariant->SetAsInt32(aIntVal);
+ break;
+ case java::PrefsHelper::PREF_STRING:
+ rv = aVariant->SetAsAString(aStrVal->ToString());
+ break;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = aObsServ->NotifyObservers(aVariant, "android-set-pref",
+ aPrefName->ToString().get());
+ }
+
+ uint16_t varType = nsIDataType::VTYPE_EMPTY;
+ if (NS_SUCCEEDED(rv)) {
+ varType = aVariant->GetDataType();
+ }
+
+ // We use set-to-empty to signal the pref was handled.
+ const bool handled = varType == nsIDataType::VTYPE_EMPTY;
+
+ if (NS_SUCCEEDED(rv) && handled && aFlush) {
+ rv = Preferences::GetService()->SavePrefFile(nullptr);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ return handled;
+ }
+
+ NS_WARNING(
+ nsPrintfCString("Failed to set pref %s", aPrefName->ToCString().get())
+ .get());
+ // Pretend we handled the pref.
+ return true;
+ }
+
+ public:
+ static void GetPrefs(const jni::Class::LocalRef& aCls,
+ jni::ObjectArray::Param aPrefNames,
+ jni::Object::Param aPrefHandler) {
+ nsTArray<jni::Object::LocalRef> nameRefArray(aPrefNames->GetElements());
+ nsCOMPtr<nsIObserverService> obsServ;
+ nsCOMPtr<nsIWritableVariant> value;
+ nsAutoString strVal;
+
+ for (jni::Object::LocalRef& nameRef : nameRefArray) {
+ jni::String::LocalRef nameStr(std::move(nameRef));
+ const nsCString& name = nameStr->ToCString();
+
+ int32_t type = java::PrefsHelper::PREF_INVALID;
+ bool boolVal = false;
+ int32_t intVal = 0;
+ strVal.Truncate();
+
+ switch (Preferences::GetType(name.get())) {
+ case nsIPrefBranch::PREF_BOOL:
+ type = java::PrefsHelper::PREF_BOOL;
+ boolVal = Preferences::GetBool(name.get());
+ break;
+
+ case nsIPrefBranch::PREF_INT:
+ type = java::PrefsHelper::PREF_INT;
+ intVal = Preferences::GetInt(name.get());
+ break;
+
+ case nsIPrefBranch::PREF_STRING: {
+ type = java::PrefsHelper::PREF_STRING;
+ nsresult rv = Preferences::GetLocalizedString(name.get(), strVal);
+ if (NS_FAILED(rv)) {
+ Preferences::GetString(name.get(), strVal);
+ }
+ break;
+ }
+ default:
+ // Pref not found; try to find it.
+ if (!obsServ) {
+ obsServ = services::GetObserverService();
+ if (!obsServ) {
+ continue;
+ }
+ }
+ if (value) {
+ value->SetAsEmpty();
+ } else {
+ value = new nsVariant();
+ }
+ if (!GetVariantPref(obsServ, value, aPrefHandler, nameStr)) {
+ NS_WARNING(
+ nsPrintfCString("Failed to get pref %s", name.get()).get());
+ }
+ continue;
+ }
+
+ java::PrefsHelper::CallPrefHandler(
+ aPrefHandler, type, nameStr, boolVal, intVal,
+ jni::StringParam(type == java::PrefsHelper::PREF_STRING
+ ? jni::StringParam(strVal, aCls.Env())
+ : jni::StringParam(nullptr)));
+ }
+
+ java::PrefsHelper::CallPrefHandler(aPrefHandler,
+ java::PrefsHelper::PREF_FINISH, nullptr,
+ false, 0, nullptr);
+ }
+
+ static void SetPref(jni::String::Param aPrefName, bool aFlush, int32_t aType,
+ bool aBoolVal, int32_t aIntVal,
+ jni::String::Param aStrVal) {
+ const nsCString& name = aPrefName->ToCString();
+
+ if (Preferences::GetType(name.get()) == nsIPrefBranch::PREF_INVALID) {
+ // No pref; try asking first.
+ nsCOMPtr<nsIObserverService> obsServ = services::GetObserverService();
+ nsCOMPtr<nsIWritableVariant> value = new nsVariant();
+ if (obsServ && SetVariantPref(obsServ, value, aPrefName, aFlush, aType,
+ aBoolVal, aIntVal, aStrVal)) {
+ // The "pref" has changed; send a notification.
+ GetVariantPref(obsServ, value, nullptr,
+ jni::String::LocalRef(aPrefName));
+ return;
+ }
+ }
+
+ switch (aType) {
+ case java::PrefsHelper::PREF_BOOL:
+ Preferences::SetBool(name.get(), aBoolVal);
+ break;
+ case java::PrefsHelper::PREF_INT:
+ Preferences::SetInt(name.get(), aIntVal);
+ break;
+ case java::PrefsHelper::PREF_STRING:
+ Preferences::SetString(name.get(), aStrVal->ToString());
+ break;
+ default:
+ MOZ_ASSERT(false, "Invalid pref type");
+ }
+
+ if (aFlush) {
+ Preferences::GetService()->SavePrefFile(nullptr);
+ }
+ }
+
+ static void AddObserver(const jni::Class::LocalRef& aCls,
+ jni::ObjectArray::Param aPrefNames,
+ jni::Object::Param aPrefHandler,
+ jni::ObjectArray::Param aPrefsToObserve) {
+ // Call observer immediately with existing pref values.
+ GetPrefs(aCls, aPrefNames, aPrefHandler);
+
+ if (!aPrefsToObserve) {
+ return;
+ }
+
+ nsTArray<jni::Object::LocalRef> nameRefArray(
+ aPrefsToObserve->GetElements());
+ nsAppShell* const appShell = nsAppShell::Get();
+ MOZ_ASSERT(appShell);
+
+ for (jni::Object::LocalRef& nameRef : nameRefArray) {
+ jni::String::LocalRef nameStr(std::move(nameRef));
+ MOZ_ALWAYS_SUCCEEDS(
+ Preferences::AddStrongObserver(appShell, nameStr->ToCString()));
+ }
+ }
+
+ static void RemoveObserver(const jni::Class::LocalRef& aCls,
+ jni::ObjectArray::Param aPrefsToUnobserve) {
+ nsTArray<jni::Object::LocalRef> nameRefArray(
+ aPrefsToUnobserve->GetElements());
+ nsAppShell* const appShell = nsAppShell::Get();
+ MOZ_ASSERT(appShell);
+
+ for (jni::Object::LocalRef& nameRef : nameRefArray) {
+ jni::String::LocalRef nameStr(std::move(nameRef));
+ MOZ_ALWAYS_SUCCEEDS(
+ Preferences::RemoveObserver(appShell, nameStr->ToCString()));
+ }
+ }
+
+ static void OnPrefChange(const char16_t* aData) {
+ const nsCString& name = NS_LossyConvertUTF16toASCII(aData);
+
+ int32_t type = -1;
+ bool boolVal = false;
+ int32_t intVal = false;
+ nsAutoString strVal;
+
+ switch (Preferences::GetType(name.get())) {
+ case nsIPrefBranch::PREF_BOOL:
+ type = java::PrefsHelper::PREF_BOOL;
+ boolVal = Preferences::GetBool(name.get());
+ break;
+ case nsIPrefBranch::PREF_INT:
+ type = java::PrefsHelper::PREF_INT;
+ intVal = Preferences::GetInt(name.get());
+ break;
+ case nsIPrefBranch::PREF_STRING: {
+ type = java::PrefsHelper::PREF_STRING;
+ nsresult rv = Preferences::GetLocalizedString(name.get(), strVal);
+ if (NS_FAILED(rv)) {
+ Preferences::GetString(name.get(), strVal);
+ }
+ break;
+ }
+ default:
+ NS_WARNING(nsPrintfCString("Invalid pref %s", name.get()).get());
+ return;
+ }
+
+ java::PrefsHelper::OnPrefChange(
+ name, type, boolVal, intVal,
+ jni::StringParam(type == java::PrefsHelper::PREF_STRING
+ ? jni::StringParam(strVal)
+ : jni::StringParam(nullptr)));
+ }
+};
+
+} // namespace mozilla
+
+#endif // PrefsHelper_h
diff --git a/widget/android/ScreenHelperAndroid.cpp b/widget/android/ScreenHelperAndroid.cpp
new file mode 100644
index 0000000000..4242bfcdaa
--- /dev/null
+++ b/widget/android/ScreenHelperAndroid.cpp
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set sw=2 ts=4 expandtab:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "ScreenHelperAndroid.h"
+#include "AndroidRect.h"
+#include "nsThreadUtils.h"
+
+#include <mozilla/jni/Refs.h>
+
+#include "mozilla/Atomics.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+#include "mozilla/java/ScreenManagerHelperNatives.h"
+#include "mozilla/widget/ScreenManager.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+static ScreenHelperAndroid* gHelper = nullptr;
+
+class ScreenHelperAndroid::ScreenHelperSupport final
+ : public java::ScreenManagerHelper::Natives<ScreenHelperSupport> {
+ public:
+ typedef java::ScreenManagerHelper::Natives<ScreenHelperSupport> Base;
+
+ static void RefreshScreenInfo() { gHelper->Refresh(); }
+
+ static int32_t AddDisplay(int32_t aDisplayType, int32_t aWidth,
+ int32_t aHeight, float aDensity) {
+ static Atomic<uint32_t> nextId;
+
+ uint32_t screenId = ++nextId;
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction(
+ "ScreenHelperAndroid::ScreenHelperSupport::AddDisplay",
+ [aDisplayType, aWidth, aHeight, aDensity, screenId] {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ gHelper->AddScreen(
+ screenId, static_cast<DisplayType>(aDisplayType),
+ LayoutDeviceIntRect(0, 0, aWidth, aHeight), aDensity);
+ })
+ .take());
+ return screenId;
+ }
+
+ static void RemoveDisplay(int32_t aScreenId) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction(
+ "ScreenHelperAndroid::ScreenHelperSupport::RemoveDisplay",
+ [aScreenId] {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ gHelper->RemoveScreen(aScreenId);
+ })
+ .take());
+ }
+};
+
+static already_AddRefed<Screen> MakePrimaryScreen() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ java::sdk::Rect::LocalRef rect = java::GeckoAppShell::GetScreenSize();
+ LayoutDeviceIntRect bounds = LayoutDeviceIntRect(
+ rect->Left(), rect->Top(), rect->Width(), rect->Height());
+ uint32_t depth = java::GeckoAppShell::GetScreenDepth();
+ float density = java::GeckoAppShell::GetDensity();
+ float dpi = java::GeckoAppShell::GetDpi();
+ RefPtr<Screen> screen = new Screen(bounds, bounds, depth, depth,
+ DesktopToLayoutDeviceScale(density),
+ CSSToLayoutDeviceScale(1.0f), dpi);
+ return screen.forget();
+}
+
+ScreenHelperAndroid::ScreenHelperAndroid() {
+ MOZ_ASSERT(!gHelper);
+ gHelper = this;
+
+ ScreenHelperSupport::Base::Init();
+
+ Refresh();
+}
+
+ScreenHelperAndroid::~ScreenHelperAndroid() { gHelper = nullptr; }
+
+/* static */
+ScreenHelperAndroid* ScreenHelperAndroid::GetSingleton() { return gHelper; }
+
+void ScreenHelperAndroid::Refresh() {
+ mScreens.Remove(0);
+
+ AutoTArray<RefPtr<Screen>, 1> screenList;
+ RefPtr<Screen> screen = MakePrimaryScreen();
+ if (screen) {
+ mScreens.Put(0, screen);
+ }
+
+ for (auto iter = mScreens.ConstIter(); !iter.Done(); iter.Next()) {
+ screenList.AppendElement(iter.Data());
+ }
+
+ ScreenManager& manager = ScreenManager::GetSingleton();
+ manager.Refresh(std::move(screenList));
+}
+
+void ScreenHelperAndroid::AddScreen(uint32_t aScreenId,
+ DisplayType aDisplayType,
+ LayoutDeviceIntRect aRect, float aDensity) {
+ MOZ_ASSERT(aScreenId > 0);
+ MOZ_ASSERT(!mScreens.Get(aScreenId, nullptr));
+
+ RefPtr<Screen> screen =
+ new Screen(aRect, aRect, 24, 24, DesktopToLayoutDeviceScale(aDensity),
+ CSSToLayoutDeviceScale(1.0f), 160.0f);
+
+ mScreens.Put(aScreenId, screen);
+ Refresh();
+}
+
+void ScreenHelperAndroid::RemoveScreen(uint32_t aScreenId) {
+ mScreens.Remove(aScreenId);
+ Refresh();
+}
+
+already_AddRefed<Screen> ScreenHelperAndroid::ScreenForId(uint32_t aScreenId) {
+ RefPtr<Screen> screen = mScreens.Get(aScreenId);
+ return screen.forget();
+}
diff --git a/widget/android/ScreenHelperAndroid.h b/widget/android/ScreenHelperAndroid.h
new file mode 100644
index 0000000000..96c34d4b52
--- /dev/null
+++ b/widget/android/ScreenHelperAndroid.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: ts=4 sw=2 expandtab:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 ScreenHelperAndroid_h___
+#define ScreenHelperAndroid_h___
+
+#include "mozilla/widget/ScreenManager.h"
+#include "nsDataHashtable.h"
+
+namespace mozilla {
+namespace widget {
+
+class ScreenHelperAndroid final : public ScreenManager::Helper {
+ public:
+ class ScreenHelperSupport;
+
+ ScreenHelperAndroid();
+ ~ScreenHelperAndroid();
+
+ static ScreenHelperAndroid* GetSingleton();
+
+ void Refresh();
+
+ void AddScreen(uint32_t aScreenId, DisplayType aDisplayType,
+ LayoutDeviceIntRect aRect = LayoutDeviceIntRect(),
+ float aDensity = 1.0f);
+ void RemoveScreen(uint32_t aId);
+ already_AddRefed<Screen> ScreenForId(uint32_t aScreenId);
+
+ private:
+ nsDataHashtable<nsUint32HashKey, RefPtr<Screen>> mScreens;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* ScreenHelperAndroid_h___ */
diff --git a/widget/android/Telemetry.h b/widget/android/Telemetry.h
new file mode 100644
index 0000000000..3e86ba8278
--- /dev/null
+++ b/widget/android/Telemetry.h
@@ -0,0 +1,86 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 mozilla_widget_Telemetry_h__
+#define mozilla_widget_Telemetry_h__
+
+#include "mozilla/java/TelemetryUtilsNatives.h"
+#include "nsAppShell.h"
+#include "nsIAndroidBridge.h"
+
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace widget {
+
+class Telemetry final : public java::TelemetryUtils::Natives<Telemetry> {
+ Telemetry() = delete;
+
+ static already_AddRefed<nsIUITelemetryObserver> GetObserver() {
+ nsAppShell* const appShell = nsAppShell::Get();
+ if (!appShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIAndroidBrowserApp> browserApp = appShell->GetBrowserApp();
+ nsCOMPtr<nsIUITelemetryObserver> obs;
+
+ if (!browserApp ||
+ NS_FAILED(browserApp->GetUITelemetryObserver(getter_AddRefs(obs))) ||
+ !obs) {
+ return nullptr;
+ }
+
+ return obs.forget();
+ }
+
+ public:
+ static void AddHistogram(jni::String::Param aName, int32_t aValue) {
+ MOZ_ASSERT(aName);
+ mozilla::Telemetry::Accumulate(aName->ToCString().get(), aValue);
+ }
+
+ static void AddKeyedHistogram(jni::String::Param aName,
+ jni::String::Param aKey, int32_t aValue) {
+ MOZ_ASSERT(aName && aKey);
+ mozilla::Telemetry::Accumulate(aName->ToCString().get(), aKey->ToCString(),
+ aValue);
+ }
+
+ static void StartUISession(jni::String::Param aName, int64_t aTimestamp) {
+ MOZ_ASSERT(aName);
+ nsCOMPtr<nsIUITelemetryObserver> obs = GetObserver();
+ if (obs) {
+ obs->StartSession(aName->ToString().get(), aTimestamp);
+ }
+ }
+
+ static void StopUISession(jni::String::Param aName,
+ jni::String::Param aReason, int64_t aTimestamp) {
+ MOZ_ASSERT(aName);
+ nsCOMPtr<nsIUITelemetryObserver> obs = GetObserver();
+ if (obs) {
+ obs->StopSession(aName->ToString().get(),
+ aReason ? aReason->ToString().get() : nullptr,
+ aTimestamp);
+ }
+ }
+
+ static void AddUIEvent(jni::String::Param aAction, jni::String::Param aMethod,
+ int64_t aTimestamp, jni::String::Param aExtras) {
+ MOZ_ASSERT(aAction);
+ nsCOMPtr<nsIUITelemetryObserver> obs = GetObserver();
+ if (obs) {
+ obs->AddEvent(aAction->ToString().get(),
+ aMethod ? aMethod->ToString().get() : nullptr, aTimestamp,
+ aExtras ? aExtras->ToString().get() : nullptr);
+ }
+ }
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_Telemetry_h__
diff --git a/widget/android/WebAuthnTokenManager.h b/widget/android/WebAuthnTokenManager.h
new file mode 100644
index 0000000000..046e6eba80
--- /dev/null
+++ b/widget/android/WebAuthnTokenManager.h
@@ -0,0 +1,79 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 WebAuthnTokenManager_h
+#define WebAuthnTokenManager_h
+
+#include "mozilla/dom/AndroidWebAuthnTokenManager.h"
+
+#include "mozilla/java/WebAuthnTokenManagerNatives.h"
+
+namespace mozilla {
+class WebAuthnTokenManager final
+ : public java::WebAuthnTokenManager::Natives<WebAuthnTokenManager> {
+ public:
+ static void WebAuthnMakeCredentialFinish(
+ jni::ByteArray::Param aClientDataJson, jni::ByteArray::Param aKeyHandle,
+ jni::ByteArray::Param aAttestationObject) {
+ mozilla::dom::AndroidWebAuthnResult result;
+
+ result.mClientDataJSON.Assign(
+ reinterpret_cast<const char*>(
+ aClientDataJson->GetElements().Elements()),
+ aClientDataJson->Length());
+ result.mKeyHandle.Assign(
+ reinterpret_cast<uint8_t*>(aKeyHandle->GetElements().Elements()),
+ aKeyHandle->Length());
+ result.mAttObj.Assign(reinterpret_cast<uint8_t*>(
+ aAttestationObject->GetElements().Elements()),
+ aAttestationObject->Length());
+
+ mozilla::dom::AndroidWebAuthnTokenManager::GetInstance()
+ ->HandleRegisterResult(std::move(result));
+ }
+
+ static void WebAuthnMakeCredentialReturnError(jni::String::Param aErrorCode) {
+ mozilla::dom::AndroidWebAuthnResult result(aErrorCode->ToString());
+ mozilla::dom::AndroidWebAuthnTokenManager::GetInstance()
+ ->HandleRegisterResult(std::move(result));
+ }
+
+ static void WebAuthnGetAssertionFinish(jni::ByteArray::Param aClientDataJson,
+ jni::ByteArray::Param aKeyHandle,
+ jni::ByteArray::Param aAuthData,
+ jni::ByteArray::Param aSignature,
+ jni::ByteArray::Param aUserHandle) {
+ mozilla::dom::AndroidWebAuthnResult result;
+
+ result.mClientDataJSON.Assign(
+ reinterpret_cast<const char*>(
+ aClientDataJson->GetElements().Elements()),
+ aClientDataJson->Length());
+ result.mKeyHandle.Assign(
+ reinterpret_cast<uint8_t*>(aKeyHandle->GetElements().Elements()),
+ aKeyHandle->Length());
+ result.mAuthData.Assign(
+ reinterpret_cast<uint8_t*>(aAuthData->GetElements().Elements()),
+ aAuthData->Length());
+ result.mSignature.Assign(
+ reinterpret_cast<uint8_t*>(aSignature->GetElements().Elements()),
+ aSignature->Length());
+ result.mUserHandle.Assign(
+ reinterpret_cast<uint8_t*>(aUserHandle->GetElements().Elements()),
+ aUserHandle->Length());
+
+ mozilla::dom::AndroidWebAuthnTokenManager::GetInstance()->HandleSignResult(
+ std::move(result));
+ }
+
+ static void WebAuthnGetAssertionReturnError(jni::String::Param aErrorCode) {
+ mozilla::dom::AndroidWebAuthnResult result(aErrorCode->ToString());
+ mozilla::dom::AndroidWebAuthnTokenManager::GetInstance()->HandleSignResult(
+ std::move(result));
+ }
+};
+} // namespace mozilla
+
+#endif
diff --git a/widget/android/WebExecutorSupport.cpp b/widget/android/WebExecutorSupport.cpp
new file mode 100644
index 0000000000..ac498da4fd
--- /dev/null
+++ b/widget/android/WebExecutorSupport.cpp
@@ -0,0 +1,460 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 <algorithm>
+
+#include "WebExecutorSupport.h"
+
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsINSSErrorsService.h"
+#include "nsIUploadChannel2.h"
+#include "nsIX509Cert.h"
+
+#include "nsIDNSService.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+
+#include "mozilla/java/GeckoWebExecutorWrappers.h"
+#include "mozilla/java/WebMessageWrappers.h"
+#include "mozilla/java/WebRequestErrorWrappers.h"
+#include "mozilla/java/WebResponseWrappers.h"
+#include "mozilla/net/DNS.h" // for NetAddr
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/Preferences.h"
+#include "GeckoViewStreamListener.h"
+#include "nsIPrivateBrowsingChannel.h"
+
+#include "nsNetUtil.h" // for NS_NewURI, NS_NewChannel, NS_NewStreamLoader
+
+#include "InetAddress.h" // for java::sdk::InetAddress and java::sdk::UnknownHostException
+#include "ReferrerInfo.h"
+
+namespace mozilla {
+using namespace net;
+
+namespace widget {
+
+static void CompleteWithError(java::GeckoResult::Param aResult,
+ nsresult aStatus, nsIChannel* aChannel) {
+ nsCOMPtr<nsINSSErrorsService> errSvc =
+ do_GetService("@mozilla.org/nss_errors_service;1");
+ MOZ_ASSERT(errSvc);
+
+ uint32_t errorClass;
+ nsresult rv = errSvc->GetErrorClass(aStatus, &errorClass);
+ if (NS_FAILED(rv)) {
+ errorClass = 0;
+ }
+
+ jni::ByteArray::LocalRef certBytes;
+ if (aChannel) {
+ std::tie(certBytes, std::ignore) =
+ GeckoViewStreamListener::CertificateFromChannel(aChannel);
+ }
+
+ java::WebRequestError::LocalRef error = java::WebRequestError::FromGeckoError(
+ int64_t(aStatus), NS_ERROR_GET_MODULE(aStatus), errorClass, certBytes);
+
+ aResult->CompleteExceptionally(error.Cast<jni::Throwable>());
+}
+
+static void CompleteWithError(java::GeckoResult::Param aResult,
+ nsresult aStatus) {
+ CompleteWithError(aResult, aStatus, nullptr);
+}
+
+class ByteBufferStream final : public nsIInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit ByteBufferStream(jni::ByteBuffer::Param buffer)
+ : mBuffer(buffer), mPosition(0), mClosed(false) {
+ MOZ_ASSERT(mBuffer);
+ MOZ_ASSERT(mBuffer->Address());
+ }
+
+ NS_IMETHOD
+ Close() override {
+ mClosed = true;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ Available(uint64_t* aResult) override {
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ *aResult = (mBuffer->Capacity() - mPosition);
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ Read(char* aBuf, uint32_t aCount, uint32_t* aCountRead) override {
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ *aCountRead = uint32_t(
+ std::min(uint64_t(mBuffer->Capacity() - mPosition), uint64_t(aCount)));
+
+ if (*aCountRead > 0) {
+ memcpy(aBuf, (char*)mBuffer->Address() + mPosition, *aCountRead);
+ mPosition += *aCountRead;
+ }
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount,
+ uint32_t* aResult) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD
+ IsNonBlocking(bool* aResult) override {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ protected:
+ virtual ~ByteBufferStream() {}
+
+ const jni::ByteBuffer::GlobalRef mBuffer;
+ uint64_t mPosition;
+ bool mClosed;
+};
+
+NS_IMPL_ISUPPORTS(ByteBufferStream, nsIInputStream)
+
+class LoaderListener final : public GeckoViewStreamListener {
+ public:
+ explicit LoaderListener(java::GeckoResult::Param aResult,
+ bool aAllowRedirects, bool testStreamFailure)
+ : GeckoViewStreamListener(),
+ mResult(aResult),
+ mTestStreamFailure(testStreamFailure),
+ mAllowRedirects(aAllowRedirects) {
+ MOZ_ASSERT(mResult);
+ }
+
+ NS_IMETHOD
+ OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) override {
+ MOZ_ASSERT(mStream);
+
+ if (mTestStreamFailure) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // We only need this for the ReadSegments call, the value is unused.
+ uint32_t countRead;
+ nsresult rv =
+ aInputStream->ReadSegments(WriteSegment, this, aCount, &countRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+ }
+
+ NS_IMETHOD
+ AsyncOnChannelRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel,
+ uint32_t flags,
+ nsIAsyncVerifyRedirectCallback* callback) override {
+ if (!mAllowRedirects) {
+ return NS_ERROR_ABORT;
+ }
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+ }
+
+ void SendWebResponse(java::WebResponse::Param aResponse) override {
+ mResult->Complete(aResponse);
+ }
+
+ void CompleteWithError(nsresult aStatus, nsIChannel* aChannel) override {
+ ::CompleteWithError(mResult, aStatus, aChannel);
+ }
+
+ virtual ~LoaderListener() {}
+
+ const java::GeckoResult::GlobalRef mResult;
+ const bool mTestStreamFailure;
+ bool mAllowRedirects;
+};
+
+class DNSListener final : public nsIDNSListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ DNSListener(const nsCString& aHost, java::GeckoResult::Param aResult)
+ : mHost(aHost), mResult(aResult) {}
+
+ NS_IMETHOD
+ OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRecord,
+ nsresult aStatus) override {
+ if (NS_FAILED(aStatus)) {
+ CompleteUnknownHostError();
+ return NS_OK;
+ }
+
+ nsresult rv = CompleteWithRecord(aRecord);
+ if (NS_FAILED(rv)) {
+ CompleteUnknownHostError();
+ return NS_OK;
+ }
+
+ return NS_OK;
+ }
+
+ void CompleteUnknownHostError() {
+ java::sdk::UnknownHostException::LocalRef error =
+ java::sdk::UnknownHostException::New();
+ mResult->CompleteExceptionally(error.Cast<jni::Throwable>());
+ }
+
+ private:
+ nsresult CompleteWithRecord(nsIDNSRecord* aRecord) {
+ nsTArray<NetAddr> addrs;
+ nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(aRecord);
+ if (!rec) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsresult rv = rec->GetAddresses(addrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ jni::ByteArray::LocalRef bytes;
+ auto objects =
+ jni::ObjectArray::New<java::sdk::InetAddress>(addrs.Length());
+ for (size_t i = 0; i < addrs.Length(); i++) {
+ const auto& addr = addrs[i];
+ if (addr.raw.family == AF_INET) {
+ bytes = jni::ByteArray::New(
+ reinterpret_cast<const int8_t*>(&addr.inet.ip), 4);
+ } else if (addr.raw.family == AF_INET6) {
+ bytes = jni::ByteArray::New(
+ reinterpret_cast<const int8_t*>(&addr.inet6.ip), 16);
+ } else {
+ // We don't handle this, skip it.
+ continue;
+ }
+
+ objects->SetElement(i,
+ java::sdk::InetAddress::GetByAddress(mHost, bytes));
+ }
+
+ mResult->Complete(objects);
+ return NS_OK;
+ }
+
+ virtual ~DNSListener() {}
+
+ const nsCString mHost;
+ const java::GeckoResult::GlobalRef mResult;
+};
+
+NS_IMPL_ISUPPORTS(DNSListener, nsIDNSListener)
+
+static nsresult ConvertCacheMode(int32_t mode, int32_t& result) {
+ switch (mode) {
+ case java::WebRequest::CACHE_MODE_DEFAULT:
+ result = nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT;
+ break;
+ case java::WebRequest::CACHE_MODE_NO_STORE:
+ result = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE;
+ break;
+ case java::WebRequest::CACHE_MODE_RELOAD:
+ result = nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD;
+ break;
+ case java::WebRequest::CACHE_MODE_NO_CACHE:
+ result = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE;
+ break;
+ case java::WebRequest::CACHE_MODE_FORCE_CACHE:
+ result = nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE;
+ break;
+ case java::WebRequest::CACHE_MODE_ONLY_IF_CACHED:
+ result = nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED;
+ break;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+static nsresult SetupHttpChannel(nsIHttpChannel* aHttpChannel,
+ nsIChannel* aChannel,
+ java::WebRequest::Param aRequest) {
+ const auto req = java::WebRequest::LocalRef(aRequest);
+ const auto reqBase = java::WebMessage::LocalRef(req.Cast<java::WebMessage>());
+
+ // Method
+ nsresult rv = aHttpChannel->SetRequestMethod(aRequest->Method()->ToCString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Headers
+ const auto keys = reqBase->GetHeaderKeys();
+ const auto values = reqBase->GetHeaderValues();
+ nsCString contentType;
+ for (size_t i = 0; i < keys->Length(); i++) {
+ const auto key = jni::String::LocalRef(keys->GetElement(i))->ToCString();
+ const auto value =
+ jni::String::LocalRef(values->GetElement(i))->ToCString();
+
+ if (key.LowerCaseEqualsASCII("content-type")) {
+ contentType = value;
+ }
+
+ // We clobber any duplicate keys here because we've already merged them
+ // in the upstream WebRequest.
+ rv = aHttpChannel->SetRequestHeader(key, value, false /* merge */);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Body
+ const auto body = req->Body();
+ if (body) {
+ nsCOMPtr<nsIInputStream> stream = new ByteBufferStream(body);
+
+ nsCOMPtr<nsIUploadChannel2> uploadChannel(do_QueryInterface(aChannel, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = uploadChannel->ExplicitSetUploadStream(
+ stream, contentType, -1, aRequest->Method()->ToCString(), false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Referrer
+ RefPtr<nsIURI> referrerUri;
+ const auto referrer = req->Referrer();
+ if (referrer) {
+ rv = NS_NewURI(getter_AddRefs(referrerUri), referrer->ToString());
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
+ }
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new dom::ReferrerInfo(referrerUri);
+ rv = aHttpChannel->SetReferrerInfoWithoutClone(referrerInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Cache mode
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel(
+ do_QueryInterface(aChannel, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t cacheMode;
+ rv = ConvertCacheMode(req->CacheMode(), cacheMode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = internalChannel->SetFetchCacheMode(cacheMode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We don't have any UI
+ rv = internalChannel->SetBlockAuthPrompt(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult WebExecutorSupport::CreateStreamLoader(
+ java::WebRequest::Param aRequest, int32_t aFlags,
+ java::GeckoResult::Param aResult) {
+ const auto req = java::WebRequest::LocalRef(aRequest);
+ const auto reqBase = java::WebMessage::LocalRef(req.Cast<java::WebMessage>());
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), reqBase->Uri()->ToString());
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aFlags & java::GeckoWebExecutor::FETCH_FLAGS_ANONYMOUS) {
+ channel->SetLoadFlags(nsIRequest::LOAD_ANONYMOUS);
+ }
+
+ if (aFlags & java::GeckoWebExecutor::FETCH_FLAGS_PRIVATE) {
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(channel);
+ NS_ENSURE_TRUE(pbChannel, NS_ERROR_FAILURE);
+ pbChannel->SetPrivate(true);
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ CookieJarSettings::Create();
+ MOZ_ASSERT(cookieJarSettings);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ loadInfo->SetCookieJarSettings(cookieJarSettings);
+
+ // setup http/https specific things
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel, &rv));
+ if (httpChannel) {
+ rv = SetupHttpChannel(httpChannel, channel, aRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // set up the listener
+ const bool allowRedirects =
+ !(aFlags & java::GeckoWebExecutor::FETCH_FLAGS_NO_REDIRECTS);
+ const bool testStreamFailure =
+ (aFlags & java::GeckoWebExecutor::FETCH_FLAGS_STREAM_FAILURE_TEST);
+
+ RefPtr<LoaderListener> listener =
+ new LoaderListener(aResult, allowRedirects, testStreamFailure);
+
+ rv = channel->SetNotificationCallbacks(listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, open the channel
+ rv = channel->AsyncOpen(listener);
+
+ return NS_OK;
+}
+
+void WebExecutorSupport::Fetch(jni::Object::Param aRequest, int32_t aFlags,
+ jni::Object::Param aResult) {
+ const auto request = java::WebRequest::LocalRef(aRequest);
+ auto result = java::GeckoResult::LocalRef(aResult);
+
+ nsresult rv = CreateStreamLoader(request, aFlags, result);
+ if (NS_FAILED(rv)) {
+ CompleteWithError(result, rv);
+ }
+}
+
+static nsresult ResolveHost(nsCString& host, java::GeckoResult::Param result) {
+ nsresult rv;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICancelable> cancelable;
+ RefPtr<DNSListener> listener = new DNSListener(host, result);
+ rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT, 0,
+ nullptr, listener, nullptr /* aListenerTarget */,
+ OriginAttributes(), getter_AddRefs(cancelable));
+ return rv;
+}
+
+void WebExecutorSupport::Resolve(jni::String::Param aUri,
+ jni::Object::Param aResult) {
+ auto result = java::GeckoResult::LocalRef(aResult);
+
+ nsCString uri = aUri->ToCString();
+ nsresult rv = ResolveHost(uri, result);
+ if (NS_FAILED(rv)) {
+ java::sdk::UnknownHostException::LocalRef error =
+ java::sdk::UnknownHostException::New();
+ result->CompleteExceptionally(error.Cast<jni::Throwable>());
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/android/WebExecutorSupport.h b/widget/android/WebExecutorSupport.h
new file mode 100644
index 0000000000..65a68fcc40
--- /dev/null
+++ b/widget/android/WebExecutorSupport.h
@@ -0,0 +1,32 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 WebExecutorSupport_h__
+#define WebExecutorSupport_h__
+
+#include "mozilla/java/GeckoWebExecutorNatives.h"
+#include "mozilla/java/GeckoResultWrappers.h"
+#include "mozilla/java/WebRequestWrappers.h"
+
+namespace mozilla {
+namespace widget {
+
+class WebExecutorSupport final
+ : public java::GeckoWebExecutor::Natives<WebExecutorSupport> {
+ public:
+ static void Fetch(jni::Object::Param request, int32_t flags,
+ jni::Object::Param result);
+ static void Resolve(jni::String::Param aUri, jni::Object::Param result);
+
+ protected:
+ static nsresult CreateStreamLoader(java::WebRequest::Param aRequest,
+ int32_t aFlags,
+ java::GeckoResult::Param aResult);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // WebExecutorSupport_h__
diff --git a/widget/android/WindowEvent.h b/widget/android/WindowEvent.h
new file mode 100644
index 0000000000..fdb73bf692
--- /dev/null
+++ b/widget/android/WindowEvent.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_WindowEvent_h
+#define mozilla_widget_WindowEvent_h
+
+#include "nsThreadUtils.h"
+#include "mozilla/jni/Natives.h"
+
+namespace mozilla {
+namespace widget {
+
+// An Event subclass that guards against stale events.
+// (See the implmentation of mozilla::jni::detail::ProxyNativeCall for info
+// about the default template parameters for this class)
+template <typename Lambda, bool IsStatic = Lambda::isStatic,
+ typename InstanceType = typename Lambda::ThisArgType,
+ class Impl = typename Lambda::TargetClass>
+class WindowEvent : public Runnable {
+ bool IsStaleCall() {
+ if (IsStatic) {
+ // Static calls are never stale.
+ return false;
+ }
+
+ return jni::NativePtrTraits<Impl>::IsStale(mInstance);
+ }
+
+ Lambda mLambda;
+ const InstanceType mInstance;
+
+ public:
+ WindowEvent(Lambda&& aLambda, InstanceType&& aInstance)
+ : Runnable("mozilla::widget::WindowEvent"),
+ mLambda(std::move(aLambda)),
+ mInstance(std::forward<InstanceType>(aInstance)) {}
+
+ explicit WindowEvent(Lambda&& aLambda)
+ : Runnable("mozilla::widget::WindowEvent"),
+ mLambda(std::move(aLambda)),
+ mInstance(mLambda.GetThisArg()) {}
+
+ NS_IMETHOD Run() override {
+ if (!IsStaleCall()) {
+ mLambda();
+ }
+ return NS_OK;
+ }
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_WindowEvent_h
diff --git a/widget/android/bindings/AccessibilityEvent-classes.txt b/widget/android/bindings/AccessibilityEvent-classes.txt
new file mode 100644
index 0000000000..b54e3ce105
--- /dev/null
+++ b/widget/android/bindings/AccessibilityEvent-classes.txt
@@ -0,0 +1,3 @@
+# We only use constants from AccessibilityEvent
+[android.view.accessibility.AccessibilityEvent = skip:true]
+<field> = skip:false
diff --git a/widget/android/bindings/AndroidBuild-classes.txt b/widget/android/bindings/AndroidBuild-classes.txt
new file mode 100644
index 0000000000..a76aa12c66
--- /dev/null
+++ b/widget/android/bindings/AndroidBuild-classes.txt
@@ -0,0 +1,5 @@
+[android.os.Build]
+<field> = noLiteral:true
+
+[android.os.Build$VERSION]
+<field> = noLiteral:true
diff --git a/widget/android/bindings/AndroidGraphics-classes.txt b/widget/android/bindings/AndroidGraphics-classes.txt
new file mode 100644
index 0000000000..452ba404e8
--- /dev/null
+++ b/widget/android/bindings/AndroidGraphics-classes.txt
@@ -0,0 +1,10 @@
+[android.graphics.Bitmap = skip:true]
+copyPixelsFromBuffer(Ljava/nio/Buffer;)V =
+createBitmap(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap; =
+
+[android.graphics.Bitmap$Config = skip:true]
+valueOf(Ljava/lang/String;)Landroid/graphics/Bitmap$Config; =
+ALPHA_8 =
+ARGB_8888 =
+RGBA_F16 =
+RGB_565 = \ No newline at end of file
diff --git a/widget/android/bindings/AndroidInputType-classes.txt b/widget/android/bindings/AndroidInputType-classes.txt
new file mode 100644
index 0000000000..fa1138dce4
--- /dev/null
+++ b/widget/android/bindings/AndroidInputType-classes.txt
@@ -0,0 +1,3 @@
+# We only use constants from InputType
+[android.text.InputType = skip:true]
+<field> = skip:false
diff --git a/widget/android/bindings/AndroidRect-classes.txt b/widget/android/bindings/AndroidRect-classes.txt
new file mode 100644
index 0000000000..76d3094a9f
--- /dev/null
+++ b/widget/android/bindings/AndroidRect-classes.txt
@@ -0,0 +1,2 @@
+[android.graphics.Rect]
+[android.graphics.RectF]
diff --git a/widget/android/bindings/InetAddress-classes.txt b/widget/android/bindings/InetAddress-classes.txt
new file mode 100644
index 0000000000..65788be252
--- /dev/null
+++ b/widget/android/bindings/InetAddress-classes.txt
@@ -0,0 +1,6 @@
+# We only want getByAddress(String, byte[])
+[java.net.InetAddress = skip:true]
+getByAddress(Ljava/lang/String;[B)Ljava/net/InetAddress; = skip:false
+
+[java.net.UnknownHostException = skip:true]
+<init>()V =
diff --git a/widget/android/bindings/JavaBuiltins-classes.txt b/widget/android/bindings/JavaBuiltins-classes.txt
new file mode 100644
index 0000000000..c6be5dde34
--- /dev/null
+++ b/widget/android/bindings/JavaBuiltins-classes.txt
@@ -0,0 +1,25 @@
+[java.lang.Boolean = skip:true]
+# Use static fields for boxing boolean.
+TRUE =
+FALSE =
+booleanValue =
+
+[java.lang.Double = skip:true]
+<init>(D)V =
+
+[java.lang.Integer = skip:true]
+# Use valueOf() for boxing int; don't use constructor
+# because some Integer values are cached.
+valueOf(I)Ljava/lang/Integer; =
+
+[java.lang.Long = skip:true]
+valueOf(J)Ljava/lang/Long; =
+
+[java.lang.Number = skip:true]
+# Use doubleValue() for unboxing Double/Float/Long.
+doubleValue =
+# Use intValue() for unboxing Byte/Int/Short.
+intValue =
+
+[java.lang.String = skip:true]
+valueOf(Ljava/lang/Object;)Ljava/lang/String; =
diff --git a/widget/android/bindings/JavaExceptions-classes.txt b/widget/android/bindings/JavaExceptions-classes.txt
new file mode 100644
index 0000000000..fbe98b278e
--- /dev/null
+++ b/widget/android/bindings/JavaExceptions-classes.txt
@@ -0,0 +1,5 @@
+[java.lang.IllegalStateException = skip:true]
+<init>(Ljava/lang/String;)V =
+
+[java.lang.IllegalArgumentException = skip:true]
+<init>(Ljava/lang/String;)V =
diff --git a/widget/android/bindings/KeyEvent-classes.txt b/widget/android/bindings/KeyEvent-classes.txt
new file mode 100644
index 0000000000..6001a5025b
--- /dev/null
+++ b/widget/android/bindings/KeyEvent-classes.txt
@@ -0,0 +1,3 @@
+# We only use constants from KeyEvent
+[android.view.KeyEvent = skip:true]
+<field> = skip:false
diff --git a/widget/android/bindings/MediaCodec-classes.txt b/widget/android/bindings/MediaCodec-classes.txt
new file mode 100644
index 0000000000..81b02a66bb
--- /dev/null
+++ b/widget/android/bindings/MediaCodec-classes.txt
@@ -0,0 +1,9 @@
+[android.media.MediaCodec = exceptionMode:nsresult]
+[android.media.MediaCodec$BufferInfo = exceptionMode:nsresult]
+[android.media.MediaCodec$CryptoInfo = exceptionMode:nsresult]
+
+# We only use constants from KeyStatus
+[android.media.MediaDrm$KeyStatus = skip:true]
+<field> = skip:false
+
+[android.media.MediaFormat = exceptionMode:nsresult]
diff --git a/widget/android/bindings/MotionEvent-classes.txt b/widget/android/bindings/MotionEvent-classes.txt
new file mode 100644
index 0000000000..17874a16af
--- /dev/null
+++ b/widget/android/bindings/MotionEvent-classes.txt
@@ -0,0 +1,3 @@
+# We only use constants from MotionEvent
+[android.view.MotionEvent = skip:true]
+<field> = skip:false
diff --git a/widget/android/bindings/SurfaceTexture-classes.txt b/widget/android/bindings/SurfaceTexture-classes.txt
new file mode 100644
index 0000000000..782dee0836
--- /dev/null
+++ b/widget/android/bindings/SurfaceTexture-classes.txt
@@ -0,0 +1,2 @@
+[android.graphics.SurfaceTexture = exceptionMode:nsresult]
+[android.view.Surface = exceptionMode:nsresult]
diff --git a/widget/android/bindings/ViewConfiguration-classes.txt b/widget/android/bindings/ViewConfiguration-classes.txt
new file mode 100644
index 0000000000..cf8689f25a
--- /dev/null
+++ b/widget/android/bindings/ViewConfiguration-classes.txt
@@ -0,0 +1 @@
+[android.view.ViewConfiguration = exceptionMode:nsresult]
diff --git a/widget/android/bindings/moz.build b/widget/android/bindings/moz.build
new file mode 100644
index 0000000000..650cd8637c
--- /dev/null
+++ b/widget/android/bindings/moz.build
@@ -0,0 +1,53 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("GeckoView", "General")
+
+# List of stems to generate .cpp and .h files for. To add a stem, add it to
+# this list and ensure that $(stem)-classes.txt exists in this directory.
+generated = [
+ "AccessibilityEvent",
+ "AndroidBuild",
+ "AndroidGraphics",
+ "AndroidInputType",
+ "AndroidRect",
+ "InetAddress",
+ "JavaBuiltins",
+ "JavaExceptions",
+ "KeyEvent",
+ "MediaCodec",
+ "MotionEvent",
+ "SurfaceTexture",
+ "ViewConfiguration",
+]
+
+SOURCES += ["!%s.cpp" % stem for stem in generated]
+
+EXPORTS += ["!%s.h" % stem for stem in generated]
+
+# The recursive make backend treats the first output specially: it's passed as
+# an open FileAvoidWrite to the invoked script. That doesn't work well with
+# the Gradle task that generates all of the outputs, so we add a dummy first
+# output.
+t = tuple(
+ ["sdk_bindings"]
+ + ["%s.cpp" % stem for stem in generated]
+ + ["%s.h" % stem for stem in generated]
+)
+
+GeneratedFile(
+ *t,
+ script="/mobile/android/gradle.py",
+ entry_point="generate_sdk_bindings",
+ inputs=["%s-classes.txt" % stem for stem in generated]
+)
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/widget/android",
+]
diff --git a/widget/android/components.conf b/widget/android/components.conf
new file mode 100644
index 0000000000..089eefac51
--- /dev/null
+++ b/widget/android/components.conf
@@ -0,0 +1,114 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Headers = [
+ '/widget/android/nsWidgetFactory.h',
+]
+
+InitFunc = 'nsWidgetAndroidModuleCtor'
+UnloadFunc = 'nsWidgetAndroidModuleDtor'
+
+Classes = [
+ {
+ 'cid': '{2d96b3df-c051-11d1-a827-0040959a28c9}',
+ 'contract_ids': ['@mozilla.org/widget/appshell/android;1'],
+ 'legacy_constructor': 'nsAppShellConstructor',
+ 'headers': ['/widget/android/nsWidgetFactory.h'],
+ 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{c401eb80-f9ea-11d3-bb6f-e732b73ebe7c}',
+ 'contract_ids': ['@mozilla.org/gfx/screenmanager;1'],
+ 'singleton': True,
+ 'type': 'mozilla::widget::ScreenManager',
+ 'headers': ['mozilla/widget/ScreenManager.h'],
+ 'constructor': 'mozilla::widget::ScreenManager::GetAddRefedSingleton',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{6987230e-0098-4e78-bc5f-1493ee7519fa}',
+ 'contract_ids': ['@mozilla.org/widget/useridleservice;1'],
+ 'singleton': True,
+ 'type': 'nsUserIdleServiceAndroid',
+ 'headers': ['/widget/android/nsUserIdleServiceAndroid.h'],
+ 'constructor': 'nsUserIdleServiceAndroid::GetInstance',
+ },
+ {
+ 'cid': '{8b5314bc-db01-11d2-96ce-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/transferable;1'],
+ 'type': 'nsTransferable',
+ 'headers': ['/widget/nsTransferable.h'],
+ },
+ {
+ 'js_name': 'clipboard',
+ 'cid': '{8b5314ba-db01-11d2-96ce-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/clipboard;1'],
+ 'interfaces': ['nsIClipboard'],
+ 'type': 'nsClipboard',
+ 'headers': ['/widget/android/nsClipboard.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ 'overridable': True,
+ },
+ {
+ 'cid': '{77221d5a-1dd2-11b2-8c69-c710f15d2ed5}',
+ 'contract_ids': ['@mozilla.org/widget/clipboardhelper;1'],
+ 'type': 'nsClipboardHelper',
+ 'headers': ['/widget/nsClipboardHelper.h'],
+ },
+ {
+ 'cid': '{841387c8-72e6-484b-9296-bf6eea80d58a}',
+ 'contract_ids': ['@mozilla.org/gfx/printsettings-service;1'],
+ 'type': 'nsPrintSettingsServiceAndroid',
+ 'headers': ['/widget/android/nsPrintSettingsServiceAndroid.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{2f977d53-5485-11d4-87e2-0010a4e75ef2}',
+ 'contract_ids': ['@mozilla.org/gfx/printsession;1'],
+ 'type': 'nsPrintSession',
+ 'headers': ['/widget/nsPrintSession.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{d3f69889-e13a-4321-980c-a39332e21f34}',
+ 'contract_ids': ['@mozilla.org/gfx/devicecontextspec;1'],
+ 'type': 'nsDeviceContextSpecAndroid',
+ 'headers': ['/widget/android/nsDeviceContextAndroid.h'],
+ },
+ {
+ 'cid': '{948a0023-e3a7-11d2-96cf-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/htmlformatconverter;1'],
+ 'type': 'nsHTMLFormatConverter',
+ 'headers': ['/widget/nsHTMLFormatConverter.h'],
+ },
+ {
+ 'cid': '{d755a760-9f27-11df-0800-200c9a664242}',
+ 'contract_ids': ['@mozilla.org/gfx/info;1'],
+ 'type': 'mozilla::widget::GfxInfo',
+ 'headers': ['/widget/android/GfxInfo.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'js_name': 'androidBridge',
+ 'cid': '{0fe2321d-ebd9-467d-a743-03a68d40599e}',
+ 'contract_ids': ['@mozilla.org/android/bridge;1'],
+ 'interfaces': ['nsIAndroidBridge'],
+ 'type': 'nsAndroidBridge',
+ 'headers': ['/widget/android/AndroidBridge.h'],
+ },
+ {
+ 'cid': '{e9cd2b7f-8386-441b-aaf5-0b371846bfd0}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=android'],
+ 'type': 'nsAndroidProtocolHandler',
+ 'headers': ['/widget/android/nsAndroidProtocolHandler.h'],
+ },
+ {
+ 'cid': '{84e11f80-ca55-11dd-ad8b-0800200c9a66}',
+ 'contract_ids': ['@mozilla.org/system-alerts-service;1'],
+ 'type': 'mozilla::widget::AndroidAlerts',
+ 'headers': ['/widget/android/AndroidAlerts.h'],
+ },
+]
diff --git a/widget/android/jni/Accessors.h b/widget/android/jni/Accessors.h
new file mode 100644
index 0000000000..7496cbcb5a
--- /dev/null
+++ b/widget/android/jni/Accessors.h
@@ -0,0 +1,251 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_jni_Accessors_h__
+#define mozilla_jni_Accessors_h__
+
+#include <jni.h>
+
+#include "mozilla/jni/Refs.h"
+#include "mozilla/jni/Types.h"
+#include "mozilla/jni/Utils.h"
+#include "AndroidBridge.h"
+
+namespace mozilla {
+namespace jni {
+
+namespace detail {
+
+// Helper class to convert an arbitrary type to a jvalue, e.g. Value(123).val.
+struct Value {
+ explicit Value(jboolean z) { val.z = z; }
+ explicit Value(jbyte b) { val.b = b; }
+ explicit Value(jchar c) { val.c = c; }
+ explicit Value(jshort s) { val.s = s; }
+ explicit Value(jint i) { val.i = i; }
+ explicit Value(jlong j) { val.j = j; }
+ explicit Value(jfloat f) { val.f = f; }
+ explicit Value(jdouble d) { val.d = d; }
+ explicit Value(jobject l) { val.l = l; }
+
+ jvalue val;
+};
+
+} // namespace detail
+
+using namespace detail;
+
+// Base class for Method<>, Field<>, and Constructor<>.
+class Accessor {
+ static void GetNsresult(JNIEnv* env, nsresult* rv) {
+ if (env->ExceptionCheck()) {
+#ifdef MOZ_CHECK_JNI
+ env->ExceptionDescribe();
+#endif
+ env->ExceptionClear();
+ *rv = NS_ERROR_FAILURE;
+ } else {
+ *rv = NS_OK;
+ }
+ }
+
+ protected:
+ // Called after making a JNIEnv call.
+ template <class Traits>
+ static void EndAccess(const typename Traits::Owner::Context& ctx,
+ nsresult* rv) {
+ if (Traits::exceptionMode == ExceptionMode::ABORT) {
+ MOZ_CATCH_JNI_EXCEPTION(ctx.Env());
+
+ } else if (Traits::exceptionMode == ExceptionMode::NSRESULT) {
+ GetNsresult(ctx.Env(), rv);
+ }
+ }
+};
+
+// Member<> is used to call a JNI method given a traits class.
+template <class Traits, typename ReturnType = typename Traits::ReturnType>
+class Method : public Accessor {
+ typedef Accessor Base;
+ typedef typename Traits::Owner::Context Context;
+
+ protected:
+ static jmethodID sID;
+
+ static void BeginAccess(const Context& ctx) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+ static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT,
+ "Dispatching not supported for method call");
+
+ if (sID) {
+ return;
+ }
+
+ if (Traits::isStatic) {
+ MOZ_ALWAYS_TRUE(
+ sID = AndroidBridge::GetStaticMethodID(
+ ctx.Env(), ctx.ClassRef(), Traits::name, Traits::signature));
+ } else {
+ MOZ_ALWAYS_TRUE(
+ sID = AndroidBridge::GetMethodID(ctx.Env(), ctx.ClassRef(),
+ Traits::name, Traits::signature));
+ }
+ }
+
+ static void EndAccess(const Context& ctx, nsresult* rv) {
+ return Base::EndAccess<Traits>(ctx, rv);
+ }
+
+ public:
+ template <typename... Args>
+ static ReturnType Call(const Context& ctx, nsresult* rv,
+ const Args&... args) {
+ JNIEnv* const env = ctx.Env();
+ BeginAccess(ctx);
+
+ jvalue jargs[] = {Value(TypeAdapter<Args>::FromNative(env, args)).val...};
+
+ auto result = TypeAdapter<ReturnType>::ToNative(
+ env, Traits::isStatic ? (env->*TypeAdapter<ReturnType>::StaticCall)(
+ ctx.ClassRef(), sID, jargs)
+ : (env->*TypeAdapter<ReturnType>::Call)(
+ ctx.Get(), sID, jargs));
+
+ EndAccess(ctx, rv);
+ return result;
+ }
+};
+
+// Define sID member.
+template <class T, typename R>
+jmethodID Method<T, R>::sID;
+
+// Specialize void because C++ forbids us from
+// using a "void" temporary result variable.
+template <class Traits>
+class Method<Traits, void> : public Method<Traits, bool> {
+ typedef Method<Traits, bool> Base;
+ typedef typename Traits::Owner::Context Context;
+
+ public:
+ template <typename... Args>
+ static void Call(const Context& ctx, nsresult* rv, const Args&... args) {
+ JNIEnv* const env = ctx.Env();
+ Base::BeginAccess(ctx);
+
+ jvalue jargs[] = {Value(TypeAdapter<Args>::FromNative(env, args)).val...};
+
+ if (Traits::isStatic) {
+ env->CallStaticVoidMethodA(ctx.ClassRef(), Base::sID, jargs);
+ } else {
+ env->CallVoidMethodA(ctx.Get(), Base::sID, jargs);
+ }
+
+ Base::EndAccess(ctx, rv);
+ }
+};
+
+// Constructor<> is used to construct a JNI instance given a traits class.
+template <class Traits>
+class Constructor : protected Method<Traits, typename Traits::ReturnType> {
+ typedef typename Traits::Owner::Context Context;
+ typedef typename Traits::ReturnType ReturnType;
+ typedef Method<Traits, ReturnType> Base;
+
+ public:
+ template <typename... Args>
+ static ReturnType Call(const Context& ctx, nsresult* rv,
+ const Args&... args) {
+ JNIEnv* const env = ctx.Env();
+ Base::BeginAccess(ctx);
+
+ jvalue jargs[] = {Value(TypeAdapter<Args>::FromNative(env, args)).val...};
+
+ auto result = TypeAdapter<ReturnType>::ToNative(
+ env, env->NewObjectA(ctx.ClassRef(), Base::sID, jargs));
+
+ Base::EndAccess(ctx, rv);
+ return result;
+ }
+};
+
+// Field<> is used to access a JNI field given a traits class.
+template <class Traits>
+class Field : public Accessor {
+ typedef Accessor Base;
+ typedef typename Traits::Owner::Context Context;
+ typedef typename Traits::ReturnType GetterType;
+ typedef typename Traits::SetterType SetterType;
+
+ private:
+ static jfieldID sID;
+
+ static void BeginAccess(const Context& ctx) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+ static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT,
+ "Dispatching not supported for field access");
+
+ if (sID) {
+ return;
+ }
+
+ if (Traits::isStatic) {
+ MOZ_ALWAYS_TRUE(
+ sID = AndroidBridge::GetStaticFieldID(
+ ctx.Env(), ctx.ClassRef(), Traits::name, Traits::signature));
+ } else {
+ MOZ_ALWAYS_TRUE(sID = AndroidBridge::GetFieldID(ctx.Env(), ctx.ClassRef(),
+ Traits::name,
+ Traits::signature));
+ }
+ }
+
+ static void EndAccess(const Context& ctx, nsresult* rv) {
+ return Base::EndAccess<Traits>(ctx, rv);
+ }
+
+ public:
+ static GetterType Get(const Context& ctx, nsresult* rv) {
+ JNIEnv* const env = ctx.Env();
+ BeginAccess(ctx);
+
+ auto result = TypeAdapter<GetterType>::ToNative(
+ env, Traits::isStatic
+ ?
+
+ (env->*TypeAdapter<GetterType>::StaticGet)(ctx.ClassRef(), sID)
+ :
+
+ (env->*TypeAdapter<GetterType>::Get)(ctx.Get(), sID));
+
+ EndAccess(ctx, rv);
+ return result;
+ }
+
+ static void Set(const Context& ctx, nsresult* rv, SetterType val) {
+ JNIEnv* const env = ctx.Env();
+ BeginAccess(ctx);
+
+ if (Traits::isStatic) {
+ (env->*TypeAdapter<SetterType>::StaticSet)(
+ ctx.ClassRef(), sID, TypeAdapter<SetterType>::FromNative(env, val));
+ } else {
+ (env->*TypeAdapter<SetterType>::Set)(
+ ctx.Get(), sID, TypeAdapter<SetterType>::FromNative(env, val));
+ }
+
+ EndAccess(ctx, rv);
+ }
+};
+
+// Define sID member.
+template <class T>
+jfieldID Field<T>::sID;
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Accessors_h__
diff --git a/widget/android/jni/Conversions.cpp b/widget/android/jni/Conversions.cpp
new file mode 100644
index 0000000000..e8d5413ca3
--- /dev/null
+++ b/widget/android/jni/Conversions.cpp
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "Conversions.h"
+#include "JavaBuiltins.h"
+
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+
+namespace mozilla {
+namespace jni {
+
+template <class T>
+jfieldID GetValueFieldID(JNIEnv* aEnv, const char* aType) {
+ const jfieldID id = aEnv->GetFieldID(
+ typename T::Context(aEnv, nullptr).ClassRef(), "value", aType);
+ aEnv->ExceptionClear();
+ return id;
+}
+
+// Cached locations of the primitive types within their standard boxed objects
+// to skip doing that lookup on every get.
+static jfieldID gBooleanValueField;
+static jfieldID gIntValueField;
+static jfieldID gDoubleValueField;
+
+void InitConversionStatics() {
+ MOZ_ASSERT(NS_IsMainThread());
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ gBooleanValueField = GetValueFieldID<java::sdk::Boolean>(env, "Z");
+ gIntValueField = GetValueFieldID<java::sdk::Integer>(env, "I");
+ gDoubleValueField = GetValueFieldID<java::sdk::Double>(env, "D");
+}
+
+template <>
+bool Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) {
+ MOZ_ASSERT(aData.IsInstanceOf<jni::Boolean>());
+
+ bool result = false;
+ if (gBooleanValueField) {
+ if (!aEnv) {
+ aEnv = jni::GetEnvForThread();
+ }
+ result =
+ aEnv->GetBooleanField(aData.Get(), gBooleanValueField) != JNI_FALSE;
+ MOZ_CATCH_JNI_EXCEPTION(aEnv);
+ } else {
+ result = java::sdk::Boolean::Ref::From(aData)->BooleanValue();
+ }
+
+ return result;
+}
+
+template <>
+int Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) {
+ MOZ_ASSERT(aData.IsInstanceOf<jni::Integer>());
+
+ int result = 0;
+ if (gIntValueField) {
+ if (!aEnv) {
+ aEnv = jni::GetEnvForThread();
+ }
+ result = aEnv->GetIntField(aData.Get(), gIntValueField);
+ MOZ_CATCH_JNI_EXCEPTION(aEnv);
+ } else {
+ result = java::sdk::Number::Ref::From(aData)->IntValue();
+ }
+
+ return result;
+}
+
+template <>
+double Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) {
+ MOZ_ASSERT(aData.IsInstanceOf<jni::Double>());
+
+ double result = 0;
+ if (gDoubleValueField) {
+ if (!aEnv) {
+ aEnv = jni::GetEnvForThread();
+ }
+ result = aEnv->GetDoubleField(aData.Get(), gDoubleValueField);
+ MOZ_CATCH_JNI_EXCEPTION(aEnv);
+ } else {
+ result = java::sdk::Number::Ref::From(aData)->DoubleValue();
+ }
+
+ return result;
+}
+
+template <>
+ipc::LaunchError Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) {
+ return ipc::LaunchError{};
+}
+
+template <>
+nsString Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) {
+ nsString result;
+ if (aData != NULL && aData.IsInstanceOf<jni::String>()) {
+ result = jni::String::Ref::From(aData)->ToString();
+ }
+ return result;
+}
+
+} // namespace jni
+} // namespace mozilla
diff --git a/widget/android/jni/Conversions.h b/widget/android/jni/Conversions.h
new file mode 100644
index 0000000000..1d9e20acc7
--- /dev/null
+++ b/widget/android/jni/Conversions.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_jni_Conversions_h__
+#define mozilla_jni_Conversions_h__
+
+#include "mozilla/jni/Refs.h"
+
+namespace mozilla {
+namespace jni {
+
+template <typename ArgType>
+ArgType Java2Native(mozilla::jni::Object::Param, JNIEnv* aEnv = nullptr);
+
+void InitConversionStatics();
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Conversions_h__
diff --git a/widget/android/jni/GeckoBundleUtils.h b/widget/android/jni/GeckoBundleUtils.h
new file mode 100644
index 0000000000..df62b0c760
--- /dev/null
+++ b/widget/android/jni/GeckoBundleUtils.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_jni_GeckoBundleUtils_h
+#define mozilla_jni_GeckoBundleUtils_h
+
+#include "mozilla/java/GeckoBundleWrappers.h"
+
+namespace mozilla {
+namespace jni {
+
+#define GECKOBUNDLE_START(name) \
+ nsTArray<jni::String::LocalRef> _##name##_keys; \
+ nsTArray<jni::Object::LocalRef> _##name##_values;
+
+#define GECKOBUNDLE_PUT(name, key, value) \
+ _##name##_keys.AppendElement( \
+ jni::StringParam(NS_LITERAL_STRING_FROM_CSTRING(key))); \
+ _##name##_values.AppendElement(value);
+
+#define GECKOBUNDLE_FINISH(name) \
+ MOZ_ASSERT(_##name##_keys.Length() == _##name##_values.Length()); \
+ auto _##name##_jkeys = \
+ jni::ObjectArray::New<jni::String>(_##name##_keys.Length()); \
+ auto _##name##_jvalues = \
+ jni::ObjectArray::New<jni::Object>(_##name##_values.Length()); \
+ for (size_t i = 0; \
+ i < _##name##_keys.Length() && i < _##name##_values.Length(); i++) { \
+ _##name##_jkeys->SetElement(i, _##name##_keys.ElementAt(i)); \
+ _##name##_jvalues->SetElement(i, _##name##_values.ElementAt(i)); \
+ } \
+ auto name = \
+ mozilla::java::GeckoBundle::New(_##name##_jkeys, _##name##_jvalues);
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_GeckoBundleUtils_h
diff --git a/widget/android/jni/GeckoResultUtils.h b/widget/android/jni/GeckoResultUtils.h
new file mode 100644
index 0000000000..eee56d41c3
--- /dev/null
+++ b/widget/android/jni/GeckoResultUtils.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_jni_GeckoResultUtils_h
+#define mozilla_jni_GeckoResultUtils_h
+
+#include "mozilla/java/GeckoResultNatives.h"
+#include "mozilla/jni/Conversions.h"
+
+namespace mozilla {
+namespace jni {
+
+// C++-side object bound to Java's GeckoResult.GeckoCallback.
+//
+// Note that we can't template this class because that breaks JNI dispatch
+// (surprisingly: it compiles, but selects the wrong method specialization
+// during dispatch). So instead we use a templated factory function, which
+// bundles the per-ArgType conversion logic into the callback.
+class GeckoResultCallback final
+ : public java::GeckoResult::GeckoCallback::Natives<GeckoResultCallback> {
+ public:
+ typedef java::GeckoResult::GeckoCallback::Natives<GeckoResultCallback> Base;
+ typedef std::function<void(mozilla::jni::Object::Param)> OuterCallback;
+
+ void Call(mozilla::jni::Object::Param aArg) { mCallback(aArg); }
+
+ template <typename ArgType>
+ static java::GeckoResult::GeckoCallback::LocalRef CreateAndAttach(
+ std::function<void(ArgType)>&& aInnerCallback) {
+ auto java = java::GeckoResult::GeckoCallback::New();
+ OuterCallback outerCallback =
+ [inner{std::move(aInnerCallback)}](mozilla::jni::Object::Param aParam) {
+ ArgType converted = Java2Native<ArgType>(aParam);
+ inner(converted);
+ };
+ auto native = MakeUnique<GeckoResultCallback>(std::move(outerCallback));
+ Base::AttachNative(java, std::move(native));
+ return java;
+ }
+
+ explicit GeckoResultCallback(OuterCallback&& aCallback)
+ : mCallback(std::move(aCallback)) {}
+
+ private:
+ OuterCallback mCallback;
+};
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_GeckoResultUtils_h
diff --git a/widget/android/jni/Natives.h b/widget/android/jni/Natives.h
new file mode 100644
index 0000000000..fc243a0e97
--- /dev/null
+++ b/widget/android/jni/Natives.h
@@ -0,0 +1,1615 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_jni_Natives_h__
+#define mozilla_jni_Natives_h__
+
+#include <jni.h>
+
+#include <type_traits>
+#include <utility>
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/RWLock.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/TypeTraits.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/jni/Accessors.h"
+#include "mozilla/jni/Refs.h"
+#include "mozilla/jni/Types.h"
+#include "mozilla/jni/Utils.h"
+#include "nsThreadUtils.h"
+
+struct NativeException {
+ const char* str;
+};
+
+template <class T>
+static NativeException NullHandle() {
+ return {__func__};
+}
+
+template <class T>
+static NativeException NullWeakPtr() {
+ return {__func__};
+}
+
+namespace mozilla {
+namespace jni {
+
+/**
+ * C++ classes implementing instance (non-static) native methods can choose
+ * from one of two ownership models, when associating a C++ object with a Java
+ * instance.
+ *
+ * * If the C++ class inherits from mozilla::SupportsWeakPtr, weak pointers
+ * will be used. The Java instance will store and own the pointer to a
+ * WeakPtr object. The C++ class itself is otherwise not owned or directly
+ * referenced. Note that mozilla::SupportsWeakPtr only supports being used on
+ * a single thread. To attach a Java instance to a C++ instance, pass in a
+ * mozilla::SupportsWeakPtr pointer to the C++ class (i.e. MyClass*).
+ *
+ * class MyClass : public SupportsWeakPtr
+ * , public MyJavaClass::Natives<MyClass>
+ * {
+ * // ...
+ *
+ * public:
+ * using MyJavaClass::Natives<MyClass>::DisposeNative;
+ *
+ * void AttachTo(const MyJavaClass::LocalRef& instance)
+ * {
+ * MyJavaClass::Natives<MyClass>::AttachNative(
+ * instance, static_cast<SupportsWeakPtr*>(this));
+ *
+ * // "instance" does NOT own "this", so the C++ object
+ * // lifetime is separate from the Java object lifetime.
+ * }
+ * };
+ *
+ * * If the C++ class contains public members AddRef() and Release(), the Java
+ * instance will store and own the pointer to a RefPtr object, which holds a
+ * strong reference on the C++ instance. Normal ref-counting considerations
+ * apply in this case; for example, disposing may cause the C++ instance to
+ * be deleted and the destructor to be run on the current thread, which may
+ * not be desirable. To attach a Java instance to a C++ instance, pass in a
+ * pointer to the C++ class (i.e. MyClass*).
+ *
+ * class MyClass : public RefCounted<MyClass>
+ * , public MyJavaClass::Natives<MyClass>
+ * {
+ * // ...
+ *
+ * public:
+ * using MyJavaClass::Natives<MyClass>::DisposeNative;
+ *
+ * void AttachTo(const MyJavaClass::LocalRef& instance)
+ * {
+ * MyJavaClass::Natives<MyClass>::AttachNative(instance, this);
+ *
+ * // "instance" owns "this" through the RefPtr, so the C++ object
+ * // may be destroyed as soon as instance.disposeNative() is called.
+ * }
+ * };
+ *
+ * * In other cases, the Java instance will store and own a pointer to the C++
+ * object itself. This pointer must not be stored or deleted elsewhere. To
+ * attach a Java instance to a C++ instance, pass in a reference to a
+ * UniquePtr of the C++ class (i.e. UniquePtr<MyClass>).
+ *
+ * class MyClass : public MyJavaClass::Natives<MyClass>
+ * {
+ * // ...
+ *
+ * public:
+ * using MyJavaClass::Natives<MyClass>::DisposeNative;
+ *
+ * static void AttachTo(const MyJavaClass::LocalRef& instance)
+ * {
+ * MyJavaClass::Natives<MyClass>::AttachNative(
+ * instance, mozilla::MakeUnique<MyClass>());
+ *
+ * // "instance" owns the newly created C++ object, so the C++
+ * // object is destroyed as soon as instance.disposeNative() is
+ * // called.
+ * }
+ * };
+ */
+
+namespace detail {
+
+/**
+ * Type trait that determines whether a given class has a member named
+ * T::OnWeakNonIntrusiveDetach.
+ *
+ * Example usage:
+ * class Foo {};
+ * class Bar {
+ * public:
+ * void OnWeakNonIntrusiveDetach(already_AddRefed<nsIRunnable> aRunnable);
+ * };
+ *
+ * constexpr bool foo = HasWeakNonIntrusiveDetach<Foo>::value; // Expect false
+ * constexpr bool bar = HasWeakNonIntrusiveDetach<Bar>::value; // Expect true
+ */
+template <typename, typename = std::void_t<>>
+struct HasWeakNonIntrusiveDetach : std::false_type {};
+
+template <typename T>
+struct HasWeakNonIntrusiveDetach<
+ T, std::void_t<decltype(std::declval<T>().OnWeakNonIntrusiveDetach(
+ std::declval<already_AddRefed<nsIRunnable>>()))>> : std::true_type {
+};
+
+/**
+ * Type trait that determines whether a given class is refcounted, ie. it has
+ * both T::AddRef and T::Release methods.
+ *
+ * Example usage:
+ * class Foo {};
+ * class Bar {
+ * public:
+ * void AddRef();
+ * void Release();
+ * };
+ *
+ * constexpr bool foo = IsRefCounted<Foo>::value; // Expect false
+ * constexpr bool bar = IsRefCounted<Bar>::value; // Expect true
+ */
+template <typename, typename = std::void_t<>>
+struct IsRefCounted : std::false_type {};
+
+template <typename T>
+struct IsRefCounted<T, std::void_t<decltype(std::declval<T>().AddRef(),
+ std::declval<T>().Release())>>
+ : std::true_type {};
+
+/**
+ * This enum is used for classifying the type of pointer that is stored
+ * within a NativeWeakPtr. This classification is different from the one used
+ * for normal native pointers.
+ */
+enum class NativePtrInternalType : size_t {
+ OWNING = 1,
+ WEAK = 2,
+ REFPTR = 3,
+};
+
+/**
+ * NativePtrInternalPicker uses some C++ SFINAE template-fu to figure out
+ * what type of pointer the class specified by Impl needs to be.
+ *
+ * It does this by supplying multiple overloads of a method named Test.
+ * Various overloads are enabled or disabled depending on whether or not Impl
+ * can possibly support them.
+ *
+ * Each overload "returns" a reference to an array whose size corresponds to the
+ * value of each enum in NativePtrInternalType. That size is then converted back
+ * to the enum value, yielding the right type.
+ */
+template <class Impl>
+class NativePtrInternalPicker {
+ // Enable if Impl derives from SupportsWeakPtr, yielding type WEAK
+ template <class I>
+ static std::enable_if_t<
+ std::is_base_of<SupportsWeakPtr, I>::value,
+ char (&)[static_cast<size_t>(NativePtrInternalType::WEAK)]>
+ Test(char);
+
+ // Enable if Impl implements AddRef and Release, yielding type REFPTR
+ template <class I, typename = decltype(&I::AddRef, &I::Release)>
+ static char (&Test(int))[static_cast<size_t>(NativePtrInternalType::REFPTR)];
+
+ // This overload uses '...' as its param to make its arguments less specific;
+ // the compiler prefers more-specific overloads to less-specific ones.
+ // OWNING is the fallback type.
+ template <class>
+ static char (&Test(...))[static_cast<size_t>(NativePtrInternalType::OWNING)];
+
+ public:
+ // Given a hypothetical function call Test<Impl>, convert the size of its
+ // resulting array back into a NativePtrInternalType enum value.
+ static const NativePtrInternalType value = static_cast<NativePtrInternalType>(
+ sizeof(Test<Impl>('\0')) / sizeof(char));
+};
+
+/**
+ * This enum is used for classifying the type of pointer that is stored in a
+ * JNIObject's handle.
+ *
+ * We have two different weak pointer types:
+ * * WEAK_INTRUSIVE is a pointer to a class that derives from
+ * mozilla::SupportsWeakPtr.
+ * * WEAK_NON_INTRUSIVE is a pointer to a class that does not have any
+ * internal support for weak pointers, but does supply a
+ * OnWeakNonIntrusiveDetach method.
+ */
+enum class NativePtrType : size_t {
+ OWNING = 1,
+ WEAK_INTRUSIVE = 2,
+ WEAK_NON_INTRUSIVE = 3,
+ REFPTR = 4,
+};
+
+/**
+ * NativePtrPicker uses some C++ SFINAE template-fu to figure out what type of
+ * pointer the class specified by Impl needs to be.
+ *
+ * It does this by supplying multiple overloads of a method named Test.
+ * Various overloads are enabled or disabled depending on whether or not Impl
+ * can possibly support them.
+ *
+ * Each overload "returns" a reference to an array whose size corresponds to the
+ * value of each enum in NativePtrInternalType. That size is then converted back
+ * to the enum value, yielding the right type.
+ */
+template <class Impl>
+class NativePtrPicker {
+ // Just shorthand for each overload's return type
+ template <NativePtrType PtrType>
+ using ResultTypeT = char (&)[static_cast<size_t>(PtrType)];
+
+ // Enable if Impl derives from SupportsWeakPtr, yielding type WEAK_INTRUSIVE
+ template <typename I>
+ static auto Test(void*)
+ -> std::enable_if_t<std::is_base_of<SupportsWeakPtr, I>::value,
+ ResultTypeT<NativePtrType::WEAK_INTRUSIVE>>;
+
+ // Enable if Impl implements OnWeakNonIntrusiveDetach, yielding type
+ // WEAK_NON_INTRUSIVE
+ template <typename I>
+ static auto Test(void*)
+ -> std::enable_if_t<HasWeakNonIntrusiveDetach<I>::value,
+ ResultTypeT<NativePtrType::WEAK_NON_INTRUSIVE>>;
+
+ // We want the WEAK_NON_INTRUSIVE overload to take precedence over this one,
+ // so we only enable this overload if Impl is refcounted AND it does not
+ // implement OnWeakNonIntrusiveDetach. Yields type REFPTR.
+ template <typename I>
+ static auto Test(void*) -> std::enable_if_t<
+ std::conjunction_v<IsRefCounted<I>,
+ std::negation<HasWeakNonIntrusiveDetach<I>>>,
+ ResultTypeT<NativePtrType::REFPTR>>;
+
+ // This overload uses '...' as its param to make its arguments less specific;
+ // the compiler prefers more-specific overloads to less-specific ones.
+ // OWNING is the fallback type.
+ template <typename>
+ static char (&Test(...))[static_cast<size_t>(NativePtrType::OWNING)];
+
+ public:
+ // Given a hypothetical function call Test<Impl>, convert the size of its
+ // resulting array back into a NativePtrType enum value.
+ static const NativePtrType value =
+ static_cast<NativePtrType>(sizeof(Test<Impl>(nullptr)));
+};
+
+template <class Impl>
+inline uintptr_t CheckNativeHandle(JNIEnv* env, uintptr_t handle) {
+ if (!handle) {
+ if (!env->ExceptionCheck()) {
+ ThrowException(env, "java/lang/NullPointerException",
+ NullHandle<Impl>().str);
+ }
+ return 0;
+ }
+ return handle;
+}
+
+/**
+ * This struct is used to describe various traits of a native pointer of type
+ * Impl that will be attached to a JNIObject.
+ *
+ * See the definition of the NativePtrType::OWNING specialization for comments
+ * describing the required fields.
+ */
+template <class Impl, NativePtrType Type = NativePtrPicker<Impl>::value>
+struct NativePtrTraits;
+
+template <class Impl>
+struct NativePtrTraits<Impl, /* Type = */ NativePtrType::OWNING> {
+ using AccessorType =
+ Impl*; // Pointer-like type returned by Access() (an actual pointer in
+ // this case, but this is not strictly necessary)
+ using HandleType = Impl*; // Type of the pointer stored in JNIObject.mHandle
+ using RefType = Impl*; // Type of the pointer returned by Get()
+
+ /**
+ * Returns a RefType to the native implementation belonging to
+ * the given Java object.
+ */
+ static RefType Get(JNIEnv* env, jobject instance) {
+ static_assert(
+ std::is_same<HandleType, RefType>::value,
+ "HandleType and RefType must be identical for owning pointers");
+ return reinterpret_cast<HandleType>(
+ CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance)));
+ }
+
+ /**
+ * Returns a RefType to the native implementation belonging to
+ * the given Java object.
+ */
+ template <class LocalRef>
+ static RefType Get(const LocalRef& instance) {
+ return Get(instance.Env(), instance.Get());
+ }
+
+ /**
+ * Given a RefType, returns the pointer-like AccessorType used for
+ * manipulating the native object.
+ */
+ static AccessorType Access(RefType aImpl, JNIEnv* aEnv = nullptr) {
+ static_assert(
+ std::is_same<AccessorType, RefType>::value,
+ "AccessorType and RefType must be identical for owning pointers");
+ return aImpl;
+ }
+
+ /**
+ * Set the JNIObject's handle to the provided pointer, clearing any previous
+ * handle if necessary.
+ */
+ template <class LocalRef>
+ static void Set(const LocalRef& instance, UniquePtr<Impl>&& ptr) {
+ Clear(instance);
+ SetNativeHandle(instance.Env(), instance.Get(),
+ reinterpret_cast<uintptr_t>(ptr.release()));
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ }
+
+ /**
+ * Clear the JNIObject's handle.
+ */
+ template <class LocalRef>
+ static void Clear(const LocalRef& instance) {
+ UniquePtr<Impl> ptr(reinterpret_cast<RefType>(
+ GetNativeHandle(instance.Env(), instance.Get())));
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+
+ if (ptr) {
+ SetNativeHandle(instance.Env(), instance.Get(), 0);
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ }
+ }
+};
+
+template <class Impl>
+struct NativePtrTraits<Impl, /* Type = */ NativePtrType::WEAK_INTRUSIVE> {
+ using AccessorType = Impl*;
+ using HandleType = WeakPtr<Impl>*;
+ using RefType = WeakPtr<Impl>;
+
+ static RefType Get(JNIEnv* env, jobject instance) {
+ const auto ptr = reinterpret_cast<HandleType>(
+ CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance)));
+ return *ptr;
+ }
+
+ template <class LocalRef>
+ static RefType Get(const LocalRef& instance) {
+ return Get(instance.Env(), instance.Get());
+ }
+
+ static AccessorType Access(RefType aPtr, JNIEnv* aEnv = nullptr) {
+ AccessorType const impl = *aPtr;
+ if (!impl) {
+ JNIEnv* env = aEnv ? aEnv : mozilla::jni::GetEnvForThread();
+ ThrowException(env, "java/lang/NullPointerException",
+ NullWeakPtr<Impl>().str);
+ }
+
+ return impl;
+ }
+
+ template <class LocalRef>
+ static void Set(const LocalRef& instance, Impl* ptr) {
+ // Create the new handle first before clearing any old handle, so the
+ // new handle is guaranteed to have different value than any old handle.
+ const uintptr_t handle =
+ reinterpret_cast<uintptr_t>(new WeakPtr<Impl>(ptr));
+ Clear(instance);
+ SetNativeHandle(instance.Env(), instance.Get(), handle);
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ }
+
+ template <class LocalRef>
+ static void Clear(const LocalRef& instance) {
+ const auto ptr = reinterpret_cast<HandleType>(
+ GetNativeHandle(instance.Env(), instance.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+
+ if (ptr) {
+ SetNativeHandle(instance.Env(), instance.Get(), 0);
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ delete ptr;
+ }
+ }
+};
+
+template <class Impl>
+struct NativePtrTraits<Impl, /* Type = */ NativePtrType::REFPTR> {
+ using AccessorType = Impl*;
+ using HandleType = RefPtr<Impl>*;
+ using RefType = Impl*;
+
+ static RefType Get(JNIEnv* env, jobject instance) {
+ const auto ptr = reinterpret_cast<HandleType>(
+ CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance)));
+ if (!ptr) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(*ptr);
+ return *ptr;
+ }
+
+ template <class LocalRef>
+ static RefType Get(const LocalRef& instance) {
+ return Get(instance.Env(), instance.Get());
+ }
+
+ static AccessorType Access(RefType aImpl, JNIEnv* aEnv = nullptr) {
+ static_assert(std::is_same<AccessorType, RefType>::value,
+ "AccessorType and RefType must be identical for refpointers");
+ return aImpl;
+ }
+
+ template <class LocalRef>
+ static void Set(const LocalRef& instance, RefType ptr) {
+ // Create the new handle first before clearing any old handle, so the
+ // new handle is guaranteed to have different value than any old handle.
+ const uintptr_t handle = reinterpret_cast<uintptr_t>(new RefPtr<Impl>(ptr));
+ Clear(instance);
+ SetNativeHandle(instance.Env(), instance.Get(), handle);
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ }
+
+ template <class LocalRef>
+ static void Clear(const LocalRef& instance) {
+ const auto ptr = reinterpret_cast<HandleType>(
+ GetNativeHandle(instance.Env(), instance.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+
+ if (ptr) {
+ SetNativeHandle(instance.Env(), instance.Get(), 0);
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ delete ptr;
+ }
+ }
+};
+
+} // namespace detail
+
+// Forward declarations
+template <typename NativeImpl>
+class NativeWeakPtr;
+template <typename NativeImpl>
+class NativeWeakPtrHolder;
+
+namespace detail {
+
+/**
+ * Given the class of a native implementation, as well as its
+ * NativePtrInternalType, resolve traits for that type that will be used by
+ * the NativeWeakPtrControlBlock.
+ *
+ * Note that we only implement specializations for OWNING and REFPTR types,
+ * as a WEAK_INTRUSIVE type should not be using NativeWeakPtr anyway. The build
+ * will fail if such an attempt is made.
+ *
+ * Traits need to implement two things:
+ * 1. A |Type| field that resolves to a pointer type to be stored in the
+ * JNIObject's handle. It is assumed that setting a |Type| object to nullptr
+ * is sufficient to delete the underlying object.
+ * 2. A static |AsRaw| method that converts a pointer of |Type| into a raw
+ * pointer.
+ */
+template <
+ typename NativeImpl,
+ NativePtrInternalType PtrType =
+ ::mozilla::jni::detail::NativePtrInternalPicker<NativeImpl>::value>
+struct NativeWeakPtrControlBlockStorageTraits;
+
+template <typename NativeImpl>
+struct NativeWeakPtrControlBlockStorageTraits<
+ NativeImpl, ::mozilla::jni::detail::NativePtrInternalType::OWNING> {
+ using Type = UniquePtr<NativeImpl>;
+
+ static NativeImpl* AsRaw(const Type& aStorage) { return aStorage.get(); }
+};
+
+template <typename NativeImpl>
+struct NativeWeakPtrControlBlockStorageTraits<
+ NativeImpl, ::mozilla::jni::detail::NativePtrInternalType::REFPTR> {
+ using Type = RefPtr<NativeImpl>;
+
+ static NativeImpl* AsRaw(const Type& aStorage) { return aStorage.get(); }
+};
+
+// Forward Declaration
+template <typename NativeImpl>
+class Accessor;
+
+/**
+ * This class contains the shared data that is referenced by all NativeWeakPtr
+ * objects that reference the same object.
+ *
+ * It retains a WeakRef to the Java object that owns this native object.
+ * It uses a RWLock to control access to the native pointer itself.
+ * Read locks are used when accessing the pointer (even when calling non-const
+ * methods on the native object).
+ * A write lock is only used when it is time to destroy the native object and
+ * we need to clear the value of mNativeImpl.
+ */
+template <typename NativeImpl>
+class MOZ_HEAP_CLASS NativeWeakPtrControlBlock final {
+ public:
+ using StorageTraits = NativeWeakPtrControlBlockStorageTraits<NativeImpl>;
+ using StorageType = typename StorageTraits::Type;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NativeWeakPtrControlBlock)
+
+ NativeWeakPtrControlBlock(const NativeWeakPtrControlBlock&) = delete;
+ NativeWeakPtrControlBlock(NativeWeakPtrControlBlock&&) = delete;
+ NativeWeakPtrControlBlock& operator=(const NativeWeakPtrControlBlock&) =
+ delete;
+ NativeWeakPtrControlBlock& operator=(NativeWeakPtrControlBlock&&) = delete;
+
+ // This is safe to call on any thread because mJavaOwner is immutable.
+ mozilla::jni::Object::WeakRef GetJavaOwner() const { return mJavaOwner; }
+
+ private:
+ NativeWeakPtrControlBlock(::mozilla::jni::Object::Param aJavaOwner,
+ StorageType&& aNativeImpl)
+ : mJavaOwner(aJavaOwner),
+ mLock("mozilla::jni::detail::NativeWeakPtrControlBlock"),
+ mNativeImpl(std::move(aNativeImpl)) {}
+
+ ~NativeWeakPtrControlBlock() {
+ // Make sure that somebody, somewhere, has detached us before destroying.
+ MOZ_ASSERT(!(*this));
+ }
+
+ /**
+ * Clear the native pointer so that subsequent accesses to the native pointer
+ * via this control block are no longer available.
+ *
+ * We return the native pointer to the caller so that it may proceed with
+ * cleaning up its resources.
+ */
+ StorageType Clear() {
+ StorageType nativeImpl(nullptr);
+
+ { // Scope for lock
+ AutoWriteLock lock(mLock);
+ std::swap(mNativeImpl, nativeImpl);
+ }
+
+ return nativeImpl;
+ }
+
+ void Lock() const { mLock.ReadLock(); }
+
+ void Unlock() const { mLock.ReadUnlock(); }
+
+#if defined(DEBUG)
+ // This is kind of expensive, so we only support it in debug builds.
+ explicit operator bool() const {
+ AutoReadLock lock(mLock);
+ return !!mNativeImpl;
+ }
+#endif // defined(DEBUG)
+
+ private:
+ friend class Accessor<NativeImpl>;
+ friend class NativeWeakPtr<NativeImpl>;
+ friend class NativeWeakPtrHolder<NativeImpl>;
+
+ private:
+ const mozilla::jni::Object::WeakRef mJavaOwner;
+ mutable RWLock mLock; // Protects mNativeImpl
+ StorageType mNativeImpl;
+};
+
+/**
+ * When a NativeWeakPtr is detached from its owning Java object, the calling
+ * thread invokes the implementation's OnWeakNonIntrusiveDetach to perform
+ * cleanup. We complete the remainder of the cleanup sequence on the Gecko
+ * main thread by expecting OnWeakNonIntrusiveDetach implementations to invoke
+ * this Runnable before exiting. It will move itself to the main thread if it
+ * is not already there.
+ */
+template <typename NativeImpl>
+class NativeWeakPtrDetachRunnable final : public Runnable {
+ public:
+ NativeWeakPtrDetachRunnable(
+ already_AddRefed<detail::NativeWeakPtrControlBlock<NativeImpl>> aCtlBlock,
+ const Object::LocalRef& aOwner,
+ typename NativeWeakPtrControlBlockStorageTraits<NativeImpl>::Type
+ aNativeImpl)
+ : Runnable("mozilla::jni::detail::NativeWeakPtrDetachRunnable"),
+ mCtlBlock(aCtlBlock),
+ mOwner(aOwner),
+ mNativeImpl(std::move(aNativeImpl)),
+ mHasRun(false) {
+ MOZ_RELEASE_ASSERT(!!mCtlBlock);
+ MOZ_RELEASE_ASSERT(!!mNativeImpl);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(NativeWeakPtrDetachRunnable, Runnable)
+
+ NS_IMETHOD Run() override {
+ mHasRun = true;
+
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(this);
+ return NS_OK;
+ }
+
+ // Get the owner object's native implementation
+ auto owner = ToLocalRef(mOwner);
+ auto attachedNativeImpl = NativePtrTraits<NativeImpl>::Get(owner);
+ MOZ_RELEASE_ASSERT(!!attachedNativeImpl);
+
+ // NativePtrTraits::ClearFinish cleans out the JNIObject's handle, which
+ // obviously we don't want to attempt unless that handle still points to
+ // our native implementation.
+ if (attachedNativeImpl->IsSame(mCtlBlock)) {
+ NativePtrTraits<NativeImpl>::ClearFinish(owner);
+ }
+
+ // Now we destroy that native object.
+ mNativeImpl = nullptr;
+ return NS_OK;
+ }
+
+ private:
+ ~NativeWeakPtrDetachRunnable() {
+ // Guard against somebody forgetting to call this runnable.
+ MOZ_RELEASE_ASSERT(mHasRun, "You must run/dispatch this runnable!");
+ }
+
+ private:
+ RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>> mCtlBlock;
+ Object::GlobalRef mOwner;
+ typename NativeWeakPtrControlBlockStorageTraits<NativeImpl>::Type mNativeImpl;
+ bool mHasRun;
+};
+
+/**
+ * If you want to temporarily access the object held by a NativeWeakPtr, you
+ * must obtain one of these Accessor objects from the pointer. Access must
+ * be done _exclusively_ using once of these objects!
+ */
+template <typename NativeImpl>
+class MOZ_STACK_CLASS Accessor final {
+ public:
+ ~Accessor() {
+ if (mCtlBlock) {
+ mCtlBlock->Unlock();
+ }
+ }
+
+ // Check whether the object is still valid before doing anything else
+ explicit operator bool() const { return mCtlBlock && mCtlBlock->mNativeImpl; }
+
+ // Normal member access
+ NativeImpl* operator->() const {
+ return NativeWeakPtrControlBlockStorageTraits<NativeImpl>::AsRaw(
+ mCtlBlock->mNativeImpl);
+ }
+
+ // This allows us to support calling a pointer to a member function
+ template <typename Member>
+ auto operator->*(Member aMember) const {
+ NativeImpl* impl =
+ NativeWeakPtrControlBlockStorageTraits<NativeImpl>::AsRaw(
+ mCtlBlock->mNativeImpl);
+ return [impl, member = aMember](auto&&... aArgs) {
+ return (impl->*member)(std::forward<decltype(aArgs)>(aArgs)...);
+ };
+ }
+
+ // Only available for NativeImpl types that actually use refcounting.
+ // The idea here is that it should be possible to obtain a strong ref from
+ // a NativeWeakPtr if and only if NativeImpl supports refcounting.
+ template <typename I = NativeImpl>
+ auto AsRefPtr() const -> std::enable_if_t<IsRefCounted<I>::value, RefPtr<I>> {
+ MOZ_ASSERT(I::HasThreadSafeRefCnt::value || NS_IsMainThread());
+ return mCtlBlock->mNativeImpl;
+ }
+
+ Accessor(const Accessor&) = delete;
+ Accessor(Accessor&&) = delete;
+ Accessor& operator=(const Accessor&) = delete;
+ Accessor& operator=(Accessor&&) = delete;
+
+ private:
+ explicit Accessor(
+ const RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>>& aCtlBlock)
+ : mCtlBlock(aCtlBlock) {
+ if (aCtlBlock) {
+ aCtlBlock->Lock();
+ }
+ }
+
+ private:
+ friend class NativeWeakPtr<NativeImpl>;
+ friend class NativeWeakPtrHolder<NativeImpl>;
+
+ private:
+ const RefPtr<NativeWeakPtrControlBlock<NativeImpl>> mCtlBlock;
+};
+
+} // namespace detail
+
+/**
+ * This class implements support for thread-safe weak pointers to native objects
+ * that are owned by Java objects deriving from JNIObject.
+ *
+ * Any code that wants to access such a native object must have a copy of
+ * a NativeWeakPtr to that object.
+ */
+template <typename NativeImpl>
+class NativeWeakPtr {
+ public:
+ using Accessor = detail::Accessor<NativeImpl>;
+
+ /**
+ * Call this method to access the underlying object referenced by this
+ * NativeWeakPtr.
+ *
+ * Always check the returned Accessor object for availability before calling
+ * methods on it.
+ *
+ * For example, given:
+ *
+ * NativeWeakPtr<Foo> foo;
+ * auto accessor = foo.Access();
+ * if (accessor) {
+ * // Okay, safe to work with
+ * accessor->DoStuff();
+ * } else {
+ * // The object's strong reference was cleared and is no longer available!
+ * }
+ */
+ Accessor Access() const { return Accessor(mCtlBlock); }
+
+ /**
+ * Detach the underlying object's strong reference from its owning Java object
+ * and clean it up.
+ */
+ void Detach() {
+ if (!IsAttached()) {
+ // Never attached to begin with; no-op
+ return;
+ }
+
+ auto native = mCtlBlock->Clear();
+ if (!native) {
+ // Detach already in progress
+ return;
+ }
+
+ Object::LocalRef owner(mCtlBlock->GetJavaOwner());
+ MOZ_RELEASE_ASSERT(!!owner);
+
+ // Save the raw pointer before we move native into the runnable so that we
+ // may call OnWeakNonIntrusiveDetach on it even after moving native into
+ // the runnable.
+ NativeImpl* rawImpl =
+ detail::NativeWeakPtrControlBlock<NativeImpl>::StorageTraits::AsRaw(
+ native);
+ rawImpl->OnWeakNonIntrusiveDetach(
+ do_AddRef(new NativeWeakPtrDetachRunnable<NativeImpl>(
+ mCtlBlock.forget(), owner, std::move(native))));
+ }
+
+ /**
+ * This method does not indicate whether or not the weak pointer is still
+ * valid; it only indicates whether we're actually attached to one.
+ */
+ bool IsAttached() const { return !!mCtlBlock; }
+
+ /**
+ * Does this pointer reference the same object as the one referenced by the
+ * provided Accessor?
+ */
+ bool IsSame(const Accessor& aAccessor) const {
+ return mCtlBlock == aAccessor.mCtlBlock;
+ }
+
+ /**
+ * Does this pointer reference the same object as the one referenced by the
+ * provided Control Block?
+ */
+ bool IsSame(const RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>>&
+ aOther) const {
+ return mCtlBlock == aOther;
+ }
+
+ NativeWeakPtr() = default;
+ MOZ_IMPLICIT NativeWeakPtr(decltype(nullptr)) {}
+ NativeWeakPtr(const NativeWeakPtr& aOther) = default;
+ NativeWeakPtr(NativeWeakPtr&& aOther) = default;
+ NativeWeakPtr& operator=(const NativeWeakPtr& aOther) = default;
+ NativeWeakPtr& operator=(NativeWeakPtr&& aOther) = default;
+
+ NativeWeakPtr& operator=(decltype(nullptr)) {
+ mCtlBlock = nullptr;
+ return *this;
+ }
+
+ protected:
+ // Construction of initial NativeWeakPtr for aCtlBlock
+ explicit NativeWeakPtr(
+ already_AddRefed<detail::NativeWeakPtrControlBlock<NativeImpl>> aCtlBlock)
+ : mCtlBlock(aCtlBlock) {}
+
+ private:
+ // Construction of subsequent NativeWeakPtrs for aCtlBlock
+ explicit NativeWeakPtr(
+ const RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>>& aCtlBlock)
+ : mCtlBlock(aCtlBlock) {}
+
+ friend class NativeWeakPtrHolder<NativeImpl>;
+
+ protected:
+ RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>> mCtlBlock;
+};
+
+/**
+ * A pointer to an instance of this class should be stored in a Java object's
+ * JNIObject handle. New instances of native objects wrapped by NativeWeakPtr
+ * are created using the static methods of this class.
+ *
+ * Why do we have distinct methods here instead of using AttachNative like other
+ * pointer types that may be stored in JNIObject?
+ *
+ * Essentially, we want the creation and use of NativeWeakPtr to be as
+ * deliberate as possible. Forcing a different creation mechanism is part of
+ * that emphasis.
+ *
+ * Example:
+ *
+ * class NativeFoo {
+ * public:
+ * NativeFoo();
+ * void Bar();
+ * // The following method is required to be used with NativeWeakPtr
+ * void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer);
+ * };
+ *
+ * java::Object::LocalRef javaObj(...);
+ *
+ * // Create a new Foo that is attached to javaObj
+ * auto weakFoo = NativeWeakPtrHolder<NativeFoo>::Attach(javaObj);
+ *
+ * // Now I can save weakFoo, access it, do whatever I want
+ * if (auto accWeakFoo = weakFoo.Access()) {
+ * accWeakFoo->Bar();
+ * }
+ *
+ * // Detach from javaObj and clean up
+ * weakFoo.Detach();
+ */
+template <typename NativeImpl>
+class MOZ_HEAP_CLASS NativeWeakPtrHolder final
+ : public NativeWeakPtr<NativeImpl> {
+ using Base = NativeWeakPtr<NativeImpl>;
+
+ public:
+ using Accessor = typename Base::Accessor;
+ using StorageTraits =
+ typename detail::NativeWeakPtrControlBlock<NativeImpl>::StorageTraits;
+ using StorageType = typename StorageTraits::Type;
+
+ /**
+ * Create a new NativeImpl object, wrap it in a NativeWeakPtr, and store it
+ * in the Java object's JNIObject handle.
+ *
+ * @return A NativeWeakPtr object that references the newly-attached object.
+ */
+ template <typename Cls, typename JNIType, typename... Args>
+ static NativeWeakPtr<NativeImpl> Attach(const Ref<Cls, JNIType>& aJavaObject,
+ Args&&... aArgs) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ StorageType nativeImpl(new NativeImpl(std::forward<Args>(aArgs)...));
+ return AttachInternal(aJavaObject, std::move(nativeImpl));
+ }
+
+ /**
+ * Given a new NativeImpl object, wrap it in a NativeWeakPtr, and store it
+ * in the Java object's JNIObject handle.
+ *
+ * @return A NativeWeakPtr object that references the newly-attached object.
+ */
+ template <typename Cls, typename JNIType>
+ static NativeWeakPtr<NativeImpl> AttachExisting(
+ const Ref<Cls, JNIType>& aJavaObject,
+ already_AddRefed<NativeImpl> aNativeImpl) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ StorageType nativeImpl(aNativeImpl);
+ return AttachInternal(aJavaObject, std::move(nativeImpl));
+ }
+
+ ~NativeWeakPtrHolder() = default;
+
+ MOZ_IMPLICIT NativeWeakPtrHolder(decltype(nullptr)) = delete;
+ NativeWeakPtrHolder(const NativeWeakPtrHolder&) = delete;
+ NativeWeakPtrHolder(NativeWeakPtrHolder&&) = delete;
+ NativeWeakPtrHolder& operator=(const NativeWeakPtrHolder&) = delete;
+ NativeWeakPtrHolder& operator=(NativeWeakPtrHolder&&) = delete;
+ NativeWeakPtrHolder& operator=(decltype(nullptr)) = delete;
+
+ private:
+ template <typename Cls>
+ NativeWeakPtrHolder(const LocalRef<Cls>& aJavaObject,
+ StorageType&& aNativeImpl)
+ : NativeWeakPtr<NativeImpl>(
+ do_AddRef(new NativeWeakPtrControlBlock<NativeImpl>(
+ aJavaObject, std::move(aNativeImpl)))) {}
+
+ /**
+ * Internal function that actually wraps the native pointer, binds it to the
+ * JNIObject, and then returns the NativeWeakPtr result.
+ */
+ template <typename Cls, typename JNIType>
+ static NativeWeakPtr<NativeImpl> AttachInternal(
+ const Ref<Cls, JNIType>& aJavaObject, StorageType&& aPtr) {
+ auto localJavaObject = ToLocalRef(aJavaObject);
+ NativeWeakPtrHolder<NativeImpl>* holder =
+ new NativeWeakPtrHolder<NativeImpl>(localJavaObject, std::move(aPtr));
+ static_assert(
+ NativePtrPicker<NativeImpl>::value == NativePtrType::WEAK_NON_INTRUSIVE,
+ "This type is not compatible with mozilla::jni::NativeWeakPtr");
+ NativePtrTraits<NativeImpl>::Set(localJavaObject, holder);
+ return NativeWeakPtr<NativeImpl>(holder->mCtlBlock);
+ }
+};
+
+namespace detail {
+
+/**
+ * NativePtrTraits for the WEAK_NON_INTRUSIVE pointer type.
+ */
+template <class Impl>
+struct NativePtrTraits<Impl, /* Type = */ NativePtrType::WEAK_NON_INTRUSIVE> {
+ using AccessorType = typename NativeWeakPtrHolder<Impl>::Accessor;
+ using HandleType = NativeWeakPtrHolder<Impl>*;
+ using RefType = NativeWeakPtrHolder<Impl>* const;
+
+ static RefType Get(JNIEnv* env, jobject instance) {
+ return GetHandle(env, instance);
+ }
+
+ template <typename Cls>
+ static RefType Get(const LocalRef<Cls>& instance) {
+ return GetHandle(instance.Env(), instance.Get());
+ }
+
+ static AccessorType Access(RefType aPtr) { return aPtr->Access(); }
+
+ template <typename Cls>
+ static void Set(const LocalRef<Cls>& instance, HandleType ptr) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ const uintptr_t handle = reinterpret_cast<uintptr_t>(ptr);
+ Clear(instance);
+ SetNativeHandle(instance.Env(), instance.Get(), handle);
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ }
+
+ template <typename Cls>
+ static void Clear(const LocalRef<Cls>& instance) {
+ auto ptr = reinterpret_cast<HandleType>(
+ GetNativeHandle(instance.Env(), instance.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+
+ if (!ptr) {
+ return;
+ }
+
+ ptr->Detach();
+ }
+
+ // This call is not safe to do unless we know for sure that instance's
+ // native handle has not changed. It is up to NativeWeakPtrDetachRunnable
+ // to perform this check.
+ template <typename Cls>
+ static void ClearFinish(const LocalRef<Cls>& instance) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ JNIEnv* const env = instance.Env();
+ auto ptr =
+ reinterpret_cast<HandleType>(GetNativeHandle(env, instance.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ MOZ_RELEASE_ASSERT(!!ptr);
+
+ SetNativeHandle(env, instance.Get(), 0);
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ // Deletion of ptr is done by the caller
+ }
+
+ // The call is stale if the native object has been destroyed on the
+ // Gecko side, but the Java object is still attached to it through
+ // a weak pointer. Stale calls should be discarded. Note that it's
+ // an error if holder is nullptr here; we return false but the
+ // native call will throw an error.
+ template <class LocalRef>
+ static bool IsStale(const LocalRef& instance) {
+ JNIEnv* const env = mozilla::jni::GetEnvForThread();
+
+ // We cannot use Get here because that method throws an exception when the
+ // object is null, which is a valid state for a stale call.
+ const auto holder =
+ reinterpret_cast<HandleType>(GetNativeHandle(env, instance.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(env);
+
+ if (!holder || !holder->IsAttached()) {
+ return true;
+ }
+
+ auto acc(holder->Access());
+ return !acc;
+ }
+
+ private:
+ static HandleType GetHandle(JNIEnv* env, jobject instance) {
+ return reinterpret_cast<HandleType>(
+ CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance)));
+ }
+
+ template <typename Cls>
+ static HandleType GetHandle(const LocalRef<Cls>& instance) {
+ return GetHandle(instance.Env(), instance.Get());
+ }
+
+ friend class NativeWeakPtrHolder<Impl>;
+};
+
+} // namespace detail
+
+using namespace detail;
+
+/**
+ * For JNI native methods that are dispatched to a proxy, i.e. using
+ * @WrapForJNI(dispatchTo = "proxy"), the implementing C++ class must provide a
+ * OnNativeCall member. Subsequently, every native call is automatically
+ * wrapped in a functor object, and the object is passed to OnNativeCall. The
+ * OnNativeCall implementation can choose to invoke the call, save it, dispatch
+ * it to a different thread, etc. Each copy of functor may only be invoked
+ * once.
+ *
+ * class MyClass : public MyJavaClass::Natives<MyClass>
+ * {
+ * // ...
+ *
+ * template<class Functor>
+ * class ProxyRunnable final : public Runnable
+ * {
+ * Functor mCall;
+ * public:
+ * ProxyRunnable(Functor&& call) : mCall(std::move(call)) {}
+ * virtual void run() override { mCall(); }
+ * };
+ *
+ * public:
+ * template<class Functor>
+ * static void OnNativeCall(Functor&& call)
+ * {
+ * RunOnAnotherThread(new ProxyRunnable(std::move(call)));
+ * }
+ * };
+ */
+
+namespace detail {
+
+// ProxyArg is used to handle JNI ref arguments for proxies. Because a proxied
+// call may happen outside of the original JNI native call, we must save all
+// JNI ref arguments as global refs to avoid the arguments going out of scope.
+template <typename T>
+struct ProxyArg {
+ static_assert(mozilla::IsPod<T>::value, "T must be primitive type");
+
+ // Primitive types can be saved by value.
+ typedef T Type;
+ typedef typename TypeAdapter<T>::JNIType JNIType;
+
+ static void Clear(JNIEnv* env, Type&) {}
+
+ static Type From(JNIEnv* env, JNIType val) {
+ return TypeAdapter<T>::ToNative(env, val);
+ }
+};
+
+template <class C, typename T>
+struct ProxyArg<Ref<C, T>> {
+ // Ref types need to be saved by global ref.
+ typedef typename C::GlobalRef Type;
+ typedef typename TypeAdapter<Ref<C, T>>::JNIType JNIType;
+
+ static void Clear(JNIEnv* env, Type& ref) { ref.Clear(env); }
+
+ static Type From(JNIEnv* env, JNIType val) {
+ return Type(env, C::Ref::From(val));
+ }
+};
+
+template <typename C>
+struct ProxyArg<const C&> : ProxyArg<C> {};
+template <>
+struct ProxyArg<StringParam> : ProxyArg<String::Ref> {};
+template <class C>
+struct ProxyArg<LocalRef<C>> : ProxyArg<typename C::Ref> {};
+
+// ProxyNativeCall implements the functor object that is passed to OnNativeCall
+template <class Impl, class Owner, bool IsStatic,
+ bool HasThisArg /* has instance/class local ref in the call */,
+ typename... Args>
+class ProxyNativeCall {
+ // "this arg" refers to the Class::LocalRef (for static methods) or
+ // Owner::LocalRef (for instance methods) that we optionally (as indicated
+ // by HasThisArg) pass into the destination C++ function.
+ using ThisArgClass = std::conditional_t<IsStatic, Class, Owner>;
+ using ThisArgJNIType = std::conditional_t<IsStatic, jclass, jobject>;
+
+ // Type signature of the destination C++ function, which matches the
+ // Method template parameter in NativeStubImpl::Wrap.
+ using NativeCallType = std::conditional_t<
+ IsStatic,
+ std::conditional_t<HasThisArg, void (*)(const Class::LocalRef&, Args...),
+ void (*)(Args...)>,
+ std::conditional_t<
+ HasThisArg, void (Impl::*)(const typename Owner::LocalRef&, Args...),
+ void (Impl::*)(Args...)>>;
+
+ // Destination C++ function.
+ NativeCallType mNativeCall;
+ // Saved this arg.
+ typename ThisArgClass::GlobalRef mThisArg;
+ // Saved arguments.
+ mozilla::Tuple<typename ProxyArg<Args>::Type...> mArgs;
+
+ // We cannot use IsStatic and HasThisArg directly (without going through
+ // extra hoops) because GCC complains about invalid overloads, so we use
+ // another pair of template parameters, Static and ThisArg.
+
+ template <bool Static, bool ThisArg, size_t... Indices>
+ std::enable_if_t<Static && ThisArg, void> Call(
+ const Class::LocalRef& cls, std::index_sequence<Indices...>) const {
+ (*mNativeCall)(cls, mozilla::Get<Indices>(mArgs)...);
+ }
+
+ template <bool Static, bool ThisArg, size_t... Indices>
+ std::enable_if_t<Static && !ThisArg, void> Call(
+ const Class::LocalRef& cls, std::index_sequence<Indices...>) const {
+ (*mNativeCall)(mozilla::Get<Indices>(mArgs)...);
+ }
+
+ template <bool Static, bool ThisArg, size_t... Indices>
+ std::enable_if_t<!Static && ThisArg, void> Call(
+ const typename Owner::LocalRef& inst,
+ std::index_sequence<Indices...>) const {
+ auto impl = NativePtrTraits<Impl>::Access(NativePtrTraits<Impl>::Get(inst));
+ MOZ_CATCH_JNI_EXCEPTION(inst.Env());
+ (impl->*mNativeCall)(inst, mozilla::Get<Indices>(mArgs)...);
+ }
+
+ template <bool Static, bool ThisArg, size_t... Indices>
+ std::enable_if_t<!Static && !ThisArg, void> Call(
+ const typename Owner::LocalRef& inst,
+ std::index_sequence<Indices...>) const {
+ auto impl = NativePtrTraits<Impl>::Access(NativePtrTraits<Impl>::Get(inst));
+ MOZ_CATCH_JNI_EXCEPTION(inst.Env());
+ (impl->*mNativeCall)(mozilla::Get<Indices>(mArgs)...);
+ }
+
+ template <size_t... Indices>
+ void Clear(JNIEnv* env, std::index_sequence<Indices...>) {
+ int dummy[] = {(ProxyArg<Args>::Clear(env, Get<Indices>(mArgs)), 0)...};
+ mozilla::Unused << dummy;
+ }
+
+ static decltype(auto) GetNativeObject(Class::Param thisArg) {
+ return nullptr;
+ }
+
+ static decltype(auto) GetNativeObject(typename Owner::Param thisArg) {
+ return NativePtrTraits<Impl>::Access(
+ NativePtrTraits<Impl>::Get(GetEnvForThread(), thisArg.Get()));
+ }
+
+ public:
+ // The class that implements the call target.
+ typedef Impl TargetClass;
+ typedef typename ThisArgClass::Param ThisArgType;
+
+ static const bool isStatic = IsStatic;
+
+ ProxyNativeCall(ThisArgJNIType thisArg, NativeCallType nativeCall,
+ JNIEnv* env, typename ProxyArg<Args>::JNIType... args)
+ : mNativeCall(nativeCall),
+ mThisArg(env, ThisArgClass::Ref::From(thisArg)),
+ mArgs(ProxyArg<Args>::From(env, args)...) {}
+
+ ProxyNativeCall(ProxyNativeCall&&) = default;
+ ProxyNativeCall(const ProxyNativeCall&) = default;
+
+ // Get class ref for static calls or object ref for instance calls.
+ typename ThisArgClass::Param GetThisArg() const { return mThisArg; }
+
+ // Get the native object targeted by this call.
+ // Returns nullptr for static calls.
+ decltype(auto) GetNativeObject() const { return GetNativeObject(mThisArg); }
+
+ // Return if target is the given function pointer / pointer-to-member.
+ // Because we can only compare pointers of the same type, we use a
+ // templated overload that is chosen only if given a different type of
+ // pointer than our target pointer type.
+ bool IsTarget(NativeCallType call) const { return call == mNativeCall; }
+ template <typename T>
+ bool IsTarget(T&&) const {
+ return false;
+ }
+
+ // Redirect the call to another function / class member with the same
+ // signature as the original target. Crash if given a wrong signature.
+ void SetTarget(NativeCallType call) { mNativeCall = call; }
+ template <typename T>
+ void SetTarget(T&&) const {
+ MOZ_CRASH();
+ }
+
+ void operator()() {
+ JNIEnv* const env = GetEnvForThread();
+ typename ThisArgClass::LocalRef thisArg(env, mThisArg);
+ Call<IsStatic, HasThisArg>(thisArg, std::index_sequence_for<Args...>{});
+
+ // Clear all saved global refs. We do this after the call is invoked,
+ // and not inside the destructor because we already have a JNIEnv here,
+ // so it's more efficient to clear out the saved args here. The
+ // downside is that the call can only be invoked once.
+ Clear(env, std::index_sequence_for<Args...>{});
+ mThisArg.Clear(env);
+ }
+};
+
+template <class Impl, bool HasThisArg, typename... Args>
+struct Dispatcher {
+ template <class Traits, bool IsStatic = Traits::isStatic,
+ typename... ProxyArgs>
+ static std::enable_if_t<Traits::dispatchTarget == DispatchTarget::PROXY, void>
+ Run(ProxyArgs&&... args) {
+ Impl::OnNativeCall(
+ ProxyNativeCall<Impl, typename Traits::Owner, IsStatic, HasThisArg,
+ Args...>(std::forward<ProxyArgs>(args)...));
+ }
+
+ template <class Traits, bool IsStatic = Traits::isStatic, typename ThisArg,
+ typename... ProxyArgs>
+ static std::enable_if_t<
+ Traits::dispatchTarget == DispatchTarget::GECKO_PRIORITY, void>
+ Run(ThisArg thisArg, ProxyArgs&&... args) {
+ // For a static method, do not forward the "this arg" (i.e. the class
+ // local ref) if the implementation does not request it. This saves us
+ // a pair of calls to add/delete global ref.
+ auto proxy =
+ ProxyNativeCall<Impl, typename Traits::Owner, IsStatic, HasThisArg,
+ Args...>((HasThisArg || !IsStatic) ? thisArg : nullptr,
+ std::forward<ProxyArgs>(args)...);
+ DispatchToGeckoPriorityQueue(
+ NS_NewRunnableFunction("PriorityNativeCall", std::move(proxy)));
+ }
+
+ template <class Traits, bool IsStatic = Traits::isStatic, typename ThisArg,
+ typename... ProxyArgs>
+ static std::enable_if_t<Traits::dispatchTarget == DispatchTarget::GECKO, void>
+ Run(ThisArg thisArg, ProxyArgs&&... args) {
+ // For a static method, do not forward the "this arg" (i.e. the class
+ // local ref) if the implementation does not request it. This saves us
+ // a pair of calls to add/delete global ref.
+ auto proxy =
+ ProxyNativeCall<Impl, typename Traits::Owner, IsStatic, HasThisArg,
+ Args...>((HasThisArg || !IsStatic) ? thisArg : nullptr,
+ std::forward<ProxyArgs>(args)...);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("GeckoNativeCall", std::move(proxy)));
+ }
+
+ template <class Traits, bool IsStatic = false, typename... ProxyArgs>
+ static std::enable_if_t<Traits::dispatchTarget == DispatchTarget::CURRENT,
+ void>
+ Run(ProxyArgs&&... args) {
+ MOZ_CRASH("Unreachable code");
+ }
+};
+
+} // namespace detail
+
+// Wrapper methods that convert arguments from the JNI types to the native
+// types, e.g. from jobject to jni::Object::Ref. For instance methods, the
+// wrapper methods also convert calls to calls on objects.
+//
+// We need specialization for static/non-static because the two have different
+// signatures (jobject vs jclass and Impl::*Method vs *Method).
+// We need specialization for return type, because void return type requires
+// us to not deal with the return value.
+
+// Bug 1207642 - Work around Dalvik bug by realigning stack on JNI entry
+#ifdef __i386__
+# define MOZ_JNICALL JNICALL __attribute__((force_align_arg_pointer))
+#else
+# define MOZ_JNICALL JNICALL
+#endif
+
+template <class Traits, class Impl, class Args = typename Traits::Args>
+class NativeStub;
+
+template <class Traits, class Impl, typename... Args>
+class NativeStub<Traits, Impl, jni::Args<Args...>> {
+ using Owner = typename Traits::Owner;
+ using ReturnType = typename Traits::ReturnType;
+
+ static constexpr bool isStatic = Traits::isStatic;
+ static constexpr bool isVoid = std::is_void_v<ReturnType>;
+
+ struct VoidType {
+ using JNIType = void;
+ };
+ using ReturnJNIType =
+ typename std::conditional_t<isVoid, VoidType,
+ TypeAdapter<ReturnType>>::JNIType;
+
+ using ReturnTypeForNonVoidInstance =
+ std::conditional_t<!isStatic && !isVoid, ReturnType, VoidType>;
+ using ReturnTypeForVoidInstance =
+ std::conditional_t<!isStatic && isVoid, ReturnType, VoidType&>;
+ using ReturnTypeForNonVoidStatic =
+ std::conditional_t<isStatic && !isVoid, ReturnType, VoidType>;
+ using ReturnTypeForVoidStatic =
+ std::conditional_t<isStatic && isVoid, ReturnType, VoidType&>;
+
+ static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT || isVoid,
+ "Dispatched calls must have void return type");
+
+ public:
+ // Non-void instance method
+ template <ReturnTypeForNonVoidInstance (Impl::*Method)(Args...)>
+ static MOZ_JNICALL ReturnJNIType
+ Wrap(JNIEnv* env, jobject instance,
+ typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ auto impl = NativePtrTraits<Impl>::Access(
+ NativePtrTraits<Impl>::Get(env, instance));
+ if (!impl) {
+ // There is a pending JNI exception at this point.
+ return ReturnJNIType();
+ }
+ return TypeAdapter<ReturnType>::FromNative(
+ env, (impl->*Method)(TypeAdapter<Args>::ToNative(env, args)...));
+ }
+
+ // Non-void instance method with instance reference
+ template <ReturnTypeForNonVoidInstance (Impl::*Method)(
+ const typename Owner::LocalRef&, Args...)>
+ static MOZ_JNICALL ReturnJNIType
+ Wrap(JNIEnv* env, jobject instance,
+ typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ auto impl = NativePtrTraits<Impl>::Access(
+ NativePtrTraits<Impl>::Get(env, instance));
+ if (!impl) {
+ // There is a pending JNI exception at this point.
+ return ReturnJNIType();
+ }
+ auto self = Owner::LocalRef::Adopt(env, instance);
+ const auto res = TypeAdapter<ReturnType>::FromNative(
+ env, (impl->*Method)(self, TypeAdapter<Args>::ToNative(env, args)...));
+ self.Forget();
+ return res;
+ }
+
+ // Void instance method
+ template <ReturnTypeForVoidInstance (Impl::*Method)(Args...)>
+ static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance,
+ typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
+ Dispatcher<Impl, /* HasThisArg */ false, Args...>::template Run<Traits>(
+ instance, Method, env, args...);
+ return;
+ }
+
+ auto impl = NativePtrTraits<Impl>::Access(
+ NativePtrTraits<Impl>::Get(env, instance));
+ if (!impl) {
+ // There is a pending JNI exception at this point.
+ return;
+ }
+ (impl->*Method)(TypeAdapter<Args>::ToNative(env, args)...);
+ }
+
+ // Void instance method with instance reference
+ template <ReturnTypeForVoidInstance (Impl::*Method)(
+ const typename Owner::LocalRef&, Args...)>
+ static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance,
+ typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
+ Dispatcher<Impl, /* HasThisArg */ true, Args...>::template Run<Traits>(
+ instance, Method, env, args...);
+ return;
+ }
+
+ auto impl = NativePtrTraits<Impl>::Access(
+ NativePtrTraits<Impl>::Get(env, instance));
+ if (!impl) {
+ // There is a pending JNI exception at this point.
+ return;
+ }
+ auto self = Owner::LocalRef::Adopt(env, instance);
+ (impl->*Method)(self, TypeAdapter<Args>::ToNative(env, args)...);
+ self.Forget();
+ }
+
+ // Overload for DisposeNative
+ template <ReturnTypeForVoidInstance (*DisposeNative)(
+ const typename Owner::LocalRef&)>
+ static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
+ using LocalRef = typename Owner::LocalRef;
+ Dispatcher<Impl, /* HasThisArg */ false, const LocalRef&>::template Run<
+ Traits, /* IsStatic */ true>(
+ /* ThisArg */ nullptr, DisposeNative, env, instance);
+ return;
+ }
+
+ auto self = Owner::LocalRef::Adopt(env, instance);
+ DisposeNative(self);
+ self.Forget();
+ }
+
+ // Non-void static method
+ template <ReturnTypeForNonVoidStatic (*Method)(Args...)>
+ static MOZ_JNICALL ReturnJNIType
+ Wrap(JNIEnv* env, jclass, typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ return TypeAdapter<ReturnType>::FromNative(
+ env, (*Method)(TypeAdapter<Args>::ToNative(env, args)...));
+ }
+
+ // Non-void static method with class reference
+ template <ReturnTypeForNonVoidStatic (*Method)(const Class::LocalRef&,
+ Args...)>
+ static MOZ_JNICALL ReturnJNIType
+ Wrap(JNIEnv* env, jclass cls, typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ auto clazz = Class::LocalRef::Adopt(env, cls);
+ const auto res = TypeAdapter<ReturnType>::FromNative(
+ env, (*Method)(clazz, TypeAdapter<Args>::ToNative(env, args)...));
+ clazz.Forget();
+ return res;
+ }
+
+ // Void static method
+ template <ReturnTypeForVoidStatic (*Method)(Args...)>
+ static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls,
+ typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
+ Dispatcher<Impl, /* HasThisArg */ false, Args...>::template Run<Traits>(
+ cls, Method, env, args...);
+ return;
+ }
+
+ (*Method)(TypeAdapter<Args>::ToNative(env, args)...);
+ }
+
+ // Void static method with class reference
+ template <ReturnTypeForVoidStatic (*Method)(const Class::LocalRef&, Args...)>
+ static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls,
+ typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
+ Dispatcher<Impl, /* HasThisArg */ true, Args...>::template Run<Traits>(
+ cls, Method, env, args...);
+ return;
+ }
+
+ auto clazz = Class::LocalRef::Adopt(env, cls);
+ (*Method)(clazz, TypeAdapter<Args>::ToNative(env, args)...);
+ clazz.Forget();
+ }
+};
+
+// Generate a JNINativeMethod from a native
+// method's traits class and a wrapped stub.
+template <class Traits, typename Ret, typename... Args>
+constexpr JNINativeMethod MakeNativeMethod(MOZ_JNICALL Ret (*stub)(JNIEnv*,
+ Args...)) {
+ return {Traits::name, Traits::signature, reinterpret_cast<void*>(stub)};
+}
+
+// Class inherited by implementing class.
+template <class Cls, class Impl>
+class NativeImpl {
+ typedef typename Cls::template Natives<Impl> Natives;
+
+ static bool sInited;
+
+ public:
+ static void Init() {
+ if (sInited) {
+ return;
+ }
+ const auto& ctx = typename Cls::Context();
+ ctx.Env()->RegisterNatives(
+ ctx.ClassRef(), Natives::methods,
+ sizeof(Natives::methods) / sizeof(Natives::methods[0]));
+ MOZ_CATCH_JNI_EXCEPTION(ctx.Env());
+ sInited = true;
+ }
+
+ protected:
+ // Associate a C++ instance with a Java instance.
+ static void AttachNative(const typename Cls::LocalRef& instance,
+ SupportsWeakPtr* ptr) {
+ static_assert(NativePtrPicker<Impl>::value == NativePtrType::WEAK_INTRUSIVE,
+ "Use another AttachNative for non-WeakPtr usage");
+ return NativePtrTraits<Impl>::Set(instance, static_cast<Impl*>(ptr));
+ }
+
+ static void AttachNative(const typename Cls::LocalRef& instance,
+ UniquePtr<Impl>&& ptr) {
+ static_assert(NativePtrPicker<Impl>::value == NativePtrType::OWNING,
+ "Use another AttachNative for WeakPtr or RefPtr usage");
+ return NativePtrTraits<Impl>::Set(instance, std::move(ptr));
+ }
+
+ static void AttachNative(const typename Cls::LocalRef& instance, Impl* ptr) {
+ static_assert(NativePtrPicker<Impl>::value == NativePtrType::REFPTR,
+ "Use another AttachNative for non-RefPtr usage");
+ return NativePtrTraits<Impl>::Set(instance, ptr);
+ }
+
+ // Get the C++ instance associated with a Java instance.
+ // There is always a pending exception if the return value is nullptr.
+ static decltype(auto) GetNative(const typename Cls::LocalRef& instance) {
+ return NativePtrTraits<Impl>::Get(instance);
+ }
+
+ static void DisposeNative(const typename Cls::LocalRef& instance) {
+ NativePtrTraits<Impl>::Clear(instance);
+ }
+
+ NativeImpl() {
+ // Initialize on creation if not already initialized.
+ Init();
+ }
+};
+
+// Define static member.
+template <class C, class I>
+bool NativeImpl<C, I>::sInited;
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Natives_h__
diff --git a/widget/android/jni/Refs.h b/widget/android/jni/Refs.h
new file mode 100644
index 0000000000..8a7276f595
--- /dev/null
+++ b/widget/android/jni/Refs.h
@@ -0,0 +1,1016 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_jni_Refs_h__
+#define mozilla_jni_Refs_h__
+
+#include <jni.h>
+
+#include <utility>
+
+#include "mozilla/jni/Utils.h"
+#include "nsError.h" // for nsresult
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace jni {
+
+// Wrapped object reference (e.g. jobject, jclass, etc...)
+template <class Cls, typename JNIType>
+class Ref;
+// Represents a calling context for JNI methods.
+template <class Cls, typename JNIType>
+class Context;
+// Wrapped local reference that inherits from Ref.
+template <class Cls>
+class LocalRef;
+// Wrapped global reference that inherits from Ref.
+template <class Cls>
+class GlobalRef;
+// Wrapped weak reference that inherits from Ref.
+template <class Cls>
+class WeakRef;
+// Wrapped dangling reference that's owned by someone else.
+template <class Cls>
+class DependentRef;
+
+// Class to hold the native types of a method's arguments.
+// For example, if a method has signature (ILjava/lang/String;)V,
+// its arguments class would be jni::Args<int32_t, jni::String::Param>
+template <typename...>
+struct Args {};
+
+class Object;
+
+// Base class for Ref and its specializations.
+template <class Cls, typename Type>
+class Ref {
+ template <class C, typename T>
+ friend class Ref;
+
+ using Self = Ref<Cls, Type>;
+ using bool_type = void (Self::*)() const;
+ void non_null_reference() const {}
+
+ // A Cls-derivative that allows copying
+ // (e.g. when acting as a return value).
+ struct CopyableCtx : public Context<Cls, Type> {
+ CopyableCtx(JNIEnv* env, Type instance)
+ : Context<Cls, Type>(env, instance) {}
+
+ CopyableCtx(const CopyableCtx& cls)
+ : Context<Cls, Type>(cls.Env(), cls.Get()) {}
+ };
+
+ // Private copy constructor so that there's no danger of assigning a
+ // temporary LocalRef/GlobalRef to a Ref, and potentially use the Ref
+ // after the source had been freed.
+ Ref(const Ref&) = default;
+
+ protected:
+ static JNIEnv* FindEnv() {
+ return Cls::callingThread == CallingThread::GECKO ? GetGeckoThreadEnv()
+ : GetEnvForThread();
+ }
+
+ Type mInstance;
+
+ // Protected jobject constructor because outside code should be using
+ // Ref::From. Using Ref::From makes it very easy to see which code is using
+ // raw JNI types for future refactoring.
+ explicit Ref(Type instance) : mInstance(instance) {}
+
+ public:
+ using JNIType = Type;
+
+ class AutoLock {
+ friend class Ref<Cls, Type>;
+
+ JNIEnv* const mEnv;
+ Type mInstance;
+
+ explicit AutoLock(Type aInstance)
+ : mEnv(FindEnv()), mInstance(mEnv->NewLocalRef(aInstance)) {
+ mEnv->MonitorEnter(mInstance);
+ MOZ_CATCH_JNI_EXCEPTION(mEnv);
+ }
+
+ public:
+ AutoLock(AutoLock&& aOther)
+ : mEnv(aOther.mEnv), mInstance(aOther.mInstance) {
+ aOther.mInstance = nullptr;
+ }
+
+ ~AutoLock() { Unlock(); }
+
+ void Unlock() {
+ if (mInstance) {
+ mEnv->MonitorExit(mInstance);
+ mEnv->DeleteLocalRef(mInstance);
+ MOZ_CATCH_JNI_EXCEPTION(mEnv);
+ mInstance = nullptr;
+ }
+ }
+ };
+
+ // Construct a Ref form a raw JNI reference.
+ static Ref<Cls, Type> From(JNIType obj) { return Ref<Cls, Type>(obj); }
+
+ // Construct a Ref form a generic object reference.
+ static Ref<Cls, Type> From(const Ref<Object, jobject>& obj) {
+ return Ref<Cls, Type>(JNIType(obj.Get()));
+ }
+
+ MOZ_IMPLICIT Ref(decltype(nullptr)) : mInstance(nullptr) {}
+
+ // Get the raw JNI reference.
+ JNIType Get() const { return mInstance; }
+
+ template <class T>
+ bool IsInstanceOf() const {
+ return FindEnv()->IsInstanceOf(mInstance, typename T::Context().ClassRef());
+ }
+
+ template <class T>
+ typename T::Ref Cast() const {
+#ifdef MOZ_CHECK_JNI
+ MOZ_RELEASE_ASSERT(FindEnv()->IsAssignableFrom(
+ Context<Cls, Type>().ClassRef(), typename T::Context().ClassRef()));
+#endif
+ return T::Ref::From(*this);
+ }
+
+ AutoLock Lock() const { return AutoLock(mInstance); }
+
+ bool operator==(const Ref& other) const {
+ // Treat two references of the same object as being the same.
+ return mInstance == other.mInstance ||
+ JNI_FALSE != FindEnv()->IsSameObject(mInstance, other.mInstance);
+ }
+
+ bool operator!=(const Ref& other) const { return !operator==(other); }
+
+ bool operator==(decltype(nullptr)) const { return !mInstance; }
+
+ bool operator!=(decltype(nullptr)) const { return !!mInstance; }
+
+ CopyableCtx operator->() const { return CopyableCtx(FindEnv(), mInstance); }
+
+ CopyableCtx operator*() const { return operator->(); }
+
+ // Any ref can be cast to an object ref.
+ operator Ref<Object, jobject>() const {
+ return Ref<Object, jobject>(mInstance);
+ }
+
+ // Null checking (e.g. !!ref) using the safe-bool idiom.
+ operator bool_type() const {
+ return mInstance ? &Self::non_null_reference : nullptr;
+ }
+
+ // We don't allow implicit conversion to jobject because that can lead
+ // to easy mistakes such as assigning a temporary LocalRef to a jobject,
+ // and using the jobject after the LocalRef has been freed.
+
+ // We don't allow explicit conversion, to make outside code use Ref::Get.
+ // Using Ref::Get makes it very easy to see which code is using raw JNI
+ // types to make future refactoring easier.
+
+ // operator JNIType() const = delete;
+};
+
+// Represents a calling context for JNI methods.
+template <class Cls, typename Type>
+class Context : public Ref<Cls, Type> {
+ using Ref = jni::Ref<Cls, Type>;
+
+ static jclass sClassRef; // global reference
+
+ protected:
+ JNIEnv* const mEnv;
+
+ public:
+ Context() : Ref(nullptr), mEnv(Ref::FindEnv()) {}
+
+ Context(JNIEnv* env, Type instance) : Ref(instance), mEnv(env) {}
+
+ jclass ClassRef() const {
+ if (!sClassRef) {
+ const jclass cls = GetClassRef(mEnv, Cls::name);
+ sClassRef = jclass(mEnv->NewGlobalRef(cls));
+ mEnv->DeleteLocalRef(cls);
+ }
+ return sClassRef;
+ }
+
+ JNIEnv* Env() const { return mEnv; }
+
+ template <class T>
+ bool IsInstanceOf() const {
+ return mEnv->IsInstanceOf(Ref::mInstance,
+ typename T::Context(mEnv, nullptr).ClassRef());
+ }
+
+ bool operator==(const Ref& other) const {
+ // Treat two references of the same object as being the same.
+ return Ref::mInstance == other.Get() ||
+ JNI_FALSE != mEnv->IsSameObject(Ref::mInstance, other.Get());
+ }
+
+ bool operator!=(const Ref& other) const { return !operator==(other); }
+
+ bool operator==(decltype(nullptr)) const { return !Ref::mInstance; }
+
+ bool operator!=(decltype(nullptr)) const { return !!Ref::mInstance; }
+
+ Cls operator->() const {
+ MOZ_ASSERT(Ref::mInstance, "Null jobject");
+ return Cls(*this);
+ }
+
+ const Context<Cls, Type>& operator*() const { return *this; }
+};
+
+template <class C, typename T>
+jclass Context<C, T>::sClassRef;
+
+template <class Cls, typename Type = jobject>
+class ObjectBase {
+ protected:
+ const jni::Context<Cls, Type>& mCtx;
+
+ jclass ClassRef() const { return mCtx.ClassRef(); }
+ JNIEnv* Env() const { return mCtx.Env(); }
+ Type Instance() const { return mCtx.Get(); }
+
+ public:
+ using Ref = jni::Ref<Cls, Type>;
+ using Context = jni::Context<Cls, Type>;
+ using LocalRef = jni::LocalRef<Cls>;
+ using GlobalRef = jni::GlobalRef<Cls>;
+ using WeakRef = jni::WeakRef<Cls>;
+ using Param = const Ref&;
+
+ static const CallingThread callingThread = CallingThread::ANY;
+ static const char name[];
+
+ explicit ObjectBase(const Context& ctx) : mCtx(ctx) {}
+
+ Cls* operator->() { return static_cast<Cls*>(this); }
+};
+
+// Binding for a plain jobject.
+class Object : public ObjectBase<Object, jobject> {
+ public:
+ explicit Object(const Context& ctx) : ObjectBase<Object, jobject>(ctx) {}
+};
+
+// Binding for a built-in object reference other than jobject.
+template <typename T>
+class TypedObject : public ObjectBase<TypedObject<T>, T> {
+ public:
+ explicit TypedObject(const Context<TypedObject<T>, T>& ctx)
+ : ObjectBase<TypedObject<T>, T>(ctx) {}
+};
+
+// Binding for a boxed primitive object.
+template <typename T>
+class BoxedObject : public ObjectBase<BoxedObject<T>, jobject> {
+ public:
+ explicit BoxedObject(const Context<BoxedObject<T>, jobject>& ctx)
+ : ObjectBase<BoxedObject<T>, jobject>(ctx) {}
+};
+
+template <>
+const char ObjectBase<Object, jobject>::name[];
+template <>
+const char ObjectBase<TypedObject<jstring>, jstring>::name[];
+template <>
+const char ObjectBase<TypedObject<jclass>, jclass>::name[];
+template <>
+const char ObjectBase<TypedObject<jthrowable>, jthrowable>::name[];
+template <>
+const char ObjectBase<BoxedObject<jboolean>, jobject>::name[];
+template <>
+const char ObjectBase<BoxedObject<jbyte>, jobject>::name[];
+template <>
+const char ObjectBase<BoxedObject<jchar>, jobject>::name[];
+template <>
+const char ObjectBase<BoxedObject<jshort>, jobject>::name[];
+template <>
+const char ObjectBase<BoxedObject<jint>, jobject>::name[];
+template <>
+const char ObjectBase<BoxedObject<jlong>, jobject>::name[];
+template <>
+const char ObjectBase<BoxedObject<jfloat>, jobject>::name[];
+template <>
+const char ObjectBase<BoxedObject<jdouble>, jobject>::name[];
+template <>
+const char ObjectBase<TypedObject<jbooleanArray>, jbooleanArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jbyteArray>, jbyteArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jcharArray>, jcharArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jshortArray>, jshortArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jintArray>, jintArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jlongArray>, jlongArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jfloatArray>, jfloatArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jdoubleArray>, jdoubleArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jobjectArray>, jobjectArray>::name[];
+
+// Define bindings for built-in types.
+using String = TypedObject<jstring>;
+using Class = TypedObject<jclass>;
+using Throwable = TypedObject<jthrowable>;
+
+using Boolean = BoxedObject<jboolean>;
+using Byte = BoxedObject<jbyte>;
+using Character = BoxedObject<jchar>;
+using Short = BoxedObject<jshort>;
+using Integer = BoxedObject<jint>;
+using Long = BoxedObject<jlong>;
+using Float = BoxedObject<jfloat>;
+using Double = BoxedObject<jdouble>;
+
+using BooleanArray = TypedObject<jbooleanArray>;
+using ByteArray = TypedObject<jbyteArray>;
+using CharArray = TypedObject<jcharArray>;
+using ShortArray = TypedObject<jshortArray>;
+using IntArray = TypedObject<jintArray>;
+using LongArray = TypedObject<jlongArray>;
+using FloatArray = TypedObject<jfloatArray>;
+using DoubleArray = TypedObject<jdoubleArray>;
+using ObjectArray = TypedObject<jobjectArray>;
+
+namespace detail {
+
+// See explanation in LocalRef.
+template <class Cls>
+struct GenericObject {
+ using Type = Object;
+};
+template <>
+struct GenericObject<Object> {
+ struct Type {
+ using Ref = jni::Ref<Type, jobject>;
+ using Context = jni::Context<Type, jobject>;
+ };
+};
+template <class Cls>
+struct GenericLocalRef {
+ template <class C>
+ struct Type : jni::Object {};
+};
+template <>
+struct GenericLocalRef<Object> {
+ template <class C>
+ using Type = jni::LocalRef<C>;
+};
+
+} // namespace detail
+
+template <class Cls>
+class LocalRef : public Cls::Context {
+ template <class C>
+ friend class LocalRef;
+
+ using Ctx = typename Cls::Context;
+ using Ref = typename Cls::Ref;
+ using JNIType = typename Ref::JNIType;
+
+ // In order to be able to convert LocalRef<Object> to LocalRef<Cls>, we
+ // need constructors and copy assignment operators that take in a
+ // LocalRef<Object> argument. However, if Cls *is* Object, we would have
+ // duplicated constructors and operators with LocalRef<Object> arguments. To
+ // avoid this conflict, we use GenericObject, which is defined as Object for
+ // LocalRef<non-Object> and defined as a dummy class for LocalRef<Object>.
+ using GenericObject = typename detail::GenericObject<Cls>::Type;
+
+ // Similarly, GenericLocalRef is useed to convert LocalRef<Cls> to,
+ // LocalRef<Object>. It's defined as LocalRef<C> for Cls == Object,
+ // and defined as a dummy template class for Cls != Object.
+ template <class C>
+ using GenericLocalRef =
+ typename detail::GenericLocalRef<Cls>::template Type<C>;
+
+ static JNIType NewLocalRef(JNIEnv* env, JNIType obj) {
+ return JNIType(obj ? env->NewLocalRef(obj) : nullptr);
+ }
+
+ LocalRef(JNIEnv* env, JNIType instance) : Ctx(env, instance) {}
+
+ LocalRef& swap(LocalRef& other) {
+ auto instance = other.mInstance;
+ other.mInstance = Ctx::mInstance;
+ Ctx::mInstance = instance;
+ return *this;
+ }
+
+ public:
+ // Construct a LocalRef from a raw JNI local reference. Unlike Ref::From,
+ // LocalRef::Adopt returns a LocalRef that will delete the local reference
+ // when going out of scope.
+ static LocalRef Adopt(JNIType instance) {
+ return LocalRef(Ref::FindEnv(), instance);
+ }
+
+ static LocalRef Adopt(JNIEnv* env, JNIType instance) {
+ return LocalRef(env, instance);
+ }
+
+ // Copy constructor.
+ LocalRef(const LocalRef<Cls>& ref)
+ : Ctx(ref.mEnv, NewLocalRef(ref.mEnv, ref.mInstance)) {}
+
+ // Move constructor.
+ LocalRef(LocalRef<Cls>&& ref) : Ctx(ref.mEnv, ref.mInstance) {
+ ref.mInstance = nullptr;
+ }
+
+ explicit LocalRef(JNIEnv* env = Ref::FindEnv()) : Ctx(env, nullptr) {}
+
+ // Construct a LocalRef from any Ref,
+ // which means creating a new local reference.
+ MOZ_IMPLICIT LocalRef(const Ref& ref) : Ctx(Ref::FindEnv(), nullptr) {
+ Ctx::mInstance = NewLocalRef(Ctx::mEnv, ref.Get());
+ }
+
+ LocalRef(JNIEnv* env, const Ref& ref)
+ : Ctx(env, NewLocalRef(env, ref.Get())) {}
+
+ // Move a LocalRef<Object> into a LocalRef<Cls> without
+ // creating/deleting local references.
+ MOZ_IMPLICIT LocalRef(LocalRef<GenericObject>&& ref)
+ : Ctx(ref.mEnv, JNIType(ref.mInstance)) {
+ ref.mInstance = nullptr;
+ }
+
+ template <class C>
+ MOZ_IMPLICIT LocalRef(GenericLocalRef<C>&& ref)
+ : Ctx(ref.mEnv, ref.mInstance) {
+ ref.mInstance = nullptr;
+ }
+
+ // Implicitly converts nullptr to LocalRef.
+ MOZ_IMPLICIT LocalRef(decltype(nullptr)) : Ctx(Ref::FindEnv(), nullptr) {}
+
+ ~LocalRef() {
+ if (Ctx::mInstance) {
+ Ctx::mEnv->DeleteLocalRef(Ctx::mInstance);
+ Ctx::mInstance = nullptr;
+ }
+ }
+
+ // Get the raw JNI reference that can be used as a return value.
+ // Returns the same JNI type (jobject, jstring, etc.) as the underlying Ref.
+ typename Ref::JNIType Forget() {
+ const auto obj = Ctx::Get();
+ Ctx::mInstance = nullptr;
+ return obj;
+ }
+
+ LocalRef<Cls>& operator=(LocalRef<Cls> ref) & { return swap(ref); }
+
+ LocalRef<Cls>& operator=(const Ref& ref) & {
+ LocalRef<Cls> newRef(Ctx::mEnv, ref);
+ return swap(newRef);
+ }
+
+ LocalRef<Cls>& operator=(LocalRef<GenericObject>&& ref) & {
+ LocalRef<Cls> newRef(std::move(ref));
+ return swap(newRef);
+ }
+
+ template <class C>
+ LocalRef<Cls>& operator=(GenericLocalRef<C>&& ref) & {
+ LocalRef<Cls> newRef(std::move(ref));
+ return swap(newRef);
+ }
+
+ LocalRef<Cls>& operator=(decltype(nullptr)) & {
+ LocalRef<Cls> newRef(Ctx::mEnv, nullptr);
+ return swap(newRef);
+ }
+};
+
+template <class Cls>
+class GlobalRef : public Cls::Ref {
+ using Ref = typename Cls::Ref;
+ using JNIType = typename Ref::JNIType;
+
+ static JNIType NewGlobalRef(JNIEnv* env, JNIType instance) {
+ return JNIType(instance ? env->NewGlobalRef(instance) : nullptr);
+ }
+
+ GlobalRef& swap(GlobalRef& other) {
+ auto instance = other.mInstance;
+ other.mInstance = Ref::mInstance;
+ Ref::mInstance = instance;
+ return *this;
+ }
+
+ public:
+ GlobalRef() : Ref(nullptr) {}
+
+ // Copy constructor
+ GlobalRef(const GlobalRef& ref)
+ : Ref(NewGlobalRef(GetEnvForThread(), ref.mInstance)) {}
+
+ // Move constructor
+ GlobalRef(GlobalRef&& ref) : Ref(ref.mInstance) { ref.mInstance = nullptr; }
+
+ MOZ_IMPLICIT GlobalRef(const Ref& ref)
+ : Ref(NewGlobalRef(GetEnvForThread(), ref.Get())) {}
+
+ GlobalRef(JNIEnv* env, const Ref& ref) : Ref(NewGlobalRef(env, ref.Get())) {}
+
+ MOZ_IMPLICIT GlobalRef(const LocalRef<Cls>& ref)
+ : Ref(NewGlobalRef(ref.Env(), ref.Get())) {}
+
+ // Implicitly converts nullptr to GlobalRef.
+ MOZ_IMPLICIT GlobalRef(decltype(nullptr)) : Ref(nullptr) {}
+
+ ~GlobalRef() {
+ if (Ref::mInstance) {
+ Clear(GetEnvForThread());
+ }
+ }
+
+ // Get the raw JNI reference that can be used as a return value.
+ // Returns the same JNI type (jobject, jstring, etc.) as the underlying Ref.
+ typename Ref::JNIType Forget() {
+ const auto obj = Ref::Get();
+ Ref::mInstance = nullptr;
+ return obj;
+ }
+
+ void Clear(JNIEnv* env) {
+ if (Ref::mInstance) {
+ env->DeleteGlobalRef(Ref::mInstance);
+ Ref::mInstance = nullptr;
+ }
+ }
+
+ GlobalRef<Cls>& operator=(GlobalRef<Cls> ref) & { return swap(ref); }
+
+ GlobalRef<Cls>& operator=(const Ref& ref) & {
+ GlobalRef<Cls> newRef(ref);
+ return swap(newRef);
+ }
+
+ GlobalRef<Cls>& operator=(const LocalRef<Cls>& ref) & {
+ GlobalRef<Cls> newRef(ref);
+ return swap(newRef);
+ }
+
+ GlobalRef<Cls>& operator=(decltype(nullptr)) & {
+ GlobalRef<Cls> newRef(nullptr);
+ return swap(newRef);
+ }
+};
+
+template <class Cls>
+class WeakRef : public Ref<Cls, jweak> {
+ using Ref = Ref<Cls, jweak>;
+ using JNIType = typename Ref::JNIType;
+
+ static JNIType NewWeakRef(JNIEnv* env, JNIType instance) {
+ return JNIType(instance ? env->NewWeakGlobalRef(instance) : nullptr);
+ }
+
+ WeakRef& swap(WeakRef& other) {
+ auto instance = other.mInstance;
+ other.mInstance = Ref::mInstance;
+ Ref::mInstance = instance;
+ return *this;
+ }
+
+ public:
+ WeakRef() : Ref(nullptr) {}
+
+ // Copy constructor
+ WeakRef(const WeakRef& ref)
+ : Ref(NewWeakRef(GetEnvForThread(), ref.mInstance)) {}
+
+ // Move constructor
+ WeakRef(WeakRef&& ref) : Ref(ref.mInstance) { ref.mInstance = nullptr; }
+
+ MOZ_IMPLICIT WeakRef(const Ref& ref)
+ : Ref(NewWeakRef(GetEnvForThread(), ref.Get())) {}
+
+ WeakRef(JNIEnv* env, const Ref& ref) : Ref(NewWeakRef(env, ref.Get())) {}
+
+ MOZ_IMPLICIT WeakRef(const LocalRef<Cls>& ref)
+ : Ref(NewWeakRef(ref.Env(), ref.Get())) {}
+
+ // Implicitly converts nullptr to WeakRef.
+ MOZ_IMPLICIT WeakRef(decltype(nullptr)) : Ref(nullptr) {}
+
+ ~WeakRef() {
+ if (Ref::mInstance) {
+ Clear(GetEnvForThread());
+ }
+ }
+
+ // Get the raw JNI reference that can be used as a return value.
+ // Returns the same JNI type (jobject, jstring, etc.) as the underlying Ref.
+ typename Ref::JNIType Forget() {
+ const auto obj = Ref::Get();
+ Ref::mInstance = nullptr;
+ return obj;
+ }
+
+ void Clear(JNIEnv* env) {
+ if (Ref::mInstance) {
+ env->DeleteWeakGlobalRef(Ref::mInstance);
+ Ref::mInstance = nullptr;
+ }
+ }
+
+ WeakRef<Cls>& operator=(WeakRef<Cls> ref) & { return swap(ref); }
+
+ WeakRef<Cls>& operator=(const Ref& ref) & {
+ WeakRef<Cls> newRef(ref);
+ return swap(newRef);
+ }
+
+ WeakRef<Cls>& operator=(const LocalRef<Cls>& ref) & {
+ WeakRef<Cls> newRef(ref);
+ return swap(newRef);
+ }
+
+ WeakRef<Cls>& operator=(decltype(nullptr)) & {
+ WeakRef<Cls> newRef(nullptr);
+ return swap(newRef);
+ }
+
+ void operator->() const = delete;
+ void operator*() const = delete;
+};
+
+template <class Cls>
+class DependentRef : public Cls::Ref {
+ using Ref = typename Cls::Ref;
+
+ public:
+ explicit DependentRef(typename Ref::JNIType instance) : Ref(instance) {}
+
+ DependentRef(const DependentRef& ref) : Ref(ref.Get()) {}
+};
+
+class StringParam;
+
+template <>
+class TypedObject<jstring> : public ObjectBase<TypedObject<jstring>, jstring> {
+ using Base = ObjectBase<TypedObject<jstring>, jstring>;
+
+ public:
+ using Param = const StringParam&;
+
+ explicit TypedObject(const Context& ctx) : Base(ctx) {}
+
+ size_t Length() const {
+ const size_t ret = Base::Env()->GetStringLength(Base::Instance());
+ MOZ_CATCH_JNI_EXCEPTION(Base::Env());
+ return ret;
+ }
+
+ nsString ToString() const {
+ const jchar* const str =
+ Base::Env()->GetStringChars(Base::Instance(), nullptr);
+ const jsize len = Base::Env()->GetStringLength(Base::Instance());
+
+ nsString result(reinterpret_cast<const char16_t*>(str), len);
+ Base::Env()->ReleaseStringChars(Base::Instance(), str);
+ return result;
+ }
+
+ nsCString ToCString() const { return NS_ConvertUTF16toUTF8(ToString()); }
+
+ // Convert jstring to a nsString.
+ operator nsString() const { return ToString(); }
+
+ // Convert jstring to a nsCString.
+ operator nsCString() const { return ToCString(); }
+};
+
+// Define a custom parameter type for String,
+// which accepts both String::Ref and nsAString/nsACString
+class StringParam : public String::Ref {
+ using Ref = String::Ref;
+
+ private:
+ // Not null if we should delete ref on destruction.
+ JNIEnv* const mEnv;
+
+ static jstring GetString(JNIEnv* env, const nsAString& str) {
+ const jstring result = env->NewString(
+ reinterpret_cast<const jchar*>(str.BeginReading()), str.Length());
+ if (!result) {
+ NS_ABORT_OOM(str.Length() * sizeof(char16_t));
+ }
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ return result;
+ }
+
+ public:
+ MOZ_IMPLICIT StringParam(decltype(nullptr)) : Ref(nullptr), mEnv(nullptr) {}
+
+ MOZ_IMPLICIT StringParam(const Ref& ref) : Ref(ref.Get()), mEnv(nullptr) {}
+
+ MOZ_IMPLICIT StringParam(const nsAString& str, JNIEnv* env = Ref::FindEnv())
+ : Ref(GetString(env, str)), mEnv(env) {}
+
+ MOZ_IMPLICIT StringParam(const nsLiteralString& str,
+ JNIEnv* env = Ref::FindEnv())
+ : Ref(GetString(env, str)), mEnv(env) {}
+
+ MOZ_IMPLICIT StringParam(const char16_t* str, JNIEnv* env = Ref::FindEnv())
+ : Ref(GetString(env, nsDependentString(str))), mEnv(env) {}
+
+ MOZ_IMPLICIT StringParam(const nsACString& str, JNIEnv* env = Ref::FindEnv())
+ : Ref(GetString(env, NS_ConvertUTF8toUTF16(str))), mEnv(env) {}
+
+ MOZ_IMPLICIT StringParam(const nsLiteralCString& str,
+ JNIEnv* env = Ref::FindEnv())
+ : Ref(GetString(env, NS_ConvertUTF8toUTF16(str))), mEnv(env) {}
+
+ MOZ_IMPLICIT StringParam(const char* str, JNIEnv* env = Ref::FindEnv())
+ : Ref(GetString(env, NS_ConvertUTF8toUTF16(str))), mEnv(env) {}
+
+ StringParam(StringParam&& other) : Ref(other.Get()), mEnv(other.mEnv) {
+ other.mInstance = nullptr;
+ }
+
+ ~StringParam() {
+ if (mEnv && Get()) {
+ mEnv->DeleteLocalRef(Get());
+ }
+ }
+
+ operator String::LocalRef() const {
+ // We can't return our existing ref because the returned
+ // LocalRef could be freed first, so we need a new local ref.
+ return String::LocalRef(mEnv ? mEnv : Ref::FindEnv(), *this);
+ }
+};
+
+namespace detail {
+template <typename T>
+struct TypeAdapter;
+}
+
+// Ref specialization for arrays.
+template <typename JNIType, class ElementType>
+class ArrayRefBase : public ObjectBase<TypedObject<JNIType>, JNIType> {
+ using Base = ObjectBase<TypedObject<JNIType>, JNIType>;
+
+ public:
+ explicit ArrayRefBase(const Context<TypedObject<JNIType>, JNIType>& ctx)
+ : Base(ctx) {}
+
+ static typename Base::LocalRef New(const ElementType* data, size_t length) {
+ using JNIElemType = typename detail::TypeAdapter<ElementType>::JNIType;
+ static_assert(sizeof(ElementType) == sizeof(JNIElemType),
+ "Size of native type must match size of JNI type");
+ JNIEnv* const jenv = mozilla::jni::GetEnvForThread();
+ auto result = (jenv->*detail::TypeAdapter<ElementType>::NewArray)(length);
+ MOZ_CATCH_JNI_EXCEPTION(jenv);
+ (jenv->*detail::TypeAdapter<ElementType>::SetArray)(
+ result, jsize(0), length, reinterpret_cast<const JNIElemType*>(data));
+ MOZ_CATCH_JNI_EXCEPTION(jenv);
+ return Base::LocalRef::Adopt(jenv, result);
+ }
+
+ size_t Length() const {
+ const size_t ret = Base::Env()->GetArrayLength(Base::Instance());
+ MOZ_CATCH_JNI_EXCEPTION(Base::Env());
+ return ret;
+ }
+
+ ElementType GetElement(size_t index) const {
+ using JNIElemType = typename detail::TypeAdapter<ElementType>::JNIType;
+ static_assert(sizeof(ElementType) == sizeof(JNIElemType),
+ "Size of native type must match size of JNI type");
+
+ ElementType ret;
+ (Base::Env()->*detail::TypeAdapter<ElementType>::GetArray)(
+ Base::Instance(), jsize(index), 1,
+ reinterpret_cast<JNIElemType*>(&ret));
+ MOZ_CATCH_JNI_EXCEPTION(Base::Env());
+ return ret;
+ }
+
+ nsTArray<ElementType> GetElements() const {
+ using JNIElemType = typename detail::TypeAdapter<ElementType>::JNIType;
+ static_assert(sizeof(ElementType) == sizeof(JNIElemType),
+ "Size of native type must match size of JNI type");
+
+ const size_t len = size_t(Base::Env()->GetArrayLength(Base::Instance()));
+
+ nsTArray<ElementType> array(len);
+ array.SetLength(len);
+ CopyTo(array.Elements(), len);
+ return array;
+ }
+
+ // returns number of elements copied
+ size_t CopyTo(ElementType* buffer, size_t size) const {
+ using JNIElemType = typename detail::TypeAdapter<ElementType>::JNIType;
+ static_assert(sizeof(ElementType) == sizeof(JNIElemType),
+ "Size of native type must match size of JNI type");
+
+ const size_t len = size_t(Base::Env()->GetArrayLength(Base::Instance()));
+ const size_t amountToCopy = (len > size ? size : len);
+ (Base::Env()->*detail::TypeAdapter<ElementType>::GetArray)(
+ Base::Instance(), 0, jsize(amountToCopy),
+ reinterpret_cast<JNIElemType*>(buffer));
+ return amountToCopy;
+ }
+
+ ElementType operator[](size_t index) const { return GetElement(index); }
+
+ operator nsTArray<ElementType>() const { return GetElements(); }
+};
+
+#define DEFINE_PRIMITIVE_ARRAY_REF(JNIType, ElementType) \
+ template <> \
+ class TypedObject<JNIType> : public ArrayRefBase<JNIType, ElementType> { \
+ public: \
+ explicit TypedObject(const Context& ctx) \
+ : ArrayRefBase<JNIType, ElementType>(ctx) {} \
+ }
+
+DEFINE_PRIMITIVE_ARRAY_REF(jbooleanArray, bool);
+DEFINE_PRIMITIVE_ARRAY_REF(jbyteArray, int8_t);
+DEFINE_PRIMITIVE_ARRAY_REF(jcharArray, char16_t);
+DEFINE_PRIMITIVE_ARRAY_REF(jshortArray, int16_t);
+DEFINE_PRIMITIVE_ARRAY_REF(jintArray, int32_t);
+DEFINE_PRIMITIVE_ARRAY_REF(jlongArray, int64_t);
+DEFINE_PRIMITIVE_ARRAY_REF(jfloatArray, float);
+DEFINE_PRIMITIVE_ARRAY_REF(jdoubleArray, double);
+
+#undef DEFINE_PRIMITIVE_ARRAY_REF
+
+class ByteBuffer : public ObjectBase<ByteBuffer, jobject> {
+ public:
+ explicit ByteBuffer(const Context& ctx)
+ : ObjectBase<ByteBuffer, jobject>(ctx) {}
+
+ static LocalRef New(void* data, size_t capacity) {
+ JNIEnv* const env = GetEnvForThread();
+ const auto ret =
+ LocalRef::Adopt(env, env->NewDirectByteBuffer(data, jlong(capacity)));
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ return ret;
+ }
+
+ void* Address() {
+ void* const ret = Env()->GetDirectBufferAddress(Instance());
+ MOZ_CATCH_JNI_EXCEPTION(Env());
+ return ret;
+ }
+
+ size_t Capacity() {
+ const size_t ret = size_t(Env()->GetDirectBufferCapacity(Instance()));
+ MOZ_CATCH_JNI_EXCEPTION(Env());
+ return ret;
+ }
+};
+
+template <>
+const char ObjectBase<ByteBuffer, jobject>::name[];
+
+template <>
+class TypedObject<jobjectArray>
+ : public ObjectBase<TypedObject<jobjectArray>, jobjectArray> {
+ using Base = ObjectBase<TypedObject<jobjectArray>, jobjectArray>;
+
+ public:
+ template <class Cls = Object>
+ static Base::LocalRef New(size_t length,
+ typename Cls::Param initialElement = nullptr) {
+ JNIEnv* const env = GetEnvForThread();
+ jobjectArray array = env->NewObjectArray(
+ jsize(length), typename Cls::Context(env, nullptr).ClassRef(),
+ initialElement.Get());
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ return Base::LocalRef::Adopt(env, array);
+ }
+
+ explicit TypedObject(const Context& ctx) : Base(ctx) {}
+
+ size_t Length() const {
+ const size_t ret = Base::Env()->GetArrayLength(Base::Instance());
+ MOZ_CATCH_JNI_EXCEPTION(Base::Env());
+ return ret;
+ }
+
+ Object::LocalRef GetElement(size_t index) const {
+ auto ret = Object::LocalRef::Adopt(
+ Base::Env(),
+ Base::Env()->GetObjectArrayElement(Base::Instance(), jsize(index)));
+ MOZ_CATCH_JNI_EXCEPTION(Base::Env());
+ return ret;
+ }
+
+ nsTArray<Object::LocalRef> GetElements() const {
+ const jsize len = size_t(Base::Env()->GetArrayLength(Base::Instance()));
+
+ nsTArray<Object::LocalRef> array((size_t(len)));
+ for (jsize i = 0; i < len; i++) {
+ array.AppendElement(Object::LocalRef::Adopt(
+ Base::Env(),
+ Base::Env()->GetObjectArrayElement(Base::Instance(), i)));
+ MOZ_CATCH_JNI_EXCEPTION(Base::Env());
+ }
+ return array;
+ }
+
+ Object::LocalRef operator[](size_t index) const { return GetElement(index); }
+
+ operator nsTArray<Object::LocalRef>() const { return GetElements(); }
+
+ void SetElement(size_t index, Object::Param element) const {
+ Base::Env()->SetObjectArrayElement(Base::Instance(), jsize(index),
+ element.Get());
+ MOZ_CATCH_JNI_EXCEPTION(Base::Env());
+ }
+};
+
+// Support conversion from LocalRef<T>* to LocalRef<Object>*:
+// LocalRef<Foo> foo;
+// Foo::GetFoo(&foo); // error because parameter type is LocalRef<Object>*.
+// Foo::GetFoo(ReturnTo(&foo)); // OK because ReturnTo converts the argument.
+template <class Cls>
+class ReturnToLocal {
+ private:
+ LocalRef<Cls>* const localRef;
+ LocalRef<Object> objRef;
+
+ public:
+ explicit ReturnToLocal(LocalRef<Cls>* ref) : localRef(ref) {}
+ operator LocalRef<Object>*() { return &objRef; }
+
+ ~ReturnToLocal() {
+ if (objRef) {
+ *localRef = std::move(objRef);
+ }
+ }
+};
+
+template <class Cls>
+ReturnToLocal<Cls> ReturnTo(LocalRef<Cls>* ref) {
+ return ReturnToLocal<Cls>(ref);
+}
+
+// Support conversion from GlobalRef<T>* to LocalRef<Object/T>*:
+// GlobalRef<Foo> foo;
+// Foo::GetFoo(&foo); // error because parameter type is LocalRef<Foo>*.
+// Foo::GetFoo(ReturnTo(&foo)); // OK because ReturnTo converts the argument.
+template <class Cls>
+class ReturnToGlobal {
+ private:
+ GlobalRef<Cls>* const globalRef;
+ LocalRef<Object> objRef;
+ LocalRef<Cls> clsRef;
+
+ public:
+ explicit ReturnToGlobal(GlobalRef<Cls>* ref) : globalRef(ref) {}
+ operator LocalRef<Object>*() { return &objRef; }
+ operator LocalRef<Cls>*() { return &clsRef; }
+
+ ~ReturnToGlobal() {
+ if (objRef) {
+ *globalRef = (clsRef = std::move(objRef));
+ } else if (clsRef) {
+ *globalRef = clsRef;
+ }
+ }
+};
+
+template <class Cls>
+ReturnToGlobal<Cls> ReturnTo(GlobalRef<Cls>* ref) {
+ return ReturnToGlobal<Cls>(ref);
+}
+
+// Make a LocalRef<T> from any other Ref<T>
+template <typename Cls, typename JNIType>
+LocalRef<Cls> ToLocalRef(const Ref<Cls, JNIType>& aRef) {
+ return LocalRef<Cls>(aRef);
+}
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Refs_h__
diff --git a/widget/android/jni/Types.h b/widget/android/jni/Types.h
new file mode 100644
index 0000000000..2c0905979a
--- /dev/null
+++ b/widget/android/jni/Types.h
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_jni_Types_h__
+#define mozilla_jni_Types_h__
+
+#include <jni.h>
+
+#include "mozilla/jni/Refs.h"
+
+namespace mozilla {
+namespace jni {
+namespace detail {
+
+// TypeAdapter specializations are the interfaces between native/C++ types such
+// as int32_t and JNI types such as jint. The template parameter T is the native
+// type, and each TypeAdapter specialization can have the following members:
+//
+// * Call: JNIEnv member pointer for making a method call that returns T.
+// * StaticCall: JNIEnv member pointer for making a static call that returns T.
+// * Get: JNIEnv member pointer for getting a field of type T.
+// * StaticGet: JNIEnv member pointer for getting a static field of type T.
+// * Set: JNIEnv member pointer for setting a field of type T.
+// * StaticGet: JNIEnv member pointer for setting a static field of type T.
+// * ToNative: static function that converts the JNI type to the native type.
+// * FromNative: static function that converts the native type to the JNI type.
+
+template <typename T>
+struct TypeAdapter;
+
+// TypeAdapter<LocalRef<Cls>> applies when jobject is a return value.
+template <class Cls>
+struct TypeAdapter<LocalRef<Cls>> {
+ using JNIType = typename Cls::Ref::JNIType;
+
+ static constexpr auto Call = &JNIEnv::CallObjectMethodA;
+ static constexpr auto StaticCall = &JNIEnv::CallStaticObjectMethodA;
+ static constexpr auto Get = &JNIEnv::GetObjectField;
+ static constexpr auto StaticGet = &JNIEnv::GetStaticObjectField;
+
+ // Declare instance as jobject because JNI methods return
+ // jobject even if the return value is really jstring, etc.
+ static LocalRef<Cls> ToNative(JNIEnv* env, jobject instance) {
+ return LocalRef<Cls>::Adopt(env, JNIType(instance));
+ }
+
+ static JNIType FromNative(JNIEnv*, LocalRef<Cls>&& instance) {
+ return instance.Forget();
+ }
+};
+
+// clang is picky about function types, including attributes that modify the
+// calling convention, lining up. GCC appears to be somewhat less so.
+#ifdef __clang__
+# define MOZ_JNICALL_ABI JNICALL
+#else
+# define MOZ_JNICALL_ABI
+#endif
+
+// NDK r18 made jvalue* method parameters const. We detect the change directly
+// instead of using ndk-version.h in order to remain compatible with r15 for
+// now, which doesn't include those headers.
+class CallArgs {
+ static const jvalue* test(void (JNIEnv::*)(jobject, jmethodID,
+ const jvalue*));
+ static jvalue* test(void (JNIEnv::*)(jobject, jmethodID, jvalue*));
+
+ public:
+ using JValueType = decltype(test(&JNIEnv::CallVoidMethodA));
+};
+
+template <class Cls>
+constexpr jobject (JNIEnv::*TypeAdapter<LocalRef<Cls>>::Call)(
+ jobject, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI;
+template <class Cls>
+constexpr jobject (JNIEnv::*TypeAdapter<LocalRef<Cls>>::StaticCall)(
+ jclass, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI;
+template <class Cls>
+constexpr jobject (JNIEnv::*TypeAdapter<LocalRef<Cls>>::Get)(jobject, jfieldID);
+template <class Cls>
+constexpr jobject (JNIEnv::*TypeAdapter<LocalRef<Cls>>::StaticGet)(jclass,
+ jfieldID);
+
+// TypeAdapter<Ref<Cls>> applies when jobject is a parameter value.
+template <class Cls, typename T>
+struct TypeAdapter<Ref<Cls, T>> {
+ using JNIType = typename Ref<Cls, T>::JNIType;
+
+ static constexpr auto Set = &JNIEnv::SetObjectField;
+ static constexpr auto StaticSet = &JNIEnv::SetStaticObjectField;
+
+ static DependentRef<Cls> ToNative(JNIEnv* env, JNIType instance) {
+ return DependentRef<Cls>(instance);
+ }
+
+ static JNIType FromNative(JNIEnv*, const Ref<Cls, T>& instance) {
+ return instance.Get();
+ }
+};
+
+template <class Cls, typename T>
+constexpr void (JNIEnv::*TypeAdapter<Ref<Cls, T>>::Set)(jobject, jfieldID,
+ jobject);
+template <class Cls, typename T>
+constexpr void (JNIEnv::*TypeAdapter<Ref<Cls, T>>::StaticSet)(jclass, jfieldID,
+ jobject);
+
+// jstring has its own Param type.
+template <>
+struct TypeAdapter<StringParam> : public TypeAdapter<String::Ref> {};
+
+template <class Cls>
+struct TypeAdapter<const Cls&> : public TypeAdapter<Cls> {};
+
+#define DEFINE_PRIMITIVE_TYPE_ADAPTER(NativeType, JNIType, JNIName) \
+ \
+ template <> \
+ struct TypeAdapter<NativeType> { \
+ using JNI##Type = JNIType; \
+ \
+ static constexpr auto Call = &JNIEnv::Call##JNIName##MethodA; \
+ static constexpr auto StaticCall = &JNIEnv::CallStatic##JNIName##MethodA; \
+ static constexpr auto Get = &JNIEnv::Get##JNIName##Field; \
+ static constexpr auto StaticGet = &JNIEnv::GetStatic##JNIName##Field; \
+ static constexpr auto Set = &JNIEnv::Set##JNIName##Field; \
+ static constexpr auto StaticSet = &JNIEnv::SetStatic##JNIName##Field; \
+ static constexpr auto GetArray = &JNIEnv::Get##JNIName##ArrayRegion; \
+ static constexpr auto SetArray = &JNIEnv::Set##JNIName##ArrayRegion; \
+ static constexpr auto NewArray = &JNIEnv::New##JNIName##Array; \
+ \
+ static JNIType FromNative(JNIEnv*, NativeType val) { \
+ return static_cast<JNIType>(val); \
+ } \
+ static NativeType ToNative(JNIEnv*, JNIType val) { \
+ return static_cast<NativeType>(val); \
+ } \
+ }
+
+DEFINE_PRIMITIVE_TYPE_ADAPTER(bool, jboolean, Boolean);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int8_t, jbyte, Byte);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(char16_t, jchar, Char);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int16_t, jshort, Short);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int32_t, jint, Int);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int64_t, jlong, Long);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(float, jfloat, Float);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(double, jdouble, Double);
+
+#undef DEFINE_PRIMITIVE_TYPE_ADAPTER
+
+} // namespace detail
+
+using namespace detail;
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Types_h__
diff --git a/widget/android/jni/Utils.cpp b/widget/android/jni/Utils.cpp
new file mode 100644
index 0000000000..974b46a408
--- /dev/null
+++ b/widget/android/jni/Utils.cpp
@@ -0,0 +1,341 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "Utils.h"
+#include "Types.h"
+
+#include <android/log.h>
+#include <pthread.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+#include "mozilla/java/GeckoThreadWrappers.h"
+
+#include "AndroidBuild.h"
+#include "nsAppShell.h"
+#include "nsExceptionHandler.h"
+
+namespace mozilla {
+namespace jni {
+
+namespace detail {
+
+#define DEFINE_PRIMITIVE_TYPE_ADAPTER(NativeType, JNIType, JNIName, ABIName) \
+ \
+ constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::Call)( \
+ jobject, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI; \
+ constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::StaticCall)( \
+ jclass, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI; \
+ constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::Get)(jobject, jfieldID) \
+ ABIName; \
+ constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::StaticGet)( \
+ jclass, jfieldID) ABIName; \
+ constexpr void (JNIEnv::*TypeAdapter<NativeType>::Set)(jobject, jfieldID, \
+ JNIType) ABIName; \
+ constexpr void (JNIEnv::*TypeAdapter<NativeType>::StaticSet)( \
+ jclass, jfieldID, JNIType) ABIName; \
+ constexpr void (JNIEnv::*TypeAdapter<NativeType>::GetArray)( \
+ JNIType##Array, jsize, jsize, JNIType*)
+
+DEFINE_PRIMITIVE_TYPE_ADAPTER(bool, jboolean, Boolean, /*nothing*/);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int8_t, jbyte, Byte, /*nothing*/);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(char16_t, jchar, Char, /*nothing*/);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int16_t, jshort, Short, /*nothing*/);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int32_t, jint, Int, /*nothing*/);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int64_t, jlong, Long, /*nothing*/);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(float, jfloat, Float, MOZ_JNICALL_ABI);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(double, jdouble, Double, MOZ_JNICALL_ABI);
+
+#undef DEFINE_PRIMITIVE_TYPE_ADAPTER
+
+} // namespace detail
+
+template <>
+const char ObjectBase<Object, jobject>::name[] = "java/lang/Object";
+template <>
+const char ObjectBase<TypedObject<jstring>, jstring>::name[] =
+ "java/lang/String";
+template <>
+const char ObjectBase<TypedObject<jclass>, jclass>::name[] = "java/lang/Class";
+template <>
+const char ObjectBase<TypedObject<jthrowable>, jthrowable>::name[] =
+ "java/lang/Throwable";
+template <>
+const char ObjectBase<BoxedObject<jboolean>, jobject>::name[] =
+ "java/lang/Boolean";
+template <>
+const char ObjectBase<BoxedObject<jbyte>, jobject>::name[] = "java/lang/Byte";
+template <>
+const char ObjectBase<BoxedObject<jchar>, jobject>::name[] =
+ "java/lang/Character";
+template <>
+const char ObjectBase<BoxedObject<jshort>, jobject>::name[] = "java/lang/Short";
+template <>
+const char ObjectBase<BoxedObject<jint>, jobject>::name[] = "java/lang/Integer";
+template <>
+const char ObjectBase<BoxedObject<jlong>, jobject>::name[] = "java/lang/Long";
+template <>
+const char ObjectBase<BoxedObject<jfloat>, jobject>::name[] = "java/lang/Float";
+template <>
+const char ObjectBase<BoxedObject<jdouble>, jobject>::name[] =
+ "java/lang/Double";
+template <>
+const char ObjectBase<TypedObject<jbooleanArray>, jbooleanArray>::name[] = "[Z";
+template <>
+const char ObjectBase<TypedObject<jbyteArray>, jbyteArray>::name[] = "[B";
+template <>
+const char ObjectBase<TypedObject<jcharArray>, jcharArray>::name[] = "[C";
+template <>
+const char ObjectBase<TypedObject<jshortArray>, jshortArray>::name[] = "[S";
+template <>
+const char ObjectBase<TypedObject<jintArray>, jintArray>::name[] = "[I";
+template <>
+const char ObjectBase<TypedObject<jlongArray>, jlongArray>::name[] = "[J";
+template <>
+const char ObjectBase<TypedObject<jfloatArray>, jfloatArray>::name[] = "[F";
+template <>
+const char ObjectBase<TypedObject<jdoubleArray>, jdoubleArray>::name[] = "[D";
+template <>
+const char ObjectBase<TypedObject<jobjectArray>, jobjectArray>::name[] =
+ "[Ljava/lang/Object;";
+template <>
+const char ObjectBase<ByteBuffer, jobject>::name[] = "java/nio/ByteBuffer";
+
+JavaVM* sJavaVM;
+JNIEnv* sGeckoThreadEnv;
+
+namespace {
+
+pthread_key_t sThreadEnvKey;
+jclass sOOMErrorClass;
+jobject sClassLoader;
+jmethodID sClassLoaderLoadClass;
+
+void UnregisterThreadEnv(void* env) {
+ if (!env) {
+ // We were never attached.
+ return;
+ }
+ // The thread may have already been detached. In that case, it's still
+ // okay to call DetachCurrentThread(); it'll simply return an error.
+ // However, we must not access | env | because it may be invalid.
+ MOZ_ASSERT(sJavaVM);
+ sJavaVM->DetachCurrentThread();
+}
+
+} // namespace
+
+void SetGeckoThreadEnv(JNIEnv* aEnv) {
+ MOZ_ASSERT(aEnv);
+ MOZ_ASSERT(!sGeckoThreadEnv || sGeckoThreadEnv == aEnv);
+
+ if (!sGeckoThreadEnv &&
+ pthread_key_create(&sThreadEnvKey, UnregisterThreadEnv)) {
+ MOZ_CRASH("Failed to initialize required TLS");
+ }
+
+ sGeckoThreadEnv = aEnv;
+ MOZ_ALWAYS_TRUE(!pthread_setspecific(sThreadEnvKey, aEnv));
+
+ MOZ_ALWAYS_TRUE(!aEnv->GetJavaVM(&sJavaVM));
+ MOZ_ASSERT(sJavaVM);
+
+ sOOMErrorClass =
+ Class::GlobalRef(
+ Class::LocalRef::Adopt(aEnv->FindClass("java/lang/OutOfMemoryError")))
+ .Forget();
+ aEnv->ExceptionClear();
+
+ sClassLoader = Object::GlobalRef(java::GeckoThread::ClsLoader()).Forget();
+ sClassLoaderLoadClass = aEnv->GetMethodID(
+ Class::LocalRef::Adopt(aEnv->GetObjectClass(sClassLoader)).Get(),
+ "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+ MOZ_ASSERT(sClassLoader && sClassLoaderLoadClass);
+}
+
+JNIEnv* GetEnvForThread() {
+ MOZ_ASSERT(sGeckoThreadEnv);
+
+ JNIEnv* env = static_cast<JNIEnv*>(pthread_getspecific(sThreadEnvKey));
+ if (env) {
+ return env;
+ }
+
+ // We don't have a saved JNIEnv, so try to get one.
+ // AttachCurrentThread() does the same thing as GetEnv() when a thread is
+ // already attached, so we don't have to call GetEnv() at all.
+ if (!sJavaVM->AttachCurrentThread(&env, nullptr)) {
+ MOZ_ASSERT(env);
+ MOZ_ALWAYS_TRUE(!pthread_setspecific(sThreadEnvKey, env));
+ return env;
+ }
+
+ MOZ_CRASH("Failed to get JNIEnv for thread");
+ return nullptr; // unreachable
+}
+
+bool ThrowException(JNIEnv* aEnv, const char* aClass, const char* aMessage) {
+ MOZ_ASSERT(aEnv, "Invalid thread JNI env");
+
+ Class::LocalRef cls = Class::LocalRef::Adopt(aEnv->FindClass(aClass));
+ MOZ_ASSERT(cls, "Cannot find exception class");
+
+ return !aEnv->ThrowNew(cls.Get(), aMessage);
+}
+
+bool HandleUncaughtException(JNIEnv* aEnv) {
+ MOZ_ASSERT(aEnv, "Invalid thread JNI env");
+
+ if (!aEnv->ExceptionCheck()) {
+ return false;
+ }
+
+#ifdef MOZ_CHECK_JNI
+ aEnv->ExceptionDescribe();
+#endif
+
+ Throwable::LocalRef e =
+ Throwable::LocalRef::Adopt(aEnv, aEnv->ExceptionOccurred());
+ MOZ_ASSERT(e);
+ aEnv->ExceptionClear();
+
+ String::LocalRef stack = java::GeckoAppShell::GetExceptionStackTrace(e);
+ if (stack && ReportException(aEnv, e.Get(), stack.Get())) {
+ return true;
+ }
+
+ aEnv->ExceptionClear();
+ java::GeckoAppShell::HandleUncaughtException(e);
+
+ if (NS_WARN_IF(aEnv->ExceptionCheck())) {
+ aEnv->ExceptionDescribe();
+ aEnv->ExceptionClear();
+ }
+
+ return true;
+}
+
+bool ReportException(JNIEnv* aEnv, jthrowable aExc, jstring aStack) {
+ bool result = true;
+
+ result &= NS_SUCCEEDED(CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::JavaStackTrace,
+ String::Ref::From(aStack)->ToCString()));
+
+ auto appNotes = java::GeckoAppShell::GetAppNotes();
+ if (NS_WARN_IF(aEnv->ExceptionCheck())) {
+ aEnv->ExceptionDescribe();
+ aEnv->ExceptionClear();
+ } else if (appNotes) {
+ CrashReporter::AppendAppNotesToCrashReport("\n"_ns + appNotes->ToCString());
+ }
+
+ if (sOOMErrorClass && aEnv->IsInstanceOf(aExc, sOOMErrorClass)) {
+ NS_ABORT_OOM(0); // Unknown OOM size
+ }
+ return result;
+}
+
+namespace {
+
+jclass sJNIObjectClass;
+jfieldID sJNIObjectHandleField;
+
+bool EnsureJNIObject(JNIEnv* env, jobject instance) {
+ if (!sJNIObjectClass) {
+ sJNIObjectClass =
+ Class::GlobalRef(Class::LocalRef::Adopt(GetClassRef(
+ env, "org/mozilla/gecko/mozglue/JNIObject")))
+ .Forget();
+
+ sJNIObjectHandleField = env->GetFieldID(sJNIObjectClass, "mHandle", "J");
+ }
+
+ MOZ_ASSERT(env->IsInstanceOf(instance, sJNIObjectClass),
+ "Java class is not derived from JNIObject");
+ return true;
+}
+
+} // namespace
+
+uintptr_t GetNativeHandle(JNIEnv* env, jobject instance) {
+ if (!EnsureJNIObject(env, instance)) {
+ return 0;
+ }
+
+ return static_cast<uintptr_t>(
+ env->GetLongField(instance, sJNIObjectHandleField));
+}
+
+void SetNativeHandle(JNIEnv* env, jobject instance, uintptr_t handle) {
+ if (!EnsureJNIObject(env, instance)) {
+ return;
+ }
+
+ env->SetLongField(instance, sJNIObjectHandleField,
+ static_cast<jlong>(handle));
+}
+
+jclass GetClassRef(JNIEnv* aEnv, const char* aClassName) {
+ // First try the default class loader.
+ auto classRef = Class::LocalRef::Adopt(aEnv, aEnv->FindClass(aClassName));
+
+ if ((!classRef || aEnv->ExceptionCheck()) && sClassLoader) {
+ // If the default class loader failed but we have an app class loader, try
+ // that. Clear the pending exception from failed FindClass call above.
+ aEnv->ExceptionClear();
+ classRef = Class::LocalRef::Adopt(
+ aEnv,
+ jclass(aEnv->CallObjectMethod(sClassLoader, sClassLoaderLoadClass,
+ StringParam(aClassName, aEnv).Get())));
+ }
+
+ if (classRef && !aEnv->ExceptionCheck()) {
+ return classRef.Forget();
+ }
+
+ __android_log_print(
+ ANDROID_LOG_ERROR, "Gecko",
+ ">>> FATAL JNI ERROR! FindClass(\"%s\") failed. "
+ "Does the class require a newer API version? "
+ "Or did ProGuard optimize away something it shouldn't have?",
+ aClassName);
+ aEnv->ExceptionDescribe();
+ MOZ_CRASH("Cannot find JNI class");
+ return nullptr;
+}
+
+void DispatchToGeckoPriorityQueue(already_AddRefed<nsIRunnable> aCall) {
+ class RunnableEvent : public nsAppShell::Event {
+ nsCOMPtr<nsIRunnable> mCall;
+
+ public:
+ explicit RunnableEvent(already_AddRefed<nsIRunnable> aCall)
+ : mCall(aCall) {}
+ void Run() override { NS_ENSURE_SUCCESS_VOID(mCall->Run()); }
+ };
+
+ nsAppShell::PostEvent(MakeUnique<RunnableEvent>(std::move(aCall)));
+}
+
+int GetAPIVersion() {
+ static int32_t apiVersion = 0;
+ if (!apiVersion && IsAvailable()) {
+ apiVersion = java::sdk::VERSION::SDK_INT();
+ }
+ return apiVersion;
+}
+
+pid_t GetUIThreadId() {
+ static pid_t uiThreadId;
+ if (!uiThreadId) {
+ uiThreadId = pid_t(java::GeckoThread::UiThreadId());
+ }
+ return uiThreadId;
+}
+
+} // namespace jni
+} // namespace mozilla
diff --git a/widget/android/jni/Utils.h b/widget/android/jni/Utils.h
new file mode 100644
index 0000000000..037295c82c
--- /dev/null
+++ b/widget/android/jni/Utils.h
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_jni_Utils_h__
+#define mozilla_jni_Utils_h__
+
+#include <jni.h>
+
+#include "nsIRunnable.h"
+
+#include "mozilla/UniquePtr.h"
+
+#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
+# define MOZ_CHECK_JNI
+#endif
+
+#ifdef MOZ_CHECK_JNI
+# include <unistd.h>
+# include "mozilla/Assertions.h"
+# include "APKOpen.h"
+# include "MainThreadUtils.h"
+#endif
+
+namespace mozilla {
+namespace jni {
+
+// How exception during a JNI call should be treated.
+enum class ExceptionMode {
+ // Abort on unhandled excepion (default).
+ ABORT,
+ // Ignore the exception and return to caller.
+ IGNORE,
+ // Catch any exception and return a nsresult.
+ NSRESULT,
+};
+
+// Thread that a particular JNI call is allowed on.
+enum class CallingThread {
+ // Can be called from any thread (default).
+ ANY,
+ // Can be called from the Gecko thread.
+ GECKO,
+ // Can be called from the Java UI thread.
+ UI,
+};
+
+// If and where a JNI call will be dispatched.
+enum class DispatchTarget {
+ // Call happens synchronously on the calling thread (default).
+ CURRENT,
+ // Call happens synchronously on the calling thread, but the call is
+ // wrapped in a function object and is passed thru UsesNativeCallProxy.
+ // Method must return void.
+ PROXY,
+ // Call is dispatched asynchronously on the Gecko thread to the XPCOM
+ // (nsThread) event queue. Method must return void.
+ GECKO,
+ // Call is dispatched asynchronously on the Gecko thread to the widget
+ // (nsAppShell) event queue. In most cases, events in the widget event
+ // queue (aka native event queue) are favored over events in the XPCOM
+ // event queue. Method must return void.
+ GECKO_PRIORITY,
+};
+
+extern JavaVM* sJavaVM;
+extern JNIEnv* sGeckoThreadEnv;
+
+inline bool IsAvailable() { return !!sGeckoThreadEnv; }
+
+inline JavaVM* GetVM() {
+#ifdef MOZ_CHECK_JNI
+ MOZ_ASSERT(sJavaVM);
+#endif
+ return sJavaVM;
+}
+
+inline JNIEnv* GetGeckoThreadEnv() {
+#ifdef MOZ_CHECK_JNI
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Must be on Gecko thread");
+ MOZ_RELEASE_ASSERT(sGeckoThreadEnv, "Must have a JNIEnv");
+#endif
+ return sGeckoThreadEnv;
+}
+
+void SetGeckoThreadEnv(JNIEnv* aEnv);
+
+JNIEnv* GetEnvForThread();
+
+#ifdef MOZ_CHECK_JNI
+# define MOZ_ASSERT_JNI_THREAD(thread) \
+ do { \
+ if ((thread) == mozilla::jni::CallingThread::GECKO) { \
+ MOZ_RELEASE_ASSERT(::NS_IsMainThread()); \
+ } else if ((thread) == mozilla::jni::CallingThread::UI) { \
+ const bool isOnUiThread = (GetUIThreadId() == ::gettid()); \
+ MOZ_RELEASE_ASSERT(isOnUiThread); \
+ } \
+ } while (0)
+#else
+# define MOZ_ASSERT_JNI_THREAD(thread) \
+ do { \
+ } while (0)
+#endif
+
+bool ThrowException(JNIEnv* aEnv, const char* aClass, const char* aMessage);
+
+inline bool ThrowException(JNIEnv* aEnv, const char* aMessage) {
+ return ThrowException(aEnv, "java/lang/Exception", aMessage);
+}
+
+inline bool ThrowException(const char* aClass, const char* aMessage) {
+ return ThrowException(GetEnvForThread(), aClass, aMessage);
+}
+
+inline bool ThrowException(const char* aMessage) {
+ return ThrowException(GetEnvForThread(), aMessage);
+}
+
+bool HandleUncaughtException(JNIEnv* aEnv);
+
+bool ReportException(JNIEnv* aEnv, jthrowable aExc, jstring aStack);
+
+#define MOZ_CATCH_JNI_EXCEPTION(env) \
+ do { \
+ if (mozilla::jni::HandleUncaughtException((env))) { \
+ MOZ_CRASH("JNI exception"); \
+ } \
+ } while (0)
+
+uintptr_t GetNativeHandle(JNIEnv* env, jobject instance);
+
+void SetNativeHandle(JNIEnv* env, jobject instance, uintptr_t handle);
+
+jclass GetClassRef(JNIEnv* aEnv, const char* aClassName);
+
+void DispatchToGeckoPriorityQueue(already_AddRefed<nsIRunnable> aCall);
+
+int GetAPIVersion();
+
+pid_t GetUIThreadId();
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Utils_h__
diff --git a/widget/android/jni/moz.build b/widget/android/jni/moz.build
new file mode 100644
index 0000000000..422c69d590
--- /dev/null
+++ b/widget/android/jni/moz.build
@@ -0,0 +1,33 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("GeckoView", "General")
+
+EXPORTS.mozilla.jni += [
+ "Accessors.h",
+ "Conversions.h",
+ "GeckoBundleUtils.h",
+ "GeckoResultUtils.h",
+ "Natives.h",
+ "Refs.h",
+ "Types.h",
+ "Utils.h",
+]
+
+UNIFIED_SOURCES += [
+ "Conversions.cpp",
+ "Utils.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/widget",
+ "/widget/android",
+]
diff --git a/widget/android/moz.build b/widget/android/moz.build
new file mode 100644
index 0000000000..25caff0869
--- /dev/null
+++ b/widget/android/moz.build
@@ -0,0 +1,198 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget: Android")
+ SCHEDULES.exclusive = ["android"]
+
+with Files("*CompositorWidget*"):
+ BUG_COMPONENT = ("Core", "Graphics")
+
+DIRS += [
+ "bindings",
+ "jni",
+]
+
+XPIDL_SOURCES += [
+ "nsIAndroidBridge.idl",
+]
+
+XPIDL_MODULE = "widget_android"
+
+EXPORTS += [
+ "AndroidBridge.h",
+]
+
+classes_with_WrapForJNI = [
+ "AndroidGamepadManager",
+ "AndroidVsync",
+ "Base64Utils",
+ "Clipboard",
+ "CodecProxy",
+ "EnterpriseRoots",
+ "EventCallback",
+ "EventDispatcher",
+ "GeckoAppShell",
+ "GeckoAudioInfo",
+ "GeckoBatteryManager",
+ "GeckoBundle",
+ "GeckoEditableChild",
+ "GeckoHLSDemuxerWrapper",
+ "GeckoHLSResourceWrapper",
+ "GeckoHLSSample",
+ "GeckoInputStream",
+ "GeckoJavaSampler",
+ "GeckoNetworkManager",
+ "GeckoProcessManager",
+ "GeckoProcessType",
+ "GeckoResult",
+ "GeckoRuntime",
+ "GeckoScreenOrientation",
+ "GeckoServiceChildProcess",
+ "GeckoSession",
+ "GeckoSurface",
+ "GeckoSurfaceTexture",
+ "GeckoSystemStateListener",
+ "GeckoThread",
+ "GeckoVRManager",
+ "GeckoVideoInfo",
+ "GeckoWebExecutor",
+ "HardwareCodecCapabilityUtils",
+ "ImageDecoder",
+ "MediaDrmProxy",
+ "PanZoomController",
+ "PrefsHelper",
+ "RuntimeTelemetry",
+ "Sample",
+ "SampleBuffer",
+ "ScreenManagerHelper",
+ "ServiceAllocator",
+ "SessionAccessibility",
+ "SessionKeyInfo",
+ "SessionTextInput",
+ "SpeechSynthesisService",
+ "SurfaceAllocator",
+ "SurfaceTextureListener",
+ "TelemetryUtils",
+ "WebAuthnTokenManager",
+ "WebMessage",
+ "WebNotification",
+ "WebNotificationDelegate",
+ "WebRequest",
+ "WebRequestError",
+ "WebResponse",
+ "XPCOMEventTarget",
+]
+
+natives_from_WrapForJNI = sorted(
+ ["GeneratedJNI/{}Natives.h".format(c) for c in classes_with_WrapForJNI]
+)
+
+wrappers_from_WrapForJNI = sorted(
+ ["GeneratedJNI/{}Wrappers.h".format(c) for c in classes_with_WrapForJNI]
+)
+
+sources_from_WrapForJNI = sorted(
+ "GeneratedJNI{}Wrappers.cpp".format(c) for c in classes_with_WrapForJNI
+)
+
+EXPORTS.mozilla.widget += [
+ "AndroidCompositorWidget.h",
+ "AndroidUiThread.h",
+ "AndroidView.h",
+ "AndroidVsync.h",
+ "EventDispatcher.h",
+ "GeckoViewSupport.h",
+ "nsWindow.h",
+ "WindowEvent.h",
+]
+
+EXPORTS.mozilla.java += ["!{}".format(c) for c in natives_from_WrapForJNI]
+
+EXPORTS.mozilla.java += ["!{}".format(c) for c in wrappers_from_WrapForJNI]
+
+SOURCES += ["!{}".format(c) for c in sources_from_WrapForJNI]
+
+SOURCES += [
+ "MediaKeysEventSourceFactory.cpp",
+]
+
+UNIFIED_SOURCES += [
+ "AndroidAlerts.cpp",
+ "AndroidBridge.cpp",
+ "AndroidCompositorWidget.cpp",
+ "AndroidContentController.cpp",
+ "AndroidUiThread.cpp",
+ "AndroidVsync.cpp",
+ "EventDispatcher.cpp",
+ "GeckoEditableSupport.cpp",
+ "GeckoProcessManager.cpp",
+ "GfxInfo.cpp",
+ "ImageDecoderSupport.cpp",
+ "nsAndroidProtocolHandler.cpp",
+ "nsAppShell.cpp",
+ "nsClipboard.cpp",
+ "nsDeviceContextAndroid.cpp",
+ "nsLookAndFeel.cpp",
+ "nsNativeBasicThemeAndroid.cpp",
+ "nsNativeThemeAndroid.cpp",
+ "nsPrintSettingsServiceAndroid.cpp",
+ "nsUserIdleServiceAndroid.cpp",
+ "nsWidgetFactory.cpp",
+ "nsWindow.cpp",
+ "ScreenHelperAndroid.cpp",
+ "WebExecutorSupport.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+# The recursive make backend treats the first output specially: it's passed as
+# an open FileAvoidWrite to the invoked script. That doesn't work well with
+# the Gradle task that generates all of the outputs, so we add a dummy first
+# output.
+
+t = tuple(
+ ["generated_jni_wrappers"]
+ + natives_from_WrapForJNI
+ + sources_from_WrapForJNI
+ + wrappers_from_WrapForJNI
+)
+
+GeneratedFile(
+ *t,
+ script="/mobile/android/gradle.py",
+ entry_point="generate_generated_jni_wrappers"
+)
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/docshell/base",
+ "/dom/base",
+ "/dom/system/android",
+ "/gfx/2d",
+ "/gfx/vr",
+ "/layout/forms",
+ "/layout/painting",
+ "/netwerk/base",
+ "/netwerk/cache",
+ "/toolkit/components/telemetry",
+ "/widget",
+ "/xpcom/threads",
+]
+
+CXXFLAGS += ["-Wno-error=shadow"]
+
+OS_LIBS += ["android"]
+
+if CONFIG["MOZ_NATIVE_DEVICES"]:
+ DEFINES["MOZ_NATIVE_DEVICES"] = True
+
+# DEFINES['DEBUG_WIDGETS'] = True
diff --git a/widget/android/nsAndroidProtocolHandler.cpp b/widget/android/nsAndroidProtocolHandler.cpp
new file mode 100644
index 0000000000..6453db0df6
--- /dev/null
+++ b/widget/android/nsAndroidProtocolHandler.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=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 "nsAndroidProtocolHandler.h"
+#include "nsCOMPtr.h"
+#include "nsIChannel.h"
+#include "android/log.h"
+#include "nsBaseChannel.h"
+#include "AndroidBridge.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+
+using namespace mozilla;
+
+class AndroidInputStream : public nsIInputStream {
+ public:
+ explicit AndroidInputStream(jni::Object::Param connection) {
+ mBridgeInputStream = java::GeckoAppShell::CreateInputStream(connection);
+ mBridgeChannel = AndroidBridge::ChannelCreate(mBridgeInputStream);
+ }
+
+ private:
+ virtual ~AndroidInputStream() {}
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+ private:
+ jni::Object::GlobalRef mBridgeInputStream;
+ jni::Object::GlobalRef mBridgeChannel;
+};
+
+NS_IMPL_ISUPPORTS(AndroidInputStream, nsIInputStream)
+
+NS_IMETHODIMP AndroidInputStream::Close(void) {
+ AndroidBridge::InputStreamClose(mBridgeInputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP AndroidInputStream::Available(uint64_t* _retval) {
+ *_retval = AndroidBridge::InputStreamAvailable(mBridgeInputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP AndroidInputStream::Read(char* aBuf, uint32_t aCount,
+ uint32_t* _retval) {
+ return AndroidBridge::InputStreamRead(mBridgeChannel, aBuf, aCount, _retval);
+}
+
+NS_IMETHODIMP AndroidInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure, uint32_t aCount,
+ uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP AndroidInputStream::IsNonBlocking(bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+class AndroidChannel : public nsBaseChannel {
+ private:
+ AndroidChannel(nsIURI* aURI, jni::Object::Param aConnection) {
+ mConnection = aConnection;
+ SetURI(aURI);
+
+ auto type = java::GeckoAppShell::ConnectionGetMimeType(mConnection);
+ if (type) {
+ SetContentType(type->ToCString());
+ }
+ }
+
+ public:
+ static AndroidChannel* CreateChannel(nsIURI* aURI) {
+ nsCString spec;
+ aURI->GetSpec(spec);
+
+ auto connection = java::GeckoAppShell::GetConnection(spec);
+ return connection ? new AndroidChannel(aURI, connection) : nullptr;
+ }
+
+ virtual ~AndroidChannel() {}
+
+ virtual nsresult OpenContentStream(bool async, nsIInputStream** result,
+ nsIChannel** channel) {
+ nsCOMPtr<nsIInputStream> stream = new AndroidInputStream(mConnection);
+ NS_ADDREF(*result = stream);
+ return NS_OK;
+ }
+
+ private:
+ jni::Object::GlobalRef mConnection;
+};
+
+NS_IMPL_ISUPPORTS(nsAndroidProtocolHandler, nsIProtocolHandler,
+ nsISupportsWeakReference)
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::GetScheme(nsACString& result) {
+ result.AssignLiteral("android");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::GetDefaultPort(int32_t* result) {
+ *result = -1; // no port for android: URLs
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::AllowPort(int32_t port, const char* scheme,
+ bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::GetProtocolFlags(uint32_t* result) {
+ *result = URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE |
+ URI_NORELATIVE | URI_DANGEROUS_TO_LOAD;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** aResult) {
+ nsCOMPtr<nsIChannel> channel = AndroidChannel::CreateChannel(aURI);
+ if (!channel) return NS_ERROR_FAILURE;
+
+ // set the loadInfo on the new channel
+ nsresult rv = channel->SetLoadInfo(aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aResult = channel);
+ return NS_OK;
+}
diff --git a/widget/android/nsAndroidProtocolHandler.h b/widget/android/nsAndroidProtocolHandler.h
new file mode 100644
index 0000000000..811b02e45c
--- /dev/null
+++ b/widget/android/nsAndroidProtocolHandler.h
@@ -0,0 +1,33 @@
+/* -*- 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 nsAndroidProtocolHandler_h___
+#define nsAndroidProtocolHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+
+#define NS_ANDROIDPROTOCOLHANDLER_CID \
+ { /* e9cd2b7f-8386-441b-aaf5-0b371846bfd0 */ \
+ 0xe9cd2b7f, 0x8386, 0x441b, { 0x0b, 0x37, 0x18, 0x46, 0xbf, 0xd0 } \
+ }
+
+class nsAndroidProtocolHandler final : public nsIProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIProtocolHandler methods:
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ // nsAndroidProtocolHandler methods:
+ nsAndroidProtocolHandler() {}
+
+ private:
+ ~nsAndroidProtocolHandler() {}
+};
+
+#endif /* nsAndroidProtocolHandler_h___ */
diff --git a/widget/android/nsAppShell.cpp b/widget/android/nsAppShell.cpp
new file mode 100644
index 0000000000..4dc7e3213a
--- /dev/null
+++ b/widget/android/nsAppShell.cpp
@@ -0,0 +1,781 @@
+/* -*- 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 "nsAppShell.h"
+
+#include "base/basictypes.h"
+#include "base/message_loop.h"
+#include "base/task.h"
+#include "mozilla/Hal.h"
+#include "nsExceptionHandler.h"
+#include "nsIScreen.h"
+#include "nsWindow.h"
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsIAppStartup.h"
+#include "nsIGeolocationProvider.h"
+#include "nsCacheService.h"
+#include "nsIDOMWakeLockListener.h"
+#include "nsIPowerManagerService.h"
+#include "nsISpeculativeConnect.h"
+#include "nsIURIFixup.h"
+#include "nsCategoryManagerUtils.h"
+#include "mozilla/dom/GeolocationPosition.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Components.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Hal.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/intl/OSPreferences.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#include "mozilla/java/GeckoAppShellNatives.h"
+#include "mozilla/java/GeckoThreadNatives.h"
+#include "mozilla/java/XPCOMEventTargetNatives.h"
+#include "mozilla/widget/ScreenManager.h"
+#include "prenv.h"
+
+#include "AndroidBridge.h"
+#include "AndroidBridgeUtilities.h"
+#include "AndroidSurfaceTexture.h"
+#include <android/log.h>
+#include <pthread.h>
+#include <wchar.h>
+
+#include "GeckoProfiler.h"
+#ifdef MOZ_ANDROID_HISTORY
+# include "nsNetUtil.h"
+# include "nsIURI.h"
+# include "IHistory.h"
+#endif
+
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+#endif
+
+#include "AndroidAlerts.h"
+#include "AndroidUiThread.h"
+#include "GeckoBatteryManager.h"
+#include "GeckoEditableSupport.h"
+#include "GeckoNetworkManager.h"
+#include "GeckoProcessManager.h"
+#include "GeckoScreenOrientation.h"
+#include "GeckoSystemStateListener.h"
+#include "GeckoTelemetryDelegate.h"
+#include "GeckoVRManager.h"
+#include "ImageDecoderSupport.h"
+#include "PrefsHelper.h"
+#include "ScreenHelperAndroid.h"
+#include "Telemetry.h"
+#include "WebExecutorSupport.h"
+#include "Base64UtilsSupport.h"
+#include "WebAuthnTokenManager.h"
+
+#ifdef DEBUG_ANDROID_EVENTS
+# define EVLOG(args...) ALOG(args)
+#else
+# define EVLOG(args...) \
+ do { \
+ } while (0)
+#endif
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+nsIGeolocationUpdate* gLocationCallback = nullptr;
+
+nsAppShell* nsAppShell::sAppShell;
+StaticAutoPtr<Mutex> nsAppShell::sAppShellLock;
+
+uint32_t nsAppShell::Queue::sLatencyCount[];
+uint64_t nsAppShell::Queue::sLatencyTime[];
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAppShell, nsBaseAppShell, nsIObserver)
+
+class WakeLockListener final : public nsIDOMMozWakeLockListener {
+ private:
+ ~WakeLockListener() {}
+
+ public:
+ NS_DECL_ISUPPORTS;
+
+ nsresult Callback(const nsAString& topic, const nsAString& state) override {
+ java::GeckoAppShell::NotifyWakeLockChanged(topic, state);
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(WakeLockListener, nsIDOMMozWakeLockListener)
+nsCOMPtr<nsIPowerManagerService> sPowerManagerService = nullptr;
+StaticRefPtr<WakeLockListener> sWakeLockListener;
+
+class GeckoThreadSupport final
+ : public java::GeckoThread::Natives<GeckoThreadSupport> {
+ // When this number goes above 0, the app is paused. When less than or
+ // equal to zero, the app is resumed.
+ static int32_t sPauseCount;
+
+ public:
+ static void SpeculativeConnect(jni::String::Param aUriStr) {
+ if (!NS_IsMainThread()) {
+ // We will be on the main thread if the call was queued on the Java
+ // side during startup. Otherwise, the call was not queued, which
+ // means Gecko is already sufficiently loaded, and we don't really
+ // care about speculative connections at this point.
+ return;
+ }
+
+ nsCOMPtr<nsIIOService> ioServ = do_GetIOService();
+ nsCOMPtr<nsISpeculativeConnect> specConn = do_QueryInterface(ioServ);
+ if (!specConn) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = nsAppShell::ResolveURI(aUriStr->ToCString());
+ if (!uri) {
+ return;
+ }
+
+ OriginAttributes attrs;
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(uri, attrs);
+ specConn->SpeculativeConnect(uri, principal, nullptr);
+ }
+
+ static void OnPause() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ sPauseCount++;
+ // If sPauseCount is now 1, we just crossed the threshold from "resumed"
+ // "paused". so we should notify observers and so on.
+ if (sPauseCount != 1) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ obsServ->NotifyObservers(nullptr, "application-background", nullptr);
+
+ obsServ->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize");
+
+ // If we are OOM killed with the disk cache enabled, the entire
+ // cache will be cleared (bug 105843), so shut down the cache here
+ // and re-init on foregrounding
+ if (nsCacheService::GlobalInstance()) {
+ nsCacheService::GlobalInstance()->Shutdown();
+ }
+
+ // We really want to send a notification like profile-before-change,
+ // but profile-before-change ends up shutting some things down instead
+ // of flushing data
+ Preferences* prefs = static_cast<Preferences*>(Preferences::GetService());
+ if (prefs) {
+ // Force a main thread blocking save
+ prefs->SavePrefFileBlocking();
+ }
+ }
+
+ static void OnResume() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ sPauseCount--;
+ // If sPauseCount is now 0, we just crossed the threshold from "paused"
+ // to "resumed", so we should notify observers and so on.
+ if (sPauseCount != 0) {
+ return;
+ }
+
+ // If we are OOM killed with the disk cache enabled, the entire
+ // cache will be cleared (bug 105843), so shut down cache on backgrounding
+ // and re-init here
+ if (nsCacheService::GlobalInstance()) {
+ nsCacheService::GlobalInstance()->Init();
+ }
+
+ // We didn't return from one of our own activities, so restore
+ // to foreground status
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ obsServ->NotifyObservers(nullptr, "application-foreground", nullptr);
+ }
+
+ static void CreateServices(jni::String::Param aCategory,
+ jni::String::Param aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCString category(aCategory->ToCString());
+
+ NS_CreateServicesFromCategory(category.get(),
+ nullptr, // aOrigin
+ category.get(),
+ aData ? aData->ToString().get() : nullptr);
+ }
+
+ static int64_t RunUiThreadCallback() { return RunAndroidUiTasks(); }
+
+ static void ForceQuit() {
+ nsCOMPtr<nsIAppStartup> appStartup = components::AppStartup::Service();
+
+ if (appStartup) {
+ bool userAllowedQuit = true;
+ appStartup->Quit(nsIAppStartup::eForceQuit, 0, &userAllowedQuit);
+ }
+ }
+
+ static void Crash() {
+ printf_stderr("Intentionally crashing...\n");
+ MOZ_CRASH("intentional crash");
+ }
+};
+
+int32_t GeckoThreadSupport::sPauseCount;
+
+class GeckoAppShellSupport final
+ : public java::GeckoAppShell::Natives<GeckoAppShellSupport> {
+ public:
+ static void ReportJavaCrash(const jni::Class::LocalRef& aCls,
+ jni::Throwable::Param aException,
+ jni::String::Param aStack) {
+ if (!jni::ReportException(aCls.Env(), aException.Get(), aStack.Get())) {
+ // Only crash below if crash reporter is initialized and annotation
+ // succeeded. Otherwise try other means of reporting the crash in
+ // Java.
+ return;
+ }
+
+ MOZ_CRASH("Uncaught Java exception");
+ }
+
+ static void NotifyObservers(jni::String::Param aTopic,
+ jni::String::Param aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTopic);
+
+ nsCOMPtr<nsIObserverService> obsServ = services::GetObserverService();
+ if (!obsServ) {
+ return;
+ }
+
+ obsServ->NotifyObservers(nullptr, aTopic->ToCString().get(),
+ aData ? aData->ToString().get() : nullptr);
+ }
+
+ static void AppendAppNotesToCrashReport(jni::String::Param aNotes) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aNotes);
+ CrashReporter::AppendAppNotesToCrashReport(aNotes->ToCString());
+ }
+
+ static void OnSensorChanged(int32_t aType, float aX, float aY, float aZ,
+ float aW, int64_t aTime) {
+ AutoTArray<float, 4> values;
+
+ switch (aType) {
+ // Bug 938035, transfer HAL data for orientation sensor to meet w3c
+ // spec, ex: HAL report alpha=90 means East but alpha=90 means West
+ // in w3c spec
+ case hal::SENSOR_ORIENTATION:
+ values.AppendElement(360.0f - aX);
+ values.AppendElement(-aY);
+ values.AppendElement(-aZ);
+ break;
+
+ case hal::SENSOR_LINEAR_ACCELERATION:
+ case hal::SENSOR_ACCELERATION:
+ case hal::SENSOR_GYROSCOPE:
+ case hal::SENSOR_PROXIMITY:
+ values.AppendElement(aX);
+ values.AppendElement(aY);
+ values.AppendElement(aZ);
+ break;
+
+ case hal::SENSOR_LIGHT:
+ values.AppendElement(aX);
+ break;
+
+ case hal::SENSOR_ROTATION_VECTOR:
+ case hal::SENSOR_GAME_ROTATION_VECTOR:
+ values.AppendElement(aX);
+ values.AppendElement(aY);
+ values.AppendElement(aZ);
+ values.AppendElement(aW);
+ break;
+
+ default:
+ __android_log_print(ANDROID_LOG_ERROR, "Gecko",
+ "Unknown sensor type %d", aType);
+ }
+
+ hal::SensorData sdata(hal::SensorType(aType), aTime, values);
+ hal::NotifySensorChange(sdata);
+ }
+
+ static void OnLocationChanged(double aLatitude, double aLongitude,
+ double aAltitude, float aAccuracy,
+ float aAltitudeAccuracy, float aHeading,
+ float aSpeed, int64_t aTime) {
+ if (!gLocationCallback) {
+ return;
+ }
+
+ RefPtr<nsIDOMGeoPosition> geoPosition(
+ new nsGeoPosition(aLatitude, aLongitude, aAltitude, aAccuracy,
+ aAltitudeAccuracy, aHeading, aSpeed, aTime));
+ gLocationCallback->Update(geoPosition);
+ }
+
+ static void NotifyAlertListener(jni::String::Param aName,
+ jni::String::Param aTopic,
+ jni::String::Param aCookie) {
+ if (!aName || !aTopic || !aCookie) {
+ return;
+ }
+
+ widget::AndroidAlerts::NotifyListener(aName->ToString(),
+ aTopic->ToCString().get(),
+ aCookie->ToString().get());
+ }
+};
+
+class XPCOMEventTargetWrapper final
+ : public java::XPCOMEventTarget::Natives<XPCOMEventTargetWrapper> {
+ public:
+ // Wraps a java runnable into an XPCOM runnable and dispatches it to mTarget.
+ void DispatchNative(mozilla::jni::Object::Param aJavaRunnable) {
+ java::XPCOMEventTarget::JNIRunnable::GlobalRef r =
+ java::XPCOMEventTarget::JNIRunnable::Ref::From(aJavaRunnable);
+ mTarget->Dispatch(NS_NewRunnableFunction(
+ "XPCOMEventTargetWrapper::DispatchNative",
+ [runnable = std::move(r)]() { runnable->Run(); }));
+ }
+
+ bool IsOnCurrentThread() { return mTarget->IsOnCurrentThread(); }
+
+ static void Init() {
+ java::XPCOMEventTarget::Natives<XPCOMEventTargetWrapper>::Init();
+ CreateWrapper(u"main"_ns, do_GetMainThread());
+ if (XRE_IsParentProcess()) {
+ CreateWrapper(u"launcher"_ns, ipc::GetIPCLauncher());
+ }
+ }
+
+ static void CreateWrapper(mozilla::jni::String::Param aName,
+ nsCOMPtr<nsIEventTarget> aTarget) {
+ auto java = java::XPCOMEventTarget::New();
+ auto native = MakeUnique<XPCOMEventTargetWrapper>(aTarget.forget());
+ AttachNative(java, std::move(native));
+
+ java::XPCOMEventTarget::SetTarget(aName, java);
+ }
+
+ static void ResolveAndDispatchNative(mozilla::jni::String::Param aName,
+ mozilla::jni::Object::Param aRunnable) {
+ java::XPCOMEventTarget::ResolveAndDispatch(aName, aRunnable);
+ }
+
+ explicit XPCOMEventTargetWrapper(already_AddRefed<nsIEventTarget> aTarget)
+ : mTarget(aTarget) {}
+
+ private:
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+nsAppShell::nsAppShell()
+ : mSyncRunFinished(*(sAppShellLock = new Mutex("nsAppShell")),
+ "nsAppShell.SyncRun"),
+ mSyncRunQuit(false) {
+ {
+ MutexAutoLock lock(*sAppShellLock);
+ sAppShell = this;
+ }
+
+ hal::Init();
+
+ if (!XRE_IsParentProcess()) {
+ if (jni::IsAvailable()) {
+ GeckoThreadSupport::Init();
+ GeckoAppShellSupport::Init();
+ XPCOMEventTargetWrapper::Init();
+ mozilla::GeckoSystemStateListener::Init();
+ mozilla::widget::Telemetry::Init();
+ mozilla::widget::GeckoTelemetryDelegate::Init();
+
+ // Set the corresponding state in GeckoThread.
+ java::GeckoThread::SetState(java::GeckoThread::State::RUNNING());
+ }
+ return;
+ }
+
+ if (jni::IsAvailable()) {
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperAndroid>());
+
+ // Initialize JNI and Set the corresponding state in GeckoThread.
+ AndroidBridge::ConstructBridge();
+ GeckoAppShellSupport::Init();
+ GeckoThreadSupport::Init();
+ XPCOMEventTargetWrapper::Init();
+ mozilla::GeckoBatteryManager::Init();
+ mozilla::GeckoNetworkManager::Init();
+ mozilla::GeckoProcessManager::Init();
+ mozilla::GeckoScreenOrientation::Init();
+ mozilla::GeckoSystemStateListener::Init();
+ mozilla::PrefsHelper::Init();
+ mozilla::widget::Telemetry::Init();
+ mozilla::widget::ImageDecoderSupport::Init();
+ mozilla::widget::WebExecutorSupport::Init();
+ mozilla::widget::Base64UtilsSupport::Init();
+ nsWindow::InitNatives();
+ mozilla::gl::AndroidSurfaceTexture::Init();
+ mozilla::WebAuthnTokenManager::Init();
+ mozilla::widget::GeckoTelemetryDelegate::Init();
+
+ java::GeckoThread::SetState(java::GeckoThread::State::JNI_READY());
+
+ CreateAndroidUiThread();
+ }
+
+ sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+
+ if (sPowerManagerService) {
+ sWakeLockListener = new WakeLockListener();
+ } else {
+ NS_WARNING(
+ "Failed to retrieve PowerManagerService, wakelocks will be broken!");
+ }
+}
+
+nsAppShell::~nsAppShell() {
+ {
+ // Release any thread waiting for a sync call to finish.
+ MutexAutoLock lock(*sAppShellLock);
+ sAppShell = nullptr;
+ mSyncRunFinished.NotifyAll();
+ }
+
+ while (mEventQueue.Pop(/* mayWait */ false)) {
+ NS_WARNING("Discarded event on shutdown");
+ }
+
+ if (sPowerManagerService) {
+ sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
+
+ sPowerManagerService = nullptr;
+ sWakeLockListener = nullptr;
+ }
+
+ hal::Shutdown();
+
+ if (jni::IsAvailable() && XRE_IsParentProcess()) {
+ DestroyAndroidUiThread();
+ AndroidBridge::DeconstructBridge();
+ }
+}
+
+void nsAppShell::NotifyNativeEvent() { mEventQueue.Signal(); }
+
+void nsAppShell::RecordLatencies() {
+ if (!mozilla::Telemetry::CanRecordExtended()) {
+ return;
+ }
+
+ const mozilla::Telemetry::HistogramID timeIDs[] = {
+ mozilla::Telemetry::HistogramID::FENNEC_LOOP_UI_LATENCY,
+ mozilla::Telemetry::HistogramID::FENNEC_LOOP_OTHER_LATENCY};
+
+ static_assert(ArrayLength(Queue::sLatencyCount) == Queue::LATENCY_COUNT,
+ "Count array length mismatch");
+ static_assert(ArrayLength(Queue::sLatencyTime) == Queue::LATENCY_COUNT,
+ "Time array length mismatch");
+ static_assert(ArrayLength(timeIDs) == Queue::LATENCY_COUNT,
+ "Time ID array length mismatch");
+
+ for (size_t i = 0; i < Queue::LATENCY_COUNT; i++) {
+ if (!Queue::sLatencyCount[i]) {
+ continue;
+ }
+
+ const uint64_t time =
+ Queue::sLatencyTime[i] / 1000ull / Queue::sLatencyCount[i];
+ if (time) {
+ mozilla::Telemetry::Accumulate(
+ timeIDs[i], uint32_t(std::min<uint64_t>(UINT32_MAX, time)));
+ }
+
+ // Reset latency counts.
+ Queue::sLatencyCount[i] = 0;
+ Queue::sLatencyTime[i] = 0;
+ }
+}
+
+nsresult nsAppShell::Init() {
+ nsresult rv = nsBaseAppShell::Init();
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ if (obsServ) {
+ obsServ->AddObserver(this, "browser-delayed-startup-finished", false);
+ obsServ->AddObserver(this, "geckoview-startup-complete", false);
+ obsServ->AddObserver(this, "profile-after-change", false);
+ obsServ->AddObserver(this, "quit-application", false);
+ obsServ->AddObserver(this, "quit-application-granted", false);
+ obsServ->AddObserver(this, "xpcom-shutdown", false);
+
+ if (XRE_IsParentProcess()) {
+ obsServ->AddObserver(this, "chrome-document-loaded", false);
+ } else {
+ obsServ->AddObserver(this, "content-document-global-created", false);
+ obsServ->AddObserver(this, "geckoview-content-global-transferred", false);
+ }
+ }
+
+ if (sPowerManagerService)
+ sPowerManagerService->AddWakeLockListener(sWakeLockListener);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAppShell::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ bool removeObserver = false;
+
+ if (!strcmp(aTopic, "xpcom-shutdown")) {
+ {
+ // Release any thread waiting for a sync call to finish.
+ mozilla::MutexAutoLock shellLock(*sAppShellLock);
+ mSyncRunQuit = true;
+ mSyncRunFinished.NotifyAll();
+ }
+ // We need to ensure no observers stick around after XPCOM shuts down
+ // or we'll see crashes, as the app shell outlives XPConnect.
+ mObserversHash.Clear();
+ return nsBaseAppShell::Observe(aSubject, aTopic, aData);
+
+ } else if (!strcmp(aTopic, "browser-delayed-startup-finished")) {
+ NS_CreateServicesFromCategory("browser-delayed-startup-finished", nullptr,
+ "browser-delayed-startup-finished");
+ } else if (!strcmp(aTopic, "geckoview-startup-complete")) {
+ if (jni::IsAvailable()) {
+ java::GeckoThread::CheckAndSetState(
+ java::GeckoThread::State::PROFILE_READY(),
+ java::GeckoThread::State::RUNNING());
+ }
+ } else if (!strcmp(aTopic, "profile-after-change")) {
+ if (jni::IsAvailable()) {
+ java::GeckoThread::SetState(java::GeckoThread::State::PROFILE_READY());
+
+ // Gecko on Android follows the Android app model where it never
+ // stops until it is killed by the system or told explicitly to
+ // quit. Therefore, we should *not* exit Gecko when there is no
+ // window or the last window is closed. nsIAppStartup::Quit will
+ // still force Gecko to exit.
+ nsCOMPtr<nsIAppStartup> appStartup = components::AppStartup::Service();
+ if (appStartup) {
+ appStartup->EnterLastWindowClosingSurvivalArea();
+ }
+ }
+ removeObserver = true;
+
+ } else if (!strcmp(aTopic, "chrome-document-loaded")) {
+ // Set the global ready state and enable the window event dispatcher
+ // for this particular GeckoView.
+ nsCOMPtr<dom::Document> doc = do_QueryInterface(aSubject);
+ MOZ_ASSERT(doc);
+ if (const RefPtr<nsWindow> window = nsWindow::From(doc->GetWindow())) {
+ window->OnGeckoViewReady();
+ }
+ } else if (!strcmp(aTopic, "quit-application")) {
+ if (jni::IsAvailable()) {
+ const bool restarting = aData && u"restart"_ns.Equals(aData);
+ java::GeckoThread::SetState(restarting
+ ? java::GeckoThread::State::RESTARTING()
+ : java::GeckoThread::State::EXITING());
+ }
+ removeObserver = true;
+
+ } else if (!strcmp(aTopic, "quit-application-granted")) {
+ if (jni::IsAvailable()) {
+ // We are told explicitly to quit, perhaps due to
+ // nsIAppStartup::Quit being called. We should release our hold on
+ // nsIAppStartup and let it continue to quit.
+ nsCOMPtr<nsIAppStartup> appStartup = components::AppStartup::Service();
+ if (appStartup) {
+ appStartup->ExitLastWindowClosingSurvivalArea();
+ }
+ }
+ removeObserver = true;
+
+ } else if (!strcmp(aTopic, "nsPref:changed")) {
+ if (jni::IsAvailable()) {
+ mozilla::PrefsHelper::OnPrefChange(aData);
+ }
+
+ } else if (!strcmp(aTopic, "content-document-global-created")) {
+ // Associate the PuppetWidget of the newly-created BrowserChild with a
+ // GeckoEditableChild instance.
+ MOZ_ASSERT(!XRE_IsParentProcess());
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow = do_QueryInterface(aSubject);
+ MOZ_ASSERT(domWindow);
+ nsCOMPtr<nsIWidget> domWidget = widget::WidgetUtils::DOMWindowToWidget(
+ nsPIDOMWindowOuter::From(domWindow));
+ NS_ENSURE_TRUE(domWidget, NS_OK);
+
+ widget::GeckoEditableSupport::SetOnBrowserChild(
+ domWidget->GetOwningBrowserChild());
+
+ } else if (!strcmp(aTopic, "geckoview-content-global-transferred")) {
+ // We're transferring to a new GeckoEditableParent, so notify the
+ // existing GeckoEditableChild instance associated with the docshell.
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aSubject);
+ widget::GeckoEditableSupport::SetOnBrowserChild(
+ dom::BrowserChild::GetFrom(docShell));
+ }
+
+ if (removeObserver) {
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ if (obsServ) {
+ obsServ->RemoveObserver(this, aTopic);
+ }
+ }
+ return NS_OK;
+}
+
+bool nsAppShell::ProcessNextNativeEvent(bool mayWait) {
+ EVLOG("nsAppShell::ProcessNextNativeEvent %d", mayWait);
+
+ AUTO_PROFILER_LABEL("nsAppShell::ProcessNextNativeEvent", OTHER);
+
+ mozilla::UniquePtr<Event> curEvent;
+
+ {
+ curEvent = mEventQueue.Pop(/* mayWait */ false);
+
+ if (!curEvent && mayWait) {
+ // This processes messages in the Android Looper. Note that we only
+ // get here if the normal Gecko event loop has been awoken
+ // (bug 750713). Looper messages effectively have the lowest
+ // priority because we only process them before we're about to
+ // wait for new events.
+ if (jni::IsAvailable() && XRE_IsParentProcess() &&
+ AndroidBridge::Bridge()->PumpMessageLoop()) {
+ return true;
+ }
+
+ AUTO_PROFILER_LABEL("nsAppShell::ProcessNextNativeEvent:Wait", IDLE);
+ mozilla::BackgroundHangMonitor().NotifyWait();
+
+ curEvent = mEventQueue.Pop(/* mayWait */ true);
+ }
+ }
+
+ if (!curEvent) return false;
+
+ mozilla::BackgroundHangMonitor().NotifyActivity();
+
+ curEvent->Run();
+ return true;
+}
+
+bool nsAppShell::SyncRunEvent(
+ Event&& event, UniquePtr<Event> (*eventFactory)(UniquePtr<Event>&&),
+ const TimeDuration timeout) {
+ // Perform the call on the Gecko thread in a separate lambda, and wait
+ // on the monitor on the current thread.
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // This is the lock to check that app shell is still alive,
+ // and to wait on for the sync call to complete.
+ mozilla::MutexAutoLock shellLock(*sAppShellLock);
+ nsAppShell* const appShell = sAppShell;
+
+ if (MOZ_UNLIKELY(!appShell)) {
+ // Post-shutdown.
+ return false;
+ }
+
+ bool finished = false;
+ auto runAndNotify = [&event, &finished] {
+ nsAppShell* const appShell = nsAppShell::Get();
+ if (MOZ_UNLIKELY(!appShell || appShell->mSyncRunQuit)) {
+ return false;
+ }
+ event.Run();
+ finished = true;
+ mozilla::MutexAutoLock shellLock(*sAppShellLock);
+ appShell->mSyncRunFinished.NotifyAll();
+ return finished;
+ };
+
+ UniquePtr<Event> runAndNotifyEvent =
+ mozilla::MakeUnique<LambdaEvent<decltype(runAndNotify)>>(
+ std::move(runAndNotify));
+
+ if (eventFactory) {
+ runAndNotifyEvent = (*eventFactory)(std::move(runAndNotifyEvent));
+ }
+
+ appShell->mEventQueue.Post(std::move(runAndNotifyEvent));
+
+ while (!finished && MOZ_LIKELY(sAppShell && !sAppShell->mSyncRunQuit)) {
+ appShell->mSyncRunFinished.Wait(timeout);
+ }
+
+ return finished;
+}
+
+already_AddRefed<nsIURI> nsAppShell::ResolveURI(const nsCString& aUriStr) {
+ nsCOMPtr<nsIIOService> ioServ = do_GetIOService();
+ nsCOMPtr<nsIURI> uri;
+
+ if (NS_SUCCEEDED(
+ ioServ->NewURI(aUriStr, nullptr, nullptr, getter_AddRefs(uri)))) {
+ return uri.forget();
+ }
+
+ nsCOMPtr<nsIURIFixup> fixup = components::URIFixup::Service();
+ nsCOMPtr<nsIURIFixupInfo> fixupInfo;
+ if (fixup &&
+ NS_SUCCEEDED(fixup->GetFixupURIInfo(aUriStr, nsIURIFixup::FIXUP_FLAG_NONE,
+ getter_AddRefs(fixupInfo))) &&
+ NS_SUCCEEDED(fixupInfo->GetPreferredURI(getter_AddRefs(uri)))) {
+ return uri.forget();
+ }
+ return nullptr;
+}
+
+nsresult nsAppShell::AddObserver(const nsAString& aObserverKey,
+ nsIObserver* aObserver) {
+ NS_ASSERTION(aObserver != nullptr,
+ "nsAppShell::AddObserver: aObserver is null!");
+ mObserversHash.Put(aObserverKey, aObserver);
+ return NS_OK;
+}
+
+// Used by IPC code
+namespace mozilla {
+
+bool ProcessNextEvent() {
+ nsAppShell* const appShell = nsAppShell::Get();
+ if (!appShell) {
+ return false;
+ }
+
+ return appShell->ProcessNextNativeEvent(true) ? true : false;
+}
+
+void NotifyEvent() {
+ nsAppShell* const appShell = nsAppShell::Get();
+ if (!appShell) {
+ return;
+ }
+ appShell->NotifyNativeEvent();
+}
+
+} // namespace mozilla
diff --git a/widget/android/nsAppShell.h b/widget/android/nsAppShell.h
new file mode 100644
index 0000000000..62c7c6205b
--- /dev/null
+++ b/widget/android/nsAppShell.h
@@ -0,0 +1,250 @@
+/* -*- 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 nsAppShell_h__
+#define nsAppShell_h__
+
+#include <time.h>
+
+#include <type_traits>
+#include <utility>
+
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h" // for mozilla::TimeDuration
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/jni/Natives.h"
+#include "nsBaseAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsIAndroidBridge.h"
+#include "nsInterfaceHashtable.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+bool ProcessNextEvent();
+void NotifyEvent();
+} // namespace mozilla
+
+class nsWindow;
+
+class nsAppShell : public nsBaseAppShell {
+ public:
+ struct Event : mozilla::LinkedListElement<Event> {
+ static uint64_t GetTime() {
+ timespec time;
+ if (clock_gettime(CLOCK_MONOTONIC, &time)) {
+ return 0ull;
+ }
+ return uint64_t(time.tv_sec) * 1000000000ull + time.tv_nsec;
+ }
+
+ uint64_t mPostTime{0};
+
+ bool HasSameTypeAs(const Event* other) const {
+ // Compare vtable addresses to determine same type.
+ return *reinterpret_cast<const uintptr_t*>(this) ==
+ *reinterpret_cast<const uintptr_t*>(other);
+ }
+
+ virtual ~Event() {}
+ virtual void Run() = 0;
+
+ virtual void PostTo(mozilla::LinkedList<Event>& queue) {
+ queue.insertBack(this);
+ }
+
+ virtual bool IsUIEvent() const { return false; }
+ };
+
+ template <typename T>
+ class LambdaEvent : public Event {
+ protected:
+ T lambda;
+
+ public:
+ explicit LambdaEvent(T&& l) : lambda(std::move(l)) {}
+ void Run() override { lambda(); }
+ };
+
+ class ProxyEvent : public Event {
+ protected:
+ mozilla::UniquePtr<Event> baseEvent;
+
+ public:
+ explicit ProxyEvent(mozilla::UniquePtr<Event>&& event)
+ : baseEvent(std::move(event)) {}
+
+ void PostTo(mozilla::LinkedList<Event>& queue) override {
+ baseEvent->PostTo(queue);
+ }
+
+ void Run() override { baseEvent->Run(); }
+ };
+
+ static nsAppShell* Get() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return sAppShell;
+ }
+
+ nsAppShell();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOBSERVER
+
+ nsresult Init();
+
+ void NotifyNativeEvent();
+ bool ProcessNextNativeEvent(bool mayWait) override;
+
+ // Post a subclass of Event.
+ // e.g. PostEvent(mozilla::MakeUnique<MyEvent>());
+ template <typename T, typename D>
+ static void PostEvent(mozilla::UniquePtr<T, D>&& event) {
+ mozilla::MutexAutoLock lock(*sAppShellLock);
+ if (!sAppShell) {
+ return;
+ }
+ sAppShell->mEventQueue.Post(std::move(event));
+ }
+
+ // Post a event that will call a lambda
+ // e.g. PostEvent([=] { /* do something */ });
+ template <typename T>
+ static void PostEvent(T&& lambda) {
+ mozilla::MutexAutoLock lock(*sAppShellLock);
+ if (!sAppShell) {
+ return;
+ }
+ sAppShell->mEventQueue.Post(
+ mozilla::MakeUnique<LambdaEvent<T>>(std::move(lambda)));
+ }
+
+ // Post a event and wait for it to finish running on the Gecko thread.
+ static bool SyncRunEvent(
+ Event&& event,
+ mozilla::UniquePtr<Event> (*eventFactory)(mozilla::UniquePtr<Event>&&) =
+ nullptr,
+ const mozilla::TimeDuration timeout = mozilla::TimeDuration::Forever());
+
+ template <typename T>
+ static std::enable_if_t<!std::is_base_of<Event, T>::value, void> SyncRunEvent(
+ T&& lambda) {
+ SyncRunEvent(LambdaEvent<T>(std::forward<T>(lambda)));
+ }
+
+ static already_AddRefed<nsIURI> ResolveURI(const nsCString& aUriStr);
+
+ void SetBrowserApp(nsIAndroidBrowserApp* aBrowserApp) {
+ mBrowserApp = aBrowserApp;
+ }
+
+ nsIAndroidBrowserApp* GetBrowserApp() { return mBrowserApp; }
+
+ protected:
+ static nsAppShell* sAppShell;
+ static mozilla::StaticAutoPtr<mozilla::Mutex> sAppShellLock;
+
+ static void RecordLatencies();
+
+ virtual ~nsAppShell();
+
+ nsresult AddObserver(const nsAString& aObserverKey, nsIObserver* aObserver);
+
+ class NativeCallbackEvent : public Event {
+ // Capturing the nsAppShell instance is safe because if the app
+ // shell is destroyed, this lambda will not be called either.
+ nsAppShell* const appShell;
+
+ public:
+ explicit NativeCallbackEvent(nsAppShell* as) : appShell(as) {}
+ void Run() override { appShell->NativeEventCallback(); }
+ };
+
+ void ScheduleNativeEventCallback() override {
+ mEventQueue.Post(mozilla::MakeUnique<NativeCallbackEvent>(this));
+ }
+
+ class Queue {
+ private:
+ mozilla::Monitor mMonitor;
+ mozilla::LinkedList<Event> mQueue;
+
+ public:
+ enum { LATENCY_UI, LATENCY_OTHER, LATENCY_COUNT };
+ static uint32_t sLatencyCount[LATENCY_COUNT];
+ static uint64_t sLatencyTime[LATENCY_COUNT];
+
+ Queue() : mMonitor("nsAppShell.Queue") {}
+
+ void Signal() {
+ mozilla::MonitorAutoLock lock(mMonitor);
+ lock.NotifyAll();
+ }
+
+ void Post(mozilla::UniquePtr<Event>&& event) {
+ MOZ_ASSERT(event && !event->isInList());
+
+ mozilla::MonitorAutoLock lock(mMonitor);
+ event->PostTo(mQueue);
+ if (event->isInList()) {
+ event->mPostTime = Event::GetTime();
+ // Ownership of event object transfers to the queue.
+ mozilla::Unused << event.release();
+ }
+ lock.NotifyAll();
+ }
+
+ mozilla::UniquePtr<Event> Pop(bool mayWait) {
+#ifdef EARLY_BETA_OR_EARLIER
+ bool isQueueEmpty = false;
+ if (mayWait) {
+ mozilla::MonitorAutoLock lock(mMonitor);
+ isQueueEmpty = mQueue.isEmpty();
+ }
+ if (isQueueEmpty) {
+ // Record latencies when we're about to be idle.
+ // Note: We can't call this while holding the lock because
+ // nsAppShell::RecordLatencies may try to dispatch an event to the main
+ // thread which tries to acquire the lock again.
+ nsAppShell::RecordLatencies();
+ }
+#endif
+ mozilla::MonitorAutoLock lock(mMonitor);
+
+ if (mayWait && mQueue.isEmpty()) {
+ lock.Wait();
+ }
+
+ // Ownership of event object transfers to the return value.
+ mozilla::UniquePtr<Event> event(mQueue.popFirst());
+ if (!event || !event->mPostTime) {
+ return event;
+ }
+
+#ifdef EARLY_BETA_OR_EARLIER
+ const size_t latencyType =
+ event->IsUIEvent() ? LATENCY_UI : LATENCY_OTHER;
+ const uint64_t latency = Event::GetTime() - event->mPostTime;
+
+ sLatencyCount[latencyType]++;
+ sLatencyTime[latencyType] += latency;
+#endif
+ return event;
+ }
+
+ } mEventQueue;
+
+ private:
+ mozilla::CondVar mSyncRunFinished;
+ bool mSyncRunQuit;
+
+ nsCOMPtr<nsIAndroidBrowserApp> mBrowserApp;
+ nsInterfaceHashtable<nsStringHashKey, nsIObserver> mObserversHash;
+};
+
+#endif // nsAppShell_h__
diff --git a/widget/android/nsClipboard.cpp b/widget/android/nsClipboard.cpp
new file mode 100644
index 0000000000..6008b5264f
--- /dev/null
+++ b/widget/android/nsClipboard.cpp
@@ -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 "mozilla/java/ClipboardWrappers.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+#include "nsClipboard.h"
+#include "nsISupportsPrimitives.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsPrimitiveHelpers.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
+
+/* The Android clipboard only supports text and doesn't support mime types
+ * so we assume all clipboard data is text/unicode for now. Documentation
+ * indicates that support for other data types is planned for future
+ * releases.
+ */
+
+nsClipboard::nsClipboard() {}
+
+NS_IMETHODIMP
+nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* anOwner,
+ int32_t aWhichClipboard) {
+ if (aWhichClipboard != kGlobalClipboard) return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (!jni::IsAvailable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsTArray<nsCString> flavors;
+ aTransferable->FlavorsTransferableCanImport(flavors);
+
+ nsAutoString html;
+ nsAutoString text;
+
+ for (auto& flavorStr : flavors) {
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ nsCOMPtr<nsISupports> item;
+ nsresult rv =
+ aTransferable->GetTransferData(kUnicodeMime, getter_AddRefs(item));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ nsCOMPtr<nsISupportsString> supportsString = do_QueryInterface(item);
+ if (supportsString) {
+ supportsString->GetData(text);
+ }
+ } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
+ nsCOMPtr<nsISupports> item;
+ nsresult rv =
+ aTransferable->GetTransferData(kHTMLMime, getter_AddRefs(item));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ nsCOMPtr<nsISupportsString> supportsString = do_QueryInterface(item);
+ if (supportsString) {
+ supportsString->GetData(html);
+ }
+ }
+ }
+
+ if (!html.IsEmpty() &&
+ java::Clipboard::SetHTML(java::GeckoAppShell::GetApplicationContext(),
+ text, html)) {
+ return NS_OK;
+ }
+ if (!text.IsEmpty() &&
+ java::Clipboard::SetText(java::GeckoAppShell::GetApplicationContext(),
+ text)) {
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
+ if (aWhichClipboard != kGlobalClipboard) return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (!jni::IsAvailable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsTArray<nsCString> flavors;
+ aTransferable->FlavorsTransferableCanImport(flavors);
+
+ for (auto& flavorStr : flavors) {
+ if (flavorStr.EqualsLiteral(kUnicodeMime) ||
+ flavorStr.EqualsLiteral(kHTMLMime)) {
+ auto text = java::Clipboard::GetData(
+ java::GeckoAppShell::GetApplicationContext(), flavorStr);
+ if (!text) {
+ continue;
+ }
+ nsString buffer = text->ToString();
+ if (buffer.IsEmpty()) {
+ continue;
+ }
+ nsCOMPtr<nsISupports> wrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, buffer.get(),
+ buffer.Length() * 2,
+ getter_AddRefs(wrapper));
+ if (wrapper) {
+ aTransferable->SetTransferData(flavorStr.get(), wrapper);
+ return NS_OK;
+ }
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
+ if (aWhichClipboard != kGlobalClipboard) return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (!jni::IsAvailable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ java::Clipboard::ClearText(java::GeckoAppShell::GetApplicationContext());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
+ int32_t aWhichClipboard, bool* aHasText) {
+ *aHasText = false;
+ if (aWhichClipboard != kGlobalClipboard) return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (!jni::IsAvailable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ for (auto& flavor : aFlavorList) {
+ bool hasData =
+ java::Clipboard::HasData(java::GeckoAppShell::GetApplicationContext(),
+ NS_ConvertASCIItoUTF16(flavor));
+ if (hasData) {
+ *aHasText = true;
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsSelectionClipboard(bool* aIsSupported) {
+ *aIsSupported = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsFindClipboard(bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
diff --git a/widget/android/nsClipboard.h b/widget/android/nsClipboard.h
new file mode 100644
index 0000000000..f714effaed
--- /dev/null
+++ b/widget/android/nsClipboard.h
@@ -0,0 +1,22 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 NS_CLIPBOARD_H
+#define NS_CLIPBOARD_H
+
+#include "nsIClipboard.h"
+
+class nsClipboard final : public nsIClipboard {
+ private:
+ ~nsClipboard() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICLIPBOARD
+
+ nsClipboard();
+};
+
+#endif
diff --git a/widget/android/nsDeviceContextAndroid.cpp b/widget/android/nsDeviceContextAndroid.cpp
new file mode 100644
index 0000000000..68cd4c62f9
--- /dev/null
+++ b/widget/android/nsDeviceContextAndroid.cpp
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDeviceContextAndroid.h"
+
+#include "mozilla/gfx/PrintTargetPDF.h"
+#include "mozilla/RefPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsString.h"
+#include "nsIFile.h"
+#include "nsIFileStreams.h"
+#include "nsIPrintSettings.h"
+#include "nsDirectoryServiceDefs.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+NS_IMPL_ISUPPORTS(nsDeviceContextSpecAndroid, nsIDeviceContextSpec)
+
+already_AddRefed<PrintTarget> nsDeviceContextSpecAndroid::MakePrintTarget() {
+ nsresult rv =
+ NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mTempFile));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsAutoCString filename("tmp-printing.pdf");
+ mTempFile->AppendNative(filename);
+ rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIFileOutputStream> stream =
+ do_CreateInstance("@mozilla.org/network/file-output-stream;1");
+ rv = stream->Init(mTempFile, -1, -1, 0);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ // XXX: what should we do here for size? screen size?
+ IntSize size(480, 800);
+
+ return PrintTargetPDF::CreateOrNull(stream, size);
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecAndroid::Init(nsIWidget* aWidget, nsIPrintSettings* aPS,
+ bool aIsPrintPreview) {
+ mPrintSettings = aPS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecAndroid::BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage,
+ int32_t aEndPage) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecAndroid::EndDocument() {
+ nsString targetPath;
+ nsCOMPtr<nsIFile> destFile;
+ mPrintSettings->GetToFileName(targetPath);
+
+ nsresult rv = NS_NewLocalFile(targetPath, false, getter_AddRefs(destFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString destLeafName;
+ rv = destFile->GetLeafName(destLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> destDir;
+ rv = destFile->GetParent(getter_AddRefs(destDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mTempFile->MoveTo(destDir, destLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ destFile->SetPermissions(0666);
+ return NS_OK;
+}
diff --git a/widget/android/nsDeviceContextAndroid.h b/widget/android/nsDeviceContextAndroid.h
new file mode 100644
index 0000000000..546f079594
--- /dev/null
+++ b/widget/android/nsDeviceContextAndroid.h
@@ -0,0 +1,33 @@
+/* -*- 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 nsDeviceContextAndroid_h__
+#define nsDeviceContextAndroid_h__
+
+#include "nsIDeviceContextSpec.h"
+#include "nsCOMPtr.h"
+
+class nsDeviceContextSpecAndroid final : public nsIDeviceContextSpec {
+ private:
+ ~nsDeviceContextSpecAndroid() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ already_AddRefed<PrintTarget> MakePrintTarget() final;
+
+ NS_IMETHOD Init(nsIWidget* aWidget, nsIPrintSettings* aPS,
+ bool aIsPrintPreview) override;
+ NS_IMETHOD BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) override;
+ NS_IMETHOD EndDocument() override;
+ NS_IMETHOD BeginPage() override { return NS_OK; }
+ NS_IMETHOD EndPage() override { return NS_OK; }
+
+ private:
+ nsCOMPtr<nsIPrintSettings> mPrintSettings;
+ nsCOMPtr<nsIFile> mTempFile;
+};
+#endif // nsDeviceContextAndroid_h__
diff --git a/widget/android/nsIAndroidBridge.idl b/widget/android/nsIAndroidBridge.idl
new file mode 100644
index 0000000000..40cfa61692
--- /dev/null
+++ b/widget/android/nsIAndroidBridge.idl
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 mozIDOMWindowProxy;
+
+[scriptable, uuid(e8420a7b-659b-4325-968b-a114a6a067aa)]
+interface nsIBrowserTab : nsISupports {
+ readonly attribute mozIDOMWindowProxy window;
+};
+
+[scriptable, uuid(08426a73-e70b-4680-9282-630932e2b2bb)]
+interface nsIUITelemetryObserver : nsISupports {
+ void startSession(in wstring name,
+ in long long timestamp);
+ void stopSession(in wstring name,
+ in wstring reason,
+ in long long timestamp);
+ void addEvent(in wstring action,
+ in wstring method,
+ in long long timestamp,
+ in wstring extras);
+};
+
+[scriptable, uuid(0370450f-2e9c-4d16-b333-8ca6ce31a5ff)]
+interface nsIAndroidBrowserApp : nsISupports {
+ readonly attribute nsIBrowserTab selectedTab;
+ nsIBrowserTab getBrowserTab(in int32_t tabId);
+ nsIUITelemetryObserver getUITelemetryObserver();
+};
+
+[scriptable, uuid(e64c39b8-b8ec-477d-aef5-89d517ff9219)]
+interface nsIAndroidEventCallback : nsISupports
+{
+ [implicit_jscontext]
+ void onSuccess([optional] in jsval data);
+ [implicit_jscontext]
+ void onError([optional] in jsval data);
+};
+
+[scriptable, function, uuid(819ee2db-d3b8-46dd-a476-40f89c49133c)]
+interface nsIAndroidEventFinalizer : nsISupports
+{
+ void onFinalize();
+};
+
+[scriptable, function, uuid(73569a75-78eb-4c7f-82b9-2d4f5ccf44c3)]
+interface nsIAndroidEventListener : nsISupports
+{
+ void onEvent(in AString event,
+ [optional] in jsval data,
+ [optional] in nsIAndroidEventCallback callback);
+};
+
+[scriptable, uuid(e98bf792-4145-411e-b298-8219d9b03817)]
+interface nsIAndroidEventDispatcher : nsISupports
+{
+ [implicit_jscontext]
+ void dispatch(in jsval event,
+ [optional] in jsval data,
+ [optional] in nsIAndroidEventCallback callback,
+ [optional] in nsIAndroidEventFinalizer finalizer);
+ [implicit_jscontext]
+ void registerListener(in nsIAndroidEventListener listener,
+ in jsval events);
+ [implicit_jscontext]
+ void unregisterListener(in nsIAndroidEventListener listener,
+ in jsval events);
+};
+
+[scriptable, uuid(60a78a94-6117-432f-9d49-304913a931c5)]
+interface nsIAndroidView : nsIAndroidEventDispatcher
+{
+ [implicit_jscontext] readonly attribute jsval initData;
+};
+
+[scriptable, uuid(1beb70d3-70f3-4742-98cc-a3d301b26c0c)]
+interface nsIAndroidBridge : nsIAndroidEventDispatcher
+{
+ attribute nsIAndroidBrowserApp browserApp;
+ readonly attribute boolean isFennec;
+ void contentDocumentChanged(in mozIDOMWindowProxy window);
+ boolean isContentDocumentDisplayed(in mozIDOMWindowProxy window);
+ nsIAndroidEventDispatcher getDispatcherByName(in string name);
+};
diff --git a/widget/android/nsLookAndFeel.cpp b/widget/android/nsLookAndFeel.cpp
new file mode 100644
index 0000000000..b6dec95a9b
--- /dev/null
+++ b/widget/android/nsLookAndFeel.cpp
@@ -0,0 +1,531 @@
+/* -*- 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 "mozilla/dom/ContentChild.h"
+#include "nsStyleConsts.h"
+#include "nsXULAppAPI.h"
+#include "nsLookAndFeel.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+#include "mozilla/java/GeckoRuntimeWrappers.h"
+#include "mozilla/java/GeckoSystemStateListenerWrappers.h"
+
+using namespace mozilla;
+using mozilla::dom::ContentChild;
+
+static const char16_t UNICODE_BULLET = 0x2022;
+
+nsLookAndFeel::nsLookAndFeel(const LookAndFeelCache* aCache) {
+ if (aCache) {
+ DoSetCache(*aCache);
+ }
+}
+
+nsLookAndFeel::~nsLookAndFeel() {}
+
+#define BG_PRELIGHT_COLOR NS_RGB(0xee, 0xee, 0xee)
+#define FG_PRELIGHT_COLOR NS_RGB(0x77, 0x77, 0x77)
+#define BLACK_COLOR NS_RGB(0x00, 0x00, 0x00)
+#define DARK_GRAY_COLOR NS_RGB(0x40, 0x40, 0x40)
+#define GRAY_COLOR NS_RGB(0x80, 0x80, 0x80)
+#define LIGHT_GRAY_COLOR NS_RGB(0xa0, 0xa0, 0xa0)
+#define RED_COLOR NS_RGB(0xff, 0x00, 0x00)
+
+nsresult nsLookAndFeel::GetSystemColors() {
+ if (!jni::IsAvailable()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto arr = java::GeckoAppShell::GetSystemColors();
+ if (!arr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JNIEnv* const env = arr.Env();
+ uint32_t len = static_cast<uint32_t>(env->GetArrayLength(arr.Get()));
+ jint* elements = env->GetIntArrayElements(arr.Get(), 0);
+
+ uint32_t colorsCount = sizeof(AndroidSystemColors) / sizeof(nscolor);
+ if (len < colorsCount) colorsCount = len;
+
+ // Convert Android colors to nscolor by switching R and B in the ARGB 32 bit
+ // value
+ nscolor* colors = (nscolor*)&mSystemColors;
+
+ for (uint32_t i = 0; i < colorsCount; i++) {
+ uint32_t androidColor = static_cast<uint32_t>(elements[i]);
+ uint8_t r = (androidColor & 0x00ff0000) >> 16;
+ uint8_t b = (androidColor & 0x000000ff);
+ colors[i] = (androidColor & 0xff00ff00) | (b << 16) | r;
+ }
+
+ env->ReleaseIntArrayElements(arr.Get(), elements, 0);
+
+ return NS_OK;
+}
+
+void nsLookAndFeel::NativeInit() {
+ EnsureInitSystemColors();
+ EnsureInitShowPassword();
+ RecordTelemetry();
+}
+
+/* virtual */
+void nsLookAndFeel::RefreshImpl() {
+ nsXPLookAndFeel::RefreshImpl();
+
+ mInitializedSystemColors = false;
+ mInitializedShowPassword = false;
+ mPrefersReducedMotionCached = false;
+ mSystemUsesDarkThemeCached = false;
+}
+
+nsresult nsLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor) {
+ nsresult rv = NS_OK;
+
+ EnsureInitSystemColors();
+ if (!mInitializedSystemColors) {
+ // Failure to initialize colors is an error condition. Return black.
+ aColor = 0;
+ return NS_ERROR_FAILURE;
+ }
+
+ // XXX we'll want to use context.obtainStyledAttributes on the java side to
+ // get all of these; see TextView.java for a good exmaple.
+
+ switch (aID) {
+ // These colors don't seem to be used for anything anymore in Mozilla
+ // (except here at least TextSelectBackground and TextSelectForeground)
+ // The CSS2 colors below are used.
+ case ColorID::WindowBackground:
+ aColor = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::WindowForeground:
+ aColor = mSystemColors.textColorPrimary;
+ break;
+ case ColorID::WidgetBackground:
+ aColor = mSystemColors.colorBackground;
+ break;
+ case ColorID::WidgetForeground:
+ aColor = mSystemColors.colorForeground;
+ break;
+ case ColorID::WidgetSelectBackground:
+ aColor = mSystemColors.textColorHighlight;
+ break;
+ case ColorID::WidgetSelectForeground:
+ aColor = mSystemColors.textColorPrimaryInverse;
+ break;
+ case ColorID::Widget3DHighlight:
+ aColor = LIGHT_GRAY_COLOR;
+ break;
+ case ColorID::Widget3DShadow:
+ aColor = DARK_GRAY_COLOR;
+ break;
+ case ColorID::TextBackground:
+ // not used?
+ aColor = mSystemColors.colorBackground;
+ break;
+ case ColorID::TextForeground:
+ // not used?
+ aColor = mSystemColors.textColorPrimary;
+ break;
+ case ColorID::TextSelectBackground:
+ /* matched to action_accent in java codebase */
+ aColor = NS_RGBA(10, 132, 255, 153);
+ break;
+ case ColorID::TextSelectForeground:
+ aColor = NS_RGB(0, 0, 0);
+ break;
+ case ColorID::IMESelectedRawTextBackground:
+ case ColorID::IMESelectedConvertedTextBackground:
+ // still used
+ aColor = mSystemColors.textColorHighlight;
+ break;
+ case ColorID::IMESelectedRawTextForeground:
+ case ColorID::IMESelectedConvertedTextForeground:
+ // still used
+ aColor = mSystemColors.textColorPrimaryInverse;
+ break;
+ case ColorID::IMERawInputBackground:
+ case ColorID::IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ break;
+ case ColorID::IMERawInputForeground:
+ case ColorID::IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case ColorID::IMERawInputUnderline:
+ case ColorID::IMEConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case ColorID::IMESelectedRawTextUnderline:
+ case ColorID::IMESelectedConvertedTextUnderline:
+ aColor = NS_TRANSPARENT;
+ break;
+ case ColorID::SpellCheckerUnderline:
+ aColor = RED_COLOR;
+ break;
+
+ // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ case ColorID::Activeborder:
+ // active window border
+ aColor = mSystemColors.colorBackground;
+ break;
+ case ColorID::Activecaption:
+ // active window caption background
+ aColor = mSystemColors.colorBackground;
+ break;
+ case ColorID::Appworkspace:
+ // MDI background color
+ aColor = mSystemColors.colorBackground;
+ break;
+ case ColorID::Background:
+ // desktop background
+ aColor = mSystemColors.colorBackground;
+ break;
+ case ColorID::Graytext:
+ // disabled text in windows, menus, etc.
+ aColor = NS_RGB(0xb1, 0xa5, 0x98);
+ break;
+ case ColorID::MozCellhighlight:
+ case ColorID::MozHtmlCellhighlight:
+ case ColorID::Highlight:
+ // background of selected item
+ aColor = NS_RGB(0xfa, 0xd1, 0x84);
+ break;
+ case ColorID::MozCellhighlighttext:
+ case ColorID::MozHtmlCellhighlighttext:
+ case ColorID::Highlighttext:
+ case ColorID::Fieldtext:
+ aColor = NS_RGB(0x1a, 0x1a, 0x1a);
+ break;
+ case ColorID::Inactiveborder:
+ // inactive window border
+ aColor = mSystemColors.colorBackground;
+ break;
+ case ColorID::Inactivecaption:
+ // inactive window caption
+ aColor = mSystemColors.colorBackground;
+ break;
+ case ColorID::Inactivecaptiontext:
+ // text in inactive window caption
+ aColor = mSystemColors.textColorTertiary;
+ break;
+ case ColorID::Infobackground:
+ aColor = NS_RGB(0xf5, 0xf5, 0xb5);
+ break;
+ case ColorID::Infotext:
+ aColor = BLACK_COLOR;
+ break;
+ case ColorID::Menu:
+ aColor = NS_RGB(0xf7, 0xf5, 0xf3);
+ break;
+ case ColorID::Scrollbar:
+ // scrollbar gray area
+ aColor = mSystemColors.colorBackground;
+ break;
+
+ case ColorID::Threedface:
+ case ColorID::Buttonface:
+ case ColorID::Threedlightshadow:
+ aColor = NS_RGB(0xec, 0xe7, 0xe2);
+ break;
+
+ case ColorID::Buttonhighlight:
+ case ColorID::Field:
+ case ColorID::Threedhighlight:
+ case ColorID::MozCombobox:
+ case ColorID::MozEventreerow:
+ aColor = NS_RGB(0xff, 0xff, 0xff);
+ break;
+
+ case ColorID::Buttonshadow:
+ case ColorID::Threedshadow:
+ aColor = NS_RGB(0xae, 0xa1, 0x94);
+ break;
+
+ case ColorID::Threeddarkshadow:
+ // 3-D shadow outer edge color
+ aColor = BLACK_COLOR;
+ break;
+
+ case ColorID::MozDialog:
+ case ColorID::Window:
+ case ColorID::Windowframe:
+ aColor = NS_RGB(0xef, 0xeb, 0xe7);
+ break;
+ case ColorID::Buttontext:
+ case ColorID::Captiontext:
+ case ColorID::Menutext:
+ case ColorID::MozButtonhovertext:
+ case ColorID::MozDialogtext:
+ case ColorID::MozComboboxtext:
+ case ColorID::Windowtext:
+ case ColorID::MozColheadertext:
+ case ColorID::MozColheaderhovertext:
+ aColor = NS_RGB(0x10, 0x10, 0x10);
+ break;
+ case ColorID::MozDragtargetzone:
+ aColor = mSystemColors.textColorHighlight;
+ break;
+ case ColorID::MozButtondefault:
+ // default button border color
+ aColor = BLACK_COLOR;
+ break;
+ case ColorID::MozButtonhoverface:
+ aColor = NS_RGB(0xf3, 0xf0, 0xed);
+ break;
+ case ColorID::MozMenuhover:
+ aColor = BG_PRELIGHT_COLOR;
+ break;
+ case ColorID::MozMenuhovertext:
+ aColor = FG_PRELIGHT_COLOR;
+ break;
+ case ColorID::MozOddtreerow:
+ aColor = NS_TRANSPARENT;
+ break;
+ case ColorID::MozNativehyperlinktext:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case ColorID::MozMenubartext:
+ aColor = mSystemColors.colorForeground;
+ break;
+ case ColorID::MozMenubarhovertext:
+ aColor = FG_PRELIGHT_COLOR;
+ break;
+ default:
+ /* default color is BLACK */
+ aColor = 0;
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ return rv;
+}
+
+nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
+ nsresult rv = NS_OK;
+
+ switch (aID) {
+ case IntID::ScrollButtonLeftMouseButtonAction:
+ aResult = 0;
+ break;
+
+ case IntID::ScrollButtonMiddleMouseButtonAction:
+ case IntID::ScrollButtonRightMouseButtonAction:
+ aResult = 3;
+ break;
+
+ case IntID::CaretBlinkTime:
+ aResult = 500;
+ break;
+
+ case IntID::CaretWidth:
+ aResult = 1;
+ break;
+
+ case IntID::ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+
+ case IntID::SelectTextfieldsOnKeyFocus:
+ // Select textfield content when focused by kbd
+ // used by EventStateManager::sTextfieldSelectModel
+ aResult = 1;
+ break;
+
+ case IntID::SubmenuDelay:
+ aResult = 200;
+ break;
+
+ case IntID::TooltipDelay:
+ aResult = 500;
+ break;
+
+ case IntID::MenusCanOverlapOSBar:
+ // we want XUL popups to be able to overlap the task bar.
+ aResult = 1;
+ break;
+
+ case IntID::ScrollArrowStyle:
+ aResult = eScrollArrowStyle_Single;
+ break;
+
+ case IntID::ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+
+ case IntID::TouchEnabled:
+ aResult = 1;
+ break;
+
+ case IntID::WindowsDefaultTheme:
+ case IntID::WindowsThemeIdentifier:
+ case IntID::OperatingSystemVersionIdentifier:
+ aResult = 0;
+ rv = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+
+ case IntID::SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
+ break;
+
+ case IntID::ScrollbarButtonAutoRepeatBehavior:
+ aResult = 0;
+ break;
+
+ case IntID::ContextMenuOffsetVertical:
+ case IntID::ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+
+ case IntID::PrefersReducedMotion:
+ if (!mPrefersReducedMotionCached && XRE_IsParentProcess()) {
+ mPrefersReducedMotion =
+ java::GeckoSystemStateListener::PrefersReducedMotion();
+ mPrefersReducedMotionCached = true;
+ }
+ aResult = mPrefersReducedMotion;
+ break;
+
+ case IntID::PrimaryPointerCapabilities:
+ aResult = java::GeckoAppShell::GetPrimaryPointerCapabilities();
+ break;
+ case IntID::AllPointerCapabilities:
+ aResult = java::GeckoAppShell::GetAllPointerCapabilities();
+ break;
+
+ case IntID::SystemUsesDarkTheme: {
+ if (!mSystemUsesDarkThemeCached && XRE_IsParentProcess()) {
+ // Bail out if AndroidBridge hasn't initialized since we try to query
+ // this value via nsMediaFeatures::InitSystemMetrics without
+ // initializing AndroidBridge on xpcshell tests.
+ if (!jni::IsAvailable()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ java::GeckoRuntime::LocalRef runtime =
+ java::GeckoRuntime::GetInstance();
+ mSystemUsesDarkTheme = runtime && runtime->UsesDarkTheme();
+ mSystemUsesDarkThemeCached = true;
+ }
+
+ aResult = mSystemUsesDarkTheme;
+ break;
+ }
+
+ case IntID::DragThresholdX:
+ case IntID::DragThresholdY:
+ // Threshold where a tap becomes a drag, in 1/240" reference pixels.
+ aResult = 25;
+ break;
+
+ default:
+ aResult = 0;
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
+ nsresult rv = NS_OK;
+
+ switch (aID) {
+ case FloatID::IMEUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+
+ case FloatID::SpellCheckerUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+
+ default:
+ aResult = -1.0;
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ return rv;
+}
+
+/*virtual*/
+bool nsLookAndFeel::NativeGetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) {
+ aFontName.AssignLiteral("\"Roboto\"");
+ aFontStyle.style = FontSlantStyle::Normal();
+ aFontStyle.weight = FontWeight::Normal();
+ aFontStyle.stretch = FontStretch::Normal();
+ aFontStyle.size = 9.0 * 96.0f / 72.0f;
+ aFontStyle.systemFont = true;
+ return true;
+}
+
+/*virtual*/
+bool nsLookAndFeel::GetEchoPasswordImpl() {
+ EnsureInitShowPassword();
+ return mShowPassword;
+}
+
+uint32_t nsLookAndFeel::GetPasswordMaskDelayImpl() {
+ // This value is hard-coded in Android OS's PasswordTransformationMethod.java
+ return 1500;
+}
+
+/* virtual */
+char16_t nsLookAndFeel::GetPasswordCharacterImpl() {
+ // This value is hard-coded in Android OS's PasswordTransformationMethod.java
+ return UNICODE_BULLET;
+}
+
+void nsLookAndFeel::EnsureInitSystemColors() {
+ if (!mInitializedSystemColors) {
+ mInitializedSystemColors = NS_SUCCEEDED(GetSystemColors());
+ }
+}
+
+void nsLookAndFeel::EnsureInitShowPassword() {
+ if (!mInitializedShowPassword && jni::IsAvailable()) {
+ mShowPassword = java::GeckoAppShell::GetShowPasswordSetting();
+ mInitializedShowPassword = true;
+ }
+}
+
+widget::LookAndFeelCache nsLookAndFeel::GetCacheImpl() {
+ LookAndFeelCache cache = nsXPLookAndFeel::GetCacheImpl();
+
+ const IntID kIdsToCache[] = {IntID::PrefersReducedMotion,
+ IntID::SystemUsesDarkTheme};
+
+ for (IntID id : kIdsToCache) {
+ cache.mInts().AppendElement(LookAndFeelInt(id, GetInt(id)));
+ }
+
+ return cache;
+}
+
+void nsLookAndFeel::SetCacheImpl(const LookAndFeelCache& aCache) {
+ DoSetCache(aCache);
+}
+
+void nsLookAndFeel::DoSetCache(const LookAndFeelCache& aCache) {
+ for (const auto& entry : aCache.mInts()) {
+ switch (entry.id()) {
+ case IntID::PrefersReducedMotion:
+ mPrefersReducedMotion = entry.value();
+ mPrefersReducedMotionCached = true;
+ break;
+ case IntID::SystemUsesDarkTheme:
+ mSystemUsesDarkTheme = !!entry.value();
+ mSystemUsesDarkThemeCached = true;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Bogus Int ID in cache");
+ break;
+ }
+ }
+}
diff --git a/widget/android/nsLookAndFeel.h b/widget/android/nsLookAndFeel.h
new file mode 100644
index 0000000000..9d3edaca71
--- /dev/null
+++ b/widget/android/nsLookAndFeel.h
@@ -0,0 +1,49 @@
+/* -*- 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 __nsLookAndFeel
+#define __nsLookAndFeel
+
+#include "nsXPLookAndFeel.h"
+#include "AndroidBridge.h"
+
+class nsLookAndFeel final : public nsXPLookAndFeel {
+ public:
+ explicit nsLookAndFeel(const LookAndFeelCache* aCache);
+ virtual ~nsLookAndFeel();
+
+ void NativeInit() final;
+ virtual void RefreshImpl() override;
+ nsresult NativeGetInt(IntID aID, int32_t& aResult) override;
+ nsresult NativeGetFloat(FloatID aID, float& aResult) override;
+ nsresult NativeGetColor(ColorID aID, nscolor& aResult) override;
+ bool NativeGetFont(FontID aID, nsString& aName,
+ gfxFontStyle& aStyle) override;
+ virtual bool GetEchoPasswordImpl() override;
+ virtual uint32_t GetPasswordMaskDelayImpl() override;
+ virtual char16_t GetPasswordCharacterImpl() override;
+ LookAndFeelCache GetCacheImpl() override;
+ void SetCacheImpl(const LookAndFeelCache& aCache) override;
+
+ protected:
+ void DoSetCache(const LookAndFeelCache& aCache);
+
+ bool mInitializedSystemColors = false;
+ mozilla::AndroidSystemColors mSystemColors;
+ bool mInitializedShowPassword = false;
+ bool mShowPassword = false;
+
+ bool mSystemUsesDarkTheme = false;
+ bool mSystemUsesDarkThemeCached = false;
+
+ bool mPrefersReducedMotion = false;
+ bool mPrefersReducedMotionCached = false;
+
+ nsresult GetSystemColors();
+
+ void EnsureInitSystemColors();
+ void EnsureInitShowPassword();
+};
+
+#endif
diff --git a/widget/android/nsNativeBasicThemeAndroid.cpp b/widget/android/nsNativeBasicThemeAndroid.cpp
new file mode 100644
index 0000000000..425baf6239
--- /dev/null
+++ b/widget/android/nsNativeBasicThemeAndroid.cpp
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 40; 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 "nsNativeBasicThemeAndroid.h"
+
+already_AddRefed<nsITheme> do_GetBasicNativeThemeDoNotUseDirectly() {
+ static StaticRefPtr<nsITheme> gInstance;
+ if (MOZ_UNLIKELY(!gInstance)) {
+ gInstance = new nsNativeBasicThemeAndroid();
+ ClearOnShutdown(&gInstance);
+ }
+ return do_AddRef(gInstance);
+}
diff --git a/widget/android/nsNativeBasicThemeAndroid.h b/widget/android/nsNativeBasicThemeAndroid.h
new file mode 100644
index 0000000000..a17efbfe5f
--- /dev/null
+++ b/widget/android/nsNativeBasicThemeAndroid.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 nsNativeBasicThemeAndroid_h
+#define nsNativeBasicThemeAndroid_h
+
+#include "nsNativeBasicTheme.h"
+
+class nsNativeBasicThemeAndroid : public nsNativeBasicTheme {
+ public:
+ nsNativeBasicThemeAndroid() = default;
+
+ protected:
+ virtual ~nsNativeBasicThemeAndroid() = default;
+};
+
+#endif
diff --git a/widget/android/nsNativeThemeAndroid.cpp b/widget/android/nsNativeThemeAndroid.cpp
new file mode 100644
index 0000000000..3f42a38374
--- /dev/null
+++ b/widget/android/nsNativeThemeAndroid.cpp
@@ -0,0 +1,926 @@
+/* -*- Mode: C++; tab-width: 40; 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 "nsNativeThemeAndroid.h"
+
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsCSSRendering.h"
+#include "nsDateTimeControlFrame.h"
+#include "nsDeviceContext.h"
+#include "nsLayoutUtils.h"
+#include "PathHelpers.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+
+namespace mozilla {
+namespace widget {
+
+static const sRGBColor sBackgroundColor(sRGBColor(1.0f, 1.0f, 1.0f));
+static const sRGBColor sBackgroundActiveColor(sRGBColor(0.88f, 0.88f, 0.9f));
+static const sRGBColor sBackgroundActiveColorDisabled(sRGBColor(0.88f, 0.88f,
+ 0.9f, 0.4f));
+static const sRGBColor sBorderColor(sRGBColor(0.62f, 0.62f, 0.68f));
+static const sRGBColor sBorderColorDisabled(sRGBColor(0.44f, 0.44f, 0.44f,
+ 0.4f));
+static const sRGBColor sBorderHoverColor(sRGBColor(0.5f, 0.5f, 0.56f));
+static const sRGBColor sBorderHoverColorDisabled(sRGBColor(0.5f, 0.5f, 0.56f,
+ 0.4f));
+static const sRGBColor sBorderFocusColor(sRGBColor(0.04f, 0.52f, 1.0f));
+static const sRGBColor sCheckBackgroundColor(sRGBColor(0.18f, 0.39f, 0.89f));
+static const sRGBColor sCheckBackgroundColorDisabled(sRGBColor(0.18f, 0.39f,
+ 0.89f, 0.4f));
+static const sRGBColor sCheckBackgroundHoverColor(sRGBColor(0.02f, 0.24f,
+ 0.58f));
+static const sRGBColor sCheckBackgroundHoverColorDisabled(
+ sRGBColor(0.02f, 0.24f, 0.58f, 0.4f));
+static const sRGBColor sCheckBackgroundActiveColor(sRGBColor(0.03f, 0.19f,
+ 0.45f));
+static const sRGBColor sCheckBackgroundActiveColorDisabled(
+ sRGBColor(0.03f, 0.19f, 0.45f, 0.4f));
+static const sRGBColor sDisabledColor(sRGBColor(0.89f, 0.89f, 0.89f));
+static const sRGBColor sActiveColor(sRGBColor(0.47f, 0.47f, 0.48f));
+static const sRGBColor sInputHoverColor(sRGBColor(0.05f, 0.05f, 0.05f, 0.5f));
+static const sRGBColor sRangeInputBackgroundColor(sRGBColor(0.89f, 0.89f,
+ 0.89f));
+static const sRGBColor sButtonColor(sRGBColor(0.98f, 0.98f, 0.98f));
+static const sRGBColor sButtonHoverColor(sRGBColor(0.94f, 0.94f, 0.96f));
+static const sRGBColor sButtonActiveColor(sRGBColor(0.88f, 0.88f, 0.90f));
+static const sRGBColor sWhiteColor(sRGBColor(1.0f, 1.0f, 1.0f, 0.0f));
+
+static const CSSIntCoord kMinimumAndroidWidgetSize = 17;
+
+} // namespace widget
+} // namespace mozilla
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeAndroid, nsNativeTheme, nsITheme)
+
+static uint32_t GetDPIRatio(nsIFrame* aFrame) {
+ return AppUnitsPerCSSPixel() / aFrame->PresContext()
+ ->DeviceContext()
+ ->AppUnitsPerDevPixelAtUnitFullZoom();
+}
+
+static bool IsDateTimeResetButton(nsIFrame* aFrame) {
+ nsIFrame* parent = aFrame->GetParent();
+ if (parent && (parent = parent->GetParent()) &&
+ (parent = parent->GetParent())) {
+ nsDateTimeControlFrame* dateTimeFrame = do_QueryFrame(parent);
+ if (dateTimeFrame) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool IsDateTimeTextField(nsIFrame* aFrame) {
+ nsDateTimeControlFrame* dateTimeFrame = do_QueryFrame(aFrame);
+ return dateTimeFrame;
+}
+
+static void ComputeCheckColors(const EventStates& aState,
+ sRGBColor& aBackgroundColor,
+ sRGBColor& aBorderColor) {
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ bool isPressed = !isDisabled && aState.HasAllStates(NS_EVENT_STATE_HOVER |
+ NS_EVENT_STATE_ACTIVE);
+ bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
+ bool isFocused = aState.HasState(NS_EVENT_STATE_FOCUS);
+ bool isChecked = aState.HasState(NS_EVENT_STATE_CHECKED);
+
+ sRGBColor fillColor = sBackgroundColor;
+ sRGBColor borderColor = sBorderColor;
+ if (isDisabled) {
+ if (isChecked) {
+ fillColor = borderColor = sCheckBackgroundColorDisabled;
+ } else {
+ fillColor = sBackgroundColor;
+ borderColor = sBorderColorDisabled;
+ }
+ } else {
+ if (isChecked) {
+ if (isPressed) {
+ fillColor = borderColor = sCheckBackgroundActiveColor;
+ } else if (isHovered) {
+ fillColor = borderColor = sCheckBackgroundHoverColor;
+ } else {
+ fillColor = borderColor = sCheckBackgroundColor;
+ }
+ } else if (isPressed) {
+ fillColor = sBackgroundActiveColor;
+ borderColor = sBorderHoverColor;
+ } else if (isFocused) {
+ fillColor = sBackgroundActiveColor;
+ borderColor = sBorderFocusColor;
+ } else if (isHovered) {
+ fillColor = sBackgroundColor;
+ borderColor = sBorderHoverColor;
+ } else {
+ fillColor = sBackgroundColor;
+ borderColor = sBorderColor;
+ }
+ }
+
+ aBackgroundColor = fillColor;
+ aBorderColor = borderColor;
+}
+
+// Checkbox and radio need to preserve aspect-ratio for compat.
+static Rect FixAspectRatio(const Rect& aRect) {
+ Rect rect(aRect);
+ if (rect.width == rect.height) {
+ return rect;
+ }
+
+ if (rect.width > rect.height) {
+ auto diff = rect.width - rect.height;
+ rect.width = rect.height;
+ rect.x += diff / 2;
+ } else {
+ auto diff = rect.height - rect.width;
+ rect.height = rect.width;
+ rect.y += diff / 2;
+ }
+
+ return rect;
+}
+
+// This pushes and pops a clip rect to the draw target.
+//
+// This is done to reduce fuzz in places where we may have antialiasing, because
+// skia is not clip-invariant: given different clips, it does not guarantee the
+// same result, even if the painted content doesn't intersect the clips.
+//
+// This is a bit sad, overall, but...
+struct MOZ_RAII AutoClipRect {
+ AutoClipRect(DrawTarget& aDt, const Rect& aRect) : mDt(aDt) {
+ mDt.PushClipRect(aRect);
+ }
+
+ ~AutoClipRect() { mDt.PopClip(); }
+
+ private:
+ DrawTarget& mDt;
+};
+
+static void PaintRoundedRectWithBorder(DrawTarget* aDrawTarget,
+ const Rect& aRect,
+ const sRGBColor& aBackgroundColor,
+ const sRGBColor& aBorderColor,
+ CSSCoord aBorderWidth, CSSCoord aRadius,
+ uint32_t aDpi) {
+ const LayoutDeviceCoord borderWidth(aBorderWidth * aDpi);
+ const LayoutDeviceCoord radius(aRadius * aDpi);
+
+ Rect rect(aRect);
+ // Deflate the rect by half the border width, so that the middle of the stroke
+ // fills exactly the area we want to fill and not more.
+ rect.Deflate(borderWidth * 0.5f);
+
+ RectCornerRadii radii(radius, radius, radius, radius);
+ RefPtr<Path> roundedRect = MakePathForRoundedRect(*aDrawTarget, rect, radii);
+
+ aDrawTarget->Fill(roundedRect, ColorPattern(ToDeviceColor(aBackgroundColor)));
+ aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(aBorderColor)),
+ StrokeOptions(borderWidth));
+}
+
+static void PaintCheckboxControl(DrawTarget* aDrawTarget, const Rect& aRect,
+ const EventStates& aState, uint32_t aDpi) {
+ const CSSCoord kBorderWidth = 2.0f;
+ const CSSCoord kRadius = 4.0f;
+
+ sRGBColor backgroundColor;
+ sRGBColor borderColor;
+ ComputeCheckColors(aState, backgroundColor, borderColor);
+ PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor,
+ kBorderWidth, kRadius, aDpi);
+}
+
+static void PaintCheckMark(DrawTarget* aDrawTarget, const Rect& aRect,
+ const EventStates& aState, uint32_t aDpi) {
+ // Points come from the coordinates on a 7X7 unit box centered at 0,0
+ const float checkPolygonX[] = {-2.5, -0.7, 2.5};
+ const float checkPolygonY[] = {-0.3, 1.7, -1.5};
+ const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(float);
+ const int32_t checkSize = 8;
+
+ auto center = aRect.Center();
+
+ // Scale the checkmark based on the smallest dimension
+ nscoord paintScale = std::min(aRect.width, aRect.height) / checkSize;
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ Point p = center +
+ Point(checkPolygonX[0] * paintScale, checkPolygonY[0] * paintScale);
+ builder->MoveTo(p);
+ for (int32_t polyIndex = 1; polyIndex < checkNumPoints; polyIndex++) {
+ p = center + Point(checkPolygonX[polyIndex] * paintScale,
+ checkPolygonY[polyIndex] * paintScale);
+ builder->LineTo(p);
+ }
+ RefPtr<Path> path = builder->Finish();
+ aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(sBackgroundColor)),
+ StrokeOptions(2.0f * aDpi));
+}
+
+static void PaintIndeterminateMark(DrawTarget* aDrawTarget, const Rect& aRect,
+ const EventStates& aState) {
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+
+ Rect rect(aRect);
+ rect.y += (rect.height - rect.height / 4) / 2;
+ rect.height /= 4;
+
+ aDrawTarget->FillRect(
+ rect, ColorPattern(
+ ToDeviceColor(isDisabled ? sDisabledColor : sBackgroundColor)));
+}
+
+static void PaintStrokedEllipse(DrawTarget* aDrawTarget, const Rect& aRect,
+ const sRGBColor& aBackgroundColor,
+ const sRGBColor& aBorderColor,
+ const CSSCoord aBorderWidth, uint32_t aDpi) {
+ const LayoutDeviceCoord borderWidth(aBorderWidth * aDpi);
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+
+ // Deflate for the same reason as PaintRoundedRectWithBorder. Note that the
+ // size is the diameter, so we just shrink by the border width once.
+ Size size(aRect.Size() - Size(borderWidth, borderWidth));
+ AppendEllipseToPath(builder, aRect.Center(), size);
+ RefPtr<Path> ellipse = builder->Finish();
+
+ aDrawTarget->Fill(ellipse, ColorPattern(ToDeviceColor(aBackgroundColor)));
+ aDrawTarget->Stroke(ellipse, ColorPattern(ToDeviceColor(aBorderColor)),
+ StrokeOptions(borderWidth));
+}
+
+static void PaintRadioControl(DrawTarget* aDrawTarget, const Rect& aRect,
+ const EventStates& aState, uint32_t aDpi) {
+ const CSSCoord kBorderWidth = 2.0f;
+
+ sRGBColor backgroundColor;
+ sRGBColor borderColor;
+ ComputeCheckColors(aState, backgroundColor, borderColor);
+
+ PaintStrokedEllipse(aDrawTarget, aRect, backgroundColor, borderColor,
+ kBorderWidth, aDpi);
+}
+
+static void PaintCheckedRadioButton(DrawTarget* aDrawTarget, const Rect& aRect,
+ uint32_t aDpi) {
+ Rect rect(aRect);
+ rect.x += 4.5f * aDpi;
+ rect.width -= 9.0f * aDpi;
+ rect.y += 4.5f * aDpi;
+ rect.height -= 9.0f * aDpi;
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ AppendEllipseToPath(builder, rect.Center(), rect.Size());
+ RefPtr<Path> ellipse = builder->Finish();
+ aDrawTarget->Fill(ellipse, ColorPattern(ToDeviceColor(sBackgroundColor)));
+}
+
+static sRGBColor ComputeBorderColor(const EventStates& aState) {
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
+ bool isFocused = aState.HasState(NS_EVENT_STATE_FOCUS);
+ if (isFocused) {
+ return sBorderFocusColor;
+ }
+ if (isHovered) {
+ return sBorderHoverColor;
+ }
+ return sBorderColor;
+}
+
+static void PaintTextField(DrawTarget* aDrawTarget, const Rect& aRect,
+ const EventStates& aState, uint32_t aDpi) {
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ const sRGBColor& backgroundColor =
+ isDisabled ? sDisabledColor : sBackgroundColor;
+ const sRGBColor borderColor = ComputeBorderColor(aState);
+
+ const CSSCoord kRadius = 4.0f;
+
+ PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor,
+ kTextFieldBorderWidth, kRadius, aDpi);
+}
+
+std::pair<sRGBColor, sRGBColor> ComputeButtonColors(
+ const EventStates& aState, bool aIsDatetimeResetButton = false) {
+ bool isActive =
+ aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
+
+ const sRGBColor& backgroundColor = [&] {
+ if (isDisabled) {
+ return sDisabledColor;
+ }
+ if (aIsDatetimeResetButton) {
+ return sWhiteColor;
+ }
+ if (isActive) {
+ return sButtonActiveColor;
+ }
+ if (isHovered) {
+ return sButtonHoverColor;
+ }
+ return sButtonColor;
+ }();
+
+ const sRGBColor borderColor = ComputeBorderColor(aState);
+
+ return std::make_pair(backgroundColor, borderColor);
+}
+
+static void PaintMenulist(DrawTarget* aDrawTarget, const Rect& aRect,
+ const EventStates& aState, uint32_t aDpi) {
+ const CSSCoord kRadius = 4.0f;
+
+ sRGBColor backgroundColor, borderColor;
+ std::tie(backgroundColor, borderColor) = ComputeButtonColors(aState);
+
+ PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor,
+ kMenulistBorderWidth, kRadius, aDpi);
+}
+
+static void PaintArrow(DrawTarget* aDrawTarget, const Rect& aRect,
+ const int32_t aArrowPolygonX[],
+ const int32_t aArrowPolygonY[],
+ const int32_t aArrowNumPoints, const int32_t aArrowSize,
+ const sRGBColor aFillColor, uint32_t aDpi) {
+ nscoord paintScale = std::min(aRect.width, aRect.height) / aArrowSize;
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ Point p = aRect.Center() + Point(aArrowPolygonX[0] * paintScale,
+ aArrowPolygonY[0] * paintScale);
+
+ builder->MoveTo(p);
+ for (int32_t polyIndex = 1; polyIndex < aArrowNumPoints; polyIndex++) {
+ p = aRect.Center() + Point(aArrowPolygonX[polyIndex] * paintScale,
+ aArrowPolygonY[polyIndex] * paintScale);
+ builder->LineTo(p);
+ }
+ RefPtr<Path> path = builder->Finish();
+
+ aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(aFillColor)),
+ StrokeOptions(2.0f * aDpi));
+}
+
+static void PaintMenulistArrowButton(nsIFrame* aFrame, DrawTarget* aDrawTarget,
+ const Rect& aRect,
+ const EventStates& aState, uint32_t aDpi) {
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ bool isPressed = !isDisabled && aState.HasAllStates(NS_EVENT_STATE_HOVER |
+ NS_EVENT_STATE_ACTIVE);
+ bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
+ bool isHTML = nsNativeTheme::IsHTMLContent(aFrame);
+
+ if (!isHTML && nsNativeTheme::CheckBooleanAttr(aFrame, nsGkAtoms::open)) {
+ isHovered = false;
+ }
+
+ const int32_t arrowSize = 8;
+ int32_t arrowPolygonX[] = {-4, -2, 0};
+ int32_t arrowPolygonY[] = {-1, 1, -1};
+ const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
+
+ PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
+ arrowSize,
+ isPressed ? sActiveColor
+ : isHovered ? sBorderHoverColor
+ : sBorderColor,
+ aDpi);
+}
+
+static void PaintSpinnerButton(DrawTarget* aDrawTarget, const Rect& aRect,
+ const EventStates& aState,
+ StyleAppearance aAppearance, uint32_t aDpi) {
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ bool isPressed = !isDisabled && aState.HasAllStates(NS_EVENT_STATE_HOVER |
+ NS_EVENT_STATE_ACTIVE);
+ bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
+
+ const int32_t arrowSize = 8;
+ int32_t arrowPolygonX[] = {0, 2, 4};
+ int32_t arrowPolygonY[] = {-3, -1, -3};
+ const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
+
+ if (aAppearance == StyleAppearance::SpinnerUpbutton) {
+ for (int32_t i = 0; i < arrowNumPoints; i++) {
+ arrowPolygonY[i] *= -1;
+ }
+ }
+
+ PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
+ arrowSize,
+ isPressed ? sActiveColor
+ : isHovered ? sBorderHoverColor
+ : sBorderColor,
+ aDpi);
+}
+
+static void PaintRangeInputBackground(DrawTarget* aDrawTarget,
+ const Rect& aRect,
+ const EventStates& aState, uint32_t aDpi,
+ bool aHorizontal) {
+ Rect rect(aRect);
+ const LayoutDeviceCoord kVerticalSize =
+ kMinimumAndroidWidgetSize * 0.25f * aDpi;
+
+ if (aHorizontal) {
+ rect.y += (rect.height - kVerticalSize) / 2;
+ rect.height = kVerticalSize;
+ } else {
+ rect.x += (rect.width - kVerticalSize) / 2;
+ rect.width = kVerticalSize;
+ }
+
+ aDrawTarget->FillRect(
+ rect, ColorPattern(ToDeviceColor(sRangeInputBackgroundColor)));
+ aDrawTarget->StrokeRect(rect,
+ ColorPattern(ToDeviceColor(sButtonActiveColor)));
+}
+
+static void PaintScrollbarthumbHorizontal(DrawTarget* aDrawTarget,
+ const Rect& aRect,
+ const EventStates& aState) {
+ sRGBColor thumbColor = sScrollbarThumbColor;
+ if (aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) {
+ thumbColor = sScrollbarThumbColorActive;
+ } else if (aState.HasState(NS_EVENT_STATE_HOVER)) {
+ thumbColor = sScrollbarThumbColorHover;
+ }
+ aDrawTarget->FillRect(aRect, ColorPattern(ToDeviceColor(thumbColor)));
+}
+
+static void PaintScrollbarthumbVertical(DrawTarget* aDrawTarget,
+ const Rect& aRect,
+ const EventStates& aState) {
+ sRGBColor thumbColor = sScrollbarThumbColor;
+ if (aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) {
+ thumbColor = sScrollbarThumbColorActive;
+ } else if (aState.HasState(NS_EVENT_STATE_HOVER)) {
+ thumbColor = sScrollbarThumbColorHover;
+ }
+ aDrawTarget->FillRect(aRect, ColorPattern(ToDeviceColor(thumbColor)));
+}
+
+static void PaintScrollbarHorizontal(DrawTarget* aDrawTarget,
+ const Rect& aRect) {
+ aDrawTarget->FillRect(aRect, ColorPattern(ToDeviceColor(sScrollbarColor)));
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ builder->MoveTo(Point(aRect.x, aRect.y));
+ builder->LineTo(Point(aRect.x + aRect.width, aRect.y));
+ RefPtr<Path> path = builder->Finish();
+ aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(sScrollbarBorderColor)));
+}
+
+static void PaintScrollbarVerticalAndCorner(DrawTarget* aDrawTarget,
+ const Rect& aRect, uint32_t aDpi) {
+ aDrawTarget->FillRect(aRect, ColorPattern(ToDeviceColor(sScrollbarColor)));
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ builder->MoveTo(Point(aRect.x, aRect.y));
+ builder->LineTo(Point(aRect.x, aRect.y + aRect.height));
+ RefPtr<Path> path = builder->Finish();
+ aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(sScrollbarBorderColor)),
+ StrokeOptions(1.0f * aDpi));
+}
+
+static void PaintScrollbarbutton(DrawTarget* aDrawTarget,
+ StyleAppearance aAppearance, const Rect& aRect,
+ const EventStates& aState, uint32_t aDpi) {
+ bool isActive =
+ aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
+ bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER);
+
+ aDrawTarget->FillRect(
+ aRect, ColorPattern(ToDeviceColor(isActive ? sScrollbarButtonActiveColor
+ : isHovered ? sScrollbarButtonHoverColor
+ : sScrollbarColor)));
+
+ // Start with Up arrow.
+ int32_t arrowPolygonX[] = {3, 0, -3};
+ int32_t arrowPolygonY[] = {2, -1, 2};
+ const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
+ const int32_t arrowSize = 14;
+
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarbuttonUp:
+ break;
+ case StyleAppearance::ScrollbarbuttonDown:
+ for (int32_t i = 0; i < arrowNumPoints; i++) {
+ arrowPolygonY[i] *= -1;
+ }
+ break;
+ case StyleAppearance::ScrollbarbuttonLeft:
+ for (int32_t i = 0; i < arrowNumPoints; i++) {
+ int32_t temp = arrowPolygonX[i];
+ arrowPolygonX[i] = arrowPolygonY[i];
+ arrowPolygonY[i] = temp;
+ }
+ break;
+ case StyleAppearance::ScrollbarbuttonRight:
+ for (int32_t i = 0; i < arrowNumPoints; i++) {
+ int32_t temp = arrowPolygonX[i];
+ arrowPolygonX[i] = arrowPolygonY[i] * -1;
+ arrowPolygonY[i] = temp;
+ }
+ break;
+ default:
+ return;
+ }
+
+ PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
+ arrowSize,
+ isActive ? sScrollbarArrowColorActive
+ : isHovered ? sScrollbarArrowColorHover
+ : sScrollbarArrowColor,
+ aDpi);
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ builder->MoveTo(Point(aRect.x, aRect.y));
+ if (aAppearance == StyleAppearance::ScrollbarbuttonUp ||
+ aAppearance == StyleAppearance::ScrollbarbuttonDown) {
+ builder->LineTo(Point(aRect.x, aRect.y + aRect.height));
+ } else {
+ builder->LineTo(Point(aRect.x + aRect.width, aRect.y));
+ }
+
+ RefPtr<Path> path = builder->Finish();
+ aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(sScrollbarBorderColor)),
+ StrokeOptions(1.0f * aDpi));
+}
+
+static void PaintButton(nsIFrame* aFrame, DrawTarget* aDrawTarget,
+ const Rect& aRect, const EventStates& aState,
+ uint32_t aDpi) {
+ const CSSCoord kRadius = 4.0f;
+
+ // FIXME: The DateTimeResetButton bit feels like a bit of a hack.
+ sRGBColor backgroundColor, borderColor;
+ std::tie(backgroundColor, borderColor) =
+ ComputeButtonColors(aState, IsDateTimeResetButton(aFrame));
+
+ PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor,
+ kButtonBorderWidth, kRadius, aDpi);
+}
+
+static void PaintRangeThumb(DrawTarget* aDrawTarget, const Rect& aRect,
+ const EventStates& aState, uint32_t aDpi) {
+ const CSSCoord kBorderWidth = 2.0f;
+
+ sRGBColor backgroundColor, borderColor;
+ std::tie(backgroundColor, borderColor) = ComputeButtonColors(aState);
+
+ PaintStrokedEllipse(aDrawTarget, aRect, backgroundColor, borderColor,
+ kBorderWidth, aDpi);
+}
+
+NS_IMETHODIMP
+nsNativeThemeAndroid::DrawWidgetBackground(gfxContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& /* aDirtyRect */) {
+ DrawTarget* dt = aContext->GetDrawTarget();
+ const nscoord twipsPerPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+
+ Rect devPxRect = NSRectToSnappedRect(aRect, twipsPerPixel, *dt);
+ AutoClipRect clip(*dt, devPxRect);
+
+ if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
+ bool isHTML = IsHTMLContent(aFrame);
+ nsIFrame* parentFrame = aFrame->GetParent();
+ bool isMenulist = !isHTML && parentFrame->IsMenuFrame();
+ // HTML select and XUL menulist dropdown buttons get state from the
+ // parent.
+ if (isHTML || isMenulist) {
+ aFrame = parentFrame;
+ eventState = GetContentState(parentFrame, aAppearance);
+ }
+ }
+
+ uint32_t dpi = GetDPIRatio(aFrame);
+
+ switch (aAppearance) {
+ case StyleAppearance::Radio: {
+ auto rect = FixAspectRatio(devPxRect);
+ PaintRadioControl(dt, rect, eventState, dpi);
+ if (IsSelected(aFrame)) {
+ PaintCheckedRadioButton(dt, rect, dpi);
+ }
+ break;
+ }
+ case StyleAppearance::Checkbox: {
+ auto rect = FixAspectRatio(devPxRect);
+ PaintCheckboxControl(dt, rect, eventState, dpi);
+ if (IsChecked(aFrame)) {
+ PaintCheckMark(dt, rect, eventState, dpi);
+ }
+ if (GetIndeterminate(aFrame)) {
+ PaintIndeterminateMark(dt, rect, eventState);
+ }
+ break;
+ }
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::NumberInput:
+ PaintTextField(dt, devPxRect, eventState, dpi);
+ break;
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ PaintMenulist(dt, devPxRect, eventState, dpi);
+ break;
+ case StyleAppearance::MozMenulistArrowButton:
+ PaintMenulistArrowButton(aFrame, dt, devPxRect, eventState, dpi);
+ break;
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ PaintSpinnerButton(dt, devPxRect, eventState, aAppearance, dpi);
+ break;
+ case StyleAppearance::Range:
+ PaintRangeInputBackground(dt, devPxRect, eventState, dpi,
+ IsRangeHorizontal(aFrame));
+ break;
+ case StyleAppearance::RangeThumb:
+ // TODO(emilio): Do we want to enforce it being a circle using
+ // FixAspectRatio here? For now let authors tweak, it's a custom pseudo so
+ // it doesn't probably have much compat impact if at all.
+ PaintRangeThumb(dt, devPxRect, eventState, dpi);
+ break;
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ PaintScrollbarthumbHorizontal(dt, devPxRect, eventState);
+ break;
+ case StyleAppearance::ScrollbarthumbVertical:
+ PaintScrollbarthumbVertical(dt, devPxRect, eventState);
+ break;
+ case StyleAppearance::ScrollbarHorizontal:
+ PaintScrollbarHorizontal(dt, devPxRect);
+ break;
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::Scrollcorner:
+ PaintScrollbarVerticalAndCorner(dt, devPxRect, dpi);
+ break;
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ PaintScrollbarbutton(dt, aAppearance, devPxRect, eventState, dpi);
+ break;
+ case StyleAppearance::Button:
+ PaintButton(aFrame, dt, devPxRect, eventState, dpi);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "Should not get here with a widget type we don't support.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return NS_OK;
+}
+
+/*bool
+nsNativeThemeAndroid::CreateWebRenderCommandsForWidget(mozilla::wr::DisplayListBuilder&
+aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const
+mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager*
+aManager, nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
+}*/
+
+LayoutDeviceIntMargin nsNativeThemeAndroid::GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
+ uint32_t dpi = GetDPIRatio(aFrame);
+ switch (aAppearance) {
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::NumberInput: {
+ const LayoutDeviceIntCoord w = kTextFieldBorderWidth * dpi;
+ return LayoutDeviceIntMargin(w, w, w, w);
+ }
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton: {
+ const LayoutDeviceIntCoord w = kMenulistBorderWidth * dpi;
+ return LayoutDeviceIntMargin(w, w, w, w);
+ }
+ case StyleAppearance::Button: {
+ const LayoutDeviceIntCoord w = kButtonBorderWidth * dpi;
+ return LayoutDeviceIntMargin(w, w, w, w);
+ }
+ default:
+ return LayoutDeviceIntMargin();
+ }
+}
+
+bool nsNativeThemeAndroid::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) {
+ uint32_t dpiRatio = GetDPIRatio(aFrame);
+ switch (aAppearance) {
+ // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
+ // and have a meaningful baseline, so they can't have
+ // author-specified padding.
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ aResult->SizeTo(0, 0, 0, 0);
+ return true;
+ case StyleAppearance::MozMenulistArrowButton:
+ aResult->SizeTo(1 * dpiRatio, 4 * dpiRatio, 1 * dpiRatio, 4 * dpiRatio);
+ return true;
+ default:
+ break;
+ }
+
+ // Respect author padding.
+ //
+ // TODO(emilio): Consider just unconditionally returning false, so that the
+ // default size of all elements matches other platforms and the UA stylesheet.
+ if (aFrame->PresContext()->HasAuthorSpecifiedRules(
+ aFrame, NS_AUTHOR_SPECIFIED_PADDING)) {
+ return false;
+ }
+
+ uint32_t dpi = GetDPIRatio(aFrame);
+ switch (aAppearance) {
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::NumberInput:
+ aResult->SizeTo(6 * dpi, 7 * dpi, 6 * dpi, 7 * dpi);
+ return true;
+ case StyleAppearance::Button:
+ aResult->SizeTo(6 * dpi, 21 * dpi, 6 * dpi, 21 * dpi);
+ return true;
+ case StyleAppearance::Textfield:
+ if (IsDateTimeTextField(aFrame)) {
+ aResult->SizeTo(7 * dpi, 7 * dpi, 5 * dpi, 7 * dpi);
+ return true;
+ }
+ aResult->SizeTo(6 * dpi, 7 * dpi, 6 * dpi, 7 * dpi);
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool nsNativeThemeAndroid::GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) {
+ // TODO(bug 1620360): This should return non-zero for
+ // StyleAppearance::FocusOutline, if we implement outline-style: auto.
+ return false;
+}
+
+NS_IMETHODIMP
+nsNativeThemeAndroid::GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) {
+ uint32_t dpiRatio = GetDPIRatio(aFrame);
+ aResult->width = aResult->height =
+ static_cast<uint32_t>(kMinimumAndroidWidgetSize) * dpiRatio;
+ *aIsOverridable = true;
+ if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
+ aResult->width =
+ static_cast<uint32_t>(kMinimumDropdownArrowButtonWidth) * dpiRatio;
+ }
+ return NS_OK;
+}
+
+nsITheme::Transparency nsNativeThemeAndroid::GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ return eUnknownTransparency;
+}
+
+NS_IMETHODIMP
+nsNativeThemeAndroid::WidgetStateChanged(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsAtom* aAttribute,
+ bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) {
+ 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
+nsNativeThemeAndroid::ThemeChanged() { return NS_OK; }
+
+bool nsNativeThemeAndroid::WidgetAppearanceDependsOnWindowFocus(
+ StyleAppearance) {
+ return false;
+}
+
+nsITheme::ThemeGeometryType nsNativeThemeAndroid::ThemeGeometryTypeForWidget(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ return eThemeGeometryTypeUnknown;
+}
+
+bool nsNativeThemeAndroid::ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ if (IsWidgetScrollbarPart(aAppearance)) {
+ const auto* style = nsLayoutUtils::StyleForScrollbar(aFrame);
+ // We don't currently handle custom scrollbars on
+ // nsNativeThemeAndroid. We could, potentially.
+ if (style->StyleUI()->HasCustomScrollbars() ||
+ style->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin) {
+ return false;
+ }
+ }
+
+ switch (aAppearance) {
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarNonDisappearing:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::Scrollcorner:
+ case StyleAppearance::Button:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
+ default:
+ return false;
+ }
+}
+
+bool nsNativeThemeAndroid::WidgetIsContainer(StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ return false;
+ default:
+ return true;
+ }
+}
+
+bool nsNativeThemeAndroid::ThemeDrawsFocusForWidget(
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Range:
+ // TODO(emilio): Checkbox / Radio don't have focus indicators when checked.
+ // If they did, we could just return true here unconditionally.
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio:
+ return false;
+ default:
+ return true;
+ }
+}
+
+bool nsNativeThemeAndroid::ThemeNeedsComboboxDropmarker() { return true; }
+
+already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() {
+ static StaticRefPtr<nsITheme> gInstance;
+ if (MOZ_UNLIKELY(!gInstance)) {
+ gInstance = new nsNativeThemeAndroid();
+ ClearOnShutdown(&gInstance);
+ }
+ return do_AddRef(gInstance);
+}
diff --git a/widget/android/nsNativeThemeAndroid.h b/widget/android/nsNativeThemeAndroid.h
new file mode 100644
index 0000000000..a450ae3085
--- /dev/null
+++ b/widget/android/nsNativeThemeAndroid.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/. */
+
+#ifndef nsNativeThemeAndroid_h
+#define nsNativeThemeAndroid_h
+
+#include "nsITheme.h"
+#include "nsNativeTheme.h"
+
+class nsNativeThemeAndroid : private nsNativeTheme, public nsITheme {
+ public:
+ nsNativeThemeAndroid() = default;
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // The nsITheme interface.
+ NS_IMETHOD DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) override;
+ /*bool CreateWebRenderCommandsForWidget(mozilla::wr::DisplayListBuilder&
+ aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const
+ mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager*
+ aManager, nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect&
+ aRect) override;*/
+ [[nodiscard]] LayoutDeviceIntMargin GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+ bool GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) override;
+ virtual bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) override;
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) override;
+ virtual Transparency GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) override;
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) override;
+ NS_IMETHOD ThemeChanged() override;
+ virtual bool WidgetAppearanceDependsOnWindowFocus(
+ StyleAppearance aAppearance) override;
+ /*virtual bool NeedToClearBackgroundBehindWidget(nsIFrame* aFrame,
+ StyleAppearance aAppearance)
+ override;*/
+ virtual ThemeGeometryType ThemeGeometryTypeForWidget(
+ nsIFrame* aFrame, StyleAppearance aAppearance) override;
+ bool ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+ bool WidgetIsContainer(StyleAppearance aAppearance) override;
+ bool ThemeDrawsFocusForWidget(StyleAppearance aAppearance) override;
+ bool ThemeNeedsComboboxDropmarker() override;
+
+ protected:
+ virtual ~nsNativeThemeAndroid() = default;
+};
+
+#endif
diff --git a/widget/android/nsPrintSettingsServiceAndroid.cpp b/widget/android/nsPrintSettingsServiceAndroid.cpp
new file mode 100644
index 0000000000..67f978c6e3
--- /dev/null
+++ b/widget/android/nsPrintSettingsServiceAndroid.cpp
@@ -0,0 +1,33 @@
+/* -*- 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 "nsPrintSettingsServiceAndroid.h"
+
+#include "nsPrintSettingsImpl.h"
+
+class nsPrintSettingsAndroid : public nsPrintSettings {
+ public:
+ nsPrintSettingsAndroid() {
+ // The aim here is to set up the objects enough that silent printing works
+ SetOutputFormat(nsIPrintSettings::kOutputFormatPDF);
+ SetPrinterName(u"PDF printer"_ns);
+ }
+};
+
+nsresult nsPrintSettingsServiceAndroid::_CreatePrintSettings(
+ nsIPrintSettings** _retval) {
+ nsPrintSettings* printSettings = new nsPrintSettingsAndroid();
+ NS_ENSURE_TRUE(printSettings, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*_retval = printSettings);
+ (void)InitPrintSettingsFromPrefs(*_retval, false,
+ nsIPrintSettings::kInitSaveAll);
+ return NS_OK;
+}
+
+already_AddRefed<nsIPrintSettings> CreatePlatformPrintSettings(
+ const mozilla::PrintSettingsInitializer& aSettings) {
+ RefPtr<nsPrintSettings> settings = new nsPrintSettingsAndroid();
+ settings->InitWithInitializer(aSettings);
+ return settings.forget();
+}
diff --git a/widget/android/nsPrintSettingsServiceAndroid.h b/widget/android/nsPrintSettingsServiceAndroid.h
new file mode 100644
index 0000000000..28710feb54
--- /dev/null
+++ b/widget/android/nsPrintSettingsServiceAndroid.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsPrintSettingsServiceAndroid_h
+#define nsPrintSettingsServiceAndroid_h
+
+#include "nsPrintSettingsService.h"
+#include "nsIPrintSettings.h"
+
+class nsPrintSettingsServiceAndroid final : public nsPrintSettingsService {
+ public:
+ nsPrintSettingsServiceAndroid() {}
+
+ nsresult _CreatePrintSettings(nsIPrintSettings** _retval) override;
+};
+
+#endif // nsPrintSettingsServiceAndroid_h
diff --git a/widget/android/nsUserIdleServiceAndroid.cpp b/widget/android/nsUserIdleServiceAndroid.cpp
new file mode 100644
index 0000000000..15f9d7e132
--- /dev/null
+++ b/widget/android/nsUserIdleServiceAndroid.cpp
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsUserIdleServiceAndroid.h"
+
+bool nsUserIdleServiceAndroid::PollIdleTime(uint32_t* aIdleTime) {
+ return false;
+}
+
+bool nsUserIdleServiceAndroid::UsePollMode() { return false; }
diff --git a/widget/android/nsUserIdleServiceAndroid.h b/widget/android/nsUserIdleServiceAndroid.h
new file mode 100644
index 0000000000..2169673088
--- /dev/null
+++ b/widget/android/nsUserIdleServiceAndroid.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsUserIdleServiceAndroid_h__
+#define nsUserIdleServiceAndroid_h__
+
+#include "nsUserIdleService.h"
+
+class nsUserIdleServiceAndroid : public nsUserIdleService {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsUserIdleServiceAndroid,
+ nsUserIdleService)
+
+ bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsUserIdleServiceAndroid> GetInstance() {
+ RefPtr<nsUserIdleService> idleService = nsUserIdleService::GetInstance();
+ if (!idleService) {
+ idleService = new nsUserIdleServiceAndroid();
+ }
+
+ return idleService.forget().downcast<nsUserIdleServiceAndroid>();
+ }
+
+ protected:
+ nsUserIdleServiceAndroid() {}
+ virtual ~nsUserIdleServiceAndroid() {}
+ bool UsePollMode() override;
+};
+
+#endif // nsUserIdleServiceAndroid_h__
diff --git a/widget/android/nsWidgetFactory.cpp b/widget/android/nsWidgetFactory.cpp
new file mode 100644
index 0000000000..00ebd20dd9
--- /dev/null
+++ b/widget/android/nsWidgetFactory.cpp
@@ -0,0 +1,21 @@
+/* -*- 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 "mozilla/WidgetUtils.h"
+
+#include "nsAppShell.h"
+
+#include "nsLookAndFeel.h"
+#include "nsAppShellSingleton.h"
+
+nsresult nsWidgetAndroidModuleCtor() { return nsAppShellInit(); }
+
+void nsWidgetAndroidModuleDtor() {
+ // Shutdown all XP level widget classes.
+ mozilla::widget::WidgetUtils::Shutdown();
+
+ nsLookAndFeel::Shutdown();
+ nsAppShellShutdown();
+}
diff --git a/widget/android/nsWidgetFactory.h b/widget/android/nsWidgetFactory.h
new file mode 100644
index 0000000000..57b96c1b3b
--- /dev/null
+++ b/widget/android/nsWidgetFactory.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 widget_android_nsWidgetFactory_h
+#define widget_android_nsWidgetFactory_h
+
+#include "nscore.h"
+#include "nsID.h"
+
+class nsISupports;
+
+nsresult nsAppShellConstructor(nsISupports* outer, const nsIID& iid,
+ void** result);
+
+nsresult nsWidgetAndroidModuleCtor();
+void nsWidgetAndroidModuleDtor();
+
+#endif // defined widget_android_nsWidgetFactory_h
diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp
new file mode 100644
index 0000000000..479ce25ef0
--- /dev/null
+++ b/widget/android/nsWindow.cpp
@@ -0,0 +1,2619 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 4; indent-tabs-mode: nil; -*-
+ * vim: set sw=2 ts=4 expandtab:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <android/log.h>
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <math.h>
+#include <queue>
+#include <type_traits>
+#include <unistd.h>
+
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_android.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/WheelHandlingHelper.h" // for WheelDeltaAdjustmentStrategy
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "mozilla/a11y/SessionAccessibility.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/layers/RenderTrace.h"
+#include "mozilla/widget/AndroidVsync.h"
+#include <algorithm>
+
+using mozilla::Unused;
+using mozilla::dom::ContentChild;
+using mozilla::dom::ContentParent;
+using mozilla::gfx::DataSourceSurface;
+using mozilla::gfx::IntSize;
+using mozilla::gfx::Matrix;
+using mozilla::gfx::SurfaceFormat;
+
+#include "nsWindow.h"
+
+#include "AndroidGraphics.h"
+#include "JavaExceptions.h"
+
+#include "nsIWidgetListener.h"
+#include "nsIWindowWatcher.h"
+#include "nsIAppWindow.h"
+
+#include "nsAppShell.h"
+#include "nsFocusManager.h"
+#include "nsUserIdleService.h"
+#include "nsLayoutUtils.h"
+#include "nsViewManager.h"
+
+#include "WidgetUtils.h"
+#include "nsContentUtils.h"
+
+#include "nsGfxCIID.h"
+#include "nsGkAtoms.h"
+#include "nsWidgetsCID.h"
+
+#include "gfxContext.h"
+
+#include "AndroidContentController.h"
+#include "GLContext.h"
+#include "GLContextProvider.h"
+#include "Layers.h"
+#include "ScopedGLHelpers.h"
+#include "mozilla/layers/APZEventState.h"
+#include "mozilla/layers/APZInputBridge.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/AsyncCompositionManager.h"
+#include "mozilla/layers/CompositorOGL.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "mozilla/layers/LayerManagerComposite.h"
+
+#include "nsTArray.h"
+
+#include "AndroidBridge.h"
+#include "AndroidBridgeUtilities.h"
+#include "AndroidUiThread.h"
+#include "AndroidView.h"
+#include "GeckoEditableSupport.h"
+#include "GeckoViewSupport.h"
+#include "KeyEvent.h"
+#include "MotionEvent.h"
+#include "mozilla/java/EventDispatcherWrappers.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+#include "mozilla/java/GeckoEditableChildWrappers.h"
+#include "mozilla/java/GeckoResultWrappers.h"
+#include "mozilla/java/GeckoSessionNatives.h"
+#include "mozilla/java/GeckoSystemStateListenerWrappers.h"
+#include "mozilla/java/PanZoomControllerNatives.h"
+#include "mozilla/java/SessionAccessibilityWrappers.h"
+#include "ScreenHelperAndroid.h"
+#include "TouchResampler.h"
+
+#include "GeckoProfiler.h" // For AUTO_PROFILER_LABEL
+#include "nsPrintfCString.h"
+#include "nsString.h"
+
+#include "JavaBuiltins.h"
+
+#include "mozilla/ipc/Shmem.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+using namespace mozilla::ipc;
+
+using mozilla::java::GeckoSession;
+
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/CompositorSession.h"
+#include "mozilla/layers/LayerTransactionParent.h"
+#include "mozilla/layers/UiCompositorControllerChild.h"
+#include "nsThreadUtils.h"
+
+// All the toplevel windows that have been created; these are in
+// stacking order, so the window at gTopLevelWindows[0] is the topmost
+// one.
+static nsTArray<nsWindow*> gTopLevelWindows;
+
+static bool sFailedToCreateGLContext = false;
+
+// Multitouch swipe thresholds in inches
+static const double SWIPE_MAX_PINCH_DELTA_INCHES = 0.4;
+static const double SWIPE_MIN_DISTANCE_INCHES = 0.6;
+
+static const double kTouchResampleVsyncAdjustMs = 5.0;
+
+static const int32_t INPUT_RESULT_UNHANDLED =
+ java::PanZoomController::INPUT_RESULT_UNHANDLED;
+static const int32_t INPUT_RESULT_HANDLED =
+ java::PanZoomController::INPUT_RESULT_HANDLED;
+static const int32_t INPUT_RESULT_HANDLED_CONTENT =
+ java::PanZoomController::INPUT_RESULT_HANDLED_CONTENT;
+static const int32_t INPUT_RESULT_IGNORED =
+ java::PanZoomController::INPUT_RESULT_IGNORED;
+
+namespace {
+template <class Instance, class Impl>
+std::enable_if_t<jni::detail::NativePtrPicker<Impl>::value ==
+ jni::detail::NativePtrType::REFPTR,
+ void>
+CallAttachNative(Instance aInstance, Impl* aImpl) {
+ Impl::AttachNative(aInstance, RefPtr<Impl>(aImpl).get());
+}
+
+template <class Instance, class Impl>
+std::enable_if_t<jni::detail::NativePtrPicker<Impl>::value ==
+ jni::detail::NativePtrType::OWNING,
+ void>
+CallAttachNative(Instance aInstance, Impl* aImpl) {
+ Impl::AttachNative(aInstance, UniquePtr<Impl>(aImpl));
+}
+
+template <class Lambda>
+bool DispatchToUiThread(const char* aName, Lambda&& aLambda) {
+ if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
+ uiThread->Dispatch(NS_NewRunnableFunction(aName, std::move(aLambda)));
+ return true;
+ }
+ return false;
+}
+} // namespace
+
+namespace mozilla {
+namespace widget {
+
+using WindowPtr = jni::NativeWeakPtr<GeckoViewSupport>;
+
+/**
+ * PanZoomController handles its native calls on the UI thread, so make
+ * it separate from GeckoViewSupport.
+ */
+class NPZCSupport final
+ : public java::PanZoomController::NativeProvider::Natives<NPZCSupport>,
+ public AndroidVsync::Observer {
+ WindowPtr mWindow;
+ java::PanZoomController::NativeProvider::WeakRef mNPZC;
+
+ // Stores the returnResult of each pending motion event between
+ // HandleMotionEvent and FinishHandlingMotionEvent.
+ std::queue<std::pair<uint64_t, java::GeckoResult::GlobalRef>>
+ mPendingMotionEventReturnResults;
+
+ RefPtr<AndroidVsync> mAndroidVsync;
+ TouchResampler mTouchResampler;
+ int mPreviousButtons = 0;
+ bool mListeningToVsync = false;
+
+ // Only true if mAndroidVsync is non-null and the resampling pref is set.
+ bool mTouchResamplingEnabled = false;
+
+ template <typename Lambda>
+ class InputEvent final : public nsAppShell::Event {
+ java::PanZoomController::NativeProvider::GlobalRef mNPZC;
+ Lambda mLambda;
+
+ public:
+ InputEvent(const NPZCSupport* aNPZCSupport, Lambda&& aLambda)
+ : mNPZC(aNPZCSupport->mNPZC), mLambda(std::move(aLambda)) {}
+
+ void Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ const auto npzcSupportWeak = GetNative(
+ java::PanZoomController::NativeProvider::LocalRef(env, mNPZC));
+ if (!npzcSupportWeak) {
+ // We already shut down.
+ env->ExceptionClear();
+ return;
+ }
+
+ auto acc = npzcSupportWeak->Access();
+ if (!acc) {
+ // We already shut down.
+ env->ExceptionClear();
+ return;
+ }
+
+ auto win = acc->mWindow.Access();
+ if (!win) {
+ // We already shut down.
+ env->ExceptionClear();
+ return;
+ }
+
+ nsWindow* const window = win->GetNsWindow();
+ if (!window) {
+ // We already shut down.
+ env->ExceptionClear();
+ return;
+ }
+
+ window->UserActivity();
+ return mLambda(window);
+ }
+
+ bool IsUIEvent() const override { return true; }
+ };
+
+ template <typename Lambda>
+ void PostInputEvent(Lambda&& aLambda) {
+ // Use priority queue for input events.
+ nsAppShell::PostEvent(
+ MakeUnique<InputEvent<Lambda>>(this, std::move(aLambda)));
+ }
+
+ public:
+ typedef java::PanZoomController::NativeProvider::Natives<NPZCSupport> Base;
+
+ NPZCSupport(WindowPtr aWindow,
+ const java::PanZoomController::NativeProvider::LocalRef& aNPZC)
+ : mWindow(aWindow), mNPZC(aNPZC) {
+#if defined(DEBUG)
+ auto win(mWindow.Access());
+ MOZ_ASSERT(!!win);
+#endif // defined(DEBUG)
+
+ // Use vsync for touch resampling on API level 19 and above.
+ // See gfxAndroidPlatform::CreateHardwareVsyncSource() for comparison.
+ if (AndroidBridge::Bridge() &&
+ AndroidBridge::Bridge()->GetAPIVersion() >= 19) {
+ mAndroidVsync = AndroidVsync::GetInstance();
+ }
+ }
+
+ ~NPZCSupport() {
+ if (mListeningToVsync) {
+ MOZ_RELEASE_ASSERT(mAndroidVsync);
+ mAndroidVsync->UnregisterObserver(this, AndroidVsync::INPUT);
+ mListeningToVsync = false;
+ }
+ }
+
+ using Base::AttachNative;
+ using Base::DisposeNative;
+
+ void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer) {
+ RefPtr<Runnable> disposer = aDisposer;
+ // There are several considerations when shutting down NPZC. 1) The
+ // Gecko thread may destroy NPZC at any time when nsWindow closes. 2)
+ // There may be pending events on the Gecko thread when NPZC is
+ // destroyed. 3) mWindow may not be available when the pending event
+ // runs. 4) The UI thread may destroy NPZC at any time when GeckoView
+ // is destroyed. 5) The UI thread may destroy NPZC at the same time as
+ // Gecko thread trying to destroy NPZC. 6) There may be pending calls
+ // on the UI thread when NPZC is destroyed. 7) mWindow may have been
+ // cleared on the Gecko thread when the pending call happens on the UI
+ // thread.
+ //
+ // 1) happens through OnWeakNonIntrusiveDetach, which first notifies the UI
+ // thread through Destroy; Destroy then calls DisposeNative, which
+ // finally disposes the native instance back on the Gecko thread. Using
+ // Destroy to indirectly call DisposeNative here also solves 5), by
+ // making everything go through the UI thread, avoiding contention.
+ //
+ // 2) and 3) are solved by clearing mWindow, which signals to the
+ // pending event that we had shut down. In that case the event bails
+ // and does not touch mWindow.
+ //
+ // 4) happens through DisposeNative directly.
+ //
+ // 6) is solved by keeping a destroyed flag in the Java NPZC instance,
+ // and only make a pending call if the destroyed flag is not set.
+ //
+ // 7) is solved by taking a lock whenever mWindow is modified on the
+ // Gecko thread or accessed on the UI thread. That way, we don't
+ // release mWindow until the UI thread is done using it, thus avoiding
+ // the race condition.
+
+ if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
+ auto npzc = java::PanZoomController::NativeProvider::GlobalRef(mNPZC);
+ if (!npzc) {
+ return;
+ }
+
+ uiThread->Dispatch(
+ NS_NewRunnableFunction("NPZCSupport::OnWeakNonIntrusiveDetach",
+ [npzc, disposer = std::move(disposer)] {
+ npzc->SetAttached(false);
+ disposer->Run();
+ }));
+ }
+ }
+
+ const java::PanZoomController::NativeProvider::Ref& GetJavaNPZC() const {
+ return mNPZC;
+ }
+
+ public:
+ void SetIsLongpressEnabled(bool aIsLongpressEnabled) {
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (auto window = mWindow.Access()) {
+ nsWindow* gkWindow = window->GetNsWindow();
+ if (gkWindow) {
+ controller = gkWindow->mAPZC;
+ }
+ }
+
+ if (controller) {
+ controller->SetLongTapEnabled(aIsLongpressEnabled);
+ }
+ }
+
+ int32_t HandleScrollEvent(int64_t aTime, int32_t aMetaState, float aX,
+ float aY, float aHScroll, float aVScroll) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (auto window = mWindow.Access()) {
+ nsWindow* gkWindow = window->GetNsWindow();
+ if (gkWindow) {
+ controller = gkWindow->mAPZC;
+ }
+ }
+
+ if (!controller) {
+ return INPUT_RESULT_UNHANDLED;
+ }
+
+ ScreenPoint origin = ScreenPoint(aX, aY);
+
+ if (StaticPrefs::ui_scrolling_negate_wheel_scroll()) {
+ aHScroll = -aHScroll;
+ aVScroll = -aVScroll;
+ }
+
+ ScrollWheelInput input(
+ aTime, nsWindow::GetEventTimeStamp(aTime),
+ nsWindow::GetModifiers(aMetaState), ScrollWheelInput::SCROLLMODE_SMOOTH,
+ ScrollWheelInput::SCROLLDELTA_PIXEL, origin, aHScroll, aVScroll, false,
+ // XXX Do we need to support auto-dir scrolling
+ // for Android widgets with a wheel device?
+ // Currently, I just leave it unimplemented. If
+ // we need to implement it, what's the extra work
+ // to do?
+ WheelDeltaAdjustmentStrategy::eNone);
+
+ APZEventResult result = controller->InputBridge()->ReceiveInputEvent(input);
+ if (result.mStatus == nsEventStatus_eConsumeNoDefault) {
+ return INPUT_RESULT_IGNORED;
+ }
+
+ PostInputEvent([input, result](nsWindow* window) {
+ WidgetWheelEvent wheelEvent = input.ToWidgetEvent(window);
+ window->ProcessUntransformedAPZEvent(&wheelEvent, result);
+ });
+
+ switch (result.mStatus) {
+ case nsEventStatus_eIgnore:
+ return INPUT_RESULT_UNHANDLED;
+ case nsEventStatus_eConsumeDoDefault:
+ return (result.mHandledResult == Some(APZHandledResult::HandledByRoot))
+ ? INPUT_RESULT_HANDLED
+ : INPUT_RESULT_HANDLED_CONTENT;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected nsEventStatus");
+ return INPUT_RESULT_UNHANDLED;
+ }
+ }
+
+ private:
+ static MouseInput::ButtonType GetButtonType(int button) {
+ MouseInput::ButtonType result = MouseInput::NONE;
+
+ switch (button) {
+ case java::sdk::MotionEvent::BUTTON_PRIMARY:
+ result = MouseInput::PRIMARY_BUTTON;
+ break;
+ case java::sdk::MotionEvent::BUTTON_SECONDARY:
+ result = MouseInput::SECONDARY_BUTTON;
+ break;
+ case java::sdk::MotionEvent::BUTTON_TERTIARY:
+ result = MouseInput::MIDDLE_BUTTON;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ static int16_t ConvertButtons(int buttons) {
+ int16_t result = 0;
+
+ if (buttons & java::sdk::MotionEvent::BUTTON_PRIMARY) {
+ result |= MouseButtonsFlag::ePrimaryFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_SECONDARY) {
+ result |= MouseButtonsFlag::eSecondaryFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_TERTIARY) {
+ result |= MouseButtonsFlag::eMiddleFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_BACK) {
+ result |= MouseButtonsFlag::e4thFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_FORWARD) {
+ result |= MouseButtonsFlag::e5thFlag;
+ }
+
+ return result;
+ }
+
+ static int32_t ConvertAPZHandledResult(APZHandledResult aHandledResult) {
+ switch (aHandledResult) {
+ case APZHandledResult::Unhandled:
+ return INPUT_RESULT_UNHANDLED;
+ case APZHandledResult::HandledByRoot:
+ return INPUT_RESULT_HANDLED;
+ case APZHandledResult::HandledByContent:
+ return INPUT_RESULT_HANDLED_CONTENT;
+ case APZHandledResult::Invalid:
+ MOZ_ASSERT_UNREACHABLE("The handled result should NOT be Invalid");
+ return INPUT_RESULT_UNHANDLED;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown handled result");
+ return INPUT_RESULT_UNHANDLED;
+ }
+
+ public:
+ int32_t HandleMouseEvent(int32_t aAction, int64_t aTime, int32_t aMetaState,
+ float aX, float aY, int buttons) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (auto window = mWindow.Access()) {
+ nsWindow* gkWindow = window->GetNsWindow();
+ if (gkWindow) {
+ controller = gkWindow->mAPZC;
+ }
+ }
+
+ if (!controller) {
+ return INPUT_RESULT_UNHANDLED;
+ }
+
+ MouseInput::MouseType mouseType = MouseInput::MOUSE_NONE;
+ MouseInput::ButtonType buttonType = MouseInput::NONE;
+ switch (aAction) {
+ case java::sdk::MotionEvent::ACTION_DOWN:
+ mouseType = MouseInput::MOUSE_DOWN;
+ buttonType = GetButtonType(buttons ^ mPreviousButtons);
+ mPreviousButtons = buttons;
+ break;
+ case java::sdk::MotionEvent::ACTION_UP:
+ mouseType = MouseInput::MOUSE_UP;
+ buttonType = GetButtonType(buttons ^ mPreviousButtons);
+ mPreviousButtons = buttons;
+ break;
+ case java::sdk::MotionEvent::ACTION_MOVE:
+ mouseType = MouseInput::MOUSE_MOVE;
+ break;
+ case java::sdk::MotionEvent::ACTION_HOVER_MOVE:
+ mouseType = MouseInput::MOUSE_MOVE;
+ break;
+ case java::sdk::MotionEvent::ACTION_HOVER_ENTER:
+ mouseType = MouseInput::MOUSE_WIDGET_ENTER;
+ break;
+ case java::sdk::MotionEvent::ACTION_HOVER_EXIT:
+ mouseType = MouseInput::MOUSE_WIDGET_EXIT;
+ break;
+ default:
+ break;
+ }
+
+ if (mouseType == MouseInput::MOUSE_NONE) {
+ return INPUT_RESULT_UNHANDLED;
+ }
+
+ ScreenPoint origin = ScreenPoint(aX, aY);
+
+ MouseInput input(
+ mouseType, buttonType, MouseEvent_Binding::MOZ_SOURCE_MOUSE,
+ ConvertButtons(buttons), origin, aTime,
+ nsWindow::GetEventTimeStamp(aTime), nsWindow::GetModifiers(aMetaState));
+
+ APZEventResult result = controller->InputBridge()->ReceiveInputEvent(input);
+ if (result.mStatus == nsEventStatus_eConsumeNoDefault) {
+ return INPUT_RESULT_IGNORED;
+ }
+
+ PostInputEvent([input, result](nsWindow* window) {
+ WidgetMouseEvent mouseEvent = input.ToWidgetEvent(window);
+ window->ProcessUntransformedAPZEvent(&mouseEvent, result);
+ });
+
+ switch (result.mStatus) {
+ case nsEventStatus_eIgnore:
+ return INPUT_RESULT_UNHANDLED;
+ case nsEventStatus_eConsumeDoDefault:
+ return (result.mHandledResult == Some(APZHandledResult::HandledByRoot))
+ ? INPUT_RESULT_HANDLED
+ : INPUT_RESULT_HANDLED_CONTENT;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected nsEventStatus");
+ return INPUT_RESULT_UNHANDLED;
+ }
+ }
+
+ // Convert MotionEvent touch radius and orientation into the format required
+ // by w3c touchevents.
+ // toolMajor and toolMinor span a rectangle that's oriented as per
+ // aOrientation, centered around the touch point.
+ static std::pair<float, ScreenSize> ConvertOrientationAndRadius(
+ float aOrientation, float aToolMajor, float aToolMinor) {
+ float angle = aOrientation * 180.0f / M_PI;
+ // w3c touchevents spec does not allow orientations == 90
+ // this shifts it to -90, which will be shifted to zero below
+ if (angle >= 90.0) {
+ angle -= 180.0f;
+ }
+
+ // w3c touchevent radii are given with an orientation between 0 and
+ // 90. The radii are found by removing the orientation and
+ // measuring the x and y radii of the resulting ellipse. For
+ // Android orientations >= 0 and < 90, use the y radius as the
+ // major radius, and x as the minor radius. However, for an
+ // orientation < 0, we have to shift the orientation by adding 90,
+ // and reverse which radius is major and minor.
+ ScreenSize radius;
+ if (angle < 0.0f) {
+ angle += 90.0f;
+ radius =
+ ScreenSize(int32_t(aToolMajor / 2.0f), int32_t(aToolMinor / 2.0f));
+ } else {
+ radius =
+ ScreenSize(int32_t(aToolMinor / 2.0f), int32_t(aToolMajor / 2.0f));
+ }
+
+ return std::make_pair(angle, radius);
+ }
+
+ void HandleMotionEvent(
+ const java::PanZoomController::NativeProvider::LocalRef& aInstance,
+ jni::Object::Param aEventData, float aScreenX, float aScreenY,
+ jni::Object::Param aResult) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ auto returnResult = java::GeckoResult::Ref::From(aResult);
+ auto eventData =
+ java::PanZoomController::MotionEventData::Ref::From(aEventData);
+ nsTArray<int32_t> pointerId(eventData->PointerId()->GetElements());
+ size_t pointerCount = pointerId.Length();
+ MultiTouchInput::MultiTouchType type;
+ size_t startIndex = 0;
+ size_t endIndex = pointerCount;
+
+ switch (eventData->Action()) {
+ case java::sdk::MotionEvent::ACTION_DOWN:
+ case java::sdk::MotionEvent::ACTION_POINTER_DOWN:
+ type = MultiTouchInput::MULTITOUCH_START;
+ break;
+ case java::sdk::MotionEvent::ACTION_MOVE:
+ type = MultiTouchInput::MULTITOUCH_MOVE;
+ break;
+ case java::sdk::MotionEvent::ACTION_UP:
+ case java::sdk::MotionEvent::ACTION_POINTER_UP:
+ // for pointer-up events we only want the data from
+ // the one pointer that went up
+ type = MultiTouchInput::MULTITOUCH_END;
+ startIndex = eventData->ActionIndex();
+ endIndex = startIndex + 1;
+ break;
+ case java::sdk::MotionEvent::ACTION_OUTSIDE:
+ case java::sdk::MotionEvent::ACTION_CANCEL:
+ type = MultiTouchInput::MULTITOUCH_CANCEL;
+ break;
+ default:
+ if (returnResult) {
+ returnResult->Complete(
+ java::sdk::Integer::ValueOf(INPUT_RESULT_UNHANDLED));
+ }
+ return;
+ }
+
+ MultiTouchInput input(type, eventData->Time(),
+ nsWindow::GetEventTimeStamp(eventData->Time()), 0);
+ input.modifiers = nsWindow::GetModifiers(eventData->MetaState());
+ input.mTouches.SetCapacity(endIndex - startIndex);
+ input.mScreenOffset =
+ ExternalIntPoint(int32_t(floorf(aScreenX)), int32_t(floorf(aScreenY)));
+
+ size_t historySize = eventData->HistorySize();
+ nsTArray<int64_t> historicalTime(
+ eventData->HistoricalTime()->GetElements());
+ MOZ_RELEASE_ASSERT(historicalTime.Length() == historySize);
+
+ // Each of these is |historySize| sets of |pointerCount| values.
+ size_t historicalDataCount = historySize * pointerCount;
+ nsTArray<float> historicalX(eventData->HistoricalX()->GetElements());
+ nsTArray<float> historicalY(eventData->HistoricalY()->GetElements());
+ nsTArray<float> historicalOrientation(
+ eventData->HistoricalOrientation()->GetElements());
+ nsTArray<float> historicalPressure(
+ eventData->HistoricalPressure()->GetElements());
+ nsTArray<float> historicalToolMajor(
+ eventData->HistoricalToolMajor()->GetElements());
+ nsTArray<float> historicalToolMinor(
+ eventData->HistoricalToolMinor()->GetElements());
+
+ MOZ_RELEASE_ASSERT(historicalX.Length() == historicalDataCount);
+ MOZ_RELEASE_ASSERT(historicalY.Length() == historicalDataCount);
+ MOZ_RELEASE_ASSERT(historicalOrientation.Length() == historicalDataCount);
+ MOZ_RELEASE_ASSERT(historicalPressure.Length() == historicalDataCount);
+ MOZ_RELEASE_ASSERT(historicalToolMajor.Length() == historicalDataCount);
+ MOZ_RELEASE_ASSERT(historicalToolMinor.Length() == historicalDataCount);
+
+ // Each of these is |pointerCount| values.
+ nsTArray<float> x(eventData->X()->GetElements());
+ nsTArray<float> y(eventData->Y()->GetElements());
+ nsTArray<float> orientation(eventData->Orientation()->GetElements());
+ nsTArray<float> pressure(eventData->Pressure()->GetElements());
+ nsTArray<float> toolMajor(eventData->ToolMajor()->GetElements());
+ nsTArray<float> toolMinor(eventData->ToolMinor()->GetElements());
+
+ MOZ_ASSERT(x.Length() == pointerCount);
+ MOZ_ASSERT(y.Length() == pointerCount);
+ MOZ_ASSERT(orientation.Length() == pointerCount);
+ MOZ_ASSERT(pressure.Length() == pointerCount);
+ MOZ_ASSERT(toolMajor.Length() == pointerCount);
+ MOZ_ASSERT(toolMinor.Length() == pointerCount);
+
+ for (size_t i = startIndex; i < endIndex; i++) {
+ float orien;
+ ScreenSize radius;
+ std::tie(orien, radius) = ConvertOrientationAndRadius(
+ orientation[i], toolMajor[i], toolMinor[i]);
+
+ ScreenIntPoint point(int32_t(floorf(x[i])), int32_t(floorf(y[i])));
+ SingleTouchData singleTouchData(pointerId[i], point, radius, orien,
+ pressure[i]);
+
+ for (size_t historyIndex = 0; historyIndex < historySize;
+ historyIndex++) {
+ size_t historicalI = historyIndex * pointerCount + i;
+ float historicalAngle;
+ ScreenSize historicalRadius;
+ std::tie(historicalAngle, historicalRadius) =
+ ConvertOrientationAndRadius(historicalOrientation[historicalI],
+ historicalToolMajor[historicalI],
+ historicalToolMinor[historicalI]);
+ ScreenIntPoint historicalPoint(
+ int32_t(floorf(historicalX[historicalI])),
+ int32_t(floorf(historicalY[historicalI])));
+ singleTouchData.mHistoricalData.AppendElement(
+ SingleTouchData::HistoricalTouchData{
+ nsWindow::GetEventTimeStamp(historicalTime[historyIndex]),
+ historicalPoint,
+ {}, // mLocalScreenPoint will be computed later by APZ
+ historicalRadius,
+ historicalAngle,
+ historicalPressure[historicalI]});
+ }
+
+ input.mTouches.AppendElement(singleTouchData);
+ }
+
+ if (mAndroidVsync &&
+ eventData->Action() == java::sdk::MotionEvent::ACTION_DOWN) {
+ // Query pref value at the beginning of a touch gesture so that we don't
+ // leave events stuck in the resampler after a pref flip.
+ mTouchResamplingEnabled = StaticPrefs::android_touch_resampling_enabled();
+ }
+
+ if (!mTouchResamplingEnabled) {
+ FinishHandlingMotionEvent(std::move(input),
+ java::GeckoResult::LocalRef(returnResult));
+ return;
+ }
+
+ uint64_t eventId = mTouchResampler.ProcessEvent(std::move(input));
+ mPendingMotionEventReturnResults.push(
+ {eventId, java::GeckoResult::GlobalRef(returnResult)});
+
+ RegisterOrUnregisterForVsync(mTouchResampler.InTouchingState());
+ ConsumeMotionEventsFromResampler();
+ }
+
+ void RegisterOrUnregisterForVsync(bool aNeedVsync) {
+ MOZ_RELEASE_ASSERT(mAndroidVsync);
+ if (aNeedVsync && !mListeningToVsync) {
+ mAndroidVsync->RegisterObserver(this, AndroidVsync::INPUT);
+ } else if (!aNeedVsync && mListeningToVsync) {
+ mAndroidVsync->UnregisterObserver(this, AndroidVsync::INPUT);
+ }
+ mListeningToVsync = aNeedVsync;
+ }
+
+ void OnVsync(const TimeStamp& aTimeStamp) override {
+ mTouchResampler.NotifyFrame(aTimeStamp - TimeDuration::FromMilliseconds(
+ kTouchResampleVsyncAdjustMs));
+ ConsumeMotionEventsFromResampler();
+ }
+
+ void ConsumeMotionEventsFromResampler() {
+ auto outgoing = mTouchResampler.ConsumeOutgoingEvents();
+ while (!outgoing.empty()) {
+ auto outgoingEvent = std::move(outgoing.front());
+ outgoing.pop();
+ java::GeckoResult::GlobalRef returnResult;
+ if (outgoingEvent.mEventId) {
+ // Look up the GeckoResult for this event.
+ // The outgoing events from the resampler are in the same order as the
+ // original events, and no event IDs are skipped.
+ MOZ_RELEASE_ASSERT(!mPendingMotionEventReturnResults.empty());
+ auto pair = mPendingMotionEventReturnResults.front();
+ mPendingMotionEventReturnResults.pop();
+ MOZ_RELEASE_ASSERT(pair.first == *outgoingEvent.mEventId);
+ returnResult = pair.second;
+ }
+ FinishHandlingMotionEvent(std::move(outgoingEvent.mEvent),
+ java::GeckoResult::LocalRef(returnResult));
+ }
+ }
+
+ void FinishHandlingMotionEvent(MultiTouchInput&& aInput,
+ java::GeckoResult::LocalRef&& aReturnResult) {
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (auto window = mWindow.Access()) {
+ nsWindow* gkWindow = window->GetNsWindow();
+ if (gkWindow) {
+ controller = gkWindow->mAPZC;
+ }
+ }
+
+ if (!controller) {
+ if (aReturnResult) {
+ aReturnResult->Complete(
+ java::sdk::Integer::ValueOf(INPUT_RESULT_UNHANDLED));
+ }
+ return;
+ }
+
+ APZEventResult result =
+ controller->InputBridge()->ReceiveInputEvent(aInput);
+ if (result.mStatus == nsEventStatus_eConsumeNoDefault) {
+ if (aReturnResult) {
+ aReturnResult->Complete(
+ java::sdk::Integer::ValueOf(INPUT_RESULT_IGNORED));
+ }
+ return;
+ }
+
+ // Dispatch APZ input event on Gecko thread.
+ PostInputEvent([aInput, result](nsWindow* window) {
+ WidgetTouchEvent touchEvent = aInput.ToWidgetTouchEvent(window);
+ window->ProcessUntransformedAPZEvent(&touchEvent, result);
+ window->DispatchHitTest(touchEvent);
+ });
+
+ if (!aReturnResult) {
+ // We don't care how APZ handled the event so we're done here.
+ return;
+ }
+
+ if (result.mHandledResult != Nothing()) {
+ // We know conclusively that the root APZ handled this or not and
+ // don't need to do any more work.
+ switch (result.mStatus) {
+ case nsEventStatus_eIgnore:
+ aReturnResult->Complete(
+ java::sdk::Integer::ValueOf(INPUT_RESULT_UNHANDLED));
+ break;
+ case nsEventStatus_eConsumeDoDefault:
+ aReturnResult->Complete(java::sdk::Integer::ValueOf(
+ ConvertAPZHandledResult(result.mHandledResult.value())));
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected nsEventStatus");
+ aReturnResult->Complete(
+ java::sdk::Integer::ValueOf(INPUT_RESULT_UNHANDLED));
+ break;
+ }
+ return;
+ }
+
+ // Wait to see if APZ handled the event or not...
+ controller->AddInputBlockCallback(
+ result.mInputBlockId,
+ [aReturnResult = java::GeckoResult::GlobalRef(aReturnResult)](
+ uint64_t aInputBlockId, APZHandledResult aHandledResult) {
+ aReturnResult->Complete(java::sdk::Integer::ValueOf(
+ ConvertAPZHandledResult(aHandledResult)));
+ });
+ }
+};
+
+NS_IMPL_ISUPPORTS(AndroidView, nsIAndroidEventDispatcher, nsIAndroidView)
+
+nsresult AndroidView::GetInitData(JSContext* aCx, JS::MutableHandleValue aOut) {
+ if (!mInitData) {
+ aOut.setNull();
+ return NS_OK;
+ }
+
+ return widget::EventDispatcher::UnboxBundle(aCx, mInitData, aOut);
+}
+
+/**
+ * Compositor has some unique requirements for its native calls, so make it
+ * separate from GeckoViewSupport.
+ */
+class LayerViewSupport final
+ : public GeckoSession::Compositor::Natives<LayerViewSupport> {
+ WindowPtr mWindow;
+ GeckoSession::Compositor::WeakRef mCompositor;
+ Atomic<bool, ReleaseAcquire> mCompositorPaused;
+ jni::Object::GlobalRef mSurface;
+
+ struct CaptureRequest {
+ explicit CaptureRequest() : mResult(nullptr) {}
+ explicit CaptureRequest(java::GeckoResult::GlobalRef aResult,
+ java::sdk::Bitmap::GlobalRef aBitmap,
+ const ScreenRect& aSource,
+ const IntSize& aOutputSize)
+ : mResult(aResult),
+ mBitmap(aBitmap),
+ mSource(aSource),
+ mOutputSize(aOutputSize) {}
+
+ // where to send the pixels
+ java::GeckoResult::GlobalRef mResult;
+
+ // where to store the pixels
+ java::sdk::Bitmap::GlobalRef mBitmap;
+
+ ScreenRect mSource;
+
+ IntSize mOutputSize;
+ };
+ std::queue<CaptureRequest> mCapturePixelsResults;
+
+ // In order to use Event::HasSameTypeAs in PostTo(), we cannot make
+ // LayerViewEvent a template because each template instantiation is
+ // a different type. So implement LayerViewEvent as a ProxyEvent.
+ class LayerViewEvent final : public nsAppShell::ProxyEvent {
+ using Event = nsAppShell::Event;
+
+ public:
+ static UniquePtr<Event> MakeEvent(UniquePtr<Event>&& event) {
+ return MakeUnique<LayerViewEvent>(std::move(event));
+ }
+
+ explicit LayerViewEvent(UniquePtr<Event>&& event)
+ : nsAppShell::ProxyEvent(std::move(event)) {}
+
+ void PostTo(LinkedList<Event>& queue) override {
+ // Give priority to compositor events, but keep in order with
+ // existing compositor events.
+ nsAppShell::Event* event = queue.getFirst();
+ while (event && event->HasSameTypeAs(this)) {
+ event = event->getNext();
+ }
+ if (event) {
+ event->setPrevious(this);
+ } else {
+ queue.insertBack(this);
+ }
+ }
+ };
+
+ public:
+ typedef GeckoSession::Compositor::Natives<LayerViewSupport> Base;
+
+ LayerViewSupport(WindowPtr aWindow,
+ const GeckoSession::Compositor::LocalRef& aInstance)
+ : mWindow(aWindow), mCompositor(aInstance), mCompositorPaused(true) {
+#if defined(DEBUG)
+ auto win(mWindow.Access());
+ MOZ_ASSERT(!!win);
+#endif // defined(DEBUG)
+ }
+
+ ~LayerViewSupport() {}
+
+ using Base::AttachNative;
+ using Base::DisposeNative;
+
+ void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer) {
+ RefPtr<Runnable> disposer = aDisposer;
+ if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
+ GeckoSession::Compositor::GlobalRef compositor(mCompositor);
+ if (!compositor) {
+ return;
+ }
+
+ uiThread->Dispatch(NS_NewRunnableFunction(
+ "LayerViewSupport::OnWeakNonIntrusiveDetach",
+ [compositor, disposer = std::move(disposer),
+ results = &mCapturePixelsResults, window = mWindow]() mutable {
+ if (auto accWindow = window.Access()) {
+ while (!results->empty()) {
+ auto aResult =
+ java::GeckoResult::LocalRef(results->front().mResult);
+ if (aResult) {
+ aResult->CompleteExceptionally(
+ java::sdk::IllegalStateException::New(
+ "The compositor has detached from the session")
+ .Cast<jni::Throwable>());
+ }
+ results->pop();
+ }
+ compositor->OnCompositorDetached();
+ }
+
+ disposer->Run();
+ }));
+ }
+ }
+
+ const GeckoSession::Compositor::Ref& GetJavaCompositor() const {
+ return mCompositor;
+ }
+
+ bool CompositorPaused() const { return mCompositorPaused; }
+
+ jni::Object::Param GetSurface() { return mSurface; }
+
+ private:
+ already_AddRefed<UiCompositorControllerChild>
+ GetUiCompositorControllerChild() {
+ RefPtr<UiCompositorControllerChild> child;
+ if (auto window = mWindow.Access()) {
+ nsWindow* gkWindow = window->GetNsWindow();
+ if (gkWindow) {
+ child = gkWindow->GetUiCompositorControllerChild();
+ }
+ }
+ return child.forget();
+ }
+
+ already_AddRefed<DataSourceSurface> FlipScreenPixels(
+ Shmem& aMem, const ScreenIntSize& aInSize, const ScreenRect& aInRegion,
+ const IntSize& aOutSize) {
+ RefPtr<gfx::DataSourceSurface> image =
+ gfx::Factory::CreateWrappingDataSourceSurface(
+ aMem.get<uint8_t>(),
+ StrideForFormatAndWidth(SurfaceFormat::B8G8R8A8, aInSize.width),
+ IntSize(aInSize.width, aInSize.height), SurfaceFormat::B8G8R8A8);
+ RefPtr<gfx::DrawTarget> drawTarget =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ aOutSize, SurfaceFormat::B8G8R8A8);
+ if (!drawTarget) {
+ return nullptr;
+ }
+
+ drawTarget->SetTransform(Matrix::Scaling(1.0, -1.0) *
+ Matrix::Translation(0, aOutSize.height));
+
+ gfx::Rect srcRect(aInRegion.x,
+ (aInSize.height - aInRegion.height) - aInRegion.y,
+ aInRegion.width, aInRegion.height);
+ gfx::Rect destRect(0, 0, aOutSize.width, aOutSize.height);
+ drawTarget->DrawSurface(image, destRect, srcRect);
+
+ RefPtr<gfx::SourceSurface> snapshot = drawTarget->Snapshot();
+ RefPtr<gfx::DataSourceSurface> data = snapshot->GetDataSurface();
+ return data.forget();
+ }
+
+ /**
+ * Compositor methods
+ */
+ public:
+ void AttachNPZC(jni::Object::Param aNPZC) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aNPZC);
+
+ auto locked(mWindow.Access());
+ if (!locked) {
+ return; // Already shut down.
+ }
+
+ nsWindow* gkWindow = locked->GetNsWindow();
+
+ // We can have this situation if we get two GeckoViewSupport::Transfer()
+ // called before the first AttachNPZC() gets here. Just detach the current
+ // instance since that's what happens in GeckoViewSupport::Transfer() as
+ // well.
+ gkWindow->mNPZCSupport.Detach();
+
+ auto npzc = java::PanZoomController::NativeProvider::LocalRef(
+ jni::GetGeckoThreadEnv(),
+ java::PanZoomController::NativeProvider::Ref::From(aNPZC));
+ gkWindow->mNPZCSupport =
+ jni::NativeWeakPtrHolder<NPZCSupport>::Attach(npzc, mWindow, npzc);
+
+ DispatchToUiThread(
+ "LayerViewSupport::AttachNPZC",
+ [npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc)] {
+ npzc->SetAttached(true);
+ });
+ }
+
+ void OnBoundsChanged(int32_t aLeft, int32_t aTop, int32_t aWidth,
+ int32_t aHeight) {
+ MOZ_ASSERT(NS_IsMainThread());
+ auto acc = mWindow.Access();
+ if (!acc) {
+ return; // Already shut down.
+ }
+
+ nsWindow* gkWindow = acc->GetNsWindow();
+ if (!gkWindow) {
+ return;
+ }
+
+ gkWindow->Resize(aLeft, aTop, aWidth, aHeight, /* repaint */ false);
+ }
+
+ void SetDynamicToolbarMaxHeight(int32_t aHeight) {
+ MOZ_ASSERT(NS_IsMainThread());
+ auto acc = mWindow.Access();
+ if (!acc) {
+ return; // Already shut down.
+ }
+
+ nsWindow* gkWindow = acc->GetNsWindow();
+ if (!gkWindow) {
+ return;
+ }
+
+ gkWindow->UpdateDynamicToolbarMaxHeight(ScreenIntCoord(aHeight));
+ }
+
+ void SyncPauseCompositor() {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ if (RefPtr<UiCompositorControllerChild> child =
+ GetUiCompositorControllerChild()) {
+ mCompositorPaused = true;
+ child->Pause();
+ }
+
+ if (auto lock{mWindow.Access()}) {
+ while (!mCapturePixelsResults.empty()) {
+ auto result =
+ java::GeckoResult::LocalRef(mCapturePixelsResults.front().mResult);
+ if (result) {
+ result->CompleteExceptionally(
+ java::sdk::IllegalStateException::New(
+ "The compositor has detached from the session")
+ .Cast<jni::Throwable>());
+ }
+ mCapturePixelsResults.pop();
+ }
+ }
+ }
+
+ void SyncResumeCompositor() {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ if (RefPtr<UiCompositorControllerChild> child =
+ GetUiCompositorControllerChild()) {
+ mCompositorPaused = false;
+ child->Resume();
+ }
+ }
+
+ void SyncResumeResizeCompositor(
+ const GeckoSession::Compositor::LocalRef& aObj, int32_t aX, int32_t aY,
+ int32_t aWidth, int32_t aHeight, jni::Object::Param aSurface) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ mSurface = aSurface;
+
+ if (RefPtr<UiCompositorControllerChild> child =
+ GetUiCompositorControllerChild()) {
+ child->ResumeAndResize(aX, aY, aWidth, aHeight);
+ }
+
+ mCompositorPaused = false;
+
+ class OnResumedEvent : public nsAppShell::Event {
+ GeckoSession::Compositor::GlobalRef mCompositor;
+
+ public:
+ explicit OnResumedEvent(GeckoSession::Compositor::GlobalRef&& aCompositor)
+ : mCompositor(std::move(aCompositor)) {}
+
+ void Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ const auto lvsHolder =
+ GetNative(GeckoSession::Compositor::LocalRef(env, mCompositor));
+
+ if (!lvsHolder) {
+ env->ExceptionClear();
+ return; // Already shut down.
+ }
+
+ auto lvs(lvsHolder->Access());
+ if (!lvs) {
+ env->ExceptionClear();
+ return; // Already shut down.
+ }
+
+ auto win = lvs->mWindow.Access();
+ if (!win) {
+ env->ExceptionClear();
+ return; // Already shut down.
+ }
+
+ // When we get here, the compositor has already been told to
+ // resume. This means it's now safe for layer updates to occur.
+ // Since we might have prevented one or more draw events from
+ // occurring while the compositor was paused, we need to
+ // schedule a draw event now.
+ if (!lvs->mCompositorPaused) {
+ nsWindow* const gkWindow = win->GetNsWindow();
+ if (gkWindow) {
+ gkWindow->RedrawAll();
+ }
+ }
+ }
+ };
+
+ // Use priority queue for timing-sensitive event.
+ nsAppShell::PostEvent(
+ MakeUnique<LayerViewEvent>(MakeUnique<OnResumedEvent>(aObj)));
+ }
+
+ void SyncInvalidateAndScheduleComposite() {
+ RefPtr<UiCompositorControllerChild> child =
+ GetUiCompositorControllerChild();
+ if (!child) {
+ return;
+ }
+
+ if (AndroidBridge::IsJavaUiThread()) {
+ child->InvalidateAndRender();
+ return;
+ }
+
+ if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
+ uiThread->Dispatch(NewRunnableMethod<>(
+ "LayerViewSupport::InvalidateAndRender", child,
+ &UiCompositorControllerChild::InvalidateAndRender),
+ nsIThread::DISPATCH_NORMAL);
+ }
+ }
+
+ void SetMaxToolbarHeight(int32_t aHeight) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ if (RefPtr<UiCompositorControllerChild> child =
+ GetUiCompositorControllerChild()) {
+ child->SetMaxToolbarHeight(aHeight);
+ }
+ }
+
+ void SetFixedBottomOffset(int32_t aOffset) {
+ if (auto acc{mWindow.Access()}) {
+ nsWindow* gkWindow = acc->GetNsWindow();
+ if (gkWindow) {
+ gkWindow->UpdateDynamicToolbarOffset(ScreenIntCoord(aOffset));
+ }
+ }
+
+ if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
+ uiThread->Dispatch(NS_NewRunnableFunction(
+ "LayerViewSupport::SetFixedBottomOffset", [this, offset = aOffset] {
+ if (RefPtr<UiCompositorControllerChild> child =
+ GetUiCompositorControllerChild()) {
+ child->SetFixedBottomOffset(offset);
+ }
+ }));
+ }
+ }
+
+ void SendToolbarAnimatorMessage(int32_t aMessage) {
+ RefPtr<UiCompositorControllerChild> child =
+ GetUiCompositorControllerChild();
+ if (!child) {
+ return;
+ }
+
+ if (AndroidBridge::IsJavaUiThread()) {
+ child->ToolbarAnimatorMessageFromUI(aMessage);
+ return;
+ }
+
+ if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
+ uiThread->Dispatch(
+ NewRunnableMethod<int32_t>(
+ "LayerViewSupport::ToolbarAnimatorMessageFromUI", child,
+ &UiCompositorControllerChild::ToolbarAnimatorMessageFromUI,
+ aMessage),
+ nsIThread::DISPATCH_NORMAL);
+ }
+ }
+
+ void RecvToolbarAnimatorMessage(int32_t aMessage) {
+ auto compositor = GeckoSession::Compositor::LocalRef(mCompositor);
+ if (compositor) {
+ compositor->RecvToolbarAnimatorMessage(aMessage);
+ }
+ }
+
+ void SetDefaultClearColor(int32_t aColor) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ if (RefPtr<UiCompositorControllerChild> child =
+ GetUiCompositorControllerChild()) {
+ child->SetDefaultClearColor((uint32_t)aColor);
+ }
+ }
+
+ void RequestScreenPixels(jni::Object::Param aResult,
+ jni::Object::Param aTarget, int32_t aXOffset,
+ int32_t aYOffset, int32_t aSrcWidth,
+ int32_t aSrcHeight, int32_t aOutWidth,
+ int32_t aOutHeight) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ int size = 0;
+ if (auto window = mWindow.Access()) {
+ mCapturePixelsResults.push(CaptureRequest(
+ java::GeckoResult::GlobalRef(java::GeckoResult::LocalRef(aResult)),
+ java::sdk::Bitmap::GlobalRef(java::sdk::Bitmap::LocalRef(aTarget)),
+ ScreenRect(aXOffset, aYOffset, aSrcWidth, aSrcHeight),
+ IntSize(aOutWidth, aOutHeight)));
+ size = mCapturePixelsResults.size();
+ }
+
+ if (size == 1) {
+ if (RefPtr<UiCompositorControllerChild> child =
+ GetUiCompositorControllerChild()) {
+ child->RequestScreenPixels();
+ }
+ }
+ }
+
+ void RecvScreenPixels(Shmem&& aMem, const ScreenIntSize& aSize,
+ bool aNeedsYFlip) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ CaptureRequest request;
+ java::GeckoResult::LocalRef result = nullptr;
+ java::sdk::Bitmap::LocalRef bitmap = nullptr;
+ if (auto window = mWindow.Access()) {
+ // The result might have been already rejected if the compositor was
+ // detached from the session
+ if (!mCapturePixelsResults.empty()) {
+ request = mCapturePixelsResults.front();
+ result = java::GeckoResult::LocalRef(request.mResult);
+ bitmap = java::sdk::Bitmap::LocalRef(request.mBitmap);
+ mCapturePixelsResults.pop();
+ }
+ }
+
+ if (result) {
+ if (bitmap) {
+ RefPtr<DataSourceSurface> surf;
+ if (aNeedsYFlip) {
+ surf = FlipScreenPixels(aMem, aSize, request.mSource,
+ request.mOutputSize);
+ } else {
+ surf = gfx::Factory::CreateWrappingDataSourceSurface(
+ aMem.get<uint8_t>(),
+ StrideForFormatAndWidth(SurfaceFormat::B8G8R8A8, aSize.width),
+ IntSize(aSize.width, aSize.height), SurfaceFormat::B8G8R8A8);
+ }
+ if (surf) {
+ DataSourceSurface::ScopedMap smap(surf, DataSourceSurface::READ);
+ auto pixels = mozilla::jni::ByteBuffer::New(
+ reinterpret_cast<int8_t*>(smap.GetData()),
+ smap.GetStride() * request.mOutputSize.height);
+ bitmap->CopyPixelsFromBuffer(pixels);
+ result->Complete(bitmap);
+ } else {
+ result->CompleteExceptionally(
+ java::sdk::IllegalStateException::New(
+ "Failed to create flipped snapshot surface (probably out of "
+ "memory)")
+ .Cast<jni::Throwable>());
+ }
+ } else {
+ result->CompleteExceptionally(java::sdk::IllegalArgumentException::New(
+ "No target bitmap argument provided")
+ .Cast<jni::Throwable>());
+ }
+ }
+
+ // Pixels have been copied, so Dealloc Shmem
+ if (RefPtr<UiCompositorControllerChild> child =
+ GetUiCompositorControllerChild()) {
+ child->DeallocPixelBuffer(aMem);
+
+ if (auto window = mWindow.Access()) {
+ if (!mCapturePixelsResults.empty()) {
+ child->RequestScreenPixels();
+ }
+ }
+ }
+ }
+
+ void EnableLayerUpdateNotifications(bool aEnable) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ if (RefPtr<UiCompositorControllerChild> child =
+ GetUiCompositorControllerChild()) {
+ child->EnableLayerUpdateNotifications(aEnable);
+ }
+ }
+
+ void OnSafeAreaInsetsChanged(int32_t aTop, int32_t aRight, int32_t aBottom,
+ int32_t aLeft) {
+ MOZ_ASSERT(NS_IsMainThread());
+ auto win(mWindow.Access());
+ if (!win) {
+ return; // Already shut down.
+ }
+
+ nsWindow* gkWindow = win->GetNsWindow();
+ if (!gkWindow) {
+ return;
+ }
+
+ ScreenIntMargin safeAreaInsets(aTop, aRight, aBottom, aLeft);
+ gkWindow->UpdateSafeAreaInsets(safeAreaInsets);
+ }
+};
+
+GeckoViewSupport::~GeckoViewSupport() {
+ if (mWindow) {
+ mWindow->DetachNatives();
+ }
+}
+
+/* static */
+void GeckoViewSupport::Open(
+ const jni::Class::LocalRef& aCls, GeckoSession::Window::Param aWindow,
+ jni::Object::Param aQueue, jni::Object::Param aCompositor,
+ jni::Object::Param aDispatcher, jni::Object::Param aSessionAccessibility,
+ jni::Object::Param aInitData, jni::String::Param aId,
+ jni::String::Param aChromeURI, int32_t aScreenId, bool aPrivateMode) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ AUTO_PROFILER_LABEL("mozilla::widget::GeckoViewSupport::Open", OTHER);
+
+ nsCOMPtr<nsIWindowWatcher> ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
+ MOZ_RELEASE_ASSERT(ww);
+
+ nsAutoCString url;
+ if (aChromeURI) {
+ url = aChromeURI->ToCString();
+ } else {
+ nsresult rv = Preferences::GetCString("toolkit.defaultChromeURI", url);
+ if (NS_FAILED(rv)) {
+ url = "chrome://geckoview/content/geckoview.xhtml"_ns;
+ }
+ }
+
+ // Prepare an nsIAndroidView to pass as argument to the window.
+ RefPtr<AndroidView> androidView = new AndroidView();
+ androidView->mEventDispatcher->Attach(
+ java::EventDispatcher::Ref::From(aDispatcher), nullptr);
+ androidView->mInitData = java::GeckoBundle::Ref::From(aInitData);
+
+ nsAutoCString chromeFlags("chrome,dialog=0,remote,resizable,scrollbars");
+ if (aPrivateMode) {
+ chromeFlags += ",private";
+ }
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ ww->OpenWindow(nullptr, url, nsDependentCString(aId->ToCString().get()),
+ chromeFlags, androidView, getter_AddRefs(domWindow));
+ MOZ_RELEASE_ASSERT(domWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> pdomWindow = nsPIDOMWindowOuter::From(domWindow);
+ const RefPtr<nsWindow> window = nsWindow::From(pdomWindow);
+ MOZ_ASSERT(window);
+ window->SetScreenId(aScreenId);
+
+ // Attach a new GeckoView support object to the new window.
+ GeckoSession::Window::LocalRef sessionWindow(aCls.Env(), aWindow);
+ auto weakGeckoViewSupport =
+ jni::NativeWeakPtrHolder<GeckoViewSupport>::Attach(
+ sessionWindow, window, sessionWindow, pdomWindow);
+
+ window->mGeckoViewSupport = weakGeckoViewSupport;
+ window->mAndroidView = androidView;
+
+ // Attach other session support objects.
+ { // Scope for gvsAccess
+ auto gvsAccess = weakGeckoViewSupport.Access();
+ MOZ_ASSERT(gvsAccess);
+
+ gvsAccess->Transfer(sessionWindow, aQueue, aCompositor, aDispatcher,
+ aSessionAccessibility, aInitData);
+ }
+
+ if (window->mWidgetListener) {
+ nsCOMPtr<nsIAppWindow> appWindow(window->mWidgetListener->GetAppWindow());
+ if (appWindow) {
+ // Our window is not intrinsically sized, so tell AppWindow to
+ // not set a size for us.
+ appWindow->SetIntrinsicallySized(false);
+ }
+ }
+}
+
+void GeckoViewSupport::Close() {
+ if (mWindow) {
+ if (mWindow->mAndroidView) {
+ mWindow->mAndroidView->mEventDispatcher->Detach();
+ }
+ mWindow = nullptr;
+ }
+
+ if (!mDOMWindow) {
+ return;
+ }
+
+ mDOMWindow->ForceClose();
+ mDOMWindow = nullptr;
+ mGeckoViewWindow = nullptr;
+}
+
+void GeckoViewSupport::Transfer(const GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aQueue,
+ jni::Object::Param aCompositor,
+ jni::Object::Param aDispatcher,
+ jni::Object::Param aSessionAccessibility,
+ jni::Object::Param aInitData) {
+ mWindow->mNPZCSupport.Detach();
+
+ auto compositor = GeckoSession::Compositor::LocalRef(
+ inst.Env(), GeckoSession::Compositor::Ref::From(aCompositor));
+
+ bool attachLvs;
+ { // Scope for lvsAccess
+ auto lvsAccess{mWindow->mLayerViewSupport.Access()};
+ // If we do not yet have mLayerViewSupport, or if the compositor has
+ // changed, then we must attach a new one.
+ attachLvs = !lvsAccess || lvsAccess->GetJavaCompositor() != compositor;
+ }
+
+ if (attachLvs) {
+ mWindow->mLayerViewSupport =
+ jni::NativeWeakPtrHolder<LayerViewSupport>::Attach(
+ compositor, mWindow->mGeckoViewSupport, compositor);
+ }
+
+ MOZ_ASSERT(mWindow->mAndroidView);
+ mWindow->mAndroidView->mEventDispatcher->Attach(
+ java::EventDispatcher::Ref::From(aDispatcher), mDOMWindow);
+
+ mWindow->mSessionAccessibility.Detach();
+ if (aSessionAccessibility) {
+ AttachAccessibility(inst, aSessionAccessibility);
+ }
+
+ if (mIsReady) {
+ // We're in a transfer; update init-data and notify JS code.
+ mWindow->mAndroidView->mInitData = java::GeckoBundle::Ref::From(aInitData);
+ OnReady(aQueue);
+ mWindow->mAndroidView->mEventDispatcher->Dispatch(
+ u"GeckoView:UpdateInitData");
+ }
+
+ DispatchToUiThread("GeckoViewSupport::Transfer",
+ [compositor = GeckoSession::Compositor::GlobalRef(
+ compositor)] { compositor->OnCompositorAttached(); });
+}
+
+void GeckoViewSupport::AttachEditable(
+ const GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aEditableParent) {
+ if (auto win{mWindow->mEditableSupport.Access()}) {
+ win->TransferParent(aEditableParent);
+ } else {
+ auto editableChild = java::GeckoEditableChild::New(aEditableParent,
+ /* default */ true);
+ mWindow->mEditableSupport =
+ jni::NativeWeakPtrHolder<GeckoEditableSupport>::Attach(
+ editableChild, mWindow->mGeckoViewSupport, editableChild);
+ }
+
+ mWindow->mEditableParent = aEditableParent;
+}
+
+void GeckoViewSupport::AttachAccessibility(
+ const GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aSessionAccessibility) {
+ java::SessionAccessibility::NativeProvider::LocalRef sessionAccessibility(
+ inst.Env());
+ sessionAccessibility = java::SessionAccessibility::NativeProvider::Ref::From(
+ aSessionAccessibility);
+
+ mWindow->mSessionAccessibility =
+ jni::NativeWeakPtrHolder<a11y::SessionAccessibility>::Attach(
+ sessionAccessibility, mWindow->mGeckoViewSupport,
+ sessionAccessibility);
+}
+
+auto GeckoViewSupport::OnLoadRequest(mozilla::jni::String::Param aUri,
+ int32_t aWindowType, int32_t aFlags,
+ mozilla::jni::String::Param aTriggeringUri,
+ bool aHasUserGesture,
+ bool aIsTopLevel) const
+ -> java::GeckoResult::LocalRef {
+ GeckoSession::Window::LocalRef window(mGeckoViewWindow);
+ if (!window) {
+ return nullptr;
+ }
+ return window->OnLoadRequest(aUri, aWindowType, aFlags, aTriggeringUri,
+ aHasUserGesture, aIsTopLevel);
+}
+
+void GeckoViewSupport::OnReady(jni::Object::Param aQueue) {
+ GeckoSession::Window::LocalRef window(mGeckoViewWindow);
+ if (!window) {
+ return;
+ }
+ window->OnReady(aQueue);
+ mIsReady = true;
+}
+
+void GeckoViewSupport::PassExternalResponse(
+ java::WebResponse::Param aResponse) {
+ GeckoSession::Window::LocalRef window(mGeckoViewWindow);
+ if (!window) {
+ return;
+ }
+
+ auto response = java::WebResponse::GlobalRef(aResponse);
+
+ DispatchToUiThread("GeckoViewSupport::PassExternalResponse",
+ [window = java::GeckoSession::Window::GlobalRef(window),
+ response] { window->PassExternalWebResponse(response); });
+}
+
+} // namespace widget
+} // namespace mozilla
+
+void nsWindow::InitNatives() {
+ jni::InitConversionStatics();
+ mozilla::widget::GeckoViewSupport::Base::Init();
+ mozilla::widget::LayerViewSupport::Init();
+ mozilla::widget::NPZCSupport::Init();
+
+ mozilla::widget::GeckoEditableSupport::Init();
+ a11y::SessionAccessibility::Init();
+}
+
+void nsWindow::DetachNatives() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mEditableSupport.Detach();
+ mNPZCSupport.Detach();
+ mLayerViewSupport.Detach();
+ mSessionAccessibility.Detach();
+}
+
+/* static */
+already_AddRefed<nsWindow> nsWindow::From(nsPIDOMWindowOuter* aDOMWindow) {
+ nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aDOMWindow);
+ return From(widget);
+}
+
+/* static */
+already_AddRefed<nsWindow> nsWindow::From(nsIWidget* aWidget) {
+ // `widget` may be one of several different types in the parent
+ // process, including the Android nsWindow, PuppetWidget, etc. To
+ // ensure that the cast to the Android nsWindow is valid, we check that the
+ // widget is a top-level window and that its NS_NATIVE_WIDGET value is
+ // non-null, which is not the case for non-native widgets like
+ // PuppetWidget.
+ if (aWidget && aWidget->WindowType() == nsWindowType::eWindowType_toplevel &&
+ aWidget->GetNativeData(NS_NATIVE_WIDGET) == aWidget) {
+ RefPtr<nsWindow> window = static_cast<nsWindow*>(aWidget);
+ return window.forget();
+ }
+ return nullptr;
+}
+
+nsWindow* nsWindow::TopWindow() {
+ if (!gTopLevelWindows.IsEmpty()) return gTopLevelWindows[0];
+ return nullptr;
+}
+
+void nsWindow::LogWindow(nsWindow* win, int index, int indent) {
+#if defined(DEBUG) || defined(FORCE_ALOG)
+ char spaces[] = " ";
+ spaces[indent < 20 ? indent : 20] = 0;
+ ALOG("%s [% 2d] 0x%p [parent 0x%p] [% 3d,% 3dx% 3d,% 3d] vis %d type %d",
+ spaces, index, win, win->mParent, win->mBounds.x, win->mBounds.y,
+ win->mBounds.width, win->mBounds.height, win->mIsVisible,
+ win->mWindowType);
+#endif
+}
+
+void nsWindow::DumpWindows() { DumpWindows(gTopLevelWindows); }
+
+void nsWindow::DumpWindows(const nsTArray<nsWindow*>& wins, int indent) {
+ for (uint32_t i = 0; i < wins.Length(); ++i) {
+ nsWindow* w = wins[i];
+ LogWindow(w, i, indent);
+ DumpWindows(w->mChildren, indent + 1);
+ }
+}
+
+nsWindow::nsWindow()
+ : mScreenId(0), // Use 0 (primary screen) as the default value.
+ mIsVisible(false),
+ mParent(nullptr),
+ mDynamicToolbarMaxHeight(0),
+ mIsFullScreen(false),
+ mIsDisablingWebRender(false) {}
+
+nsWindow::~nsWindow() {
+ gTopLevelWindows.RemoveElement(this);
+ ALOG("nsWindow %p destructor", (void*)this);
+ // The mCompositorSession should have been cleaned up in nsWindow::Destroy()
+ // DestroyLayerManager() will call DestroyCompositor() which will crash if
+ // called from nsBaseWidget destructor. See Bug 1392705
+ MOZ_ASSERT(!mCompositorSession);
+}
+
+bool nsWindow::IsTopLevel() {
+ return mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog ||
+ mWindowType == eWindowType_invisible;
+}
+
+nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) {
+ ALOG("nsWindow[%p]::Create %p [%d %d %d %d]", (void*)this, (void*)aParent,
+ aRect.x, aRect.y, aRect.width, aRect.height);
+
+ nsWindow* parent = (nsWindow*)aParent;
+ if (aNativeParent) {
+ if (parent) {
+ ALOG(
+ "Ignoring native parent on Android window [%p], "
+ "since parent was specified (%p %p)",
+ (void*)this, (void*)aNativeParent, (void*)aParent);
+ } else {
+ parent = (nsWindow*)aNativeParent;
+ }
+ }
+
+ // A default size of 1x1 confuses MobileViewportManager, so
+ // use 0x0 instead. This is also a little more fitting since
+ // we don't yet have a surface yet (and therefore a valid size)
+ // and 0x0 is usually recognized as invalid.
+ LayoutDeviceIntRect rect = aRect;
+ if (aRect.width == 1 && aRect.height == 1) {
+ rect.width = 0;
+ rect.height = 0;
+ }
+
+ mBounds = rect;
+
+ BaseCreate(nullptr, aInitData);
+
+ NS_ASSERTION(IsTopLevel() || parent,
+ "non-top-level window doesn't have a parent!");
+
+ if (IsTopLevel()) {
+ gTopLevelWindows.AppendElement(this);
+
+ } else if (parent) {
+ parent->mChildren.AppendElement(this);
+ mParent = parent;
+ }
+
+ CreateLayerManager();
+
+#ifdef DEBUG_ANDROID_WIDGET
+ DumpWindows();
+#endif
+
+ return NS_OK;
+}
+
+void nsWindow::Destroy() {
+ nsBaseWidget::mOnDestroyCalled = true;
+
+ // Disassociate our native object from GeckoView.
+ mGeckoViewSupport.Detach();
+
+ // Stuff below may release the last ref to this
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ while (mChildren.Length()) {
+ // why do we still have children?
+ ALOG("### Warning: Destroying window %p and reparenting child %p to null!",
+ (void*)this, (void*)mChildren[0]);
+ mChildren[0]->SetParent(nullptr);
+ }
+
+ // Ensure the compositor has been shutdown before this nsWindow is potentially
+ // deleted
+ nsBaseWidget::DestroyCompositor();
+
+ nsBaseWidget::Destroy();
+
+ if (IsTopLevel()) gTopLevelWindows.RemoveElement(this);
+
+ SetParent(nullptr);
+
+ nsBaseWidget::OnDestroy();
+
+#ifdef DEBUG_ANDROID_WIDGET
+ DumpWindows();
+#endif
+}
+
+nsresult nsWindow::ConfigureChildren(
+ const nsTArray<nsIWidget::Configuration>& config) {
+ for (uint32_t i = 0; i < config.Length(); ++i) {
+ nsWindow* childWin = (nsWindow*)config[i].mChild.get();
+ childWin->Resize(config[i].mBounds.x, config[i].mBounds.y,
+ config[i].mBounds.width, config[i].mBounds.height, false);
+ }
+
+ return NS_OK;
+}
+
+mozilla::widget::EventDispatcher* nsWindow::GetEventDispatcher() const {
+ if (mAndroidView) {
+ return mAndroidView->mEventDispatcher;
+ }
+ return nullptr;
+}
+
+void nsWindow::RedrawAll() {
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->RequestRepaint();
+ } else if (mWidgetListener) {
+ mWidgetListener->RequestRepaint();
+ }
+}
+
+RefPtr<UiCompositorControllerChild> nsWindow::GetUiCompositorControllerChild() {
+ return mCompositorSession
+ ? mCompositorSession->GetUiCompositorControllerChild()
+ : nullptr;
+}
+
+mozilla::layers::LayersId nsWindow::GetRootLayerId() const {
+ return mCompositorSession ? mCompositorSession->RootLayerTreeId()
+ : mozilla::layers::LayersId{0};
+}
+
+void nsWindow::OnGeckoViewReady() {
+ auto acc(mGeckoViewSupport.Access());
+ if (!acc) {
+ return;
+ }
+
+ acc->OnReady();
+}
+
+void nsWindow::SetParent(nsIWidget* aNewParent) {
+ if ((nsIWidget*)mParent == aNewParent) return;
+
+ // If we had a parent before, remove ourselves from its list of
+ // children.
+ if (mParent) mParent->mChildren.RemoveElement(this);
+
+ mParent = (nsWindow*)aNewParent;
+
+ if (mParent) mParent->mChildren.AppendElement(this);
+
+ // if we are now in the toplevel window's hierarchy, schedule a redraw
+ if (FindTopLevel() == nsWindow::TopWindow()) RedrawAll();
+}
+
+nsIWidget* nsWindow::GetParent() { return mParent; }
+
+RefPtr<MozPromise<bool, bool, false>> nsWindow::OnLoadRequest(
+ nsIURI* aUri, int32_t aWindowType, int32_t aFlags,
+ nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture,
+ bool aIsTopLevel) {
+ auto geckoViewSupport(mGeckoViewSupport.Access());
+ if (!geckoViewSupport) {
+ return MozPromise<bool, bool, false>::CreateAndResolve(false, __func__);
+ }
+ nsAutoCString spec, triggeringSpec;
+ if (aUri) {
+ aUri->GetDisplaySpec(spec);
+ }
+
+ bool isNullPrincipal = false;
+ if (aTriggeringPrincipal) {
+ aTriggeringPrincipal->GetIsNullPrincipal(&isNullPrincipal);
+
+ if (!isNullPrincipal) {
+ nsCOMPtr<nsIURI> triggeringUri;
+ BasePrincipal::Cast(aTriggeringPrincipal)
+ ->GetURI(getter_AddRefs(triggeringUri));
+ if (triggeringUri) {
+ triggeringUri->GetDisplaySpec(triggeringSpec);
+ }
+ }
+ }
+
+ auto geckoResult = geckoViewSupport->OnLoadRequest(
+ spec.get(), aWindowType, aFlags,
+ isNullPrincipal ? nullptr : triggeringSpec.get(), aHasUserGesture,
+ aIsTopLevel);
+ return geckoResult
+ ? MozPromise<bool, bool, false>::FromGeckoResult(geckoResult)
+ : nullptr;
+}
+
+float nsWindow::GetDPI() {
+ float dpi = 160.0f;
+
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ if (screen) {
+ screen->GetDpi(&dpi);
+ }
+
+ return dpi;
+}
+
+double nsWindow::GetDefaultScaleInternal() {
+ double scale = 1.0f;
+
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ if (screen) {
+ screen->GetContentsScaleFactor(&scale);
+ }
+
+ return scale;
+}
+
+void nsWindow::Show(bool aState) {
+ ALOG("nsWindow[%p]::Show %d", (void*)this, aState);
+
+ if (mWindowType == eWindowType_invisible) {
+ ALOG("trying to show invisible window! ignoring..");
+ return;
+ }
+
+ if (aState == mIsVisible) return;
+
+ mIsVisible = aState;
+
+ if (IsTopLevel()) {
+ // XXX should we bring this to the front when it's shown,
+ // if it's a toplevel widget?
+
+ // XXX we should synthesize a eMouseExitFromWidget (for old top
+ // window)/eMouseEnterIntoWidget (for new top window) since we need
+ // to pretend that the top window always has focus. Not sure
+ // if Show() is the right place to do this, though.
+
+ if (aState) {
+ // It just became visible, so bring it to the front.
+ BringToFront();
+
+ } else if (nsWindow::TopWindow() == this) {
+ // find the next visible window to show
+ unsigned int i;
+ for (i = 1; i < gTopLevelWindows.Length(); i++) {
+ nsWindow* win = gTopLevelWindows[i];
+ if (!win->mIsVisible) continue;
+
+ win->BringToFront();
+ break;
+ }
+ }
+ } else if (FindTopLevel() == nsWindow::TopWindow()) {
+ RedrawAll();
+ }
+
+#ifdef DEBUG_ANDROID_WIDGET
+ DumpWindows();
+#endif
+}
+
+bool nsWindow::IsVisible() const { return mIsVisible; }
+
+void nsWindow::ConstrainPosition(bool aAllowSlop, int32_t* aX, int32_t* aY) {
+ ALOG("nsWindow[%p]::ConstrainPosition %d [%d %d]", (void*)this, aAllowSlop,
+ *aX, *aY);
+
+ // constrain toplevel windows; children we don't care about
+ if (IsTopLevel()) {
+ *aX = 0;
+ *aY = 0;
+ }
+}
+
+void nsWindow::Move(double aX, double aY) {
+ if (IsTopLevel()) return;
+
+ Resize(aX, aY, mBounds.width, mBounds.height, true);
+}
+
+void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
+ Resize(mBounds.x, mBounds.y, aWidth, aHeight, aRepaint);
+}
+
+void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) {
+ ALOG("nsWindow[%p]::Resize [%f %f %f %f] (repaint %d)", (void*)this, aX, aY,
+ aWidth, aHeight, aRepaint);
+
+ bool needPositionDispatch = aX != mBounds.x || aY != mBounds.y;
+ bool needSizeDispatch = aWidth != mBounds.width || aHeight != mBounds.height;
+
+ mBounds.x = NSToIntRound(aX);
+ mBounds.y = NSToIntRound(aY);
+ mBounds.width = NSToIntRound(aWidth);
+ mBounds.height = NSToIntRound(aHeight);
+
+ if (needSizeDispatch) {
+ OnSizeChanged(gfx::IntSize::Truncate(aWidth, aHeight));
+ }
+
+ if (needPositionDispatch) {
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+ }
+
+ // Should we skip honoring aRepaint here?
+ if (aRepaint && FindTopLevel() == nsWindow::TopWindow()) RedrawAll();
+}
+
+void nsWindow::SetZIndex(int32_t aZIndex) {
+ ALOG("nsWindow[%p]::SetZIndex %d ignored", (void*)this, aZIndex);
+}
+
+void nsWindow::SetSizeMode(nsSizeMode aMode) {
+ if (aMode == mSizeMode) {
+ return;
+ }
+
+ nsBaseWidget::SetSizeMode(aMode);
+
+ switch (aMode) {
+ case nsSizeMode_Minimized:
+ java::GeckoAppShell::MoveTaskToBack();
+ break;
+ case nsSizeMode_Fullscreen:
+ MakeFullScreen(true);
+ break;
+ default:
+ break;
+ }
+}
+
+void nsWindow::Enable(bool aState) {
+ ALOG("nsWindow[%p]::Enable %d ignored", (void*)this, aState);
+}
+
+bool nsWindow::IsEnabled() const { return true; }
+
+void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {}
+
+nsWindow* nsWindow::FindTopLevel() {
+ nsWindow* toplevel = this;
+ while (toplevel) {
+ if (toplevel->IsTopLevel()) return toplevel;
+
+ toplevel = toplevel->mParent;
+ }
+
+ ALOG(
+ "nsWindow::FindTopLevel(): couldn't find a toplevel or dialog window in "
+ "this [%p] widget's hierarchy!",
+ (void*)this);
+ return this;
+}
+
+void nsWindow::SetFocus(Raise, mozilla::dom::CallerType aCallerType) {
+ FindTopLevel()->BringToFront();
+}
+
+void nsWindow::BringToFront() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // If the window to be raised is the same as the currently raised one,
+ // do nothing. We need to check the focus manager as well, as the first
+ // window that is created will be first in the window list but won't yet
+ // be focused.
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm && fm->GetActiveWindow() && FindTopLevel() == nsWindow::TopWindow()) {
+ return;
+ }
+
+ if (!IsTopLevel()) {
+ FindTopLevel()->BringToFront();
+ return;
+ }
+
+ RefPtr<nsWindow> kungFuDeathGrip(this);
+
+ nsWindow* oldTop = nullptr;
+ if (!gTopLevelWindows.IsEmpty()) {
+ oldTop = gTopLevelWindows[0];
+ }
+
+ gTopLevelWindows.RemoveElement(this);
+ gTopLevelWindows.InsertElementAt(0, this);
+
+ if (oldTop) {
+ nsIWidgetListener* listener = oldTop->GetWidgetListener();
+ if (listener) {
+ listener->WindowDeactivated();
+ }
+ }
+
+ if (mWidgetListener) {
+ mWidgetListener->WindowActivated();
+ }
+
+ RedrawAll();
+}
+
+LayoutDeviceIntRect nsWindow::GetScreenBounds() {
+ return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size());
+}
+
+LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
+ LayoutDeviceIntPoint p(0, 0);
+
+ for (nsWindow* w = this; !!w; w = w->mParent) {
+ p.x += w->mBounds.x;
+ p.y += w->mBounds.y;
+
+ if (w->IsTopLevel()) {
+ break;
+ }
+ }
+ return p;
+}
+
+nsresult nsWindow::DispatchEvent(WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) {
+ aStatus = DispatchEvent(aEvent);
+ return NS_OK;
+}
+
+nsEventStatus nsWindow::DispatchEvent(WidgetGUIEvent* aEvent) {
+ if (mAttachedWidgetListener) {
+ return mAttachedWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
+ } else if (mWidgetListener) {
+ return mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
+ }
+ return nsEventStatus_eIgnore;
+}
+
+nsresult nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen*) {
+ if (!mAndroidView) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsIWidgetListener* listener = GetWidgetListener();
+ if (listener) {
+ listener->FullscreenWillChange(aFullScreen);
+ }
+
+ mIsFullScreen = aFullScreen;
+ mAndroidView->mEventDispatcher->Dispatch(
+ aFullScreen ? u"GeckoView:FullScreenEnter" : u"GeckoView:FullScreenExit");
+
+ if (listener) {
+ mSizeMode = mIsFullScreen ? nsSizeMode_Fullscreen : nsSizeMode_Normal;
+ listener->SizeModeChanged(mSizeMode);
+ listener->FullscreenChanged(mIsFullScreen);
+ }
+ return NS_OK;
+}
+
+mozilla::layers::LayerManager* nsWindow::GetLayerManager(
+ PLayerTransactionChild*, LayersBackend, LayerManagerPersistence) {
+ if (mLayerManager) {
+ return mLayerManager;
+ }
+
+ if (mIsDisablingWebRender) {
+ CreateLayerManager();
+ mIsDisablingWebRender = false;
+ return mLayerManager;
+ }
+
+ return nullptr;
+}
+
+void nsWindow::CreateLayerManager() {
+ if (mLayerManager) {
+ return;
+ }
+
+ nsWindow* topLevelWindow = FindTopLevel();
+ if (!topLevelWindow || topLevelWindow->mWindowType == eWindowType_invisible) {
+ // don't create a layer manager for an invisible top-level window
+ return;
+ }
+
+ // Ensure that gfxPlatform is initialized first.
+ gfxPlatform::GetPlatform();
+
+ if (ShouldUseOffMainThreadCompositing()) {
+ LayoutDeviceIntRect rect = GetBounds();
+ CreateCompositor(rect.Width(), rect.Height());
+ if (mLayerManager) {
+ return;
+ }
+
+ // If we get here, then off main thread compositing failed to initialize.
+ sFailedToCreateGLContext = true;
+ }
+
+ if (!ComputeShouldAccelerate() || sFailedToCreateGLContext) {
+ printf_stderr(" -- creating basic, not accelerated\n");
+ mLayerManager = CreateBasicLayerManager();
+ }
+}
+
+void nsWindow::NotifyDisablingWebRender() {
+ mIsDisablingWebRender = true;
+ RedrawAll();
+}
+
+void nsWindow::OnSizeChanged(const gfx::IntSize& aSize) {
+ ALOG("nsWindow: %p OnSizeChanged [%d %d]", (void*)this, aSize.width,
+ aSize.height);
+
+ mBounds.width = aSize.width;
+ mBounds.height = aSize.height;
+
+ if (mWidgetListener) {
+ mWidgetListener->WindowResized(this, aSize.width, aSize.height);
+ }
+
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->WindowResized(this, aSize.width, aSize.height);
+ }
+}
+
+void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) {
+ if (aPoint) {
+ event.mRefPoint = *aPoint;
+ } else {
+ event.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ }
+
+ event.mTime = PR_Now() / 1000;
+}
+
+void nsWindow::UpdateOverscrollVelocity(const float aX, const float aY) {
+ if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
+ mLayerViewSupport.Access()}) {
+ const auto& compositor = lvs->GetJavaCompositor();
+ if (AndroidBridge::IsJavaUiThread()) {
+ compositor->UpdateOverscrollVelocity(aX, aY);
+ return;
+ }
+
+ DispatchToUiThread(
+ "nsWindow::UpdateOverscrollVelocity",
+ [compositor = GeckoSession::Compositor::GlobalRef(compositor), aX, aY] {
+ compositor->UpdateOverscrollVelocity(aX, aY);
+ });
+ }
+}
+
+void nsWindow::UpdateOverscrollOffset(const float aX, const float aY) {
+ if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
+ mLayerViewSupport.Access()}) {
+ const auto& compositor = lvs->GetJavaCompositor();
+ if (AndroidBridge::IsJavaUiThread()) {
+ compositor->UpdateOverscrollOffset(aX, aY);
+ return;
+ }
+
+ DispatchToUiThread(
+ "nsWindow::UpdateOverscrollOffset",
+ [compositor = GeckoSession::Compositor::GlobalRef(compositor), aX, aY] {
+ compositor->UpdateOverscrollOffset(aX, aY);
+ });
+ }
+}
+
+void* nsWindow::GetNativeData(uint32_t aDataType) {
+ switch (aDataType) {
+ // used by GLContextProviderEGL, nullptr is EGL_DEFAULT_DISPLAY
+ case NS_NATIVE_DISPLAY:
+ return nullptr;
+
+ case NS_NATIVE_WIDGET:
+ return (void*)this;
+
+ case NS_RAW_NATIVE_IME_CONTEXT: {
+ void* pseudoIMEContext = GetPseudoIMEContext();
+ if (pseudoIMEContext) {
+ return pseudoIMEContext;
+ }
+ // We assume that there is only one context per process on Android
+ return NS_ONLY_ONE_NATIVE_IME_CONTEXT;
+ }
+
+ case NS_JAVA_SURFACE:
+ if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
+ mLayerViewSupport.Access()}) {
+ return lvs->GetSurface().Get();
+ }
+ return nullptr;
+ }
+
+ return nullptr;
+}
+
+void nsWindow::SetNativeData(uint32_t aDataType, uintptr_t aVal) {
+ switch (aDataType) {}
+}
+
+void nsWindow::DispatchHitTest(const WidgetTouchEvent& aEvent) {
+ if (aEvent.mMessage == eTouchStart && aEvent.mTouches.Length() == 1) {
+ // Since touch events don't get retargeted by PositionedEventTargeting.cpp
+ // code, we dispatch a dummy mouse event that *does* get retargeted.
+ // Front-end code can use this to activate the highlight element in case
+ // this touchstart is the start of a tap.
+ WidgetMouseEvent hittest(true, eMouseHitTest, this,
+ WidgetMouseEvent::eReal);
+ hittest.mRefPoint = aEvent.mTouches[0]->mRefPoint;
+ hittest.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
+ nsEventStatus status;
+ DispatchEvent(&hittest, status);
+ }
+}
+
+void nsWindow::PassExternalResponse(java::WebResponse::Param aResponse) {
+ auto acc(mGeckoViewSupport.Access());
+ if (!acc) {
+ return;
+ }
+
+ acc->PassExternalResponse(aResponse);
+}
+
+mozilla::Modifiers nsWindow::GetModifiers(int32_t metaState) {
+ using mozilla::java::sdk::KeyEvent;
+ return (metaState & KeyEvent::META_ALT_MASK ? MODIFIER_ALT : 0) |
+ (metaState & KeyEvent::META_SHIFT_MASK ? MODIFIER_SHIFT : 0) |
+ (metaState & KeyEvent::META_CTRL_MASK ? MODIFIER_CONTROL : 0) |
+ (metaState & KeyEvent::META_META_MASK ? MODIFIER_META : 0) |
+ (metaState & KeyEvent::META_FUNCTION_ON ? MODIFIER_FN : 0) |
+ (metaState & KeyEvent::META_CAPS_LOCK_ON ? MODIFIER_CAPSLOCK : 0) |
+ (metaState & KeyEvent::META_NUM_LOCK_ON ? MODIFIER_NUMLOCK : 0) |
+ (metaState & KeyEvent::META_SCROLL_LOCK_ON ? MODIFIER_SCROLLLOCK : 0);
+}
+
+TimeStamp nsWindow::GetEventTimeStamp(int64_t aEventTime) {
+ // Android's event time is SystemClock.uptimeMillis that is counted in ms
+ // since OS was booted.
+ // (https://developer.android.com/reference/android/os/SystemClock.html)
+ // and this SystemClock.uptimeMillis uses SYSTEM_TIME_MONOTONIC.
+ // Our posix implemententaion of TimeStamp::Now uses SYSTEM_TIME_MONOTONIC
+ // too. Due to same implementation, we can use this via FromSystemTime.
+ int64_t tick =
+ BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aEventTime);
+ return TimeStamp::FromSystemTime(tick);
+}
+
+void nsWindow::UserActivity() {
+ if (!mIdleService) {
+ mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1");
+ }
+
+ if (mIdleService) {
+ mIdleService->ResetIdleTimeOut(0);
+ }
+
+ if (FindTopLevel() != nsWindow::TopWindow()) {
+ BringToFront();
+ }
+}
+
+RefPtr<mozilla::a11y::SessionAccessibility>
+nsWindow::GetSessionAccessibility() {
+ auto acc(mSessionAccessibility.Access());
+ if (!acc) {
+ return nullptr;
+ }
+
+ return acc.AsRefPtr();
+}
+
+TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
+ nsWindow* top = FindTopLevel();
+ MOZ_ASSERT(top);
+
+ auto acc(top->mEditableSupport.Access());
+ if (!acc) {
+ // Non-GeckoView windows don't support IME operations.
+ return nullptr;
+ }
+
+ nsCOMPtr<TextEventDispatcherListener> ptr;
+ if (NS_FAILED(acc->QueryInterface(NS_GET_IID(TextEventDispatcherListener),
+ getter_AddRefs(ptr)))) {
+ return nullptr;
+ }
+
+ return ptr.get();
+}
+
+void nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) {
+ nsWindow* top = FindTopLevel();
+ MOZ_ASSERT(top);
+
+ auto acc(top->mEditableSupport.Access());
+ if (!acc) {
+ // Non-GeckoView windows don't support IME operations.
+ return;
+ }
+
+ // We are using an IME event later to notify Java, and the IME event
+ // will be processed by the top window. Therefore, to ensure the
+ // IME event uses the correct mInputContext, we need to let the top
+ // window process SetInputContext
+ acc->SetInputContext(aContext, aAction);
+}
+
+InputContext nsWindow::GetInputContext() {
+ nsWindow* top = FindTopLevel();
+ MOZ_ASSERT(top);
+
+ auto acc(top->mEditableSupport.Access());
+ if (!acc) {
+ // Non-GeckoView windows don't support IME operations.
+ return InputContext();
+ }
+
+ // We let the top window process SetInputContext,
+ // so we should let it process GetInputContext as well.
+ return acc->GetInputContext();
+}
+
+nsresult nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) {
+ mozilla::widget::AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ int eventType;
+ switch (aPointerState) {
+ case TOUCH_CONTACT:
+ // This could be a ACTION_DOWN or ACTION_MOVE depending on the
+ // existing state; it is mapped to the right thing in Java.
+ eventType = java::sdk::MotionEvent::ACTION_POINTER_DOWN;
+ break;
+ case TOUCH_REMOVE:
+ // This could be turned into a ACTION_UP in Java
+ eventType = java::sdk::MotionEvent::ACTION_POINTER_UP;
+ break;
+ case TOUCH_CANCEL:
+ eventType = java::sdk::MotionEvent::ACTION_CANCEL;
+ break;
+ case TOUCH_HOVER: // not supported for now
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mNPZCSupport.IsAttached());
+ auto npzcSup(mNPZCSupport.Access());
+ MOZ_ASSERT(!!npzcSup);
+
+ const auto& npzc = npzcSup->GetJavaNPZC();
+ const auto& bounds = FindTopLevel()->mBounds;
+ aPoint.x -= bounds.x;
+ aPoint.y -= bounds.y;
+
+ DispatchToUiThread(
+ "nsWindow::SynthesizeNativeTouchPoint",
+ [npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc),
+ aPointerId, eventType, aPoint, aPointerPressure, aPointerOrientation] {
+ npzc->SynthesizeNativeTouchPoint(aPointerId, eventType, aPoint.x,
+ aPoint.y, aPointerPressure,
+ aPointerOrientation);
+ });
+ return NS_OK;
+}
+
+nsresult nsWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) {
+ mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ MOZ_ASSERT(mNPZCSupport.IsAttached());
+ auto npzcSup(mNPZCSupport.Access());
+ MOZ_ASSERT(!!npzcSup);
+
+ const auto& npzc = npzcSup->GetJavaNPZC();
+ const auto& bounds = FindTopLevel()->mBounds;
+ aPoint.x -= bounds.x;
+ aPoint.y -= bounds.y;
+
+ DispatchToUiThread(
+ "nsWindow::SynthesizeNativeMouseEvent",
+ [npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc),
+ aNativeMessage, aPoint] {
+ npzc->SynthesizeNativeMouseEvent(aNativeMessage, aPoint.x, aPoint.y);
+ });
+ return NS_OK;
+}
+
+nsresult nsWindow::SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver) {
+ mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ MOZ_ASSERT(mNPZCSupport.IsAttached());
+ auto npzcSup(mNPZCSupport.Access());
+ MOZ_ASSERT(!!npzcSup);
+
+ const auto& npzc = npzcSup->GetJavaNPZC();
+ const auto& bounds = FindTopLevel()->mBounds;
+ aPoint.x -= bounds.x;
+ aPoint.y -= bounds.y;
+
+ DispatchToUiThread(
+ "nsWindow::SynthesizeNativeMouseMove",
+ [npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc),
+ aPoint] {
+ npzc->SynthesizeNativeMouseEvent(
+ java::sdk::MotionEvent::ACTION_HOVER_MOVE, aPoint.x, aPoint.y);
+ });
+ return NS_OK;
+}
+
+bool nsWindow::WidgetPaintsBackground() {
+ return StaticPrefs::android_widget_paints_background();
+}
+
+bool nsWindow::NeedsPaint() {
+ auto lvs(mLayerViewSupport.Access());
+ if (!lvs || lvs->CompositorPaused() || !GetLayerManager(nullptr)) {
+ return false;
+ }
+
+ return nsIWidget::NeedsPaint();
+}
+
+void nsWindow::ConfigureAPZControllerThread() {
+ nsCOMPtr<nsISerialEventTarget> thread = mozilla::GetAndroidUiThread();
+ APZThreadUtils::SetControllerThread(thread);
+}
+
+already_AddRefed<GeckoContentController>
+nsWindow::CreateRootContentController() {
+ RefPtr<GeckoContentController> controller =
+ new AndroidContentController(this, mAPZEventState, mAPZC);
+ return controller.forget();
+}
+
+uint32_t nsWindow::GetMaxTouchPoints() const {
+ return java::GeckoAppShell::GetMaxTouchPoints();
+}
+
+void nsWindow::UpdateZoomConstraints(
+ const uint32_t& aPresShellId, const ScrollableLayerGuid::ViewID& aViewId,
+ const mozilla::Maybe<ZoomConstraints>& aConstraints) {
+ nsBaseWidget::UpdateZoomConstraints(aPresShellId, aViewId, aConstraints);
+}
+
+CompositorBridgeChild* nsWindow::GetCompositorBridgeChild() const {
+ return mCompositorSession ? mCompositorSession->GetCompositorBridgeChild()
+ : nullptr;
+}
+
+already_AddRefed<nsIScreen> nsWindow::GetWidgetScreen() {
+ RefPtr<nsIScreen> screen =
+ ScreenHelperAndroid::GetSingleton()->ScreenForId(mScreenId);
+ return screen.forget();
+}
+
+void nsWindow::SetContentDocumentDisplayed(bool aDisplayed) {
+ mContentDocumentDisplayed = aDisplayed;
+}
+
+bool nsWindow::IsContentDocumentDisplayed() {
+ return mContentDocumentDisplayed;
+}
+
+void nsWindow::RecvToolbarAnimatorMessageFromCompositor(int32_t aMessage) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
+ mLayerViewSupport.Access()}) {
+ lvs->RecvToolbarAnimatorMessage(aMessage);
+ }
+}
+
+void nsWindow::UpdateRootFrameMetrics(const ScreenPoint& aScrollOffset,
+ const CSSToScreenScale& aZoom) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
+ mLayerViewSupport.Access()}) {
+ const auto& compositor = lvs->GetJavaCompositor();
+ mContentDocumentDisplayed = true;
+ compositor->UpdateRootFrameMetrics(aScrollOffset.x, aScrollOffset.y,
+ aZoom.scale);
+ }
+}
+
+void nsWindow::RecvScreenPixels(Shmem&& aMem, const ScreenIntSize& aSize,
+ bool aNeedsYFlip) {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+ if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
+ mLayerViewSupport.Access()}) {
+ lvs->RecvScreenPixels(std::move(aMem), aSize, aNeedsYFlip);
+ }
+}
+
+void nsWindow::UpdateDynamicToolbarMaxHeight(ScreenIntCoord aHeight) {
+ if (mDynamicToolbarMaxHeight == aHeight) {
+ return;
+ }
+
+ mDynamicToolbarMaxHeight = aHeight;
+
+ if (mWidgetListener) {
+ mWidgetListener->DynamicToolbarMaxHeightChanged(aHeight);
+ }
+
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->DynamicToolbarMaxHeightChanged(aHeight);
+ }
+}
+
+void nsWindow::UpdateDynamicToolbarOffset(ScreenIntCoord aOffset) {
+ if (mWidgetListener) {
+ mWidgetListener->DynamicToolbarOffsetChanged(aOffset);
+ }
+
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->DynamicToolbarOffsetChanged(aOffset);
+ }
+}
+
+ScreenIntMargin nsWindow::GetSafeAreaInsets() const { return mSafeAreaInsets; }
+
+void nsWindow::UpdateSafeAreaInsets(const ScreenIntMargin& aSafeAreaInsets) {
+ mSafeAreaInsets = aSafeAreaInsets;
+
+ if (mWidgetListener) {
+ mWidgetListener->SafeAreaInsetsChanged(aSafeAreaInsets);
+ }
+
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->SafeAreaInsetsChanged(aSafeAreaInsets);
+ }
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow();
+ return window.forget();
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow();
+ return window.forget();
+}
diff --git a/widget/android/nsWindow.h b/widget/android/nsWindow.h
new file mode 100644
index 0000000000..76b3a7b32d
--- /dev/null
+++ b/widget/android/nsWindow.h
@@ -0,0 +1,279 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * vim: set sw=2 ts=4 expandtab:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIUserIdleServiceInternal.h"
+#include "nsTArray.h"
+#include "EventDispatcher.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/java/GeckoSessionNatives.h"
+#include "mozilla/java/WebResponseWrappers.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TextRange.h"
+#include "mozilla/UniquePtr.h"
+
+struct ANPEvent;
+
+namespace mozilla {
+class WidgetTouchEvent;
+
+namespace layers {
+class CompositorBridgeChild;
+class LayerManager;
+class APZCTreeManager;
+class UiCompositorControllerChild;
+} // namespace layers
+
+namespace widget {
+class AndroidView;
+class GeckoEditableSupport;
+class GeckoViewSupport;
+class LayerViewSupport;
+class NPZCSupport;
+} // namespace widget
+
+namespace ipc {
+class Shmem;
+} // namespace ipc
+
+namespace a11y {
+class SessionAccessibility;
+} // namespace a11y
+} // namespace mozilla
+
+class nsWindow final : public nsBaseWidget {
+ private:
+ virtual ~nsWindow();
+
+ public:
+ using nsBaseWidget::GetLayerManager;
+
+ nsWindow();
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsWindow, nsBaseWidget)
+
+ static void InitNatives();
+ void SetScreenId(uint32_t aScreenId) { mScreenId = aScreenId; }
+ void OnGeckoViewReady();
+ RefPtr<mozilla::MozPromise<bool, bool, false>> OnLoadRequest(
+ nsIURI* aUri, int32_t aWindowType, int32_t aFlags,
+ nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture,
+ bool aIsTopLevel);
+
+ private:
+ uint32_t mScreenId;
+
+ private:
+ RefPtr<mozilla::widget::AndroidView> mAndroidView;
+
+ // Object that implements native LayerView calls.
+ // Owned by the Java Compositor instance.
+ mozilla::jni::NativeWeakPtr<mozilla::widget::LayerViewSupport>
+ mLayerViewSupport;
+
+ // Object that implements native NativePanZoomController calls.
+ // Owned by the Java NativePanZoomController instance.
+ mozilla::jni::NativeWeakPtr<mozilla::widget::NPZCSupport> mNPZCSupport;
+
+ // Object that implements native GeckoEditable calls.
+ // Strong referenced by the Java instance.
+ mozilla::jni::NativeWeakPtr<mozilla::widget::GeckoEditableSupport>
+ mEditableSupport;
+ mozilla::jni::Object::GlobalRef mEditableParent;
+
+ // Object that implements native SessionAccessibility calls.
+ // Strong referenced by the Java instance.
+ mozilla::jni::NativeWeakPtr<mozilla::a11y::SessionAccessibility>
+ mSessionAccessibility;
+
+ // Object that implements native GeckoView calls and associated states.
+ // nullptr for nsWindows that were not opened from GeckoView.
+ mozilla::jni::NativeWeakPtr<mozilla::widget::GeckoViewSupport>
+ mGeckoViewSupport;
+
+ mozilla::Atomic<bool, mozilla::ReleaseAcquire> mContentDocumentDisplayed;
+
+ public:
+ static already_AddRefed<nsWindow> From(nsPIDOMWindowOuter* aDOMWindow);
+ static already_AddRefed<nsWindow> From(nsIWidget* aWidget);
+
+ static nsWindow* TopWindow();
+
+ static mozilla::Modifiers GetModifiers(int32_t aMetaState);
+ static mozilla::TimeStamp GetEventTimeStamp(int64_t aEventTime);
+
+ void OnSizeChanged(const mozilla::gfx::IntSize& aSize);
+
+ void InitEvent(mozilla::WidgetGUIEvent& event,
+ LayoutDeviceIntPoint* aPoint = 0);
+
+ void UpdateOverscrollVelocity(const float aX, const float aY);
+ void UpdateOverscrollOffset(const float aX, const float aY);
+
+ mozilla::widget::EventDispatcher* GetEventDispatcher() const;
+
+ void PassExternalResponse(mozilla::java::WebResponse::Param aResponse);
+
+ void NotifyDisablingWebRender();
+
+ void DetachNatives();
+
+ //
+ // nsIWidget
+ //
+
+ using nsBaseWidget::Create; // for Create signature not overridden here
+ [[nodiscard]] virtual nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) override;
+ virtual void Destroy() override;
+ virtual nsresult ConfigureChildren(
+ const nsTArray<nsIWidget::Configuration>&) override;
+ virtual void SetParent(nsIWidget* aNewParent) override;
+ virtual nsIWidget* GetParent(void) override;
+ virtual float GetDPI() override;
+ virtual double GetDefaultScaleInternal() override;
+ virtual void Show(bool aState) override;
+ virtual bool IsVisible() const override;
+ virtual void ConstrainPosition(bool aAllowSlop, int32_t* aX,
+ int32_t* aY) override;
+ virtual void Move(double aX, double aY) override;
+ virtual void Resize(double aWidth, double aHeight, bool aRepaint) override;
+ virtual void Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) override;
+ void SetZIndex(int32_t aZIndex) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ virtual void Enable(bool aState) override;
+ virtual bool IsEnabled() const override;
+ virtual void Invalidate(const LayoutDeviceIntRect& aRect) override;
+ virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ virtual nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+ nsEventStatus DispatchEvent(mozilla::WidgetGUIEvent* aEvent);
+ virtual already_AddRefed<nsIScreen> GetWidgetScreen() override;
+ virtual nsresult MakeFullScreen(bool aFullScreen,
+ nsIScreen* aTargetScreen = nullptr) override;
+ void SetCursor(nsCursor aDefaultCursor, imgIContainer* aImageCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY) override {}
+ void* GetNativeData(uint32_t aDataType) override;
+ void SetNativeData(uint32_t aDataType, uintptr_t aVal) override;
+ virtual nsresult SetTitle(const nsAString& aTitle) override { return NS_OK; }
+ [[nodiscard]] virtual nsresult GetAttention(int32_t aCycleCount) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ TextEventDispatcherListener* GetNativeTextEventDispatcherListener() override;
+ virtual void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ virtual InputContext GetInputContext() override;
+
+ LayerManager* GetLayerManager(
+ PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
+
+ virtual bool NeedsPaint() override;
+
+ virtual bool WidgetPaintsBackground() override;
+
+ virtual uint32_t GetMaxTouchPoints() const override;
+
+ void UpdateZoomConstraints(
+ const uint32_t& aPresShellId, const ScrollableLayerGuid::ViewID& aViewId,
+ const mozilla::Maybe<ZoomConstraints>& aConstraints) override;
+
+ nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) override;
+ nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) override;
+ nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver) override;
+
+ mozilla::layers::CompositorBridgeChild* GetCompositorBridgeChild() const;
+
+ void SetContentDocumentDisplayed(bool aDisplayed);
+ bool IsContentDocumentDisplayed();
+
+ // Call this function when the users activity is the direct cause of an
+ // event (like a keypress or mouse click).
+ void UserActivity();
+
+ mozilla::jni::Object::Ref& GetEditableParent() { return mEditableParent; }
+
+ RefPtr<mozilla::a11y::SessionAccessibility> GetSessionAccessibility();
+
+ void RecvToolbarAnimatorMessageFromCompositor(int32_t aMessage) override;
+ void UpdateRootFrameMetrics(const ScreenPoint& aScrollOffset,
+ const CSSToScreenScale& aZoom) override;
+ void RecvScreenPixels(mozilla::ipc::Shmem&& aMem, const ScreenIntSize& aSize,
+ bool aNeedsYFlip) override;
+ void UpdateDynamicToolbarMaxHeight(mozilla::ScreenIntCoord aHeight) override;
+ mozilla::ScreenIntCoord GetDynamicToolbarMaxHeight() const override {
+ return mDynamicToolbarMaxHeight;
+ }
+
+ void UpdateDynamicToolbarOffset(mozilla::ScreenIntCoord aOffset);
+
+ virtual mozilla::ScreenIntMargin GetSafeAreaInsets() const override;
+ void UpdateSafeAreaInsets(const mozilla::ScreenIntMargin& aSafeAreaInsets);
+
+ protected:
+ void BringToFront();
+ nsWindow* FindTopLevel();
+ bool IsTopLevel();
+
+ void ConfigureAPZControllerThread() override;
+ void DispatchHitTest(const mozilla::WidgetTouchEvent& aEvent);
+
+ already_AddRefed<GeckoContentController> CreateRootContentController()
+ override;
+
+ bool mIsVisible;
+ nsTArray<nsWindow*> mChildren;
+ nsWindow* mParent;
+
+ nsCOMPtr<nsIUserIdleServiceInternal> mIdleService;
+ mozilla::ScreenIntCoord mDynamicToolbarMaxHeight;
+ mozilla::ScreenIntMargin mSafeAreaInsets;
+
+ bool mIsFullScreen;
+ bool mIsDisablingWebRender;
+
+ bool UseExternalCompositingSurface() const override { return true; }
+
+ static void DumpWindows();
+ static void DumpWindows(const nsTArray<nsWindow*>& wins, int indent = 0);
+ static void LogWindow(nsWindow* win, int index, int indent);
+
+ private:
+ void CreateLayerManager();
+ void RedrawAll();
+
+ mozilla::layers::LayersId GetRootLayerId() const;
+ RefPtr<mozilla::layers::UiCompositorControllerChild>
+ GetUiCompositorControllerChild();
+
+ friend class mozilla::widget::GeckoViewSupport;
+ friend class mozilla::widget::LayerViewSupport;
+ friend class mozilla::widget::NPZCSupport;
+};
+
+#endif /* NSWINDOW_H_ */
diff --git a/widget/cocoa/CFTypeRefPtr.h b/widget/cocoa/CFTypeRefPtr.h
new file mode 100644
index 0000000000..185355777e
--- /dev/null
+++ b/widget/cocoa/CFTypeRefPtr.h
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 CFTypeRefPtr_h
+#define CFTypeRefPtr_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DbgMacro.h"
+#include "mozilla/HashFunctions.h"
+
+// A smart pointer for CoreFoundation classes which does reference counting.
+//
+// Manual reference counting:
+//
+// UInt32 someNumber = 10;
+// CFNumberRef numberObject =
+// CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &someNumber);
+// // do something with numberObject
+// CFRelease(numberObject);
+//
+// Automatic reference counting using CFTypeRefPtr:
+//
+// UInt32 someNumber = 10;
+// auto numberObject =
+// CFTypeRefPtr<CFNumberRef>::WrapUnderCreateRule(
+// CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &someNumber));
+// // do something with numberObject
+// // no CFRelease
+
+template <class PtrT>
+class CFTypeRefPtr {
+ private:
+ void assign_with_CFRetain(PtrT aRawPtr) {
+ CFRetain(aRawPtr);
+ assign_assuming_CFRetain(aRawPtr);
+ }
+
+ void assign_assuming_CFRetain(PtrT aNewPtr) {
+ PtrT oldPtr = mRawPtr;
+ mRawPtr = aNewPtr;
+ if (oldPtr) {
+ CFRelease(oldPtr);
+ }
+ }
+
+ private:
+ PtrT mRawPtr;
+
+ public:
+ ~CFTypeRefPtr() {
+ if (mRawPtr) {
+ CFRelease(mRawPtr);
+ }
+ }
+
+ // Constructors
+
+ CFTypeRefPtr() : mRawPtr(nullptr) {}
+
+ CFTypeRefPtr(const CFTypeRefPtr<PtrT>& aSmartPtr)
+ : mRawPtr(aSmartPtr.mRawPtr) {
+ if (mRawPtr) {
+ CFRetain(mRawPtr);
+ }
+ }
+
+ CFTypeRefPtr(CFTypeRefPtr<PtrT>&& aRefPtr) : mRawPtr(aRefPtr.mRawPtr) {
+ aRefPtr.mRawPtr = nullptr;
+ }
+
+ MOZ_IMPLICIT CFTypeRefPtr(decltype(nullptr)) : mRawPtr(nullptr) {}
+
+ // There is no constructor from a raw pointer value.
+ // Use one of the static WrapUnder*Rule methods below instead.
+
+ static CFTypeRefPtr<PtrT> WrapUnderCreateRule(PtrT aRawPtr) {
+ CFTypeRefPtr<PtrT> ptr;
+ ptr.AssignUnderCreateRule(aRawPtr);
+ return ptr;
+ }
+
+ static CFTypeRefPtr<PtrT> WrapUnderGetRule(PtrT aRawPtr) {
+ CFTypeRefPtr<PtrT> ptr;
+ ptr.AssignUnderGetRule(aRawPtr);
+ return ptr;
+ }
+
+ // Assignment operators
+
+ CFTypeRefPtr<PtrT>& operator=(decltype(nullptr)) {
+ assign_assuming_CFRetain(nullptr);
+ return *this;
+ }
+
+ CFTypeRefPtr<PtrT>& operator=(const CFTypeRefPtr<PtrT>& aRhs) {
+ assign_with_CFRetain(aRhs.mRawPtr);
+ return *this;
+ }
+
+ CFTypeRefPtr<PtrT>& operator=(CFTypeRefPtr<PtrT>&& aRefPtr) {
+ assign_assuming_CFRetain(aRefPtr.mRawPtr);
+ aRefPtr.mRawPtr = nullptr;
+ return *this;
+ }
+
+ // There is no operator= for a raw pointer value.
+ // Use one of the AssignUnder*Rule methods below instead.
+
+ CFTypeRefPtr<PtrT>& AssignUnderCreateRule(PtrT aRawPtr) {
+ // Freshly-created objects come with a retain count of 1.
+ assign_assuming_CFRetain(aRawPtr);
+ return *this;
+ }
+
+ CFTypeRefPtr<PtrT>& AssignUnderGetRule(PtrT aRawPtr) {
+ assign_with_CFRetain(aRawPtr);
+ return *this;
+ }
+
+ // Other pointer operators
+
+ // This is the only way to get the raw pointer out of the smart pointer.
+ // There is no implicit conversion to a raw pointer.
+ PtrT get() const { return mRawPtr; }
+
+ // Don't allow implicit conversion of temporary CFTypeRefPtr to raw pointer,
+ // because the refcount might be one and the pointer will immediately become
+ // invalid.
+ operator PtrT() const&& = delete;
+ // Also don't allow implicit conversion of non-temporary CFTypeRefPtr.
+ operator PtrT() const& = delete;
+
+ // These let you null-check a pointer without calling get().
+ explicit operator bool() const { return !!mRawPtr; }
+};
+
+template <class PtrT>
+inline bool operator==(const CFTypeRefPtr<PtrT>& aLhs,
+ const CFTypeRefPtr<PtrT>& aRhs) {
+ return aLhs.get() == aRhs.get();
+}
+
+template <class PtrT>
+inline bool operator!=(const CFTypeRefPtr<PtrT>& aLhs,
+ const CFTypeRefPtr<PtrT>& aRhs) {
+ return !(aLhs == aRhs);
+}
+
+// Comparing an |CFTypeRefPtr| to |nullptr|
+
+template <class PtrT>
+inline bool operator==(const CFTypeRefPtr<PtrT>& aLhs, decltype(nullptr)) {
+ return aLhs.get() == nullptr;
+}
+
+template <class PtrT>
+inline bool operator==(decltype(nullptr), const CFTypeRefPtr<PtrT>& aRhs) {
+ return nullptr == aRhs.get();
+}
+
+template <class PtrT>
+inline bool operator!=(const CFTypeRefPtr<PtrT>& aLhs, decltype(nullptr)) {
+ return aLhs.get() != nullptr;
+}
+
+template <class PtrT>
+inline bool operator!=(decltype(nullptr), const CFTypeRefPtr<PtrT>& aRhs) {
+ return nullptr != aRhs.get();
+}
+
+// MOZ_DBG support
+
+template <class PtrT>
+std::ostream& operator<<(std::ostream& aOut, const CFTypeRefPtr<PtrT>& aObj) {
+ return mozilla::DebugValue(aOut, aObj.get());
+}
+
+// std::hash support (e.g. for unordered_map)
+namespace std {
+template <class PtrT>
+struct hash<CFTypeRefPtr<PtrT>> {
+ typedef CFTypeRefPtr<PtrT> argument_type;
+ typedef std::size_t result_type;
+ result_type operator()(argument_type const& aPtr) const {
+ return mozilla::HashGeneric(reinterpret_cast<uintptr_t>(aPtr.get()));
+ }
+};
+} // namespace std
+
+#endif /* CFTypeRefPtr_h */
diff --git a/widget/cocoa/CustomCocoaEvents.h b/widget/cocoa/CustomCocoaEvents.h
new file mode 100644
index 0000000000..3c02feb4b0
--- /dev/null
+++ b/widget/cocoa/CustomCocoaEvents.h
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file defines constants to be used in the "subtype" field of
+ * NSEventTypeApplicationDefined type NSEvents.
+ */
+
+#ifndef WIDGET_COCOA_CUSTOMCOCOAEVENTS_H_
+#define WIDGET_COCOA_CUSTOMCOCOAEVENTS_H_
+
+// Empty event, just used for prodding the event loop into responding.
+const short kEventSubtypeNone = 0;
+// Tracer event, used for timing the event loop responsiveness.
+const short kEventSubtypeTrace = 1;
+
+#endif /* WIDGET_COCOA_CUSTOMCOCOAEVENTS_H_ */
diff --git a/widget/cocoa/DesktopBackgroundImage.h b/widget/cocoa/DesktopBackgroundImage.h
new file mode 100644
index 0000000000..2fd7565369
--- /dev/null
+++ b/widget/cocoa/DesktopBackgroundImage.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 WIDGET_COCOA_DESKTOPBACKGROUNDIMAGE_H_
+#define WIDGET_COCOA_DESKTOPBACKGROUNDIMAGE_H_
+
+class nsIFile;
+
+namespace mozilla {
+namespace widget {
+
+void SetDesktopImage(nsIFile* aImage);
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // WIDGET_COCOA_DESKTOPBACKGROUNDIMAGE_H_
diff --git a/widget/cocoa/DesktopBackgroundImage.mm b/widget/cocoa/DesktopBackgroundImage.mm
new file mode 100644
index 0000000000..5ebb7ea938
--- /dev/null
+++ b/widget/cocoa/DesktopBackgroundImage.mm
@@ -0,0 +1,68 @@
+/* -*- 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 "mozilla/Logging.h"
+#include "nsCocoaUtils.h"
+#include "nsIFile.h"
+#include "DesktopBackgroundImage.h"
+
+#import <Foundation/Foundation.h>
+
+extern mozilla::LazyLogModule gCocoaUtilsLog;
+#undef LOG
+#define LOG(...) MOZ_LOG(gCocoaUtilsLog, LogLevel::Debug, (__VA_ARGS__))
+
+namespace mozilla {
+namespace widget {
+
+void SetDesktopImage(nsIFile* aImage) {
+ nsAutoCString imagePath;
+ nsresult rv = aImage->GetNativePath(imagePath);
+ if (NS_FAILED(rv)) {
+ LOG("%s ERROR: failed to get image path", __func__);
+ return;
+ }
+
+ bool exists = false;
+ rv = aImage->Exists(&exists);
+ if (NS_FAILED(rv) || !exists) {
+ LOG("%s ERROR: file \"%s\" does not exist", __func__, imagePath.get());
+ return;
+ }
+
+ NSString* urlString = [NSString stringWithUTF8String:imagePath.get()];
+ if (!urlString) {
+ LOG("%s ERROR: null image path \"%s\"", __func__, imagePath.get());
+ return;
+ }
+
+ NSURL* url = [NSURL fileURLWithPath:urlString];
+ if (!url) {
+ LOG("%s ERROR: null image path URL \"%s\"", __func__, imagePath.get());
+ return;
+ }
+
+ // Only apply the background to the screen with focus
+ NSScreen* currentScreen = [NSScreen mainScreen];
+ if (!currentScreen) {
+ LOG("%s ERROR: got null NSScreen", __func__);
+ return;
+ }
+
+ // Use existing options for this screen
+ NSDictionary* screenOptions =
+ [[NSWorkspace sharedWorkspace] desktopImageOptionsForScreen:currentScreen];
+
+ NSError* error = nil;
+ if (![[NSWorkspace sharedWorkspace] setDesktopImageURL:url
+ forScreen:currentScreen
+ options:screenOptions
+ error:&error]) {
+ LOG("%s ERROR: setDesktopImageURL failed (%ld)", __func__, (long)[error code]);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/cocoa/GfxInfo.h b/widget/cocoa/GfxInfo.h
new file mode 100644
index 0000000000..5399ef5537
--- /dev/null
+++ b/widget/cocoa/GfxInfo.h
@@ -0,0 +1,102 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __mozilla_widget_GfxInfo_h__
+#define __mozilla_widget_GfxInfo_h__
+
+#include "GfxInfoBase.h"
+
+#include "nsString.h"
+
+namespace mozilla {
+namespace widget {
+
+class GfxInfo : public GfxInfoBase {
+ public:
+ GfxInfo();
+ // We only declare the subset of nsIGfxInfo that we actually implement. The
+ // rest is brought forward from GfxInfoBase.
+ NS_IMETHOD GetD2DEnabled(bool* aD2DEnabled) override;
+ NS_IMETHOD GetDWriteEnabled(bool* aDWriteEnabled) override;
+ NS_IMETHOD GetDWriteVersion(nsAString& aDwriteVersion) override;
+ NS_IMETHOD GetEmbeddedInFirefoxReality(
+ bool* aEmbeddedInFirefoxReality) override;
+ NS_IMETHOD GetHasBattery(bool* aHasBattery) override;
+ NS_IMETHOD GetWindowProtocol(nsAString& aWindowProtocol) override;
+ NS_IMETHOD GetDesktopEnvironment(nsAString& aDesktopEnvironment) override;
+ NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override;
+ NS_IMETHOD GetAdapterDescription(nsAString& aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver(nsAString& aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID(nsAString& aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID(nsAString& aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID(nsAString& aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM(uint32_t* aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) override;
+ NS_IMETHOD GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate(nsAString& aAdapterDriverDate) override;
+ NS_IMETHOD GetAdapterDescription2(nsAString& aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver2(nsAString& aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID2(nsAString& aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID2(nsAString& aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID2(nsAString& aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM2(uint32_t* aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) override;
+ NS_IMETHOD GetAdapterDriverVersion2(
+ nsAString& aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate2(nsAString& aAdapterDriverDate) override;
+ NS_IMETHOD GetIsGPU2Active(bool* aIsGPU2Active) override;
+ NS_IMETHOD GetDisplayInfo(nsTArray<nsString>& aDisplayInfo) override;
+ NS_IMETHOD GetDisplayWidth(nsTArray<uint32_t>& aDisplayWidth) override;
+ NS_IMETHOD GetDisplayHeight(nsTArray<uint32_t>& aDisplayHeight) override;
+ NS_IMETHOD GetDrmRenderDevice(nsACString& aDrmRenderDevice) override;
+
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+
+ virtual nsresult Init() override;
+
+#ifdef DEBUG
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+ virtual uint32_t OperatingSystemVersion() override { return mOSXVersion; }
+
+ nsresult FindMonitors(JSContext* cx, JS::HandleObject array) override;
+
+ protected:
+ virtual ~GfxInfo() {}
+
+ virtual nsresult GetFeatureStatusImpl(
+ int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
+ OperatingSystem* aOS = nullptr) override;
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() override;
+
+ private:
+ void GetDeviceInfo();
+ void GetSelectedCityInfo();
+ void AddCrashReportAnnotations();
+
+ uint32_t mNumGPUsDetected;
+
+ uint32_t mAdapterRAM[2];
+ nsString mDeviceID[2];
+ nsString mDriverVersion[2];
+ nsString mDriverDate[2];
+ nsString mDeviceKey[2];
+
+ nsString mAdapterVendorID[2];
+ nsString mAdapterDeviceID[2];
+
+ uint32_t mOSXVersion;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_GfxInfo_h__ */
diff --git a/widget/cocoa/GfxInfo.mm b/widget/cocoa/GfxInfo.mm
new file mode 100644
index 0000000000..7035e74875
--- /dev/null
+++ b/widget/cocoa/GfxInfo.mm
@@ -0,0 +1,560 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <OpenGL/OpenGL.h>
+#include <OpenGL/CGLRenderers.h>
+
+#include "mozilla/ArrayUtils.h"
+
+#include "GfxInfo.h"
+#include "nsUnicharUtils.h"
+#include "nsExceptionHandler.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include "mozilla/Preferences.h"
+#include <algorithm>
+
+#import <Foundation/Foundation.h>
+#import <IOKit/IOKitLib.h>
+#import <Cocoa/Cocoa.h>
+
+#include "jsapi.h"
+
+#define NS_CRASHREPORTER_CONTRACTID "@mozilla.org/toolkit/crash-reporter;1"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#ifdef DEBUG
+NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug)
+#endif
+
+GfxInfo::GfxInfo() : mNumGPUsDetected(0), mOSXVersion{0} { mAdapterRAM[0] = mAdapterRAM[1] = 0; }
+
+static OperatingSystem OSXVersionToOperatingSystem(uint32_t aOSXVersion) {
+ switch (nsCocoaFeatures::ExtractMajorVersion(aOSXVersion)) {
+ case 10:
+ switch (nsCocoaFeatures::ExtractMinorVersion(aOSXVersion)) {
+ case 6:
+ return OperatingSystem::OSX10_6;
+ case 7:
+ return OperatingSystem::OSX10_7;
+ case 8:
+ return OperatingSystem::OSX10_8;
+ case 9:
+ return OperatingSystem::OSX10_9;
+ case 10:
+ return OperatingSystem::OSX10_10;
+ case 11:
+ return OperatingSystem::OSX10_11;
+ case 12:
+ return OperatingSystem::OSX10_12;
+ case 13:
+ return OperatingSystem::OSX10_13;
+ case 14:
+ return OperatingSystem::OSX10_14;
+ case 15:
+ return OperatingSystem::OSX10_15;
+ case 16:
+ // Depending on the SDK version, we either get 10.16 or 11.0.
+ // Normalize this to 11.0.
+ return OperatingSystem::OSX11_0;
+ default:
+ break;
+ }
+ break;
+ case 11:
+ switch (nsCocoaFeatures::ExtractMinorVersion(aOSXVersion)) {
+ case 0:
+ return OperatingSystem::OSX11_0;
+ default:
+ break;
+ }
+ break;
+ }
+
+ return OperatingSystem::Unknown;
+}
+// The following three functions are derived from Chromium code
+static CFTypeRef SearchPortForProperty(io_registry_entry_t dspPort, CFStringRef propertyName) {
+ return IORegistryEntrySearchCFProperty(dspPort, kIOServicePlane, propertyName,
+ kCFAllocatorDefault,
+ kIORegistryIterateRecursively | kIORegistryIterateParents);
+}
+
+static uint32_t IntValueOfCFData(CFDataRef d) {
+ uint32_t value = 0;
+
+ if (d) {
+ const uint32_t* vp = reinterpret_cast<const uint32_t*>(CFDataGetBytePtr(d));
+ if (vp != NULL) value = *vp;
+ }
+
+ return value;
+}
+
+void GfxInfo::GetDeviceInfo() {
+ mNumGPUsDetected = 0;
+
+ CFMutableDictionaryRef pci_dev_dict = IOServiceMatching("IOPCIDevice");
+ io_iterator_t io_iter;
+ if (IOServiceGetMatchingServices(kIOMasterPortDefault, pci_dev_dict, &io_iter) !=
+ kIOReturnSuccess) {
+ MOZ_DIAGNOSTIC_ASSERT(false,
+ "Failed to detect any GPUs (couldn't enumerate IOPCIDevice services)");
+ return;
+ }
+
+ io_registry_entry_t entry = IO_OBJECT_NULL;
+ while ((entry = IOIteratorNext(io_iter)) != IO_OBJECT_NULL) {
+ constexpr uint32_t kClassCodeDisplayVGA = 0x30000;
+ CFTypeRef class_code_ref = SearchPortForProperty(entry, CFSTR("class-code"));
+ if (class_code_ref) {
+ const uint32_t class_code = IntValueOfCFData((CFDataRef)class_code_ref);
+ CFRelease(class_code_ref);
+
+ if (class_code == kClassCodeDisplayVGA) {
+ CFTypeRef vendor_id_ref = SearchPortForProperty(entry, CFSTR("vendor-id"));
+ if (vendor_id_ref) {
+ mAdapterVendorID[mNumGPUsDetected].AppendPrintf(
+ "0x%04x", IntValueOfCFData((CFDataRef)vendor_id_ref));
+ CFRelease(vendor_id_ref);
+ }
+ CFTypeRef device_id_ref = SearchPortForProperty(entry, CFSTR("device-id"));
+ if (device_id_ref) {
+ mAdapterDeviceID[mNumGPUsDetected].AppendPrintf(
+ "0x%04x", IntValueOfCFData((CFDataRef)device_id_ref));
+ CFRelease(device_id_ref);
+ }
+ ++mNumGPUsDetected;
+ }
+ }
+ IOObjectRelease(entry);
+ if (mNumGPUsDetected == 2) {
+ break;
+ }
+ }
+ IOObjectRelease(io_iter);
+
+#if defined(__aarch64__)
+ // If we found IOPCI VGA devices, don't look for AGXAccelerator devices
+ if (mNumGPUsDetected > 0) {
+ return;
+ }
+
+ CFMutableDictionaryRef agx_dev_dict = IOServiceMatching("AGXAccelerator");
+ if (IOServiceGetMatchingServices(kIOMasterPortDefault, agx_dev_dict, &io_iter) ==
+ kIOReturnSuccess) {
+ io_registry_entry_t entry = IO_OBJECT_NULL;
+ while ((entry = IOIteratorNext(io_iter)) != IO_OBJECT_NULL) {
+ CFTypeRef vendor_id_ref = SearchPortForProperty(entry, CFSTR("vendor-id"));
+ if (vendor_id_ref) {
+ mAdapterVendorID[mNumGPUsDetected].AppendPrintf("0x%04x",
+ IntValueOfCFData((CFDataRef)vendor_id_ref));
+ CFRelease(vendor_id_ref);
+ ++mNumGPUsDetected;
+ }
+ IOObjectRelease(entry);
+ }
+
+ IOObjectRelease(io_iter);
+ }
+#endif
+
+ MOZ_DIAGNOSTIC_ASSERT(mNumGPUsDetected > 0, "Failed to detect any GPUs");
+}
+
+nsresult GfxInfo::Init() {
+ nsresult rv = GfxInfoBase::Init();
+
+ // Calling CGLQueryRendererInfo causes us to switch to the discrete GPU
+ // even when we don't want to. We'll avoid doing so for now and just
+ // use the device ids.
+
+ GetDeviceInfo();
+
+ AddCrashReportAnnotations();
+
+ mOSXVersion = nsCocoaFeatures::macOSVersion();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetD2DEnabled(bool* aEnabled) { return NS_ERROR_FAILURE; }
+
+NS_IMETHODIMP
+GfxInfo::GetDWriteEnabled(bool* aEnabled) { return NS_ERROR_FAILURE; }
+
+/* readonly attribute bool HasBattery; */
+NS_IMETHODIMP GfxInfo::GetHasBattery(bool* aHasBattery) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+/* readonly attribute DOMString DWriteVersion; */
+NS_IMETHODIMP
+GfxInfo::GetDWriteVersion(nsAString& aDwriteVersion) { return NS_ERROR_FAILURE; }
+
+NS_IMETHODIMP
+GfxInfo::GetEmbeddedInFirefoxReality(bool* aEmbeddedInFirefoxReality) { return NS_ERROR_FAILURE; }
+
+/* readonly attribute DOMString cleartypeParameters; */
+NS_IMETHODIMP
+GfxInfo::GetCleartypeParameters(nsAString& aCleartypeParams) { return NS_ERROR_FAILURE; }
+
+/* readonly attribute DOMString windowProtocol; */
+NS_IMETHODIMP
+GfxInfo::GetWindowProtocol(nsAString& aWindowProtocol) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+/* readonly attribute DOMString desktopEnvironment; */
+NS_IMETHODIMP
+GfxInfo::GetDesktopEnvironment(nsAString& aDesktopEnvironment) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+/* readonly attribute DOMString adapterDescription; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription(nsAString& aAdapterDescription) {
+ aAdapterDescription.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDescription2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription2(nsAString& aAdapterDescription) {
+ if (mNumGPUsDetected < 2) {
+ return NS_ERROR_FAILURE;
+ }
+ aAdapterDescription.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterRAM; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM(uint32_t* aAdapterRAM) {
+ *aAdapterRAM = mAdapterRAM[0];
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterRAM2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(uint32_t* aAdapterRAM) {
+ if (mNumGPUsDetected < 2) {
+ return NS_ERROR_FAILURE;
+ }
+ *aAdapterRAM = mAdapterRAM[1];
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriver; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver(nsAString& aAdapterDriver) {
+ aAdapterDriver.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriver2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver2(nsAString& aAdapterDriver) {
+ if (mNumGPUsDetected < 2) {
+ return NS_ERROR_FAILURE;
+ }
+ aAdapterDriver.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriverVendor; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) {
+ aAdapterDriverVendor.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriverVendor2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) {
+ if (mNumGPUsDetected < 2) {
+ return NS_ERROR_FAILURE;
+ }
+ aAdapterDriverVendor.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriverVersion; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) {
+ aAdapterDriverVersion.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriverVersion2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion2(nsAString& aAdapterDriverVersion) {
+ if (mNumGPUsDetected < 2) {
+ return NS_ERROR_FAILURE;
+ }
+ aAdapterDriverVersion.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriverDate; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate(nsAString& aAdapterDriverDate) {
+ aAdapterDriverDate.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriverDate2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate2(nsAString& aAdapterDriverDate) {
+ if (mNumGPUsDetected < 2) {
+ return NS_ERROR_FAILURE;
+ }
+ aAdapterDriverDate.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterVendorID; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID(nsAString& aAdapterVendorID) {
+ aAdapterVendorID = mAdapterVendorID[0];
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterVendorID2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString& aAdapterVendorID) {
+ if (mNumGPUsDetected < 2) {
+ return NS_ERROR_FAILURE;
+ }
+ aAdapterVendorID = mAdapterVendorID[1];
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDeviceID; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString& aAdapterDeviceID) {
+ aAdapterDeviceID = mAdapterDeviceID[0];
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDeviceID2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID2(nsAString& aAdapterDeviceID) {
+ if (mNumGPUsDetected < 2) {
+ return NS_ERROR_FAILURE;
+ }
+ aAdapterDeviceID = mAdapterDeviceID[1];
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterSubsysID; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID(nsAString& aAdapterSubsysID) { return NS_ERROR_FAILURE; }
+
+/* readonly attribute DOMString adapterSubsysID2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID2(nsAString& aAdapterSubsysID) { return NS_ERROR_FAILURE; }
+
+/* readonly attribute Array<DOMString> displayInfo; */
+NS_IMETHODIMP
+GfxInfo::GetDisplayInfo(nsTArray<nsString>& aDisplayInfo) {
+ nsAutoreleasePool localPool;
+ for (NSScreen* screen in [NSScreen screens]) {
+ NSRect rect = [screen frame];
+ nsString desc;
+ desc.AppendPrintf("%dx%d scale:%f", (int32_t)rect.size.width, (int32_t)rect.size.height,
+ nsCocoaUtils::GetBackingScaleFactor(screen));
+ aDisplayInfo.AppendElement(desc);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDisplayWidth(nsTArray<uint32_t>& aDisplayWidth) {
+ nsAutoreleasePool localPool;
+ for (NSScreen* screen in [NSScreen screens]) {
+ NSRect rect = [screen frame];
+ aDisplayWidth.AppendElement((uint32_t)rect.size.width);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDisplayHeight(nsTArray<uint32_t>& aDisplayHeight) {
+ nsAutoreleasePool localPool;
+ for (NSScreen* screen in [NSScreen screens]) {
+ NSRect rect = [screen frame];
+ aDisplayHeight.AppendElement((uint32_t)rect.size.height);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDrmRenderDevice(nsACString& aDrmRenderDevice) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+/* readonly attribute boolean isGPU2Active; */
+NS_IMETHODIMP
+GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active) { return NS_ERROR_FAILURE; }
+
+void GfxInfo::AddCrashReportAnnotations() {
+ nsString deviceID, vendorID, driverVersion;
+ nsAutoCString narrowDeviceID, narrowVendorID, narrowDriverVersion;
+
+ GetAdapterDeviceID(deviceID);
+ CopyUTF16toUTF8(deviceID, narrowDeviceID);
+ GetAdapterVendorID(vendorID);
+ CopyUTF16toUTF8(vendorID, narrowVendorID);
+ GetAdapterDriverVersion(driverVersion);
+ CopyUTF16toUTF8(driverVersion, narrowDriverVersion);
+
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterVendorID, narrowVendorID);
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterDeviceID, narrowDeviceID);
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterDriverVersion,
+ narrowDriverVersion);
+}
+
+// We don't support checking driver versions on Mac.
+#define IMPLEMENT_MAC_DRIVER_BLOCKLIST(os, device, features, blockOn, ruleId) \
+ APPEND_TO_DRIVER_BLOCKLIST(os, device, features, blockOn, DRIVER_COMPARISON_IGNORED, \
+ V(0, 0, 0, 0), ruleId, "")
+
+const nsTArray<GfxDriverInfo>& GfxInfo::GetGfxDriverInfo() {
+ if (!sDriverInfo->Length()) {
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(
+ OperatingSystem::OSX, DeviceFamily::RadeonX1000, nsIGfxInfo::FEATURE_OPENGL_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, "FEATURE_FAILURE_MAC_RADEONX1000_NO_TEXTURE2D");
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(
+ OperatingSystem::OSX, DeviceFamily::Geforce7300GT, nsIGfxInfo::FEATURE_WEBGL_OPENGL,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, "FEATURE_FAILURE_MAC_7300_NO_WEBGL");
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(OperatingSystem::OSX, DeviceFamily::IntelHDGraphicsToIvyBridge,
+ nsIGfxInfo::FEATURE_GL_SWIZZLE,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ "FEATURE_FAILURE_MAC_INTELHD4000_NO_SWIZZLE");
+ // We block texture swizzling everwhere on mac because it's broken in some configurations
+ // and we want to support GPU switching.
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(
+ OperatingSystem::OSX, DeviceFamily::All, nsIGfxInfo::FEATURE_GL_SWIZZLE,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, "FEATURE_FAILURE_MAC_GPU_SWITCHING_NO_SWIZZLE");
+
+ // FEATURE_WEBRENDER - ALLOWLIST
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(OperatingSystem::OSX, DeviceFamily::IntelRolloutWebRender,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_ALLOW_ALWAYS,
+ "FEATURE_ROLLOUT_INTEL_MAC");
+ // Intel HD3000 disabled due to bug 1661505
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(
+ OperatingSystem::OSX, DeviceFamily::IntelSandyBridge, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, "FEATURE_FAILURE_INTEL_MAC_HD3000_NO_WEBRENDER");
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(OperatingSystem::OSX, DeviceFamily::AtiRolloutWebRender,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_ALLOW_ALWAYS,
+ "FEATURE_ROLLOUT_AMD_MAC");
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(OperatingSystem::OSX, DeviceFamily::NvidiaRolloutWebRender,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_ALLOW_ALWAYS,
+ "FEATURE_ROLLOUT_NVIDIA_MAC");
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(OperatingSystem::OSX, DeviceFamily::AppleAll,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_ALLOW_ALWAYS,
+ "FEATURE_ROLLOUT_APPLE_SILICON_MAC");
+ }
+ return *sDriverInfo;
+}
+
+nsresult GfxInfo::GetFeatureStatusImpl(int32_t aFeature, int32_t* aStatus,
+ nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ nsACString& aFailureId,
+ OperatingSystem* aOS /* = nullptr */) {
+ NS_ENSURE_ARG_POINTER(aStatus);
+ aSuggestedDriverVersion.SetIsVoid(true);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ OperatingSystem os = OSXVersionToOperatingSystem(mOSXVersion);
+ if (aOS) *aOS = os;
+
+ if (sShutdownOccurred) {
+ return NS_OK;
+ }
+
+ // Don't evaluate special cases when we're evaluating the downloaded blocklist.
+ if (!aDriverInfo.Length()) {
+ if (aFeature == nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION) {
+ // See bug 1249659
+ switch (os) {
+ case OperatingSystem::OSX10_5:
+ case OperatingSystem::OSX10_6:
+ case OperatingSystem::OSX10_7:
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION;
+ aFailureId = "FEATURE_FAILURE_CANVAS_OSX_VERSION";
+ break;
+ default:
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ break;
+ }
+ return NS_OK;
+ } else if (aFeature == nsIGfxInfo::FEATURE_WEBRENDER &&
+ nsCocoaFeatures::ProcessIsRosettaTranslated()) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_UNQUALIFIED_WEBRENDER_MAC_ROSETTA";
+ return NS_OK;
+ }
+ }
+
+ return GfxInfoBase::GetFeatureStatusImpl(aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo,
+ aFailureId, &os);
+}
+
+nsresult GfxInfo::FindMonitors(JSContext* aCx, JS::HandleObject aOutArray) {
+ nsAutoreleasePool localPool;
+ // Getting the refresh rate is a little hard on OS X. We could use
+ // CVDisplayLinkGetNominalOutputVideoRefreshPeriod, but that's a little
+ // involved. Ideally we could query it from vsync. For now, we leave it out.
+ int32_t deviceCount = 0;
+ for (NSScreen* screen in [NSScreen screens]) {
+ NSRect rect = [screen frame];
+
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+
+ JS::Rooted<JS::Value> screenWidth(aCx, JS::Int32Value((int)rect.size.width));
+ JS_SetProperty(aCx, obj, "screenWidth", screenWidth);
+
+ JS::Rooted<JS::Value> screenHeight(aCx, JS::Int32Value((int)rect.size.height));
+ JS_SetProperty(aCx, obj, "screenHeight", screenHeight);
+
+ JS::Rooted<JS::Value> scale(aCx, JS::NumberValue(nsCocoaUtils::GetBackingScaleFactor(screen)));
+ JS_SetProperty(aCx, obj, "scale", scale);
+
+ JS::Rooted<JS::Value> element(aCx, JS::ObjectValue(*obj));
+ JS_SetElement(aCx, aOutArray, deviceCount++, element);
+ }
+ return NS_OK;
+}
+
+#ifdef DEBUG
+
+// Implement nsIGfxInfoDebug
+
+/* void spoofVendorID (in DOMString aVendorID); */
+NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString& aVendorID) {
+ mAdapterVendorID[0] = aVendorID;
+ return NS_OK;
+}
+
+/* void spoofDeviceID (in unsigned long aDeviceID); */
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString& aDeviceID) {
+ mAdapterDeviceID[0] = aDeviceID;
+ return NS_OK;
+}
+
+/* void spoofDriverVersion (in DOMString aDriverVersion); */
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString& aDriverVersion) {
+ mDriverVersion[0] = aDriverVersion;
+ return NS_OK;
+}
+
+/* void spoofOSVersion (in unsigned long aVersion); */
+NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion) {
+ mOSXVersion = aVersion;
+ return NS_OK;
+}
+
+/* void fireTestProcess (); */
+NS_IMETHODIMP GfxInfo::FireTestProcess() { return NS_OK; }
+
+#endif
diff --git a/widget/cocoa/IconLoaderHelperCocoa.h b/widget/cocoa/IconLoaderHelperCocoa.h
new file mode 100644
index 0000000000..6becaa7ba1
--- /dev/null
+++ b/widget/cocoa/IconLoaderHelperCocoa.h
@@ -0,0 +1,72 @@
+/* -*- 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_IconLoaderHelperCocoa_h
+#define mozilla_widget_IconLoaderHelperCocoa_h
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/widget/IconLoader.h"
+
+namespace mozilla::widget {
+
+/**
+ * Classes that want to hear about when icons load should subclass
+ * IconLoaderListenerCocoa, and implement the OnComplete() method,
+ * which will be called once the load of the icon has completed.
+ */
+class IconLoaderListenerCocoa {
+ public:
+ IconLoaderListenerCocoa() = default;
+
+ NS_INLINE_DECL_REFCOUNTING(mozilla::widget::IconLoaderListenerCocoa)
+
+ virtual nsresult OnComplete() = 0;
+
+ protected:
+ virtual ~IconLoaderListenerCocoa() = default;
+};
+
+/**
+ * This is a Helper used with mozilla::widget::IconLoader that implements the
+ * macOS-specific functionality for converting a loaded icon into an NSImage*.
+ */
+class IconLoaderHelperCocoa final : public mozilla::widget::IconLoader::Helper {
+ public:
+ IconLoaderHelperCocoa(mozilla::widget::IconLoaderListenerCocoa* aLoadListener,
+ uint32_t aIconHeight, uint32_t aIconWidth, CGFloat aScaleFactor = 0.0f);
+
+ NS_DECL_ISUPPORTS
+
+ nsresult OnComplete(imgIContainer* aImage, const nsIntRect& aRect) override;
+
+ /**
+ * IconLoaderHelperCocoa will default the NSImage* returned by
+ * GetNativeIconImage to an empty icon. Once the load of the icon
+ * by IconLoader has completed, GetNativeIconImage will return the
+ * loaded icon.
+ *
+ * Note that IconLoaderHelperCocoa owns this NSImage. If you don't
+ * need it to hold onto the NSImage anymore, call Destroy on it to
+ * deallocate. The IconLoaderHelperCocoa destructor will also deallocate
+ * the NSImage if necessary.
+ */
+ NSImage* GetNativeIconImage(); // Owned by IconLoaderHelperCocoa
+ void Destroy();
+
+ protected:
+ ~IconLoaderHelperCocoa();
+
+ private:
+ RefPtr<mozilla::widget::IconLoaderListenerCocoa> mLoadListener;
+ uint32_t mIconHeight;
+ uint32_t mIconWidth;
+ CGFloat mScaleFactor;
+ NSImage* mNativeIconImage;
+};
+
+} // namespace mozilla::widget
+
+#endif // mozilla_widget_IconLoaderHelperCocoa_h
diff --git a/widget/cocoa/IconLoaderHelperCocoa.mm b/widget/cocoa/IconLoaderHelperCocoa.mm
new file mode 100644
index 0000000000..0d379b8e9f
--- /dev/null
+++ b/widget/cocoa/IconLoaderHelperCocoa.mm
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Retrieves and displays icons in native menu items on Mac OS X.
+ */
+
+/* exception_defines.h defines 'try' to 'if (true)' which breaks objective-c
+ exceptions and produces errors like: error: unexpected '@' in program'.
+ If we define __EXCEPTIONS exception_defines.h will avoid doing this.
+
+ See bug 666609 for more information.
+
+ We use <limits> to get the libstdc++ version. */
+#include <limits>
+#if __GLIBCXX__ <= 20070719
+# ifndef __EXCEPTIONS
+# define __EXCEPTIONS
+# endif
+#endif
+
+#include "gfxPlatform.h"
+#include "imgIContainer.h"
+#include "imgLoader.h"
+#include "imgRequestProxy.h"
+#include "mozilla/dom/Document.h"
+#include "nsCocoaUtils.h"
+#include "nsContentUtils.h"
+#include "nsIContent.h"
+#include "nsNameSpaceManager.h"
+#include "nsNetUtil.h"
+#include "nsObjCExceptions.h"
+#include "nsThreadUtils.h"
+#include "nsToolkit.h"
+#include "IconLoaderHelperCocoa.h"
+
+using namespace mozilla;
+
+using mozilla::gfx::SourceSurface;
+using mozilla::widget::IconLoaderListenerCocoa;
+
+namespace mozilla::widget {
+
+NS_IMPL_ISUPPORTS0(IconLoaderHelperCocoa)
+
+IconLoaderHelperCocoa::IconLoaderHelperCocoa(IconLoaderListenerCocoa* aListener,
+ uint32_t aIconHeight, uint32_t aIconWidth,
+ CGFloat aScaleFactor)
+ : mLoadListener(aListener),
+ mIconHeight(aIconHeight),
+ mIconWidth(aIconWidth),
+ mScaleFactor(aScaleFactor) {
+ // Placeholder icon, which will later be replaced.
+ mNativeIconImage = [[NSImage alloc] initWithSize:NSMakeSize(mIconHeight, mIconWidth)];
+ MOZ_ASSERT(aListener);
+}
+
+IconLoaderHelperCocoa::~IconLoaderHelperCocoa() { Destroy(); }
+
+nsresult IconLoaderHelperCocoa::OnComplete(imgIContainer* aImage, const nsIntRect& aRect) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT
+
+ NS_ENSURE_ARG_POINTER(aImage);
+
+ bool isEntirelyBlack = false;
+ NSImage* newImage = nil;
+ nsresult rv;
+ if (mScaleFactor != 0.0f) {
+ rv = nsCocoaUtils::CreateNSImageFromImageContainer(aImage, imgIContainer::FRAME_CURRENT,
+ &newImage, mScaleFactor, &isEntirelyBlack);
+ } else {
+ rv = nsCocoaUtils::CreateDualRepresentationNSImageFromImageContainer(
+ aImage, imgIContainer::FRAME_CURRENT, &newImage, &isEntirelyBlack);
+ }
+
+ if (NS_FAILED(rv) || !newImage) {
+ mNativeIconImage = nil;
+ [newImage release];
+ return NS_ERROR_FAILURE;
+ }
+
+ NSSize requestedSize = NSMakeSize(mIconWidth, mIconHeight);
+
+ int32_t origWidth = 0, origHeight = 0;
+ aImage->GetWidth(&origWidth);
+ aImage->GetHeight(&origHeight);
+
+ bool createSubImage =
+ !(aRect.x == 0 && aRect.y == 0 && aRect.width == origWidth && aRect.height == origHeight);
+
+ if (createSubImage) {
+ // If aRect is set using CSS, we need to slice a piece out of the
+ // overall image to use as the icon.
+ NSImage* subImage =
+ [NSImage imageWithSize:requestedSize
+ flipped:NO
+ drawingHandler:^BOOL(NSRect subImageRect) {
+ [newImage drawInRect:NSMakeRect(0, 0, mIconWidth, mIconHeight)
+ fromRect:NSMakeRect(aRect.x, aRect.y, aRect.width, aRect.height)
+ operation:NSCompositingOperationCopy
+ fraction:1.0f];
+ return YES;
+ }];
+ [newImage release];
+ newImage = [subImage retain];
+ subImage = nil;
+ }
+
+ // If all the color channels in the image are black, treat the image as a
+ // template. This will cause macOS to use the image's alpha channel as a mask
+ // and it will fill it with a color that looks good in the context that it's
+ // used in. For example, for regular menu items, the image will be black, but
+ // when the menu item is hovered (and its background is blue), it will be
+ // filled with white.
+ [newImage setTemplate:isEntirelyBlack];
+
+ [newImage setSize:requestedSize];
+
+ NSImage* placeholderImage = mNativeIconImage;
+ mNativeIconImage = newImage;
+ [placeholderImage release];
+ placeholderImage = nil;
+
+ mLoadListener->OnComplete();
+
+ return NS_OK;
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT
+}
+
+NSImage* IconLoaderHelperCocoa::GetNativeIconImage() { return mNativeIconImage; }
+
+void IconLoaderHelperCocoa::Destroy() {
+ if (mNativeIconImage) {
+ [mNativeIconImage release];
+ mNativeIconImage = nil;
+ }
+}
+
+} // namespace mozilla::widget
diff --git a/widget/cocoa/MediaHardwareKeysEventSourceMac.h b/widget/cocoa/MediaHardwareKeysEventSourceMac.h
new file mode 100644
index 0000000000..da08b8108d
--- /dev/null
+++ b/widget/cocoa/MediaHardwareKeysEventSourceMac.h
@@ -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/. */
+
+#ifndef WIDGET_COCOA_MEDIAHARDWAREKEYSEVENTSOURCEMAC_H_
+#define WIDGET_COCOA_MEDIAHARDWAREKEYSEVENTSOURCEMAC_H_
+
+#import <ApplicationServices/ApplicationServices.h>
+#import <CoreFoundation/CoreFoundation.h>
+
+#include "mozilla/dom/MediaControlKeySource.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace widget {
+
+class MediaHardwareKeysEventSourceMac final
+ : public mozilla::dom::MediaControlKeySource {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(MediaHardwareKeysEventSourceMac, override)
+ MediaHardwareKeysEventSourceMac() = default;
+
+ static CGEventRef EventTapCallback(CGEventTapProxy proxy, CGEventType type,
+ CGEventRef event, void* refcon);
+
+ bool Open() override;
+ void Close() override;
+ bool IsOpened() const override;
+
+ // Currently we don't support showing supported keys on the touch bar.
+ void SetSupportedMediaKeys(const MediaKeysArray& aSupportedKeys) override {}
+
+ private:
+ ~MediaHardwareKeysEventSourceMac() = default;
+
+ bool StartEventTap();
+ void StopEventTap();
+
+ // They are used to intercept mac hardware media keys.
+ CFMachPortRef mEventTap = nullptr;
+ CFRunLoopSourceRef mEventTapSource = nullptr;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/cocoa/MediaHardwareKeysEventSourceMac.mm b/widget/cocoa/MediaHardwareKeysEventSourceMac.mm
new file mode 100644
index 0000000000..e8347f64c7
--- /dev/null
+++ b/widget/cocoa/MediaHardwareKeysEventSourceMac.mm
@@ -0,0 +1,183 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "MediaHardwareKeysEventSourceMac.h"
+
+#import <AppKit/AppKit.h>
+#import <AppKit/NSEvent.h>
+#import <IOKit/hidsystem/ev_keymap.h>
+
+#include "mozilla/dom/MediaControlUtils.h"
+
+using namespace mozilla::dom;
+
+// avoid redefined macro in unified build
+#undef LOG
+#define LOG(msg, ...) \
+ MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
+ ("MediaHardwareKeysEventSourceMac=%p, " msg, this, ##__VA_ARGS__))
+
+// This macro is used in static callback function, where we have to send `this`
+// explicitly.
+#define LOG2(msg, this, ...) \
+ MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
+ ("MediaHardwareKeysEventSourceMac=%p, " msg, this, ##__VA_ARGS__))
+
+static const char* ToMediaControlKeyStr(int aKeyCode) {
+ switch (aKeyCode) {
+ case NX_KEYTYPE_PLAY:
+ return "Play";
+ case NX_KEYTYPE_NEXT:
+ return "Next";
+ case NX_KEYTYPE_PREVIOUS:
+ return "Previous";
+ case NX_KEYTYPE_FAST:
+ return "Fast";
+ case NX_KEYTYPE_REWIND:
+ return "Rewind";
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid key code.");
+ return "UNKNOWN";
+ }
+}
+
+// The media keys subtype. No official docs found, but widely known.
+// http://lists.apple.com/archives/cocoa-dev/2007/Aug/msg00499.html
+const int kSystemDefinedEventMediaKeysSubtype = 8;
+
+static bool IsSupportedKeyCode(int aKeyCode) {
+ return aKeyCode == NX_KEYTYPE_PLAY || aKeyCode == NX_KEYTYPE_NEXT ||
+ aKeyCode == NX_KEYTYPE_FAST || aKeyCode == NX_KEYTYPE_PREVIOUS ||
+ aKeyCode == NX_KEYTYPE_REWIND;
+}
+
+static MediaControlKey ToMediaControlKey(int aKeyCode) {
+ MOZ_ASSERT(IsSupportedKeyCode(aKeyCode));
+ switch (aKeyCode) {
+ case NX_KEYTYPE_NEXT:
+ case NX_KEYTYPE_FAST:
+ return MediaControlKey::Nexttrack;
+ case NX_KEYTYPE_PREVIOUS:
+ case NX_KEYTYPE_REWIND:
+ return MediaControlKey::Previoustrack;
+ default:
+ MOZ_ASSERT(aKeyCode == NX_KEYTYPE_PLAY);
+ return MediaControlKey::Playpause;
+ }
+}
+
+namespace mozilla {
+namespace widget {
+
+bool MediaHardwareKeysEventSourceMac::IsOpened() const { return mEventTap && mEventTapSource; }
+
+bool MediaHardwareKeysEventSourceMac::Open() {
+ LOG("Open MediaHardwareKeysEventSourceMac");
+ return StartEventTap();
+}
+
+void MediaHardwareKeysEventSourceMac::Close() {
+ LOG("Close MediaHardwareKeysEventSourceMac");
+ StopEventTap();
+ MediaControlKeySource::Close();
+}
+
+bool MediaHardwareKeysEventSourceMac::StartEventTap() {
+ LOG("StartEventTap");
+ MOZ_ASSERT(!mEventTap);
+ MOZ_ASSERT(!mEventTapSource);
+
+ // Add an event tap to intercept the system defined media key events.
+ mEventTap =
+ CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly,
+ CGEventMaskBit(NX_SYSDEFINED), EventTapCallback, this);
+ if (!mEventTap) {
+ LOG("Fail to create event tap");
+ return false;
+ }
+
+ mEventTapSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, mEventTap, 0);
+ if (!mEventTapSource) {
+ LOG("Fail to create an event tap source.");
+ return false;
+ }
+
+ LOG("Add an event tap source to current loop");
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapSource, kCFRunLoopCommonModes);
+ return true;
+}
+
+void MediaHardwareKeysEventSourceMac::StopEventTap() {
+ LOG("StopEventTapIfNecessary");
+ if (mEventTap) {
+ CFMachPortInvalidate(mEventTap);
+ mEventTap = nullptr;
+ }
+ if (mEventTapSource) {
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventTapSource, kCFRunLoopCommonModes);
+ CFRelease(mEventTapSource);
+ mEventTapSource = nullptr;
+ }
+}
+
+CGEventRef MediaHardwareKeysEventSourceMac::EventTapCallback(CGEventTapProxy proxy,
+ CGEventType type, CGEventRef event,
+ void* refcon) {
+ // Re-enable event tap when receiving disabled events.
+ MediaHardwareKeysEventSourceMac* source = static_cast<MediaHardwareKeysEventSourceMac*>(refcon);
+ if (type == kCGEventTapDisabledByUserInput || type == kCGEventTapDisabledByTimeout) {
+ MOZ_ASSERT(source->mEventTap);
+ CGEventTapEnable(source->mEventTap, true);
+ return event;
+ }
+
+ NSEvent* nsEvent = [NSEvent eventWithCGEvent:event];
+ if (nsEvent == nil) {
+ return event;
+ }
+
+ // Ignore not system defined media keys event.
+ if ([nsEvent type] != NSSystemDefined ||
+ [nsEvent subtype] != kSystemDefinedEventMediaKeysSubtype) {
+ return event;
+ }
+
+ // Ignore media keys that aren't previous, next and play/pause.
+ // Magical constants are from http://weblog.rogueamoeba.com/2007/09/29/
+ // - keyCode = (([event data1] & 0xFFFF0000) >> 16)
+ // - keyFlags = ([event data1] & 0x0000FFFF)
+ // - keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA
+ // - keyRepeat = (keyFlags & 0x1);
+ const NSInteger data1 = [nsEvent data1];
+ int keyCode = (data1 & 0xFFFF0000) >> 16;
+ if (keyCode != NX_KEYTYPE_PLAY && keyCode != NX_KEYTYPE_NEXT && keyCode != NX_KEYTYPE_PREVIOUS &&
+ keyCode != NX_KEYTYPE_FAST && keyCode != NX_KEYTYPE_REWIND) {
+ return event;
+ }
+
+ // Ignore non-key pressed event (eg. key released).
+ int keyFlags = data1 & 0x0000FFFF;
+ bool isKeyPressed = ((keyFlags & 0xFF00) >> 8) == 0xA;
+ if (!isKeyPressed) {
+ return event;
+ }
+
+ // There is no listener waiting to process event.
+ if (source->mListeners.IsEmpty()) {
+ return event;
+ }
+
+ if (!IsSupportedKeyCode(keyCode)) {
+ return event;
+ }
+
+ LOG2("Get media key %s", source, ToMediaControlKeyStr(keyCode));
+ for (auto iter = source->mListeners.begin(); iter != source->mListeners.end(); ++iter) {
+ (*iter)->OnActionPerformed(MediaControlAction(ToMediaControlKey(keyCode)));
+ }
+ return event;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/cocoa/MediaHardwareKeysEventSourceMacMediaCenter.h b/widget/cocoa/MediaHardwareKeysEventSourceMacMediaCenter.h
new file mode 100644
index 0000000000..04a3aeba48
--- /dev/null
+++ b/widget/cocoa/MediaHardwareKeysEventSourceMacMediaCenter.h
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 WIDGET_COCOA_MEDIAHARDWAREKEYSEVENTSOURCEMACMEDIACENTER_H_
+#define WIDGET_COCOA_MEDIAHARDWAREKEYSEVENTSOURCEMACMEDIACENTER_H_
+
+#include "mozilla/dom/MediaControlKeySource.h"
+
+#ifdef __OBJC__
+@class MPRemoteCommandEvent;
+#else
+typedef struct objc_object MPRemoteCommandEvent;
+#endif
+enum MPRemoteCommandHandlerStatus : long;
+
+namespace mozilla {
+namespace widget {
+
+typedef MPRemoteCommandHandlerStatus (^MediaCenterEventHandler)(MPRemoteCommandEvent* event);
+
+class MediaHardwareKeysEventSourceMacMediaCenter final
+ : public mozilla::dom::MediaControlKeySource {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(MediaHardwareKeysEventSourceMacMediaCenter, override)
+ MediaHardwareKeysEventSourceMacMediaCenter();
+
+ MediaCenterEventHandler CreatePlayPauseHandler();
+ MediaCenterEventHandler CreateNextTrackHandler();
+ MediaCenterEventHandler CreatePreviousTrackHandler();
+ MediaCenterEventHandler CreatePlayHandler();
+ MediaCenterEventHandler CreatePauseHandler();
+
+ bool Open() override;
+ void Close() override;
+ bool IsOpened() const override;
+ void SetPlaybackState(dom::MediaSessionPlaybackState aState) override;
+ void SetMediaMetadata(const dom::MediaMetadataBase& aMetadata) override;
+ // Currently we don't support showing supported keys on the touch bar.
+ void SetSupportedMediaKeys(const MediaKeysArray& aSupportedKeys) override {}
+
+ private:
+ ~MediaHardwareKeysEventSourceMacMediaCenter();
+ void BeginListeningForEvents();
+ void EndListeningForEvents();
+ void HandleEvent(dom::MediaControlKey aKey);
+
+ bool mOpened = false;
+
+ MediaCenterEventHandler mPlayPauseHandler;
+ MediaCenterEventHandler mNextTrackHandler;
+ MediaCenterEventHandler mPreviousTrackHandler;
+ MediaCenterEventHandler mPauseHandler;
+ MediaCenterEventHandler mPlayHandler;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // WIDGET_COCOA_MEDIAHARDWAREKEYSEVENTSOURCEMACMEDIACENTER_H_
diff --git a/widget/cocoa/MediaHardwareKeysEventSourceMacMediaCenter.mm b/widget/cocoa/MediaHardwareKeysEventSourceMacMediaCenter.mm
new file mode 100644
index 0000000000..a258c7900c
--- /dev/null
+++ b/widget/cocoa/MediaHardwareKeysEventSourceMacMediaCenter.mm
@@ -0,0 +1,172 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <MediaPlayer/MediaPlayer.h>
+
+#include "MediaHardwareKeysEventSourceMacMediaCenter.h"
+
+#include "mozilla/dom/MediaControlUtils.h"
+#include "nsCocoaUtils.h"
+
+using namespace mozilla::dom;
+
+// avoid redefined macro in unified build
+#undef LOG
+#define LOG(msg, ...) \
+ MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
+ ("MediaHardwareKeysEventSourceMacMediaCenter=%p, " msg, this, ##__VA_ARGS__))
+
+namespace mozilla {
+namespace widget {
+
+MediaCenterEventHandler MediaHardwareKeysEventSourceMacMediaCenter::CreatePlayPauseHandler() {
+ return Block_copy(^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent* event) {
+ MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
+ center.playbackState = center.playbackState == MPNowPlayingPlaybackStatePlaying
+ ? MPNowPlayingPlaybackStatePaused
+ : MPNowPlayingPlaybackStatePlaying;
+ HandleEvent(MediaControlKey::Playpause);
+ return MPRemoteCommandHandlerStatusSuccess;
+ });
+}
+
+MediaCenterEventHandler MediaHardwareKeysEventSourceMacMediaCenter::CreateNextTrackHandler() {
+ return Block_copy(^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent* event) {
+ HandleEvent(MediaControlKey::Nexttrack);
+ return MPRemoteCommandHandlerStatusSuccess;
+ });
+}
+
+MediaCenterEventHandler MediaHardwareKeysEventSourceMacMediaCenter::CreatePreviousTrackHandler() {
+ return Block_copy(^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent* event) {
+ HandleEvent(MediaControlKey::Previoustrack);
+ return MPRemoteCommandHandlerStatusSuccess;
+ });
+}
+
+MediaCenterEventHandler MediaHardwareKeysEventSourceMacMediaCenter::CreatePlayHandler() {
+ return Block_copy(^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent* event) {
+ MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
+ if (center.playbackState != MPNowPlayingPlaybackStatePlaying) {
+ center.playbackState = MPNowPlayingPlaybackStatePlaying;
+ }
+ HandleEvent(MediaControlKey::Play);
+ return MPRemoteCommandHandlerStatusSuccess;
+ });
+}
+
+MediaCenterEventHandler MediaHardwareKeysEventSourceMacMediaCenter::CreatePauseHandler() {
+ return Block_copy(^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent* event) {
+ MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
+ if (center.playbackState != MPNowPlayingPlaybackStatePaused) {
+ center.playbackState = MPNowPlayingPlaybackStatePaused;
+ }
+ HandleEvent(MediaControlKey::Pause);
+ return MPRemoteCommandHandlerStatusSuccess;
+ });
+}
+
+MediaHardwareKeysEventSourceMacMediaCenter::MediaHardwareKeysEventSourceMacMediaCenter() {
+ mPlayPauseHandler = CreatePlayPauseHandler();
+ mNextTrackHandler = CreateNextTrackHandler();
+ mPreviousTrackHandler = CreatePreviousTrackHandler();
+ mPlayHandler = CreatePlayHandler();
+ mPauseHandler = CreatePauseHandler();
+ LOG("Create MediaHardwareKeysEventSourceMacMediaCenter");
+}
+
+MediaHardwareKeysEventSourceMacMediaCenter::~MediaHardwareKeysEventSourceMacMediaCenter() {
+ LOG("Destroy MediaHardwareKeysEventSourceMacMediaCenter");
+ EndListeningForEvents();
+ MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
+ center.playbackState = MPNowPlayingPlaybackStateStopped;
+}
+
+void MediaHardwareKeysEventSourceMacMediaCenter::BeginListeningForEvents() {
+ MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
+ center.playbackState = MPNowPlayingPlaybackStatePlaying;
+ MPRemoteCommandCenter* commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
+ commandCenter.togglePlayPauseCommand.enabled = true;
+ [commandCenter.togglePlayPauseCommand addTargetWithHandler:mPlayPauseHandler];
+ commandCenter.nextTrackCommand.enabled = true;
+ [commandCenter.nextTrackCommand addTargetWithHandler:mNextTrackHandler];
+ commandCenter.previousTrackCommand.enabled = true;
+ [commandCenter.previousTrackCommand addTargetWithHandler:mPreviousTrackHandler];
+ commandCenter.playCommand.enabled = true;
+ [commandCenter.playCommand addTargetWithHandler:mPlayHandler];
+ commandCenter.pauseCommand.enabled = true;
+ [commandCenter.pauseCommand addTargetWithHandler:mPauseHandler];
+}
+
+void MediaHardwareKeysEventSourceMacMediaCenter::EndListeningForEvents() {
+ MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
+ center.playbackState = MPNowPlayingPlaybackStatePaused;
+ center.nowPlayingInfo = nil;
+ MPRemoteCommandCenter* commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
+ commandCenter.togglePlayPauseCommand.enabled = false;
+ [commandCenter.togglePlayPauseCommand removeTarget:nil];
+ commandCenter.nextTrackCommand.enabled = false;
+ [commandCenter.nextTrackCommand removeTarget:nil];
+ commandCenter.previousTrackCommand.enabled = false;
+ [commandCenter.previousTrackCommand removeTarget:nil];
+ commandCenter.playCommand.enabled = false;
+ [commandCenter.playCommand removeTarget:nil];
+ commandCenter.pauseCommand.enabled = false;
+ [commandCenter.pauseCommand removeTarget:nil];
+}
+
+bool MediaHardwareKeysEventSourceMacMediaCenter::Open() {
+ LOG("Open MediaHardwareKeysEventSourceMacMediaCenter");
+ mOpened = true;
+ BeginListeningForEvents();
+ return true;
+}
+
+void MediaHardwareKeysEventSourceMacMediaCenter::Close() {
+ LOG("Close MediaHardwareKeysEventSourceMacMediaCenter");
+ SetPlaybackState(dom::MediaSessionPlaybackState::None);
+ EndListeningForEvents();
+ mOpened = false;
+ MediaControlKeySource::Close();
+}
+
+bool MediaHardwareKeysEventSourceMacMediaCenter::IsOpened() const { return mOpened; }
+
+void MediaHardwareKeysEventSourceMacMediaCenter::HandleEvent(MediaControlKey aEvent) {
+ for (auto iter = mListeners.begin(); iter != mListeners.end(); ++iter) {
+ (*iter)->OnActionPerformed(MediaControlAction(aEvent));
+ }
+}
+
+void MediaHardwareKeysEventSourceMacMediaCenter::SetPlaybackState(
+ MediaSessionPlaybackState aState) {
+ MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
+ if (aState == MediaSessionPlaybackState::Playing) {
+ center.playbackState = MPNowPlayingPlaybackStatePlaying;
+ } else if (aState == MediaSessionPlaybackState::Paused) {
+ center.playbackState = MPNowPlayingPlaybackStatePaused;
+ } else if (aState == MediaSessionPlaybackState::None) {
+ center.playbackState = MPNowPlayingPlaybackStateStopped;
+ }
+ MediaControlKeySource::SetPlaybackState(aState);
+}
+
+void MediaHardwareKeysEventSourceMacMediaCenter::SetMediaMetadata(
+ const dom::MediaMetadataBase& aMetadata) {
+ NSMutableDictionary* nowPlayingInfo = [NSMutableDictionary dictionary];
+ [nowPlayingInfo setObject:nsCocoaUtils::ToNSString(aMetadata.mTitle)
+ forKey:MPMediaItemPropertyTitle];
+ [nowPlayingInfo setObject:nsCocoaUtils::ToNSString(aMetadata.mArtist)
+ forKey:MPMediaItemPropertyArtist];
+ [nowPlayingInfo setObject:nsCocoaUtils::ToNSString(aMetadata.mAlbum)
+ forKey:MPMediaItemPropertyAlbumTitle];
+ // The procedure of updating `nowPlayingInfo` is actually an async operation
+ // from our testing, Apple's documentation doesn't mention that though. So be
+ // aware that checking `nowPlayingInfo` immedately after setting it might not
+ // yield the expected result.
+ [MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = nowPlayingInfo;
+}
+
+}
+}
diff --git a/widget/cocoa/MediaKeysEventSourceFactory.cpp b/widget/cocoa/MediaKeysEventSourceFactory.cpp
new file mode 100644
index 0000000000..1a5ad93b4c
--- /dev/null
+++ b/widget/cocoa/MediaKeysEventSourceFactory.cpp
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "MediaKeysEventSourceFactory.h"
+
+#include "MediaHardwareKeysEventSourceMac.h"
+#include "MediaHardwareKeysEventSourceMacMediaCenter.h"
+#include "nsCocoaFeatures.h"
+
+namespace mozilla {
+namespace widget {
+
+mozilla::dom::MediaControlKeySource* CreateMediaControlKeySource() {
+ if (nsCocoaFeatures::IsAtLeastVersion(10, 12, 2)) {
+ return new MediaHardwareKeysEventSourceMacMediaCenter();
+ } else {
+ return new MediaHardwareKeysEventSourceMac();
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/cocoa/NativeKeyBindings.h b/widget/cocoa/NativeKeyBindings.h
new file mode 100644
index 0000000000..65a1304b6b
--- /dev/null
+++ b/widget/cocoa/NativeKeyBindings.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_NativeKeyBindings_h_
+#define mozilla_widget_NativeKeyBindings_h_
+
+#import <Cocoa/Cocoa.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "nsDataHashtable.h"
+#include "nsIWidget.h"
+
+namespace mozilla {
+namespace widget {
+
+typedef nsDataHashtable<nsPtrHashKey<struct objc_selector>, Command>
+ SelectorCommandHashtable;
+
+class NativeKeyBindings final {
+ typedef nsIWidget::NativeKeyBindingsType NativeKeyBindingsType;
+
+ public:
+ static NativeKeyBindings* GetInstance(NativeKeyBindingsType aType);
+ static void Shutdown();
+
+ void Init(NativeKeyBindingsType aType);
+
+ void GetEditCommands(const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands);
+
+ private:
+ NativeKeyBindings();
+
+ SelectorCommandHashtable mSelectorToCommand;
+
+ static NativeKeyBindings* sInstanceForSingleLineEditor;
+ static NativeKeyBindings* sInstanceForMultiLineEditor;
+}; // NativeKeyBindings
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_NativeKeyBindings_h_
diff --git a/widget/cocoa/NativeKeyBindings.mm b/widget/cocoa/NativeKeyBindings.mm
new file mode 100644
index 0000000000..0e3d1e4d5d
--- /dev/null
+++ b/widget/cocoa/NativeKeyBindings.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 "NativeKeyBindings.h"
+
+#include "nsTArray.h"
+#include "nsCocoaUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/TextEvents.h"
+
+namespace mozilla {
+namespace widget {
+
+static LazyLogModule gNativeKeyBindingsLog("NativeKeyBindings");
+
+NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr;
+NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr;
+
+// static
+NativeKeyBindings* NativeKeyBindings::GetInstance(NativeKeyBindingsType aType) {
+ switch (aType) {
+ case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+ if (!sInstanceForSingleLineEditor) {
+ sInstanceForSingleLineEditor = new NativeKeyBindings();
+ sInstanceForSingleLineEditor->Init(aType);
+ }
+ return sInstanceForSingleLineEditor;
+ case nsIWidget::NativeKeyBindingsForMultiLineEditor:
+ case nsIWidget::NativeKeyBindingsForRichTextEditor:
+ if (!sInstanceForMultiLineEditor) {
+ sInstanceForMultiLineEditor = new NativeKeyBindings();
+ sInstanceForMultiLineEditor->Init(aType);
+ }
+ return sInstanceForMultiLineEditor;
+ default:
+ MOZ_CRASH("Not implemented");
+ return nullptr;
+ }
+}
+
+// static
+void NativeKeyBindings::Shutdown() {
+ delete sInstanceForSingleLineEditor;
+ sInstanceForSingleLineEditor = nullptr;
+ delete sInstanceForMultiLineEditor;
+ sInstanceForMultiLineEditor = nullptr;
+}
+
+NativeKeyBindings::NativeKeyBindings() {}
+
+#define SEL_TO_COMMAND(aSel, aCommand) \
+ mSelectorToCommand.Put(reinterpret_cast<struct objc_selector*>(@selector(aSel)), aCommand)
+
+void NativeKeyBindings::Init(NativeKeyBindingsType aType) {
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info, ("%p NativeKeyBindings::Init", this));
+
+ // Many selectors have a one-to-one mapping to a Gecko command. Those mappings
+ // are registered in mSelectorToCommand.
+
+ // Selectors from NSResponder's "Responding to Action Messages" section and
+ // from NSText's "Action Methods for Editing" section
+
+ // TODO: Improves correctness of left / right meaning
+ // TODO: Add real paragraph motions
+
+ // SEL_TO_COMMAND(cancelOperation:, );
+ // SEL_TO_COMMAND(capitalizeWord:, );
+ // SEL_TO_COMMAND(centerSelectionInVisibleArea:, );
+ // SEL_TO_COMMAND(changeCaseOfLetter:, );
+ // SEL_TO_COMMAND(complete:, );
+ SEL_TO_COMMAND(copy:, Command::Copy);
+ // SEL_TO_COMMAND(copyFont:, );
+ // SEL_TO_COMMAND(copyRuler:, );
+ SEL_TO_COMMAND(cut:, Command::Cut);
+ SEL_TO_COMMAND(delete:, Command::Delete);
+ SEL_TO_COMMAND(deleteBackward:, Command::DeleteCharBackward);
+ // SEL_TO_COMMAND(deleteBackwardByDecomposingPreviousCharacter:, );
+ SEL_TO_COMMAND(deleteForward:, Command::DeleteCharForward);
+
+ // TODO: deleteTo* selectors are also supposed to add text to a kill buffer
+ SEL_TO_COMMAND(deleteToBeginningOfLine:, Command::DeleteToBeginningOfLine);
+ SEL_TO_COMMAND(deleteToBeginningOfParagraph:, Command::DeleteToBeginningOfLine);
+ SEL_TO_COMMAND(deleteToEndOfLine:, Command::DeleteToEndOfLine);
+ SEL_TO_COMMAND(deleteToEndOfParagraph:, Command::DeleteToEndOfLine);
+ // SEL_TO_COMMAND(deleteToMark:, );
+
+ SEL_TO_COMMAND(deleteWordBackward:, Command::DeleteWordBackward);
+ SEL_TO_COMMAND(deleteWordForward:, Command::DeleteWordForward);
+ // SEL_TO_COMMAND(indent:, );
+ // SEL_TO_COMMAND(insertBacktab:, );
+ // SEL_TO_COMMAND(insertContainerBreak:, );
+ // SEL_TO_COMMAND(insertLineBreak:, );
+ // SEL_TO_COMMAND(insertNewline:, );
+ // SEL_TO_COMMAND(insertNewlineIgnoringFieldEditor:, );
+ // SEL_TO_COMMAND(insertParagraphSeparator:, );
+ // SEL_TO_COMMAND(insertTab:, );
+ // SEL_TO_COMMAND(insertTabIgnoringFieldEditor:, );
+ // SEL_TO_COMMAND(insertDoubleQuoteIgnoringSubstitution:, );
+ // SEL_TO_COMMAND(insertSingleQuoteIgnoringSubstitution:, );
+ // SEL_TO_COMMAND(lowercaseWord:, );
+ SEL_TO_COMMAND(moveBackward:, Command::CharPrevious);
+ SEL_TO_COMMAND(moveBackwardAndModifySelection:, Command::SelectCharPrevious);
+ if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) {
+ SEL_TO_COMMAND(moveDown:, Command::EndLine);
+ } else {
+ SEL_TO_COMMAND(moveDown:, Command::LineNext);
+ }
+ SEL_TO_COMMAND(moveDownAndModifySelection:, Command::SelectLineNext);
+ SEL_TO_COMMAND(moveForward:, Command::CharNext);
+ SEL_TO_COMMAND(moveForwardAndModifySelection:, Command::SelectCharNext);
+ SEL_TO_COMMAND(moveLeft:, Command::CharPrevious);
+ SEL_TO_COMMAND(moveLeftAndModifySelection:, Command::SelectCharPrevious);
+ SEL_TO_COMMAND(moveParagraphBackwardAndModifySelection:, Command::SelectBeginLine);
+ SEL_TO_COMMAND(moveParagraphForwardAndModifySelection:, Command::SelectEndLine);
+ SEL_TO_COMMAND(moveRight:, Command::CharNext);
+ SEL_TO_COMMAND(moveRightAndModifySelection:, Command::SelectCharNext);
+ SEL_TO_COMMAND(moveToBeginningOfDocument:, Command::MoveTop);
+ SEL_TO_COMMAND(moveToBeginningOfDocumentAndModifySelection:, Command::SelectTop);
+ SEL_TO_COMMAND(moveToBeginningOfLine:, Command::BeginLine);
+ SEL_TO_COMMAND(moveToBeginningOfLineAndModifySelection:, Command::SelectBeginLine);
+ SEL_TO_COMMAND(moveToBeginningOfParagraph:, Command::BeginLine);
+ SEL_TO_COMMAND(moveToBeginningOfParagraphAndModifySelection:, Command::SelectBeginLine);
+ SEL_TO_COMMAND(moveToEndOfDocument:, Command::MoveBottom);
+ SEL_TO_COMMAND(moveToEndOfDocumentAndModifySelection:, Command::SelectBottom);
+ SEL_TO_COMMAND(moveToEndOfLine:, Command::EndLine);
+ SEL_TO_COMMAND(moveToEndOfLineAndModifySelection:, Command::SelectEndLine);
+ SEL_TO_COMMAND(moveToEndOfParagraph:, Command::EndLine);
+ SEL_TO_COMMAND(moveToEndOfParagraphAndModifySelection:, Command::SelectEndLine);
+ SEL_TO_COMMAND(moveToLeftEndOfLine:, Command::BeginLine);
+ SEL_TO_COMMAND(moveToLeftEndOfLineAndModifySelection:, Command::SelectBeginLine);
+ SEL_TO_COMMAND(moveToRightEndOfLine:, Command::EndLine);
+ SEL_TO_COMMAND(moveToRightEndOfLineAndModifySelection:, Command::SelectEndLine);
+ if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) {
+ SEL_TO_COMMAND(moveUp:, Command::BeginLine);
+ } else {
+ SEL_TO_COMMAND(moveUp:, Command::LinePrevious);
+ }
+ SEL_TO_COMMAND(moveUpAndModifySelection:, Command::SelectLinePrevious);
+ SEL_TO_COMMAND(moveWordBackward:, Command::WordPrevious);
+ SEL_TO_COMMAND(moveWordBackwardAndModifySelection:, Command::SelectWordPrevious);
+ SEL_TO_COMMAND(moveWordForward:, Command::WordNext);
+ SEL_TO_COMMAND(moveWordForwardAndModifySelection:, Command::SelectWordNext);
+ SEL_TO_COMMAND(moveWordLeft:, Command::WordPrevious);
+ SEL_TO_COMMAND(moveWordLeftAndModifySelection:, Command::SelectWordPrevious);
+ SEL_TO_COMMAND(moveWordRight:, Command::WordNext);
+ SEL_TO_COMMAND(moveWordRightAndModifySelection:, Command::SelectWordNext);
+ SEL_TO_COMMAND(pageDown:, Command::MovePageDown);
+ SEL_TO_COMMAND(pageDownAndModifySelection:, Command::SelectPageDown);
+ SEL_TO_COMMAND(pageUp:, Command::MovePageUp);
+ SEL_TO_COMMAND(pageUpAndModifySelection:, Command::SelectPageUp);
+ SEL_TO_COMMAND(paste:, Command::Paste);
+ // SEL_TO_COMMAND(pasteFont:, );
+ // SEL_TO_COMMAND(pasteRuler:, );
+ SEL_TO_COMMAND(scrollLineDown:, Command::ScrollLineDown);
+ SEL_TO_COMMAND(scrollLineUp:, Command::ScrollLineUp);
+ SEL_TO_COMMAND(scrollPageDown:, Command::ScrollPageDown);
+ SEL_TO_COMMAND(scrollPageUp:, Command::ScrollPageUp);
+ SEL_TO_COMMAND(scrollToBeginningOfDocument:, Command::ScrollTop);
+ SEL_TO_COMMAND(scrollToEndOfDocument:, Command::ScrollBottom);
+ SEL_TO_COMMAND(selectAll:, Command::SelectAll);
+ // selectLine: is complex, see KeyDown
+ if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) {
+ SEL_TO_COMMAND(selectParagraph:, Command::SelectAll);
+ }
+ // SEL_TO_COMMAND(selectToMark:, );
+ // selectWord: is complex, see KeyDown
+ // SEL_TO_COMMAND(setMark:, );
+ // SEL_TO_COMMAND(showContextHelp:, );
+ // SEL_TO_COMMAND(supplementalTargetForAction:sender:, );
+ // SEL_TO_COMMAND(swapWithMark:, );
+ // SEL_TO_COMMAND(transpose:, );
+ // SEL_TO_COMMAND(transposeWords:, );
+ // SEL_TO_COMMAND(uppercaseWord:, );
+ // SEL_TO_COMMAND(yank:, );
+}
+
+#undef SEL_TO_COMMAND
+
+void NativeKeyBindings::GetEditCommands(const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands) {
+ MOZ_ASSERT(aCommands.IsEmpty());
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info, ("%p NativeKeyBindings::GetEditCommands", this));
+
+ // Recover the current event, which should always be the key down we are
+ // responding to.
+
+ NSEvent* cocoaEvent = reinterpret_cast<NSEvent*>(aEvent.mNativeKeyEvent);
+
+ if (!cocoaEvent || [cocoaEvent type] != NSEventTypeKeyDown) {
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::GetEditCommands, no Cocoa key down event", this));
+
+ return;
+ }
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::GetEditCommands, interpreting", this));
+
+ AutoTArray<KeyBindingsCommand, 2> bindingCommands;
+ nsCocoaUtils::GetCommandsFromKeyEvent(cocoaEvent, bindingCommands);
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::GetEditCommands, bindingCommands=%zu", this,
+ bindingCommands.Length()));
+
+ for (uint32_t i = 0; i < bindingCommands.Length(); i++) {
+ SEL selector = bindingCommands[i].selector;
+
+ if (MOZ_LOG_TEST(gNativeKeyBindingsLog, LogLevel::Info)) {
+ NSString* selectorString = NSStringFromSelector(selector);
+ nsAutoString nsSelectorString;
+ nsCocoaUtils::GetStringForNSString(selectorString, nsSelectorString);
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::GetEditCommands, selector=%s", this,
+ NS_LossyConvertUTF16toASCII(nsSelectorString).get()));
+ }
+
+ // Try to find a simple mapping in the hashtable
+ Command geckoCommand = Command::DoNothing;
+ if (mSelectorToCommand.Get(reinterpret_cast<struct objc_selector*>(selector), &geckoCommand) &&
+ geckoCommand != Command::DoNothing) {
+ aCommands.AppendElement(static_cast<CommandInt>(geckoCommand));
+ } else if (selector == @selector(selectLine:)) {
+ // This is functional, but Cocoa's version is direction-less in that
+ // selection direction is not determined until some future directed action
+ // is taken. See bug 282097, comment 79 for more details.
+ aCommands.AppendElement(static_cast<CommandInt>(Command::BeginLine));
+ aCommands.AppendElement(static_cast<CommandInt>(Command::SelectEndLine));
+ } else if (selector == @selector(selectWord:)) {
+ // This is functional, but Cocoa's version is direction-less in that
+ // selection direction is not determined until some future directed action
+ // is taken. See bug 282097, comment 79 for more details.
+ aCommands.AppendElement(static_cast<CommandInt>(Command::WordPrevious));
+ aCommands.AppendElement(static_cast<CommandInt>(Command::SelectWordNext));
+ }
+ }
+
+ if (!MOZ_LOG_TEST(gNativeKeyBindingsLog, LogLevel::Info)) {
+ return;
+ }
+
+ if (aCommands.IsEmpty()) {
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::GetEditCommands, handled=false", this));
+ return;
+ }
+
+ for (CommandInt commandInt : aCommands) {
+ Command geckoCommand = static_cast<Command>(commandInt);
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::GetEditCommands, command=%s", this,
+ WidgetKeyboardEvent::GetCommandStr(geckoCommand)));
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/cocoa/OSXNotificationCenter.h b/widget/cocoa/OSXNotificationCenter.h
new file mode 100644
index 0000000000..b850682472
--- /dev/null
+++ b/widget/cocoa/OSXNotificationCenter.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef OSXNotificationCenter_h
+#define OSXNotificationCenter_h
+
+#import <Foundation/Foundation.h>
+#include "nsIAlertsService.h"
+#include "nsTArray.h"
+#include "mozilla/RefPtr.h"
+
+// mozNotificationCenterDelegate is used to access the macOS notification
+// center. It is not related to the DesktopNotificationCenter object, which was
+// removed in bug 952453. While there are no direct references to this class
+// elsewhere, removing this will cause push notifications on macOS to stop
+// working.
+@class mozNotificationCenterDelegate;
+
+#if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8)
+typedef NSInteger NSUserNotificationActivationType;
+#endif
+
+namespace mozilla {
+
+class OSXNotificationInfo;
+
+class OSXNotificationCenter : public nsIAlertsService,
+ public nsIAlertsIconData,
+ public nsIAlertsDoNotDisturb,
+ public nsIAlertNotificationImageListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIALERTSSERVICE
+ NS_DECL_NSIALERTSICONDATA
+ NS_DECL_NSIALERTSDONOTDISTURB
+ NS_DECL_NSIALERTNOTIFICATIONIMAGELISTENER
+
+ OSXNotificationCenter();
+
+ nsresult Init();
+ void CloseAlertCocoaString(NSString* aAlertName);
+ void OnActivate(NSString* aAlertName, NSUserNotificationActivationType aActivationType,
+ unsigned long long aAdditionalActionIndex);
+ void ShowPendingNotification(OSXNotificationInfo* osxni);
+
+ protected:
+ virtual ~OSXNotificationCenter();
+
+ private:
+ mozNotificationCenterDelegate* mDelegate;
+ nsTArray<RefPtr<OSXNotificationInfo> > mActiveAlerts;
+ nsTArray<RefPtr<OSXNotificationInfo> > mPendingAlerts;
+ bool mSuppressForScreenSharing;
+};
+
+} // namespace mozilla
+
+#endif // OSXNotificationCenter_h
diff --git a/widget/cocoa/OSXNotificationCenter.mm b/widget/cocoa/OSXNotificationCenter.mm
new file mode 100644
index 0000000000..e7ede3409c
--- /dev/null
+++ b/widget/cocoa/OSXNotificationCenter.mm
@@ -0,0 +1,572 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OSXNotificationCenter.h"
+#import <AppKit/AppKit.h>
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "nsICancelable.h"
+#include "nsIStringBundle.h"
+#include "nsNetUtil.h"
+#import "nsCocoaUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+
+using namespace mozilla;
+
+#define MAX_NOTIFICATION_NAME_LEN 5000
+
+#if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8)
+@protocol NSUserNotificationCenterDelegate
+@end
+static NSString* const NSUserNotificationDefaultSoundName = @"DefaultSoundName";
+enum {
+ NSUserNotificationActivationTypeNone = 0,
+ NSUserNotificationActivationTypeContentsClicked = 1,
+ NSUserNotificationActivationTypeActionButtonClicked = 2,
+};
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_9) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9)
+enum {
+ NSUserNotificationActivationTypeReplied = 3,
+};
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_10) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10)
+enum { NSUserNotificationActivationTypeAdditionalActionClicked = 4 };
+#endif
+
+@protocol FakeNSUserNotification <NSObject>
+@property(copy) NSString* title;
+@property(copy) NSString* subtitle;
+@property(copy) NSString* informativeText;
+@property(copy) NSString* actionButtonTitle;
+@property(copy) NSDictionary* userInfo;
+@property(copy) NSDate* deliveryDate;
+@property(copy) NSTimeZone* deliveryTimeZone;
+@property(copy) NSDateComponents* deliveryRepeatInterval;
+@property(readonly) NSDate* actualDeliveryDate;
+@property(readonly, getter=isPresented) BOOL presented;
+@property(readonly, getter=isRemote) BOOL remote;
+@property(copy) NSString* soundName;
+@property BOOL hasActionButton;
+@property(readonly) NSUserNotificationActivationType activationType;
+@property(copy) NSString* otherButtonTitle;
+@property(copy) NSImage* contentImage;
+@end
+
+@protocol FakeNSUserNotificationCenter <NSObject>
++ (id<FakeNSUserNotificationCenter>)defaultUserNotificationCenter;
+@property(assign) id<NSUserNotificationCenterDelegate> delegate;
+@property(copy) NSArray* scheduledNotifications;
+- (void)scheduleNotification:(id<FakeNSUserNotification>)notification;
+- (void)removeScheduledNotification:(id<FakeNSUserNotification>)notification;
+@property(readonly) NSArray* deliveredNotifications;
+- (void)deliverNotification:(id<FakeNSUserNotification>)notification;
+- (void)removeDeliveredNotification:(id<FakeNSUserNotification>)notification;
+- (void)removeAllDeliveredNotifications;
+- (void)_removeAllDisplayedNotifications;
+- (void)_removeDisplayedNotification:(id<FakeNSUserNotification>)notification;
+@end
+
+@interface mozNotificationCenterDelegate : NSObject <NSUserNotificationCenterDelegate> {
+ OSXNotificationCenter* mOSXNC;
+}
+- (id)initWithOSXNC:(OSXNotificationCenter*)osxnc;
+@end
+
+@implementation mozNotificationCenterDelegate
+
+- (id)initWithOSXNC:(OSXNotificationCenter*)osxnc {
+ [super init];
+ // We should *never* outlive this OSXNotificationCenter.
+ mOSXNC = osxnc;
+ return self;
+}
+
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didDeliverNotification:(id<FakeNSUserNotification>)notification {
+}
+
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didActivateNotification:(id<FakeNSUserNotification>)notification {
+ unsigned long long additionalActionIndex = ULLONG_MAX;
+ if ([notification respondsToSelector:@selector(_alternateActionIndex)]) {
+ NSNumber* alternateActionIndex = [(NSObject*)notification valueForKey:@"_alternateActionIndex"];
+ additionalActionIndex = [alternateActionIndex unsignedLongLongValue];
+ }
+ mOSXNC->OnActivate([[notification userInfo] valueForKey:@"name"], notification.activationType,
+ additionalActionIndex);
+}
+
+- (BOOL)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ shouldPresentNotification:(id<FakeNSUserNotification>)notification {
+ return YES;
+}
+
+// This is an undocumented method that we need for parity with Safari.
+// Apple bug #15440664.
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didRemoveDeliveredNotifications:(NSArray*)notifications {
+ for (id<FakeNSUserNotification> notification in notifications) {
+ NSString* name = [[notification userInfo] valueForKey:@"name"];
+ mOSXNC->CloseAlertCocoaString(name);
+ }
+}
+
+// This is an undocumented method that we need to be notified if a user clicks the close button.
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didDismissAlert:(id<FakeNSUserNotification>)notification {
+ NSString* name = [[notification userInfo] valueForKey:@"name"];
+ mOSXNC->CloseAlertCocoaString(name);
+}
+
+@end
+
+namespace mozilla {
+
+enum {
+ OSXNotificationActionDisable = 0,
+ OSXNotificationActionSettings = 1,
+};
+
+class OSXNotificationInfo final : public nsISupports {
+ private:
+ virtual ~OSXNotificationInfo();
+
+ public:
+ NS_DECL_ISUPPORTS
+ OSXNotificationInfo(NSString* name, nsIObserver* observer, const nsAString& alertCookie);
+
+ NSString* mName;
+ nsCOMPtr<nsIObserver> mObserver;
+ nsString mCookie;
+ RefPtr<nsICancelable> mIconRequest;
+ id<FakeNSUserNotification> mPendingNotification;
+};
+
+NS_IMPL_ISUPPORTS0(OSXNotificationInfo)
+
+OSXNotificationInfo::OSXNotificationInfo(NSString* name, nsIObserver* observer,
+ const nsAString& alertCookie) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ASSERTION(name, "Cannot create OSXNotificationInfo without a name!");
+ mName = [name retain];
+ mObserver = observer;
+ mCookie = alertCookie;
+ mPendingNotification = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+OSXNotificationInfo::~OSXNotificationInfo() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mName release];
+ [mPendingNotification release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static id<FakeNSUserNotificationCenter> GetNotificationCenter() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ Class c = NSClassFromString(@"NSUserNotificationCenter");
+ return [c performSelector:@selector(defaultUserNotificationCenter)];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+OSXNotificationCenter::OSXNotificationCenter() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mDelegate = [[mozNotificationCenterDelegate alloc] initWithOSXNC:this];
+ GetNotificationCenter().delegate = mDelegate;
+ mSuppressForScreenSharing = false;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+OSXNotificationCenter::~OSXNotificationCenter() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [GetNotificationCenter() removeAllDeliveredNotifications];
+ [mDelegate release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMPL_ISUPPORTS(OSXNotificationCenter, nsIAlertsService, nsIAlertsIconData, nsIAlertsDoNotDisturb,
+ nsIAlertNotificationImageListener)
+
+nsresult OSXNotificationCenter::Init() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ return (!!NSClassFromString(@"NSUserNotification")) ? NS_OK : NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowAlertNotification(
+ const nsAString& aImageUrl, const nsAString& aAlertTitle, const nsAString& aAlertText,
+ bool aAlertTextClickable, const nsAString& aAlertCookie, nsIObserver* aAlertListener,
+ const nsAString& aAlertName, const nsAString& aBidi, const nsAString& aLang,
+ const nsAString& aData, nsIPrincipal* aPrincipal, bool aInPrivateBrowsing,
+ bool aRequireInteraction) {
+ nsCOMPtr<nsIAlertNotification> alert = do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
+ NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE);
+ nsresult rv =
+ alert->Init(aAlertName, aImageUrl, aAlertTitle, aAlertText, aAlertTextClickable, aAlertCookie,
+ aBidi, aLang, aData, aPrincipal, aInPrivateBrowsing, aRequireInteraction);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ShowAlert(alert, aAlertListener);
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowPersistentNotification(const nsAString& aPersistentData,
+ nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener) {
+ return ShowAlert(aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowAlert(nsIAlertNotification* aAlert, nsIObserver* aAlertListener) {
+ return ShowAlertWithIconData(aAlert, aAlertListener, 0, nullptr);
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowAlertWithIconData(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener, uint32_t aIconSize,
+ const uint8_t* aIconData) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ENSURE_ARG(aAlert);
+
+ if (mSuppressForScreenSharing) {
+ return NS_OK;
+ }
+
+ Class unClass = NSClassFromString(@"NSUserNotification");
+ id<FakeNSUserNotification> notification = [[unClass alloc] init];
+
+ nsAutoString title;
+ nsresult rv = aAlert->GetTitle(title);
+ NS_ENSURE_SUCCESS(rv, rv);
+ notification.title = nsCocoaUtils::ToNSString(title);
+
+ nsAutoString hostPort;
+ rv = aAlert->GetSource(hostPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ sbs->CreateBundle("chrome://alerts/locale/alert.properties", getter_AddRefs(bundle));
+
+ if (!hostPort.IsEmpty() && bundle) {
+ AutoTArray<nsString, 1> formatStrings = {hostPort};
+ nsAutoString notificationSource;
+ bundle->FormatStringFromName("source.label", formatStrings, notificationSource);
+ notification.subtitle = nsCocoaUtils::ToNSString(notificationSource);
+ }
+
+ nsAutoString text;
+ rv = aAlert->GetText(text);
+ NS_ENSURE_SUCCESS(rv, rv);
+ notification.informativeText = nsCocoaUtils::ToNSString(text);
+
+ notification.soundName = NSUserNotificationDefaultSoundName;
+ notification.hasActionButton = NO;
+
+ // If this is not an application/extension alert, show additional actions dealing with
+ // permissions.
+ bool isActionable;
+ if (bundle && NS_SUCCEEDED(aAlert->GetActionable(&isActionable)) && isActionable) {
+ nsAutoString closeButtonTitle, actionButtonTitle, disableButtonTitle, settingsButtonTitle;
+ bundle->GetStringFromName("closeButton.title", closeButtonTitle);
+ bundle->GetStringFromName("actionButton.label", actionButtonTitle);
+ if (!hostPort.IsEmpty()) {
+ AutoTArray<nsString, 1> formatStrings = {hostPort};
+ bundle->FormatStringFromName("webActions.disableForOrigin.label", formatStrings,
+ disableButtonTitle);
+ }
+ bundle->GetStringFromName("webActions.settings.label", settingsButtonTitle);
+
+ notification.otherButtonTitle = nsCocoaUtils::ToNSString(closeButtonTitle);
+
+ // OS X 10.8 only shows action buttons if the "Alerts" style is set in
+ // Notification Center preferences, and doesn't support the alternate
+ // action menu.
+ if ([notification respondsToSelector:@selector(set_showsButtons:)] &&
+ [notification respondsToSelector:@selector(set_alwaysShowAlternateActionMenu:)] &&
+ [notification respondsToSelector:@selector(set_alternateActionButtonTitles:)]) {
+ notification.hasActionButton = YES;
+ notification.actionButtonTitle = nsCocoaUtils::ToNSString(actionButtonTitle);
+
+ [(NSObject*)notification setValue:@(YES) forKey:@"_showsButtons"];
+ [(NSObject*)notification setValue:@(YES) forKey:@"_alwaysShowAlternateActionMenu"];
+ [(NSObject*)notification setValue:@[
+ nsCocoaUtils::ToNSString(disableButtonTitle), nsCocoaUtils::ToNSString(settingsButtonTitle)
+ ]
+ forKey:@"_alternateActionButtonTitles"];
+ }
+ }
+ nsAutoString name;
+ rv = aAlert->GetName(name);
+ // Don't let an alert name be more than MAX_NOTIFICATION_NAME_LEN characters.
+ // More than that shouldn't be necessary and userInfo (assigned to below) has
+ // a length limit of 16k on OS X 10.11. Exception thrown if limit exceeded.
+ if (name.Length() > MAX_NOTIFICATION_NAME_LEN) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ NSString* alertName = nsCocoaUtils::ToNSString(name);
+ if (!alertName) {
+ return NS_ERROR_FAILURE;
+ }
+ notification.userInfo =
+ [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:alertName, nil]
+ forKeys:[NSArray arrayWithObjects:@"name", nil]];
+
+ nsAutoString cookie;
+ rv = aAlert->GetCookie(cookie);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ OSXNotificationInfo* osxni = new OSXNotificationInfo(alertName, aAlertListener, cookie);
+
+ // Show the favicon if supported on this version of OS X.
+ if (aIconSize > 0 && [notification respondsToSelector:@selector(set_identityImage:)] &&
+ [notification respondsToSelector:@selector(set_identityImageHasBorder:)]) {
+ NSData* iconData = [NSData dataWithBytes:aIconData length:aIconSize];
+ NSImage* icon = [[[NSImage alloc] initWithData:iconData] autorelease];
+
+ [(NSObject*)notification setValue:icon forKey:@"_identityImage"];
+ [(NSObject*)notification setValue:@(NO) forKey:@"_identityImageHasBorder"];
+ }
+
+ bool inPrivateBrowsing;
+ rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Show the notification without waiting for an image if there is no icon URL or
+ // notification icons are not supported on this version of OS X.
+ if (![unClass instancesRespondToSelector:@selector(setContentImage:)]) {
+ CloseAlertCocoaString(alertName);
+ mActiveAlerts.AppendElement(osxni);
+ [GetNotificationCenter() deliverNotification:notification];
+ [notification release];
+ if (aAlertListener) {
+ aAlertListener->Observe(nullptr, "alertshow", cookie.get());
+ }
+ } else {
+ mPendingAlerts.AppendElement(osxni);
+ osxni->mPendingNotification = notification;
+ // Wait six seconds for the image to load.
+ rv = aAlert->LoadImage(6000, this, osxni, getter_AddRefs(osxni->mIconRequest));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ShowPendingNotification(osxni);
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::CloseAlert(const nsAString& aAlertName) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSString* alertName = nsCocoaUtils::ToNSString(aAlertName);
+ CloseAlertCocoaString(alertName);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void OSXNotificationCenter::CloseAlertCocoaString(NSString* aAlertName) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aAlertName) {
+ return; // Can't do anything without a name
+ }
+
+ NSArray* notifications = [GetNotificationCenter() deliveredNotifications];
+ for (id<FakeNSUserNotification> notification in notifications) {
+ NSString* name = [[notification userInfo] valueForKey:@"name"];
+ if ([name isEqualToString:aAlertName]) {
+ [GetNotificationCenter() removeDeliveredNotification:notification];
+ [GetNotificationCenter() _removeDisplayedNotification:notification];
+ break;
+ }
+ }
+
+ for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
+ OSXNotificationInfo* osxni = mActiveAlerts[i];
+ if ([aAlertName isEqualToString:osxni->mName]) {
+ if (osxni->mObserver) {
+ osxni->mObserver->Observe(nullptr, "alertfinished", osxni->mCookie.get());
+ }
+ if (osxni->mIconRequest) {
+ osxni->mIconRequest->Cancel(NS_BINDING_ABORTED);
+ osxni->mIconRequest = nullptr;
+ }
+ mActiveAlerts.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void OSXNotificationCenter::OnActivate(NSString* aAlertName,
+ NSUserNotificationActivationType aActivationType,
+ unsigned long long aAdditionalActionIndex) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aAlertName) {
+ return; // Can't do anything without a name
+ }
+
+ for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
+ OSXNotificationInfo* osxni = mActiveAlerts[i];
+ if ([aAlertName isEqualToString:osxni->mName]) {
+ if (osxni->mObserver) {
+ switch ((int)aActivationType) {
+ case NSUserNotificationActivationTypeAdditionalActionClicked:
+ case NSUserNotificationActivationTypeActionButtonClicked:
+ switch (aAdditionalActionIndex) {
+ case OSXNotificationActionDisable:
+ osxni->mObserver->Observe(nullptr, "alertdisablecallback", osxni->mCookie.get());
+ break;
+ case OSXNotificationActionSettings:
+ osxni->mObserver->Observe(nullptr, "alertsettingscallback", osxni->mCookie.get());
+ break;
+ default:
+ NS_WARNING("Unknown NSUserNotification additional action clicked");
+ break;
+ }
+ break;
+ default:
+ osxni->mObserver->Observe(nullptr, "alertclickcallback", osxni->mCookie.get());
+ break;
+ }
+ }
+ return;
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void OSXNotificationCenter::ShowPendingNotification(OSXNotificationInfo* osxni) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (osxni->mIconRequest) {
+ osxni->mIconRequest->Cancel(NS_BINDING_ABORTED);
+ osxni->mIconRequest = nullptr;
+ }
+
+ CloseAlertCocoaString(osxni->mName);
+
+ for (unsigned int i = 0; i < mPendingAlerts.Length(); i++) {
+ if (mPendingAlerts[i] == osxni) {
+ mActiveAlerts.AppendElement(osxni);
+ mPendingAlerts.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ [GetNotificationCenter() deliverNotification:osxni->mPendingNotification];
+
+ if (osxni->mObserver) {
+ osxni->mObserver->Observe(nullptr, "alertshow", osxni->mCookie.get());
+ }
+
+ [osxni->mPendingNotification release];
+ osxni->mPendingNotification = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::OnImageMissing(nsISupports* aUserData) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ OSXNotificationInfo* osxni = static_cast<OSXNotificationInfo*>(aUserData);
+ if (osxni->mPendingNotification) {
+ // If there was an error getting the image, or the request timed out, show
+ // the notification without a content image.
+ ShowPendingNotification(osxni);
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::OnImageReady(nsISupports* aUserData, imgIRequest* aRequest) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsCOMPtr<imgIContainer> image;
+ nsresult rv = aRequest->GetImage(getter_AddRefs(image));
+ if (NS_WARN_IF(NS_FAILED(rv) || !image)) {
+ return rv;
+ }
+
+ OSXNotificationInfo* osxni = static_cast<OSXNotificationInfo*>(aUserData);
+ if (!osxni->mPendingNotification) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSImage* cocoaImage = nil;
+ nsCocoaUtils::CreateDualRepresentationNSImageFromImageContainer(image, imgIContainer::FRAME_FIRST,
+ &cocoaImage);
+ (osxni->mPendingNotification).contentImage = cocoaImage;
+ [cocoaImage release];
+ ShowPendingNotification(osxni);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// nsIAlertsDoNotDisturb
+NS_IMETHODIMP
+OSXNotificationCenter::GetManualDoNotDisturb(bool* aRetVal) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+OSXNotificationCenter::SetManualDoNotDisturb(bool aDoNotDisturb) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::GetSuppressForScreenSharing(bool* aRetVal) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT
+
+ NS_ENSURE_ARG(aRetVal);
+ *aRetVal = mSuppressForScreenSharing;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::SetSuppressForScreenSharing(bool aSuppress) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT
+
+ mSuppressForScreenSharing = aSuppress;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT
+}
+
+} // namespace mozilla
diff --git a/widget/cocoa/ScreenHelperCocoa.h b/widget/cocoa/ScreenHelperCocoa.h
new file mode 100644
index 0000000000..91f4a19677
--- /dev/null
+++ b/widget/cocoa/ScreenHelperCocoa.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_cocoa_ScreenHelperCocoa_h
+#define mozilla_widget_cocoa_ScreenHelperCocoa_h
+
+#include "mozilla/widget/ScreenManager.h"
+
+@class ScreenHelperDelegate;
+@class NSScreen;
+
+namespace mozilla {
+namespace widget {
+
+class ScreenHelperCocoa final : public ScreenManager::Helper {
+ public:
+ ScreenHelperCocoa();
+ ~ScreenHelperCocoa() override;
+
+ void RefreshScreens();
+
+ static NSScreen* CocoaScreenForScreen(nsIScreen* aScreen);
+
+ private:
+ ScreenHelperDelegate* mDelegate;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_gtk_ScreenHelperGtk_h
diff --git a/widget/cocoa/ScreenHelperCocoa.mm b/widget/cocoa/ScreenHelperCocoa.mm
new file mode 100644
index 0000000000..af4c201f14
--- /dev/null
+++ b/widget/cocoa/ScreenHelperCocoa.mm
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "ScreenHelperCocoa.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/Logging.h"
+#include "nsCocoaUtils.h"
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+
+static LazyLogModule sScreenLog("WidgetScreen");
+
+@interface ScreenHelperDelegate : NSObject {
+ @private
+ mozilla::widget::ScreenHelperCocoa* mHelper;
+}
+
+- (id)initWithScreenHelper:(mozilla::widget::ScreenHelperCocoa*)aScreenHelper;
+- (void)didChangeScreenParameters:(NSNotification*)aNotification;
+@end
+
+@implementation ScreenHelperDelegate
+- (id)initWithScreenHelper:(mozilla::widget::ScreenHelperCocoa*)aScreenHelper {
+ if ((self = [self init])) {
+ mHelper = aScreenHelper;
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(didChangeScreenParameters:)
+ name:NSApplicationDidChangeScreenParametersNotification
+ object:nil];
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
+- (void)didChangeScreenParameters:(NSNotification*)aNotification {
+ MOZ_LOG(sScreenLog, LogLevel::Debug,
+ ("Received NSApplicationDidChangeScreenParametersNotification"));
+
+ mHelper->RefreshScreens();
+}
+@end
+
+namespace mozilla {
+namespace widget {
+
+ScreenHelperCocoa::ScreenHelperCocoa() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("ScreenHelperCocoa created"));
+
+ mDelegate = [[ScreenHelperDelegate alloc] initWithScreenHelper:this];
+
+ RefreshScreens();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+ScreenHelperCocoa::~ScreenHelperCocoa() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mDelegate release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static already_AddRefed<Screen> MakeScreen(NSScreen* aScreen) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ DesktopToLayoutDeviceScale contentsScaleFactor(nsCocoaUtils::GetBackingScaleFactor(aScreen));
+ CSSToLayoutDeviceScale defaultCssScaleFactor(contentsScaleFactor.scale);
+ NSRect frame = [aScreen frame];
+ LayoutDeviceIntRect rect =
+ nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, contentsScaleFactor.scale);
+ frame = [aScreen visibleFrame];
+ LayoutDeviceIntRect availRect =
+ nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, contentsScaleFactor.scale);
+ NSWindowDepth depth = [aScreen depth];
+ uint32_t pixelDepth = NSBitsPerPixelFromDepth(depth);
+ float dpi = 96.0f;
+ CGDirectDisplayID displayID =
+ [[[aScreen deviceDescription] objectForKey:@"NSScreenNumber"] intValue];
+ CGFloat heightMM = ::CGDisplayScreenSize(displayID).height;
+ if (heightMM > 0) {
+ dpi = rect.height / (heightMM / MM_PER_INCH_FLOAT);
+ }
+ MOZ_LOG(sScreenLog, LogLevel::Debug,
+ ("New screen [%d %d %d %d (%d %d %d %d) %d %f %f %f]", rect.x, rect.y, rect.width,
+ rect.height, availRect.x, availRect.y, availRect.width, availRect.height, pixelDepth,
+ contentsScaleFactor.scale, defaultCssScaleFactor.scale, dpi));
+
+ RefPtr<Screen> screen = new Screen(rect, availRect, pixelDepth, pixelDepth, contentsScaleFactor,
+ defaultCssScaleFactor, dpi);
+ return screen.forget();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr);
+}
+
+void ScreenHelperCocoa::RefreshScreens() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens"));
+
+ AutoTArray<RefPtr<Screen>, 4> screens;
+
+ for (NSScreen* screen in [NSScreen screens]) {
+ NSDictionary* desc = [screen deviceDescription];
+ if ([desc objectForKey:NSDeviceIsScreen] == nil) {
+ continue;
+ }
+ screens.AppendElement(MakeScreen(screen));
+ }
+
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ screenManager.Refresh(std::move(screens));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NSScreen* ScreenHelperCocoa::CocoaScreenForScreen(nsIScreen* aScreen) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ for (NSScreen* screen in [NSScreen screens]) {
+ NSDictionary* desc = [screen deviceDescription];
+ if ([desc objectForKey:NSDeviceIsScreen] == nil) {
+ continue;
+ }
+ LayoutDeviceIntRect rect;
+ double scale;
+ aScreen->GetRect(&rect.x, &rect.y, &rect.width, &rect.height);
+ aScreen->GetContentsScaleFactor(&scale);
+ NSRect frame = [screen frame];
+ LayoutDeviceIntRect frameRect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, scale);
+ if (rect == frameRect) {
+ return screen;
+ }
+ }
+ return [NSScreen mainScreen];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/cocoa/SwipeTracker.h b/widget/cocoa/SwipeTracker.h
new file mode 100644
index 0000000000..41685c1e29
--- /dev/null
+++ b/widget/cocoa/SwipeTracker.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SwipeTracker_h
+#define SwipeTracker_h
+
+#include "EventForwards.h"
+#include "mozilla/layers/AxisPhysicsMSDModel.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsRefreshObservers.h"
+#include "Units.h"
+
+class nsChildView;
+class nsIWidget;
+class nsRefreshDriver;
+
+namespace mozilla {
+
+class PanGestureInput;
+
+/**
+ * SwipeTracker turns PanGestureInput events into swipe events
+ * (WidgetSimpleGestureEvent) and dispatches them into Gecko.
+ * The swiping behavior mirrors the behavior of the Cocoa API
+ * -[NSEvent
+ * trackSwipeEventWithOptions:dampenAmountThresholdMin:max:usingHandler:].
+ * The advantage of using this class over the Cocoa API is that this class
+ * properly supports submitting queued up events to it, and that it hopefully
+ * doesn't intermittently break scrolling the way the Cocoa API does (bug
+ * 927702).
+ *
+ * The swipe direction is either left or right. It is determined before the
+ * SwipeTracker is created and stays fixed during the swipe.
+ * During the swipe, the swipe has a current "value" which is between 0 and the
+ * target value. The target value is either 1 (swiping left) or -1 (swiping
+ * right) - see SwipeSuccessTargetValue().
+ * A swipe can either succeed or fail. If it succeeds, the swipe animation
+ * animates towards the success target value; if it fails, it animates back to
+ * a value of 0. A swipe can only succeed if the user is swiping in an allowed
+ * direction. (Since both the allowed directions and the swipe direction are
+ * known at swipe start time, it's clear from the beginning whether a swipe is
+ * doomed to fail. In that case, the purpose of the SwipeTracker is to simulate
+ * a bounce-back animation.)
+ */
+class SwipeTracker final : public nsARefreshObserver {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(SwipeTracker, override)
+
+ SwipeTracker(nsChildView& aWidget, const PanGestureInput& aSwipeStartEvent,
+ uint32_t aAllowedDirections, uint32_t aSwipeDirection);
+
+ void Destroy();
+
+ nsEventStatus ProcessEvent(const PanGestureInput& aEvent);
+ void CancelSwipe(const TimeStamp& aTimeStamp);
+
+ static WidgetSimpleGestureEvent CreateSwipeGestureEvent(
+ EventMessage aMsg, nsIWidget* aWidget,
+ const LayoutDeviceIntPoint& aPosition, const TimeStamp& aTimeStamp);
+
+ // nsARefreshObserver
+ void WillRefresh(mozilla::TimeStamp aTime) override;
+
+ protected:
+ ~SwipeTracker();
+
+ bool SwipingInAllowedDirection() const {
+ return mAllowedDirections & mSwipeDirection;
+ }
+ double SwipeSuccessTargetValue() const;
+ double ClampToAllowedRange(double aGestureAmount) const;
+ bool ComputeSwipeSuccess() const;
+ void StartAnimating(double aTargetValue);
+ void SwipeFinished(const TimeStamp& aTimeStamp);
+ void UnregisterFromRefreshDriver();
+ bool SendSwipeEvent(EventMessage aMsg, uint32_t aDirection, double aDelta,
+ const TimeStamp& aTimeStamp);
+
+ nsChildView& mWidget;
+ RefPtr<nsRefreshDriver> mRefreshDriver;
+ layers::AxisPhysicsMSDModel mAxis;
+ const LayoutDeviceIntPoint mEventPosition;
+ TimeStamp mLastEventTimeStamp;
+ TimeStamp mLastAnimationFrameTime;
+ const uint32_t mAllowedDirections;
+ const uint32_t mSwipeDirection;
+ double mGestureAmount;
+ double mCurrentVelocity;
+ bool mEventsAreControllingSwipe;
+ bool mEventsHaveStartedNewGesture;
+ bool mRegisteredWithRefreshDriver;
+};
+
+} // namespace mozilla
+
+#endif // SwipeTracker_h
diff --git a/widget/cocoa/SwipeTracker.mm b/widget/cocoa/SwipeTracker.mm
new file mode 100644
index 0000000000..50c9579200
--- /dev/null
+++ b/widget/cocoa/SwipeTracker.mm
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SwipeTracker.h"
+
+#include "InputData.h"
+#include "mozilla/FlushType.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/dom/SimpleGestureEventBinding.h"
+#include "nsAlgorithm.h"
+#include "nsChildView.h"
+#include "nsRefreshDriver.h"
+#include "UnitTransforms.h"
+
+// These values were tweaked to make the physics feel similar to the native swipe.
+static const double kSpringForce = 250.0;
+static const double kVelocityTwitchTolerance = 0.0000001;
+static const double kWholePagePixelSize = 1000.0;
+static const double kRubberBandResistanceFactor = 4.0;
+static const double kSwipeSuccessThreshold = 0.25;
+static const double kSwipeSuccessVelocityContribution = 0.3;
+
+namespace mozilla {
+
+static already_AddRefed<nsRefreshDriver> GetRefreshDriver(nsIWidget& aWidget) {
+ nsIWidgetListener* widgetListener = aWidget.GetWidgetListener();
+ PresShell* presShell = widgetListener ? widgetListener->GetPresShell() : nullptr;
+ nsPresContext* presContext = presShell ? presShell->GetPresContext() : nullptr;
+ RefPtr<nsRefreshDriver> refreshDriver = presContext ? presContext->RefreshDriver() : nullptr;
+ return refreshDriver.forget();
+}
+
+SwipeTracker::SwipeTracker(nsChildView& aWidget, const PanGestureInput& aSwipeStartEvent,
+ uint32_t aAllowedDirections, uint32_t aSwipeDirection)
+ : mWidget(aWidget),
+ mRefreshDriver(GetRefreshDriver(mWidget)),
+ mAxis(0.0, 0.0, 0.0, kSpringForce, 1.0),
+ mEventPosition(RoundedToInt(ViewAs<LayoutDevicePixel>(
+ aSwipeStartEvent.mPanStartPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent))),
+ mLastEventTimeStamp(aSwipeStartEvent.mTimeStamp),
+ mAllowedDirections(aAllowedDirections),
+ mSwipeDirection(aSwipeDirection),
+ mGestureAmount(0.0),
+ mCurrentVelocity(0.0),
+ mEventsAreControllingSwipe(true),
+ mEventsHaveStartedNewGesture(false),
+ mRegisteredWithRefreshDriver(false) {
+ SendSwipeEvent(eSwipeGestureStart, 0, 0.0, aSwipeStartEvent.mTimeStamp);
+ ProcessEvent(aSwipeStartEvent);
+}
+
+void SwipeTracker::Destroy() { UnregisterFromRefreshDriver(); }
+
+SwipeTracker::~SwipeTracker() {
+ MOZ_ASSERT(!mRegisteredWithRefreshDriver, "Destroy needs to be called before deallocating");
+}
+
+double SwipeTracker::SwipeSuccessTargetValue() const {
+ return (mSwipeDirection == dom::SimpleGestureEvent_Binding::DIRECTION_RIGHT) ? -1.0 : 1.0;
+}
+
+double SwipeTracker::ClampToAllowedRange(double aGestureAmount) const {
+ // gestureAmount needs to stay between -1 and 0 when swiping right and
+ // between 0 and 1 when swiping left.
+ double min = (mSwipeDirection == dom::SimpleGestureEvent_Binding::DIRECTION_RIGHT) ? -1.0 : 0.0;
+ double max = (mSwipeDirection == dom::SimpleGestureEvent_Binding::DIRECTION_LEFT) ? 1.0 : 0.0;
+ return clamped(aGestureAmount, min, max);
+}
+
+bool SwipeTracker::ComputeSwipeSuccess() const {
+ double targetValue = SwipeSuccessTargetValue();
+
+ // If the fingers were moving away from the target direction when they were
+ // lifted from the touchpad, abort the swipe.
+ if (mCurrentVelocity * targetValue < -kVelocityTwitchTolerance) {
+ return false;
+ }
+
+ return (mGestureAmount * targetValue +
+ mCurrentVelocity * targetValue * kSwipeSuccessVelocityContribution) >=
+ kSwipeSuccessThreshold;
+}
+
+nsEventStatus SwipeTracker::ProcessEvent(const PanGestureInput& aEvent) {
+ // If the fingers have already been lifted, don't process this event for swiping.
+ if (!mEventsAreControllingSwipe) {
+ // Return nsEventStatus_eConsumeNoDefault for events from the swipe gesture
+ // and nsEventStatus_eIgnore for events of subsequent scroll gestures.
+ if (aEvent.mType == PanGestureInput::PANGESTURE_MAYSTART ||
+ aEvent.mType == PanGestureInput::PANGESTURE_START) {
+ mEventsHaveStartedNewGesture = true;
+ }
+ return mEventsHaveStartedNewGesture ? nsEventStatus_eIgnore : nsEventStatus_eConsumeNoDefault;
+ }
+
+ double delta = -aEvent.mPanDisplacement.x / mWidget.BackingScaleFactor() / kWholePagePixelSize;
+ if (!SwipingInAllowedDirection()) {
+ delta /= kRubberBandResistanceFactor;
+ }
+ mGestureAmount = ClampToAllowedRange(mGestureAmount + delta);
+ SendSwipeEvent(eSwipeGestureUpdate, 0, mGestureAmount, aEvent.mTimeStamp);
+
+ if (aEvent.mType != PanGestureInput::PANGESTURE_END) {
+ double elapsedSeconds = std::max(0.008, (aEvent.mTimeStamp - mLastEventTimeStamp).ToSeconds());
+ mCurrentVelocity = delta / elapsedSeconds;
+ mLastEventTimeStamp = aEvent.mTimeStamp;
+ } else {
+ mEventsAreControllingSwipe = false;
+ bool didSwipeSucceed = SwipingInAllowedDirection() && ComputeSwipeSuccess();
+ double targetValue = 0.0;
+ if (didSwipeSucceed) {
+ // Let's use same timestamp as previous event because this is caused by
+ // the preceding event.
+ SendSwipeEvent(eSwipeGesture, mSwipeDirection, 0.0, aEvent.mTimeStamp);
+ targetValue = SwipeSuccessTargetValue();
+ }
+ StartAnimating(targetValue);
+ }
+
+ return nsEventStatus_eConsumeNoDefault;
+}
+
+void SwipeTracker::StartAnimating(double aTargetValue) {
+ mAxis.SetPosition(mGestureAmount);
+ mAxis.SetDestination(aTargetValue);
+ mAxis.SetVelocity(mCurrentVelocity);
+
+ mLastAnimationFrameTime = TimeStamp::Now();
+
+ // Add ourselves as a refresh driver observer. The refresh driver
+ // will call WillRefresh for each animation frame until we
+ // unregister ourselves.
+ MOZ_ASSERT(!mRegisteredWithRefreshDriver);
+ if (mRefreshDriver) {
+ mRefreshDriver->AddRefreshObserver(this, FlushType::Style, "Swipe animation");
+ mRegisteredWithRefreshDriver = true;
+ }
+}
+
+void SwipeTracker::WillRefresh(mozilla::TimeStamp aTime) {
+ TimeStamp now = TimeStamp::Now();
+ mAxis.Simulate(now - mLastAnimationFrameTime);
+ mLastAnimationFrameTime = now;
+
+ bool isFinished = mAxis.IsFinished(1.0 / kWholePagePixelSize);
+ mGestureAmount = (isFinished ? mAxis.GetDestination() : mAxis.GetPosition());
+ SendSwipeEvent(eSwipeGestureUpdate, 0, mGestureAmount, now);
+
+ if (isFinished) {
+ UnregisterFromRefreshDriver();
+ SwipeFinished(now);
+ }
+}
+
+void SwipeTracker::CancelSwipe(const TimeStamp& aTimeStamp) {
+ SendSwipeEvent(eSwipeGestureEnd, 0, 0.0, aTimeStamp);
+}
+
+void SwipeTracker::SwipeFinished(const TimeStamp& aTimeStamp) {
+ SendSwipeEvent(eSwipeGestureEnd, 0, 0.0, aTimeStamp);
+ mWidget.SwipeFinished();
+}
+
+void SwipeTracker::UnregisterFromRefreshDriver() {
+ if (mRegisteredWithRefreshDriver) {
+ MOZ_ASSERT(mRefreshDriver, "How were we able to register, then?");
+ mRefreshDriver->RemoveRefreshObserver(this, FlushType::Style);
+ }
+ mRegisteredWithRefreshDriver = false;
+}
+
+/* static */ WidgetSimpleGestureEvent SwipeTracker::CreateSwipeGestureEvent(
+ EventMessage aMsg, nsIWidget* aWidget, const LayoutDeviceIntPoint& aPosition,
+ const TimeStamp& aTimeStamp) {
+ // XXX Why isn't this initialized with nsCocoaUtils::InitInputEvent()?
+ WidgetSimpleGestureEvent geckoEvent(true, aMsg, aWidget);
+ geckoEvent.mModifiers = 0;
+ // XXX How about geckoEvent.mTime?
+ geckoEvent.mTimeStamp = aTimeStamp;
+ geckoEvent.mRefPoint = aPosition;
+ geckoEvent.mButtons = 0;
+ return geckoEvent;
+}
+
+bool SwipeTracker::SendSwipeEvent(EventMessage aMsg, uint32_t aDirection, double aDelta,
+ const TimeStamp& aTimeStamp) {
+ WidgetSimpleGestureEvent geckoEvent =
+ CreateSwipeGestureEvent(aMsg, &mWidget, mEventPosition, aTimeStamp);
+ geckoEvent.mDirection = aDirection;
+ geckoEvent.mDelta = aDelta;
+ geckoEvent.mAllowedDirections = mAllowedDirections;
+ return mWidget.DispatchWindowEvent(geckoEvent);
+}
+
+} // namespace mozilla
diff --git a/widget/cocoa/TextInputHandler.h b/widget/cocoa/TextInputHandler.h
new file mode 100644
index 0000000000..96d4346aae
--- /dev/null
+++ b/widget/cocoa/TextInputHandler.h
@@ -0,0 +1,1287 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TextInputHandler_h_
+#define TextInputHandler_h_
+
+#include "nsCocoaUtils.h"
+
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+#include "mozView.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "WritingModes.h"
+
+class nsChildView;
+
+namespace mozilla {
+namespace widget {
+
+// Key code constants
+enum {
+#if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
+ kVK_RightCommand = 0x36, // right command key
+#endif
+
+ kVK_PC_PrintScreen = kVK_F13,
+ kVK_PC_ScrollLock = kVK_F14,
+ kVK_PC_Pause = kVK_F15,
+
+ kVK_PC_Insert = kVK_Help,
+ kVK_PC_Backspace = kVK_Delete,
+ kVK_PC_Delete = kVK_ForwardDelete,
+
+ kVK_PC_ContextMenu = 0x6E,
+
+ kVK_Powerbook_KeypadEnter = 0x34 // Enter on Powerbook's keyboard is different
+};
+
+/**
+ * TISInputSourceWrapper is a wrapper for the TISInputSourceRef. If we get the
+ * TISInputSourceRef from InputSourceID, we need to release the CFArray instance
+ * which is returned by TISCreateInputSourceList. However, when we release the
+ * list, we cannot access the TISInputSourceRef. So, it's not usable, and it
+ * may cause the memory leak bugs. nsTISInputSource automatically releases the
+ * list when the instance is destroyed.
+ */
+class TISInputSourceWrapper {
+ public:
+ static TISInputSourceWrapper& CurrentInputSource();
+ /**
+ * Shutdown() should be called when nobody doesn't need to use this class.
+ */
+ static void Shutdown();
+
+ TISInputSourceWrapper()
+ : mInputSource{nullptr},
+ mKeyboardLayout{nullptr},
+ mUCKeyboardLayout{nullptr},
+ mIsRTL{0},
+ mOverrideKeyboard{false} {
+ mInputSourceList = nullptr;
+ Clear();
+ }
+
+ explicit TISInputSourceWrapper(const char* aID)
+ : mInputSource{nullptr},
+ mKeyboardLayout{nullptr},
+ mUCKeyboardLayout{nullptr},
+ mIsRTL{0},
+ mOverrideKeyboard{false} {
+ mInputSourceList = nullptr;
+ InitByInputSourceID(aID);
+ }
+
+ explicit TISInputSourceWrapper(SInt32 aLayoutID)
+ : mInputSource{nullptr},
+ mKeyboardLayout{nullptr},
+ mUCKeyboardLayout{nullptr},
+ mIsRTL{0},
+ mOverrideKeyboard{false} {
+ mInputSourceList = nullptr;
+ InitByLayoutID(aLayoutID);
+ }
+
+ explicit TISInputSourceWrapper(TISInputSourceRef aInputSource)
+ : mInputSource{nullptr},
+ mKeyboardLayout{nullptr},
+ mUCKeyboardLayout{nullptr},
+ mIsRTL{0},
+ mOverrideKeyboard{false} {
+ mInputSourceList = nullptr;
+ InitByTISInputSourceRef(aInputSource);
+ }
+
+ ~TISInputSourceWrapper() { Clear(); }
+
+ void InitByInputSourceID(const char* aID);
+ void InitByInputSourceID(const nsString& aID);
+ void InitByInputSourceID(const CFStringRef aID);
+ /**
+ * InitByLayoutID() initializes the keyboard layout by the layout ID.
+ *
+ * @param aLayoutID An ID of keyboard layout.
+ * 0: US
+ * 1: Greek
+ * 2: German
+ * 3: Swedish-Pro
+ * 4: Dvorak-Qwerty Cmd
+ * 5: Thai
+ * 6: Arabic
+ * 7: French
+ * 8: Hebrew
+ * 9: Lithuanian
+ * 10: Norwegian
+ * 11: Spanish
+ * @param aOverrideKeyboard When testing set to TRUE, otherwise, set to
+ * FALSE. When TRUE, we use an ANSI keyboard
+ * instead of the actual keyboard.
+ */
+ void InitByLayoutID(SInt32 aLayoutID, bool aOverrideKeyboard = false);
+ void InitByCurrentInputSource();
+ void InitByCurrentKeyboardLayout();
+ void InitByCurrentASCIICapableInputSource();
+ void InitByCurrentASCIICapableKeyboardLayout();
+ void InitByCurrentInputMethodKeyboardLayoutOverride();
+ void InitByTISInputSourceRef(TISInputSourceRef aInputSource);
+ void InitByLanguage(CFStringRef aLanguage);
+
+ /**
+ * If the instance is initialized with a keyboard layout input source,
+ * returns it.
+ * If the instance is initialized with an IME mode input source, the result
+ * references the keyboard layout for the IME mode. However, this can be
+ * initialized only when the IME mode is actually selected. I.e, if IME mode
+ * input source is initialized with LayoutID or SourceID, this returns null.
+ */
+ TISInputSourceRef GetKeyboardLayoutInputSource() const { return mKeyboardLayout; }
+ const UCKeyboardLayout* GetUCKeyboardLayout();
+
+ bool IsOpenedIMEMode();
+ bool IsIMEMode();
+ bool IsKeyboardLayout();
+
+ bool IsASCIICapable() {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetBoolProperty(kTISPropertyInputSourceIsASCIICapable);
+ }
+
+ bool IsEnabled() {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetBoolProperty(kTISPropertyInputSourceIsEnabled);
+ }
+
+ bool GetLanguageList(CFArrayRef& aLanguageList);
+ bool GetPrimaryLanguage(CFStringRef& aPrimaryLanguage);
+ bool GetPrimaryLanguage(nsAString& aPrimaryLanguage);
+
+ bool GetLocalizedName(CFStringRef& aName) {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyLocalizedName, aName);
+ }
+
+ bool GetLocalizedName(nsAString& aName) {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyLocalizedName, aName);
+ }
+
+ bool GetInputSourceID(CFStringRef& aID) {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyInputSourceID, aID);
+ }
+
+ bool GetInputSourceID(nsAString& aID) {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyInputSourceID, aID);
+ }
+
+ bool GetBundleID(CFStringRef& aBundleID) {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyBundleID, aBundleID);
+ }
+
+ bool GetBundleID(nsAString& aBundleID) {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyBundleID, aBundleID);
+ }
+
+ bool GetInputSourceType(CFStringRef& aType) {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyInputSourceType, aType);
+ }
+
+ bool GetInputSourceType(nsAString& aType) {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyInputSourceType, aType);
+ }
+
+ bool IsForRTLLanguage();
+ bool IsForJapaneseLanguage();
+ bool IsInitializedByCurrentInputSource();
+
+ enum {
+ // 40 is an actual result of the ::LMGetKbdType() when we connect an
+ // unknown keyboard and set the keyboard type to ANSI manually on the
+ // set up dialog.
+ eKbdType_ANSI = 40
+ };
+
+ void Select();
+ void Clear();
+
+ /**
+ * InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent.
+ *
+ * @param aNativeKeyEvent A native key event for which you want to
+ * dispatch a Gecko key event.
+ * @param aKeyEvent The result -- a Gecko key event initialized
+ * from the native key event.
+ * @param aIsProcessedByIME true if aNativeKeyEvent has been handled
+ * by IME (but except if the composition was
+ * started with dead key).
+ * @param aInsertString If caller expects that the event will cause
+ * a character to be input (say in an editor),
+ * the caller should set this. Otherwise,
+ * if caller sets null to this, this method will
+ * compute the character to be input from
+ * characters of aNativeKeyEvent.
+ */
+ void InitKeyEvent(NSEvent* aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
+ bool aIsProcessedByIME, const nsAString* aInsertString = nullptr);
+
+ /**
+ * WillDispatchKeyboardEvent() computes aKeyEvent.mAlternativeCharCodes and
+ * recompute aKeyEvent.mCharCode if it's necessary.
+ *
+ * @param aNativeKeyEvent A native key event for which you want to
+ * dispatch a Gecko key event.
+ * @param aInsertString If caller expects that the event will cause
+ * a character to be input (say in an editor),
+ * the caller should set this. Otherwise,
+ * if caller sets null to this, this method will
+ * compute the character to be input from
+ * characters of aNativeKeyEvent.
+ * @param aIndexOfKeypress Index of the eKeyPress event. If a key
+ * inputs 2 or more characters, eKeyPress events
+ * are dispatched for each character. This is
+ * 0 for the first eKeyPress event.
+ * @param aKeyEvent The result -- a Gecko key event initialized
+ * from the native key event. This must be
+ * eKeyPress event.
+ */
+ void WillDispatchKeyboardEvent(NSEvent* aNativeKeyEvent, const nsAString* aInsertString,
+ uint32_t aIndexOfKeypress, WidgetKeyboardEvent& aKeyEvent);
+
+ /**
+ * ComputeGeckoKeyCode() returns Gecko keycode for aNativeKeyCode on current
+ * keyboard layout.
+ *
+ * @param aNativeKeyCode A native keycode.
+ * @param aKbType A native Keyboard Type value. Typically,
+ * this is a result of ::LMGetKbdType().
+ * @param aCmdIsPressed TRUE if Cmd key is pressed. Otherwise, FALSE.
+ * @return The computed Gecko keycode.
+ */
+ uint32_t ComputeGeckoKeyCode(UInt32 aNativeKeyCode, UInt32 aKbType, bool aCmdIsPressed);
+
+ /**
+ * ComputeGeckoKeyNameIndex() returns Gecko key name index for the key.
+ *
+ * @param aNativeKeyCode A native keycode.
+ */
+ static KeyNameIndex ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode);
+
+ /**
+ * ComputeGeckoCodeNameIndex() returns Gecko code name index for the key.
+ *
+ * @param aNativeKeyCode A native keycode.
+ * @param aKbType A native Keyboard Type value. Typically,
+ * this is a result of ::LMGetKbdType().
+ */
+ static CodeNameIndex ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode, UInt32 aKbType);
+
+ /**
+ * TranslateToChar() checks if aNativeKeyEvent is a dead key.
+ *
+ * @param aNativeKeyEvent A native key event.
+ * @return Returns true if the key event is a dead key
+ * event. Otherwise, false.
+ */
+ bool IsDeadKey(NSEvent* aNativeKeyEvent);
+
+ protected:
+ /**
+ * TranslateToString() computes the inputted text from the native keyCode,
+ * modifier flags and keyboard type.
+ *
+ * @param aKeyCode A native keyCode.
+ * @param aModifiers Combination of native modifier flags.
+ * @param aKbType A native Keyboard Type value. Typically,
+ * this is a result of ::LMGetKbdType().
+ * @param aStr Result, i.e., inputted text.
+ * The result can be two or more characters.
+ * @return If succeeded, TRUE. Otherwise, FALSE.
+ * Even if TRUE, aStr can be empty string.
+ */
+ bool TranslateToString(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType, nsAString& aStr);
+
+ /**
+ * TranslateToChar() computes the inputted character from the native keyCode,
+ * modifier flags and keyboard type. If two or more characters would be
+ * input, this returns 0.
+ *
+ * @param aKeyCode A native keyCode.
+ * @param aModifiers Combination of native modifier flags.
+ * @param aKbType A native Keyboard Type value. Typically,
+ * this is a result of ::LMGetKbdType().
+ * @return If succeeded and the result is one character,
+ * returns the charCode of it. Otherwise,
+ * returns 0.
+ */
+ uint32_t TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType);
+
+ /**
+ * TranslateToChar() checks if aKeyCode with aModifiers is a dead key.
+ *
+ * @param aKeyCode A native keyCode.
+ * @param aModifiers Combination of native modifier flags.
+ * @param aKbType A native Keyboard Type value. Typically,
+ * this is a result of ::LMGetKbdType().
+ * @return Returns true if the key with specified
+ * modifier state is a dead key. Otherwise,
+ * false.
+ */
+ bool IsDeadKey(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType);
+
+ /**
+ * ComputeInsertString() computes string to be inserted with the key event.
+ *
+ * @param aNativeKeyEvent The native key event which causes our keyboard
+ * event(s).
+ * @param aKeyEvent A Gecko key event which was partially
+ * initialized with aNativeKeyEvent.
+ * @param aInsertString The string to be inputting by aNativeKeyEvent.
+ * This should be specified by InsertText().
+ * In other words, if the key event doesn't cause
+ * a call of InsertText(), this can be nullptr.
+ * @param aResult The string which should be set to charCode of
+ * keypress event(s).
+ */
+ void ComputeInsertStringForCharCode(NSEvent* aNativeKeyEvent,
+ const WidgetKeyboardEvent& aKeyEvent,
+ const nsAString* aInsertString, nsAString& aResult);
+
+ /**
+ * IsPrintableKeyEvent() returns true if aNativeKeyEvent is caused by
+ * a printable key. Otherwise, returns false.
+ */
+ bool IsPrintableKeyEvent(NSEvent* aNativeKeyEvent) const;
+
+ /**
+ * GetKbdType() returns physical keyboard type.
+ */
+ UInt32 GetKbdType() const;
+
+ bool GetBoolProperty(const CFStringRef aKey);
+ bool GetStringProperty(const CFStringRef aKey, CFStringRef& aStr);
+ bool GetStringProperty(const CFStringRef aKey, nsAString& aStr);
+
+ TISInputSourceRef mInputSource;
+ TISInputSourceRef mKeyboardLayout;
+ CFArrayRef mInputSourceList;
+ const UCKeyboardLayout* mUCKeyboardLayout;
+ int8_t mIsRTL;
+
+ bool mOverrideKeyboard;
+
+ static TISInputSourceWrapper* sCurrentInputSource;
+};
+
+/**
+ * TextInputHandlerBase is a base class of IMEInputHandler and TextInputHandler.
+ * Utility methods should be implemented this level.
+ */
+
+class TextInputHandlerBase : public TextEventDispatcherListener {
+ public:
+ /**
+ * Other TextEventDispatcherListener methods should be implemented in
+ * IMEInputHandler.
+ */
+ NS_DECL_ISUPPORTS
+
+ /**
+ * DispatchEvent() dispatches aEvent on mWidget.
+ *
+ * @param aEvent An event which you want to dispatch.
+ * @return TRUE if the event is consumed by web contents
+ * or chrome contents. Otherwise, FALSE.
+ */
+ bool DispatchEvent(WidgetGUIEvent& aEvent);
+
+ /**
+ * SetSelection() dispatches eSetSelection event for the aRange.
+ *
+ * @param aRange The range which will be selected.
+ * @return TRUE if setting selection is succeeded and
+ * the widget hasn't been destroyed.
+ * Otherwise, FALSE.
+ */
+ bool SetSelection(NSRange& aRange);
+
+ /**
+ * InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent.
+ *
+ * @param aNativeKeyEvent A native key event for which you want to
+ * dispatch a Gecko key event.
+ * @param aKeyEvent The result -- a Gecko key event initialized
+ * from the native key event.
+ * @param aIsProcessedByIME true if aNativeKeyEvent has been handled
+ * by IME (but except if the composition was
+ * started with dead key).
+ * @param aInsertString If caller expects that the event will cause
+ * a character to be input (say in an editor),
+ * the caller should set this. Otherwise,
+ * if caller sets null to this, this method will
+ * compute the character to be input from
+ * characters of aNativeKeyEvent.
+ */
+ void InitKeyEvent(NSEvent* aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
+ bool aIsProcessedByIME, const nsAString* aInsertString = nullptr);
+
+ /**
+ * SynthesizeNativeKeyEvent() is an implementation of
+ * nsIWidget::SynthesizeNativeKeyEvent(). See the document in nsIWidget.h
+ * for the detail.
+ */
+ nsresult SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode,
+ uint32_t aModifierFlags, const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters);
+
+ /**
+ * Utility method intended for testing. Attempts to construct a native key
+ * event that would have been generated during an actual key press. This
+ * *does not dispatch* the native event. Instead, it is attached to the
+ * |mNativeKeyEvent| field of the Gecko event that is passed in.
+ * @param aKeyEvent Gecko key event to attach the native event to
+ */
+ NS_IMETHOD AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent);
+
+ /**
+ * GetWindowLevel() returns the window level of current focused (in Gecko)
+ * window. E.g., if an <input> element in XUL panel has focus, this returns
+ * the XUL panel's window level.
+ */
+ NSInteger GetWindowLevel();
+
+ /**
+ * IsSpecialGeckoKey() checks whether aNativeKeyCode is mapped to a special
+ * Gecko keyCode. A key is "special" if it isn't used for text input.
+ *
+ * @param aNativeKeyCode A native keycode.
+ * @return If the keycode is mapped to a special key,
+ * TRUE. Otherwise, FALSE.
+ */
+ static bool IsSpecialGeckoKey(UInt32 aNativeKeyCode);
+
+ /**
+ * EnableSecureEventInput() and DisableSecureEventInput() wrap the Carbon
+ * Event Manager APIs with the same names. In addition they keep track of
+ * how many times we've called them (in the same process) -- unlike the
+ * Carbon Event Manager APIs, which only keep track of how many times they've
+ * been called from any and all processes.
+ *
+ * The Carbon Event Manager's IsSecureEventInputEnabled() returns whether
+ * secure event input mode is enabled (in any process). This class's
+ * IsSecureEventInputEnabled() returns whether we've made any calls to
+ * EnableSecureEventInput() that are not (yet) offset by the calls we've
+ * made to DisableSecureEventInput().
+ */
+ static void EnableSecureEventInput();
+ static void DisableSecureEventInput();
+ static bool IsSecureEventInputEnabled();
+
+ /**
+ * EnsureSecureEventInputDisabled() calls DisableSecureEventInput() until
+ * our call count becomes 0.
+ */
+ static void EnsureSecureEventInputDisabled();
+
+ public:
+ /**
+ * mWidget must not be destroyed without OnDestroyWidget being called.
+ *
+ * @param aDestroyingWidget Destroying widget. This might not be mWidget.
+ * @return This result doesn't have any meaning for
+ * callers. When aDstroyingWidget isn't the same
+ * as mWidget, FALSE. Then, inherited methods in
+ * sub classes should return from this method
+ * without cleaning up.
+ */
+ virtual bool OnDestroyWidget(nsChildView* aDestroyingWidget);
+
+ protected:
+ // The creator of this instance, client and its text event dispatcher.
+ // These members must not be nullptr after initialized until
+ // OnDestroyWidget() is called.
+ nsChildView* mWidget; // [WEAK]
+ RefPtr<TextEventDispatcher> mDispatcher;
+
+ // The native view for mWidget.
+ // This view handles the actual text inputting.
+ NSView<mozView>* mView; // [STRONG]
+
+ TextInputHandlerBase(nsChildView* aWidget, NSView<mozView>* aNativeView);
+ virtual ~TextInputHandlerBase();
+
+ bool Destroyed() { return !mWidget; }
+
+ /**
+ * mCurrentKeyEvent indicates what key event we are handling. While
+ * handling a native keydown event, we need to store the event for insertText,
+ * doCommandBySelector and various action message handlers of NSResponder
+ * such as [NSResponder insertNewline:sender].
+ */
+ struct KeyEventState {
+ // Handling native key event
+ NSEvent* mKeyEvent;
+ // String specified by InsertText(). This is not null only during a
+ // call of InsertText().
+ nsAString* mInsertString;
+ // String which are included in [mKeyEvent characters] and already handled
+ // by InsertText() call(s).
+ nsString mInsertedString;
+ // Unique id associated with a keydown / keypress event. It's ok if this
+ // wraps over long periods.
+ uint32_t mUniqueId;
+ // Whether keydown event was dispatched for mKeyEvent.
+ bool mKeyDownDispatched;
+ // Whether keydown event was consumed by web contents or chrome contents.
+ bool mKeyDownHandled;
+ // Whether keypress event was dispatched for mKeyEvent.
+ bool mKeyPressDispatched;
+ // Whether keypress event was consumed by web contents or chrome contents.
+ bool mKeyPressHandled;
+ // Whether the key event causes other key events via IME or something.
+ bool mCausedOtherKeyEvents;
+ // Whether the key event causes composition change or committing
+ // composition. So, even if InsertText() is called, this may be false
+ // if it dispatches keypress event.
+ bool mCompositionDispatched;
+
+ KeyEventState() : mKeyEvent(nullptr), mUniqueId(0) { Clear(); }
+
+ explicit KeyEventState(NSEvent* aNativeKeyEvent, uint32_t aUniqueId = 0)
+ : mKeyEvent(nullptr), mUniqueId(0) {
+ Clear();
+ Set(aNativeKeyEvent, aUniqueId);
+ }
+
+ KeyEventState(const KeyEventState& aOther) = delete;
+
+ ~KeyEventState() { Clear(); }
+
+ void Set(NSEvent* aNativeKeyEvent, uint32_t aUniqueId = 0) {
+ MOZ_ASSERT(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
+ Clear();
+ mKeyEvent = [aNativeKeyEvent retain];
+ mUniqueId = aUniqueId;
+ }
+
+ void Clear() {
+ if (mKeyEvent) {
+ [mKeyEvent release];
+ mKeyEvent = nullptr;
+ mUniqueId = 0;
+ }
+ mInsertString = nullptr;
+ mInsertedString.Truncate();
+ mKeyDownDispatched = false;
+ mKeyDownHandled = false;
+ mKeyPressDispatched = false;
+ mKeyPressHandled = false;
+ mCausedOtherKeyEvents = false;
+ mCompositionDispatched = false;
+ }
+
+ bool IsDefaultPrevented() const {
+ return mKeyDownHandled || mKeyPressHandled || mCausedOtherKeyEvents || mCompositionDispatched;
+ }
+
+ bool CanDispatchKeyDownEvent() const { return !mKeyDownDispatched; }
+
+ bool CanDispatchKeyPressEvent() const { return !mKeyPressDispatched && !IsDefaultPrevented(); }
+
+ bool CanHandleCommand() const { return !mKeyDownHandled && !mKeyPressHandled; }
+
+ bool IsProperKeyEvent(Command aCommand) const {
+ if (NS_WARN_IF(!mKeyEvent)) {
+ return false;
+ }
+ KeyNameIndex keyNameIndex =
+ TISInputSourceWrapper::ComputeGeckoKeyNameIndex([mKeyEvent keyCode]);
+ Modifiers modifiers = nsCocoaUtils::ModifiersForEvent(mKeyEvent) &
+ (MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
+ switch (aCommand) {
+ case Command::InsertLineBreak:
+ return keyNameIndex == KEY_NAME_INDEX_Enter && modifiers == MODIFIER_CONTROL;
+ case Command::InsertParagraph:
+ return keyNameIndex == KEY_NAME_INDEX_Enter && modifiers == MODIFIER_NONE;
+ case Command::DeleteCharBackward:
+ return keyNameIndex == KEY_NAME_INDEX_Backspace && modifiers == MODIFIER_NONE;
+ case Command::DeleteToBeginningOfLine:
+ return keyNameIndex == KEY_NAME_INDEX_Backspace && modifiers == MODIFIER_META;
+ case Command::DeleteWordBackward:
+ return keyNameIndex == KEY_NAME_INDEX_Backspace && modifiers == MODIFIER_ALT;
+ case Command::DeleteCharForward:
+ return keyNameIndex == KEY_NAME_INDEX_Delete && modifiers == MODIFIER_NONE;
+ case Command::DeleteWordForward:
+ return keyNameIndex == KEY_NAME_INDEX_Delete && modifiers == MODIFIER_ALT;
+ case Command::InsertTab:
+ return keyNameIndex == KEY_NAME_INDEX_Tab && modifiers == MODIFIER_NONE;
+ case Command::InsertBacktab:
+ return keyNameIndex == KEY_NAME_INDEX_Tab && modifiers == MODIFIER_SHIFT;
+ case Command::CharNext:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowRight && modifiers == MODIFIER_NONE;
+ case Command::SelectCharNext:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowRight && modifiers == MODIFIER_SHIFT;
+ case Command::WordNext:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowRight && modifiers == MODIFIER_ALT;
+ case Command::SelectWordNext:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowRight &&
+ modifiers == (MODIFIER_ALT | MODIFIER_SHIFT);
+ case Command::EndLine:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowRight && modifiers == MODIFIER_META;
+ case Command::SelectEndLine:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowRight &&
+ modifiers == (MODIFIER_META | MODIFIER_SHIFT);
+ case Command::CharPrevious:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowLeft && modifiers == MODIFIER_NONE;
+ case Command::SelectCharPrevious:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowLeft && modifiers == MODIFIER_SHIFT;
+ case Command::WordPrevious:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowLeft && modifiers == MODIFIER_ALT;
+ case Command::SelectWordPrevious:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowLeft &&
+ modifiers == (MODIFIER_ALT | MODIFIER_SHIFT);
+ case Command::BeginLine:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowLeft && modifiers == MODIFIER_META;
+ case Command::SelectBeginLine:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowLeft &&
+ modifiers == (MODIFIER_META | MODIFIER_SHIFT);
+ case Command::LinePrevious:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowUp && modifiers == MODIFIER_NONE;
+ case Command::SelectLinePrevious:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowUp && modifiers == MODIFIER_SHIFT;
+ case Command::MoveTop:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowUp && modifiers == MODIFIER_META;
+ case Command::SelectTop:
+ return (keyNameIndex == KEY_NAME_INDEX_ArrowUp &&
+ modifiers == (MODIFIER_META | MODIFIER_SHIFT)) ||
+ (keyNameIndex == KEY_NAME_INDEX_Home && modifiers == MODIFIER_SHIFT);
+ case Command::LineNext:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowDown && modifiers == MODIFIER_NONE;
+ case Command::SelectLineNext:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowDown && modifiers == MODIFIER_SHIFT;
+ case Command::MoveBottom:
+ return keyNameIndex == KEY_NAME_INDEX_ArrowDown && modifiers == MODIFIER_META;
+ case Command::SelectBottom:
+ return (keyNameIndex == KEY_NAME_INDEX_ArrowDown &&
+ modifiers == (MODIFIER_META | MODIFIER_SHIFT)) ||
+ (keyNameIndex == KEY_NAME_INDEX_End && modifiers == MODIFIER_SHIFT);
+ case Command::ScrollPageUp:
+ return keyNameIndex == KEY_NAME_INDEX_PageUp && modifiers == MODIFIER_NONE;
+ case Command::SelectPageUp:
+ return keyNameIndex == KEY_NAME_INDEX_PageUp && modifiers == MODIFIER_SHIFT;
+ case Command::ScrollPageDown:
+ return keyNameIndex == KEY_NAME_INDEX_PageDown && modifiers == MODIFIER_NONE;
+ case Command::SelectPageDown:
+ return keyNameIndex == KEY_NAME_INDEX_PageDown && modifiers == MODIFIER_SHIFT;
+ case Command::ScrollBottom:
+ return keyNameIndex == KEY_NAME_INDEX_End && modifiers == MODIFIER_NONE;
+ case Command::ScrollTop:
+ return keyNameIndex == KEY_NAME_INDEX_Home && modifiers == MODIFIER_NONE;
+ case Command::CancelOperation:
+ return (keyNameIndex == KEY_NAME_INDEX_Escape &&
+ (modifiers == MODIFIER_NONE || modifiers == MODIFIER_SHIFT)) ||
+ ([mKeyEvent keyCode] == kVK_ANSI_Period && modifiers == MODIFIER_META);
+ case Command::Complete:
+ return keyNameIndex == KEY_NAME_INDEX_Escape &&
+ (modifiers == MODIFIER_ALT || modifiers == (MODIFIER_ALT | MODIFIER_SHIFT));
+ default:
+ return false;
+ }
+ }
+
+ void InitKeyEvent(TextInputHandlerBase* aHandler, WidgetKeyboardEvent& aKeyEvent,
+ bool aIsProcessedByIME);
+
+ /**
+ * GetUnhandledString() returns characters of the event which have not been
+ * handled with InsertText() yet. For example, if there is a composition
+ * caused by a dead key press like '`' and it's committed by some key
+ * combinations like |Cmd+v|, then, the |v|'s KeyDown event's |characters|
+ * is |`v|. Then, after |`| is committed with a call of InsertString(),
+ * this returns only 'v'.
+ */
+ void GetUnhandledString(nsAString& aUnhandledString) const;
+ };
+
+ /**
+ * Helper classes for guaranteeing cleaning mCurrentKeyEvent
+ */
+ class AutoKeyEventStateCleaner {
+ public:
+ explicit AutoKeyEventStateCleaner(TextInputHandlerBase* aHandler) : mHandler(aHandler) {}
+
+ ~AutoKeyEventStateCleaner() { mHandler->RemoveCurrentKeyEvent(); }
+
+ private:
+ RefPtr<TextInputHandlerBase> mHandler;
+ };
+
+ class MOZ_STACK_CLASS AutoInsertStringClearer {
+ public:
+ explicit AutoInsertStringClearer(KeyEventState* aState) : mState(aState) {}
+ ~AutoInsertStringClearer();
+
+ private:
+ KeyEventState* mState;
+ };
+
+ /**
+ * mCurrentKeyEvents stores all key events which are being processed.
+ * When we call interpretKeyEvents, IME may generate other key events.
+ * mCurrentKeyEvents[0] is the latest key event.
+ */
+ nsTArray<KeyEventState*> mCurrentKeyEvents;
+
+ /**
+ * mFirstKeyEvent must be used for first key event. This member prevents
+ * memory fragmentation for most key events.
+ */
+ KeyEventState mFirstKeyEvent;
+
+ /**
+ * PushKeyEvent() adds the current key event to mCurrentKeyEvents.
+ */
+ KeyEventState* PushKeyEvent(NSEvent* aNativeKeyEvent, uint32_t aUniqueId = 0) {
+ uint32_t nestCount = mCurrentKeyEvents.Length();
+ for (uint32_t i = 0; i < nestCount; i++) {
+ // When the key event is caused by another key event, all key events
+ // which are being handled should be marked as "consumed".
+ mCurrentKeyEvents[i]->mCausedOtherKeyEvents = true;
+ }
+
+ KeyEventState* keyEvent = nullptr;
+ if (nestCount == 0) {
+ mFirstKeyEvent.Set(aNativeKeyEvent, aUniqueId);
+ keyEvent = &mFirstKeyEvent;
+ } else {
+ keyEvent = new KeyEventState(aNativeKeyEvent, aUniqueId);
+ }
+ return *mCurrentKeyEvents.AppendElement(keyEvent);
+ }
+
+ /**
+ * RemoveCurrentKeyEvent() removes the current key event from
+ * mCurrentKeyEvents.
+ */
+ void RemoveCurrentKeyEvent() {
+ NS_ASSERTION(mCurrentKeyEvents.Length() > 0, "RemoveCurrentKeyEvent() is called unexpectedly");
+ KeyEventState* keyEvent = mCurrentKeyEvents.PopLastElement();
+ if (keyEvent == &mFirstKeyEvent) {
+ keyEvent->Clear();
+ } else {
+ delete keyEvent;
+ }
+ }
+
+ /**
+ * GetCurrentKeyEvent() returns current processing key event.
+ */
+ KeyEventState* GetCurrentKeyEvent() {
+ if (mCurrentKeyEvents.Length() == 0) {
+ return nullptr;
+ }
+ return mCurrentKeyEvents[mCurrentKeyEvents.Length() - 1];
+ }
+
+ struct KeyboardLayoutOverride final {
+ int32_t mKeyboardLayout;
+ bool mOverrideEnabled;
+
+ KeyboardLayoutOverride() : mKeyboardLayout(0), mOverrideEnabled(false) {}
+ };
+
+ const KeyboardLayoutOverride& KeyboardLayoutOverrideRef() const { return mKeyboardOverride; }
+
+ /**
+ * IsPrintableChar() checks whether the unicode character is
+ * a non-printable ASCII character or not. Note that this returns
+ * TRUE even if aChar is a non-printable UNICODE character.
+ *
+ * @param aChar A unicode character.
+ * @return TRUE if aChar is a printable ASCII character
+ * or a unicode character. Otherwise, i.e,
+ * if aChar is a non-printable ASCII character,
+ * FALSE.
+ */
+ static bool IsPrintableChar(char16_t aChar);
+
+ /**
+ * IsNormalCharInputtingEvent() checks whether aNativeEvent causes text input.
+ *
+ * @param aNativeEvent A key event.
+ * @return TRUE if the key event causes text input.
+ * Otherwise, FALSE.
+ */
+ static bool IsNormalCharInputtingEvent(NSEvent* aNativeEvent);
+
+ /**
+ * IsModifierKey() checks whether the native keyCode is for a modifier key.
+ *
+ * @param aNativeKeyCode A native keyCode.
+ * @return TRUE if aNativeKeyCode is for a modifier key.
+ * Otherwise, FALSE.
+ */
+ static bool IsModifierKey(UInt32 aNativeKeyCode);
+
+ private:
+ KeyboardLayoutOverride mKeyboardOverride;
+
+ static int32_t sSecureEventInputCount;
+};
+
+/**
+ * IMEInputHandler manages:
+ * 1. The IME/keyboard layout statement of nsChildView.
+ * 2. The IME composition statement of nsChildView.
+ * And also provides the methods which controls the current IME transaction of
+ * the instance.
+ *
+ * Note that an nsChildView handles one or more NSView's events. E.g., even if
+ * a text editor on XUL panel element, the input events handled on the parent
+ * (or its ancestor) widget handles it (the native focus is set to it). The
+ * actual focused view is notified by OnFocusChangeInGecko.
+ */
+
+class IMEInputHandler : public TextInputHandlerBase {
+ public:
+ // TextEventDispatcherListener methods
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+ NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;
+ NS_IMETHOD_(void) OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override;
+ NS_IMETHOD_(void)
+ WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
+ void* aData) override;
+
+ public:
+ virtual bool OnDestroyWidget(nsChildView* aDestroyingWidget) override;
+
+ virtual void OnFocusChangeInGecko(bool aFocus);
+
+ void OnSelectionChange(const IMENotification& aIMENotification);
+ void OnLayoutChange();
+
+ /**
+ * Call [NSTextInputContext handleEvent] for mouse event support of IME
+ */
+ bool OnHandleEvent(NSEvent* aEvent);
+
+ /**
+ * SetMarkedText() is a handler of setMarkedText of NSTextInput.
+ *
+ * @param aAttrString This mut be an instance of NSAttributedString.
+ * If the aString parameter to
+ * [ChildView setMarkedText:setSelectedRange:]
+ * isn't an instance of NSAttributedString,
+ * create an NSAttributedString from it and pass
+ * that instead.
+ * @param aSelectedRange Current selected range (or caret position).
+ * @param aReplacementRange The range which will be replaced with the
+ * aAttrString instead of current marked range.
+ */
+ void SetMarkedText(NSAttributedString* aAttrString, NSRange& aSelectedRange,
+ NSRange* aReplacementRange = nullptr);
+
+ /**
+ * GetAttributedSubstringFromRange() returns an NSAttributedString instance
+ * which is allocated as autorelease for aRange.
+ *
+ * @param aRange The range of string which you want.
+ * @param aActualRange The actual range of the result.
+ * @return The string in aRange. If the string is empty,
+ * this returns nil. If succeeded, this returns
+ * an instance which is allocated as autorelease.
+ * If this has some troubles, returns nil.
+ */
+ NSAttributedString* GetAttributedSubstringFromRange(NSRange& aRange,
+ NSRange* aActualRange = nullptr);
+
+ /**
+ * SelectedRange() returns current selected range.
+ *
+ * @return If an editor has focus, this returns selection
+ * range in the editor. Otherwise, this returns
+ * selection range in the focused document.
+ */
+ NSRange SelectedRange();
+
+ /**
+ * DrawsVerticallyForCharacterAtIndex() returns whether the character at
+ * the given index is being rendered vertically.
+ *
+ * @param aCharIndex The character offset to query.
+ *
+ * @return True if writing-mode is vertical at the given
+ * character offset; otherwise false.
+ */
+ bool DrawsVerticallyForCharacterAtIndex(uint32_t aCharIndex);
+
+ /**
+ * FirstRectForCharacterRange() returns first *character* rect in the range.
+ * Cocoa needs the first line rect in the range, but we cannot compute it
+ * on current implementation.
+ *
+ * @param aRange A range of text to examine. Its position is
+ * an offset from the beginning of the focused
+ * editor or document.
+ * @param aActualRange If this is not null, this returns the actual
+ * range used for computing the result.
+ * @return An NSRect containing the first character in
+ * aRange, in screen coordinates.
+ * If the length of aRange is 0, the width will
+ * be 0.
+ */
+ NSRect FirstRectForCharacterRange(NSRange& aRange, NSRange* aActualRange = nullptr);
+
+ /**
+ * CharacterIndexForPoint() returns an offset of a character at aPoint.
+ * XXX This isn't implemented, always returns 0.
+ *
+ * @param The point in screen coordinates.
+ * @return The offset of the character at aPoint from
+ * the beginning of the focused editor or
+ * document.
+ */
+ NSUInteger CharacterIndexForPoint(NSPoint& aPoint);
+
+ /**
+ * GetValidAttributesForMarkedText() returns attributes which we support.
+ *
+ * @return Always empty array for now.
+ */
+ NSArray* GetValidAttributesForMarkedText();
+
+ bool HasMarkedText();
+ NSRange MarkedRange();
+
+ bool IsIMEComposing() { return mIsIMEComposing; }
+ bool IsDeadKeyComposing() { return mIsDeadKeyComposing; }
+ bool IsIMEOpened();
+ bool IsIMEEnabled() { return mIsIMEEnabled; }
+ bool IsASCIICapableOnly() { return mIsASCIICapableOnly; }
+ bool IsEditableContent() const { return mIsIMEEnabled || mIsASCIICapableOnly; }
+ bool IgnoreIMECommit() { return mIgnoreIMECommit; }
+
+ void CommitIMEComposition();
+ void CancelIMEComposition();
+
+ void EnableIME(bool aEnableIME);
+ void SetIMEOpenState(bool aOpen);
+ void SetASCIICapableOnly(bool aASCIICapableOnly);
+
+ /**
+ * True if OSX believes that our view has keyboard focus.
+ */
+ bool IsFocused();
+
+ static CFArrayRef CreateAllIMEModeList();
+ static void DebugPrintAllIMEModes();
+
+ // Don't use ::TSMGetActiveDocument() API directly, the document may not
+ // be what you want.
+ static TSMDocumentID GetCurrentTSMDocumentID();
+
+ protected:
+ // We cannot do some jobs in the given stack by some reasons.
+ // Following flags and the timer provide the execution pending mechanism,
+ // See the comment in nsCocoaTextInputHandler.mm.
+ nsCOMPtr<nsITimer> mTimer;
+ enum { kNotifyIMEOfFocusChangeInGecko = 1, kSyncASCIICapableOnly = 2 };
+ uint32_t mPendingMethods;
+
+ IMEInputHandler(nsChildView* aWidget, NSView<mozView>* aNativeView);
+ virtual ~IMEInputHandler();
+
+ void ResetTimer();
+
+ virtual void ExecutePendingMethods();
+
+ /**
+ * InsertTextAsCommittingComposition() commits current composition. If there
+ * is no composition, this starts a composition and commits it immediately.
+ *
+ * @param aAttrString A string which is committed.
+ * @param aReplacementRange The range which will be replaced with the
+ * aAttrString instead of current selection.
+ */
+ void InsertTextAsCommittingComposition(NSAttributedString* aAttrString,
+ NSRange* aReplacementRange);
+
+ /**
+ * MaybeDispatchCurrentKeydownEvent() dispatches eKeyDown event for current
+ * key event. If eKeyDown for current key event has already been dispatched,
+ * this does nothing.
+ *
+ * @param aIsProcessedByIME true if current key event is handled by IME.
+ * @return true if the caller can continue to handle
+ * current key event. Otherwise, false. E.g.,
+ * focus is moved, the widget has been destroyed
+ * or something.
+ */
+ bool MaybeDispatchCurrentKeydownEvent(bool aIsProcessedByIME);
+
+ private:
+ // If mIsIMEComposing is true, the composition string is stored here.
+ NSString* mIMECompositionString;
+ // If mIsIMEComposing is true, the start offset of the composition string.
+ uint32_t mIMECompositionStart;
+
+ NSRange mMarkedRange;
+ NSRange mSelectedRange;
+
+ NSRange mRangeForWritingMode; // range within which mWritingMode applies
+ mozilla::WritingMode mWritingMode;
+
+ bool mIsIMEComposing;
+ // If the composition started with dead key, mIsDeadKeyComposing is set to
+ // true.
+ bool mIsDeadKeyComposing;
+ bool mIsIMEEnabled;
+ bool mIsASCIICapableOnly;
+ bool mIgnoreIMECommit;
+ bool mIMEHasFocus;
+
+ void KillIMEComposition();
+ void SendCommittedText(NSString* aString);
+ void OpenSystemPreferredLanguageIME();
+
+ // Pending methods
+ void NotifyIMEOfFocusChangeInGecko();
+ void SyncASCIICapableOnly();
+
+ static bool sStaticMembersInitialized;
+ static CFStringRef sLatestIMEOpenedModeInputSourceID;
+ static void InitStaticMembers();
+ static void OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter, void* aObserver,
+ CFStringRef aName, const void* aObject,
+ CFDictionaryRef aUserInfo);
+
+ static void FlushPendingMethods(nsITimer* aTimer, void* aClosure);
+
+ /**
+ * ConvertToTextRangeStyle converts the given native underline style to
+ * our defined text range type.
+ *
+ * @param aUnderlineStyle NSUnderlineStyleSingle or
+ * NSUnderlineStyleThick.
+ * @param aSelectedRange Current selected range (or caret position).
+ * @return NS_TEXTRANGE_*.
+ */
+ TextRangeType ConvertToTextRangeType(uint32_t aUnderlineStyle, NSRange& aSelectedRange);
+
+ /**
+ * GetRangeCount() computes the range count of aAttrString.
+ *
+ * @param aAttrString An NSAttributedString instance whose number of
+ * NSUnderlineStyleAttributeName ranges you with
+ * to know.
+ * @return The count of NSUnderlineStyleAttributeName
+ * ranges in aAttrString.
+ */
+ uint32_t GetRangeCount(NSAttributedString* aString);
+
+ /**
+ * CreateTextRangeArray() returns text ranges for clauses and/or caret.
+ *
+ * @param aAttrString An NSAttributedString instance which indicates
+ * current composition string.
+ * @param aSelectedRange Current selected range (or caret position).
+ * @return The result is set to the
+ * NSUnderlineStyleAttributeName ranges in
+ * aAttrString.
+ */
+ already_AddRefed<mozilla::TextRangeArray> CreateTextRangeArray(NSAttributedString* aAttrString,
+ NSRange& aSelectedRange);
+
+ /**
+ * DispatchCompositionStartEvent() dispatches a compositionstart event and
+ * initializes the members indicating composition state.
+ *
+ * @return true if it can continues handling composition.
+ * Otherwise, e.g., canceled by the web page,
+ * this returns false.
+ */
+ bool DispatchCompositionStartEvent();
+
+ /**
+ * DispatchCompositionChangeEvent() dispatches a compositionchange event on
+ * mWidget and modifies the members indicating composition state.
+ *
+ * @param aText User text input.
+ * @param aAttrString An NSAttributedString instance which indicates
+ * current composition string.
+ * @param aSelectedRange Current selected range (or caret position).
+ *
+ * @return true if it can continues handling composition.
+ * Otherwise, e.g., canceled by the web page,
+ * this returns false.
+ */
+ bool DispatchCompositionChangeEvent(const nsString& aText, NSAttributedString* aAttrString,
+ NSRange& aSelectedRange);
+
+ /**
+ * DispatchCompositionCommitEvent() dispatches a compositioncommit event or
+ * compositioncommitasis event. If aCommitString is null, dispatches
+ * compositioncommitasis event. I.e., if aCommitString is null, this
+ * commits the composition with the last data. Otherwise, commits the
+ * composition with aCommitString value.
+ *
+ * @return true if the widget isn't destroyed.
+ * Otherwise, false.
+ */
+ bool DispatchCompositionCommitEvent(const nsAString* aCommitString = nullptr);
+
+ // The focused IME handler. Please note that the handler might lost the
+ // actual focus by deactivating the application. If we are active, this
+ // must have the actual focused handle.
+ // We cannot access to the NSInputManager during we aren't active, so, the
+ // focused handler can have an IME transaction even if we are deactive.
+ static IMEInputHandler* sFocusedIMEHandler;
+
+ static bool sCachedIsForRTLLangage;
+};
+
+/**
+ * TextInputHandler implements the NSTextInput protocol.
+ */
+class TextInputHandler : public IMEInputHandler {
+ public:
+ static NSUInteger sLastModifierState;
+
+ static CFArrayRef CreateAllKeyboardLayoutList();
+ static void DebugPrintAllKeyboardLayouts();
+
+ TextInputHandler(nsChildView* aWidget, NSView<mozView>* aNativeView);
+ virtual ~TextInputHandler();
+
+ /**
+ * KeyDown event handler.
+ *
+ * @param aNativeEvent A native NSEventTypeKeyDown event.
+ * @param aUniqueId A unique ID for the event.
+ * @return TRUE if the event is dispatched to web
+ * contents or chrome contents. Otherwise, FALSE.
+ */
+ bool HandleKeyDownEvent(NSEvent* aNativeEvent, uint32_t aUniqueId);
+
+ /**
+ * KeyUp event handler.
+ *
+ * @param aNativeEvent A native NSEventTypeKeyUp event.
+ */
+ void HandleKeyUpEvent(NSEvent* aNativeEvent);
+
+ /**
+ * FlagsChanged event handler.
+ *
+ * @param aNativeEvent A native NSEventTypeFlagsChanged event.
+ */
+ void HandleFlagsChanged(NSEvent* aNativeEvent);
+
+ /**
+ * Insert the string to content. I.e., this is a text input event handler.
+ * If this is called during keydown event handling, this may dispatch a
+ * eKeyPress event. If this is called during composition, this commits
+ * the composition by the aAttrString.
+ *
+ * @param aAttrString An inserted string.
+ * @param aReplacementRange The range which will be replaced with the
+ * aAttrString instead of current selection.
+ */
+ void InsertText(NSAttributedString* aAttrString, NSRange* aReplacementRange = nullptr);
+
+ /**
+ * Handles aCommand. This may cause dispatching an eKeyPress event.
+ *
+ * @param aCommand The command which receives from Cocoa.
+ * @return true if this handles the command even if it does
+ * nothing actually. Otherwise, false.
+ */
+ bool HandleCommand(Command aCommand);
+
+ /**
+ * doCommandBySelector event handler.
+ *
+ * @param aSelector A selector of the command.
+ * @return TRUE if the command is consumed. Otherwise,
+ * FALSE.
+ */
+ bool DoCommandBySelector(const char* aSelector);
+
+ /**
+ * KeyPressWasHandled() checks whether keypress event was handled or not.
+ *
+ * @return TRUE if keypress event for latest native key
+ * event was handled. Otherwise, FALSE.
+ * If this handler isn't handling any key events,
+ * always returns FALSE.
+ */
+ bool KeyPressWasHandled() {
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+ return currentKeyEvent && currentKeyEvent->mKeyPressHandled;
+ }
+
+ protected:
+ // Stores the association of device dependent modifier flags with a modifier
+ // keyCode. Being device dependent, this association may differ from one kind
+ // of hardware to the next.
+ struct ModifierKey {
+ NSUInteger flags;
+ unsigned short keyCode;
+
+ ModifierKey(NSUInteger aFlags, unsigned short aKeyCode) : flags(aFlags), keyCode(aKeyCode) {}
+
+ NSUInteger GetDeviceDependentFlags() const {
+ return (flags & ~NSEventModifierFlagDeviceIndependentFlagsMask);
+ }
+
+ NSUInteger GetDeviceIndependentFlags() const {
+ return (flags & NSEventModifierFlagDeviceIndependentFlagsMask);
+ }
+ };
+ typedef nsTArray<ModifierKey> ModifierKeyArray;
+ ModifierKeyArray mModifierKeys;
+
+ /**
+ * GetModifierKeyForNativeKeyCode() returns the stored ModifierKey for
+ * the key.
+ */
+ const ModifierKey* GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const;
+
+ /**
+ * GetModifierKeyForDeviceDependentFlags() returns the stored ModifierKey for
+ * the device dependent flags.
+ */
+ const ModifierKey* GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const;
+
+ /**
+ * DispatchKeyEventForFlagsChanged() dispatches keydown event or keyup event
+ * for the aNativeEvent.
+ *
+ * @param aNativeEvent A native flagschanged event which you want to
+ * dispatch our key event for.
+ * @param aDispatchKeyDown TRUE if you want to dispatch a keydown event.
+ * Otherwise, i.e., to dispatch keyup event,
+ * FALSE.
+ */
+ void DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent, bool aDispatchKeyDown);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // TextInputHandler_h_
diff --git a/widget/cocoa/TextInputHandler.mm b/widget/cocoa/TextInputHandler.mm
new file mode 100644
index 0000000000..c833a4cb9e
--- /dev/null
+++ b/widget/cocoa/TextInputHandler.mm
@@ -0,0 +1,5061 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TextInputHandler.h"
+
+#include "mozilla/Logging.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/ToString.h"
+
+#include "nsChildView.h"
+#include "nsCocoaFeatures.h"
+#include "nsObjCExceptions.h"
+#include "nsBidiUtils.h"
+#include "nsToolkit.h"
+#include "nsCocoaUtils.h"
+#include "WidgetUtils.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+LazyLogModule gLog("TextInputHandlerWidgets");
+
+static const char* OnOrOff(bool aBool) { return aBool ? "ON" : "off"; }
+
+static const char* TrueOrFalse(bool aBool) { return aBool ? "TRUE" : "FALSE"; }
+
+static const char* GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode) {
+ switch (aNativeKeyCode) {
+ case kVK_Escape:
+ return "Escape";
+ case kVK_RightCommand:
+ return "Right-Command";
+ case kVK_Command:
+ return "Command";
+ case kVK_Shift:
+ return "Shift";
+ case kVK_CapsLock:
+ return "CapsLock";
+ case kVK_Option:
+ return "Option";
+ case kVK_Control:
+ return "Control";
+ case kVK_RightShift:
+ return "Right-Shift";
+ case kVK_RightOption:
+ return "Right-Option";
+ case kVK_RightControl:
+ return "Right-Control";
+ case kVK_ANSI_KeypadClear:
+ return "Clear";
+
+ case kVK_F1:
+ return "F1";
+ case kVK_F2:
+ return "F2";
+ case kVK_F3:
+ return "F3";
+ case kVK_F4:
+ return "F4";
+ case kVK_F5:
+ return "F5";
+ case kVK_F6:
+ return "F6";
+ case kVK_F7:
+ return "F7";
+ case kVK_F8:
+ return "F8";
+ case kVK_F9:
+ return "F9";
+ case kVK_F10:
+ return "F10";
+ case kVK_F11:
+ return "F11";
+ case kVK_F12:
+ return "F12";
+ case kVK_F13:
+ return "F13/PrintScreen";
+ case kVK_F14:
+ return "F14/ScrollLock";
+ case kVK_F15:
+ return "F15/Pause";
+
+ case kVK_ANSI_Keypad0:
+ return "NumPad-0";
+ case kVK_ANSI_Keypad1:
+ return "NumPad-1";
+ case kVK_ANSI_Keypad2:
+ return "NumPad-2";
+ case kVK_ANSI_Keypad3:
+ return "NumPad-3";
+ case kVK_ANSI_Keypad4:
+ return "NumPad-4";
+ case kVK_ANSI_Keypad5:
+ return "NumPad-5";
+ case kVK_ANSI_Keypad6:
+ return "NumPad-6";
+ case kVK_ANSI_Keypad7:
+ return "NumPad-7";
+ case kVK_ANSI_Keypad8:
+ return "NumPad-8";
+ case kVK_ANSI_Keypad9:
+ return "NumPad-9";
+
+ case kVK_ANSI_KeypadMultiply:
+ return "NumPad-*";
+ case kVK_ANSI_KeypadPlus:
+ return "NumPad-+";
+ case kVK_ANSI_KeypadMinus:
+ return "NumPad--";
+ case kVK_ANSI_KeypadDecimal:
+ return "NumPad-.";
+ case kVK_ANSI_KeypadDivide:
+ return "NumPad-/";
+ case kVK_ANSI_KeypadEquals:
+ return "NumPad-=";
+ case kVK_ANSI_KeypadEnter:
+ return "NumPad-Enter";
+ case kVK_Return:
+ return "Return";
+ case kVK_Powerbook_KeypadEnter:
+ return "NumPad-EnterOnPowerBook";
+
+ case kVK_PC_Insert:
+ return "Insert/Help";
+ case kVK_PC_Delete:
+ return "Delete";
+ case kVK_Tab:
+ return "Tab";
+ case kVK_PC_Backspace:
+ return "Backspace";
+ case kVK_Home:
+ return "Home";
+ case kVK_End:
+ return "End";
+ case kVK_PageUp:
+ return "PageUp";
+ case kVK_PageDown:
+ return "PageDown";
+ case kVK_LeftArrow:
+ return "LeftArrow";
+ case kVK_RightArrow:
+ return "RightArrow";
+ case kVK_UpArrow:
+ return "UpArrow";
+ case kVK_DownArrow:
+ return "DownArrow";
+ case kVK_PC_ContextMenu:
+ return "ContextMenu";
+
+ case kVK_Function:
+ return "Function";
+ case kVK_VolumeUp:
+ return "VolumeUp";
+ case kVK_VolumeDown:
+ return "VolumeDown";
+ case kVK_Mute:
+ return "Mute";
+
+ case kVK_ISO_Section:
+ return "ISO_Section";
+
+ case kVK_JIS_Yen:
+ return "JIS_Yen";
+ case kVK_JIS_Underscore:
+ return "JIS_Underscore";
+ case kVK_JIS_KeypadComma:
+ return "JIS_KeypadComma";
+ case kVK_JIS_Eisu:
+ return "JIS_Eisu";
+ case kVK_JIS_Kana:
+ return "JIS_Kana";
+
+ case kVK_ANSI_A:
+ return "A";
+ case kVK_ANSI_B:
+ return "B";
+ case kVK_ANSI_C:
+ return "C";
+ case kVK_ANSI_D:
+ return "D";
+ case kVK_ANSI_E:
+ return "E";
+ case kVK_ANSI_F:
+ return "F";
+ case kVK_ANSI_G:
+ return "G";
+ case kVK_ANSI_H:
+ return "H";
+ case kVK_ANSI_I:
+ return "I";
+ case kVK_ANSI_J:
+ return "J";
+ case kVK_ANSI_K:
+ return "K";
+ case kVK_ANSI_L:
+ return "L";
+ case kVK_ANSI_M:
+ return "M";
+ case kVK_ANSI_N:
+ return "N";
+ case kVK_ANSI_O:
+ return "O";
+ case kVK_ANSI_P:
+ return "P";
+ case kVK_ANSI_Q:
+ return "Q";
+ case kVK_ANSI_R:
+ return "R";
+ case kVK_ANSI_S:
+ return "S";
+ case kVK_ANSI_T:
+ return "T";
+ case kVK_ANSI_U:
+ return "U";
+ case kVK_ANSI_V:
+ return "V";
+ case kVK_ANSI_W:
+ return "W";
+ case kVK_ANSI_X:
+ return "X";
+ case kVK_ANSI_Y:
+ return "Y";
+ case kVK_ANSI_Z:
+ return "Z";
+
+ case kVK_ANSI_1:
+ return "1";
+ case kVK_ANSI_2:
+ return "2";
+ case kVK_ANSI_3:
+ return "3";
+ case kVK_ANSI_4:
+ return "4";
+ case kVK_ANSI_5:
+ return "5";
+ case kVK_ANSI_6:
+ return "6";
+ case kVK_ANSI_7:
+ return "7";
+ case kVK_ANSI_8:
+ return "8";
+ case kVK_ANSI_9:
+ return "9";
+ case kVK_ANSI_0:
+ return "0";
+ case kVK_ANSI_Equal:
+ return "Equal";
+ case kVK_ANSI_Minus:
+ return "Minus";
+ case kVK_ANSI_RightBracket:
+ return "RightBracket";
+ case kVK_ANSI_LeftBracket:
+ return "LeftBracket";
+ case kVK_ANSI_Quote:
+ return "Quote";
+ case kVK_ANSI_Semicolon:
+ return "Semicolon";
+ case kVK_ANSI_Backslash:
+ return "Backslash";
+ case kVK_ANSI_Comma:
+ return "Comma";
+ case kVK_ANSI_Slash:
+ return "Slash";
+ case kVK_ANSI_Period:
+ return "Period";
+ case kVK_ANSI_Grave:
+ return "Grave";
+
+ default:
+ return "undefined";
+ }
+}
+
+static const char* GetCharacters(const nsAString& aString) {
+ if (aString.IsEmpty()) {
+ return "";
+ }
+ nsAutoString escapedStr;
+ for (uint32_t i = 0; i < aString.Length(); i++) {
+ char16_t ch = aString.CharAt(i);
+ if (ch < 0x20) {
+ nsPrintfCString utf8str("(U+%04X)", ch);
+ escapedStr += NS_ConvertUTF8toUTF16(utf8str);
+ } else if (ch <= 0x7E) {
+ escapedStr += ch;
+ } else {
+ nsPrintfCString utf8str("(U+%04X)", ch);
+ escapedStr += ch;
+ escapedStr += NS_ConvertUTF8toUTF16(utf8str);
+ }
+ }
+
+ // the result will be freed automatically by cocoa.
+ NSString* result = nsCocoaUtils::ToNSString(escapedStr);
+ return [result UTF8String];
+}
+
+static const char* GetCharacters(const NSString* aString) {
+ nsAutoString str;
+ nsCocoaUtils::GetStringForNSString(aString, str);
+ return GetCharacters(str);
+}
+
+static const char* GetCharacters(const CFStringRef aString) {
+ const NSString* str = reinterpret_cast<const NSString*>(aString);
+ return GetCharacters(str);
+}
+
+static const char* GetNativeKeyEventType(NSEvent* aNativeEvent) {
+ switch ([aNativeEvent type]) {
+ case NSEventTypeKeyDown:
+ return "NSEventTypeKeyDown";
+ case NSEventTypeKeyUp:
+ return "NSEventTypeKeyUp";
+ case NSEventTypeFlagsChanged:
+ return "NSEventTypeFlagsChanged";
+ default:
+ return "not key event";
+ }
+}
+
+static const char* GetGeckoKeyEventType(const WidgetEvent& aEvent) {
+ switch (aEvent.mMessage) {
+ case eKeyDown:
+ return "eKeyDown";
+ case eKeyUp:
+ return "eKeyUp";
+ case eKeyPress:
+ return "eKeyPress";
+ default:
+ return "not key event";
+ }
+}
+
+static const char* GetWindowLevelName(NSInteger aWindowLevel) {
+ switch (aWindowLevel) {
+ case kCGBaseWindowLevelKey:
+ return "kCGBaseWindowLevelKey (NSNormalWindowLevel)";
+ case kCGMinimumWindowLevelKey:
+ return "kCGMinimumWindowLevelKey";
+ case kCGDesktopWindowLevelKey:
+ return "kCGDesktopWindowLevelKey";
+ case kCGBackstopMenuLevelKey:
+ return "kCGBackstopMenuLevelKey";
+ case kCGNormalWindowLevelKey:
+ return "kCGNormalWindowLevelKey";
+ case kCGFloatingWindowLevelKey:
+ return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)";
+ case kCGTornOffMenuWindowLevelKey:
+ return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, NSTornOffMenuWindowLevel)";
+ case kCGDockWindowLevelKey:
+ return "kCGDockWindowLevelKey (NSDockWindowLevel)";
+ case kCGMainMenuWindowLevelKey:
+ return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)";
+ case kCGStatusWindowLevelKey:
+ return "kCGStatusWindowLevelKey (NSStatusWindowLevel)";
+ case kCGModalPanelWindowLevelKey:
+ return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)";
+ case kCGPopUpMenuWindowLevelKey:
+ return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)";
+ case kCGDraggingWindowLevelKey:
+ return "kCGDraggingWindowLevelKey";
+ case kCGScreenSaverWindowLevelKey:
+ return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)";
+ case kCGMaximumWindowLevelKey:
+ return "kCGMaximumWindowLevelKey";
+ case kCGOverlayWindowLevelKey:
+ return "kCGOverlayWindowLevelKey";
+ case kCGHelpWindowLevelKey:
+ return "kCGHelpWindowLevelKey";
+ case kCGUtilityWindowLevelKey:
+ return "kCGUtilityWindowLevelKey";
+ case kCGDesktopIconWindowLevelKey:
+ return "kCGDesktopIconWindowLevelKey";
+ case kCGCursorWindowLevelKey:
+ return "kCGCursorWindowLevelKey";
+ case kCGNumberOfWindowLevelKeys:
+ return "kCGNumberOfWindowLevelKeys";
+ default:
+ return "unknown window level";
+ }
+}
+
+static bool IsControlChar(uint32_t aCharCode) { return aCharCode < ' ' || aCharCode == 0x7F; }
+
+static uint32_t gHandlerInstanceCount = 0;
+
+static void EnsureToLogAllKeyboardLayoutsAndIMEs() {
+ static bool sDone = false;
+ if (!sDone) {
+ sDone = true;
+ TextInputHandler::DebugPrintAllKeyboardLayouts();
+ IMEInputHandler::DebugPrintAllIMEModes();
+ }
+}
+
+inline NSRange MakeNSRangeFrom(const Maybe<OffsetAndData<uint32_t>>& aOffsetAndData) {
+ if (aOffsetAndData.isNothing()) {
+ return NSMakeRange(NSNotFound, 0);
+ }
+ return NSMakeRange(aOffsetAndData->StartOffset(), aOffsetAndData->Length());
+}
+
+#pragma mark -
+
+/******************************************************************************
+ *
+ * TISInputSourceWrapper implementation
+ *
+ ******************************************************************************/
+
+TISInputSourceWrapper* TISInputSourceWrapper::sCurrentInputSource = nullptr;
+
+// static
+TISInputSourceWrapper& TISInputSourceWrapper::CurrentInputSource() {
+ if (!sCurrentInputSource) {
+ sCurrentInputSource = new TISInputSourceWrapper();
+ }
+ if (!sCurrentInputSource->IsInitializedByCurrentInputSource()) {
+ sCurrentInputSource->InitByCurrentInputSource();
+ }
+ return *sCurrentInputSource;
+}
+
+// static
+void TISInputSourceWrapper::Shutdown() {
+ if (!sCurrentInputSource) {
+ return;
+ }
+ sCurrentInputSource->Clear();
+ delete sCurrentInputSource;
+ sCurrentInputSource = nullptr;
+}
+
+bool TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType,
+ nsAString& aStr) {
+ aStr.Truncate();
+
+ const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, "
+ "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n "
+ "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
+ this, static_cast<unsigned int>(aKeyCode), static_cast<unsigned int>(aModifiers),
+ static_cast<unsigned int>(aKbType), UCKey, OnOrOff(aModifiers & shiftKey),
+ OnOrOff(aModifiers & controlKey), OnOrOff(aModifiers & optionKey),
+ OnOrOff(aModifiers & cmdKey), OnOrOff(aModifiers & alphaLock),
+ OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
+
+ NS_ENSURE_TRUE(UCKey, false);
+
+ UInt32 deadKeyState = 0;
+ UniCharCount len;
+ UniChar chars[5];
+ OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode, kUCKeyActionDown, aModifiers >> 8, aKbType,
+ kUCKeyTranslateNoDeadKeysMask, &deadKeyState, 5, &len, chars);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::TranslateToString, err=0x%X, len=%zu", this,
+ static_cast<int>(err), len));
+
+ NS_ENSURE_TRUE(err == noErr, false);
+ if (len == 0) {
+ return true;
+ }
+ if (!aStr.SetLength(len, fallible)) {
+ return false;
+ }
+ NS_ASSERTION(sizeof(char16_t) == sizeof(UniChar),
+ "size of char16_t and size of UniChar are different");
+ memcpy(aStr.BeginWriting(), chars, len * sizeof(char16_t));
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::TranslateToString, aStr=\"%s\"", this,
+ NS_ConvertUTF16toUTF8(aStr).get()));
+
+ return true;
+}
+
+uint32_t TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers,
+ UInt32 aKbType) {
+ nsAutoString str;
+ if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) || str.Length() != 1) {
+ return 0;
+ }
+ return static_cast<uint32_t>(str.CharAt(0));
+}
+
+bool TISInputSourceWrapper::IsDeadKey(NSEvent* aNativeKeyEvent) {
+ if ([[aNativeKeyEvent characters] length]) {
+ return false;
+ }
+
+ // Assmue that if control key or command key is pressed, it's not a dead key.
+ NSUInteger cocoaState = [aNativeKeyEvent modifierFlags];
+ if (cocoaState & (NSEventModifierFlagControl | NSEventModifierFlagCommand)) {
+ return false;
+ }
+
+ UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
+ switch (nativeKeyCode) {
+ case kVK_ANSI_A:
+ case kVK_ANSI_B:
+ case kVK_ANSI_C:
+ case kVK_ANSI_D:
+ case kVK_ANSI_E:
+ case kVK_ANSI_F:
+ case kVK_ANSI_G:
+ case kVK_ANSI_H:
+ case kVK_ANSI_I:
+ case kVK_ANSI_J:
+ case kVK_ANSI_K:
+ case kVK_ANSI_L:
+ case kVK_ANSI_M:
+ case kVK_ANSI_N:
+ case kVK_ANSI_O:
+ case kVK_ANSI_P:
+ case kVK_ANSI_Q:
+ case kVK_ANSI_R:
+ case kVK_ANSI_S:
+ case kVK_ANSI_T:
+ case kVK_ANSI_U:
+ case kVK_ANSI_V:
+ case kVK_ANSI_W:
+ case kVK_ANSI_X:
+ case kVK_ANSI_Y:
+ case kVK_ANSI_Z:
+ case kVK_ANSI_1:
+ case kVK_ANSI_2:
+ case kVK_ANSI_3:
+ case kVK_ANSI_4:
+ case kVK_ANSI_5:
+ case kVK_ANSI_6:
+ case kVK_ANSI_7:
+ case kVK_ANSI_8:
+ case kVK_ANSI_9:
+ case kVK_ANSI_0:
+ case kVK_ANSI_Equal:
+ case kVK_ANSI_Minus:
+ case kVK_ANSI_RightBracket:
+ case kVK_ANSI_LeftBracket:
+ case kVK_ANSI_Quote:
+ case kVK_ANSI_Semicolon:
+ case kVK_ANSI_Backslash:
+ case kVK_ANSI_Comma:
+ case kVK_ANSI_Slash:
+ case kVK_ANSI_Period:
+ case kVK_ANSI_Grave:
+ case kVK_JIS_Yen:
+ case kVK_JIS_Underscore:
+ break;
+ default:
+ // Let's assume that dead key can be only a printable key in standard
+ // position.
+ return false;
+ }
+
+ // If TranslateToChar() returns non-zero value, that means that
+ // the key may input a character with different dead key state.
+ UInt32 kbType = GetKbdType();
+ UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
+ return IsDeadKey(nativeKeyCode, carbonState, kbType);
+}
+
+bool TISInputSourceWrapper::IsDeadKey(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType) {
+ const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::IsDeadKey, aKeyCode=0x%X, "
+ "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n "
+ "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
+ this, static_cast<unsigned int>(aKeyCode), static_cast<unsigned int>(aModifiers),
+ static_cast<unsigned int>(aKbType), UCKey, OnOrOff(aModifiers & shiftKey),
+ OnOrOff(aModifiers & controlKey), OnOrOff(aModifiers & optionKey),
+ OnOrOff(aModifiers & cmdKey), OnOrOff(aModifiers & alphaLock),
+ OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
+
+ if (NS_WARN_IF(!UCKey)) {
+ return false;
+ }
+
+ UInt32 deadKeyState = 0;
+ UniCharCount len;
+ UniChar chars[5];
+ OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode, kUCKeyActionDown, aModifiers >> 8, aKbType, 0,
+ &deadKeyState, 5, &len, chars);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::IsDeadKey, err=0x%X, "
+ "len=%zu, deadKeyState=%u",
+ this, static_cast<int>(err), len, deadKeyState));
+
+ if (NS_WARN_IF(err != noErr)) {
+ return false;
+ }
+
+ return deadKeyState != 0;
+}
+
+void TISInputSourceWrapper::InitByInputSourceID(const char* aID) {
+ Clear();
+ if (!aID) return;
+
+ CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID, kCFStringEncodingASCII);
+ InitByInputSourceID(idstr);
+ ::CFRelease(idstr);
+}
+
+void TISInputSourceWrapper::InitByInputSourceID(const nsString& aID) {
+ Clear();
+ if (aID.IsEmpty()) return;
+ CFStringRef idstr = ::CFStringCreateWithCharacters(
+ kCFAllocatorDefault, reinterpret_cast<const UniChar*>(aID.get()), aID.Length());
+ InitByInputSourceID(idstr);
+ ::CFRelease(idstr);
+}
+
+void TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID) {
+ Clear();
+ if (!aID) return;
+ const void* keys[] = {kTISPropertyInputSourceID};
+ const void* values[] = {aID};
+ CFDictionaryRef filter = ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
+ NS_ASSERTION(filter, "failed to create the filter");
+ mInputSourceList = ::TISCreateInputSourceList(filter, true);
+ ::CFRelease(filter);
+ if (::CFArrayGetCount(mInputSourceList) > 0) {
+ mInputSource = static_cast<TISInputSourceRef>(
+ const_cast<void*>(::CFArrayGetValueAtIndex(mInputSourceList, 0)));
+ if (IsKeyboardLayout()) {
+ mKeyboardLayout = mInputSource;
+ }
+ }
+}
+
+void TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID, bool aOverrideKeyboard) {
+ // NOTE: Doument new layout IDs in TextInputHandler.h when you add ones.
+ switch (aLayoutID) {
+ case 0:
+ InitByInputSourceID("com.apple.keylayout.US");
+ break;
+ case 1:
+ InitByInputSourceID("com.apple.keylayout.Greek");
+ break;
+ case 2:
+ InitByInputSourceID("com.apple.keylayout.German");
+ break;
+ case 3:
+ InitByInputSourceID("com.apple.keylayout.Swedish-Pro");
+ break;
+ case 4:
+ InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD");
+ break;
+ case 5:
+ InitByInputSourceID("com.apple.keylayout.Thai");
+ break;
+ case 6:
+ InitByInputSourceID("com.apple.keylayout.Arabic");
+ break;
+ case 7:
+ InitByInputSourceID("com.apple.keylayout.ArabicPC");
+ break;
+ case 8:
+ InitByInputSourceID("com.apple.keylayout.French");
+ break;
+ case 9:
+ InitByInputSourceID("com.apple.keylayout.Hebrew");
+ break;
+ case 10:
+ InitByInputSourceID("com.apple.keylayout.Lithuanian");
+ break;
+ case 11:
+ InitByInputSourceID("com.apple.keylayout.Norwegian");
+ break;
+ case 12:
+ InitByInputSourceID("com.apple.keylayout.Spanish");
+ break;
+ default:
+ Clear();
+ break;
+ }
+ mOverrideKeyboard = aOverrideKeyboard;
+}
+
+void TISInputSourceWrapper::InitByCurrentInputSource() {
+ Clear();
+ mInputSource = ::TISCopyCurrentKeyboardInputSource();
+ mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
+ if (!mKeyboardLayout) {
+ mKeyboardLayout = ::TISCopyCurrentKeyboardLayoutInputSource();
+ }
+ // If this causes composition, the current keyboard layout may input non-ASCII
+ // characters such as Japanese Kana characters or Hangul characters.
+ // However, we need to set ASCII characters to DOM key events for consistency
+ // with other platforms.
+ if (IsOpenedIMEMode()) {
+ TISInputSourceWrapper tis(mKeyboardLayout);
+ if (!tis.IsASCIICapable()) {
+ mKeyboardLayout = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
+ }
+ }
+}
+
+void TISInputSourceWrapper::InitByCurrentKeyboardLayout() {
+ Clear();
+ mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource();
+ mKeyboardLayout = mInputSource;
+}
+
+void TISInputSourceWrapper::InitByCurrentASCIICapableInputSource() {
+ Clear();
+ mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource();
+ mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
+ if (mKeyboardLayout) {
+ TISInputSourceWrapper tis(mKeyboardLayout);
+ if (!tis.IsASCIICapable()) {
+ mKeyboardLayout = nullptr;
+ }
+ }
+ if (!mKeyboardLayout) {
+ mKeyboardLayout = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
+ }
+}
+
+void TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout() {
+ Clear();
+ mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
+ mKeyboardLayout = mInputSource;
+}
+
+void TISInputSourceWrapper::InitByCurrentInputMethodKeyboardLayoutOverride() {
+ Clear();
+ mInputSource = ::TISCopyInputMethodKeyboardLayoutOverride();
+ mKeyboardLayout = mInputSource;
+}
+
+void TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource) {
+ Clear();
+ mInputSource = aInputSource;
+ if (IsKeyboardLayout()) {
+ mKeyboardLayout = mInputSource;
+ }
+}
+
+void TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage) {
+ Clear();
+ mInputSource = ::TISCopyInputSourceForLanguage(aLanguage);
+ if (IsKeyboardLayout()) {
+ mKeyboardLayout = mInputSource;
+ }
+}
+
+const UCKeyboardLayout* TISInputSourceWrapper::GetUCKeyboardLayout() {
+ NS_ENSURE_TRUE(mKeyboardLayout, nullptr);
+ if (mUCKeyboardLayout) {
+ return mUCKeyboardLayout;
+ }
+ CFDataRef uchr = static_cast<CFDataRef>(
+ ::TISGetInputSourceProperty(mKeyboardLayout, kTISPropertyUnicodeKeyLayoutData));
+
+ // We should be always able to get the layout here.
+ NS_ENSURE_TRUE(uchr, nullptr);
+ mUCKeyboardLayout = reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr));
+ return mUCKeyboardLayout;
+}
+
+bool TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey) {
+ CFBooleanRef ret = static_cast<CFBooleanRef>(::TISGetInputSourceProperty(mInputSource, aKey));
+ return ::CFBooleanGetValue(ret);
+}
+
+bool TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey, CFStringRef& aStr) {
+ aStr = static_cast<CFStringRef>(::TISGetInputSourceProperty(mInputSource, aKey));
+ return aStr != nullptr;
+}
+
+bool TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey, nsAString& aStr) {
+ CFStringRef str;
+ GetStringProperty(aKey, str);
+ nsCocoaUtils::GetStringForNSString((const NSString*)str, aStr);
+ return !aStr.IsEmpty();
+}
+
+bool TISInputSourceWrapper::IsOpenedIMEMode() {
+ NS_ENSURE_TRUE(mInputSource, false);
+ if (!IsIMEMode()) return false;
+ return !IsASCIICapable();
+}
+
+bool TISInputSourceWrapper::IsIMEMode() {
+ NS_ENSURE_TRUE(mInputSource, false);
+ CFStringRef str;
+ GetInputSourceType(str);
+ NS_ENSURE_TRUE(str, false);
+ return ::CFStringCompare(kTISTypeKeyboardInputMode, str, 0) == kCFCompareEqualTo;
+}
+
+bool TISInputSourceWrapper::IsKeyboardLayout() {
+ NS_ENSURE_TRUE(mInputSource, false);
+ CFStringRef str;
+ GetInputSourceType(str);
+ NS_ENSURE_TRUE(str, false);
+ return ::CFStringCompare(kTISTypeKeyboardLayout, str, 0) == kCFCompareEqualTo;
+}
+
+bool TISInputSourceWrapper::GetLanguageList(CFArrayRef& aLanguageList) {
+ NS_ENSURE_TRUE(mInputSource, false);
+ aLanguageList = static_cast<CFArrayRef>(
+ ::TISGetInputSourceProperty(mInputSource, kTISPropertyInputSourceLanguages));
+ return aLanguageList != nullptr;
+}
+
+bool TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef& aPrimaryLanguage) {
+ NS_ENSURE_TRUE(mInputSource, false);
+ CFArrayRef langList;
+ NS_ENSURE_TRUE(GetLanguageList(langList), false);
+ if (::CFArrayGetCount(langList) == 0) return false;
+ aPrimaryLanguage = static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0));
+ return aPrimaryLanguage != nullptr;
+}
+
+bool TISInputSourceWrapper::GetPrimaryLanguage(nsAString& aPrimaryLanguage) {
+ NS_ENSURE_TRUE(mInputSource, false);
+ CFStringRef primaryLanguage;
+ NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false);
+ nsCocoaUtils::GetStringForNSString((const NSString*)primaryLanguage, aPrimaryLanguage);
+ return !aPrimaryLanguage.IsEmpty();
+}
+
+bool TISInputSourceWrapper::IsForRTLLanguage() {
+ if (mIsRTL < 0) {
+ // Get the input character of the 'A' key of ANSI keyboard layout.
+ nsAutoString str;
+ bool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str);
+ NS_ENSURE_TRUE(ret, ret);
+ char16_t ch = str.IsEmpty() ? char16_t(0) : str.CharAt(0);
+ mIsRTL = UTF16_CODE_UNIT_IS_BIDI(ch);
+ }
+ return mIsRTL != 0;
+}
+
+bool TISInputSourceWrapper::IsForJapaneseLanguage() {
+ nsAutoString lang;
+ GetPrimaryLanguage(lang);
+ return lang.EqualsLiteral("ja");
+}
+
+bool TISInputSourceWrapper::IsInitializedByCurrentInputSource() {
+ return mInputSource == ::TISCopyCurrentKeyboardInputSource();
+}
+
+void TISInputSourceWrapper::Select() {
+ if (!mInputSource) return;
+ ::TISSelectInputSource(mInputSource);
+}
+
+void TISInputSourceWrapper::Clear() {
+ // Clear() is always called when TISInputSourceWrappper is created.
+ EnsureToLogAllKeyboardLayoutsAndIMEs();
+
+ if (mInputSourceList) {
+ ::CFRelease(mInputSourceList);
+ }
+ mInputSourceList = nullptr;
+ mInputSource = nullptr;
+ mKeyboardLayout = nullptr;
+ mIsRTL = -1;
+ mUCKeyboardLayout = nullptr;
+ mOverrideKeyboard = false;
+}
+
+bool TISInputSourceWrapper::IsPrintableKeyEvent(NSEvent* aNativeKeyEvent) const {
+ UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
+
+ bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode);
+ if (isPrintableKey && [aNativeKeyEvent type] != NSEventTypeKeyDown &&
+ [aNativeKeyEvent type] != NSEventTypeKeyUp) {
+ NS_WARNING("Why would a printable key not be an NSEventTypeKeyDown or NSEventTypeKeyUp event?");
+ isPrintableKey = false;
+ }
+ return isPrintableKey;
+}
+
+UInt32 TISInputSourceWrapper::GetKbdType() const {
+ // If a keyboard layout override is set, we also need to force the keyboard
+ // type to something ANSI to avoid test failures on machines with JIS
+ // keyboards (since the pair of keyboard layout and physical keyboard type
+ // form the actual key layout). This assumes that the test setting the
+ // override was written assuming an ANSI keyboard.
+ return mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType();
+}
+
+void TISInputSourceWrapper::ComputeInsertStringForCharCode(NSEvent* aNativeKeyEvent,
+ const WidgetKeyboardEvent& aKeyEvent,
+ const nsAString* aInsertString,
+ nsAString& aResult) {
+ if (aInsertString) {
+ // If the caller expects that the aInsertString will be input, we shouldn't
+ // change it.
+ aResult = *aInsertString;
+ } else if (IsPrintableKeyEvent(aNativeKeyEvent)) {
+ // If IME is open, [aNativeKeyEvent characters] may be a character
+ // which will be appended to the composition string. However, especially,
+ // while IME is disabled, most users and developers expect the key event
+ // works as IME closed. So, we should compute the aResult with
+ // the ASCII capable keyboard layout.
+ // NOTE: Such keyboard layouts typically change the layout to its ASCII
+ // capable layout when Command key is pressed. And we don't worry
+ // when Control key is pressed too because it causes inputting
+ // control characters.
+ // Additionally, if the key event doesn't input any text, the event may be
+ // dead key event. In this case, the charCode value should be the dead
+ // character.
+ UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
+ if ((!aKeyEvent.IsMeta() && !aKeyEvent.IsControl() && IsOpenedIMEMode()) ||
+ ![[aNativeKeyEvent characters] length]) {
+ UInt32 state = nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
+ uint32_t ch = TranslateToChar(nativeKeyCode, state, GetKbdType());
+ if (ch) {
+ aResult = ch;
+ }
+ } else {
+ // If the caller isn't sure what string will be input, let's use
+ // characters of NSEvent.
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aResult);
+ }
+
+ // If control key is pressed and the eventChars is a non-printable control
+ // character, we should convert it to ASCII alphabet.
+ if (aKeyEvent.IsControl() && !aResult.IsEmpty() && aResult[0] <= char16_t(26)) {
+ aResult = (aKeyEvent.IsShift() ^ aKeyEvent.IsCapsLocked())
+ ? static_cast<char16_t>(aResult[0] + ('A' - 1))
+ : static_cast<char16_t>(aResult[0] + ('a' - 1));
+ }
+ // If Meta key is pressed, it may cause to switch the keyboard layout like
+ // Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
+ else if (aKeyEvent.IsMeta() && !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) {
+ UInt32 kbType = GetKbdType();
+ UInt32 numLockState = aKeyEvent.IsNumLocked() ? kEventKeyModifierNumLockMask : 0;
+ UInt32 capsLockState = aKeyEvent.IsCapsLocked() ? alphaLock : 0;
+ UInt32 shiftState = aKeyEvent.IsShift() ? shiftKey : 0;
+ uint32_t uncmdedChar = TranslateToChar(nativeKeyCode, numLockState, kbType);
+ uint32_t cmdedChar = TranslateToChar(nativeKeyCode, cmdKey | numLockState, kbType);
+ // If we can make a good guess at the characters that the user would
+ // expect this key combination to produce (with and without Shift) then
+ // use those characters. This also corrects for CapsLock.
+ uint32_t ch = 0;
+ if (uncmdedChar == cmdedChar) {
+ // The characters produced with Command seem similar to those without
+ // Command.
+ ch = TranslateToChar(nativeKeyCode, shiftState | capsLockState | numLockState, kbType);
+ } else {
+ TISInputSourceWrapper USLayout("com.apple.keylayout.US");
+ uint32_t uncmdedUSChar = USLayout.TranslateToChar(nativeKeyCode, numLockState, kbType);
+ // If it looks like characters from US keyboard layout when Command key
+ // is pressed, we should compute a character in the layout.
+ if (uncmdedUSChar == cmdedChar) {
+ ch = USLayout.TranslateToChar(nativeKeyCode, shiftState | capsLockState | numLockState,
+ kbType);
+ }
+ }
+
+ // If there is a more preferred character for the commanded key event,
+ // we should use it.
+ if (ch) {
+ aResult = ch;
+ }
+ }
+ }
+
+ // Remove control characters which shouldn't be inputted on editor.
+ // XXX Currently, we don't find any cases inserting control characters with
+ // printable character. So, just checking first character is enough.
+ if (!aResult.IsEmpty() && IsControlChar(aResult[0])) {
+ aResult.Truncate();
+ }
+}
+
+void TISInputSourceWrapper::InitKeyEvent(NSEvent* aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
+ bool aIsProcessedByIME, const nsAString* aInsertString) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_ASSERT(!aIsProcessedByIME || aKeyEvent.mMessage != eKeyPress,
+ "eKeyPress event should not be marked as proccessed by IME");
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, "
+ "aKeyEvent.mMessage=%s, aProcessedByIME=%s, aInsertString=%p, "
+ "IsOpenedIMEMode()=%s",
+ this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent), TrueOrFalse(aIsProcessedByIME),
+ aInsertString, TrueOrFalse(IsOpenedIMEMode())));
+
+ if (NS_WARN_IF(!aNativeKeyEvent)) {
+ return;
+ }
+
+ nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent);
+
+ // This is used only while dispatching the event (which is a synchronous
+ // call), so there is no need to retain and release this data.
+ aKeyEvent.mNativeKeyEvent = aNativeKeyEvent;
+
+ aKeyEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
+
+ UInt32 kbType = GetKbdType();
+ UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
+
+ // macOS handles dead key as IME. If the key is first key press of dead
+ // key, we should use KEY_NAME_INDEX_Dead for first (dead) key event.
+ // So, if aIsProcessedByIME is true, it may be dead key. Let's check
+ // if current key event is a dead key's keydown event.
+ bool isProcessedByIME =
+ aIsProcessedByIME && !TISInputSourceWrapper::CurrentInputSource().IsDeadKey(aNativeKeyEvent);
+
+ aKeyEvent.mKeyCode = isProcessedByIME
+ ? NS_VK_PROCESSKEY
+ : ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta());
+
+ switch (nativeKeyCode) {
+ case kVK_Command:
+ case kVK_Shift:
+ case kVK_Option:
+ case kVK_Control:
+ aKeyEvent.mLocation = eKeyLocationLeft;
+ break;
+
+ case kVK_RightCommand:
+ case kVK_RightShift:
+ case kVK_RightOption:
+ case kVK_RightControl:
+ aKeyEvent.mLocation = eKeyLocationRight;
+ break;
+
+ case kVK_ANSI_Keypad0:
+ case kVK_ANSI_Keypad1:
+ case kVK_ANSI_Keypad2:
+ case kVK_ANSI_Keypad3:
+ case kVK_ANSI_Keypad4:
+ case kVK_ANSI_Keypad5:
+ case kVK_ANSI_Keypad6:
+ case kVK_ANSI_Keypad7:
+ case kVK_ANSI_Keypad8:
+ case kVK_ANSI_Keypad9:
+ case kVK_ANSI_KeypadMultiply:
+ case kVK_ANSI_KeypadPlus:
+ case kVK_ANSI_KeypadMinus:
+ case kVK_ANSI_KeypadDecimal:
+ case kVK_ANSI_KeypadDivide:
+ case kVK_ANSI_KeypadEquals:
+ case kVK_ANSI_KeypadEnter:
+ case kVK_JIS_KeypadComma:
+ case kVK_Powerbook_KeypadEnter:
+ aKeyEvent.mLocation = eKeyLocationNumpad;
+ break;
+
+ default:
+ aKeyEvent.mLocation = eKeyLocationStandard;
+ break;
+ }
+
+ aKeyEvent.mIsRepeat =
+ ([aNativeKeyEvent type] == NSEventTypeKeyDown) ? [aNativeKeyEvent isARepeat] : false;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::InitKeyEvent, "
+ "shift=%s, ctrl=%s, alt=%s, meta=%s",
+ this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()),
+ OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta())));
+
+ if (isProcessedByIME) {
+ aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
+ } else if (IsPrintableKeyEvent(aNativeKeyEvent)) {
+ aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ // If insertText calls this method, let's use the string.
+ if (aInsertString && !aInsertString->IsEmpty() && !IsControlChar((*aInsertString)[0])) {
+ aKeyEvent.mKeyValue = *aInsertString;
+ }
+ // If meta key is pressed, the printable key layout may be switched from
+ // non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY.
+ // KeyboardEvent.key value should be the switched layout's character.
+ else if (aKeyEvent.IsMeta()) {
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aKeyEvent.mKeyValue);
+ }
+ // If control key is pressed, some keys may produce printable character via
+ // [aNativeKeyEvent characters]. Otherwise, translate input character of
+ // the key without control key.
+ else if (aKeyEvent.IsControl()) {
+ NSUInteger cocoaState = [aNativeKeyEvent modifierFlags] & ~NSEventModifierFlagControl;
+ UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
+ if (IsDeadKey(nativeKeyCode, carbonState, kbType)) {
+ aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Dead;
+ } else {
+ aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, carbonState, kbType);
+ if (!aKeyEvent.mKeyValue.IsEmpty() && IsControlChar(aKeyEvent.mKeyValue[0])) {
+ // Don't expose control character to the web.
+ aKeyEvent.mKeyValue.Truncate();
+ }
+ }
+ }
+ // Otherwise, KeyboardEvent.key expose
+ // [aNativeKeyEvent characters] value. However, if IME is open and the
+ // keyboard layout isn't ASCII capable, exposing the non-ASCII character
+ // doesn't match with other platform's behavior. For the compatibility
+ // with other platform's Gecko, we need to set a translated character.
+ else if (IsOpenedIMEMode()) {
+ UInt32 state = nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
+ aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, state, kbType);
+ } else {
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aKeyEvent.mKeyValue);
+ // If the key value is empty, the event may be a dead key event.
+ // If TranslateToChar() returns non-zero value, that means that
+ // the key may input a character with different dead key state.
+ if (aKeyEvent.mKeyValue.IsEmpty()) {
+ NSUInteger cocoaState = [aNativeKeyEvent modifierFlags];
+ UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
+ if (TranslateToChar(nativeKeyCode, carbonState, kbType)) {
+ aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Dead;
+ }
+ }
+ }
+
+ // Last resort. If .key value becomes empty string, we should use
+ // charactersIgnoringModifiers, if it's available.
+ if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
+ (aKeyEvent.mKeyValue.IsEmpty() || IsControlChar(aKeyEvent.mKeyValue[0]))) {
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent charactersIgnoringModifiers],
+ aKeyEvent.mKeyValue);
+ // But don't expose it if it's a control character.
+ if (!aKeyEvent.mKeyValue.IsEmpty() && IsControlChar(aKeyEvent.mKeyValue[0])) {
+ aKeyEvent.mKeyValue.Truncate();
+ }
+ }
+ } else {
+ // Compute the key for non-printable keys and some special printable keys.
+ aKeyEvent.mKeyNameIndex = ComputeGeckoKeyNameIndex(nativeKeyCode);
+ }
+
+ aKeyEvent.mCodeNameIndex = ComputeGeckoCodeNameIndex(nativeKeyCode, kbType);
+ MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+void TISInputSourceWrapper::WillDispatchKeyboardEvent(NSEvent* aNativeKeyEvent,
+ const nsAString* aInsertString,
+ uint32_t aIndexOfKeypress,
+ WidgetKeyboardEvent& aKeyEvent) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Nothing to do here if the native key event is neither NSEventTypeKeyDown nor
+ // NSEventTypeKeyUp because accessing [aNativeKeyEvent characters] causes throwing
+ // an exception.
+ if ([aNativeKeyEvent type] != NSEventTypeKeyDown && [aNativeKeyEvent type] != NSEventTypeKeyUp) {
+ return;
+ }
+
+ UInt32 kbType = GetKbdType();
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ nsAutoString chars;
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], chars);
+ NS_ConvertUTF16toUTF8 utf8Chars(chars);
+ char16_t uniChar = static_cast<char16_t>(aKeyEvent.mCharCode);
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "aNativeKeyEvent=%p, aInsertString=%p (\"%s\"), "
+ "aIndexOfKeypress=%u, [aNativeKeyEvent characters]=\"%s\", "
+ "aKeyEvent={ mMessage=%s, mCharCode=0x%X(%s) }, kbType=0x%X, "
+ "IsOpenedIMEMode()=%s",
+ this, aNativeKeyEvent, aInsertString,
+ aInsertString ? GetCharacters(*aInsertString) : "", aIndexOfKeypress,
+ GetCharacters([aNativeKeyEvent characters]), GetGeckoKeyEventType(aKeyEvent),
+ aKeyEvent.mCharCode, uniChar ? NS_ConvertUTF16toUTF8(&uniChar, 1).get() : "",
+ static_cast<unsigned int>(kbType), TrueOrFalse(IsOpenedIMEMode())));
+ }
+
+ nsAutoString insertStringForCharCode;
+ ComputeInsertStringForCharCode(aNativeKeyEvent, aKeyEvent, aInsertString,
+ insertStringForCharCode);
+
+ // The mCharCode was set from mKeyValue. However, for example, when Ctrl key
+ // is pressed, its value should indicate an ASCII character for backward
+ // compatibility rather than inputting character without the modifiers.
+ // Therefore, we need to modify mCharCode value here.
+ uint32_t charCode = 0;
+ if (aIndexOfKeypress < insertStringForCharCode.Length()) {
+ charCode = insertStringForCharCode[aIndexOfKeypress];
+ }
+ aKeyEvent.SetCharCode(charCode);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "aKeyEvent.mKeyCode=0x%X, aKeyEvent.mCharCode=0x%X",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode));
+
+ // If aInsertString is not nullptr (it means InsertText() is called)
+ // and it acutally inputs a character, we don't need to append alternative
+ // charCode values since such keyboard event shouldn't be handled as
+ // a shortcut key.
+ if (aInsertString && charCode) {
+ return;
+ }
+
+ TISInputSourceWrapper USLayout("com.apple.keylayout.US");
+ bool isRomanKeyboardLayout = IsASCIICapable();
+
+ UInt32 key = [aNativeKeyEvent keyCode];
+
+ // Caps lock and num lock modifier state:
+ UInt32 lockState = 0;
+ if ([aNativeKeyEvent modifierFlags] & NSEventModifierFlagCapsLock) {
+ lockState |= alphaLock;
+ }
+ if ([aNativeKeyEvent modifierFlags] & NSEventModifierFlagNumericPad) {
+ lockState |= kEventKeyModifierNumLockMask;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "isRomanKeyboardLayout=%s, kbType=0x%X, key=0x%X",
+ this, TrueOrFalse(isRomanKeyboardLayout), static_cast<unsigned int>(kbType),
+ static_cast<unsigned int>(key)));
+
+ nsString str;
+
+ // normal chars
+ uint32_t unshiftedChar = TranslateToChar(key, lockState, kbType);
+ UInt32 shiftLockMod = shiftKey | lockState;
+ uint32_t shiftedChar = TranslateToChar(key, shiftLockMod, kbType);
+
+ // characters generated with Cmd key
+ // XXX we should remove CapsLock state, which changes characters from
+ // Latin to Cyrillic with Russian layout on 10.4 only when Cmd key
+ // is pressed.
+ UInt32 numState = (lockState & ~alphaLock); // only num lock state
+ uint32_t uncmdedChar = TranslateToChar(key, numState, kbType);
+ UInt32 shiftNumMod = numState | shiftKey;
+ uint32_t uncmdedShiftChar = TranslateToChar(key, shiftNumMod, kbType);
+ uint32_t uncmdedUSChar = USLayout.TranslateToChar(key, numState, kbType);
+ UInt32 cmdNumMod = cmdKey | numState;
+ uint32_t cmdedChar = TranslateToChar(key, cmdNumMod, kbType);
+ UInt32 cmdShiftNumMod = shiftKey | cmdNumMod;
+ uint32_t cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, kbType);
+
+ // Is the keyboard layout changed by Cmd key?
+ // E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
+ bool isCmdSwitchLayout = uncmdedChar != cmdedChar;
+ // Is the keyboard layout for Latin, but Cmd key switches the layout?
+ // I.e., Dvorak-QWERTY
+ bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout;
+
+ // If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed,
+ // we should append unshiftedChar and shiftedChar for handling the
+ // normal characters. These are the characters that the user is most
+ // likely to associate with this key.
+ if ((unshiftedChar || shiftedChar) && (!aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
+ AlternativeCharCode altCharCodes(unshiftedChar, shiftedChar);
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
+ }
+ MOZ_LOG(
+ gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, "
+ "unshiftedChar=U+%X, shiftedChar=U+%X",
+ this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY), unshiftedChar, shiftedChar));
+
+ // Most keyboard layouts provide the same characters in the NSEvents
+ // with Command+Shift as with Command. However, with Command+Shift we
+ // want the character on the second level. e.g. With a US QWERTY
+ // layout, we want "?" when the "/","?" key is pressed with
+ // Command+Shift.
+
+ // On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett)
+ // even though Cmd+SS is 'SS' and Shift+'SS' is '?'. This '/' seems
+ // like a hack to make the Cmd+"?" event look the same as the Cmd+"?"
+ // event on a US keyboard. The user thinks they are typing Cmd+"?", so
+ // we'll prefer the "?" character, replacing mCharCode with shiftedChar
+ // when Shift is pressed. However, in case there is a layout where the
+ // character unique to Cmd+Shift is the character that the user expects,
+ // we'll send it as an alternative char.
+ bool hasCmdShiftOnlyChar = cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar;
+ uint32_t originalCmdedShiftChar = cmdedShiftChar;
+
+ // If we can make a good guess at the characters that the user would
+ // expect this key combination to produce (with and without Shift) then
+ // use those characters. This also corrects for CapsLock, which was
+ // ignored above.
+ if (!isCmdSwitchLayout) {
+ // The characters produced with Command seem similar to those without
+ // Command.
+ if (unshiftedChar) {
+ cmdedChar = unshiftedChar;
+ }
+ if (shiftedChar) {
+ cmdedShiftChar = shiftedChar;
+ }
+ } else if (uncmdedUSChar == cmdedChar) {
+ // It looks like characters from a US layout are provided when Command
+ // is down.
+ uint32_t ch = USLayout.TranslateToChar(key, lockState, kbType);
+ if (ch) {
+ cmdedChar = ch;
+ }
+ ch = USLayout.TranslateToChar(key, shiftLockMod, kbType);
+ if (ch) {
+ cmdedShiftChar = ch;
+ }
+ }
+
+ // If the current keyboard layout is switched by the Cmd key,
+ // we should append cmdedChar and shiftedCmdChar that are
+ // Latin char for the key.
+ // If the keyboard layout is Dvorak-QWERTY, we should append them only when
+ // command key is pressed because when command key isn't pressed, uncmded
+ // chars have been appended already.
+ if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout &&
+ (aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
+ AlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar);
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
+ }
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, "
+ "cmdedChar=U+%X, cmdedShiftChar=U+%X",
+ this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY),
+ TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar));
+ // Special case for 'SS' key of German layout. See the comment of
+ // hasCmdShiftOnlyChar definition for the detail.
+ if (hasCmdShiftOnlyChar && originalCmdedShiftChar) {
+ AlternativeCharCode altCharCodes(0, originalCmdedShiftChar);
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
+ }
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X",
+ this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+uint32_t TISInputSourceWrapper::ComputeGeckoKeyCode(UInt32 aNativeKeyCode, UInt32 aKbType,
+ bool aCmdIsPressed) {
+ MOZ_LOG(
+ gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::ComputeGeckoKeyCode, aNativeKeyCode=0x%X, "
+ "aKbType=0x%X, aCmdIsPressed=%s, IsOpenedIMEMode()=%s, "
+ "IsASCIICapable()=%s",
+ this, static_cast<unsigned int>(aNativeKeyCode), static_cast<unsigned int>(aKbType),
+ TrueOrFalse(aCmdIsPressed), TrueOrFalse(IsOpenedIMEMode()), TrueOrFalse(IsASCIICapable())));
+
+ switch (aNativeKeyCode) {
+ case kVK_Space:
+ return NS_VK_SPACE;
+ case kVK_Escape:
+ return NS_VK_ESCAPE;
+
+ // modifiers
+ case kVK_RightCommand:
+ case kVK_Command:
+ return NS_VK_META;
+ case kVK_RightShift:
+ case kVK_Shift:
+ return NS_VK_SHIFT;
+ case kVK_CapsLock:
+ return NS_VK_CAPS_LOCK;
+ case kVK_RightControl:
+ case kVK_Control:
+ return NS_VK_CONTROL;
+ case kVK_RightOption:
+ case kVK_Option:
+ return NS_VK_ALT;
+
+ case kVK_ANSI_KeypadClear:
+ return NS_VK_CLEAR;
+
+ // function keys
+ case kVK_F1:
+ return NS_VK_F1;
+ case kVK_F2:
+ return NS_VK_F2;
+ case kVK_F3:
+ return NS_VK_F3;
+ case kVK_F4:
+ return NS_VK_F4;
+ case kVK_F5:
+ return NS_VK_F5;
+ case kVK_F6:
+ return NS_VK_F6;
+ case kVK_F7:
+ return NS_VK_F7;
+ case kVK_F8:
+ return NS_VK_F8;
+ case kVK_F9:
+ return NS_VK_F9;
+ case kVK_F10:
+ return NS_VK_F10;
+ case kVK_F11:
+ return NS_VK_F11;
+ case kVK_F12:
+ return NS_VK_F12;
+ // case kVK_F13: return NS_VK_F13; // clash with the 3 below
+ // case kVK_F14: return NS_VK_F14;
+ // case kVK_F15: return NS_VK_F15;
+ case kVK_F16:
+ return NS_VK_F16;
+ case kVK_F17:
+ return NS_VK_F17;
+ case kVK_F18:
+ return NS_VK_F18;
+ case kVK_F19:
+ return NS_VK_F19;
+
+ case kVK_PC_Pause:
+ return NS_VK_PAUSE;
+ case kVK_PC_ScrollLock:
+ return NS_VK_SCROLL_LOCK;
+ case kVK_PC_PrintScreen:
+ return NS_VK_PRINTSCREEN;
+
+ // keypad
+ case kVK_ANSI_Keypad0:
+ return NS_VK_NUMPAD0;
+ case kVK_ANSI_Keypad1:
+ return NS_VK_NUMPAD1;
+ case kVK_ANSI_Keypad2:
+ return NS_VK_NUMPAD2;
+ case kVK_ANSI_Keypad3:
+ return NS_VK_NUMPAD3;
+ case kVK_ANSI_Keypad4:
+ return NS_VK_NUMPAD4;
+ case kVK_ANSI_Keypad5:
+ return NS_VK_NUMPAD5;
+ case kVK_ANSI_Keypad6:
+ return NS_VK_NUMPAD6;
+ case kVK_ANSI_Keypad7:
+ return NS_VK_NUMPAD7;
+ case kVK_ANSI_Keypad8:
+ return NS_VK_NUMPAD8;
+ case kVK_ANSI_Keypad9:
+ return NS_VK_NUMPAD9;
+
+ case kVK_ANSI_KeypadMultiply:
+ return NS_VK_MULTIPLY;
+ case kVK_ANSI_KeypadPlus:
+ return NS_VK_ADD;
+ case kVK_ANSI_KeypadMinus:
+ return NS_VK_SUBTRACT;
+ case kVK_ANSI_KeypadDecimal:
+ return NS_VK_DECIMAL;
+ case kVK_ANSI_KeypadDivide:
+ return NS_VK_DIVIDE;
+
+ case kVK_JIS_KeypadComma:
+ return NS_VK_SEPARATOR;
+
+ // IME keys
+ case kVK_JIS_Eisu:
+ return NS_VK_EISU;
+ case kVK_JIS_Kana:
+ return NS_VK_KANA;
+
+ // these may clash with forward delete and help
+ case kVK_PC_Insert:
+ return NS_VK_INSERT;
+ case kVK_PC_Delete:
+ return NS_VK_DELETE;
+
+ case kVK_PC_Backspace:
+ return NS_VK_BACK;
+ case kVK_Tab:
+ return NS_VK_TAB;
+
+ case kVK_Home:
+ return NS_VK_HOME;
+ case kVK_End:
+ return NS_VK_END;
+
+ case kVK_PageUp:
+ return NS_VK_PAGE_UP;
+ case kVK_PageDown:
+ return NS_VK_PAGE_DOWN;
+
+ case kVK_LeftArrow:
+ return NS_VK_LEFT;
+ case kVK_RightArrow:
+ return NS_VK_RIGHT;
+ case kVK_UpArrow:
+ return NS_VK_UP;
+ case kVK_DownArrow:
+ return NS_VK_DOWN;
+
+ case kVK_PC_ContextMenu:
+ return NS_VK_CONTEXT_MENU;
+
+ case kVK_ANSI_1:
+ return NS_VK_1;
+ case kVK_ANSI_2:
+ return NS_VK_2;
+ case kVK_ANSI_3:
+ return NS_VK_3;
+ case kVK_ANSI_4:
+ return NS_VK_4;
+ case kVK_ANSI_5:
+ return NS_VK_5;
+ case kVK_ANSI_6:
+ return NS_VK_6;
+ case kVK_ANSI_7:
+ return NS_VK_7;
+ case kVK_ANSI_8:
+ return NS_VK_8;
+ case kVK_ANSI_9:
+ return NS_VK_9;
+ case kVK_ANSI_0:
+ return NS_VK_0;
+
+ case kVK_ANSI_KeypadEnter:
+ case kVK_Return:
+ case kVK_Powerbook_KeypadEnter:
+ return NS_VK_RETURN;
+ }
+
+ // If Cmd key is pressed, that causes switching keyboard layout temporarily.
+ // E.g., Dvorak-QWERTY. Therefore, if Cmd key is pressed, we should honor it.
+ UInt32 modifiers = aCmdIsPressed ? cmdKey : 0;
+
+ uint32_t charCode = TranslateToChar(aNativeKeyCode, modifiers, aKbType);
+
+ // Special case for Mac. Mac inputs Yen sign (U+00A5) directly instead of
+ // Back slash (U+005C). We should return NS_VK_BACK_SLASH for compatibility
+ // with other platforms.
+ // XXX How about Won sign (U+20A9) which has same problem as Yen sign?
+ if (charCode == 0x00A5) {
+ return NS_VK_BACK_SLASH;
+ }
+
+ uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
+ if (keyCode) {
+ return keyCode;
+ }
+
+ // If the unshifed char isn't an ASCII character, use shifted char.
+ charCode = TranslateToChar(aNativeKeyCode, modifiers | shiftKey, aKbType);
+ keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
+ if (keyCode) {
+ return keyCode;
+ }
+
+ if (!IsASCIICapable()) {
+ // Retry with ASCII capable keyboard layout.
+ TISInputSourceWrapper currentKeyboardLayout;
+ currentKeyboardLayout.InitByCurrentASCIICapableKeyboardLayout();
+ NS_ENSURE_TRUE(mInputSource != currentKeyboardLayout.mInputSource, 0);
+ keyCode = currentKeyboardLayout.ComputeGeckoKeyCode(aNativeKeyCode, aKbType, aCmdIsPressed);
+ // We've returned 0 for long time if keyCode isn't for an alphabet keys or
+ // a numeric key even in alternative ASCII capable keyboard layout because
+ // we decided that we should avoid setting same keyCode value to 2 or
+ // more keys since active keyboard layout may have a key to input the
+ // punctuation with different key. However, setting keyCode to 0 makes
+ // some web applications which are aware of neither KeyboardEvent.key nor
+ // KeyboardEvent.code not work with Firefox when user selects non-ASCII
+ // capable keyboard layout such as Russian and Thai. So, if alternative
+ // ASCII capable keyboard layout has keyCode value for the key, we should
+ // use it. In other words, this behavior does that non-ASCII capable
+ // keyboard layout overrides some keys' keyCode value only if the key
+ // produces ASCII character by itself or with Shift key.
+ if (keyCode) {
+ return keyCode;
+ }
+ }
+
+ // Otherwise, let's decide keyCode value from the native virtual keycode
+ // value on major keyboard layout.
+ CodeNameIndex code = ComputeGeckoCodeNameIndex(aNativeKeyCode, aKbType);
+ return WidgetKeyboardEvent::GetFallbackKeyCodeOfPunctuationKey(code);
+}
+
+// static
+KeyNameIndex TISInputSourceWrapper::ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode) {
+ // NOTE:
+ // When unsupported keys like Convert, Nonconvert of Japanese keyboard is
+ // pressed:
+ // on 10.6.x, 'A' key event is fired (and also actually 'a' is inserted).
+ // on 10.7.x, Nothing happens.
+ // on 10.8.x, Nothing happens.
+ // on 10.9.x, FlagsChanged event is fired with keyCode 0xFF.
+ switch (aNativeKeyCode) {
+#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: \
+ return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ return KEY_NAME_INDEX_Unidentified;
+ }
+}
+
+// static
+CodeNameIndex TISInputSourceWrapper::ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode,
+ UInt32 aKbType) {
+ // macOS swaps native key code between Backquote key and IntlBackslash key
+ // only when the keyboard type is ISO. Let's treat the key code after
+ // swapping them here because Chromium does so only when computing .code
+ // value.
+ if (::KBGetLayoutType(aKbType) == kKeyboardISO) {
+ if (aNativeKeyCode == kVK_ISO_Section) {
+ aNativeKeyCode = kVK_ANSI_Grave;
+ } else if (aNativeKeyCode == kVK_ANSI_Grave) {
+ aNativeKeyCode = kVK_ISO_Section;
+ }
+ }
+
+ switch (aNativeKeyCode) {
+#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
+ case aNativeKey: \
+ return aCodeNameIndex;
+
+#include "NativeKeyToDOMCodeName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
+
+ default:
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+}
+
+#pragma mark -
+
+/******************************************************************************
+ *
+ * TextInputHandler implementation (static methods)
+ *
+ ******************************************************************************/
+
+NSUInteger TextInputHandler::sLastModifierState = 0;
+
+// static
+CFArrayRef TextInputHandler::CreateAllKeyboardLayoutList() {
+ const void* keys[] = {kTISPropertyInputSourceType};
+ const void* values[] = {kTISTypeKeyboardLayout};
+ CFDictionaryRef filter = ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
+ NS_ASSERTION(filter, "failed to create the filter");
+ CFArrayRef list = ::TISCreateInputSourceList(filter, true);
+ ::CFRelease(filter);
+ return list;
+}
+
+// static
+void TextInputHandler::DebugPrintAllKeyboardLayouts() {
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ CFArrayRef list = CreateAllKeyboardLayoutList();
+ MOZ_LOG(gLog, LogLevel::Info, ("Keyboard layout configuration:"));
+ CFIndex idx = ::CFArrayGetCount(list);
+ TISInputSourceWrapper tis;
+ for (CFIndex i = 0; i < idx; ++i) {
+ TISInputSourceRef inputSource =
+ static_cast<TISInputSourceRef>(const_cast<void*>(::CFArrayGetValueAtIndex(list, i)));
+ tis.InitByTISInputSourceRef(inputSource);
+ nsAutoString name, isid;
+ tis.GetLocalizedName(name);
+ tis.GetInputSourceID(isid);
+ MOZ_LOG(
+ gLog, LogLevel::Info,
+ (" %s\t<%s>%s%s\n", NS_ConvertUTF16toUTF8(name).get(), NS_ConvertUTF16toUTF8(isid).get(),
+ tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
+ tis.IsKeyboardLayout() && tis.GetUCKeyboardLayout() ? "" : "\t(uchr is NOT AVAILABLE)"));
+ }
+ ::CFRelease(list);
+ }
+}
+
+#pragma mark -
+
+/******************************************************************************
+ *
+ * TextInputHandler implementation
+ *
+ ******************************************************************************/
+
+TextInputHandler::TextInputHandler(nsChildView* aWidget, NSView<mozView>* aNativeView)
+ : IMEInputHandler(aWidget, aNativeView) {
+ EnsureToLogAllKeyboardLayoutsAndIMEs();
+ [mView installTextInputHandler:this];
+}
+
+TextInputHandler::~TextInputHandler() { [mView uninstallTextInputHandler]; }
+
+bool TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent, uint32_t aUniqueId) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, "
+ "widget has been already destroyed",
+ this));
+ return false;
+ }
+
+ // Insert empty line to the log for easier to read.
+ MOZ_LOG(gLog, LogLevel::Info, (""));
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, aNativeEvent=%p, "
+ "type=%s, keyCode=%u (0x%X), modifierFlags=0x%lX, characters=\"%s\", "
+ "charactersIgnoringModifiers=\"%s\"",
+ this, aNativeEvent, GetNativeKeyEventType(aNativeEvent), [aNativeEvent keyCode],
+ [aNativeEvent keyCode], static_cast<unsigned long>([aNativeEvent modifierFlags]),
+ GetCharacters([aNativeEvent characters]),
+ GetCharacters([aNativeEvent charactersIgnoringModifiers])));
+
+ // Except when Command key is pressed, we should hide mouse cursor until
+ // next mousemove. Handling here means that:
+ // - Don't hide mouse cursor at pressing modifier key
+ // - Hide mouse cursor even if the key event will be handled by IME (i.e.,
+ // even without dispatching eKeyPress events)
+ // - Hide mouse cursor even when a plugin has focus
+ if (!([aNativeEvent modifierFlags] & NSEventModifierFlagCommand)) {
+ [NSCursor setHiddenUntilMouseMoves:YES];
+ }
+
+ RefPtr<nsChildView> widget(mWidget);
+
+ KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent, aUniqueId);
+ AutoKeyEventStateCleaner remover(this);
+
+ RefPtr<TextInputHandler> kungFuDeathGrip(this);
+
+ // When we're already in a composition, we need always to mark the eKeyDown
+ // event as "processed by IME". So, let's dispatch eKeyDown event here in
+ // such case.
+ if (IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(true)) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::HandleKeyDownEvent, eKeyDown caused focus move or "
+ "something and canceling the composition",
+ this));
+ return false;
+ }
+
+ // Let Cocoa interpret the key events, caching IsIMEComposing first.
+ bool wasComposing = IsIMEComposing();
+ bool interpretKeyEventsCalled = false;
+ // Don't call interpretKeyEvents when a plugin has focus. If we call it,
+ // for example, a character is inputted twice during a composition in e10s
+ // mode.
+ if (IsIMEEnabled() || IsASCIICapableOnly()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, calling interpretKeyEvents", this));
+ [mView interpretKeyEvents:[NSArray arrayWithObject:aNativeEvent]];
+ interpretKeyEventsCalled = true;
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents", this));
+ }
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, widget was destroyed", this));
+ return currentKeyEvent->IsDefaultPrevented();
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, wasComposing=%s, "
+ "IsIMEComposing()=%s",
+ this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing())));
+
+ if (currentKeyEvent->CanDispatchKeyDownEvent()) {
+ // Dispatch eKeyDown event if nobody has dispatched it yet.
+ // NOTE: Although reaching here means that the native keydown event may
+ // not be handled by IME. However, we cannot know if it is.
+ // For example, Japanese IME of Apple shows candidate window for
+ // typing window. They, you can switch the sort order with Tab key.
+ // However, when you choose "Symbol" of the sort order, there may
+ // be no candiate words. In this case, IME handles the Tab key
+ // actually, but we cannot know it because composition string is
+ // not updated. So, let's mark eKeyDown event as "processed by IME"
+ // when there is composition string. This is same as Chrome.
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, trying to dispatch eKeyDown "
+ "event since it's not yet dispatched",
+ this));
+ if (!MaybeDispatchCurrentKeydownEvent(IsIMEComposing())) {
+ return true; // treat the eKeydDown event as consumed.
+ }
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, eKeyDown event has been "
+ "dispatched",
+ this));
+ }
+
+ if (currentKeyEvent->CanDispatchKeyPressEvent() && !wasComposing && !IsIMEComposing()) {
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::HandleKeyDownEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure "
+ "at dispatching keypress",
+ this));
+ return false;
+ }
+
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
+ currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
+
+ // If we called interpretKeyEvents and this isn't normal character input
+ // then IME probably ate the event for some reason. We do not want to
+ // send a key press event in that case.
+ // TODO:
+ // There are some other cases which IME eats the current event.
+ // 1. If key events were nested during calling interpretKeyEvents, it means
+ // that IME did something. Then, we should do nothing.
+ // 2. If one or more commands are called like "deleteBackward", we should
+ // dispatch keypress event at that time. Note that the command may have
+ // been a converted or generated action by IME. Then, we shouldn't do
+ // our default action for this key.
+ if (!(interpretKeyEventsCalled && IsNormalCharInputtingEvent(aNativeEvent))) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, trying to dispatch "
+ "eKeyPress event since it's not yet dispatched",
+ this));
+ nsEventStatus status = nsEventStatus_eIgnore;
+ currentKeyEvent->mKeyPressDispatched =
+ mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, currentKeyEvent);
+ currentKeyEvent->mKeyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
+ currentKeyEvent->mKeyPressDispatched = true;
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, eKeyPress event has been "
+ "dispatched",
+ this));
+ }
+ }
+
+ // Note: mWidget might have become null here. Don't count on it from here on.
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, "
+ "keydown handled=%s, keypress handled=%s, causedOtherKeyEvents=%s, "
+ "compositionDispatched=%s",
+ this, TrueOrFalse(currentKeyEvent->mKeyDownHandled),
+ TrueOrFalse(currentKeyEvent->mKeyPressHandled),
+ TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents),
+ TrueOrFalse(currentKeyEvent->mCompositionDispatched)));
+ // Insert empty line to the log for easier to read.
+ MOZ_LOG(gLog, LogLevel::Info, (""));
+ return currentKeyEvent->IsDefaultPrevented();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+void TextInputHandler::HandleKeyUpEvent(NSEvent* aNativeEvent) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(
+ gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyUpEvent, aNativeEvent=%p, "
+ "type=%s, keyCode=%u (0x%X), modifierFlags=0x%lX, characters=\"%s\", "
+ "charactersIgnoringModifiers=\"%s\", "
+ "IsIMEComposing()=%s",
+ this, aNativeEvent, GetNativeKeyEventType(aNativeEvent), [aNativeEvent keyCode],
+ [aNativeEvent keyCode], static_cast<unsigned long>([aNativeEvent modifierFlags]),
+ GetCharacters([aNativeEvent characters]),
+ GetCharacters([aNativeEvent charactersIgnoringModifiers]), TrueOrFalse(IsIMEComposing())));
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyUpEvent, "
+ "widget has been already destroyed",
+ this));
+ return;
+ }
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::HandleKeyUpEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure",
+ this));
+ return;
+ }
+
+ // Neither Chrome for macOS nor Safari marks "keyup" event as "processed by
+ // IME" even during composition. So, let's follow this behavior.
+ WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
+ InitKeyEvent(aNativeEvent, keyupEvent, false);
+
+ KeyEventState currentKeyEvent(aNativeEvent);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status, &currentKeyEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void TextInputHandler::HandleFlagsChanged(NSEvent* aNativeEvent) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleFlagsChanged, "
+ "widget has been already destroyed",
+ this));
+ return;
+ }
+
+ RefPtr<nsChildView> kungFuDeathGrip(mWidget);
+ mozilla::Unused << kungFuDeathGrip; // Not referenced within this function
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleFlagsChanged, aNativeEvent=%p, "
+ "type=%s, keyCode=%s (0x%X), modifierFlags=0x%08lX, "
+ "sLastModifierState=0x%08lX, IsIMEComposing()=%s",
+ this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
+ GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
+ static_cast<unsigned long>([aNativeEvent modifierFlags]),
+ static_cast<unsigned long>(sLastModifierState), TrueOrFalse(IsIMEComposing())));
+
+ MOZ_ASSERT([aNativeEvent type] == NSEventTypeFlagsChanged);
+
+ NSUInteger diff = [aNativeEvent modifierFlags] ^ sLastModifierState;
+ // Device dependent flags for left-control key, both shift keys, both command
+ // keys and both option keys have been defined in Next's SDK. But we
+ // shouldn't use it directly as far as possible since Cocoa SDK doesn't
+ // define them. Fortunately, we need them only when we dispatch keyup
+ // events. So, we can usually know the actual relation between keyCode and
+ // device dependent flags. However, we need to remove following flags first
+ // since the differences don't indicate modifier key state.
+ // NX_STYLUSPROXIMITYMASK: Probably used for pen like device.
+ // kCGEventFlagMaskNonCoalesced (= NX_NONCOALSESCEDMASK): See the document for
+ // Quartz Event Services.
+ diff &= ~(NX_STYLUSPROXIMITYMASK | kCGEventFlagMaskNonCoalesced);
+
+ switch ([aNativeEvent keyCode]) {
+ // CapsLock state and other modifier states are different:
+ // CapsLock state does not revert when the CapsLock key goes up, as the
+ // modifier state does for other modifier keys on key up.
+ case kVK_CapsLock: {
+ // Fire key down event for caps lock.
+ DispatchKeyEventForFlagsChanged(aNativeEvent, true);
+ // XXX should we fire keyup event too? The keyup event for CapsLock key
+ // is never dispatched on Gecko.
+ // XXX WebKit dispatches keydown event when CapsLock is locked, otherwise,
+ // keyup event. If we do so, we cannot keep the consistency with other
+ // platform's behavior...
+ break;
+ }
+
+ // If the event is caused by pressing or releasing a modifier key, just
+ // dispatch the key's event.
+ case kVK_Shift:
+ case kVK_RightShift:
+ case kVK_Command:
+ case kVK_RightCommand:
+ case kVK_Control:
+ case kVK_RightControl:
+ case kVK_Option:
+ case kVK_RightOption:
+ case kVK_Help: {
+ // We assume that at most one modifier is changed per event if the event
+ // is caused by pressing or releasing a modifier key.
+ bool isKeyDown = ([aNativeEvent modifierFlags] & diff) != 0;
+ DispatchKeyEventForFlagsChanged(aNativeEvent, isKeyDown);
+ // XXX Some applications might send the event with incorrect device-
+ // dependent flags.
+ if (isKeyDown && ((diff & ~NSEventModifierFlagDeviceIndependentFlagsMask) != 0)) {
+ unsigned short keyCode = [aNativeEvent keyCode];
+ const ModifierKey* modifierKey = GetModifierKeyForDeviceDependentFlags(diff);
+ if (modifierKey && modifierKey->keyCode != keyCode) {
+ // Although, we're not sure the actual cause of this case, the stored
+ // modifier information and the latest key event information may be
+ // mismatched. Then, let's reset the stored information.
+ // NOTE: If this happens, it may fail to handle NSEventTypeFlagsChanged event
+ // in the default case (below). However, it's the rare case handler
+ // and this case occurs rarely. So, we can ignore the edge case bug.
+ NS_WARNING("Resetting stored modifier key information");
+ mModifierKeys.Clear();
+ modifierKey = nullptr;
+ }
+ if (!modifierKey) {
+ mModifierKeys.AppendElement(ModifierKey(diff, keyCode));
+ }
+ }
+ break;
+ }
+
+ // Currently we don't support Fn key since other browsers don't dispatch
+ // events for it and we don't have keyCode for this key.
+ // It should be supported when we implement .key and .char.
+ case kVK_Function:
+ break;
+
+ // If the event is caused by something else than pressing or releasing a
+ // single modifier key (for example by the app having been deactivated
+ // using command-tab), use the modifiers themselves to determine which
+ // key's event to dispatch, and whether it's a keyup or keydown event.
+ // In all cases we assume one or more modifiers are being deactivated
+ // (never activated) -- otherwise we'd have received one or more events
+ // corresponding to a single modifier key being pressed.
+ default: {
+ NSUInteger modifiers = sLastModifierState;
+ AutoTArray<unsigned short, 10> dispatchedKeyCodes;
+ for (int32_t bit = 0; bit < 32; ++bit) {
+ NSUInteger flag = 1 << bit;
+ if (!(diff & flag)) {
+ continue;
+ }
+
+ // Given correct information from the application, a flag change here
+ // will normally be a deactivation (except for some lockable modifiers
+ // such as CapsLock). But some applications (like VNC) can send an
+ // activating event with a zero keyCode. So we need to check for that
+ // here.
+ bool dispatchKeyDown = ((flag & [aNativeEvent modifierFlags]) != 0);
+
+ unsigned short keyCode = 0;
+ if (flag & NSEventModifierFlagDeviceIndependentFlagsMask) {
+ switch (flag) {
+ case NSEventModifierFlagCapsLock:
+ keyCode = kVK_CapsLock;
+ dispatchKeyDown = true;
+ break;
+
+ case NSEventModifierFlagNumericPad:
+ // NSEventModifierFlagNumericPad is fired by VNC a lot. But not all of
+ // these events can really be Clear key events, so we just ignore
+ // them.
+ continue;
+
+ case NSEventModifierFlagHelp:
+ keyCode = kVK_Help;
+ break;
+
+ case NSEventModifierFlagFunction:
+ // An NSEventModifierFlagFunction change here will normally be a
+ // deactivation. But sometimes it will be an activation send (by
+ // VNC for example) with a zero keyCode.
+ continue;
+
+ // These cases (NSEventModifierFlagShift, NSEventModifierFlagControl,
+ // NSEventModifierFlagOption and NSEventModifierFlagCommand) should be handled by the
+ // other branch of the if statement, below (which handles device dependent flags).
+ // However, some applications (like VNC) can send key events without
+ // any device dependent flags, so we handle them here instead.
+ case NSEventModifierFlagShift:
+ keyCode = (modifiers & 0x0004) ? kVK_RightShift : kVK_Shift;
+ break;
+ case NSEventModifierFlagControl:
+ keyCode = (modifiers & 0x2000) ? kVK_RightControl : kVK_Control;
+ break;
+ case NSEventModifierFlagOption:
+ keyCode = (modifiers & 0x0040) ? kVK_RightOption : kVK_Option;
+ break;
+ case NSEventModifierFlagCommand:
+ keyCode = (modifiers & 0x0010) ? kVK_RightCommand : kVK_Command;
+ break;
+
+ default:
+ continue;
+ }
+ } else {
+ const ModifierKey* modifierKey = GetModifierKeyForDeviceDependentFlags(flag);
+ if (!modifierKey) {
+ // See the note above (in the other branch of the if statement)
+ // about the NSEventModifierFlagShift, NSEventModifierFlagControl,
+ // NSEventModifierFlagOption and NSEventModifierFlagCommand cases.
+ continue;
+ }
+ keyCode = modifierKey->keyCode;
+ }
+
+ // Remove flags
+ modifiers &= ~flag;
+ switch (keyCode) {
+ case kVK_Shift: {
+ const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_RightShift);
+ if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSEventModifierFlagShift;
+ }
+ break;
+ }
+ case kVK_RightShift: {
+ const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_Shift);
+ if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSEventModifierFlagShift;
+ }
+ break;
+ }
+ case kVK_Command: {
+ const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_RightCommand);
+ if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSEventModifierFlagCommand;
+ }
+ break;
+ }
+ case kVK_RightCommand: {
+ const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_Command);
+ if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSEventModifierFlagCommand;
+ }
+ break;
+ }
+ case kVK_Control: {
+ const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_RightControl);
+ if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSEventModifierFlagControl;
+ }
+ break;
+ }
+ case kVK_RightControl: {
+ const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_Control);
+ if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSEventModifierFlagControl;
+ }
+ break;
+ }
+ case kVK_Option: {
+ const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_RightOption);
+ if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSEventModifierFlagOption;
+ }
+ break;
+ }
+ case kVK_RightOption: {
+ const ModifierKey* modifierKey = GetModifierKeyForNativeKeyCode(kVK_Option);
+ if (!modifierKey || !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSEventModifierFlagOption;
+ }
+ break;
+ }
+ case kVK_Help:
+ modifiers &= ~NSEventModifierFlagHelp;
+ break;
+ default:
+ break;
+ }
+
+ // Avoid dispatching same keydown/keyup events twice or more.
+ // We must be able to assume that there is no case to dispatch
+ // both keydown and keyup events with same key code value here.
+ if (dispatchedKeyCodes.Contains(keyCode)) {
+ continue;
+ }
+ dispatchedKeyCodes.AppendElement(keyCode);
+
+ NSEvent* event = [NSEvent keyEventWithType:NSEventTypeFlagsChanged
+ location:[aNativeEvent locationInWindow]
+ modifierFlags:modifiers
+ timestamp:[aNativeEvent timestamp]
+ windowNumber:[aNativeEvent windowNumber]
+ context:[aNativeEvent context]
+ characters:@""
+ charactersIgnoringModifiers:@""
+ isARepeat:NO
+ keyCode:keyCode];
+ DispatchKeyEventForFlagsChanged(event, dispatchKeyDown);
+ if (Destroyed()) {
+ break;
+ }
+
+ // Stop if focus has changed.
+ // Check to see if mView is still the first responder.
+ if (![mView isFirstResponder]) {
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ // Be aware, the widget may have been destroyed.
+ sLastModifierState = [aNativeEvent modifierFlags];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+const TextInputHandler::ModifierKey* TextInputHandler::GetModifierKeyForNativeKeyCode(
+ unsigned short aKeyCode) const {
+ for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
+ if (mModifierKeys[i].keyCode == aKeyCode) {
+ return &((ModifierKey&)mModifierKeys[i]);
+ }
+ }
+ return nullptr;
+}
+
+const TextInputHandler::ModifierKey* TextInputHandler::GetModifierKeyForDeviceDependentFlags(
+ NSUInteger aFlags) const {
+ for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
+ if (mModifierKeys[i].GetDeviceDependentFlags() ==
+ (aFlags & ~NSEventModifierFlagDeviceIndependentFlagsMask)) {
+ return &((ModifierKey&)mModifierKeys[i]);
+ }
+ }
+ return nullptr;
+}
+
+void TextInputHandler::DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
+ bool aDispatchKeyDown) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (Destroyed()) {
+ return;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::DispatchKeyEventForFlagsChanged, aNativeEvent=%p, "
+ "type=%s, keyCode=%s (0x%X), aDispatchKeyDown=%s, IsIMEComposing()=%s",
+ this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
+ GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
+ TrueOrFalse(aDispatchKeyDown), TrueOrFalse(IsIMEComposing())));
+
+ if ([aNativeEvent type] != NSEventTypeFlagsChanged) {
+ return;
+ }
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchKeyEventForFlagsChanged, "
+ "FAILED, due to BeginNativeInputTransaction() failure",
+ this));
+ return;
+ }
+
+ EventMessage message = aDispatchKeyDown ? eKeyDown : eKeyUp;
+
+ // Fire a key event for the modifier key. Note that even if modifier key
+ // is pressed during composition, we shouldn't mark the keyboard event as
+ // "processed by IME" since neither Chrome for macOS nor Safari does it.
+ WidgetKeyboardEvent keyEvent(true, message, mWidget);
+ InitKeyEvent(aNativeEvent, keyEvent, false);
+
+ // Attach a plugin event, in case keyEvent gets dispatched to a plugin. Only
+ // one field is needed -- the type. The other fields can be constructed as
+ // the need arises. But Gecko doesn't have anything equivalent to the
+ // NPCocoaEventFlagsChanged type, and this needs to be passed accurately to
+ // any plugin to which this event is sent.
+ NPCocoaEvent cocoaEvent;
+ nsCocoaUtils::InitNPCocoaEvent(&cocoaEvent);
+ cocoaEvent.type = NPCocoaEventFlagsChanged;
+ keyEvent.mPluginEvent.Copy(cocoaEvent);
+
+ KeyEventState currentKeyEvent(aNativeEvent);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mDispatcher->DispatchKeyboardEvent(message, keyEvent, status, &currentKeyEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void TextInputHandler::InsertText(NSAttributedString* aAttrString, NSRange* aReplacementRange) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (Destroyed()) {
+ return;
+ }
+
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::InsertText, aAttrString=\"%s\", "
+ "aReplacementRange=%p { location=%lu, length=%lu }, "
+ "IsIMEComposing()=%s, "
+ "keyevent=%p, keydownDispatched=%s, "
+ "keydownHandled=%s, keypressDispatched=%s, "
+ "causedOtherKeyEvents=%s, compositionDispatched=%s",
+ this, GetCharacters([aAttrString string]), aReplacementRange,
+ static_cast<unsigned long>(aReplacementRange ? aReplacementRange->location : 0),
+ static_cast<unsigned long>(aReplacementRange ? aReplacementRange->length : 0),
+ TrueOrFalse(IsIMEComposing()), currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownDispatched) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
+
+ InputContext context = mWidget->GetInputContext();
+ bool isEditable = (context.mIMEState.mEnabled == IMEEnabled::Enabled ||
+ context.mIMEState.mEnabled == IMEEnabled::Password);
+ NSRange selectedRange = SelectedRange();
+
+ nsAutoString str;
+ nsCocoaUtils::GetStringForNSString([aAttrString string], str);
+
+ AutoInsertStringClearer clearer(currentKeyEvent);
+ if (currentKeyEvent) {
+ currentKeyEvent->mInsertString = &str;
+ }
+
+ if (!IsIMEComposing() && str.IsEmpty()) {
+ // nothing to do if there is no content which can be removed.
+ if (!isEditable) {
+ return;
+ }
+ // If replacement range is specified, we need to remove the range.
+ // Otherwise, we need to remove the selected range if it's not collapsed.
+ if (aReplacementRange && aReplacementRange->location != NSNotFound) {
+ // nothing to do since the range is collapsed.
+ if (aReplacementRange->length == 0) {
+ return;
+ }
+ // If the replacement range is different from current selected range,
+ // select the range.
+ if (!NSEqualRanges(selectedRange, *aReplacementRange)) {
+ NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
+ }
+ selectedRange = SelectedRange();
+ }
+ NS_ENSURE_TRUE_VOID(selectedRange.location != NSNotFound);
+ if (selectedRange.length == 0) {
+ return; // nothing to do
+ }
+ // If this is caused by a key input, the keypress event which will be
+ // dispatched later should cause the delete. Therefore, nothing to do here.
+ // Although, we're not sure if such case is actually possible.
+ if (!currentKeyEvent) {
+ return;
+ }
+
+ // When current keydown event causes this empty text input, let's
+ // dispatch eKeyDown event before any other events. Note that if we're
+ // in a composition, we've already dispatched eKeyDown event from
+ // TextInputHandler::HandleKeyDownEvent().
+ // XXX Should we mark this eKeyDown event as "processed by IME"?
+ RefPtr<TextInputHandler> kungFuDeathGrip(this);
+ if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertText, eKeyDown caused focus move or "
+ "something and canceling the composition",
+ this));
+ return;
+ }
+
+ // Delete the selected range.
+ WidgetContentCommandEvent deleteCommandEvent(true, eContentCommandDelete, mWidget);
+ DispatchEvent(deleteCommandEvent);
+ NS_ENSURE_TRUE_VOID(deleteCommandEvent.mSucceeded);
+ // Be aware! The widget might be destroyed here.
+ return;
+ }
+
+ bool isReplacingSpecifiedRange = isEditable && aReplacementRange &&
+ aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(selectedRange, *aReplacementRange);
+
+ // If this is not caused by pressing a key, there is a composition or
+ // replacing a range which is different from current selection, let's
+ // insert the text as committing a composition.
+ // If InsertText() is called two or more times, we should insert all
+ // text with composition events.
+ // XXX When InsertText() is called multiple times, Chromium dispatches
+ // only one composition event. So, we need to store InsertText()
+ // calls and flush later.
+ if (!currentKeyEvent || currentKeyEvent->mCompositionDispatched || IsIMEComposing() ||
+ isReplacingSpecifiedRange) {
+ InsertTextAsCommittingComposition(aAttrString, aReplacementRange);
+ if (currentKeyEvent) {
+ currentKeyEvent->mCompositionDispatched = true;
+ }
+ return;
+ }
+
+ // Don't let the same event be fired twice when hitting
+ // enter/return for Bug 420502. However, Korean IME (or some other
+ // simple IME) may work without marked text. For example, composing
+ // character may be inserted as committed text and it's modified with
+ // aReplacementRange. When a keydown starts new composition with
+ // committing previous character, InsertText() may be called twice,
+ // one is for committing previous character and then, inserting new
+ // composing character as committed character. In the latter case,
+ // |CanDispatchKeyPressEvent()| returns true but we need to dispatch
+ // keypress event for the new character. So, when IME tries to insert
+ // printable characters, we should ignore current key event state even
+ // after the keydown has already caused dispatching composition event.
+ // XXX Anyway, we should sort out around this at fixing bug 1338460.
+ if (currentKeyEvent && !currentKeyEvent->CanDispatchKeyPressEvent() &&
+ (str.IsEmpty() || (str.Length() == 1 && !IsPrintableChar(str[0])))) {
+ return;
+ }
+
+ // This is the normal path to input a character when you press a key.
+ // Let's dispatch eKeyDown event now.
+ RefPtr<TextInputHandler> kungFuDeathGrip(this);
+ if (!MaybeDispatchCurrentKeydownEvent(false)) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertText, eKeyDown caused focus move or "
+ "something and canceling the composition",
+ this));
+ return;
+ }
+
+ // XXX Shouldn't we hold mDispatcher instead of mWidget?
+ RefPtr<nsChildView> widget(mWidget);
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::InsertText, "
+ "FAILED, due to BeginNativeInputTransaction() failure",
+ this));
+ return;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertText, "
+ "maybe dispatches eKeyPress event without control, alt and meta modifiers",
+ this));
+
+ // Dispatch keypress event with char instead of compositionchange event
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
+ // XXX Why do we need to dispatch keypress event for not inputting any
+ // string? If it wants to delete the specified range, should we
+ // dispatch an eContentCommandDelete event instead? Because this
+ // must not be caused by a key operation, a part of IME's processing.
+
+ // Don't set other modifiers from the current event, because here in
+ // -insertText: they've already been taken into account in creating
+ // the input string.
+
+ if (currentKeyEvent) {
+ currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
+ } else {
+ nsCocoaUtils::InitInputEvent(keypressEvent, static_cast<NSEvent*>(nullptr));
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ keypressEvent.mKeyValue = str;
+ // FYI: TextEventDispatcher will set mKeyCode to 0 for printable key's
+ // keypress events even if they don't cause inputting non-empty string.
+ }
+
+ // TODO:
+ // If mCurrentKeyEvent.mKeyEvent is null, the text should be inputted as
+ // composition events.
+ nsEventStatus status = nsEventStatus_eIgnore;
+ bool keyPressDispatched =
+ mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, currentKeyEvent);
+ bool keyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
+
+ // Note: mWidget might have become null here. Don't count on it from here on.
+
+ if (currentKeyEvent) {
+ currentKeyEvent->mKeyPressHandled = keyPressHandled;
+ currentKeyEvent->mKeyPressDispatched = keyPressDispatched;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+bool TextInputHandler::HandleCommand(Command aCommand) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (Destroyed()) {
+ return false;
+ }
+
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleCommand, "
+ "aCommand=%s, IsIMEComposing()=%s, "
+ "keyevent=%p, keydownHandled=%s, keypressDispatched=%s, "
+ "causedOtherKeyEvents=%s, compositionDispatched=%s",
+ this, ToChar(aCommand), TrueOrFalse(IsIMEComposing()),
+ currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
+
+ // The command shouldn't be handled, let's ignore it.
+ if (currentKeyEvent && !currentKeyEvent->CanHandleCommand()) {
+ return false;
+ }
+
+ // When current keydown event causes this command, let's dispatch
+ // eKeyDown event before any other events. Note that if we're in a
+ // composition, we've already dispatched eKeyDown event from
+ // TextInputHandler::HandleKeyDownEvent().
+ RefPtr<TextInputHandler> kungFuDeathGrip(this);
+ if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, eKeyDown caused focus move or "
+ "something and canceling the composition",
+ this));
+ return false;
+ }
+
+ // If it's in composition, we cannot dispatch keypress event.
+ // Therefore, we should use different approach or give up to handle
+ // the command.
+ if (IsIMEComposing()) {
+ switch (aCommand) {
+ case Command::InsertLineBreak:
+ case Command::InsertParagraph: {
+ // Insert '\n' as committing composition.
+ // Otherwise, we need to dispatch keypress event because HTMLEditor
+ // doesn't treat "\n" in composition string as a line break unless
+ // the whitespace is treated as pre (see bug 1350541). In strictly
+ // speaking, we should dispatch keypress event as-is if it's handling
+ // NSEventTypeKeyDown event or should insert it with committing composition.
+ NSAttributedString* lineBreaker = [[NSAttributedString alloc] initWithString:@"\n"];
+ InsertTextAsCommittingComposition(lineBreaker, nullptr);
+ if (currentKeyEvent) {
+ currentKeyEvent->mCompositionDispatched = true;
+ }
+ [lineBreaker release];
+ return true;
+ }
+ case Command::DeleteCharBackward:
+ case Command::DeleteCharForward:
+ case Command::DeleteToBeginningOfLine:
+ case Command::DeleteWordBackward:
+ case Command::DeleteWordForward:
+ // Don't remove any contents during composition.
+ return false;
+ case Command::InsertTab:
+ case Command::InsertBacktab:
+ // Don't move focus during composition.
+ return false;
+ case Command::CharNext:
+ case Command::SelectCharNext:
+ case Command::WordNext:
+ case Command::SelectWordNext:
+ case Command::EndLine:
+ case Command::SelectEndLine:
+ case Command::CharPrevious:
+ case Command::SelectCharPrevious:
+ case Command::WordPrevious:
+ case Command::SelectWordPrevious:
+ case Command::BeginLine:
+ case Command::SelectBeginLine:
+ case Command::LinePrevious:
+ case Command::SelectLinePrevious:
+ case Command::MoveTop:
+ case Command::LineNext:
+ case Command::SelectLineNext:
+ case Command::MoveBottom:
+ case Command::SelectBottom:
+ case Command::SelectPageUp:
+ case Command::SelectPageDown:
+ case Command::ScrollBottom:
+ case Command::ScrollTop:
+ // Don't move selection during composition.
+ return false;
+ case Command::CancelOperation:
+ case Command::Complete:
+ // Don't handle Escape key by ourselves during composition.
+ return false;
+ case Command::ScrollPageUp:
+ case Command::ScrollPageDown:
+ // Allow to scroll.
+ break;
+ default:
+ break;
+ }
+ }
+
+ RefPtr<nsChildView> widget(mWidget);
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p, IMEInputHandler::HandleCommand, "
+ "FAILED, due to BeginNativeInputTransaction() failure",
+ this));
+ return false;
+ }
+
+ // TODO: If it's not appropriate keypress but user customized the OS
+ // settings to do the command with other key, we should just set
+ // command to the keypress event and it should be handled as
+ // the key press in editor.
+
+ // If it's handling actual key event and hasn't cause any composition
+ // events nor other key events, we should expose actual modifier state.
+ // Otherwise, we should adjust Control, Option and Command state since
+ // editor may behave differently if some of them are active.
+ bool dispatchFakeKeyPress = !(currentKeyEvent && currentKeyEvent->IsProperKeyEvent(aCommand));
+
+ WidgetKeyboardEvent keydownEvent(true, eKeyDown, widget);
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
+ if (!dispatchFakeKeyPress) {
+ // If we're acutally handling a key press, we should dispatch
+ // the keypress event as-is.
+ currentKeyEvent->InitKeyEvent(this, keydownEvent, false);
+ currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
+ } else {
+ // Otherwise, we should dispatch "fake" keypress event.
+ // However, for making it possible to compute edit commands, we need to
+ // set current native key event to the fake keyboard event even if it's
+ // not same as what we expect since the native keyboard event caused
+ // this command.
+ NSEvent* keyEvent = currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr;
+ keydownEvent.mNativeKeyEvent = keypressEvent.mNativeKeyEvent = keyEvent;
+ NS_WARNING_ASSERTION(keypressEvent.mNativeKeyEvent,
+ "Without native key event, NativeKeyBindings cannot compute aCommand");
+ switch (aCommand) {
+ case Command::InsertLineBreak:
+ case Command::InsertParagraph: {
+ // Although, Shift+Enter and Enter are work differently in HTML
+ // editor, we should expose actual Shift state if it's caused by
+ // Enter key for compatibility with Chromium. Chromium breaks
+ // line in HTML editor with default pargraph separator when Enter
+ // is pressed, with <br> element when Shift+Enter. Safari breaks
+ // line in HTML editor with default paragraph separator when
+ // Enter, Shift+Enter or Option+Enter. So, we should not change
+ // Shift+Enter meaning when there was composition string or not.
+ nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+ keypressEvent.mKeyCode = NS_VK_RETURN;
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Enter;
+ keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
+ if (aCommand == Command::InsertLineBreak) {
+ // In default settings, Ctrl + Enter causes insertLineBreak command.
+ // So, let's make Ctrl state active of the keypress event.
+ keypressEvent.mModifiers |= MODIFIER_CONTROL;
+ }
+ break;
+ }
+ case Command::InsertTab:
+ case Command::InsertBacktab:
+ nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+ keypressEvent.mKeyCode = NS_VK_TAB;
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Tab;
+ keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
+ if (aCommand == Command::InsertBacktab) {
+ keypressEvent.mModifiers |= MODIFIER_SHIFT;
+ }
+ break;
+ case Command::DeleteCharBackward:
+ case Command::DeleteToBeginningOfLine:
+ case Command::DeleteWordBackward: {
+ nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+ keypressEvent.mKeyCode = NS_VK_BACK;
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Backspace;
+ keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
+ if (aCommand == Command::DeleteToBeginningOfLine) {
+ keypressEvent.mModifiers |= MODIFIER_META;
+ } else if (aCommand == Command::DeleteWordBackward) {
+ keypressEvent.mModifiers |= MODIFIER_ALT;
+ }
+ break;
+ }
+ case Command::DeleteCharForward:
+ case Command::DeleteWordForward: {
+ nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+ keypressEvent.mKeyCode = NS_VK_DELETE;
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Delete;
+ keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
+ if (aCommand == Command::DeleteWordForward) {
+ keypressEvent.mModifiers |= MODIFIER_ALT;
+ }
+ break;
+ }
+ case Command::CharNext:
+ case Command::SelectCharNext:
+ case Command::WordNext:
+ case Command::SelectWordNext:
+ case Command::EndLine:
+ case Command::SelectEndLine: {
+ nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+ keypressEvent.mKeyCode = NS_VK_RIGHT;
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowRight;
+ keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
+ if (aCommand == Command::SelectCharNext || aCommand == Command::SelectWordNext ||
+ aCommand == Command::SelectEndLine) {
+ keypressEvent.mModifiers |= MODIFIER_SHIFT;
+ }
+ if (aCommand == Command::WordNext || aCommand == Command::SelectWordNext) {
+ keypressEvent.mModifiers |= MODIFIER_ALT;
+ }
+ if (aCommand == Command::EndLine || aCommand == Command::SelectEndLine) {
+ keypressEvent.mModifiers |= MODIFIER_META;
+ }
+ break;
+ }
+ case Command::CharPrevious:
+ case Command::SelectCharPrevious:
+ case Command::WordPrevious:
+ case Command::SelectWordPrevious:
+ case Command::BeginLine:
+ case Command::SelectBeginLine: {
+ nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+ keypressEvent.mKeyCode = NS_VK_LEFT;
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowLeft;
+ keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
+ if (aCommand == Command::SelectCharPrevious || aCommand == Command::SelectWordPrevious ||
+ aCommand == Command::SelectBeginLine) {
+ keypressEvent.mModifiers |= MODIFIER_SHIFT;
+ }
+ if (aCommand == Command::WordPrevious || aCommand == Command::SelectWordPrevious) {
+ keypressEvent.mModifiers |= MODIFIER_ALT;
+ }
+ if (aCommand == Command::BeginLine || aCommand == Command::SelectBeginLine) {
+ keypressEvent.mModifiers |= MODIFIER_META;
+ }
+ break;
+ }
+ case Command::LinePrevious:
+ case Command::SelectLinePrevious:
+ case Command::MoveTop:
+ case Command::SelectTop: {
+ nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+ keypressEvent.mKeyCode = NS_VK_UP;
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowUp;
+ keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
+ if (aCommand == Command::SelectLinePrevious || aCommand == Command::SelectTop) {
+ keypressEvent.mModifiers |= MODIFIER_SHIFT;
+ }
+ if (aCommand == Command::MoveTop || aCommand == Command::SelectTop) {
+ keypressEvent.mModifiers |= MODIFIER_META;
+ }
+ break;
+ }
+ case Command::LineNext:
+ case Command::SelectLineNext:
+ case Command::MoveBottom:
+ case Command::SelectBottom: {
+ nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+ keypressEvent.mKeyCode = NS_VK_DOWN;
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowDown;
+ keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
+ if (aCommand == Command::SelectLineNext || aCommand == Command::SelectBottom) {
+ keypressEvent.mModifiers |= MODIFIER_SHIFT;
+ }
+ if (aCommand == Command::MoveBottom || aCommand == Command::SelectBottom) {
+ keypressEvent.mModifiers |= MODIFIER_META;
+ }
+ break;
+ }
+ case Command::ScrollPageUp:
+ case Command::SelectPageUp: {
+ nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+ keypressEvent.mKeyCode = NS_VK_PAGE_UP;
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_PageUp;
+ keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
+ if (aCommand == Command::SelectPageUp) {
+ keypressEvent.mModifiers |= MODIFIER_SHIFT;
+ }
+ break;
+ }
+ case Command::ScrollPageDown:
+ case Command::SelectPageDown: {
+ nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+ keypressEvent.mKeyCode = NS_VK_PAGE_DOWN;
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_PageDown;
+ keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
+ if (aCommand == Command::SelectPageDown) {
+ keypressEvent.mModifiers |= MODIFIER_SHIFT;
+ }
+ break;
+ }
+ case Command::ScrollBottom:
+ case Command::ScrollTop: {
+ nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+ if (aCommand == Command::ScrollBottom) {
+ keypressEvent.mKeyCode = NS_VK_END;
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_End;
+ } else {
+ keypressEvent.mKeyCode = NS_VK_HOME;
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Home;
+ }
+ keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
+ break;
+ }
+ case Command::CancelOperation:
+ case Command::Complete: {
+ nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+ keypressEvent.mKeyCode = NS_VK_ESCAPE;
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Escape;
+ keypressEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
+ if (aCommand == Command::Complete) {
+ keypressEvent.mModifiers |= MODIFIER_ALT;
+ }
+ break;
+ }
+ default:
+ return false;
+ }
+
+ nsCocoaUtils::InitInputEvent(keydownEvent, keyEvent);
+ keydownEvent.mKeyCode = keypressEvent.mKeyCode;
+ keydownEvent.mKeyNameIndex = keypressEvent.mKeyNameIndex;
+ keydownEvent.mModifiers = keypressEvent.mModifiers;
+ }
+
+ // We've stopped dispatching "keypress" events of non-printable keys on
+ // the web. Therefore, we need to dispatch eKeyDown event here for web
+ // apps. This is non-standard behavior if we've already dispatched a
+ // "keydown" event. However, Chrome also dispatches such fake "keydown"
+ // (and "keypress") event for making same behavior as Safari.
+ nsEventStatus status = nsEventStatus_eIgnore;
+ if (mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status, nullptr)) {
+ bool keydownHandled = status == nsEventStatus_eConsumeNoDefault;
+ if (currentKeyEvent) {
+ currentKeyEvent->mKeyDownDispatched = true;
+ currentKeyEvent->mKeyDownHandled |= keydownHandled;
+ }
+ if (keydownHandled) {
+ // Don't dispatch eKeyPress event if preceding eKeyDown event is
+ // consumed for conforming to UI Events.
+ // XXX Perhaps, we should ignore previous eKeyDown event result
+ // even if we've already dispatched because it may notify web apps
+ // of different key information, e.g., it's handled by IME, but
+ // web apps want to handle only this key.
+ return true;
+ }
+ }
+
+ bool keyPressDispatched =
+ mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, currentKeyEvent);
+ bool keyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
+
+ // NOTE: mWidget might have become null here.
+
+ if (keyPressDispatched) {
+ // Record the keypress event state only when it dispatched actual Enter
+ // keypress event because in other cases, the keypress event just a
+ // messenger. E.g., if it's caused by different key, keypress event for
+ // the actual key should be dispatched.
+ if (!dispatchFakeKeyPress && currentKeyEvent) {
+ currentKeyEvent->mKeyPressHandled = keyPressHandled;
+ currentKeyEvent->mKeyPressDispatched = keyPressDispatched;
+ }
+ return true;
+ }
+
+ // If keypress event isn't dispatched as expected, we should fallback to
+ // using composition events.
+ if (aCommand == Command::InsertLineBreak || aCommand == Command::InsertParagraph) {
+ NSAttributedString* lineBreaker = [[NSAttributedString alloc] initWithString:@"\n"];
+ InsertTextAsCommittingComposition(lineBreaker, nullptr);
+ if (currentKeyEvent) {
+ currentKeyEvent->mCompositionDispatched = true;
+ }
+ [lineBreaker release];
+ return true;
+ }
+
+ return false;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+bool TextInputHandler::DoCommandBySelector(const char* aSelector) {
+ RefPtr<nsChildView> widget(mWidget);
+
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", "
+ "Destroyed()=%s, keydownDispatched=%s, keydownHandled=%s, "
+ "keypressDispatched=%s, keypressHandled=%s, causedOtherKeyEvents=%s",
+ this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()),
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownDispatched) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
+
+ // If the command isn't caused by key operation, the command should
+ // be handled in the super class of the caller.
+ if (!currentKeyEvent) {
+ return Destroyed();
+ }
+
+ // When current keydown event causes this command, let's dispatch
+ // eKeyDown event before any other events. Note that if we're in a
+ // composition, we've already dispatched eKeyDown event from
+ // TextInputHandler::HandleKeyDownEvent().
+ RefPtr<TextInputHandler> kungFuDeathGrip(this);
+ if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, eKeyDown caused focus move or "
+ "something and canceling the composition",
+ this));
+ return true;
+ }
+
+ // If the key operation causes this command, should dispatch a keypress
+ // event.
+ // XXX This must be worng. Even if this command is caused by the key
+ // operation, its our default action can be different from the
+ // command. So, in this case, we should dispatch a keypress event
+ // which have the command and editor should handle it.
+ if (currentKeyEvent->CanDispatchKeyPressEvent()) {
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DoCommandBySelector, "
+ "FAILED, due to BeginNativeInputTransaction() failure "
+ "at dispatching keypress",
+ this));
+ return Destroyed();
+ }
+
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
+ currentKeyEvent->InitKeyEvent(this, keypressEvent, false);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ currentKeyEvent->mKeyPressDispatched =
+ mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, currentKeyEvent);
+ currentKeyEvent->mKeyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::DoCommandBySelector, keypress event "
+ "dispatched, Destroyed()=%s, keypressHandled=%s",
+ this, TrueOrFalse(Destroyed()), TrueOrFalse(currentKeyEvent->mKeyPressHandled)));
+ // This command is now dispatched with keypress event.
+ // So, this shouldn't be handled by nobody anymore.
+ return true;
+ }
+
+ // If the key operation didn't cause keypress event or caused keypress event
+ // but not prevented its default, we need to honor the command. For example,
+ // Korean IME sends "insertNewline:" when committing existing composition
+ // with Enter key press. In such case, the key operation has been consumed
+ // by the committing composition but we still need to handle the command.
+ if (Destroyed() || !currentKeyEvent->CanHandleCommand()) {
+ return true;
+ }
+
+ // cancelOperation: command is fired after Escape or Command + Period.
+ // However, if ChildView implements cancelOperation:, calling
+ // [[ChildView super] doCommandBySelector:aSelector] when Command + Period
+ // causes only a call of [ChildView cancelOperation:sender]. I.e.,
+ // [ChildView keyDown:theEvent] becomes to be never called. For avoiding
+ // this odd behavior, we need to handle the command before super class of
+ // ChildView only when current key event is proper event to fire Escape
+ // keypress event.
+ if (!strcmp(aSelector, "cancelOperatiorn:") && currentKeyEvent &&
+ currentKeyEvent->IsProperKeyEvent(Command::CancelOperation)) {
+ return HandleCommand(Command::CancelOperation);
+ }
+
+ // Otherwise, we've not handled the command yet. Propagate the command
+ // to the super class of ChildView.
+ return false;
+}
+
+#pragma mark -
+
+/******************************************************************************
+ *
+ * IMEInputHandler implementation (static methods)
+ *
+ ******************************************************************************/
+
+bool IMEInputHandler::sStaticMembersInitialized = false;
+bool IMEInputHandler::sCachedIsForRTLLangage = false;
+CFStringRef IMEInputHandler::sLatestIMEOpenedModeInputSourceID = nullptr;
+IMEInputHandler* IMEInputHandler::sFocusedIMEHandler = nullptr;
+
+// static
+void IMEInputHandler::InitStaticMembers() {
+ if (sStaticMembersInitialized) return;
+ sStaticMembersInitialized = true;
+ // We need to check the keyboard layout changes on all applications.
+ CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
+ // XXX Don't we need to remove the observer at shut down?
+ // Mac Dev Center's document doesn't say how to remove the observer if
+ // the second parameter is NULL.
+ ::CFNotificationCenterAddObserver(center, NULL, OnCurrentTextInputSourceChange,
+ kTISNotifySelectedKeyboardInputSourceChanged, NULL,
+ CFNotificationSuspensionBehaviorDeliverImmediately);
+ // Initiailize with the current keyboard layout
+ OnCurrentTextInputSourceChange(NULL, NULL, kTISNotifySelectedKeyboardInputSourceChanged, NULL,
+ NULL);
+}
+
+// static
+void IMEInputHandler::OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
+ void* aObserver, CFStringRef aName,
+ const void* aObject,
+ CFDictionaryRef aUserInfo) {
+ // Cache the latest IME opened mode to sLatestIMEOpenedModeInputSourceID.
+ TISInputSourceWrapper tis;
+ tis.InitByCurrentInputSource();
+ if (tis.IsOpenedIMEMode()) {
+ tis.GetInputSourceID(sLatestIMEOpenedModeInputSourceID);
+ // Collect Input Source ID which includes input mode in most cases.
+ // However, if it's Japanese IME, collecting input mode (e.g.,
+ // "HiraganaKotei") does not make sense because in most languages,
+ // input mode changes "how to input", but Japanese IME changes
+ // "which type of characters to input". I.e., only Japanese IME
+ // users may use multiple input modes. If we'd collect each type of
+ // input mode of Japanese IMEs, it'd be difficult to count actual
+ // users of each IME from the result. So, only when active IME is
+ // a Japanese IME, we should use Bundle ID which does not contain
+ // input mode instead.
+ nsAutoString key;
+ if (tis.IsForJapaneseLanguage()) {
+ tis.GetBundleID(key);
+ } else {
+ tis.GetInputSourceID(key);
+ }
+ // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
+ if (key.Length() > 72) {
+ if (NS_IS_SURROGATE_PAIR(key[72 - 2], key[72 - 1])) {
+ key.Truncate(72 - 2);
+ } else {
+ key.Truncate(72 - 1);
+ }
+ // U+2026 is "..."
+ key.Append(char16_t(0x2026));
+ }
+ Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_MAC, key, true);
+ }
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ static CFStringRef sLastTIS = nullptr;
+ CFStringRef newTIS;
+ tis.GetInputSourceID(newTIS);
+ if (!sLastTIS || ::CFStringCompare(sLastTIS, newTIS, 0) != kCFCompareEqualTo) {
+ TISInputSourceWrapper tis1, tis2, tis3, tis4, tis5;
+ tis1.InitByCurrentKeyboardLayout();
+ tis2.InitByCurrentASCIICapableInputSource();
+ tis3.InitByCurrentASCIICapableKeyboardLayout();
+ tis4.InitByCurrentInputMethodKeyboardLayoutOverride();
+ tis5.InitByTISInputSourceRef(tis.GetKeyboardLayoutInputSource());
+ CFStringRef is0 = nullptr, is1 = nullptr, is2 = nullptr, is3 = nullptr, is4 = nullptr,
+ is5 = nullptr, type0 = nullptr, lang0 = nullptr, bundleID0 = nullptr;
+ tis.GetInputSourceID(is0);
+ tis1.GetInputSourceID(is1);
+ tis2.GetInputSourceID(is2);
+ tis3.GetInputSourceID(is3);
+ tis4.GetInputSourceID(is4);
+ tis5.GetInputSourceID(is5);
+ tis.GetInputSourceType(type0);
+ tis.GetPrimaryLanguage(lang0);
+ tis.GetBundleID(bundleID0);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("IMEInputHandler::OnCurrentTextInputSourceChange,\n"
+ " Current Input Source is changed to:\n"
+ " currentInputContext=%p\n"
+ " %s\n"
+ " type=%s %s\n"
+ " overridden keyboard layout=%s\n"
+ " used keyboard layout for translation=%s\n"
+ " primary language=%s\n"
+ " bundle ID=%s\n"
+ " current ASCII capable Input Source=%s\n"
+ " current Keyboard Layout=%s\n"
+ " current ASCII capable Keyboard Layout=%s",
+ [NSTextInputContext currentInputContext], GetCharacters(is0), GetCharacters(type0),
+ tis.IsASCIICapable() ? "- ASCII capable " : "", GetCharacters(is4),
+ GetCharacters(is5), GetCharacters(lang0), GetCharacters(bundleID0),
+ GetCharacters(is2), GetCharacters(is1), GetCharacters(is3)));
+ }
+ sLastTIS = newTIS;
+ }
+
+ /**
+ * When the direction is changed, all the children are notified.
+ * No need to treat the initial case separately because it is covered
+ * by the general case (sCachedIsForRTLLangage is initially false)
+ */
+ if (sCachedIsForRTLLangage != tis.IsForRTLLanguage()) {
+ WidgetUtils::SendBidiKeyboardInfoToContent();
+ sCachedIsForRTLLangage = tis.IsForRTLLanguage();
+ }
+}
+
+// static
+void IMEInputHandler::FlushPendingMethods(nsITimer* aTimer, void* aClosure) {
+ NS_ASSERTION(aClosure, "aClosure is null");
+ static_cast<IMEInputHandler*>(aClosure)->ExecutePendingMethods();
+}
+
+// static
+CFArrayRef IMEInputHandler::CreateAllIMEModeList() {
+ const void* keys[] = {kTISPropertyInputSourceType};
+ const void* values[] = {kTISTypeKeyboardInputMode};
+ CFDictionaryRef filter = ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
+ NS_ASSERTION(filter, "failed to create the filter");
+ CFArrayRef list = ::TISCreateInputSourceList(filter, true);
+ ::CFRelease(filter);
+ return list;
+}
+
+// static
+void IMEInputHandler::DebugPrintAllIMEModes() {
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ CFArrayRef list = CreateAllIMEModeList();
+ MOZ_LOG(gLog, LogLevel::Info, ("IME mode configuration:"));
+ CFIndex idx = ::CFArrayGetCount(list);
+ TISInputSourceWrapper tis;
+ for (CFIndex i = 0; i < idx; ++i) {
+ TISInputSourceRef inputSource =
+ static_cast<TISInputSourceRef>(const_cast<void*>(::CFArrayGetValueAtIndex(list, i)));
+ tis.InitByTISInputSourceRef(inputSource);
+ nsAutoString name, isid, bundleID;
+ tis.GetLocalizedName(name);
+ tis.GetInputSourceID(isid);
+ tis.GetBundleID(bundleID);
+ MOZ_LOG(gLog, LogLevel::Info,
+ (" %s\t<%s>%s%s\n"
+ " bundled in <%s>\n",
+ NS_ConvertUTF16toUTF8(name).get(), NS_ConvertUTF16toUTF8(isid).get(),
+ tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
+ tis.IsEnabled() ? "" : "\t(Isn't Enabled)", NS_ConvertUTF16toUTF8(bundleID).get()));
+ }
+ ::CFRelease(list);
+ }
+}
+
+// static
+TSMDocumentID IMEInputHandler::GetCurrentTSMDocumentID() {
+ // At least on Mac OS X 10.6.x and 10.7.x, ::TSMGetActiveDocument() has a bug.
+ // The result of ::TSMGetActiveDocument() isn't modified for new active text
+ // input context until [NSTextInputContext currentInputContext] is called.
+ // Therefore, we need to call it here.
+ [NSTextInputContext currentInputContext];
+ return ::TSMGetActiveDocument();
+}
+
+#pragma mark -
+
+/******************************************************************************
+ *
+ * IMEInputHandler implementation #1
+ * The methods are releated to the pending methods. Some jobs should be
+ * run after the stack is finished, e.g, some methods cannot run the jobs
+ * during processing the focus event. And also some other jobs should be
+ * run at the next focus event is processed.
+ * The pending methods are recorded in mPendingMethods. They are executed
+ * by ExecutePendingMethods via FlushPendingMethods.
+ *
+ ******************************************************************************/
+
+nsresult IMEInputHandler::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) {
+ switch (aNotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ CommitIMEComposition();
+ return NS_OK;
+ case REQUEST_TO_CANCEL_COMPOSITION:
+ CancelIMEComposition();
+ return NS_OK;
+ case NOTIFY_IME_OF_FOCUS:
+ if (IsFocused()) {
+ nsIWidget* widget = aTextEventDispatcher->GetWidget();
+ if (widget && widget->GetInputContext().IsPasswordEditor()) {
+ EnableSecureEventInput();
+ } else {
+ EnsureSecureEventInputDisabled();
+ }
+ }
+ OnFocusChangeInGecko(true);
+ return NS_OK;
+ case NOTIFY_IME_OF_BLUR:
+ OnFocusChangeInGecko(false);
+ return NS_OK;
+ case NOTIFY_IME_OF_SELECTION_CHANGE:
+ OnSelectionChange(aNotification);
+ return NS_OK;
+ case NOTIFY_IME_OF_POSITION_CHANGE:
+ OnLayoutChange();
+ return NS_OK;
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+}
+
+NS_IMETHODIMP_(IMENotificationRequests)
+IMEInputHandler::GetIMENotificationRequests() {
+ // 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 IMENotificationRequests();
+}
+
+NS_IMETHODIMP_(void)
+IMEInputHandler::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) {
+ // XXX When input transaction is being stolen by add-on, what should we do?
+}
+
+NS_IMETHODIMP_(void)
+IMEInputHandler::WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress, void* aData) {
+ // If the keyboard event is not caused by a native key event, we can do
+ // nothing here.
+ if (!aData) {
+ return;
+ }
+
+ KeyEventState* currentKeyEvent = static_cast<KeyEventState*>(aData);
+ NSEvent* nativeEvent = currentKeyEvent->mKeyEvent;
+ nsAString* insertString = currentKeyEvent->mInsertString;
+ if (aKeyboardEvent.mMessage == eKeyPress && aIndexOfKeypress == 0 &&
+ (!insertString || insertString->IsEmpty())) {
+ // Inform the child process that this is an event that we want a reply
+ // from.
+ // XXX This should be called only when the target is a remote process.
+ // However, it's difficult to check it under widget/.
+ // So, let's do this here for now, then,
+ // EventStateManager::PreHandleEvent() will reset the flags if
+ // the event target isn't in remote process.
+ aKeyboardEvent.MarkAsWaitingReplyFromRemoteProcess();
+ }
+ if (KeyboardLayoutOverrideRef().mOverrideEnabled) {
+ TISInputSourceWrapper tis;
+ tis.InitByLayoutID(KeyboardLayoutOverrideRef().mKeyboardLayout, true);
+ tis.WillDispatchKeyboardEvent(nativeEvent, insertString, aIndexOfKeypress, aKeyboardEvent);
+ } else {
+ TISInputSourceWrapper::CurrentInputSource().WillDispatchKeyboardEvent(
+ nativeEvent, insertString, aIndexOfKeypress, aKeyboardEvent);
+ }
+
+ // Remove basic modifiers from keypress event because if they are included
+ // but this causes inputting text, since TextEditor won't handle eKeyPress
+ // events whose ctrlKey, altKey or metaKey is true as text input.
+ // Note that this hack should be used only when an editor has focus because
+ // this is a hack for TextEditor and modifier key information may be
+ // important for current web app.
+ if (IsEditableContent() && insertString && aKeyboardEvent.mMessage == eKeyPress &&
+ aKeyboardEvent.mCharCode) {
+ aKeyboardEvent.mModifiers &= ~(MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META);
+ }
+}
+
+void IMEInputHandler::NotifyIMEOfFocusChangeInGecko() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::NotifyIMEOfFocusChangeInGecko, "
+ "Destroyed()=%s, IsFocused()=%s, inputContext=%p",
+ this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
+ mView ? [mView inputContext] : nullptr));
+
+ if (Destroyed()) {
+ return;
+ }
+
+ if (!IsFocused()) {
+ // retry at next focus event
+ mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
+ return;
+ }
+
+ MOZ_ASSERT(mView);
+ NSTextInputContext* inputContext = [mView inputContext];
+ NS_ENSURE_TRUE_VOID(inputContext);
+
+ // When an <input> element on a XUL <panel> element gets focus from an <input>
+ // element on the opener window of the <panel> element, the owner window
+ // still has native focus. Therefore, IMEs may store the opener window's
+ // level at this time because they don't know the actual focus is moved to
+ // different window. If IMEs try to get the newest window level after the
+ // focus change, we return the window level of the XUL <panel>'s widget.
+ // Therefore, let's emulate the native focus change. Then, IMEs can refresh
+ // the stored window level.
+ [inputContext deactivate];
+ [inputContext activate];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void IMEInputHandler::SyncASCIICapableOnly() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SyncASCIICapableOnly, "
+ "Destroyed()=%s, IsFocused()=%s, mIsASCIICapableOnly=%s, "
+ "GetCurrentTSMDocumentID()=%p",
+ this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
+ TrueOrFalse(mIsASCIICapableOnly), GetCurrentTSMDocumentID()));
+
+ if (Destroyed()) {
+ return;
+ }
+
+ if (!IsFocused()) {
+ // retry at next focus event
+ mPendingMethods |= kSyncASCIICapableOnly;
+ return;
+ }
+
+ TSMDocumentID doc = GetCurrentTSMDocumentID();
+ if (!doc) {
+ // retry
+ mPendingMethods |= kSyncASCIICapableOnly;
+ NS_WARNING("Application is active but there is no active document");
+ ResetTimer();
+ return;
+ }
+
+ if (mIsASCIICapableOnly) {
+ CFArrayRef ASCIICapableTISList = ::TISCreateASCIICapableInputSourceList();
+ ::TSMSetDocumentProperty(doc, kTSMDocumentEnabledInputSourcesPropertyTag, sizeof(CFArrayRef),
+ &ASCIICapableTISList);
+ ::CFRelease(ASCIICapableTISList);
+ } else {
+ ::TSMRemoveDocumentProperty(doc, kTSMDocumentEnabledInputSourcesPropertyTag);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void IMEInputHandler::ResetTimer() {
+ NS_ASSERTION(mPendingMethods != 0, "There are not pending methods, why this is called?");
+ if (mTimer) {
+ mTimer->Cancel();
+ } else {
+ mTimer = NS_NewTimer();
+ NS_ENSURE_TRUE(mTimer, );
+ }
+ mTimer->InitWithNamedFuncCallback(FlushPendingMethods, this, 0, nsITimer::TYPE_ONE_SHOT,
+ "IMEInputHandler::FlushPendingMethods");
+}
+
+void IMEInputHandler::ExecutePendingMethods() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ if (![[NSApplication sharedApplication] isActive]) {
+ // If we're not active, we should retry at focus event
+ return;
+ }
+
+ uint32_t pendingMethods = mPendingMethods;
+ // First, reset the pending method flags because if each methods cannot
+ // run now, they can reentry to the pending flags by theirselves.
+ mPendingMethods = 0;
+
+ if (pendingMethods & kSyncASCIICapableOnly) SyncASCIICapableOnly();
+ if (pendingMethods & kNotifyIMEOfFocusChangeInGecko) {
+ NotifyIMEOfFocusChangeInGecko();
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#pragma mark -
+
+/******************************************************************************
+ *
+ * IMEInputHandler implementation (native event handlers)
+ *
+ ******************************************************************************/
+
+TextRangeType IMEInputHandler::ConvertToTextRangeType(uint32_t aUnderlineStyle,
+ NSRange& aSelectedRange) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::ConvertToTextRangeType, "
+ "aUnderlineStyle=%u, aSelectedRange.length=%lu,",
+ this, aUnderlineStyle, static_cast<unsigned long>(aSelectedRange.length)));
+
+ // We assume that aUnderlineStyle is NSUnderlineStyleSingle or
+ // NSUnderlineStyleThick. NSUnderlineStyleThick should indicate a selected
+ // clause. Otherwise, should indicate non-selected clause.
+
+ if (aSelectedRange.length == 0) {
+ switch (aUnderlineStyle) {
+ case NSUnderlineStyleSingle:
+ return TextRangeType::eRawClause;
+ case NSUnderlineStyleThick:
+ return TextRangeType::eSelectedRawClause;
+ default:
+ NS_WARNING("Unexpected line style");
+ return TextRangeType::eSelectedRawClause;
+ }
+ }
+
+ switch (aUnderlineStyle) {
+ case NSUnderlineStyleSingle:
+ return TextRangeType::eConvertedClause;
+ case NSUnderlineStyleThick:
+ return TextRangeType::eSelectedClause;
+ default:
+ NS_WARNING("Unexpected line style");
+ return TextRangeType::eSelectedClause;
+ }
+}
+
+uint32_t IMEInputHandler::GetRangeCount(NSAttributedString* aAttrString) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // Iterate through aAttrString for the NSUnderlineStyleAttributeName and
+ // count the different segments adjusting limitRange as we go.
+ uint32_t count = 0;
+ NSRange effectiveRange;
+ NSRange limitRange = NSMakeRange(0, [aAttrString length]);
+ while (limitRange.length > 0) {
+ [aAttrString attribute:NSUnderlineStyleAttributeName
+ atIndex:limitRange.location
+ longestEffectiveRange:&effectiveRange
+ inRange:limitRange];
+ limitRange = NSMakeRange(NSMaxRange(effectiveRange),
+ NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
+ count++;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::GetRangeCount, aAttrString=\"%s\", count=%u", this,
+ GetCharacters([aAttrString string]), count));
+
+ return count;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+already_AddRefed<mozilla::TextRangeArray> IMEInputHandler::CreateTextRangeArray(
+ NSAttributedString* aAttrString, NSRange& aSelectedRange) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ RefPtr<mozilla::TextRangeArray> textRangeArray = new mozilla::TextRangeArray();
+
+ // Note that we shouldn't append ranges when composition string
+ // is empty because it may cause TextComposition confused.
+ if (![aAttrString length]) {
+ return textRangeArray.forget();
+ }
+
+ // Convert the Cocoa range into the TextRange Array used in Gecko.
+ // Iterate through the attributed string and map the underline attribute to
+ // Gecko IME textrange attributes. We may need to change the code here if
+ // we change the implementation of validAttributesForMarkedText.
+ NSRange limitRange = NSMakeRange(0, [aAttrString length]);
+ uint32_t rangeCount = GetRangeCount(aAttrString);
+ for (uint32_t i = 0; i < rangeCount && limitRange.length > 0; i++) {
+ NSRange effectiveRange;
+ id attributeValue = [aAttrString attribute:NSUnderlineStyleAttributeName
+ atIndex:limitRange.location
+ longestEffectiveRange:&effectiveRange
+ inRange:limitRange];
+
+ TextRange range;
+ range.mStartOffset = effectiveRange.location;
+ range.mEndOffset = NSMaxRange(effectiveRange);
+ range.mRangeType = ConvertToTextRangeType([attributeValue intValue], aSelectedRange);
+ textRangeArray->AppendElement(range);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CreateTextRangeArray, "
+ "range={ mStartOffset=%u, mEndOffset=%u, mRangeType=%s }",
+ this, range.mStartOffset, range.mEndOffset, ToChar(range.mRangeType)));
+
+ limitRange = NSMakeRange(NSMaxRange(effectiveRange),
+ NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
+ }
+
+ // Get current caret position.
+ TextRange range;
+ range.mStartOffset = aSelectedRange.location + aSelectedRange.length;
+ range.mEndOffset = range.mStartOffset;
+ range.mRangeType = TextRangeType::eCaret;
+ textRangeArray->AppendElement(range);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CreateTextRangeArray, "
+ "range={ mStartOffset=%u, mEndOffset=%u, mRangeType=%s }",
+ this, range.mStartOffset, range.mEndOffset, ToChar(range.mRangeType)));
+
+ return textRangeArray.forget();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
+
+bool IMEInputHandler::DispatchCompositionStartEvent() {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionStartEvent, "
+ "mSelectedRange={ location=%lu, length=%lu }, Destroyed()=%s, "
+ "mView=%p, mWidget=%p, inputContext=%p, mIsIMEComposing=%s",
+ this, static_cast<unsigned long>(SelectedRange().location),
+ static_cast<unsigned long>(mSelectedRange.length), TrueOrFalse(Destroyed()), mView,
+ mWidget, mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionStartEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure",
+ this));
+ return false;
+ }
+
+ NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
+ mIsIMEComposing = true;
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+ mIsDeadKeyComposing =
+ currentKeyEvent && currentKeyEvent->mKeyEvent &&
+ TISInputSourceWrapper::CurrentInputSource().IsDeadKey(currentKeyEvent->mKeyEvent);
+
+ nsEventStatus status;
+ rv = mDispatcher->StartComposition(status);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionStartEvent, "
+ "FAILED, due to StartComposition() failure",
+ this));
+ return false;
+ }
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionStartEvent, "
+ "destroyed by compositionstart event",
+ this));
+ return false;
+ }
+
+ // FYI: compositionstart may cause committing composition by the webapp.
+ if (!mIsIMEComposing) {
+ return false;
+ }
+
+ // FYI: The selection range might have been modified by a compositionstart
+ // event handler.
+ mIMECompositionStart = SelectedRange().location;
+ return true;
+}
+
+bool IMEInputHandler::DispatchCompositionChangeEvent(const nsString& aText,
+ NSAttributedString* aAttrString,
+ NSRange& aSelectedRange) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "aText=\"%s\", aAttrString=\"%s\", "
+ "aSelectedRange={ location=%lu, length=%lu }, Destroyed()=%s, mView=%p, "
+ "mWidget=%p, inputContext=%p, mIsIMEComposing=%s",
+ this, NS_ConvertUTF16toUTF8(aText).get(), GetCharacters([aAttrString string]),
+ static_cast<unsigned long>(aSelectedRange.location),
+ static_cast<unsigned long>(aSelectedRange.length), TrueOrFalse(Destroyed()), mView,
+ mWidget, mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
+
+ NS_ENSURE_TRUE(!Destroyed(), false);
+
+ NS_ASSERTION(mIsIMEComposing, "We're not in composition");
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure",
+ this));
+ return false;
+ }
+
+ RefPtr<TextRangeArray> rangeArray = CreateTextRangeArray(aAttrString, aSelectedRange);
+
+ rv = mDispatcher->SetPendingComposition(aText, rangeArray);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "FAILED, due to SetPendingComposition() failure",
+ this));
+ return false;
+ }
+
+ mSelectedRange.location = mIMECompositionStart + aSelectedRange.location;
+ mSelectedRange.length = aSelectedRange.length;
+
+ if (mIMECompositionString) {
+ [mIMECompositionString release];
+ }
+ mIMECompositionString = [[aAttrString string] retain];
+
+ nsEventStatus status;
+ rv = mDispatcher->FlushPendingComposition(status);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "FAILED, due to FlushPendingComposition() failure",
+ this));
+ return false;
+ }
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "destroyed by compositionchange event",
+ this));
+ return false;
+ }
+
+ // FYI: compositionstart may cause committing composition by the webapp.
+ return mIsIMEComposing;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+bool IMEInputHandler::DispatchCompositionCommitEvent(const nsAString* aCommitString) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
+ "aCommitString=0x%p (\"%s\"), Destroyed()=%s, mView=%p, mWidget=%p, "
+ "inputContext=%p, mIsIMEComposing=%s",
+ this, aCommitString, aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : "",
+ TrueOrFalse(Destroyed()), mView, mWidget, mView ? [mView inputContext] : nullptr,
+ TrueOrFalse(mIsIMEComposing)));
+
+ NS_ASSERTION(mIsIMEComposing, "We're not in composition");
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ if (!Destroyed()) {
+ // IME may query selection immediately after this, however, in e10s mode,
+ // OnSelectionChange() will be called asynchronously. Until then, we
+ // should emulate expected selection range if the webapp does nothing.
+ mSelectedRange.location = mIMECompositionStart;
+ if (aCommitString) {
+ mSelectedRange.location += aCommitString->Length();
+ } else if (mIMECompositionString) {
+ nsAutoString commitString;
+ nsCocoaUtils::GetStringForNSString(mIMECompositionString, commitString);
+ mSelectedRange.location += commitString.Length();
+ }
+ mSelectedRange.length = 0;
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure",
+ this));
+ } else {
+ nsEventStatus status;
+ rv = mDispatcher->CommitComposition(status, aCommitString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure",
+ this));
+ }
+ }
+ }
+
+ mIsIMEComposing = mIsDeadKeyComposing = false;
+ mIMECompositionStart = UINT32_MAX;
+ if (mIMECompositionString) {
+ [mIMECompositionString release];
+ mIMECompositionString = nullptr;
+ }
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
+ "destroyed by compositioncommit event",
+ this));
+ return false;
+ }
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+bool IMEInputHandler::MaybeDispatchCurrentKeydownEvent(bool aIsProcessedByIME) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (Destroyed()) {
+ return false;
+ }
+ MOZ_ASSERT(mWidget);
+
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+ if (!currentKeyEvent || !currentKeyEvent->CanDispatchKeyDownEvent()) {
+ return true;
+ }
+
+ NSEvent* nativeEvent = currentKeyEvent->mKeyEvent;
+ if (NS_WARN_IF(!nativeEvent) || [nativeEvent type] != NSEventTypeKeyDown) {
+ return true;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::MaybeDispatchKeydownEvent, aIsProcessedByIME=%s "
+ "currentKeyEvent={ mKeyEvent(%p)={ type=%s, keyCode=%s (0x%X) } }, "
+ "aIsProcessedBy=%s, IsDeadKeyComposing()=%s",
+ this, TrueOrFalse(aIsProcessedByIME), nativeEvent, GetNativeKeyEventType(nativeEvent),
+ GetKeyNameForNativeKeyCode([nativeEvent keyCode]), [nativeEvent keyCode],
+ TrueOrFalse(IsIMEComposing()), TrueOrFalse(IsDeadKeyComposing())));
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+ RefPtr<TextEventDispatcher> dispatcher(mDispatcher);
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchKeyEventForFlagsChanged, "
+ "FAILED, due to BeginNativeInputTransaction() failure",
+ this));
+ return false;
+ }
+
+ NSResponder* firstResponder = [[mView window] firstResponder];
+
+ // Mark currentKeyEvent as "dispatched eKeyDown event" and actually do it.
+ currentKeyEvent->mKeyDownDispatched = true;
+
+ RefPtr<nsChildView> widget(mWidget);
+
+ WidgetKeyboardEvent keydownEvent(true, eKeyDown, widget);
+ // Don't mark the eKeyDown event as "processed by IME" if the composition
+ // is started with dead key.
+ currentKeyEvent->InitKeyEvent(this, keydownEvent, aIsProcessedByIME && !IsDeadKeyComposing());
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ dispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status, currentKeyEvent);
+ currentKeyEvent->mKeyDownHandled = (status == nsEventStatus_eConsumeNoDefault);
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::MaybeDispatchKeydownEvent, "
+ "widget was destroyed by keydown event",
+ this));
+ return false;
+ }
+
+ // The key down event may have shifted the focus, in which case, we should
+ // not continue to handle current key sequence and let's commit current
+ // composition.
+ if (firstResponder != [[mView window] firstResponder]) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::MaybeDispatchKeydownEvent, "
+ "view lost focus by keydown event",
+ this));
+ CommitIMEComposition();
+ return false;
+ }
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+void IMEInputHandler::InsertTextAsCommittingComposition(NSAttributedString* aAttrString,
+ NSRange* aReplacementRange) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
+ "aAttrString=\"%s\", aReplacementRange=%p { location=%lu, length=%lu }, "
+ "Destroyed()=%s, IsIMEComposing()=%s, "
+ "mMarkedRange={ location=%lu, length=%lu }",
+ this, GetCharacters([aAttrString string]), aReplacementRange,
+ static_cast<unsigned long>(aReplacementRange ? aReplacementRange->location : 0),
+ static_cast<unsigned long>(aReplacementRange ? aReplacementRange->length : 0),
+ TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
+ static_cast<unsigned long>(mMarkedRange.location),
+ static_cast<unsigned long>(mMarkedRange.length)));
+
+ if (IgnoreIMECommit()) {
+ MOZ_CRASH("IMEInputHandler::InsertTextAsCommittingComposition() must not"
+ "be called while canceling the composition");
+ }
+
+ if (Destroyed()) {
+ return;
+ }
+
+ // When current keydown event causes this text input, let's dispatch
+ // eKeyDown event before any other events. Note that if we're in a
+ // composition, we've already dispatched eKeyDown event from
+ // TextInputHandler::HandleKeyDownEvent().
+ // XXX Should we mark the eKeyDown event as "processed by IME"?
+ // However, if the key causes two or more Unicode characters as
+ // UTF-16 string, this is used. So, perhaps, we need to improve
+ // HandleKeyDownEvent() before do that.
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+ if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(false)) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertTextAsCommittingComposition, eKeyDown "
+ "caused focus move or something and canceling the composition",
+ this));
+ return;
+ }
+
+ // First, commit current composition with the latest composition string if the
+ // replacement range is different from marked range.
+ if (IsIMEComposing() && aReplacementRange && aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
+ if (!DispatchCompositionCommitEvent()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
+ "destroyed by commiting composition for setting replacement range",
+ this));
+ return;
+ }
+ }
+
+ nsString str;
+ nsCocoaUtils::GetStringForNSString([aAttrString string], str);
+
+ if (!IsIMEComposing()) {
+ // If there is no selection and replacement range is specified, set the
+ // range as selection.
+ if (aReplacementRange && aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
+ NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
+ }
+
+ if (!DispatchCompositionStartEvent()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
+ "cannot continue handling composition after compositionstart",
+ this));
+ return;
+ }
+ }
+
+ if (!DispatchCompositionCommitEvent(&str)) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
+ "destroyed by compositioncommit event",
+ this));
+ return;
+ }
+
+ mMarkedRange = NSMakeRange(NSNotFound, 0);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString, NSRange& aSelectedRange,
+ NSRange* aReplacementRange) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, "
+ "aAttrString=\"%s\", aSelectedRange={ location=%lu, length=%lu }, "
+ "aReplacementRange=%p { location=%lu, length=%lu }, "
+ "Destroyed()=%s, IsIMEComposing()=%s, "
+ "mMarkedRange={ location=%lu, length=%lu }, keyevent=%p, "
+ "keydownDispatched=%s, keydownHandled=%s, "
+ "keypressDispatched=%s, causedOtherKeyEvents=%s, "
+ "compositionDispatched=%s",
+ this, GetCharacters([aAttrString string]),
+ static_cast<unsigned long>(aSelectedRange.location),
+ static_cast<unsigned long>(aSelectedRange.length), aReplacementRange,
+ static_cast<unsigned long>(aReplacementRange ? aReplacementRange->location : 0),
+ static_cast<unsigned long>(aReplacementRange ? aReplacementRange->length : 0),
+ TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
+ static_cast<unsigned long>(mMarkedRange.location),
+ static_cast<unsigned long>(mMarkedRange.length),
+ currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownDispatched) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
+ currentKeyEvent ? TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ // If SetMarkedText() is called during handling a key press, that means that
+ // the key event caused this composition. So, keypress event shouldn't
+ // be dispatched later, let's mark the key event causing composition event.
+ if (currentKeyEvent) {
+ currentKeyEvent->mCompositionDispatched = true;
+
+ // When current keydown event causes this text input, let's dispatch
+ // eKeyDown event before any other events. Note that if we're in a
+ // composition, we've already dispatched eKeyDown event from
+ // TextInputHandler::HandleKeyDownEvent(). On the other hand, if we're
+ // not in composition, the key event starts new composition. So, we
+ // need to mark the eKeyDown event as "processed by IME".
+ if (!IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(true)) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, eKeyDown caused focus move or "
+ "something and canceling the composition",
+ this));
+ return;
+ }
+ }
+
+ if (Destroyed()) {
+ return;
+ }
+
+ // First, commit current composition with the latest composition string if the
+ // replacement range is different from marked range.
+ if (IsIMEComposing() && aReplacementRange && aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
+ AutoRestore<bool> ignoreIMECommit(mIgnoreIMECommit);
+ mIgnoreIMECommit = false;
+ if (!DispatchCompositionCommitEvent()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, "
+ "destroyed by commiting composition for setting replacement range",
+ this));
+ return;
+ }
+ }
+
+ nsString str;
+ nsCocoaUtils::GetStringForNSString([aAttrString string], str);
+
+ mMarkedRange.length = str.Length();
+
+ if (!IsIMEComposing() && !str.IsEmpty()) {
+ // If there is no selection and replacement range is specified, set the
+ // range as selection.
+ if (aReplacementRange && aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
+ // Set temporary selection range since OnSelectionChange is async.
+ mSelectedRange = *aReplacementRange;
+ if (NS_WARN_IF(!SetSelection(*aReplacementRange))) {
+ mSelectedRange.location = NSNotFound; // Marking dirty
+ return;
+ }
+ }
+
+ mMarkedRange.location = SelectedRange().location;
+
+ if (!DispatchCompositionStartEvent()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, cannot continue handling "
+ "composition after dispatching compositionstart",
+ this));
+ return;
+ }
+ }
+
+ if (!str.IsEmpty()) {
+ if (!DispatchCompositionChangeEvent(str, aAttrString, aSelectedRange)) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, cannot continue handling "
+ "composition after dispatching compositionchange",
+ this));
+ }
+ return;
+ }
+
+ // If the composition string becomes empty string, we should commit
+ // current composition.
+ if (!DispatchCompositionCommitEvent(&EmptyString())) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, "
+ "destroyed by compositioncommit event",
+ this));
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NSAttributedString* IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange,
+ NSRange* aActualRange) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
+ "aRange={ location=%lu, length=%lu }, aActualRange=%p, Destroyed()=%s",
+ this, static_cast<unsigned long>(aRange.location),
+ static_cast<unsigned long>(aRange.length), aActualRange, TrueOrFalse(Destroyed())));
+
+ if (aActualRange) {
+ *aActualRange = NSMakeRange(NSNotFound, 0);
+ }
+
+ if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) {
+ return nil;
+ }
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ // If we're in composing, the queried range may be in the composition string.
+ // In such case, we should use mIMECompositionString since if the composition
+ // string is handled by a remote process, the content cache may be out of
+ // date.
+ // XXX Should we set composition string attributes? Although, Blink claims
+ // that some attributes of marked text are supported, but they return
+ // just marked string without any style. So, let's keep current behavior
+ // at least for now.
+ NSUInteger compositionLength = mIMECompositionString ? [mIMECompositionString length] : 0;
+ if (mIMECompositionStart != UINT32_MAX && mIMECompositionStart >= aRange.location &&
+ mIMECompositionStart + compositionLength <= aRange.location + aRange.length) {
+ NSRange range = NSMakeRange(aRange.location - mIMECompositionStart, aRange.length);
+ NSString* nsstr = [mIMECompositionString substringWithRange:range];
+ NSMutableAttributedString* result =
+ [[[NSMutableAttributedString alloc] initWithString:nsstr attributes:nil] autorelease];
+ // XXX We cannot return font information in this case. However, this
+ // case must occur only when IME tries to confirm if composing string
+ // is handled as expected.
+ if (aActualRange) {
+ *aActualRange = aRange;
+ }
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ nsAutoString str;
+ nsCocoaUtils::GetStringForNSString(nsstr, str);
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
+ "computed with mIMECompositionString (result string=\"%s\")",
+ this, NS_ConvertUTF16toUTF8(str).get()));
+ }
+ return result;
+ }
+
+ nsAutoString str;
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent, mWidget);
+ WidgetQueryContentEvent::Options options;
+ int64_t startOffset = aRange.location;
+ if (IsIMEComposing()) {
+ // The composition may be at different offset from the selection start
+ // offset at dispatching compositionstart because start of composition
+ // is fixed when composition string becomes non-empty in the editor.
+ // Therefore, we need to use query event which is relative to insertion
+ // point.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mIMECompositionStart;
+ }
+ queryTextContentEvent.InitForQueryTextContent(startOffset, aRange.length, options);
+ queryTextContentEvent.RequestFontRanges();
+ DispatchEvent(queryTextContentEvent);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
+ "queryTextContentEvent={ mReply=%s }",
+ this, ToString(queryTextContentEvent.mReply).c_str()));
+
+ if (queryTextContentEvent.Failed()) {
+ return nil;
+ }
+
+ // We don't set vertical information at this point. If required,
+ // OS will calls drawsVerticallyForCharacterAtIndex.
+ NSMutableAttributedString* result = nsCocoaUtils::GetNSMutableAttributedString(
+ queryTextContentEvent.mReply->DataRef(), queryTextContentEvent.mReply->mFontRanges, false,
+ mWidget->BackingScaleFactor());
+ if (aActualRange) {
+ *aActualRange = MakeNSRangeFrom(queryTextContentEvent.mReply->mOffsetAndData);
+ }
+ return result;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+bool IMEInputHandler::HasMarkedText() {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::HasMarkedText, "
+ "mMarkedRange={ location=%lu, length=%lu }",
+ this, static_cast<unsigned long>(mMarkedRange.location),
+ static_cast<unsigned long>(mMarkedRange.length)));
+
+ return (mMarkedRange.location != NSNotFound) && (mMarkedRange.length != 0);
+}
+
+NSRange IMEInputHandler::MarkedRange() {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::MarkedRange, "
+ "mMarkedRange={ location=%lu, length=%lu }",
+ this, static_cast<unsigned long>(mMarkedRange.location),
+ static_cast<unsigned long>(mMarkedRange.length)));
+
+ if (!HasMarkedText()) {
+ return NSMakeRange(NSNotFound, 0);
+ }
+ return mMarkedRange;
+}
+
+NSRange IMEInputHandler::SelectedRange() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SelectedRange, Destroyed()=%s, mSelectedRange={ "
+ "location=%lu, length=%lu }",
+ this, TrueOrFalse(Destroyed()), static_cast<unsigned long>(mSelectedRange.location),
+ static_cast<unsigned long>(mSelectedRange.length)));
+
+ if (Destroyed()) {
+ return mSelectedRange;
+ }
+
+ if (mSelectedRange.location != NSNotFound) {
+ MOZ_ASSERT(mIMEHasFocus);
+ return mSelectedRange;
+ }
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText, mWidget);
+ DispatchEvent(querySelectedTextEvent);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SelectedRange, querySelectedTextEvent={ mReply=%s }", this,
+ ToString(querySelectedTextEvent.mReply).c_str()));
+
+ if (querySelectedTextEvent.Failed()) {
+ return mSelectedRange;
+ }
+
+ mWritingMode = querySelectedTextEvent.mReply->WritingModeRef();
+ mRangeForWritingMode = MakeNSRangeFrom(querySelectedTextEvent.mReply->mOffsetAndData);
+
+ if (mIMEHasFocus) {
+ mSelectedRange = mRangeForWritingMode;
+ }
+
+ return mRangeForWritingMode;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(mSelectedRange);
+}
+
+bool IMEInputHandler::DrawsVerticallyForCharacterAtIndex(uint32_t aCharIndex) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (Destroyed()) {
+ return false;
+ }
+
+ if (mRangeForWritingMode.location == NSNotFound) {
+ // Update cached writing-mode value for the current selection.
+ SelectedRange();
+ }
+
+ if (aCharIndex < mRangeForWritingMode.location ||
+ aCharIndex > mRangeForWritingMode.location + mRangeForWritingMode.length) {
+ // It's not clear to me whether this ever happens in practice, but if an
+ // IME ever wants to query writing mode at an offset outside the current
+ // selection, the writing-mode value may not be correct for the index.
+ // In that case, use FirstRectForCharacterRange to get a fresh value.
+ // This does more work than strictly necessary (we don't need the rect here),
+ // but should be a rare case.
+ NS_WARNING("DrawsVerticallyForCharacterAtIndex not using cached writing mode");
+ NSRange range = NSMakeRange(aCharIndex, 1);
+ NSRange actualRange;
+ FirstRectForCharacterRange(range, &actualRange);
+ }
+
+ return mWritingMode.IsVertical();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+NSRect IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange, NSRange* aActualRange) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s, "
+ "aRange={ location=%lu, length=%lu }, aActualRange=%p }",
+ this, TrueOrFalse(Destroyed()), static_cast<unsigned long>(aRange.location),
+ static_cast<unsigned long>(aRange.length), aActualRange));
+
+ // XXX this returns first character rect or caret rect, it is limitation of
+ // now. We need more work for returns first line rect. But current
+ // implementation is enough for IMEs.
+
+ NSRect rect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
+ NSRange actualRange = NSMakeRange(NSNotFound, 0);
+ if (aActualRange) {
+ *aActualRange = actualRange;
+ }
+ if (Destroyed() || aRange.location == NSNotFound) {
+ return rect;
+ }
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ LayoutDeviceIntRect r;
+ bool useCaretRect = (aRange.length == 0);
+ if (!useCaretRect) {
+ WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, mWidget);
+ WidgetQueryContentEvent::Options options;
+ int64_t startOffset = aRange.location;
+ if (IsIMEComposing()) {
+ // The composition may be at different offset from the selection start
+ // offset at dispatching compositionstart because start of composition
+ // is fixed when composition string becomes non-empty in the editor.
+ // Therefore, we need to use query event which is relative to insertion
+ // point.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mIMECompositionStart;
+ }
+ queryTextRectEvent.InitForQueryTextRect(startOffset, 1, options);
+ DispatchEvent(queryTextRectEvent);
+ if (queryTextRectEvent.Succeeded()) {
+ r = queryTextRectEvent.mReply->mRect;
+ actualRange = MakeNSRangeFrom(queryTextRectEvent.mReply->mOffsetAndData);
+ mWritingMode = queryTextRectEvent.mReply->WritingModeRef();
+ mRangeForWritingMode = actualRange;
+ } else {
+ useCaretRect = true;
+ }
+ }
+
+ if (useCaretRect) {
+ WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, mWidget);
+ WidgetQueryContentEvent::Options options;
+ int64_t startOffset = aRange.location;
+ if (IsIMEComposing()) {
+ // The composition may be at different offset from the selection start
+ // offset at dispatching compositionstart because start of composition
+ // is fixed when composition string becomes non-empty in the editor.
+ // Therefore, we need to use query event which is relative to insertion
+ // point.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mIMECompositionStart;
+ }
+ queryCaretRectEvent.InitForQueryCaretRect(startOffset, options);
+ DispatchEvent(queryCaretRectEvent);
+ if (queryCaretRectEvent.Failed()) {
+ return rect;
+ }
+ r = queryCaretRectEvent.mReply->mRect;
+ r.width = 0;
+ actualRange.location = queryCaretRectEvent.mReply->StartOffset();
+ actualRange.length = 0;
+ }
+
+ nsIWidget* rootWidget = mWidget->GetTopLevelWidget();
+ NSWindow* rootWindow = static_cast<NSWindow*>(rootWidget->GetNativeData(NS_NATIVE_WINDOW));
+ NSView* rootView = static_cast<NSView*>(rootWidget->GetNativeData(NS_NATIVE_WIDGET));
+ if (!rootWindow || !rootView) {
+ return rect;
+ }
+ rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, mWidget->BackingScaleFactor());
+ rect = [rootView convertRect:rect toView:nil];
+ rect.origin = nsCocoaUtils::ConvertPointToScreen(rootWindow, rect.origin);
+
+ if (aActualRange) {
+ *aActualRange = actualRange;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::FirstRectForCharacterRange, "
+ "useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }, "
+ "actualRange={ location=%lu, length=%lu }",
+ this, TrueOrFalse(useCaretRect), rect.origin.x, rect.origin.y, rect.size.width,
+ rect.size.height, static_cast<unsigned long>(actualRange.location),
+ static_cast<unsigned long>(actualRange.length)));
+
+ return rect;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0));
+}
+
+NSUInteger IMEInputHandler::CharacterIndexForPoint(NSPoint& aPoint) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CharacterIndexForPoint, aPoint={ x=%f, y=%f }", this, aPoint.x,
+ aPoint.y));
+
+ NSWindow* mainWindow = [NSApp mainWindow];
+ if (!mWidget || !mainWindow) {
+ return NSNotFound;
+ }
+
+ WidgetQueryContentEvent queryCharAtPointEvent(true, eQueryCharacterAtPoint, mWidget);
+ NSPoint ptInWindow = nsCocoaUtils::ConvertPointFromScreen(mainWindow, aPoint);
+ NSPoint ptInView = [mView convertPoint:ptInWindow fromView:nil];
+ queryCharAtPointEvent.mRefPoint.x =
+ static_cast<int32_t>(ptInView.x) * mWidget->BackingScaleFactor();
+ queryCharAtPointEvent.mRefPoint.y =
+ static_cast<int32_t>(ptInView.y) * mWidget->BackingScaleFactor();
+ mWidget->DispatchWindowEvent(queryCharAtPointEvent);
+ if (queryCharAtPointEvent.Failed() || queryCharAtPointEvent.DidNotFindChar() ||
+ queryCharAtPointEvent.mReply->StartOffset() >= static_cast<uint32_t>(NSNotFound)) {
+ return NSNotFound;
+ }
+
+ return queryCharAtPointEvent.mReply->StartOffset();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNotFound);
+}
+
+extern "C" {
+extern NSString* NSTextInputReplacementRangeAttributeName;
+}
+
+NSArray* IMEInputHandler::GetValidAttributesForMarkedText() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ MOZ_LOG(gLog, LogLevel::Info, ("%p IMEInputHandler::GetValidAttributesForMarkedText", this));
+
+ // Return same attributes as Chromium (see render_widget_host_view_mac.mm)
+ // because most IMEs must be tested with Safari (OS default) and Chrome
+ // (having most market share). Therefore, we need to follow their behavior.
+ // XXX It might be better to reuse an array instance for this result because
+ // this may be called a lot. Note that Chromium does so.
+ return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName, NSUnderlineColorAttributeName,
+ NSMarkedClauseSegmentAttributeName,
+ NSTextInputReplacementRangeAttributeName, nil];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+#pragma mark -
+
+/******************************************************************************
+ *
+ * IMEInputHandler implementation #2
+ *
+ ******************************************************************************/
+
+IMEInputHandler::IMEInputHandler(nsChildView* aWidget, NSView<mozView>* aNativeView)
+ : TextInputHandlerBase(aWidget, aNativeView),
+ mPendingMethods(0),
+ mIMECompositionString(nullptr),
+ mIMECompositionStart(UINT32_MAX),
+ mRangeForWritingMode(),
+ mIsIMEComposing(false),
+ mIsDeadKeyComposing(false),
+ mIsIMEEnabled(true),
+ mIsASCIICapableOnly(false),
+ mIgnoreIMECommit(false),
+ mIMEHasFocus(false) {
+ InitStaticMembers();
+
+ mMarkedRange.location = NSNotFound;
+ mMarkedRange.length = 0;
+ mSelectedRange.location = NSNotFound;
+ mSelectedRange.length = 0;
+}
+
+IMEInputHandler::~IMEInputHandler() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ if (sFocusedIMEHandler == this) {
+ sFocusedIMEHandler = nullptr;
+ }
+ if (mIMECompositionString) {
+ [mIMECompositionString release];
+ mIMECompositionString = nullptr;
+ }
+}
+
+void IMEInputHandler::OnFocusChangeInGecko(bool aFocus) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OnFocusChangeInGecko, aFocus=%s, Destroyed()=%s, "
+ "sFocusedIMEHandler=%p",
+ this, TrueOrFalse(aFocus), TrueOrFalse(Destroyed()), sFocusedIMEHandler));
+
+ mSelectedRange.location = NSNotFound; // Marking dirty
+ mIMEHasFocus = aFocus;
+
+ // This is called when the native focus is changed and when the native focus
+ // isn't changed but the focus is changed in Gecko.
+ if (!aFocus) {
+ if (sFocusedIMEHandler == this) sFocusedIMEHandler = nullptr;
+ return;
+ }
+
+ sFocusedIMEHandler = this;
+
+ // We need to notify IME of focus change in Gecko as native focus change
+ // because the window level of the focused element in Gecko may be changed.
+ mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
+ ResetTimer();
+}
+
+bool IMEInputHandler::OnDestroyWidget(nsChildView* aDestroyingWidget) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OnDestroyWidget, aDestroyingWidget=%p, "
+ "sFocusedIMEHandler=%p, IsIMEComposing()=%s",
+ this, aDestroyingWidget, sFocusedIMEHandler, TrueOrFalse(IsIMEComposing())));
+
+ // If we're not focused, the focused IMEInputHandler may have been
+ // created by another widget/nsChildView.
+ if (sFocusedIMEHandler && sFocusedIMEHandler != this) {
+ sFocusedIMEHandler->OnDestroyWidget(aDestroyingWidget);
+ }
+
+ if (!TextInputHandlerBase::OnDestroyWidget(aDestroyingWidget)) {
+ return false;
+ }
+
+ if (IsIMEComposing()) {
+ // If our view is in the composition, we should clean up it.
+ CancelIMEComposition();
+ }
+
+ mSelectedRange.location = NSNotFound; // Marking dirty
+ mIMEHasFocus = false;
+
+ return true;
+}
+
+void IMEInputHandler::SendCommittedText(NSString* aString) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(
+ gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SendCommittedText, mView=%p, mWidget=%p, "
+ "inputContext=%p, mIsIMEComposing=%s",
+ this, mView, mWidget, mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
+
+ NS_ENSURE_TRUE(mWidget, );
+ // XXX We should send the string without mView.
+ if (!mView) {
+ return;
+ }
+
+ NSAttributedString* attrStr = [[NSAttributedString alloc] initWithString:aString];
+ if ([mView conformsToProtocol:@protocol(NSTextInputClient)]) {
+ NSObject<NSTextInputClient>* textInputClient = static_cast<NSObject<NSTextInputClient>*>(mView);
+ [textInputClient insertText:attrStr replacementRange:NSMakeRange(NSNotFound, 0)];
+ }
+
+ // Last resort. If we cannot retrieve NSTextInputProtocol from mView
+ // or blocking to call our InsertText(), we should call InsertText()
+ // directly to commit composition forcibly.
+ if (mIsIMEComposing) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SendCommittedText, trying to insert text directly "
+ "due to IME not calling our InsertText()",
+ this));
+ static_cast<TextInputHandler*>(this)->InsertText(attrStr);
+ MOZ_ASSERT(!mIsIMEComposing);
+ }
+
+ [attrStr release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void IMEInputHandler::KillIMEComposition() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::KillIMEComposition, mView=%p, mWidget=%p, "
+ "inputContext=%p, mIsIMEComposing=%s, "
+ "Destroyed()=%s, IsFocused()=%s",
+ this, mView, mWidget, mView ? [mView inputContext] : nullptr,
+ TrueOrFalse(mIsIMEComposing), TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused())));
+
+ if (Destroyed() || NS_WARN_IF(!mView)) {
+ return;
+ }
+
+ NSTextInputContext* inputContext = [mView inputContext];
+ if (NS_WARN_IF(!inputContext)) {
+ return;
+ }
+ [inputContext discardMarkedText];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void IMEInputHandler::CommitIMEComposition() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CommitIMEComposition, mIMECompositionString=%s", this,
+ GetCharacters(mIMECompositionString)));
+
+ // If this is called before dispatching eCompositionStart, IsIMEComposing()
+ // returns false. Even in such case, we need to commit composition *in*
+ // IME if this is called by preceding eKeyDown event of eCompositionStart.
+ // So, we need to call KillIMEComposition() even when IsIMEComposing()
+ // returns false.
+ KillIMEComposition();
+
+ if (!IsIMEComposing()) return;
+
+ // If the composition is still there, KillIMEComposition only kills the
+ // composition in TSM. We also need to finish the our composition too.
+ SendCommittedText(mIMECompositionString);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void IMEInputHandler::CancelIMEComposition() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!IsIMEComposing()) return;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CancelIMEComposition, mIMECompositionString=%s", this,
+ GetCharacters(mIMECompositionString)));
+
+ // For canceling the current composing, we need to ignore the param of
+ // insertText. But this code is ugly...
+ mIgnoreIMECommit = true;
+ KillIMEComposition();
+ mIgnoreIMECommit = false;
+
+ if (!IsIMEComposing()) return;
+
+ // If the composition is still there, KillIMEComposition only kills the
+ // composition in TSM. We also need to kill the our composition too.
+ SendCommittedText(@"");
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+bool IMEInputHandler::IsFocused() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ENSURE_TRUE(!Destroyed(), false);
+ NSWindow* window = [mView window];
+ NS_ENSURE_TRUE(window, false);
+ return [window firstResponder] == mView && [window isKeyWindow] &&
+ [[NSApplication sharedApplication] isActive];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+bool IMEInputHandler::IsIMEOpened() {
+ TISInputSourceWrapper tis;
+ tis.InitByCurrentInputSource();
+ return tis.IsOpenedIMEMode();
+}
+
+void IMEInputHandler::SetASCIICapableOnly(bool aASCIICapableOnly) {
+ if (aASCIICapableOnly == mIsASCIICapableOnly) return;
+
+ CommitIMEComposition();
+ mIsASCIICapableOnly = aASCIICapableOnly;
+ SyncASCIICapableOnly();
+}
+
+void IMEInputHandler::EnableIME(bool aEnableIME) {
+ if (aEnableIME == mIsIMEEnabled) return;
+
+ CommitIMEComposition();
+ mIsIMEEnabled = aEnableIME;
+}
+
+void IMEInputHandler::SetIMEOpenState(bool aOpenIME) {
+ if (!IsFocused() || IsIMEOpened() == aOpenIME) return;
+
+ if (!aOpenIME) {
+ TISInputSourceWrapper tis;
+ tis.InitByCurrentASCIICapableInputSource();
+ tis.Select();
+ return;
+ }
+
+ // If we know the latest IME opened mode, we should select it.
+ if (sLatestIMEOpenedModeInputSourceID) {
+ TISInputSourceWrapper tis;
+ tis.InitByInputSourceID(sLatestIMEOpenedModeInputSourceID);
+ tis.Select();
+ return;
+ }
+
+ // XXX If the current input source is a mode of IME, we should turn on it,
+ // but we haven't found such way...
+
+ // Finally, we should refer the system locale but this is a little expensive,
+ // we shouldn't retry this (if it was succeeded, we already set
+ // sLatestIMEOpenedModeInputSourceID at that time).
+ static bool sIsPrefferredIMESearched = false;
+ if (sIsPrefferredIMESearched) return;
+ sIsPrefferredIMESearched = true;
+ OpenSystemPreferredLanguageIME();
+}
+
+void IMEInputHandler::OpenSystemPreferredLanguageIME() {
+ MOZ_LOG(gLog, LogLevel::Info, ("%p IMEInputHandler::OpenSystemPreferredLanguageIME", this));
+
+ CFArrayRef langList = ::CFLocaleCopyPreferredLanguages();
+ if (!langList) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, langList is NULL", this));
+ return;
+ }
+ CFIndex count = ::CFArrayGetCount(langList);
+ for (CFIndex i = 0; i < count; i++) {
+ CFLocaleRef locale = ::CFLocaleCreate(
+ kCFAllocatorDefault, static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, i)));
+ if (!locale) {
+ continue;
+ }
+
+ bool changed = false;
+ CFStringRef lang = static_cast<CFStringRef>(::CFLocaleGetValue(locale, kCFLocaleLanguageCode));
+ NS_ASSERTION(lang, "lang is null");
+ if (lang) {
+ TISInputSourceWrapper tis;
+ tis.InitByLanguage(lang);
+ if (tis.IsOpenedIMEMode()) {
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ CFStringRef foundTIS;
+ tis.GetInputSourceID(foundTIS);
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, "
+ "foundTIS=%s, lang=%s",
+ this, GetCharacters(foundTIS), GetCharacters(lang)));
+ }
+ tis.Select();
+ changed = true;
+ }
+ }
+ ::CFRelease(locale);
+ if (changed) {
+ break;
+ }
+ }
+ ::CFRelease(langList);
+}
+
+void IMEInputHandler::OnSelectionChange(const IMENotification& aIMENotification) {
+ MOZ_LOG(gLog, LogLevel::Info, ("%p IMEInputHandler::OnSelectionChange", this));
+
+ if (aIMENotification.mSelectionChangeData.mOffset == UINT32_MAX) {
+ mSelectedRange.location = NSNotFound;
+ mSelectedRange.length = 0;
+ mRangeForWritingMode.location = NSNotFound;
+ mRangeForWritingMode.length = 0;
+ return;
+ }
+
+ mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
+ mRangeForWritingMode = NSMakeRange(aIMENotification.mSelectionChangeData.mOffset,
+ aIMENotification.mSelectionChangeData.Length());
+ if (mIMEHasFocus) {
+ mSelectedRange = mRangeForWritingMode;
+ }
+}
+
+void IMEInputHandler::OnLayoutChange() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!IsFocused()) {
+ return;
+ }
+ NSTextInputContext* inputContext = [mView inputContext];
+ [inputContext invalidateCharacterCoordinates];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+bool IMEInputHandler::OnHandleEvent(NSEvent* aEvent) {
+ if (!IsFocused()) {
+ return false;
+ }
+
+ bool allowConsumeEvent = true;
+ if (nsCocoaFeatures::OnCatalinaOrLater() && !IsIMEComposing()) {
+ // Hack for bug of Korean IMEs on Catalina (10.15).
+ // If we are inactivated during composition, active Korean IME keeps
+ // consuming all mousedown events of any mouse buttons. So, we should
+ // allow Korean IMEs to handle mousedown events only when there is
+ // composition string.
+ // List of ID of Korean IME:
+ // * com.apple.inputmethod.Korean.2SetKorean
+ // * com.apple.inputmethod.Korean.3SetKorean
+ // * com.apple.inputmethod.Korean.390Sebulshik
+ // * com.apple.inputmethod.Korean.GongjinCheongRomaja
+ // * com.apple.inputmethod.Korean.HNCRomaja
+ TISInputSourceWrapper tis;
+ tis.InitByCurrentInputSource();
+ nsAutoString inputSourceID;
+ tis.GetInputSourceID(inputSourceID);
+ allowConsumeEvent = !StringBeginsWith(inputSourceID, u"com.apple.inputmethod.Korean."_ns);
+ }
+ NSTextInputContext* inputContext = [mView inputContext];
+ return [inputContext handleEvent:aEvent] && allowConsumeEvent;
+}
+
+#pragma mark -
+
+/******************************************************************************
+ *
+ * TextInputHandlerBase implementation
+ *
+ ******************************************************************************/
+
+int32_t TextInputHandlerBase::sSecureEventInputCount = 0;
+
+NS_IMPL_ISUPPORTS(TextInputHandlerBase, TextEventDispatcherListener, nsISupportsWeakReference)
+
+TextInputHandlerBase::TextInputHandlerBase(nsChildView* aWidget, NSView<mozView>* aNativeView)
+ : mWidget(aWidget), mDispatcher(aWidget->GetTextEventDispatcher()) {
+ gHandlerInstanceCount++;
+ mView = [aNativeView retain];
+}
+
+TextInputHandlerBase::~TextInputHandlerBase() {
+ [mView release];
+ if (--gHandlerInstanceCount == 0) {
+ TISInputSourceWrapper::Shutdown();
+ }
+}
+
+bool TextInputHandlerBase::OnDestroyWidget(nsChildView* aDestroyingWidget) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandlerBase::OnDestroyWidget, "
+ "aDestroyingWidget=%p, mWidget=%p",
+ this, aDestroyingWidget, mWidget));
+
+ if (aDestroyingWidget != mWidget) {
+ return false;
+ }
+
+ mWidget = nullptr;
+ mDispatcher = nullptr;
+ return true;
+}
+
+bool TextInputHandlerBase::DispatchEvent(WidgetGUIEvent& aEvent) {
+ return mWidget->DispatchWindowEvent(aEvent);
+}
+
+void TextInputHandlerBase::InitKeyEvent(NSEvent* aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
+ bool aIsProcessedByIME, const nsAString* aInsertString) {
+ NS_ASSERTION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
+
+ if (mKeyboardOverride.mOverrideEnabled) {
+ TISInputSourceWrapper tis;
+ tis.InitByLayoutID(mKeyboardOverride.mKeyboardLayout, true);
+ tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent, aIsProcessedByIME, aInsertString);
+ return;
+ }
+ TISInputSourceWrapper::CurrentInputSource().InitKeyEvent(aNativeKeyEvent, aKeyEvent,
+ aIsProcessedByIME, aInsertString);
+}
+
+nsresult TextInputHandlerBase::SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ static const uint32_t sModifierFlagMap[][2] = {
+ {nsIWidget::CAPS_LOCK, NSEventModifierFlagCapsLock},
+ {nsIWidget::SHIFT_L, NSEventModifierFlagShift | 0x0002},
+ {nsIWidget::SHIFT_R, NSEventModifierFlagShift | 0x0004},
+ {nsIWidget::CTRL_L, NSEventModifierFlagControl | 0x0001},
+ {nsIWidget::CTRL_R, NSEventModifierFlagControl | 0x2000},
+ {nsIWidget::ALT_L, NSEventModifierFlagOption | 0x0020},
+ {nsIWidget::ALT_R, NSEventModifierFlagOption | 0x0040},
+ {nsIWidget::COMMAND_L, NSEventModifierFlagCommand | 0x0008},
+ {nsIWidget::COMMAND_R, NSEventModifierFlagCommand | 0x0010},
+ {nsIWidget::NUMERIC_KEY_PAD, NSEventModifierFlagNumericPad},
+ {nsIWidget::HELP, NSEventModifierFlagHelp},
+ {nsIWidget::FUNCTION, NSEventModifierFlagFunction}};
+
+ uint32_t modifierFlags = 0;
+ for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
+ if (aModifierFlags & sModifierFlagMap[i][0]) {
+ modifierFlags |= sModifierFlagMap[i][1];
+ }
+ }
+
+ NSInteger windowNumber = [[mView window] windowNumber];
+ bool sendFlagsChangedEvent = IsModifierKey(aNativeKeyCode);
+ NSEventType eventType = sendFlagsChangedEvent ? NSEventTypeFlagsChanged : NSEventTypeKeyDown;
+ NSEvent* downEvent = [NSEvent keyEventWithType:eventType
+ location:NSMakePoint(0, 0)
+ modifierFlags:modifierFlags
+ timestamp:0
+ windowNumber:windowNumber
+ context:[NSGraphicsContext currentContext]
+ characters:nsCocoaUtils::ToNSString(aCharacters)
+ charactersIgnoringModifiers:nsCocoaUtils::ToNSString(aUnmodifiedCharacters)
+ isARepeat:NO
+ keyCode:aNativeKeyCode];
+
+ NSEvent* upEvent = sendFlagsChangedEvent
+ ? nil
+ : nsCocoaUtils::MakeNewCocoaEventWithType(NSEventTypeKeyUp, downEvent);
+
+ if (downEvent && (sendFlagsChangedEvent || upEvent)) {
+ KeyboardLayoutOverride currentLayout = mKeyboardOverride;
+ mKeyboardOverride.mKeyboardLayout = aNativeKeyboardLayout;
+ mKeyboardOverride.mOverrideEnabled = true;
+ [NSApp sendEvent:downEvent];
+ if (upEvent) {
+ [NSApp sendEvent:upEvent];
+ }
+ // processKeyDownEvent and keyUp block exceptions so we're sure to
+ // reach here to restore mKeyboardOverride
+ mKeyboardOverride = currentLayout;
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NSInteger TextInputHandlerBase::GetWindowLevel() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(
+ gLog, LogLevel::Info,
+ ("%p TextInputHandlerBase::GetWindowLevel, Destryoed()=%s", this, TrueOrFalse(Destroyed())));
+
+ if (Destroyed()) {
+ return NSNormalWindowLevel;
+ }
+
+ // When an <input> element on a XUL <panel> is focused, the actual focused view
+ // is the panel's parent view (mView). But the editor is displayed on the
+ // popped-up widget's view (editorView). We want the latter's window level.
+ NSView<mozView>* editorView = mWidget->GetEditorView();
+ NS_ENSURE_TRUE(editorView, NSNormalWindowLevel);
+ NSInteger windowLevel = [[editorView window] level];
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandlerBase::GetWindowLevel, windowLevel=%s (%lX)", this,
+ GetWindowLevelName(windowLevel), static_cast<unsigned long>(windowLevel)));
+
+ return windowLevel;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel);
+}
+
+NS_IMETHODIMP
+TextInputHandlerBase::AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Don't try to replace a native event if one already exists.
+ // OS X doesn't have an OS modifier, can't make a native event.
+ if (aKeyEvent.mNativeKeyEvent || aKeyEvent.mModifiers & MODIFIER_OS) {
+ return NS_OK;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandlerBase::AttachNativeKeyEvent, key=0x%X, char=0x%X, "
+ "mod=0x%X",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, aKeyEvent.mModifiers));
+
+ NSInteger windowNumber = [[mView window] windowNumber];
+ NSGraphicsContext* context = [NSGraphicsContext currentContext];
+ aKeyEvent.mNativeKeyEvent =
+ nsCocoaUtils::MakeNewCococaEventFromWidgetEvent(aKeyEvent, windowNumber, context);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+bool TextInputHandlerBase::SetSelection(NSRange& aRange) {
+ MOZ_ASSERT(!Destroyed());
+
+ RefPtr<TextInputHandlerBase> kungFuDeathGrip(this);
+ WidgetSelectionEvent selectionEvent(true, eSetSelection, mWidget);
+ selectionEvent.mOffset = aRange.location;
+ selectionEvent.mLength = aRange.length;
+ selectionEvent.mReversed = false;
+ selectionEvent.mExpandToClusterBoundary = false;
+ DispatchEvent(selectionEvent);
+ NS_ENSURE_TRUE(selectionEvent.mSucceeded, false);
+ return !Destroyed();
+}
+
+/* static */ bool TextInputHandlerBase::IsPrintableChar(char16_t aChar) {
+ return (aChar >= 0x20 && aChar <= 0x7E) || aChar >= 0xA0;
+}
+
+/* static */ bool TextInputHandlerBase::IsSpecialGeckoKey(UInt32 aNativeKeyCode) {
+ // this table is used to determine which keys are special and should not
+ // generate a charCode
+ switch (aNativeKeyCode) {
+ // modifiers - we don't get separate events for these yet
+ case kVK_Escape:
+ case kVK_Shift:
+ case kVK_RightShift:
+ case kVK_Command:
+ case kVK_RightCommand:
+ case kVK_CapsLock:
+ case kVK_Control:
+ case kVK_RightControl:
+ case kVK_Option:
+ case kVK_RightOption:
+ case kVK_ANSI_KeypadClear:
+ case kVK_Function:
+
+ // function keys
+ case kVK_F1:
+ case kVK_F2:
+ case kVK_F3:
+ case kVK_F4:
+ case kVK_F5:
+ case kVK_F6:
+ case kVK_F7:
+ case kVK_F8:
+ case kVK_F9:
+ case kVK_F10:
+ case kVK_F11:
+ case kVK_F12:
+ case kVK_PC_Pause:
+ case kVK_PC_ScrollLock:
+ case kVK_PC_PrintScreen:
+ case kVK_F16:
+ case kVK_F17:
+ case kVK_F18:
+ case kVK_F19:
+
+ case kVK_PC_Insert:
+ case kVK_PC_Delete:
+ case kVK_Tab:
+ case kVK_PC_Backspace:
+ case kVK_PC_ContextMenu:
+
+ case kVK_JIS_Eisu:
+ case kVK_JIS_Kana:
+
+ case kVK_Home:
+ case kVK_End:
+ case kVK_PageUp:
+ case kVK_PageDown:
+ case kVK_LeftArrow:
+ case kVK_RightArrow:
+ case kVK_UpArrow:
+ case kVK_DownArrow:
+ case kVK_Return:
+ case kVK_ANSI_KeypadEnter:
+ case kVK_Powerbook_KeypadEnter:
+ return true;
+ }
+ return false;
+}
+
+/* static */ bool TextInputHandlerBase::IsNormalCharInputtingEvent(NSEvent* aNativeEvent) {
+ if ([aNativeEvent type] != NSEventTypeKeyDown && [aNativeEvent type] != NSEventTypeKeyUp) {
+ return false;
+ }
+ nsAutoString nativeChars;
+ nsCocoaUtils::GetStringForNSString([aNativeEvent characters], nativeChars);
+
+ // this is not character inputting event, simply.
+ if (nativeChars.IsEmpty() || ([aNativeEvent modifierFlags] & NSEventModifierFlagCommand)) {
+ return false;
+ }
+ return !IsControlChar(nativeChars[0]);
+}
+
+/* static */ bool TextInputHandlerBase::IsModifierKey(UInt32 aNativeKeyCode) {
+ switch (aNativeKeyCode) {
+ case kVK_CapsLock:
+ case kVK_RightCommand:
+ case kVK_Command:
+ case kVK_Shift:
+ case kVK_Option:
+ case kVK_Control:
+ case kVK_RightShift:
+ case kVK_RightOption:
+ case kVK_RightControl:
+ case kVK_Function:
+ return true;
+ }
+ return false;
+}
+
+/* static */ void TextInputHandlerBase::EnableSecureEventInput() {
+ sSecureEventInputCount++;
+ ::EnableSecureEventInput();
+}
+
+/* static */ void TextInputHandlerBase::DisableSecureEventInput() {
+ if (!sSecureEventInputCount) {
+ return;
+ }
+ sSecureEventInputCount--;
+ ::DisableSecureEventInput();
+}
+
+/* static */ bool TextInputHandlerBase::IsSecureEventInputEnabled() {
+ NS_ASSERTION(!!sSecureEventInputCount == !!::IsSecureEventInputEnabled(),
+ "Some other process has enabled secure event input");
+ return !!sSecureEventInputCount;
+}
+
+/* static */ void TextInputHandlerBase::EnsureSecureEventInputDisabled() {
+ while (sSecureEventInputCount) {
+ TextInputHandlerBase::DisableSecureEventInput();
+ }
+}
+
+#pragma mark -
+
+/******************************************************************************
+ *
+ * TextInputHandlerBase::KeyEventState implementation
+ *
+ ******************************************************************************/
+
+void TextInputHandlerBase::KeyEventState::InitKeyEvent(TextInputHandlerBase* aHandler,
+ WidgetKeyboardEvent& aKeyEvent,
+ bool aIsProcessedByIME) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_ASSERT(aHandler);
+ MOZ_RELEASE_ASSERT(mKeyEvent);
+
+ NSEvent* nativeEvent = mKeyEvent;
+ if (!mInsertedString.IsEmpty()) {
+ nsAutoString unhandledString;
+ GetUnhandledString(unhandledString);
+ NSString* unhandledNSString = nsCocoaUtils::ToNSString(unhandledString);
+ // If the key event's some characters were already handled by
+ // InsertString() calls, we need to create a dummy event which doesn't
+ // include the handled characters.
+ nativeEvent = [NSEvent keyEventWithType:[mKeyEvent type]
+ location:[mKeyEvent locationInWindow]
+ modifierFlags:[mKeyEvent modifierFlags]
+ timestamp:[mKeyEvent timestamp]
+ windowNumber:[mKeyEvent windowNumber]
+ context:[mKeyEvent context]
+ characters:unhandledNSString
+ charactersIgnoringModifiers:[mKeyEvent charactersIgnoringModifiers]
+ isARepeat:[mKeyEvent isARepeat]
+ keyCode:[mKeyEvent keyCode]];
+ }
+
+ aKeyEvent.mUniqueId = mUniqueId;
+ aHandler->InitKeyEvent(nativeEvent, aKeyEvent, aIsProcessedByIME, mInsertString);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void TextInputHandlerBase::KeyEventState::GetUnhandledString(nsAString& aUnhandledString) const {
+ aUnhandledString.Truncate();
+ if (NS_WARN_IF(!mKeyEvent)) {
+ return;
+ }
+ nsAutoString characters;
+ nsCocoaUtils::GetStringForNSString([mKeyEvent characters], characters);
+ if (characters.IsEmpty()) {
+ return;
+ }
+ if (mInsertedString.IsEmpty()) {
+ aUnhandledString = characters;
+ return;
+ }
+
+ // The insertes string must match with the start of characters.
+ MOZ_ASSERT(StringBeginsWith(characters, mInsertedString));
+
+ aUnhandledString = nsDependentSubstring(characters, mInsertedString.Length());
+}
+
+#pragma mark -
+
+/******************************************************************************
+ *
+ * TextInputHandlerBase::AutoInsertStringClearer implementation
+ *
+ ******************************************************************************/
+
+TextInputHandlerBase::AutoInsertStringClearer::~AutoInsertStringClearer() {
+ if (mState && mState->mInsertString) {
+ // If inserting string is a part of characters of the event,
+ // we should record it as inserted string.
+ nsAutoString characters;
+ nsCocoaUtils::GetStringForNSString([mState->mKeyEvent characters], characters);
+ nsAutoString insertedString(mState->mInsertedString);
+ insertedString += *mState->mInsertString;
+ if (StringBeginsWith(characters, insertedString)) {
+ mState->mInsertedString = insertedString;
+ }
+ }
+ if (mState) {
+ mState->mInsertString = nullptr;
+ }
+}
diff --git a/widget/cocoa/VibrancyManager.h b/widget/cocoa/VibrancyManager.h
new file mode 100644
index 0000000000..c03a286850
--- /dev/null
+++ b/widget/cocoa/VibrancyManager.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef VibrancyManager_h
+#define VibrancyManager_h
+
+#include "mozilla/Assertions.h"
+#include "nsClassHashtable.h"
+#include "nsRegion.h"
+#include "nsTArray.h"
+#include "ViewRegion.h"
+
+#import <Foundation/NSGeometry.h>
+
+@class NSColor;
+@class NSView;
+class nsChildView;
+
+namespace mozilla {
+
+enum class VibrancyType {
+ LIGHT,
+ DARK,
+ TOOLTIP,
+ MENU,
+ HIGHLIGHTED_MENUITEM,
+ SHEET,
+ SOURCE_LIST,
+ SOURCE_LIST_SELECTION,
+ ACTIVE_SOURCE_LIST_SELECTION
+};
+
+/**
+ * VibrancyManager takes care of updating the vibrant regions of a window.
+ * Vibrancy is a visual look that was introduced on OS X starting with 10.10.
+ * An app declares vibrant window regions to the window server, and the window
+ * server will display a blurred rendering of the screen contents from behind
+ * the window in these areas, behind the actual window contents. Consequently,
+ * the effect is only visible in areas where the window contents are not
+ * completely opaque. Usually this is achieved by clearing the background of
+ * the window prior to drawing in the vibrant areas. This is possible even if
+ * the window is declared as opaque.
+ */
+class VibrancyManager {
+ public:
+ /**
+ * Create a new VibrancyManager instance and provide it with an NSView
+ * to attach NSVisualEffectViews to.
+ *
+ * @param aCoordinateConverter The nsChildView to use for converting
+ * nsIntRect device pixel coordinates into Cocoa NSRect coordinates. Must
+ * outlive this VibrancyManager instance.
+ * @param aContainerView The view that's going to be the superview of the
+ * NSVisualEffectViews which will be created for vibrant regions.
+ */
+ VibrancyManager(const nsChildView& aCoordinateConverter, NSView* aContainerView)
+ : mCoordinateConverter(aCoordinateConverter), mContainerView(aContainerView) {}
+
+ /**
+ * Update the placement of the NSVisualEffectViews inside the container
+ * NSView so that they cover aRegion, and create new NSVisualEffectViews
+ * or remove existing ones as needed.
+ * @param aType The vibrancy type to use in the region.
+ * @param aRegion The vibrant area, in device pixels.
+ * @return Whether the region changed.
+ */
+ bool UpdateVibrantRegion(VibrancyType aType, const LayoutDeviceIntRegion& aRegion);
+
+ bool HasVibrantRegions() { return !mVibrantRegions.IsEmpty(); }
+
+ LayoutDeviceIntRegion GetUnionOfVibrantRegions() const;
+
+ /**
+ * Create an NSVisualEffectView for the specified vibrancy type. The return
+ * value is not autoreleased. We return an object of type NSView* because we
+ * compile with an SDK that does not contain a definition for
+ * NSVisualEffectView.
+ * @param aIsContainer Whether this NSView will have child views. This value
+ * affects hit testing: Container views will pass through
+ * hit testing requests to their children, and leaf views
+ * will be transparent to hit testing.
+ */
+ static NSView* CreateEffectView(VibrancyType aType, BOOL aIsContainer = NO);
+
+ protected:
+ const nsChildView& mCoordinateConverter;
+ NSView* mContainerView;
+ nsClassHashtable<nsUint32HashKey, ViewRegion> mVibrantRegions;
+};
+
+} // namespace mozilla
+
+#endif // VibrancyManager_h
diff --git a/widget/cocoa/VibrancyManager.mm b/widget/cocoa/VibrancyManager.mm
new file mode 100644
index 0000000000..f082f904af
--- /dev/null
+++ b/widget/cocoa/VibrancyManager.mm
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "VibrancyManager.h"
+
+#include "nsChildView.h"
+#include "nsCocoaFeatures.h"
+#import <objc/message.h>
+
+using namespace mozilla;
+
+#if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
+enum { NSVisualEffectMaterialSelection = 4 };
+
+@interface NSVisualEffectView (NSVisualEffectViewMethods)
+- (void)setEmphasized:(BOOL)emphasized;
+@end
+#endif
+
+@interface MOZVibrantView : NSVisualEffectView {
+ VibrancyType mType;
+}
+- (instancetype)initWithFrame:(NSRect)aRect vibrancyType:(VibrancyType)aVibrancyType;
+@end
+
+@interface MOZVibrantLeafView : MOZVibrantView
+@end
+
+static NSAppearance* AppearanceForVibrancyType(VibrancyType aType) {
+ switch (aType) {
+ case VibrancyType::LIGHT:
+ case VibrancyType::TOOLTIP:
+ case VibrancyType::MENU:
+ case VibrancyType::HIGHLIGHTED_MENUITEM:
+ case VibrancyType::SHEET:
+ case VibrancyType::SOURCE_LIST:
+ case VibrancyType::SOURCE_LIST_SELECTION:
+ case VibrancyType::ACTIVE_SOURCE_LIST_SELECTION:
+ return [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantLight"];
+ case VibrancyType::DARK:
+ return [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantDark"];
+ }
+}
+
+static NSVisualEffectState VisualEffectStateForVibrancyType(VibrancyType aType) {
+ switch (aType) {
+ case VibrancyType::TOOLTIP:
+ case VibrancyType::MENU:
+ case VibrancyType::HIGHLIGHTED_MENUITEM:
+ case VibrancyType::SHEET:
+ // Tooltip and menu windows are never "key" and sheets always looks
+ // active, so we need to tell the vibrancy effect to look active
+ // regardless of window state.
+ return NSVisualEffectStateActive;
+ default:
+ return NSVisualEffectStateFollowsWindowActiveState;
+ }
+}
+
+static NSVisualEffectMaterial VisualEffectMaterialForVibrancyType(VibrancyType aType,
+ BOOL* aOutIsEmphasized) {
+ switch (aType) {
+ case VibrancyType::MENU:
+ return NSVisualEffectMaterialMenu;
+ case VibrancyType::SOURCE_LIST:
+ return NSVisualEffectMaterialSidebar;
+ case VibrancyType::SOURCE_LIST_SELECTION:
+ return (NSVisualEffectMaterial)NSVisualEffectMaterialSelection;
+ case VibrancyType::HIGHLIGHTED_MENUITEM:
+ case VibrancyType::ACTIVE_SOURCE_LIST_SELECTION:
+ *aOutIsEmphasized = YES;
+ return (NSVisualEffectMaterial)NSVisualEffectMaterialSelection;
+ default:
+ return NSVisualEffectMaterialAppearanceBased;
+ }
+}
+
+static BOOL HasVibrantForeground(VibrancyType aType) {
+ switch (aType) {
+ case VibrancyType::MENU:
+ return YES;
+ default:
+ return NO;
+ }
+}
+
+@implementation MOZVibrantView
+
+- (instancetype)initWithFrame:(NSRect)aRect vibrancyType:(VibrancyType)aType {
+ self = [super initWithFrame:aRect];
+ mType = aType;
+
+ self.appearance = AppearanceForVibrancyType(mType);
+ self.state = VisualEffectStateForVibrancyType(mType);
+
+ BOOL isEmphasized = NO;
+ self.material = VisualEffectMaterialForVibrancyType(mType, &isEmphasized);
+
+ if (isEmphasized && [self respondsToSelector:@selector(setEmphasized:)]) {
+ [self setEmphasized:YES];
+ }
+
+ return self;
+}
+
+// Don't override allowsVibrancy here, because this view may have subviews, and
+// returning YES from allowsVibrancy forces on foreground vibrancy for all
+// descendant views, which can have unintended effects.
+
+@end
+
+@implementation MOZVibrantLeafView
+
+- (NSView*)hitTest:(NSPoint)aPoint {
+ // This view must be transparent to mouse events.
+ return nil;
+}
+
+// MOZVibrantLeafView does not have subviews, so we can return YES here without
+// having unintended effects on other contents of the window.
+- (BOOL)allowsVibrancy {
+ return HasVibrantForeground(mType);
+}
+
+@end
+
+bool VibrancyManager::UpdateVibrantRegion(VibrancyType aType,
+ const LayoutDeviceIntRegion& aRegion) {
+ if (aRegion.IsEmpty()) {
+ return mVibrantRegions.Remove(uint32_t(aType));
+ }
+ auto& vr = *mVibrantRegions.LookupOrAdd(uint32_t(aType));
+ return vr.UpdateRegion(aRegion, mCoordinateConverter, mContainerView, ^() {
+ return this->CreateEffectView(aType);
+ });
+}
+
+LayoutDeviceIntRegion VibrancyManager::GetUnionOfVibrantRegions() const {
+ LayoutDeviceIntRegion result;
+ for (auto it = mVibrantRegions.ConstIter(); !it.Done(); it.Next()) {
+ result.OrWith(it.UserData()->Region());
+ }
+ return result;
+}
+
+/* static */ NSView* VibrancyManager::CreateEffectView(VibrancyType aType, BOOL aIsContainer) {
+ return aIsContainer ? [[MOZVibrantView alloc] initWithFrame:NSZeroRect vibrancyType:aType]
+ : [[MOZVibrantLeafView alloc] initWithFrame:NSZeroRect vibrancyType:aType];
+}
diff --git a/widget/cocoa/ViewRegion.h b/widget/cocoa/ViewRegion.h
new file mode 100644
index 0000000000..4c98fab882
--- /dev/null
+++ b/widget/cocoa/ViewRegion.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ViewRegion_h
+#define ViewRegion_h
+
+#include "Units.h"
+#include "nsTArray.h"
+
+class nsChildView;
+
+@class NSView;
+
+namespace mozilla {
+
+/**
+ * Manages a set of NSViews to cover a LayoutDeviceIntRegion.
+ */
+class ViewRegion {
+ public:
+ ~ViewRegion();
+
+ mozilla::LayoutDeviceIntRegion Region() { return mRegion; }
+
+ /**
+ * Update the region.
+ * @param aRegion The new region.
+ * @param aCoordinateConverter The nsChildView to use for converting
+ * LayoutDeviceIntRect device pixel coordinates into Cocoa NSRect coordinates.
+ * @param aContainerView The view that's going to be the superview of the
+ * NSViews which will be created for this region.
+ * @param aViewCreationCallback A block that instantiates new NSViews.
+ * @return Whether or not the region changed.
+ */
+ bool UpdateRegion(const mozilla::LayoutDeviceIntRegion& aRegion,
+ const nsChildView& aCoordinateConverter, NSView* aContainerView,
+ NSView* (^aViewCreationCallback)());
+
+ /**
+ * Return an NSView from the region, if there is any.
+ */
+ NSView* GetAnyView() { return mViews.Length() > 0 ? mViews[0] : NULL; }
+
+ private:
+ mozilla::LayoutDeviceIntRegion mRegion;
+ nsTArray<NSView*> mViews;
+};
+
+} // namespace mozilla
+
+#endif // ViewRegion_h
diff --git a/widget/cocoa/ViewRegion.mm b/widget/cocoa/ViewRegion.mm
new file mode 100644
index 0000000000..c99785f956
--- /dev/null
+++ b/widget/cocoa/ViewRegion.mm
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ViewRegion.h"
+#import <Cocoa/Cocoa.h>
+
+#include "nsChildView.h"
+
+using namespace mozilla;
+
+ViewRegion::~ViewRegion() {
+ for (size_t i = 0; i < mViews.Length(); i++) {
+ [mViews[i] removeFromSuperview];
+ }
+}
+
+bool ViewRegion::UpdateRegion(const LayoutDeviceIntRegion& aRegion,
+ const nsChildView& aCoordinateConverter, NSView* aContainerView,
+ NSView* (^aViewCreationCallback)()) {
+ if (mRegion == aRegion) {
+ return false;
+ }
+
+ // We need to construct the required region using as many EffectViews
+ // as necessary. We try to update the geometry of existing views if
+ // possible, or create new ones or remove old ones if the number of
+ // rects in the region has changed.
+
+ nsTArray<NSView*> viewsToRecycle = std::move(mViews);
+ // The mViews array is now empty.
+
+ size_t i = 0;
+ for (auto iter = aRegion.RectIter(); !iter.Done() || i < viewsToRecycle.Length(); i++) {
+ if (!iter.Done()) {
+ NSView* view = nil;
+ NSRect rect = aCoordinateConverter.DevPixelsToCocoaPoints(iter.Get());
+ if (i < viewsToRecycle.Length()) {
+ view = viewsToRecycle[i];
+ } else {
+ view = aViewCreationCallback();
+ [aContainerView addSubview:view];
+
+ // Now that the view is in the view hierarchy, it'll be kept alive by
+ // its superview, so we can drop our reference.
+ [view release];
+ }
+ if (!NSEqualRects(rect, [view frame])) {
+ [view setFrame:rect];
+ }
+ [view setNeedsDisplay:YES];
+ mViews.AppendElement(view);
+ iter.Next();
+ } else {
+ // Our new region is made of fewer rects than the old region, so we can
+ // remove this view. We only have a weak reference to it, so removing it
+ // from the view hierarchy will release it.
+ [viewsToRecycle[i] removeFromSuperview];
+ }
+ }
+
+ mRegion = aRegion;
+ return true;
+}
diff --git a/widget/cocoa/WidgetTraceEvent.mm b/widget/cocoa/WidgetTraceEvent.mm
new file mode 100644
index 0000000000..54fe497e3b
--- /dev/null
+++ b/widget/cocoa/WidgetTraceEvent.mm
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <Cocoa/Cocoa.h>
+#include "CustomCocoaEvents.h"
+#include <Foundation/NSAutoreleasePool.h>
+#include <mozilla/CondVar.h>
+#include <mozilla/Mutex.h>
+#include "mozilla/WidgetTraceEvent.h"
+
+using mozilla::CondVar;
+using mozilla::Mutex;
+using mozilla::MutexAutoLock;
+
+namespace {
+
+Mutex* sMutex = NULL;
+CondVar* sCondVar = NULL;
+bool sTracerProcessed = false;
+
+} // namespace
+
+namespace mozilla {
+
+bool InitWidgetTracing() {
+ sMutex = new Mutex("Event tracer thread mutex");
+ sCondVar = new CondVar(*sMutex, "Event tracer thread condvar");
+ return sMutex && sCondVar;
+}
+
+void CleanUpWidgetTracing() {
+ delete sMutex;
+ delete sCondVar;
+ sMutex = NULL;
+ sCondVar = NULL;
+}
+
+// This function is called from the main (UI) thread.
+void SignalTracerThread() {
+ if (!sMutex || !sCondVar) return;
+ MutexAutoLock lock(*sMutex);
+ if (!sTracerProcessed) {
+ sTracerProcessed = true;
+ sCondVar->Notify();
+ }
+}
+
+// This function is called from the background tracer thread.
+bool FireAndWaitForTracerEvent() {
+ MOZ_ASSERT(sMutex && sCondVar, "Tracing not initialized!");
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+ MutexAutoLock lock(*sMutex);
+ if (sTracerProcessed) {
+ // Things are out of sync. This is likely because we're in
+ // the middle of shutting down. Just return false and hope the
+ // tracer thread is quitting anyway.
+ return false;
+ }
+
+ // Post an application-defined event to the main thread's event queue
+ // and wait for it to get processed.
+ [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined
+ location:NSMakePoint(0, 0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:NULL
+ subtype:kEventSubtypeTrace
+ data1:0
+ data2:0]
+ atStart:NO];
+ while (!sTracerProcessed) sCondVar->Wait();
+ sTracerProcessed = false;
+ [pool release];
+ return true;
+}
+
+} // namespace mozilla
diff --git a/widget/cocoa/components.conf b/widget/cocoa/components.conf
new file mode 100644
index 0000000000..fdedd9519d
--- /dev/null
+++ b/widget/cocoa/components.conf
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'js_name': 'clipboard',
+ 'cid': '{8b5314ba-db01-11d2-96ce-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/clipboard;1'],
+ 'interfaces': ['nsIClipboard'],
+ 'type': 'nsIClipboard',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ 'overridable': True,
+ },
+]
diff --git a/widget/cocoa/crashtests/373122-1-inner.html b/widget/cocoa/crashtests/373122-1-inner.html
new file mode 100644
index 0000000000..5c14166b75
--- /dev/null
+++ b/widget/cocoa/crashtests/373122-1-inner.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+
+<script>
+function boom()
+{
+ document.body.style.position = "fixed"
+
+ setTimeout(boom2, 1);
+}
+
+function boom2()
+{
+ lappy = document.getElementById("lappy");
+ lappy.style.display = "none"
+
+ setTimeout(boom3, 200);
+}
+
+function boom3()
+{
+ dump("Reloading\n");
+ location.reload();
+}
+
+</script>
+
+
+</head>
+
+
+<body bgcolor="black" onload="boom()">
+
+ <span style="overflow: scroll; display: -moz-box;"></span>
+
+ <embed id="lappy" src="" width=550 height=400 TYPE="application/x-shockwave-flash" ></embed>
+
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/373122-1.html b/widget/cocoa/crashtests/373122-1.html
new file mode 100644
index 0000000000..a57e5f4249
--- /dev/null
+++ b/widget/cocoa/crashtests/373122-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="373122-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/397209-1.html b/widget/cocoa/crashtests/397209-1.html
new file mode 100644
index 0000000000..554b2dac72
--- /dev/null
+++ b/widget/cocoa/crashtests/397209-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+<body>
+<button style="width: 8205em;"></button>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/403296-1.xhtml b/widget/cocoa/crashtests/403296-1.xhtml
new file mode 100644
index 0000000000..800eaa3558
--- /dev/null
+++ b/widget/cocoa/crashtests/403296-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ class="reftest-wait"
+ style="margin: 12em; padding: 20px 10em; opacity: 0.2; font-size: 11.2px; -moz-appearance: toolbar; white-space: nowrap;"><body
+ style="position: absolute;"
+ onload="setTimeout(function() { document.body.removeChild(document.getElementById('tr')); document.documentElement.removeAttribute('class'); }, 30);">
+
+xxx
+yyy
+
+<tr id="tr">300</tr></body></html>
diff --git a/widget/cocoa/crashtests/419737-1.html b/widget/cocoa/crashtests/419737-1.html
new file mode 100644
index 0000000000..fe6e4532b4
--- /dev/null
+++ b/widget/cocoa/crashtests/419737-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div><span style="-moz-appearance: radio; padding: 15000px;"></span></div>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/435223-1.html b/widget/cocoa/crashtests/435223-1.html
new file mode 100644
index 0000000000..c7f70860fc
--- /dev/null
+++ b/widget/cocoa/crashtests/435223-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div style="min-width: max-content;"><div style="-moz-appearance: button;"><div style="margin: 0 100%;"></div></div></div>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/444260-1.xhtml b/widget/cocoa/crashtests/444260-1.xhtml
new file mode 100644
index 0000000000..f1a84023df
--- /dev/null
+++ b/widget/cocoa/crashtests/444260-1.xhtml
@@ -0,0 +1,3 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<hbox><button width="7788025414616">S</button></hbox>
+</window>
diff --git a/widget/cocoa/crashtests/444864-1.html b/widget/cocoa/crashtests/444864-1.html
new file mode 100644
index 0000000000..f8bac76e6a
--- /dev/null
+++ b/widget/cocoa/crashtests/444864-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="padding: 10px;"><input type="button" value="Go" style="letter-spacing: 331989pt;"></div>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/449111-1.html b/widget/cocoa/crashtests/449111-1.html
new file mode 100644
index 0000000000..4494591803
--- /dev/null
+++ b/widget/cocoa/crashtests/449111-1.html
@@ -0,0 +1,4 @@
+<html>
+<head></head>
+<body><div style="display: -moz-box; word-spacing: 549755813889px;"><button>T </button></div></body>
+</html>
diff --git a/widget/cocoa/crashtests/460349-1.xhtml b/widget/cocoa/crashtests/460349-1.xhtml
new file mode 100644
index 0000000000..cc9b9700c7
--- /dev/null
+++ b/widget/cocoa/crashtests/460349-1.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head></head>
+<body><div><mstyle xmlns="http://www.w3.org/1998/Math/MathML" style="-moz-appearance: button;"/></div></body>
+</html>
diff --git a/widget/cocoa/crashtests/460387-1.html b/widget/cocoa/crashtests/460387-1.html
new file mode 100644
index 0000000000..cab7e7eb32
--- /dev/null
+++ b/widget/cocoa/crashtests/460387-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><head></head><body><div style="display: table; padding: 625203mm; -moz-appearance: menulist;"></div></body></html>
diff --git a/widget/cocoa/crashtests/464589-1.html b/widget/cocoa/crashtests/464589-1.html
new file mode 100644
index 0000000000..d25d92315d
--- /dev/null
+++ b/widget/cocoa/crashtests/464589-1.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var o2 = document.createElement("option");
+ document.getElementById("o1").appendChild(o2);
+ o2.style.padding = "131072cm";
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<select><option id="o1" style="height: 0cm;"></option></select>
+
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/crashtests.list b/widget/cocoa/crashtests/crashtests.list
new file mode 100644
index 0000000000..0559273a43
--- /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 chrome://reftest/content/crashtests/widget/cocoa/crashtests/444260-1.xhtml
+load 444864-1.html
+load 449111-1.html
+load 460349-1.xhtml
+load 460387-1.html
+load 464589-1.html
diff --git a/widget/cocoa/cursors/arrowN.png b/widget/cocoa/cursors/arrowN.png
new file mode 100644
index 0000000000..c65924091a
--- /dev/null
+++ b/widget/cocoa/cursors/arrowN.png
Binary files differ
diff --git a/widget/cocoa/cursors/arrowN@2x.png b/widget/cocoa/cursors/arrowN@2x.png
new file mode 100644
index 0000000000..496f856a1d
--- /dev/null
+++ b/widget/cocoa/cursors/arrowN@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/arrowS.png b/widget/cocoa/cursors/arrowS.png
new file mode 100644
index 0000000000..3975e0837b
--- /dev/null
+++ b/widget/cocoa/cursors/arrowS.png
Binary files differ
diff --git a/widget/cocoa/cursors/arrowS@2x.png b/widget/cocoa/cursors/arrowS@2x.png
new file mode 100644
index 0000000000..c7f817afd4
--- /dev/null
+++ b/widget/cocoa/cursors/arrowS@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/cell.png b/widget/cocoa/cursors/cell.png
new file mode 100644
index 0000000000..1400d93f6e
--- /dev/null
+++ b/widget/cocoa/cursors/cell.png
Binary files differ
diff --git a/widget/cocoa/cursors/cell@2x.png b/widget/cocoa/cursors/cell@2x.png
new file mode 100644
index 0000000000..5a1543b16b
--- /dev/null
+++ b/widget/cocoa/cursors/cell@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/colResize.png b/widget/cocoa/cursors/colResize.png
new file mode 100644
index 0000000000..ef4b24936b
--- /dev/null
+++ b/widget/cocoa/cursors/colResize.png
Binary files differ
diff --git a/widget/cocoa/cursors/colResize@2x.png b/widget/cocoa/cursors/colResize@2x.png
new file mode 100644
index 0000000000..d868ee6b57
--- /dev/null
+++ b/widget/cocoa/cursors/colResize@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/help.png b/widget/cocoa/cursors/help.png
new file mode 100644
index 0000000000..7070b4b7bc
--- /dev/null
+++ b/widget/cocoa/cursors/help.png
Binary files differ
diff --git a/widget/cocoa/cursors/help@2x.png b/widget/cocoa/cursors/help@2x.png
new file mode 100644
index 0000000000..7ddf157cd3
--- /dev/null
+++ b/widget/cocoa/cursors/help@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/move.png b/widget/cocoa/cursors/move.png
new file mode 100644
index 0000000000..f6291500a4
--- /dev/null
+++ b/widget/cocoa/cursors/move.png
Binary files differ
diff --git a/widget/cocoa/cursors/move@2x.png b/widget/cocoa/cursors/move@2x.png
new file mode 100644
index 0000000000..d094ce7a15
--- /dev/null
+++ b/widget/cocoa/cursors/move@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/rowResize.png b/widget/cocoa/cursors/rowResize.png
new file mode 100644
index 0000000000..7f68a4007c
--- /dev/null
+++ b/widget/cocoa/cursors/rowResize.png
Binary files differ
diff --git a/widget/cocoa/cursors/rowResize@2x.png b/widget/cocoa/cursors/rowResize@2x.png
new file mode 100644
index 0000000000..e0987a3b95
--- /dev/null
+++ b/widget/cocoa/cursors/rowResize@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNE.png b/widget/cocoa/cursors/sizeNE.png
new file mode 100644
index 0000000000..fd71dea6ab
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNE.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNE@2x.png b/widget/cocoa/cursors/sizeNE@2x.png
new file mode 100644
index 0000000000..400f5fe46f
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNE@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNESW.png b/widget/cocoa/cursors/sizeNESW.png
new file mode 100644
index 0000000000..5b2c300f4a
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNESW.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNESW@2x.png b/widget/cocoa/cursors/sizeNESW@2x.png
new file mode 100644
index 0000000000..7299b98be1
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNESW@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNS.png b/widget/cocoa/cursors/sizeNS.png
new file mode 100644
index 0000000000..12b1602f14
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNS.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNS@2x.png b/widget/cocoa/cursors/sizeNS@2x.png
new file mode 100644
index 0000000000..0dc7d15d75
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNS@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNW.png b/widget/cocoa/cursors/sizeNW.png
new file mode 100644
index 0000000000..57d270e0db
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNW.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNW@2x.png b/widget/cocoa/cursors/sizeNW@2x.png
new file mode 100644
index 0000000000..312ee61ce4
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNW@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNWSE.png b/widget/cocoa/cursors/sizeNWSE.png
new file mode 100644
index 0000000000..d33c2486e4
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNWSE.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNWSE@2x.png b/widget/cocoa/cursors/sizeNWSE@2x.png
new file mode 100644
index 0000000000..ecf1438265
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNWSE@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeSE.png b/widget/cocoa/cursors/sizeSE.png
new file mode 100644
index 0000000000..1689138419
--- /dev/null
+++ b/widget/cocoa/cursors/sizeSE.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeSE@2x.png b/widget/cocoa/cursors/sizeSE@2x.png
new file mode 100644
index 0000000000..7abce00fd5
--- /dev/null
+++ b/widget/cocoa/cursors/sizeSE@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeSW.png b/widget/cocoa/cursors/sizeSW.png
new file mode 100644
index 0000000000..5eadafb054
--- /dev/null
+++ b/widget/cocoa/cursors/sizeSW.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeSW@2x.png b/widget/cocoa/cursors/sizeSW@2x.png
new file mode 100644
index 0000000000..b9ad862aa5
--- /dev/null
+++ b/widget/cocoa/cursors/sizeSW@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/vtIBeam.png b/widget/cocoa/cursors/vtIBeam.png
new file mode 100644
index 0000000000..4609922319
--- /dev/null
+++ b/widget/cocoa/cursors/vtIBeam.png
Binary files differ
diff --git a/widget/cocoa/cursors/vtIBeam@2x.png b/widget/cocoa/cursors/vtIBeam@2x.png
new file mode 100644
index 0000000000..a001362b04
--- /dev/null
+++ b/widget/cocoa/cursors/vtIBeam@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/zoomIn.png b/widget/cocoa/cursors/zoomIn.png
new file mode 100644
index 0000000000..7ddfd4056d
--- /dev/null
+++ b/widget/cocoa/cursors/zoomIn.png
Binary files differ
diff --git a/widget/cocoa/cursors/zoomIn@2x.png b/widget/cocoa/cursors/zoomIn@2x.png
new file mode 100644
index 0000000000..b1b844fa8c
--- /dev/null
+++ b/widget/cocoa/cursors/zoomIn@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/zoomOut.png b/widget/cocoa/cursors/zoomOut.png
new file mode 100644
index 0000000000..ebacf25889
--- /dev/null
+++ b/widget/cocoa/cursors/zoomOut.png
Binary files differ
diff --git a/widget/cocoa/cursors/zoomOut@2x.png b/widget/cocoa/cursors/zoomOut@2x.png
new file mode 100644
index 0000000000..5f84b767ec
--- /dev/null
+++ b/widget/cocoa/cursors/zoomOut@2x.png
Binary files differ
diff --git a/widget/cocoa/docs/index.md b/widget/cocoa/docs/index.md
new file mode 100644
index 0000000000..46c3da066d
--- /dev/null
+++ b/widget/cocoa/docs/index.md
@@ -0,0 +1,11 @@
+# Firefox on macOS
+
+```eval_rst
+.. toctree::
+ :titlesonly:
+ :maxdepth: 1
+ :glob:
+
+ *
+
+```
diff --git a/widget/cocoa/docs/macos-apis.md b/widget/cocoa/docs/macos-apis.md
new file mode 100644
index 0000000000..f1f530aeea
--- /dev/null
+++ b/widget/cocoa/docs/macos-apis.md
@@ -0,0 +1,187 @@
+# Using macOS APIs
+
+With each new macOS release, new APIs are added. Due to the wide range of platforms that Firefox runs on,
+and due to the [wide range of SDKs that we support building with](./sdks.html#supported-sdks),
+using macOS APIs in Firefox requires some extra care.
+
+## Availability of APIs, and runtime checks
+
+First of all, if you use an API that is supported by all versions of macOS that Firefox runs on,
+i.e. 10.9 and above, then you don't need to worry about anything:
+The API declaration will be present in any of the supported SDKs, and you don't need any runtime checks.
+
+If you want to use a macOS API that was added after 10.9, then you have to have a runtime check.
+This requirement is completely independent of what SDK is being used for building.
+
+The runtime check [should have the following form](https://developer.apple.com/documentation/macos_release_notes/macos_mojave_10_14_release_notes/appkit_release_notes_for_macos_10_14?language=objc#3014609)
+(replace `10.14` with the appropriate version):
+
+```objc++
+if (@available(macOS 10.14, *)) {
+ // Code for macOS 10.14 or later
+} else {
+ // Code for versions earlier than 10.14.
+}
+```
+
+`@available` guards can be used in Objective-C(++) code.
+(In C++ code, you can use [these `nsCocoaFeatures` methods](https://searchfox.org/mozilla-central/rev/9ad88f80aeedcd3cd7d7f63be07f577861727054/widget/cocoa/nsCocoaFeatures.h#21-27) instead.)
+
+For each API, the API declarations in the SDK headers are annotated with `API_AVAILABLE` macros.
+For example, the definition of the `NSVisualEffectMaterial` enum looks like this:
+
+```objc++
+typedef NS_ENUM(NSInteger, NSVisualEffectMaterial) {
+ NSVisualEffectMaterialTitlebar = 3,
+ NSVisualEffectMaterialSelection = 4,
+ NSVisualEffectMaterialMenu API_AVAILABLE(macos(10.11)) = 5,
+ // [...]
+ NSVisualEffectMaterialSheet API_AVAILABLE(macos(10.14)) = 11,
+ // [...]
+} API_AVAILABLE(macos(10.10));
+```
+
+The compiler understands these annotations and makes sure that you wrap all uses of the annotated APIs
+in appropriate `@available` runtime checks.
+
+### Frameworks
+
+In some rare cases, you need functionality from frameworks that are not available on all supported macOS versions.
+Examples of this are `Metal.framework` (added in 10.11) and `MediaPlayer.framework` (added in 10.12.2).
+
+In that case, you can either `dlopen` your framework at runtime ([like we do for MediaPlayer](https://searchfox.org/mozilla-central/rev/9ad88f80aeedcd3cd7d7f63be07f577861727054/widget/cocoa/MediaPlayerWrapper.mm#21-27)),
+or you can [use `-weak_framework`](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html#//apple_ref/doc/uid/20002378-107026)
+[like we do for Metal](https://searchfox.org/mozilla-central/rev/9ad88f80aeedcd3cd7d7f63be07f577861727054/toolkit/library/moz.build#301-304):
+
+```python
+if CONFIG['OS_ARCH'] == 'Darwin':
+ OS_LIBS += [
+ # Link to Metal as required by the Metal gfx-hal backend
+ '-weak_framework Metal',
+ ]
+```
+
+## Using new APIs with old SDKs
+
+If you want to use an API that was introduced after 10.12, you now have one extra thing to worry about.
+In addition to the runtime check [described in the previous section](#using-macos-apis), you also
+have to jump through extra hoops in order to allow the build to succeed with older SDKs, because
+[we need to support building Firefox with SDK versions all the way down to the 10.12 SDK](./sdks.html#supported-sdks).
+
+In order to make the compiler accept your code, you will need to copy some amount of the API declaration
+into your own code. Copy it from the newest recent SDK you can get your hands on.
+The exact procedure varies based on the type of API (enum, objc class, method, etc.),
+but the general approach looks like this:
+
+```objc++
+#if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
+@interface NSWindow (AutomaticWindowTabbing)
+@property (class) BOOL allowsAutomaticWindowTabbing API_AVAILABLE(macos(10.12));
+@end
+#endif
+```
+
+See the [Supporting Multiple SDKs](./sdks.html#supporting-multiple-sdks) docs for more information on the `MAC_OS_X_VERSION_MAX_ALLOWED` macro.
+
+Keep these three things in mind:
+
+ - Copy only what you need.
+ - Wrap your declaration in `MAC_OS_X_VERSION_MAX_ALLOWED` checks so that, if an SDK is used that
+ already contains these declarations, your declaration does not conflict with the declaration in the SDK.
+ - Include the `API_AVAILABLE` annotations so that the compiler can protect you from accidentally
+ calling the API on unsupported macOS versions.
+
+Our current code does not always follow the `API_AVAILABLE` advice, but it should.
+
+### Enum types and C structs
+
+If you need a new enum type or C struct, copy the entire type declaration and wrap it in the appropriate ifdefs. Example:
+
+```objc++
+#if !defined(MAC_OS_X_VERSION_10_12_2) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12_2
+typedef NS_ENUM(NSUInteger, MPNowPlayingPlaybackState) {
+ MPNowPlayingPlaybackStateUnknown = 0,
+ MPNowPlayingPlaybackStatePlaying,
+ MPNowPlayingPlaybackStatePaused,
+ MPNowPlayingPlaybackStateStopped,
+ MPNowPlayingPlaybackStateInterrupted
+} MP_API(ios(11.0), tvos(11.0), macos(10.12.2), watchos(5.0));
+#endif
+```
+### New enum values for existing enum type
+
+If the enum type itself already exists, but gained a new value, define the value in an unnamed enum:
+
+```objc++
+#if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
+enum { NSVisualEffectMaterialSelection = 4 };
+#endif
+```
+
+(This is an example of an interesting case: `NSVisualEffectMaterialSelection` is available starting with
+macOS 10.10, but it's only defined in SDKs starting with the 10.12 SDK.)
+
+### Objective-C classes
+
+For a new Objective-C class, copy the entire `@interface` declaration and wrap it in the appropriate ifdefs.
+
+I haven't personally tested this. If this does not compile (or maybe link?), you can use the following workaround:
+
+ - Define your methods and properties as a category on `NSObject`.
+ - Look up the class at runtime using `NSClassFromString()`.
+ - If you need to create a subclass, do it at runtime using `objc_allocateClassPair` and `class_addMethod`.
+ [Here's an example of that.](https://searchfox.org/mozilla-central/rev/9ad88f80aeedcd3cd7d7f63be07f577861727054/widget/cocoa/VibrancyManager.mm#44-60)
+
+### Objective-C properties and methods on an existing class
+
+If an Objective-C class that already exists gains a new method or property, you can "add" it to the
+existing class declaration with the help of a [category](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html):
+
+```objc++
+@interface ExistingClass (YourMadeUpCategoryName)
+// methods and properties here
+@end
+```
+
+### Functions
+
+With free-standing functions I'm not entirely sure what to do.
+In theory, copying the declarations from the new SDK headers should work. Example:
+
+```objc++
+extern "C" {
+ __attribute__((warn_unused_result)) bool
+SecTrustEvaluateWithError(SecTrustRef trust, CFErrorRef _Nullable * _Nullable CF_RETURNS_RETAINED error)
+ API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0));
+
+ __nullable
+CFDataRef SecCertificateCopyNormalizedSubjectSequence(SecCertificateRef certificate)
+ __OSX_AVAILABLE_STARTING(__MAC_10_12_4, __IPHONE_10_3);
+}
+```
+
+I'm not sure what the linker or the dynamic linker do when the symbol is not available.
+Does this require [`__attribute__((weak_import))` annotations](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html#//apple_ref/doc/uid/20002378-107262-CJBJAEID)?
+
+And maybe this is where .tbd files in the SDK come in? So that the linker knows which symbols to allow?
+So then that part cannot be worked around by copying code from headers.
+
+Anyway, what always works is the pure runtime approach:
+
+ 1. Define types for the functions you need, but not the functions themselves.
+ 2. At runtime, look up the functions using `dlsym`.
+
+## Notes on Rust
+
+If you call macOS APIs from Rust code, you're kind of on your own. Apple does not provide any Rust
+"headers", so there isn't really an SDK to speak of. So you have to supply your own API declarations
+anyway, regardless of what SDK is being used for building.
+
+In a way, you're side-stepping some of the build time trouble. You don't need to worry about any
+`#ifdefs` because there are no system headers you could conflict with.
+
+On the other hand, you still need to worry about API availability at runtime.
+And in Rust, there are no [availability attributes](https://clang.llvm.org/docs/AttributeReference.html#availability)
+on your API declarations, and there are no
+[`@available` runtime check helpers](https://clang.llvm.org/docs/LanguageExtensions.html#objective-c-available),
+and the compiler cannot warn you if you call APIs outside of availability checks.
diff --git a/widget/cocoa/docs/sdks.md b/widget/cocoa/docs/sdks.md
new file mode 100644
index 0000000000..d705b4fb52
--- /dev/null
+++ b/widget/cocoa/docs/sdks.md
@@ -0,0 +1,240 @@
+# A primer on macOS SDKs
+
+## Overview
+
+A macOS SDK is an on-disk directory that contains header files and meta information for macOS APIs.
+Apple distributes SDKs as part of the Xcode app bundle. Each Xcode version comes with one macOS SDK,
+the SDK for the most recent released version of macOS at the time of the Xcode release.
+The SDK is located at `/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk`.
+
+Compiling Firefox for macOS requires a macOS SDK. The build system uses the SDK from Xcode.app by
+default, and you can select a different SDK using the `mozconfig` option `--with-macos-sdk`:
+
+```text
+ac_add_options --with-macos-sdk=/Users/username/SDKs/MacOSX10.12.sdk
+```
+
+## Supported SDKs
+
+First off, Firefox runs on 10.9 and above. This is called the "minimum deployment target" and is
+independent of the SDK version.
+
+Our official Firefox builds compiled in CI (continuous integration) currently use the 10.12 SDK.
+[Bug 1475652](https://bugzilla.mozilla.org/show_bug.cgi?id=1475652) tracks updating this SDK.
+
+For local builds, all SDKs from 10.12 to 10.15 are supported. Firefox should compile successfully
+with all of those SDKs, but minor differences in runtime behavior can occur.
+
+However, since only the 10.12 SDK is used in CI, compiling with different SDKs breaks from time to time.
+Such breakages should be [reported in Bugzilla](https://bugzilla.mozilla.org/enter_bug.cgi?blocked=mach-busted&bug_type=defect&cc=:spohl,:mstange&component=General&form_name=enter_bug&keywords=regression&op_sys=macOS&product=Firefox%20Build%20System&rep_platform=All) and fixed quickly.
+
+Aside: Firefox seems to be a bit of a special snowflake with its ability to build with an arbitrary SDK.
+For example, at the time of this writing (June 2020),
+[building Chrome requires the 10.15 SDK](https://chromium.googlesource.com/chromium/src/+/master/docs/mac_build_instructions.md#system-requirements).
+Some apps even require a certain version of Xcode and only support building with the SDK of that Xcode version.
+
+Why are we using such an old SDK in CI, you ask? It basically comes down to the fact that macOS
+hardware is expensive, and the fact that the compilers and linkers supplied by Xcode don't run on Linux.
+
+## Obtaining SDKs
+
+Sometimes you need an SDK that's different from the one in your Xcode.app, for example
+to check whether your code change breaks building with other SDKs, or to verify the
+runtime behavior with the SDK used for CI builds.
+
+The easy but slightly questionable way to obtain an SDK is to download it from a public github repo.
+
+Here's another option:
+
+ 1. Have your Apple ID login details ready, and bring enough time and patience for a 5GB download.
+ 2. Check [these tables in the Xcode wikipedia article](https://en.wikipedia.org/wiki/Xcode#Xcode_7.0_-_10.x_(since_Free_On-Device_Development))
+ and find an Xcode version that contains the SDK you need.
+ 3. Look up the Xcode version number on [xcodereleases.com](https://xcodereleases.com/) and click the Download link for it.
+ 4. Log in with your Apple ID. Then the download should start.
+ 5. Wait for the 5GB Xcode_*.xip download to finish.
+ 6. Open the downloaded xip file. This will extract the Xcode.app bundle.
+ 7. Inside the app bundle, the SDK is at `Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk`.
+
+## Effects of the SDK version
+
+An SDK only contains declarations of APIs. It does not contain the implementations for these APIs.
+
+The implementation of an API is provided by the OS that the app runs on. It is supplied at runtime,
+when your app starts up, by the dynamic linker. For example, the AppKit implementation comes
+from `/System/Library/Frameworks/AppKit.framework` from the OS that the app is run on, regardless
+of what SDK was used when compiling the app.
+
+In other words, building with a macOS SDK of a higher version doesn't magically make new APIs available
+when running on older versions of macOS. And, conversely, building with a lower macOS SDK doesn't limit
+which APIs you can use if your app is run on a newer version of macOS, assuming you manage to convince the
+compiler to accept your code.
+
+The SDK used for building an app determines three things:
+
+ 1. Whether your code compiles at all,
+ 2. which range of macOS versions your app can run on (available deployment targets), and
+ 3. certain aspects of runtime behavior.
+
+The first is straightforward: An SDK contains header files. If you call an API that's not declared
+anywhere - neither in a header file nor in your own code - then your compiler will emit an error.
+(Special case: Calling an unknown Objective-C method usually only emits a warning, not an error.)
+
+The second aspect, available deployment targets, is usually not worth worrying about:
+SDKs have large ranges of supported macOS deployment targets.
+For example, the 10.15 SDK supports running your app on macOS versions all the way back to 10.6.
+This information is written down in the SDK's `SDKSettings.plist`.
+
+The third aspect, varying runtime behavior, is perhaps the most insidious and surprising aspect, and is described
+in the next section.
+
+## Runtime differences based on macOS SDK version
+
+When a new version of macOS is released, existing APIs can change their behavior.
+These changes are usually described in the AppKit release notes:
+
+ - [macOS 10.15 release notes](https://developer.apple.com/documentation/macos_release_notes/macos_catalina_10_15_release_notes?language=objc)
+ - [macOS 10.14 AppKit release notes](https://developer.apple.com/documentation/macos_release_notes/macos_mojave_10_14_release_notes/appkit_release_notes_for_macos_10_14?language=objc)
+ - [macOS 10.13 AppKit release notes](https://developer.apple.com/library/archive/releasenotes/AppKit/RN-AppKit/)
+ - [macOS 10.12 and older AppKit release notes](https://developer.apple.com/library/archive/releasenotes/AppKit/RN-AppKitOlderNotes/)
+
+Sometimes, these differences in behavior have the potential to break existing apps. In those instances,
+Apple often provides the old (compatible) behavior until the app is re-built with the new SDK, expecting
+developers to update their apps so that they work with the new behavior, at the same time as
+they update to the new SDK.
+
+Here's an [example from the 10.13 release notes](https://developer.apple.com/library/archive/releasenotes/AppKit/RN-AppKit/#10_13NSCollectionView%20Responsive%20Scrolling):
+
+> Responsive Scrolling in NSCollectionViews is enabled only for apps linked on or after macOS 10.13.
+
+Here, "linked on or after macOS 10.13" means "linked against the macOS 10.13 SDK or newer".
+
+Apple's expectation is that you upgrade to the new macOS version when it is released, download a new
+Xcode version when it is released, synchronize these updates across the machines of all developers
+that work on your app, use the SDK in the newest Xcode to compile your app, and make changes to your
+app to be compatible with any behavior changes whenever you update Xcode.
+This expectation does not always match reality. It definitely doesn't match what we're doing with Firefox.
+
+For Firefox, SDK-dependent compatibility behaviors mean that developers who build Firefox locally
+can see different runtime behavior than the users of our CI builds, if they use a different SDK than
+the SDK used in CI.
+That is, unless we change the Firefox code so that it has the same behavior regardless of SDK version.
+Often this can be achieved by using APIs in a way that's more in line with the API's recommended use.
+
+For example, we've had cases of
+[broken placeholder text in search fields](https://bugzilla.mozilla.org/show_bug.cgi?id=1273106),
+[missing](https://bugzilla.mozilla.org/show_bug.cgi?id=941325) or [double-drawn focus rings](https://searchfox.org/mozilla-central/rev/9ad88f80aeedcd3cd7d7f63be07f577861727054/widget/cocoa/nsNativeThemeCocoa.mm#149-169),
+[a startup crash](https://bugzilla.mozilla.org/show_bug.cgi?id=1516437),
+[fully black windows](https://bugzilla.mozilla.org/show_bug.cgi?id=1494022),
+[fully gray windows](https://bugzilla.mozilla.org/show_bug.cgi?id=1576113#c4),
+[broken vibrancy](https://bugzilla.mozilla.org/show_bug.cgi?id=1475694), and
+[broken colors in dark mode](https://bugzilla.mozilla.org/show_bug.cgi?id=1578917).
+
+In most of these cases, the breakage was either very minor, or it was caused by Firefox doing things
+that were explicitly discouraged, like creating unexpected NSView hierarchies, or relying on unspecified
+implementation details. (With one exception: In 10.14, HiDPI-aware `NSOpenGLContext` rendering in
+layer-backed windows simply broke.)
+
+And in all of these cases, it was the SDK-dependent compatibility behavior that protected our users from being
+exposed to the breakage. Our CI builds continued to work because they were built with an older SDK.
+
+We have addressed all known cases of breakage when building Firefox with newer SDKs.
+I am not aware of any current instances of this problem as of this writing (June 2020).
+
+For more information about how these compatibility tricks work,
+read the [Overriding SDK-dependent runtime behavior](#overriding-sdk-dependent-runtime-behavior) section.
+
+## Supporting multiple SDKs
+
+As described under [Supported SDKs](#supported-sdks), Firefox can be built with a wide variety of SDK versions.
+
+This ability comes at the cost of some manual labor; it requires some well-placed `#ifdefs` and
+copying of header definitions.
+
+Every SDK defines the macro `MAC_OS_X_VERSION_MAX_ALLOWED` with a value that matches the SDK version,
+in the SDK's `AvailabilityMacros.h` header. This header also defines version constants like `MAC_OS_X_VERSION_10_12`.
+For example, I have a version of the 10.12 SDK which contains the line
+
+```cpp
+#define MAC_OS_X_VERSION_MAX_ALLOWED MAC_OS_X_VERSION_10_12_4
+```
+
+The name `MAC_OS_X_VERSION_MAX_ALLOWED` is rather misleading; a better name would be
+`MAC_OS_X_VERSION_MAX_KNOWN_BY_SDK`. Compiling with an old SDK *does not* prevent apps from running
+on newer versions of macOS.
+
+With the help of the `MAC_OS_X_VERSION_MAX_ALLOWED` macro, we can make our code adapt to the SDK that's
+being used. Here's [an example](https://searchfox.org/mozilla-central/rev/9ad88f80aeedcd3cd7d7f63be07f577861727054/toolkit/xre/MacApplicationDelegate.mm#345-351) where the 10.14 SDK changed the signature of
+[an `NSApplicationDelegate` method](https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428471-application?language=objc):
+
+```objc++
+- (BOOL)application:(NSApplication*)application
+ continueUserActivity:(NSUserActivity*)userActivity
+#if defined(MAC_OS_X_VERSION_10_14) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14
+ restorationHandler:(void (^)(NSArray<id<NSUserActivityRestoring>>*))restorationHandler {
+#else
+ restorationHandler:(void (^)(NSArray*))restorationHandler {
+#endif
+ ...
+}
+```
+
+We can also use this macro to supply missing API definitions in such a way that
+they don't conflict with the definitions from the SDK.
+This is described in the "Using macOS APIs" document, under [Using new APIs with old SDKs](./macos-apis.html#using-new-apis-with-old-sdks).
+
+## Overriding SDK-dependent runtime behavior
+
+This section contains some more details on the compatibility tricks that cause different runtime
+behavior dependent on the SDK, as described in
+[Runtime differences based on macOS SDK version](#runtime-differences-based-on-macos-sdk-version).
+
+### How it works
+
+AppKit is the one system framework I know of that employs these tricks. Let's explore how AppKit makes this work,
+by going back to the [NSCollectionView example](https://developer.apple.com/library/archive/releasenotes/AppKit/RN-AppKit/#10_13NSCollectionView%20Responsive%20Scrolling) from above:
+
+> Responsive Scrolling in NSCollectionViews is enabled only for apps linked on or after macOS 10.13.
+
+For each of these SDK-dependent behavior differences, both the old and the new behavior are implemented
+in the version of AppKit that ships with the new macOS version.
+At runtime, AppKit selects one of the behaviors based on the SDK version, with a call to
+`_CFExecutableLinkedOnOrAfter()`. This call checks the SDK version of the main executable of the
+process that's running AppKit code; in our case that's the `firefox` or `plugin-container` executable.
+The SDK version is stored in the mach-o headers of the executable by the linker.
+
+One interesting design aspect of AppKit's compatibility tricks is the fact that most of these behavior differences
+can be toggled with a "user default" preference.
+For example, the "responsive scrolling in NSCollectionViews" behavior change can be controlled with
+a user default with the name "NSCollectionViewPrefetchingEnabled".
+The SDK check only happens if "NSCollectionViewPrefetchingEnabled" is not set to either YES or NO.
+
+More precisely, this example works as follows:
+
+ - `-[NSCollectionView prepareContentInRect:]` is the function that supports both the old and the new behavior.
+ - It calls `_NSGetBoolAppConfig` for the value "NSCollectionViewPrefetchingEnabled", and also supplies a "default
+ value function".
+ - If the user default is not set, the default value function is called. This function has the name
+ `NSCollectionViewPrefetchingEnabledDefaultValueFunction`.
+ - `NSCollectionViewPrefetchingEnabledDefaultValueFunction` calls `_CFExecutableLinkedOnOrAfter(13)`.
+
+You can find many similar toggles if you list the AppKit symbols that end in `DefaultValueFunction`,
+for example by executing `nm /System/Library/Frameworks/AppKit.framework/AppKit | grep DefaultValueFunction`.
+
+### Overriding SDK-dependent runtime behavior
+
+You can set these preferences programmatically, in a way that `_NSGetBoolAppConfig()` can pick them up,
+for example with [`registerDefaults`](https://developer.apple.com/documentation/foundation/nsuserdefaults/1417065-registerdefaults?language=objc)
+or like this:
+
+```objc++
+[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSViewAllowsRootLayerBacking"];
+```
+
+The AppKit release notes mention this ability but ask for it to only be used for debugging purposes:
+
+> In some cases, we provide defaults (preferences) settings which can be used to get the old or new behavior,
+> independent of what system an application was built against. Often these preferences are provided for
+> debugging purposes only; in some cases the preferences can be used to globally modify the behavior
+> of an application by registering the values (do it somewhere very early, with `-[NSUserDefaults registerDefaults:]`).
+
+It's interesting that they mention this at all because, as far as I can tell, none of these values are documented.
diff --git a/widget/cocoa/moz.build b/widget/cocoa/moz.build
new file mode 100644
index 0000000000..11f6500a66
--- /dev/null
+++ b/widget/cocoa/moz.build
@@ -0,0 +1,179 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+ SCHEDULES.exclusive = ["macosx"]
+
+with Files("*TextInput*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+XPIDL_SOURCES += [
+ "nsPIWidgetCocoa.idl",
+]
+
+XPIDL_MODULE = "widget_cocoa"
+
+EXPORTS += [
+ "CFTypeRefPtr.h",
+ "DesktopBackgroundImage.h",
+ "MediaHardwareKeysEventSourceMac.h",
+ "MediaHardwareKeysEventSourceMacMediaCenter.h",
+ "mozView.h",
+ "nsBidiKeyboard.h",
+ "nsChangeObserver.h",
+ "nsCocoaDebugUtils.h",
+ "nsCocoaFeatures.h",
+ "nsCocoaUtils.h",
+]
+
+UNIFIED_SOURCES += [
+ "GfxInfo.mm",
+ "IconLoaderHelperCocoa.mm",
+ "NativeKeyBindings.mm",
+ "nsAppShell.mm",
+ "nsBidiKeyboard.mm",
+ "nsCocoaFeatures.mm",
+ "nsCocoaUtils.mm",
+ "nsCocoaWindow.mm",
+ "nsColorPicker.mm",
+ "nsCursorManager.mm",
+ "nsDeviceContextSpecX.mm",
+ "nsFilePicker.mm",
+ "nsLookAndFeel.mm",
+ "nsMacCursor.mm",
+ "nsMacDockSupport.mm",
+ "nsMacFinderProgress.mm",
+ "nsMacSharingService.mm",
+ "nsMacWebAppUtils.mm",
+ "nsMenuBarX.mm",
+ "nsMenuGroupOwnerX.mm",
+ "nsMenuItemIconX.mm",
+ "nsMenuItemX.mm",
+ "nsMenuUtilsX.mm",
+ "nsMenuX.mm",
+ "nsNativeBasicThemeCocoa.cpp",
+ "nsPrintDialogX.mm",
+ "nsPrintSettingsServiceX.mm",
+ "nsPrintSettingsX.mm",
+ "nsSound.mm",
+ "nsStandaloneNativeMenu.mm",
+ "nsSystemStatusBarCocoa.mm",
+ "nsToolkit.mm",
+ "nsTouchBar.mm",
+ "nsTouchBarInput.mm",
+ "nsTouchBarInputIcon.mm",
+ "nsTouchBarUpdater.mm",
+ "nsUserIdleServiceX.mm",
+ "nsWidgetFactory.mm",
+ "nsWindowMap.mm",
+ "OSXNotificationCenter.mm",
+ "ScreenHelperCocoa.mm",
+ "SwipeTracker.mm",
+ "TextInputHandler.mm",
+ "VibrancyManager.mm",
+ "ViewRegion.mm",
+ "WidgetTraceEvent.mm",
+]
+
+# These files cannot be built in unified mode because they cause symbol conflicts
+SOURCES += [
+ "DesktopBackgroundImage.mm",
+ "MediaHardwareKeysEventSourceMac.mm",
+ "MediaHardwareKeysEventSourceMacMediaCenter.mm",
+ "MediaKeysEventSourceFactory.cpp",
+ "nsChildView.mm",
+ "nsClipboard.mm",
+ "nsCocoaDebugUtils.mm",
+ "nsDragService.mm",
+ "nsNativeThemeCocoa.mm",
+]
+
+if not CONFIG["RELEASE_OR_BETA"] or CONFIG["MOZ_DEBUG"]:
+ SOURCES += [
+ "nsSandboxViolationSink.mm",
+ ]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "/dom/media/platforms/apple",
+ "/layout/base",
+ "/layout/forms",
+ "/layout/generic",
+ "/layout/style",
+ "/layout/xul",
+ "/widget",
+ "/widget/headless",
+]
+
+if CONFIG["MOZ_ENABLE_SKIA_PDF"]:
+ # Skia includes because widget code includes PrintTargetSkPDF.h, and that
+ # includes skia headers.
+ LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"]
+
+RESOURCE_FILES.cursors += [
+ "cursors/arrowN.png",
+ "cursors/arrowN@2x.png",
+ "cursors/arrowS.png",
+ "cursors/arrowS@2x.png",
+ "cursors/cell.png",
+ "cursors/cell@2x.png",
+ "cursors/colResize.png",
+ "cursors/colResize@2x.png",
+ "cursors/help.png",
+ "cursors/help@2x.png",
+ "cursors/move.png",
+ "cursors/move@2x.png",
+ "cursors/rowResize.png",
+ "cursors/rowResize@2x.png",
+ "cursors/sizeNE.png",
+ "cursors/sizeNE@2x.png",
+ "cursors/sizeNESW.png",
+ "cursors/sizeNESW@2x.png",
+ "cursors/sizeNS.png",
+ "cursors/sizeNS@2x.png",
+ "cursors/sizeNW.png",
+ "cursors/sizeNW@2x.png",
+ "cursors/sizeNWSE.png",
+ "cursors/sizeNWSE@2x.png",
+ "cursors/sizeSE.png",
+ "cursors/sizeSE@2x.png",
+ "cursors/sizeSW.png",
+ "cursors/sizeSW@2x.png",
+ "cursors/vtIBeam.png",
+ "cursors/vtIBeam@2x.png",
+ "cursors/zoomIn.png",
+ "cursors/zoomIn@2x.png",
+ "cursors/zoomOut.png",
+ "cursors/zoomOut@2x.png",
+]
+
+# These resources go in $(DIST)/bin/res/MainMenu.nib, but we can't use a magic
+# RESOURCE_FILES.MainMenu.nib attribute, since that would put the files in
+# $(DIST)/bin/res/MainMenu/nib. Instead, we call __setattr__ directly to create
+# an attribute with the correct name.
+RESOURCE_FILES.__setattr__(
+ "MainMenu.nib",
+ [
+ "resources/MainMenu.nib/classes.nib",
+ "resources/MainMenu.nib/info.nib",
+ "resources/MainMenu.nib/keyedobjects.nib",
+ ],
+)
+
+CXXFLAGS += CONFIG["TK_CFLAGS"]
+
+OS_LIBS += [
+ "-framework IOSurface",
+]
+
+SPHINX_TREES["/widget/cocoa"] = "docs"
diff --git a/widget/cocoa/mozView.h b/widget/cocoa/mozView.h
new file mode 100644
index 0000000000..deb719254e
--- /dev/null
+++ b/widget/cocoa/mozView.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozView_h_
+#define mozView_h_
+
+#undef DARWIN
+#import <Cocoa/Cocoa.h>
+class nsIWidget;
+
+namespace mozilla {
+namespace widget {
+class TextInputHandler;
+} // namespace widget
+} // namespace mozilla
+
+// A protocol with some of the methods that ChildView implements. In the distant
+// past, this protocol was used by embedders: They would create their own NSView
+// subclass, implement mozView on it, and then embed a Gecko ChildView by adding
+// it as a subview of this view. This scenario no longer exists.
+// Now this protocol is mostly just used by TextInputHandler and mozAccessible
+// in order to communicate with ChildView without seeing the entire ChildView
+// interface definition.
+@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;
+
+// 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..c08f705065
--- /dev/null
+++ b/widget/cocoa/nsAppShell.h
@@ -0,0 +1,68 @@
+/* -*- 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) override;
+
+ nsAppShell();
+
+ nsresult Init();
+
+ NS_IMETHOD Run(void) override;
+ NS_IMETHOD Exit(void) override;
+ NS_IMETHOD OnProcessNextEvent(nsIThreadInternal* aThread, bool aMayWait) override;
+ NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal* aThread, bool aEventWasProcessed) override;
+
+ // public only to be visible to Objective-C code that must call it
+ void WillTerminate();
+
+ protected:
+ virtual ~nsAppShell();
+
+ virtual void ScheduleNativeEventCallback() override;
+ virtual bool ProcessNextNativeEvent(bool aMayWait) override;
+
+ 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..c02655d94d
--- /dev/null
+++ b/widget/cocoa/nsAppShell.mm
@@ -0,0 +1,908 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Runs the main native Cocoa run loop, interrupting it as needed to process
+ * Gecko events.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+#include "CustomCocoaEvents.h"
+#include "mozilla/WidgetTraceEvent.h"
+#include "nsAppShell.h"
+#include "gfxPlatform.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsString.h"
+#include "nsIRollupListener.h"
+#include "nsIWidget.h"
+#include "nsThreadUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsCocoaUtils.h"
+#include "nsChildView.h"
+#include "nsToolkit.h"
+#include "TextInputHandler.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "GeckoProfiler.h"
+#include "ScreenHelperCocoa.h"
+#include "mozilla/Hal.h"
+#include "mozilla/widget/ScreenManager.h"
+#include "HeadlessScreenHelper.h"
+#include "pratom.h"
+#if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+# include "nsSandboxViolationSink.h"
+#endif
+
+#include <IOKit/pwr_mgt/IOPMLib.h>
+#include "nsIDOMWakeLockListener.h"
+#include "nsIPowerManagerService.h"
+
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#define WAKE_LOCK_LOG(...) MOZ_LOG(gMacWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+static mozilla::LazyLogModule gMacWakeLockLog("MacWakeLock");
+
+// 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 mAssertionNoDisplaySleepID = kIOPMNullAssertionID;
+ IOPMAssertionID mAssertionNoIdleSleepID = kIOPMNullAssertionID;
+
+ NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) override {
+ if (!aTopic.EqualsASCII("screen") && !aTopic.EqualsASCII("audio-playing") &&
+ !aTopic.EqualsASCII("video-playing")) {
+ return NS_OK;
+ }
+
+ // we should still hold the lock for background audio.
+ if (aTopic.EqualsASCII("audio-playing") && aState.EqualsASCII("locked-background")) {
+ WAKE_LOCK_LOG("keep audio playing even in background");
+ return NS_OK;
+ }
+
+ bool shouldKeepDisplayOn = aTopic.EqualsASCII("screen") || aTopic.EqualsASCII("video-playing");
+ CFStringRef assertionType =
+ shouldKeepDisplayOn ? kIOPMAssertionTypeNoDisplaySleep : kIOPMAssertionTypeNoIdleSleep;
+ IOPMAssertionID& assertionId =
+ shouldKeepDisplayOn ? mAssertionNoDisplaySleepID : mAssertionNoIdleSleepID;
+ WAKE_LOCK_LOG("topic=%s, state=%s, shouldKeepDisplayOn=%d", NS_ConvertUTF16toUTF8(aTopic).get(),
+ NS_ConvertUTF16toUTF8(aState).get(), shouldKeepDisplayOn);
+
+ // 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")) {
+ if (assertionId != kIOPMNullAssertionID) {
+ WAKE_LOCK_LOG("already has a lock");
+ return NS_OK;
+ }
+ // Prevent screen saver.
+ CFStringRef cf_topic = ::CFStringCreateWithCharacters(
+ kCFAllocatorDefault, reinterpret_cast<const UniChar*>(aTopic.Data()), aTopic.Length());
+ IOReturn success = ::IOPMAssertionCreateWithName(assertionType, kIOPMAssertionLevelOn,
+ cf_topic, &assertionId);
+ CFRelease(cf_topic);
+ if (success != kIOReturnSuccess) {
+ WAKE_LOCK_LOG("failed to disable screensaver");
+ }
+ WAKE_LOCK_LOG("create screensaver");
+ } else {
+ // Re-enable screen saver.
+ if (assertionId != kIOPMNullAssertionID) {
+ IOReturn result = ::IOPMAssertionRelease(assertionId);
+ if (result != kIOReturnSuccess) {
+ WAKE_LOCK_LOG("failed to release screensaver");
+ }
+ WAKE_LOCK_LOG("Release screensaver");
+ assertionId = kIOPMNullAssertionID;
+ }
+ }
+ return NS_OK;
+ }
+}; // MacWakeLockListener
+
+// defined in nsCocoaWindow.mm
+extern int32_t gXULModalLevel;
+
+static bool gAppShellMethodsSwizzled = false;
+
+@implementation GeckoNSApplication
+
+- (void)sendEvent:(NSEvent*)anEvent {
+ // Mark this function as non-idle because it's one of the exit points from
+ // the event loop (running inside of -[GeckoNSApplication nextEventMatchingMask:...])
+ // into non-idle code. So we basically unset the IDLE category from the inside.
+ AUTO_PROFILER_LABEL("-[GeckoNSApplication sendEvent:]", OTHER);
+
+ mozilla::BackgroundHangMonitor().NotifyActivity();
+
+ if ([anEvent type] == NSEventTypeApplicationDefined && [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 {
+ // When we're waiting in the event loop, this is the last function under our
+ // control that's on the stack, so this is the function that we mark with the
+ // IDLE category.
+ // However, when we're processing an event or when our CFRunLoopSource runs,
+ // this function is still on the stack - "the event loop calls us". So we
+ // need to mark functions that enter non-idle code with a different profiler
+ // category, usually OTHER. This gives the profiler a rough approximation of
+ // idleness but isn't perfect. For example, sometimes there's some Cocoa-
+ // internal activity that's triggered from the event loop, and we'll
+ // misidentify the stacks for that activity as idle because there's no Gecko
+ // code on the stack that can change the stack's category to something
+ // non-idle.
+ AUTO_PROFILER_LABEL("-[GeckoNSApplication nextEventMatchingMask:untilDate:inMode:dequeue:]",
+ IDLE);
+
+ if (expiration) {
+ mozilla::BackgroundHangMonitor().NotifyWait();
+ }
+ NSEvent* nextEvent = [super nextEventMatchingMask:mask
+ untilDate:expiration
+ inMode:mode
+ dequeue:flag];
+ if (expiration) {
+ mozilla::BackgroundHangMonitor().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;
+
+ hal::Shutdown();
+
+ if (mCFRunLoop) {
+ if (mCFRunLoopSource) {
+ ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
+ ::CFRelease(mCFRunLoopSource);
+ }
+ ::CFRelease(mCFRunLoop);
+ }
+
+ if (mAutoreleasePools) {
+ NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0,
+ "nsAppShell destroyed without popping all autorelease pools");
+ ::CFRelease(mAutoreleasePools);
+ }
+
+ [mDelegate release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+NS_IMPL_ISUPPORTS(MacWakeLockListener, nsIDOMMozWakeLockListener)
+mozilla::StaticRefPtr<MacWakeLockListener> sWakeLockListener;
+
+static void AddScreenWakeLockListener() {
+ nsCOMPtr<nsIPowerManagerService> sPowerManagerService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (sPowerManagerService) {
+ sWakeLockListener = new MacWakeLockListener();
+ sPowerManagerService->AddWakeLockListener(sWakeLockListener);
+ } else {
+ NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
+ }
+}
+
+static void RemoveScreenWakeLockListener() {
+ nsCOMPtr<nsIPowerManagerService> sPowerManagerService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (sPowerManagerService) {
+ sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
+ sPowerManagerService = nullptr;
+ sWakeLockListener = nullptr;
+ }
+}
+
+// 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);
+
+ bool isNSApplicationProcessType = (XRE_GetProcessType() != GeckoProcessType_RDD) &&
+ (XRE_GetProcessType() != GeckoProcessType_Socket);
+
+ if (isNSApplicationProcessType) {
+ // 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 mainBundle] loadNibNamed:@"res/MainMenu"
+ owner:[GeckoNSApplication sharedApplication]
+ topLevelObjects:nil];
+ }
+
+ 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);
+
+ hal::Init();
+
+ if (XRE_IsParentProcess()) {
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+
+ if (gfxPlatform::IsHeadless()) {
+ screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
+ } else {
+ screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperCocoa>());
+ }
+ }
+
+ nsresult rv = nsBaseAppShell::Init();
+
+ if (isNSApplicationProcessType && !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 !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;
+ AUTO_PROFILER_LABEL("nsAppShell::ProcessGeckoEvents", OTHER);
+
+ nsAppShell* self = static_cast<nsAppShell*>(aInfo);
+
+ if (self->mRunningEventLoop) {
+ self->mRunningEventLoop = false;
+
+ // The run loop may be sleeping -- [NSRunLoop runMode:...]
+ // won't return until it's given a reason to wake up. Awaken it by
+ // posting a bogus event. There's no need to make the event
+ // presentable.
+ //
+ // But _don't_ set windowNumber to '-1' -- that can lead to nasty
+ // weirdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of
+ // these fake events, because the -1 has gotten changed into the number
+ // of an actual NSWindow object, and that NSWindow object has just been
+ // destroyed). Setting windowNumber to '0' seems to work fine -- this
+ // seems to prevent the OS from ever trying to associate our bogus event
+ // with a particular NSWindow object.
+ [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined
+ 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:NSEventTypeApplicationDefined
+ 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::BackgroundHangMonitor().NotifyWait();
+ }
+
+ // 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:NSEventMaskAny
+ untilDate:waitUntil
+ inMode:currentMode
+ dequeue:YES];
+ if (nextEvent) {
+ mozilla::BackgroundHangMonitor().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 != NSEventTypeApplicationDefined)));
+ // 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;
+
+ if (XRE_IsParentProcess()) {
+ AddScreenWakeLockListener();
+ }
+
+ // We use the native Gecko event loop in content processes.
+ nsresult rv = NS_OK;
+ if (XRE_UseNativeEventProcessing()) {
+ NS_OBJC_TRY_ABORT([NSApp run]);
+ } else {
+ rv = nsBaseAppShell::Run();
+ }
+
+ if (XRE_IsParentProcess()) {
+ RemoveScreenWakeLockListener();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAppShell::Exit(void) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // This method is currently called more than once -- from (according to
+ // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an
+ // XPCOM shutdown notification that nsBaseAppShell has registered to
+ // receive. So we need to ensure that multiple calls won't break anything.
+ // But we should also complain about it (since it isn't quite kosher).
+ if (mTerminated) {
+ NS_WARNING("nsAppShell::Exit() called redundantly");
+ return NS_OK;
+ }
+
+ mTerminated = true;
+
+#if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+ nsSandboxViolationSink::Stop();
+#endif
+
+ // Quoting from Apple's doc on the [NSApplication stop:] method (from their
+ // doc on the NSApplication class): "If this method is invoked during a
+ // modal event loop, it will break that loop but not the main event loop."
+ // nsAppShell::Exit() shouldn't be called from a modal event loop. So if
+ // it is we complain about it (to users of debug builds) and call [NSApp
+ // stop:] one extra time. (I'm not sure if modal event loops can be nested
+ // -- Apple's docs don't say one way or the other. But the return value
+ // of [NSApp _isRunningModal] doesn't change immediately after a call to
+ // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:]
+ // will do the job.)
+ BOOL cocoaModal = [NSApp _isRunningModal];
+ NS_ASSERTION(!cocoaModal, "Don't call nsAppShell::Exit() from a modal event loop!");
+ if (cocoaModal) [NSApp stop:nullptr];
+ [NSApp stop:nullptr];
+
+ // A call to Exit() just after a call to ScheduleNativeEventCallback()
+ // prevents the (normally) matching call to ProcessGeckoEvents() from
+ // happening. If we've been called from ProcessGeckoEvents() (as usually
+ // happens), we take care of it there. But if we have an unbalanced call
+ // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the
+ // stack, we need to take care of the problem here.
+ if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) {
+ int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0);
+ while (releaseCount-- > 0) NS_RELEASE_THIS();
+ }
+
+ return nsBaseAppShell::Exit();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// OnProcessNextEvent
+//
+// This nsIThreadObserver method is called prior to processing an event.
+// Set up an autorelease pool that will service any autoreleased Cocoa
+// objects during this event. This includes native events processed by
+// ProcessNextNativeEvent. The autorelease pool will be popped by
+// AfterProcessNextEvent, it is important for these two methods to be
+// tightly coupled.
+//
+// public
+NS_IMETHODIMP
+nsAppShell::OnProcessNextEvent(nsIThreadInternal* aThread, bool aMayWait) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ASSERTION(mAutoreleasePools, "No stack on which to store autorelease pool");
+
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+ ::CFArrayAppendValue(mAutoreleasePools, pool);
+
+ return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// AfterProcessNextEvent
+//
+// This nsIThreadObserver method is called after event processing is complete.
+// The Cocoa implementation cleans up the autorelease pool create by the
+// previous OnProcessNextEvent call.
+//
+// public
+NS_IMETHODIMP
+nsAppShell::AfterProcessNextEvent(nsIThreadInternal* aThread, bool aEventWasProcessed) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
+
+ NS_ASSERTION(mAutoreleasePools && count, "Processed an event, but there's no autorelease pool?");
+
+ const NSAutoreleasePool* pool =
+ static_cast<const NSAutoreleasePool*>(::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1));
+ ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
+ [pool release];
+
+ return nsBaseAppShell::AfterProcessNextEvent(aThread, aEventWasProcessed);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// AppShellDelegate implementation
+
+@implementation AppShellDelegate
+// initWithAppShell:
+//
+// Constructs the AppShellDelegate object
+- (id)initWithAppShell:(nsAppShell*)aAppShell {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [self init])) {
+ mAppShell = aAppShell;
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(applicationWillTerminate:)
+ name:NSApplicationWillTerminateNotification
+ object:NSApp];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(applicationDidBecomeActive:)
+ name:NSApplicationDidBecomeActiveNotification
+ object:NSApp];
+ [[NSDistributedNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(beginMenuTracking:)
+ name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
+ object:nil];
+ }
+
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// applicationWillTerminate:
+//
+// Notify the nsAppShell that native event processing should be discontinued.
+- (void)applicationWillTerminate:(NSNotification*)aNotification {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mAppShell->WillTerminate();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// applicationDidBecomeActive
+//
+// Make sure TextInputHandler::sLastModifierState is updated when we become
+// active (since we won't have received [ChildView flagsChanged:] messages
+// while inactive).
+- (void)applicationDidBecomeActive:(NSNotification*)aNotification {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // [NSEvent modifierFlags] is valid on every kind of event, so we don't need
+ // to worry about getting an NSInternalInconsistencyException here.
+ NSEvent* currentEvent = [NSApp currentEvent];
+ if (currentEvent) {
+ TextInputHandler::sLastModifierState =
+ [currentEvent modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(nullptr, NS_WIDGET_MAC_APP_ACTIVATE_OBSERVER_TOPIC, nullptr);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// beginMenuTracking
+//
+// Roll up our context menu (if any) when some other app (or the OS) opens
+// any sort of menu. But make sure we don't do this for notifications we
+// send ourselves (whose 'sender' will be @"org.mozilla.gecko.PopupWindow").
+- (void)beginMenuTracking:(NSNotification*)aNotification {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSString* sender = [aNotification object];
+ if (!sender || ![sender isEqualToString:@"org.mozilla.gecko.PopupWindow"]) {
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (rollupWidget) rollupListener->Rollup(0, true, nullptr, nullptr);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+// We hook terminate: in order to make OS-initiated termination work nicely
+// with Gecko's shutdown sequence. (Two ways to trigger OS-initiated
+// termination: 1) Quit from the Dock menu; 2) Log out from (or shut down)
+// your computer while the browser is active.)
+@interface NSApplication (MethodSwizzling)
+- (void)nsAppShell_NSApplication_terminate:(id)sender;
+@end
+
+@implementation NSApplication (MethodSwizzling)
+
+// Called by the OS after [MacApplicationDelegate applicationShouldTerminate:]
+// has returned NSTerminateNow. This method "subclasses" and replaces the
+// OS's original implementation. The only thing the orginal method does which
+// we need is that it posts NSApplicationWillTerminateNotification. Everything
+// else is unneeded (because it's handled elsewhere), or actively interferes
+// with Gecko's shutdown sequence. For example the original terminate: method
+// causes the app to exit() inside [NSApp run] (called from nsAppShell::Run()
+// above), which means that nothing runs after the call to nsAppStartup::Run()
+// in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor
+// and NS_ShutdownXPCOM() never get called.
+- (void)nsAppShell_NSApplication_terminate:(id)sender {
+ [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification
+ object:NSApp];
+}
+
+@end
diff --git a/widget/cocoa/nsBidiKeyboard.h b/widget/cocoa/nsBidiKeyboard.h
new file mode 100644
index 0000000000..3a9a6fe2fb
--- /dev/null
+++ b/widget/cocoa/nsBidiKeyboard.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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..4193bdf6f0
--- /dev/null
+++ b/widget/cocoa/nsBidiKeyboard.mm
@@ -0,0 +1,38 @@
+/* -*- 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"
+#include "nsIWidget.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;
+}
+
+// static
+already_AddRefed<nsIBidiKeyboard> nsIWidget::CreateBidiKeyboardInner() {
+ return do_AddRef(new nsBidiKeyboard());
+}
diff --git a/widget/cocoa/nsChangeObserver.h b/widget/cocoa/nsChangeObserver.h
new file mode 100644
index 0000000000..696f0be87e
--- /dev/null
+++ b/widget/cocoa/nsChangeObserver.h
@@ -0,0 +1,53 @@
+/* -*- 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 nsAtom;
+namespace mozilla {
+namespace dom {
+class Document;
+}
+} // namespace mozilla
+
+#define NS_DECL_CHANGEOBSERVER \
+ void ObserveAttributeChanged(mozilla::dom::Document* aDocument, \
+ nsIContent* aContent, nsAtom* aAttribute) \
+ override; \
+ void ObserveContentRemoved(mozilla::dom::Document* aDocument, \
+ nsIContent* aContainer, nsIContent* aChild, \
+ nsIContent* aPreviousChild) override; \
+ void ObserveContentInserted(mozilla::dom::Document* 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(mozilla::dom::Document* aDocument,
+ nsIContent* aContent,
+ nsAtom* aAttribute) = 0;
+
+ virtual void ObserveContentRemoved(mozilla::dom::Document* aDocument,
+ nsIContent* aContainer, nsIContent* aChild,
+ nsIContent* aPreviousSibling) = 0;
+
+ virtual void ObserveContentInserted(mozilla::dom::Document* 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..01ea18b457
--- /dev/null
+++ b/widget/cocoa/nsChildView.h
@@ -0,0 +1,599 @@
+/* -*- 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 "nsIWeakReferenceUtils.h"
+#include "TextInputHandler.h"
+#include "nsCocoaUtils.h"
+#include "gfxQuartzSurface.h"
+#include "GLContextTypes.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/Mutex.h"
+#include "nsRegion.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+
+#include "nsString.h"
+#include "nsIDragService.h"
+#include "ViewRegion.h"
+#include "CFTypeRefPtr.h"
+
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+#import <AppKit/NSOpenGL.h>
+
+class nsChildView;
+class nsCocoaWindow;
+
+namespace {
+class GLPresenter;
+} // namespace
+
+namespace mozilla {
+class InputData;
+class PanGestureInput;
+class SwipeTracker;
+struct SwipeEventQueue;
+class VibrancyManager;
+namespace layers {
+class GLManager;
+class IAPZCTreeManager;
+class NativeLayerRootCA;
+class NativeLayerCA;
+} // namespace layers
+namespace widget {
+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;
+
+// stage From 10.10.3 for force touch event
+@property(readonly) NSInteger stage;
+
+@end
+
+@interface NSView (Undocumented)
+
+// 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
+
+// Available since 10.7.4:
+- (void)viewDidChangeBackingProperties;
+@end
+
+@interface ChildView : NSView <
+#ifdef ACCESSIBILITY
+ mozAccessible,
+#endif
+ mozView,
+ NSTextInputClient,
+ NSDraggingSource,
+ NSDraggingDestination,
+ NSPasteboardItemDataProvider> {
+ @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;
+
+ // Whether we're inside updateRootCALayer at the moment.
+ BOOL mIsUpdatingLayer;
+
+ // 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;
+
+ // 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.
+ //
+ // 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 mCumulativeRotation;
+
+#ifdef __LP64__
+ // Support for fluid swipe tracking.
+ BOOL* mCancelSwipeAnimation;
+#endif
+
+ // Whether this uses off-main-thread compositing.
+ BOOL mUsingOMTCompositor;
+
+ // Subviews of self, which act as container views for vibrancy views and
+ // non-draggable views.
+ NSView* mVibrancyViewsContainer; // [STRONG]
+ NSView* mNonDraggableViewsContainer; // [STRONG]
+
+ // The layer-backed view that hosts our drawing. Always non-null.
+ // This is a subview of self so that it can be ordered on top of mVibrancyViewsContainer.
+ PixelHostingView* mPixelHostingView;
+
+ // The CALayer that wraps Gecko's rendered contents. It's a sublayer of
+ // mPixelHostingView's backing layer. Always non-null.
+ CALayer* mRootCALayer; // [STRONG]
+
+ // Last pressure stage by trackpad's force click
+ NSInteger mLastPressureStage;
+}
+
+// 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)handleMouseMoved:(NSEvent*)aEvent;
+
+- (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent
+ enter:(BOOL)aEnter
+ exitFrom:(mozilla::WidgetMouseEvent::ExitFrom)aExitFrom;
+
+// Call this during operations that will likely trigger a main thread
+// CoreAnimation paint of the window, during which Gecko should do its own
+// painting and present the results atomically with that main thread transaction.
+// This method will suspend off-thread window updates so that the upcoming paint
+// can be atomic, and mark the layer as needing display so that
+// HandleMainThreadCATransaction gets called and Gecko gets a chance to paint.
+- (void)ensureNextCompositeIsAtomicWithMainThreadPaint;
+
+- (NSView*)vibrancyViewsContainer;
+- (NSView*)nonDraggableViewsContainer;
+- (NSView*)pixelHostingView;
+
+- (BOOL)isCoveringTitlebar;
+
+- (void)viewWillStartLiveResize;
+- (void)viewDidEndLiveResize;
+
+/*
+ * Gestures support
+ *
+ * The prototypes swipeWithEvent, beginGestureWithEvent, 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;
+- (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;
+
++ (uint32_t)sUniqueKeyEventId;
+
++ (NSMutableDictionary*)sNativeKeyEventsMap;
+@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 final : public nsBaseWidget {
+ private:
+ typedef nsBaseWidget Inherited;
+ typedef mozilla::layers::IAPZCTreeManager IAPZCTreeManager;
+
+ public:
+ nsChildView();
+
+ // nsIWidget interface
+ [[nodiscard]] virtual nsresult Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr) override;
+
+ virtual void Destroy() override;
+
+ virtual void Show(bool aState) override;
+ virtual bool IsVisible() const override;
+
+ virtual void SetParent(nsIWidget* aNewParent) override;
+ virtual nsIWidget* GetParent(void) override;
+ virtual float GetDPI() override;
+
+ virtual void Move(double aX, double aY) override;
+ virtual void Resize(double aWidth, double aHeight, bool aRepaint) override;
+ virtual void Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) override;
+
+ virtual void Enable(bool aState) override;
+ virtual bool IsEnabled() const override;
+ virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override;
+ virtual LayoutDeviceIntRect GetBounds() override;
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+
+ // Refresh mBounds with up-to-date values from [mView frame].
+ // Only called if this nsChildView is the popup content view of a popup window.
+ // For popup windows, the nsIWidget interface to Gecko is provided by
+ // nsCocoaWindow, not by nsChildView. So nsCocoaWindow manages resize requests
+ // from Gecko, fires resize events, and resizes the native NSWindow and NSView.
+ void UpdateBoundsFromView();
+
+ // 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;
+
+ virtual void Invalidate(const LayoutDeviceIntRect& aRect) override;
+ void EnsureContentLayerForMainThreadPainting();
+
+ virtual void* GetNativeData(uint32_t aDataType) override;
+ virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ virtual bool ShowsResizeIndicator(LayoutDeviceIntRect* aResizerRect) override { return false; }
+
+ static bool ConvertStatus(nsEventStatus aStatus) {
+ return aStatus == nsEventStatus_eConsumeNoDefault;
+ }
+ virtual nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent, nsEventStatus& aStatus) override;
+
+ virtual bool WidgetTypeSupportsAcceleration() override;
+ virtual bool ShouldUseOffMainThreadCompositing() override;
+
+ virtual void SetCursor(nsCursor aDefaultCursor, imgIContainer* aCursor, uint32_t aHotspotX,
+ uint32_t aHotspotY) override;
+
+ virtual nsresult SetTitle(const nsAString& title) override;
+
+ [[nodiscard]] virtual nsresult GetAttention(int32_t aCycleCount) override;
+
+ virtual bool HasPendingInputEvent() override;
+
+ bool SendEventToNativeMenuSystem(NSEvent* aEvent);
+ virtual void PostHandleKeyEvent(mozilla::WidgetKeyboardEvent* aEvent) override;
+ virtual nsresult ActivateNativeMenuItemAt(const nsAString& indexString) override;
+ virtual nsresult ForceUpdateNativeMenuAt(const nsAString& indexString) override;
+ [[nodiscard]] virtual nsresult GetSelectionAsPlaintext(nsAString& aResult) override;
+
+ virtual void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ virtual InputContext GetInputContext() override;
+ virtual TextEventDispatcherListener* GetNativeTextEventDispatcherListener() override;
+ [[nodiscard]] virtual nsresult AttachNativeKeyEvent(
+ mozilla::WidgetKeyboardEvent& aEvent) override;
+ virtual bool GetEditCommands(NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ nsTArray<mozilla::CommandInt>& aCommands) override;
+ void GetEditCommandsRemapped(NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ nsTArray<mozilla::CommandInt>& aCommands, uint32_t aGeckoKeyCode,
+ uint32_t aCocoaKeyCode);
+
+ virtual void SuppressAnimation(bool aSuppress) 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, NSEventTypeMouseMoved, 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 PaintWindowInDrawTarget(mozilla::gfx::DrawTarget* aDT, const LayoutDeviceIntRegion& aRegion,
+ const mozilla::gfx::IntSize& aSurfaceSize);
+
+ void PaintWindowInContentLayer();
+ void HandleMainThreadCATransaction();
+
+#ifdef ACCESSIBILITY
+ already_AddRefed<mozilla::a11y::Accessible> GetDocumentAccessible();
+#endif
+
+ virtual void CreateCompositor() override;
+
+ virtual bool WidgetPaintsBackground() override { return true; }
+
+ virtual bool PreRender(mozilla::widget::WidgetRenderingContext* aContext) override;
+ virtual void PostRender(mozilla::widget::WidgetRenderingContext* aContext) override;
+ virtual RefPtr<mozilla::layers::NativeLayerRoot> GetNativeLayerRoot() override;
+
+ virtual void UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) override;
+
+ virtual void UpdateWindowDraggingRegion(const LayoutDeviceIntRegion& aRegion) override;
+ LayoutDeviceIntRegion GetNonDraggableRegion() { return mNonDraggableRegion.Region(); }
+
+ virtual void ReportSwipeStarted(uint64_t aInputBlockId, bool aStartSwipe) override;
+
+ virtual void LookUpDictionary(const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRangeArray,
+ const bool aIsVertical,
+ const LayoutDeviceIntPoint& aPoint) override;
+
+ void ResetParent();
+
+ static bool DoHasPendingInputEvent();
+ static uint32_t GetCurrentInputEventCount();
+ static void UpdateCurrentInputEventCount();
+
+ NSView<mozView>* GetEditorView();
+
+ nsCocoaWindow* GetAppWindowWidget() const;
+
+ virtual void ReparentNativeWidget(nsIWidget* aNewParent) override;
+
+ mozilla::widget::TextInputHandler* GetTextInputHandler() { return mTextInputHandler; }
+
+ // 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());
+ }
+
+ virtual LayoutDeviceIntPoint GetClientOffset() override;
+
+ void DispatchAPZWheelInputEvent(mozilla::InputData& aEvent, bool aCanTriggerSwipe);
+ nsEventStatus DispatchAPZInputEvent(mozilla::InputData& aEvent);
+
+ void SwipeFinished();
+
+ // Called when the main thread enters a phase during which visual changes
+ // are imminent and any layer updates on the compositor thread would interfere
+ // with visual atomicity.
+ // "Async" CATransactions are CATransactions which happen on a thread that's
+ // not the main thread.
+ void SuspendAsyncCATransactions();
+
+ // Called when we know that the current main thread paint will be completed once
+ // the main thread goes back to the event loop.
+ void MaybeScheduleUnsuspendAsyncCATransactions();
+
+ // Called from the runnable dispatched by MaybeScheduleUnsuspendAsyncCATransactions().
+ // At this point we know that the main thread is done handling the visual change
+ // (such as a window resize) and we can start modifying CALayers from the
+ // compositor thread again.
+ void UnsuspendAsyncCATransactions();
+
+ protected:
+ virtual ~nsChildView();
+
+ void ReportMoveEvent();
+ void ReportSizeEvent();
+
+ void TearDownView();
+
+ virtual already_AddRefed<nsIWidget> AllocateChildPopupWidget() override {
+ return nsIWidget::CreateTopLevelWindow();
+ }
+
+ void ConfigureAPZCTreeManager() override;
+ void ConfigureAPZControllerThread() override;
+
+ void UpdateVibrancy(const nsTArray<ThemeGeometry>& aThemeGeometries);
+ mozilla::VibrancyManager& EnsureVibrancyManager();
+
+ nsIWidget* GetWidgetForListenerEvents();
+
+ struct SwipeInfo {
+ bool wantsSwipe;
+ uint32_t allowedDirections;
+ };
+
+ SwipeInfo SendMayStartSwipe(const mozilla::PanGestureInput& aSwipeStartEvent);
+ void TrackScrollEventAsSwipe(const mozilla::PanGestureInput& aSwipeStartEvent,
+ uint32_t aAllowedDirections);
+
+ protected:
+ ChildView* mView; // my parallel cocoa view, [STRONG]
+ RefPtr<mozilla::widget::TextInputHandler> 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
+
+ // Held while the compositor (or WR renderer) thread is compositing.
+ // Protects from tearing down the view during compositing and from presenting
+ // half-composited layers to the screen.
+ mozilla::Mutex mCompositingLock;
+
+ 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
+
+ RefPtr<mozilla::layers::NativeLayerRootCA> mNativeLayerRoot;
+
+ // In BasicLayers mode, this is the CoreAnimation layer that contains the
+ // rendering from Gecko. It is a sublayer of mNativeLayerRoot's underlying
+ // wrapper layer.
+ // Lazily created by EnsureContentLayerForMainThreadPainting().
+ RefPtr<mozilla::layers::NativeLayerCA> mContentLayer;
+ RefPtr<mozilla::layers::SurfacePoolHandle> mPoolHandle;
+
+ // In BasicLayers mode, this is the invalid region of mContentLayer.
+ LayoutDeviceIntRegion mContentLayerInvalidRegion;
+
+ mozilla::UniquePtr<mozilla::VibrancyManager> mVibrancyManager;
+ RefPtr<mozilla::SwipeTracker> mSwipeTracker;
+ mozilla::UniquePtr<mozilla::SwipeEventQueue> mSwipeEventQueue;
+
+ RefPtr<mozilla::CancelableRunnable> mUnsuspendAsyncCATransactionsRunnable;
+
+ // 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;
+
+ // This is used by SynthesizeNativeTouchPoint to maintain state between
+ // multiple synthesized points
+ mozilla::UniquePtr<mozilla::MultiTouchInput> mSynthesizedTouchInput;
+};
+
+#endif // nsChildView_h_
diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm
new file mode 100644
index 0000000000..c924a66d6c
--- /dev/null
+++ b/widget/cocoa/nsChildView.mm
@@ -0,0 +1,5118 @@
+/* -*- 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 "mozilla/Unused.h"
+
+#include <unistd.h>
+#include <math.h>
+
+#include "nsChildView.h"
+#include "nsCocoaWindow.h"
+
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/WheelHandlingHelper.h" // for WheelDeltaAdjustmentStrategy
+#include "mozilla/dom/DataTransfer.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/SimpleGestureEventBinding.h"
+#include "mozilla/dom/WheelEventBinding.h"
+
+#include "nsArrayUtils.h"
+#include "nsExceptionHandler.h"
+#include "nsObjCExceptions.h"
+#include "nsCOMPtr.h"
+#include "nsThreadUtils.h"
+#include "nsToolkit.h"
+#include "nsCRT.h"
+
+#include "nsFontMetrics.h"
+#include "nsIRollupListener.h"
+#include "nsViewManager.h"
+#include "nsIFile.h"
+#include "nsILocalFileMac.h"
+#include "nsGfxCIID.h"
+#include "nsStyleConsts.h"
+#include "nsIWidgetListener.h"
+#include "nsIScreen.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 "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 "OGLShaderProgram.h"
+#include "ScopedGLHelpers.h"
+#include "HeapCopyOfStackArray.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "mozilla/layers/APZInputBridge.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/CompositorOGL.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/BasicCompositor.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/layers/IpcResourceUpdateQueue.h"
+#include "mozilla/layers/NativeLayerCA.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/widget/CompositorWidget.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/BorrowedContext.h"
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+# include "mozilla/a11y/Platform.h"
+#endif
+
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_general.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_ui.h"
+
+#include <dlfcn.h>
+
+#include <ApplicationServices/ApplicationServices.h>
+
+#include "GeckoProfiler.h"
+
+#include "mozilla/layers/ChromeProcessController.h"
+#include "nsLayoutUtils.h"
+#include "InputData.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
+
+LazyLogModule sCocoaLog("nsCocoaWidgets");
+
+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
+
+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;
+
+static bool sIsTabletPointerActivated = false;
+
+static uint32_t sUniqueKeyEventId = 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;
+- (void)convertCocoaTabletPointerEvent:(NSEvent*)aMouseEvent
+ toGeckoEvent:(WidgetMouseEvent*)outGeckoEvent;
+- (NSMenu*)contextMenu;
+
+- (void)markLayerForDisplay;
+- (CALayer*)rootCALayer;
+- (void)updateRootCALayer;
+
+#ifdef ACCESSIBILITY
+- (id<mozAccessible>)accessible;
+#endif
+
+- (LayoutDeviceIntPoint)convertWindowCoordinates:(NSPoint)aPoint;
+- (LayoutDeviceIntPoint)convertWindowCoordinatesRoundDown:(NSPoint)aPoint;
+
+- (BOOL)inactiveWindowAcceptsMouseEvent:(NSEvent*)aEvent;
+- (void)updateWindowDraggableState;
+
+- (bool)beginOrEndGestureForEventPhase:(NSEvent*)aEvent;
+
+- (bool)shouldConsiderStartingSwipeFromEvent:(NSEvent*)aEvent;
+
+@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);
+}
+
+namespace mozilla {
+
+struct SwipeEventQueue {
+ SwipeEventQueue(uint32_t aAllowedDirections, uint64_t aInputBlockId)
+ : allowedDirections(aAllowedDirections), inputBlockId(aInputBlockId) {}
+
+ nsTArray<PanGestureInput> queuedEvents;
+ uint32_t allowedDirections;
+ uint64_t inputBlockId;
+};
+
+} // namespace mozilla
+
+#pragma mark -
+
+nsChildView::nsChildView()
+ : nsBaseWidget(),
+ mView(nullptr),
+ mParentView(nil),
+ mParentWidget(nullptr),
+ mCompositingLock("ChildViewCompositing"),
+ mBackingScaleFactor(0.0),
+ mVisible(false),
+ mDrawing(false),
+ mIsDispatchPaint(false),
+ mCurrentPanGestureBelongsToSwipe{false} {}
+
+nsChildView::~nsChildView() {
+ if (mSwipeTracker) {
+ mSwipeTracker->Destroy();
+ mSwipeTracker = nullptr;
+ }
+
+ // Notify the children that we're gone. childView->ResetParent() can change
+ // our list of children while it's being iterated, so the way we iterate the
+ // list must allow for this.
+ for (nsIWidget* kid = mLastChild; kid;) {
+ nsChildView* childView = static_cast<nsChildView*>(kid);
+ kid = kid->GetPrevSibling();
+ childView->ResetParent();
+ }
+
+ NS_WARNING_ASSERTION(mOnDestroyCalled, "nsChildView object destroyed without calling Destroy()");
+
+ if (mContentLayer) {
+ mNativeLayerRoot->RemoveLayer(mContentLayer); // safe if already removed
+ }
+
+ 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.
+}
+
+nsresult nsChildView::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect, nsWidgetInitData* aInitData) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // 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;
+
+ mBounds = aRect;
+
+ // Ensure that the toolkit is created.
+ nsToolkit::GetToolkit();
+
+ BaseCreate(aParent, aInitData);
+
+ mParentView = nil;
+ if (aParent) {
+ // This is the popup window case. aParent is the nsCocoaWindow for the
+ // popup window, and mParentView will be its content view.
+ mParentView = (NSView*)aParent->GetNativeData(NS_NATIVE_WIDGET);
+ mParentWidget = aParent;
+ } else {
+ // This is the top-level window case.
+ // aNativeParent will be the contentView of our window, since that's what
+ // nsCocoaWindow returns when asked for an NS_NATIVE_VIEW.
+ // We do not have a direct "parent widget" association with the top level
+ // window's nsCocoaWindow object.
+ mParentView = reinterpret_cast<NSView*>(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];
+
+ mNativeLayerRoot = NativeLayerRootCA::CreateForCALayer([mView rootCALayer]);
+ mNativeLayerRoot->SetBackingScale(scaleFactor);
+
+ // 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);
+
+ 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::GetAppWindowWidget() const {
+ 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(mCompositingLock);
+
+ if (mOnDestroyCalled) return;
+ mOnDestroyCalled = true;
+
+ // Stuff below may delete the last ref to this
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ [mView widgetDestroyed];
+
+ nsBaseWidget::Destroy();
+
+ NotifyWindowDestroyed();
+ mParentWidget = nil;
+
+ TearDownView();
+
+ nsBaseWidget::OnDestroy();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#pragma mark -
+
+#if 0
+static void PrintViewHierarchy(NSView *view)
+{
+ while (view) {
+ NSLog(@" view is %x, frame %@", view, NSStringFromRect([view frame]));
+ view = [view superview];
+ }
+}
+#endif
+
+// Return native data according to aDataType
+void* nsChildView::GetNativeData(uint32_t aDataType) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL;
+
+ void* retVal = nullptr;
+
+ switch (aDataType) {
+ case NS_NATIVE_WIDGET:
+ case NS_NATIVE_DISPLAY:
+ retVal = (void*)mView;
+ break;
+
+ case NS_NATIVE_WINDOW:
+ retVal = [mView window];
+ break;
+
+ case NS_NATIVE_GRAPHIC:
+ NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a Mac OS X child view!");
+ retVal = nullptr;
+ break;
+
+ case NS_NATIVE_OFFSETX:
+ retVal = 0;
+ break;
+
+ case NS_NATIVE_OFFSETY:
+ retVal = 0;
+ break;
+
+ case NS_RAW_NATIVE_IME_CONTEXT:
+ retVal = GetPseudoIMEContext();
+ if (retVal) {
+ break;
+ }
+ retVal = [mView inputContext];
+ // If input context isn't available on this widget, we should set |this|
+ // instead of nullptr since if this returns nullptr, IMEStateManager
+ // cannot manage composition with TextComposition instance. Although,
+ // this case shouldn't occur.
+ if (NS_WARN_IF(!retVal)) {
+ retVal = this;
+ }
+ break;
+
+ case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID: {
+ NSWindow* win = [mView window];
+ if (win) {
+ retVal = (void*)[win windowNumber];
+ }
+ break;
+ }
+ }
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
+
+#pragma mark -
+
+void nsChildView::SuppressAnimation(bool aSuppress) {
+ GetAppWindowWidget()->SuppressAnimation(aSuppress);
+}
+
+bool nsChildView::IsVisible() const {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (!mVisible) {
+ return mVisible;
+ }
+
+ if (!GetAppWindowWidget()->IsVisible()) {
+ return false;
+ }
+
+ // 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
+void nsChildView::Show(bool aState) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ 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;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Change the parent of this widget
+void nsChildView::SetParent(nsIWidget* aNewParent) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mOnDestroyCalled) return;
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ if (mParentWidget) {
+ mParentWidget->RemoveChild(this);
+ }
+
+ if (aNewParent) {
+ ReparentNativeWidget(aNewParent);
+ } else {
+ [mView removeFromSuperview];
+ mParentView = nil;
+ }
+
+ mParentWidget = aNewParent;
+
+ if (mParentWidget) {
+ mParentWidget->AddChild(this);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsChildView::ReparentNativeWidget(nsIWidget* aNewParent) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_ASSERT(aNewParent, "null widget");
+
+ if (mOnDestroyCalled) return;
+
+ NSView<mozView>* newParentView = (NSView<mozView>*)aNewParent->GetNativeData(NS_NATIVE_WIDGET);
+ NS_ENSURE_TRUE_VOID(newParentView);
+
+ // we hold a ref to mView, so this is safe
+ [mView removeFromSuperview];
+ mParentView = newParentView;
+ [mParentView addSubview:mView];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsChildView::ResetParent() {
+ if (!mOnDestroyCalled) {
+ if (mParentWidget) mParentWidget->RemoveChild(this);
+ if (mView) [mView removeFromSuperview];
+ }
+ mParentWidget = nullptr;
+}
+
+nsIWidget* nsChildView::GetParent() { return mParentWidget; }
+
+float nsChildView::GetDPI() {
+ float dpi = 96.0;
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ if (screen) {
+ screen->GetDpi(&dpi);
+ }
+ return dpi;
+}
+
+void nsChildView::Enable(bool aState) {}
+
+bool nsChildView::IsEnabled() const { return true; }
+
+void nsChildView::SetFocus(Raise, mozilla::dom::CallerType aCallerType) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSWindow* window = [mView window];
+ if (window) [window makeFirstResponder:mView];
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Override to set the cursor on the mac
+void nsChildView::SetCursor(nsCursor aDefaultCursor, imgIContainer* aImageCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if ([mView isDragInProgress]) return; // Don't change the cursor during dragging.
+
+ if (aImageCursor) {
+ nsresult rv = [[nsCursorManager sharedInstance] setCursorWithImage:aImageCursor
+ hotSpotX:aHotspotX
+ hotSpotY:aHotspotY
+ scaleFactor:BackingScaleFactor()];
+ if (NS_SUCCEEDED(rv)) {
+ return;
+ }
+ }
+
+ nsBaseWidget::SetCursor(aDefaultCursor, nullptr, 0, 0);
+ [[nsCursorManager sharedInstance] setCursor:aDefaultCursor];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#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;
+ }
+
+ SuspendAsyncCATransactions();
+ mBackingScaleFactor = newScale;
+ NSRect frame = [mView frame];
+ mBounds = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, newScale);
+
+ mNativeLayerRoot->SetBackingScale(mBackingScaleFactor);
+
+ if (mWidgetListener && !mWidgetListener->GetAppWindow()) {
+ if (PresShell* presShell = mWidgetListener->GetPresShell()) {
+ 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
+void nsChildView::Move(double aX, double aY) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ int32_t x = NSToIntRound(aX);
+ int32_t y = NSToIntRound(aY);
+
+ if (!mView || (mBounds.x == x && mBounds.y == y)) return;
+
+ mBounds.x = x;
+ mBounds.y = y;
+
+ ManipulateViewWithoutNeedingDisplay(mView, ^{
+ [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
+ });
+
+ NotifyRollupGeometryChange();
+ ReportMoveEvent();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsChildView::Resize(double aWidth, double aHeight, bool aRepaint) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ int32_t width = NSToIntRound(aWidth);
+ int32_t height = NSToIntRound(aHeight);
+
+ if (!mView || (mBounds.width == width && mBounds.height == height)) return;
+
+ SuspendAsyncCATransactions();
+ mBounds.width = width;
+ mBounds.height = height;
+
+ ManipulateViewWithoutNeedingDisplay(mView, ^{
+ [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
+ });
+
+ if (mVisible && aRepaint) {
+ [[mView pixelHostingView] setNeedsDisplay:YES];
+ }
+
+ NotifyRollupGeometryChange();
+ ReportSizeEvent();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsChildView::Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ 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;
+
+ if (isMoving) {
+ mBounds.x = x;
+ mBounds.y = y;
+ }
+ if (isResizing) {
+ SuspendAsyncCATransactions();
+ 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;
+ }
+ if (isResizing) ReportSizeEvent();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// The following three methods are primarily an attempt to avoid glitches during
+// window resizing.
+// Here's some background on how these glitches come to be:
+// CoreAnimation transactions are per-thread. They don't nest across threads.
+// If you submit a transaction on the main thread and a transaction on a
+// different thread, the two will race to the window server and show up on the
+// screen in the order that they happen to arrive in at the window server.
+// When the window size changes, there's another event that needs to be
+// synchronized with: the window "shape" change. Cocoa has built-in synchronization
+// mechanics that make sure that *main thread* window paints during window resizes
+// are synchronized properly with the window shape change. But no such built-in
+// synchronization exists for CATransactions that are triggered on a non-main
+// thread.
+// To cope with this, we define a "danger zone" during which we simply avoid
+// triggering any CATransactions on a non-main thread (called "async" CATransactions
+// here). This danger zone starts at the earliest opportunity at which we know
+// about the size change, which is nsChildView::Resize, and ends at a point at
+// which we know for sure that the paint has been handled completely, which is
+// when we return to the event loop after layer display.
+void nsChildView::SuspendAsyncCATransactions() {
+ if (mUnsuspendAsyncCATransactionsRunnable) {
+ mUnsuspendAsyncCATransactionsRunnable->Cancel();
+ mUnsuspendAsyncCATransactionsRunnable = nullptr;
+ }
+
+ // Make sure that there actually will be a CATransaction on the main thread
+ // during which we get a chance to schedule unsuspension. Otherwise we might
+ // accidentally stay suspended indefinitely.
+ [mView markLayerForDisplay];
+
+ mNativeLayerRoot->SuspendOffMainThreadCommits();
+}
+
+void nsChildView::MaybeScheduleUnsuspendAsyncCATransactions() {
+ if (mNativeLayerRoot->AreOffMainThreadCommitsSuspended() &&
+ !mUnsuspendAsyncCATransactionsRunnable) {
+ mUnsuspendAsyncCATransactionsRunnable =
+ NewCancelableRunnableMethod("nsChildView::MaybeScheduleUnsuspendAsyncCATransactions", this,
+ &nsChildView::UnsuspendAsyncCATransactions);
+ NS_DispatchToMainThread(mUnsuspendAsyncCATransactionsRunnable);
+ }
+}
+
+void nsChildView::UnsuspendAsyncCATransactions() {
+ mUnsuspendAsyncCATransactionsRunnable = nullptr;
+
+ if (mNativeLayerRoot->UnsuspendOffMainThreadCommits()) {
+ // We need to call mNativeLayerRoot->CommitToScreen() at the next available
+ // opportunity.
+ // The easiest way to handle this request is to mark the layer as needing
+ // display, because this will schedule a main thread CATransaction, during
+ // which HandleMainThreadCATransaction will call CommitToScreen().
+ [mView markLayerForDisplay];
+ }
+}
+
+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 == NSEventTypeMouseEntered) {
+ [window mouseEntered:event];
+ return NS_OK;
+ }
+ if (aNativeMessage == NSEventTypeMouseExited) {
+ [window mouseExited:event];
+ return NS_OK;
+ }
+ if (aNativeMessage == NSEventTypeMouseMoved) {
+ [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, (int32_t)aDeltaY,
+ (int32_t)aDeltaX, (int32_t)aDeltaZ);
+ if (!cgEvent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aNativeMessage) {
+ CGEventSetIntegerValueField(cgEvent, kCGScrollWheelEventScrollPhase, aNativeMessage);
+ }
+
+ // On macOS 10.14 and up CGEventPost won't work because of changes in macOS
+ // to improve security. This code makes an NSEvent corresponding to the
+ // wheel event and dispatches it directly to the scrollWheel handler. Some
+ // fiddling is needed with the coordinates in order to simulate what macOS
+ // would do; this code adapted from the Chromium equivalent function at
+ // https://chromium.googlesource.com/chromium/src.git/+/62.0.3178.1/ui/events/test/cocoa_test_event_utils.mm#38
+ CGPoint location = CGEventGetLocation(cgEvent);
+ location.y += NSMinY([[mView window] frame]);
+ location.x -= NSMinX([[mView window] frame]);
+ CGEventSetLocation(cgEvent, location);
+
+ uint64_t kNanosPerSec = 1000000000L;
+ CGEventSetTimestamp(cgEvent, [[NSProcessInfo processInfo] systemUptime] * kNanosPerSec);
+
+ NSEvent* event = [NSEvent eventWithCGEvent:cgEvent];
+ [event setValue:[mView window] forKey:@"_window"];
+ [mView scrollWheel:event];
+
+ CFRelease(cgEvent);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsChildView::SynthesizeNativeTouchPoint(
+ uint32_t aPointerId, TouchPointerState aPointerState, mozilla::LayoutDeviceIntPoint aPoint,
+ double aPointerPressure, uint32_t aPointerOrientation, nsIObserver* aObserver) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aPointerState == TOUCH_HOVER) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mSynthesizedTouchInput) {
+ mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
+ }
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
+ mSynthesizedTouchInput.get(), PR_IntervalNow(), TimeStamp::Now(), aPointerId, aPointerState,
+ pointInWindow, aPointerPressure, aPointerOrientation);
+ DispatchTouchInput(inputToDispatch);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// First argument has to be an NSMenu representing the application's top-level
+// menu bar. The returned item is *not* retained.
+static NSMenuItem* NativeMenuItemWithLocation(NSMenu* menubar, NSString* locationString) {
+ NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
+ unsigned int indexCount = [indexes count];
+ if (indexCount == 0) return nil;
+
+ NSMenu* currentSubmenu = [NSApp mainMenu];
+ for (unsigned int i = 0; i < indexCount; i++) {
+ int targetIndex;
+ // We remove the application menu from consideration for the top-level menu
+ if (i == 0)
+ targetIndex = [[indexes objectAtIndex:i] intValue] + 1;
+ else
+ targetIndex = [[indexes objectAtIndex:i] intValue];
+ int itemCount = [currentSubmenu numberOfItems];
+ if (targetIndex < itemCount) {
+ NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex];
+ // if this is the last index just return the menu item
+ if (i == (indexCount - 1)) return menuItem;
+ // if this is not the last index find the submenu and keep going
+ if ([menuItem hasSubmenu])
+ currentSubmenu = [menuItem submenu];
+ else
+ return nil;
+ }
+ }
+
+ return nil;
+}
+
+bool nsChildView::SendEventToNativeMenuSystem(NSEvent* aEvent) {
+ bool handled = false;
+ nsCocoaWindow* widget = GetAppWindowWidget();
+ if (widget) {
+ nsMenuBarX* mb = widget->GetMenuBar();
+ if (mb) {
+ // Check if main menu wants to handle the event.
+ handled = mb->PerformKeyEquivalent(aEvent);
+ }
+ }
+
+ if (!handled && sApplicationMenu) {
+ // Check if application menu wants to handle the event.
+ handled = [sApplicationMenu performKeyEquivalent:aEvent];
+ }
+
+ return handled;
+}
+
+void nsChildView::PostHandleKeyEvent(mozilla::WidgetKeyboardEvent* aEvent) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // We always allow keyboard events to propagate to keyDown: but if they are
+ // not handled we give menu items a chance to act. This allows for handling of
+ // custom shortcuts. Note that existing shortcuts cannot be reassigned yet and
+ // will have been handled by keyDown: before we get here.
+ NSMutableDictionary* nativeKeyEventsMap = [ChildView sNativeKeyEventsMap];
+ NSEvent* cocoaEvent = [nativeKeyEventsMap objectForKey:@(aEvent->mUniqueId)];
+ if (!cocoaEvent) {
+ return;
+ }
+
+ if (SendEventToNativeMenuSystem(cocoaEvent)) {
+ aEvent->PreventDefault();
+ }
+ [nativeKeyEventsMap removeObjectForKey:@(aEvent->mUniqueId)];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Used for testing native menu system structure and event handling.
+nsresult nsChildView::ActivateNativeMenuItemAt(const nsAString& indexString) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSString* locationString =
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSMenuItem* item = NativeMenuItemWithLocation([NSApp mainMenu], locationString);
+ // We can't perform an action on an item with a submenu, that will raise
+ // an obj-c exception.
+ if (item && ![item hasSubmenu]) {
+ NSMenu* parent = [item menu];
+ if (parent) {
+ // NSLog(@"Performing action for native menu item titled: %@\n",
+ // [[currentSubmenu itemAtIndex:targetIndex] title]);
+ [parent performActionForItemAtIndex:[parent indexOfItem:item]];
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Used for testing native menu system structure and event handling.
+nsresult nsChildView::ForceUpdateNativeMenuAt(const nsAString& indexString) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsCocoaWindow* widget = GetAppWindowWidget();
+ 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
+void nsChildView::Invalidate(const LayoutDeviceIntRect& aRect) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mView || !mVisible) return;
+
+ NS_ASSERTION(GetLayerManager()->GetBackendType() != LayersBackend::LAYERS_CLIENT,
+ "Shouldn't need to invalidate with accelerated OMTC layers!");
+
+ EnsureContentLayerForMainThreadPainting();
+ mContentLayerInvalidRegion.OrWith(aRect.Intersect(GetBounds()));
+ [mView markLayerForDisplay];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+bool nsChildView::WidgetTypeSupportsAcceleration() {
+ // All widget types support acceleration.
+ return true;
+}
+
+bool nsChildView::ShouldUseOffMainThreadCompositing() {
+ // We need to enable OMTC in popups which contain remote layer
+ // trees, since the remote content won't be rendered at all otherwise.
+ if (HasRemoteContent()) {
+ return true;
+ }
+
+ // Don't use OMTC for popup windows, because we do not want context menus to
+ // pay the overhead of starting up a compositor. With the OpenGL compositor,
+ // new windows are expensive because of shader re-compilation, and with
+ // WebRender, new windows are expensive because they create their own threads
+ // and texture caches.
+ // Using OMTC with BasicCompositor for context menus would probably be fine
+ // but isn't a well-tested configuration.
+ if ([mView window] && [[mView window] isKindOfClass:[PopupWindow class]]) {
+ // Use main-thread BasicLayerManager for drawing menus.
+ return false;
+ }
+
+ return nsBaseWidget::ShouldUseOffMainThreadCompositing();
+}
+
+#pragma mark -
+
+nsresult nsChildView::ConfigureChildren(const nsTArray<Configuration>& aConfigurations) {
+ return NS_OK;
+}
+
+// Invokes callback and ProcessEvent methods on Event Listener object
+nsresult nsChildView::DispatchEvent(WidgetGUIEvent* event, nsEventStatus& aStatus) {
+ RefPtr<nsChildView> kungFuDeathGrip(this);
+
+#ifdef DEBUG
+ debug_DumpEvent(stdout, event->mWidget, event, "something", 0);
+#endif
+
+ if (event->mFlags.mIsSynthesizedForTests) {
+ WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
+ if (keyEvent) {
+ nsresult rv = mTextInputHandler->AttachNativeKeyEvent(*keyEvent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ aStatus = nsEventStatus_eIgnore;
+
+ nsIWidgetListener* listener = mWidgetListener;
+
+ // If the listener is NULL, check if the parent is a popup. If it is, then
+ // this child is the popup content view attached to a popup. Get the
+ // listener from the parent popup instead.
+ nsCOMPtr<nsIWidget> parentWidget = mParentWidget;
+ if (!listener && parentWidget) {
+ if (parentWidget->WindowType() == eWindowType_popup) {
+ // Check just in case event->mWidget isn't this widget
+ if (event->mWidget) {
+ listener = event->mWidget->GetWidgetListener();
+ }
+ if (!listener) {
+ event->mWidget = parentWidget;
+ listener = parentWidget->GetWidgetListener();
+ }
+ }
+ }
+
+ if (listener) aStatus = listener->HandleEvent(event, mUseAttachedEvents);
+
+ return NS_OK;
+}
+
+bool nsChildView::DispatchWindowEvent(WidgetGUIEvent& event) {
+ nsEventStatus status;
+ DispatchEvent(&event, status);
+ return ConvertStatus(status);
+}
+
+nsIWidget* nsChildView::GetWidgetForListenerEvents() {
+ // If there is no listener, use the parent popup's listener if that exists.
+ if (!mWidgetListener && mParentWidget && mParentWidget->WindowType() == eWindowType_popup) {
+ return mParentWidget;
+ }
+
+ return this;
+}
+
+void nsChildView::WillPaintWindow() {
+ nsCOMPtr<nsIWidget> widget = GetWidgetForListenerEvents();
+
+ nsIWidgetListener* listener = widget->GetWidgetListener();
+ if (listener) {
+ listener->WillPaintWindow(widget);
+ }
+}
+
+bool nsChildView::PaintWindow(LayoutDeviceIntRegion aRegion) {
+ nsCOMPtr<nsIWidget> widget = GetWidgetForListenerEvents();
+
+ nsIWidgetListener* listener = widget->GetWidgetListener();
+ if (!listener) return false;
+
+ bool returnValue = false;
+ bool oldDispatchPaint = mIsDispatchPaint;
+ mIsDispatchPaint = true;
+ returnValue = listener->PaintWindow(widget, aRegion);
+
+ listener = widget->GetWidgetListener();
+ if (listener) {
+ listener->DidPaintWindow();
+ }
+
+ mIsDispatchPaint = oldDispatchPaint;
+ return returnValue;
+}
+
+bool nsChildView::PaintWindowInDrawTarget(gfx::DrawTarget* aDT,
+ const LayoutDeviceIntRegion& aRegion,
+ const gfx::IntSize& aSurfaceSize) {
+ RefPtr<gfxContext> targetContext = gfxContext::CreateOrNull(aDT);
+ MOZ_ASSERT(targetContext);
+
+ // 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));
+ aDT->ClearRect(gfx::Rect(r.ToUnknownRect()));
+ }
+ targetContext->Clip();
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(mView);
+ if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+ nsBaseWidget::AutoLayerManagerSetup setupLayerManager(this, targetContext,
+ BufferMode::BUFFER_NONE);
+ return PaintWindow(aRegion);
+ }
+ if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT) {
+ // We only need this so that we actually get DidPaintWindow fired
+ return PaintWindow(aRegion);
+ }
+ return false;
+}
+
+void nsChildView::EnsureContentLayerForMainThreadPainting() {
+ // Ensure we have an mContentLayer of the correct size.
+ // The content layer gets created on demand for BasicLayers windows. We do
+ // not create it during widget creation because, for non-BasicLayers windows,
+ // the compositing layer manager will create any layers it needs.
+ gfx::IntSize size = GetBounds().Size().ToUnknownSize();
+ if (mContentLayer && mContentLayer->GetSize() != size) {
+ mNativeLayerRoot->RemoveLayer(mContentLayer);
+ mContentLayer = nullptr;
+ }
+ if (!mContentLayer) {
+ mPoolHandle = SurfacePool::Create(0)->GetHandleForGL(nullptr);
+ RefPtr<NativeLayer> contentLayer = mNativeLayerRoot->CreateLayer(size, false, mPoolHandle);
+ mNativeLayerRoot->AppendLayer(contentLayer);
+ mContentLayer = contentLayer->AsNativeLayerCA();
+ mContentLayer->SetSurfaceIsFlipped(false);
+ mContentLayerInvalidRegion = GetBounds();
+ }
+}
+
+void nsChildView::PaintWindowInContentLayer() {
+ EnsureContentLayerForMainThreadPainting();
+ mPoolHandle->OnBeginFrame();
+ RefPtr<DrawTarget> dt = mContentLayer->NextSurfaceAsDrawTarget(
+ gfx::IntRect({}, mContentLayer->GetSize()), mContentLayerInvalidRegion.ToUnknownRegion(),
+ gfx::BackendType::SKIA);
+ if (!dt) {
+ return;
+ }
+
+ PaintWindowInDrawTarget(dt, mContentLayerInvalidRegion, dt->GetSize());
+ mContentLayer->NotifySurfaceReady();
+ mContentLayerInvalidRegion.SetEmpty();
+ mPoolHandle->OnEndFrame();
+}
+
+void nsChildView::HandleMainThreadCATransaction() {
+ WillPaintWindow();
+
+ if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+ // We're in BasicLayers mode, i.e. main thread software compositing.
+ // Composite the window into our layer's surface.
+ PaintWindowInContentLayer();
+ } else {
+ // Trigger a synchronous OMTC composite. This will call NextSurface and
+ // NotifySurfaceReady on the compositor thread to update mNativeLayerRoot's
+ // contents, and the main thread (this thread) will wait inside PaintWindow
+ // during that time.
+ PaintWindow(LayoutDeviceIntRegion(GetBounds()));
+ }
+
+ {
+ // Apply the changes inside mNativeLayerRoot to the underlying CALayers. Now is a
+ // good time to call this because we know we're currently inside a main thread
+ // CATransaction, and the lock makes sure that no composition is currently in
+ // progress, so we won't present half-composited state to the screen.
+ MutexAutoLock lock(mCompositingLock);
+ mNativeLayerRoot->CommitToScreen();
+ }
+
+ MaybeScheduleUnsuspendAsyncCATransactions();
+}
+
+#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));
+}
+
+nsresult nsChildView::SetTitle(const nsAString& title) {
+ // child views don't have titles
+ return NS_OK;
+}
+
+nsresult 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 -
+
+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();
+ }
+ }
+
+ // IMEInputHandler::IsEditableContent() returns false when both
+ // IsASCIICableOnly() and IsIMEEnabled() return false. So, be careful
+ // when you change the following code. You might need to change
+ // IMEInputHandler::IsEditableContent() too.
+ mInputContext = aContext;
+ switch (aContext.mIMEState.mEnabled) {
+ case IMEEnabled::Enabled:
+ mTextInputHandler->SetASCIICapableOnly(false);
+ mTextInputHandler->EnableIME(true);
+ if (mInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE) {
+ mTextInputHandler->SetIMEOpenState(mInputContext.mIMEState.mOpen == IMEState::OPEN);
+ }
+ break;
+ case IMEEnabled::Disabled:
+ mTextInputHandler->SetASCIICapableOnly(false);
+ mTextInputHandler->EnableIME(false);
+ break;
+ case IMEEnabled::Password:
+ mTextInputHandler->SetASCIICapableOnly(true);
+ mTextInputHandler->EnableIME(false);
+ break;
+ default:
+ NS_ERROR("not implemented!");
+ }
+}
+
+InputContext nsChildView::GetInputContext() {
+ switch (mInputContext.mIMEState.mEnabled) {
+ case IMEEnabled::Enabled:
+ if (mTextInputHandler) {
+ mInputContext.mIMEState.mOpen =
+ mTextInputHandler->IsIMEOpened() ? IMEState::OPEN : IMEState::CLOSED;
+ break;
+ }
+ // If mTextInputHandler is null, set CLOSED instead...
+ [[fallthrough]];
+ default:
+ mInputContext.mIMEState.mOpen = IMEState::CLOSED;
+ break;
+ }
+ return mInputContext;
+}
+
+TextEventDispatcherListener* nsChildView::GetNativeTextEventDispatcherListener() {
+ if (NS_WARN_IF(!mTextInputHandler)) {
+ return nullptr;
+ }
+ return mTextInputHandler;
+}
+
+nsresult nsChildView::AttachNativeKeyEvent(mozilla::WidgetKeyboardEvent& aEvent) {
+ NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE);
+ return mTextInputHandler->AttachNativeKeyEvent(aEvent);
+}
+
+void nsChildView::GetEditCommandsRemapped(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands, uint32_t aGeckoKeyCode,
+ uint32_t aCocoaKeyCode) {
+ NSEvent* originalEvent = reinterpret_cast<NSEvent*>(aEvent.mNativeKeyEvent);
+
+ WidgetKeyboardEvent modifiedEvent(aEvent);
+ modifiedEvent.mKeyCode = aGeckoKeyCode;
+
+ unichar ch = nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aGeckoKeyCode);
+ NSString* chars = [[[NSString alloc] initWithCharacters:&ch length:1] autorelease];
+
+ modifiedEvent.mNativeKeyEvent = [NSEvent keyEventWithType:[originalEvent type]
+ location:[originalEvent locationInWindow]
+ modifierFlags:[originalEvent modifierFlags]
+ timestamp:[originalEvent timestamp]
+ windowNumber:[originalEvent windowNumber]
+ context:[originalEvent context]
+ characters:chars
+ charactersIgnoringModifiers:chars
+ isARepeat:[originalEvent isARepeat]
+ keyCode:aCocoaKeyCode];
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ keyBindings->GetEditCommands(modifiedEvent, aCommands);
+}
+
+bool nsChildView::GetEditCommands(NativeKeyBindingsType aType, const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands) {
+ // Validate the arguments.
+ if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
+ return false;
+ }
+
+ // 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) {
+ // XXX This may be expensive. Should use the cache in TextInputHandler.
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText, this);
+ DispatchWindowEvent(querySelectedTextEvent);
+
+ if (querySelectedTextEvent.FoundSelection() &&
+ querySelectedTextEvent.mReply->mWritingMode.IsVertical()) {
+ uint32_t geckoKey = 0;
+ uint32_t cocoaKey = 0;
+
+ switch (aEvent.mKeyCode) {
+ case NS_VK_LEFT:
+ if (querySelectedTextEvent.mReply->mWritingMode.IsVerticalLR()) {
+ geckoKey = NS_VK_UP;
+ cocoaKey = kVK_UpArrow;
+ } else {
+ geckoKey = NS_VK_DOWN;
+ cocoaKey = kVK_DownArrow;
+ }
+ break;
+
+ case NS_VK_RIGHT:
+ if (querySelectedTextEvent.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;
+ }
+
+ GetEditCommandsRemapped(aType, aEvent, aCommands, geckoKey, cocoaKey);
+ return true;
+ }
+ }
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ keyBindings->GetEditCommands(aEvent, aCommands);
+ return true;
+}
+
+NSView<mozView>* nsChildView::GetEditorView() {
+ NSView<mozView>* editorView = mView;
+ // We need to get editor's view. E.g., when the focus is in the bookmark
+ // dialog, the view is <panel> element of the dialog. At this time, the key
+ // events are processed the parent window's view that has native focus.
+ WidgetQueryContentEvent queryContentState(true, eQueryContentState, this);
+ // This may be called during creating a menu popup frame due to creating
+ // widget synchronously and that causes Cocoa asking current window level.
+ // In this case, it's not safe to flush layout on the document and we don't
+ // need any layout information right now.
+ queryContentState.mNeedsToFlushLayout = false;
+ DispatchWindowEvent(queryContentState);
+ if (queryContentState.Succeeded() && queryContentState.mReply->mFocusedWidget) {
+ NSView<mozView>* view = static_cast<NSView<mozView>*>(
+ queryContentState.mReply->mFocusedWidget->GetNativeData(NS_NATIVE_WIDGET));
+ if (view) editorView = view;
+ }
+ return editorView;
+}
+
+#pragma mark -
+
+void nsChildView::CreateCompositor() {
+ nsBaseWidget::CreateCompositor();
+ if (mCompositorBridgeChild) {
+ [mView setUsingOMTCompositor:true];
+ }
+}
+
+void nsChildView::ConfigureAPZCTreeManager() { nsBaseWidget::ConfigureAPZCTreeManager(); }
+
+void nsChildView::ConfigureAPZControllerThread() { nsBaseWidget::ConfigureAPZControllerThread(); }
+
+bool nsChildView::PreRender(WidgetRenderingContext* aContext) {
+ // 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.
+ mCompositingLock.Lock();
+
+ if (aContext->mGL && gfxPlatform::CanMigrateMacGPUs()) {
+ GLContextCGL::Cast(aContext->mGL)->MigrateToActiveGPU();
+ }
+
+ return true;
+}
+
+void nsChildView::PostRender(WidgetRenderingContext* aContext) { mCompositingLock.Unlock(); }
+
+RefPtr<layers::NativeLayerRoot> nsChildView::GetNativeLayerRoot() { return mNativeLayerRoot; }
+
+static int32_t FindTitlebarBottom(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
+ int32_t aWindowWidth) {
+ int32_t titlebarBottom = 0;
+ for (auto& g : aThemeGeometries) {
+ if ((g.mType == nsNativeThemeCocoa::eThemeGeometryTypeTitlebar ||
+ g.mType == nsNativeThemeCocoa::eThemeGeometryTypeVibrantTitlebarLight ||
+ g.mType == nsNativeThemeCocoa::eThemeGeometryTypeVibrantTitlebarDark) &&
+ g.mRect.X() <= 0 && g.mRect.XMost() >= aWindowWidth && g.mRect.Y() <= 0) {
+ titlebarBottom = std::max(titlebarBottom, g.mRect.YMost());
+ }
+ }
+ return titlebarBottom;
+}
+
+static int32_t FindUnifiedToolbarBottom(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
+ int32_t aWindowWidth, int32_t aTitlebarBottom) {
+ int32_t unifiedToolbarBottom = aTitlebarBottom;
+ for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) {
+ const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
+ if ((g.mType == nsNativeThemeCocoa::eThemeGeometryTypeToolbar) && g.mRect.X() <= 0 &&
+ g.mRect.XMost() >= aWindowWidth && g.mRect.Y() <= aTitlebarBottom) {
+ unifiedToolbarBottom = std::max(unifiedToolbarBottom, g.mRect.YMost());
+ }
+ }
+ return unifiedToolbarBottom;
+}
+
+static LayoutDeviceIntRect FindFirstRectOfType(
+ const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
+ nsITheme::ThemeGeometryType aThemeGeometryType) {
+ for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) {
+ const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
+ if (g.mType == aThemeGeometryType) {
+ return g.mRect;
+ }
+ }
+ return LayoutDeviceIntRect();
+}
+
+void nsChildView::UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) {
+ if (![mView window]) return;
+
+ UpdateVibrancy(aThemeGeometries);
+
+ if (![[mView window] isKindOfClass:[ToolbarWindow class]]) return;
+
+ // Update unified toolbar height and sheet attachment position.
+ int32_t windowWidth = mBounds.width;
+ int32_t titlebarBottom = FindTitlebarBottom(aThemeGeometries, windowWidth);
+ int32_t unifiedToolbarBottom =
+ FindUnifiedToolbarBottom(aThemeGeometries, windowWidth, titlebarBottom);
+ int32_t toolboxBottom =
+ FindFirstRectOfType(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeToolbox).YMost();
+
+ ToolbarWindow* win = (ToolbarWindow*)[mView window];
+ int32_t titlebarHeight = CocoaPointsToDevPixels([win titlebarHeight]);
+ int32_t devUnifiedHeight = titlebarHeight + unifiedToolbarBottom;
+ [win setUnifiedToolbarHeight:DevPixelsToCocoaPoints(devUnifiedHeight)];
+
+ int32_t sheetPositionDevPx = std::max(toolboxBottom, unifiedToolbarBottom);
+ NSPoint sheetPositionView = {0, DevPixelsToCocoaPoints(sheetPositionDevPx)};
+ NSPoint sheetPositionWindow = [mView convertPoint:sheetPositionView toView:nil];
+ [win setSheetAttachmentPosition:sheetPositionWindow.y];
+
+ // 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 Maybe<VibrancyType> ThemeGeometryTypeToVibrancyType(
+ nsITheme::ThemeGeometryType aThemeGeometryType) {
+ switch (aThemeGeometryType) {
+ case nsNativeThemeCocoa::eThemeGeometryTypeVibrancyLight:
+ case nsNativeThemeCocoa::eThemeGeometryTypeVibrantTitlebarLight:
+ return Some(VibrancyType::LIGHT);
+ case nsNativeThemeCocoa::eThemeGeometryTypeVibrancyDark:
+ case nsNativeThemeCocoa::eThemeGeometryTypeVibrantTitlebarDark:
+ return Some(VibrancyType::DARK);
+ case nsNativeThemeCocoa::eThemeGeometryTypeSheet:
+ return Some(VibrancyType::SHEET);
+ case nsNativeThemeCocoa::eThemeGeometryTypeTooltip:
+ return Some(VibrancyType::TOOLTIP);
+ case nsNativeThemeCocoa::eThemeGeometryTypeMenu:
+ return Some(VibrancyType::MENU);
+ case nsNativeThemeCocoa::eThemeGeometryTypeHighlightedMenuItem:
+ return Some(VibrancyType::HIGHLIGHTED_MENUITEM);
+ case nsNativeThemeCocoa::eThemeGeometryTypeSourceList:
+ return Some(VibrancyType::SOURCE_LIST);
+ case nsNativeThemeCocoa::eThemeGeometryTypeSourceListSelection:
+ return Some(VibrancyType::SOURCE_LIST_SELECTION);
+ case nsNativeThemeCocoa::eThemeGeometryTypeActiveSourceListSelection:
+ return Some(VibrancyType::ACTIVE_SOURCE_LIST_SELECTION);
+ default:
+ return Nothing();
+ }
+}
+
+static LayoutDeviceIntRegion GatherVibrantRegion(
+ const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries, VibrancyType aVibrancyType) {
+ LayoutDeviceIntRegion region;
+ for (auto& geometry : aThemeGeometries) {
+ if (ThemeGeometryTypeToVibrancyType(geometry.mType) == Some(aVibrancyType)) {
+ region.OrWith(geometry.mRect);
+ }
+ }
+ return region;
+}
+
+template <typename Region>
+static void MakeRegionsNonOverlappingImpl(Region& aOutUnion) {}
+
+template <typename Region, typename... Regions>
+static void MakeRegionsNonOverlappingImpl(Region& aOutUnion, Region& aFirst, Regions&... aRest) {
+ MakeRegionsNonOverlappingImpl(aOutUnion, aRest...);
+ aFirst.SubOut(aOutUnion);
+ aOutUnion.OrWith(aFirst);
+}
+
+// Subtracts parts from regions in such a way that they don't have any overlap.
+// Each region in the argument list will have the union of all the regions
+// *following* it subtracted from itself. In other words, the arguments are
+// sorted low priority to high priority.
+template <typename Region, typename... Regions>
+static void MakeRegionsNonOverlapping(Region& aFirst, Regions&... aRest) {
+ Region unionOfAll;
+ MakeRegionsNonOverlappingImpl(unionOfAll, aFirst, aRest...);
+}
+
+void nsChildView::UpdateVibrancy(const nsTArray<ThemeGeometry>& aThemeGeometries) {
+ LayoutDeviceIntRegion sheetRegion = GatherVibrantRegion(aThemeGeometries, VibrancyType::SHEET);
+ LayoutDeviceIntRegion vibrantLightRegion =
+ GatherVibrantRegion(aThemeGeometries, VibrancyType::LIGHT);
+ LayoutDeviceIntRegion vibrantDarkRegion =
+ GatherVibrantRegion(aThemeGeometries, VibrancyType::DARK);
+ LayoutDeviceIntRegion menuRegion = GatherVibrantRegion(aThemeGeometries, VibrancyType::MENU);
+ LayoutDeviceIntRegion tooltipRegion =
+ GatherVibrantRegion(aThemeGeometries, VibrancyType::TOOLTIP);
+ LayoutDeviceIntRegion highlightedMenuItemRegion =
+ GatherVibrantRegion(aThemeGeometries, VibrancyType::HIGHLIGHTED_MENUITEM);
+ LayoutDeviceIntRegion sourceListRegion =
+ GatherVibrantRegion(aThemeGeometries, VibrancyType::SOURCE_LIST);
+ LayoutDeviceIntRegion sourceListSelectionRegion =
+ GatherVibrantRegion(aThemeGeometries, VibrancyType::SOURCE_LIST_SELECTION);
+ LayoutDeviceIntRegion activeSourceListSelectionRegion =
+ GatherVibrantRegion(aThemeGeometries, VibrancyType::ACTIVE_SOURCE_LIST_SELECTION);
+
+ MakeRegionsNonOverlapping(sheetRegion, vibrantLightRegion, vibrantDarkRegion, menuRegion,
+ tooltipRegion, highlightedMenuItemRegion, sourceListRegion,
+ sourceListSelectionRegion, activeSourceListSelectionRegion);
+
+ auto& vm = EnsureVibrancyManager();
+ bool changed = false;
+ changed |= vm.UpdateVibrantRegion(VibrancyType::LIGHT, vibrantLightRegion);
+ changed |= vm.UpdateVibrantRegion(VibrancyType::DARK, vibrantDarkRegion);
+ changed |= vm.UpdateVibrantRegion(VibrancyType::MENU, menuRegion);
+ changed |= vm.UpdateVibrantRegion(VibrancyType::TOOLTIP, tooltipRegion);
+ changed |= vm.UpdateVibrantRegion(VibrancyType::HIGHLIGHTED_MENUITEM, highlightedMenuItemRegion);
+ changed |= vm.UpdateVibrantRegion(VibrancyType::SHEET, sheetRegion);
+ changed |= vm.UpdateVibrantRegion(VibrancyType::SOURCE_LIST, sourceListRegion);
+ changed |= vm.UpdateVibrantRegion(VibrancyType::SOURCE_LIST_SELECTION, sourceListSelectionRegion);
+ changed |= vm.UpdateVibrantRegion(VibrancyType::ACTIVE_SOURCE_LIST_SELECTION,
+ activeSourceListSelectionRegion);
+
+ if (changed) {
+ SuspendAsyncCATransactions();
+ }
+}
+
+mozilla::VibrancyManager& nsChildView::EnsureVibrancyManager() {
+ MOZ_ASSERT(mView, "Only call this once we have a view!");
+ if (!mVibrancyManager) {
+ mVibrancyManager = MakeUnique<VibrancyManager>(*this, [mView vibrancyViewsContainer]);
+ }
+ return *mVibrancyManager;
+}
+
+nsChildView::SwipeInfo nsChildView::SendMayStartSwipe(
+ const mozilla::PanGestureInput& aSwipeStartEvent) {
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ uint32_t direction = (aSwipeStartEvent.mPanDisplacement.x > 0.0)
+ ? (uint32_t)dom::SimpleGestureEvent_Binding::DIRECTION_RIGHT
+ : (uint32_t)dom::SimpleGestureEvent_Binding::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, aSwipeStartEvent.mTimeStamp);
+ 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(aSwipeStartEvent.mTimeStamp);
+ mSwipeTracker->Destroy();
+ mSwipeTracker = nullptr;
+ }
+
+ uint32_t direction = (aSwipeStartEvent.mPanDisplacement.x > 0.0)
+ ? (uint32_t)dom::SimpleGestureEvent_Binding::DIRECTION_RIGHT
+ : (uint32_t)dom::SimpleGestureEvent_Binding::DIRECTION_LEFT;
+
+ mSwipeTracker = new SwipeTracker(*this, aSwipeStartEvent, aAllowedDirections, direction);
+
+ if (!mAPZC) {
+ mCurrentPanGestureBelongsToSwipe = true;
+ }
+}
+
+void nsChildView::SwipeFinished() { mSwipeTracker = nullptr; }
+
+void nsChildView::UpdateBoundsFromView() {
+ auto oldSize = mBounds.Size();
+ mBounds = CocoaPointsToDevPixels([mView frame]);
+ if (mBounds.Size() != oldSize) {
+ SuspendAsyncCATransactions();
+ }
+}
+
+@interface NonDraggableView : NSView
+@end
+
+@implementation NonDraggableView
+- (BOOL)mouseDownCanMoveWindow {
+ return NO;
+}
+- (NSView*)hitTest:(NSPoint)aPoint {
+ return nil;
+}
+- (NSRect)_opaqueRectForWindowMoveWhenInTitlebar {
+ // In NSWindows that use NSWindowStyleMaskFullSizeContentView, NSViews which
+ // overlap the titlebar do not disable window dragging in the overlapping
+ // areas even if they return NO from mouseDownCanMoveWindow. This can have
+ // unfortunate effects: For example, dragging tabs in a browser window would
+ // move the window if those tabs are in the titlebar.
+ // macOS does not seem to offer a documented way to opt-out of the forced
+ // window dragging in the titlebar.
+ // Overriding _opaqueRectForWindowMoveWhenInTitlebar is an undocumented way
+ // of opting out of this behavior. This method was added in 10.11 and is used
+ // by some NSControl subclasses to prevent window dragging in the titlebar.
+ // The function which assembles the draggable area of the window calls
+ // _opaqueRect for the content area and _opaqueRectForWindowMoveWhenInTitlebar
+ // for the titlebar area, on all visible NSViews. The default implementation
+ // of _opaqueRect returns [self visibleRect], and the default implementation
+ // of _opaqueRectForWindowMoveWhenInTitlebar returns NSZeroRect unless it's
+ // overridden.
+ return [self visibleRect];
+}
+@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;
+ }
+}
+
+nsEventStatus nsChildView::DispatchAPZInputEvent(InputData& aEvent) {
+ APZEventResult result;
+
+ if (mAPZC) {
+ result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent);
+ }
+
+ if (result.mStatus == nsEventStatus_eConsumeNoDefault) {
+ return result.mStatus;
+ }
+
+ if (aEvent.mInputType == PINCHGESTURE_INPUT) {
+ PinchGestureInput& pinchEvent = aEvent.AsPinchGestureInput();
+ WidgetWheelEvent wheelEvent = pinchEvent.ToWidgetEvent(this);
+ ProcessUntransformedAPZEvent(&wheelEvent, result);
+ }
+
+ return result.mStatus;
+}
+
+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) {
+ APZEventResult result;
+
+ switch (aEvent.mInputType) {
+ case PANGESTURE_INPUT: {
+ result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent);
+ if (result.mStatus == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+
+ PanGestureInput& panInput = aEvent.AsPanGestureInput();
+
+ event = panInput.ToWidgetEvent(this);
+ if (aCanTriggerSwipe && panInput.mOverscrollBehaviorAllowsSwipe) {
+ SwipeInfo swipeInfo = SendMayStartSwipe(panInput);
+ event.mCanTriggerSwipe = swipeInfo.wantsSwipe;
+ if (swipeInfo.wantsSwipe) {
+ if (result.mStatus == nsEventStatus_eIgnore) {
+ // APZ has determined and that scrolling horizontally in the
+ // requested direction is impossible, so it didn't do any
+ // scrolling for the event.
+ // We know now that MayStartSwipe wants a swipe, so we can start
+ // the swipe now.
+ TrackScrollEventAsSwipe(panInput, swipeInfo.allowedDirections);
+ } else {
+ // We don't know whether this event can start a swipe, so we need
+ // to queue up events and wait for a call to ReportSwipeStarted.
+ // APZ might already have started scrolling in response to the
+ // event if it knew that it's the right thing to do. In that case
+ // we'll still get a call to ReportSwipeStarted, and we will
+ // discard the queued events at that point.
+ mSwipeEventQueue =
+ MakeUnique<SwipeEventQueue>(swipeInfo.allowedDirections, result.mInputBlockId);
+ }
+ }
+ }
+
+ if (mSwipeEventQueue && mSwipeEventQueue->inputBlockId == result.mInputBlockId) {
+ mSwipeEventQueue->queuedEvents.AppendElement(panInput);
+ }
+ break;
+ }
+ case SCROLLWHEEL_INPUT: {
+ // For wheel events on OS X, send it to APZ using the WidgetInputEvent
+ // variant of ReceiveInputEvent, because the APZInputBridge version of
+ // that function has special handling (for delta multipliers etc.) that
+ // we need to run. Using the InputData variant would bypass that and
+ // go straight to the APZCTreeManager subclass.
+ event = aEvent.AsScrollWheelInput().ToWidgetEvent(this);
+ result = mAPZC->InputBridge()->ReceiveInputEvent(event);
+ if (result.mStatus == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+ break;
+ };
+ default:
+ MOZ_CRASH("unsupported event type");
+ return;
+ }
+ if (event.mMessage == eWheel && (event.mDeltaX != 0 || event.mDeltaY != 0)) {
+ ProcessUntransformedAPZEvent(&event, 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.ToWidgetEvent(this);
+ if (aCanTriggerSwipe) {
+ SwipeInfo swipeInfo = SendMayStartSwipe(panInput);
+
+ // We're in the non-APZ case here, but we still want to know whether
+ // the event was routed to a child process, so we use InputAPZContext
+ // to get that piece of information.
+ ScrollableLayerGuid guid;
+ InputAPZContext context(guid, 0, nsEventStatus_eIgnore);
+
+ event.mCanTriggerSwipe = swipeInfo.wantsSwipe;
+ DispatchEvent(&event, status);
+ if (swipeInfo.wantsSwipe) {
+ if (context.WasRoutedToChildProcess()) {
+ // We don't know whether this event can start a swipe, so we need
+ // to queue up events and wait for a call to ReportSwipeStarted.
+ mSwipeEventQueue = MakeUnique<SwipeEventQueue>(swipeInfo.allowedDirections, 0);
+ } else if (event.TriggersSwipe()) {
+ TrackScrollEventAsSwipe(panInput, swipeInfo.allowedDirections);
+ }
+ }
+
+ if (mSwipeEventQueue && mSwipeEventQueue->inputBlockId == 0) {
+ mSwipeEventQueue->queuedEvents.AppendElement(panInput);
+ }
+ return;
+ }
+ break;
+ }
+ case SCROLLWHEEL_INPUT: {
+ event = aEvent.AsScrollWheelInput().ToWidgetEvent(this);
+ break;
+ }
+ default:
+ MOZ_CRASH("unexpected event type");
+ return;
+ }
+ if (event.mMessage == eWheel && (event.mDeltaX != 0 || event.mDeltaY != 0)) {
+ DispatchEvent(&event, status);
+ }
+}
+
+void nsChildView::LookUpDictionary(const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRangeArray,
+ const bool aIsVertical, const LayoutDeviceIntPoint& aPoint) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSMutableAttributedString* attrStr = nsCocoaUtils::GetNSMutableAttributedString(
+ aText, aFontRangeArray, aIsVertical, BackingScaleFactor());
+ NSPoint pt = nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor());
+ NSDictionary* attributes = [attrStr attributesAtIndex:0 effectiveRange:nil];
+ NSFont* font = [attributes objectForKey:NSFontAttributeName];
+ if (font) {
+ if (aIsVertical) {
+ pt.x -= [font descender];
+ } else {
+ pt.y += [font ascender];
+ }
+ }
+
+ [mView showDefinitionForAttributedString:attrStr atPoint:pt];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#ifdef ACCESSIBILITY
+already_AddRefed<a11y::Accessible> nsChildView::GetDocumentAccessible() {
+ if (!mozilla::a11y::ShouldA11yBeEnabled()) return nullptr;
+
+ // mAccessible might be dead if accessibility was previously disabled and is
+ // now being enabled again.
+ if (mAccessible && mAccessible->IsAlive()) {
+ RefPtr<a11y::Accessible> ret;
+ CallQueryReferent(mAccessible.get(), static_cast<a11y::Accessible**>(getter_AddRefs(ret)));
+ return ret.forget();
+ }
+
+ // need to fetch the accessible anew, because it has gone away.
+ // cache the accessible in our weak ptr
+ RefPtr<a11y::Accessible> acc = GetRootAccessible();
+ mAccessible = do_GetWeakReference(acc.get());
+
+ return acc.forget();
+}
+#endif
+
+class WidgetsReleaserRunnable final : public mozilla::Runnable {
+ public:
+ explicit WidgetsReleaserRunnable(nsTArray<nsCOMPtr<nsIWidget>>&& aWidgetArray)
+ : mozilla::Runnable("WidgetsReleaserRunnable"), mWidgetArray(std::move(aWidgetArray)) {}
+
+ // Do nothing; all this runnable does is hold a reference the widgets in
+ // mWidgetArray, and those references will be dropped when this runnable
+ // is destroyed.
+
+ private:
+ nsTArray<nsCOMPtr<nsIWidget>> mWidgetArray;
+};
+
+#pragma mark -
+
+// ViewRegionContainerView is a view class for certain subviews of ChildView
+// which contain the NSViews created for ViewRegions (see ViewRegion.h).
+// It doesn't do anything interesting, it only acts as a container so that it's
+// easier for ChildView to control the z order of its children.
+@interface ViewRegionContainerView : NSView {
+}
+@end
+
+@implementation ViewRegionContainerView
+
+- (NSView*)hitTest:(NSPoint)aPoint {
+ return nil; // Be transparent to mouse events.
+}
+
+- (BOOL)isFlipped {
+ return [[self superview] isFlipped];
+}
+
+- (BOOL)mouseDownCanMoveWindow {
+ return [[self superview] mouseDownCanMoveWindow];
+}
+
+@end
+
+@implementation ChildView
+
+// globalDragPboard is non-null during native drag sessions that did not originate
+// in our native NSView (it is set in |draggingEntered:|). It is unset when the
+// drag session ends for this view, either with the mouse exiting or when a drop
+// occurs in this view.
+NSPasteboard* globalDragPboard = nil;
+
+// gLastDragView and gLastDragMouseDownEvent are used to communicate information
+// to the drag service during drag invocation (starting a drag in from the view).
+// gLastDragView is only non-null while a mouse button is pressed, so between
+// mouseDown and mouseUp.
+NSView* gLastDragView = nil; // [weak]
+NSEvent* gLastDragMouseDownEvent = nil; // [strong]
+
++ (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* types = @[
+ [UTIHelper stringFromPboardType:NSPasteboardTypeString],
+ [UTIHelper stringFromPboardType:NSPasteboardTypeHTML]
+ ];
+ [NSApp registerServicesMenuSendTypes:types returnTypes:types];
+ initialized = YES;
+ }
+}
+
++ (void)registerViewForDraggedTypes:(NSView*)aView {
+ [aView
+ registerForDraggedTypes:
+ [NSArray
+ arrayWithObjects:[UTIHelper stringFromPboardType:NSFilenamesPboardType],
+ [UTIHelper stringFromPboardType:kMozFileUrlsPboardType],
+ [UTIHelper stringFromPboardType:NSPasteboardTypeString],
+ [UTIHelper stringFromPboardType:NSPasteboardTypeHTML],
+ [UTIHelper
+ stringFromPboardType:(NSString*)kPasteboardTypeFileURLPromise],
+ [UTIHelper stringFromPboardType:kMozWildcardPboardType],
+ [UTIHelper stringFromPboardType:kPublicUrlPboardType],
+ [UTIHelper stringFromPboardType:kPublicUrlNamePboardType],
+ [UTIHelper stringFromPboardType:kUrlsWithTitlesPboardType], 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;
+ mCumulativeRotation = 0.0;
+
+ mIsUpdatingLayer = 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];
+
+ mRootCALayer = [[CALayer layer] retain];
+ mRootCALayer.position = NSZeroPoint;
+ mRootCALayer.bounds = NSZeroRect;
+ mRootCALayer.anchorPoint = NSZeroPoint;
+ mRootCALayer.contentsGravity = kCAGravityTopLeft;
+ [[mPixelHostingView layer] addSublayer:mRootCALayer];
+
+ mLastPressureStage = 0;
+ }
+
+ // 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];
+
+ if (nsCocoaFeatures::OnMojaveOrLater() &&
+ NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification) {
+ [[[NSWorkspace sharedWorkspace] notificationCenter]
+ addObserver:self
+ selector:@selector(systemMetricsChanged)
+ name:NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification
+ object:nil];
+ } else if (NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification) {
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(systemMetricsChanged)
+ name:NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification
+ 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];
+
+ [[NSDistributedNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(systemMetricsChanged)
+ name:@"AppleInterfaceThemeChangedNotification"
+ object:nil
+ suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
+
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSTextInputContext*)inputContext {
+ 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;
+}
+
+- (NSView*)vibrancyViewsContainer {
+ return mVibrancyViewsContainer;
+}
+
+- (NSView*)nonDraggableViewsContainer {
+ return mNonDraggableViewsContainer;
+}
+
+- (NSView*)pixelHostingView {
+ return mPixelHostingView;
+}
+
+- (void)dealloc {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mLastMouseDownEvent release];
+ [mLastKeyDownEvent release];
+ [mClickThroughMouseDownEvent release];
+ ChildViewMouseTracker::OnDestroyView(self);
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
+ [mVibrancyViewsContainer removeFromSuperview];
+ [mVibrancyViewsContainer release];
+ [mNonDraggableViewsContainer removeFromSuperview];
+ [mNonDraggableViewsContainer release];
+ [mPixelHostingView removeFromSuperview];
+ [mPixelHostingView release];
+ [mRootCALayer release];
+
+ if (gLastDragView == self) {
+ gLastDragView = nil;
+ }
+
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)widgetDestroyed {
+ if (mTextInputHandler) {
+ mTextInputHandler->OnDestroyWidget(mGeckoChild);
+ mTextInputHandler = nullptr;
+ }
+ mGeckoChild = nullptr;
+
+ // Just in case we're destroyed abruptly and missed the draggingExited
+ // or performDragOperation message.
+ NS_IF_RELEASE(mDragService);
+}
+
+// mozView method, return our gecko child view widget. Note this does not AddRef.
+- (nsIWidget*)widget {
+ return static_cast<nsIWidget*>(mGeckoChild);
+}
+
+- (void)systemMetricsChanged {
+ // TODO(emilio): We could make this more fine-grained by only passing true
+ // here when system colors / fonts change, but right now we tunnel all the
+ // relevant notifications through here.
+ if (mGeckoChild) mGeckoChild->NotifyThemeChanged(widget::ThemeChangeKind::StyleAndLayout);
+}
+
+- (void)scrollbarSystemMetricChanged {
+ [self systemMetricsChanged];
+}
+
+- (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;
+}
+
+// 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)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 {
+ nsCocoaWindow* windowWidget = mGeckoChild ? mGeckoChild->GetAppWindowWidget() : nullptr;
+ if (windowWidget) {
+ windowWidget->NotifyLiveResizeStarted();
+ }
+}
+
+- (void)viewDidEndLiveResize {
+ // mGeckoChild may legitimately be null here. It should also have been null
+ // in viewWillStartLiveResize, so there's no problem. However if we run into
+ // cases where the windowWidget was non-null in viewWillStartLiveResize but
+ // is null here, that might be problematic because we might get stuck with
+ // a content process that has the displayport suppressed. If that scenario
+ // arises (I'm not sure that it does) we will need to handle it gracefully.
+ nsCocoaWindow* windowWidget = mGeckoChild ? mGeckoChild->GetAppWindowWidget() : nullptr;
+ if (windowWidget) {
+ windowWidget->NotifyLiveResizeStopped();
+ }
+}
+
+- (void)markLayerForDisplay {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (!mIsUpdatingLayer) {
+ // This call will cause updateRootCALayer to be called during the upcoming
+ // main thread CoreAnimation transaction. It will also trigger a transaction
+ // if no transaction is currently pending.
+ [[mPixelHostingView layer] setNeedsDisplay];
+ }
+}
+
+- (void)ensureNextCompositeIsAtomicWithMainThreadPaint {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (mGeckoChild) {
+ mGeckoChild->SuspendAsyncCATransactions();
+ }
+}
+
+- (void)updateRootCALayer {
+ if (NS_IsMainThread() && mGeckoChild) {
+ MOZ_RELEASE_ASSERT(!mIsUpdatingLayer, "Re-entrant layer display?");
+ mIsUpdatingLayer = YES;
+ mGeckoChild->HandleMainThreadCATransaction();
+ mIsUpdatingLayer = NO;
+ }
+}
+
+- (CALayer*)rootCALayer {
+ return mRootCALayer;
+}
+
+// If we've just created a non-native context menu, we need to mark it as
+// such and let the OS (and other programs) know when it opens and closes
+// (this is how the OS knows to close other programs' context menus when
+// ours open). We send the initial notification here, but others are sent
+// in nsCocoaWindow::Show().
+- (void)maybeInitContextMenuTracking {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
+ return;
+#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ NS_ENSURE_TRUE_VOID(rollupListener);
+ nsCOMPtr<nsIWidget> widget = rollupListener->GetRollupWidget();
+ NS_ENSURE_TRUE_VOID(widget);
+
+ NSWindow* popupWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
+ if (!popupWindow || ![popupWindow isKindOfClass:[PopupWindow class]]) return;
+
+ [[NSDistributedNotificationCenter defaultCenter]
+ postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification"
+ object:@"org.mozilla.gecko.PopupWindow"];
+ [(PopupWindow*)popupWindow setIsContextMenu:YES];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Returns true if the event should no longer be processed, false otherwise.
+// This does not return whether or not anything was rolled up.
+- (BOOL)maybeRollup:(NSEvent*)theEvent {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ BOOL consumeEvent = NO;
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ NS_ENSURE_TRUE(rollupListener, false);
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (rollupWidget) {
+ NSWindow* currentPopup = static_cast<NSWindow*>(rollupWidget->GetNativeData(NS_NATIVE_WINDOW));
+ if (!nsCocoaUtils::IsEventOverWindow(theEvent, currentPopup)) {
+ // event is not over the rollup window, default is to roll up
+ bool shouldRollup = true;
+
+ // check to see if scroll/zoom events should roll up the popup
+ if ([theEvent type] == NSEventTypeScrollWheel || [theEvent type] == NSEventTypeMagnify) {
+ shouldRollup = rollupListener->ShouldRollupOnMouseWheelEvent();
+ // consume scroll events that aren't over the popup
+ // unless the popup is an arrow panel
+ consumeEvent = rollupListener->ShouldConsumeOnMouseWheelEvent();
+ }
+
+ // if we're dealing with menus, we probably have submenus and
+ // we don't want to rollup if the click is in a parent menu of
+ // the current submenu
+ uint32_t popupsToRollup = UINT32_MAX;
+ AutoTArray<nsIWidget*, 5> widgetChain;
+ uint32_t sameTypeCount = rollupListener->GetSubmenuWidgetChain(&widgetChain);
+ for (uint32_t i = 0; i < widgetChain.Length(); i++) {
+ nsIWidget* widget = widgetChain[i];
+ NSWindow* currWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
+ if (nsCocoaUtils::IsEventOverWindow(theEvent, currWindow)) {
+ // don't roll up if the mouse event occurred within a menu of the
+ // same type. If the mouse event occurred in a menu higher than
+ // that, roll up, but pass the number of popups to Rollup so
+ // that only those of the same type close up.
+ if (i < sameTypeCount) {
+ shouldRollup = false;
+ } else {
+ popupsToRollup = sameTypeCount;
+ }
+ break;
+ }
+ }
+
+ if (shouldRollup) {
+ if ([theEvent type] == NSEventTypeLeftMouseDown) {
+ NSPoint point = [NSEvent mouseLocation];
+ FlipCocoaScreenCoordinate(point);
+ LayoutDeviceIntPoint devPoint = mGeckoChild->CocoaPointsToDevPixels(point);
+ gfx::IntPoint pos = devPoint.ToUnknownPoint();
+ 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);
+}
+
+- (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 |= dom::SimpleGestureEvent_Binding::DIRECTION_LEFT;
+ else if (deltaX < 0.0)
+ geckoEvent.mDirection |= dom::SimpleGestureEvent_Binding::DIRECTION_RIGHT;
+
+ // Record the up/down direction.
+ if (deltaY > 0.0)
+ geckoEvent.mDirection |= dom::SimpleGestureEvent_Binding::DIRECTION_UP;
+ else if (deltaY < 0.0)
+ geckoEvent.mDirection |= dom::SimpleGestureEvent_Binding::DIRECTION_DOWN;
+
+ // Send the event.
+ mGeckoChild->DispatchWindowEvent(geckoEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Pinch zoom gesture.
+- (void)magnifyWithEvent:(NSEvent*)anEvent {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if ([self maybeRollup:anEvent]) {
+ return;
+ }
+
+ if (!mGeckoChild) {
+ return;
+ }
+
+ // Instead of calling beginOrEndGestureForEventPhase we basically inline
+ // the effects of it here, because that function doesn't play too well with
+ // how we create PinchGestureInput events below. The main point of that
+ // function is to avoid flip-flopping between rotation/magnify gestures, which
+ // we can do by checking and setting mGestureState appropriately. A secondary
+ // result of that function is to send the final eMagnifyGesture event when
+ // the gesture ends, but APZ takes care of that for us.
+ if (mGestureState == eGestureState_RotateGesture && [anEvent phase] != NSEventPhaseBegan) {
+ // If we're already in a rotation and not "starting" a magnify, abort.
+ return;
+ }
+ mGestureState = eGestureState_MagnifyGesture;
+
+ NSPoint locationInWindow = nsCocoaUtils::EventLocationForWindow(anEvent, [self window]);
+ ScreenPoint position =
+ ViewAs<ScreenPixel>([self convertWindowCoordinatesRoundDown:locationInWindow],
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+ ExternalPoint screenOffset =
+ ViewAs<ExternalPixel>(mGeckoChild->WidgetToScreenOffset(),
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+
+ PRIntervalTime eventIntervalTime = PR_IntervalNow();
+ TimeStamp eventTimeStamp = nsCocoaUtils::GetEventTimeStamp([anEvent timestamp]);
+ NSEventPhase eventPhase = [anEvent phase];
+ PinchGestureInput::PinchGestureType pinchGestureType;
+
+ switch (eventPhase) {
+ case NSEventPhaseBegan: {
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
+ break;
+ }
+ case NSEventPhaseChanged: {
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
+ break;
+ }
+ case NSEventPhaseEnded: {
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
+ mGestureState = eGestureState_None;
+ break;
+ }
+ default: {
+ NS_WARNING("Unexpected phase for pinch gesture event.");
+ return;
+ }
+ }
+
+ PinchGestureInput event{pinchGestureType,
+ PinchGestureInput::TRACKPAD,
+ eventIntervalTime,
+ eventTimeStamp,
+ screenOffset,
+ position,
+ 100.0,
+ 100.0 * (1.0 - [anEvent magnification]),
+ nsCocoaUtils::ModifiersForEvent(anEvent)};
+
+ mGeckoChild->DispatchAPZInputEvent(event);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Smart zoom gesture, i.e. two-finger double tap on trackpads.
+- (void)smartMagnifyWithEvent:(NSEvent*)anEvent {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!anEvent || !mGeckoChild || [self beginOrEndGestureForEventPhase:anEvent]) {
+ 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 || [self beginOrEndGestureForEventPhase:anEvent]) {
+ 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 = dom::SimpleGestureEvent_Binding::ROTATION_COUNTERCLOCKWISE;
+ } else {
+ geckoEvent.mDirection = dom::SimpleGestureEvent_Binding::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;
+}
+
+// `beginGestureWithEvent` and `endGestureWithEvent` are not called for
+// applications that link against the macOS 10.11 or later SDK when we're
+// running on macOS 10.11 or later. For compatibility with all supported macOS
+// versions, we have to call {begin,end}GestureWithEvent ourselves based on
+// the event phase when we're handling gestures.
+- (bool)beginOrEndGestureForEventPhase:(NSEvent*)aEvent {
+ if (!aEvent) {
+ return false;
+ }
+
+ bool usingElCapitanOrLaterSDK = true;
+#if !defined(MAC_OS_X_VERSION_10_11) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_11
+ usingElCapitanOrLaterSDK = false;
+#endif
+
+ if (usingElCapitanOrLaterSDK) {
+ if (aEvent.phase == NSEventPhaseBegan) {
+ [self beginGestureWithEvent:aEvent];
+ return true;
+ }
+
+ if (aEvent.phase == NSEventPhaseEnded || aEvent.phase == NSEventPhaseCancelled) {
+ [self endGestureWithEvent:aEvent];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+- (void)beginGestureWithEvent:(NSEvent*)aEvent {
+ if (!aEvent) {
+ return;
+ }
+
+ mGestureState = eGestureState_StartGesture;
+ mCumulativeRotation = 0.0;
+}
+
+- (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;
+ mCumulativeRotation = 0.0;
+ return;
+ }
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ switch (mGestureState) {
+ 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 = dom::SimpleGestureEvent_Binding::ROTATION_COUNTERCLOCKWISE;
+ } else {
+ geckoEvent.mDirection = dom::SimpleGestureEvent_Binding::ROTATION_CLOCKWISE;
+ }
+
+ // Send the event.
+ mGeckoChild->DispatchWindowEvent(geckoEvent);
+ } break;
+
+ case eGestureState_MagnifyGesture: // APZ handles sending the widget events
+ case eGestureState_None:
+ case eGestureState_StartGesture:
+ default:
+ break;
+ }
+
+ // Clear the gestures state.
+ mGestureState = eGestureState_None;
+ 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] != NSEventTypeScrollWheel || 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];
+ gLastDragView = self;
+
+ // 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;
+ }
+
+ // in order to send gecko events we'll need a gecko widget
+ if (!mGeckoChild) return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ 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 (!StaticPrefs::dom_event_treat_ctrl_click_as_right_click_disabled() &&
+ ([theEvent modifierFlags] & NSEventModifierFlagControl)) {
+ geckoEvent.mButton = MouseButton::eSecondary;
+ } else {
+ geckoEvent.mButton = MouseButton::ePrimary;
+ }
+
+ 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;
+
+ gLastDragView = nil;
+
+ 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 (!StaticPrefs::dom_event_treat_ctrl_click_as_right_click_disabled() &&
+ ([theEvent modifierFlags] & NSEventModifierFlagControl)) {
+ geckoEvent.mButton = MouseButton::eSecondary;
+ } else {
+ geckoEvent.mButton = MouseButton::ePrimary;
+ }
+
+ // Remember the event's position before calling DispatchInputEvent, because
+ // that call can mutate it and convert it into a different coordinate space.
+ LayoutDeviceIntPoint pos = geckoEvent.mRefPoint;
+
+ // This might destroy our widget (and null out mGeckoChild).
+ bool defaultPrevented =
+ (mGeckoChild->DispatchInputEvent(&geckoEvent) == nsEventStatus_eConsumeNoDefault);
+
+ if (!mGeckoChild) {
+ return;
+ }
+
+ // Check to see if we are double-clicking in draggable parts of the window.
+ if (!defaultPrevented && [theEvent clickCount] == 2 &&
+ !mGeckoChild->GetNonDraggableRegion().Contains(pos.x, pos.y)) {
+ if (nsCocoaUtils::ShouldZoomOnTitlebarDoubleClick()) {
+ [[self window] performZoom:nil];
+ } else if (nsCocoaUtils::ShouldMinimizeOnTitlebarDoubleClick()) {
+ [[self window] performMiniaturize:nil];
+ }
+ }
+
+ 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);
+ if (event.mMessage == eMouseExitFromWidget) {
+ event.mExitFrom = Some(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;
+ }
+
+ 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.
+
+ // 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.mButton = MouseButton::eSecondary;
+ geckoEvent.mClickCount = [theEvent clickCount];
+
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+ if (!mGeckoChild) return;
+
+ if (!StaticPrefs::ui_context_menus_after_mouseup()) {
+ // 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.mButton = MouseButton::eSecondary;
+ geckoEvent.mClickCount = [theEvent clickCount];
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+ if (!mGeckoChild) return;
+
+ if (StaticPrefs::ui_context_menus_after_mouseup()) {
+ // Let the superclass do the context menu stuff, but pretend it's rightMouseDown.
+ NSEvent* dupeEvent = [NSEvent mouseEventWithType:NSEventTypeRightMouseDown
+ location:theEvent.locationInWindow
+ modifierFlags:theEvent.modifierFlags
+ timestamp:theEvent.timestamp
+ windowNumber:theEvent.windowNumber
+ context:theEvent.context
+ eventNumber:theEvent.eventNumber
+ clickCount:theEvent.clickCount
+ pressure:theEvent.pressure];
+
+ [super rightMouseDown:dupeEvent];
+ }
+
+ 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.mButton = MouseButton::eSecondary;
+
+ // 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.mButton = MouseButton::eMiddle;
+ 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.mButton = MouseButton::eMiddle;
+
+ 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.mButton = MouseButton::eMiddle;
+
+ // send event into Gecko by going directly to the
+ // the widget.
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+}
+
+- (void)sendWheelStartOrStop:(EventMessage)msg forEvent:(NSEvent*)theEvent {
+ WidgetWheelEvent wheelEvent(true, msg, mGeckoChild);
+ [self convertCocoaMouseWheelEvent:theEvent toGeckoEvent:&wheelEvent];
+ mExpectingWheelStop = (msg == eWheelOperationStart);
+ mGeckoChild->DispatchInputEvent(wheelEvent.AsInputEvent());
+}
+
+- (void)sendWheelCondition:(BOOL)condition
+ first:(EventMessage)first
+ second:(EventMessage)second
+ forEvent:(NSEvent*)theEvent {
+ if (mExpectingWheelStop == condition) {
+ [self sendWheelStartOrStop:first forEvent:theEvent];
+ }
+ [self sendWheelStartOrStop:second forEvent:theEvent];
+}
+
+static PanGestureInput::PanGestureType PanGestureTypeForEvent(NSEvent* aEvent) {
+ switch (nsCocoaUtils::EventPhase(aEvent)) {
+ case NSEventPhaseMayBegin:
+ return PanGestureInput::PANGESTURE_MAYSTART;
+ case NSEventPhaseCancelled:
+ return PanGestureInput::PANGESTURE_CANCELLED;
+ case NSEventPhaseBegan:
+ return PanGestureInput::PANGESTURE_START;
+ case NSEventPhaseChanged:
+ return PanGestureInput::PANGESTURE_PAN;
+ case NSEventPhaseEnded:
+ return PanGestureInput::PANGESTURE_END;
+ case NSEventPhaseNone:
+ switch (nsCocoaUtils::EventMomentumPhase(aEvent)) {
+ case NSEventPhaseBegan:
+ return PanGestureInput::PANGESTURE_MOMENTUMSTART;
+ case NSEventPhaseChanged:
+ return PanGestureInput::PANGESTURE_MOMENTUMPAN;
+ case NSEventPhaseEnded:
+ return PanGestureInput::PANGESTURE_MOMENTUMEND;
+ default:
+ NS_ERROR("unexpected event phase");
+ return PanGestureInput::PANGESTURE_PAN;
+ }
+ default:
+ NS_ERROR("unexpected event phase");
+ return PanGestureInput::PANGESTURE_PAN;
+ }
+}
+
+static int32_t RoundUp(double aDouble) {
+ return aDouble < 0 ? static_cast<int32_t>(floor(aDouble)) : static_cast<int32_t>(ceil(aDouble));
+}
+
+static gfx::IntPoint GetIntegerDeltaForEvent(NSEvent* aEvent) {
+ if ([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 PanGestureInput::GetIntegerDeltaForEvent(
+ (nsCocoaUtils::EventPhase(aEvent) == NSEventPhaseBegan), [aEvent deltaX], [aEvent deltaY]);
+ }
+
+ // For line scrolls, or pre-10.12, just use the rounded up value of deltaX / deltaY.
+ return gfx::IntPoint(RoundUp([aEvent deltaX]), RoundUp([aEvent deltaY]));
+}
+
+- (void)scrollWheel:(NSEvent*)theEvent {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ ChildViewMouseTracker::MouseScrolled(theEvent);
+
+ if ([self maybeRollup:theEvent]) {
+ return;
+ }
+
+ if (!mGeckoChild) {
+ return;
+ }
+
+ NSEventPhase phase = nsCocoaUtils::EventPhase(theEvent);
+ // Fire eWheelOperationStart/End events when 2 fingers touch/release the
+ // touchpad.
+ if (phase & NSEventPhaseMayBegin) {
+ [self sendWheelCondition:YES
+ first:eWheelOperationEnd
+ second:eWheelOperationStart
+ forEvent:theEvent];
+ } else if (phase & (NSEventPhaseEnded | NSEventPhaseCancelled)) {
+ [self sendWheelCondition:NO
+ first:eWheelOperationStart
+ second:eWheelOperationEnd
+ forEvent:theEvent];
+ }
+
+ if (!mGeckoChild) {
+ return;
+ }
+ RefPtr<nsChildView> geckoChildDeathGrip(mGeckoChild);
+
+ NSPoint locationInWindow = nsCocoaUtils::EventLocationForWindow(theEvent, [self window]);
+
+ // Use convertWindowCoordinatesRoundDown when converting the position to
+ // integer screen pixels in order to ensure that coordinates which are just
+ // inside the right / bottom edges of the window don't end up outside of the
+ // window after rounding.
+ ScreenPoint position =
+ ViewAs<ScreenPixel>([self convertWindowCoordinatesRoundDown:locationInWindow],
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+
+ bool usePreciseDeltas = nsCocoaUtils::HasPreciseScrollingDeltas(theEvent) &&
+ Preferences::GetBool("mousewheel.enable_pixel_scrolling", true);
+ bool hasPhaseInformation = nsCocoaUtils::EventHasPhaseInformation(theEvent);
+
+ gfx::IntPoint lineOrPageDelta = -GetIntegerDeltaForEvent(theEvent);
+
+ Modifiers modifiers = nsCocoaUtils::ModifiersForEvent(theEvent);
+
+ NSTimeInterval beforeNow = [[NSProcessInfo processInfo] systemUptime] - [theEvent timestamp];
+ PRIntervalTime eventIntervalTime = PR_IntervalNow() - PR_MillisecondsToInterval(beforeNow * 1000);
+ TimeStamp eventTimeStamp = TimeStamp::Now() - TimeDuration::FromSeconds(beforeNow);
+
+ ScreenPoint preciseDelta;
+ if (usePreciseDeltas) {
+ CGFloat pixelDeltaX = 0, pixelDeltaY = 0;
+ nsCocoaUtils::GetScrollingDeltas(theEvent, &pixelDeltaX, &pixelDeltaY);
+ double scale = geckoChildDeathGrip->BackingScaleFactor();
+ preciseDelta = ScreenPoint(-pixelDeltaX * scale, -pixelDeltaY * scale);
+ }
+
+ if (usePreciseDeltas && hasPhaseInformation) {
+ PanGestureInput panEvent(PanGestureTypeForEvent(theEvent), eventIntervalTime, eventTimeStamp,
+ position, preciseDelta, modifiers);
+ panEvent.mLineOrPageDeltaX = lineOrPageDelta.x;
+ panEvent.mLineOrPageDeltaY = lineOrPageDelta.y;
+
+ if (panEvent.mType == PanGestureInput::PANGESTURE_END) {
+ // Check if there's a momentum start event in the event queue, so that we
+ // can annotate this event.
+ NSEvent* nextWheelEvent = [NSApp nextEventMatchingMask:NSEventMaskScrollWheel
+ 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,
+ // This parameter is used for wheel delta
+ // adjustment, such as auto-dir scrolling,
+ // but we do't need to do anything special here
+ // since this wheel event is sent to
+ // DispatchAPZWheelInputEvent, which turns this
+ // ScrollWheelInput back into a WidgetWheelEvent
+ // and then it goes through the regular handling
+ // in APZInputBridge. So passing |eNone| won't
+ // pass up the necessary wheel delta adjustment.
+ WheelDeltaAdjustmentStrategy::eNone);
+ 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 (StaticPrefs::general_smoothScroll() && StaticPrefs::general_smoothScroll_mouseWheel()) {
+ scrollMode = ScrollWheelInput::SCROLLMODE_SMOOTH;
+ }
+ ScrollWheelInput wheelEvent(eventIntervalTime, eventTimeStamp, modifiers, scrollMode,
+ ScrollWheelInput::SCROLLDELTA_LINE, position, lineOrPageDelta.x,
+ lineOrPageDelta.y, false,
+ // This parameter is used for wheel delta
+ // adjustment, such as auto-dir scrolling,
+ // but we do't need to do anything special here
+ // since this wheel event is sent to
+ // DispatchAPZWheelInputEvent, which turns this
+ // ScrollWheelInput back into a WidgetWheelEvent
+ // and then it goes through the regular handling
+ // in APZInputBridge. So passing |eNone| won't
+ // pass up the necessary wheel delta adjustment.
+ WheelDeltaAdjustmentStrategy::eNone);
+ 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] == NSEventTypeLeftMouseDown) {
+ [self mouseDown:theEvent];
+ if (!mGeckoChild) return nil;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eContextMenu, mGeckoChild, WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ if (StaticPrefs::dom_event_treat_ctrl_click_as_right_click_disabled() &&
+ [theEvent type] == NSEventTypeLeftMouseDown) {
+ geckoEvent.mContextMenuTrigger = WidgetMouseEvent::eControlClick;
+ geckoEvent.mButton = MouseButton::ePrimary;
+ } else {
+ geckoEvent.mButton = MouseButton::eSecondary;
+ }
+
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+ if (!mGeckoChild) return nil;
+
+ [self maybeInitContextMenuTracking];
+
+ // We never return an actual NSMenu* for the context menu. Gecko might have
+ // responded to the eContextMenu event by putting up a fake context menu.
+ 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 ? dom::WheelEvent_Binding::DOM_DELTA_PIXEL
+ : dom::WheelEvent_Binding::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->mButtons = 0;
+ NSUInteger mouseButtons = [NSEvent pressedMouseButtons];
+
+ if (mouseButtons & 0x01) {
+ mouseEvent->mButtons |= MouseButtonsFlag::ePrimaryFlag;
+ }
+ if (mouseButtons & 0x02) {
+ mouseEvent->mButtons |= MouseButtonsFlag::eSecondaryFlag;
+ }
+ if (mouseButtons & 0x04) {
+ mouseEvent->mButtons |= MouseButtonsFlag::eMiddleFlag;
+ }
+ if (mouseButtons & 0x08) {
+ mouseEvent->mButtons |= MouseButtonsFlag::e4thFlag;
+ }
+ if (mouseButtons & 0x10) {
+ mouseEvent->mButtons |= MouseButtonsFlag::e5thFlag;
+ }
+
+ switch ([aMouseEvent type]) {
+ case NSEventTypeLeftMouseDown:
+ case NSEventTypeLeftMouseUp:
+ case NSEventTypeLeftMouseDragged:
+ case NSEventTypeRightMouseDown:
+ case NSEventTypeRightMouseUp:
+ case NSEventTypeRightMouseDragged:
+ case NSEventTypeOtherMouseDown:
+ case NSEventTypeOtherMouseUp:
+ case NSEventTypeOtherMouseDragged:
+ case NSEventTypeMouseMoved:
+ if ([aMouseEvent subtype] == NSEventSubtypeTabletPoint) {
+ [self convertCocoaTabletPointerEvent:aMouseEvent toGeckoEvent:mouseEvent->AsMouseEvent()];
+ }
+ break;
+
+ default:
+ // Don't check other NSEvents for pressure.
+ break;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)convertCocoaTabletPointerEvent:(NSEvent*)aPointerEvent
+ toGeckoEvent:(WidgetMouseEvent*)aOutGeckoEvent {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN
+ if (!aOutGeckoEvent || !sIsTabletPointerActivated) {
+ return;
+ }
+ if ([aPointerEvent type] != NSEventTypeMouseMoved) {
+ aOutGeckoEvent->mPressure = [aPointerEvent pressure];
+ MOZ_ASSERT(aOutGeckoEvent->mPressure >= 0.0 && aOutGeckoEvent->mPressure <= 1.0);
+ }
+ aOutGeckoEvent->mInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_PEN;
+ aOutGeckoEvent->tiltX = lround([aPointerEvent tilt].x * 90);
+ aOutGeckoEvent->tiltY = lround([aPointerEvent tilt].y * 90);
+ aOutGeckoEvent->tangentialPressure = [aPointerEvent tangentialPressure];
+ // Make sure the twist value is in the range of 0-359.
+ int32_t twist = fmod([aPointerEvent rotation], 360);
+ aOutGeckoEvent->twist = twist >= 0 ? twist : twist + 360;
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)tabletProximity:(NSEvent*)theEvent {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN
+ sIsTabletPointerActivated = [theEvent isEnteringProximity];
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+#pragma mark -
+// NSTextInputClient implementation
+
+- (NSRange)markedRange {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NS_ENSURE_TRUE(mTextInputHandler, NSMakeRange(NSNotFound, 0));
+ return mTextInputHandler->MarkedRange();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRange(0, 0));
+}
+
+- (NSRange)selectedRange {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NS_ENSURE_TRUE(mTextInputHandler, NSMakeRange(NSNotFound, 0));
+ return mTextInputHandler->SelectedRange();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRange(0, 0));
+}
+
+- (BOOL)drawsVerticallyForCharacterAtIndex:(NSUInteger)charIndex {
+ NS_ENSURE_TRUE(mTextInputHandler, NO);
+ if (charIndex == NSNotFound) {
+ return NO;
+ }
+ return mTextInputHandler->DrawsVerticallyForCharacterAtIndex(charIndex);
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
+ NS_ENSURE_TRUE(mTextInputHandler, 0);
+ return mTextInputHandler->CharacterIndexForPoint(thePoint);
+}
+
+- (NSArray*)validAttributesForMarkedText {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NS_ENSURE_TRUE(mTextInputHandler, [NSArray array]);
+ return mTextInputHandler->GetValidAttributesForMarkedText();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ENSURE_TRUE_VOID(mGeckoChild);
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ NSAttributedString* attrStr;
+ if ([aString isKindOfClass:[NSAttributedString class]]) {
+ attrStr = static_cast<NSAttributedString*>(aString);
+ } else {
+ attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease];
+ }
+
+ mTextInputHandler->InsertText(attrStr, &replacementRange);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)doCommandBySelector:(SEL)aSelector {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mGeckoChild || !mTextInputHandler) {
+ return;
+ }
+
+ const char* sel = reinterpret_cast<const char*>(aSelector);
+ if (!mTextInputHandler->DoCommandBySelector(sel)) {
+ [super doCommandBySelector:aSelector];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)unmarkText {
+ NS_ENSURE_TRUE_VOID(mTextInputHandler);
+ mTextInputHandler->CommitIMEComposition();
+}
+
+- (BOOL)hasMarkedText {
+ NS_ENSURE_TRUE(mTextInputHandler, NO);
+ return mTextInputHandler->HasMarkedText();
+}
+
+- (void)setMarkedText:(id)aString
+ selectedRange:(NSRange)selectedRange
+ replacementRange:(NSRange)replacementRange {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ENSURE_TRUE_VOID(mTextInputHandler);
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ NSAttributedString* attrStr;
+ if ([aString isKindOfClass:[NSAttributedString class]]) {
+ attrStr = static_cast<NSAttributedString*>(aString);
+ } else {
+ attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease];
+ }
+
+ mTextInputHandler->SetMarkedText(attrStr, selectedRange, &replacementRange);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)aRange
+ actualRange:(NSRangePointer)actualRange {
+ NS_ENSURE_TRUE(mTextInputHandler, nil);
+ return mTextInputHandler->GetAttributedSubstringFromRange(aRange, actualRange);
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange {
+ NS_ENSURE_TRUE(mTextInputHandler, NSMakeRect(0.0, 0.0, 0.0, 0.0));
+ return mTextInputHandler->FirstRectForCharacterRange(aRange, actualRange);
+}
+
+- (void)quickLookWithEvent:(NSEvent*)event {
+ // Show dictionary by current point
+ WidgetContentCommandEvent contentCommandEvent(true, eContentCommandLookUpDictionary, mGeckoChild);
+ NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
+ contentCommandEvent.mRefPoint = mGeckoChild->CocoaPointsToDevPixels(point);
+ mGeckoChild->DispatchWindowEvent(contentCommandEvent);
+ // The widget might have been destroyed.
+}
+
+- (NSInteger)windowLevel {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NS_ENSURE_TRUE(mTextInputHandler, [[self window] level]);
+ return mTextInputHandler->GetWindowLevel();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel);
+}
+
+#pragma mark -
+
+// This is a private API that Cocoa uses.
+// Cocoa will call this after the menu system returns "NO" for "performKeyEquivalent:".
+// We want all they key events we can get so just return YES. In particular, this fixes
+// ctrl-tab - we don't get a "keyDown:" call for that without this.
+- (BOOL)_wantsKeyDownForEvent:(NSEvent*)event {
+ return YES;
+}
+
+- (NSEvent*)lastKeyDownEvent {
+ return mLastKeyDownEvent;
+}
+
+- (void)keyDown:(NSEvent*)theEvent {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mLastKeyDownEvent release];
+ mLastKeyDownEvent = [theEvent retain];
+
+ // Weird things can happen on keyboard input if the key window isn't in the
+ // current space. For example see bug 1056251. To get around this, always
+ // make sure that, if our window is key, it's also made frontmost. Doing
+ // this automatically switches to whatever space our window is in. Safari
+ // does something similar. Our window should normally always be key --
+ // otherwise why is the OS sending us a key down event? But it's just
+ // possible we're in Gecko's hidden window, so we check first.
+ NSWindow* viewWindow = [self window];
+ if (viewWindow && [viewWindow isKeyWindow]) {
+ [viewWindow orderWindow:NSWindowAbove relativeTo:0];
+ }
+
+#if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+ if (!Preferences::GetBool("intl.allow-insecure-text-input", false) && mGeckoChild &&
+ mTextInputHandler && mTextInputHandler->IsFocused()) {
+ NSWindow* window = [self window];
+ NSString* info = [NSString
+ stringWithFormat:
+ @"\nview [%@], window [%@], window is key %i, is fullscreen %i, app is active %i", self,
+ window, [window isKeyWindow], ([window styleMask] & (1 << 14)) != 0, [NSApp isActive]];
+ nsAutoCString additionalInfo([info UTF8String]);
+
+ if (mGeckoChild->GetInputContext().IsPasswordEditor() &&
+ !TextInputHandler::IsSecureEventInputEnabled()) {
+# define CRASH_MESSAGE "A password editor has focus, but not in secure input mode"
+
+ CrashReporter::AppendAppNotesToCrashReport("\nBug 893973: "_ns +
+ nsLiteralCString(CRASH_MESSAGE));
+ CrashReporter::AppendAppNotesToCrashReport(additionalInfo);
+
+ 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"
+
+ CrashReporter::AppendAppNotesToCrashReport("\nBug 893973: "_ns +
+ nsLiteralCString(CRASH_MESSAGE));
+ CrashReporter::AppendAppNotesToCrashReport(additionalInfo);
+
+ MOZ_CRASH(CRASH_MESSAGE);
+# undef CRASH_MESSAGE
+ }
+ }
+#endif // #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ if (mGeckoChild) {
+ if (mTextInputHandler) {
+ sUniqueKeyEventId++;
+ NSMutableDictionary* nativeKeyEventsMap = [ChildView sNativeKeyEventsMap];
+ [nativeKeyEventsMap setObject:theEvent forKey:@(sUniqueKeyEventId)];
+ // Purge old native events, in case we're still holding on to them. We
+ // keep at most 10 references to 10 different native events.
+ [nativeKeyEventsMap removeObjectForKey:@(sUniqueKeyEventId - 10)];
+ mTextInputHandler->HandleKeyDownEvent(theEvent, sUniqueKeyEventId);
+ } else {
+ // There was no text input handler. Offer the event to the native menu
+ // system to check if there are any registered custom shortcuts for this
+ // event.
+ mGeckoChild->SendEventToNativeMenuSystem(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) {
+ mTextInputHandler->HandleCommand(Command::InsertParagraph);
+ }
+}
+
+- (void)insertLineBreak:(id)sender {
+ // Ctrl + Enter in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::InsertLineBreak);
+ }
+}
+
+- (void)deleteBackward:(id)sender {
+ // Backspace in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::DeleteCharBackward);
+ }
+}
+
+- (void)deleteBackwardByDecomposingPreviousCharacter:(id)sender {
+ // Ctrl + Backspace in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::DeleteCharBackward);
+ }
+}
+
+- (void)deleteWordBackward:(id)sender {
+ // Alt + Backspace in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::DeleteWordBackward);
+ }
+}
+
+- (void)deleteToBeginningOfBackward:(id)sender {
+ // Command + Backspace in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::DeleteToBeginningOfLine);
+ }
+}
+
+- (void)deleteForward:(id)sender {
+ // Delete in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::DeleteCharForward);
+ }
+}
+
+- (void)deleteWordForward:(id)sender {
+ // Alt + Delete in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::DeleteWordForward);
+ }
+}
+
+- (void)insertTab:(id)sender {
+ // Tab in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::InsertTab);
+ }
+}
+
+- (void)insertBacktab:(id)sender {
+ // Shift + Tab in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::InsertBacktab);
+ }
+}
+
+- (void)moveRight:(id)sender {
+ // RightArrow in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::CharNext);
+ }
+}
+
+- (void)moveRightAndModifySelection:(id)sender {
+ // Shift + RightArrow in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::SelectCharNext);
+ }
+}
+
+- (void)moveWordRight:(id)sender {
+ // Alt + RightArrow in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::WordNext);
+ }
+}
+
+- (void)moveWordRightAndModifySelection:(id)sender {
+ // Alt + Shift + RightArrow in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::SelectWordNext);
+ }
+}
+
+- (void)moveToRightEndOfLine:(id)sender {
+ // Command + RightArrow in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::EndLine);
+ }
+}
+
+- (void)moveToRightEndOfLineAndModifySelection:(id)sender {
+ // Command + Shift + RightArrow in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::SelectEndLine);
+ }
+}
+
+- (void)moveLeft:(id)sender {
+ // LeftArrow in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::CharPrevious);
+ }
+}
+
+- (void)moveLeftAndModifySelection:(id)sender {
+ // Shift + LeftArrow in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::SelectCharPrevious);
+ }
+}
+
+- (void)moveWordLeft:(id)sender {
+ // Alt + LeftArrow in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::WordPrevious);
+ }
+}
+
+- (void)moveWordLeftAndModifySelection:(id)sender {
+ // Alt + Shift + LeftArrow in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::SelectWordPrevious);
+ }
+}
+
+- (void)moveToLeftEndOfLine:(id)sender {
+ // Command + LeftArrow in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::BeginLine);
+ }
+}
+
+- (void)moveToLeftEndOfLineAndModifySelection:(id)sender {
+ // Command + Shift + LeftArrow in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::SelectBeginLine);
+ }
+}
+
+- (void)moveUp:(id)sender {
+ // ArrowUp in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::LinePrevious);
+ }
+}
+
+- (void)moveUpAndModifySelection:(id)sender {
+ // Shift + ArrowUp in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::SelectLinePrevious);
+ }
+}
+
+- (void)moveToBeginningOfDocument:(id)sender {
+ // Command + ArrowUp in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::MoveTop);
+ }
+}
+
+- (void)moveToBeginningOfDocumentAndModifySelection:(id)sender {
+ // Command + Shift + ArrowUp or Shift + Home in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::SelectTop);
+ }
+}
+
+- (void)moveDown:(id)sender {
+ // ArrowDown in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::LineNext);
+ }
+}
+
+- (void)moveDownAndModifySelection:(id)sender {
+ // Shift + ArrowDown in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::SelectLineNext);
+ }
+}
+
+- (void)moveToEndOfDocument:(id)sender {
+ // Command + ArrowDown in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::MoveBottom);
+ }
+}
+
+- (void)moveToEndOfDocumentAndModifySelection:(id)sender {
+ // Command + Shift + ArrowDown or Shift + End in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::SelectBottom);
+ }
+}
+
+- (void)scrollPageUp:(id)sender {
+ // PageUp in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::ScrollPageUp);
+ }
+}
+
+- (void)pageUpAndModifySelection:(id)sender {
+ // Shift + PageUp in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::SelectPageUp);
+ }
+}
+
+- (void)scrollPageDown:(id)sender {
+ // PageDown in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::ScrollPageDown);
+ }
+}
+
+- (void)pageDownAndModifySelection:(id)sender {
+ // Shift + PageDown in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::SelectPageDown);
+ }
+}
+
+- (void)scrollToEndOfDocument:(id)sender {
+ // End in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::ScrollBottom);
+ }
+}
+
+- (void)scrollToBeginningOfDocument:(id)sender {
+ // Home in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::ScrollTop);
+ }
+}
+
+// XXX Don't decleare nor implement calcelOperation: because it
+// causes not calling keyDown: for Command + Period.
+// We need to handle it from doCommandBySelector:.
+
+- (void)complete:(id)sender {
+ // Alt + Escape or Alt + Shift + Escape in the default settings.
+ if (mTextInputHandler) {
+ mTextInputHandler->HandleCommand(Command::Complete);
+ }
+}
+
+- (void)flagsChanged:(NSEvent*)theEvent {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ENSURE_TRUE(mGeckoChild, );
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ mTextInputHandler->HandleFlagsChanged(theEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (BOOL)isFirstResponder {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSResponder* resp = [[self window] firstResponder];
+ return (resp == (NSResponder*)self);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (BOOL)isDragInProgress {
+ if (!mDragService) return NO;
+
+ nsCOMPtr<nsIDragSession> dragSession;
+ mDragService->GetCurrentSession(getter_AddRefs(dragSession));
+ return dragSession != nullptr;
+}
+
+- (BOOL)inactiveWindowAcceptsMouseEvent:(NSEvent*)aEvent {
+ // If we're being destroyed assume the default -- return YES.
+ if (!mGeckoChild) return YES;
+
+ WidgetMouseEvent geckoEvent(true, eMouseActivate, mGeckoChild, WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:aEvent toGeckoEvent:&geckoEvent];
+ return (mGeckoChild->DispatchInputEvent(&geckoEvent) != nsEventStatus_eConsumeNoDefault);
+}
+
+// We must always call through to our superclass, even when mGeckoChild is
+// nil -- otherwise the keyboard focus can end up in the wrong NSView.
+- (BOOL)becomeFirstResponder {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return [super becomeFirstResponder];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(YES);
+}
+
+- (void)viewsWindowDidBecomeKey {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mGeckoChild) return;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ // check to see if the window implements the mozWindow protocol. This
+ // allows embedders to avoid re-entrant calls to -makeKeyAndOrderFront,
+ // which can happen because these activate calls propagate out
+ // to the embedder via nsIEmbeddingSiteWindow::SetFocus().
+ BOOL isMozWindow = [[self window] respondsToSelector:@selector(setSuppressMakeKeyFront:)];
+ if (isMozWindow) [[self window] setSuppressMakeKeyFront:YES];
+
+ nsIWidgetListener* listener = mGeckoChild->GetWidgetListener();
+ if (listener) listener->WindowActivated();
+
+ if (isMozWindow) [[self window] setSuppressMakeKeyFront:NO];
+
+ if (mGeckoChild->GetInputContext().IsPasswordEditor()) {
+ TextInputHandler::EnableSecureEventInput();
+ } else {
+ TextInputHandler::EnsureSecureEventInputDisabled();
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)viewsWindowDidResignKey {
+ if (!mGeckoChild) return;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ nsIWidgetListener* listener = mGeckoChild->GetWidgetListener();
+ if (listener) listener->WindowDeactivated();
+
+ TextInputHandler::EnsureSecureEventInputDisabled();
+}
+
+// If the call to removeFromSuperview isn't delayed from nsChildView::
+// TearDownView(), the NSView hierarchy might get changed during calls to
+// [ChildView drawRect:], which leads to "beyond bounds" exceptions in
+// NSCFArray. For more info see bmo bug 373122. Apple's docs claim that
+// removeFromSuperviewWithoutNeedingDisplay "can be safely invoked during
+// display" (whatever "display" means). But it's _not_ true that it can be
+// safely invoked during calls to [NSView drawRect:]. We use
+// removeFromSuperview here because there's no longer any danger of being
+// "invoked during display", and because doing do clears up bmo bug 384343.
+- (void)delayedTearDown {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self removeFromSuperview];
+ [self release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#pragma mark -
+
+// drag'n'drop stuff
+#define kDragServiceContractID "@mozilla.org/widget/dragservice;1"
+
+- (NSDragOperation)dragOperationFromDragAction:(int32_t)aDragAction {
+ if (nsIDragService::DRAGDROP_ACTION_LINK & aDragAction) return NSDragOperationLink;
+ if (nsIDragService::DRAGDROP_ACTION_COPY & aDragAction) return NSDragOperationCopy;
+ if (nsIDragService::DRAGDROP_ACTION_MOVE & aDragAction) return NSDragOperationGeneric;
+ return NSDragOperationNone;
+}
+
+- (LayoutDeviceIntPoint)convertWindowCoordinates:(NSPoint)aPoint {
+ if (!mGeckoChild) {
+ return LayoutDeviceIntPoint(0, 0);
+ }
+
+ NSPoint localPoint = [self convertPoint:aPoint fromView:nil];
+ return mGeckoChild->CocoaPointsToDevPixels(localPoint);
+}
+
+- (LayoutDeviceIntPoint)convertWindowCoordinatesRoundDown:(NSPoint)aPoint {
+ if (!mGeckoChild) {
+ return LayoutDeviceIntPoint(0, 0);
+ }
+
+ NSPoint localPoint = [self convertPoint:aPoint fromView:nil];
+ return mGeckoChild->CocoaPointsToDevPixelsRoundDown(localPoint);
+}
+
+// This is a utility function used by NSView drag event methods
+// to send events. It contains all of the logic needed for Gecko
+// dragging to work. Returns the appropriate cocoa drag operation code.
+- (NSDragOperation)doDragAction:(EventMessage)aMessage sender:(id)aSender {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (!mGeckoChild) return NSDragOperationNone;
+
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView doDragAction: entered\n"));
+
+ if (!mDragService) {
+ CallGetService(kDragServiceContractID, &mDragService);
+ NS_ASSERTION(mDragService, "Couldn't get a drag service - big problem!");
+ if (!mDragService) return NSDragOperationNone;
+ }
+
+ if (aMessage == eDragEnter) {
+ mDragService->StartDragSession();
+ }
+
+ nsCOMPtr<nsIDragSession> dragSession;
+ mDragService->GetCurrentSession(getter_AddRefs(dragSession));
+ if (dragSession) {
+ if (aMessage == eDragOver) {
+ // fire the drag event at the source. Just ignore whether it was
+ // cancelled or not as there isn't actually a means to stop the drag
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ dragService->FireDragEventAtSource(eDrag,
+ nsCocoaUtils::ModifiersForEvent([NSApp currentEvent]));
+ 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<nsINode> sourceNode;
+ dragSession->GetSourceNode(getter_AddRefs(sourceNode));
+ if (!sourceNode) {
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ dragService->EndDragSession(false, nsCocoaUtils::ModifiersForEvent([NSApp currentEvent]));
+ }
+ 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 & NSEventModifierFlagOption) {
+ if (modifierFlags & NSEventModifierFlagCommand)
+ action = nsIDragService::DRAGDROP_ACTION_LINK;
+ else
+ action = nsIDragService::DRAGDROP_ACTION_COPY;
+ }
+ dragSession->SetDragAction(action);
+ }
+
+ // set up gecko event
+ WidgetDragEvent geckoEvent(true, aMessage, mGeckoChild);
+ nsCocoaUtils::InitInputEvent(geckoEvent, [NSApp currentEvent]);
+
+ // Use our own coordinates in the gecko event.
+ // Convert event from gecko global coords to gecko view coords.
+ NSPoint draggingLoc = [aSender draggingLocation];
+
+ geckoEvent.mRefPoint = [self convertWindowCoordinates:draggingLoc];
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+ if (!mGeckoChild) return NSDragOperationNone;
+
+ if (dragSession) {
+ switch (aMessage) {
+ case eDragEnter:
+ case eDragOver: {
+ uint32_t dragAction;
+ dragSession->GetDragAction(&dragAction);
+
+ // If TakeChildProcessDragAction returns something other than
+ // DRAGDROP_ACTION_UNINITIALIZED, it means that the last event was sent
+ // to the child process and this event is also being sent to the child
+ // process. In this case, use the last event's action instead.
+ nsDragService* dragService = static_cast<nsDragService*>(mDragService);
+ int32_t childDragAction = dragService->TakeChildProcessDragAction();
+ if (childDragAction != nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) {
+ dragAction = childDragAction;
+ }
+
+ return [self dragOperationFromDragAction:dragAction];
+ }
+ case eDragExit:
+ case eDrop: {
+ nsCOMPtr<nsINode> 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).
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ dragService->EndDragSession(false, nsCocoaUtils::ModifiersForEvent([NSApp currentEvent]));
+ }
+ }
+ default:
+ break;
+ }
+ }
+
+ return NSDragOperationGeneric;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSDragOperationNone);
+}
+
+// NSDraggingDestination
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingEntered: entered\n"));
+
+ // there should never be a globalDragPboard when "draggingEntered:" is
+ // called, but just in case we'll take care of it here.
+ [globalDragPboard release];
+
+ // Set the global drag pasteboard that will be used for this drag session.
+ // This will be set back to nil when the drag session ends (mouse exits
+ // the view or a drop happens within the view).
+ globalDragPboard = [[sender draggingPasteboard] retain];
+
+ return [self doDragAction:eDragEnter sender:sender];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSDragOperationNone);
+}
+
+// NSDraggingDestination
+- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingUpdated: entered\n"));
+ return [self doDragAction:eDragOver sender:sender];
+}
+
+// NSDraggingDestination
+- (void)draggingExited:(id<NSDraggingInfo>)sender {
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingExited: entered\n"));
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ [self doDragAction:eDragExit sender:sender];
+ NS_IF_RELEASE(mDragService);
+}
+
+// NSDraggingDestination
+- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ BOOL handled = [self doDragAction:eDrop sender:sender] != NSDragOperationNone;
+ NS_IF_RELEASE(mDragService);
+ return handled;
+}
+
+// NSDraggingSource
+// This is just implemented so we comply with the NSDraggingSource protocol.
+- (NSDragOperation)draggingSession:(NSDraggingSession*)session
+ sourceOperationMaskForDraggingContext:(NSDraggingContext)context {
+ return UINT_MAX;
+}
+
+// NSDraggingSource
+- (BOOL)ignoreModifierKeysForDraggingSession:(NSDraggingSession*)session {
+ return YES;
+}
+
+// NSDraggingSource
+- (void)draggingSession:(NSDraggingSession*)aSession
+ endedAtPoint:(NSPoint)aPoint
+ operation:(NSDragOperation)aOperation {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ gDraggedTransferables = nullptr;
+
+ NSEvent* currentEvent = [NSApp currentEvent];
+ gUserCancelledDrag =
+ ([currentEvent type] == NSEventTypeKeyDown && [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
+ RefPtr<nsDragService> dragService = static_cast<nsDragService*>(mDragService);
+ FlipCocoaScreenCoordinate(aPoint);
+ dragService->SetDragEndPoint(gfx::IntPoint::Round(aPoint.x, aPoint.y));
+
+ NSPoint pnt = [NSEvent mouseLocation];
+ FlipCocoaScreenCoordinate(pnt);
+ dragService->SetDragEndPoint(gfx::IntPoint::Round(pnt.x, pnt.y));
+
+ // XXX: dropEffect should be updated per |aOperation|.
+ // As things stand though, |aOperation| 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 (aOperation == NSDragOperationNone) {
+ RefPtr<dom::DataTransfer> dataTransfer = dragService->GetDataTransfer();
+ if (dataTransfer) {
+ dataTransfer->SetDropEffectInt(nsIDragService::DRAGDROP_ACTION_NONE);
+ }
+ }
+
+ dragService->EndDragSession(true, nsCocoaUtils::ModifiersForEvent(currentEvent));
+ NS_RELEASE(mDragService);
+ }
+
+ [globalDragPboard release];
+ globalDragPboard = nil;
+ [gLastDragMouseDownEvent release];
+ gLastDragMouseDownEvent = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// NSDraggingSource
+- (void)draggingSession:(NSDraggingSession*)aSession movedToPoint:(NSPoint)aPoint {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Get the drag service if it isn't already cached. The drag service
+ // isn't cached when dragging over a different application.
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ if (!dragService) {
+ dragService = do_GetService(kDragServiceContractID);
+ }
+
+ if (dragService) {
+ nsDragService* ds = static_cast<nsDragService*>(dragService.get());
+ ds->DragMovedWithView(aSession, aPoint);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// NSDraggingSource
+- (void)draggingSession:(NSDraggingSession*)aSession willBeginAtPoint:(NSPoint)aPoint {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // there should never be a globalDragPboard when "willBeginAtPoint:" 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 = [[aSession draggingPasteboard] retain];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// NSPasteboardItemDataProvider
+- (void)pasteboard:(NSPasteboard*)aPasteboard
+ item:(NSPasteboardItem*)aItem
+ provideDataForType:(NSString*)aType {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!gDraggedTransferables) {
+ return;
+ }
+
+ uint32_t count = 0;
+ gDraggedTransferables->GetLength(&count);
+
+ for (uint32_t j = 0; j < count; j++) {
+ nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(gDraggedTransferables, j);
+ if (!currentTransferable) {
+ return;
+ }
+
+ // Transform the transferable to an NSDictionary.
+ NSDictionary* pasteboardOutputDict =
+ nsClipboard::PasteboardDictFromTransferable(currentTransferable);
+ if (!pasteboardOutputDict) {
+ return;
+ }
+
+ // Write everything out to the pasteboard.
+ unsigned int typeCount = [pasteboardOutputDict count];
+ NSMutableArray* types = [NSMutableArray arrayWithCapacity:typeCount + 1];
+ [types addObjectsFromArray:[pasteboardOutputDict allKeys]];
+ [types addObject:[UTIHelper stringFromPboardType:kMozWildcardPboardType]];
+ for (unsigned int k = 0; k < typeCount; k++) {
+ NSString* curType = [types objectAtIndex:k];
+ if ([curType isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeString]] ||
+ [curType isEqualToString:[UTIHelper stringFromPboardType:kPublicUrlPboardType]] ||
+ [curType isEqualToString:[UTIHelper stringFromPboardType:kPublicUrlNamePboardType]] ||
+ [curType isEqualToString:[UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL]]) {
+ [aPasteboard setString:[pasteboardOutputDict valueForKey:curType] forType:curType];
+ } else if ([curType
+ isEqualToString:[UTIHelper stringFromPboardType:kUrlsWithTitlesPboardType]]) {
+ [aPasteboard setPropertyList:[pasteboardOutputDict valueForKey:curType] forType:curType];
+ } else if ([curType isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeHTML]]) {
+ [aPasteboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(
+ [pasteboardOutputDict valueForKey:curType]))
+ forType:curType];
+ } else if ([curType isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeTIFF]] ||
+ [curType
+ isEqualToString:[UTIHelper stringFromPboardType:kMozCustomTypesPboardType]]) {
+ [aPasteboard setData:[pasteboardOutputDict valueForKey:curType] forType:curType];
+ } else if ([curType
+ isEqualToString:[UTIHelper stringFromPboardType:kMozFileUrlsPboardType]]) {
+ [aPasteboard writeObjects:[pasteboardOutputDict valueForKey:curType]];
+ } else if ([curType
+ isEqualToString:[UTIHelper
+ stringFromPboardType:(NSString*)
+ kPasteboardTypeFileURLPromise]]) {
+ nsCOMPtr<nsIFile> targFile;
+ NS_NewLocalFile(u""_ns, true, getter_AddRefs(targFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(targFile);
+ if (!macLocalFile) {
+ NS_ERROR("No Mac local file");
+ continue;
+ }
+
+ // get paste location from low level pasteboard
+ PasteboardRef pboardRef = NULL;
+ PasteboardCreate((CFStringRef)[aPasteboard name], &pboardRef);
+ if (!pboardRef) {
+ continue;
+ }
+
+ PasteboardSynchronize(pboardRef);
+ CFURLRef urlRef = NULL;
+ PasteboardCopyPasteLocation(pboardRef, &urlRef);
+ if (!urlRef) {
+ CFRelease(pboardRef);
+ continue;
+ }
+
+ if (!NS_SUCCEEDED(macLocalFile->InitWithCFURL(urlRef))) {
+ NS_ERROR("failed InitWithCFURL");
+ CFRelease(urlRef);
+ CFRelease(pboardRef);
+ continue;
+ }
+
+ if (!gDraggedTransferables) {
+ CFRelease(urlRef);
+ CFRelease(pboardRef);
+ continue;
+ }
+
+ uint32_t transferableCount;
+ nsresult rv = gDraggedTransferables->GetLength(&transferableCount);
+ if (NS_FAILED(rv)) {
+ CFRelease(urlRef);
+ CFRelease(pboardRef);
+ continue;
+ }
+
+ for (uint32_t i = 0; i < transferableCount; i++) {
+ nsCOMPtr<nsITransferable> item = do_QueryElementAt(gDraggedTransferables, i);
+ if (!item) {
+ NS_ERROR("no transferable");
+ continue;
+ }
+
+ item->SetTransferData(kFilePromiseDirectoryMime, macLocalFile);
+
+ // Now request the kFilePromiseMime data, which will invoke the data
+ // provider. If successful, the file will have been created.
+ nsCOMPtr<nsISupports> fileDataPrimitive;
+ Unused << item->GetTransferData(kFilePromiseMime, getter_AddRefs(fileDataPrimitive));
+ }
+ CFRelease(urlRef);
+ CFRelease(pboardRef);
+
+ [aPasteboard setPropertyList:[pasteboardOutputDict valueForKey:curType] forType:curType];
+ }
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#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.
+
+ id result = nil;
+
+ NSString* stringType = [UTIHelper stringFromPboardType:NSPasteboardTypeString];
+ NSString* htmlType = [UTIHelper stringFromPboardType:NSPasteboardTypeHTML];
+ if ((!sendType || [sendType isEqualToString:stringType] || [sendType isEqualToString:htmlType]) &&
+ (!returnType || [returnType isEqualToString:stringType] ||
+ [returnType isEqualToString:htmlType])) {
+ 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;
+ }
+ }
+ }
+
+ // 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:[UTIHelper stringFromPboardType:NSStringPboardType]] &&
+ ![types containsObject:[UTIHelper stringFromPboardType:NSPasteboardTypeString]] &&
+ ![types containsObject:[UTIHelper stringFromPboardType:NSPasteboardTypeHTML]]) {
+ 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 isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeString]] ||
+ [currentKey isEqualToString:[UTIHelper stringFromPboardType:kPublicUrlPboardType]] ||
+ [currentKey isEqualToString:[UTIHelper stringFromPboardType:kPublicUrlNamePboardType]]) {
+ [pboard setString:currentValue forType:currentKey];
+ } else if ([currentKey isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeHTML]]) {
+ [pboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
+ forType:currentKey];
+ } else if ([currentKey isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeTIFF]]) {
+ [pboard setData:currentValue forType:currentKey];
+ } else if ([currentKey
+ isEqualToString:[UTIHelper
+ stringFromPboardType:(NSString*)
+ kPasteboardTypeFileURLPromise]] ||
+ [currentKey
+ isEqualToString:[UTIHelper stringFromPboardType:kUrlsWithTitlesPboardType]]) {
+ [pboard setPropertyList:currentValue forType:currentKey];
+ }
+ }
+ return YES;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+// Called if the service wants us to replace the current selection.
+- (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard {
+ nsresult rv;
+ nsCOMPtr<nsITransferable> trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
+ if (NS_FAILED(rv)) return NO;
+ trans->Init(nullptr);
+
+ trans->AddDataFlavor(kUnicodeMime);
+ trans->AddDataFlavor(kHTMLMime);
+
+ rv = nsClipboard::TransferableFromPasteboard(trans, pboard);
+ if (NS_FAILED(rv)) return NO;
+
+ NS_ENSURE_TRUE(mGeckoChild, false);
+
+ WidgetContentCommandEvent command(true, eContentCommandPasteTransferable, mGeckoChild);
+ command.mTransferable = trans;
+ mGeckoChild->DispatchWindowEvent(command);
+
+ return command.mSucceeded && command.mIsEnabled;
+}
+
+- (void)pressureChangeWithEvent:(NSEvent*)event {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK
+
+ NSInteger stage = [event stage];
+ if (mLastPressureStage == 1 && stage == 2) {
+ NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
+ if ([userDefaults integerForKey:@"com.apple.trackpad.forceClick"] == 1) {
+ // This is no public API to get configuration for current force click.
+ // This is filed as radar 29294285.
+ [self quickLookWithEvent:event];
+ }
+ }
+ mLastPressureStage = stage;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+nsresult nsChildView::GetSelectionAsPlaintext(nsAString& aResult) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!nsClipboard::sSelectionCache) {
+ MOZ_ASSERT(aResult.IsEmpty());
+ return NS_OK;
+ }
+
+ // Get the current chrome or content selection.
+ NSDictionary* pasteboardOutputDict = nullptr;
+ pasteboardOutputDict = nsClipboard::PasteboardDictFromTransferable(nsClipboard::sSelectionCache);
+
+ if (NS_WARN_IF(!pasteboardOutputDict)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Declare the pasteboard types.
+ unsigned int typeCount = [pasteboardOutputDict count];
+ NSMutableArray* declaredTypes = [NSMutableArray arrayWithCapacity:typeCount];
+ [declaredTypes addObjectsFromArray:[pasteboardOutputDict allKeys]];
+ NSString* currentKey = [declaredTypes objectAtIndex:0];
+ NSString* currentValue = [pasteboardOutputDict valueForKey:currentKey];
+ const char* textSelection = [currentValue UTF8String];
+ aResult = NS_ConvertUTF8toUTF16(textSelection);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+#pragma mark -
+
+#ifdef ACCESSIBILITY
+
+/* Every ChildView has a corresponding mozDocAccessible object that is doing all
+ the heavy lifting. The topmost ChildView corresponds to a mozRootAccessible
+ object.
+
+ All ChildView needs to do is to route all accessibility calls (from the NSAccessibility APIs)
+ down to its object, pretending that they are the same.
+*/
+- (id<mozAccessible>)accessible {
+ if (!mGeckoChild) return nil;
+
+ id<mozAccessible> nativeAccessible = nil;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ RefPtr<nsChildView> geckoChild(mGeckoChild);
+ RefPtr<a11y::Accessible> accessible = geckoChild->GetDocumentAccessible();
+ if (!accessible) return nil;
+
+ accessible->GetNativeInterface((void**)&nativeAccessible);
+
+# ifdef DEBUG_hakan
+ NSAssert(![nativeAccessible isExpired], @"native acc is expired!!!");
+# endif
+
+ return nativeAccessible;
+}
+
+/* Implementation of formal mozAccessible formal protocol (enabling mozViews
+ to talk to mozAccessible objects in the accessibility module). */
+
+- (BOOL)hasRepresentedView {
+ return YES;
+}
+
+- (id)representedView {
+ return self;
+}
+
+- (BOOL)isRoot {
+ return [[self accessible] isRoot];
+}
+
+# pragma mark -
+
+// general
+
+- (BOOL)isAccessibilityElement {
+ if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super isAccessibilityElement];
+
+ return [[self accessible] isAccessibilityElement];
+}
+
+- (id)accessibilityHitTest:(NSPoint)point {
+ if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super accessibilityHitTest:point];
+
+ return [[self accessible] accessibilityHitTest:point];
+}
+
+- (id)accessibilityFocusedUIElement {
+ if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super accessibilityFocusedUIElement];
+
+ return [[self accessible] accessibilityFocusedUIElement];
+}
+
+// actions
+
+- (NSArray*)accessibilityActionNames {
+ if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super accessibilityActionNames];
+
+ return [[self accessible] accessibilityActionNames];
+}
+
+- (NSString*)accessibilityActionDescription:(NSString*)action {
+ if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super accessibilityActionDescription:action];
+
+ return [[self accessible] accessibilityActionDescription:action];
+}
+
+- (void)accessibilityPerformAction:(NSString*)action {
+ if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super accessibilityPerformAction:action];
+
+ return [[self accessible] accessibilityPerformAction:action];
+}
+
+// attributes
+
+- (NSArray*)accessibilityAttributeNames {
+ if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super accessibilityAttributeNames];
+
+ return [[self accessible] accessibilityAttributeNames];
+}
+
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute {
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityIsAttributeSettable:attribute];
+
+ return [[self accessible] accessibilityIsAttributeSettable:attribute];
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!mozilla::a11y::ShouldA11yBeEnabled()) return [super accessibilityAttributeValue:attribute];
+
+ id<mozAccessible> accessible = [self accessible];
+
+ // if we're the root (topmost) accessible, we need to return our native AXParent as we
+ // traverse outside to the hierarchy of whoever embeds us. thus, fall back on NSView's
+ // default implementation for this attribute.
+ if ([attribute isEqualToString:NSAccessibilityParentAttribute] && [accessible isRoot]) {
+ id parentAccessible = [super accessibilityAttributeValue:attribute];
+ return parentAccessible;
+ }
+
+ return [accessible accessibilityAttributeValue:attribute];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+#endif /* ACCESSIBILITY */
+
++ (uint32_t)sUniqueKeyEventId {
+ return sUniqueKeyEventId;
+}
+
++ (NSMutableDictionary*)sNativeKeyEventsMap {
+ // This dictionary is "leaked".
+ static NSMutableDictionary* sNativeKeyEventsMap = [[NSMutableDictionary alloc] init];
+ return sNativeKeyEventsMap;
+}
+
+@end
+
+@implementation PixelHostingView
+
+- (id)initWithFrame:(NSRect)aRect {
+ self = [super initWithFrame:aRect];
+
+ self.wantsLayer = YES;
+ self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
+
+ return self;
+}
+
+- (BOOL)isFlipped {
+ return YES;
+}
+
+- (NSView*)hitTest:(NSPoint)aPoint {
+ return nil;
+}
+
+- (void)drawRect:(NSRect)aRect {
+ NS_WARNING("Unexpected call to drawRect: This view returns YES from wantsUpdateLayer, so "
+ "drawRect should not be called.");
+}
+
+- (BOOL)wantsUpdateLayer {
+ return YES;
+}
+
+- (void)updateLayer {
+ [(ChildView*)[self superview] updateRootCALayer];
+}
+
+- (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::ePlatformChild
+ : WidgetMouseEvent::ePlatformTopLevel;
+ [oldView sendMouseEnterOrExitEvent:aEvent enter:NO exitFrom:exitFrom];
+ // After the cursor exits the window set it to a visible regular arrow cursor.
+ if (exitFrom == WidgetMouseEvent::ePlatformTopLevel) {
+ [[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] == NSEventTypeRightMouseDown) 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] == NSEventTypeOtherMouseDown ||
+ (([aEvent modifierFlags] & NSEventModifierFlagCommand) != 0 &&
+ [aEvent type] != NSEventTypeMouseMoved))
+ 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 -
diff --git a/widget/cocoa/nsClipboard.h b/widget/cocoa/nsClipboard.h
new file mode 100644
index 0000000000..ce4b7a6675
--- /dev/null
+++ b/widget/cocoa/nsClipboard.h
@@ -0,0 +1,57 @@
+/* -*- 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 "nsString.h"
+#include "mozilla/StaticPtr.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsITransferable;
+
+@interface UTIHelper : NSObject
++ (NSString*)stringFromPboardType:(NSString*)aType;
+@end
+
+class nsClipboard : public nsIClipboard {
+ public:
+ nsClipboard();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICLIPBOARD
+
+ // On macOS, cache the transferable of the current selection (chrome/content)
+ // in the parent process. This is needed for the services menu which
+ // requires synchronous access to the current selection.
+ static mozilla::StaticRefPtr<nsITransferable> sSelectionCache;
+
+ // Helper methods, used also by nsDragService
+ static NSDictionary* PasteboardDictFromTransferable(nsITransferable* aTransferable);
+ // aPasteboardType is being retained and needs to be released by the caller.
+ static bool IsStringType(const nsCString& aMIMEType, NSString** aPasteboardType);
+ static NSString* WrapHtmlForSystemPasteboard(NSString* aString);
+ static nsresult TransferableFromPasteboard(nsITransferable* aTransferable, NSPasteboard* pboard);
+
+ protected:
+ // impelement the native clipboard behavior
+ NS_IMETHOD SetNativeClipboardData(int32_t aWhichClipboard);
+ NS_IMETHOD GetNativeClipboardData(nsITransferable* aTransferable, int32_t aWhichClipboard);
+ void ClearSelectionCache();
+ void SetSelectionCache(nsITransferable* aTransferable);
+
+ private:
+ virtual ~nsClipboard();
+ int32_t mCachedClipboard;
+ int32_t mChangeCount; // Set to the native change count after any modification of the clipboard.
+
+ bool mIgnoreEmptyNotification;
+ nsCOMPtr<nsIClipboardOwner> mClipboardOwner;
+ nsCOMPtr<nsITransferable> mTransferable;
+};
+
+#endif // nsClipboard_h_
diff --git a/widget/cocoa/nsClipboard.mm b/widget/cocoa/nsClipboard.mm
new file mode 100644
index 0000000000..7754bcd71f
--- /dev/null
+++ b/widget/cocoa/nsClipboard.mm
@@ -0,0 +1,768 @@
+/* -*- 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 "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;
+
+extern mozilla::LazyLogModule sCocoaLog;
+
+mozilla::StaticRefPtr<nsITransferable> nsClipboard::sSelectionCache;
+
+@implementation UTIHelper
+
++ (NSString*)stringFromPboardType:(NSString*)aType {
+ if ([aType isEqualToString:kMozWildcardPboardType] ||
+ [aType isEqualToString:kMozCustomTypesPboardType] ||
+ [aType isEqualToString:kPublicUrlPboardType] ||
+ [aType isEqualToString:kPublicUrlNamePboardType] ||
+ [aType isEqualToString:kMozFileUrlsPboardType] ||
+ [aType isEqualToString:(NSString*)kPasteboardTypeFileURLPromise] ||
+ [aType isEqualToString:(NSString*)kPasteboardTypeFilePromiseContent] ||
+ [aType isEqualToString:(NSString*)kUTTypeFileURL] ||
+ [aType isEqualToString:NSStringPboardType] ||
+ [aType isEqualToString:NSPasteboardTypeString] ||
+ [aType isEqualToString:NSPasteboardTypeHTML] || [aType isEqualToString:NSPasteboardTypeRTF] ||
+ [aType isEqualToString:NSPasteboardTypeTIFF] || [aType isEqualToString:NSPasteboardTypePNG]) {
+ return [NSString stringWithString:aType];
+ }
+ NSString* dynamicType = (NSString*)UTTypeCreatePreferredIdentifierForTag(
+ kUTTagClassNSPboardType, (CFStringRef)aType, kUTTypeData);
+ NSString* result = [NSString stringWithString:dynamicType];
+ [dynamicType release];
+ return result;
+}
+
+@end // UTIHelper
+
+nsClipboard::nsClipboard()
+ : mCachedClipboard(-1), mChangeCount(0), mIgnoreEmptyNotification(false) {}
+
+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];
+ NSString* stringType = [UTIHelper stringFromPboardType:NSPasteboardTypeString];
+ [cocoaPasteboard declareTypes:[NSArray arrayWithObject:stringType] 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 isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeString]]) {
+ [cocoaPasteboard setString:currentValue forType:currentKey];
+ }
+ } else {
+ if ([currentKey isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeString]] ||
+ [currentKey isEqualToString:[UTIHelper stringFromPboardType:kPublicUrlPboardType]] ||
+ [currentKey isEqualToString:[UTIHelper stringFromPboardType:kPublicUrlNamePboardType]]) {
+ [cocoaPasteboard setString:currentValue forType:currentKey];
+ } else if ([currentKey
+ isEqualToString:[UTIHelper stringFromPboardType:kUrlsWithTitlesPboardType]]) {
+ [cocoaPasteboard setPropertyList:[pasteboardOutputDict valueForKey:currentKey]
+ forType:currentKey];
+ } else if ([currentKey
+ isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeHTML]]) {
+ [cocoaPasteboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
+ forType:currentKey];
+ } else if ([currentKey
+ isEqualToString:[UTIHelper stringFromPboardType:kMozFileUrlsPboardType]]) {
+ [cocoaPasteboard writeObjects:currentValue];
+ } 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)
+ nsTArray<nsCString> flavors;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+
+ // 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) {
+ [pboardType release];
+ continue;
+ }
+
+ NSData* stringData;
+ if ([pboardType isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeRTF]]) {
+ stringData = [pString dataUsingEncoding:NSASCIIStringEncoding];
+ } else {
+ stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
+ }
+ unsigned int dataLength = [stringData length];
+ void* clipboardDataPtr = malloc(dataLength);
+ if (!clipboardDataPtr) {
+ [pboardType release];
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ [stringData getBytes:clipboardDataPtr length:dataLength];
+
+ // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
+ int32_t signedDataLength = dataLength;
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr,
+ &signedDataLength);
+ dataLength = signedDataLength;
+
+ // skip BOM (Byte Order Mark to distinguish little or big endian)
+ char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
+ if ((dataLength > 2) &&
+ ((clipboardDataPtrNoBOM[0] == 0xFEFF) || (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
+ dataLength -= sizeof(char16_t);
+ clipboardDataPtrNoBOM += 1;
+ }
+
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
+ getter_AddRefs(genericDataWrapper));
+ aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
+ free(clipboardDataPtr);
+ [pboardType release];
+ break;
+ } else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ NSString* type = [cocoaPasteboard
+ availableTypeFromArray:
+ [NSArray arrayWithObject:[UTIHelper stringFromPboardType:kMozCustomTypesPboardType]]];
+ 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 length:dataLength];
+
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtr, dataLength,
+ getter_AddRefs(genericDataWrapper));
+
+ aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
+ 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:[UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL],
+ [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF],
+ [UTIHelper stringFromPboardType:NSPasteboardTypePNG], nil]];
+ 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, kCGImageSourceTypeIdentifierHint, nil];
+ CGImageSourceRef source = nullptr;
+ if (type == [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL]) {
+ NSString* urlStr = [cocoaPasteboard stringForType:type];
+ NSURL* url = [NSURL URLWithString:urlStr];
+ source = CGImageSourceCreateWithURL((CFURLRef)url, (CFDictionaryRef)options);
+ } else {
+ source = CGImageSourceCreateWithData((CFDataRef)pasteboardData, (CFDictionaryRef)options);
+ }
+
+ NSMutableData* encodedData = [NSMutableData data];
+ CGImageDestinationRef dest =
+ CGImageDestinationCreateWithData((CFMutableDataRef)encodedData, outputType, 1, NULL);
+ CGImageDestinationAddImageFromSource(dest, source, 0, NULL);
+ bool successfullyConverted = CGImageDestinationFinalize(dest);
+
+ if (successfullyConverted) {
+ // Put the converted data in a form Gecko can understand
+ nsCOMPtr<nsIInputStream> byteStream;
+ NS_NewByteInputStream(getter_AddRefs(byteStream),
+ mozilla::Span((const char*)[encodedData bytes], [encodedData length]),
+ NS_ASSIGNMENT_COPY);
+
+ aTransferable->SetTransferData(flavorStr.get(), byteStream);
+ }
+
+ 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)
+ nsTArray<nsCString> flavors;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ // 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 < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+
+ nsCOMPtr<nsISupports> dataSupports;
+ rv = mTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(dataSupports));
+ if (NS_SUCCEEDED(rv)) {
+ aTransferable->SetTransferData(flavorStr.get(), dataSupports);
+ 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 nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
+ bool* outResult) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *outResult = false;
+
+ if (aWhichClipboard != kGlobalClipboard) return NS_OK;
+
+ // first see if we have data for this in our cached transferable
+ if (mTransferable) {
+ nsTArray<nsCString> flavors;
+ nsresult rv = mTransferable->FlavorsTransferableCanImport(flavors);
+ if (NS_SUCCEEDED(rv)) {
+ for (uint32_t j = 0; j < flavors.Length(); j++) {
+ const nsCString& transferableFlavorStr = flavors[j];
+
+ for (uint32_t k = 0; k < aFlavorList.Length(); k++) {
+ if (transferableFlavorStr.Equals(aFlavorList[k])) {
+ *outResult = true;
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+
+ NSPasteboard* generalPBoard = [NSPasteboard generalPasteboard];
+
+ for (auto& mimeType : aFlavorList) {
+ NSString* pboardType = nil;
+ if (nsClipboard::IsStringType(mimeType, &pboardType)) {
+ NSString* availableType =
+ [generalPBoard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
+ if (availableType && [availableType isEqualToString:pboardType]) {
+ [pboardType release];
+ *outResult = true;
+ break;
+ }
+ [pboardType release];
+ } else if (mimeType.EqualsLiteral(kCustomTypesMime)) {
+ NSString* availableType = [generalPBoard
+ availableTypeFromArray:
+ [NSArray arrayWithObject:[UTIHelper stringFromPboardType:kMozCustomTypesPboardType]]];
+ if (availableType) {
+ *outResult = true;
+ break;
+ }
+ } else if (mimeType.EqualsLiteral(kJPEGImageMime) || mimeType.EqualsLiteral(kJPGImageMime) ||
+ mimeType.EqualsLiteral(kPNGImageMime) || mimeType.EqualsLiteral(kGIFImageMime)) {
+ NSString* availableType = [generalPBoard
+ availableTypeFromArray:
+ [NSArray arrayWithObjects:[UTIHelper stringFromPboardType:NSPasteboardTypeTIFF],
+ [UTIHelper stringFromPboardType:NSPasteboardTypePNG], nil]];
+ 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];
+
+ nsTArray<nsCString> flavors;
+ nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors);
+ if (NS_FAILED(rv)) {
+ return nil;
+ }
+
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+
+ 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)) {
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericDataWrapper));
+ if (NS_FAILED(rv)) {
+ [pboardType release];
+ continue;
+ }
+
+ nsAutoString data;
+ if (nsCOMPtr<nsISupportsString> text = do_QueryInterface(genericDataWrapper)) {
+ text->GetData(data);
+ }
+
+ NSString* nativeString;
+ if (!data.IsEmpty()) {
+ nativeString = [NSString stringWithCharacters:(const unichar*)data.get()
+ length:data.Length()];
+ } else {
+ nativeString = [NSString string];
+ }
+
+ // be nice to Carbon apps, normalize the receiver's contents using Form C.
+ nativeString = [nativeString precomposedStringWithCanonicalMapping];
+ if (nativeString) {
+ [pasteboardOutputDict setObject:nativeString forKey:pboardType];
+ }
+ [pboardType release];
+ } else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericDataWrapper));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsAutoCString data;
+ if (nsCOMPtr<nsISupportsCString> text = do_QueryInterface(genericDataWrapper)) {
+ text->GetData(data);
+ }
+
+ if (!data.IsEmpty()) {
+ NSData* nativeData = [NSData dataWithBytes:data.get() length:data.Length()];
+ NSString* customType = [UTIHelper stringFromPboardType:kMozCustomTypesPboardType];
+ if (!nativeData) {
+ continue;
+ }
+ [pasteboardOutputDict setObject:nativeData forKey:customType];
+ }
+ } else if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime) ||
+ flavorStr.EqualsLiteral(kNativeImageMime)) {
+ nsCOMPtr<nsISupports> transferSupports;
+ rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(transferSupports));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<imgIContainer> image(do_QueryInterface(transferSupports));
+ if (!image) {
+ NS_WARNING("Image isn't an imgIContainer in transferable");
+ continue;
+ }
+
+ RefPtr<SourceSurface> surface =
+ image->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ 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 || !tiffData) {
+ if (tiffData) {
+ CFRelease(tiffData);
+ }
+ continue;
+ }
+
+ NSString* tiffType = [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF];
+ [pasteboardOutputDict setObject:(NSMutableData*)tiffData forKey:tiffType];
+ CFRelease(tiffData);
+ } else if (flavorStr.EqualsLiteral(kFileMime)) {
+ nsCOMPtr<nsISupports> genericFile;
+ rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericFile));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> file(do_QueryInterface(genericFile));
+ if (!file) {
+ continue;
+ }
+
+ nsAutoString fileURI;
+ rv = file->GetPath(fileURI);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ NSString* str = nsCocoaUtils::ToNSString(fileURI);
+ NSURL* url = [NSURL fileURLWithPath:str isDirectory:NO];
+ NSString* fileUTType = [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL];
+ if (!url || ![url absoluteString]) {
+ continue;
+ }
+ [pasteboardOutputDict setObject:[url absoluteString] forKey:fileUTType];
+ } else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
+ NSString* urlPromise =
+ [UTIHelper stringFromPboardType:(NSString*)kPasteboardTypeFileURLPromise];
+ NSString* urlPromiseContent =
+ [UTIHelper stringFromPboardType:(NSString*)kPasteboardTypeFilePromiseContent];
+ [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""] forKey:urlPromise];
+ [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""] forKey:urlPromiseContent];
+ } else if (flavorStr.EqualsLiteral(kURLMime)) {
+ nsCOMPtr<nsISupports> genericURL;
+ rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericURL));
+ nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
+
+ nsAutoString url;
+ urlObject->GetData(url);
+
+ NSString* nativeTitle = nil;
+
+ // A newline embedded in the URL means that the form is actually URL +
+ // title. This embedding occurs in nsDragService::GetData.
+ int32_t newlinePos = url.FindChar(char16_t('\n'));
+ if (newlinePos >= 0) {
+ url.Truncate(newlinePos);
+
+ nsAutoString urlTitle;
+ urlObject->GetData(urlTitle);
+ urlTitle.Mid(urlTitle, newlinePos + 1, urlTitle.Length() - (newlinePos + 1));
+
+ nativeTitle =
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(urlTitle.get())
+ length:urlTitle.Length()];
+ }
+ // 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);
+
+ NSString* nativeURL = [NSString stringWithUTF8String:escData.get()];
+ NSString* publicUrl = [UTIHelper stringFromPboardType:kPublicUrlPboardType];
+ if (!nativeURL) {
+ continue;
+ }
+ [pasteboardOutputDict setObject:nativeURL forKey:publicUrl];
+ if (nativeTitle) {
+ NSArray* urlsAndTitles = @[ @[ nativeURL ], @[ nativeTitle ] ];
+ NSString* urlName = [UTIHelper stringFromPboardType:kPublicUrlNamePboardType];
+ NSString* urlsWithTitles = [UTIHelper stringFromPboardType:kUrlsWithTitlesPboardType];
+ [pasteboardOutputDict setObject:nativeTitle forKey:urlName];
+ [pasteboardOutputDict setObject:urlsAndTitles forKey:urlsWithTitles];
+ }
+ }
+ // 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;
+}
+
+// aPasteboardType is being retained and needs to be released by the caller.
+bool nsClipboard::IsStringType(const nsCString& aMIMEType, NSString** aPasteboardType) {
+ if (aMIMEType.EqualsLiteral(kUnicodeMime)) {
+ *aPasteboardType = [[UTIHelper stringFromPboardType:NSPasteboardTypeString] retain];
+ return true;
+ } else if (aMIMEType.EqualsLiteral(kRTFMime)) {
+ *aPasteboardType = [[UTIHelper stringFromPboardType:NSPasteboardTypeRTF] retain];
+ return true;
+ } else if (aMIMEType.EqualsLiteral(kHTMLMime)) {
+ *aPasteboardType = [[UTIHelper stringFromPboardType:NSPasteboardTypeHTML] retain];
+ return true;
+ } else {
+ return false;
+ }
+}
+
+NSString* nsClipboard::WrapHtmlForSystemPasteboard(NSString* aString) {
+ NSString* wrapped = [NSString
+ stringWithFormat:@"<html>"
+ "<head>"
+ "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">"
+ "</head>"
+ "<body>"
+ "%@"
+ "</body>"
+ "</html>",
+ aString];
+ return wrapped;
+}
+
+/**
+ * Sets the transferable object
+ *
+ */
+NS_IMETHODIMP
+nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* anOwner,
+ int32_t aWhichClipboard) {
+ NS_ASSERTION(aTransferable, "clipboard given a null transferable");
+
+ if (aWhichClipboard == kSelectionCache) {
+ if (aTransferable) {
+ SetSelectionCache(aTransferable);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aTransferable == mTransferable && anOwner == mClipboardOwner) {
+ return NS_OK;
+ }
+ bool selectClipPresent;
+ SupportsSelectionClipboard(&selectClipPresent);
+ bool findClipPresent;
+ SupportsFindClipboard(&findClipPresent);
+ if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_FAILURE;
+ }
+
+ EmptyClipboard(aWhichClipboard);
+
+ mClipboardOwner = anOwner;
+ mTransferable = aTransferable;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ if (mTransferable) {
+ rv = SetNativeClipboardData(aWhichClipboard);
+ }
+
+ return rv;
+}
+
+/**
+ * Gets the transferable object
+ *
+ */
+NS_IMETHODIMP
+nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
+ NS_ASSERTION(aTransferable, "clipboard given a null transferable");
+
+ bool selectClipPresent;
+ SupportsSelectionClipboard(&selectClipPresent);
+ bool findClipPresent;
+ SupportsFindClipboard(&findClipPresent);
+ if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard)
+ return NS_ERROR_FAILURE;
+
+ if (aTransferable) {
+ return GetNativeClipboardData(aTransferable, aWhichClipboard);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
+ if (aWhichClipboard == kSelectionCache) {
+ ClearSelectionCache();
+ return NS_OK;
+ }
+
+ bool selectClipPresent;
+ SupportsSelectionClipboard(&selectClipPresent);
+ bool findClipPresent;
+ SupportsFindClipboard(&findClipPresent);
+ if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mIgnoreEmptyNotification) {
+ return NS_OK;
+ }
+
+ if (mClipboardOwner) {
+ mClipboardOwner->LosingOwnership(mTransferable);
+ mClipboardOwner = nullptr;
+ }
+
+ mTransferable = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsSelectionClipboard(bool* _retval) {
+ *_retval = false; // we don't support the selection clipboard by default.
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsCocoaDebugUtils.h b/widget/cocoa/nsCocoaDebugUtils.h
new file mode 100644
index 0000000000..d95807ad01
--- /dev/null
+++ b/widget/cocoa/nsCocoaDebugUtils.h
@@ -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/. */
+
+#ifndef nsCocoaDebugUtils_h_
+#define nsCocoaDebugUtils_h_
+
+#include <CoreServices/CoreServices.h>
+
+// Definitions and declarations of stuff used by us from the CoreSymbolication
+// framework. This is an undocumented, private framework available on OS X
+// 10.6 and up. It's used by Apple utilities like dtrace, atos, ReportCrash
+// and crashreporterd.
+
+typedef struct _CSTypeRef {
+ unsigned long type;
+ void* contents;
+} CSTypeRef;
+
+typedef CSTypeRef CSSymbolicatorRef;
+typedef CSTypeRef CSSymbolOwnerRef;
+typedef CSTypeRef CSSymbolRef;
+typedef CSTypeRef CSSourceInfoRef;
+
+typedef struct _CSRange {
+ unsigned long long location;
+ unsigned long long length;
+} CSRange;
+
+typedef unsigned long long CSArchitecture;
+
+#define kCSNow LONG_MAX
+
+extern "C" {
+
+CSSymbolicatorRef CSSymbolicatorCreateWithPid(pid_t pid);
+
+CSSymbolicatorRef CSSymbolicatorCreateWithPidFlagsAndNotification(
+ pid_t pid, uint32_t flags, uint32_t notification);
+
+CSArchitecture CSSymbolicatorGetArchitecture(CSSymbolicatorRef symbolicator);
+
+CSSymbolOwnerRef CSSymbolicatorGetSymbolOwnerWithAddressAtTime(
+ CSSymbolicatorRef symbolicator, unsigned long long address, long time);
+
+const char* CSSymbolOwnerGetName(CSSymbolOwnerRef owner);
+
+unsigned long long CSSymbolOwnerGetBaseAddress(CSSymbolOwnerRef owner);
+
+CSSymbolRef CSSymbolOwnerGetSymbolWithAddress(CSSymbolOwnerRef owner,
+ unsigned long long address);
+
+CSSourceInfoRef CSSymbolOwnerGetSourceInfoWithAddress(
+ CSSymbolOwnerRef owner, unsigned long long address);
+
+const char* CSSymbolGetName(CSSymbolRef symbol);
+
+CSRange CSSymbolGetRange(CSSymbolRef symbol);
+
+const char* CSSourceInfoGetFilename(CSSourceInfoRef info);
+
+uint32_t CSSourceInfoGetLineNumber(CSSourceInfoRef info);
+
+CSTypeRef CSRetain(CSTypeRef);
+
+void CSRelease(CSTypeRef);
+
+bool CSIsNull(CSTypeRef);
+
+void CSShow(CSTypeRef);
+
+const char* CSArchitectureGetFamilyName(CSArchitecture);
+
+} // extern "C"
+
+class nsCocoaDebugUtils {
+ public:
+ // Like NSLog() but records more information (for example the full path to
+ // the executable and the "thread name"). Like NSLog(), writes to both
+ // stdout and the system log.
+ static void DebugLog(const char* aFormat, ...);
+
+ // Logs a stack trace of the current point of execution, to both stdout and
+ // the system log.
+ static void PrintStackTrace();
+
+ // Returns the name of the module that "owns" aAddress. This must be
+ // free()ed by the caller.
+ static char* GetOwnerName(void* aAddress);
+
+ // Returns a symbolicated representation of aAddress. This must be
+ // free()ed by the caller.
+ static char* GetAddressString(void* aAddress);
+
+ private:
+ static void DebugLogInt(bool aDecorate, const char* aFormat, ...);
+ static void DebugLogV(bool aDecorate, CFStringRef aFormat, va_list aArgs);
+
+ static void PrintAddress(void* aAddress);
+
+ // The values returned by GetOwnerNameInt() and GetAddressStringInt() must
+ // be free()ed by the caller.
+ static char* GetOwnerNameInt(void* aAddress, CSTypeRef aOwner = sInitializer);
+ static char* GetAddressStringInt(void* aAddress,
+ CSTypeRef aOwner = sInitializer);
+
+ static CSSymbolicatorRef GetSymbolicatorRef();
+ static void ReleaseSymbolicator();
+
+ static CSTypeRef sInitializer;
+ static CSSymbolicatorRef sSymbolicator;
+};
+
+#endif // nsCocoaDebugUtils_h_
diff --git a/widget/cocoa/nsCocoaDebugUtils.mm b/widget/cocoa/nsCocoaDebugUtils.mm
new file mode 100644
index 0000000000..9a0642389a
--- /dev/null
+++ b/widget/cocoa/nsCocoaDebugUtils.mm
@@ -0,0 +1,236 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCocoaDebugUtils.h"
+
+#include <pthread.h>
+#include <libproc.h>
+#include <stdarg.h>
+#include <time.h>
+#include <execinfo.h>
+#include <asl.h>
+
+static char gProcPath[PROC_PIDPATHINFO_MAXSIZE] = {0};
+static char gBundleID[MAXPATHLEN] = {0};
+
+static void MaybeGetPathAndID() {
+ if (!gProcPath[0]) {
+ proc_pidpath(getpid(), gProcPath, sizeof(gProcPath));
+ }
+ if (!gBundleID[0]) {
+ // Apple's CFLog() uses "com.apple.console" (in its call to asl_open()) if
+ // it can't find the bundle id.
+ CFStringRef bundleID = NULL;
+ CFBundleRef mainBundle = CFBundleGetMainBundle();
+ if (mainBundle) {
+ bundleID = CFBundleGetIdentifier(mainBundle);
+ }
+ if (!bundleID) {
+ strcpy(gBundleID, "com.apple.console");
+ } else {
+ CFStringGetCString(bundleID, gBundleID, sizeof(gBundleID), kCFStringEncodingUTF8);
+ }
+ }
+}
+
+static void GetThreadName(char* aName, size_t aSize) {
+ pthread_getname_np(pthread_self(), aName, aSize);
+}
+
+void nsCocoaDebugUtils::DebugLog(const char* aFormat, ...) {
+ va_list args;
+ va_start(args, aFormat);
+ CFStringRef formatCFSTR =
+ CFStringCreateWithCString(kCFAllocatorDefault, aFormat, kCFStringEncodingUTF8);
+ DebugLogV(true, formatCFSTR, args);
+ CFRelease(formatCFSTR);
+ va_end(args);
+}
+
+void nsCocoaDebugUtils::DebugLogInt(bool aDecorate, const char* aFormat, ...) {
+ va_list args;
+ va_start(args, aFormat);
+ CFStringRef formatCFSTR =
+ CFStringCreateWithCString(kCFAllocatorDefault, aFormat, kCFStringEncodingUTF8);
+ DebugLogV(aDecorate, formatCFSTR, args);
+ CFRelease(formatCFSTR);
+ va_end(args);
+}
+
+void nsCocoaDebugUtils::DebugLogV(bool aDecorate, CFStringRef aFormat, va_list aArgs) {
+ MaybeGetPathAndID();
+
+ CFStringRef message =
+ CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, NULL, aFormat, aArgs);
+
+ int msgLength =
+ CFStringGetMaximumSizeForEncoding(CFStringGetLength(message), kCFStringEncodingUTF8);
+ char* msgUTF8 = (char*)calloc(msgLength, 1);
+ CFStringGetCString(message, msgUTF8, msgLength, kCFStringEncodingUTF8);
+ CFRelease(message);
+
+ int finishedLength = msgLength + PROC_PIDPATHINFO_MAXSIZE;
+ char* finished = (char*)calloc(finishedLength, 1);
+ const time_t currentTime = time(NULL);
+ char timestamp[30] = {0};
+ ctime_r(&currentTime, timestamp);
+ if (aDecorate) {
+ char threadName[MAXPATHLEN] = {0};
+ GetThreadName(threadName, sizeof(threadName));
+ snprintf(finished, finishedLength, "(%s) %s[%u] %s[%p] %s\n", timestamp, gProcPath, getpid(),
+ threadName, pthread_self(), msgUTF8);
+ } else {
+ snprintf(finished, finishedLength, "%s\n", msgUTF8);
+ }
+ free(msgUTF8);
+
+ fputs(finished, stdout);
+
+ // Use the Apple System Log facility, as NSLog and CFLog do.
+ aslclient asl = asl_open(NULL, gBundleID, ASL_OPT_NO_DELAY);
+ aslmsg msg = asl_new(ASL_TYPE_MSG);
+ asl_set(msg, ASL_KEY_LEVEL, "4"); // kCFLogLevelWarning, used by NSLog()
+ asl_set(msg, ASL_KEY_MSG, finished);
+ asl_send(asl, msg);
+ asl_free(msg);
+ asl_close(asl);
+
+ free(finished);
+}
+
+CSTypeRef nsCocoaDebugUtils::sInitializer = {0};
+
+CSSymbolicatorRef nsCocoaDebugUtils::sSymbolicator = {0};
+
+#define STACK_MAX 256
+
+void nsCocoaDebugUtils::PrintStackTrace() {
+ void** addresses = (void**)calloc(STACK_MAX, sizeof(void*));
+ if (!addresses) {
+ return;
+ }
+
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+ if (CSIsNull(symbolicator)) {
+ free(addresses);
+ return;
+ }
+
+ uint32_t count = backtrace(addresses, STACK_MAX);
+ for (uint32_t i = 0; i < count; ++i) {
+ PrintAddress(addresses[i]);
+ }
+
+ ReleaseSymbolicator();
+ free(addresses);
+}
+
+void nsCocoaDebugUtils::PrintAddress(void* aAddress) {
+ const char* ownerName = "unknown";
+ const char* addressString = "unknown + 0";
+
+ char* allocatedOwnerName = nullptr;
+ char* allocatedAddressString = nullptr;
+
+ CSSymbolOwnerRef owner = {0};
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+
+ if (!CSIsNull(symbolicator)) {
+ owner = CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+ (unsigned long long)aAddress, kCSNow);
+ }
+ if (!CSIsNull(owner)) {
+ ownerName = allocatedOwnerName = GetOwnerNameInt(aAddress, owner);
+ addressString = allocatedAddressString = GetAddressStringInt(aAddress, owner);
+ }
+ DebugLogInt(false, " (%s) %s", ownerName, addressString);
+
+ free(allocatedOwnerName);
+ free(allocatedAddressString);
+
+ ReleaseSymbolicator();
+}
+
+char* nsCocoaDebugUtils::GetOwnerName(void* aAddress) { return GetOwnerNameInt(aAddress); }
+
+char* nsCocoaDebugUtils::GetOwnerNameInt(void* aAddress, CSTypeRef aOwner) {
+ char* retval = (char*)calloc(MAXPATHLEN, 1);
+
+ const char* ownerName = "unknown";
+
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+ CSTypeRef owner = aOwner;
+
+ if (CSIsNull(owner) && !CSIsNull(symbolicator)) {
+ owner = CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+ (unsigned long long)aAddress, kCSNow);
+ }
+
+ if (!CSIsNull(owner)) {
+ ownerName = CSSymbolOwnerGetName(owner);
+ }
+
+ snprintf(retval, MAXPATHLEN, "%s", ownerName);
+ ReleaseSymbolicator();
+
+ return retval;
+}
+
+char* nsCocoaDebugUtils::GetAddressString(void* aAddress) { return GetAddressStringInt(aAddress); }
+
+char* nsCocoaDebugUtils::GetAddressStringInt(void* aAddress, CSTypeRef aOwner) {
+ char* retval = (char*)calloc(MAXPATHLEN, 1);
+
+ const char* addressName = "unknown";
+ unsigned long long addressOffset = 0;
+
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+ CSTypeRef owner = aOwner;
+
+ if (CSIsNull(owner) && !CSIsNull(symbolicator)) {
+ owner = CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+ (unsigned long long)aAddress, kCSNow);
+ }
+
+ if (!CSIsNull(owner)) {
+ CSSymbolRef symbol = CSSymbolOwnerGetSymbolWithAddress(owner, (unsigned long long)aAddress);
+ if (!CSIsNull(symbol)) {
+ addressName = CSSymbolGetName(symbol);
+ CSRange range = CSSymbolGetRange(symbol);
+ addressOffset = (unsigned long long)aAddress - range.location;
+ } else {
+ addressOffset = (unsigned long long)aAddress - CSSymbolOwnerGetBaseAddress(owner);
+ }
+ }
+
+ snprintf(retval, MAXPATHLEN, "%s + 0x%llx", addressName, addressOffset);
+ ReleaseSymbolicator();
+
+ return retval;
+}
+
+CSSymbolicatorRef nsCocoaDebugUtils::GetSymbolicatorRef() {
+ if (CSIsNull(sSymbolicator)) {
+ // 0x40e0000 is the value returned by
+ // uint32_t CSSymbolicatorGetFlagsForNListOnlyData(void). We don't use
+ // this method directly because it doesn't exist on OS X 10.6. Unless
+ // we limit ourselves to NList data, it will take too long to get a
+ // stack trace where Dwarf debugging info is available (about 15 seconds
+ // with Firefox). This means we won't be able to get a CSSourceInfoRef,
+ // or line number information. Oh well.
+ sSymbolicator = CSSymbolicatorCreateWithPidFlagsAndNotification(getpid(), 0x40e0000, 0);
+ }
+ // Retaining just after creation prevents crashes when calling symbolicator
+ // code (for example from PrintStackTrace()) as Firefox is quitting. Not
+ // sure why. Doing this may mean that we leak sSymbolicator on quitting
+ // (if we ever created it). No particular harm in that, though.
+ return CSRetain(sSymbolicator);
+}
+
+void nsCocoaDebugUtils::ReleaseSymbolicator() {
+ if (!CSIsNull(sSymbolicator)) {
+ CSRelease(sSymbolicator);
+ }
+}
diff --git a/widget/cocoa/nsCocoaFeatures.h b/widget/cocoa/nsCocoaFeatures.h
new file mode 100644
index 0000000000..9ab9d5927c
--- /dev/null
+++ b/widget/cocoa/nsCocoaFeatures.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCocoaFeatures_h_
+#define nsCocoaFeatures_h_
+
+#include <stdint.h>
+
+/// Note that this class assumes we support the platform we are running on.
+/// For better or worse, if the version is unknown or less than what we
+/// support, we set it to the minimum supported version. GetSystemVersion
+/// is the only call that returns the unadjusted values.
+class nsCocoaFeatures {
+ public:
+ static int32_t macOSVersion();
+ static int32_t macOSVersionMajor();
+ static int32_t macOSVersionMinor();
+ static int32_t macOSVersionBugFix();
+ static bool OnSierraExactly();
+ 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);
+
+ static bool ProcessIsRosettaTranslated();
+
+ // These are utilities that do not change or depend on the value of
+ // mOSVersion 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:
+ nsCocoaFeatures() = delete; // Prevent instantiation.
+ static void InitializeVersionNumbers();
+
+ static int32_t mOSVersion;
+};
+
+// C-callable helper for cairo-quartz-font.c and SkFontHost_mac.cpp
+extern "C" {
+bool Gecko_OnSierraExactly();
+}
+
+#endif // nsCocoaFeatures_h_
diff --git a/widget/cocoa/nsCocoaFeatures.mm b/widget/cocoa/nsCocoaFeatures.mm
new file mode 100644
index 0000000000..5f1138baf0
--- /dev/null
+++ b/widget/cocoa/nsCocoaFeatures.mm
@@ -0,0 +1,196 @@
+/* -*- 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/. */
+
+// 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_9_HEX 0x000A0900
+#define MACOS_VERSION_10_10_HEX 0x000A0A00
+#define MACOS_VERSION_10_11_HEX 0x000A0B00
+#define MACOS_VERSION_10_12_HEX 0x000A0C00
+#define MACOS_VERSION_10_13_HEX 0x000A0D00
+#define MACOS_VERSION_10_14_HEX 0x000A0E00
+#define MACOS_VERSION_10_15_HEX 0x000A0F00
+#define MACOS_VERSION_10_16_HEX 0x000A1000
+#define MACOS_VERSION_11_0_HEX 0x000B0000
+
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include "nsDebug.h"
+#include "nsObjCExceptions.h"
+
+#import <Cocoa/Cocoa.h>
+#include <sys/sysctl.h>
+
+/*static*/ 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"];
+ if (!versionString) {
+ NS_ERROR("Couldn't read /System/Library/CoreServices/SystemVersion.plist to determine macOS "
+ "version.");
+ return;
+ }
+ 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.9");
+ macOSVersion = MACOS_VERSION_10_9_HEX;
+ } else if (aMajor == 10 && aMinor < 9) {
+ aMinor = 9;
+ NS_ERROR("macOS version too old, assuming 10.9");
+ macOSVersion = MACOS_VERSION_10_9_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::OnSierraExactly() {
+ return (macOSVersion() >= MACOS_VERSION_10_12_HEX) && (macOSVersion() < MACOS_VERSION_10_13_HEX);
+}
+
+/* Version of OnSierraExactly as global function callable from cairo & skia */
+bool Gecko_OnSierraExactly() { return nsCocoaFeatures::OnSierraExactly(); }
+
+/* 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);
+}
+
+/*
+ * Returns true if the process is running under Rosetta translation. Returns
+ * false if running natively or if an error was encountered. We use the
+ * `sysctl.proc_translated` sysctl which is documented by Apple to be used
+ * for this purpose. Note: using this in a sandboxed process requires allowing
+ * the sysctl in the sandbox policy.
+ */
+/* static */ bool nsCocoaFeatures::ProcessIsRosettaTranslated() {
+ int ret = 0;
+ size_t size = sizeof(ret);
+ if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) {
+ if (errno != ENOENT) {
+ fprintf(stderr, "Failed to check for translation environment\n");
+ }
+ return false;
+ }
+ return (ret == 1);
+}
diff --git a/widget/cocoa/nsCocoaUtils.h b/widget/cocoa/nsCocoaUtils.h
new file mode 100644
index 0000000000..4b15a8dbcc
--- /dev/null
+++ b/widget/cocoa/nsCocoaUtils.h
@@ -0,0 +1,492 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCocoaUtils_h_
+#define nsCocoaUtils_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsRect.h"
+#include "imgIContainer.h"
+#include "npapi.h"
+#include "nsTArray.h"
+#include "Units.h"
+
+// This must be the last include:
+#include "nsObjCExceptions.h"
+
+#include "mozilla/EventForwards.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.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 {
+class TimeStamp;
+namespace gfx {
+class SourceSurface;
+} // namespace gfx
+namespace dom {
+class Promise;
+} // namespace dom
+} // namespace mozilla
+
+using mozilla::StaticAutoPtr;
+using mozilla::StaticMutex;
+
+// 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;
+
+// Send an event to the current Cocoa app-modal session. Present in all
+// versions of OS X from (at least) 10.2.8 through 10.5.
+- (void)_modalSession:(NSModalSession)aSession sendEvent:(NSEvent*)theEvent;
+
+@end
+
+struct KeyBindingsCommand {
+ SEL selector;
+ id data;
+};
+
+@interface NativeKeyBindingsRecorder : NSResponder {
+ @private
+ nsTArray<KeyBindingsCommand>* mCommands;
+}
+
+- (void)startRecording:(nsTArray<KeyBindingsCommand>&)aCommands;
+
+- (void)doCommandBySelector:(SEL)aSelector;
+
+- (void)insertText:(id)aString;
+
+@end // NativeKeyBindingsRecorder
+
+#if !defined(MAC_OS_X_VERSION_10_14) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_14
+typedef NSString* AVMediaType;
+#endif
+
+class nsCocoaUtils {
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+ typedef mozilla::LayoutDeviceIntPoint LayoutDeviceIntPoint;
+ typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
+ typedef mozilla::dom::Promise Promise;
+ typedef StaticAutoPtr<nsTArray<RefPtr<Promise>>> PromiseArray;
+
+ public:
+ // Get the backing scale factor from an object that supports this selector
+ // (NSView/Window/Screen, on 10.7 or later), returning 1.0 if not supported
+ static CGFloat GetBackingScaleFactor(id aObject) {
+ if (HiDPIEnabled() && [aObject respondsToSelector:@selector(backingScaleFactor)]) {
+ return [aObject backingScaleFactor];
+ }
+ return 1.0;
+ }
+
+ // Conversions between Cocoa points and device pixels, given the backing
+ // scale factor from a view/window/screen.
+ static int32_t CocoaPointsToDevPixels(CGFloat aPts, CGFloat aBackingScale) {
+ return NSToIntRound(aPts * aBackingScale);
+ }
+
+ static LayoutDeviceIntPoint CocoaPointsToDevPixels(const NSPoint& aPt, CGFloat aBackingScale) {
+ return LayoutDeviceIntPoint(NSToIntRound(aPt.x * aBackingScale),
+ NSToIntRound(aPt.y * aBackingScale));
+ }
+
+ static LayoutDeviceIntPoint CocoaPointsToDevPixelsRoundDown(const NSPoint& aPt,
+ CGFloat aBackingScale) {
+ return LayoutDeviceIntPoint(NSToIntFloor(aPt.x * aBackingScale),
+ NSToIntFloor(aPt.y * aBackingScale));
+ }
+
+ static LayoutDeviceIntRect CocoaPointsToDevPixels(const NSRect& aRect, CGFloat aBackingScale) {
+ return LayoutDeviceIntRect(NSToIntRound(aRect.origin.x * aBackingScale),
+ NSToIntRound(aRect.origin.y * aBackingScale),
+ NSToIntRound(aRect.size.width * aBackingScale),
+ NSToIntRound(aRect.size.height * aBackingScale));
+ }
+
+ static CGFloat DevPixelsToCocoaPoints(int32_t aPixels, CGFloat aBackingScale) {
+ return (CGFloat)aPixels / aBackingScale;
+ }
+
+ static NSPoint DevPixelsToCocoaPoints(const mozilla::LayoutDeviceIntPoint& aPt,
+ CGFloat aBackingScale) {
+ return NSMakePoint((CGFloat)aPt.x / aBackingScale, (CGFloat)aPt.y / aBackingScale);
+ }
+
+ // Implements an NSPoint equivalent of -[NSWindow convertRectFromScreen:].
+ static NSPoint ConvertPointFromScreen(NSWindow* aWindow, const NSPoint& aPt) {
+ return [aWindow convertRectFromScreen:NSMakeRect(aPt.x, aPt.y, 0, 0)].origin;
+ }
+
+ // Implements an NSPoint equivalent of -[NSWindow convertRectToScreen:].
+ static NSPoint ConvertPointToScreen(NSWindow* aWindow, const NSPoint& aPt) {
+ return [aWindow convertRectToScreen:NSMakeRect(aPt.x, aPt.y, 0, 0)].origin;
+ }
+
+ static NSRect DevPixelsToCocoaPoints(const LayoutDeviceIntRect& aRect, CGFloat aBackingScale) {
+ return NSMakeRect((CGFloat)aRect.X() / aBackingScale, (CGFloat)aRect.Y() / aBackingScale,
+ (CGFloat)aRect.Width() / aBackingScale,
+ (CGFloat)aRect.Height() / aBackingScale);
+ }
+
+ // Returns the given y coordinate, which must be in screen coordinates,
+ // flipped from Gecko to Cocoa or Cocoa to Gecko.
+ static float FlippedScreenY(float y);
+
+ // The following functions come in "DevPix" variants that work with
+ // backing-store (device pixel) coordinates, as well as the original
+ // versions that expect coordinates in Cocoa points/CSS pixels.
+ // The difference becomes important in HiDPI display modes, where Cocoa
+ // points and backing-store pixels are no longer 1:1.
+
+ // Gecko rects (nsRect) contain an origin (x,y) in a coordinate
+ // system with (0,0) in the top-left of the primary screen. Cocoa rects
+ // (NSRect) contain an origin (x,y) in a coordinate system with (0,0)
+ // in the bottom-left of the primary screen. Both nsRect and NSRect
+ // contain width/height info, with no difference in their use.
+ // This function does no scaling, so the Gecko coordinates are
+ // expected to be desktop pixels, which are equal to Cocoa points
+ // (by definition).
+ static NSRect GeckoRectToCocoaRect(const mozilla::DesktopIntRect& geckoRect);
+
+ // Converts aGeckoRect in dev pixels to points in Cocoa coordinates
+ static NSRect GeckoRectToCocoaRectDevPix(const mozilla::LayoutDeviceIntRect& aGeckoRect,
+ CGFloat aBackingScale);
+
+ // See explanation for geckoRectToCocoaRect, guess what this does...
+ static mozilla::DesktopIntRect CocoaRectToGeckoRect(const NSRect& cocoaRect);
+
+ static mozilla::LayoutDeviceIntRect CocoaRectToGeckoRectDevPix(const NSRect& aCocoaRect,
+ CGFloat aBackingScale);
+
+ // Gives the location for the event in screen coordinates. Do not call this
+ // unless the window the event was originally targeted at is still alive!
+ // anEvent may be nil -- in that case the current mouse location is returned.
+ static NSPoint ScreenLocationForEvent(NSEvent* anEvent);
+
+ // Determines if an event happened over a window, whether or not the event
+ // is for the window. Does not take window z-order into account.
+ static BOOL IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow);
+
+ // Events are set up so that their coordinates refer to the window to which they
+ // were originally sent. If we reroute the event somewhere else, we'll have
+ // to get the window coordinates this way. Do not call this unless the window
+ // the event was originally targeted at is still alive!
+ static NSPoint EventLocationForWindow(NSEvent* anEvent, NSWindow* aWindow);
+
+ // Compatibility wrappers for the -[NSEvent phase], -[NSEvent momentumPhase],
+ // -[NSEvent hasPreciseScrollingDeltas] and -[NSEvent scrollingDeltaX/Y] APIs
+ // that became availaible starting with the 10.7 SDK.
+ // All of these can be removed once we drop support for 10.6.
+ static NSEventPhase EventPhase(NSEvent* aEvent);
+ static NSEventPhase EventMomentumPhase(NSEvent* aEvent);
+ static BOOL IsMomentumScrollEvent(NSEvent* aEvent);
+ static BOOL HasPreciseScrollingDeltas(NSEvent* aEvent);
+ static void GetScrollingDeltas(NSEvent* aEvent, CGFloat* aOutDeltaX, CGFloat* aOutDeltaY);
+ static BOOL EventHasPhaseInformation(NSEvent* aEvent);
+
+ // Hides the Menu bar and the Dock. Multiple hide/show requests can be nested.
+ static void HideOSChromeOnScreen(bool aShouldHide);
+
+ static nsIWidget* GetHiddenWindowWidget();
+
+ static void PrepareForNativeAppModalDialog();
+ static void CleanUpAfterNativeAppModalDialog();
+
+ // 3 utility functions to go from a frame of imgIContainer to CGImage and then to NSImage
+ // Convert imgIContainer -> CGImageRef, caller owns result
+
+ /** Creates a <code>CGImageRef</code> from a frame contained in an <code>imgIContainer</code>.
+ Copies the pixel data from the indicated frame of the <code>imgIContainer</code> into a new
+ <code>CGImageRef</code>. The caller owns the <code>CGImageRef</code>.
+ @param aFrame the frame to convert
+ @param aResult the resulting CGImageRef
+ @param aIsEntirelyBlack an outparam that, if non-null, will be set to a
+ bool that indicates whether the RGB values on all
+ pixels are zero
+ @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
+ */
+ static nsresult CreateCGImageFromSurface(SourceSurface* aSurface, CGImageRef* aResult,
+ bool* aIsEntirelyBlack = nullptr);
+
+ /** Creates a Cocoa <code>NSImage</code> from a <code>CGImageRef</code>.
+ Copies the pixel data from the <code>CGImageRef</code> into a new <code>NSImage</code>.
+ The caller owns the <code>NSImage</code>.
+ @param aInputImage the image to convert
+ @param aResult the resulting NSImage
+ @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
+ */
+ static nsresult CreateNSImageFromCGImage(CGImageRef aInputImage, NSImage** aResult);
+
+ /** Creates a Cocoa <code>NSImage</code> from a frame of an <code>imgIContainer</code>.
+ Combines the two methods above. The caller owns the <code>NSImage</code>.
+ @param aImage the image to extract a frame from
+ @param aWhichFrame the frame to extract (see imgIContainer FRAME_*)
+ @param aResult the resulting NSImage
+ @param scaleFactor the desired scale factor of the NSImage (2 for a retina display)
+ @param aIsEntirelyBlack an outparam that, if non-null, will be set to a
+ bool that indicates whether the RGB values on all
+ pixels are zero
+ @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
+ */
+ static nsresult CreateNSImageFromImageContainer(imgIContainer* aImage, uint32_t aWhichFrame,
+ NSImage** aResult, CGFloat scaleFactor,
+ bool* aIsEntirelyBlack = nullptr);
+
+ /** Creates a Cocoa <code>NSImage</code> from a frame of an <code>imgIContainer</code>.
+ The new <code>NSImage</code> will have both a regular and HiDPI representation.
+ The caller owns the <code>NSImage</code>.
+ @param aImage the image to extract a frame from
+ @param aWhichFrame the frame to extract (see imgIContainer FRAME_*)
+ @param aResult the resulting NSImage
+ @param aIsEntirelyBlack an outparam that, if non-null, will be set to a
+ bool that indicates whether the RGB values on all
+ pixels are zero
+ @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
+ */
+ static nsresult CreateDualRepresentationNSImageFromImageContainer(
+ imgIContainer* aImage, uint32_t aWhichFrame, NSImage** aResult,
+ bool* aIsEntirelyBlack = nullptr);
+
+ /**
+ * Returns nsAString for aSrc.
+ */
+ static void GetStringForNSString(const NSString* aSrc, nsAString& aDist);
+
+ /**
+ * Makes NSString instance for aString.
+ */
+ static NSString* ToNSString(const nsAString& aString);
+
+ /**
+ * Makes NSString instance for aCString.
+ */
+ static NSString* ToNSString(const nsACString& aCString);
+
+ /**
+ * 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);
+
+ /**
+ * Makes a cocoa event from a widget keyboard event.
+ */
+ static NSEvent* MakeNewCococaEventFromWidgetEvent(const mozilla::WidgetKeyboardEvent& aKeyEvent,
+ NSInteger aWindowNumber,
+ NSGraphicsContext* aContext);
+
+ /**
+ * Initializes aNPCocoaEvent.
+ */
+ static void InitNPCocoaEvent(NPCocoaEvent* aNPCocoaEvent);
+
+ /**
+ * Initializes WidgetInputEvent for aNativeEvent or aModifiers.
+ */
+ static void InitInputEvent(mozilla::WidgetInputEvent& aInputEvent, NSEvent* aNativeEvent);
+
+ /**
+ * Converts the native modifiers from aNativeEvent into WidgetMouseEvent
+ * Modifiers. aNativeEvent can be null.
+ */
+ static mozilla::Modifiers ModifiersForEvent(NSEvent* aNativeEvent);
+
+ /**
+ * ConvertToCarbonModifier() returns carbon modifier flags for the cocoa
+ * modifier flags.
+ * NOTE: The result never includes right*Key.
+ */
+ static UInt32 ConvertToCarbonModifier(NSUInteger aCocoaModifier);
+
+ /**
+ * Whether to support HiDPI rendering. For testing purposes, to be removed
+ * once we're comfortable with the HiDPI behavior.
+ */
+ static bool HiDPIEnabled();
+
+ /**
+ * Keys can optionally be bound by system or user key bindings to one or more
+ * commands based on selectors. This collects any such commands in the
+ * provided array.
+ */
+ static void GetCommandsFromKeyEvent(NSEvent* aEvent, nsTArray<KeyBindingsCommand>& aCommands);
+
+ /**
+ * Converts the string name of a Gecko key (like "VK_HOME") to the
+ * corresponding Cocoa Unicode character.
+ */
+ static uint32_t ConvertGeckoNameToMacCharCode(const nsAString& aKeyCodeName);
+
+ /**
+ * Converts a Gecko key code (like NS_VK_HOME) to the corresponding Cocoa
+ * Unicode character.
+ */
+ static uint32_t ConvertGeckoKeyCodeToMacCharCode(uint32_t aKeyCode);
+
+ /**
+ * Convert string with font attribute to NSMutableAttributedString
+ */
+ static NSMutableAttributedString* GetNSMutableAttributedString(
+ const nsAString& aText, const nsTArray<mozilla::FontRange>& aFontRanges,
+ const bool aIsVertical, const CGFloat aBackingScaleFactor);
+
+ /**
+ * Compute TimeStamp from an event's timestamp.
+ * If aEventTime is 0, this returns current timestamp.
+ */
+ static mozilla::TimeStamp GetEventTimeStamp(NSTimeInterval aEventTime);
+
+ /**
+ * Check whether double clicking on the titlebar should cause the window to
+ * zoom (maximize).
+ */
+ static bool ShouldZoomOnTitlebarDoubleClick();
+
+ /**
+ * Check whether double clicking on the titlebar should cause the window to
+ * minimize.
+ */
+ static bool ShouldMinimizeOnTitlebarDoubleClick();
+
+ /**
+ * Get the current video capture permission status.
+ * Returns NS_ERROR_NOT_IMPLEMENTED on 10.13 and earlier macOS versions.
+ */
+ static nsresult GetVideoCapturePermissionState(uint16_t& aPermissionState);
+
+ /**
+ * Get the current audio capture permission status.
+ * Returns NS_ERROR_NOT_IMPLEMENTED on 10.13 and earlier macOS versions.
+ */
+ static nsresult GetAudioCapturePermissionState(uint16_t& aPermissionState);
+
+ /**
+ * Get the current screen capture permission status.
+ * Returns NS_ERROR_NOT_IMPLEMENTED on 10.14 and earlier macOS versions.
+ */
+ static nsresult GetScreenCapturePermissionState(uint16_t& aPermissionState);
+
+ /**
+ * Request video capture permission from the OS. Caller must be running
+ * on the main thread and the promise will be resolved on the main thread.
+ * Returns NS_ERROR_NOT_IMPLEMENTED on 10.13 and earlier macOS versions.
+ */
+ static nsresult RequestVideoCapturePermission(RefPtr<Promise>& aPromise);
+
+ /**
+ * Request audio capture permission from the OS. Caller must be running
+ * on the main thread and the promise will be resolved on the main thread.
+ * Returns NS_ERROR_NOT_IMPLEMENTED on 10.13 and earlier macOS versions.
+ */
+ static nsresult RequestAudioCapturePermission(RefPtr<Promise>& aPromise);
+
+ /**
+ * Request screen capture permission from the OS using an unreliable method.
+ */
+ static nsresult MaybeRequestScreenCapturePermission();
+
+ private:
+ /**
+ * Completion handlers used as an argument to the macOS API to
+ * request media capture permission. These are called asynchronously
+ * on an arbitrary dispatch queue.
+ */
+ static void (^AudioCompletionHandler)(BOOL);
+ static void (^VideoCompletionHandler)(BOOL);
+
+ /**
+ * Called from the audio and video completion handlers in order to
+ * dispatch the handling back to the main thread.
+ */
+ static void ResolveAudioCapturePromises(bool aGranted);
+ static void ResolveVideoCapturePromises(bool aGranted);
+
+ /**
+ * Main implementation for Request{Audio,Video}CapturePermission.
+ * @param aType the AVMediaType to request capture permission for
+ * @param aPromise the Promise to resolve when capture permission
+ * is either allowed or denied
+ * @param aPromiseList the array of promises to save |aPromise| in
+ * @param aHandler the block function (either ResolveAudioCapturePromises
+ * or ResolveVideoCapturePromises) to be used as
+ * the requestAccessForMediaType callback.
+ */
+ static nsresult RequestCapturePermission(NSString* aType, RefPtr<Promise>& aPromise,
+ PromiseArray& aPromiseList,
+ void (^aHandler)(BOOL granted));
+ /**
+ * Resolves the pending promises that are waiting for a response
+ * to a request video or audio capture permission.
+ */
+ static void ResolveMediaCapturePromises(bool aGranted, PromiseArray& aPromiseList);
+
+ /**
+ * Array of promises waiting to be resolved due to a video capture request.
+ */
+ static PromiseArray sVideoCapturePromises;
+
+ /**
+ * Array of promises waiting to be resolved due to an audio capture request.
+ */
+ static PromiseArray sAudioCapturePromises;
+
+ /**
+ * Lock protecting |sVideoCapturePromises| and |sAudioCapturePromises|.
+ */
+ static StaticMutex sMediaCaptureMutex;
+};
+
+#endif // nsCocoaUtils_h_
diff --git a/widget/cocoa/nsCocoaUtils.mm b/widget/cocoa/nsCocoaUtils.mm
new file mode 100644
index 0000000000..e8250bbc68
--- /dev/null
+++ b/widget/cocoa/nsCocoaUtils.mm
@@ -0,0 +1,1456 @@
+/* -*- 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/. */
+
+#import <AVFoundation/AVFoundation.h>
+
+#include <cmath>
+
+#include "AppleUtils.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "ImageRegion.h"
+#include "nsCocoaUtils.h"
+#include "nsChildView.h"
+#include "nsMenuBarX.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaWindow.h"
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIAppShellService.h"
+#include "nsIOSPermissionRequest.h"
+#include "nsIRunnable.h"
+#include "nsIAppWindow.h"
+#include "nsIBaseWindow.h"
+#include "nsMenuUtilsX.h"
+#include "nsToolkit.h"
+#include "nsCRT.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/SVGImageContext.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/gfx/2D.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+using mozilla::dom::Promise;
+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;
+
+LazyLogModule gCocoaUtilsLog("nsCocoaUtils");
+#undef LOG
+#define LOG(...) MOZ_LOG(gCocoaUtilsLog, LogLevel::Debug, (__VA_ARGS__))
+
+/*
+ * For each audio and video capture request, we hold an owning reference
+ * to a promise to be resolved when the request's async callback is invoked.
+ * sVideoCapturePromises and sAudioCapturePromises are arrays of video and
+ * audio promises waiting for to be resolved. Each array is protected by a
+ * mutex.
+ */
+nsCocoaUtils::PromiseArray nsCocoaUtils::sVideoCapturePromises;
+nsCocoaUtils::PromiseArray nsCocoaUtils::sAudioCapturePromises;
+StaticMutex nsCocoaUtils::sMediaCaptureMutex;
+
+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] == NSEventTypeMouseMoved) 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] == NSEventTypeScrollWheel && EventMomentumPhase(aEvent) != NSEventPhaseNone;
+}
+
+@interface NSEvent (HasPreciseScrollingDeltas)
+// 10.7 and above
+- (BOOL)hasPreciseScrollingDeltas;
+// For 10.6 and below, see the comment in nsChildView.h about _eventRef
+- (EventRef)_eventRef;
+@end
+
+BOOL nsCocoaUtils::HasPreciseScrollingDeltas(NSEvent* aEvent) {
+ if ([aEvent respondsToSelector:@selector(hasPreciseScrollingDeltas)]) {
+ return [aEvent hasPreciseScrollingDeltas];
+ }
+
+ // For events that don't contain pixel scrolling information, the event
+ // kind of their underlaying carbon event is kEventMouseWheelMoved instead
+ // of kEventMouseScroll.
+ EventRef carbonEvent = [aEvent _eventRef];
+ return carbonEvent && ::GetEventKind(carbonEvent) == kEventMouseScroll;
+}
+
+@interface NSEvent (ScrollingDeltas)
+// 10.6 and below
+- (CGFloat)deviceDeltaX;
+- (CGFloat)deviceDeltaY;
+// 10.7 and above
+- (CGFloat)scrollingDeltaX;
+- (CGFloat)scrollingDeltaY;
+@end
+
+void nsCocoaUtils::GetScrollingDeltas(NSEvent* aEvent, CGFloat* aOutDeltaX, CGFloat* aOutDeltaY) {
+ if ([aEvent respondsToSelector:@selector(scrollingDeltaX)]) {
+ *aOutDeltaX = [aEvent scrollingDeltaX];
+ *aOutDeltaY = [aEvent scrollingDeltaY];
+ return;
+ }
+ if ([aEvent respondsToSelector:@selector(deviceDeltaX)] && HasPreciseScrollingDeltas(aEvent)) {
+ // Calling deviceDeltaX/Y on those events that do not contain pixel
+ // scrolling information triggers a Cocoa assertion and an
+ // Objective-C NSInternalInconsistencyException.
+ *aOutDeltaX = [aEvent deviceDeltaX];
+ *aOutDeltaY = [aEvent deviceDeltaY];
+ return;
+ }
+
+ // This is only hit pre-10.7 when we are called on a scroll event that does
+ // not contain pixel scrolling information.
+ CGFloat lineDeltaPixels = 12;
+ *aOutDeltaX = [aEvent deltaX] * lineDeltaPixels;
+ *aOutDeltaY = [aEvent deltaY] * lineDeltaPixels;
+}
+
+BOOL nsCocoaUtils::EventHasPhaseInformation(NSEvent* aEvent) {
+ if (![aEvent respondsToSelector:@selector(phase)]) {
+ return NO;
+ }
+ return EventPhase(aEvent) != NSEventPhaseNone || EventMomentumPhase(aEvent) != NSEventPhaseNone;
+}
+
+void nsCocoaUtils::HideOSChromeOnScreen(bool aShouldHide) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Keep track of how many hiding requests have been made, so that they can
+ // be nested.
+ static int sHiddenCount = 0;
+
+ sHiddenCount += aShouldHide ? 1 : -1;
+ NS_ASSERTION(sHiddenCount >= 0, "Unbalanced HideMenuAndDockForWindow calls");
+
+ NSApplicationPresentationOptions options =
+ sHiddenCount <= 0 ? NSApplicationPresentationDefault
+ : NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
+ [NSApp setPresentationOptions:options];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
+nsIWidget* nsCocoaUtils::GetHiddenWindowWidget() {
+ nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ if (!appShell) {
+ NS_WARNING("Couldn't get AppShellService in order to get hidden window ref");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIAppWindow> hiddenWindow;
+ appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow));
+ if (!hiddenWindow) {
+ // Don't warn, this happens during shutdown, bug 358607.
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIBaseWindow> baseHiddenWindow;
+ baseHiddenWindow = do_GetInterface(hiddenWindow);
+ if (!baseHiddenWindow) {
+ NS_WARNING("Couldn't get nsIBaseWindow from hidden window (nsIAppWindow)");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIWidget> hiddenWindowWidget;
+ if (NS_FAILED(baseHiddenWindow->GetMainWidget(getter_AddRefs(hiddenWindowWidget)))) {
+ NS_WARNING("Couldn't get nsIWidget from hidden window (nsIBaseWindow)");
+ return nullptr;
+ }
+
+ return hiddenWindowWidget;
+}
+
+void nsCocoaUtils::PrepareForNativeAppModalDialog() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Don't do anything if this is embedding. We'll assume that if there is no hidden
+ // window we shouldn't do anything, and that should cover the embedding case.
+ nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
+ if (!hiddenWindowMenuBar) return;
+
+ // First put up the hidden window menu bar so that app menu event handling is correct.
+ hiddenWindowMenuBar->Paint();
+
+ NSMenu* mainMenu = [NSApp mainMenu];
+ NS_ASSERTION([mainMenu numberOfItems] > 0,
+ "Main menu does not have any items, something is terribly wrong!");
+
+ // Create new menu bar for use with modal dialog
+ NSMenu* newMenuBar = [[NSMenu alloc] initWithTitle:@""];
+
+ // Swap in our app menu. Note that the event target is whatever window is up when
+ // the app modal dialog goes up.
+ NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain];
+ [mainMenu removeItemAtIndex:0];
+ [newMenuBar insertItem:firstMenuItem atIndex:0];
+ [firstMenuItem release];
+
+ // Add standard edit menu
+ [newMenuBar addItem:nsMenuUtilsX::GetStandardEditMenuItem()];
+
+ // Show the new menu bar
+ [NSApp setMainMenu:newMenuBar];
+ [newMenuBar release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaUtils::CleanUpAfterNativeAppModalDialog() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Don't do anything if this is embedding. We'll assume that if there is no hidden
+ // window we shouldn't do anything, and that should cover the embedding case.
+ nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
+ if (!hiddenWindowMenuBar) return;
+
+ NSWindow* mainWindow = [NSApp mainWindow];
+ if (!mainWindow)
+ hiddenWindowMenuBar->Paint();
+ else
+ [WindowDelegate paintMenubarForWindow:mainWindow];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static void data_ss_release_callback(void* aDataSourceSurface, const void* data, size_t size) {
+ if (aDataSourceSurface) {
+ static_cast<DataSourceSurface*>(aDataSourceSurface)->Unmap();
+ static_cast<DataSourceSurface*>(aDataSourceSurface)->Release();
+ }
+}
+
+// This function assumes little endian byte order.
+static bool ComputeIsEntirelyBlack(const DataSourceSurface::MappedSurface& aMap,
+ const IntSize& aSize) {
+ for (int32_t y = 0; y < aSize.height; y++) {
+ size_t rowStart = y * aMap.mStride;
+ for (int32_t x = 0; x < aSize.width; x++) {
+ size_t index = rowStart + x * 4;
+ if (aMap.mData[index + 0] != 0 || aMap.mData[index + 1] != 0 || aMap.mData[index + 2] != 0) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+nsresult nsCocoaUtils::CreateCGImageFromSurface(SourceSurface* aSurface, CGImageRef* aResult,
+ bool* aIsEntirelyBlack) {
+ RefPtr<DataSourceSurface> dataSurface;
+
+ if (aSurface->GetFormat() == SurfaceFormat::B8G8R8A8) {
+ dataSurface = aSurface->GetDataSurface();
+ } else {
+ // CGImageCreate only supports 16- and 32-bit bit-depth
+ // Convert format to SurfaceFormat::B8G8R8A8
+ dataSurface =
+ gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(aSurface, SurfaceFormat::B8G8R8A8);
+ }
+
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ int32_t width = dataSurface->GetSize().width;
+ int32_t height = dataSurface->GetSize().height;
+ if (height < 1 || width < 1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ return NS_ERROR_FAILURE;
+ }
+ // The Unmap() call happens in data_ss_release_callback
+
+ if (aIsEntirelyBlack) {
+ *aIsEntirelyBlack = ComputeIsEntirelyBlack(map, dataSurface->GetSize());
+ }
+
+ // 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,
+ bool* aIsEntirelyBlack) {
+ RefPtr<SourceSurface> surface;
+ int32_t width = 0, height = 0;
+ aImage->GetWidth(&width);
+ aImage->GetHeight(&height);
+
+ // Render a vector image at the correct resolution on a retina display
+ if (aImage->GetType() == imgIContainer::TYPE_VECTOR && scaleFactor != 1.0f) {
+ IntSize scaledSize = IntSize::Ceil(width * scaleFactor, height * scaleFactor);
+
+ RefPtr<DrawTarget> drawTarget = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ scaledSize, SurfaceFormat::B8G8R8A8);
+ if (!drawTarget || !drawTarget->IsValid()) {
+ NS_ERROR("Failed to create valid DrawTarget");
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<gfxContext> context = gfxContext::CreateOrNull(drawTarget);
+ MOZ_ASSERT(context);
+
+ mozilla::image::ImgDrawResult res = aImage->Draw(
+ context, scaledSize, ImageRegion::Create(scaledSize), aWhichFrame, SamplingFilter::POINT,
+ /* no SVGImageContext */ Nothing(), imgIContainer::FLAG_SYNC_DECODE, 1.0);
+
+ if (res != mozilla::image::ImgDrawResult::SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ surface = drawTarget->Snapshot();
+ } else {
+ surface = aImage->GetFrame(aWhichFrame,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ }
+
+ NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
+
+ CGImageRef imageRef = NULL;
+ nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef, aIsEntirelyBlack);
+ 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;
+}
+
+nsresult nsCocoaUtils::CreateDualRepresentationNSImageFromImageContainer(imgIContainer* aImage,
+ uint32_t aWhichFrame,
+ NSImage** aResult,
+ bool* aIsEntirelyBlack) {
+ int32_t width = 0, height = 0;
+ aImage->GetWidth(&width);
+ aImage->GetHeight(&height);
+ NSSize size = NSMakeSize(width, height);
+ *aResult = [[NSImage alloc] init];
+ [*aResult setSize:size];
+
+ NSImage* newRepresentation = nil;
+ nsresult rv = nsCocoaUtils::CreateNSImageFromImageContainer(
+ aImage, aWhichFrame, &newRepresentation, 1.0f, aIsEntirelyBlack);
+ if (NS_FAILED(rv) || !newRepresentation) {
+ return NS_ERROR_FAILURE;
+ }
+
+ [[[newRepresentation representations] objectAtIndex:0] setSize:size];
+ [*aResult addRepresentation:[[newRepresentation representations] objectAtIndex:0]];
+ [newRepresentation release];
+ newRepresentation = nil;
+
+ rv = nsCocoaUtils::CreateNSImageFromImageContainer(aImage, aWhichFrame, &newRepresentation, 2.0f,
+ aIsEntirelyBlack);
+ if (NS_FAILED(rv) || !newRepresentation) {
+ return NS_ERROR_FAILURE;
+ }
+
+ [[[newRepresentation representations] objectAtIndex:0] setSize:size];
+ [*aResult addRepresentation:[[newRepresentation representations] objectAtIndex:0]];
+ [newRepresentation release];
+ return NS_OK;
+}
+
+// static
+void nsCocoaUtils::GetStringForNSString(const NSString* aSrc, nsAString& aDist) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aSrc) {
+ aDist.Truncate();
+ return;
+ }
+
+ aDist.SetLength([aSrc length]);
+ [aSrc getCharacters:reinterpret_cast<unichar*>(aDist.BeginWriting())
+ range:NSMakeRange(0, [aSrc length])];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// static
+NSString* nsCocoaUtils::ToNSString(const nsAString& aString) {
+ if (aString.IsEmpty()) {
+ return [NSString string];
+ }
+ return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aString.BeginReading())
+ length:aString.Length()];
+}
+
+// static
+NSString* nsCocoaUtils::ToNSString(const nsACString& aCString) {
+ if (aCString.IsEmpty()) {
+ return [NSString string];
+ }
+ return [[[NSString alloc] initWithBytes:aCString.BeginReading()
+ length:aCString.Length()
+ encoding:NSUTF8StringEncoding] autorelease];
+}
+
+// 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
+NSEvent* nsCocoaUtils::MakeNewCococaEventFromWidgetEvent(const WidgetKeyboardEvent& aKeyEvent,
+ NSInteger aWindowNumber,
+ NSGraphicsContext* aContext) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSEventType eventType;
+ if (aKeyEvent.mMessage == eKeyUp) {
+ eventType = NSEventTypeKeyUp;
+ } else {
+ eventType = NSEventTypeKeyDown;
+ }
+
+ static const uint32_t sModifierFlagMap[][2] = {{MODIFIER_SHIFT, NSEventModifierFlagShift},
+ {MODIFIER_CONTROL, NSEventModifierFlagControl},
+ {MODIFIER_ALT, NSEventModifierFlagOption},
+ {MODIFIER_ALTGRAPH, NSEventModifierFlagOption},
+ {MODIFIER_META, NSEventModifierFlagCommand},
+ {MODIFIER_CAPSLOCK, NSEventModifierFlagCapsLock},
+ {MODIFIER_NUMLOCK, NSEventModifierFlagNumericPad}};
+
+ NSUInteger modifierFlags = 0;
+ for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
+ if (aKeyEvent.mModifiers & sModifierFlagMap[i][0]) {
+ modifierFlags |= sModifierFlagMap[i][1];
+ }
+ }
+
+ NSString* characters;
+ if (aKeyEvent.mCharCode) {
+ characters =
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(&(aKeyEvent.mCharCode))
+ length:1];
+ } else {
+ uint32_t cocoaCharCode = nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aKeyEvent.mKeyCode);
+ characters = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(&cocoaCharCode)
+ length:1];
+ }
+
+ return [NSEvent keyEventWithType:eventType
+ location:NSMakePoint(0, 0)
+ modifierFlags:modifierFlags
+ timestamp:0
+ windowNumber:aWindowNumber
+ context:aContext
+ characters:characters
+ charactersIgnoringModifiers:characters
+ isARepeat:NO
+ keyCode:0]; // Native key code not currently needed
+
+ 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();
+ aInputEvent.mTimeStamp = GetEventTimeStamp([aNativeEvent timestamp]);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// static
+Modifiers nsCocoaUtils::ModifiersForEvent(NSEvent* aNativeEvent) {
+ NSUInteger modifiers = aNativeEvent ? [aNativeEvent modifierFlags] : [NSEvent modifierFlags];
+ Modifiers result = 0;
+ if (modifiers & NSEventModifierFlagShift) {
+ result |= MODIFIER_SHIFT;
+ }
+ if (modifiers & NSEventModifierFlagControl) {
+ result |= MODIFIER_CONTROL;
+ }
+ if (modifiers & NSEventModifierFlagOption) {
+ 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 & NSEventModifierFlagCommand) {
+ result |= MODIFIER_META;
+ }
+
+ if (modifiers & NSEventModifierFlagCapsLock) {
+ 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 & NSEventModifierFlagNumericPad) {
+ result |= MODIFIER_NUMLOCK;
+ }
+
+ // Be aware, NSEventModifierFlagFunction 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 & NSEventModifierFlagCapsLock) {
+ carbonModifier |= alphaLock;
+ }
+ if (aCocoaModifier & NSEventModifierFlagControl) {
+ carbonModifier |= controlKey;
+ }
+ if (aCocoaModifier & NSEventModifierFlagOption) {
+ carbonModifier |= optionKey;
+ }
+ if (aCocoaModifier & NSEventModifierFlagShift) {
+ carbonModifier |= shiftKey;
+ }
+ if (aCocoaModifier & NSEventModifierFlagCommand) {
+ carbonModifier |= cmdKey;
+ }
+ if (aCocoaModifier & NSEventModifierFlagNumericPad) {
+ carbonModifier |= kEventKeyModifierNumLockMask;
+ }
+ if (aCocoaModifier & NSEventModifierFlagFunction) {
+ carbonModifier |= kEventKeyModifierFnMask;
+ }
+ return carbonModifier;
+}
+
+// While HiDPI support is not 100% complete and tested, we'll have a pref
+// to allow it to be turned off in case of problems (or for testing purposes).
+
+// gfx.hidpi.enabled is an integer with the meaning:
+// <= 0 : HiDPI support is disabled
+// 1 : HiDPI enabled provided all screens have the same backing resolution
+// > 1 : HiDPI enabled even if there are a mixture of screen modes
+
+// All the following code is to be removed once HiDPI work is more complete.
+
+static bool sHiDPIEnabled = false;
+static bool sHiDPIPrefInitialized = false;
+
+// static
+bool nsCocoaUtils::HiDPIEnabled() {
+ if (!sHiDPIPrefInitialized) {
+ sHiDPIPrefInitialized = true;
+
+ int prefSetting = Preferences::GetInt("gfx.hidpi.enabled", 1);
+ if (prefSetting <= 0) {
+ return false;
+ }
+
+ // prefSetting is at least 1, need to check attached screens...
+
+ int scaleFactors = 0; // used as a bitset to track the screen types found
+ NSEnumerator* screenEnum = [[NSScreen screens] objectEnumerator];
+ while (NSScreen* screen = [screenEnum nextObject]) {
+ NSDictionary* desc = [screen deviceDescription];
+ if ([desc objectForKey:NSDeviceIsScreen] == nil) {
+ continue;
+ }
+ CGFloat scale = [screen respondsToSelector:@selector(backingScaleFactor)]
+ ? [screen backingScaleFactor]
+ : 1.0;
+ // Currently, we only care about differentiating "1.0" and "2.0",
+ // so we set one of the two low bits to record which.
+ if (scale > 1.0) {
+ scaleFactors |= 2;
+ } else {
+ scaleFactors |= 1;
+ }
+ }
+
+ // Now scaleFactors will be:
+ // 0 if no screens (supporting backingScaleFactor) found
+ // 1 if only lo-DPI screens
+ // 2 if only hi-DPI screens
+ // 3 if both lo- and hi-DPI screens
+ // We'll enable HiDPI support if there's only a single screen type,
+ // OR if the pref setting is explicitly greater than 1.
+ sHiDPIEnabled = (scaleFactors <= 2) || (prefSetting > 1);
+ }
+
+ return sHiDPIEnabled;
+}
+
+void nsCocoaUtils::GetCommandsFromKeyEvent(NSEvent* aEvent,
+ nsTArray<KeyBindingsCommand>& aCommands) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_ASSERT(aEvent);
+
+ static NativeKeyBindingsRecorder* sNativeKeyBindingsRecorder;
+ if (!sNativeKeyBindingsRecorder) {
+ sNativeKeyBindingsRecorder = [NativeKeyBindingsRecorder new];
+ }
+
+ [sNativeKeyBindingsRecorder startRecording:aCommands];
+
+ // This will trigger 0 - N calls to doCommandBySelector: and insertText:
+ [sNativeKeyBindingsRecorder interpretKeyEvents:[NSArray arrayWithObject:aEvent]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@implementation NativeKeyBindingsRecorder
+
+- (void)startRecording:(nsTArray<KeyBindingsCommand>&)aCommands {
+ mCommands = &aCommands;
+ mCommands->Clear();
+}
+
+- (void)doCommandBySelector:(SEL)aSelector {
+ KeyBindingsCommand command = {aSelector, nil};
+
+ mCommands->AppendElement(command);
+}
+
+- (void)insertText:(id)aString {
+ KeyBindingsCommand command = {@selector(insertText:), aString};
+
+ mCommands->AppendElement(command);
+}
+
+@end // NativeKeyBindingsRecorder
+
+struct KeyConversionData {
+ const char* str;
+ size_t strLength;
+ uint32_t geckoKeyCode;
+ uint32_t charCode;
+};
+
+static const KeyConversionData gKeyConversions[] = {
+
+#define KEYCODE_ENTRY(aStr, aCode) \
+ { #aStr, sizeof(#aStr) - 1, NS_##aStr, aCode }
+
+// Some keycodes may have different name in KeyboardEvent 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;
+ LossyCopyUTF16toASCII(aKeyCodeName, keyCodeName);
+ // We want case-insensitive comparison with data stored as uppercase.
+ ToUpperCase(keyCodeName);
+
+ uint32_t keyCodeNameLength = keyCodeName.Length();
+ const char* keyCodeNameStr = keyCodeName.get();
+ for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) {
+ if (keyCodeNameLength == gKeyConversions[i].strLength &&
+ nsCRT::strcmp(gKeyConversions[i].str, keyCodeNameStr) == 0) {
+ return gKeyConversions[i].charCode;
+ }
+ }
+
+ return 0;
+}
+
+uint32_t nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(uint32_t aKeyCode) {
+ if (!aKeyCode) {
+ return 0;
+ }
+
+ for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) {
+ if (gKeyConversions[i].geckoKeyCode == aKeyCode) {
+ return gKeyConversions[i].charCode;
+ }
+ }
+
+ return 0;
+}
+
+NSMutableAttributedString* nsCocoaUtils::GetNSMutableAttributedString(
+ const nsAString& aText, const nsTArray<mozilla::FontRange>& aFontRanges, const bool aIsVertical,
+ const CGFloat aBackingScaleFactor) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL
+
+ NSString* nsstr = nsCocoaUtils::ToNSString(aText);
+ NSMutableAttributedString* attrStr =
+ [[[NSMutableAttributedString alloc] initWithString:nsstr attributes:nil] autorelease];
+
+ int32_t lastOffset = aText.Length();
+ for (auto i = aFontRanges.Length(); i > 0; --i) {
+ const FontRange& fontRange = aFontRanges[i - 1];
+ NSString* fontName = nsCocoaUtils::ToNSString(fontRange.mFontName);
+ CGFloat fontSize = fontRange.mFontSize / aBackingScaleFactor;
+ NSFont* font = [NSFont fontWithName:fontName size:fontSize];
+ if (!font) {
+ font = [NSFont systemFontOfSize:fontSize];
+ }
+
+ NSDictionary* attrs = @{NSFontAttributeName : font};
+ NSRange range = NSMakeRange(fontRange.mStartOffset, lastOffset - fontRange.mStartOffset);
+ [attrStr setAttributes:attrs range:range];
+ lastOffset = fontRange.mStartOffset;
+ }
+
+ if (aIsVertical) {
+ [attrStr addAttribute:NSVerticalGlyphFormAttributeName
+ value:[NSNumber numberWithInt:1]
+ range:NSMakeRange(0, [attrStr length])];
+ }
+
+ return attrStr;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL
+}
+
+TimeStamp nsCocoaUtils::GetEventTimeStamp(NSTimeInterval aEventTime) {
+ if (!aEventTime) {
+ // If the event is generated by a 3rd party application, its timestamp
+ // may be 0. In this case, just return current timestamp.
+ // XXX Should we cache last event time?
+ return TimeStamp::Now();
+ }
+ // The internal value of the macOS implementation of TimeStamp is based on
+ // mach_absolute_time(), which measures "ticks" since boot.
+ // Event timestamps are NSTimeIntervals (seconds) since boot. So the two time
+ // representations already have the same base; we only need to convert
+ // seconds into ticks.
+ int64_t tick = BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aEventTime * 1000.0);
+ return TimeStamp::FromSystemTime(tick);
+}
+
+static NSString* ActionOnDoubleClickSystemPref() {
+ NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
+ NSString* kAppleActionOnDoubleClickKey = @"AppleActionOnDoubleClick";
+ id value = [userDefaults objectForKey:kAppleActionOnDoubleClickKey];
+ if ([value isKindOfClass:[NSString class]]) {
+ return value;
+ }
+ return nil;
+}
+
+@interface NSWindow (NSWindowShouldZoomOnDoubleClick)
++ (BOOL)_shouldZoomOnDoubleClick; // present on 10.7 and above
+@end
+
+bool nsCocoaUtils::ShouldZoomOnTitlebarDoubleClick() {
+ if ([NSWindow respondsToSelector:@selector(_shouldZoomOnDoubleClick)]) {
+ return [NSWindow _shouldZoomOnDoubleClick];
+ }
+ return [ActionOnDoubleClickSystemPref() isEqualToString:@"Maximize"];
+}
+
+bool nsCocoaUtils::ShouldMinimizeOnTitlebarDoubleClick() {
+ // Check the system preferences.
+ // We could also check -[NSWindow _shouldMiniaturizeOnDoubleClick]. It's not clear to me which
+ // approach would be preferable; neither is public API.
+ return [ActionOnDoubleClickSystemPref() isEqualToString:@"Minimize"];
+}
+
+// AVAuthorizationStatus is not needed unless we are running on 10.14.
+// However, on pre-10.14 SDK's, AVAuthorizationStatus and its enum values
+// are both defined and prohibited from use by compile-time checks. We
+// define a copy of AVAuthorizationStatus to allow compilation on pre-10.14
+// SDK's. The enum values must match what is defined in the 10.14 SDK.
+// We use ASSERTS for 10.14 SDK builds to check the enum values match.
+enum GeckoAVAuthorizationStatus : NSInteger {
+ GeckoAVAuthorizationStatusNotDetermined = 0,
+ GeckoAVAuthorizationStatusRestricted = 1,
+ GeckoAVAuthorizationStatusDenied = 2,
+ GeckoAVAuthorizationStatusAuthorized = 3
+};
+
+#if !defined(MAC_OS_X_VERSION_10_14) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_14
+// Define authorizationStatusForMediaType: as returning
+// GeckoAVAuthorizationStatus instead of AVAuthorizationStatus to allow
+// compilation on pre-10.14 SDK's.
+@interface AVCaptureDevice (GeckoAVAuthorizationStatus)
++ (GeckoAVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType;
+@end
+
+@interface AVCaptureDevice (WithCompletionHandler)
++ (void)requestAccessForMediaType:(AVMediaType)mediaType
+ completionHandler:(void (^)(BOOL granted))handler;
+@end
+#endif
+
+static const char* AVMediaTypeToString(AVMediaType aType) {
+ if (aType == AVMediaTypeVideo) {
+ return "video";
+ }
+
+ if (aType == AVMediaTypeAudio) {
+ return "audio";
+ }
+
+ return "unexpected type";
+}
+
+static void LogAuthorizationStatus(AVMediaType aType, int aState) {
+ const char* stateString;
+
+ switch (aState) {
+ case GeckoAVAuthorizationStatusAuthorized:
+ stateString = "AVAuthorizationStatusAuthorized";
+ break;
+ case GeckoAVAuthorizationStatusDenied:
+ stateString = "AVAuthorizationStatusDenied";
+ break;
+ case GeckoAVAuthorizationStatusNotDetermined:
+ stateString = "AVAuthorizationStatusNotDetermined";
+ break;
+ case GeckoAVAuthorizationStatusRestricted:
+ stateString = "AVAuthorizationStatusRestricted";
+ break;
+ default:
+ stateString = "Invalid state";
+ }
+
+ LOG("%s authorization status: %s\n", AVMediaTypeToString(aType), stateString);
+}
+
+static nsresult GetPermissionState(AVMediaType aMediaType, uint16_t& aState) {
+ MOZ_ASSERT(aMediaType == AVMediaTypeVideo || aMediaType == AVMediaTypeAudio);
+
+ // Only attempt to check authorization status on 10.14+.
+ if (@available(macOS 10.14, *)) {
+ GeckoAVAuthorizationStatus authStatus = static_cast<GeckoAVAuthorizationStatus>(
+ [AVCaptureDevice authorizationStatusForMediaType:aMediaType]);
+ LogAuthorizationStatus(aMediaType, authStatus);
+
+ // Convert GeckoAVAuthorizationStatus to nsIOSPermissionRequest const
+ switch (authStatus) {
+ case GeckoAVAuthorizationStatusAuthorized:
+ aState = nsIOSPermissionRequest::PERMISSION_STATE_AUTHORIZED;
+ return NS_OK;
+ case GeckoAVAuthorizationStatusDenied:
+ aState = nsIOSPermissionRequest::PERMISSION_STATE_DENIED;
+ return NS_OK;
+ case GeckoAVAuthorizationStatusNotDetermined:
+ aState = nsIOSPermissionRequest::PERMISSION_STATE_NOTDETERMINED;
+ return NS_OK;
+ case GeckoAVAuthorizationStatusRestricted:
+ aState = nsIOSPermissionRequest::PERMISSION_STATE_RESTRICTED;
+ return NS_OK;
+ default:
+ MOZ_ASSERT(false, "Invalid authorization status");
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsCocoaUtils::GetVideoCapturePermissionState(uint16_t& aPermissionState) {
+ return GetPermissionState(AVMediaTypeVideo, aPermissionState);
+}
+
+nsresult nsCocoaUtils::GetAudioCapturePermissionState(uint16_t& aPermissionState) {
+ return GetPermissionState(AVMediaTypeAudio, aPermissionState);
+}
+
+// Set |aPermissionState| to PERMISSION_STATE_AUTHORIZED if this application
+// has already been granted permission to record the screen in macOS Security
+// and Privacy system settings. If we do not have permission (because the user
+// hasn't yet been asked yet or the user previously denied the prompt), use
+// PERMISSION_STATE_DENIED. Returns NS_ERROR_NOT_IMPLEMENTED on macOS 10.14
+// and earlier.
+nsresult nsCocoaUtils::GetScreenCapturePermissionState(uint16_t& aPermissionState) {
+ aPermissionState = nsIOSPermissionRequest::PERMISSION_STATE_NOTDETERMINED;
+
+ // Only attempt to check screen recording authorization status on 10.15+.
+ // On earlier macOS versions, screen recording is allowed by default.
+ if (@available(macOS 10.15, *)) {
+ if (!StaticPrefs::media_macos_screenrecording_oscheck_enabled()) {
+ aPermissionState = nsIOSPermissionRequest::PERMISSION_STATE_AUTHORIZED;
+ LOG("screen authorization status: authorized (test disabled via pref)");
+ return NS_OK;
+ }
+
+ // Unlike with camera and microphone capture, there is no support for
+ // checking the screen recording permission status. Instead, an application
+ // can use the presence of window names (which are privacy sensitive) in
+ // the window info list as an indication. The list only includes window
+ // names if the calling application has been authorized to record the
+ // screen. We use the window name, window level, and owning PID as
+ // heuristics to determine if we have screen recording permission.
+ AutoCFRelease<CFArrayRef> windowArray =
+ CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
+ if (!windowArray) {
+ LOG("GetScreenCapturePermissionState() ERROR: got NULL window info list");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ int32_t windowLevelDock = CGWindowLevelForKey(kCGDockWindowLevelKey);
+ int32_t windowLevelNormal = CGWindowLevelForKey(kCGNormalWindowLevelKey);
+ LOG("GetScreenCapturePermissionState(): DockWindowLevel: %d, "
+ "NormalWindowLevel: %d",
+ windowLevelDock, windowLevelNormal);
+
+ int32_t thisPid = [[NSProcessInfo processInfo] processIdentifier];
+
+ CFIndex windowCount = CFArrayGetCount(windowArray);
+ LOG("GetScreenCapturePermissionState() returned %ld windows", windowCount);
+ if (windowCount == 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ for (CFIndex i = 0; i < windowCount; i++) {
+ CFDictionaryRef windowDict =
+ reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(windowArray, i));
+
+ // Get the window owner's PID
+ int32_t windowOwnerPid = -1;
+ CFNumberRef windowPidRef =
+ reinterpret_cast<CFNumberRef>(CFDictionaryGetValue(windowDict, kCGWindowOwnerPID));
+ if (!windowPidRef || !CFNumberGetValue(windowPidRef, kCFNumberIntType, &windowOwnerPid)) {
+ LOG("GetScreenCapturePermissionState() ERROR: failed to get window owner");
+ continue;
+ }
+
+ // Our own window names are always readable and
+ // therefore not relevant to the heuristic.
+ if (thisPid == windowOwnerPid) {
+ continue;
+ }
+
+ CFStringRef windowName =
+ reinterpret_cast<CFStringRef>(CFDictionaryGetValue(windowDict, kCGWindowName));
+ if (!windowName) {
+ continue;
+ }
+
+ CFNumberRef windowLayerRef =
+ reinterpret_cast<CFNumberRef>(CFDictionaryGetValue(windowDict, kCGWindowLayer));
+ int32_t windowLayer;
+ if (!windowLayerRef || !CFNumberGetValue(windowLayerRef, kCFNumberIntType, &windowLayer)) {
+ LOG("GetScreenCapturePermissionState() ERROR: failed to get layer");
+ continue;
+ }
+
+ // If we have a window name and the window is in the dock or normal window
+ // level, and for another process, assume we have screen recording access.
+ LOG("GetScreenCapturePermissionState(): windowLayer: %d", windowLayer);
+ if (windowLayer == windowLevelDock || windowLayer == windowLevelNormal) {
+ aPermissionState = nsIOSPermissionRequest::PERMISSION_STATE_AUTHORIZED;
+ LOG("screen authorization status: authorized");
+ return NS_OK;
+ }
+ }
+
+ aPermissionState = nsIOSPermissionRequest::PERMISSION_STATE_DENIED;
+ LOG("screen authorization status: not authorized");
+ return NS_OK;
+ }
+
+ LOG("GetScreenCapturePermissionState(): nothing to do, not on 10.15+");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsCocoaUtils::RequestVideoCapturePermission(RefPtr<Promise>& aPromise) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return nsCocoaUtils::RequestCapturePermission(AVMediaTypeVideo, aPromise, sVideoCapturePromises,
+ VideoCompletionHandler);
+}
+
+nsresult nsCocoaUtils::RequestAudioCapturePermission(RefPtr<Promise>& aPromise) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return nsCocoaUtils::RequestCapturePermission(AVMediaTypeAudio, aPromise, sAudioCapturePromises,
+ AudioCompletionHandler);
+}
+
+//
+// Stores |aPromise| on |aPromiseList| and starts an asynchronous media
+// capture request for the given media type |aType|. If we are already
+// waiting for a capture request for this media type, don't start a new
+// request. |aHandler| is invoked on an arbitrary dispatch queue when the
+// request completes and must resolve any waiting Promises on the main
+// thread.
+//
+nsresult nsCocoaUtils::RequestCapturePermission(AVMediaType aType, RefPtr<Promise>& aPromise,
+ PromiseArray& aPromiseList,
+ void (^aHandler)(BOOL granted)) {
+ MOZ_ASSERT(aType == AVMediaTypeVideo || aType == AVMediaTypeAudio);
+#if defined(MAC_OS_X_VERSION_10_14)
+ // Ensure our enum constants match. We can only do this when
+ // compiling on 10.14+ because AVAuthorizationStatus is
+ // prohibited by preprocessor checks on earlier OS versions.
+ if (@available(macOS 10.14, *)) {
+ static_assert(
+ (int)GeckoAVAuthorizationStatusNotDetermined == (int)AVAuthorizationStatusNotDetermined,
+ "GeckoAVAuthorizationStatusNotDetermined does not match");
+ static_assert((int)GeckoAVAuthorizationStatusRestricted == (int)AVAuthorizationStatusRestricted,
+ "GeckoAVAuthorizationStatusRestricted does not match");
+ static_assert((int)GeckoAVAuthorizationStatusDenied == (int)AVAuthorizationStatusDenied,
+ "GeckoAVAuthorizationStatusDenied does not match");
+ static_assert((int)GeckoAVAuthorizationStatusAuthorized == (int)AVAuthorizationStatusAuthorized,
+ "GeckoAVAuthorizationStatusAuthorized does not match");
+ }
+#endif
+ LOG("RequestCapturePermission(%s)", AVMediaTypeToString(aType));
+
+ // Only attempt to request authorization on 10.14+.
+ if (@available(macOS 10.14, *)) {
+ sMediaCaptureMutex.Lock();
+
+ // Initialize our list of promises on first invocation
+ if (aPromiseList == nullptr) {
+ aPromiseList = new nsTArray<RefPtr<Promise>>;
+ ClearOnShutdown(&aPromiseList);
+ }
+
+ aPromiseList->AppendElement(aPromise);
+ size_t nPromises = aPromiseList->Length();
+
+ sMediaCaptureMutex.Unlock();
+
+ LOG("RequestCapturePermission(%s): %ld promise(s) unresolved", AVMediaTypeToString(aType),
+ nPromises);
+
+ // If we had one or more more existing promises waiting to be resolved
+ // by the completion handler, we don't need to start another request.
+ if (nPromises > 1) {
+ return NS_OK;
+ }
+
+ // Start the request
+ [AVCaptureDevice requestAccessForMediaType:aType completionHandler:aHandler];
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//
+// Audio capture request completion handler. Called from an arbitrary
+// dispatch queue.
+//
+void (^nsCocoaUtils::AudioCompletionHandler)(BOOL) = ^void(BOOL granted) {
+ nsCocoaUtils::ResolveAudioCapturePromises(granted);
+};
+
+//
+// Video capture request completion handler. Called from an arbitrary
+// dispatch queue.
+//
+void (^nsCocoaUtils::VideoCompletionHandler)(BOOL) = ^void(BOOL granted) {
+ nsCocoaUtils::ResolveVideoCapturePromises(granted);
+};
+
+void nsCocoaUtils::ResolveMediaCapturePromises(bool aGranted, PromiseArray& aPromiseList) {
+ StaticMutexAutoLock lock(sMediaCaptureMutex);
+
+ // Remove each promise from the list and resolve it.
+ while (aPromiseList->Length() > 0) {
+ RefPtr<Promise> promise = aPromiseList->PopLastElement();
+
+ // Resolve on main thread
+ nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+ "ResolveMediaAccessPromise",
+ [aGranted, aPromise = std::move(promise)]() { aPromise->MaybeResolve(aGranted); }));
+ NS_DispatchToMainThread(runnable.forget());
+ }
+}
+
+void nsCocoaUtils::ResolveAudioCapturePromises(bool aGranted) {
+ // Resolve on main thread
+ nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction("ResolveAudioCapturePromise", [aGranted]() {
+ ResolveMediaCapturePromises(aGranted, sAudioCapturePromises);
+ }));
+ NS_DispatchToMainThread(runnable.forget());
+}
+
+//
+// Attempt to trigger a dialog requesting permission to record the screen.
+// Unlike with the camera and microphone, there is no API to request permission
+// to record the screen or to receive a callback when permission is explicitly
+// allowed or denied. Here we attempt to trigger the dialog by attempting to
+// capture a 1x1 pixel section of the screen. The permission dialog is not
+// guaranteed to be displayed because the user may have already been prompted
+// in which case macOS does not display the dialog again.
+//
+nsresult nsCocoaUtils::MaybeRequestScreenCapturePermission() {
+ LOG("MaybeRequestScreenCapturePermission()");
+ AutoCFRelease<CGImageRef> image =
+ CGDisplayCreateImageForRect(kCGDirectMainDisplay, CGRectMake(0, 0, 1, 1));
+ return NS_OK;
+}
+
+void nsCocoaUtils::ResolveVideoCapturePromises(bool aGranted) {
+ // Resolve on main thread
+ nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction("ResolveVideoCapturePromise", [aGranted]() {
+ ResolveMediaCapturePromises(aGranted, sVideoCapturePromises);
+ }));
+ NS_DispatchToMainThread(runnable.forget());
+}
diff --git a/widget/cocoa/nsCocoaWindow.h b/widget/cocoa/nsCocoaWindow.h
new file mode 100644
index 0000000000..f6af32ea76
--- /dev/null
+++ b/widget/cocoa/nsCocoaWindow.h
@@ -0,0 +1,422 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCocoaWindow_h_
+#define nsCocoaWindow_h_
+
+#undef DARWIN
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/RefPtr.h"
+#include "nsBaseWidget.h"
+#include "nsPIWidgetCocoa.h"
+#include "nsCocoaUtils.h"
+#include "nsTouchBar.h"
+#include <dlfcn.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;
+
+ // Invalidation disabling
+ BOOL mDisabledNeedsDisplay;
+
+ NSTrackingArea* mTrackingArea;
+
+ NSRect mDirtyRect;
+
+ BOOL mBeingShown;
+ BOOL mDrawTitle;
+ BOOL mBrightTitlebarForeground;
+ BOOL mUseMenuStyle;
+ BOOL mIsAnimationSuppressed;
+
+ nsTouchBar* mTouchBar;
+}
+
+- (void)importState:(NSDictionary*)aState;
+- (NSMutableDictionary*)exportState;
+- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState;
+- (BOOL)drawsContentsIntoWindowFrame;
+
+// These two methods are like contentRectForFrameRect and frameRectForContentRect,
+// but they deal with the rect of the window's "main ChildView" instead of the
+// rect of the window's content view. The two are sometimes sized differently: The
+// window's content view always covers the entire window, whereas the ChildView
+// only covers the full window when drawsContentsIntoWindowFrame is YES. When
+// drawsContentsIntoWindowFrame is NO, there's a titlebar-sized gap above the
+// ChildView within the content view.
+- (NSRect)childViewRectForFrameRect:(NSRect)aFrameRect;
+- (NSRect)frameRectForChildViewRect:(NSRect)aChildViewRect;
+
+- (void)mouseEntered:(NSEvent*)aEvent;
+- (void)mouseExited:(NSEvent*)aEvent;
+- (void)mouseMoved:(NSEvent*)aEvent;
+- (void)updateTrackingArea;
+- (NSView*)trackingAreaView;
+
+- (void)setBeingShown:(BOOL)aValue;
+- (BOOL)isBeingShown;
+- (BOOL)isVisibleOrBeingShown;
+
+- (void)setIsAnimationSuppressed:(BOOL)aValue;
+- (BOOL)isAnimationSuppressed;
+
+// Returns an autoreleased NSArray containing the NSViews that we consider the
+// "contents" of this window. All views in the returned array are subviews of
+// this window's content view. However, the array may not include all of the
+// content view's subviews; concretely, the ToolbarWindow implementation will
+// exclude its TitlebarGradientView from the array that is returned here.
+// In the vast majority of cases, the array will only have a single element:
+// this window's mainChildView.
+- (NSArray<NSView*>*)contentViewContents;
+
+- (ChildView*)mainChildView;
+
+- (void)setWantsTitleDrawn:(BOOL)aDrawTitle;
+- (BOOL)wantsTitleDrawn;
+
+- (void)setUseBrightTitlebarForeground:(BOOL)aBrightForeground;
+- (BOOL)useBrightTitlebarForeground;
+
+- (void)disableSetNeedsDisplay;
+- (void)enableSetNeedsDisplay;
+
+- (NSRect)getAndResetNativeDirtyRect;
+
+- (void)setUseMenuStyle:(BOOL)aValue;
+
+- (void)releaseJSObjects;
+
+@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;
+
+- (BOOL)bottomCornerRounded;
+
+// Present in the same form on OS X since at least OS X 10.5.
+- (NSRect)contentRectForFrameRect:(NSRect)windowFrame styleMask:(NSUInteger)windowStyle;
+- (NSRect)frameRectForContentRect:(NSRect)windowContentRect styleMask:(NSUInteger)windowStyle;
+
+// Present since at least OS X 10.5. The OS calls this method on NSWindow
+// (and its subclasses) to find out which NSFrameView subclass to instantiate
+// to create its "frame view".
++ (Class)frameViewClassForStyleMask:(NSUInteger)styleMask;
+
+@end
+
+@interface PopupWindow : BaseWindow {
+ @private
+ BOOL mIsContextMenu;
+}
+
+- (id)initWithContentRect:(NSRect)contentRect
+ styleMask:(NSUInteger)styleMask
+ backing:(NSBackingStoreType)bufferingType
+ defer:(BOOL)deferCreation;
+- (BOOL)isContextMenu;
+- (void)setIsContextMenu:(BOOL)flag;
+- (BOOL)canBecomeMainWindow;
+
+@end
+
+@interface BorderlessWindow : BaseWindow {
+}
+
+- (BOOL)canBecomeKeyWindow;
+- (BOOL)canBecomeMainWindow;
+
+@end
+
+@interface WindowDelegate : NSObject <NSWindowDelegate> {
+ nsCocoaWindow* mGeckoWindow; // [WEAK] (we are owned by the window)
+ // Used to avoid duplication when we send NS_ACTIVATE and
+ // NS_DEACTIVATE to Gecko for toplevel widgets. Starts out
+ // false.
+ bool mToplevelActiveState;
+ BOOL mHasEverBeenZoomed;
+}
++ (void)paintMenubarForWindow:(NSWindow*)aWindow;
+- (id)initWithGeckoWindow:(nsCocoaWindow*)geckoWind;
+- (void)windowDidResize:(NSNotification*)aNotification;
+- (nsCocoaWindow*)geckoWidget;
+- (bool)toplevelActiveState;
+- (void)sendToplevelActivateEvents;
+- (void)sendToplevelDeactivateEvents;
+@end
+
+@interface TitlebarGradientView : NSView
+@end
+
+// NSWindow subclass for handling windows with toolbars.
+@interface ToolbarWindow : BaseWindow {
+ // This window's titlebar gradient view, if present.
+ // Will be nil if drawsContentsIntoWindowFrame is YES.
+ // This view is a subview of the window's content view and gets created and
+ // destroyed by updateTitlebarGradientViewPresence.
+ TitlebarGradientView* mTitlebarGradientView; // [STRONG]
+
+ CGFloat mUnifiedToolbarHeight;
+ CGFloat mSheetAttachmentPosition;
+ NSRect mWindowButtonsRect;
+ NSRect mFullScreenButtonRect;
+}
+- (void)setUnifiedToolbarHeight:(CGFloat)aHeight;
+- (CGFloat)unifiedToolbarHeight;
+- (CGFloat)titlebarHeight;
+- (NSRect)titlebarRect;
+- (void)setTitlebarNeedsDisplay;
+- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState;
+- (void)setSheetAttachmentPosition:(CGFloat)aY;
+- (CGFloat)sheetAttachmentPosition;
+- (void)placeWindowButtons:(NSRect)aRect;
+- (void)placeFullScreenButton:(NSRect)aRect;
+- (NSPoint)windowButtonsPositionWithDefaultPosition:(NSPoint)aDefaultPosition;
+- (NSPoint)fullScreenButtonPositionWithDefaultPosition:(NSPoint)aDefaultPosition;
+- (void)windowMainStateChanged;
+@end
+
+class nsCocoaWindow final : public nsBaseWidget, public nsPIWidgetCocoa {
+ private:
+ typedef nsBaseWidget Inherited;
+
+ public:
+ nsCocoaWindow();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSPIWIDGETCOCOA; // semicolon for clang-format bug 1629756
+
+ [[nodiscard]] virtual nsresult Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const DesktopIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr) override;
+
+ [[nodiscard]] virtual nsresult Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr) override;
+
+ virtual void Destroy() override;
+
+ virtual void Show(bool aState) override;
+ virtual bool NeedsRecreateToReshow() override;
+
+ virtual nsIWidget* GetSheetWindowParent(void) override;
+ virtual void 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;
+ virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) 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;
+ virtual void Move(double aX, double aY) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ virtual void GetWorkspaceID(nsAString& workspaceID) override;
+ virtual void MoveToWorkspace(const nsAString& workspaceID) override;
+ virtual void SuppressAnimation(bool aSuppress) override;
+ virtual void HideWindowChrome(bool aShouldHide) override;
+
+ void WillEnterFullScreen(bool aFullScreen);
+ 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;
+ nsresult MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen = nullptr) final;
+ nsresult MakeFullScreenWithNativeTransition(bool aFullScreen,
+ nsIScreen* aTargetScreen = nullptr) final;
+ NSAnimation* FullscreenTransitionAnimation() const { return mFullscreenTransitionAnimation; }
+ void ReleaseFullscreenTransitionAnimation() {
+ MOZ_ASSERT(mFullscreenTransitionAnimation, "Should only be called when there is animation");
+ [mFullscreenTransitionAnimation release];
+ mFullscreenTransitionAnimation = nil;
+ }
+
+ virtual void Resize(double aWidth, double aHeight, bool aRepaint) override;
+ virtual void Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) override;
+ NSRect GetClientCocoaRect();
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ void ReportMoveEvent();
+ void ReportSizeEvent();
+ virtual void SetCursor(nsCursor aDefaultCursor, imgIContainer* aCursorImage, 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());
+ }
+
+ virtual nsresult SetTitle(const nsAString& aTitle) override;
+
+ virtual void Invalidate(const LayoutDeviceIntRect& aRect) override;
+ virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
+ virtual LayerManager* GetLayerManager(
+ PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
+ virtual nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent, nsEventStatus& aStatus) override;
+ virtual void CaptureRollupEvents(nsIRollupListener* aListener, bool aDoCapture) override;
+ [[nodiscard]] virtual nsresult GetAttention(int32_t aCycleCount) override;
+ virtual bool HasPendingInputEvent() override;
+ virtual nsTransparencyMode GetTransparencyMode() override;
+ virtual void SetTransparencyMode(nsTransparencyMode aMode) override;
+ virtual void SetWindowShadowStyle(mozilla::StyleWindowShadow aStyle) override;
+ virtual void SetWindowOpacity(float aOpacity) override;
+ virtual void SetWindowTransform(const mozilla::gfx::Matrix& aTransform) override;
+ virtual void SetWindowMouseTransparent(bool aIsTransparent) override;
+ virtual void SetShowsToolbarButton(bool aShow) override;
+ virtual void SetSupportsNativeFullscreen(bool aShow) override;
+ virtual void SetWindowAnimationType(WindowAnimationType aType) override;
+ virtual void SetDrawsTitle(bool aDrawTitle) override;
+ virtual void SetUseBrightTitlebarForeground(bool aBrightForeground) override;
+ virtual nsresult SetNonClientMargins(LayoutDeviceIntMargin& aMargins) override;
+ virtual void SetDrawsInTitlebar(bool aState) override;
+ virtual void UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) override;
+ virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) override;
+ virtual nsresult SynthesizeNativeMouseScrollEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage, double aDeltaX,
+ double aDeltaY, double aDeltaZ,
+ uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags,
+ nsIObserver* aObserver) override;
+ virtual void LockAspectRatio(bool aShouldLock) override;
+
+ void DispatchSizeModeEvent();
+ void DispatchOcclusionEvent();
+
+ // 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();
+
+ virtual void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ virtual InputContext GetInputContext() override { return mInputContext; }
+ virtual bool GetEditCommands(NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ nsTArray<mozilla::CommandInt>& aCommands) override;
+
+ void SetPopupWindowLevel();
+
+ bool InFullScreenMode() const { return mInFullScreenMode; }
+
+ void PauseCompositor();
+ void ResumeCompositor();
+
+ 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();
+ int32_t GetWorkspaceID();
+
+ void DoResize(double aX, double aY, double aWidth, double aHeight, bool aRepaint,
+ bool aConstrainToCurrentScreen);
+
+ inline bool ShouldToggleNativeFullscreen(bool aFullScreen, bool aUseSystemTransition);
+ void UpdateFullscreenState(bool aFullScreen, bool aNativeMode);
+ nsresult DoMakeFullScreen(bool aFullScreen, bool aUseSystemTransition);
+
+ virtual already_AddRefed<nsIWidget> AllocateChildPopupWidget() override {
+ return nsIWidget::CreateTopLevelWindow();
+ }
+
+ nsIWidget* mParent; // if we're a popup, this is our parent [WEAK]
+ nsIWidget* mAncestorLink; // link to traverse ancestors [WEAK]
+ BaseWindow* mWindow; // our cocoa window [STRONG]
+ WindowDelegate* mDelegate; // our delegate for processing window msgs [STRONG]
+ RefPtr<nsMenuBarX> mMenuBar;
+ NSWindow* mSheetWindowParent; // if this is a sheet, this is the NSWindow it's attached to
+ nsChildView* mPopupContentView; // if this is a popup, this is its content widget
+ // if this is a toplevel window, and there is any ongoing fullscreen
+ // transition, it is the animation object.
+ NSAnimation* mFullscreenTransitionAnimation;
+ mozilla::StyleWindowShadow mShadowStyle;
+
+ CGFloat mBackingScaleFactor;
+ CGFloat mAspectRatio;
+
+ 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;
+
+ // 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 mWindowTransformIsIdentity;
+ bool mAlwaysOnTop;
+ bool mAspectRatioLocked;
+
+ int32_t mNumModalDescendents;
+ InputContext mInputContext;
+ NSWindowAnimationBehavior mWindowAnimationBehavior;
+
+ private:
+ // true if Show() has been called.
+ bool mWasShown;
+};
+
+#endif // nsCocoaWindow_h_
diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
new file mode 100644
index 0000000000..b7a19f82ab
--- /dev/null
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -0,0 +1,3994 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "ScreenHelperCocoa.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 "nsIAppWindow.h"
+#include "nsToolkit.h"
+#include "nsTouchBarNativeAPIDefines.h"
+#include "nsPIDOMWindow.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 "VibrancyManager.h"
+#include "nsPresContext.h"
+#include "nsDocShell.h"
+
+#include "gfxPlatform.h"
+#include "qcms.h"
+
+#include "mozilla/AutoRestore.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace layers {
+class LayerManager;
+} // namespace layers
+} // namespace mozilla
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+using namespace mozilla;
+
+int32_t gXULModalLevel = 0;
+
+// In principle there should be only one app-modal window at any given time.
+// But sometimes, despite our best efforts, another window appears above the
+// current app-modal window. So we need to keep a linked list of app-modal
+// windows. (A non-sheet window that appears above an app-modal window is
+// also made app-modal.) See nsCocoaWindow::SetModal().
+nsCocoaWindowList* gGeckoAppModalWindowList = NULL;
+
+BOOL sTouchBarIsInitialized = NO;
+
+// 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_9) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9
+
+enum NSWindowOcclusionState { NSWindowOcclusionStateVisible = 0x1 << 1 };
+
+@interface NSWindow (OcclusionState)
+- (NSWindowOcclusionState)occlusionState;
+@end
+
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_10) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10
+
+enum NSWindowTitleVisibility { NSWindowTitleVisible = 0, NSWindowTitleHidden = 1 };
+
+@interface NSWindow (TitleVisibility)
+- (void)setTitleVisibility:(NSWindowTitleVisibility)visibility;
+- (void)setTitlebarAppearsTransparent:(BOOL)isTitlebarTransparent;
+@end
+
+#endif
+
+#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 NSUInteger CGSSpaceID;
+typedef NSInteger CGSWindow;
+typedef NSUInteger CGSWindowFilterRef;
+typedef enum {
+ kCGSSpaceIncludesCurrent = 1 << 0,
+ kCGSSpaceIncludesOthers = 1 << 1,
+ kCGSSpaceIncludesUser = 1 << 2,
+
+ kCGSAllSpacesMask = kCGSSpaceIncludesCurrent | kCGSSpaceIncludesOthers | kCGSSpaceIncludesUser
+} CGSSpaceMask;
+static NSString* const CGSSpaceIDKey = @"ManagedSpaceID";
+static NSString* const CGSSpacesKey = @"Spaces";
+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);
+extern CGError CGSSetWindowTransform(CGSConnection cid, CGSWindow wid, CGAffineTransform transform);
+}
+
+#define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
+
+NS_IMPL_ISUPPORTS_INHERITED(nsCocoaWindow, Inherited, nsPIWidgetCocoa)
+
+// A note on testing to see if your object is a sheet...
+// |mWindowType == eWindowType_sheet| is true if your gecko nsIWidget is a sheet
+// widget - whether or not the sheet is showing. |[mWindow isSheet]| will return
+// true *only when the sheet is actually showing*. Choose your test wisely.
+
+static void RollUpPopups() {
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ NS_ENSURE_TRUE_VOID(rollupListener);
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (!rollupWidget) return;
+ rollupListener->Rollup(0, true, nullptr, nullptr);
+}
+
+nsCocoaWindow::nsCocoaWindow()
+ : mParent(nullptr),
+ mAncestorLink(nullptr),
+ mWindow(nil),
+ mDelegate(nil),
+ mSheetWindowParent(nil),
+ mPopupContentView(nil),
+ mFullscreenTransitionAnimation(nil),
+ mShadowStyle(StyleWindowShadow::Default),
+ mBackingScaleFactor(0.0),
+ mAnimationType(nsIWidget::eGenericWindowAnimation),
+ mWindowMadeHere(false),
+ mSheetNeedsShow(false),
+ mInFullScreenMode(false),
+ mInFullScreenTransition(false),
+ mModal(false),
+ mFakeModal(false),
+ mInNativeFullScreenMode(false),
+ mIsAnimationSuppressed(false),
+ mInReportMoveEvent(false),
+ mInResize(false),
+ mWindowTransformIsIdentity(true),
+ mAlwaysOnTop(false),
+ mAspectRatioLocked(false),
+ mNumModalDescendents(0),
+ mWindowAnimationBehavior(NSWindowAnimationBehaviorDefault),
+ mWasShown(false) {
+ 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;
+
+ [mWindow releaseJSObjects];
+ // We want to unhook the delegate here because we don't want events
+ // sent to it after this object has been destroyed.
+ [mWindow setDelegate:nil];
+ [mWindow close];
+ mWindow = nil;
+ [mDelegate autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsCocoaWindow::~nsCocoaWindow() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Notify the children that we're gone. Popup windows (e.g. tooltips) can
+ // have nsChildView children. 'kid' is an nsChildView object if and only if
+ // its 'type' is 'eWindowType_child'.
+ // childView->ResetParent() can change our list of children while it's
+ // being iterated, so the way we iterate the list must allow for this.
+ for (nsIWidget* kid = mLastChild; kid;) {
+ nsWindowType kidType = kid->WindowType();
+ if (kidType == eWindowType_child) {
+ nsChildView* childView = static_cast<nsChildView*>(kid);
+ kid = kid->GetPrevSibling();
+ childView->ResetParent();
+ } else {
+ nsCocoaWindow* childWindow = static_cast<nsCocoaWindow*>(kid);
+ childWindow->mParent = nullptr;
+ childWindow->mAncestorLink = mAncestorLink;
+ kid = kid->GetPrevSibling();
+ }
+ }
+
+ if (mWindow && mWindowMadeHere) {
+ DestroyNativeWindow();
+ }
+
+ NS_IF_RELEASE(mPopupContentView);
+
+ // Deal with the possiblity that we're being destroyed while running modal.
+ if (mModal) {
+ NS_WARNING("Widget destroyed while running modal!");
+ --gXULModalLevel;
+ NS_ASSERTION(gXULModalLevel >= 0, "Weirdness setting modality!");
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Find the screen that overlaps aRect the most,
+// if none are found default to the mainScreen.
+static NSScreen* FindTargetScreenForRect(const DesktopIntRect& aRect) {
+ NSScreen* targetScreen = [NSScreen mainScreen];
+ NSEnumerator* screenEnum = [[NSScreen screens] objectEnumerator];
+ int largestIntersectArea = 0;
+ while (NSScreen* screen = [screenEnum nextObject]) {
+ DesktopIntRect screenRect = nsCocoaUtils::CocoaRectToGeckoRect([screen visibleFrame]);
+ screenRect = screenRect.Intersect(aRect);
+ int area = screenRect.width * screenRect.height;
+ if (area > largestIntersectArea) {
+ largestIntersectArea = area;
+ targetScreen = screen;
+ }
+ }
+ return targetScreen;
+}
+
+// fits the rect to the screen that contains the largest area of it,
+// or to aScreen if a screen is passed in
+// NB: this operates with aRect in desktop pixels
+static void FitRectToVisibleAreaForScreen(DesktopIntRect& aRect, NSScreen* aScreen) {
+ if (!aScreen) {
+ aScreen = FindTargetScreenForRect(aRect);
+ }
+
+ DesktopIntRect screenBounds = nsCocoaUtils::CocoaRectToGeckoRect([aScreen visibleFrame]);
+
+ if (aRect.width > screenBounds.width) {
+ aRect.width = screenBounds.width;
+ }
+ if (aRect.height > screenBounds.height) {
+ aRect.height = screenBounds.height;
+ }
+
+ if (aRect.x - screenBounds.x + aRect.width > screenBounds.width) {
+ aRect.x += screenBounds.width - (aRect.x - screenBounds.x + aRect.width);
+ }
+ if (aRect.y - screenBounds.y + aRect.height > screenBounds.height) {
+ aRect.y += screenBounds.height - (aRect.y - screenBounds.y + aRect.height);
+ }
+
+ // If the left/top edge of the window is off the screen in either direction,
+ // then set the window to start at the left/top edge of the screen.
+ if (aRect.x < screenBounds.x || aRect.x > (screenBounds.x + screenBounds.width)) {
+ aRect.x = screenBounds.x;
+ }
+ if (aRect.y < screenBounds.y || aRect.y > (screenBounds.y + screenBounds.height)) {
+ aRect.y = screenBounds.y;
+ }
+}
+
+// Some applications use native popup windows
+// (native context menus, native tooltips)
+static bool UseNativePopupWindows() {
+#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
+ return true;
+#else
+ return false;
+#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
+}
+
+// aRect here is specified in desktop pixels
+nsresult nsCocoaWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const DesktopIntRect& aRect, nsWidgetInitData* aInitData) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Because the hidden window is created outside of an event loop,
+ // we have to provide an autorelease pool (see bug 559075).
+ nsAutoreleasePool localPool;
+
+ DesktopIntRect newBounds = aRect;
+ FitRectToVisibleAreaForScreen(newBounds, nullptr);
+
+ // Set defaults which can be overriden from aInitData in BaseCreate
+ mWindowType = eWindowType_toplevel;
+ mBorderStyle = eBorderStyle_default;
+
+ // Ensure that the toolkit is created.
+ nsToolkit::GetToolkit();
+
+ Inherited::BaseCreate(aParent, aInitData);
+
+ mParent = aParent;
+ mAncestorLink = aParent;
+ mAlwaysOnTop = aInitData->mAlwaysOnTop;
+
+ // 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) {
+ SetWindowMouseTransparent(aInitData->mMouseTransparent);
+
+ // 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 NSWindowStyleMaskTitled if it includes any of the others [besides
+ * NSWindowStyleMaskBorderless]". This implies that a borderless window
+ * shouldn't have any other styles than NSWindowStyleMaskBorderless.
+ */
+ if (!allOrDefault && !(aBorderStyle & eBorderStyle_title)) return NSWindowStyleMaskBorderless;
+
+ unsigned int mask = NSWindowStyleMaskTitled;
+ if (allOrDefault || aBorderStyle & eBorderStyle_close) mask |= NSWindowStyleMaskClosable;
+ if (allOrDefault || aBorderStyle & eBorderStyle_minimize) mask |= NSWindowStyleMaskMiniaturizable;
+ if (allOrDefault || aBorderStyle & eBorderStyle_resizeh) mask |= NSWindowStyleMaskResizable;
+
+ 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 NSWindowStyleMaskBorderless, add features if needed.
+ unsigned int features = NSWindowStyleMaskBorderless;
+
+ // 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 |= NSWindowStyleMaskTitled;
+ if (aBorderStyle & eBorderStyle_close) {
+ features |= NSWindowStyleMaskClosable;
+ }
+ }
+ break;
+ case eWindowType_toplevel:
+ case eWindowType_dialog:
+ features = WindowMaskForBorderStyle(aBorderStyle);
+ break;
+ case eWindowType_sheet:
+ if (mParent->WindowType() != eWindowType_invisible && aBorderStyle & eBorderStyle_resizeh) {
+ features = NSWindowStyleMaskResizable;
+ } else {
+ features = NSWindowStyleMaskMiniaturizable;
+ }
+ features |= NSWindowStyleMaskTitled;
+ 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 & NSWindowStyleMaskTitled))
+ 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 == NSWindowStyleMaskBorderless)
+ 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 frameRectForChildViewRect: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];
+
+ // When multiple spaces are in use and the browser is assigned to a
+ // particular space, override the "Assign To" space and display popups on
+ // the active space. Does not work with multiple displays. See
+ // NeedsRecreateToReshow() for multi-display with multi-space workaround.
+ if (!mAlwaysOnTop) {
+ NSWindowCollectionBehavior behavior = [mWindow collectionBehavior];
+ behavior |= NSWindowCollectionBehaviorMoveToActiveSpace;
+ [mWindow setCollectionBehavior:behavior];
+ }
+ } else {
+ // Non-popup windows are always opaque.
+ [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 the window use CoreAnimation from the start, so that we don't
+ // switch from a non-CA window to a CA-window in the middle.
+ [[mWindow contentView] setWantsLayer:YES];
+
+ // 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;
+}
+
+nsresult nsCocoaWindow::CreatePopupContentView(const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // We need to make our content view a ChildView.
+ mPopupContentView = new nsChildView();
+ if (!mPopupContentView) return NS_ERROR_FAILURE;
+
+ NS_ADDREF(mPopupContentView);
+
+ nsIWidget* thisAsWidget = static_cast<nsIWidget*>(this);
+ nsresult rv = mPopupContentView->Create(thisAsWidget, nullptr, aRect, aInitData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ NSView* contentView = [mWindow contentView];
+ ChildView* childView = (ChildView*)mPopupContentView->GetNativeData(NS_NATIVE_WIDGET);
+ [childView setFrame:[contentView bounds]];
+ [childView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ [contentView addSubview:childView];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsCocoaWindow::Destroy() {
+ if (mOnDestroyCalled) return;
+ mOnDestroyCalled = true;
+
+ // SetFakeModal(true) is called for non-modal window opened by modal window.
+ // On Cocoa, it needs corresponding SetFakeModal(false) on destroy to restore
+ // ancestor windows' state.
+ if (mFakeModal) {
+ SetFakeModal(false);
+ }
+
+ // If we don't hide here we run into problems with panels, this is not ideal.
+ // (Bug 891424)
+ Show(false);
+
+ if (mPopupContentView) mPopupContentView->Destroy();
+
+ if (mFullscreenTransitionAnimation) {
+ [mFullscreenTransitionAnimation stopAnimation];
+ ReleaseFullscreenTransitionAnimation();
+ }
+
+ nsBaseWidget::Destroy();
+ // nsBaseWidget::Destroy() calls GetParent()->RemoveChild(this). But we
+ // don't implement GetParent(), so we need to do the equivalent here.
+ if (mParent) {
+ mParent->RemoveChild(this);
+ }
+ nsBaseWidget::OnDestroy();
+
+ if (mInFullScreenMode) {
+ // On Lion we don't have to mess with the OS chrome when in Full Screen
+ // mode. But we do have to destroy the native window here (and not wait
+ // for that to happen in our destructor). We don't switch away from the
+ // native window's space until the window is destroyed, and otherwise this
+ // might not happen for several seconds (because at least one object
+ // holding a reference to ourselves is usually waiting to be garbage-
+ // collected). See bug 757618.
+ if (mInNativeFullScreenMode) {
+ DestroyNativeWindow();
+ } else if (mWindow) {
+ nsCocoaUtils::HideOSChromeOnScreen(false);
+ }
+ }
+}
+
+nsIWidget* nsCocoaWindow::GetSheetWindowParent(void) {
+ if (mWindowType != eWindowType_sheet) return nullptr;
+ nsCocoaWindow* parent = static_cast<nsCocoaWindow*>(mParent);
+ while (parent && (parent->mWindowType == eWindowType_sheet))
+ parent = static_cast<nsCocoaWindow*>(parent->mParent);
+ return parent;
+}
+
+void* nsCocoaWindow::GetNativeData(uint32_t aDataType) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL;
+
+ void* retVal = nullptr;
+
+ switch (aDataType) {
+ // to emulate how windows works, we always have to return a NSView
+ // for NS_NATIVE_WIDGET
+ case NS_NATIVE_WIDGET:
+ case NS_NATIVE_DISPLAY:
+ retVal = [mWindow contentView];
+ break;
+
+ case NS_NATIVE_WINDOW:
+ retVal = mWindow;
+ break;
+
+ case NS_NATIVE_GRAPHIC:
+ // There isn't anything that makes sense to return here,
+ // and it doesn't matter so just return nullptr.
+ NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a top-level window!");
+ break;
+ case NS_RAW_NATIVE_IME_CONTEXT: {
+ retVal = GetPseudoIMEContext();
+ if (retVal) {
+ break;
+ }
+ NSView* view = mWindow ? [mWindow contentView] : nil;
+ if (view) {
+ retVal = [view inputContext];
+ }
+ // If inputContext isn't available on this window, return this window's
+ // pointer instead of nullptr since if this returns nullptr,
+ // IMEStateManager cannot manage composition with TextComposition
+ // instance. Although, this case shouldn't occur.
+ if (NS_WARN_IF(!retVal)) {
+ retVal = this;
+ }
+ break;
+ }
+ }
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
+
+bool nsCocoaWindow::IsVisible() const {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return (mWindow && ([mWindow isVisibleOrBeingShown] || mSheetNeedsShow));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+void nsCocoaWindow::SetModal(bool aState) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow) return;
+
+ // This is used during startup (outside the event loop) when creating
+ // the add-ons compatibility checking dialog and the profile manager UI;
+ // therefore, it needs to provide an autorelease pool to avoid cocoa
+ // objects leaking.
+ nsAutoreleasePool localPool;
+
+ mModal = aState;
+ nsCocoaWindow* ancestor = static_cast<nsCocoaWindow*>(mAncestorLink);
+ if (aState) {
+ ++gXULModalLevel;
+ // When a non-sheet window gets "set modal", make the window(s) that it
+ // appears over behave as they should. We can't rely on native methods to
+ // do this, for the following reason: The OS runs modal non-sheet windows
+ // in an event loop (using [NSApplication runModalForWindow:] or similar
+ // methods) that's incompatible with the modal event loop in AppWindow::
+ // ShowModal() (each of these event loops is "exclusive", and can't run at
+ // the same time as other (similar) event loops).
+ if (mWindowType != eWindowType_sheet) {
+ while (ancestor) {
+ if (ancestor->mNumModalDescendents++ == 0) {
+ NSWindow* aWindow = ancestor->GetCocoaWindow();
+ if (ancestor->mWindowType != eWindowType_invisible) {
+ [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:NO];
+ [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:NO];
+ [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:NO];
+ }
+ }
+ ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent);
+ }
+ [mWindow setLevel:NSModalPanelWindowLevel];
+ nsCocoaWindowList* windowList = new nsCocoaWindowList;
+ if (windowList) {
+ windowList->window = this; // Don't ADDREF
+ windowList->prev = gGeckoAppModalWindowList;
+ gGeckoAppModalWindowList = windowList;
+ }
+ }
+ } else {
+ --gXULModalLevel;
+ NS_ASSERTION(gXULModalLevel >= 0, "Mismatched call to nsCocoaWindow::SetModal(false)!");
+ if (mWindowType != eWindowType_sheet) {
+ while (ancestor) {
+ if (--ancestor->mNumModalDescendents == 0) {
+ NSWindow* aWindow = ancestor->GetCocoaWindow();
+ if (ancestor->mWindowType != eWindowType_invisible) {
+ [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:YES];
+ [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:YES];
+ [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:YES];
+ }
+ }
+ NS_ASSERTION(ancestor->mNumModalDescendents >= 0, "Widget hierarchy changed while modal!");
+ ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent);
+ }
+ if (gGeckoAppModalWindowList) {
+ NS_ASSERTION(gGeckoAppModalWindowList->window == this,
+ "Widget hierarchy changed while modal!");
+ nsCocoaWindowList* saved = gGeckoAppModalWindowList;
+ gGeckoAppModalWindowList = gGeckoAppModalWindowList->prev;
+ delete saved; // "window" not ADDREFed
+ }
+ if (mWindowType == eWindowType_popup)
+ SetPopupWindowLevel();
+ else
+ [mWindow setLevel:NSNormalWindowLevel];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetFakeModal(bool aState) {
+ mFakeModal = aState;
+ SetModal(aState);
+}
+
+bool nsCocoaWindow::IsRunningAppModal() { return [NSApp _isRunningAppModal]; }
+
+// Hide or show this window
+void nsCocoaWindow::Show(bool bState) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow) return;
+
+ // We need to re-execute sometimes in order to bring already-visible
+ // windows forward.
+ if (!mSheetNeedsShow && !bState && ![mWindow isVisible]) return;
+
+ // Protect against re-entering.
+ if (bState && [mWindow isBeingShown]) return;
+
+ [mWindow setBeingShown:bState];
+ if (bState && !mWasShown) {
+ mWasShown = true;
+ }
+
+ nsIWidget* parentWidget = mParent;
+ nsCOMPtr<nsPIWidgetCocoa> piParentWidget(do_QueryInterface(parentWidget));
+ NSWindow* nativeParentWindow =
+ (parentWidget) ? (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW) : nil;
+
+ if (bState && !mBounds.IsEmpty()) {
+ // Don't try to show a popup when the parent isn't visible or is minimized.
+ if (mWindowType == eWindowType_popup && nativeParentWindow) {
+ if (![nativeParentWindow isVisible] || [nativeParentWindow isMiniaturized]) {
+ return;
+ }
+ }
+
+ 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;
+
+ NSWindow* topNonSheetWindow = nativeParentWindow;
+
+ // If this sheet is the child of another sheet, hide the parent so that
+ // this sheet can be displayed. Leave the parent mSheetNeedsShow alone,
+ // that is only used to handle sibling sheet contention. The parent will
+ // return once there are no more child sheets.
+ bool parentIsSheet = false;
+ if (NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) && parentIsSheet) {
+ piParentWidget->GetSheetWindowParent(&topNonSheetWindow);
+ [NSApp endSheet:nativeParentWindow];
+ }
+
+ nsCOMPtr<nsIWidget> sheetShown;
+ if (NS_SUCCEEDED(piParentWidget->GetChildSheet(true, getter_AddRefs(sheetShown))) &&
+ (!sheetShown || sheetShown == this)) {
+ // If this sheet is already the sheet actually being shown, don't
+ // tell it to show again. Otherwise the number of calls to
+ // [NSApp beginSheet...] won't match up with [NSApp endSheet...].
+ if (![mWindow isVisible]) {
+ mSheetNeedsShow = false;
+ mSheetWindowParent = topNonSheetWindow;
+ // Only set contextInfo if our parent isn't a sheet.
+ NSWindow* contextInfo = parentIsSheet ? nil : mSheetWindowParent;
+ [TopLevelWindowData deactivateInWindow:mSheetWindowParent];
+ [NSApp beginSheet:mWindow
+ modalForWindow:mSheetWindowParent
+ modalDelegate:mDelegate
+ didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
+ contextInfo:contextInfo];
+ [TopLevelWindowData activateInWindow:mWindow];
+ SendSetZLevelEvent();
+ }
+ } else {
+ // A sibling of this sheet is active, don't show this sheet yet.
+ // When the active sheet hides, its brothers and sisters that have
+ // mSheetNeedsShow set will have their opportunities to display.
+ mSheetNeedsShow = true;
+ }
+ } else if (mWindowType == eWindowType_popup) {
+ // 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:
+ MOZ_ASSERT_UNREACHABLE("unexpected mAnimationType value");
+ // fall through
+ case nsIWidget::eGenericWindowAnimation:
+ behavior = NSWindowAnimationBehaviorDefault;
+ break;
+ }
+ }
+ [mWindow setAnimationBehavior:behavior];
+ mWindowAnimationBehavior = behavior;
+ }
+
+ // We don't want alwaysontop windows to pull focus when they're opened,
+ // as these tend to be for peripheral indicators and displays.
+ if (mAlwaysOnTop) {
+ [mWindow orderFront:nil];
+ } else {
+ [mWindow makeKeyAndOrderFront:nil];
+ }
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+ SendSetZLevelEvent();
+ }
+ } else {
+ // roll up any popups if a top-level window is going away
+ if (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) RollUpPopups();
+
+ // now get rid of the window/sheet
+ if (mWindowType == eWindowType_sheet) {
+ if (mSheetNeedsShow) {
+ // This is an attempt to hide a sheet that never had a chance to
+ // be shown. There's nothing to do other than make sure that it
+ // won't show.
+ mSheetNeedsShow = false;
+ } else {
+ // get sheet's parent *before* hiding the sheet (which breaks the linkage)
+ NSWindow* sheetParent = mSheetWindowParent;
+
+ // hide the sheet
+ [NSApp endSheet:mWindow];
+
+ [TopLevelWindowData deactivateInWindow:mWindow];
+
+ nsCOMPtr<nsIWidget> siblingSheetToShow;
+ bool parentIsSheet = false;
+
+ if (nativeParentWindow && piParentWidget &&
+ NS_SUCCEEDED(
+ piParentWidget->GetChildSheet(false, getter_AddRefs(siblingSheetToShow))) &&
+ siblingSheetToShow) {
+ // First, give sibling sheets an opportunity to show.
+ siblingSheetToShow->Show(true);
+ } else if (nativeParentWindow && piParentWidget &&
+ NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) && parentIsSheet) {
+ // Only set contextInfo if the parent of the parent sheet we're about
+ // to restore isn't itself a sheet.
+ NSWindow* contextInfo = sheetParent;
+ nsIWidget* grandparentWidget = nil;
+ if (NS_SUCCEEDED(piParentWidget->GetRealParent(&grandparentWidget)) &&
+ grandparentWidget) {
+ nsCOMPtr<nsPIWidgetCocoa> piGrandparentWidget(do_QueryInterface(grandparentWidget));
+ bool grandparentIsSheet = false;
+ if (piGrandparentWidget &&
+ NS_SUCCEEDED(piGrandparentWidget->GetIsSheet(&grandparentIsSheet)) &&
+ grandparentIsSheet) {
+ contextInfo = nil;
+ }
+ }
+ // If there are no sibling sheets, but the parent is a sheet, restore
+ // it. It wasn't sent any deactivate events when it was hidden, so
+ // don't call through Show, just let the OS put it back up.
+ [NSApp beginSheet:nativeParentWindow
+ modalForWindow:sheetParent
+ modalDelegate:[nativeParentWindow delegate]
+ didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
+ contextInfo:contextInfo];
+ } else {
+ // Sheet, that was hard. No more siblings or parents, going back
+ // to a real window.
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ [sheetParent makeKeyAndOrderFront:nil];
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+ }
+ SendSetZLevelEvent();
+ }
+ } else {
+ // If the window is a popup window with a parent window we need to
+ // unhook it here before ordering it out. When you order out the child
+ // of a window it hides the parent window.
+ if (mWindowType == eWindowType_popup && nativeParentWindow)
+ [nativeParentWindow removeChildWindow:mWindow];
+
+ [mWindow orderOut:nil];
+
+ // If 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];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Work around a problem where with multiple displays and multiple spaces
+// enabled, where the browser is assigned to a single display or space, popup
+// windows that are reshown after being hidden with [NSWindow orderOut] show on
+// the assigned space even when opened from another display. Apply the
+// workaround whenever more than one display is enabled.
+bool nsCocoaWindow::NeedsRecreateToReshow() {
+ // Limit the workaround to non-tooltip popup windows because only they need to
+ // override the "Assign To" setting. i.e., to display where the parent window
+ // is.
+ return (mWindowType == eWindowType_popup) && (mPopupType != ePopupTypeTooltip) && mWasShown &&
+ ([[NSScreen screens] count] > 1);
+}
+
+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 = kWindowShadowParametersPostYosemite[uint8_t(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 != StyleWindowShadow::Menu && mShadowStyle != StyleWindowShadow::Sheet) return;
+
+ CGSConnection cid = _CGSDefaultConnection();
+ CGSSetWindowBackgroundBlurRadius(cid, [mWindow windowNumber], kWindowBackgroundBlurRadius);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult nsCocoaWindow::ConfigureChildren(const nsTArray<Configuration>& aConfigurations) {
+ if (mPopupContentView) {
+ mPopupContentView->ConfigureChildren(aConfigurations);
+ }
+ return NS_OK;
+}
+
+LayerManager* nsCocoaWindow::GetLayerManager(PLayerTransactionChild* aShadowManager,
+ LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence) {
+ if (mPopupContentView) {
+ return mPopupContentView->GetLayerManager(aShadowManager, aBackendHint, aPersistence);
+ }
+ return nullptr;
+}
+
+nsTransparencyMode nsCocoaWindow::GetTransparencyMode() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return (!mWindow || [mWindow isOpaque]) ? eTransparencyOpaque : eTransparencyTransparent;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(eTransparencyOpaque);
+}
+
+// This is called from nsMenuPopupFrame when making a popup transparent.
+void nsCocoaWindow::SetTransparencyMode(nsTransparencyMode aMode) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Only respect calls for popup windows.
+ if (!mWindow || mWindowType != eWindowType_popup) {
+ return;
+ }
+
+ BOOL isTransparent = aMode == eTransparencyTransparent;
+ BOOL currentTransparency = ![mWindow isOpaque];
+ if (isTransparent != currentTransparency) {
+ [mWindow setOpaque:!isTransparent];
+ [mWindow setBackgroundColor:(isTransparent ? [NSColor clearColor] : [NSColor whiteColor])];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::Enable(bool aState) {}
+
+bool nsCocoaWindow::IsEnabled() const { return true; }
+
+#define kWindowPositionSlop 20
+
+void nsCocoaWindow::ConstrainPosition(bool aAllowSlop, int32_t* aX, int32_t* aY) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow || ![mWindow screen]) {
+ return;
+ }
+
+ nsIntRect screenBounds;
+
+ int32_t width, height;
+
+ NSRect frame = [mWindow frame];
+
+ // zero size rects confuse the screen manager
+ width = std::max<int32_t>(frame.size.width, 1);
+ height = std::max<int32_t>(frame.size.height, 1);
+
+ nsCOMPtr<nsIScreenManager> screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (screenMgr) {
+ nsCOMPtr<nsIScreen> screen;
+ screenMgr->ScreenForRect(*aX, *aY, width, height, getter_AddRefs(screen));
+
+ if (screen) {
+ screen->GetRectDisplayPix(&(screenBounds.x), &(screenBounds.y), &(screenBounds.width),
+ &(screenBounds.height));
+ }
+ }
+
+ if (aAllowSlop) {
+ if (*aX < screenBounds.x - width + kWindowPositionSlop) {
+ *aX = screenBounds.x - width + kWindowPositionSlop;
+ } else if (*aX >= screenBounds.x + screenBounds.width - kWindowPositionSlop) {
+ *aX = screenBounds.x + screenBounds.width - kWindowPositionSlop;
+ }
+
+ if (*aY < screenBounds.y - height + kWindowPositionSlop) {
+ *aY = screenBounds.y - height + kWindowPositionSlop;
+ } else if (*aY >= screenBounds.y + screenBounds.height - kWindowPositionSlop) {
+ *aY = screenBounds.y + screenBounds.height - kWindowPositionSlop;
+ }
+ } else {
+ if (*aX < screenBounds.x) {
+ *aX = screenBounds.x;
+ } else if (*aX >= screenBounds.x + screenBounds.width - width) {
+ *aX = screenBounds.x + screenBounds.width - width;
+ }
+
+ if (*aY < screenBounds.y) {
+ *aY = screenBounds.y;
+ } else if (*aY >= screenBounds.y + screenBounds.height - height) {
+ *aY = screenBounds.y + screenBounds.height - height;
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Popups can be smaller than (32, 32)
+ NSRect rect = (mWindowType == eWindowType_popup) ? NSZeroRect : NSMakeRect(0.0, 0.0, 32, 32);
+ rect = [mWindow frameRectForChildViewRect: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
+void nsCocoaWindow::Move(double aX, double aY) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow) {
+ return;
+ }
+
+ // The point we have is in Gecko coordinates (origin top-left). Convert
+ // it to Cocoa ones (origin bottom-left).
+ NSPoint coord = {static_cast<float>(aX),
+ static_cast<float>(nsCocoaUtils::FlippedScreenY(NSToIntRound(aY)))};
+
+ NSRect frame = [mWindow frame];
+ if (frame.origin.x != coord.x || frame.origin.y + frame.size.height != coord.y) {
+ [mWindow setFrameTopLeftPoint:coord];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+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;
+}
+
+// The (work)space switching implementation below was inspired by Phoenix:
+// https://github.com/kasper/phoenix/tree/d6c877f62b30a060dff119d8416b0934f76af534
+// License: MIT.
+
+// Runtime `CGSGetActiveSpace` library function feature detection.
+typedef CGSSpaceID (*CGSGetActiveSpaceFunc)(CGSConnection cid);
+static CGSGetActiveSpaceFunc GetCGSGetActiveSpaceFunc() {
+ static CGSGetActiveSpaceFunc func = nullptr;
+ static bool lookedUpFunc = false;
+ if (!lookedUpFunc) {
+ func = (CGSGetActiveSpaceFunc)dlsym(RTLD_DEFAULT, "CGSGetActiveSpace");
+ lookedUpFunc = true;
+ }
+ return func;
+}
+// Runtime `CGSCopyManagedDisplaySpaces` library function feature detection.
+typedef CFArrayRef (*CGSCopyManagedDisplaySpacesFunc)(CGSConnection cid);
+static CGSCopyManagedDisplaySpacesFunc GetCGSCopyManagedDisplaySpacesFunc() {
+ static CGSCopyManagedDisplaySpacesFunc func = nullptr;
+ static bool lookedUpFunc = false;
+ if (!lookedUpFunc) {
+ func = (CGSCopyManagedDisplaySpacesFunc)dlsym(RTLD_DEFAULT, "CGSCopyManagedDisplaySpaces");
+ lookedUpFunc = true;
+ }
+ return func;
+}
+// Runtime `CGSCopySpacesForWindows` library function feature detection.
+typedef CFArrayRef (*CGSCopySpacesForWindowsFunc)(CGSConnection cid, CGSSpaceMask mask,
+ CFArrayRef windowIDs);
+static CGSCopySpacesForWindowsFunc GetCGSCopySpacesForWindowsFunc() {
+ static CGSCopySpacesForWindowsFunc func = nullptr;
+ static bool lookedUpFunc = false;
+ if (!lookedUpFunc) {
+ func = (CGSCopySpacesForWindowsFunc)dlsym(RTLD_DEFAULT, "CGSCopySpacesForWindows");
+ lookedUpFunc = true;
+ }
+ return func;
+}
+// Runtime `CGSAddWindowsToSpaces` library function feature detection.
+typedef void (*CGSAddWindowsToSpacesFunc)(CGSConnection cid, CFArrayRef windowIDs,
+ CFArrayRef spaceIDs);
+static CGSAddWindowsToSpacesFunc GetCGSAddWindowsToSpacesFunc() {
+ static CGSAddWindowsToSpacesFunc func = nullptr;
+ static bool lookedUpFunc = false;
+ if (!lookedUpFunc) {
+ func = (CGSAddWindowsToSpacesFunc)dlsym(RTLD_DEFAULT, "CGSAddWindowsToSpaces");
+ lookedUpFunc = true;
+ }
+ return func;
+}
+// Runtime `CGSRemoveWindowsFromSpaces` library function feature detection.
+typedef void (*CGSRemoveWindowsFromSpacesFunc)(CGSConnection cid, CFArrayRef windowIDs,
+ CFArrayRef spaceIDs);
+static CGSRemoveWindowsFromSpacesFunc GetCGSRemoveWindowsFromSpacesFunc() {
+ static CGSRemoveWindowsFromSpacesFunc func = nullptr;
+ static bool lookedUpFunc = false;
+ if (!lookedUpFunc) {
+ func = (CGSRemoveWindowsFromSpacesFunc)dlsym(RTLD_DEFAULT, "CGSRemoveWindowsFromSpaces");
+ lookedUpFunc = true;
+ }
+ return func;
+}
+
+void nsCocoaWindow::GetWorkspaceID(nsAString& workspaceID) {
+ workspaceID.Truncate();
+ int32_t sid = GetWorkspaceID();
+ if (sid != 0) {
+ workspaceID.AppendInt(sid);
+ }
+}
+
+int32_t nsCocoaWindow::GetWorkspaceID() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Mac OSX space IDs start at '1' (default space), so '0' means 'unknown',
+ // effectively.
+ CGSSpaceID sid = 0;
+
+ CGSCopySpacesForWindowsFunc CopySpacesForWindows = GetCGSCopySpacesForWindowsFunc();
+ if (!CopySpacesForWindows) {
+ return sid;
+ }
+
+ CGSConnection cid = _CGSDefaultConnection();
+ // Fetch all spaces that this window belongs to (in order).
+ NSArray<NSNumber*>* spaceIDs = CFBridgingRelease(CopySpacesForWindows(
+ cid, kCGSAllSpacesMask, (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ]));
+ if ([spaceIDs count]) {
+ // When spaces are found, return the first one.
+ // We don't support a single window painted across multiple places for now.
+ sid = [spaceIDs[0] integerValue];
+ } else {
+ // Fall back to the workspace that's currently active, which is '1' in the
+ // common case.
+ CGSGetActiveSpaceFunc GetActiveSpace = GetCGSGetActiveSpaceFunc();
+ if (GetActiveSpace) {
+ sid = GetActiveSpace(cid);
+ }
+ }
+
+ return sid;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::MoveToWorkspace(const nsAString& workspaceIDStr) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if ([NSScreen screensHaveSeparateSpaces] && [[NSScreen screens] count] > 1) {
+ // We don't support moving to a workspace when the user has this option
+ // enabled in Mission Control.
+ return;
+ }
+
+ nsresult rv = NS_OK;
+ int32_t workspaceID = workspaceIDStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ CGSConnection cid = _CGSDefaultConnection();
+ int32_t currentSpace = GetWorkspaceID();
+ // If an empty workspace ID is passed in (not valid on OSX), or when the
+ // window is already on this workspace, we don't need to do anything.
+ if (!workspaceID || workspaceID == currentSpace) {
+ return;
+ }
+
+ CGSCopyManagedDisplaySpacesFunc CopyManagedDisplaySpaces = GetCGSCopyManagedDisplaySpacesFunc();
+ CGSAddWindowsToSpacesFunc AddWindowsToSpaces = GetCGSAddWindowsToSpacesFunc();
+ CGSRemoveWindowsFromSpacesFunc RemoveWindowsFromSpaces = GetCGSRemoveWindowsFromSpacesFunc();
+ if (!CopyManagedDisplaySpaces || !AddWindowsToSpaces || !RemoveWindowsFromSpaces) {
+ return;
+ }
+
+ // Fetch an ordered list of all known spaces.
+ NSArray* displaySpacesInfo = CFBridgingRelease(CopyManagedDisplaySpaces(cid));
+ // When we found the space we're looking for, we can bail out of the loop
+ // early, which this local variable is used for.
+ BOOL found = false;
+ for (NSDictionary<NSString*, id>* spacesInfo in displaySpacesInfo) {
+ NSArray<NSNumber*>* sids = [spacesInfo[CGSSpacesKey] valueForKey:CGSSpaceIDKey];
+ for (NSNumber* sid in sids) {
+ // If we found our space in the list, we're good to go and can jump out of
+ // this loop.
+ if ((int)[sid integerValue] == workspaceID) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ break;
+ }
+ }
+
+ // We were unable to find the space to correspond with the workspaceID as
+ // requested, so let's bail out.
+ if (!found) {
+ return;
+ }
+
+ // First we add the window to the appropriate space.
+ AddWindowsToSpaces(cid, (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ],
+ (__bridge CFArrayRef) @[ @(workspaceID) ]);
+ // Then we remove the window from the active space.
+ RemoveWindowsFromSpaces(cid, (__bridge CFArrayRef) @[ @([mWindow windowNumber]) ],
+ (__bridge CFArrayRef) @[ @(currentSpace) ]);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SuppressAnimation(bool aSuppress) {
+ if ([mWindow respondsToSelector:@selector(setAnimationBehavior:)]) {
+ if (aSuppress) {
+ [mWindow setIsAnimationSuppressed:YES];
+ [mWindow setAnimationBehavior:NSWindowAnimationBehaviorNone];
+ } else {
+ [mWindow setIsAnimationSuppressed:NO];
+ [mWindow setAnimationBehavior:mWindowAnimationBehavior];
+ }
+ }
+}
+
+// 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.
+void nsCocoaWindow::HideWindowChrome(bool aShouldHide) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow || !mWindowMadeHere ||
+ (mWindowType != eWindowType_toplevel && mWindowType != eWindowType_dialog))
+ return;
+
+ 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 views in the old window's content view.
+ // The NSArray is autoreleased and retains its NSViews.
+ NSArray<NSView*>* contentViewContents = [mWindow contentViewContents];
+ for (NSView* view in contentViewContents) {
+ [view 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_VOID(rv);
+
+ // Re-import state.
+ [mWindow importState:state];
+
+ // Add the old content view subviews to the new window's content view.
+ for (NSView* view in contentViewContents) {
+ [[mWindow contentView] addSubview:view];
+ }
+
+ // 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;
+ Show(true);
+ mIsAnimationSuppressed = wasAnimationSuppressed;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+class FullscreenTransitionData : public nsISupports {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit FullscreenTransitionData(NSWindow* aWindow) : mTransitionWindow(aWindow) {}
+
+ NSWindow* mTransitionWindow;
+
+ private:
+ virtual ~FullscreenTransitionData() { [mTransitionWindow close]; }
+};
+
+NS_IMPL_ISUPPORTS0(FullscreenTransitionData)
+
+@interface FullscreenTransitionDelegate : NSObject <NSAnimationDelegate> {
+ @public
+ nsCocoaWindow* mWindow;
+ nsIRunnable* mCallback;
+}
+@end
+
+@implementation FullscreenTransitionDelegate
+- (void)cleanupAndDispatch:(NSAnimation*)animation {
+ [animation setDelegate:nil];
+ [self autorelease];
+ // The caller should have added ref for us.
+ NS_DispatchToMainThread(already_AddRefed<nsIRunnable>(mCallback));
+}
+
+- (void)animationDidEnd:(NSAnimation*)animation {
+ MOZ_ASSERT(animation == mWindow->FullscreenTransitionAnimation(),
+ "Should be handling the only animation on the window");
+ mWindow->ReleaseFullscreenTransitionAnimation();
+ [self cleanupAndDispatch:animation];
+}
+
+- (void)animationDidStop:(NSAnimation*)animation {
+ [self cleanupAndDispatch:animation];
+}
+@end
+
+static bool AlwaysUsesNativeFullScreen() {
+ return Preferences::GetBool("full-screen-api.macos-native-full-screen", false);
+}
+
+/* virtual */ bool nsCocoaWindow::PrepareForFullscreenTransition(nsISupports** aData) {
+ if (AlwaysUsesNativeFullScreen()) {
+ return false;
+ }
+ nsCOMPtr<nsIScreen> widgetScreen = GetWidgetScreen();
+ NSScreen* cocoaScreen = ScreenHelperCocoa::CocoaScreenForScreen(widgetScreen);
+
+ NSWindow* win = [[NSWindow alloc] initWithContentRect:[cocoaScreen frame]
+ styleMask:NSWindowStyleMaskBorderless
+ backing:NSBackingStoreBuffered
+ defer:YES];
+ [win setBackgroundColor:[NSColor blackColor]];
+ [win setAlphaValue:0];
+ [win setIgnoresMouseEvents:YES];
+ [win setLevel:NSScreenSaverWindowLevel];
+ [win makeKeyAndOrderFront:nil];
+
+ auto data = new FullscreenTransitionData(win);
+ *aData = data;
+ NS_ADDREF(data);
+ return true;
+}
+
+/* virtual */ void nsCocoaWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) {
+ auto data = static_cast<FullscreenTransitionData*>(aData);
+ FullscreenTransitionDelegate* delegate = [[FullscreenTransitionDelegate alloc] init];
+ delegate->mWindow = this;
+ // Storing already_AddRefed directly could cause static checking fail.
+ delegate->mCallback = nsCOMPtr<nsIRunnable>(aCallback).forget().take();
+
+ if (mFullscreenTransitionAnimation) {
+ [mFullscreenTransitionAnimation stopAnimation];
+ ReleaseFullscreenTransitionAnimation();
+ }
+
+ NSDictionary* dict = @{
+ NSViewAnimationTargetKey : data->mTransitionWindow,
+ NSViewAnimationEffectKey : aStage == eBeforeFullscreenToggle ? NSViewAnimationFadeInEffect
+ : NSViewAnimationFadeOutEffect
+ };
+ mFullscreenTransitionAnimation = [[NSViewAnimation alloc] initWithViewAnimations:@[ dict ]];
+ [mFullscreenTransitionAnimation setDelegate:delegate];
+ [mFullscreenTransitionAnimation setDuration:aDuration / 1000.0];
+ [mFullscreenTransitionAnimation startAnimation];
+}
+
+void nsCocoaWindow::WillEnterFullScreen(bool aFullScreen) {
+ if (mWidgetListener) {
+ mWidgetListener->FullscreenWillChange(aFullScreen);
+ }
+ // Update the state to full screen when we are entering, so that we switch to
+ // full screen view as soon as possible.
+ UpdateFullscreenState(aFullScreen, true);
+}
+
+void nsCocoaWindow::EnteredFullScreen(bool aFullScreen, bool aNativeMode) {
+ mInFullScreenTransition = false;
+ UpdateFullscreenState(aFullScreen, aNativeMode);
+}
+
+void nsCocoaWindow::UpdateFullscreenState(bool aFullScreen, bool aNativeMode) {
+ 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) {
+ // First check if this window supports entering native fullscreen.
+ // This is set based on the macnativefullscreen attribute on the window's
+ // document element.
+ NSWindowCollectionBehavior colBehavior = [mWindow collectionBehavior];
+ if (!(colBehavior & NSWindowCollectionBehaviorFullScreenPrimary)) {
+ 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, AlwaysUsesNativeFullScreen());
+}
+
+nsresult 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)) {
+ 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 {
+ if (mWidgetListener) {
+ mWidgetListener->FullscreenWillChange(aFullScreen);
+ }
+ 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
+void nsCocoaWindow::DoResize(double aX, double aY, double aWidth, double aHeight, bool aRepaint,
+ bool aConstrainToCurrentScreen) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow || mInResize) {
+ return;
+ }
+
+ // We are able to resize a window outside of any aspect ratio contraints
+ // applied to it, but in order to "update" the aspect ratio contraint to the
+ // new window dimensions, we must re-lock the aspect ratio.
+ auto relockAspectRatio = MakeScopeExit([&]() {
+ if (mAspectRatioLocked) {
+ LockAspectRatio(true);
+ }
+ });
+
+ AutoRestore<bool> reentrantResizeGuard(mInResize);
+ mInResize = true;
+
+ // ConstrainSize operates in device pixels, so we need to convert using
+ // the backing scale factor here
+ CGFloat scale = BackingScaleFactor();
+ int32_t width = NSToIntRound(aWidth * scale);
+ int32_t height = NSToIntRound(aHeight * scale);
+ ConstrainSize(&width, &height);
+
+ DesktopIntRect newBounds(NSToIntRound(aX), NSToIntRound(aY), NSToIntRound(width / scale),
+ NSToIntRound(height / scale));
+
+ // constrain to the screen that contains the largest area of the new rect
+ FitRectToVisibleAreaForScreen(newBounds, aConstrainToCurrentScreen ? [mWindow screen] : nullptr);
+
+ // convert requested bounds into Cocoa coordinate system
+ NSRect newFrame = nsCocoaUtils::GeckoRectToCocoaRect(newBounds);
+
+ NSRect frame = [mWindow frame];
+ BOOL isMoving = newFrame.origin.x != frame.origin.x || newFrame.origin.y != frame.origin.y;
+ BOOL isResizing =
+ newFrame.size.width != frame.size.width || newFrame.size.height != frame.size.height;
+
+ if (!isMoving && !isResizing) {
+ return;
+ }
+
+ // 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];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Coordinates are desktop pixels
+void nsCocoaWindow::Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) {
+ DoResize(aX, aY, aWidth, aHeight, aRepaint, false);
+}
+
+// Coordinates are desktop pixels
+void nsCocoaWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
+ double invScale = 1.0 / BackingScaleFactor();
+ DoResize(mBounds.x * invScale, mBounds.y * invScale, aWidth, aHeight, aRepaint, true);
+}
+
+// Return the area that the Gecko ChildView in our window should cover, as an
+// NSRect in screen coordinates (with 0,0 being the bottom left corner of the
+// primary screen).
+NSRect nsCocoaWindow::GetClientCocoaRect() {
+ if (!mWindow) {
+ return NSZeroRect;
+ }
+
+ return [mWindow childViewRectForFrameRect:[mWindow frame]];
+}
+
+LayoutDeviceIntRect nsCocoaWindow::GetClientBounds() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ CGFloat scaleFactor = BackingScaleFactor();
+ return nsCocoaUtils::CocoaRectToGeckoRectDevPix(GetClientCocoaRect(), 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());
+
+ if (mPopupContentView) {
+ mPopupContentView->UpdateBoundsFromView();
+ }
+}
+
+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 oldScale = mBackingScaleFactor;
+ 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->GetAppWindow()) {
+ return;
+ }
+
+ if (PresShell* presShell = mWidgetListener->GetPresShell()) {
+ presShell->BackingScaleFactorChanged();
+ }
+ mWidgetListener->UIResolutionChanged();
+
+ if ((mWindowType == eWindowType_popup) && (mBackingScaleFactor == 2.0)) {
+ // Recalculate the size and y-origin for the popup now that the backing
+ // scale factor has changed. After creating the popup window NSWindow,
+ // setting the frame when the menu is moved into the correct location
+ // causes the backing scale factor to change if the window is not on the
+ // menu bar display. Update the dimensions and y-origin here so that the
+ // frame is correct for the following ::Show(). Only do this when the
+ // scale factor changes from 1.0 to 2.0. When the scale factor changes
+ // from 2.0 to 1.0, the view will resize the widget before it is shown.
+ NSRect frame = [mWindow frame];
+ CGFloat previousYOrigin = frame.origin.y + frame.size.height;
+ frame.size.width = mBounds.Width() * (oldScale / newScale);
+ frame.size.height = mBounds.Height() * (oldScale / newScale);
+ frame.origin.y = previousYOrigin - frame.size.height;
+ [mWindow setFrame:frame display:NO animate:NO];
+ }
+}
+
+int32_t nsCocoaWindow::RoundsWidgetCoordinatesTo() {
+ if (BackingScaleFactor() == 2.0) {
+ return 2;
+ }
+ return 1;
+}
+
+void nsCocoaWindow::SetCursor(nsCursor aDefaultCursor, imgIContainer* aCursorImage,
+ uint32_t aHotspotX, uint32_t aHotspotY) {
+ if (mPopupContentView)
+ mPopupContentView->SetCursor(aDefaultCursor, aCursorImage, aHotspotX, aHotspotY);
+}
+
+nsresult nsCocoaWindow::SetTitle(const nsAString& aTitle) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mWindow) {
+ return NS_OK;
+ }
+
+ const nsString& strTitle = PromiseFlatString(aTitle);
+ const unichar* uniTitle = reinterpret_cast<const unichar*>(strTitle.get());
+ NSString* title = [NSString stringWithCharacters:uniTitle length:strTitle.Length()];
+ if ([mWindow drawsContentsIntoWindowFrame] && ![mWindow wantsTitleDrawn]) {
+ // Don't cause invalidations when the title isn't displayed.
+ [mWindow disableSetNeedsDisplay];
+ [mWindow setTitle:title];
+ [mWindow enableSetNeedsDisplay];
+ } else {
+ [mWindow setTitle:title];
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsCocoaWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
+ if (mPopupContentView) {
+ mPopupContentView->Invalidate(aRect);
+ }
+}
+
+// Pass notification of some drag event to Gecko
+//
+// The drag manager has let us know that something related to a drag has
+// occurred in this window. It could be any number of things, ranging from
+// a drop, to a drag enter/leave, or a drag over event. The actual event
+// is passed in |aMessage| and is passed along to our event hanlder so Gecko
+// knows about it.
+bool nsCocoaWindow::DragEvent(unsigned int aMessage, mozilla::gfx::Point aMouseGlobal,
+ UInt16 aKeyModifiers) {
+ return false;
+}
+
+NS_IMETHODIMP nsCocoaWindow::SendSetZLevelEvent() {
+ nsWindowZ placement = nsWindowZTop;
+ nsCOMPtr<nsIWidget> actualBelow;
+ if (mWidgetListener)
+ mWidgetListener->ZLevelChanged(true, &placement, nullptr, getter_AddRefs(actualBelow));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::GetChildSheet(bool aShown, nsIWidget** _retval) {
+ nsIWidget* child = GetFirstChild();
+
+ while (child) {
+ if (child->WindowType() == eWindowType_sheet) {
+ // if it's a sheet, it must be an nsCocoaWindow
+ nsCocoaWindow* cocoaWindow = static_cast<nsCocoaWindow*>(child);
+ if (cocoaWindow->mWindow && ((aShown && [cocoaWindow->mWindow isVisible]) ||
+ (!aShown && cocoaWindow->mSheetNeedsShow))) {
+ nsCOMPtr<nsIWidget> widget = cocoaWindow;
+ widget.forget(_retval);
+ return NS_OK;
+ }
+ }
+ child = child->GetNextSibling();
+ }
+
+ *_retval = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::GetRealParent(nsIWidget** parent) {
+ *parent = mParent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::GetIsSheet(bool* isSheet) {
+ mWindowType == eWindowType_sheet ? * isSheet = true : * isSheet = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::GetSheetWindowParent(NSWindow** sheetWindowParent) {
+ *sheetWindowParent = mSheetWindowParent;
+ return NS_OK;
+}
+
+// Invokes callback and ProcessEvent methods on Event Listener object
+nsresult nsCocoaWindow::DispatchEvent(WidgetGUIEvent* event, nsEventStatus& aStatus) {
+ aStatus = nsEventStatus_eIgnore;
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(event->mWidget);
+ mozilla::Unused << kungFuDeathGrip; // Not used within this function
+
+ if (mWidgetListener) aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
+
+ return NS_OK;
+}
+
+// aFullScreen should be the window's mInFullScreenMode. We don't have access to that
+// from here, so we need to pass it in. mInFullScreenMode should be the canonical
+// indicator that a window is currently full screen and it makes sense to keep
+// all sizemode logic here.
+static nsSizeMode GetWindowSizeMode(NSWindow* aWindow, bool aFullScreen) {
+ if (aFullScreen) return nsSizeMode_Fullscreen;
+ if ([aWindow isMiniaturized]) return nsSizeMode_Minimized;
+ if (([aWindow styleMask] & NSWindowStyleMaskResizable) && [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();
+
+ // The zoomed state can change when we're moving, in which case we need to
+ // update our internal mSizeMode. This can happen either if we're maximized
+ // and then moved, or if we're not maximized and moved back to zoomed state.
+ if (mWindow && ((mSizeMode == nsSizeMode_Maximized) ^ [mWindow isZoomed])) {
+ DispatchSizeModeEvent();
+ }
+
+ // 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);
+ }
+
+ if (StaticPrefs::widget_pause_compositor_when_minimized()) {
+ if (newMode == nsSizeMode_Minimized) {
+ PauseCompositor();
+ } else {
+ ResumeCompositor();
+ }
+ }
+}
+
+void nsCocoaWindow::DispatchOcclusionEvent() {
+ if (!mWindow) {
+ return;
+ }
+
+ bool newOcclusionState = !([mWindow occlusionState] & NSWindowOcclusionStateVisible);
+
+ // Don't dispatch if the new occlustion state is the same as the current state.
+ if (mIsFullyOccluded == newOcclusionState) {
+ return;
+ }
+
+ mIsFullyOccluded = newOcclusionState;
+ if (mWidgetListener) {
+ mWidgetListener->OcclusionStateChanged(mIsFullyOccluded);
+ }
+}
+
+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::PauseCompositor() {
+ nsIWidget* mainChildView = static_cast<nsIWidget*>([[mWindow mainChildView] widget]);
+ if (!mainChildView) {
+ return;
+ }
+ CompositorBridgeChild* remoteRenderer = mainChildView->GetRemoteRenderer();
+ if (!remoteRenderer) {
+ return;
+ }
+ remoteRenderer->SendPause();
+
+ // Now that the compositor has paused, we also try to mark the browser window
+ // docshell inactive to stop any animations. This does not affect docshells
+ // for browsers in other processes, but browser UI code should be managing
+ // their active state appropriately.
+ if (!mWidgetListener) {
+ return;
+ }
+ PresShell* presShell = mWidgetListener->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+ nsPresContext* presContext = presShell->GetPresContext();
+ if (!presContext) {
+ return;
+ }
+ BrowsingContext* bc = presContext->Document()->GetBrowsingContext();
+ if (!bc) {
+ return;
+ }
+ Unused << bc->SetExplicitActive(ExplicitActiveStatus::Inactive);
+}
+
+void nsCocoaWindow::ResumeCompositor() {
+ nsIWidget* mainChildView = static_cast<nsIWidget*>([[mWindow mainChildView] widget]);
+ if (!mainChildView) {
+ return;
+ }
+ CompositorBridgeChild* remoteRenderer = mainChildView->GetRemoteRenderer();
+ if (!remoteRenderer) {
+ return;
+ }
+ remoteRenderer->SendResume();
+
+ // Now that the compositor has resumed, we also try to mark the browser window
+ // docshell active to restart any animations. This does not affect docshells
+ // for browsers in other processes, but browser UI code should be managing
+ // their active state appropriately.
+ if (!mWidgetListener) {
+ return;
+ }
+ PresShell* presShell = mWidgetListener->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+ nsPresContext* presContext = presShell->GetPresContext();
+ if (!presContext) {
+ return;
+ }
+ BrowsingContext* bc = presContext->Document()->GetBrowsingContext();
+ if (!bc) {
+ return;
+ }
+ Unused << bc->SetExplicitActive(ExplicitActiveStatus::Active);
+}
+
+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.
+ if (mMenuBar && ((!gSomeMenuBarPainted && nsMenuUtilsX::GetHiddenWindowMenuBar() == mMenuBar) ||
+ [mWindow isMainWindow]))
+ mMenuBar->Paint();
+}
+
+void nsCocoaWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
+ if (!mWindow) return;
+
+ if (mPopupContentView) {
+ return mPopupContentView->SetFocus(aRaise, aCallerType);
+ }
+
+ if (aRaise == Raise::Yes && ([mWindow isVisible] || [mWindow isMiniaturized])) {
+ if ([mWindow isMiniaturized]) {
+ [mWindow deminiaturize:nil];
+ }
+
+ [mWindow makeKeyAndOrderFront:nil];
+ SendSetZLevelEvent();
+ }
+}
+
+LayoutDeviceIntPoint nsCocoaWindow::WidgetToScreenOffset() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return nsCocoaUtils::CocoaRectToGeckoRectDevPix(GetClientCocoaRect(), BackingScaleFactor())
+ .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);
+
+ // Our caller expects the inflated rect for windows *with separate titlebars*,
+ // i.e. for windows where [mWindow drawsContentsIntoWindowFrame] is NO.
+ //
+ // So we call frameRectForContentRect on NSWindow here, instead of mWindow, so
+ // that we don't run into our override if this window is a window that draws
+ // its content into the titlebar.
+ //
+ // This is the same thing the windows widget does, but we probably should fix
+ // that, see bug 1445738.
+ NSUInteger styleMask = [mWindow styleMask];
+ styleMask &= ~NSWindowStyleMaskFullSizeContentView;
+ NSRect inflatedRect = [NSWindow frameRectForContentRect:rect styleMask:styleMask];
+ 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()->MonitorAllProcessMouseEvents();
+ }
+ 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()->StopMonitoringAllProcessMouseEvents();
+
+ // 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;
+}
+
+nsresult 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(StyleWindowShadow 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 != StyleWindowShadow::None];
+ }
+
+ [mWindow setUseMenuStyle:(aStyle == StyleWindowShadow::Menu)];
+ AdjustWindowShadow();
+ SetWindowBackgroundBlur();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetWindowOpacity(float aOpacity) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow) {
+ return;
+ }
+
+ [mWindow setAlphaValue:(CGFloat)aOpacity];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static inline CGAffineTransform GfxMatrixToCGAffineTransform(const gfx::Matrix& m) {
+ CGAffineTransform t;
+ t.a = m._11;
+ t.b = m._12;
+ t.c = m._21;
+ t.d = m._22;
+ t.tx = m._31;
+ t.ty = m._32;
+ return t;
+}
+
+void nsCocoaWindow::SetWindowTransform(const gfx::Matrix& aTransform) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow) {
+ return;
+ }
+
+ // Calling CGSSetWindowTransform when the window is not visible results in
+ // misplacing the window into doubled x,y coordinates (see bug 1448132).
+ if (![mWindow isVisible] || NSIsEmptyRect([mWindow frame])) {
+ return;
+ }
+
+ if (StaticPrefs::widget_window_transforms_disabled()) {
+ // CGSSetWindowTransform is a private API. In case calling it causes
+ // problems either now or in the future, we'll want to have an easy kill
+ // switch. So we allow disabling it with a pref.
+ return;
+ }
+
+ gfx::Matrix transform = aTransform;
+
+ // aTransform is a transform that should be applied to the window relative
+ // to its regular position: If aTransform._31 is 100, then we want the
+ // window to be displayed 100 pixels to the right of its regular position.
+ // The transform that CGSSetWindowTransform accepts has a different meaning:
+ // It's used to answer the question "For the screen pixel at x,y (with the
+ // origin at the top left), what pixel in the window's buffer (again with
+ // origin top left) should be displayed at that position?"
+ // In the example above, this means that we need to call
+ // CGSSetWindowTransform with a horizontal translation of -windowPos.x - 100.
+ // So we need to invert the transform and adjust it by the window's position.
+ if (!transform.Invert()) {
+ // Treat non-invertible transforms as the identity transform.
+ transform = gfx::Matrix();
+ }
+
+ bool isIdentity = transform.IsIdentity();
+ if (isIdentity && mWindowTransformIsIdentity) {
+ return;
+ }
+
+ transform.PreTranslate(-mBounds.x, -mBounds.y);
+
+ // Snap translations to device pixels, to match what we do for CSS transforms
+ // and because the window server rounds down instead of to nearest.
+ if (!transform.HasNonTranslation() && transform.HasNonIntegerTranslation()) {
+ auto snappedTranslation = gfx::IntPoint::Round(transform.GetTranslation());
+ transform = gfx::Matrix::Translation(snappedTranslation.x, snappedTranslation.y);
+ }
+
+ // We also need to account for the backing scale factor: aTransform is given
+ // in device pixels, but CGSSetWindowTransform works with logical display
+ // pixels.
+ CGFloat backingScale = BackingScaleFactor();
+ transform.PreScale(backingScale, backingScale);
+ transform.PostScale(1 / backingScale, 1 / backingScale);
+
+ CGSConnection cid = _CGSDefaultConnection();
+ CGSSetWindowTransform(cid, [mWindow windowNumber], GfxMatrixToCGAffineTransform(transform));
+
+ mWindowTransformIsIdentity = isIdentity;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetWindowMouseTransparent(bool aIsTransparent) {
+ MOZ_ASSERT(mWindowType == eWindowType_popup, "This should only be called on popup windows.");
+ if (aIsTransparent) {
+ [mWindow setIgnoresMouseEvents:YES];
+ } else {
+ [mWindow setIgnoresMouseEvents:NO];
+ }
+}
+
+void nsCocoaWindow::SetShowsToolbarButton(bool aShow) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mWindow) [mWindow setShowsToolbarButton:aShow];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetSupportsNativeFullscreen(bool aSupportsNativeFullscreen) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mWindow) {
+ // This determines whether we tell cocoa that the window supports native
+ // full screen. If we do so, and another window is in native full screen,
+ // this window will also appear in native full screen. We generally only
+ // want to do this for primary application windows. We'll set the
+ // relevant macnativefullscreen attribute on those, which will lead to us
+ // being called with aSupportsNativeFullscreen set to `true` here.
+ NSWindowCollectionBehavior newBehavior = [mWindow collectionBehavior];
+ if (aSupportsNativeFullscreen) {
+ newBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
+ } else {
+ newBehavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
+ }
+ [mWindow setCollectionBehavior:newBehavior];
+ }
+
+ 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;
+
+ if (![mWindow drawsContentsIntoWindowFrame]) {
+ // If we don't draw into the window frame, we always want to display window
+ // titles.
+ [mWindow setWantsTitleDrawn:YES];
+ } else {
+ [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;
+}
+
+nsresult 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::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;
+}
+
+NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseScrollEvent(
+ 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");
+ if (mPopupContentView) {
+ // Pass nullptr as the observer so that the AutoObserverNotification in
+ // nsChildView::SynthesizeNativeMouseScrollEvent will be ignored.
+ return mPopupContentView->SynthesizeNativeMouseScrollEvent(aPoint, aNativeMessage, aDeltaX,
+ aDeltaY, aDeltaZ, aModifierFlags,
+ aAdditionalFlags, nullptr);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsCocoaWindow::LockAspectRatio(bool aShouldLock) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (aShouldLock) {
+ [mWindow setContentAspectRatio:mWindow.frame.size];
+ mAspectRatioLocked = true;
+ } else {
+ // According to https://developer.apple.com/documentation/appkit/nswindow/1419507-aspectratio,
+ // aspect ratios and resize increments are mutually exclusive, and the accepted way of
+ // cancelling an established aspect ratio is to set the resize increments to 1.0, 1.0
+ [mWindow setResizeIncrements:NSMakeSize(1.0, 1.0)];
+ mAspectRatioLocked = false;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mPopupContentView) {
+ return mPopupContentView->UpdateThemeGeometries(aThemeGeometries);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetPopupWindowLevel() {
+ if (!mWindow) return;
+
+ // Floating popups are at the floating level and hide when the window is
+ // deactivated.
+ if (mPopupLevel == ePopupLevelFloating) {
+ [mWindow setLevel:NSFloatingWindowLevel];
+ [mWindow setHidesOnDeactivate:YES];
+ } else {
+ // Otherwise, this is a top-level or parent popup. Parent popups always
+ // appear just above their parent and essentially ignore the level.
+ [mWindow setLevel:NSPopUpMenuWindowLevel];
+ [mWindow setHidesOnDeactivate:NO];
+ }
+}
+
+void nsCocoaWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mInputContext = aContext;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+bool nsCocoaWindow::GetEditCommands(NativeKeyBindingsType aType, const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands) {
+ // Validate the arguments.
+ if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
+ return false;
+ }
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ keyBindings->GetEditCommands(aEvent, aCommands);
+ return true;
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
+ nsCOMPtr<nsIWidget> window = new nsCocoaWindow();
+ return window.forget();
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
+ nsCOMPtr<nsIWidget> window = new nsChildView();
+ return window.forget();
+}
+
+@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();
+}
+
+- (NSArray<NSWindow*>*)customWindowsToEnterFullScreenForWindow:(NSWindow*)window {
+ return AlwaysUsesNativeFullScreen() ? @[ window ] : nil;
+}
+
+- (void)window:(NSWindow*)window
+ startCustomAnimationToEnterFullScreenOnScreen:(NSScreen*)screen
+ withDuration:(NSTimeInterval)duration {
+ // Immediately switch to cover full screen, so we don't show the default
+ // transition effect which stops video from playing.
+ // XXX Is it possible to simulate the native transition effect without
+ // triggering content size change?
+ [window setFrame:[screen frame] display:YES];
+}
+
+- (void)windowWillEnterFullScreen:(NSNotification*)notification {
+ if (!mGeckoWindow) {
+ return;
+ }
+
+ mGeckoWindow->WillEnterFullScreen(true);
+}
+
+// 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)windowWillExitFullScreen:(NSNotification*)notification {
+ if (!mGeckoWindow) {
+ return;
+ }
+
+ mGeckoWindow->WillEnterFullScreen(false);
+}
+
+- (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];
+
+ if ([window isKindOfClass:[ToolbarWindow class]]) {
+ [(ToolbarWindow*)window windowMainStateChanged];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)windowDidResignMain:(NSNotification*)aNotification {
+ RollUpPopups();
+ ChildViewMouseTracker::ReEvaluateMouseEnterState();
+
+ // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
+ // app modally. If one of those is up then we want it to retain its menu bar.
+ if ([NSApp _isRunningAppModal]) return;
+ RefPtr<nsMenuBarX> hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
+ if (hiddenWindowMenuBar) {
+ // printf("painting hidden window menu bar due to window losing main status\n");
+ hiddenWindowMenuBar->Paint();
+ }
+
+ NSWindow* window = [aNotification object];
+ if ([window isKindOfClass:[ToolbarWindow class]]) {
+ [(ToolbarWindow*)window windowMainStateChanged];
+ }
+}
+
+- (void)windowDidBecomeKey:(NSNotification*)aNotification {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ RollUpPopups();
+ ChildViewMouseTracker::ReEvaluateMouseEnterState();
+
+ NSWindow* window = [aNotification object];
+ if ([window isSheet]) [WindowDelegate paintMenubarForWindow:window];
+
+ nsChildView* mainChildView =
+ static_cast<nsChildView*>([[(BaseWindow*)window mainChildView] widget]);
+ if (mainChildView) {
+ if (mainChildView->GetInputContext().IsPasswordEditor()) {
+ TextInputHandler::EnableSecureEventInput();
+ } else {
+ TextInputHandler::EnsureSecureEventInputDisabled();
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)windowDidResignKey:(NSNotification*)aNotification {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ RollUpPopups();
+ ChildViewMouseTracker::ReEvaluateMouseEnterState();
+
+ // If a sheet just resigned key then we should paint the menu bar
+ // for whatever window is now main.
+ NSWindow* window = [aNotification object];
+ if ([window isSheet]) [WindowDelegate paintMenubarForWindow:[NSApp mainWindow]];
+
+ TextInputHandler::EnsureSecureEventInputDisabled();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)windowWillMove:(NSNotification*)aNotification {
+ RollUpPopups();
+}
+
+- (void)windowDidMove:(NSNotification*)aNotification {
+ if (mGeckoWindow) mGeckoWindow->ReportMoveEvent();
+}
+
+- (BOOL)windowShouldClose:(id)sender {
+ nsIWidgetListener* listener = mGeckoWindow ? mGeckoWindow->GetWidgetListener() : nullptr;
+ if (listener) listener->RequestWindowClose(mGeckoWindow);
+ return NO; // gecko will do it
+}
+
+- (void)windowWillClose:(NSNotification*)aNotification {
+ RollUpPopups();
+}
+
+- (void)windowWillMiniaturize:(NSNotification*)aNotification {
+ RollUpPopups();
+}
+
+- (void)windowDidMiniaturize:(NSNotification*)aNotification {
+ if (mGeckoWindow) mGeckoWindow->DispatchSizeModeEvent();
+}
+
+- (void)windowDidDeminiaturize:(NSNotification*)aNotification {
+ if (mGeckoWindow) mGeckoWindow->DispatchSizeModeEvent();
+}
+
+- (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)proposedFrame {
+ if (!mHasEverBeenZoomed && [window isZoomed]) return NO; // See bug 429954.
+
+ mHasEverBeenZoomed = YES;
+ return YES;
+}
+
+- (NSRect)window:(NSWindow*)window willPositionSheet:(NSWindow*)sheet usingRect:(NSRect)rect {
+ if ([window isKindOfClass:[ToolbarWindow class]]) {
+ rect.origin.y = [(ToolbarWindow*)window sheetAttachmentPosition];
+ }
+ return rect;
+}
+
+- (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;
+}
+
+// This method is on NSWindowDelegate starting with 10.9
+- (void)windowDidChangeOcclusionState:(NSNotification*)aNotification {
+ if (mGeckoWindow) {
+ mGeckoWindow->DispatchOcclusionEvent();
+ }
+}
+
+- (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
+
+@interface NSView (FrameViewMethodSwizzling)
+- (NSPoint)FrameView__closeButtonOrigin;
+- (NSPoint)FrameView__fullScreenButtonOrigin;
+- (CGFloat)FrameView__titlebarHeight;
+@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;
+}
+
+- (CGFloat)FrameView__titlebarHeight {
+ CGFloat height = [self FrameView__titlebarHeight];
+ if ([[self window] isKindOfClass:[ToolbarWindow class]]) {
+ // Make sure that the titlebar height includes our shifted buttons.
+ // The following coordinates are in window space, with the origin being at the bottom left
+ // corner of the window.
+ ToolbarWindow* win = (ToolbarWindow*)[self window];
+ CGFloat frameHeight = [self frame].size.height;
+ NSPoint pointAboveWindow = {0.0, frameHeight};
+ CGFloat windowButtonY = [win windowButtonsPositionWithDefaultPosition:pointAboveWindow].y;
+ CGFloat fullScreenButtonY =
+ [win fullScreenButtonPositionWithDefaultPosition:pointAboveWindow].y;
+ CGFloat maxDistanceFromWindowTopToButtonBottom =
+ std::max(frameHeight - windowButtonY, frameHeight - fullScreenButtonY);
+ height = std::max(height, maxDistanceFromWindowTopToButtonBottom);
+ }
+ return height;
+}
+
+@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
+
+#if !defined(MAC_OS_X_VERSION_10_12_2) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12_2
+@interface NSView (NSTouchBarProvider)
+- (NSTouchBar*)makeTouchBar;
+@end
+#endif
+
+@interface NSView (NSVisualEffectViewSetMaskImage)
+- (void)setMaskImage:(NSImage*)image;
+@end
+
+@interface BaseWindow (Private)
+- (void)removeTrackingArea;
+- (void)cursorUpdated:(NSEvent*)aEvent;
+- (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));
+ static IMP our_titlebarHeight =
+ class_getMethodImplementation([NSView class], @selector(FrameView__titlebarHeight));
+
+ 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));
+ }
+
+ // Override _titlebarHeight so that the floating titlebar doesn't clip the bottom of the
+ // window buttons which we move down with our override of _closeButtonOrigin.
+ IMP _titlebarHeight = class_getMethodImplementation(frameViewClass, @selector(_titlebarHeight));
+ if (_titlebarHeight && _titlebarHeight != our_titlebarHeight) {
+ nsToolkit::SwizzleMethods(frameViewClass, @selector(_titlebarHeight),
+ @selector(FrameView__titlebarHeight));
+ }
+
+ [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;
+ mDisabledNeedsDisplay = NO;
+ mTrackingArea = nil;
+ mDirtyRect = NSZeroRect;
+ mBeingShown = NO;
+ mDrawTitle = NO;
+ mBrightTitlebarForeground = NO;
+ mUseMenuStyle = NO;
+ mTouchBar = nil;
+ mIsAnimationSuppressed = 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 (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];
+ [wrapper setWantsLayer:YES];
+ [self swapOutChildViewWrapper:wrapper];
+ [wrapper release];
+ }
+ mUseMenuStyle = aValue;
+}
+
+- (NSTouchBar*)makeTouchBar {
+ mTouchBar = [[nsTouchBar alloc] init];
+ if (mTouchBar) {
+ sTouchBarIsInitialized = YES;
+ }
+ return mTouchBar;
+}
+
+- (void)setBeingShown:(BOOL)aValue {
+ mBeingShown = aValue;
+}
+
+- (BOOL)isBeingShown {
+ return mBeingShown;
+}
+
+- (BOOL)isVisibleOrBeingShown {
+ return [super isVisible] || mBeingShown;
+}
+
+- (void)setIsAnimationSuppressed:(BOOL)aValue {
+ mIsAnimationSuppressed = aValue;
+}
+
+- (BOOL)isAnimationSuppressed {
+ return mIsAnimationSuppressed;
+}
+
+- (void)disableSetNeedsDisplay {
+ mDisabledNeedsDisplay = YES;
+}
+
+- (void)enableSetNeedsDisplay {
+ mDisabledNeedsDisplay = NO;
+}
+
+- (void)dealloc {
+ [mTouchBar release];
+ [self removeTrackingArea];
+ ChildViewMouseTracker::OnDestroyWindow(self);
+ [super dealloc];
+}
+
+static const NSString* kStateTitleKey = @"title";
+static const NSString* kStateDrawsContentsIntoWindowFrameKey = @"drawsContentsIntoWindowFrame";
+static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
+static const NSString* kStateCollectionBehavior = @"collectionBehavior";
+static const NSString* kStateWantsTitleDrawn = @"wantsTitleDrawn";
+
+- (void)importState:(NSDictionary*)aState {
+ if (NSString* title = [aState objectForKey:kStateTitleKey]) {
+ [self setTitle:title];
+ }
+ [self setDrawsContentsIntoWindowFrame:[[aState objectForKey:kStateDrawsContentsIntoWindowFrameKey]
+ boolValue]];
+ [self setShowsToolbarButton:[[aState objectForKey:kStateShowsToolbarButton] boolValue]];
+ [self setCollectionBehavior:[[aState objectForKey:kStateCollectionBehavior] unsignedIntValue]];
+ [self setWantsTitleDrawn:[[aState objectForKey:kStateWantsTitleDrawn] boolValue]];
+}
+
+- (NSMutableDictionary*)exportState {
+ NSMutableDictionary* state = [NSMutableDictionary dictionaryWithCapacity:10];
+ if (NSString* title = [self title]) {
+ [state setObject:title forKey:kStateTitleKey];
+ }
+ [state setObject:[NSNumber numberWithBool:[self drawsContentsIntoWindowFrame]]
+ forKey:kStateDrawsContentsIntoWindowFrameKey];
+ [state setObject:[NSNumber numberWithBool:[self showsToolbarButton]]
+ forKey:kStateShowsToolbarButton];
+ [state setObject:[NSNumber numberWithUnsignedInt:[self collectionBehavior]]
+ forKey:kStateCollectionBehavior];
+ [state setObject:[NSNumber numberWithBool:[self wantsTitleDrawn]] forKey:kStateWantsTitleDrawn];
+ return state;
+}
+
+- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState {
+ bool changed = (aState != mDrawsIntoWindowFrame);
+ mDrawsIntoWindowFrame = aState;
+ if (changed) {
+ [self reflowTitlebarElements];
+ }
+}
+
+- (BOOL)drawsContentsIntoWindowFrame {
+ return mDrawsIntoWindowFrame;
+}
+
+- (NSRect)childViewRectForFrameRect:(NSRect)aFrameRect {
+ if (mDrawsIntoWindowFrame) {
+ return aFrameRect;
+ }
+ NSUInteger styleMask = [self styleMask];
+ styleMask &= ~NSWindowStyleMaskFullSizeContentView;
+ return [NSWindow contentRectForFrameRect:aFrameRect styleMask:styleMask];
+}
+
+- (NSRect)frameRectForChildViewRect:(NSRect)aChildViewRect {
+ if (mDrawsIntoWindowFrame) {
+ return aChildViewRect;
+ }
+ NSUInteger styleMask = [self styleMask];
+ styleMask &= ~NSWindowStyleMaskFullSizeContentView;
+ return [NSWindow frameRectForContentRect:aChildViewRect styleMask:styleMask];
+}
+
+- (NSTimeInterval)animationResizeTime:(NSRect)newFrame {
+ if (mIsAnimationSuppressed) {
+ // Should not animate the initial session-restore size change
+ return 0.0;
+ }
+
+ return [super animationResizeTime:newFrame];
+}
+
+- (void)setWantsTitleDrawn:(BOOL)aDrawTitle {
+ mDrawTitle = aDrawTitle;
+ if ([self respondsToSelector:@selector(setTitleVisibility:)]) {
+ [self setTitleVisibility:mDrawTitle ? NSWindowTitleVisible : NSWindowTitleHidden];
+ }
+}
+
+- (BOOL)wantsTitleDrawn {
+ return mDrawTitle;
+}
+
+- (void)setUseBrightTitlebarForeground:(BOOL)aBrightForeground {
+ mBrightTitlebarForeground = aBrightForeground;
+ [[self standardWindowButton:NSWindowFullScreenButton] setNeedsDisplay:YES];
+}
+
+- (BOOL)useBrightTitlebarForeground {
+ return mBrightTitlebarForeground;
+}
+
+- (NSView*)trackingAreaView {
+ NSView* contentView = [self contentView];
+ return [contentView superview] ? [contentView superview] : contentView;
+}
+
+- (NSArray<NSView*>*)contentViewContents {
+ return [[[[self contentView] subviews] copy] autorelease];
+}
+
+- (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;
+}
+
+// Possibly move the titlebar buttons.
+- (void)reflowTitlebarElements {
+ NSView* frameView = [[self contentView] superview];
+ if ([frameView respondsToSelector:@selector(_tileTitlebarAndRedisplay:)]) {
+ [frameView _tileTitlebarAndRedisplay:NO];
+ }
+}
+
+- (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 {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ 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;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)releaseJSObjects {
+ [mTouchBar releaseJSObjects];
+}
+
+@end
+
+@interface NSView (NSThemeFrame)
+- (void)_drawTitleStringInClip:(NSRect)aRect;
+- (void)_maskCorners:(NSUInteger)aFlags clipRect:(NSRect)aRect;
+@end
+
+@implementation TitlebarGradientView
+
+- (void)drawRect:(NSRect)aRect {
+ CGContextRef ctx = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ ToolbarWindow* window = (ToolbarWindow*)[self window];
+ nsNativeThemeCocoa::DrawNativeTitlebar(ctx, NSRectToCGRect([self bounds]),
+ [window unifiedToolbarHeight], [window isMainWindow], NO);
+}
+
+- (BOOL)isOpaque {
+ return YES;
+}
+
+- (BOOL)mouseDownCanMoveWindow {
+ return YES;
+}
+
+- (void)mouseUp:(NSEvent*)event {
+ if ([event clickCount] == 2) {
+ // Handle titlebar double click. We don't get the window's default behavior here because the
+ // window uses NSWindowStyleMaskFullSizeContentView, and this view (the titlebar gradient view)
+ // is technically part of the window "contents" (it's a subview of the content view).
+ if (nsCocoaUtils::ShouldZoomOnTitlebarDoubleClick()) {
+ [[self window] performZoom:nil];
+ } else if (nsCocoaUtils::ShouldMinimizeOnTitlebarDoubleClick()) {
+ [[self window] performMiniaturize:nil];
+ }
+ }
+}
+
+@end
+
+// This class allows us to exercise control over the window's title bar. It is
+// used for all windows with titlebars.
+//
+// ToolbarWindow supports two modes:
+// - drawsContentsIntoWindowFrame mode: In this mode, the Gecko ChildView is
+// sized to cover the entire window frame and manages titlebar drawing.
+// - separate titlebar mode, with support for unified toolbars: In this mode,
+// the Gecko ChildView does not extend into the titlebar. However, this
+// window's content view (which is the ChildView's superview) *does* extend
+// into the titlebar. Moreover, in this mode, we place a TitlebarGradientView
+// in the content view, as a sibling of the ChildView.
+//
+// The "separate titlebar mode" supports the "unified toolbar" look:
+// If there's a toolbar right below the titlebar, the two can "connect" and
+// form a single gradient without a separator line in between.
+//
+// The following mechanism communicates the height of the unified toolbar to
+// the ToolbarWindow:
+//
+// 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 StyleAppearance::Toolbar.
+// 3) nsChildView::UpdateThemeGeometries passes the toolbar's height, plus the
+// titlebar height, to -[ToolbarWindow setUnifiedToolbarHeight:].
+//
+// The actual drawing of the gradient happens in two parts: The titlebar part
+// (i.e. the top 22 pixels of the gradient) is drawn by the TitlebarGradientView,
+// which is a subview of the window's content view and a sibling of the ChildView.
+// The rest of the gradient is drawn by Gecko into the ChildView, as part of the
+// -moz-appearance rendering of the toolbar.
+@implementation ToolbarWindow
+
+- (id)initWithContentRect:(NSRect)aChildViewRect
+ styleMask:(NSUInteger)aStyle
+ backing:(NSBackingStoreType)aBufferingType
+ defer:(BOOL)aFlag {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // We treat aChildViewRect as the rectangle that the window's main ChildView
+ // should be sized to. Get the right frameRect for the requested child view
+ // rect.
+ NSRect frameRect = [NSWindow frameRectForContentRect:aChildViewRect styleMask:aStyle];
+
+ // Always size the content view to the full frame size of the window.
+ // We cannot use this window mask when our CoreAnimation pref is disabled: This flag forces
+ // CoreAnimation on for the entire window, which causes glitches in combination with our
+ // non-CoreAnimation drawing. (Specifically, on macOS versions up until at least 10.14.0,
+ // layer-backed NSOpenGLViews have extremely glitchy resizing behavior.)
+ aStyle |= NSWindowStyleMaskFullSizeContentView;
+
+ // -[NSWindow initWithContentRect:styleMask:backing:defer:] calls
+ // [self frameRectForContentRect:styleMask:] to convert the supplied content
+ // rect to the window's frame rect. We've overridden that method to be a
+ // pass-through function. So, in order to get the intended frameRect, we need
+ // to supply frameRect itself as the "content rect".
+ NSRect contentRect = frameRect;
+
+ if ((self = [super initWithContentRect:contentRect
+ styleMask:aStyle
+ backing:aBufferingType
+ defer:aFlag])) {
+ mTitlebarGradientView = nil;
+ mUnifiedToolbarHeight = 22.0f;
+ mSheetAttachmentPosition = aChildViewRect.size.height;
+ mWindowButtonsRect = NSZeroRect;
+ mFullScreenButtonRect = NSZeroRect;
+
+ if ([self respondsToSelector:@selector(setTitlebarAppearsTransparent:)]) {
+ [self setTitlebarAppearsTransparent:YES];
+ }
+
+ [self updateTitlebarGradientViewPresence];
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc {
+ [mTitlebarGradientView release];
+ [super dealloc];
+}
+
+- (NSArray<NSView*>*)contentViewContents {
+ NSMutableArray<NSView*>* contents = [[[self contentView] subviews] mutableCopy];
+ if (mTitlebarGradientView) {
+ // Do not include the titlebar gradient view in the returned array.
+ [contents removeObject:mTitlebarGradientView];
+ }
+ return [contents autorelease];
+}
+
+- (void)updateTitlebarGradientViewPresence {
+ BOOL needTitlebarView = ![self drawsContentsIntoWindowFrame];
+ if (needTitlebarView && !mTitlebarGradientView) {
+ mTitlebarGradientView = [[TitlebarGradientView alloc] initWithFrame:[self titlebarRect]];
+ mTitlebarGradientView.autoresizingMask = NSViewWidthSizable | NSViewMinYMargin;
+ [self.contentView addSubview:mTitlebarGradientView positioned:NSWindowBelow relativeTo:nil];
+ } else if (!needTitlebarView && mTitlebarGradientView) {
+ [mTitlebarGradientView removeFromSuperview];
+ [mTitlebarGradientView release];
+ mTitlebarGradientView = nil;
+ }
+}
+
+// Override methods that translate between content rect and frame rect.
+// These overrides are only needed on 10.9 or when the CoreAnimation pref is
+// is false; otherwise we use NSFullSizeContentViewMask and get this behavior
+// for free.
+- (NSRect)contentRectForFrameRect:(NSRect)aRect {
+ return aRect;
+}
+
+- (NSRect)contentRectForFrameRect:(NSRect)aRect styleMask:(NSUInteger)aMask {
+ return aRect;
+}
+
+- (NSRect)frameRectForContentRect:(NSRect)aRect {
+ return aRect;
+}
+
+- (NSRect)frameRectForContentRect:(NSRect)aRect styleMask:(NSUInteger)aMask {
+ return aRect;
+}
+
+- (void)setContentView:(NSView*)aView {
+ [super setContentView:aView];
+
+ if (!([self styleMask] & NSWindowStyleMaskFullSizeContentView)) {
+ // Move the contentView to the bottommost layer so that it's guaranteed
+ // to be under the window buttons.
+ // When the window uses the NSFullSizeContentViewMask, this manual
+ // adjustment is not necessary.
+ 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];
+ }
+ }
+}
+
+- (void)windowMainStateChanged {
+ [self setTitlebarNeedsDisplay];
+ [[self mainChildView] ensureNextCompositeIsAtomicWithMainThreadPaint];
+}
+
+- (void)setTitlebarNeedsDisplay {
+ [mTitlebarGradientView setNeedsDisplay:YES];
+}
+
+- (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.
+ NSRect frameRect = [self frame];
+ NSUInteger styleMask = [self styleMask];
+ styleMask &= ~NSWindowStyleMaskFullSizeContentView;
+ NSRect originalContentRect = [NSWindow contentRectForFrameRect:frameRect styleMask: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]) {
+ [self setTitlebarNeedsDisplay];
+ }
+}
+
+// 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();
+ }
+
+ [self updateTitlebarGradientViewPresence];
+}
+
+- (void)setWantsTitleDrawn:(BOOL)aDrawTitle {
+ [super setWantsTitleDrawn:aDrawTitle];
+ [self setTitlebarNeedsDisplay];
+}
+
+- (void)setSheetAttachmentPosition:(CGFloat)aY {
+ mSheetAttachmentPosition = aY;
+}
+
+- (CGFloat)sheetAttachmentPosition {
+ return mSheetAttachmentPosition;
+}
+
+- (void)placeWindowButtons:(NSRect)aRect {
+ if (!NSEqualRects(mWindowButtonsRect, aRect)) {
+ mWindowButtonsRect = aRect;
+ [self reflowTitlebarElements];
+ }
+}
+
+- (NSPoint)windowButtonsPositionWithDefaultPosition:(NSPoint)aDefaultPosition {
+ NSInteger styleMask = [self styleMask];
+ if ([self drawsContentsIntoWindowFrame] && !(styleMask & NSWindowStyleMaskFullScreen) &&
+ (styleMask & NSWindowStyleMaskTitled)) {
+ 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 NSEventTypeScrollWheel:
+ case NSEventTypeLeftMouseDown:
+ case NSEventTypeLeftMouseUp:
+ case NSEventTypeRightMouseDown:
+ case NSEventTypeRightMouseUp:
+ case NSEventTypeOtherMouseDown:
+ case NSEventTypeOtherMouseUp:
+ case NSEventTypeMouseMoved:
+ case NSEventTypeLeftMouseDragged:
+ case NSEventTypeRightMouseDragged:
+ case NSEventTypeOtherMouseDragged: {
+ // 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
+
+@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;
+}
+
+// Override the private API _backdropBleedAmount. This determines how much the
+// desktop wallpaper contributes to the vibrancy backdrop.
+// Return 0 in order to match what the system does for sheet windows and
+// _NSPopoverWindows.
+- (CGFloat)_backdropBleedAmount {
+ return 0.0;
+}
+
+- (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 NSEventTypeScrollWheel:
+ case NSEventTypeLeftMouseDown:
+ case NSEventTypeLeftMouseUp:
+ case NSEventTypeRightMouseDown:
+ case NSEventTypeRightMouseUp:
+ case NSEventTypeOtherMouseDown:
+ case NSEventTypeOtherMouseUp:
+ case NSEventTypeMouseMoved:
+ case NSEventTypeLeftMouseDragged:
+ case NSEventTypeRightMouseDragged:
+ case NSEventTypeOtherMouseDragged: {
+ // 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..b8ee6328ba
--- /dev/null
+++ b/widget/cocoa/nsColorPicker.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 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);
+ void Done();
+
+ private:
+ ~nsColorPicker();
+
+ static NSColor* GetNSColorFromHexString(const nsAString& aColor);
+ static void GetHexStringFromNSColor(NSColor* aColor, nsAString& aResult);
+
+ NSColorPanelWrapper* mColorPanelWrapper;
+
+ nsString mTitle;
+ nsString mColor;
+ nsCOMPtr<nsIColorPickerShownCallback> mCallback;
+};
+
+#endif // nsColorPicker_h_
diff --git a/widget/cocoa/nsColorPicker.mm b/widget/cocoa/nsColorPicker.mm
new file mode 100644
index 0000000000..4eaa6e43c2
--- /dev/null
+++ b/widget/cocoa/nsColorPicker.mm
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsColorPicker.h"
+#include "nsCocoaUtils.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+static unsigned int HexStrToInt(NSString* str) {
+ unsigned int result = 0;
+
+ for (unsigned int i = 0; i < [str length]; ++i) {
+ char c = [str characterAtIndex:i];
+ result *= 16;
+ if (c >= '0' && c <= '9') {
+ result += c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ result += 10 + (c - 'A');
+ } else {
+ result += 10 + (c - 'a');
+ }
+ }
+
+ return result;
+}
+
+@interface NSColorPanelWrapper : NSObject <NSWindowDelegate> {
+ NSColorPanel* mColorPanel;
+ nsColorPicker* mColorPicker;
+}
+- (id)initWithPicker:(nsColorPicker*)aPicker;
+- (void)open:(NSColor*)aInitialColor title:(NSString*)aTitle;
+- (void)colorChanged:(NSColorPanel*)aPanel;
+- (void)windowWillClose:(NSNotification*)aNotification;
+- (void)close;
+@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 {
+ if (!mColorPicker) {
+ return;
+ }
+ mColorPicker->Update([mColorPanel color]);
+}
+
+- (void)windowWillClose:(NSNotification*)aNotification {
+ if (!mColorPicker) {
+ return;
+ }
+ mColorPicker->Done();
+}
+
+- (void)close {
+ [mColorPanel setTarget:nil];
+ [mColorPanel setAction:nil];
+ [mColorPanel setDelegate:nil];
+
+ mColorPanel = nil;
+ mColorPicker = nullptr;
+}
+@end
+
+NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
+
+nsColorPicker::~nsColorPicker() {
+ if (mColorPanelWrapper) {
+ [mColorPanelWrapper close];
+ [mColorPanelWrapper release];
+ mColorPanelWrapper = nullptr;
+ }
+}
+
+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;
+ mColorPanelWrapper = [[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;
+
+ [mColorPanelWrapper 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::Done() {
+ [mColorPanelWrapper close];
+ [mColorPanelWrapper release];
+ mColorPanelWrapper = nullptr;
+ mCallback->Done(u""_ns);
+ mCallback = nullptr;
+ NS_RELEASE_THIS();
+}
diff --git a/widget/cocoa/nsCursorManager.h b/widget/cocoa/nsCursorManager.h
new file mode 100644
index 0000000000..a444951fb1
--- /dev/null
+++ b/widget/cocoa/nsCursorManager.h
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCursorManager_h_
+#define nsCursorManager_h_
+
+#import <Foundation/Foundation.h>
+#include "nsIWidget.h"
+#include "nsMacCursor.h"
+
+/*! @class nsCursorManager
+ @abstract Singleton service provides access to all cursors available in the application.
+ @discussion Use <code>nsCusorManager</code> to set the current cursor using
+ an XP <code>nsCusor</code> enum value.
+ <code>nsCursorManager</code> encapsulates the details of
+ setting different types of cursors, animating cursors and
+ cleaning up cursors when they are no longer in use.
+ */
+@interface nsCursorManager : NSObject {
+ @private
+ NSMutableDictionary* mCursors;
+ nsMacCursor* mCurrentMacCursor;
+}
+
+/*! @method setCursor:
+ @abstract Sets the current cursor.
+ @discussion Sets the current cursor to the cursor indicated by the XP cursor constant given as
+ an argument. Resources associated with the previous cursor are cleaned up.
+ @param aCursor the cursor to use
+*/
+- (nsresult)setCursor:(nsCursor)aCursor;
+
+/*! @method setCursorWithImage:hotSpotX:hotSpotY:
+ @abstract Sets the current cursor to a custom image
+ @discussion Sets the current cursor to the cursor given by the aCursorImage argument.
+ Resources associated with the previous cursor are cleaned up.
+ @param aCursorImage the cursor image to use
+ @param aHotSpotX the x coordinate of the cursor's hotspot
+ @param aHotSpotY the y coordinate of the cursor's hotspot
+ @param scaleFactor the scale factor of the target display (2 for a retina display)
+ */
+- (nsresult)setCursorWithImage:(imgIContainer*)aCursorImage
+ hotSpotX:(uint32_t)aHotspotX
+ hotSpotY:(uint32_t)aHotspotY
+ scaleFactor:(CGFloat)scaleFactor;
+
+/*! @method sharedInstance
+ @abstract Get the Singleton instance of the cursor manager.
+ @discussion Use this method to obtain a reference to the cursor manager.
+ @result a reference to the cursor manager
+*/
++ (nsCursorManager*)sharedInstance;
+
+/*! @method dispose
+ @abstract Releases the shared instance of the cursor manager.
+ @discussion Use dispose to clean up the cursor manager and associated cursors.
+*/
++ (void)dispose;
+@end
+
+@interface NSCursor (Undocumented)
+// busyButClickableCursor is an undocumented NSCursor API, but has been in use since
+// at least OS X 10.4 and through 10.9.
++ (NSCursor*)busyButClickableCursor;
+@end
+
+#endif // nsCursorManager_h_
diff --git a/widget/cocoa/nsCursorManager.mm b/widget/cocoa/nsCursorManager.mm
new file mode 100644
index 0000000000..e27641160d
--- /dev/null
+++ b/widget/cocoa/nsCursorManager.mm
@@ -0,0 +1,318 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "imgIContainer.h"
+#include "nsCocoaUtils.h"
+#include "nsCursorManager.h"
+#include "nsObjCExceptions.h"
+#include <math.h>
+
+static nsCursorManager* gInstance;
+static CGFloat sCursorScaleFactor = 0.0f;
+static imgIContainer* sCursorImgContainer = nullptr;
+static const nsCursor sCustomCursor = eCursorCount;
+
+/*! @category nsCursorManager(PrivateMethods)
+ Private methods for the cursor manager class.
+*/
+@interface nsCursorManager (PrivateMethods)
+/*! @method getCursor:
+ @abstract Get a reference to the native Mac representation of a cursor.
+ @discussion Gets a reference to the Mac native implementation of a cursor.
+ If the cursor has been requested before, it is retreived from the cursor cache,
+ otherwise it is created and cached.
+ @param aCursor the cursor to get
+ @result the Mac native implementation of the cursor
+*/
+- (nsMacCursor*)getCursor:(nsCursor)aCursor;
+
+/*! @method setMacCursor:
+ @abstract Set the current Mac native cursor
+ @discussion Sets the current cursor - this routine is what actually causes the cursor to change.
+ The argument is retained and the old cursor is released.
+ @param aMacCursor the cursor to set
+ @result NS_OK
+ */
+- (nsresult)setMacCursor:(nsMacCursor*)aMacCursor;
+
+/*! @method createCursor:
+ @abstract Create a Mac native representation of a cursor.
+ @discussion Creates a version of the Mac native representation of this cursor
+ @param aCursor the cursor to create
+ @result the Mac native implementation of the cursor
+*/
++ (nsMacCursor*)createCursor:(enum nsCursor)aCursor;
+
+@end
+
+@implementation nsCursorManager
+
++ (nsCursorManager*)sharedInstance {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!gInstance) {
+ gInstance = [[nsCursorManager alloc] init];
+ }
+ return gInstance;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (void)dispose {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [gInstance release];
+ gInstance = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
++ (nsMacCursor*)createCursor:(enum nsCursor)aCursor {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ switch (aCursor) {
+ SEL cursorSelector;
+ case eCursor_standard:
+ return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor];
+ case eCursor_wait:
+ case eCursor_spinning: {
+ return [nsMacCursor cursorWithCursor:[NSCursor busyButClickableCursor] type:aCursor];
+ }
+ case eCursor_select:
+ return [nsMacCursor cursorWithCursor:[NSCursor IBeamCursor] type:aCursor];
+ case eCursor_hyperlink:
+ return [nsMacCursor cursorWithCursor:[NSCursor pointingHandCursor] type:aCursor];
+ case eCursor_crosshair:
+ return [nsMacCursor cursorWithCursor:[NSCursor crosshairCursor] type:aCursor];
+ case eCursor_move:
+ return [nsMacCursor cursorWithImageNamed:@"move" hotSpot:NSMakePoint(12, 12) type:aCursor];
+ case eCursor_help:
+ return [nsMacCursor cursorWithImageNamed:@"help" hotSpot:NSMakePoint(12, 12) type:aCursor];
+ case eCursor_copy:
+ cursorSelector = @selector(dragCopyCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
+ ? [NSCursor performSelector:cursorSelector]
+ : [NSCursor arrowCursor]
+ type:aCursor];
+ case eCursor_alias:
+ cursorSelector = @selector(dragLinkCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
+ ? [NSCursor performSelector:cursorSelector]
+ : [NSCursor arrowCursor]
+ type:aCursor];
+ case eCursor_context_menu:
+ cursorSelector = @selector(contextualMenuCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
+ ? [NSCursor performSelector:cursorSelector]
+ : [NSCursor arrowCursor]
+ type:aCursor];
+ case eCursor_cell:
+ return [nsMacCursor cursorWithImageNamed:@"cell" hotSpot:NSMakePoint(12, 12) type:aCursor];
+ case eCursor_grab:
+ return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor] type:aCursor];
+ case eCursor_grabbing:
+ return [nsMacCursor cursorWithCursor:[NSCursor closedHandCursor] type:aCursor];
+ case eCursor_zoom_in:
+ return [nsMacCursor cursorWithImageNamed:@"zoomIn" hotSpot:NSMakePoint(10, 10) type:aCursor];
+ case eCursor_zoom_out:
+ return [nsMacCursor cursorWithImageNamed:@"zoomOut" hotSpot:NSMakePoint(10, 10) type:aCursor];
+ case eCursor_vertical_text:
+ return [nsMacCursor cursorWithImageNamed:@"vtIBeam" hotSpot:NSMakePoint(12, 11) type:aCursor];
+ case eCursor_all_scroll:
+ return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor] type:aCursor];
+ case eCursor_not_allowed:
+ case eCursor_no_drop:
+ cursorSelector = @selector(operationNotAllowedCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
+ ? [NSCursor performSelector:cursorSelector]
+ : [NSCursor arrowCursor]
+ type:aCursor];
+ // Resize Cursors:
+ // North
+ case eCursor_n_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeUpCursor] type:aCursor];
+ // North East
+ case eCursor_ne_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNE" hotSpot:NSMakePoint(12, 11) type:aCursor];
+ // East
+ case eCursor_e_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeRightCursor] type:aCursor];
+ // South East
+ case eCursor_se_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeSE" hotSpot:NSMakePoint(12, 12) type:aCursor];
+ // South
+ case eCursor_s_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeDownCursor] type:aCursor];
+ // South West
+ case eCursor_sw_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeSW" hotSpot:NSMakePoint(10, 12) type:aCursor];
+ // West
+ case eCursor_w_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftCursor] type:aCursor];
+ // North West
+ case eCursor_nw_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNW" hotSpot:NSMakePoint(11, 11) type:aCursor];
+ // North & South
+ case eCursor_ns_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeUpDownCursor] type:aCursor];
+ // East & West
+ case eCursor_ew_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftRightCursor] type:aCursor];
+ // North East & South West
+ case eCursor_nesw_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNESW"
+ hotSpot:NSMakePoint(12, 12)
+ type:aCursor];
+ // North West & South East
+ case eCursor_nwse_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNWSE"
+ hotSpot:NSMakePoint(12, 12)
+ type:aCursor];
+ // Column Resize
+ case eCursor_col_resize:
+ return [nsMacCursor cursorWithImageNamed:@"colResize"
+ hotSpot:NSMakePoint(12, 12)
+ type:aCursor];
+ // Row Resize
+ case eCursor_row_resize:
+ return [nsMacCursor cursorWithImageNamed:@"rowResize"
+ hotSpot:NSMakePoint(12, 12)
+ type:aCursor];
+ default:
+ return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)init {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ mCursors = [[NSMutableDictionary alloc] initWithCapacity:25];
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (nsresult)setCursor:(enum nsCursor)aCursor {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsCursor oldType = [mCurrentMacCursor type];
+ [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;
+
+ // 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];
+ nsCursor newType = [aMacCursor type];
+ if (oldType != newType) {
+ if (newType == eCursor_none) {
+ [NSCursor hide];
+ } else if (oldType == eCursor_none) {
+ [NSCursor unhide];
+ }
+ }
+
+ 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;
+ }
+
+ 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..794692e57f
--- /dev/null
+++ b/widget/cocoa/nsDeviceContextSpecX.h
@@ -0,0 +1,50 @@
+/* -*- 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 nsDeviceContextSpecX_h_
+#define nsDeviceContextSpecX_h_
+
+#include "nsIDeviceContextSpec.h"
+#include "nsIPrinter.h"
+#include "nsIPrinterList.h"
+
+#include "nsCOMPtr.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+
+class nsDeviceContextSpecX : public nsIDeviceContextSpec {
+ public:
+ NS_DECL_ISUPPORTS
+
+ nsDeviceContextSpecX();
+
+ NS_IMETHOD Init(nsIWidget* aWidget, nsIPrintSettings* aPS,
+ bool aIsPrintPreview) override;
+ already_AddRefed<PrintTarget> MakePrintTarget() final;
+ NS_IMETHOD BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) override;
+ NS_IMETHOD EndDocument() override;
+ NS_IMETHOD BeginPage() override { return NS_OK; };
+ NS_IMETHOD EndPage() override { return NS_OK; };
+
+ 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.
+#ifdef MOZ_ENABLE_SKIA_PDF
+ nsCOMPtr<nsIFile>
+ mTempFile; // file "print" output is generated to if printing via PDF
+ bool mPrintViaSkPDF;
+#endif
+};
+
+#endif // nsDeviceContextSpecX_h_
diff --git a/widget/cocoa/nsDeviceContextSpecX.mm b/widget/cocoa/nsDeviceContextSpecX.mm
new file mode 100644
index 0000000000..0d8fb4140d
--- /dev/null
+++ b/widget/cocoa/nsDeviceContextSpecX.mm
@@ -0,0 +1,303 @@
+/* -*- 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 "nsDeviceContextSpecX.h"
+
+#import <Cocoa/Cocoa.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <unistd.h>
+
+#ifdef MOZ_ENABLE_SKIA_PDF
+# include "mozilla/gfx/PrintTargetSkPDF.h"
+#endif
+#include "mozilla/gfx/PrintTargetCG.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Telemetry.h"
+
+#include "AppleUtils.h"
+#include "nsCocoaUtils.h"
+#include "nsCRT.h"
+#include "nsCUPSShim.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsILocalFileMac.h"
+#include "nsPaper.h"
+#include "nsPrinterListCUPS.h"
+#include "nsPrintSettingsX.h"
+#include "nsQueryObject.h"
+#include "prenv.h"
+
+// This must be the last include:
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+using mozilla::gfx::IntSize;
+using mozilla::gfx::PrintTarget;
+using mozilla::gfx::PrintTargetCG;
+#ifdef MOZ_ENABLE_SKIA_PDF
+using mozilla::gfx::PrintTargetSkPDF;
+#endif
+using mozilla::gfx::SurfaceFormat;
+
+static LazyLogModule sDeviceContextSpecXLog("DeviceContextSpecX");
+
+//----------------------------------------------------------------------
+// nsDeviceContentSpecX
+
+nsDeviceContextSpecX::nsDeviceContextSpecX()
+ : mPrintSession(nullptr),
+ mPageFormat(nullptr),
+ mPrintSettings(nullptr)
+#ifdef MOZ_ENABLE_SKIA_PDF
+ ,
+ mPrintViaSkPDF(false)
+#endif
+{
+}
+
+nsDeviceContextSpecX::~nsDeviceContextSpecX() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mPrintSession) {
+ ::PMRelease(mPrintSession);
+ }
+ if (mPageFormat) {
+ ::PMRelease(mPageFormat);
+ }
+ if (mPrintSettings) {
+ ::PMRelease(mPrintSettings);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMPL_ISUPPORTS(nsDeviceContextSpecX, nsIDeviceContextSpec)
+
+NS_IMETHODIMP nsDeviceContextSpecX::Init(nsIWidget* aWidget, nsIPrintSettings* aPS,
+ bool aIsPrintPreview) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ RefPtr<nsPrintSettingsX> settings(do_QueryObject(aPS));
+ if (!settings) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ bool toFile;
+ settings->GetPrintToFile(&toFile);
+
+ NSPrintInfo* printInfo = settings->CreateOrCopyPrintInfo();
+ if (!printInfo) {
+ return NS_ERROR_FAILURE;
+ }
+ mPrintSession = static_cast<PMPrintSession>([printInfo PMPrintSession]);
+ mPageFormat = static_cast<PMPageFormat>([printInfo PMPageFormat]);
+ mPrintSettings = static_cast<PMPrintSettings>([printInfo PMPrintSettings]);
+ MOZ_ASSERT(mPrintSession && mPageFormat && mPrintSettings);
+ ::PMRetain(mPrintSession);
+ ::PMRetain(mPageFormat);
+ ::PMRetain(mPrintSettings);
+ [printInfo release];
+
+#ifdef MOZ_ENABLE_SKIA_PDF
+ nsAutoString printViaPdf;
+ mozilla::Preferences::GetString("print.print_via_pdf_encoder", printViaPdf);
+ if (printViaPdf.EqualsLiteral("skia-pdf")) {
+ // Annoyingly, PMPrinterPrintWithFile does not pay attention to the
+ // kPMDestination* value set in the PMPrintSession; it always sends the PDF
+ // to the specified printer. This means that if we create the PDF using
+ // SkPDF then we need to manually handle user actions like "Open PDF in
+ // Preview" and "Save as PDF...".
+ // TODO: Currently we do not support using SkPDF for kPMDestinationFax or
+ // kPMDestinationProcessPDF ("Add PDF to iBooks, etc.), and we only support
+ // it for kPMDestinationFile if the destination file is a PDF.
+ // XXX Could PMWorkflowSubmitPDFWithSettings/PMPrinterPrintWithProvider help?
+ OSStatus status = noErr;
+ PMDestinationType destination;
+ status = ::PMSessionGetDestinationType(mPrintSession, mPrintSettings, &destination);
+ if (status == noErr) {
+ if (destination == kPMDestinationPrinter || destination == kPMDestinationPreview) {
+ mPrintViaSkPDF = true;
+ } else if (destination == kPMDestinationFile) {
+ AutoCFRelease<CFURLRef> destURL(nullptr);
+ status =
+ ::PMSessionCopyDestinationLocation(mPrintSession, mPrintSettings, destURL.receive());
+ if (status == noErr) {
+ AutoCFRelease<CFStringRef> destPathRef =
+ CFURLCopyFileSystemPath(destURL, kCFURLPOSIXPathStyle);
+ NSString* destPath = (NSString*)CFStringRef(destPathRef);
+ NSString* destPathExt = [destPath pathExtension];
+ if ([destPathExt isEqualToString:@"pdf"]) {
+ mPrintViaSkPDF = true;
+ }
+ }
+ }
+ }
+ }
+#endif
+
+ int16_t outputFormat;
+ aPS->GetOutputFormat(&outputFormat);
+
+ if (outputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ // We don't actually currently support/use kOutputFormatPDF on mac, but
+ // this is for completeness in case we add that (we probably need to in
+ // order to support adding links into saved PDFs, for example).
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE, u"pdf_file"_ns, 1);
+ } else {
+ PMDestinationType destination;
+ OSStatus status = ::PMSessionGetDestinationType(mPrintSession, mPrintSettings, &destination);
+ if (status == noErr &&
+ (destination == kPMDestinationFile || destination == kPMDestinationPreview ||
+ destination == kPMDestinationProcessPDF)) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE, u"pdf_file"_ns, 1);
+ } else {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE, u"unknown"_ns, 1);
+ }
+ }
+
+ 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;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecX::EndDocument() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+#ifdef MOZ_ENABLE_SKIA_PDF
+ if (mPrintViaSkPDF) {
+ OSStatus status = noErr;
+
+ nsCOMPtr<nsILocalFileMac> tmpPDFFile = do_QueryInterface(mTempFile);
+ if (!tmpPDFFile) {
+ return NS_ERROR_FAILURE;
+ }
+ AutoCFRelease<CFURLRef> pdfURL(nullptr);
+ // Note that the caller is responsible to release pdfURL according to nsILocalFileMac.idl,
+ // even though we didn't follow the Core Foundation naming conventions here (the method
+ // should've been called CopyCFURL).
+ nsresult rv = tmpPDFFile->GetCFURL(pdfURL.receive());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PMDestinationType destination;
+ status = ::PMSessionGetDestinationType(mPrintSession, mPrintSettings, &destination);
+
+ switch (destination) {
+ case kPMDestinationPrinter: {
+ PMPrinter currentPrinter = NULL;
+ status = ::PMSessionGetCurrentPrinter(mPrintSession, &currentPrinter);
+ if (status != noErr) {
+ return NS_ERROR_FAILURE;
+ }
+ CFStringRef mimeType = CFSTR("application/pdf");
+ status =
+ ::PMPrinterPrintWithFile(currentPrinter, mPrintSettings, mPageFormat, mimeType, pdfURL);
+ break;
+ }
+ case kPMDestinationPreview: {
+ // XXXjwatt Or should we use CocoaFileUtils::RevealFileInFinder(pdfURL);
+ AutoCFRelease<CFStringRef> pdfPath = CFURLCopyFileSystemPath(pdfURL, kCFURLPOSIXPathStyle);
+ NSString* path = (NSString*)CFStringRef(pdfPath);
+ NSWorkspace* ws = [NSWorkspace sharedWorkspace];
+ [ws openFile:path];
+ break;
+ }
+ case kPMDestinationFile: {
+ AutoCFRelease<CFURLRef> destURL(nullptr);
+ status =
+ ::PMSessionCopyDestinationLocation(mPrintSession, mPrintSettings, destURL.receive());
+ if (status == noErr) {
+ AutoCFRelease<CFStringRef> sourcePathRef =
+ CFURLCopyFileSystemPath(pdfURL, kCFURLPOSIXPathStyle);
+ NSString* sourcePath = (NSString*)CFStringRef(sourcePathRef);
+# ifdef DEBUG
+ AutoCFRelease<CFStringRef> destPathRef =
+ CFURLCopyFileSystemPath(destURL, kCFURLPOSIXPathStyle);
+ NSString* destPath = (NSString*)CFStringRef(destPathRef);
+ NSString* destPathExt = [destPath pathExtension];
+ MOZ_ASSERT([destPathExt isEqualToString:@"pdf"],
+ "nsDeviceContextSpecX::Init only allows '.pdf' for now");
+ // We could use /usr/sbin/cupsfilter to convert the PDF to PS, but
+ // currently we don't.
+# endif
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+ if ([fileManager fileExistsAtPath:sourcePath]) {
+ NSURL* src = static_cast<NSURL*>(CFURLRef(pdfURL));
+ NSURL* dest = static_cast<NSURL*>(CFURLRef(destURL));
+ bool ok = [fileManager replaceItemAtURL:dest
+ withItemAtURL:src
+ backupItemName:nil
+ options:NSFileManagerItemReplacementUsingNewMetadataOnly
+ resultingItemURL:nil
+ error:nil];
+ if (!ok) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("nsDeviceContextSpecX::Init doesn't set "
+ "mPrintViaSkPDF for other values");
+ }
+
+ return (status == noErr) ? NS_OK : NS_ERROR_FAILURE;
+ }
+#endif
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsDeviceContextSpecX::GetPaperRect(double* aTop, double* aLeft, double* aBottom,
+ double* aRight) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ PMRect paperRect;
+ ::PMGetAdjustedPaperRect(mPageFormat, &paperRect);
+
+ *aTop = paperRect.top;
+ *aLeft = paperRect.left;
+ *aBottom = paperRect.bottom;
+ *aRight = paperRect.right;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+already_AddRefed<PrintTarget> nsDeviceContextSpecX::MakePrintTarget() {
+ double top, left, bottom, right;
+ GetPaperRect(&top, &left, &bottom, &right);
+ const double width = right - left;
+ const double height = bottom - top;
+ IntSize size = IntSize::Ceil(width, height);
+
+#ifdef MOZ_ENABLE_SKIA_PDF
+ if (mPrintViaSkPDF) {
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mTempFile));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ nsAutoCString tempPath("tmp-printing.pdf");
+ mTempFile->AppendNative(tempPath);
+ rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ mTempFile->GetNativePath(tempPath);
+ auto stream = MakeUnique<SkFILEWStream>(tempPath.get());
+ return PrintTargetSkPDF::CreateOrNull(std::move(stream), size);
+ }
+#endif
+
+ return PrintTargetCG::CreateOrNull(mPrintSession, mPageFormat, mPrintSettings, size);
+}
diff --git a/widget/cocoa/nsDragService.h b/widget/cocoa/nsDragService.h
new file mode 100644
index 0000000000..e3e284fe81
--- /dev/null
+++ b/widget/cocoa/nsDragService.h
@@ -0,0 +1,70 @@
+/* -*- 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 "nsChildView.h"
+
+#include <Cocoa/Cocoa.h>
+
+extern NSString* const kPublicUrlPboardType;
+extern NSString* const kPublicUrlNamePboardType;
+extern NSString* const kUrlsWithTitlesPboardType;
+extern NSString* const kMozWildcardPboardType;
+extern NSString* const kMozCustomTypesPboardType;
+extern NSString* const kMozFileUrlsPboardType;
+
+class nsDragService : public nsBaseDragService {
+ public:
+ nsDragService();
+
+ // nsBaseDragService
+ MOZ_CAN_RUN_SCRIPT virtual nsresult InvokeDragSessionImpl(
+ nsIArray* anArrayTransferables, const mozilla::Maybe<mozilla::CSSIntRegion>& aRegion,
+ uint32_t aActionType) override;
+ // nsIDragService
+ MOZ_CAN_RUN_SCRIPT NS_IMETHOD EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) override;
+ NS_IMETHOD UpdateDragImage(nsINode* aImage, int32_t aImageX, int32_t aImageY) override;
+
+ // nsIDragSession
+ NS_IMETHOD GetData(nsITransferable* aTransferable, uint32_t aItemIndex) override;
+ NS_IMETHOD IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) override;
+ NS_IMETHOD GetNumDropItems(uint32_t* aNumItems) override;
+
+ void DragMovedWithView(NSDraggingSession* aSession, NSPoint aPoint);
+
+ protected:
+ virtual ~nsDragService();
+
+ private:
+ // Creates and returns the drag image for a drag. aImagePoint will be set to
+ // the origin of the drag relative to mNativeDragView.
+ NSImage* ConstructDragImage(nsINode* aDOMNode,
+ const mozilla::Maybe<mozilla::CSSIntRegion>& aRegion,
+ NSPoint* aImagePoint);
+
+ // Creates and returns the drag image for a drag. aPoint should be the origin
+ // of the drag, for example the mouse coordinate of the mousedown event.
+ // aDragRect will be set the area of the drag relative to this.
+ NSImage* ConstructDragImage(nsINode* aDOMNode,
+ const mozilla::Maybe<mozilla::CSSIntRegion>& aRegion,
+ mozilla::CSSIntPoint aPoint, mozilla::LayoutDeviceIntRect* aDragRect);
+
+ bool IsValidType(NSString* availableType, bool allowFileURL);
+ NSString* GetStringForType(NSPasteboardItem* item, const NSString* type,
+ bool allowFileURL = false);
+ NSString* GetTitleForURL(NSPasteboardItem* item);
+ NSString* GetFilePath(NSPasteboardItem* item);
+
+ nsCOMPtr<nsIArray> mDataItems; // only valid for a drag started within gecko
+ ChildView* mNativeDragView;
+ NSEvent* mNativeDragEvent;
+
+ bool mDragImageChanged;
+};
+
+#endif // nsDragService_h_
diff --git a/widget/cocoa/nsDragService.mm b/widget/cocoa/nsDragService.mm
new file mode 100644
index 0000000000..8fedc01968
--- /dev/null
+++ b/widget/cocoa/nsDragService.mm
@@ -0,0 +1,655 @@
+/* -*- 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 "gfxContext.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 "nsCOMPtr.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsLinebreakConverter.h"
+#include "nsINode.h"
+#include "nsRect.h"
+#include "nsPoint.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsIContent.h"
+#include "nsView.h"
+#include "nsCocoaUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxPlatform.h"
+#include "nsDeviceContext.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+extern mozilla::LazyLogModule sCocoaLog;
+
+extern NSPasteboard* globalDragPboard;
+extern ChildView* 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 kPublicUrlPboardType = @"public.url";
+NSString* const kPublicUrlNamePboardType = @"public.url-name";
+NSString* const kUrlsWithTitlesPboardType = @"WebURLsWithTitlesPboardType";
+NSString* const kMozWildcardPboardType = @"org.mozilla.MozillaWildcard";
+NSString* const kMozCustomTypesPboardType = @"org.mozilla.custom-clipdata";
+NSString* const kMozFileUrlsPboardType = @"org.mozilla.file-urls";
+
+nsDragService::nsDragService()
+ : mNativeDragView(nil), mNativeDragEvent(nil), mDragImageChanged(false) {}
+
+nsDragService::~nsDragService() {}
+
+NSImage* nsDragService::ConstructDragImage(nsINode* aDOMNode, const Maybe<CSSIntRegion>& aRegion,
+ NSPoint* aDragPoint) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
+
+ LayoutDeviceIntRect dragRect(0, 0, 20, 20);
+ NSImage* image = ConstructDragImage(mSourceNode, aRegion, mScreenPosition, &dragRect);
+ 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 imageWithSize:size
+ flipped:YES
+ drawingHandler:^BOOL(NSRect dstRect) {
+ [[NSColor grayColor] set];
+ NSBezierPath* path = [NSBezierPath bezierPathWithRect:dstRect];
+ [path setLineWidth:2.0];
+ [path stroke];
+ return YES;
+ }];
+ }
+
+ LayoutDeviceIntPoint pt(dragRect.x, dragRect.YMost());
+ NSPoint point = nsCocoaUtils::DevPixelsToCocoaPoints(pt, scaleFactor);
+ point.y = nsCocoaUtils::FlippedScreenY(point.y);
+
+ point = nsCocoaUtils::ConvertPointFromScreen([mNativeDragView window], point);
+ *aDragPoint = [mNativeDragView convertPoint:point fromView:nil];
+
+ return image;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+NSImage* nsDragService::ConstructDragImage(nsINode* aDOMNode, const Maybe<CSSIntRegion>& aRegion,
+ CSSIntPoint aPoint, LayoutDeviceIntRect* aDragRect) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
+
+ RefPtr<SourceSurface> surface;
+ nsPresContext* pc;
+ nsresult rv = DrawDrag(aDOMNode, aRegion, aPoint, 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(aPoint.x), pc->CSSPixelsToDevPixels(aPoint.y), size,
+ size);
+ }
+
+ if (NS_FAILED(rv) || !surface) return nil;
+
+ uint32_t width = aDragRect->width;
+ uint32_t height = aDragRect->height;
+
+ RefPtr<DataSourceSurface> dataSurface =
+ Factory::CreateDataSourceSurface(IntSize(width, height), SurfaceFormat::B8G8R8A8);
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
+ return nil;
+ }
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
+ BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride, dataSurface->GetFormat());
+ if (!dt) {
+ dataSurface->Unmap();
+ return nil;
+ }
+
+ dt->FillRect(gfx::Rect(0, 0, width, height), SurfacePattern(surface, ExtendMode::CLAMP),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+
+ NSBitmapImageRep* imageRep =
+ [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
+ pixelsWide:width
+ pixelsHigh:height
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bytesPerRow:width * 4
+ bitsPerPixel:32];
+
+ uint8_t* dest = [imageRep bitmapData];
+ for (uint32_t i = 0; i < height; ++i) {
+ uint8_t* src = map.mData + i * map.mStride;
+ for (uint32_t j = 0; j < width; ++j) {
+ // Reduce transparency overall by multipying by a factor. Remember, Alpha
+ // is premultipled here. Also, Quartz likes RGBA, so do that translation as well.
+#ifdef IS_BIG_ENDIAN
+ dest[0] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
+ dest[1] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
+ dest[2] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
+ dest[3] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
+#else
+ dest[0] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
+ dest[1] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
+ dest[2] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
+ dest[3] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
+#endif
+ src += 4;
+ dest += 4;
+ }
+ }
+ dataSurface->Unmap();
+
+ NSImage* image =
+ [[NSImage alloc] initWithSize:NSMakeSize(width / scaleFactor, height / scaleFactor)];
+ [image addRepresentation:imageRep];
+ [imageRep release];
+
+ return [image autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+bool nsDragService::IsValidType(NSString* availableType, bool allowFileURL) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // Prevent exposing fileURL for non-fileURL type.
+ // We need URL provided by dropped webloc file, but don't need file's URL.
+ // kUTTypeFileURL is returned by [NSPasteboard availableTypeFromArray:] for
+ // kPublicUrlPboardType, since it conforms to kPublicUrlPboardType.
+ bool isValid = true;
+ if (!allowFileURL &&
+ [availableType isEqualToString:[UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL]]) {
+ isValid = false;
+ }
+
+ return isValid;
+
+ 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, [UTIHelper stringFromPboardType:kPublicUrlNamePboardType]);
+ 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, [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL], true);
+ if (urlString) {
+ NSURL* url = [NSURL URLWithString:urlString];
+ if (url) {
+ return [url path];
+ }
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+nsresult nsDragService::InvokeDragSessionImpl(nsIArray* aTransferableArray,
+ const Maybe<CSSIntRegion>& aRegion,
+ uint32_t aActionType) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!gLastDragView) {
+ // gLastDragView is non-null between -[ChildView mouseDown:] and -[ChildView mouseUp:].
+ // If we get here with gLastDragView being null, that means that the mouse button has already
+ // been released. In that case we need to abort the drag because the OS won't know where to drop
+ // whatever's being dragged, and we might end up with a stuck drag & drop session.
+ return NS_ERROR_FAILURE;
+ }
+
+ mDataItems = aTransferableArray;
+
+ // Save the transferables away in case a promised file callback is invoked.
+ gDraggedTransferables = aTransferableArray;
+
+ // 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;
+
+ NSPasteboardItem* pbItem = [NSPasteboardItem new];
+ NSMutableArray* types = [NSMutableArray arrayWithCapacity:5];
+
+ if (gDraggedTransferables) {
+ uint32_t count = 0;
+ gDraggedTransferables->GetLength(&count);
+
+ for (uint32_t j = 0; j < count; j++) {
+ nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(aTransferableArray, j);
+ if (!currentTransferable) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Transform the transferable to an NSDictionary
+ NSDictionary* pasteboardOutputDict =
+ nsClipboard::PasteboardDictFromTransferable(currentTransferable);
+ if (!pasteboardOutputDict) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // write everything out to the general pasteboard
+ [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:[UTIHelper stringFromPboardType:kMozWildcardPboardType]];
+ }
+ }
+ [pbItem setDataProvider:mNativeDragView forTypes:types];
+
+ NSPoint draggingPoint;
+ NSImage* image = ConstructDragImage(mSourceNode, aRegion, &draggingPoint);
+
+ NSRect localDragRect = image.alignmentRect;
+ localDragRect.origin.x = draggingPoint.x;
+ localDragRect.origin.y = draggingPoint.y - localDragRect.size.height;
+
+ NSDraggingItem* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pbItem];
+ [pbItem release];
+ [dragItem setDraggingFrame:localDragRect contents:image];
+
+ nsBaseDragService::StartDragSession();
+ nsBaseDragService::OpenDragPopup();
+
+ NSDraggingSession* draggingSession = [mNativeDragView
+ beginDraggingSessionWithItems:[NSArray arrayWithObject:[dragItem autorelease]]
+ event:mNativeDragEvent
+ source:mNativeDragView];
+ draggingSession.animatesToStartingPositionsOnCancelOrFail = YES;
+
+ 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)
+ nsTArray<nsCString> flavors;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ // if this drag originated within Mozilla we should just use the cached data from
+ // when the drag started if possible
+ if (mDataItems) {
+ nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(mDataItems, aItemIndex);
+ if (currentTransferable) {
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+
+ nsCOMPtr<nsISupports> dataSupports;
+ rv = currentTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(dataSupports));
+ if (NS_SUCCEEDED(rv)) {
+ aTransferable->SetTransferData(flavorStr.get(), dataSupports);
+ 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 < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+
+ MOZ_LOG(sCocoaLog, LogLevel::Info,
+ ("nsDragService::GetData: looking for clipboard data of type %s\n", flavorStr.get()));
+
+ NSArray* droppedItems = [globalDragPboard pasteboardItems];
+ if (!droppedItems) {
+ continue;
+ }
+
+ uint32_t itemCount = [droppedItems count];
+ if (aItemIndex >= itemCount) {
+ continue;
+ }
+
+ NSPasteboardItem* item = [droppedItems objectAtIndex:aItemIndex];
+ if (!item) {
+ continue;
+ }
+
+ if (flavorStr.EqualsLiteral(kFileMime)) {
+ NSString* filePath = GetFilePath(item);
+ if (!filePath) continue;
+
+ unsigned int stringLength = [filePath length];
+ unsigned int dataLength = (stringLength + 1) * sizeof(char16_t); // in bytes
+ char16_t* clipboardDataPtr = (char16_t*)malloc(dataLength);
+ if (!clipboardDataPtr) return NS_ERROR_OUT_OF_MEMORY;
+ [filePath getCharacters:reinterpret_cast<unichar*>(clipboardDataPtr)];
+ clipboardDataPtr[stringLength] = 0; // null terminate
+
+ nsCOMPtr<nsIFile> file;
+ rv = NS_NewLocalFile(nsDependentString(clipboardDataPtr), true, getter_AddRefs(file));
+ free(clipboardDataPtr);
+ if (NS_FAILED(rv)) continue;
+
+ aTransferable->SetTransferData(flavorStr.get(), file);
+
+ break;
+ } else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ NSString* availableType =
+ [item availableTypeFromArray:[NSArray arrayWithObject:kMozCustomTypesPboardType]];
+ 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 length:dataLength];
+
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtr, dataLength,
+ getter_AddRefs(genericDataWrapper));
+
+ aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
+ free(clipboardDataPtr);
+ break;
+ }
+
+ NSString* pString = nil;
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ pString = GetStringForType(item, [UTIHelper stringFromPboardType:NSPasteboardTypeString]);
+ } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
+ pString = GetStringForType(item, [UTIHelper stringFromPboardType:NSPasteboardTypeHTML]);
+ } else if (flavorStr.EqualsLiteral(kURLMime)) {
+ pString = GetStringForType(item, [UTIHelper stringFromPboardType:kPublicUrlPboardType]);
+ if (pString) {
+ NSString* title = GetTitleForURL(item);
+ if (!title) {
+ title = pString;
+ }
+ pString = [NSString stringWithFormat:@"%@\n%@", pString, title];
+ }
+ } else if (flavorStr.EqualsLiteral(kURLDataMime)) {
+ pString = GetStringForType(item, [UTIHelper stringFromPboardType:kPublicUrlPboardType]);
+ } else if (flavorStr.EqualsLiteral(kURLDescriptionMime)) {
+ pString = GetTitleForURL(item);
+ } else if (flavorStr.EqualsLiteral(kRTFMime)) {
+ pString = GetStringForType(item, [UTIHelper stringFromPboardType:NSPasteboardTypeRTF]);
+ }
+ 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 length:dataLength];
+
+ // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
+ int32_t signedDataLength = dataLength;
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr,
+ &signedDataLength);
+ dataLength = signedDataLength;
+
+ // skip BOM (Byte Order Mark to distinguish little or big endian)
+ char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
+ if ((dataLength > 2) &&
+ ((clipboardDataPtrNoBOM[0] == 0xFEFF) || (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
+ dataLength -= sizeof(char16_t);
+ clipboardDataPtrNoBOM += 1;
+ }
+
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
+ getter_AddRefs(genericDataWrapper));
+ aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
+ free(clipboardDataPtr);
+ break;
+ }
+
+ // We have never supported this on Mac OS X, we should someday. Normally dragging images
+ // in is accomplished with a file path drag instead of the image data itself.
+ /*
+ if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime)) {
+
+ }
+ */
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *_retval = false;
+
+ if (!globalDragPboard) return NS_ERROR_FAILURE;
+
+ nsDependentCString dataFlavor(aDataFlavor);
+
+ // first see if we have data for this in our cached transferable
+ if (mDataItems) {
+ uint32_t dataItemsCount;
+ mDataItems->GetLength(&dataItemsCount);
+ for (unsigned int i = 0; i < dataItemsCount; i++) {
+ nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(mDataItems, i);
+ if (!currentTransferable) continue;
+
+ nsTArray<nsCString> flavors;
+ nsresult rv = currentTransferable->FlavorsTransferableCanImport(flavors);
+ if (NS_FAILED(rv)) continue;
+
+ for (uint32_t j = 0; j < flavors.Length(); j++) {
+ if (dataFlavor.Equals(flavors[j])) {
+ *_retval = true;
+ return NS_OK;
+ }
+ }
+ }
+ }
+
+ const NSString* type = nil;
+ bool allowFileURL = false;
+ if (dataFlavor.EqualsLiteral(kFileMime)) {
+ type = [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL];
+ allowFileURL = true;
+ } else if (dataFlavor.EqualsLiteral(kUnicodeMime)) {
+ type = [UTIHelper stringFromPboardType:NSPasteboardTypeString];
+ } else if (dataFlavor.EqualsLiteral(kHTMLMime)) {
+ type = [UTIHelper stringFromPboardType:NSPasteboardTypeHTML];
+ } else if (dataFlavor.EqualsLiteral(kURLMime) || dataFlavor.EqualsLiteral(kURLDataMime)) {
+ type = [UTIHelper stringFromPboardType:kPublicUrlPboardType];
+ } else if (dataFlavor.EqualsLiteral(kURLDescriptionMime)) {
+ type = [UTIHelper stringFromPboardType:kPublicUrlNamePboardType];
+ } else if (dataFlavor.EqualsLiteral(kRTFMime)) {
+ type = [UTIHelper stringFromPboardType:NSPasteboardTypeRTF];
+ } else if (dataFlavor.EqualsLiteral(kCustomTypesMime)) {
+ type = [UTIHelper stringFromPboardType:kMozCustomTypesPboardType];
+ }
+
+ 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::UpdateDragImage(nsINode* aImage, int32_t aImageX, int32_t aImageY) {
+ nsBaseDragService::UpdateDragImage(aImage, aImageX, aImageY);
+ mDragImageChanged = true;
+ return NS_OK;
+}
+
+void nsDragService::DragMovedWithView(NSDraggingSession* aSession, NSPoint aPoint) {
+ aPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y);
+
+ // XXX It feels like we should be using the backing scale factor at aPoint
+ // rather than the initial drag view, but I've seen no ill effects of this.
+ CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
+ LayoutDeviceIntPoint devPoint = nsCocoaUtils::CocoaPointsToDevPixels(aPoint, scaleFactor);
+
+ // If the image has changed, call enumerateDraggingItemsWithOptions to get
+ // the item being dragged and update its image.
+ if (mDragImageChanged && mNativeDragView) {
+ mDragImageChanged = false;
+
+ nsPresContext* pc = nullptr;
+ nsCOMPtr<nsIContent> content = do_QueryInterface(mImage);
+ if (content) {
+ pc = content->OwnerDoc()->GetPresContext();
+ }
+
+ if (pc) {
+ void (^changeImageBlock)(NSDraggingItem*, NSInteger, BOOL*) =
+ ^(NSDraggingItem* draggingItem, NSInteger idx, BOOL* stop) {
+ // We never add more than one item right now, but check just in case.
+ if (idx > 0) {
+ return;
+ }
+
+ nsPoint pt = LayoutDevicePixel::ToAppUnits(
+ devPoint, pc->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
+ CSSIntPoint screenPoint = CSSIntPoint(nsPresContext::AppUnitsToIntCSSPixels(pt.x),
+ nsPresContext::AppUnitsToIntCSSPixels(pt.y));
+
+ // Create a new image; if one isn't returned don't change the current one.
+ LayoutDeviceIntRect newRect;
+ NSImage* image = ConstructDragImage(mSourceNode, Nothing(), screenPoint, &newRect);
+ if (image) {
+ NSRect draggingRect = nsCocoaUtils::GeckoRectToCocoaRectDevPix(newRect, scaleFactor);
+ [draggingItem setDraggingFrame:draggingRect contents:image];
+ }
+ };
+
+ [aSession enumerateDraggingItemsWithOptions:NSDraggingItemEnumerationConcurrent
+ forView:nil
+ classes:[NSArray arrayWithObject:[NSPasteboardItem class]]
+ searchOptions:@{}
+ usingBlock:changeImageBlock];
+ }
+ }
+
+ DragMoved(devPoint.x, devPoint.y);
+}
+
+NS_IMETHODIMP
+nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
+ 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, aKeyModifiers);
+ 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..2b1c60bdae
--- /dev/null
+++ b/widget/cocoa/nsFilePicker.h
@@ -0,0 +1,72 @@
+/* -*- 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 "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 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;
+ nsresult Show(int16_t* _retval) 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(bool inAllowMultiple, nsCOMArray<nsIFile>& outFiles);
+ int16_t GetLocalFolder(nsIFile** outFile);
+ int16_t PutLocalFile(nsIFile** outFile);
+
+ void SetDialogTitle(const nsString& inTitle, id aDialog);
+ NSString* PanelDefaultDirectory();
+ NSView* GetAccessoryView();
+
+ nsString mTitle;
+ nsCOMArray<nsIFile> mFiles;
+ nsString mDefaultFilename;
+
+ nsTArray<nsString> mFilters;
+ nsTArray<nsString> mTitles;
+
+ int32_t mSelectedTypeIndex;
+};
+
+#endif // nsFilePicker_h_
diff --git a/widget/cocoa/nsFilePicker.mm b/widget/cocoa/nsFilePicker.mm
new file mode 100644
index 0000000000..ef7acb6d45
--- /dev/null
+++ b/widget/cocoa/nsFilePicker.mm
@@ -0,0 +1,638 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsFilePicker.h"
+#include "nsCOMPtr.h"
+#include "nsReadableUtils.h"
+#include "nsNetUtil.h"
+#include "nsIFile.h"
+#include "nsILocalFileMac.h"
+#include "nsArrayEnumerator.h"
+#include "nsIStringBundle.h"
+#include "nsCocoaUtils.h"
+#include "mozilla/Preferences.h"
+
+// This must be included last:
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+
+const float kAccessoryViewPadding = 5;
+const int kSaveTypeControlTag = 1;
+
+static bool gCallSecretHiddenFileAPI = false;
+const char kShowHiddenFilesPref[] = "filepicker.showHiddenFiles";
+
+/**
+ * This class is an observer of NSPopUpButton selection change.
+ */
+@interface NSPopUpButtonObserver : NSObject {
+ NSPopUpButton* mPopUpButton;
+ NSOpenPanel* mOpenPanel;
+ nsFilePicker* mFilePicker;
+}
+- (void)setPopUpButton:(NSPopUpButton*)aPopUpButton;
+- (void)setOpenPanel:(NSOpenPanel*)aOpenPanel;
+- (void)setFilePicker:(nsFilePicker*)aFilePicker;
+- (void)menuChangedItem:(NSNotification*)aSender;
+@end
+
+NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
+
+// We never want to call the secret show hidden files API unless the pref
+// has been set. Once the pref has been set we always need to call it even
+// if it disappears so that we stop showing hidden files if a user deletes
+// the pref. If the secret API was used once and things worked out it should
+// continue working for subsequent calls so the user is at no more risk.
+static void SetShowHiddenFileState(NSSavePanel* panel) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ bool show = false;
+ if (NS_SUCCEEDED(Preferences::GetBool(kShowHiddenFilesPref, &show))) {
+ gCallSecretHiddenFileAPI = true;
+ }
+
+ if (gCallSecretHiddenFileAPI) {
+ // invoke a method to get a Cocoa-internal nav view
+ SEL navViewSelector = @selector(_navView);
+ NSMethodSignature* navViewSignature = [panel methodSignatureForSelector:navViewSelector];
+ if (!navViewSignature) return;
+ NSInvocation* navViewInvocation = [NSInvocation invocationWithMethodSignature:navViewSignature];
+ [navViewInvocation setSelector:navViewSelector];
+ [navViewInvocation setTarget:panel];
+ [navViewInvocation invoke];
+
+ // get the returned nav view
+ id navView = nil;
+ [navViewInvocation getReturnValue:&navView];
+
+ // invoke the secret show hidden file state method on the nav view
+ SEL showHiddenFilesSelector = @selector(setShowsHiddenFiles:);
+ NSMethodSignature* showHiddenFilesSignature =
+ [navView methodSignatureForSelector:showHiddenFilesSelector];
+ if (!showHiddenFilesSignature) return;
+ NSInvocation* showHiddenFilesInvocation =
+ [NSInvocation invocationWithMethodSignature:showHiddenFilesSignature];
+ [showHiddenFilesInvocation setSelector:showHiddenFilesSelector];
+ [showHiddenFilesInvocation setTarget:navView];
+ [showHiddenFilesInvocation setArgument:&show atIndex:2];
+ [showHiddenFilesInvocation invoke];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsFilePicker::nsFilePicker() : mSelectedTypeIndex(0) {}
+
+nsFilePicker::~nsFilePicker() {}
+
+void nsFilePicker::InitNative(nsIWidget* aParent, const nsAString& aTitle) { mTitle = aTitle; }
+
+NSView* nsFilePicker::GetAccessoryView() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSView* accessoryView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)] autorelease];
+
+ // Set a label's default value.
+ NSString* label = @"Format:";
+
+ // Try to get the localized string.
+ nsCOMPtr<nsIStringBundleService> sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv =
+ sbs->CreateBundle("chrome://global/locale/filepicker.properties", getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString locaLabel;
+ rv = bundle->GetStringFromName("formatLabel", locaLabel);
+ if (NS_SUCCEEDED(rv)) {
+ label = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(locaLabel.get())
+ length:locaLabel.Length()];
+ }
+ }
+
+ // set up label text field
+ NSTextField* textField = [[[NSTextField alloc] init] autorelease];
+ [textField setEditable:NO];
+ [textField setSelectable:NO];
+ [textField setDrawsBackground:NO];
+ [textField setBezeled:NO];
+ [textField setBordered:NO];
+ [textField setFont:[NSFont labelFontOfSize:13.0]];
+ [textField setStringValue:label];
+ [textField setTag:0];
+ [textField sizeToFit];
+
+ // set up popup button
+ NSPopUpButton* popupButton = [[[NSPopUpButton alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)
+ pullsDown:NO] autorelease];
+ uint32_t numMenuItems = mTitles.Length();
+ for (uint32_t i = 0; i < numMenuItems; i++) {
+ const nsString& currentTitle = mTitles[i];
+ NSString* titleString;
+ if (currentTitle.IsEmpty()) {
+ const nsString& currentFilter = mFilters[i];
+ titleString =
+ [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(currentFilter.get())
+ length:currentFilter.Length()];
+ } else {
+ titleString =
+ [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(currentTitle.get())
+ length:currentTitle.Length()];
+ }
+ [popupButton addItemWithTitle:titleString];
+ [titleString release];
+ }
+ if (mSelectedTypeIndex >= 0 && (uint32_t)mSelectedTypeIndex < numMenuItems)
+ [popupButton selectItemAtIndex:mSelectedTypeIndex];
+ [popupButton setTag:kSaveTypeControlTag];
+ [popupButton sizeToFit]; // we have to do sizeToFit to get the height calculated for us
+ // This is just a default width that works well, doesn't truncate the vast majority of
+ // things that might end up in the menu.
+ [popupButton setFrameSize:NSMakeSize(180, [popupButton frame].size.height)];
+
+ // position everything based on control sizes with kAccessoryViewPadding pix padding
+ // on each side kAccessoryViewPadding pix horizontal padding between controls
+ float greatestHeight = [textField frame].size.height;
+ if ([popupButton frame].size.height > greatestHeight)
+ greatestHeight = [popupButton frame].size.height;
+ float totalViewHeight = greatestHeight + kAccessoryViewPadding * 2;
+ float totalViewWidth =
+ [textField frame].size.width + [popupButton frame].size.width + kAccessoryViewPadding * 3;
+ [accessoryView setFrameSize:NSMakeSize(totalViewWidth, totalViewHeight)];
+
+ float textFieldOriginY =
+ ((greatestHeight - [textField frame].size.height) / 2 + 1) + kAccessoryViewPadding;
+ [textField setFrameOrigin:NSMakePoint(kAccessoryViewPadding, textFieldOriginY)];
+
+ float popupOriginX = [textField frame].size.width + kAccessoryViewPadding * 2;
+ float popupOriginY =
+ ((greatestHeight - [popupButton frame].size.height) / 2) + kAccessoryViewPadding;
+ [popupButton setFrameOrigin:NSMakePoint(popupOriginX, popupOriginY)];
+
+ [accessoryView addSubview:textField];
+ [accessoryView addSubview:popupButton];
+ return accessoryView;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// Display the file dialog
+nsresult nsFilePicker::Show(int16_t* retval) {
+ NS_ENSURE_ARG_POINTER(retval);
+
+ *retval = returnCancel;
+
+ int16_t userClicksOK = returnCancel;
+
+ mFiles.Clear();
+ nsCOMPtr<nsIFile> theFile;
+
+ // Note that GetLocalFolder shares a lot of code with GetLocalFiles.
+ // Could combine the functions and just pass the mode in.
+ switch (mMode) {
+ case modeOpen:
+ userClicksOK = GetLocalFiles(false, mFiles);
+ break;
+
+ case modeOpenMultiple:
+ userClicksOK = GetLocalFiles(true, mFiles);
+ break;
+
+ case modeSave:
+ userClicksOK = PutLocalFile(getter_AddRefs(theFile));
+ break;
+
+ case modeGetFolder:
+ userClicksOK = GetLocalFolder(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(bool inAllowMultiple, nsCOMArray<nsIFile>& outFiles) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ int16_t retVal = (int16_t)returnCancel;
+ NSOpenPanel* thePanel = [NSOpenPanel openPanel];
+
+ SetShowHiddenFileState(thePanel);
+
+ // Set the options for how the get file dialog will appear
+ SetDialogTitle(mTitle, thePanel);
+ [thePanel setAllowsMultipleSelection:inAllowMultiple];
+ [thePanel setCanSelectHiddenExtension:YES];
+ [thePanel setCanChooseDirectories:NO];
+ [thePanel setCanChooseFiles:YES];
+ [thePanel setResolvesAliases:YES];
+
+ // Get filters
+ // filters may be null, if we should allow all file types.
+ NSArray* filters = GetFilterList();
+
+ // set up default directory
+ NSString* theDir = PanelDefaultDirectory();
+
+ // if this is the "Choose application..." dialog, and no other start
+ // dir has been set, then use the Applications folder.
+ if (!theDir) {
+ if (filters && [filters count] == 1 &&
+ [(NSString*)[filters objectAtIndex:0] isEqualToString:@"app"])
+ theDir = @"/Applications/";
+ else
+ theDir = @"";
+ }
+
+ if (theDir) {
+ [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
+ }
+
+ int result;
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ if (mFilters.Length() > 1) {
+ // [NSURL initWithString:] (below) throws an exception if URLString is nil.
+
+ NSPopUpButtonObserver* observer = [[NSPopUpButtonObserver alloc] init];
+
+ NSView* accessoryView = GetAccessoryView();
+ [thePanel setAccessoryView:accessoryView];
+
+ [observer setPopUpButton:[accessoryView viewWithTag:kSaveTypeControlTag]];
+ [observer setOpenPanel:thePanel];
+ [observer setFilePicker:this];
+
+ [[NSNotificationCenter defaultCenter] addObserver:observer
+ selector:@selector(menuChangedItem:)
+ name:NSMenuWillSendActionNotification
+ object:nil];
+
+ UpdatePanelFileTypes(thePanel, filters);
+ result = [thePanel runModal];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:observer];
+ [observer release];
+ } else {
+ // If we show all file types, also "expose" bundles' contents.
+ if (!filters) {
+ [thePanel setTreatsFilePackagesAsDirectories:YES];
+ }
+ [thePanel setAllowedFileTypes:filters];
+ result = [thePanel runModal];
+ }
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ if (result == NSFileHandlingPanelCancelButton) return retVal;
+
+ // Converts data from a NSArray of NSURL to the returned format.
+ // We should be careful to not call [thePanel URLs] more than once given that
+ // it creates a new array each time.
+ // We are using Fast Enumeration, thus the NSURL array is created once then
+ // iterated.
+ for (NSURL* url in [thePanel URLs]) {
+ if (!url) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> localFile;
+ NS_NewLocalFile(u""_ns, true, getter_AddRefs(localFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
+ if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)url))) {
+ outFiles.AppendObject(localFile);
+ }
+ }
+
+ if (outFiles.Count() > 0) retVal = returnOK;
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+// Use OpenPanel to do a GetFolder. Returns |returnOK| if the user presses OK in the dialog.
+int16_t nsFilePicker::GetLocalFolder(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(mTitle, thePanel);
+ [thePanel setAllowsMultipleSelection:NO];
+ [thePanel setCanSelectHiddenExtension:YES];
+ [thePanel setCanChooseDirectories:YES];
+ [thePanel setCanChooseFiles:NO];
+ [thePanel setResolvesAliases:YES];
+ [thePanel setCanCreateDirectories:YES];
+
+ // packages != folders
+ [thePanel setTreatsFilePackagesAsDirectories:NO];
+
+ // set up default directory
+ NSString* theDir = PanelDefaultDirectory();
+ if (theDir) {
+ [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
+ }
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ int result = [thePanel runModal];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ if (result == NSFileHandlingPanelCancelButton) return retVal;
+
+ // get the path for the folder (we allow just 1, so that's all we get)
+ NSURL* theURL = [[thePanel URLs] objectAtIndex:0];
+ if (theURL) {
+ nsCOMPtr<nsIFile> localFile;
+ NS_NewLocalFile(u""_ns, true, getter_AddRefs(localFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
+ if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)theURL))) {
+ *outFile = localFile;
+ NS_ADDREF(*outFile);
+ retVal = returnOK;
+ }
+ }
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+// Returns |returnOK| if the user presses OK in the dialog.
+int16_t nsFilePicker::PutLocalFile(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(mTitle, 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*)mDefaultFilename.get()
+ length:mDefaultFilename.Length()];
+
+ // Set up the allowed type. This prevents the extension from being selected.
+ NSString* extension = defaultFilename.pathExtension;
+ if (extension.length != 0) {
+ thePanel.allowedFileTypes = @[ extension ];
+ }
+ // Allow users to change the extension.
+ thePanel.allowsOtherFileTypes = YES;
+
+ // If extensions are hidden and we’re saving a file with multiple extensions,
+ // only the last extension will be hidden in the panel (".tar.gz" will become
+ // ".tar"). If the remaining extension is known, the OS will think that we're
+ // trying to add a non-default extension. To avoid the confusion, we ensure
+ // that all extensions are shown in the panel if the remaining extension is
+ // known by the OS.
+ NSString* fileName = [[defaultFilename lastPathComponent] stringByDeletingPathExtension];
+ NSString* otherExtension = fileName.pathExtension;
+ if (otherExtension.length != 0) {
+ // There's another extension here. Get the UTI.
+ CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
+ (CFStringRef)otherExtension, NULL);
+ if (type) {
+ if (!CFStringHasPrefix(type, CFSTR("dyn."))) {
+ // We have a UTI, otherwise the type would have a "dyn." prefix. Ensure
+ // extensions are shown in the panel.
+ [thePanel setExtensionHidden:NO];
+ }
+ CFRelease(type);
+ }
+ }
+
+ // set up default directory
+ NSString* theDir = PanelDefaultDirectory();
+ if (theDir) {
+ [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
+ }
+
+ // load the panel
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ [thePanel setNameFieldStringValue:defaultFilename];
+ int result = [thePanel runModal];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+ if (result == NSFileHandlingPanelCancelButton) return retVal;
+
+ // get the save type
+ NSPopUpButton* popupButton = [accessoryView viewWithTag:kSaveTypeControlTag];
+ if (popupButton) {
+ mSelectedTypeIndex = [popupButton indexOfSelectedItem];
+ }
+
+ NSURL* fileURL = [thePanel URL];
+ if (fileURL) {
+ nsCOMPtr<nsIFile> localFile;
+ NS_NewLocalFile(u""_ns, true, getter_AddRefs(localFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
+ if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)fileURL))) {
+ *outFile = localFile;
+ NS_ADDREF(*outFile);
+ // We tell if we are replacing or not by just looking to see if the file exists.
+ // The user could not have hit OK and not meant to replace the file.
+ if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]])
+ retVal = returnReplace;
+ else
+ retVal = returnOK;
+ }
+ }
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+NSArray* nsFilePicker::GetFilterList() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!mFilters.Length()) {
+ return nil;
+ }
+
+ if (mFilters.Length() <= (uint32_t)mSelectedTypeIndex) {
+ NS_WARNING("An out of range index has been selected. Using the first index instead.");
+ mSelectedTypeIndex = 0;
+ }
+
+ const nsString& filterWide = mFilters[mSelectedTypeIndex];
+ if (!filterWide.Length()) {
+ return nil;
+ }
+
+ if (filterWide.Equals(u"*"_ns)) {
+ return nil;
+ }
+
+ // The extensions in filterWide are in the format "*.ext" but are expected
+ // in the format "ext" by NSOpenPanel. So we need to filter some characters.
+ NSMutableString* filterString = [[[NSMutableString alloc]
+ initWithString:[NSString
+ stringWithCharacters:reinterpret_cast<const unichar*>(filterWide.get())
+ length:filterWide.Length()]] autorelease];
+ NSCharacterSet* set = [NSCharacterSet characterSetWithCharactersInString:@". *"];
+ NSRange range = [filterString rangeOfCharacterFromSet:set];
+ while (range.length) {
+ [filterString replaceCharactersInRange:range withString:@""];
+ range = [filterString rangeOfCharacterFromSet:set];
+ }
+
+ return
+ [[[NSArray alloc] initWithArray:[filterString componentsSeparatedByString:@";"]] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// Sets the dialog title to whatever it should be. If it fails, eh,
+// the OS will provide a sensible default.
+void nsFilePicker::SetDialogTitle(const nsString& inTitle, id aPanel) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [aPanel setTitle:[NSString stringWithCharacters:(const unichar*)inTitle.get()
+ length:inTitle.Length()]];
+
+ if (!mOkButtonLabel.IsEmpty()) {
+ [aPanel setPrompt:[NSString stringWithCharacters:(const unichar*)mOkButtonLabel.get()
+ length:mOkButtonLabel.Length()]];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Converts path from an nsIFile into a NSString path
+// If it fails, returns an empty string.
+NSString* nsFilePicker::PanelDefaultDirectory() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSString* directory = nil;
+ if (mDisplayDirectory) {
+ nsAutoString pathStr;
+ mDisplayDirectory->GetPath(pathStr);
+ directory =
+ [[[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(pathStr.get())
+ length:pathStr.Length()] autorelease];
+ }
+ return directory;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+NS_IMETHODIMP nsFilePicker::GetFile(nsIFile** aFile) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ *aFile = nullptr;
+
+ // just return the first file
+ if (mFiles.Count() > 0) {
+ *aFile = mFiles.ObjectAt(0);
+ NS_IF_ADDREF(*aFile);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFilePicker::GetFileURL(nsIURI** aFileURL) {
+ NS_ENSURE_ARG_POINTER(aFileURL);
+ *aFileURL = nullptr;
+
+ if (mFiles.Count() == 0) return NS_OK;
+
+ return NS_NewFileURI(aFileURL, mFiles.ObjectAt(0));
+}
+
+NS_IMETHODIMP nsFilePicker::GetFiles(nsISimpleEnumerator** aFiles) {
+ return NS_NewArrayEnumerator(aFiles, mFiles);
+}
+
+NS_IMETHODIMP nsFilePicker::SetDefaultString(const nsAString& aString) {
+ mDefaultFilename = 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(u"*.app"_ns);
+ } 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/nsLookAndFeel.h b/widget/cocoa/nsLookAndFeel.h
new file mode 100644
index 0000000000..91eee7387b
--- /dev/null
+++ b/widget/cocoa/nsLookAndFeel.h
@@ -0,0 +1,93 @@
+/* -*- 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 nsLookAndFeel_h_
+#define nsLookAndFeel_h_
+#include "nsXPLookAndFeel.h"
+
+class nsLookAndFeel final : public nsXPLookAndFeel {
+ public:
+ explicit nsLookAndFeel(const LookAndFeelCache* aCache);
+ virtual ~nsLookAndFeel();
+
+ void NativeInit() final;
+ virtual void RefreshImpl() override;
+ nsresult NativeGetColor(ColorID aID, nscolor& aResult) override;
+ nsresult NativeGetInt(IntID aID, int32_t& aResult) override;
+ nsresult NativeGetFloat(FloatID aID, float& aResult) override;
+ bool NativeGetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) override;
+
+ virtual char16_t GetPasswordCharacterImpl() override {
+ // unicode value for the bullet character, used for password textfields.
+ return 0x2022;
+ }
+
+ static bool UseOverlayScrollbars();
+
+ LookAndFeelCache GetCacheImpl() override;
+ void SetCacheImpl(const LookAndFeelCache& aCache) override;
+
+ protected:
+ void DoSetCache(const LookAndFeelCache& aCache);
+ static bool AllowOverlayScrollbarsOverlap();
+
+ static bool SystemWantsDarkTheme();
+ static nscolor ProcessSelectionBackground(nscolor aColor);
+
+ private:
+ int32_t mUseOverlayScrollbars;
+ bool mUseOverlayScrollbarsCached;
+
+ int32_t mAllowOverlayScrollbarsOverlap;
+ bool mAllowOverlayScrollbarsOverlapCached;
+
+ int32_t mSystemUsesDarkTheme;
+ bool mSystemUsesDarkThemeCached;
+
+ int32_t mPrefersReducedMotion = -1;
+ bool mPrefersReducedMotionCached = false;
+
+ nscolor mColorTextSelectBackground;
+ nscolor mColorTextSelectBackgroundDisabled;
+ nscolor mColorHighlight;
+ nscolor mColorMenuHover;
+ nscolor mColorTextSelectForeground;
+ nscolor mColorMenuHoverText;
+ nscolor mColorButtonText;
+ bool mHasColorButtonText;
+ nscolor mColorButtonHoverText;
+ nscolor mColorText;
+ nscolor mColorWindowText;
+ nscolor mColorActiveCaption;
+ nscolor mColorActiveBorder;
+ nscolor mColorGrayText;
+ nscolor mColorInactiveBorder;
+ nscolor mColorInactiveCaption;
+ nscolor mColorScrollbar;
+ nscolor mColorThreeDHighlight;
+ nscolor mColorMenu;
+ nscolor mColorWindowFrame;
+ nscolor mColorFieldText;
+ nscolor mColorDialog;
+ nscolor mColorDialogText;
+ nscolor mColorDragTargetZone;
+ nscolor mColorChromeActive;
+ nscolor mColorChromeInactive;
+ nscolor mColorFocusRing;
+ nscolor mColorTextSelect;
+ nscolor mColorDisabledToolbarText;
+ nscolor mColorMenuSelect;
+ nscolor mColorCellHighlight;
+ nscolor mColorEvenTreeRow;
+ nscolor mColorOddTreeRow;
+ nscolor mColorActiveSourceListSelection;
+
+ bool mInitialized;
+
+ void EnsureInit();
+};
+
+#endif // nsLookAndFeel_h_
diff --git a/widget/cocoa/nsLookAndFeel.mm b/widget/cocoa/nsLookAndFeel.mm
new file mode 100644
index 0000000000..17306a6f7b
--- /dev/null
+++ b/widget/cocoa/nsLookAndFeel.mm
@@ -0,0 +1,777 @@
+/* -*- 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 "nsLookAndFeel.h"
+#include "nsCocoaFeatures.h"
+#include "nsNativeThemeColors.h"
+#include "nsStyleConsts.h"
+#include "nsCocoaFeatures.h"
+#include "nsIContent.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+#include "gfxPlatformMac.h"
+#include "nsCSSColorUtils.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/widget/WidgetMessageUtils.h"
+
+#import <Cocoa/Cocoa.h>
+
+// This must be included last:
+#include "nsObjCExceptions.h"
+
+// Available from 10.12 onwards; test availability at runtime before using
+@interface NSWorkspace (AvailableSinceSierra)
+@property(readonly) BOOL accessibilityDisplayShouldReduceMotion;
+@end
+
+nsLookAndFeel::nsLookAndFeel(const LookAndFeelCache* aCache)
+ : nsXPLookAndFeel(),
+ mUseOverlayScrollbars(-1),
+ mUseOverlayScrollbarsCached(false),
+ mAllowOverlayScrollbarsOverlap(-1),
+ mAllowOverlayScrollbarsOverlapCached(false),
+ mSystemUsesDarkTheme(-1),
+ mSystemUsesDarkThemeCached(false),
+ mColorTextSelectBackground(0),
+ mColorTextSelectBackgroundDisabled(0),
+ mColorHighlight(0),
+ mColorMenuHover(0),
+ mColorTextSelectForeground(0),
+ mColorMenuHoverText(0),
+ mColorButtonText(0),
+ mHasColorButtonText(false),
+ mColorButtonHoverText(0),
+ mColorText(0),
+ mColorWindowText(0),
+ mColorActiveCaption(0),
+ mColorActiveBorder(0),
+ mColorGrayText(0),
+ mColorInactiveBorder(0),
+ mColorInactiveCaption(0),
+ mColorScrollbar(0),
+ mColorThreeDHighlight(0),
+ mColorMenu(0),
+ mColorWindowFrame(0),
+ mColorFieldText(0),
+ mColorDialog(0),
+ mColorDialogText(0),
+ mColorDragTargetZone(0),
+ mColorChromeActive(0),
+ mColorChromeInactive(0),
+ mColorFocusRing(0),
+ mColorTextSelect(0),
+ mColorDisabledToolbarText(0),
+ mColorMenuSelect(0),
+ mColorCellHighlight(0),
+ mColorEvenTreeRow(0),
+ mColorOddTreeRow(0),
+ mColorActiveSourceListSelection(0),
+ mInitialized(false) {
+ if (aCache) {
+ DoSetCache(*aCache);
+ }
+}
+
+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));
+}
+
+void nsLookAndFeel::NativeInit() { EnsureInit(); }
+
+void nsLookAndFeel::RefreshImpl() {
+ nsXPLookAndFeel::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;
+ mPrefersReducedMotionCached = false;
+ mSystemUsesDarkThemeCached = false;
+ }
+
+ // Fetch colors next time they are requested.
+ mInitialized = false;
+}
+
+// Turns an opaque selection color into a partially transparent selection color,
+// which usually leads to better contrast with the text color and which should
+// look more visually appealing in most contexts.
+// The idea is that the text and its regular, non-selected background are
+// usually chosen in such a way that they contrast well. Making the selection
+// color partially transparent causes the selection color to mix with the text's
+// regular background, so the end result will often have better contrast with
+// the text than an arbitrary opaque selection color.
+// The motivating example for this is the URL bar text field in the dark theme:
+// White text on a light blue selection color has very bad contrast, whereas
+// white text on dark blue (which what you get if you mix partially-transparent
+// light blue with the black textbox background) has much better contrast.
+nscolor nsLookAndFeel::ProcessSelectionBackground(nscolor aColor) {
+ uint16_t hue, sat, value;
+ uint8_t alpha;
+ nscolor resultColor = aColor;
+ NS_RGB2HSV(resultColor, hue, sat, value, alpha);
+ int factor = 2;
+ alpha = alpha / factor;
+ if (sat > 0) {
+ // The color is not a shade of grey, restore the saturation taken away by
+ // the transparency.
+ sat = mozilla::clamped(sat * factor, 0, 255);
+ } else {
+ // The color is a shade of grey, find the value that looks equivalent
+ // on a white background with the given opacity.
+ value = mozilla::clamped(255 - (255 - value) * factor, 0, 255);
+ }
+ NS_HSV2RGB(resultColor, hue, sat, value, alpha);
+ return resultColor;
+}
+
+nsresult nsLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor) {
+ EnsureInit();
+
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ case ColorID::WindowBackground:
+ aColor = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::WindowForeground:
+ aColor = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::WidgetBackground:
+ aColor = NS_RGB(0xdd, 0xdd, 0xdd);
+ break;
+ case ColorID::WidgetForeground:
+ aColor = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::WidgetSelectBackground:
+ aColor = NS_RGB(0x80, 0x80, 0x80);
+ break;
+ case ColorID::WidgetSelectForeground:
+ aColor = NS_RGB(0x00, 0x00, 0x80);
+ break;
+ case ColorID::Widget3DHighlight:
+ aColor = NS_RGB(0xa0, 0xa0, 0xa0);
+ break;
+ case ColorID::Widget3DShadow:
+ aColor = NS_RGB(0x40, 0x40, 0x40);
+ break;
+ case ColorID::TextBackground:
+ aColor = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::TextForeground:
+ aColor = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::TextSelectBackground:
+ aColor = ProcessSelectionBackground(mColorTextSelectBackground);
+ break;
+ // This is used to gray out the selection when it's not focused. Used with
+ // nsISelectionController::SELECTION_DISABLED.
+ case ColorID::TextSelectBackgroundDisabled:
+ aColor = ProcessSelectionBackground(mColorTextSelectBackgroundDisabled);
+ break;
+ case ColorID::Highlight: // CSS2 color
+ aColor = mColorHighlight;
+ break;
+ case ColorID::MozMenuhover:
+ aColor = mColorMenuHover;
+ break;
+ case ColorID::TextSelectForeground:
+ aColor = mColorTextSelectForeground;
+ break;
+ case ColorID::Highlighttext: // CSS2 color
+ case ColorID::MozMenuhovertext:
+ aColor = mColorMenuHoverText;
+ break;
+ case ColorID::IMESelectedRawTextBackground:
+ case ColorID::IMESelectedConvertedTextBackground:
+ case ColorID::IMERawInputBackground:
+ case ColorID::IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ break;
+ case ColorID::IMESelectedRawTextForeground:
+ case ColorID::IMESelectedConvertedTextForeground:
+ case ColorID::IMERawInputForeground:
+ case ColorID::IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case ColorID::IMERawInputUnderline:
+ case ColorID::IMEConvertedTextUnderline:
+ aColor = NS_40PERCENT_FOREGROUND_COLOR;
+ break;
+ case ColorID::IMESelectedRawTextUnderline:
+ case ColorID::IMESelectedConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case ColorID::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 ColorID::MozMacButtonactivetext:
+ case ColorID::MozMacDefaultbuttontext:
+ if (mHasColorButtonText) {
+ aColor = mColorButtonText;
+ break;
+ }
+ // Otherwise fall through and return the regular button text:
+ [[fallthrough]];
+ case ColorID::Buttontext:
+ case ColorID::MozButtonhovertext:
+ aColor = mColorButtonHoverText;
+ break;
+ case ColorID::Captiontext:
+ case ColorID::Menutext:
+ case ColorID::Infotext:
+ case ColorID::MozMenubartext:
+ aColor = mColorText;
+ break;
+ case ColorID::Windowtext:
+ aColor = mColorWindowText;
+ break;
+ case ColorID::Activecaption:
+ aColor = mColorActiveCaption;
+ break;
+ case ColorID::Activeborder:
+ aColor = mColorActiveBorder;
+ break;
+ case ColorID::Appworkspace:
+ aColor = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::Background:
+ aColor = NS_RGB(0x63, 0x63, 0xCE);
+ break;
+ case ColorID::Buttonface:
+ case ColorID::MozButtonhoverface:
+ aColor = NS_RGB(0xF0, 0xF0, 0xF0);
+ break;
+ case ColorID::Buttonhighlight:
+ aColor = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::Buttonshadow:
+ aColor = NS_RGB(0xDC, 0xDC, 0xDC);
+ break;
+ case ColorID::Graytext:
+ aColor = mColorGrayText;
+ break;
+ case ColorID::Inactiveborder:
+ aColor = mColorInactiveBorder;
+ break;
+ case ColorID::Inactivecaption:
+ aColor = mColorInactiveCaption;
+ break;
+ case ColorID::Inactivecaptiontext:
+ aColor = NS_RGB(0x45, 0x45, 0x45);
+ break;
+ case ColorID::Scrollbar:
+ aColor = mColorScrollbar;
+ break;
+ case ColorID::Threeddarkshadow:
+ aColor = NS_RGB(0xDC, 0xDC, 0xDC);
+ break;
+ case ColorID::Threedshadow:
+ aColor = NS_RGB(0xE0, 0xE0, 0xE0);
+ break;
+ case ColorID::Threedface:
+ aColor = NS_RGB(0xF0, 0xF0, 0xF0);
+ break;
+ case ColorID::Threedhighlight:
+ aColor = mColorThreeDHighlight;
+ break;
+ case ColorID::Threedlightshadow:
+ aColor = NS_RGB(0xDA, 0xDA, 0xDA);
+ break;
+ case ColorID::Menu:
+ aColor = mColorMenu;
+ break;
+ case ColorID::Infobackground:
+ aColor = NS_RGB(0xFF, 0xFF, 0xC7);
+ break;
+ case ColorID::Windowframe:
+ aColor = mColorWindowFrame;
+ break;
+ case ColorID::Window:
+ case ColorID::Field:
+ case ColorID::MozCombobox:
+ aColor = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::Fieldtext:
+ case ColorID::MozComboboxtext:
+ aColor = mColorFieldText;
+ break;
+ case ColorID::MozDialog:
+ aColor = mColorDialog;
+ break;
+ case ColorID::MozDialogtext:
+ case ColorID::MozCellhighlighttext:
+ case ColorID::MozHtmlCellhighlighttext:
+ case ColorID::MozColheadertext:
+ case ColorID::MozColheaderhovertext:
+ aColor = mColorDialogText;
+ break;
+ case ColorID::MozDragtargetzone:
+ aColor = mColorDragTargetZone;
+ break;
+ case ColorID::MozMacChromeActive:
+ aColor = mColorChromeActive;
+ break;
+ case ColorID::MozMacChromeInactive:
+ aColor = mColorChromeInactive;
+ break;
+ case ColorID::MozMacFocusring:
+ aColor = mColorFocusRing;
+ break;
+ case ColorID::MozMacMenushadow:
+ aColor = NS_RGB(0xA3, 0xA3, 0xA3);
+ break;
+ case ColorID::MozMacMenutextdisable:
+ aColor = NS_RGB(0x98, 0x98, 0x98);
+ break;
+ case ColorID::MozMacMenutextselect:
+ aColor = mColorTextSelect;
+ break;
+ case ColorID::MozMacDisabledtoolbartext:
+ aColor = mColorDisabledToolbarText;
+ break;
+ case ColorID::MozMacMenuselect:
+ aColor = mColorMenuSelect;
+ break;
+ case ColorID::MozButtondefault:
+ aColor = NS_RGB(0xDC, 0xDC, 0xDC);
+ break;
+ case ColorID::MozCellhighlight:
+ case ColorID::MozHtmlCellhighlight:
+ case ColorID::MozMacSecondaryhighlight:
+ // For inactive list selection
+ aColor = mColorCellHighlight;
+ break;
+ case ColorID::MozEventreerow:
+ // Background color of even list rows.
+ aColor = mColorEvenTreeRow;
+ break;
+ case ColorID::MozOddtreerow:
+ // Background color of odd list rows.
+ aColor = mColorOddTreeRow;
+ break;
+ case ColorID::MozNativehyperlinktext:
+ // There appears to be no available system defined color. HARDCODING to the appropriate color.
+ aColor = NS_RGB(0x14, 0x4F, 0xAE);
+ break;
+ // The following colors are supposed to be used as font-smoothing background
+ // colors, in the chrome-only -moz-font-smoothing-background-color property.
+ // This property is used for text on "vibrant" -moz-appearances.
+ // The colors have been obtained from the system on 10.12.6 using the
+ // program at https://bugzilla.mozilla.org/attachment.cgi?id=8907533 .
+ // We could obtain them at runtime, but doing so may be expensive and
+ // requires the use of the private API
+ // -[NSVisualEffectView fontSmoothingBackgroundColor].
+ case ColorID::MozMacVibrancyLight:
+ case ColorID::MozMacVibrantTitlebarLight:
+ case ColorID::MozMacSourceList:
+ case ColorID::MozMacTooltip:
+ aColor = NS_RGB(0xf7, 0xf7, 0xf7);
+ break;
+ case ColorID::MozMacVibrancyDark:
+ case ColorID::MozMacVibrantTitlebarDark:
+ aColor = NS_RGB(0x28, 0x28, 0x28);
+ break;
+ case ColorID::MozMacMenupopup:
+ case ColorID::MozMacMenuitem:
+ aColor = NS_RGB(0xe6, 0xe6, 0xe6);
+ break;
+ case ColorID::MozMacSourceListSelection:
+ aColor = NS_RGB(0xc8, 0xc8, 0xc8);
+ break;
+ case ColorID::MozMacActiveMenuitem:
+ case ColorID::MozMacActiveSourceListSelection:
+ aColor = mColorActiveSourceListSelection;
+ 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;
+}
+
+nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ case IntID::ScrollButtonLeftMouseButtonAction:
+ aResult = 0;
+ break;
+ case IntID::ScrollButtonMiddleMouseButtonAction:
+ case IntID::ScrollButtonRightMouseButtonAction:
+ aResult = 3;
+ break;
+ case IntID::CaretBlinkTime:
+ aResult = 567;
+ break;
+ case IntID::CaretWidth:
+ aResult = 1;
+ break;
+ case IntID::ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case IntID::SelectTextfieldsOnKeyFocus:
+ // Select textfield content when focused by kbd
+ // used by EventStateManager::sTextfieldSelectModel
+ aResult = 1;
+ break;
+ case IntID::SubmenuDelay:
+ aResult = 200;
+ break;
+ case IntID::TooltipDelay:
+ aResult = 500;
+ break;
+ case IntID::MenusCanOverlapOSBar:
+ // xul popups are not allowed to overlap the menubar.
+ aResult = 0;
+ break;
+ case IntID::SkipNavigatingDisabledMenuItem:
+ aResult = 1;
+ break;
+ case IntID::DragThresholdX:
+ case IntID::DragThresholdY:
+ aResult = 4;
+ break;
+ case IntID::ScrollArrowStyle:
+ aResult = eScrollArrow_None;
+ break;
+ case IntID::ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+ case IntID::UseOverlayScrollbars:
+ if (!mUseOverlayScrollbarsCached) {
+ mUseOverlayScrollbars = NSScroller.preferredScrollerStyle == NSScrollerStyleOverlay ? 1 : 0;
+ mUseOverlayScrollbarsCached = true;
+ }
+ aResult = mUseOverlayScrollbars;
+ break;
+ case IntID::AllowOverlayScrollbarsOverlap:
+ if (!mAllowOverlayScrollbarsOverlapCached) {
+ mAllowOverlayScrollbarsOverlap = AllowOverlayScrollbarsOverlap() ? 1 : 0;
+ mAllowOverlayScrollbarsOverlapCached = true;
+ }
+ aResult = mAllowOverlayScrollbarsOverlap;
+ break;
+ case IntID::ScrollbarDisplayOnMouseMove:
+ aResult = 0;
+ break;
+ case IntID::ScrollbarFadeBeginDelay:
+ aResult = 450;
+ break;
+ case IntID::ScrollbarFadeDuration:
+ aResult = 200;
+ break;
+ case IntID::TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case IntID::TreeCloseDelay:
+ aResult = 1000;
+ break;
+ case IntID::TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case IntID::TreeScrollDelay:
+ aResult = 100;
+ break;
+ case IntID::TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case IntID::DWMCompositor:
+ case IntID::WindowsClassic:
+ case IntID::WindowsDefaultTheme:
+ case IntID::TouchEnabled:
+ case IntID::WindowsThemeIdentifier:
+ case IntID::OperatingSystemVersionIdentifier:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case IntID::MacGraphiteTheme:
+ aResult = [NSColor currentControlTint] == NSGraphiteControlTint;
+ break;
+ case IntID::MacBigSurTheme:
+ aResult = nsCocoaFeatures::OnBigSurOrLater();
+ break;
+ case IntID::AlertNotificationOrigin:
+ aResult = NS_ALERT_TOP;
+ break;
+ case IntID::TabFocusModel:
+ aResult = [NSApp isFullKeyboardAccessEnabled] ? nsIContent::eTabFocus_any
+ : nsIContent::eTabFocus_textControlsMask;
+ break;
+ case IntID::ScrollToClick: {
+ aResult = [[NSUserDefaults standardUserDefaults] boolForKey:@"AppleScrollerPagingBehavior"];
+ } break;
+ case IntID::ChosenMenuItemsShouldBlink:
+ aResult = 1;
+ break;
+ case IntID::IMERawInputUnderlineStyle:
+ case IntID::IMEConvertedTextUnderlineStyle:
+ case IntID::IMESelectedRawTextUnderlineStyle:
+ case IntID::IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ break;
+ case IntID::SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_DOTTED;
+ break;
+ case IntID::ScrollbarButtonAutoRepeatBehavior:
+ aResult = 0;
+ break;
+ case IntID::SwipeAnimationEnabled:
+ aResult = 0;
+ if ([NSEvent respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) {
+ aResult = [NSEvent isSwipeTrackingFromScrollEventsEnabled] ? 1 : 0;
+ }
+ break;
+ case IntID::ContextMenuOffsetVertical:
+ aResult = -6;
+ break;
+ case IntID::ContextMenuOffsetHorizontal:
+ aResult = 1;
+ break;
+ case IntID::SystemUsesDarkTheme:
+ if (!mSystemUsesDarkThemeCached) {
+ mSystemUsesDarkTheme = SystemWantsDarkTheme();
+ mSystemUsesDarkThemeCached = true;
+ }
+ aResult = mSystemUsesDarkTheme;
+ break;
+ case IntID::PrefersReducedMotion:
+ // Without native event loops,
+ // NSWorkspace.accessibilityDisplayShouldReduceMotion returns stale
+ // information, so we get the information only on the parent processes
+ // or when it's the initial query on child processes. Otherwise we will
+ // get the info via LookAndFeel::SetIntCache on child processes.
+ if (!mPrefersReducedMotionCached &&
+ [[NSWorkspace sharedWorkspace]
+ respondsToSelector:@selector(accessibilityDisplayShouldReduceMotion)]) {
+ mPrefersReducedMotion =
+ [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceMotion] ? 1 : 0;
+ mPrefersReducedMotionCached = true;
+ }
+ aResult = mPrefersReducedMotion;
+ break;
+ default:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ case FloatID::IMEUnderlineRelativeSize:
+ aResult = 2.0f;
+ break;
+ case FloatID::SpellCheckerUnderlineRelativeSize:
+ aResult = 2.0f;
+ break;
+ default:
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ }
+
+ return res;
+}
+
+bool nsLookAndFeel::UseOverlayScrollbars() { return GetInt(IntID::UseOverlayScrollbars) != 0; }
+
+bool nsLookAndFeel::AllowOverlayScrollbarsOverlap() { return (UseOverlayScrollbars()); }
+
+bool nsLookAndFeel::SystemWantsDarkTheme() {
+ // This returns true if the macOS system appearance is set to dark mode on
+ // 10.14+, false otherwise.
+ if (!nsCocoaFeatures::OnMojaveOrLater()) {
+ return false;
+ }
+ return !![[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
+}
+
+bool nsLookAndFeel::NativeGetFont(FontID aID, nsString& aFontName, gfxFontStyle& aFontStyle) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // hack for now
+ if (aID == FontID::Window || aID == FontID::Document) {
+ aFontStyle.style = mozilla::FontSlantStyle::Normal();
+ aFontStyle.weight = mozilla::FontWeight::Normal();
+ aFontStyle.stretch = mozilla::FontStretch::Normal();
+ aFontStyle.size = 14;
+ aFontStyle.systemFont = true;
+
+ aFontName.AssignLiteral("sans-serif");
+ return true;
+ }
+
+ // TODO: Add caching? Note that it needs to be thread-safe for stylo use.
+
+ nsAutoCString name;
+ gfxPlatformMac::LookupSystemFont(aID, name, aFontStyle);
+ aFontName.Append(NS_ConvertUTF8toUTF16(name));
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+mozilla::widget::LookAndFeelCache nsLookAndFeel::GetCacheImpl() {
+ LookAndFeelCache cache = nsXPLookAndFeel::GetCacheImpl();
+
+ LookAndFeelInt useOverlayScrollbars;
+ useOverlayScrollbars.id() = IntID::UseOverlayScrollbars;
+ useOverlayScrollbars.value() = GetInt(IntID::UseOverlayScrollbars);
+ cache.mInts().AppendElement(useOverlayScrollbars);
+
+ LookAndFeelInt allowOverlayScrollbarsOverlap;
+ allowOverlayScrollbarsOverlap.id() = IntID::AllowOverlayScrollbarsOverlap;
+ allowOverlayScrollbarsOverlap.value() = GetInt(IntID::AllowOverlayScrollbarsOverlap);
+ cache.mInts().AppendElement(allowOverlayScrollbarsOverlap);
+
+ LookAndFeelInt prefersReducedMotion;
+ prefersReducedMotion.id() = IntID::PrefersReducedMotion;
+ prefersReducedMotion.value() = GetInt(IntID::PrefersReducedMotion);
+ cache.mInts().AppendElement(prefersReducedMotion);
+
+ LookAndFeelInt systemUsesDarkTheme;
+ systemUsesDarkTheme.id() = IntID::SystemUsesDarkTheme;
+ systemUsesDarkTheme.value() = GetInt(IntID::SystemUsesDarkTheme);
+ cache.mInts().AppendElement(systemUsesDarkTheme);
+
+ return cache;
+}
+
+void nsLookAndFeel::SetCacheImpl(const LookAndFeelCache& aCache) { DoSetCache(aCache); }
+
+void nsLookAndFeel::DoSetCache(const LookAndFeelCache& aCache) {
+ for (auto entry : aCache.mInts()) {
+ switch (entry.id()) {
+ case IntID::UseOverlayScrollbars:
+ mUseOverlayScrollbars = entry.value();
+ mUseOverlayScrollbarsCached = true;
+ break;
+ case IntID::AllowOverlayScrollbarsOverlap:
+ mAllowOverlayScrollbarsOverlap = entry.value();
+ mAllowOverlayScrollbarsOverlapCached = true;
+ break;
+ case IntID::SystemUsesDarkTheme:
+ mSystemUsesDarkTheme = entry.value();
+ mSystemUsesDarkThemeCached = true;
+ break;
+ case IntID::PrefersReducedMotion:
+ mPrefersReducedMotion = entry.value();
+ mPrefersReducedMotionCached = true;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Bogus Int ID in cache");
+ break;
+ }
+ }
+}
+
+void nsLookAndFeel::EnsureInit() {
+ if (mInitialized) {
+ return;
+ }
+ mInitialized = true;
+
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK
+
+ nscolor color;
+
+ mColorTextSelectBackground = GetColorFromNSColor([NSColor selectedTextBackgroundColor]);
+ mColorTextSelectBackgroundDisabled = GetColorFromNSColor([NSColor secondarySelectedControlColor]);
+
+ mColorHighlight = GetColorFromNSColor([NSColor alternateSelectedControlColor]);
+ mColorMenuHover = GetColorFromNSColor([NSColor alternateSelectedControlColor]);
+
+ GetColor(ColorID::TextSelectBackground, color);
+ if (color == 0x000000) {
+ mColorTextSelectForeground = NS_RGB(0xff, 0xff, 0xff);
+ } else {
+ mColorTextSelectForeground = NS_DONT_CHANGE_COLOR;
+ }
+
+ mColorMenuHoverText = GetColorFromNSColor([NSColor alternateSelectedControlTextColor]);
+
+ mColorButtonText = NS_RGB(0xFF, 0xFF, 0xFF);
+ mHasColorButtonText = true;
+
+ mColorButtonHoverText = GetColorFromNSColor([NSColor controlTextColor]);
+ mColorText = GetColorFromNSColor([NSColor textColor]);
+ mColorWindowText = GetColorFromNSColor([NSColor windowFrameTextColor]);
+ mColorActiveCaption = GetColorFromNSColor([NSColor gridColor]);
+ mColorActiveBorder = GetColorFromNSColor([NSColor keyboardFocusIndicatorColor]);
+ NSColor* disabledColor = [NSColor disabledControlTextColor];
+ mColorGrayText = GetColorFromNSColorWithAlpha(disabledColor, [disabledColor alphaComponent]);
+ mColorInactiveBorder = GetColorFromNSColor([NSColor controlBackgroundColor]);
+ mColorInactiveCaption = GetColorFromNSColor([NSColor controlBackgroundColor]);
+ mColorScrollbar = GetColorFromNSColor([NSColor scrollBarColor]);
+ mColorThreeDHighlight = GetColorFromNSColor([NSColor highlightColor]);
+ mColorMenu = GetColorFromNSColor([NSColor alternateSelectedControlTextColor]);
+ mColorWindowFrame = GetColorFromNSColor([NSColor gridColor]);
+ mColorFieldText = GetColorFromNSColor([NSColor controlTextColor]);
+ mColorDialog = GetColorFromNSColor([NSColor controlHighlightColor]);
+ mColorDialogText = GetColorFromNSColor([NSColor controlTextColor]);
+ mColorDragTargetZone = GetColorFromNSColor([NSColor selectedControlColor]);
+
+ int grey = NativeGreyColorAsInt(toolbarFillGrey, true);
+ mColorChromeActive = NS_RGB(grey, grey, grey);
+ grey = NativeGreyColorAsInt(toolbarFillGrey, false);
+ mColorChromeInactive = NS_RGB(grey, grey, grey);
+
+ mColorFocusRing = GetColorFromNSColorWithAlpha([NSColor keyboardFocusIndicatorColor], 0.48);
+
+ mColorTextSelect = GetColorFromNSColor([NSColor selectedMenuItemTextColor]);
+ mColorDisabledToolbarText = GetColorFromNSColor([NSColor disabledControlTextColor]);
+ mColorMenuSelect = GetColorFromNSColor([NSColor alternateSelectedControlColor]);
+ mColorCellHighlight = GetColorFromNSColor([NSColor secondarySelectedControlColor]);
+ mColorEvenTreeRow =
+ GetColorFromNSColor([[NSColor controlAlternatingRowBackgroundColors] objectAtIndex:0]);
+ mColorOddTreeRow =
+ GetColorFromNSColor([[NSColor controlAlternatingRowBackgroundColors] objectAtIndex:1]);
+
+ color = [NSColor currentControlTint];
+ mColorActiveSourceListSelection =
+ (color == NSGraphiteControlTint) ? NS_RGB(0xa0, 0xa0, 0xa0) : NS_RGB(0x0a, 0x64, 0xdc);
+
+ RecordTelemetry();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
diff --git a/widget/cocoa/nsMacCursor.h b/widget/cocoa/nsMacCursor.h
new file mode 100644
index 0000000000..fc97728da5
--- /dev/null
+++ b/widget/cocoa/nsMacCursor.h
@@ -0,0 +1,128 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMacCursor_h_
+#define nsMacCursor_h_
+
+#import <Cocoa/Cocoa.h>
+#import "nsIWidget.h"
+
+/*! @class nsMacCursor
+ @abstract Represents a native Mac cursor.
+ @discussion <code>nsMacCursor</code> provides a simple API for creating and
+ working with native Macintosh cursors. Cursors can be created
+ used without needing to be aware of the way different cursors
+ are implemented, in particular the details of managing an
+ animated cursor are hidden.
+*/
+@interface nsMacCursor : NSObject {
+ @private
+ NSTimer* mTimer;
+ @protected
+ nsCursor mType;
+ int mFrameCounter;
+}
+
+/*! @method cursorWithCursor:
+ @abstract Create a cursor by specifying a Cocoa <code>NSCursor</code>.
+ @discussion Creates a cursor representing the given Cocoa built-in cursor.
+ @param aCursor the <code>NSCursor</code> to use
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an autoreleased instance of <code>nsMacCursor</code>
+ representing the given <code>NSCursor</code>
+ */
++ (nsMacCursor*)cursorWithCursor:(NSCursor*)aCursor type:(nsCursor)aType;
+
+/*! @method cursorWithImageNamed:hotSpot:type:
+ @abstract Create a cursor by specifying the name of an image resource to
+ use for the cursor and a hotspot.
+ @discussion Creates a cursor by loading the named image using the
+ <code>+[NSImage imageNamed:]</code> method.
+ <p>The image must be compatible with any restrictions laid down
+ by <code>NSCursor</code>. These vary by operating system
+ version.</p>
+ <p>The hotspot precisely determines the point where the user
+ clicks when using the cursor.</p>
+ @param aCursor the name of the image to use for the cursor
+ @param aPoint the point within the cursor to use as the hotspot
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an autoreleased instance of <code>nsMacCursor</code> that uses the given image and
+ hotspot
+ */
++ (nsMacCursor*)cursorWithImageNamed:(NSString*)aCursorImage
+ hotSpot:(NSPoint)aPoint
+ type:(nsCursor)aType;
+
+/*! @method cursorWithFrames:type:
+ @abstract Create an animated cursor by specifying the frames to use for
+ the animation.
+ @discussion Creates a cursor that will animate by cycling through the given
+ frames. Each element of the array must be an instance of
+ <code>NSCursor</code>
+ @param aCursorFrames an array of <code>NSCursor</code>, representing
+ the frames of an animated cursor, in the order they should be
+ played.
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an autoreleased instance of <code>nsMacCursor</code> that will
+ animate the given cursor frames
+ */
++ (nsMacCursor*)cursorWithFrames:(NSArray*)aCursorFrames type:(nsCursor)aType;
+
+/*! @method cocoaCursorWithImageNamed:hotSpot:
+ @abstract Create a Cocoa NSCursor object with a Gecko image resource name
+ and a hotspot point.
+ @discussion Create a Cocoa NSCursor object with a Gecko image resource name
+ and a hotspot point.
+ @param imageName the name of the gecko image resource, "tiff"
+ extension is assumed, do not append.
+ @param aPoint the point within the cursor to use as the hotspot
+ @result an autoreleased instance of <code>nsMacCursor</code> that will
+ animate the given cursor frames
+ */
++ (NSCursor*)cocoaCursorWithImageNamed:(NSString*)imageName hotSpot:(NSPoint)aPoint;
+
+/*! @method isSet
+ @abstract Determines whether this cursor is currently active.
+ @discussion This can be helpful when the Cocoa NSCursor state can be
+ influenced without going through nsCursorManager.
+ @result whether the cursor is currently set
+ */
+- (BOOL)isSet;
+
+/*! @method set
+ @abstract Set the cursor.
+ @discussion Makes this cursor the current cursor. If the cursor is
+ animated, the animation is started.
+ */
+- (void)set;
+
+/*! @method unset
+ @abstract Unset the cursor. The cursor will return to the default
+ (usually the arrow cursor).
+ @discussion Unsets the cursor. If the cursor is animated, the animation is
+ stopped.
+ */
+- (void)unset;
+
+/*! @method isAnimated
+ @abstract Tests whether this cursor is animated.
+ @discussion Use this method to determine whether a cursor is animated
+ @result YES if the cursor is animated (has more than one frame), NO if
+ it is a simple static cursor.
+ */
+- (BOOL)isAnimated;
+
+/** @method cursorType
+ @abstract Get the cursor type for this cursor
+ @discussion This method returns the <code>nsCursor</code> constant that
+ corresponds to this cursor, which is equivalent to the CSS
+ name for the cursor.
+ @result The nsCursor constant corresponding to this cursor, or
+ nsCursor's 'eCursorCount' if the cursor is a custom cursor
+ loaded from a URI
+ */
+- (nsCursor)type;
+@end
+
+#endif // nsMacCursor_h_
diff --git a/widget/cocoa/nsMacCursor.mm b/widget/cocoa/nsMacCursor.mm
new file mode 100644
index 0000000000..ff656254c9
--- /dev/null
+++ b/widget/cocoa/nsMacCursor.mm
@@ -0,0 +1,367 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMacCursor.h"
+#include "nsObjCExceptions.h"
+#include "nsDebug.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsString.h"
+
+/*! @category nsMacCursor (PrivateMethods)
+ @abstract Private methods internal to the nsMacCursor class.
+ @discussion <code>nsMacCursor</code> is effectively an abstract class. It does not define
+ complete behaviour in and of itself, the subclasses defined in this file provide the useful
+ implementations.
+*/
+@interface nsMacCursor (PrivateMethods)
+
+/*! @method getNextCursorFrame
+ @abstract get the index of the next cursor frame to display.
+ @discussion Increments and returns the frame counter of an animated cursor.
+ @result The index of the next frame to display in the cursor animation
+*/
+- (int)getNextCursorFrame;
+
+/*! @method numFrames
+ @abstract Query the number of frames in this cursor's animation.
+ @discussion Returns the number of frames in this cursor's animation. Static cursors return 1.
+*/
+- (int)numFrames;
+
+/*! @method createTimer
+ @abstract Create a Timer to use to animate the cursor.
+ @discussion Creates an instance of <code>NSTimer</code> which is used to drive the cursor
+ animation. This method should only be called for cursors that are animated.
+*/
+- (void)createTimer;
+
+/*! @method destroyTimer
+ @abstract Destroy any timer instance associated with this cursor.
+ @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this
+ cursor.
+ */
+- (void)destroyTimer;
+/*! @method destroyTimer
+ @abstract Destroy any timer instance associated with this cursor.
+ @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this
+ cursor.
+*/
+
+/*! @method advanceAnimatedCursor:
+ @abstract Method called by animation timer to perform animation.
+ @discussion Called by an animated cursor's associated timer to advance the animation to the next
+ frame. Determines which frame should occur next and sets the cursor to that frame.
+ @param aTimer the timer causing the animation
+*/
+- (void)advanceAnimatedCursor:(NSTimer*)aTimer;
+
+/*! @method setFrame:
+ @abstract Sets the current cursor, using an index to determine which frame in the animation to
+ display.
+ @discussion Sets the current cursor. The frame index determines which frame is shown if the
+ cursor is animated. Frames and numbered from <code>0</code> to <code>-[nsMacCursor numFrames] -
+ 1</code>. A static cursor has a single frame, numbered 0.
+ @param aFrameIndex the index indicating which frame from the animation to display
+*/
+- (void)setFrame:(int)aFrameIndex;
+
+@end
+
+/*! @class nsCocoaCursor
+ @abstract Implementation of <code>nsMacCursor</code> that uses Cocoa <code>NSCursor</code>
+ instances.
+ @discussion Displays a static or animated cursor, using Cocoa <code>NSCursor</code> instances.
+ These can be either built-in <code>NSCursor</code> instances, or custom <code>NSCursor</code>s
+ created from images. When more than one <code>NSCursor</code> is provided, the cursor will use
+ these as animation frames.
+*/
+@interface nsCocoaCursor : nsMacCursor {
+ @private
+ NSArray* mFrames;
+ NSCursor* mLastSetCocoaCursor;
+}
+
+/*! @method initWithFrames:
+ @abstract Create an animated cursor by specifying the frames to use for the animation.
+ @discussion Creates a cursor that will animate by cycling through the given frames. Each element
+ of the array must be an instance of <code>NSCursor</code>
+ @param aCursorFrames an array of <code>NSCursor</code>, representing the frames of an
+ animated cursor, in the order they should be played.
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an instance of <code>nsCocoaCursor</code> that will animate the given cursor frames
+ */
+- (id)initWithFrames:(NSArray*)aCursorFrames type:(nsCursor)aType;
+
+/*! @method initWithCursor:
+ @abstract Create a cursor by specifying a Cocoa <code>NSCursor</code>.
+ @discussion Creates a cursor representing the given Cocoa built-in cursor.
+ @param aCursor the <code>NSCursor</code> to use
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an instance of <code>nsCocoaCursor</code> representing the given
+ <code>NSCursor</code>
+*/
+- (id)initWithCursor:(NSCursor*)aCursor type:(nsCursor)aType;
+
+/*! @method initWithImageNamed:hotSpot:
+ @abstract Create a cursor by specifying the name of an image resource to use for the cursor
+ and a hotspot.
+ @discussion Creates a cursor by loading the named image using the <code>+[NSImage
+ imageNamed:]</code> method. <p>The image must be compatible with any restrictions laid down by
+ <code>NSCursor</code>. These vary by operating system version.</p> <p>The hotspot precisely
+ determines the point where the user clicks when using the cursor.</p>
+ @param aCursor the name of the image to use for the cursor
+ @param aPoint the point within the cursor to use as the hotspot
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an instance of <code>nsCocoaCursor</code> that uses the given image and hotspot
+*/
+- (id)initWithImageNamed:(NSString*)aCursorImage hotSpot:(NSPoint)aPoint type:(nsCursor)aType;
+
+@end
+
+@implementation nsMacCursor
+
++ (nsMacCursor*)cursorWithCursor:(NSCursor*)aCursor type:(nsCursor)aType {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [[[nsCocoaCursor alloc] initWithCursor:aCursor type:aType] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (nsMacCursor*)cursorWithImageNamed:(NSString*)aCursorImage
+ hotSpot:(NSPoint)aPoint
+ type:(nsCursor)aType {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [[[nsCocoaCursor alloc] initWithImageNamed:aCursorImage hotSpot:aPoint
+ type:aType] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (nsMacCursor*)cursorWithFrames:(NSArray*)aCursorFrames type:(nsCursor)aType {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [[[nsCocoaCursor alloc] initWithFrames:aCursorFrames type:aType] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (NSCursor*)cocoaCursorWithImageNamed:(NSString*)imageName hotSpot:(NSPoint)aPoint {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsCOMPtr<nsIFile> resDir;
+ nsAutoCString resPath;
+ NSString *pathToImage, *pathToHiDpiImage;
+ NSImage *cursorImage, *hiDpiCursorImage;
+
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir));
+ if (NS_FAILED(rv)) goto INIT_FAILURE;
+ resDir->AppendNative("res"_ns);
+ resDir->AppendNative("cursors"_ns);
+
+ 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..f3a12485b3
--- /dev/null
+++ b/widget/cocoa/nsMacDockSupport.h
@@ -0,0 +1,35 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nsString.h"
+
+@class MOZProgressDockOverlayView;
+
+class nsMacDockSupport : public nsIMacDockSupport, public nsITaskbarProgress {
+ public:
+ nsMacDockSupport();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMACDOCKSUPPORT
+ NS_DECL_NSITASKBARPROGRESS
+
+ protected:
+ virtual ~nsMacDockSupport();
+
+ nsCOMPtr<nsIStandaloneNativeMenu> mDockMenu;
+ nsString mBadgeText;
+
+ NSView* mDockTileWrapperView;
+ MOZProgressDockOverlayView* mProgressDockOverlayView;
+
+ nsTaskbarProgressState mProgressState;
+ double mProgressFraction;
+
+ nsresult UpdateDockTile();
+};
diff --git a/widget/cocoa/nsMacDockSupport.mm b/widget/cocoa/nsMacDockSupport.mm
new file mode 100644
index 0000000000..7929755aa1
--- /dev/null
+++ b/widget/cocoa/nsMacDockSupport.mm
@@ -0,0 +1,198 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsComponentManagerUtils.h"
+#include "nsMacDockSupport.h"
+#include "nsObjCExceptions.h"
+#include "nsNativeThemeColors.h"
+
+NS_IMPL_ISUPPORTS(nsMacDockSupport, nsIMacDockSupport, nsITaskbarProgress)
+
+// This view is used in the dock tile when we're downloading a file.
+// It draws a progress bar that looks similar to the native progress bar on
+// 10.12. This style of progress bar is not animated, unlike the pre-10.10
+// progress bar look which had to redrawn multiple times per second.
+@interface MOZProgressDockOverlayView : NSView {
+ double mFractionValue;
+}
+@property double fractionValue;
+
+@end
+
+@implementation MOZProgressDockOverlayView
+
+@synthesize fractionValue = mFractionValue;
+
+- (void)drawRect:(NSRect)aRect {
+ // Erase the background behind this view, i.e. cut a rectangle hole in the icon.
+ [[NSColor clearColor] set];
+ NSRectFill(self.bounds);
+
+ // Split the height of this view into four quarters. The middle two quarters
+ // will be covered by the actual progress bar.
+ CGFloat radius = self.bounds.size.height / 4;
+ NSRect barBounds = NSInsetRect(self.bounds, 0, radius);
+
+ NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:barBounds
+ xRadius:radius
+ yRadius:radius];
+
+ // Draw a grayish background first.
+ [[NSColor colorWithDeviceWhite:0 alpha:0.1] setFill];
+ [path fill];
+
+ // Draw a fill in the control accent color for the progress part.
+ NSRect progressFillRect = self.bounds;
+ progressFillRect.size.width *= mFractionValue;
+ [NSGraphicsContext saveGraphicsState];
+ [NSBezierPath clipRect:progressFillRect];
+ [ControlAccentColor() setFill];
+ [path fill];
+ [NSGraphicsContext restoreGraphicsState];
+
+ // Add a shadowy stroke on top.
+ [NSGraphicsContext saveGraphicsState];
+ [path addClip];
+ [[NSColor colorWithDeviceWhite:0 alpha:0.2] setStroke];
+ path.lineWidth = barBounds.size.height / 10;
+ [path stroke];
+ [NSGraphicsContext restoreGraphicsState];
+}
+
+@end
+
+nsMacDockSupport::nsMacDockSupport()
+ : mDockTileWrapperView(nil),
+ mProgressDockOverlayView(nil),
+ mProgressState(STATE_NO_PROGRESS),
+ mProgressFraction(0.0) {}
+
+nsMacDockSupport::~nsMacDockSupport() {
+ if (mDockTileWrapperView) {
+ [mDockTileWrapperView release];
+ mDockTileWrapperView = nil;
+ }
+ if (mProgressDockOverlayView) {
+ [mProgressDockOverlayView release];
+ mProgressDockOverlayView = nil;
+ }
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::GetDockMenu(nsIStandaloneNativeMenu** aDockMenu) {
+ nsCOMPtr<nsIStandaloneNativeMenu> dockMenu(mDockMenu);
+ dockMenu.forget(aDockMenu);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::SetDockMenu(nsIStandaloneNativeMenu* aDockMenu) {
+ mDockMenu = aDockMenu;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::ActivateApplication(bool aIgnoreOtherApplications) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ [[NSApplication sharedApplication] activateIgnoringOtherApps:aIgnoreOtherApplications];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::SetBadgeText(const nsAString& aBadgeText) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSDockTile* tile = [[NSApplication sharedApplication] dockTile];
+ mBadgeText = aBadgeText;
+ if (aBadgeText.IsEmpty())
+ [tile setBadgeLabel:nil];
+ else
+ [tile setBadgeLabel:[NSString
+ stringWithCharacters:reinterpret_cast<const unichar*>(mBadgeText.get())
+ length:mBadgeText.Length()]];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::GetBadgeText(nsAString& aBadgeText) {
+ aBadgeText = mBadgeText;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::SetProgressState(nsTaskbarProgressState aState, uint64_t aCurrentValue,
+ uint64_t aMaxValue) {
+ NS_ENSURE_ARG_RANGE(aState, 0, STATE_PAUSED);
+ if (aState == STATE_NO_PROGRESS || aState == STATE_INDETERMINATE) {
+ NS_ENSURE_TRUE(aCurrentValue == 0, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(aMaxValue == 0, NS_ERROR_INVALID_ARG);
+ }
+ if (aCurrentValue > aMaxValue) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mProgressState = aState;
+ if (aMaxValue == 0) {
+ mProgressFraction = 0;
+ } else {
+ mProgressFraction = (double)aCurrentValue / aMaxValue;
+ }
+
+ return UpdateDockTile();
+}
+
+nsresult nsMacDockSupport::UpdateDockTile() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mProgressState == STATE_NORMAL || mProgressState == STATE_INDETERMINATE) {
+ if (!mDockTileWrapperView) {
+ // Create the following NSView hierarchy:
+ // * mDockTileWrapperView (NSView)
+ // * imageView (NSImageView) <- has the application icon
+ // * mProgressDockOverlayView (MOZProgressDockOverlayView) <- draws the progress bar
+
+ mDockTileWrapperView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 32, 32)];
+ mDockTileWrapperView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
+
+ NSImageView* imageView = [[NSImageView alloc] initWithFrame:[mDockTileWrapperView bounds]];
+ imageView.image = [NSImage imageNamed:@"NSApplicationIcon"];
+ imageView.imageScaling = NSImageScaleAxesIndependently;
+ imageView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
+ [mDockTileWrapperView addSubview:imageView];
+
+ mProgressDockOverlayView =
+ [[MOZProgressDockOverlayView alloc] initWithFrame:NSMakeRect(1, 3, 30, 4)];
+ mProgressDockOverlayView.autoresizingMask = NSViewMinXMargin | NSViewWidthSizable |
+ NSViewMaxXMargin | NSViewMinYMargin |
+ NSViewHeightSizable | NSViewMaxYMargin;
+ [mDockTileWrapperView addSubview:mProgressDockOverlayView];
+ }
+ if (NSApp.dockTile.contentView != mDockTileWrapperView) {
+ NSApp.dockTile.contentView = mDockTileWrapperView;
+ }
+
+ if (mProgressState == STATE_NORMAL) {
+ mProgressDockOverlayView.fractionValue = mProgressFraction;
+ } else {
+ // Indeterminate states are rare. Just fill the entire progress bar in
+ // that case.
+ mProgressDockOverlayView.fractionValue = 1.0;
+ }
+ [NSApp.dockTile display];
+ } else if (NSApp.dockTile.contentView) {
+ NSApp.dockTile.contentView = nil;
+ [NSApp.dockTile display];
+ }
+
+ return NS_OK;
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsMacFinderProgress.h b/widget/cocoa/nsMacFinderProgress.h
new file mode 100644
index 0000000000..a0e48a0d59
--- /dev/null
+++ b/widget/cocoa/nsMacFinderProgress.h
@@ -0,0 +1,24 @@
+/* -*- 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 _MACFINDERPROGRESS_H_
+#define _MACFINDERPROGRESS_H_
+
+#include "nsIMacFinderProgress.h"
+#include "nsCOMPtr.h"
+
+class nsMacFinderProgress : public nsIMacFinderProgress {
+ public:
+ nsMacFinderProgress();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMACFINDERPROGRESS
+
+ protected:
+ virtual ~nsMacFinderProgress();
+
+ NSProgress* mProgress;
+};
+
+#endif
diff --git a/widget/cocoa/nsMacFinderProgress.mm b/widget/cocoa/nsMacFinderProgress.mm
new file mode 100644
index 0000000000..a191b40fb2
--- /dev/null
+++ b/widget/cocoa/nsMacFinderProgress.mm
@@ -0,0 +1,87 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsMacFinderProgress.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsString.h"
+#include "nsObjCExceptions.h"
+
+NS_IMPL_ISUPPORTS(nsMacFinderProgress, nsIMacFinderProgress)
+
+nsMacFinderProgress::nsMacFinderProgress() : mProgress(nil) {}
+
+nsMacFinderProgress::~nsMacFinderProgress() {
+ if (mProgress) {
+ [mProgress unpublish];
+ [mProgress release];
+ }
+}
+
+NS_IMETHODIMP
+nsMacFinderProgress::Init(const nsAString& path,
+ nsIMacFinderProgressCanceledCallback* cancellationCallback) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSURL* pathUrl = [NSURL
+ fileURLWithPath:[NSString
+ stringWithCharacters:reinterpret_cast<const unichar*>(path.BeginReading())
+ length:path.Length()]];
+ NSDictionary* userInfo = @{
+ @"NSProgressFileOperationKindKey" : @"NSProgressFileOperationKindDownloading",
+ @"NSProgressFileURLKey" : pathUrl
+ };
+
+ mProgress = [[NSProgress alloc] initWithParent:nil userInfo:userInfo];
+ mProgress.kind = NSProgressKindFile;
+ mProgress.cancellable = YES;
+
+ nsMainThreadPtrHandle<nsIMacFinderProgressCanceledCallback> cancellationCallbackHandle(
+ new nsMainThreadPtrHolder<nsIMacFinderProgressCanceledCallback>(
+ "MacFinderProgress::CancellationCallback", cancellationCallback));
+
+ mProgress.cancellationHandler = ^{
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("MacFinderProgress::Canceled", [cancellationCallbackHandle] {
+ MOZ_ASSERT(NS_IsMainThread());
+ cancellationCallbackHandle->Canceled();
+ }));
+ };
+
+ [mProgress publish];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMacFinderProgress::UpdateProgress(uint64_t currentProgress, uint64_t totalProgress) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+ if (mProgress) {
+ mProgress.totalUnitCount = totalProgress;
+ mProgress.completedUnitCount = currentProgress;
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMacFinderProgress::End() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mProgress) {
+ [mProgress unpublish];
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsMacSharingService.h b/widget/cocoa/nsMacSharingService.h
new file mode 100644
index 0000000000..d97ce59380
--- /dev/null
+++ b/widget/cocoa/nsMacSharingService.h
@@ -0,0 +1,22 @@
+/* -*- 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 nsMacSharingService_h_
+#define nsMacSharingService_h_
+
+#include "nsIMacSharingService.h"
+
+class nsMacSharingService : public nsIMacSharingService {
+ public:
+ nsMacSharingService() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMACSHARINGSERVICE
+
+ protected:
+ virtual ~nsMacSharingService() {}
+};
+
+#endif // nsMacSharingService_h_
diff --git a/widget/cocoa/nsMacSharingService.mm b/widget/cocoa/nsMacSharingService.mm
new file mode 100644
index 0000000000..ec1c53e7e0
--- /dev/null
+++ b/widget/cocoa/nsMacSharingService.mm
@@ -0,0 +1,202 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsMacSharingService.h"
+
+#include "jsapi.h"
+#include "js/Array.h" // JS::NewArrayObject
+#include "nsCocoaUtils.h"
+#include "mozilla/MacStringHelpers.h"
+
+NS_IMPL_ISUPPORTS(nsMacSharingService, nsIMacSharingService)
+
+NSString* const remindersServiceName = @"com.apple.reminders.RemindersShareExtension";
+
+// These are some undocumented constants also used by Safari
+// to let us open the preferences window
+NSString* const extensionPrefPanePath = @"/System/Library/PreferencePanes/Extensions.prefPane";
+const UInt32 openSharingSubpaneDescriptorType = 'ptru';
+NSString* const openSharingSubpaneActionKey = @"action";
+NSString* const openSharingSubpaneActionValue = @"revealExtensionPoint";
+NSString* const openSharingSubpaneProtocolKey = @"protocol";
+NSString* const openSharingSubpaneProtocolValue = @"com.apple.share-services";
+
+// Expose the id so we can pass reference through to JS and back
+@interface NSSharingService (ExposeName)
+- (id)name;
+@end
+
+// Filter providers that we do not want to expose to the user, because they are duplicates or do not
+// work correctly within the context
+static bool ShouldIgnoreProvider(NSString* aProviderName) {
+ return [aProviderName isEqualToString:@"com.apple.share.System.add-to-safari-reading-list"] ||
+ [aProviderName isEqualToString:@"com.apple.share.Mail.compose"];
+}
+
+// Clean up the activity once the share is complete
+@interface SharingServiceDelegate : NSObject <NSSharingServiceDelegate> {
+ NSUserActivity* mShareActivity;
+}
+
+- (void)cleanup;
+
+@end
+
+@implementation SharingServiceDelegate
+
+- (id)initWithActivity:(NSUserActivity*)activity {
+ self = [super init];
+ mShareActivity = [activity retain];
+ return self;
+}
+
+- (void)cleanup {
+ [mShareActivity resignCurrent];
+ [mShareActivity invalidate];
+ [mShareActivity release];
+ mShareActivity = nil;
+}
+
+- (void)sharingService:(NSSharingService*)sharingService didShareItems:(NSArray*)items {
+ [self cleanup];
+}
+
+- (void)sharingService:(NSSharingService*)service
+ didFailToShareItems:(NSArray*)items
+ error:(NSError*)error {
+ [self cleanup];
+}
+
+- (void)dealloc {
+ [mShareActivity release];
+ [super dealloc];
+}
+
+@end
+
+static NSString* NSImageToBase64(const NSImage* aImage) {
+ CGImageRef cgRef = [aImage CGImageForProposedRect:nil context:nil hints:nil];
+ NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
+ [bitmapRep setSize:[aImage size]];
+ NSData* imageData = [bitmapRep representationUsingType:NSPNGFileType properties:@{}];
+ NSString* base64Encoded = [imageData base64EncodedStringWithOptions:0];
+ [bitmapRep release];
+ return [NSString stringWithFormat:@"data:image/png;base64,%@", base64Encoded];
+}
+
+static void SetStrAttribute(JSContext* aCx, JS::Rooted<JSObject*>& aObj, const char* aKey,
+ NSString* aVal) {
+ nsAutoString strVal;
+ mozilla::CopyCocoaStringToXPCOMString(aVal, strVal);
+ JS::Rooted<JSString*> title(aCx, JS_NewUCStringCopyZ(aCx, strVal.get()));
+ JS::Rooted<JS::Value> attVal(aCx, JS::StringValue(title));
+ JS_SetProperty(aCx, aObj, aKey, attVal);
+}
+
+nsresult nsMacSharingService::GetSharingProviders(const nsAString& aPageUrl, JSContext* aCx,
+ JS::MutableHandleValue aResult) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ JS::Rooted<JSObject*> array(aCx, JS::NewArrayObject(aCx, 0));
+ NSURL* url = [NSURL URLWithString:nsCocoaUtils::ToNSString(aPageUrl)];
+
+ NSArray* sharingService =
+ [NSSharingService sharingServicesForItems:[NSArray arrayWithObject:url]];
+ int32_t serviceCount = 0;
+
+ for (NSSharingService* currentService in sharingService) {
+ if (ShouldIgnoreProvider([currentService name])) {
+ continue;
+ }
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+
+ SetStrAttribute(aCx, obj, "name", [currentService name]);
+ SetStrAttribute(aCx, obj, "menuItemTitle", currentService.menuItemTitle);
+ SetStrAttribute(aCx, obj, "image", NSImageToBase64(currentService.image));
+
+ JS::Rooted<JS::Value> element(aCx, JS::ObjectValue(*obj));
+ JS_SetElement(aCx, array, serviceCount++, element);
+ }
+
+ aResult.setObject(*array);
+
+ return NS_OK;
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMacSharingService::OpenSharingPreferences() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSURL* prefPaneURL = [NSURL fileURLWithPath:extensionPrefPanePath isDirectory:YES];
+ NSDictionary* args = @{
+ openSharingSubpaneActionKey : openSharingSubpaneActionValue,
+ openSharingSubpaneProtocolKey : openSharingSubpaneProtocolValue
+ };
+ NSData* data = [NSPropertyListSerialization dataWithPropertyList:args
+ format:NSPropertyListXMLFormat_v1_0
+ options:0
+ error:nil];
+ NSAppleEventDescriptor* descriptor =
+ [[NSAppleEventDescriptor alloc] initWithDescriptorType:openSharingSubpaneDescriptorType
+ data:data];
+
+ [[NSWorkspace sharedWorkspace] openURLs:@[ prefPaneURL ]
+ withAppBundleIdentifier:nil
+ options:NSWorkspaceLaunchAsync
+ additionalEventParamDescriptor:descriptor
+ launchIdentifiers:NULL];
+
+ [descriptor release];
+
+ return NS_OK;
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMacSharingService::ShareUrl(const nsAString& aServiceName, const nsAString& aPageUrl,
+ const nsAString& aPageTitle) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSString* serviceName = nsCocoaUtils::ToNSString(aServiceName);
+ NSURL* pageUrl = [NSURL URLWithString:nsCocoaUtils::ToNSString(aPageUrl)];
+ NSString* pageTitle = nsCocoaUtils::ToNSString(aPageTitle);
+ NSSharingService* service = [NSSharingService sharingServiceNamed:serviceName];
+
+ // Reminders fetch its data from an activity, not the share data
+ if ([[service name] isEqual:remindersServiceName]) {
+ NSUserActivity* shareActivity =
+ [[NSUserActivity alloc] initWithActivityType:NSUserActivityTypeBrowsingWeb];
+
+ if ([pageUrl.scheme hasPrefix:@"http"]) {
+ [shareActivity setWebpageURL:pageUrl];
+ }
+ [shareActivity setTitle:pageTitle];
+ [shareActivity becomeCurrent];
+
+ // Pass ownership of shareActivity to shareDelegate, which will release the
+ // activity once sharing has completed.
+ SharingServiceDelegate* shareDelegate =
+ [[SharingServiceDelegate alloc] initWithActivity:shareActivity];
+ [shareActivity release];
+
+ [service setDelegate:shareDelegate];
+ [shareDelegate release];
+ }
+
+ // Twitter likes the the title as an additional share item
+ NSArray* toShare = [[service name] isEqual:NSSharingServiceNamePostOnTwitter]
+ ? @[ pageUrl, pageTitle ]
+ : @[ pageUrl ];
+
+ [service setSubject:pageTitle];
+ [service performWithItems:toShare];
+
+ 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..1e79ca92d5
--- /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..bd8070d1ae
--- /dev/null
+++ b/widget/cocoa/nsMacWebAppUtils.mm
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsMacWebAppUtils.h"
+#include "nsCOMPtr.h"
+#include "nsCocoaUtils.h"
+#include "nsString.h"
+
+// This must be included last:
+#include "nsObjCExceptions.h"
+
+// Find the path to the app with the given bundleIdentifier, if any.
+// Note that the OS will return the path to the newest binary, if there is more than one.
+// The determination of 'newest' is complex and beyond the scope of this comment.
+
+NS_IMPL_ISUPPORTS(nsMacWebAppUtils, nsIMacWebAppUtils)
+
+NS_IMETHODIMP nsMacWebAppUtils::PathForAppWithIdentifier(const nsAString& bundleIdentifier,
+ nsAString& outPath) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ outPath.Truncate();
+
+ nsAutoreleasePool localPool;
+
+ // note that the result of this expression might be nil, meaning no matching app was found.
+ NSString* temp = [[NSWorkspace sharedWorkspace]
+ absolutePathForAppBundleWithIdentifier:
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(
+ ((nsString)bundleIdentifier).get())
+ length:((nsString)bundleIdentifier).Length()]];
+
+ if (temp) {
+ // Copy out the resultant absolute path into outPath if non-nil.
+ nsCocoaUtils::GetStringForNSString(temp, outPath);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsMacWebAppUtils::LaunchAppWithIdentifier(const nsAString& bundleIdentifier) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsAutoreleasePool localPool;
+
+ // Note this might return false, meaning the app wasnt launched for some reason.
+ BOOL success = [[NSWorkspace sharedWorkspace]
+ launchAppWithBundleIdentifier:[NSString
+ stringWithCharacters:reinterpret_cast<const unichar*>(
+ ((nsString)bundleIdentifier)
+ .get())
+ length:((nsString)bundleIdentifier).Length()]
+ options:(NSWorkspaceLaunchOptions)0
+ additionalEventParamDescriptor:nil
+ launchIdentifier:NULL];
+
+ return success ? NS_OK : NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsMacWebAppUtils::TrashApp(const nsAString& path, nsITrashAppCallback* aCallback) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!aCallback)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsITrashAppCallback> callback = aCallback;
+
+ NSString* tempString =
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(((nsString)path).get())
+ length:path.Length()];
+
+ [[NSWorkspace sharedWorkspace]
+ recycleURLs:[NSArray arrayWithObject:[NSURL fileURLWithPath:tempString]]
+ completionHandler:^(NSDictionary* newURLs, NSError* error) {
+ nsresult rv = (error == nil) ? NS_OK : NS_ERROR_FAILURE;
+ callback->TrashAppFinished(rv);
+ }];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsMenuBarX.h b/widget/cocoa/nsMenuBarX.h
new file mode 100644
index 0000000000..5fd29aace4
--- /dev/null
+++ b/widget/cocoa/nsMenuBarX.h
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuBarX_h_
+#define nsMenuBarX_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/UniquePtr.h"
+#include "nsMenuBaseX.h"
+#include "nsMenuGroupOwnerX.h"
+#include "nsChangeObserver.h"
+#include "nsINativeMenuService.h"
+#include "nsString.h"
+
+class nsMenuBarX;
+class nsMenuX;
+class nsIWidget;
+class nsIContent;
+
+namespace mozilla {
+namespace dom {
+class Document;
+class Element;
+}
+}
+
+// ApplicationMenuDelegate is used to receive Cocoa notifications.
+@interface ApplicationMenuDelegate : NSObject <NSMenuDelegate> {
+ nsMenuBarX* mApplicationMenu; // weak ref
+}
+- (id)initWithApplicationMenu:(nsMenuBarX*)aApplicationMenu;
+@end
+
+// The native menu service for creating native menu bars.
+class nsNativeMenuServiceX : public nsINativeMenuService {
+ public:
+ NS_DECL_ISUPPORTS
+
+ nsNativeMenuServiceX() {}
+
+ NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent, mozilla::dom::Element* 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 {
+}
+- (BOOL)performSuperKeyEquivalent:(NSEvent*)theEvent;
+@end
+
+// Objective-C class used as action target for menu items
+@interface NativeMenuItemTarget : NSObject {
+}
+- (IBAction)menuItemHit:(id)sender;
+@end
+
+// Objective-C class used for menu items on the Services menu to allow Gecko
+// to override their standard behavior in order to stop key equivalents from
+// firing in certain instances.
+@interface GeckoServicesNSMenuItem : NSMenuItem {
+}
+- (id)target;
+- (SEL)action;
+- (void)_doNothing:(id)sender;
+@end
+
+// Objective-C class used as the Services menu so that Gecko can override the
+// standard behavior of the Services menu in order to stop key equivalents
+// from firing in certain instances.
+@interface GeckoServicesNSMenu : NSMenu {
+}
+- (void)addItem:(NSMenuItem*)newItem;
+- (NSMenuItem*)addItemWithTitle:(NSString*)aString
+ action:(SEL)aSelector
+ keyEquivalent:(NSString*)keyEquiv;
+- (void)insertItem:(NSMenuItem*)newItem atIndex:(NSInteger)index;
+- (NSMenuItem*)insertItemWithTitle:(NSString*)aString
+ action:(SEL)aSelector
+ keyEquivalent:(NSString*)keyEquiv
+ atIndex:(NSInteger)index;
+- (void)_overrideClassOfMenuItem:(NSMenuItem*)menuItem;
+@end
+
+// Once instantiated, this object lives until its DOM node or its parent window is destroyed.
+// Do not hold references to this, they can become invalid any time the DOM node can be destroyed.
+class nsMenuBarX : public nsMenuGroupOwnerX, public nsChangeObserver {
+ public:
+ nsMenuBarX();
+ virtual ~nsMenuBarX();
+
+ static NativeMenuItemTarget* sNativeEventTarget;
+ static nsMenuBarX* sLastGeckoMenuBarPainted;
+
+ // The following content nodes have been removed from the menu system.
+ // We save them here for use in command handling.
+ nsCOMPtr<nsIContent> mAboutItemContent;
+ nsCOMPtr<nsIContent> mPrefItemContent;
+ nsCOMPtr<nsIContent> mQuitItemContent;
+
+ // nsChangeObserver
+ NS_DECL_CHANGEOBSERVER
+
+ // nsMenuObjectX
+ void* NativeData() override { return (void*)mNativeMenu; }
+ nsMenuObjectTypeX MenuObjectType() override { return eMenuBarObjectType; }
+
+ // nsMenuBarX
+ nsresult Create(nsIWidget* aParent, mozilla::dom::Element* aElement);
+ 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();
+ void SetNeedsRebuild();
+ void ApplicationMenuOpened();
+ bool PerformKeyEquivalent(NSEvent* theEvent);
+
+ protected:
+ void ConstructNativeMenus();
+ void ConstructFallbackNativeMenus();
+ nsresult InsertMenuAtIndex(nsMenuX* aMenu, uint32_t aIndex);
+ void RemoveMenuAtIndex(uint32_t aIndex);
+ void HideItem(mozilla::dom::Document* inDoc, const nsAString& inID, nsIContent** outHiddenNode);
+ void AquifyMenuBar();
+ NSMenuItem* CreateNativeAppMenuItem(nsMenuX* inMenu, const nsAString& nodeID, SEL action, int tag,
+ NativeMenuItemTarget* target);
+ nsresult CreateApplicationMenu(nsMenuX* inMenu);
+
+ nsTArray<mozilla::UniquePtr<nsMenuX>> mMenuArray;
+ nsIWidget* mParentWindow; // [weak]
+ GeckoNSMenu* mNativeMenu; // root menu, representing entire menu bar
+ bool mNeedsRebuild;
+ ApplicationMenuDelegate* mApplicationMenuDelegate;
+};
+
+#endif // nsMenuBarX_h_
diff --git a/widget/cocoa/nsMenuBarX.mm b/widget/cocoa/nsMenuBarX.mm
new file mode 100644
index 0000000000..2f19995631
--- /dev/null
+++ b/widget/cocoa/nsMenuBarX.mm
@@ -0,0 +1,1007 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <objc/objc-runtime.h>
+
+#include "nsMenuBarX.h"
+#include "nsMenuX.h"
+#include "nsMenuItemX.h"
+#include "nsMenuUtilsX.h"
+#include "nsCocoaUtils.h"
+#include "nsCocoaWindow.h"
+#include "nsChildView.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsGkAtoms.h"
+#include "nsObjCExceptions.h"
+#include "nsThreadUtils.h"
+#include "nsTouchBarNativeAPIDefines.h"
+
+#include "nsIContent.h"
+#include "nsIWidget.h"
+#include "mozilla/dom/Document.h"
+#include "nsIAppStartup.h"
+#include "nsIStringBundle.h"
+#include "nsToolkitCompsCID.h"
+
+#include "mozilla/Components.h"
+#include "mozilla/dom/Element.h"
+
+NativeMenuItemTarget* nsMenuBarX::sNativeEventTarget = nil;
+nsMenuBarX* nsMenuBarX::sLastGeckoMenuBarPainted = nullptr;
+NSMenu* sApplicationMenu = nil;
+BOOL sApplicationMenuIsFallback = NO;
+BOOL gSomeMenuBarPainted = NO;
+
+// defined in nsCocoaWindow.mm.
+extern BOOL sTouchBarIsInitialized;
+
+// 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,
+ mozilla::dom::Element* aMenuBarElement) {
+ NS_ASSERTION(NS_IsMainThread(), "Attempting to create native menu bar on wrong thread!");
+
+ RefPtr<nsMenuBarX> mb = new nsMenuBarX();
+ if (!mb) return NS_ERROR_OUT_OF_MEMORY;
+
+ return mb->Create(aParent, aMenuBarElement);
+}
+
+//
+// ApplicationMenuDelegate Objective-C class
+//
+
+@implementation ApplicationMenuDelegate
+
+- (id)initWithApplicationMenu:(nsMenuBarX*)aApplicationMenu {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ mApplicationMenu = aApplicationMenu;
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)menuWillOpen:(NSMenu*)menu {
+ mApplicationMenu->ApplicationMenuOpened();
+}
+
+- (void)menuDidClose:(NSMenu*)menu {
+}
+
+@end
+
+nsMenuBarX::nsMenuBarX()
+ : nsMenuGroupOwnerX(),
+ mParentWindow(nullptr),
+ mNeedsRebuild(false),
+ mApplicationMenuDelegate(nil) {
+ 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();
+
+ if (mApplicationMenuDelegate) {
+ [mApplicationMenuDelegate release];
+ }
+
+ [mNativeMenu release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult nsMenuBarX::Create(nsIWidget* aParent, Element* aContent) {
+ if (!aParent) return NS_ERROR_INVALID_ARG;
+
+ mParentWindow = aParent;
+ mContent = aContent;
+
+ if (mContent) {
+ AquifyMenuBar();
+
+ nsresult rv = nsMenuGroupOwnerX::Create(aContent);
+ if (NS_FAILED(rv)) return rv;
+
+ RegisterForContentChanges(mContent, this);
+ ConstructNativeMenus();
+ } else {
+ ConstructFallbackNativeMenus();
+ }
+
+ // Give this to the parent window. The parent takes ownership.
+ static_cast<nsCocoaWindow*>(mParentWindow)->SetMenuBar(this);
+
+ return NS_OK;
+}
+
+void nsMenuBarX::ConstructNativeMenus() {
+ for (nsIContent* menuContent = mContent->GetFirstChild(); menuContent;
+ menuContent = menuContent->GetNextSibling()) {
+ if (menuContent->IsXULElement(nsGkAtoms::menu)) {
+ nsMenuX* newMenu = new nsMenuX();
+ if (newMenu) {
+ nsresult rv = newMenu->Create(this, this, menuContent->AsElement());
+ if (NS_SUCCEEDED(rv))
+ InsertMenuAtIndex(newMenu, GetMenuCount());
+ else
+ delete newMenu;
+ }
+ }
+ }
+}
+
+void nsMenuBarX::ConstructFallbackNativeMenus() {
+ if (sApplicationMenu) {
+ // Menu has already been built.
+ return;
+ }
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+
+ nsCOMPtr<nsIStringBundleService> bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ bundleSvc->CreateBundle("chrome://global/locale/fallbackMenubar.properties",
+ getter_AddRefs(stringBundle));
+
+ if (!stringBundle) {
+ return;
+ }
+
+ nsAutoString labelUTF16;
+ nsAutoString keyUTF16;
+
+ const char* labelProp = "quitMenuitem.label";
+ const char* keyProp = "quitMenuitem.key";
+
+ stringBundle->GetStringFromName(labelProp, labelUTF16);
+ stringBundle->GetStringFromName(keyProp, 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];
+ if (!mApplicationMenuDelegate) {
+ mApplicationMenuDelegate = [[ApplicationMenuDelegate alloc] initWithApplicationMenu:this];
+ }
+ [sApplicationMenu setDelegate:mApplicationMenuDelegate];
+ 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(mozilla::dom::Document* aDocument, nsIContent* aContent,
+ nsAtom* aAttribute) {}
+
+void nsMenuBarX::ObserveContentRemoved(mozilla::dom::Document* aDocument, nsIContent* aContainer,
+ nsIContent* aChild, nsIContent* aPreviousSibling) {
+ nsINode* parent = NODE_FROM(aContainer, aDocument);
+ MOZ_ASSERT(parent);
+ int32_t index = parent->ComputeIndexOf(aPreviousSibling) + 1;
+ RemoveMenuAtIndex(index);
+}
+
+void nsMenuBarX::ObserveContentInserted(mozilla::dom::Document* 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->ComputeIndexOf(aChild));
+ else
+ delete newMenu;
+ }
+}
+
+void nsMenuBarX::ForceUpdateNativeMenuAt(const nsAString& indexString) {
+ NSString* locationString =
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
+ unsigned int indexCount = [indexes count];
+ if (indexCount == 0) return;
+
+ nsMenuX* currentMenu = NULL;
+ int targetIndex = [[indexes objectAtIndex:0] intValue];
+ int visible = 0;
+ uint32_t length = mMenuArray.Length();
+ // first find a menu in the menu bar
+ for (unsigned int i = 0; i < length; i++) {
+ nsMenuX* menu = mMenuArray[i].get();
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) {
+ visible++;
+ if (visible == (targetIndex + 1)) {
+ currentMenu = menu;
+ break;
+ }
+ }
+ }
+
+ if (!currentMenu) return;
+
+ // fake open/close to cause lazy update to happen so submenus populate
+ currentMenu->MenuOpened();
+ currentMenu->MenuClosed();
+
+ // now find the correct submenu
+ for (unsigned int i = 1; currentMenu && i < indexCount; i++) {
+ targetIndex = [[indexes objectAtIndex:i] intValue];
+ visible = 0;
+ length = currentMenu->GetItemCount();
+ for (unsigned int j = 0; j < length; j++) {
+ nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j);
+ if (!targetMenu) return;
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) {
+ visible++;
+ if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) {
+ currentMenu = static_cast<nsMenuX*>(targetMenu);
+ // fake open/close to cause lazy update to happen
+ currentMenu->MenuOpened();
+ currentMenu->MenuClosed();
+ break;
+ }
+ }
+ }
+ }
+}
+
+// Calling this forces a full reload of the menu system, reloading all native
+// menus and their items.
+// Without this testing is hard because changes to the DOM affect the native
+// menu system lazily.
+void nsMenuBarX::ForceNativeMenuReload() {
+ // tear down everything
+ while (GetMenuCount() > 0) RemoveMenuAtIndex(0);
+
+ // construct everything
+ ConstructNativeMenus();
+}
+
+nsMenuX* nsMenuBarX::GetMenuAt(uint32_t aIndex) {
+ if (mMenuArray.Length() <= aIndex) {
+ NS_ERROR("Requesting menu at invalid index!");
+ return NULL;
+ }
+ return mMenuArray[aIndex].get();
+}
+
+nsMenuX* nsMenuBarX::GetXULHelpMenu() {
+ // The Help menu is usually (always?) the last one, so we start there and
+ // count back.
+ for (int32_t i = GetMenuCount() - 1; i >= 0; --i) {
+ nsMenuX* aMenu = GetMenuAt(i);
+ if (aMenu && nsMenuX::IsXULHelpMenu(aMenu->Content())) return aMenu;
+ }
+ return nil;
+}
+
+// On SnowLeopard and later we must tell the OS which is our Help menu.
+// Otherwise it will only add Spotlight for Help (the Search item) to our
+// Help menu if its label/title is "Help" -- i.e. if the menu is in English.
+// This resolves bugs 489196 and 539317.
+void nsMenuBarX::SetSystemHelpMenu() {
+ nsMenuX* xulHelpMenu = GetXULHelpMenu();
+ if (xulHelpMenu) {
+ NSMenu* helpMenu = (NSMenu*)xulHelpMenu->NativeData();
+ if (helpMenu) [NSApp setHelpMenu:helpMenu];
+ }
+}
+
+nsresult nsMenuBarX::Paint() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Don't try to optimize anything in this painting by checking
+ // sLastGeckoMenuBarPainted because the menubar can be manipulated by
+ // native dialogs and sheet code and other things besides this paint method.
+
+ // We have to keep the same menu item for the Application menu so we keep
+ // passing it along.
+ NSMenu* outgoingMenu = [NSApp mainMenu];
+ NS_ASSERTION([outgoingMenu numberOfItems] > 0,
+ "Main menu does not have any items, something is terribly wrong!");
+
+ NSMenuItem* appMenuItem = [[outgoingMenu itemAtIndex:0] retain];
+ [outgoingMenu removeItemAtIndex:0];
+ [mNativeMenu insertItem:appMenuItem atIndex:0];
+ [appMenuItem release];
+
+ // Set menu bar and event target.
+ [NSApp setMainMenu:mNativeMenu];
+ SetSystemHelpMenu();
+ nsMenuBarX::sLastGeckoMenuBarPainted = this;
+
+ gSomeMenuBarPainted = YES;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Returns the 'key' attribute of the 'shortcutID' object (if any) in the
+// currently active menubar's DOM document. 'shortcutID' should be the id
+// (i.e. the name) of a component that defines a commonly used (and
+// localized) cmd+key shortcut, and belongs to a keyset containing similar
+// objects. For example "key_selectAll". Returns a value that can be
+// compared to the first character of [NSEvent charactersIgnoringModifiers]
+// when [NSEvent modifierFlags] == NSEventModifierFlagCommand.
+char nsMenuBarX::GetLocalizedAccelKey(const char* shortcutID) {
+ if (!sLastGeckoMenuBarPainted) return 0;
+
+ nsCOMPtr<mozilla::dom::Document> doc = sLastGeckoMenuBarPainted->mContent->OwnerDoc();
+ if (!doc) return 0;
+
+ NS_ConvertASCIItoUTF16 shortcutIDStr(shortcutID);
+ nsCOMPtr<Element> shortcutContent = doc->GetElementById(shortcutIDStr);
+ 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;
+}
+
+void nsMenuBarX::SetNeedsRebuild() { mNeedsRebuild = true; }
+
+void nsMenuBarX::ApplicationMenuOpened() {
+ if (mNeedsRebuild) {
+ if (!mMenuArray.IsEmpty()) {
+ ResetNativeApplicationMenu();
+ CreateApplicationMenu(mMenuArray[0].get());
+ }
+ mNeedsRebuild = false;
+ }
+}
+
+bool nsMenuBarX::PerformKeyEquivalent(NSEvent* theEvent) {
+ return [mNativeMenu performSuperKeyEquivalent:theEvent];
+}
+
+// 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(mozilla::dom::Document* inDoc, const nsAString& inID,
+ nsIContent** outHiddenNode) {
+ nsCOMPtr<Element> menuElement = inDoc->GetElementById(inID);
+ if (menuElement) {
+ menuElement->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, u"true"_ns, false);
+ if (outHiddenNode) {
+ *outHiddenNode = menuElement.get();
+ NS_IF_ADDREF(*outHiddenNode);
+ }
+ }
+}
+
+// Do what is necessary to conform to the Aqua guidelines for menus.
+void nsMenuBarX::AquifyMenuBar() {
+ RefPtr<mozilla::dom::Document> domDoc = mContent->GetComposedDoc();
+ if (domDoc) {
+ // remove the "About..." item and its separator
+ HideItem(domDoc, u"aboutSeparator"_ns, nullptr);
+ HideItem(domDoc, u"aboutName"_ns, getter_AddRefs(mAboutItemContent));
+ if (!sAboutItemContent) sAboutItemContent = mAboutItemContent;
+
+ // remove quit item and its separator
+ HideItem(domDoc, u"menu_FileQuitSeparator"_ns, nullptr);
+ HideItem(domDoc, u"menu_FileQuitItem"_ns, 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, u"menu_PrefsSeparator"_ns, nullptr);
+ HideItem(domDoc, u"menu_preferences"_ns, getter_AddRefs(mPrefItemContent));
+ if (!sPrefItemContent) sPrefItemContent = mPrefItemContent;
+
+ // hide items that we use for the Application menu
+ HideItem(domDoc, u"menu_mac_services"_ns, nullptr);
+ HideItem(domDoc, u"menu_mac_hide_app"_ns, nullptr);
+ HideItem(domDoc, u"menu_mac_hide_others"_ns, nullptr);
+ HideItem(domDoc, u"menu_mac_show_all"_ns, nullptr);
+ HideItem(domDoc, u"menu_mac_touch_bar"_ns, 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;
+
+ RefPtr<mozilla::dom::Document> doc = inMenu->Content()->GetUncomposedDoc();
+ if (!doc) {
+ return nil;
+ }
+
+ RefPtr<mozilla::dom::Element> menuItem = doc->GetElementById(nodeID);
+ if (!menuItem) {
+ return nil;
+ }
+
+ // Check collapsed rather than hidden since the app menu items are always
+ // hidden in AquifyMenuBar.
+ if (menuItem->AttrValueIs(kNameSpaceID_None, nsGkAtoms::collapsed, nsGkAtoms::_true,
+ eCaseMatters)) {
+ return nil;
+ }
+
+ // Get information from the gecko menu item
+ nsAutoString label;
+ nsAutoString modifiers;
+ nsAutoString key;
+ menuItem->GetAttr(nsGkAtoms::label, label);
+ menuItem->GetAttr(nsGkAtoms::modifiers, modifiers);
+ menuItem->GetAttr(nsGkAtoms::key, key);
+
+ // 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()) {
+ RefPtr<Element> keyElement = doc->GetElementById(key);
+ if (keyElement) {
+ // first grab the key equivalent character
+ nsAutoString keyChar(u" "_ns);
+ keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyChar);
+ if (!keyChar.EqualsLiteral(" ")) {
+ keyEquiv = [[NSString stringWithCharacters:reinterpret_cast<const unichar*>(keyChar.get())
+ length:keyChar.Length()] lowercaseString];
+ }
+ // now grab the key equivalent modifiers
+ nsAutoString modifiersStr;
+ keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr);
+ uint8_t geckoModifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr);
+ macKeyModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(geckoModifiers);
+ }
+ }
+ // get the label into NSString-form
+ NSString* labelString =
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(label.get())
+ length:label.Length()];
+
+ if (!labelString) labelString = @"";
+ if (!keyEquiv) keyEquiv = @"";
+
+ // put together the actual NSMenuItem
+ NSMenuItem* newMenuItem = [[NSMenuItem alloc] initWithTitle:labelString
+ action:action
+ keyEquivalent:keyEquiv];
+
+ [newMenuItem setTag:tag];
+ [newMenuItem setTarget:target];
+ [newMenuItem setKeyEquivalentModifierMask:macKeyModifiers];
+
+ MenuItemInfo* info = [[MenuItemInfo alloc] initWithMenuGroupOwner:this];
+ [newMenuItem setRepresentedObject:info];
+ [info release];
+
+ return newMenuItem;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// build the Application menu shared by all menu bars
+nsresult nsMenuBarX::CreateApplicationMenu(nsMenuX* inMenu) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // At this point, the application menu is the application menu from
+ // the nib in cocoa widgets. We do not have a way to create an application
+ // menu manually, so we grab the one from the nib and use that.
+ sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain];
+
+ /*
+ We support the following menu items here:
+
+ Menu Item DOM Node ID Notes
+
+ ========================
+ = About This App = <- aboutName
+ ========================
+ = Preferences... = <- menu_preferences
+ ========================
+ = Services > = <- menu_mac_services <- (do not define key equivalent)
+ ========================
+ = Hide App = <- menu_mac_hide_app
+ = Hide Others = <- menu_mac_hide_others
+ = Show All = <- menu_mac_show_all
+ ========================
+ = Customize Touch Bar… = <- menu_mac_touch_bar
+ ========================
+ = Quit = <- menu_FileQuitItem
+ ========================
+
+ If any of them are ommitted from the application's DOM, we just don't add
+ them. We always add a "Quit" item, but if an app developer does not provide a
+ DOM node with the right ID for the Quit item, we add it in English. App
+ developers need only add each node with a label and a key equivalent (if they
+ want one). Other attributes are optional. Like so:
+
+ <menuitem id="menu_preferences"
+ label="&preferencesCmdMac.label;"
+ key="open_prefs_key"/>
+
+ We need to use this system for localization purposes, until we have a better way
+ to define the Application menu to be used on Mac OS X.
+ */
+
+ if (sApplicationMenu) {
+ if (!mApplicationMenuDelegate) {
+ mApplicationMenuDelegate = [[ApplicationMenuDelegate alloc] initWithApplicationMenu:this];
+ }
+ [sApplicationMenu setDelegate:mApplicationMenuDelegate];
+
+ // 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, u"aboutName"_ns, @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, u"menu_preferences"_ns, @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, u"menu_mac_services"_ns, 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, u"menu_mac_hide_app"_ns, @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, u"menu_mac_hide_others"_ns, @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, u"menu_mac_show_all"_ns, @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]];
+
+ BOOL addTouchBarSeparator = NO;
+
+ // Add Touch Bar customization menu item.
+ itemBeingAdded =
+ CreateNativeAppMenuItem(inMenu, u"menu_mac_touch_bar"_ns, @selector(menuItemHit:),
+ eCommand_ID_TouchBar, nsMenuBarX::sNativeEventTarget);
+
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ // We hide the menu item on Macs that don't have a Touch Bar.
+ if (!sTouchBarIsInitialized) {
+ [itemBeingAdded setHidden:YES];
+ } else {
+ addTouchBarSeparator = YES;
+ }
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+ }
+
+ // Add a separator after the Touch Bar menu item if it exists
+ if (addTouchBarSeparator) [sApplicationMenu addItem:[NSMenuItem separatorItem]];
+
+ // Add quit menu item
+ itemBeingAdded =
+ CreateNativeAppMenuItem(inMenu, u"menu_FileQuitItem"_ns, @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;
+}
+
+- (BOOL)performSuperKeyEquivalent:(NSEvent*)theEvent {
+ return [super performKeyEquivalent:theEvent];
+}
+
+@end
+
+//
+// Objective-C class used as action target for menu items
+//
+
+@implementation NativeMenuItemTarget
+
+// called when some menu item in this menu gets hit
+- (IBAction)menuItemHit:(id)sender {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!gMenuItemsExecuteCommands) {
+ return;
+ }
+
+ int tag = [sender tag];
+
+ nsMenuGroupOwnerX* menuGroupOwner = nullptr;
+ nsMenuBarX* menuBar = nullptr;
+ MenuItemInfo* info = [sender representedObject];
+
+ if (info) {
+ menuGroupOwner = [info menuGroupOwner];
+ if (!menuGroupOwner) {
+ return;
+ }
+ if (menuGroupOwner->MenuObjectType() == eMenuBarObjectType) {
+ menuBar = static_cast<nsMenuBarX*>(menuGroupOwner);
+ }
+ }
+
+ // Do special processing if this is for an app-global command.
+ if (tag == eCommand_ID_About) {
+ nsIContent* mostSpecificContent = sAboutItemContent;
+ if (menuBar && menuBar->mAboutItemContent) mostSpecificContent = menuBar->mAboutItemContent;
+ nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
+ return;
+ } else if (tag == eCommand_ID_Prefs) {
+ nsIContent* mostSpecificContent = sPrefItemContent;
+ if (menuBar && menuBar->mPrefItemContent) mostSpecificContent = menuBar->mPrefItemContent;
+ nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
+ return;
+ } else if (tag == eCommand_ID_HideApp) {
+ [NSApp hide:sender];
+ return;
+ } else if (tag == eCommand_ID_HideOthers) {
+ [NSApp hideOtherApplications:sender];
+ return;
+ } else if (tag == eCommand_ID_ShowAll) {
+ [NSApp unhideAllApplications:sender];
+ return;
+ } else if (tag == eCommand_ID_TouchBar) {
+ [NSApp toggleTouchBarCustomizationPalette:sender];
+ return;
+ } else if (tag == eCommand_ID_Quit) {
+ nsIContent* mostSpecificContent = sQuitItemContent;
+ if (menuBar && menuBar->mQuitItemContent) mostSpecificContent = menuBar->mQuitItemContent;
+ // If we have some content for quit we execute it. Otherwise we send a native app terminate
+ // message. If you want to stop a quit from happening, provide quit content and return
+ // the event as unhandled.
+ if (mostSpecificContent) {
+ nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
+ } else {
+ nsCOMPtr<nsIAppStartup> appStartup = mozilla::components::AppStartup::Service();
+ if (appStartup) {
+ bool userAllowedQuit = true;
+ appStartup->Quit(nsIAppStartup::eAttemptQuit, 0, &userAllowedQuit);
+ }
+ }
+ return;
+ }
+
+ // given the commandID, look it up in our hashtable and dispatch to
+ // that menu item.
+ if (menuGroupOwner) {
+ nsMenuItemX* menuItem = menuGroupOwner->GetMenuItemForCommandID(static_cast<uint32_t>(tag));
+ if (menuItem) menuItem->DoCommand();
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+// Objective-C class used for menu items on the Services menu to allow Gecko
+// to override their standard behavior in order to stop key equivalents from
+// firing in certain instances. When gMenuItemsExecuteCommands is NO, we return
+// a dummy target and action instead of the actual target and action.
+
+@implementation GeckoServicesNSMenuItem
+
+- (id)target {
+ id realTarget = [super target];
+ if (gMenuItemsExecuteCommands)
+ return realTarget;
+ else
+ return realTarget ? self : nil;
+}
+
+- (SEL)action {
+ SEL realAction = [super action];
+ if (gMenuItemsExecuteCommands)
+ return realAction;
+ else
+ return realAction ? @selector(_doNothing:) : NULL;
+}
+
+- (void)_doNothing:(id)sender {
+}
+
+@end
+
+// Objective-C class used as the Services menu so that Gecko can override the
+// standard behavior of the Services menu in order to stop key equivalents
+// from firing in certain instances.
+
+@implementation GeckoServicesNSMenu
+
+- (void)addItem:(NSMenuItem*)newItem {
+ [self _overrideClassOfMenuItem:newItem];
+ [super addItem:newItem];
+}
+
+- (NSMenuItem*)addItemWithTitle:(NSString*)aString
+ action:(SEL)aSelector
+ keyEquivalent:(NSString*)keyEquiv {
+ NSMenuItem* newItem = [super addItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv];
+ [self _overrideClassOfMenuItem:newItem];
+ return newItem;
+}
+
+- (void)insertItem:(NSMenuItem*)newItem atIndex:(NSInteger)index {
+ [self _overrideClassOfMenuItem:newItem];
+ [super insertItem:newItem atIndex:index];
+}
+
+- (NSMenuItem*)insertItemWithTitle:(NSString*)aString
+ action:(SEL)aSelector
+ keyEquivalent:(NSString*)keyEquiv
+ atIndex:(NSInteger)index {
+ NSMenuItem* newItem = [super insertItemWithTitle:aString
+ action:aSelector
+ keyEquivalent:keyEquiv
+ atIndex:index];
+ [self _overrideClassOfMenuItem:newItem];
+ return newItem;
+}
+
+- (void)_overrideClassOfMenuItem:(NSMenuItem*)menuItem {
+ if ([menuItem class] == [NSMenuItem class])
+ object_setClass(menuItem, [GeckoServicesNSMenuItem class]);
+}
+
+@end
diff --git a/widget/cocoa/nsMenuBaseX.h b/widget/cocoa/nsMenuBaseX.h
new file mode 100644
index 0000000000..ee8798ecc1
--- /dev/null
+++ b/widget/cocoa/nsMenuBaseX.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuBaseX_h_
+#define nsMenuBaseX_h_
+
+#import <Foundation/Foundation.h>
+
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+
+enum nsMenuObjectTypeX {
+ eMenuBarObjectType,
+ eSubmenuObjectType,
+ eMenuItemObjectType,
+ eStandaloneNativeMenuObjectType,
+};
+
+// All menu objects subclass this.
+// Menu bars are owned by their top-level nsIWidgets.
+// All other objects are memory-managed based on the DOM.
+// Content removal deletes them immediately and nothing else should.
+// Do not attempt to hold strong references to them or delete them.
+class nsMenuObjectX {
+ public:
+ virtual ~nsMenuObjectX() {}
+ virtual nsMenuObjectTypeX MenuObjectType() = 0;
+ virtual void* NativeData() = 0;
+ nsIContent* Content() { return mContent; }
+
+ /**
+ * Called when an icon of a menu item somewhere in this menu has updated.
+ * Menu objects with parents need to propagate the notification to their
+ * parent.
+ */
+ virtual void IconUpdated() {}
+
+ protected:
+ nsCOMPtr<nsIContent> mContent;
+};
+
+//
+// Object stored as "representedObject" for all menu items
+//
+
+class nsMenuGroupOwnerX;
+
+@interface MenuItemInfo : NSObject {
+ nsMenuGroupOwnerX* mMenuGroupOwner;
+}
+
+- (id)initWithMenuGroupOwner:(nsMenuGroupOwnerX*)aMenuGroupOwner;
+- (nsMenuGroupOwnerX*)menuGroupOwner;
+- (void)setMenuGroupOwner:(nsMenuGroupOwnerX*)aMenuGroupOwner;
+
+@end
+
+// Special command IDs that we know Mac OS X does not use for anything else.
+// We use these in place of carbon's IDs for these commands in order to stop
+// Carbon from messing with our event handlers. See bug 346883.
+
+enum {
+ eCommand_ID_About = 1,
+ eCommand_ID_Prefs = 2,
+ eCommand_ID_Quit = 3,
+ eCommand_ID_HideApp = 4,
+ eCommand_ID_HideOthers = 5,
+ eCommand_ID_ShowAll = 6,
+ eCommand_ID_Update = 7,
+ eCommand_ID_TouchBar = 8,
+ eCommand_ID_Last = 9
+};
+
+#endif // nsMenuBaseX_h_
diff --git a/widget/cocoa/nsMenuGroupOwnerX.h b/widget/cocoa/nsMenuGroupOwnerX.h
new file mode 100644
index 0000000000..627833e0df
--- /dev/null
+++ b/widget/cocoa/nsMenuGroupOwnerX.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuGroupOwnerX_h_
+#define nsMenuGroupOwnerX_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsMenuBaseX.h"
+#include "nsIMutationObserver.h"
+#include "nsHashKeys.h"
+#include "nsDataHashtable.h"
+#include "nsString.h"
+
+class nsMenuItemX;
+class nsChangeObserver;
+class nsIWidget;
+class nsIContent;
+
+class nsMenuGroupOwnerX : public nsMenuObjectX, public nsIMutationObserver {
+ public:
+ nsMenuGroupOwnerX();
+
+ nsresult Create(mozilla::dom::Element* aContent);
+
+ void RegisterForContentChanges(nsIContent* aContent, nsChangeObserver* aMenuObject);
+ void UnregisterForContentChanges(nsIContent* aContent);
+ uint32_t RegisterForCommand(nsMenuItemX* aItem);
+ void UnregisterCommand(uint32_t aCommandID);
+ nsMenuItemX* GetMenuItemForCommandID(uint32_t inCommandID);
+ void AddMenuItemInfoToSet(MenuItemInfo* info);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMUTATIONOBSERVER
+
+ protected:
+ virtual ~nsMenuGroupOwnerX();
+
+ nsChangeObserver* LookupContentChangeObserver(nsIContent* aContent);
+
+ uint32_t mCurrentCommandID; // unique command id (per menu-bar) to
+ // give to next item that asks
+
+ // stores observers for content change notification
+ nsDataHashtable<nsPtrHashKey<nsIContent>, nsChangeObserver*> mContentToObserverTable;
+
+ // stores mapping of command IDs to menu objects
+ nsDataHashtable<nsUint32HashKey, nsMenuItemX*> mCommandToMenuObjectTable;
+
+ // Stores references to all the MenuItemInfo objects created with weak
+ // references to us. They may live longer than we do, so when we're
+ // destroyed we need to clear all their weak references. This avoids
+ // crashes in -[NativeMenuItemTarget menuItemHit:]. See bug 1131473.
+ NSMutableSet* mInfoSet;
+};
+
+#endif // nsMenuGroupOwner_h_
diff --git a/widget/cocoa/nsMenuGroupOwnerX.mm b/widget/cocoa/nsMenuGroupOwnerX.mm
new file mode 100644
index 0000000000..9b5a222319
--- /dev/null
+++ b/widget/cocoa/nsMenuGroupOwnerX.mm
@@ -0,0 +1,191 @@
+/* -*- 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 "mozilla/dom/Document.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(mozilla::dom::Element* aContent) {
+ if (!aContent) return NS_ERROR_INVALID_ARG;
+
+ mContent = aContent;
+
+ return NS_OK;
+}
+
+//
+// nsIMutationObserver
+//
+
+void nsMenuGroupOwnerX::CharacterDataWillChange(nsIContent* aContent,
+ const CharacterDataChangeInfo&) {}
+
+void nsMenuGroupOwnerX::CharacterDataChanged(nsIContent* aContent, const CharacterDataChangeInfo&) {
+}
+
+void nsMenuGroupOwnerX::ContentAppended(nsIContent* aFirstNewContent) {
+ for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
+ ContentInserted(cur);
+ }
+}
+
+void nsMenuGroupOwnerX::NodeWillBeDestroyed(const nsINode* aNode) {}
+
+void nsMenuGroupOwnerX::AttributeWillChange(dom::Element* aContent, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType) {}
+
+void nsMenuGroupOwnerX::NativeAnonymousChildListChange(nsIContent* aContent, bool aIsRemove) {}
+
+void nsMenuGroupOwnerX::AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ nsChangeObserver* obs = LookupContentChangeObserver(aElement);
+ if (obs) obs->ObserveAttributeChanged(aElement->OwnerDoc(), aElement, aAttribute);
+}
+
+void nsMenuGroupOwnerX::ContentRemoved(nsIContent* aChild, nsIContent* aPreviousSibling) {
+ nsIContent* container = aChild->GetParent();
+ if (!container) {
+ return;
+ }
+
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ nsChangeObserver* obs = LookupContentChangeObserver(container);
+ if (obs)
+ obs->ObserveContentRemoved(aChild->OwnerDoc(), container, aChild, aPreviousSibling);
+ else if (container != mContent) {
+ // We do a lookup on the parent container in case things were removed
+ // under a "menupopup" item. That is basically a wrapper for the contents
+ // of a "menu" node.
+ nsCOMPtr<nsIContent> parent = container->GetParent();
+ if (parent) {
+ obs = LookupContentChangeObserver(parent);
+ if (obs)
+ obs->ObserveContentRemoved(aChild->OwnerDoc(), aChild->GetParent(), aChild,
+ aPreviousSibling);
+ }
+ }
+}
+
+void nsMenuGroupOwnerX::ContentInserted(nsIContent* aChild) {
+ nsIContent* container = aChild->GetParent();
+ if (!container) {
+ return;
+ }
+
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ nsChangeObserver* obs = LookupContentChangeObserver(container);
+ if (obs)
+ obs->ObserveContentInserted(aChild->OwnerDoc(), container, aChild);
+ else if (container != mContent) {
+ // We do a lookup on the parent container in case things were removed
+ // under a "menupopup" item. That is basically a wrapper for the contents
+ // of a "menu" node.
+ nsCOMPtr<nsIContent> parent = container->GetParent();
+ if (parent) {
+ obs = LookupContentChangeObserver(parent);
+ if (obs) obs->ObserveContentInserted(aChild->OwnerDoc(), container, 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..a5ecb3f991
--- /dev/null
+++ b/widget/cocoa/nsMenuItemIconX.h
@@ -0,0 +1,63 @@
+/* -*- 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 "IconLoaderHelperCocoa.h"
+
+class nsIconLoaderService;
+class nsIURI;
+class nsIContent;
+class nsIPrincipal;
+class imgRequestProxy;
+class nsMenuObjectX;
+
+#import <Cocoa/Cocoa.h>
+
+class nsMenuItemIconX : public mozilla::widget::IconLoaderListenerCocoa {
+ public:
+ nsMenuItemIconX(nsMenuObjectX* aMenuItem, nsIContent* aContent,
+ NSMenuItem* aNativeMenuItem);
+
+ private:
+ virtual ~nsMenuItemIconX();
+
+ public:
+ // 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);
+
+ // 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();
+
+ // Implements this method for mozilla::widget::IconLoaderListenerCocoa.
+ // Called once the icon load is complete.
+ nsresult OnComplete();
+
+ protected:
+ nsCOMPtr<nsIContent> mContent;
+ nsContentPolicyType mContentType;
+ nsMenuObjectX* mMenuObject; // [weak]
+ nsIntRect mImageRegionRect;
+ bool mSetIcon;
+ NSMenuItem* mNativeMenuItem; // [weak]
+ // The icon loader object should never outlive its creating nsMenuItemIconX
+ // object.
+ RefPtr<mozilla::widget::IconLoader> mIconLoader;
+ RefPtr<mozilla::widget::IconLoaderHelperCocoa> mIconLoaderHelper;
+};
+
+#endif // nsMenuItemIconX_h_
diff --git a/widget/cocoa/nsMenuItemIconX.mm b/widget/cocoa/nsMenuItemIconX.mm
new file mode 100644
index 0000000000..20d9d895d7
--- /dev/null
+++ b/widget/cocoa/nsMenuItemIconX.mm
@@ -0,0 +1,228 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Retrieves and displays icons in native menu items on Mac OS X.
+ */
+
+/* exception_defines.h defines 'try' to 'if (true)' which breaks objective-c
+ exceptions and produces errors like: error: unexpected '@' in program'.
+ If we define __EXCEPTIONS exception_defines.h will avoid doing this.
+
+ See bug 666609 for more information.
+
+ We use <limits> to get the libstdc++ version. */
+#include <limits>
+#if __GLIBCXX__ <= 20070719
+# ifndef __EXCEPTIONS
+# define __EXCEPTIONS
+# endif
+#endif
+
+#include "mozilla/dom/Document.h"
+#include "nsCocoaUtils.h"
+#include "nsComputedDOMStyle.h"
+#include "nsContentUtils.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsIContentPolicy.h"
+#include "nsMenuItemX.h"
+#include "nsMenuItemIconX.h"
+#include "nsNameSpaceManager.h"
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+
+using mozilla::dom::Element;
+using mozilla::widget::IconLoader;
+using mozilla::widget::IconLoaderHelperCocoa;
+
+static const uint32_t kIconSize = 16;
+
+nsMenuItemIconX::nsMenuItemIconX(nsMenuObjectX* aMenuItem, nsIContent* aContent,
+ NSMenuItem* aNativeMenuItem)
+ : mContent(aContent),
+ mContentType(nsIContentPolicy::TYPE_INTERNAL_IMAGE),
+ mMenuObject(aMenuItem),
+ mSetIcon(false),
+ mNativeMenuItem(aNativeMenuItem) {
+ MOZ_COUNT_CTOR(nsMenuItemIconX);
+}
+
+nsMenuItemIconX::~nsMenuItemIconX() {
+ Destroy();
+ MOZ_COUNT_DTOR(nsMenuItemIconX);
+}
+
+// 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 mNativeMenuItem.
+void nsMenuItemIconX::Destroy() {
+ if (mIconLoader) {
+ mIconLoader = nullptr;
+ }
+ if (mIconLoaderHelper) {
+ mIconLoaderHelper = nullptr;
+ }
+ mMenuObject = nullptr;
+ mNativeMenuItem = nil;
+}
+
+nsresult nsMenuItemIconX::SetupIcon() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Still don't have one, then something is wrong, get out of here.
+ if (!mNativeMenuItem) {
+ NS_ERROR("No native menu item");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> iconURI;
+ nsresult rv = GetIconURI(getter_AddRefs(iconURI));
+ if (NS_FAILED(rv)) {
+ // There is no icon for this menu item. An icon might have been set
+ // earlier. Clear it.
+ [mNativeMenuItem setImage:nil];
+
+ return NS_OK;
+ }
+
+ if (!mIconLoader) {
+ mIconLoaderHelper = new IconLoaderHelperCocoa(this, kIconSize, kIconSize);
+ mIconLoader = new IconLoader(mIconLoaderHelper, mContent, mImageRegionRect);
+ if (!mIconLoader) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ if (!mSetIcon) {
+ // Load placeholder icon.
+ [mNativeMenuItem setImage:mIconLoaderHelper->GetNativeIconImage()];
+ }
+
+ rv = mIconLoader->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];
+ }
+
+ mSetIcon = true;
+
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsMenuItemIconX::GetIconURI(nsIURI** aIconURI) {
+ if (!mMenuObject) return NS_ERROR_FAILURE;
+
+ // Mac native menu items support having both a checkmark and an icon
+ // simultaneously, but this is unheard of in the cross-platform toolkit,
+ // seemingly because the win32 theme is unable to cope with both at once.
+ // The downside is that it's possible to get a menu item marked with a
+ // native checkmark and a checkmark for an icon. Head off that possibility
+ // by pretending that no icon exists if this is a checkable menu item.
+ if (mMenuObject->MenuObjectType() == eMenuItemObjectType) {
+ nsMenuItemX* menuItem = static_cast<nsMenuItemX*>(mMenuObject);
+ if (menuItem->GetMenuItemType() != eRegularMenuItemType) return NS_ERROR_FAILURE;
+ }
+
+ if (!mContent) return NS_ERROR_FAILURE;
+
+ // First, look at the content node's "image" attribute.
+ nsAutoString imageURIString;
+ bool hasImageAttr =
+ mContent->IsElement() &&
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::image, imageURIString);
+
+ nsresult rv;
+ RefPtr<ComputedStyle> sc;
+ nsCOMPtr<nsIURI> iconURI;
+ if (!hasImageAttr) {
+ // If the content node has no "image" attribute, get the
+ // "list-style-image" property from CSS.
+ RefPtr<mozilla::dom::Document> document = mContent->GetComposedDoc();
+ if (!document || !mContent->IsElement()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ sc = nsComputedDOMStyle::GetComputedStyle(mContent->AsElement(), nullptr);
+ if (!sc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ iconURI = sc->StyleList()->GetListStyleImageURI();
+ if (!iconURI) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ uint64_t dummy = 0;
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = mContent->NodePrincipal();
+ nsContentUtils::GetContentPolicyTypeForUIImageLoading(
+ mContent, getter_AddRefs(triggeringPrincipal), mContentType, &dummy);
+
+ // If this menu item shouldn't have an icon, the string will be empty,
+ // and NS_NewURI will fail.
+ rv = NS_NewURI(getter_AddRefs(iconURI), 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();
+
+ iconURI.forget(aIconURI);
+
+ if (!hasImageAttr) {
+ // Check if the icon has a specified image region so that it can be
+ // cropped appropriately before being displayed.
+ const nsRect r = sc->StyleList()->GetImageRegion();
+
+ // Return NS_ERROR_FAILURE if the image region is invalid so the image
+ // is not drawn, and behavior is similar to XUL menus.
+ if (r.X() < 0 || r.Y() < 0 || r.Width() < 0 || r.Height() < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // 'auto' is represented by a [0, 0, 0, 0] rect. Only set mImageRegionRect
+ // if we have some other value.
+ if (!r.IsEmpty()) {
+ mImageRegionRect = r.ToNearestPixels(mozilla::AppUnitsPerCSSPixel());
+ }
+ }
+
+ return NS_OK;
+}
+
+//
+// mozilla::widget::IconLoaderListenerCocoa
+//
+
+nsresult nsMenuItemIconX::OnComplete() {
+ if (!mIconLoaderHelper) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSImage* image = mIconLoaderHelper->GetNativeIconImage();
+ if (!mNativeMenuItem) {
+ mIconLoaderHelper->Destroy();
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!image) {
+ [mNativeMenuItem setImage:nil];
+ return NS_OK;
+ }
+
+ [mNativeMenuItem setImage:image];
+ if (mMenuObject) {
+ mMenuObject->IconUpdated();
+ }
+
+ mIconLoaderHelper->Destroy();
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsMenuItemX.h b/widget/cocoa/nsMenuItemX.h
new file mode 100644
index 0000000000..ca102a426c
--- /dev/null
+++ b/widget/cocoa/nsMenuItemX.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 nsMenuItemX_h_
+#define nsMenuItemX_h_
+
+#include "mozilla/RefPtr.h"
+#include "nsMenuBaseX.h"
+#include "nsMenuGroupOwnerX.h"
+#include "nsChangeObserver.h"
+#include "nsStringFwd.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsMenuItemIconX;
+class nsMenuX;
+
+namespace mozilla {
+namespace dom {
+class Element;
+}
+} // namespace mozilla
+
+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]
+ RefPtr<mozilla::dom::Element> mCommandElement;
+ // The icon object should never outlive its creating nsMenuItemX object.
+ RefPtr<nsMenuItemIconX> mIcon;
+ bool mIsChecked;
+};
+
+#endif // nsMenuItemX_h_
diff --git a/widget/cocoa/nsMenuItemX.mm b/widget/cocoa/nsMenuItemX.mm
new file mode 100644
index 0000000000..92eb6a7ecf
--- /dev/null
+++ b/widget/cocoa/nsMenuItemX.mm
@@ -0,0 +1,360 @@
+/* -*- 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 "mozilla/dom/Event.h"
+#include "mozilla/ErrorResult.h"
+#include "nsIWidget.h"
+#include "mozilla/dom/Document.h"
+
+using namespace mozilla;
+
+using mozilla::dom::Event;
+using mozilla::dom::CallerType;
+
+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 (mCommandElement) mMenuGroupOwner->UnregisterForContentChanges(mCommandElement);
+
+ 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);
+
+ dom::Document* 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;
+ if (mContent->IsElement()) {
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::command, ourCommand);
+ }
+
+ if (!ourCommand.IsEmpty()) {
+ Element* commandElement = doc->GetElementById(ourCommand);
+
+ if (commandElement) {
+ mCommandElement = commandElement;
+ // register to observe the command DOM element
+ mMenuGroupOwner->RegisterForContentChanges(mCommandElement, this);
+ }
+ }
+ }
+
+ // decide enabled state based on command content if it exists, otherwise do it based
+ // on our own content
+ bool isEnabled;
+ if (mCommandElement)
+ isEnabled = !mCommandElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters);
+ else
+ isEnabled = !mContent->IsElement() ||
+ !mContent->AsElement()->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->IsElement() &&
+ mContent->AsElement()->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->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
+ mIsChecked ? u"true"_ns : u"false"_ns, 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->IsElement() ||
+ !mContent->AsElement()->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<dom::Document> parentDoc = mContent->OwnerDoc();
+
+ // create DOM event
+ ErrorResult rv;
+ RefPtr<Event> event = parentDoc->CreateEvent(u"Events"_ns, CallerType::System, rv);
+ if (rv.Failed()) {
+ NS_WARNING("Failed to create Event");
+ return rv.StealNSResult();
+ }
+ event->InitEvent(eventName, true, true);
+
+ // mark DOM event as trusted
+ event->SetTrusted(true);
+
+ // send DOM event
+ *preventDefaultCalled = mContent->DispatchEvent(*event, CallerType::System, rv);
+ if (rv.Failed()) {
+ NS_WARNING("Failed to send DOM event via EventTarget");
+ return rv.StealNSResult();
+ }
+
+ 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;
+ if (inCheckedContent->IsElement()) {
+ inCheckedContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name, myGroupName);
+ }
+ if (!myGroupName.Length()) // no groupname, nothing to do
+ return;
+
+ nsCOMPtr<nsIContent> parent = inCheckedContent->GetParent();
+ if (!parent) return;
+
+ // loop over siblings
+ for (nsIContent* sibling = parent->GetFirstChild(); sibling;
+ sibling = sibling->GetNextSibling()) {
+ if (sibling != inCheckedContent && sibling->IsElement()) { // skip this node
+ // if the current sibling is in the same group, clear it
+ if (sibling->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, myGroupName,
+ eCaseMatters)) {
+ sibling->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, u"false"_ns, true);
+ }
+ }
+ }
+}
+
+void nsMenuItemX::SetKeyEquiv() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Set key shortcut and modifiers
+ nsAutoString keyValue;
+ if (mContent->IsElement()) {
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue);
+ }
+
+ if (!keyValue.IsEmpty() && mContent->GetUncomposedDoc()) {
+ Element* 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.AssignLiteral(u" ");
+ }
+ }
+
+ 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(dom::Document* aDocument, nsIContent* aContent,
+ nsAtom* 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 && mContent->IsElement() &&
+ mContent->AsElement()->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->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters))
+ [mNativeMenuItem setEnabled:NO];
+ else
+ [mNativeMenuItem setEnabled:YES];
+ }
+ } else if (aContent == mCommandElement) {
+ // 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->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandDisabled);
+ mContent->AsElement()->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->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
+ else
+ mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandDisabled,
+ true);
+ }
+ // now we sync our native menu item with the command DOM node
+ if (aContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters))
+ [mNativeMenuItem setEnabled:NO];
+ else
+ [mNativeMenuItem setEnabled:YES];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+bool IsMenuStructureElement(nsIContent* aContent) {
+ return aContent->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuitem,
+ nsGkAtoms::menuseparator);
+}
+
+void nsMenuItemX::ObserveContentRemoved(dom::Document* aDocument, nsIContent* aContainer,
+ nsIContent* aChild, nsIContent* aPreviousSibling) {
+ if (aChild == mCommandElement) {
+ mMenuGroupOwner->UnregisterForContentChanges(mCommandElement);
+ mCommandElement = nullptr;
+ }
+ if (IsMenuStructureElement(aChild)) {
+ mMenuParent->SetRebuild(true);
+ }
+}
+
+void nsMenuItemX::ObserveContentInserted(dom::Document* aDocument, nsIContent* aContainer,
+ nsIContent* aChild) {
+ // The child node could come from the custom element that is for display, so
+ // only rebuild the menu if the child is related to the structure of the
+ // menu.
+ if (IsMenuStructureElement(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..e956efc051
--- /dev/null
+++ b/widget/cocoa/nsMenuUtilsX.h
@@ -0,0 +1,30 @@
+/* -*- 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"
+#include "nsStringFwd.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsIContent;
+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..e296ea630d
--- /dev/null
+++ b/widget/cocoa/nsMenuUtilsX.mm
@@ -0,0 +1,217 @@
+/* -*- 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 "nsMenuUtilsX.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/XULCommandEvent.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 "nsGlobalWindowInner.h"
+#include "nsPIDOMWindow.h"
+#include "nsQueryObject.h"
+
+using namespace mozilla;
+
+void nsMenuUtilsX::DispatchCommandTo(nsIContent* aTargetContent) {
+ MOZ_ASSERT(aTargetContent, "null ptr");
+
+ dom::Document* doc = aTargetContent->OwnerDoc();
+ if (doc) {
+ RefPtr<dom::XULCommandEvent> event =
+ new dom::XULCommandEvent(doc, doc->GetPresContext(), nullptr);
+
+ IgnoredErrorResult rv;
+ event->InitCommandEvent(u"command"_ns, true, true,
+ nsGlobalWindowInner::Cast(doc->GetInnerWindow()), 0, false, false,
+ false, false, nullptr, 0, rv);
+ // 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 (!rv.Failed()) {
+ event->SetTrusted(true);
+ aTargetContent->DispatchEvent(*event);
+ }
+ }
+}
+
+NSString* nsMenuUtilsX::GetTruncatedCocoaLabel(const nsString& itemLabel) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // We want to truncate long strings to some reasonable pixel length but there is no
+ // good API for doing that which works for all OS versions and architectures. For now
+ // we'll do nothing for consistency and depend on good user interface design to limit
+ // string lengths.
+ return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(itemLabel.get())
+ length:itemLabel.Length()];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+uint8_t nsMenuUtilsX::GeckoModifiersForNodeAttribute(const nsString& modifiersAttribute) {
+ uint8_t modifiers = knsMenuItemNoModifier;
+ char* str = ToNewCString(modifiersAttribute);
+ char* newStr;
+ char* token = strtok_r(str, ", \t", &newStr);
+ while (token != NULL) {
+ if (strcmp(token, "shift") == 0)
+ modifiers |= knsMenuItemShiftModifier;
+ else if (strcmp(token, "alt") == 0)
+ modifiers |= knsMenuItemAltModifier;
+ else if (strcmp(token, "control") == 0)
+ modifiers |= knsMenuItemControlModifier;
+ else if ((strcmp(token, "accel") == 0) || (strcmp(token, "meta") == 0)) {
+ modifiers |= knsMenuItemCommandModifier;
+ }
+ token = strtok_r(newStr, ", \t", &newStr);
+ }
+ free(str);
+
+ return modifiers;
+}
+
+unsigned int nsMenuUtilsX::MacModifiersForGeckoModifiers(uint8_t geckoModifiers) {
+ unsigned int macModifiers = 0;
+
+ if (geckoModifiers & knsMenuItemShiftModifier) macModifiers |= NSEventModifierFlagShift;
+ if (geckoModifiers & knsMenuItemAltModifier) macModifiers |= NSEventModifierFlagOption;
+ if (geckoModifiers & knsMenuItemControlModifier) macModifiers |= NSEventModifierFlagControl;
+ if (geckoModifiers & knsMenuItemCommandModifier) macModifiers |= NSEventModifierFlagCommand;
+
+ return macModifiers;
+}
+
+nsMenuBarX* nsMenuUtilsX::GetHiddenWindowMenuBar() {
+ nsIWidget* hiddenWindowWidgetNoCOMPtr = nsCocoaUtils::GetHiddenWindowWidget();
+ if (hiddenWindowWidgetNoCOMPtr)
+ return static_cast<nsCocoaWindow*>(hiddenWindowWidgetNoCOMPtr)->GetMenuBar();
+ else
+ return nullptr;
+}
+
+// It would be nice if we could localize these edit menu names.
+NSMenuItem* nsMenuUtilsX::GetStandardEditMenuItem() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // In principle we should be able to allocate this once and then always
+ // return the same object. But weird interactions happen between native
+ // app-modal dialogs and Gecko-modal dialogs that open above them. So what
+ // we return here isn't always released before it needs to be added to
+ // another menu. See bmo bug 468393.
+ NSMenuItem* standardEditMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Edit"
+ action:nil
+ keyEquivalent:@""] autorelease];
+ NSMenu* standardEditMenu = [[NSMenu alloc] initWithTitle:@"Edit"];
+ [standardEditMenuItem setSubmenu:standardEditMenu];
+ [standardEditMenu release];
+
+ // Add Undo
+ NSMenuItem* undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo"
+ action:@selector(undo:)
+ keyEquivalent:@"z"];
+ [standardEditMenu addItem:undoItem];
+ [undoItem release];
+
+ // Add Redo
+ NSMenuItem* redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo"
+ action:@selector(redo:)
+ keyEquivalent:@"Z"];
+ [standardEditMenu addItem:redoItem];
+ [redoItem release];
+
+ // Add separator
+ [standardEditMenu addItem:[NSMenuItem separatorItem]];
+
+ // Add Cut
+ NSMenuItem* cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut"
+ action:@selector(cut:)
+ keyEquivalent:@"x"];
+ [standardEditMenu addItem:cutItem];
+ [cutItem release];
+
+ // Add Copy
+ NSMenuItem* copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy"
+ action:@selector(copy:)
+ keyEquivalent:@"c"];
+ [standardEditMenu addItem:copyItem];
+ [copyItem release];
+
+ // Add Paste
+ NSMenuItem* pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste"
+ action:@selector(paste:)
+ keyEquivalent:@"v"];
+ [standardEditMenu addItem:pasteItem];
+ [pasteItem release];
+
+ // Add Delete
+ NSMenuItem* deleteItem = [[NSMenuItem alloc] initWithTitle:@"Delete"
+ action:@selector(delete:)
+ keyEquivalent:@""];
+ [standardEditMenu addItem:deleteItem];
+ [deleteItem release];
+
+ // Add Select All
+ NSMenuItem* selectAllItem = [[NSMenuItem alloc] initWithTitle:@"Select All"
+ action:@selector(selectAll:)
+ keyEquivalent:@"a"];
+ [standardEditMenu addItem:selectAllItem];
+ [selectAllItem release];
+
+ return standardEditMenuItem;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+bool nsMenuUtilsX::NodeIsHiddenOrCollapsed(nsIContent* inContent) {
+ return inContent->IsElement() &&
+ (inContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters) ||
+ inContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::collapsed,
+ nsGkAtoms::_true, eCaseMatters));
+}
+
+// Determines how many items are visible among the siblings in a menu that are
+// before the given child. This will not count the application menu.
+int nsMenuUtilsX::CalculateNativeInsertionPoint(nsMenuObjectX* aParent, nsMenuObjectX* aChild) {
+ int insertionPoint = 0;
+ nsMenuObjectTypeX parentType = aParent->MenuObjectType();
+ if (parentType == eMenuBarObjectType) {
+ nsMenuBarX* menubarParent = static_cast<nsMenuBarX*>(aParent);
+ uint32_t numMenus = menubarParent->GetMenuCount();
+ for (uint32_t i = 0; i < numMenus; i++) {
+ nsMenuX* currMenu = menubarParent->GetMenuAt(i);
+ if (currMenu == aChild) return insertionPoint; // we found ourselves, break out
+ if (currMenu && [currMenu->NativeMenuItem() menu]) insertionPoint++;
+ }
+ } else if (parentType == eSubmenuObjectType || parentType == eStandaloneNativeMenuObjectType) {
+ nsMenuX* menuParent;
+ if (parentType == eSubmenuObjectType)
+ menuParent = static_cast<nsMenuX*>(aParent);
+ else
+ menuParent = static_cast<nsStandaloneNativeMenu*>(aParent)->GetMenuXObject();
+
+ uint32_t numItems = menuParent->GetItemCount();
+ for (uint32_t i = 0; i < numItems; i++) {
+ // Using GetItemAt instead of GetVisibleItemAt to avoid O(N^2)
+ nsMenuObjectX* currItem = menuParent->GetItemAt(i);
+ if (currItem == aChild) return insertionPoint; // we found ourselves, break out
+ NSMenuItem* nativeItem = nil;
+ nsMenuObjectTypeX currItemType = currItem->MenuObjectType();
+ if (currItemType == eSubmenuObjectType)
+ nativeItem = static_cast<nsMenuX*>(currItem)->NativeMenuItem();
+ else
+ nativeItem = (NSMenuItem*)(currItem->NativeData());
+ if ([nativeItem menu]) insertionPoint++;
+ }
+ }
+ return insertionPoint;
+}
diff --git a/widget/cocoa/nsMenuX.h b/widget/cocoa/nsMenuX.h
new file mode 100644
index 0000000000..494f00d080
--- /dev/null
+++ b/widget/cocoa/nsMenuX.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuX_h_
+#define nsMenuX_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/EventForwards.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsMenuBaseX.h"
+#include "nsMenuBarX.h"
+#include "nsMenuGroupOwnerX.h"
+#include "nsCOMPtr.h"
+#include "nsChangeObserver.h"
+
+class nsMenuX;
+class nsMenuItemIconX;
+class nsMenuItemX;
+class nsIWidget;
+
+// MenuDelegate is used to receive Cocoa notifications for setting
+// up carbon events. Protocol is defined as of 10.6 SDK.
+@interface MenuDelegate : NSObject <NSMenuDelegate> {
+ nsMenuX* mGeckoMenu; // weak ref
+}
+- (id)initWithGeckoMenu:(nsMenuX*)geckoMenu;
+@end
+
+// Once instantiated, this object lives until its DOM node or its parent window is destroyed.
+// Do not hold references to this, they can become invalid any time the DOM node can be destroyed.
+class nsMenuX : public nsMenuObjectX, public nsChangeObserver {
+ public:
+ nsMenuX();
+ virtual ~nsMenuX();
+
+ // If > 0, the OS is indexing all the app's menus (triggered by opening
+ // Help menu on Leopard and higher). There are some things that are
+ // unsafe to do while this is happening.
+ static int32_t sIndexingMenuLevel;
+
+ NS_DECL_CHANGEOBSERVER
+
+ // nsMenuObjectX
+ void* NativeData() override { return (void*)mNativeMenu; }
+ nsMenuObjectTypeX MenuObjectType() override { return eSubmenuObjectType; }
+ void IconUpdated() override { mParent->IconUpdated(); }
+
+ // nsMenuX
+ nsresult Create(nsMenuObjectX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode);
+ uint32_t GetItemCount();
+ nsMenuObjectX* GetItemAt(uint32_t aPos);
+ nsresult GetVisibleItemCount(uint32_t& aCount);
+ nsMenuObjectX* GetVisibleItemAt(uint32_t aPos);
+ nsEventStatus MenuOpened();
+ void MenuClosed();
+ void SetRebuild(bool aMenuEvent);
+ NSMenuItem* NativeMenuItem();
+ nsresult SetupIcon();
+
+ static bool IsXULHelpMenu(nsIContent* aMenuContent);
+
+ protected:
+ void MenuConstruct();
+ nsresult RemoveAll();
+ nsresult SetEnabled(bool aIsEnabled);
+ nsresult GetEnabled(bool* aIsEnabled);
+ void GetMenuPopupContent(nsIContent** aResult);
+ bool OnOpen();
+ bool OnClose();
+ nsresult AddMenuItem(nsMenuItemX* aMenuItem);
+ nsMenuX* AddMenu(mozilla::UniquePtr<nsMenuX> aMenu);
+ void LoadMenuItem(nsIContent* inMenuItemContent);
+ void LoadSubMenu(nsIContent* inMenuContent);
+ GeckoNSMenu* CreateMenuWithGeckoString(nsString& menuTitle);
+
+ nsTArray<mozilla::UniquePtr<nsMenuObjectX>> mMenuObjectsArray;
+ nsString mLabel;
+ uint32_t mVisibleItemsCount; // cache
+ nsMenuObjectX* mParent; // [weak]
+ nsMenuGroupOwnerX* mMenuGroupOwner; // [weak]
+ // The icon object should never outlive its creating nsMenuX object.
+ RefPtr<nsMenuItemIconX> mIcon; // [strong]
+ 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;
+};
+
+#endif // nsMenuX_h_
diff --git a/widget/cocoa/nsMenuX.mm b/widget/cocoa/nsMenuX.mm
new file mode 100644
index 0000000000..0d8c13b4b7
--- /dev/null
+++ b/widget/cocoa/nsMenuX.mm
@@ -0,0 +1,933 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <dlfcn.h>
+
+#include "nsMenuX.h"
+#include "nsMenuItemX.h"
+#include "nsMenuUtilsX.h"
+#include "nsMenuItemIconX.h"
+#include "nsStandaloneNativeMenu.h"
+
+#include "nsObjCExceptions.h"
+
+#include "nsToolkit.h"
+#include "nsCocoaUtils.h"
+#include "nsCOMPtr.h"
+#include "prinrval.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "plstr.h"
+#include "nsGkAtoms.h"
+#include "nsCRT.h"
+#include "nsBaseWidget.h"
+
+#include "mozilla/dom/Document.h"
+#include "nsIContent.h"
+#include "nsIDocumentObserver.h"
+#include "nsIComponentManager.h"
+#include "nsIRollupListener.h"
+#include "nsIServiceManager.h"
+#include "nsXULPopupManager.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/EventDispatcher.h"
+
+#include "mozilla/MouseEvents.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+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) {
+ 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* aContent) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mContent = aContent;
+ if (mContent->IsElement()) {
+ mContent->AsElement()->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->IsElement() ||
+ !mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters));
+
+ // We call MenuConstruct here because keyboard commands are dependent upon
+ // native menu items being created. If we only call MenuConstruct when a menu
+ // is actually selected, then we can't access keyboard commands until the
+ // menu gets selected, which is bad.
+ MenuConstruct();
+
+ mIcon = new nsMenuItemIconX(this, mContent, mNativeMenuItem);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsMenuX::AddMenuItem(nsMenuItemX* aMenuItem) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!aMenuItem) return NS_ERROR_INVALID_ARG;
+
+ mMenuObjectsArray.AppendElement(aMenuItem);
+ if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(aMenuItem->Content())) return NS_OK;
+ ++mVisibleItemsCount;
+
+ NSMenuItem* newNativeMenuItem = (NSMenuItem*)aMenuItem->NativeData();
+
+ // add the menu item to this menu
+ [mNativeMenu addItem:newNativeMenuItem];
+
+ // set up target/action
+ [newNativeMenuItem setTarget:nsMenuBarX::sNativeEventTarget];
+ [newNativeMenuItem setAction:@selector(menuItemHit:)];
+
+ // set its command. we get the unique command id from the menubar
+ [newNativeMenuItem setTag:mMenuGroupOwner->RegisterForCommand(aMenuItem)];
+ MenuItemInfo* info = [[MenuItemInfo alloc] initWithMenuGroupOwner:mMenuGroupOwner];
+ [newNativeMenuItem setRepresentedObject:info];
+ [info release];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsMenuX* nsMenuX::AddMenu(UniquePtr<nsMenuX> aMenu) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // aMenu transfers ownership to mMenuObjectsArray and becomes nullptr, so
+ // we need to keep a raw pointer to access it conveniently.
+ nsMenuX* menu = aMenu.get();
+ mMenuObjectsArray.AppendElement(std::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.
+ if (mContent->IsElement()) {
+ mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::open, u"true"_ns, true);
+ }
+
+ // Fire a handler. If we're told to stop, don't build the menu at all
+ bool keepProcessing = OnOpen();
+
+ if (!mNeedsRebuild || !keepProcessing) return nsEventStatus_eConsumeNoDefault;
+
+ if (!mConstructed || mNeedsRebuild) {
+ if (mNeedsRebuild) RemoveAll();
+
+ MenuConstruct();
+ mConstructed = true;
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupShown, nullptr, WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ EventDispatcher::Dispatch(dispatchTo, nullptr, &event, 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;
+
+ if (mContent->IsElement()) {
+ mContent->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, true);
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupHidden, nullptr, WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ EventDispatcher::Dispatch(dispatchTo, nullptr, &event, nullptr, &status);
+
+ mDestroyHandlerCalled = true;
+ mConstructed = false;
+ }
+}
+
+void nsMenuX::MenuConstruct() {
+ mConstructed = false;
+ gConstructingMenu = true;
+
+ // reset destroy handler flag so that we'll know to fire it next time this menu goes away.
+ mDestroyHandlerCalled = false;
+
+ // printf("nsMenuX::MenuConstruct called for %s = %d \n",
+ // NS_LossyConvertUTF16toASCII(mLabel).get(), mNativeMenu);
+
+ // Retrieve our menupopup.
+ nsCOMPtr<nsIContent> menuPopup;
+ GetMenuPopupContent(getter_AddRefs(menuPopup));
+ if (!menuPopup) {
+ gConstructingMenu = false;
+ return;
+ }
+
+ // Iterate over the kids
+ for (nsIContent* child = menuPopup->GetFirstChild(); child; child = child->GetNextSibling()) {
+ // 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;
+ if (mParent->MenuObjectType() == eMenuBarObjectType) {
+ nsMenuBarX* mb = static_cast<nsMenuBarX*>(mParent);
+ mb->SetNeedsRebuild();
+ }
+ }
+}
+
+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;
+ if (inMenuItemContent->IsElement()) {
+ inMenuItemContent->AsElement()->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 if (inMenuItemContent->IsElement()) {
+ static Element::AttrValuesArray strings[] = {nsGkAtoms::checkbox, nsGkAtoms::radio, nullptr};
+ switch (inMenuItemContent->AsElement()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
+ strings, eCaseMatters)) {
+ case 0:
+ itemType = eCheckboxMenuItemType;
+ break;
+ case 1:
+ itemType = eRadioMenuItemType;
+ break;
+ }
+ }
+
+ // Create the item.
+ nsMenuItemX* menuItem = new nsMenuItemX();
+ if (!menuItem) return;
+
+ nsresult rv = menuItem->Create(this, menuitemName, itemType, mMenuGroupOwner, inMenuItemContent);
+ if (NS_FAILED(rv)) {
+ delete menuItem;
+ return;
+ }
+
+ AddMenuItem(menuItem);
+
+ // This needs to happen after the nsIMenuItem object is inserted into
+ // our item array in AddMenuItem()
+ menuItem->SetupIcon();
+}
+
+void nsMenuX::LoadSubMenu(nsIContent* inMenuContent) {
+ auto menu = MakeUnique<nsMenuX>();
+ if (!menu) return;
+
+ nsresult rv = menu->Create(this, mMenuGroupOwner, inMenuContent);
+ if (NS_FAILED(rv)) return;
+
+ // |menu|'s ownership is transfer to AddMenu but, if it is successfully
+ // added, we can access it via the returned raw pointer.
+ nsMenuX* menu_ptr = AddMenu(std::move(menu));
+
+ // This needs to happen after the nsIMenu object is inserted into
+ // our item array in AddMenu()
+ if (menu_ptr) {
+ menu_ptr->SetupIcon();
+ }
+}
+
+// This menu is about to open. Returns TRUE if we should keep processing the event,
+// FALSE if the handler wants to stop the opening of the menu.
+bool nsMenuX::OnOpen() {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupShowing, nullptr, WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+
+ nsresult rv = NS_OK;
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ rv = EventDispatcher::Dispatch(dispatchTo, nullptr, &event, nullptr, &status);
+ if (NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault) return false;
+
+ // If the open is going to succeed we need to walk our menu items, checking to
+ // see if any of them have a command attribute. If so, several attributes
+ // must potentially be updated.
+
+ // Get new popup content first since it might have changed as a result of the
+ // eXULPopupShowing event above.
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+ if (!popupContent) return true;
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ pm->UpdateMenuItems(popupContent);
+ }
+
+ return true;
+}
+
+// Returns TRUE if we should keep processing the event, FALSE if the handler
+// wants to stop the closing of the menu.
+bool nsMenuX::OnClose() {
+ if (mDestroyHandlerCalled) return true;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupHiding, nullptr, WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+
+ nsresult rv = NS_OK;
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ rv = EventDispatcher::Dispatch(dispatchTo, nullptr, &event, 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).
+ if (mContent->IsXULElement(nsGkAtoms::menupopup)) {
+ NS_ADDREF(*aResult = mContent);
+ return;
+ }
+
+ // Otherwise check our child nodes.
+
+ for (nsIContent* child = mContent->GetFirstChild(); child; child = child->GetNextSibling()) {
+ if (child->IsXULElement(nsGkAtoms::menupopup)) {
+ NS_ADDREF(*aResult = child);
+ return;
+ }
+ }
+}
+
+NSMenuItem* nsMenuX::NativeMenuItem() { return mNativeMenuItem; }
+
+bool nsMenuX::IsXULHelpMenu(nsIContent* aMenuContent) {
+ bool retval = false;
+ if (aMenuContent && aMenuContent->IsElement()) {
+ nsAutoString id;
+ aMenuContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
+ if (id.Equals(u"helpMenu"_ns)) retval = true;
+ }
+ return retval;
+}
+
+//
+// nsChangeObserver
+//
+
+void nsMenuX::ObserveAttributeChanged(dom::Document* aDocument, nsIContent* aContent,
+ nsAtom* 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->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters));
+ } else if (aAttribute == nsGkAtoms::label) {
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, mLabel);
+
+ // invalidate my parent. If we're a submenu parent, we have to rebuild
+ // the parent menu in order for the changes to be picked up. If we're
+ // a regular menu, just change the title and redraw the menubar.
+ if (parentType == eMenuBarObjectType) {
+ // reuse the existing menu, to avoid rebuilding the root menu bar.
+ NS_ASSERTION(mNativeMenu, "nsMenuX::AttributeChanged: invalid menu handle.");
+ NSString* newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel);
+ [mNativeMenu setTitle:newCocoaLabelString];
+ } else if (parentType == eSubmenuObjectType) {
+ static_cast<nsMenuX*>(mParent)->SetRebuild(true);
+ } else if (parentType == eStandaloneNativeMenuObjectType) {
+ static_cast<nsStandaloneNativeMenu*>(mParent)->GetMenuXObject()->SetRebuild(true);
+ }
+ } else if (aAttribute == nsGkAtoms::hidden || aAttribute == nsGkAtoms::collapsed) {
+ SetRebuild(true);
+
+ bool contentIsHiddenOrCollapsed = nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent);
+
+ // don't do anything if the state is correct already
+ if (contentIsHiddenOrCollapsed != mVisible) return;
+
+ if (contentIsHiddenOrCollapsed) {
+ if (parentType == eMenuBarObjectType || parentType == eSubmenuObjectType ||
+ parentType == eStandaloneNativeMenuObjectType) {
+ NSMenu* parentMenu = (NSMenu*)mParent->NativeData();
+ // An exception will get thrown if we try to remove an item that isn't
+ // in the menu.
+ if ([parentMenu indexOfItem:mNativeMenuItem] != -1) [parentMenu removeItem:mNativeMenuItem];
+ mVisible = false;
+ }
+ } else {
+ if (parentType == eMenuBarObjectType || parentType == eSubmenuObjectType ||
+ parentType == eStandaloneNativeMenuObjectType) {
+ int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(mParent, this);
+ if (parentType == eMenuBarObjectType) {
+ // Before inserting we need to figure out if we should take the native
+ // application menu into account.
+ nsMenuBarX* mb = static_cast<nsMenuBarX*>(mParent);
+ if (mb->MenuContainsAppMenu()) insertionIndex++;
+ }
+ NSMenu* parentMenu = (NSMenu*)mParent->NativeData();
+ [parentMenu insertItem:mNativeMenuItem atIndex:insertionIndex];
+ [mNativeMenuItem setSubmenu:mNativeMenu];
+ mVisible = true;
+ }
+ }
+ } else if (aAttribute == nsGkAtoms::image) {
+ SetupIcon();
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsMenuX::ObserveContentRemoved(dom::Document* aDocument, nsIContent* aContainer,
+ nsIContent* aChild, nsIContent* aPreviousSibling) {
+ if (gConstructingMenu) return;
+
+ SetRebuild(true);
+ mMenuGroupOwner->UnregisterForContentChanges(aChild);
+}
+
+void nsMenuX::ObserveContentInserted(dom::Document* aDocument, nsIContent* aContainer,
+ nsIContent* aChild) {
+ if (gConstructingMenu) return;
+
+ SetRebuild(true);
+}
+
+nsresult nsMenuX::SetupIcon() {
+ // In addition to out-of-memory, menus that are children of the menu bar
+ // will not have mIcon set.
+ if (!mIcon) return NS_ERROR_OUT_OF_MEMORY;
+
+ return mIcon->SetupIcon();
+}
+
+//
+// MenuDelegate Objective-C class, used to set up Carbon events
+//
+
+@implementation MenuDelegate
+
+- (id)initWithGeckoMenu:(nsMenuX*)geckoMenu {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ NS_ASSERTION(geckoMenu,
+ "Cannot initialize native menu delegate with NULL gecko menu! Will crash!");
+ mGeckoMenu = geckoMenu;
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)menu:(NSMenu*)menu willHighlightItem:(NSMenuItem*)item {
+ if (!menu || !item || !mGeckoMenu) return;
+
+ nsMenuObjectX* target = mGeckoMenu->GetVisibleItemAt((uint32_t)[menu indexOfItem:item]);
+ if (target && (target->MenuObjectType() == eMenuItemObjectType)) {
+ nsMenuItemX* targetMenuItem = static_cast<nsMenuItemX*>(target);
+ bool handlerCalledPreventDefault; // but we don't actually care
+ targetMenuItem->DispatchDOMEvent(u"DOMMenuItemActive"_ns, &handlerCalledPreventDefault);
+ }
+}
+
+- (void)menuWillOpen:(NSMenu*)menu {
+ if (!mGeckoMenu) return;
+
+ // Don't do anything while the OS is (re)indexing our menus (on Leopard and
+ // higher). This stops the Help menu from being able to search in our
+ // menus, but it also resolves many other problems.
+ if (nsMenuX::sIndexingMenuLevel > 0) return;
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ if (rollupListener) {
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (rollupWidget) {
+ rollupListener->Rollup(0, true, nullptr, nullptr);
+ [menu cancelTracking];
+ return;
+ }
+ }
+ mGeckoMenu->MenuOpened();
+}
+
+- (void)menuDidClose:(NSMenu*)menu {
+ if (!mGeckoMenu) return;
+
+ // Don't do anything while the OS is (re)indexing our menus (on Leopard and
+ // higher). This stops the Help menu from being able to search in our
+ // menus, but it also resolves many other problems.
+ if (nsMenuX::sIndexingMenuLevel > 0) return;
+
+ mGeckoMenu->MenuClosed();
+}
+
+@end
+
+// OS X Leopard (at least as of 10.5.2) has an obscure bug triggered by some
+// behavior that's present in Mozilla.org browsers but not (as best I can
+// tell) in Apple products like Safari. (It's not yet clear exactly what this
+// behavior is.)
+//
+// The bug is that sometimes you crash on quit in nsMenuX::RemoveAll(), on a
+// call to [NSMenu removeItemAtIndex:]. The crash is caused by trying to
+// access a deleted NSMenuItem object (sometimes (perhaps always?) by trying
+// to send it a _setChangedFlags: message). Though this object was deleted
+// some time ago, it remains registered as a potential target for a particular
+// key equivalent. So when [NSMenu removeItemAtIndex:] removes the current
+// target for that same key equivalent, the OS tries to "activate" the
+// previous target.
+//
+// The underlying reason appears to be that NSMenu's _addItem:toTable: and
+// _removeItem:fromTable: methods (which are used to keep a hashtable of
+// registered key equivalents) don't properly "retain" and "release"
+// NSMenuItem objects as they are added to and removed from the hashtable.
+//
+// Our (hackish) workaround is to shadow the OS's hashtable with another
+// hastable of our own (gShadowKeyEquivDB), and use it to "retain" and
+// "release" NSMenuItem objects as needed. This resolves bmo bugs 422287 and
+// 423669. When (if) Apple fixes this bug, we can remove this workaround.
+
+static NSMutableDictionary* gShadowKeyEquivDB = nil;
+
+// Class for values in gShadowKeyEquivDB.
+
+@interface KeyEquivDBItem : NSObject {
+ NSMenuItem* mItem;
+ NSMutableSet* mTables;
+}
+
+- (id)initWithItem:(NSMenuItem*)aItem table:(NSMapTable*)aTable;
+- (BOOL)hasTable:(NSMapTable*)aTable;
+- (int)addTable:(NSMapTable*)aTable;
+- (int)removeTable:(NSMapTable*)aTable;
+
+@end
+
+@implementation KeyEquivDBItem
+
+- (id)initWithItem:(NSMenuItem*)aItem table:(NSMapTable*)aTable {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!gShadowKeyEquivDB) gShadowKeyEquivDB = [[NSMutableDictionary alloc] init];
+ self = [super init];
+ if (aItem && aTable) {
+ mTables = [[NSMutableSet alloc] init];
+ mItem = [aItem retain];
+ [mTables addObject:[NSValue valueWithPointer:aTable]];
+ } else {
+ mTables = nil;
+ mItem = nil;
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mTables) [mTables release];
+ if (mItem) [mItem release];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (BOOL)hasTable:(NSMapTable*)aTable {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return [mTables member:[NSValue valueWithPointer:aTable]] ? YES : NO;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+// Does nothing if aTable (its index value) is already present in mTables.
+- (int)addTable:(NSMapTable*)aTable {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (aTable) [mTables addObject:[NSValue valueWithPointer:aTable]];
+ return [mTables count];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+- (int)removeTable:(NSMapTable*)aTable {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (aTable) {
+ NSValue* objectToRemove = [mTables member:[NSValue valueWithPointer:aTable]];
+ if (objectToRemove) [mTables removeObject:objectToRemove];
+ }
+ return [mTables count];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+@end
+
+@interface NSMenu (MethodSwizzling)
++ (void)nsMenuX_NSMenu_addItem:(NSMenuItem*)aItem toTable:(NSMapTable*)aTable;
++ (void)nsMenuX_NSMenu_removeItem:(NSMenuItem*)aItem fromTable:(NSMapTable*)aTable;
+@end
+
+@implementation NSMenu (MethodSwizzling)
+
++ (void)nsMenuX_NSMenu_addItem:(NSMenuItem*)aItem toTable:(NSMapTable*)aTable {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (aItem && aTable) {
+ NSValue* key = [NSValue valueWithPointer:aItem];
+ KeyEquivDBItem* shadowItem = [gShadowKeyEquivDB objectForKey:key];
+ if (shadowItem) {
+ [shadowItem addTable:aTable];
+ } else {
+ shadowItem = [[KeyEquivDBItem alloc] initWithItem:aItem table:aTable];
+ [gShadowKeyEquivDB setObject:shadowItem forKey:key];
+ // Release after [NSMutableDictionary setObject:forKey:] retains it (so
+ // that it will get dealloced when removeObjectForKey: is called).
+ [shadowItem release];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+
+ [self nsMenuX_NSMenu_addItem:aItem toTable:aTable];
+}
+
++ (void)nsMenuX_NSMenu_removeItem:(NSMenuItem*)aItem fromTable:(NSMapTable*)aTable {
+ [self nsMenuX_NSMenu_removeItem:aItem fromTable:aTable];
+
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (aItem && aTable) {
+ NSValue* key = [NSValue valueWithPointer:aItem];
+ KeyEquivDBItem* shadowItem = [gShadowKeyEquivDB objectForKey:key];
+ if (shadowItem && [shadowItem hasTable:aTable]) {
+ if (![shadowItem removeTable:aTable]) [gShadowKeyEquivDB removeObjectForKey:key];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+// This class is needed to keep track of when the OS is (re)indexing all of
+// our menus. This appears to only happen on Leopard and higher, and can
+// be triggered by opening the Help menu. Some operations are unsafe while
+// this is happening -- notably the calls to [[NSImage alloc]
+// initWithSize:imageRect.size] and [newImage lockFocus] in nsMenuItemIconX::
+// OnStopFrame(). But we don't yet have a complete list, and Apple doesn't
+// yet have any documentation on this subject. (Apple also doesn't yet have
+// any documented way to find the information we seek here.) The "original"
+// of this class (the one whose indexMenuBarDynamically method we hook) is
+// defined in the Shortcut framework in /System/Library/PrivateFrameworks.
+@interface NSObject (SCTGRLIndexMethodSwizzling)
+- (void)nsMenuX_SCTGRLIndex_indexMenuBarDynamically;
+@end
+
+@implementation NSObject (SCTGRLIndexMethodSwizzling)
+
+- (void)nsMenuX_SCTGRLIndex_indexMenuBarDynamically {
+ // This method appears to be called (once) whenever the OS (re)indexes our
+ // menus. sIndexingMenuLevel is a int32_t just in case it might be
+ // reentered. As it's running, it spawns calls to two undocumented
+ // HIToolbox methods (_SimulateMenuOpening() and _SimulateMenuClosed()),
+ // which "simulate" the opening and closing of our menus without actually
+ // displaying them.
+ ++nsMenuX::sIndexingMenuLevel;
+ [self nsMenuX_SCTGRLIndex_indexMenuBarDynamically];
+ --nsMenuX::sIndexingMenuLevel;
+}
+
+@end
diff --git a/widget/cocoa/nsNativeBasicThemeCocoa.cpp b/widget/cocoa/nsNativeBasicThemeCocoa.cpp
new file mode 100644
index 0000000000..c6efa2e7d9
--- /dev/null
+++ b/widget/cocoa/nsNativeBasicThemeCocoa.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 40; 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 "nsNativeBasicThemeCocoa.h"
+#include "mozilla/gfx/Helpers.h"
+
+already_AddRefed<nsITheme> do_GetBasicNativeThemeDoNotUseDirectly() {
+ static mozilla::StaticRefPtr<nsITheme> gInstance;
+ if (MOZ_UNLIKELY(!gInstance)) {
+ gInstance = new nsNativeBasicThemeCocoa();
+ ClearOnShutdown(&gInstance);
+ }
+ return do_AddRef(gInstance);
+}
+
+NS_IMETHODIMP
+nsNativeBasicThemeCocoa::GetMinimumWidgetSize(
+ nsPresContext* aPresContext, nsIFrame* aFrame, StyleAppearance aAppearance,
+ mozilla::LayoutDeviceIntSize* aResult, bool* aIsOverridable) {
+ DPIRatio dpiRatio = GetDPIRatio(aFrame);
+
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight: {
+ *aIsOverridable = false;
+ *aResult = ScrollbarDrawingMac::GetMinimumWidgetSize(aAppearance, aFrame,
+ dpiRatio.scale);
+ break;
+ }
+
+ default:
+ return nsNativeBasicTheme::GetMinimumWidgetSize(
+ aPresContext, aFrame, aAppearance, aResult, aIsOverridable);
+ }
+
+ return NS_OK;
+}
+
+void nsNativeBasicThemeCocoa::PaintScrollbarThumb(
+ DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, bool aHorizontal,
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aElementState, const EventStates& aDocumentState,
+ DPIRatio aDpiRatio) {
+ ScrollbarParams params =
+ ScrollbarDrawingMac::ComputeScrollbarParams(aFrame, aStyle, aHorizontal);
+ auto rect = aRect.ToUnknownRect();
+ if (aDpiRatio.scale >= 2.0f) {
+ mozilla::gfx::AutoRestoreTransform autoRestoreTransform(aDrawTarget);
+ aDrawTarget->SetTransform(aDrawTarget->GetTransform().PreScale(2.0f, 2.0f));
+ rect.Scale(1.0f / 2.0f);
+ ScrollbarDrawingMac::DrawScrollbarThumb(*aDrawTarget, rect, params);
+ } else {
+ ScrollbarDrawingMac::DrawScrollbarThumb(*aDrawTarget, rect, params);
+ }
+}
+
+void nsNativeBasicThemeCocoa::PaintScrollbarTrack(
+ DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, bool aHorizontal,
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aDocumentState, DPIRatio aDpiRatio, bool aIsRoot) {
+ ScrollbarParams params =
+ ScrollbarDrawingMac::ComputeScrollbarParams(aFrame, aStyle, aHorizontal);
+ auto rect = aRect.ToUnknownRect();
+ if (aDpiRatio.scale >= 2.0f) {
+ mozilla::gfx::AutoRestoreTransform autoRestoreTransform(aDrawTarget);
+ aDrawTarget->SetTransform(aDrawTarget->GetTransform().PreScale(2.0f, 2.0f));
+ rect.Scale(1.0f / 2.0f);
+ ScrollbarDrawingMac::DrawScrollbarTrack(*aDrawTarget, rect, params);
+ } else {
+ ScrollbarDrawingMac::DrawScrollbarTrack(*aDrawTarget, rect, params);
+ }
+}
+
+void nsNativeBasicThemeCocoa::PaintScrollbar(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ bool aHorizontal, nsIFrame* aFrame,
+ const ComputedStyle& aStyle,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio, bool aIsRoot) {
+ // Draw nothing; the scrollbar track is drawn in PaintScrollbarTrack.
+}
+
+void nsNativeBasicThemeCocoa::PaintScrollCorner(
+ DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, nsIFrame* aFrame,
+ const ComputedStyle& aStyle, const EventStates& aDocumentState,
+ DPIRatio aDpiRatio, bool aIsRoot) {
+ ScrollbarParams params =
+ ScrollbarDrawingMac::ComputeScrollbarParams(aFrame, aStyle, false);
+ if (aDpiRatio.scale >= 2.0f) {
+ mozilla::gfx::AutoRestoreTransform autoRestoreTransform(aDrawTarget);
+ aDrawTarget->SetTransform(aDrawTarget->GetTransform().PreScale(2.0f, 2.0f));
+ auto rect = aRect.ToUnknownRect();
+ rect.Scale(1 / 2.0f);
+ ScrollbarDrawingMac::DrawScrollCorner(*aDrawTarget, rect, params);
+ } else {
+ auto rect = aRect.ToUnknownRect();
+ ScrollbarDrawingMac::DrawScrollCorner(*aDrawTarget, rect, params);
+ }
+}
diff --git a/widget/cocoa/nsNativeBasicThemeCocoa.h b/widget/cocoa/nsNativeBasicThemeCocoa.h
new file mode 100644
index 0000000000..c53fd07151
--- /dev/null
+++ b/widget/cocoa/nsNativeBasicThemeCocoa.h
@@ -0,0 +1,51 @@
+/* -*- 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 nsNativeBasicThemeCocoa_h
+#define nsNativeBasicThemeCocoa_h
+
+#include "nsNativeBasicTheme.h"
+
+#include "ScrollbarDrawingMac.h"
+
+class nsNativeBasicThemeCocoa : public nsNativeBasicTheme {
+ public:
+ nsNativeBasicThemeCocoa() = default;
+
+ using ScrollbarParams = mozilla::widget::ScrollbarParams;
+ using ScrollbarDrawingMac = mozilla::widget::ScrollbarDrawingMac;
+
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) override;
+
+ void PaintScrollbarThumb(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect, bool aHorizontal,
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aElementState,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio) override;
+ void PaintScrollbarTrack(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect, bool aHorizontal,
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio, bool aIsRoot) override;
+ void PaintScrollbar(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ bool aHorizontal, nsIFrame* aFrame,
+ const ComputedStyle& aStyle,
+ const EventStates& aDocumentState, DPIRatio aDpiRatio,
+ bool aIsRoot) override;
+ void PaintScrollCorner(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aDocumentState, DPIRatio aDpiRatio,
+ bool aIsRoot) override;
+
+ protected:
+ virtual ~nsNativeBasicThemeCocoa() = default;
+};
+
+#endif
diff --git a/widget/cocoa/nsNativeThemeCocoa.h b/widget/cocoa/nsNativeThemeCocoa.h
new file mode 100644
index 0000000000..0f55590123
--- /dev/null
+++ b/widget/cocoa/nsNativeThemeCocoa.h
@@ -0,0 +1,490 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNativeThemeCocoa_h_
+#define nsNativeThemeCocoa_h_
+
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/Variant.h"
+
+#include "nsITheme.h"
+#include "nsCOMPtr.h"
+#include "nsAtom.h"
+#include "nsNativeTheme.h"
+#include "ScrollbarDrawingMac.h"
+
+@class CellDrawView;
+@class NSProgressBarCell;
+class nsDeviceContext;
+struct SegmentedControlRenderSettings;
+
+namespace mozilla {
+class EventStates;
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+} // namespace mozilla
+
+class nsNativeThemeCocoa : private nsNativeTheme, public nsITheme {
+ public:
+ enum {
+ eThemeGeometryTypeTitlebar = eThemeGeometryTypeUnknown + 1,
+ eThemeGeometryTypeToolbar,
+ eThemeGeometryTypeToolbox,
+ eThemeGeometryTypeWindowButtons,
+ eThemeGeometryTypeFullscreenButton,
+ eThemeGeometryTypeMenu,
+ eThemeGeometryTypeHighlightedMenuItem,
+ eThemeGeometryTypeVibrancyLight,
+ eThemeGeometryTypeVibrancyDark,
+ eThemeGeometryTypeVibrantTitlebarLight,
+ eThemeGeometryTypeVibrantTitlebarDark,
+ eThemeGeometryTypeTooltip,
+ eThemeGeometryTypeSheet,
+ eThemeGeometryTypeSourceList,
+ eThemeGeometryTypeSourceListSelection,
+ eThemeGeometryTypeActiveSourceListSelection
+ };
+
+ enum class MenuIcon : uint8_t {
+ eCheckmark,
+ eMenuArrow,
+ eMenuDownScrollArrow,
+ eMenuUpScrollArrow
+ };
+
+ enum class CheckboxOrRadioState : uint8_t { eOff, eOn, eIndeterminate };
+
+ enum class ButtonType : uint8_t {
+ eRegularPushButton,
+ eDefaultPushButton,
+ eRegularBevelButton,
+ eDefaultBevelButton,
+ eRoundedBezelPushButton,
+ eSquareBezelPushButton,
+ eArrowButton,
+ eHelpButton,
+ eTreeTwistyPointingRight,
+ eTreeTwistyPointingDown,
+ eDisclosureButtonClosed,
+ eDisclosureButtonOpen
+ };
+
+ enum class SpinButton : uint8_t { eUp, eDown };
+
+ enum class SegmentType : uint8_t { eToolbarButton, eTab };
+
+ enum class OptimumState : uint8_t { eOptimum, eSubOptimum, eSubSubOptimum };
+
+ struct ControlParams {
+ ControlParams()
+ : disabled(false), insideActiveWindow(false), pressed(false), focused(false), rtl(false) {}
+
+ bool disabled : 1;
+ bool insideActiveWindow : 1;
+ bool pressed : 1;
+ bool focused : 1;
+ bool rtl : 1;
+ };
+
+ struct MenuIconParams {
+ MenuIcon icon = MenuIcon::eCheckmark;
+ bool disabled = false;
+ bool insideActiveMenuItem = false;
+ bool centerHorizontally = false;
+ bool rtl = false;
+ };
+
+ struct MenuItemParams {
+ bool checked = false;
+ bool disabled = false;
+ bool selected = false;
+ bool rtl = false;
+ };
+
+ struct CheckboxOrRadioParams {
+ ControlParams controlParams;
+ CheckboxOrRadioState state = CheckboxOrRadioState::eOff;
+ float verticalAlignFactor = 0.5f;
+ };
+
+ struct ButtonParams {
+ ControlParams controlParams;
+ ButtonType button = ButtonType::eRegularPushButton;
+ };
+
+ struct DropdownParams {
+ ControlParams controlParams;
+ bool pullsDown = false;
+ bool editable = false;
+ };
+
+ struct SpinButtonParams {
+ mozilla::Maybe<SpinButton> pressedButton;
+ bool disabled = false;
+ bool insideActiveWindow = false;
+ };
+
+ struct SegmentParams {
+ SegmentType segmentType = SegmentType::eToolbarButton;
+ bool insideActiveWindow = false;
+ bool pressed = false;
+ bool selected = false;
+ bool focused = false;
+ bool atLeftEnd = false;
+ bool atRightEnd = false;
+ bool drawsLeftSeparator = false;
+ bool drawsRightSeparator = false;
+ bool rtl = false;
+ };
+
+ struct UnifiedToolbarParams {
+ float unifiedHeight = 0.0f;
+ bool isMain = false;
+ };
+
+ struct TextBoxParams {
+ bool disabled = false;
+ bool focused = false;
+ bool borderless = false;
+ };
+
+ struct SearchFieldParams {
+ float verticalAlignFactor = 0.5f;
+ bool insideToolbar = false;
+ bool disabled = false;
+ bool focused = false;
+ bool rtl = false;
+ };
+
+ struct ProgressParams {
+ double value = 0.0;
+ double max = 0.0;
+ float verticalAlignFactor = 0.5f;
+ bool insideActiveWindow = false;
+ bool indeterminate = false;
+ bool horizontal = false;
+ bool rtl = false;
+ };
+
+ struct MeterParams {
+ double value = 0;
+ double min = 0;
+ double max = 0;
+ OptimumState optimumState = OptimumState::eOptimum;
+ float verticalAlignFactor = 0.5f;
+ bool horizontal = true;
+ bool rtl = false;
+ };
+
+ struct TreeHeaderCellParams {
+ ControlParams controlParams;
+ TreeSortDirection sortDirection = eTreeSortDirection_Natural;
+ bool lastTreeHeaderCell = false;
+ };
+
+ struct ScaleParams {
+ int32_t value = 0;
+ int32_t min = 0;
+ int32_t max = 0;
+ bool insideActiveWindow = false;
+ bool disabled = false;
+ bool focused = false;
+ bool horizontal = true;
+ bool reverse = false;
+ };
+
+ using ScrollbarParams = mozilla::widget::ScrollbarParams;
+
+ enum Widget : uint8_t {
+ eColorFill, // mozilla::gfx::sRGBColor
+ eMenuIcon, // MenuIconParams
+ eMenuItem, // MenuItemParams
+ eMenuSeparator, // MenuItemParams
+ eCheckbox, // CheckboxOrRadioParams
+ eRadio, // CheckboxOrRadioParams
+ eButton, // ButtonParams
+ eDropdown, // DropdownParams
+ eFocusOutline,
+ eSpinButtons, // SpinButtonParams
+ eSpinButtonUp, // SpinButtonParams
+ eSpinButtonDown, // SpinButtonParams
+ eSegment, // SegmentParams
+ eSeparator,
+ eUnifiedToolbar, // UnifiedToolbarParams
+ eToolbar, // bool
+ eNativeTitlebar, // UnifiedToolbarParams
+ eStatusBar, // bool
+ eGroupBox,
+ eTextBox, // TextBoxParams
+ eSearchField, // SearchFieldParams
+ eProgressBar, // ProgressParams
+ eMeter, // MeterParams
+ eTreeHeaderCell, // TreeHeaderCellParams
+ eScale, // ScaleParams
+ eScrollbarThumb, // ScrollbarParams
+ eScrollbarTrack, // ScrollbarParams
+ eScrollCorner, // ScrollbarParams
+ eMultilineTextField, // bool
+ eListBox,
+ eActiveSourceListSelection, // bool
+ eInactiveSourceListSelection, // bool
+ eTabPanel,
+ eResizer
+ };
+
+ struct WidgetInfo {
+ static WidgetInfo ColorFill(const mozilla::gfx::sRGBColor& aParams) {
+ return WidgetInfo(Widget::eColorFill, aParams);
+ }
+ static WidgetInfo MenuIcon(const MenuIconParams& aParams) {
+ return WidgetInfo(Widget::eMenuIcon, aParams);
+ }
+ static WidgetInfo MenuItem(const MenuItemParams& aParams) {
+ return WidgetInfo(Widget::eMenuItem, aParams);
+ }
+ static WidgetInfo MenuSeparator(const MenuItemParams& aParams) {
+ return WidgetInfo(Widget::eMenuSeparator, aParams);
+ }
+ static WidgetInfo Checkbox(const CheckboxOrRadioParams& aParams) {
+ return WidgetInfo(Widget::eCheckbox, aParams);
+ }
+ static WidgetInfo Radio(const CheckboxOrRadioParams& aParams) {
+ return WidgetInfo(Widget::eRadio, aParams);
+ }
+ static WidgetInfo Button(const ButtonParams& aParams) {
+ return WidgetInfo(Widget::eButton, aParams);
+ }
+ static WidgetInfo Dropdown(const DropdownParams& aParams) {
+ return WidgetInfo(Widget::eDropdown, aParams);
+ }
+ static WidgetInfo FocusOutline() { return WidgetInfo(Widget::eFocusOutline, false); }
+ static WidgetInfo SpinButtons(const SpinButtonParams& aParams) {
+ return WidgetInfo(Widget::eSpinButtons, aParams);
+ }
+ static WidgetInfo SpinButtonUp(const SpinButtonParams& aParams) {
+ return WidgetInfo(Widget::eSpinButtonUp, aParams);
+ }
+ static WidgetInfo SpinButtonDown(const SpinButtonParams& aParams) {
+ return WidgetInfo(Widget::eSpinButtonDown, aParams);
+ }
+ static WidgetInfo Segment(const SegmentParams& aParams) {
+ return WidgetInfo(Widget::eSegment, aParams);
+ }
+ static WidgetInfo Separator() { return WidgetInfo(Widget::eSeparator, false); }
+ static WidgetInfo UnifiedToolbar(const UnifiedToolbarParams& aParams) {
+ return WidgetInfo(Widget::eUnifiedToolbar, aParams);
+ }
+ static WidgetInfo Toolbar(bool aParams) { return WidgetInfo(Widget::eToolbar, aParams); }
+ static WidgetInfo NativeTitlebar(const UnifiedToolbarParams& aParams) {
+ return WidgetInfo(Widget::eNativeTitlebar, aParams);
+ }
+ static WidgetInfo StatusBar(bool aParams) { return WidgetInfo(Widget::eStatusBar, aParams); }
+ static WidgetInfo GroupBox() { return WidgetInfo(Widget::eGroupBox, false); }
+ static WidgetInfo TextBox(const TextBoxParams& aParams) {
+ return WidgetInfo(Widget::eTextBox, aParams);
+ }
+ static WidgetInfo SearchField(const SearchFieldParams& aParams) {
+ return WidgetInfo(Widget::eSearchField, aParams);
+ }
+ static WidgetInfo ProgressBar(const ProgressParams& aParams) {
+ return WidgetInfo(Widget::eProgressBar, aParams);
+ }
+ static WidgetInfo Meter(const MeterParams& aParams) {
+ return WidgetInfo(Widget::eMeter, aParams);
+ }
+ static WidgetInfo TreeHeaderCell(const TreeHeaderCellParams& aParams) {
+ return WidgetInfo(Widget::eTreeHeaderCell, aParams);
+ }
+ static WidgetInfo Scale(const ScaleParams& aParams) {
+ return WidgetInfo(Widget::eScale, aParams);
+ }
+ static WidgetInfo ScrollbarThumb(const ScrollbarParams& aParams) {
+ return WidgetInfo(Widget::eScrollbarThumb, aParams);
+ }
+ static WidgetInfo ScrollbarTrack(const ScrollbarParams& aParams) {
+ return WidgetInfo(Widget::eScrollbarTrack, aParams);
+ }
+ static WidgetInfo ScrollCorner(const ScrollbarParams& aParams) {
+ return WidgetInfo(Widget::eScrollCorner, aParams);
+ }
+ static WidgetInfo MultilineTextField(bool aParams) {
+ return WidgetInfo(Widget::eMultilineTextField, aParams);
+ }
+ static WidgetInfo ListBox() { return WidgetInfo(Widget::eListBox, false); }
+ static WidgetInfo ActiveSourceListSelection(bool aParams) {
+ return WidgetInfo(Widget::eActiveSourceListSelection, aParams);
+ }
+ static WidgetInfo InactiveSourceListSelection(bool aParams) {
+ return WidgetInfo(Widget::eInactiveSourceListSelection, aParams);
+ }
+ static WidgetInfo TabPanel(bool aParams) { return WidgetInfo(Widget::eTabPanel, aParams); }
+ static WidgetInfo Resizer(bool aParams) { return WidgetInfo(Widget::eResizer, aParams); }
+
+ template <typename T>
+ T Params() const {
+ MOZ_RELEASE_ASSERT(mVariant.is<T>());
+ return mVariant.as<T>();
+ }
+
+ enum Widget Widget() const { return mWidget; }
+
+ private:
+ template <typename T>
+ WidgetInfo(enum Widget aWidget, const T& aParams) : mVariant(aParams), mWidget(aWidget) {}
+
+ mozilla::Variant<mozilla::gfx::sRGBColor, MenuIconParams, MenuItemParams, CheckboxOrRadioParams,
+ ButtonParams, DropdownParams, SpinButtonParams, SegmentParams,
+ UnifiedToolbarParams, TextBoxParams, SearchFieldParams, ProgressParams,
+ MeterParams, TreeHeaderCellParams, ScaleParams, ScrollbarParams, bool>
+ mVariant;
+
+ enum Widget mWidget;
+ };
+
+ using ScrollbarDrawingMac = mozilla::widget::ScrollbarDrawingMac;
+
+ nsNativeThemeCocoa();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // The nsITheme interface.
+ NS_IMETHOD DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance, const nsRect& aRect,
+ const nsRect& aDirtyRect) override;
+ bool CreateWebRenderCommandsForWidget(mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsIFrame* aFrame, StyleAppearance aAppearance,
+ const nsRect& aRect) override;
+ [[nodiscard]] LayoutDeviceIntMargin GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ bool GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) override;
+
+ virtual bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance, nsRect* aOverflowRect) override;
+
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) override;
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance, nsAtom* aAttribute,
+ bool* aShouldRepaint, const nsAttrValue* aOldValue) override;
+ NS_IMETHOD ThemeChanged() override;
+ bool ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+ bool WidgetIsContainer(StyleAppearance aAppearance) override;
+ bool ThemeDrawsFocusForWidget(StyleAppearance aAppearance) override;
+ bool ThemeNeedsComboboxDropmarker() override;
+ virtual bool WidgetAppearanceDependsOnWindowFocus(StyleAppearance aAppearance) override;
+ virtual ThemeGeometryType ThemeGeometryTypeForWidget(nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+ virtual Transparency GetWidgetTransparency(nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+ mozilla::Maybe<WidgetInfo> ComputeWidgetInfo(nsIFrame* aFrame, StyleAppearance aAppearance,
+ const nsRect& aRect);
+ void DrawProgress(CGContextRef context, const HIRect& inBoxRect, const ProgressParams& aParams);
+
+ static void DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
+ CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped);
+
+ protected:
+ virtual ~nsNativeThemeCocoa();
+
+ LayoutDeviceIntMargin DirectionAwareMargin(const LayoutDeviceIntMargin& aMargin,
+ nsIFrame* aFrame);
+ nsIFrame* SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter);
+ bool IsWindowSheet(nsIFrame* aFrame);
+ ControlParams ComputeControlParams(nsIFrame* aFrame, mozilla::EventStates aEventState);
+ MenuIconParams ComputeMenuIconParams(nsIFrame* aParams, mozilla::EventStates aEventState,
+ MenuIcon aIcon);
+ MenuItemParams ComputeMenuItemParams(nsIFrame* aFrame, mozilla::EventStates aEventState,
+ bool aIsChecked);
+ SegmentParams ComputeSegmentParams(nsIFrame* aFrame, mozilla::EventStates aEventState,
+ SegmentType aSegmentType);
+ SearchFieldParams ComputeSearchFieldParams(nsIFrame* aFrame, mozilla::EventStates aEventState);
+ ProgressParams ComputeProgressParams(nsIFrame* aFrame, mozilla::EventStates aEventState,
+ bool aIsHorizontal);
+ MeterParams ComputeMeterParams(nsIFrame* aFrame);
+ TreeHeaderCellParams ComputeTreeHeaderCellParams(nsIFrame* aFrame,
+ mozilla::EventStates aEventState);
+ mozilla::Maybe<ScaleParams> ComputeHTMLScaleParams(nsIFrame* aFrame,
+ mozilla::EventStates aEventState);
+
+ // HITheme drawing routines
+ void DrawTextBox(CGContextRef context, const HIRect& inBoxRect, TextBoxParams aParams);
+ void DrawMeter(CGContextRef context, const HIRect& inBoxRect, const MeterParams& aParams);
+ void DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect, const SegmentParams& aParams);
+ void DrawSegmentBackground(CGContextRef cgContext, const HIRect& inBoxRect,
+ const SegmentParams& aParams);
+ void DrawTabPanel(CGContextRef context, const HIRect& inBoxRect, bool aIsInsideActiveWindow);
+ void DrawScale(CGContextRef context, const HIRect& inBoxRect, const ScaleParams& aParams);
+ void DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox, const HIRect& inBoxRect,
+ const CheckboxOrRadioParams& aParams);
+ void DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
+ const SearchFieldParams& aParams);
+ void DrawRoundedBezelPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ ControlParams aControlParams);
+ void DrawSquareBezelPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ ControlParams aControlParams);
+ void DrawHelpButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ ControlParams aControlParams);
+ void DrawDisclosureButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ ControlParams aControlParams, NSCellStateValue aState);
+ NSString* GetMenuIconName(const MenuIconParams& aParams);
+ NSSize GetMenuIconSize(MenuIcon aIcon);
+ void DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect, const MenuIconParams& aParams);
+ void DrawMenuItem(CGContextRef cgContext, const CGRect& inBoxRect, const MenuItemParams& aParams);
+ void DrawMenuSeparator(CGContextRef cgContext, const CGRect& inBoxRect,
+ const MenuItemParams& aParams);
+ void DrawHIThemeButton(CGContextRef cgContext, const HIRect& aRect, ThemeButtonKind aKind,
+ ThemeButtonValue aValue, ThemeDrawState aState,
+ ThemeButtonAdornment aAdornment, const ControlParams& aParams);
+ void DrawButton(CGContextRef context, const HIRect& inBoxRect, const ButtonParams& aParams);
+ void DrawTreeHeaderCell(CGContextRef context, const HIRect& inBoxRect,
+ const TreeHeaderCellParams& aParams);
+ void DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect);
+ void DrawDropdown(CGContextRef context, const HIRect& inBoxRect, const DropdownParams& aParams);
+ HIThemeButtonDrawInfo SpinButtonDrawInfo(ThemeButtonKind aKind, const SpinButtonParams& aParams);
+ void DrawSpinButtons(CGContextRef context, const HIRect& inBoxRect,
+ const SpinButtonParams& aParams);
+ void DrawSpinButton(CGContextRef context, const HIRect& inBoxRect, SpinButton aDrawnButton,
+ const SpinButtonParams& aParams);
+ void DrawToolbar(CGContextRef cgContext, const CGRect& inBoxRect, bool aIsMain);
+ void DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
+ const UnifiedToolbarParams& aParams);
+ void DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
+ const UnifiedToolbarParams& aParams);
+ void DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect, bool aIsMain);
+ void DrawResizer(CGContextRef cgContext, const HIRect& aRect, bool aIsRTL);
+ void DrawMultilineTextField(CGContextRef cgContext, const CGRect& inBoxRect, bool aIsFocused);
+ void DrawSourceListSelection(CGContextRef aContext, const CGRect& aRect, bool aWindowIsActive,
+ bool aSelectionIsActive);
+
+ void RenderWidget(const WidgetInfo& aWidgetInfo, mozilla::gfx::DrawTarget& aDrawTarget,
+ const mozilla::gfx::Rect& aWidgetRect, const mozilla::gfx::Rect& aDirtyRect,
+ float aScale);
+
+ private:
+ NSButtonCell* mDisclosureButtonCell;
+ NSButtonCell* mHelpButtonCell;
+ NSButtonCell* mPushButtonCell;
+ NSButtonCell* mRadioButtonCell;
+ NSButtonCell* mCheckboxCell;
+ NSSearchFieldCell* mSearchFieldCell;
+ NSSearchFieldCell* mToolbarSearchFieldCell;
+ 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..f12e0aa2d1
--- /dev/null
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -0,0 +1,3941 @@
+/* -*- 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 "nsRect.h"
+#include "nsSize.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsIFrame.h"
+#include "nsAtom.h"
+#include "nsNameSpaceManager.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaWindow.h"
+#include "nsNativeBasicTheme.h"
+#include "nsNativeThemeColors.h"
+#include "nsIScrollableFrame.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/Range.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLMeterElement.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "nsLookAndFeel.h"
+#include "VibrancyManager.h"
+
+#include "gfxContext.h"
+#include "gfxQuartzSurface.h"
+#include "gfxQuartzNativeDrawing.h"
+#include "gfxUtils.h" // for ToDeviceColor
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using mozilla::dom::HTMLMeterElement;
+
+#define DRAW_IN_FRAME_DEBUG 0
+#define SCROLLBARS_VISUAL_DEBUG 0
+
+// private Quartz routines needed here
+extern "C" {
+CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
+CG_EXTERN void CGContextSetBaseCTM(CGContextRef, CGAffineTransform);
+typedef CFTypeRef CUIRendererRef;
+void CUIDraw(CUIRendererRef r, CGRect rect, CGContextRef ctx, CFDictionaryRef options,
+ CFDictionaryRef* result);
+}
+
+// Workaround for NSCell control tint drawing
+// Without this workaround, NSCells are always drawn with the clear control tint
+// as long as they're not attached to an NSControl which is a subview of an active window.
+// XXXmstange Why doesn't Webkit need this?
+@implementation NSCell (ControlTintWorkaround)
+- (int)_realControlTint {
+ return [self controlTint];
+}
+@end
+
+// The purpose of this class is to provide objects that can be used when drawing
+// NSCells using drawWithFrame:inView: without causing any harm. The only
+// messages that will be sent to such an object are "isFlipped" and
+// "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
+// on 10.4 (see bug 465069); currentEditor (which isn't even a method of
+// NSView) will be called when drawing search fields, and we only provide it in
+// order to prevent "unrecognized selector" exceptions.
+// There's no need to pass the actual NSView that we're drawing into to
+// drawWithFrame:inView:. What's more, doing so even causes unnecessary
+// invalidations as soon as we draw a focusring!
+@interface CellDrawView : NSView
+
+@end
+
+@implementation CellDrawView
+
+- (BOOL)isFlipped {
+ return YES;
+}
+
+- (NSText*)currentEditor {
+ return nil;
+}
+
+@end
+
+// These two classes don't actually add any behavior over NSButtonCell. Their
+// purpose is to make it easy to distinguish NSCell objects that are used for
+// drawing radio buttons / checkboxes from other cell types.
+// The class names are made up, there are no classes with these names in AppKit.
+// The reason we need them is that calling [cell setButtonType:NSRadioButton]
+// doesn't leave an easy-to-check "marker" on the cell object - there is no
+// -[NSButtonCell buttonType] method.
+@interface RadioButtonCell : NSButtonCell
+@end
+
+@implementation RadioButtonCell
+@end
+
+@interface CheckboxCell : NSButtonCell
+@end
+
+@implementation CheckboxCell
+@end
+
+static void DrawFocusRingForCellIfNeeded(NSCell* aCell, NSRect aWithFrame, NSView* aInView) {
+ if ([aCell showsFirstResponder]) {
+ CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ CGContextSaveGState(cgContext);
+
+ // It's important to set the focus ring style before we enter the
+ // transparency layer so that the transparency layer only contains
+ // the normal button mask without the focus ring, and the conversion
+ // to the focus ring shape happens only when the transparency layer is
+ // ended.
+ NSSetFocusRingStyle(NSFocusRingOnly);
+
+ // We need to draw the whole button into a transparency layer because
+ // many button types are composed of multiple parts, and if these parts
+ // were drawn while the focus ring style was active, each individual part
+ // would produce a focus ring for itself. But we only want one focus ring
+ // for the whole button. The transparency layer is a way to merge the
+ // individual button parts together before the focus ring shape is
+ // calculated.
+ CGContextBeginTransparencyLayerWithRect(cgContext, NSRectToCGRect(aWithFrame), 0);
+ [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView];
+ CGContextEndTransparencyLayer(cgContext);
+
+ CGContextRestoreGState(cgContext);
+ }
+}
+
+static bool FocusIsDrawnByDrawWithFrame(NSCell* aCell) {
+#if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
+ // When building with the 10.8 SDK or higher, focus rings don't draw as part
+ // of -[NSCell drawWithFrame:inView:] and must be drawn by a separate call
+ // to -[NSCell drawFocusRingMaskWithFrame:inView:]; .
+ // See the NSButtonCell section under
+ // https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_8Notes
+ return false;
+#else
+ // 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 == NSControlSizeRegular) {
+ tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar : kThemeLargeProgressBar;
+ } else {
+ NS_ASSERTION(size == NSControlSizeSmall,
+ "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 SearchFieldCellWithFocusRing : NSSearchFieldCell {
+}
+@end
+
+// Workaround for Bug 542048
+// On 64-bit, NSSearchFieldCells don't draw focus rings.
+@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
+
+@interface ToolbarSearchFieldCellWithFocusRing : SearchFieldCellWithFocusRing
+@end
+
+@implementation ToolbarSearchFieldCellWithFocusRing
+
+- (BOOL)_isToolbarMode {
+ // This function is called during -[NSSearchFieldCell drawWithFrame:inView:].
+ // On earlier macOS versions, returning YES from it selects the style
+ // that's appropriate for search fields inside toolbars. On Big Sur,
+ // returning YES causes the search field to be drawn incorrectly, with
+ // the toolbar gradient appearing as the field background.
+ if (nsCocoaFeatures::OnBigSurOrLater()) {
+ return NO;
+ }
+ return YES;
+}
+
+@end
+
+#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 == NSControlSizeMini)
+ return miniControlSize;
+ else if (cocoaControlSize == NSControlSizeSmall)
+ return smallControlSize;
+ else
+ return regularControlSize;
+}
+
+static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
+ if (enumControlSize == miniControlSize)
+ return NSControlSizeMini;
+ else if (enumControlSize == smallControlSize)
+ return NSControlSizeSmall;
+ else
+ return NSControlSizeRegular;
+}
+
+static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize) {
+ if (aControlSize == NSControlSizeRegular)
+ return @"regular";
+ else if (aControlSize == NSControlSizeSmall)
+ return @"small";
+ else
+ return @"mini";
+}
+
+static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize,
+ const float marginSet[][3][4]) {
+ if (!marginSet) return;
+
+ static int osIndex = yosemiteOSorlater;
+ 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 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 = nsLayoutUtils::GetCrossDocParentFrame(frame)) {
+ if (frame->StyleDisplay()->EffectiveAppearance() == StyleAppearance::MozMacSourceList) {
+ return true;
+ }
+ }
+ return false;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
+
+nsNativeThemeCocoa::nsNativeThemeCocoa() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ kMaxFocusRingWidth = 7;
+
+ // 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];
+
+ mSearchFieldCell = [[SearchFieldCellWithFocusRing alloc] initTextCell:@""];
+ [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
+ [mSearchFieldCell setBezeled:YES];
+ [mSearchFieldCell setEditable:YES];
+ [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
+
+ mToolbarSearchFieldCell = [[ToolbarSearchFieldCellWithFocusRing alloc] initTextCell:@""];
+ [mToolbarSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
+ [mToolbarSearchFieldCell setBezeled:YES];
+ [mToolbarSearchFieldCell setEditable:YES];
+ [mToolbarSearchFieldCell 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];
+ [mToolbarSearchFieldCell 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 NSControlSizeRegular 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(NSControlSizeMini)];
+ const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSControlSizeSmall)];
+ const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSControlSizeRegular)];
+
+ 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 = NSControlSizeRegular;
+ 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() {
+ 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 auto& va = aFrame->StyleDisplay()->mVerticalAlign;
+ auto kw = va.IsKeyword() ? va.AsKeyword() : StyleVerticalAlignKeyword::Middle;
+ switch (kw) {
+ case StyleVerticalAlignKeyword::Top:
+ case StyleVerticalAlignKeyword::TextTop:
+ return 0.0f;
+
+ case StyleVerticalAlignKeyword::Sub:
+ case StyleVerticalAlignKeyword::Super:
+ case StyleVerticalAlignKeyword::Middle:
+ case StyleVerticalAlignKeyword::MozMiddleWithBaseline:
+ return 0.5f;
+
+ case StyleVerticalAlignKeyword::Baseline:
+ case StyleVerticalAlignKeyword::Bottom:
+ case StyleVerticalAlignKeyword::TextBottom:
+ return 1.0f;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("invalid vertical-align");
+ return 0.5f;
+ }
+}
+
+static void ApplyControlParamsToNSCell(nsNativeThemeCocoa::ControlParams aControlParams,
+ NSCell* aCell) {
+ [aCell setEnabled:!aControlParams.disabled];
+ [aCell setShowsFirstResponder:(aControlParams.focused && !aControlParams.disabled &&
+ aControlParams.insideActiveWindow)];
+ [aCell setHighlighted:aControlParams.pressed];
+}
+
+// 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
+ }}};
+
+static NSCellStateValue CellStateForCheckboxOrRadioState(
+ nsNativeThemeCocoa::CheckboxOrRadioState aState) {
+ switch (aState) {
+ case nsNativeThemeCocoa::CheckboxOrRadioState::eOff:
+ return NSOffState;
+ case nsNativeThemeCocoa::CheckboxOrRadioState::eOn:
+ return NSOnState;
+ case nsNativeThemeCocoa::CheckboxOrRadioState::eIndeterminate:
+ return NSMixedState;
+ }
+}
+
+void nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
+ const HIRect& inBoxRect,
+ const CheckboxOrRadioParams& aParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSButtonCell* cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
+ ApplyControlParamsToNSCell(aParams.controlParams, cell);
+
+ [cell setState:CellStateForCheckboxOrRadioState(aParams.state)];
+ [cell setControlTint:(aParams.controlParams.insideActiveWindow ? [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,
+ aParams.verticalAlignFactor, 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
+ }}};
+
+static bool IsToolbarStyleContainer(nsIFrame* aFrame) {
+ nsIContent* content = aFrame->GetContent();
+ if (!content) {
+ return false;
+ }
+
+ if (content->IsAnyOfXULElements(nsGkAtoms::toolbar, nsGkAtoms::toolbox, nsGkAtoms::statusbar)) {
+ return true;
+ }
+
+ switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
+ case StyleAppearance::Toolbar:
+ case StyleAppearance::Statusbar:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool IsInsideToolbar(nsIFrame* aFrame) {
+ for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
+ if (IsToolbarStyleContainer(frame)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsNativeThemeCocoa::SearchFieldParams nsNativeThemeCocoa::ComputeSearchFieldParams(
+ nsIFrame* aFrame, EventStates aEventState) {
+ SearchFieldParams params;
+ params.insideToolbar = IsInsideToolbar(aFrame);
+ params.disabled = IsDisabled(aFrame, aEventState);
+ params.focused = IsFocused(aFrame);
+ params.rtl = IsFrameRTL(aFrame);
+ params.verticalAlignFactor = VerticalAlignFactor(aFrame);
+ return params;
+}
+
+void nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
+ const SearchFieldParams& aParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSSearchFieldCell* cell = aParams.insideToolbar ? mToolbarSearchFieldCell : mSearchFieldCell;
+ [cell setEnabled:!aParams.disabled];
+ [cell setShowsFirstResponder:aParams.focused];
+
+ // 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, aParams.verticalAlignFactor,
+ mCellDrawView, aParams.rtl);
+
+ 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;
+
+NSString* nsNativeThemeCocoa::GetMenuIconName(const MenuIconParams& aParams) {
+ switch (aParams.icon) {
+ case MenuIcon::eCheckmark:
+ return kCheckmarkImage;
+ case MenuIcon::eMenuArrow:
+ return aParams.rtl ? kMenuarrowLeftImage : kMenuarrowRightImage;
+ case MenuIcon::eMenuDownScrollArrow:
+ return kMenuDownScrollArrowImage;
+ case MenuIcon::eMenuUpScrollArrow:
+ return kMenuUpScrollArrowImage;
+ }
+}
+
+NSSize nsNativeThemeCocoa::GetMenuIconSize(MenuIcon aIcon) {
+ switch (aIcon) {
+ case MenuIcon::eCheckmark:
+ return kCheckmarkSize;
+ case MenuIcon::eMenuArrow:
+ return kMenuarrowSize;
+ case MenuIcon::eMenuDownScrollArrow:
+ case MenuIcon::eMenuUpScrollArrow:
+ return kMenuScrollArrowSize;
+ }
+}
+
+nsNativeThemeCocoa::MenuIconParams nsNativeThemeCocoa::ComputeMenuIconParams(
+ nsIFrame* aFrame, EventStates aEventState, MenuIcon aIcon) {
+ bool isDisabled = IsDisabled(aFrame, aEventState);
+
+ MenuIconParams params;
+ params.icon = aIcon;
+ params.disabled = isDisabled;
+ params.insideActiveMenuItem = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+ params.centerHorizontally = true;
+ params.rtl = IsFrameRTL(aFrame);
+ return params;
+}
+
+void nsNativeThemeCocoa::DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect,
+ const MenuIconParams& aParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSSize size = GetMenuIconSize(aParams.icon);
+
+ // Adjust size and position of our drawRect.
+ CGFloat paddingX = std::max(CGFloat(0.0), aRect.size.width - size.width);
+ CGFloat paddingY = std::max(CGFloat(0.0), aRect.size.height - size.height);
+ CGFloat paddingStartX = std::min(paddingX, kMenuIconIndent);
+ CGFloat paddingEndX = std::max(CGFloat(0.0), paddingX - kMenuIconIndent);
+ CGRect drawRect = CGRectMake(aRect.origin.x + (aParams.centerHorizontally ? ceil(paddingX / 2)
+ : aParams.rtl ? paddingEndX
+ : paddingStartX),
+ aRect.origin.y + ceil(paddingY / 2), size.width, size.height);
+
+ NSString* state =
+ aParams.disabled ? @"disabled" : (aParams.insideActiveMenuItem ? @"pressed" : @"normal");
+
+ NSString* imageName = GetMenuIconName(aParams);
+
+ 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;
+}
+
+nsNativeThemeCocoa::MenuItemParams nsNativeThemeCocoa::ComputeMenuItemParams(
+ nsIFrame* aFrame, EventStates aEventState, bool aIsChecked) {
+ bool isDisabled = IsDisabled(aFrame, aEventState);
+
+ MenuItemParams params;
+ params.checked = aIsChecked;
+ params.disabled = isDisabled;
+ params.selected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+ params.rtl = IsFrameRTL(aFrame);
+ return params;
+}
+
+static void SetCGContextFillColor(CGContextRef cgContext, const sRGBColor& aColor) {
+ DeviceColor color = ToDeviceColor(aColor);
+ CGContextSetRGBFillColor(cgContext, color.r, color.g, color.b, color.a);
+}
+
+void nsNativeThemeCocoa::DrawMenuItem(CGContextRef cgContext, const CGRect& inBoxRect,
+ const MenuItemParams& aParams) {
+ if (aParams.checked) {
+ MenuIconParams params;
+ params.disabled = aParams.disabled;
+ params.insideActiveMenuItem = aParams.selected;
+ params.rtl = aParams.rtl;
+ params.icon = MenuIcon::eCheckmark;
+ DrawMenuIcon(cgContext, inBoxRect, params);
+ }
+}
+
+void nsNativeThemeCocoa::DrawMenuSeparator(CGContextRef cgContext, const CGRect& inBoxRect,
+ const MenuItemParams& aParams) {
+ // Workaround for visual artifacts issues with
+ // HIThemeDrawMenuSeparator on macOS Big Sur.
+ if (nsCocoaFeatures::OnBigSurOrLater()) {
+ CGRect separatorRect = inBoxRect;
+ separatorRect.size.height = 1;
+ separatorRect.size.width -= 42;
+ separatorRect.origin.x += 21;
+ // Use transparent black with an alpha similar to the native separator.
+ // The values 231 (menu background) and 205 (separator color) have been
+ // sampled from a window screenshot of a native context menu.
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.0, (231 - 205) / 231.0);
+ CGContextFillRect(cgContext, separatorRect);
+ return;
+ }
+
+ ThemeMenuState menuState;
+ if (aParams.disabled) {
+ menuState = kThemeMenuDisabled;
+ } else {
+ menuState = aParams.selected ? kThemeMenuSelected : kThemeMenuActive;
+ }
+
+ HIThemeMenuItemDrawInfo midi = {0, kThemeMenuItemPlain, menuState};
+ HIThemeDrawMenuSeparator(&inBoxRect, &inBoxRect, &midi, cgContext, HITHEME_ORIENTATION);
+}
+
+static bool ShouldUnconditionallyDrawFocusRingIfFocused(nsIFrame* aFrame) {
+ // Mac always draws focus rings for textboxes and lists.
+ switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Searchfield:
+ case StyleAppearance::Listbox:
+ return true;
+ default:
+ return false;
+ }
+}
+
+nsNativeThemeCocoa::ControlParams nsNativeThemeCocoa::ComputeControlParams(
+ nsIFrame* aFrame, EventStates aEventState) {
+ ControlParams params;
+ params.disabled = IsDisabled(aFrame, aEventState);
+ params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
+ params.pressed = aEventState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER);
+ params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUS) &&
+ (aEventState.HasState(NS_EVENT_STATE_FOCUSRING) ||
+ ShouldUnconditionallyDrawFocusRingIfFocused(aFrame));
+ params.rtl = IsFrameRTL(aFrame);
+ return params;
+}
+
+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::DrawRoundedBezelPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ ControlParams aControlParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
+ [mPushButtonCell setBezelStyle:NSRoundedBezelStyle];
+ DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect, pushButtonSettings, 0.5f,
+ mCellDrawView, aControlParams.rtl, 1.0f);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsNativeThemeCocoa::DrawSquareBezelPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ ControlParams aControlParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
+ [mPushButtonCell setBezelStyle:NSShadowlessSquareBezelStyle];
+ DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
+ NSMakeSize(14, 0), NULL, mCellDrawView, aControlParams.rtl);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsNativeThemeCocoa::DrawHelpButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ ControlParams aControlParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ ApplyControlParamsToNSCell(aControlParams, mHelpButtonCell);
+ DrawCellWithScaling(mHelpButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
+ kHelpButtonSize, NULL, mCellDrawView,
+ false); // Don't mirror icon in RTL.
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsNativeThemeCocoa::DrawDisclosureButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ ControlParams aControlParams,
+ NSCellStateValue aCellState) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ ApplyControlParamsToNSCell(aControlParams, mDisclosureButtonCell);
+ [mDisclosureButtonCell setState:aCellState];
+ DrawCellWithScaling(mDisclosureButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
+ kDisclosureButtonSize, NULL, mCellDrawView,
+ false); // Don't mirror icon in RTL.
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsNativeThemeCocoa::DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext
+ flipped:YES]];
+ CGContextSaveGState(cgContext);
+ NSSetFocusRingStyle(NSFocusRingOnly);
+ NSRectFill(NSRectFromCGRect(inBoxRect));
+ CGContextRestoreGState(cgContext);
+ [NSGraphicsContext setCurrentContext:savedContext];
+
+ 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);
+}
+
+static ThemeDrawState ToThemeDrawState(const nsNativeThemeCocoa::ControlParams& aParams) {
+ if (aParams.disabled) {
+ return kThemeStateUnavailable;
+ }
+ if (aParams.pressed) {
+ return kThemeStatePressed;
+ }
+ return kThemeStateActive;
+}
+
+void nsNativeThemeCocoa::DrawHIThemeButton(CGContextRef cgContext, const HIRect& aRect,
+ ThemeButtonKind aKind, ThemeButtonValue aValue,
+ ThemeDrawState aState, ThemeButtonAdornment aAdornment,
+ const ControlParams& aParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeButtonDrawInfo bdi;
+ bdi.version = 0;
+ bdi.kind = aKind;
+ bdi.value = aValue;
+ bdi.state = aState;
+ bdi.adornment = aAdornment;
+
+ if (aParams.focused && aParams.insideActiveWindow) {
+ bdi.adornment |= kThemeAdornmentFocus;
+ }
+
+ if ((aAdornment & kThemeAdornmentDefault) && !aParams.disabled) {
+ bdi.animation.time.start = 0;
+ bdi.animation.time.current = CFAbsoluteTimeGetCurrent();
+ }
+
+ RenderTransformedHIThemeControl(cgContext, aRect, RenderButton, &bdi, aParams.rtl);
+
+#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::DrawButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ const ButtonParams& aParams) {
+ ControlParams controlParams = aParams.controlParams;
+
+ switch (aParams.button) {
+ case ButtonType::eRegularPushButton:
+ case ButtonType::eDefaultPushButton: {
+ ThemeButtonAdornment adornment = aParams.button == ButtonType::eDefaultPushButton
+ ? kThemeAdornmentDefault
+ : kThemeAdornmentNone;
+ HIRect drawFrame = inBoxRect;
+ drawFrame.size.height -= 2;
+ if (inBoxRect.size.height >= pushButtonSettings.naturalSizes[regularControlSize].height) {
+ DrawHIThemeButton(cgContext, drawFrame, kThemePushButton, kThemeButtonOff,
+ ToThemeDrawState(controlParams), adornment, controlParams);
+ return;
+ }
+ if (inBoxRect.size.height >= pushButtonSettings.naturalSizes[smallControlSize].height) {
+ drawFrame.origin.y -= 1;
+ drawFrame.origin.x += 1;
+ drawFrame.size.width -= 2;
+ DrawHIThemeButton(cgContext, drawFrame, kThemePushButtonSmall, kThemeButtonOff,
+ ToThemeDrawState(controlParams), adornment, controlParams);
+ return;
+ }
+ DrawHIThemeButton(cgContext, drawFrame, kThemePushButtonMini, kThemeButtonOff,
+ ToThemeDrawState(controlParams), adornment, controlParams);
+ return;
+ }
+ case ButtonType::eRegularBevelButton:
+ case ButtonType::eDefaultBevelButton: {
+ ThemeButtonAdornment adornment = aParams.button == ButtonType::eDefaultBevelButton
+ ? kThemeAdornmentDefault
+ : kThemeAdornmentNone;
+ DrawHIThemeButton(cgContext, inBoxRect, kThemeMediumBevelButton, kThemeButtonOff,
+ ToThemeDrawState(controlParams), adornment, controlParams);
+ return;
+ }
+ case ButtonType::eRoundedBezelPushButton:
+ DrawRoundedBezelPushButton(cgContext, inBoxRect, controlParams);
+ return;
+ case ButtonType::eSquareBezelPushButton:
+ DrawSquareBezelPushButton(cgContext, inBoxRect, controlParams);
+ return;
+ case ButtonType::eArrowButton:
+ DrawHIThemeButton(cgContext, inBoxRect, kThemeArrowButton, kThemeButtonOn,
+ kThemeStateUnavailable, kThemeAdornmentArrowDownArrow, controlParams);
+ return;
+ case ButtonType::eHelpButton:
+ DrawHelpButton(cgContext, inBoxRect, controlParams);
+ return;
+ case ButtonType::eTreeTwistyPointingRight:
+ DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton, kThemeDisclosureRight,
+ ToThemeDrawState(controlParams), kThemeAdornmentNone, controlParams);
+ return;
+ case ButtonType::eTreeTwistyPointingDown:
+ DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton, kThemeDisclosureDown,
+ ToThemeDrawState(controlParams), kThemeAdornmentNone, controlParams);
+ return;
+ case ButtonType::eDisclosureButtonClosed:
+ DrawDisclosureButton(cgContext, inBoxRect, controlParams, NSOffState);
+ return;
+ case ButtonType::eDisclosureButtonOpen:
+ DrawDisclosureButton(cgContext, inBoxRect, controlParams, NSOnState);
+ return;
+ }
+}
+
+nsNativeThemeCocoa::TreeHeaderCellParams nsNativeThemeCocoa::ComputeTreeHeaderCellParams(
+ nsIFrame* aFrame, EventStates aEventState) {
+ TreeHeaderCellParams params;
+ params.controlParams = ComputeControlParams(aFrame, aEventState);
+ params.sortDirection = GetTreeSortDirection(aFrame);
+ params.lastTreeHeaderCell = IsLastTreeHeaderCell(aFrame);
+ return params;
+}
+
+void nsNativeThemeCocoa::DrawTreeHeaderCell(CGContextRef cgContext, const HIRect& inBoxRect,
+ const TreeHeaderCellParams& aParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeButtonDrawInfo bdi;
+ bdi.version = 0;
+ bdi.kind = kThemeListHeaderButton;
+ bdi.value = kThemeButtonOff;
+ bdi.adornment = kThemeAdornmentNone;
+
+ switch (aParams.sortDirection) {
+ case eTreeSortDirection_Natural:
+ break;
+ case eTreeSortDirection_Ascending:
+ bdi.value = kThemeButtonOn;
+ bdi.adornment = kThemeAdornmentHeaderButtonSortUp;
+ break;
+ case eTreeSortDirection_Descending:
+ bdi.value = kThemeButtonOn;
+ break;
+ }
+
+ if (aParams.controlParams.disabled) {
+ bdi.state = kThemeStateUnavailable;
+ } else if (aParams.controlParams.pressed) {
+ bdi.state = kThemeStatePressed;
+ } else if (!aParams.controlParams.insideActiveWindow) {
+ bdi.state = kThemeStateInactive;
+ } else {
+ bdi.state = kThemeStateActive;
+ }
+
+ CGContextClipToRect(cgContext, inBoxRect);
+
+ HIRect drawFrame = 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;
+ if (aParams.lastTreeHeaderCell) {
+ drawFrame.size.width += 1; // Also remove the other border.
+ }
+ if (!aParams.controlParams.rtl || aParams.lastTreeHeaderCell) {
+ drawFrame.origin.x -= 1;
+ }
+
+ RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi,
+ aParams.controlParams.rtl);
+
+#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,
+ const DropdownParams& aParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mDropdownCell setPullsDown:aParams.pullsDown];
+ NSCell* cell = aParams.editable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
+
+ ApplyControlParamsToNSCell(aParams.controlParams, cell);
+
+ if (aParams.controlParams.insideActiveWindow) {
+ [cell setControlTint:[NSColor currentControlTint]];
+ } else {
+ [cell setControlTint:NSClearControlTint];
+ }
+
+ const CellRenderSettings& settings =
+ aParams.editable ? editableMenulistSettings : dropdownSettings;
+ DrawCellWithSnapping(cell, cgContext, inBoxRect, settings, 0.5f, mCellDrawView,
+ aParams.controlParams.rtl);
+
+ 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
+ }}};
+
+HIThemeButtonDrawInfo nsNativeThemeCocoa::SpinButtonDrawInfo(ThemeButtonKind aKind,
+ const SpinButtonParams& aParams) {
+ HIThemeButtonDrawInfo bdi;
+ bdi.version = 0;
+ bdi.kind = aKind;
+ bdi.value = kThemeButtonOff;
+ bdi.adornment = kThemeAdornmentNone;
+
+ if (aParams.disabled) {
+ bdi.state = kThemeStateUnavailable;
+ } else if (aParams.insideActiveWindow && aParams.pressedButton) {
+ if (*aParams.pressedButton == SpinButton::eUp) {
+ bdi.state = kThemeStatePressedUp;
+ } else {
+ bdi.state = kThemeStatePressedDown;
+ }
+ } else {
+ bdi.state = kThemeStateActive;
+ }
+
+ return bdi;
+}
+
+void nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, const HIRect& inBoxRect,
+ const SpinButtonParams& aParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButton, aParams);
+ HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ SpinButton aDrawnButton, const SpinButtonParams& aParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButtonMini, aParams);
+
+ // 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 (aDrawnButton == SpinButton::eDown) {
+ 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::DrawTextBox(CGContextRef cgContext, const HIRect& inBoxRect,
+ TextBoxParams aParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ SetCGContextFillColor(cgContext, sRGBColor(1.0, 1.0, 1.0, 1.0));
+ CGContextFillRect(cgContext, inBoxRect);
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, inBoxRect);
+#endif
+
+ if (aParams.borderless) {
+ return;
+ }
+
+ HIThemeFrameDrawInfo fdi;
+ fdi.version = 0;
+ fdi.kind = kHIThemeFrameTextFieldSquare;
+
+ // We don't ever set an inactive state for this because it doesn't
+ // look right (see other apps).
+ fdi.state = aParams.disabled ? kThemeStateUnavailable : kThemeStateActive;
+ fdi.isFocused = aParams.focused;
+
+ // 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;
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
+ drawRect.origin.x += frameOutset;
+ drawRect.origin.y += frameOutset;
+ drawRect.size.width -= frameOutset * 2;
+ drawRect.size.height -= frameOutset * 2;
+
+ 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
+ }}}}};
+
+nsNativeThemeCocoa::ProgressParams nsNativeThemeCocoa::ComputeProgressParams(
+ nsIFrame* aFrame, EventStates aEventState, bool aIsHorizontal) {
+ ProgressParams params;
+ params.value = GetProgressValue(aFrame);
+ params.max = GetProgressMaxValue(aFrame);
+ params.verticalAlignFactor = VerticalAlignFactor(aFrame);
+ params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
+ params.indeterminate = IsIndeterminateProgress(aFrame, aEventState);
+ params.horizontal = aIsHorizontal;
+ params.rtl = IsFrameRTL(aFrame);
+ return params;
+}
+
+void nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
+ const ProgressParams& aParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSProgressBarCell* cell = mProgressBarCell;
+
+ [cell setValue:aParams.value];
+ [cell setMax:aParams.max];
+ [cell setIndeterminate:aParams.indeterminate];
+ [cell setHorizontal:aParams.horizontal];
+ [cell setControlTint:(aParams.insideActiveWindow ? [NSColor currentControlTint]
+ : NSClearControlTint)];
+
+ DrawCellWithSnapping(cell, cgContext, inBoxRect,
+ progressSettings[aParams.horizontal][aParams.indeterminate],
+ aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
+
+ 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
+ }}};
+
+nsNativeThemeCocoa::MeterParams nsNativeThemeCocoa::ComputeMeterParams(nsIFrame* aFrame) {
+ nsIContent* content = aFrame->GetContent();
+ if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) {
+ return MeterParams();
+ }
+
+ HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
+ MeterParams params;
+ params.value = meterElement->Value();
+ params.min = meterElement->Min();
+ params.max = meterElement->Max();
+ EventStates states = meterElement->State();
+ if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
+ params.optimumState = OptimumState::eSubOptimum;
+ } else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
+ params.optimumState = OptimumState::eSubSubOptimum;
+ }
+ params.horizontal = !IsVerticalMeter(aFrame);
+ params.verticalAlignFactor = VerticalAlignFactor(aFrame);
+ params.rtl = IsFrameRTL(aFrame);
+
+ return params;
+}
+
+void nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect,
+ const MeterParams& aParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK
+
+ NSLevelIndicatorCell* cell = mMeterBarCell;
+
+ [cell setMinValue:aParams.min];
+ [cell setMaxValue:aParams.max];
+ [cell setDoubleValue:aParams.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.
+ */
+ switch (aParams.optimumState) {
+ case OptimumState::eOptimum:
+ [cell setWarningValue:aParams.max + 1];
+ [cell setCriticalValue:aParams.max + 1];
+ break;
+ case OptimumState::eSubOptimum:
+ [cell setWarningValue:aParams.value];
+ [cell setCriticalValue:aParams.max + 1];
+ break;
+ case OptimumState::eSubSubOptimum:
+ [cell setWarningValue:aParams.max + 1];
+ [cell setCriticalValue:aParams.value];
+ break;
+ }
+
+ HIRect rect = CGRectStandardize(inBoxRect);
+ BOOL vertical = !aParams.horizontal;
+
+ 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, aParams.verticalAlignFactor,
+ mCellDrawView, !vertical && aParams.rtl);
+
+ CGContextRestoreGState(cgContext);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+void nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
+ bool aIsInsideActiveWindow) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeTabPaneDrawInfo tpdi;
+
+ tpdi.version = 1;
+ tpdi.state = aIsInsideActiveWindow ? kThemeStateActive : kThemeStateInactive;
+ tpdi.direction = kThemeTabNorth;
+ tpdi.size = kHIThemeTabSizeNormal;
+ tpdi.kind = kHIThemeTabKindNormal;
+
+ HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+Maybe<nsNativeThemeCocoa::ScaleParams> nsNativeThemeCocoa::ComputeHTMLScaleParams(
+ nsIFrame* aFrame, EventStates aEventState) {
+ nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
+ if (!rangeFrame) {
+ return Nothing();
+ }
+
+ bool isHorizontal = IsRangeHorizontal(aFrame);
+
+ // ScaleParams requires integer min, max and value. This is purely for
+ // drawing, so we normalize to a range 0-1000 here.
+ ScaleParams params;
+ params.value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
+ params.min = 0;
+ params.max = 1000;
+ params.reverse = !isHorizontal || rangeFrame->IsRightToLeft();
+ params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
+ params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUSRING);
+ params.disabled = IsDisabled(aFrame, aEventState);
+ params.horizontal = isHorizontal;
+ return Some(params);
+}
+
+void nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
+ const ScaleParams& aParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeTrackDrawInfo tdi;
+
+ tdi.version = 0;
+ tdi.kind = kThemeMediumSlider;
+ tdi.bounds = inBoxRect;
+ tdi.min = aParams.min;
+ tdi.max = aParams.max;
+ tdi.value = aParams.value;
+ tdi.attributes = kThemeTrackShowThumb;
+ if (aParams.horizontal) {
+ tdi.attributes |= kThemeTrackHorizontal;
+ }
+ if (aParams.reverse) {
+ tdi.attributes |= kThemeTrackRightToLeft;
+ }
+ if (aParams.focused) {
+ tdi.attributes |= kThemeTrackHasFocus;
+ }
+ if (aParams.disabled) {
+ tdi.enableState = kThemeTrackDisabled;
+ } else {
+ tdi.enableState = aParams.insideActiveWindow ? 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;
+}
+
+static CGRect SeparatorAdjustedRect(CGRect aRect, nsNativeThemeCocoa::SegmentParams aParams) {
+ // 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 (!aParams.atLeftEnd && !aParams.drawsLeftSeparator) {
+ // The segment to the left of us draws the separator, so we need to make
+ // room for it.
+ aRect.origin.x += 1;
+ aRect.size.width -= 1;
+ }
+ if (aParams.drawsRightSeparator) {
+ // 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;
+};
+
+static const CGFloat tabHeights[3] = {17, 20, 23};
+
+static const SegmentedControlRenderSettings tabRenderSettings = {tabHeights, @"tab"};
+
+static const CGFloat toolbarButtonHeights[3] = {15, 18, 22};
+
+static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
+ toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve"};
+
+nsNativeThemeCocoa::SegmentParams nsNativeThemeCocoa::ComputeSegmentParams(
+ nsIFrame* aFrame, EventStates aEventState, SegmentType aSegmentType) {
+ SegmentParams params;
+ params.segmentType = aSegmentType;
+ params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
+ params.pressed = IsPressedButton(aFrame);
+ params.selected = IsSelectedButton(aFrame);
+ params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUSRING);
+ bool isRTL = IsFrameRTL(aFrame);
+ nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
+ nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
+ params.atLeftEnd = !left;
+ params.atRightEnd = !right;
+ params.drawsLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
+ params.drawsRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
+ params.rtl = isRTL;
+ return params;
+}
+
+static SegmentedControlRenderSettings RenderSettingsForSegmentType(
+ nsNativeThemeCocoa::SegmentType aSegmentType) {
+ switch (aSegmentType) {
+ case nsNativeThemeCocoa::SegmentType::eToolbarButton:
+ return toolbarButtonRenderSettings;
+ case nsNativeThemeCocoa::SegmentType::eTab:
+ return tabRenderSettings;
+ }
+}
+
+void nsNativeThemeCocoa::DrawSegmentBackground(CGContextRef cgContext, const HIRect& inBoxRect,
+ const SegmentParams& aParams) {
+ // On earlier macOS versions, the segment background is automatically
+ // drawn correctly and this method should not be used. ASSERT here
+ // to catch unnecessary usage, but the method implementation is not
+ // dependent on Big Sur in any way.
+ MOZ_ASSERT(nsCocoaFeatures::OnBigSurOrLater());
+
+ // Use colors resembling 10.15.
+ if (aParams.selected) {
+ DeviceColor color = ToDeviceColor(mozilla::gfx::sRGBColor::FromU8(93, 93, 93, 255));
+ CGContextSetRGBFillColor(cgContext, color.r, color.g, color.b, color.a);
+ } else {
+ DeviceColor color = ToDeviceColor(mozilla::gfx::sRGBColor::FromU8(247, 247, 247, 255));
+ CGContextSetRGBFillColor(cgContext, color.r, color.g, color.b, color.a);
+ }
+
+ // Create a rect for the background fill.
+ CGRect bgRect = inBoxRect;
+ bgRect.size.height -= 3.0;
+ bgRect.size.width -= 4.0;
+ bgRect.origin.x += 2.0;
+ bgRect.origin.y += 1.0;
+
+ // Round the corners unless the button is a middle button. Buttons in
+ // a grouping but on the edge will have the inner edge filled below.
+ if (aParams.atLeftEnd || aParams.atRightEnd) {
+ CGPathRef path = CGPathCreateWithRoundedRect(bgRect, 5, 4, nullptr);
+ CGContextAddPath(cgContext, path);
+ CGPathRelease(path);
+ CGContextClosePath(cgContext);
+ CGContextFillPath(cgContext);
+ }
+
+ // Handle buttons grouped together where either or both of
+ // the side edges do not have curved corners.
+ if (!aParams.atLeftEnd && aParams.atRightEnd) {
+ // Shift the rect left to draw the left side of the
+ // rect with right angle corners leaving the right side
+ // to have rounded corners drawn with the curve above.
+ // For example, the left side of the forward button in
+ // the Library window.
+ CGRect leftRectEdge = bgRect;
+ leftRectEdge.size.width -= 10;
+ leftRectEdge.origin.x -= 2;
+ CGContextFillRect(cgContext, leftRectEdge);
+ } else if (aParams.atLeftEnd && !aParams.atRightEnd) {
+ // Shift the rect right to draw the right side of the
+ // rect with right angle corners leaving the left side
+ // to have rounded corners drawn with the curve above.
+ // For example, the right side of the back button in
+ // the Library window.
+ CGRect rightRectEdge = bgRect;
+ rightRectEdge.size.width -= 10;
+ rightRectEdge.origin.x += 12;
+ CGContextFillRect(cgContext, rightRectEdge);
+ } else if (!aParams.atLeftEnd && !aParams.atRightEnd) {
+ // The middle button in a group of buttons. Widen the
+ // background rect to meet adjacent buttons seamlessly.
+ CGRect middleRect = bgRect;
+ middleRect.size.width += 4;
+ middleRect.origin.x -= 2;
+ CGContextFillRect(cgContext, middleRect);
+ }
+}
+
+void nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
+ const SegmentParams& aParams) {
+ SegmentedControlRenderSettings renderSettings = RenderSettingsForSegmentType(aParams.segmentType);
+
+ // On Big Sur, manually draw the background of the buttons to workaround a
+ // change in Big Sur where the backround is filled with the toolbar gradient.
+ if (nsCocoaFeatures::OnBigSurOrLater() &&
+ (aParams.segmentType == nsNativeThemeCocoa::SegmentType::eToolbarButton)) {
+ DrawSegmentBackground(cgContext, inBoxRect, aParams);
+ }
+
+ NSControlSize controlSize = FindControlSize(inBoxRect.size.height, renderSettings.heights, 4.0f);
+ CGRect drawRect = SeparatorAdjustedRect(inBoxRect, aParams);
+
+ NSDictionary* dict = @{
+ @"widget" : renderSettings.widgetName,
+ @"kCUIPresentationStateKey" : (aParams.insideActiveWindow ? @"kCUIPresentationStateActiveKey"
+ : @"kCUIPresentationStateInactive"),
+ @"kCUIPositionKey" : ToolbarButtonPosition(aParams.atLeftEnd, aParams.atRightEnd),
+ @"kCUISegmentLeadingSeparatorKey" : [NSNumber numberWithBool:aParams.drawsLeftSeparator],
+ @"kCUISegmentTrailingSeparatorKey" : [NSNumber numberWithBool:aParams.drawsRightSeparator],
+ @"value" : [NSNumber numberWithBool:aParams.selected],
+ @"state" :
+ (aParams.pressed ? @"pressed" : (aParams.insideActiveWindow ? @"normal" : @"inactive")),
+ @"focus" : [NSNumber numberWithBool:aParams.focused],
+ @"size" : CUIControlSizeForCocoaSize(controlSize),
+ @"is.flipped" : [NSNumber numberWithBool:YES],
+ @"direction" : @"up"
+ };
+
+ RenderWithCoreUI(drawRect, cgContext, dict);
+}
+
+void nsNativeThemeCocoa::DrawToolbar(CGContextRef cgContext, const CGRect& inBoxRect,
+ bool aIsMain) {
+ CGRect drawRect = inBoxRect;
+
+ // top border
+ drawRect.size.height = 1.0f;
+ DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, aIsMain);
+
+ // background
+ drawRect.origin.y += drawRect.size.height;
+ drawRect.size.height = inBoxRect.size.height - 2.0f;
+ DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, aIsMain);
+
+ // bottom border
+ drawRect.origin.y += drawRect.size.height;
+ drawRect.size.height = 1.0f;
+ DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, aIsMain);
+}
+
+static bool ToolbarCanBeUnified(const gfx::Rect& aRect, NSWindow* aWindow) {
+ if (![aWindow isKindOfClass:[ToolbarWindow class]]) return false;
+
+ ToolbarWindow* win = (ToolbarWindow*)aWindow;
+ float unifiedToolbarHeight = [win unifiedToolbarHeight];
+ return aRect.X() == 0 && aRect.Width() >= [win frame].size.width &&
+ aRect.YMost() <= 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,
+ const UnifiedToolbarParams& aParams) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ CGContextSaveGState(cgContext);
+ CGContextClipToRect(cgContext, inBoxRect);
+
+ CGFloat titlebarHeight = aParams.unifiedHeight - inBoxRect.size.height;
+ CGRect drawRect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y - titlebarHeight,
+ inBoxRect.size.width, inBoxRect.size.height + titlebarHeight);
+ DrawNativeTitlebarToolbarWithSquareCorners(cgContext, drawRect, aParams.unifiedHeight,
+ aParams.isMain, YES);
+
+ CGContextRestoreGState(cgContext);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
+ bool aIsMain) {
+ 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", (aIsMain ? @"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);
+}
+
+void nsNativeThemeCocoa::DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
+ const UnifiedToolbarParams& aParams) {
+ DrawNativeTitlebar(aContext, aTitlebarRect, aParams.unifiedHeight, aParams.isMain, YES);
+}
+
+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, bool aIsRTL) {
+ 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, aIsRTL);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const sRGBColor kMultilineTextFieldTopBorderColor(0.4510, 0.4510, 0.4510, 1.0);
+static const sRGBColor kMultilineTextFieldSidesAndBottomBorderColor(0.6, 0.6, 0.6, 1.0);
+static const sRGBColor kListboxTopBorderColor(0.557, 0.557, 0.557, 1.0);
+static const sRGBColor kListBoxSidesAndBottomBorderColor(0.745, 0.745, 0.745, 1.0);
+
+void nsNativeThemeCocoa::DrawMultilineTextField(CGContextRef cgContext, const CGRect& inBoxRect,
+ bool aIsFocused) {
+ SetCGContextFillColor(cgContext, sRGBColor(1.0, 1.0, 1.0, 1.0));
+
+ CGContextFillRect(cgContext, inBoxRect);
+
+ float x = inBoxRect.origin.x, y = inBoxRect.origin.y;
+ float w = inBoxRect.size.width, h = inBoxRect.size.height;
+ SetCGContextFillColor(cgContext, kMultilineTextFieldTopBorderColor);
+ CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
+ SetCGContextFillColor(cgContext, kMultilineTextFieldSidesAndBottomBorderColor);
+ 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));
+
+ if (aIsFocused) {
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext
+ setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext
+ flipped:YES]];
+ CGContextSaveGState(cgContext);
+ NSSetFocusRingStyle(NSFocusRingOnly);
+ NSRectFill(NSRectFromCGRect(inBoxRect));
+ CGContextRestoreGState(cgContext);
+ [NSGraphicsContext setCurrentContext:savedContext];
+ }
+}
+
+void nsNativeThemeCocoa::DrawSourceListSelection(CGContextRef aContext, const CGRect& aRect,
+ bool aWindowIsActive, bool aSelectionIsActive) {
+ NSColor* fillColor;
+ if (aSelectionIsActive) {
+ // Active selection, blue or graphite.
+ fillColor = ControlAccentColor();
+ } else {
+ // Inactive selection, gray.
+ if (aWindowIsActive) {
+ fillColor = [NSColor colorWithWhite:0.871 alpha:1.0];
+ } else {
+ fillColor = [NSColor colorWithWhite:0.808 alpha:1.0];
+ }
+ }
+ CGContextSetFillColorWithColor(aContext, [fillColor CGColor]);
+ CGContextFillRect(aContext, aRect);
+}
+
+static bool IsHiDPIContext(nsDeviceContext* aContext) {
+ return AppUnitsPerCSSPixel() >= 2 * aContext->AppUnitsPerDevPixelAtUnitFullZoom();
+}
+
+Maybe<nsNativeThemeCocoa::WidgetInfo> nsNativeThemeCocoa::ComputeWidgetInfo(
+ nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // setup to draw into the correct port
+ int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ gfx::Rect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
+ nativeWidgetRect.Scale(1.0 / gfxFloat(p2a));
+ float originalHeight = nativeWidgetRect.Height();
+ nativeWidgetRect.Round();
+ if (nativeWidgetRect.IsEmpty()) {
+ return Nothing(); // Don't attempt to draw invisible widgets.
+ }
+
+ bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
+ if (hidpi) {
+ // Use high-resolution drawing.
+ nativeWidgetRect.Scale(0.5f);
+ originalHeight *= 0.5f;
+ }
+
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+
+ switch (aAppearance) {
+ case StyleAppearance::Menupopup:
+ return Nothing();
+
+ case StyleAppearance::Menuarrow:
+ return Some(
+ WidgetInfo::MenuIcon(ComputeMenuIconParams(aFrame, eventState, MenuIcon::eMenuArrow)));
+
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ return Some(WidgetInfo::MenuItem(ComputeMenuItemParams(
+ aFrame, eventState, aAppearance == StyleAppearance::Checkmenuitem)));
+
+ case StyleAppearance::Menuseparator:
+ return Some(WidgetInfo::MenuSeparator(ComputeMenuItemParams(aFrame, eventState, false)));
+
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowDown: {
+ MenuIcon icon = aAppearance == StyleAppearance::ButtonArrowUp
+ ? MenuIcon::eMenuUpScrollArrow
+ : MenuIcon::eMenuDownScrollArrow;
+ return Some(WidgetInfo::MenuIcon(ComputeMenuIconParams(aFrame, eventState, icon)));
+ }
+
+ case StyleAppearance::Tooltip:
+ return Nothing();
+
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio: {
+ bool isCheckbox = (aAppearance == StyleAppearance::Checkbox);
+
+ CheckboxOrRadioParams params;
+ params.state = CheckboxOrRadioState::eOff;
+ if (isCheckbox && GetIndeterminate(aFrame)) {
+ params.state = CheckboxOrRadioState::eIndeterminate;
+ } else if (GetCheckedOrSelected(aFrame, !isCheckbox)) {
+ params.state = CheckboxOrRadioState::eOn;
+ }
+ params.controlParams = ComputeControlParams(aFrame, eventState);
+ params.verticalAlignFactor = VerticalAlignFactor(aFrame);
+ if (isCheckbox) {
+ return Some(WidgetInfo::Checkbox(params));
+ }
+ return Some(WidgetInfo::Radio(params));
+ }
+
+ case StyleAppearance::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);
+ bool hasDefaultButtonLook = isInActiveWindow && !eventState.HasState(NS_EVENT_STATE_ACTIVE);
+ ButtonType buttonType =
+ hasDefaultButtonLook ? ButtonType::eDefaultPushButton : ButtonType::eRegularPushButton;
+ ControlParams params = ComputeControlParams(aFrame, eventState);
+ params.insideActiveWindow = isInActiveWindow;
+ return Some(WidgetInfo::Button(ButtonParams{params, buttonType}));
+ }
+ if (IsButtonTypeMenu(aFrame)) {
+ ControlParams controlParams = ComputeControlParams(aFrame, eventState);
+ controlParams.focused = controlParams.focused || IsFocused(aFrame);
+ controlParams.pressed = IsOpenButton(aFrame);
+ DropdownParams params;
+ params.controlParams = controlParams;
+ params.pullsDown = true;
+ params.editable = false;
+ return Some(WidgetInfo::Dropdown(params));
+ }
+ if (originalHeight > DO_SQUARE_BUTTON_HEIGHT) {
+ // 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.
+ return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
+ ButtonType::eSquareBezelPushButton}));
+ }
+ return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
+ ButtonType::eRoundedBezelPushButton}));
+
+ case StyleAppearance::FocusOutline:
+ return Some(WidgetInfo::FocusOutline());
+
+ case StyleAppearance::MozMacHelpButton:
+ return Some(WidgetInfo::Button(
+ ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eHelpButton}));
+
+ case StyleAppearance::MozMacDisclosureButtonOpen:
+ case StyleAppearance::MozMacDisclosureButtonClosed: {
+ ButtonType buttonType = (aAppearance == StyleAppearance::MozMacDisclosureButtonClosed)
+ ? ButtonType::eDisclosureButtonClosed
+ : ButtonType::eDisclosureButtonOpen;
+ return Some(
+ WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState), buttonType}));
+ }
+
+ case StyleAppearance::Spinner: {
+ bool isSpinner = (aAppearance == StyleAppearance::Spinner);
+ nsIContent* content = aFrame->GetContent();
+ if (isSpinner && 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;
+ }
+ SpinButtonParams params;
+ if (content->IsElement()) {
+ if (content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state, u"up"_ns,
+ eCaseMatters)) {
+ params.pressedButton = Some(SpinButton::eUp);
+ } else if (content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
+ u"down"_ns, eCaseMatters)) {
+ params.pressedButton = Some(SpinButton::eDown);
+ }
+ }
+ params.disabled = IsDisabled(aFrame, eventState);
+ params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
+
+ return Some(WidgetInfo::SpinButtons(params));
+ }
+
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton: {
+ nsNumberControlFrame* numberControlFrame =
+ nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
+ if (numberControlFrame) {
+ SpinButtonParams params;
+ if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
+ params.pressedButton = Some(SpinButton::eUp);
+ } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
+ params.pressedButton = Some(SpinButton::eDown);
+ }
+ params.disabled = IsDisabled(aFrame, eventState);
+ params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
+ if (aAppearance == StyleAppearance::SpinnerUpbutton) {
+ return Some(WidgetInfo::SpinButtonUp(params));
+ }
+ return Some(WidgetInfo::SpinButtonDown(params));
+ }
+ } break;
+
+ case StyleAppearance::Toolbarbutton: {
+ SegmentParams params = ComputeSegmentParams(aFrame, eventState, SegmentType::eToolbarButton);
+ params.insideActiveWindow = [NativeWindowForFrame(aFrame) isMainWindow];
+ return Some(WidgetInfo::Segment(params));
+ }
+
+ case StyleAppearance::Separator:
+ return Some(WidgetInfo::Separator());
+
+ case StyleAppearance::Toolbar: {
+ NSWindow* win = NativeWindowForFrame(aFrame);
+ bool isMain = [win isMainWindow];
+ if (ToolbarCanBeUnified(nativeWidgetRect, win)) {
+ float unifiedHeight =
+ std::max(float([(ToolbarWindow*)win unifiedToolbarHeight]), nativeWidgetRect.Height());
+ return Some(WidgetInfo::UnifiedToolbar(UnifiedToolbarParams{unifiedHeight, isMain}));
+ }
+ return Some(WidgetInfo::Toolbar(isMain));
+ }
+
+ case StyleAppearance::MozWindowTitlebar: {
+ NSWindow* win = NativeWindowForFrame(aFrame);
+ bool isMain = [win isMainWindow];
+ float unifiedToolbarHeight = [win isKindOfClass:[ToolbarWindow class]]
+ ? [(ToolbarWindow*)win unifiedToolbarHeight]
+ : nativeWidgetRect.Height();
+ return Some(WidgetInfo::NativeTitlebar(UnifiedToolbarParams{unifiedToolbarHeight, isMain}));
+ }
+
+ case StyleAppearance::Statusbar:
+ return Some(WidgetInfo::StatusBar(IsActive(aFrame, YES)));
+
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Menulist: {
+ ControlParams controlParams = ComputeControlParams(aFrame, eventState);
+ controlParams.focused = controlParams.focused || IsFocused(aFrame);
+ controlParams.pressed = IsOpenButton(aFrame);
+ DropdownParams params;
+ params.controlParams = controlParams;
+ params.pullsDown = false;
+ params.editable = false;
+ return Some(WidgetInfo::Dropdown(params));
+ }
+
+ case StyleAppearance::MozMenulistArrowButton:
+ return Some(WidgetInfo::Button(
+ ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eArrowButton}));
+
+ case StyleAppearance::Groupbox:
+ return Some(WidgetInfo::GroupBox());
+
+ case StyleAppearance::Textfield:
+ case StyleAppearance::NumberInput: {
+ // See ShouldUnconditionallyDrawFocusRingIfFocused.
+ bool isFocused = eventState.HasState(NS_EVENT_STATE_FOCUS);
+ // 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)) {
+ isFocused = true;
+ }
+
+ bool isDisabled = IsDisabled(aFrame, eventState) || IsReadOnly(aFrame);
+ return Some(
+ WidgetInfo::TextBox(TextBoxParams{isDisabled, isFocused, /* borderless = */ false}));
+ }
+
+ case StyleAppearance::Searchfield:
+ return Some(WidgetInfo::SearchField(ComputeSearchFieldParams(aFrame, eventState)));
+
+ case StyleAppearance::ProgressBar: {
+ if (IsIndeterminateProgress(aFrame, eventState)) {
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
+ NS_WARNING("Unable to animate progressbar!");
+ }
+ }
+ return Some(WidgetInfo::ProgressBar(
+ ComputeProgressParams(aFrame, eventState, !IsVerticalProgress(aFrame))));
+ }
+
+ case StyleAppearance::Meter:
+ return Some(WidgetInfo::Meter(ComputeMeterParams(aFrame)));
+
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Meterchunk:
+ // Do nothing: progress and meter bars cases will draw chunks.
+ break;
+
+ case StyleAppearance::Treetwisty:
+ return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
+ ButtonType::eTreeTwistyPointingRight}));
+
+ case StyleAppearance::Treetwistyopen:
+ return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
+ ButtonType::eTreeTwistyPointingDown}));
+
+ case StyleAppearance::Treeheadercell:
+ return Some(WidgetInfo::TreeHeaderCell(ComputeTreeHeaderCellParams(aFrame, eventState)));
+
+ case StyleAppearance::Treeitem:
+ case StyleAppearance::Treeview:
+ return Some(WidgetInfo::ColorFill(sRGBColor(1.0, 1.0, 1.0, 1.0)));
+
+ case StyleAppearance::Treeheader:
+ // do nothing, taken care of by individual header cells
+ case StyleAppearance::Treeheadersortarrow:
+ // do nothing, taken care of by treeview header
+ case StyleAppearance::Treeline:
+ // do nothing, these lines don't exist on macos
+ break;
+
+ case StyleAppearance::Range: {
+ Maybe<ScaleParams> params = ComputeHTMLScaleParams(aFrame, eventState);
+ if (params) {
+ return Some(WidgetInfo::Scale(*params));
+ }
+ break;
+ }
+
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonRight:
+ break;
+
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::Scrollcorner: {
+ bool isHorizontal = aAppearance == StyleAppearance::ScrollbarthumbHorizontal ||
+ aAppearance == StyleAppearance::ScrollbartrackHorizontal;
+ ScrollbarParams params = ScrollbarDrawingMac::ComputeScrollbarParams(
+ aFrame, *nsLayoutUtils::StyleForScrollbar(aFrame), isHorizontal);
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ return Some(WidgetInfo::ScrollbarThumb(params));
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical:
+ return Some(WidgetInfo::ScrollbarTrack(params));
+ case StyleAppearance::Scrollcorner:
+ return Some(WidgetInfo::ScrollCorner(params));
+ default:
+ MOZ_CRASH("unexpected aAppearance");
+ }
+ break;
+ }
+
+ case StyleAppearance::Textarea:
+ return Some(WidgetInfo::MultilineTextField(eventState.HasState(NS_EVENT_STATE_FOCUS)));
+
+ case StyleAppearance::Listbox:
+ return Some(WidgetInfo::ListBox());
+
+ case StyleAppearance::MozMacSourceList: {
+ return Nothing();
+ }
+
+ case StyleAppearance::MozMacSourceListSelection:
+ case StyleAppearance::MozMacActiveSourceListSelection: {
+ // We only support vibrancy for source list selections if we're inside
+ // a source list, because we need the background to be transparent.
+ if (IsInSourceList(aFrame)) {
+ return Nothing();
+ }
+ bool isInActiveWindow = FrameIsInActiveWindow(aFrame);
+ if (aAppearance == StyleAppearance::MozMacActiveSourceListSelection) {
+ return Some(WidgetInfo::ActiveSourceListSelection(isInActiveWindow));
+ }
+ return Some(WidgetInfo::InactiveSourceListSelection(isInActiveWindow));
+ }
+
+ case StyleAppearance::Tab: {
+ SegmentParams params = ComputeSegmentParams(aFrame, eventState, SegmentType::eTab);
+ params.pressed = params.pressed && !params.selected;
+ return Some(WidgetInfo::Segment(params));
+ }
+
+ case StyleAppearance::Tabpanels:
+ return Some(WidgetInfo::TabPanel(FrameIsInActiveWindow(aFrame)));
+
+ case StyleAppearance::Resizer:
+ return Some(WidgetInfo::Resizer(IsFrameRTL(aFrame)));
+
+ default:
+ break;
+ }
+
+ return Nothing();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(Nothing());
+}
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance, const nsRect& aRect,
+ const nsRect& aDirtyRect) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ Maybe<WidgetInfo> widgetInfo = ComputeWidgetInfo(aFrame, aAppearance, aRect);
+
+ if (!widgetInfo) {
+ return NS_OK;
+ }
+
+ int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ gfx::Rect nativeWidgetRect = NSRectToRect(aRect, p2a);
+ nativeWidgetRect.Round();
+
+ bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
+
+ RenderWidget(*widgetInfo, *aContext->GetDrawTarget(), nativeWidgetRect,
+ NSRectToRect(aDirtyRect, p2a), hidpi ? 2.0f : 1.0f);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsNativeThemeCocoa::RenderWidget(const WidgetInfo& aWidgetInfo, DrawTarget& aDrawTarget,
+ const gfx::Rect& aWidgetRect, const gfx::Rect& aDirtyRect,
+ float aScale) {
+ AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
+
+ gfx::Rect dirtyRect = aDirtyRect;
+ gfx::Rect widgetRect = aWidgetRect;
+ dirtyRect.Scale(1.0f / aScale);
+ widgetRect.Scale(1.0f / aScale);
+ aDrawTarget.SetTransform(aDrawTarget.GetTransform().PreScale(aScale, aScale));
+
+ const Widget widget = aWidgetInfo.Widget();
+
+ // Some widgets render using DrawTarget, and some using CGContext.
+ switch (widget) {
+ case Widget::eColorFill: {
+ sRGBColor color = aWidgetInfo.Params<sRGBColor>();
+ aDrawTarget.FillRect(widgetRect, ColorPattern(ToDeviceColor(color)));
+ break;
+ }
+ case Widget::eScrollbarThumb: {
+ ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
+ ScrollbarDrawingMac::DrawScrollbarThumb(aDrawTarget, widgetRect, params);
+ break;
+ }
+ case Widget::eScrollbarTrack: {
+ ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
+ ScrollbarDrawingMac::DrawScrollbarTrack(aDrawTarget, widgetRect, params);
+ break;
+ }
+ case Widget::eScrollCorner: {
+ ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
+ ScrollbarDrawingMac::DrawScrollCorner(aDrawTarget, widgetRect, params);
+ break;
+ }
+ default: {
+ // The remaining widgets require a CGContext.
+ CGRect macRect =
+ CGRectMake(widgetRect.X(), widgetRect.Y(), widgetRect.Width(), widgetRect.Height());
+
+ gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, dirtyRect);
+
+ 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;
+ }
+
+ // Set the context's "base transform" to in order to get correctly-sized focus rings.
+ CGContextSetBaseCTM(cgContext, CGAffineTransformMakeScale(aScale, aScale));
+
+ switch (widget) {
+ case Widget::eColorFill:
+ case Widget::eScrollbarThumb:
+ case Widget::eScrollbarTrack:
+ case Widget::eScrollCorner: {
+ MOZ_CRASH("already handled in outer switch");
+ break;
+ }
+ case Widget::eMenuIcon: {
+ MenuIconParams params = aWidgetInfo.Params<MenuIconParams>();
+ DrawMenuIcon(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eMenuItem: {
+ MenuItemParams params = aWidgetInfo.Params<MenuItemParams>();
+ DrawMenuItem(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eMenuSeparator: {
+ MenuItemParams params = aWidgetInfo.Params<MenuItemParams>();
+ DrawMenuSeparator(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eCheckbox: {
+ CheckboxOrRadioParams params = aWidgetInfo.Params<CheckboxOrRadioParams>();
+ DrawCheckboxOrRadio(cgContext, true, macRect, params);
+ break;
+ }
+ case Widget::eRadio: {
+ CheckboxOrRadioParams params = aWidgetInfo.Params<CheckboxOrRadioParams>();
+ DrawCheckboxOrRadio(cgContext, false, macRect, params);
+ break;
+ }
+ case Widget::eButton: {
+ ButtonParams params = aWidgetInfo.Params<ButtonParams>();
+ DrawButton(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eDropdown: {
+ DropdownParams params = aWidgetInfo.Params<DropdownParams>();
+ DrawDropdown(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eFocusOutline: {
+ DrawFocusOutline(cgContext, macRect);
+ break;
+ }
+ case Widget::eSpinButtons: {
+ SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
+ DrawSpinButtons(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eSpinButtonUp: {
+ SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
+ DrawSpinButton(cgContext, macRect, SpinButton::eUp, params);
+ break;
+ }
+ case Widget::eSpinButtonDown: {
+ SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
+ DrawSpinButton(cgContext, macRect, SpinButton::eDown, params);
+ break;
+ }
+ case Widget::eSegment: {
+ SegmentParams params = aWidgetInfo.Params<SegmentParams>();
+ DrawSegment(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eSeparator: {
+ HIThemeSeparatorDrawInfo sdi = {0, kThemeStateActive};
+ HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
+ break;
+ }
+ case Widget::eUnifiedToolbar: {
+ UnifiedToolbarParams params = aWidgetInfo.Params<UnifiedToolbarParams>();
+ DrawUnifiedToolbar(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eToolbar: {
+ bool isMain = aWidgetInfo.Params<bool>();
+ DrawToolbar(cgContext, macRect, isMain);
+ break;
+ }
+ case Widget::eNativeTitlebar: {
+ UnifiedToolbarParams params = aWidgetInfo.Params<UnifiedToolbarParams>();
+ DrawNativeTitlebar(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eStatusBar: {
+ bool isMain = aWidgetInfo.Params<bool>();
+ DrawStatusBar(cgContext, macRect, isMain);
+ break;
+ }
+ case Widget::eGroupBox: {
+ HIThemeGroupBoxDrawInfo gdi = {0, kThemeStateActive, kHIThemeGroupBoxKindPrimary};
+ HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
+ break;
+ }
+ case Widget::eTextBox: {
+ TextBoxParams params = aWidgetInfo.Params<TextBoxParams>();
+ DrawTextBox(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eSearchField: {
+ SearchFieldParams params = aWidgetInfo.Params<SearchFieldParams>();
+ DrawSearchField(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eProgressBar: {
+ ProgressParams params = aWidgetInfo.Params<ProgressParams>();
+ DrawProgress(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eMeter: {
+ MeterParams params = aWidgetInfo.Params<MeterParams>();
+ DrawMeter(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eTreeHeaderCell: {
+ TreeHeaderCellParams params = aWidgetInfo.Params<TreeHeaderCellParams>();
+ DrawTreeHeaderCell(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eScale: {
+ ScaleParams params = aWidgetInfo.Params<ScaleParams>();
+ DrawScale(cgContext, macRect, params);
+ break;
+ }
+ case Widget::eMultilineTextField: {
+ bool isFocused = aWidgetInfo.Params<bool>();
+ DrawMultilineTextField(cgContext, macRect, isFocused);
+ break;
+ }
+ case Widget::eListBox: {
+ // We have to draw this by hand because kHIThemeFrameListBox drawing
+ // is buggy on 10.5, see bug 579259.
+ SetCGContextFillColor(cgContext, sRGBColor(1.0, 1.0, 1.0, 1.0));
+ CGContextFillRect(cgContext, macRect);
+
+ float x = macRect.origin.x, y = macRect.origin.y;
+ float w = macRect.size.width, h = macRect.size.height;
+ SetCGContextFillColor(cgContext, kListboxTopBorderColor);
+ CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
+ SetCGContextFillColor(cgContext, kListBoxSidesAndBottomBorderColor);
+ 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 Widget::eActiveSourceListSelection:
+ case Widget::eInactiveSourceListSelection: {
+ bool isInActiveWindow = aWidgetInfo.Params<bool>();
+ bool isActiveSelection = aWidgetInfo.Widget() == Widget::eActiveSourceListSelection;
+ DrawSourceListSelection(cgContext, macRect, isInActiveWindow, isActiveSelection);
+ break;
+ }
+ case Widget::eTabPanel: {
+ bool isInsideActiveWindow = aWidgetInfo.Params<bool>();
+ DrawTabPanel(cgContext, macRect, isInsideActiveWindow);
+ break;
+ }
+ case Widget::eResizer: {
+ bool isRTL = aWidgetInfo.Params<bool>();
+ DrawResizer(cgContext, macRect, isRTL);
+ break;
+ }
+ }
+
+ // Reset the base CTM.
+ CGContextSetBaseCTM(cgContext, CGAffineTransformIdentity);
+
+ nativeDrawing.EndNativeDrawing();
+ }
+ }
+}
+
+bool nsNativeThemeCocoa::CreateWebRenderCommandsForWidget(
+ mozilla::wr::DisplayListBuilder& aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
+ StyleAppearance aAppearance, const nsRect& aRect) {
+ nsPresContext* presContext = aFrame->PresContext();
+ wr::LayoutRect bounds =
+ wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(aRect, presContext->AppUnitsPerDevPixel()));
+
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+
+ // This list needs to stay consistent with the list in DrawWidgetBackground.
+ // For every switch case in DrawWidgetBackground, there are three choices:
+ // - If the case in DrawWidgetBackground draws nothing for the given widget
+ // type, then don't list it here. We will hit the "default: return true;"
+ // case.
+ // - If the case in DrawWidgetBackground draws something simple for the given
+ // widget type, imitate that drawing using WebRender commands.
+ // - If the case in DrawWidgetBackground draws something complicated for the
+ // given widget type, return false here.
+ switch (aAppearance) {
+ case StyleAppearance::Menuarrow:
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Menuseparator:
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowDown:
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio:
+ case StyleAppearance::Button:
+ case StyleAppearance::FocusOutline:
+ case StyleAppearance::MozMacHelpButton:
+ case StyleAppearance::MozMacDisclosureButtonOpen:
+ case StyleAppearance::MozMacDisclosureButtonClosed:
+ case StyleAppearance::Spinner:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::Separator:
+ case StyleAppearance::Toolbar:
+ case StyleAppearance::MozWindowTitlebar:
+ case StyleAppearance::Statusbar:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::Groupbox:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Searchfield:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Meter:
+ case StyleAppearance::Treetwisty:
+ case StyleAppearance::Treetwistyopen:
+ case StyleAppearance::Treeheadercell:
+ case StyleAppearance::Treeitem:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Range:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ return false;
+
+ case StyleAppearance::Scrollcorner:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical: {
+ const ComputedStyle& style = *nsLayoutUtils::StyleForScrollbar(aFrame);
+ ScrollbarParams params = ScrollbarDrawingMac::ComputeScrollbarParams(
+ aFrame, style, aAppearance == StyleAppearance::ScrollbartrackHorizontal);
+ if (params.overlay && !params.rolledOver) {
+ // There is no scrollbar track, draw nothing and return true.
+ return true;
+ }
+ // There is a scrollbar track and it needs to be drawn using fallback.
+ return false;
+ }
+
+ case StyleAppearance::Textarea: {
+ if (eventState.HasState(NS_EVENT_STATE_FOCUS)) {
+ // We can't draw the focus ring using webrender, so fall back to regular
+ // drawing if we're focused.
+ return false;
+ }
+
+ // White background
+ aBuilder.PushRect(bounds, bounds, true,
+ wr::ToColorF(ToDeviceColor(sRGBColor::OpaqueWhite())));
+
+ wr::BorderSide side[4] = {
+ wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldTopBorderColor),
+ StyleBorderStyle::Solid),
+ wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldSidesAndBottomBorderColor),
+ StyleBorderStyle::Solid),
+ wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldSidesAndBottomBorderColor),
+ StyleBorderStyle::Solid),
+ wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldSidesAndBottomBorderColor),
+ StyleBorderStyle::Solid),
+ };
+
+ wr::BorderRadius borderRadius = wr::EmptyBorderRadius();
+ float borderWidth = presContext->CSSPixelsToDevPixels(1.0f);
+ wr::LayoutSideOffsets borderWidths =
+ wr::ToBorderWidths(borderWidth, borderWidth, borderWidth, borderWidth);
+
+ mozilla::Range<const wr::BorderSide> wrsides(side, 4);
+ aBuilder.PushBorder(bounds, bounds, true, borderWidths, wrsides, borderRadius);
+
+ return true;
+ }
+
+ case StyleAppearance::Listbox: {
+ // White background
+ aBuilder.PushRect(bounds, bounds, true,
+ wr::ToColorF(ToDeviceColor(sRGBColor::OpaqueWhite())));
+
+ wr::BorderSide side[4] = {
+ wr::ToBorderSide(ToDeviceColor(kListboxTopBorderColor), StyleBorderStyle::Solid),
+ wr::ToBorderSide(ToDeviceColor(kListBoxSidesAndBottomBorderColor),
+ StyleBorderStyle::Solid),
+ wr::ToBorderSide(ToDeviceColor(kListBoxSidesAndBottomBorderColor),
+ StyleBorderStyle::Solid),
+ wr::ToBorderSide(ToDeviceColor(kListBoxSidesAndBottomBorderColor),
+ StyleBorderStyle::Solid),
+ };
+
+ wr::BorderRadius borderRadius = wr::EmptyBorderRadius();
+ float borderWidth = presContext->CSSPixelsToDevPixels(1.0f);
+ wr::LayoutSideOffsets borderWidths =
+ wr::ToBorderWidths(borderWidth, borderWidth, borderWidth, borderWidth);
+
+ mozilla::Range<const wr::BorderSide> wrsides(side, 4);
+ aBuilder.PushBorder(bounds, bounds, true, borderWidths, wrsides, borderRadius);
+ return true;
+ }
+
+ case StyleAppearance::Tab:
+ case StyleAppearance::Tabpanels:
+ case StyleAppearance::Resizer:
+ return false;
+
+ default:
+ return true;
+ }
+}
+
+LayoutDeviceIntMargin nsNativeThemeCocoa::DirectionAwareMargin(const LayoutDeviceIntMargin& 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 LayoutDeviceIntMargin(m.top, m.right, m.bottom, m.left);
+}
+
+static const LayoutDeviceIntMargin kAquaDropdownBorder(1, 22, 2, 5);
+static const LayoutDeviceIntMargin kAquaComboboxBorder(3, 20, 3, 4);
+static const LayoutDeviceIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
+static const LayoutDeviceIntMargin kAquaSearchfieldBorderBigSur(5, 5, 4, 26);
+
+LayoutDeviceIntMargin nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ LayoutDeviceIntMargin result;
+
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ switch (aAppearance) {
+ case StyleAppearance::Button: {
+ if (IsButtonTypeMenu(aFrame)) {
+ result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
+ } else {
+ result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 7, 3, 7), aFrame);
+ }
+ break;
+ }
+
+ case StyleAppearance::Toolbarbutton: {
+ result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 4, 1, 4), aFrame);
+ break;
+ }
+
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio: {
+ // nsCheckboxRadioFrame::GetIntrinsicWidth and nsCheckboxRadioFrame::GetIntrinsicHeight
+ // assume a border width of 2px.
+ result.SizeTo(2, 2, 2, 2);
+ break;
+ }
+
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::MozMenulistArrowButton:
+ result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
+ break;
+
+ case StyleAppearance::Menuarrow:
+ if (nsCocoaFeatures::OnBigSurOrLater()) {
+ result.SizeTo(0, 0, 0, 28);
+ }
+ break;
+
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield: {
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
+
+ SInt32 textPadding = 0;
+ ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
+
+ frameOutset += textPadding;
+
+ result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
+ break;
+ }
+
+ case StyleAppearance::Textarea:
+ result.SizeTo(1, 1, 1, 1);
+ break;
+
+ case StyleAppearance::Searchfield: {
+ auto border = nsCocoaFeatures::OnBigSurOrLater() ? kAquaSearchfieldBorderBigSur
+ : kAquaSearchfieldBorder;
+ result = DirectionAwareMargin(border, aFrame);
+ break;
+ }
+
+ case StyleAppearance::Listbox: {
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
+ result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
+ break;
+ }
+
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical: {
+ bool isHorizontal = (aAppearance == StyleAppearance::ScrollbartrackHorizontal);
+ if (nsLookAndFeel::UseOverlayScrollbars()) {
+ // Leave a bit of space at the start and the end on all OS X versions.
+ if (isHorizontal) {
+ result.left = 1;
+ result.right = 1;
+ } else {
+ result.top = 1;
+ result.bottom = 1;
+ }
+ }
+
+ break;
+ }
+
+ case StyleAppearance::Statusbar:
+ result.SizeTo(1, 0, 0, 0);
+ break;
+
+ default:
+ break;
+ }
+
+ if (IsHiDPIContext(aContext)) {
+ result = result + result; // doubled
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(result);
+}
+
+// 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,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) {
+ // We don't want CSS padding being used for certain widgets.
+ // See bug 381639 for an example of why.
+ switch (aAppearance) {
+ // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
+ // and have a meaningful baseline, so they can't have
+ // author-specified padding.
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio:
+ aResult->SizeTo(0, 0, 0, 0);
+ return true;
+
+ case StyleAppearance::Menuarrow:
+ case StyleAppearance::Searchfield:
+ if (nsCocoaFeatures::OnBigSurOrLater()) {
+ return true;
+ }
+ break;
+
+ default:
+ break;
+ }
+ return false;
+}
+
+bool nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance, nsRect* aOverflowRect) {
+ nsIntMargin overflow;
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ case StyleAppearance::MozMacDisclosureButtonOpen:
+ case StyleAppearance::MozMacDisclosureButtonClosed:
+ case StyleAppearance::MozMacHelpButton:
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Searchfield:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio:
+ case StyleAppearance::Tab:
+ case StyleAppearance::FocusOutline: {
+ overflow.SizeTo(kMaxFocusRingWidth, kMaxFocusRingWidth, kMaxFocusRingWidth,
+ kMaxFocusRingWidth);
+ break;
+ }
+ case StyleAppearance::ProgressBar: {
+ // Progress bars draw a 2 pixel white shadow under their progress indicators.
+ overflow.bottom = 2;
+ break;
+ }
+ case StyleAppearance::Meter: {
+ // Meter bars overflow their boxes by about 2 pixels.
+ overflow.SizeTo(2, 2, 2, 2);
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (IsHiDPIContext(aContext)) {
+ // Double the number of device pixels.
+ overflow += overflow;
+ }
+
+ if (overflow != nsIntMargin()) {
+ int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
+ aOverflowRect->Inflate(nsMargin(
+ NSIntPixelsToAppUnits(overflow.top, p2a), NSIntPixelsToAppUnits(overflow.right, p2a),
+ NSIntPixelsToAppUnits(overflow.bottom, p2a), NSIntPixelsToAppUnits(overflow.left, p2a)));
+ return true;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance, LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ aResult->SizeTo(0, 0);
+ *aIsOverridable = true;
+
+ switch (aAppearance) {
+ case StyleAppearance::Button: {
+ aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
+ pushButtonSettings.naturalSizes[miniControlSize].height);
+ break;
+ }
+
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowDown: {
+ aResult->SizeTo(kMenuScrollArrowSize.width, kMenuScrollArrowSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case StyleAppearance::Menuarrow: {
+ aResult->SizeTo(kMenuarrowSize.width, kMenuarrowSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case StyleAppearance::MozMacDisclosureButtonOpen:
+ case StyleAppearance::MozMacDisclosureButtonClosed: {
+ aResult->SizeTo(kDisclosureButtonSize.width, kDisclosureButtonSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case StyleAppearance::MozMacHelpButton: {
+ aResult->SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case StyleAppearance::Toolbarbutton: {
+ aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]);
+ break;
+ }
+
+ case StyleAppearance::Spinner:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton: {
+ SInt32 buttonHeight = 0, buttonWidth = 0;
+ if (aFrame->GetContent()->IsXULElement()) {
+ ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
+ ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
+ } else {
+ NSSize size = spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSControlSizeMini)];
+ buttonWidth = size.width;
+ buttonHeight = size.height;
+ if (aAppearance != StyleAppearance::Spinner) {
+ // the buttons are half the height of the spinner
+ buttonHeight /= 2;
+ }
+ }
+ aResult->SizeTo(buttonWidth, buttonHeight);
+ *aIsOverridable = true;
+ break;
+ }
+
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton: {
+ SInt32 popupHeight = 0;
+ ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
+ aResult->SizeTo(0, popupHeight);
+ break;
+ }
+
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::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 StyleAppearance::MozWindowButtonBox: {
+ NSSize size = WindowButtonsSize(aFrame);
+ aResult->SizeTo(size.width, size.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case StyleAppearance::MozMacFullscreenButton: {
+ *aIsOverridable = false;
+ break;
+ }
+
+ case StyleAppearance::ProgressBar: {
+ SInt32 barHeight = 0;
+ ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
+ aResult->SizeTo(0, barHeight);
+ break;
+ }
+
+ case StyleAppearance::Separator: {
+ aResult->SizeTo(1, 1);
+ break;
+ }
+
+ case StyleAppearance::Treetwisty:
+ case StyleAppearance::Treetwistyopen: {
+ SInt32 twistyHeight = 0, twistyWidth = 0;
+ ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
+ ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
+ aResult->SizeTo(twistyWidth, twistyHeight);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case StyleAppearance::Treeheader:
+ case StyleAppearance::Treeheadercell: {
+ SInt32 headerHeight = 0;
+ ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
+ aResult->SizeTo(0, headerHeight - 1); // We don't need the top border.
+ break;
+ }
+
+ case StyleAppearance::Tab: {
+ aResult->SizeTo(0, tabHeights[miniControlSize]);
+ break;
+ }
+
+ case StyleAppearance::RangeThumb: {
+ SInt32 width = 0;
+ SInt32 height = 0;
+ ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
+ ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
+ aResult->SizeTo(width, height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight: {
+ *aIsOverridable = false;
+ *aResult = ScrollbarDrawingMac::GetMinimumWidgetSize(aAppearance, aFrame, 1.0f);
+ break;
+ }
+
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::ScrollbarNonDisappearing: {
+ *aResult = ScrollbarDrawingMac::GetMinimumWidgetSize(aAppearance, aFrame, 1.0f);
+ break;
+ }
+
+ case StyleAppearance::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;
+ }
+ default:
+ break;
+ }
+
+ if (IsHiDPIContext(aPresContext->DeviceContext())) {
+ *aResult = *aResult * 2;
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) {
+ // Some widget types just never change state.
+ switch (aAppearance) {
+ case StyleAppearance::MozWindowTitlebar:
+ case StyleAppearance::Toolbox:
+ case StyleAppearance::Toolbar:
+ case StyleAppearance::Statusbar:
+ case StyleAppearance::Statusbarpanel:
+ case StyleAppearance::Resizerpanel:
+ case StyleAppearance::Tooltip:
+ case StyleAppearance::Tabpanels:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Dialog:
+ case StyleAppearance::Menupopup:
+ case StyleAppearance::Groupbox:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Meter:
+ case StyleAppearance::Meterchunk:
+ case StyleAppearance::MozMacVibrancyLight:
+ case StyleAppearance::MozMacVibrancyDark:
+ case StyleAppearance::MozMacVibrantTitlebarLight:
+ case StyleAppearance::MozMacVibrantTitlebarDark:
+ *aShouldRepaint = false;
+ return NS_OK;
+ default:
+ break;
+ }
+
+ // 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 macOS system appearance changes are handled by
+ // nsLookAndFeel::SystemWantsDarkTheme.
+ return NS_OK;
+}
+
+bool nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ // if this is a dropdown button in a combobox the answer is always no
+ if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (parentFrame && parentFrame->IsComboboxControlFrame()) return false;
+ }
+
+ switch (aAppearance) {
+ // Combobox dropdowns don't support native theming in vertical mode.
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::MenulistText:
+ if (aFrame && aFrame->GetWritingMode().IsVertical()) {
+ return false;
+ }
+ [[fallthrough]];
+
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Dialog:
+ case StyleAppearance::Window:
+ case StyleAppearance::MozWindowButtonBox:
+ case StyleAppearance::MozWindowTitlebar:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Menupopup:
+ case StyleAppearance::Menuarrow:
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Menuseparator:
+ case StyleAppearance::MozMacFullscreenButton:
+ case StyleAppearance::Tooltip:
+
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::CheckboxContainer:
+ case StyleAppearance::Radio:
+ case StyleAppearance::RadioContainer:
+ case StyleAppearance::Groupbox:
+ case StyleAppearance::MozMacHelpButton:
+ case StyleAppearance::MozMacDisclosureButtonOpen:
+ case StyleAppearance::MozMacDisclosureButtonClosed:
+ case StyleAppearance::Button:
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowDown:
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::Spinner:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ case StyleAppearance::Toolbar:
+ case StyleAppearance::Statusbar:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Searchfield:
+ case StyleAppearance::Toolbox:
+ // case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Meter:
+ case StyleAppearance::Meterchunk:
+ case StyleAppearance::Separator:
+
+ case StyleAppearance::Tabpanels:
+ case StyleAppearance::Tab:
+
+ case StyleAppearance::Treetwisty:
+ case StyleAppearance::Treetwistyopen:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Treeheader:
+ case StyleAppearance::Treeheadercell:
+ case StyleAppearance::Treeheadersortarrow:
+ case StyleAppearance::Treeitem:
+ case StyleAppearance::Treeline:
+ case StyleAppearance::MozMacSourceList:
+ case StyleAppearance::MozMacSourceListSelection:
+ case StyleAppearance::MozMacActiveSourceListSelection:
+
+ case StyleAppearance::Range:
+
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbarNonDisappearing:
+ return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
+
+ case StyleAppearance::Scrollcorner:
+ return true;
+
+ case StyleAppearance::Resizer: {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (!parentFrame || !parentFrame->IsScrollFrame()) 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().isEmpty()));
+ }
+
+ case StyleAppearance::FocusOutline:
+ return true;
+
+ case StyleAppearance::MozMacVibrancyLight:
+ case StyleAppearance::MozMacVibrancyDark:
+ case StyleAppearance::MozMacVibrantTitlebarLight:
+ case StyleAppearance::MozMacVibrantTitlebarDark:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool nsNativeThemeCocoa::WidgetIsContainer(StyleAppearance aAppearance) {
+ // flesh this out at some point
+ switch (aAppearance) {
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Meter:
+ case StyleAppearance::Range:
+ case StyleAppearance::MozMacHelpButton:
+ case StyleAppearance::MozMacDisclosureButtonOpen:
+ case StyleAppearance::MozMacDisclosureButtonClosed:
+ return false;
+ default:
+ break;
+ }
+ return true;
+}
+
+bool nsNativeThemeCocoa::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Searchfield:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Button:
+ case StyleAppearance::MozMacHelpButton:
+ case StyleAppearance::MozMacDisclosureButtonOpen:
+ case StyleAppearance::MozMacDisclosureButtonClosed:
+ case StyleAppearance::Radio:
+ case StyleAppearance::Range:
+ case StyleAppearance::Checkbox:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker() { return false; }
+
+bool nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Dialog:
+ case StyleAppearance::Groupbox:
+ case StyleAppearance::Tabpanels:
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowDown:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Menupopup:
+ case StyleAppearance::Menuarrow:
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Menuseparator:
+ case StyleAppearance::Tooltip:
+ case StyleAppearance::Spinner:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ case StyleAppearance::Separator:
+ case StyleAppearance::Toolbox:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Treeline:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::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);
+}
+
+nsITheme::ThemeGeometryType nsNativeThemeCocoa::ThemeGeometryTypeForWidget(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::MozWindowTitlebar:
+ return eThemeGeometryTypeTitlebar;
+ case StyleAppearance::Toolbar:
+ return eThemeGeometryTypeToolbar;
+ case StyleAppearance::Toolbox:
+ return eThemeGeometryTypeToolbox;
+ case StyleAppearance::MozWindowButtonBox:
+ return eThemeGeometryTypeWindowButtons;
+ case StyleAppearance::MozMacFullscreenButton:
+ return eThemeGeometryTypeFullscreenButton;
+ case StyleAppearance::MozMacVibrancyLight:
+ return eThemeGeometryTypeVibrancyLight;
+ case StyleAppearance::MozMacVibrancyDark:
+ return eThemeGeometryTypeVibrancyDark;
+ case StyleAppearance::MozMacVibrantTitlebarLight:
+ return eThemeGeometryTypeVibrantTitlebarLight;
+ case StyleAppearance::MozMacVibrantTitlebarDark:
+ return eThemeGeometryTypeVibrantTitlebarDark;
+ case StyleAppearance::Tooltip:
+ return eThemeGeometryTypeTooltip;
+ case StyleAppearance::Menupopup:
+ return eThemeGeometryTypeMenu;
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem: {
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+ bool isDisabled = IsDisabled(aFrame, eventState);
+ bool isSelected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+ return isSelected ? eThemeGeometryTypeHighlightedMenuItem : eThemeGeometryTypeMenu;
+ }
+ case StyleAppearance::Dialog:
+ return IsWindowSheet(aFrame) ? eThemeGeometryTypeSheet : eThemeGeometryTypeUnknown;
+ case StyleAppearance::MozMacSourceList:
+ return eThemeGeometryTypeSourceList;
+ case StyleAppearance::MozMacSourceListSelection:
+ return IsInSourceList(aFrame) ? eThemeGeometryTypeSourceListSelection
+ : eThemeGeometryTypeUnknown;
+ case StyleAppearance::MozMacActiveSourceListSelection:
+ return IsInSourceList(aFrame) ? eThemeGeometryTypeActiveSourceListSelection
+ : eThemeGeometryTypeUnknown;
+ default:
+ return eThemeGeometryTypeUnknown;
+ }
+}
+
+nsITheme::Transparency nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Menupopup:
+ case StyleAppearance::Tooltip:
+ case StyleAppearance::Dialog:
+ return eTransparent;
+
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::Scrollcorner: {
+ // We don't use custom scrollbars when using overlay scrollbars.
+ if (nsLookAndFeel::UseOverlayScrollbars()) {
+ return eTransparent;
+ }
+ const nsStyleUI* ui = nsLayoutUtils::StyleForScrollbar(aFrame)->StyleUI();
+ if (!ui->mScrollbarColor.IsAuto() &&
+ ui->mScrollbarColor.AsColors().track.MaybeTransparent()) {
+ return eTransparent;
+ }
+ return eOpaque;
+ }
+
+ case StyleAppearance::Statusbar:
+ // Knowing that scrollbars and statusbars are opaque improves
+ // performance, because we create layers for them.
+ return eOpaque;
+
+ case StyleAppearance::Toolbar:
+ return eOpaque;
+
+ default:
+ return eUnknownTransparency;
+ }
+}
+
+already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() {
+ static nsCOMPtr<nsITheme> inst;
+
+ if (!inst) {
+ inst = new nsNativeThemeCocoa();
+ ClearOnShutdown(&inst);
+ }
+
+ return do_AddRef(inst);
+}
diff --git a/widget/cocoa/nsNativeThemeColors.h b/widget/cocoa/nsNativeThemeColors.h
new file mode 100644
index 0000000000..af63478fcd
--- /dev/null
+++ b/widget/cocoa/nsNativeThemeColors.h
@@ -0,0 +1,66 @@
+/* -*- 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 nsNativeThemeColors_h_
+#define nsNativeThemeColors_h_
+
+#include "nsCocoaFeatures.h"
+#import <Cocoa/Cocoa.h>
+
+enum ColorName {
+ toolbarTopBorderGrey,
+ toolbarFillGrey,
+ toolbarBottomBorderGrey,
+};
+
+static const int sLionThemeColors[][2] = {
+ /* { active window, inactive window } */
+ // toolbar:
+ {0xD0, 0xF0}, // top separator line
+ {0xB2, 0xE1}, // fill color
+ {0x59, 0x87}, // bottom separator line
+};
+
+static const int sYosemiteThemeColors[][2] = {
+ /* { active window, inactive window } */
+ // toolbar:
+ {0xBD, 0xDF}, // top separator line
+ {0xD3, 0xF6}, // fill color
+ {0xB3, 0xD1}, // bottom separator line
+};
+
+inline int NativeGreyColorAsInt(ColorName name, BOOL isMain) {
+ return sYosemiteThemeColors[name][isMain ? 0 : 1];
+}
+
+inline float NativeGreyColorAsFloat(ColorName name, BOOL isMain) {
+ return NativeGreyColorAsInt(name, isMain) / 255.0f;
+}
+
+inline 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);
+}
+
+#if !defined(MAC_OS_X_VERSION_10_14) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_14
+@interface NSColor (NSColorControlAccentColor)
+@property(class, strong, readonly) NSColor* controlAccentColor NS_AVAILABLE_MAC(10_14);
+@end
+#endif
+
+inline NSColor* ControlAccentColor() {
+ if (@available(macOS 10.14, *)) {
+ return [NSColor controlAccentColor];
+ }
+
+ // Pre-10.14, use hardcoded colors.
+ return [NSColor currentControlTint] == NSGraphiteControlTint
+ ? [NSColor colorWithSRGBRed:0.635 green:0.635 blue:0.655 alpha:1.0]
+ : [NSColor colorWithSRGBRed:0.247 green:0.584 blue:0.965 alpha:1.0];
+}
+
+#endif // nsNativeThemeColors_h_
diff --git a/widget/cocoa/nsPIWidgetCocoa.idl b/widget/cocoa/nsPIWidgetCocoa.idl
new file mode 100644
index 0000000000..54aa0d5113
--- /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..1f0216a727
--- /dev/null
+++ b/widget/cocoa/nsPrintDialogX.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 nsPrintDialog_h_
+#define nsPrintDialog_h_
+
+#include "nsIPrintDialogService.h"
+#include "nsCOMPtr.h"
+#include "nsCocoaUtils.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsIPrintSettings;
+class nsIStringBundle;
+
+class nsPrintDialogServiceX : public nsIPrintDialogService {
+ public:
+ nsPrintDialogServiceX();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init() override;
+ NS_IMETHOD Show(nsPIDOMWindowOuter* aParent, nsIPrintSettings* aSettings) 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;
+ NSPopUpButton* mHeaderLeftList;
+ NSPopUpButton* mHeaderCenterList;
+ NSPopUpButton* mHeaderRightList;
+ NSPopUpButton* mFooterLeftList;
+ NSPopUpButton* mFooterCenterList;
+ NSPopUpButton* mFooterRightList;
+}
+
+- (id)initWithSettings:(nsIPrintSettings*)aSettings;
+
+- (void)exportSettings;
+
+@end
+
+@interface PrintPanelAccessoryController : NSViewController <NSPrintPanelAccessorizing>
+
+- (id)initWithSettings:(nsIPrintSettings*)aSettings;
+
+- (void)exportSettings;
+
+@end
+
+#endif // nsPrintDialog_h_
diff --git a/widget/cocoa/nsPrintDialogX.mm b/widget/cocoa/nsPrintDialogX.mm
new file mode 100644
index 0000000000..fe290418ba
--- /dev/null
+++ b/widget/cocoa/nsPrintDialogX.mm
@@ -0,0 +1,546 @@
+/* -*- 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 "mozilla/gfx/PrintTargetCG.h"
+#include "mozilla/Preferences.h"
+
+#include "nsPrintDialogX.h"
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsService.h"
+#include "nsPrintSettingsX.h"
+#include "nsCOMPtr.h"
+#include "nsQueryObject.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIStringBundle.h"
+#include "nsCRT.h"
+
+#import <Cocoa/Cocoa.h>
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+using mozilla::gfx::PrintTarget;
+
+NS_IMPL_ISUPPORTS(nsPrintDialogServiceX, nsIPrintDialogService)
+
+nsPrintDialogServiceX::nsPrintDialogServiceX() {}
+
+nsPrintDialogServiceX::~nsPrintDialogServiceX() {}
+
+NS_IMETHODIMP
+nsPrintDialogServiceX::Init() { return NS_OK; }
+
+NS_IMETHODIMP
+nsPrintDialogServiceX::Show(nsPIDOMWindowOuter* aParent, nsIPrintSettings* aSettings) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ MOZ_ASSERT(aSettings, "aSettings must not be null");
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(aSettings));
+ if (!settingsX) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIPrintSettingsService> printSettingsSvc =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+
+ // Read the saved printer settings from prefs. (This relies on the printer name
+ // stored in settingsX to read the printer-specific prefs.)
+ printSettingsSvc->InitPrintSettingsFromPrefs(settingsX, true, nsIPrintSettings::kInitSaveAll);
+
+ NSPrintInfo* printInfo = settingsX->CreateOrCopyPrintInfo(/* aWithScaling = */ true);
+ if (NS_WARN_IF(!printInfo)) {
+ return NS_ERROR_FAILURE;
+ }
+ [printInfo autorelease];
+
+ // Set the print job title
+ nsAutoString docName;
+ nsresult rv = aSettings->GetTitle(docName);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString adjustedTitle;
+ PrintTarget::AdjustPrintJobNameForIPP(docName, adjustedTitle);
+ CFStringRef cfTitleString = CFStringCreateWithCharacters(
+ NULL, reinterpret_cast<const UniChar*>(adjustedTitle.BeginReading()),
+ adjustedTitle.Length());
+ if (cfTitleString) {
+ auto pmPrintSettings = static_cast<PMPrintSettings>([printInfo PMPrintSettings]);
+ ::PMPrintSettingsSetJobName(pmPrintSettings, cfTitleString);
+ [printInfo updateFromPMPrintSettings];
+ CFRelease(cfTitleString);
+ }
+ }
+
+ // 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();
+
+ // Retrieve a printInfo with the updated settings. (The NSPrintOperation operates on a
+ // copy, so the object we passed in will not have been modified.)
+ NSPrintInfo* result = [[NSPrintOperation currentOperation] printInfo];
+ if (!result) {
+ return NS_ERROR_FAILURE;
+ }
+
+ [NSPrintOperation setCurrentOperation:nil];
+ [tmpView release];
+
+ if (button != NSFileHandlingPanelOKButton) {
+ return NS_ERROR_ABORT;
+ }
+
+ // Export settings.
+ [viewController exportSettings];
+
+ // Update our settings object based on the user's choices in the dialog.
+ // We tell settingsX to adopt this printInfo so that it will be used to run print job,
+ // so that any printer-specific custom settings from print dialog extension panels
+ // will be carried through.
+ settingsX->SetFromPrintInfo(result, /* aAdoptPrintInfo = */ true);
+
+ // Save settings unless saving is pref'd off
+ if (Preferences::GetBool("print.save_print_settings", false)) {
+ printSettingsSvc->SavePrintSettingsToPrefs(settingsX, true,
+ nsIPrintSettings::kInitSaveNativeData);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceX::ShowPageSetup(nsPIDOMWindowOuter* aParent, nsIPrintSettings* aNSSettings) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ MOZ_ASSERT(aParent, "aParent must not be null");
+ MOZ_ASSERT(aNSSettings, "aSettings must not be null");
+ NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(aNSSettings));
+ if (!settingsX) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSPrintInfo* printInfo = settingsX->CreateOrCopyPrintInfo(/* aWithScaling = */ true);
+ if (NS_WARN_IF(!printInfo)) {
+ return NS_ERROR_FAILURE;
+ }
+ [printInfo autorelease];
+
+ NSPageLayout* pageLayout = [NSPageLayout pageLayout];
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ int button = [pageLayout runModalWithPrintInfo:printInfo];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ if (button == NSFileHandlingPanelOKButton) {
+ // The Page Setup dialog does not include non-standard settings that need to be preserved,
+ // separate from what the base printSettings object handles, so we do not need it to adopt
+ // the printInfo object here.
+ settingsX->SetFromPrintInfo(printInfo, /* aAdoptPrintInfo = */ false);
+ nsCOMPtr<nsIPrintSettingsService> printSettingsService =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ if (printSettingsService && Preferences::GetBool("print.save_print_settings", false)) {
+ uint32_t flags = nsIPrintSettings::kInitSaveNativeData |
+ nsIPrintSettings::kInitSavePaperSize |
+ nsIPrintSettings::kInitSaveOrientation | nsIPrintSettings::kInitSaveScaling;
+ printSettingsService->SavePrintSettingsToPrefs(aNSSettings, true, flags);
+ }
+ return NS_OK;
+ }
+ return NS_ERROR_ABORT;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Accessory view
+
+@interface PrintPanelAccessoryView (Private)
+
+- (NSString*)localizedString:(const char*)aKey;
+
+- (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 nsAString&)aCurrentString;
+
+- (void)addOptionsSection;
+
+- (void)addAppearanceSection;
+
+- (void)addHeaderFooterSection;
+
+- (NSString*)summaryValueForCheckbox:(NSButton*)aCheckbox;
+
+- (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, 185)];
+
+ mSettings = aSettings;
+ [self initBundle];
+ [self addOptionsSection];
+ [self addAppearanceSection];
+ [self addHeaderFooterSection];
+
+ return self;
+}
+
+- (void)exportSettings {
+ mSettings->SetPrintSelectionOnly([mPrintSelectionOnlyCheckbox state] == NSOnState);
+ mSettings->SetShrinkToFit([mShrinkToFitCheckbox state] == NSOnState);
+ mSettings->SetPrintBGColors([mPrintBGColorsCheckbox state] == NSOnState);
+ mSettings->SetPrintBGImages([mPrintBGImagesCheckbox state] == NSOnState);
+
+ [self exportHeaderFooterSettings];
+}
+
+- (void)dealloc {
+ NS_IF_RELEASE(mPrintBundle);
+ [super dealloc];
+}
+
+// Localization
+
+- (void)initBundle {
+ nsCOMPtr<nsIStringBundleService> bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties", &mPrintBundle);
+}
+
+- (NSString*)localizedString:(const char*)aKey {
+ if (!mPrintBundle) return @"";
+
+ nsAutoString intlString;
+ mPrintBundle->GetStringFromName(aKey, 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:NSTextAlignmentRight];
+}
+
+- (void)addCenteredLabel:(const char*)aLabel withFrame:(NSRect)aRect {
+ [self addLabel:aLabel withFrame:aRect alignment:NSTextAlignmentCenter];
+}
+
+- (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 nsAString&)aCurrentString {
+ NSPopUpButton* list = [[[NSPopUpButton alloc] initWithFrame:aRect pullsDown:NO] autorelease];
+ [list setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+ [[list cell] setControlSize:NSControlSizeSmall];
+ 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, 155, 151, 22)];
+
+ // "Print Selection Only"
+ mPrintSelectionOnlyCheckbox = [self checkboxWithLabel:"selectionOnly"
+ andFrame:NSMakeRect(156, 155, 0, 0)];
+
+ bool canPrintSelection = mSettings->GetIsPrintSelectionRBEnabled();
+ [mPrintSelectionOnlyCheckbox setEnabled:canPrintSelection];
+
+ if (mSettings->GetPrintSelectionOnly()) {
+ [mPrintSelectionOnlyCheckbox setState:NSOnState];
+ }
+
+ [self addSubview:mPrintSelectionOnlyCheckbox];
+
+ // "Shrink To Fit"
+ mShrinkToFitCheckbox = [self checkboxWithLabel:"shrinkToFit" andFrame:NSMakeRect(156, 133, 0, 0)];
+
+ bool shrinkToFit;
+ mSettings->GetShrinkToFit(&shrinkToFit);
+ [mShrinkToFitCheckbox setState:(shrinkToFit ? NSOnState : NSOffState)];
+
+ [self addSubview:mShrinkToFitCheckbox];
+}
+
+- (void)addAppearanceSection {
+ // Title
+ [self addLabel:"appearanceTitleMac" withFrame:NSMakeRect(0, 103, 151, 22)];
+
+ // "Print Background Colors"
+ mPrintBGColorsCheckbox = [self checkboxWithLabel:"printBGColors"
+ andFrame:NSMakeRect(156, 103, 0, 0)];
+
+ bool geckoBool = mSettings->GetPrintBGColors();
+ [mPrintBGColorsCheckbox setState:(geckoBool ? NSOnState : NSOffState)];
+
+ [self addSubview:mPrintBGColorsCheckbox];
+
+ // "Print Background Images"
+ mPrintBGImagesCheckbox = [self checkboxWithLabel:"printBGImages"
+ andFrame:NSMakeRect(156, 81, 0, 0)];
+
+ geckoBool = mSettings->GetPrintBGImages();
+ [mPrintBGImagesCheckbox setState:(geckoBool ? NSOnState : NSOffState)];
+
+ [self addSubview:mPrintBGImagesCheckbox];
+}
+
+- (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
+ nsString sel;
+
+ mSettings->GetHeaderStrLeft(sel);
+ mHeaderLeftList = [self headerFooterItemListWithFrame:NSMakeRect(156, 44, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mHeaderLeftList];
+
+ mSettings->GetHeaderStrCenter(sel);
+ mHeaderCenterList = [self headerFooterItemListWithFrame:NSMakeRect(256, 44, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mHeaderCenterList];
+
+ mSettings->GetHeaderStrRight(sel);
+ mHeaderRightList = [self headerFooterItemListWithFrame:NSMakeRect(356, 44, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mHeaderRightList];
+
+ mSettings->GetFooterStrLeft(sel);
+ mFooterLeftList = [self headerFooterItemListWithFrame:NSMakeRect(156, 0, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mFooterLeftList];
+
+ mSettings->GetFooterStrCenter(sel);
+ mFooterCenterList = [self headerFooterItemListWithFrame:NSMakeRect(256, 0, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mFooterCenterList];
+
+ mSettings->GetFooterStrRight(sel);
+ mFooterRightList = [self headerFooterItemListWithFrame:NSMakeRect(356, 0, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mFooterRightList];
+}
+
+// Export settings
+
+- (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));
+
+ headerFooterStr = [self headerFooterStringForList:mHeaderCenterList];
+ mSettings->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(headerFooterStr));
+
+ headerFooterStr = [self headerFooterStringForList:mHeaderRightList];
+ mSettings->SetHeaderStrRight(NS_ConvertUTF8toUTF16(headerFooterStr));
+
+ headerFooterStr = [self headerFooterStringForList:mFooterLeftList];
+ mSettings->SetFooterStrLeft(NS_ConvertUTF8toUTF16(headerFooterStr));
+
+ headerFooterStr = [self headerFooterStringForList:mFooterCenterList];
+ mSettings->SetFooterStrCenter(NS_ConvertUTF8toUTF16(headerFooterStr));
+
+ headerFooterStr = [self headerFooterStringForList:mFooterRightList];
+ mSettings->SetFooterStrRight(NS_ConvertUTF8toUTF16(headerFooterStr));
+}
+
+// Summary
+
+- (NSString*)summaryValueForCheckbox:(NSButton*)aCheckbox {
+ if (![aCheckbox isEnabled]) return [self localizedString:"summaryNAValue"];
+
+ return [aCheckbox state] == NSOnState ? [self localizedString:"summaryOnValue"]
+ : [self localizedString:"summaryOffValue"];
+}
+
+- (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:"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/nsPrintSettingsServiceX.h b/widget/cocoa/nsPrintSettingsServiceX.h
new file mode 100644
index 0000000000..e6e6bff2b9
--- /dev/null
+++ b/widget/cocoa/nsPrintSettingsServiceX.h
@@ -0,0 +1,33 @@
+/* -*- 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 nsPrintSettingsServiceX_h
+#define nsPrintSettingsServiceX_h
+
+#include "nsPrintSettingsService.h"
+
+namespace mozilla {
+namespace embedding {
+class PrintData;
+} // namespace embedding
+} // namespace mozilla
+
+class nsPrintSettingsServiceX final : public nsPrintSettingsService {
+ public:
+ nsPrintSettingsServiceX() {}
+
+ NS_IMETHODIMP SerializeToPrintData(
+ nsIPrintSettings* aSettings,
+ mozilla::embedding::PrintData* data) override;
+
+ NS_IMETHODIMP DeserializeToPrintSettings(
+ const mozilla::embedding::PrintData& data,
+ nsIPrintSettings* settings) override;
+
+ protected:
+ nsresult _CreatePrintSettings(nsIPrintSettings** _retval) override;
+};
+
+#endif // nsPrintSettingsServiceX_h
diff --git a/widget/cocoa/nsPrintSettingsServiceX.mm b/widget/cocoa/nsPrintSettingsServiceX.mm
new file mode 100644
index 0000000000..d8fe81624c
--- /dev/null
+++ b/widget/cocoa/nsPrintSettingsServiceX.mm
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPrintSettingsServiceX.h"
+
+#include "nsCOMPtr.h"
+#include "nsQueryObject.h"
+#include "nsPrintSettingsX.h"
+#include "nsCocoaUtils.h"
+
+using namespace mozilla::embedding;
+
+NS_IMETHODIMP
+nsPrintSettingsServiceX::SerializeToPrintData(nsIPrintSettings* aSettings, PrintData* data) {
+ nsresult rv = nsPrintSettingsService::SerializeToPrintData(aSettings, data);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(aSettings));
+ if (NS_WARN_IF(!settingsX)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ settingsX->GetDisposition(data->disposition());
+ settingsX->GetDestination(&data->destination());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsServiceX::DeserializeToPrintSettings(const PrintData& data,
+ nsIPrintSettings* settings) {
+ nsresult rv = nsPrintSettingsService::DeserializeToPrintSettings(data, settings);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(settings));
+ if (NS_WARN_IF(!settingsX)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ settingsX->SetDisposition(data.disposition());
+ settingsX->SetDestination(data.destination());
+
+ return NS_OK;
+}
+
+nsresult nsPrintSettingsServiceX::_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;
+ }
+
+ auto globalPrintSettings =
+ nsIPrintSettings::kInitSaveShrinkToFit | nsIPrintSettings::kInitSaveHeaderLeft |
+ nsIPrintSettings::kInitSaveHeaderCenter | nsIPrintSettings::kInitSaveHeaderRight |
+ nsIPrintSettings::kInitSaveFooterLeft | nsIPrintSettings::kInitSaveFooterCenter |
+ nsIPrintSettings::kInitSaveFooterRight | nsIPrintSettings::kInitSaveEdges |
+ nsIPrintSettings::kInitSaveReversed | nsIPrintSettings::kInitSaveInColor;
+
+ // XXX Why is Mac special? Why are we copying global print settings here?
+ // nsPrintSettingsService::InitPrintSettingsFromPrefs already gets the few
+ // global defaults that we want, doesn't it?
+ InitPrintSettingsFromPrefs(*_retval, false, globalPrintSettings);
+ return rv;
+}
diff --git a/widget/cocoa/nsPrintSettingsX.h b/widget/cocoa/nsPrintSettingsX.h
new file mode 100644
index 0000000000..9f24694bb3
--- /dev/null
+++ b/widget/cocoa/nsPrintSettingsX.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPrintSettingsX_h_
+#define nsPrintSettingsX_h_
+
+#include "nsPrintSettingsImpl.h"
+#import <Cocoa/Cocoa.h>
+
+// clang-format off
+#define NS_PRINTSETTINGSX_IID \
+ { \
+ 0x0DF2FDBD, 0x906D, 0x4726, { \
+ 0x9E, 0x4D, 0xCF, 0xE0, 0x87, 0x8D, 0x70, 0x7C \
+ } \
+ }
+// clang-format on
+
+class nsPrintSettingsX : public nsPrintSettings {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PRINTSETTINGSX_IID)
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsPrintSettingsX();
+ explicit nsPrintSettingsX(const PrintSettingsInitializer& aSettings);
+
+ nsresult Init() { return NS_OK; }
+
+ void SetDestination(uint16_t aDestination) { mDestination = aDestination; }
+ void GetDestination(uint16_t* aDestination) { *aDestination = mDestination; }
+
+ void SetDisposition(const nsString& aDisposition) { mDisposition = aDisposition; }
+ void GetDisposition(nsString& aDisposition) { aDisposition = mDisposition; }
+
+ // Get a Cocoa NSPrintInfo that is configured with our current settings.
+ // This follows Create semantics, so the caller is responsible to release
+ // the returned object when no longer required.
+ //
+ // Pass true for aWithScaling to have the print scaling factor included in
+ // the returned printInfo. Normally we pass false, as scaling is handled
+ // by Gecko and we don't want the Cocoa print system to impose scaling again
+ // on the output, but if we're retrieving the info in order to populate the
+ // system print UI, then we do want to know about it.
+ NSPrintInfo* CreateOrCopyPrintInfo(bool aWithScaling = false);
+
+ // Update our internal settings to reflect the properties of the given
+ // NSPrintInfo.
+ //
+ // If aAdoptPrintInfo is set, the given NSPrintInfo will be retained and
+ // returned by subsequent CreateOrCopyPrintInfo calls, which is required
+ // for custom settings from the OS print dialog to be passed through to
+ // print jobs. However, this means that subsequent changes to print settings
+ // via the generic nsPrintSettings methods will NOT be reflected in the
+ // resulting NSPrintInfo.
+ void SetFromPrintInfo(NSPrintInfo* aPrintInfo, bool aAdoptPrintInfo);
+
+ protected:
+ virtual ~nsPrintSettingsX() {
+ if (mSystemPrintInfo) {
+ [mSystemPrintInfo release];
+ }
+ };
+
+ nsPrintSettingsX& operator=(const nsPrintSettingsX& rhs);
+
+ nsresult _Clone(nsIPrintSettings** _retval) override;
+ nsresult _Assign(nsIPrintSettings* aPS) override;
+
+ int GetCocoaUnit(int16_t aGeckoUnit);
+
+ double PaperSizeFromCocoaPoints(double aPointsValue) {
+ return aPointsValue * (mPaperSizeUnit == kPaperSizeInches ? 1.0 / 72.0 : 25.4 / 72.0);
+ }
+
+ double CocoaPointsFromPaperSize(double aSizeUnitValue) {
+ return aSizeUnitValue * (mPaperSizeUnit == kPaperSizeInches ? 72.0 : 72.0 / 25.4);
+ }
+
+ // Needed to correctly track the various job dispositions (spool, preview,
+ // save to file) that the user can choose via the system print dialog.
+ // Unfortunately it seems to be necessary to set both the Cocoa "job
+ // disposition" and the PrintManager "destination type" in order for all the
+ // various workflows such as "Save to Web Receipts" to work.
+ nsString mDisposition;
+ uint16_t mDestination;
+
+ // If the user has used the system print UI, we retain a reference to its
+ // printInfo because it may contain settings that we don't know how to handle
+ // and that will be lost if we round-trip through nsPrintSettings fields.
+ // We'll use this printInfo if asked to run a print job.
+ //
+ // This "wrapped" printInfo is NOT serialized or copied when printSettings
+ // objects are passed around; it is used only by the settings object to which
+ // it was originally passed.
+ NSPrintInfo* mSystemPrintInfo = nullptr;
+};
+
+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..6e45b97433
--- /dev/null
+++ b/widget/cocoa/nsPrintSettingsX.mm
@@ -0,0 +1,354 @@
+/* -*- 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 "nsXULAppAPI.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_print.h"
+#include "nsPrinterCUPS.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsX, nsPrintSettings, nsPrintSettingsX)
+
+nsPrintSettingsX::nsPrintSettingsX() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mDestination = kPMDestinationInvalid;
+
+ /*
+ * Don't save print settings after the user cancels out of the
+ * print dialog. For saving print settings after a cancellation
+ * to work properly, in addition to changing |mSaveOnCancel|,
+ * the print dialog implementation must be updated to save changed
+ * settings and serialize them back to the child process.
+ */
+ mSaveOnCancel = false;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+already_AddRefed<nsIPrintSettings> CreatePlatformPrintSettings(
+ const PrintSettingsInitializer& aSettings) {
+ RefPtr<nsPrintSettings> settings = new nsPrintSettingsX();
+ settings->InitWithInitializer(aSettings);
+ settings->SetDefaultFileName();
+ return settings.forget();
+}
+
+nsPrintSettingsX& nsPrintSettingsX::operator=(const nsPrintSettingsX& rhs) {
+ if (this == &rhs) {
+ return *this;
+ }
+
+ nsPrintSettings::operator=(rhs);
+
+ mDestination = rhs.mDestination;
+ mDisposition = rhs.mDisposition;
+
+ // We don't copy mSystemPrintInfo here, so any copied printSettings will start out
+ // without a wrapped printInfo, just using our internal settings. The system
+ // printInfo is used *only* by the nsPrintSettingsX to which it was originally
+ // passed (when the user ran a system print UI dialog).
+
+ return *this;
+}
+
+nsresult nsPrintSettingsX::_Clone(nsIPrintSettings** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ auto newSettings = MakeRefPtr<nsPrintSettingsX>();
+ *newSettings = *this;
+ newSettings.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::_Assign(nsIPrintSettings* aPS) {
+ nsPrintSettingsX* printSettingsX = static_cast<nsPrintSettingsX*>(aPS);
+ if (!printSettingsX) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ *this = *printSettingsX;
+ return NS_OK;
+}
+
+struct KnownMonochromeSetting {
+ const NSString* mName;
+ const NSString* mValue;
+};
+
+#define DECLARE_KNOWN_MONOCHROME_SETTING(key_, value_) \
+ { @key_, @value_ } \
+ ,
+static const KnownMonochromeSetting kKnownMonochromeSettings[] = {
+ CUPS_EACH_MONOCHROME_PRINTER_SETTING(DECLARE_KNOWN_MONOCHROME_SETTING)};
+#undef DECLARE_KNOWN_MONOCHROME_SETTING
+
+NSPrintInfo* nsPrintSettingsX::CreateOrCopyPrintInfo(bool aWithScaling) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // If we have a printInfo that came from the system print UI, use it so that
+ // any printer-specific settings we don't know about will still be used.
+ if (mSystemPrintInfo) {
+ NSPrintInfo* sysPrintInfo = [mSystemPrintInfo copy];
+ // Any required scaling will be done by Gecko, so we don't want it here.
+ [sysPrintInfo setScalingFactor:1.0f];
+ return sysPrintInfo;
+ }
+
+ // Note that the app shared `sharedPrintInfo` object is special! The system
+ // print dialog and print settings dialog update it with the values chosen
+ // by the user. Using that object here to initialize new nsPrintSettingsX
+ // objects could mask bugs in our code where we fail to save and/or restore
+ // print settings ourselves (e.g., bug 1636725). On other platforms we only
+ // initialize new nsPrintSettings objects from the settings that we save to
+ // prefs. Perhaps we should stop using sharedPrintInfo here for consistency?
+ NSPrintInfo* printInfo = [[NSPrintInfo sharedPrintInfo] copy];
+
+ NSSize paperSize;
+ if (GetSheetOrientation() == kPortraitOrientation) {
+ [printInfo setOrientation:NSPaperOrientationPortrait];
+ paperSize.width = CocoaPointsFromPaperSize(mPaperWidth);
+ paperSize.height = CocoaPointsFromPaperSize(mPaperHeight);
+ [printInfo setPaperSize:paperSize];
+ } else {
+ [printInfo setOrientation:NSPaperOrientationLandscape];
+ paperSize.width = CocoaPointsFromPaperSize(mPaperHeight);
+ paperSize.height = CocoaPointsFromPaperSize(mPaperWidth);
+ [printInfo setPaperSize:paperSize];
+ }
+
+ [printInfo setTopMargin:mUnwriteableMargin.top];
+ [printInfo setRightMargin:mUnwriteableMargin.right];
+ [printInfo setBottomMargin:mUnwriteableMargin.bottom];
+ [printInfo setLeftMargin:mUnwriteableMargin.left];
+
+ // If the name is our pseudo-printer "Mozilla Save to PDF", this will silently fail
+ // as no such printer is known. That's OK, because mPrinter will remain correct
+ // and is our canonical source of truth here.
+ [printInfo setPrinter:[NSPrinter printerWithName:nsCocoaUtils::ToNSString(mPrinter)]];
+
+ // Scaling is handled by gecko, we do NOT want the cocoa printing system to add
+ // a second scaling on top of that. So we only set the true scaling factor here
+ // if the caller explicitly asked for it.
+ [printInfo setScalingFactor:CGFloat(aWithScaling ? mScaling : 1.0f)];
+
+ const bool allPages = mPageRanges.IsEmpty();
+
+ NSMutableDictionary* dict = [printInfo dictionary];
+ [dict setObject:[NSNumber numberWithInt:mNumCopies] forKey:NSPrintCopies];
+ [dict setObject:[NSNumber numberWithBool:allPages] forKey:NSPrintAllPages];
+
+ int32_t start = 1;
+ int32_t end = 1;
+ for (size_t i = 0; i < mPageRanges.Length(); i += 2) {
+ start = std::min(start, mPageRanges[i]);
+ end = std::max(end, mPageRanges[i + 1]);
+ }
+
+ [dict setObject:[NSNumber numberWithInt:start] forKey:NSPrintFirstPage];
+ [dict setObject:[NSNumber numberWithInt:end] forKey:NSPrintLastPage];
+
+ NSURL* jobSavingURL = nullptr;
+ if (!mToFileName.IsEmpty()) {
+ jobSavingURL = [NSURL fileURLWithPath:nsCocoaUtils::ToNSString(mToFileName)];
+ if (jobSavingURL) {
+ // Note: the PMPrintSettingsSetJobName call in nsPrintDialogServiceX::Show
+ // seems to mean that we get a sensible file name pre-populated in the
+ // dialog there, although our mToFileName is expected to be a full path,
+ // and it's less clear where the rest of the path (the directory to save
+ // to) in nsPrintDialogServiceX::Show comes from (perhaps from the use
+ // of `sharedPrintInfo` to initialize new nsPrintSettingsX objects).
+ [dict setObject:jobSavingURL forKey:NSPrintJobSavingURL];
+ }
+ }
+
+ if (mDisposition.IsEmpty()) {
+ if (mPrintToFile) {
+ [printInfo setJobDisposition:NSPrintSaveJob];
+ } else {
+ [printInfo setJobDisposition:NSPrintSpoolJob];
+ }
+ } else {
+ [printInfo setJobDisposition:nsCocoaUtils::ToNSString(mDisposition)];
+ }
+
+ PMDuplexMode duplexSetting;
+ switch (mDuplex) {
+ default:
+ // This can't happen :) but if it does, we treat it as "none".
+ MOZ_FALLTHROUGH_ASSERT("Unknown duplex value");
+ case kSimplex:
+ duplexSetting = kPMDuplexNone;
+ break;
+ case kDuplexVertical:
+ duplexSetting = kPMDuplexTumble;
+ break;
+ case kDuplexHorizontal:
+ duplexSetting = kPMDuplexNoTumble;
+ break;
+ }
+
+ NSMutableDictionary* printSettings = [printInfo printSettings];
+ [printSettings setObject:[NSNumber numberWithUnsignedShort:duplexSetting]
+ forKey:@"com_apple_print_PrintSettings_PMDuplexing"];
+
+ if (mDestination != kPMDestinationInvalid) {
+ // Required to support PDF-workflow destinations such as Save to Web Receipts.
+ [printSettings setObject:[NSNumber numberWithUnsignedShort:mDestination]
+ forKey:@"com_apple_print_PrintSettings_PMDestinationType"];
+ if (jobSavingURL) {
+ [printSettings setObject:[jobSavingURL absoluteString]
+ forKey:@"com_apple_print_PrintSettings_PMOutputFilename"];
+ }
+ }
+
+ if (StaticPrefs::print_cups_monochrome_enabled() && !GetPrintInColor()) {
+ for (const auto& setting : kKnownMonochromeSettings) {
+ [printSettings setObject:setting.mValue forKey:setting.mName];
+ }
+ auto applySetting = [&](const nsACString& aKey, const nsACString& aValue) {
+ [printSettings setObject:nsCocoaUtils::ToNSString(aValue)
+ forKey:nsCocoaUtils::ToNSString(aKey)];
+ };
+ nsPrinterCUPS::ForEachExtraMonochromeSetting(applySetting);
+ }
+
+ return printInfo;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr);
+}
+
+void nsPrintSettingsX::SetFromPrintInfo(NSPrintInfo* aPrintInfo, bool aAdoptPrintInfo) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Set page-size/margins.
+ NSSize paperSize = [aPrintInfo paperSize];
+ const bool areSheetsOfPaperPortraitMode =
+ ([aPrintInfo orientation] == NSPaperOrientationPortrait);
+
+ // If our MacOS print settings say that we're producing portrait-mode sheets
+ // of paper, then our page format must also be portrait-mode; unless we've
+ // got a pages-per-sheet value with orthogonal pages/sheets, in which case
+ // it's reversed.
+ const bool arePagesPortraitMode = (areSheetsOfPaperPortraitMode != HasOrthogonalSheetsAndPages());
+
+ if (arePagesPortraitMode) {
+ mOrientation = nsIPrintSettings::kPortraitOrientation;
+ SetPaperWidth(PaperSizeFromCocoaPoints(paperSize.width));
+ SetPaperHeight(PaperSizeFromCocoaPoints(paperSize.height));
+ } else {
+ mOrientation = nsIPrintSettings::kLandscapeOrientation;
+ SetPaperWidth(PaperSizeFromCocoaPoints(paperSize.height));
+ SetPaperHeight(PaperSizeFromCocoaPoints(paperSize.width));
+ }
+
+ mUnwriteableMargin.top = [aPrintInfo topMargin];
+ mUnwriteableMargin.right = [aPrintInfo rightMargin];
+ mUnwriteableMargin.bottom = [aPrintInfo bottomMargin];
+ mUnwriteableMargin.left = [aPrintInfo leftMargin];
+
+ if (aAdoptPrintInfo) {
+ // Keep a reference to the printInfo; it may have settings that we don't know how to handle
+ // otherwise.
+ if (mSystemPrintInfo != aPrintInfo) {
+ if (mSystemPrintInfo) {
+ [mSystemPrintInfo release];
+ }
+ mSystemPrintInfo = aPrintInfo;
+ [mSystemPrintInfo retain];
+ }
+ } else {
+ // Clear any stored printInfo.
+ if (mSystemPrintInfo) {
+ [mSystemPrintInfo release];
+ mSystemPrintInfo = nullptr;
+ }
+ }
+
+ nsCocoaUtils::GetStringForNSString([[aPrintInfo printer] name], mPrinter);
+
+ // Only get the scaling value if shrink-to-fit is not selected:
+ bool isShrinkToFitChecked;
+ GetShrinkToFit(&isShrinkToFitChecked);
+ if (!isShrinkToFitChecked) {
+ // Limit scaling precision to whole percentage values.
+ mScaling = round(double([aPrintInfo scalingFactor]) * 100.0) / 100.0;
+ }
+
+ mPrintToFile = [aPrintInfo jobDisposition] == NSPrintSaveJob;
+
+ NSDictionary* dict = [aPrintInfo dictionary];
+ const char* filePath = [[dict objectForKey:NSPrintJobSavingURL] fileSystemRepresentation];
+ if (filePath && *filePath) {
+ CopyUTF8toUTF16(Span(filePath, strlen(filePath)), mToFileName);
+ }
+
+ nsCocoaUtils::GetStringForNSString([aPrintInfo jobDisposition], mDisposition);
+
+ mNumCopies = [[dict objectForKey:NSPrintCopies] intValue];
+ mPageRanges.Clear();
+ if (![[dict objectForKey:NSPrintAllPages] boolValue]) {
+ mPageRanges.AppendElement([[dict objectForKey:NSPrintFirstPage] intValue]);
+ mPageRanges.AppendElement([[dict objectForKey:NSPrintLastPage] intValue]);
+ }
+
+ NSDictionary* printSettings = [aPrintInfo printSettings];
+ NSNumber* value = [printSettings objectForKey:@"com_apple_print_PrintSettings_PMDuplexing"];
+ if (value) {
+ PMDuplexMode duplexSetting = [value unsignedShortValue];
+ switch (duplexSetting) {
+ default:
+ // An unknown value is treated as None.
+ MOZ_FALLTHROUGH_ASSERT("Unknown duplex value");
+ case kPMDuplexNone:
+ mDuplex = kSimplex;
+ break;
+ case kPMDuplexNoTumble:
+ mDuplex = kDuplexHorizontal;
+ break;
+ case kPMDuplexTumble:
+ mDuplex = kDuplexVertical;
+ break;
+ }
+ } else {
+ // By default a printSettings dictionary doesn't initially contain the
+ // duplex key at all, so this is not an error; its absence just means no
+ // duplexing has been requested, so we return kSimplex.
+ mDuplex = kSimplex;
+ }
+
+ value = [printSettings objectForKey:@"com_apple_print_PrintSettings_PMDestinationType"];
+ if (value) {
+ mDestination = [value unsignedShortValue];
+ }
+
+ const bool color = [&] {
+ if (StaticPrefs::print_cups_monochrome_enabled()) {
+ for (const auto& setting : kKnownMonochromeSettings) {
+ NSString* value = [printSettings objectForKey:setting.mName];
+ if (!value) {
+ continue;
+ }
+ if ([setting.mValue isEqualToString:value]) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }();
+
+ SetPrintInColor(color);
+
+ SetIsInitializedFromPrinter(true);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
diff --git a/widget/cocoa/nsSandboxViolationSink.h b/widget/cocoa/nsSandboxViolationSink.h
new file mode 100644
index 0000000000..d4b6e7ce07
--- /dev/null
+++ b/widget/cocoa/nsSandboxViolationSink.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSandboxViolationSink_h_
+#define nsSandboxViolationSink_h_
+
+#include <stdint.h>
+
+// Class for tracking sandbox violations. Currently it just logs them to
+// stdout and the system console. In the future it may do more.
+
+// What makes this possible is the fact that Apple' sandboxd calls
+// notify_post("com.apple.sandbox.violation.*") whenever it's notified by the
+// Sandbox kernel extension of a sandbox violation. We register to receive
+// these notifications. But the notifications are empty, and are sent for
+// every violation in every process. So we need to do more to get only "our"
+// violations, and to find out what kind of violation they were. See the
+// implementation of nsSandboxViolationSink::ViolationHandler().
+
+#define SANDBOX_VIOLATION_QUEUE_NAME "org.mozilla.sandbox.violation.queue"
+#define SANDBOX_VIOLATION_NOTIFICATION_NAME "com.apple.sandbox.violation.*"
+
+class nsSandboxViolationSink {
+ public:
+ static void Start();
+ static void Stop();
+
+ private:
+ static void ViolationHandler();
+ static int mNotifyToken;
+ static uint64_t mLastMsgReceived;
+};
+
+#endif // nsSandboxViolationSink_h_
diff --git a/widget/cocoa/nsSandboxViolationSink.mm b/widget/cocoa/nsSandboxViolationSink.mm
new file mode 100644
index 0000000000..e21e89a994
--- /dev/null
+++ b/widget/cocoa/nsSandboxViolationSink.mm
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSandboxViolationSink.h"
+
+#include <unistd.h>
+#include <time.h>
+#include <asl.h>
+#include <dispatch/dispatch.h>
+#include <notify.h>
+#include "nsCocoaDebugUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+
+int nsSandboxViolationSink::mNotifyToken = 0;
+uint64_t nsSandboxViolationSink::mLastMsgReceived = 0;
+
+void nsSandboxViolationSink::Start() {
+ if (mNotifyToken) {
+ return;
+ }
+ notify_register_dispatch(
+ SANDBOX_VIOLATION_NOTIFICATION_NAME, &mNotifyToken,
+ dispatch_queue_create(SANDBOX_VIOLATION_QUEUE_NAME, DISPATCH_QUEUE_SERIAL), ^(int token) {
+ ViolationHandler();
+ });
+}
+
+void nsSandboxViolationSink::Stop() {
+ if (!mNotifyToken) {
+ return;
+ }
+ notify_cancel(mNotifyToken);
+ mNotifyToken = 0;
+}
+
+// We need to query syslogd to find out what violations occurred, and whether
+// they were "ours". We can use the Apple System Log facility to do this.
+// Besides calling notify_post("com.apple.sandbox.violation.*"), Apple's
+// sandboxd also reports all sandbox violations (sent to it by the Sandbox
+// kernel extension) to syslogd, which stores them and makes them viewable
+// in the system console. This is the database we query.
+
+// ViolationHandler() is always called on its own secondary thread. This
+// makes it unlikely it will interfere with other browser activity.
+
+void nsSandboxViolationSink::ViolationHandler() {
+ aslmsg query = asl_new(ASL_TYPE_QUERY);
+
+ asl_set_query(query, ASL_KEY_FACILITY, "com.apple.sandbox", ASL_QUERY_OP_EQUAL);
+
+ // Only get reports that were generated very recently.
+ char query_time[30] = {0};
+ SprintfLiteral(query_time, "%li", time(NULL) - 2);
+ asl_set_query(query, ASL_KEY_TIME, query_time, ASL_QUERY_OP_NUMERIC | ASL_QUERY_OP_GREATER_EQUAL);
+
+ // This code is easier to test if we don't just track "our" violations,
+ // which are (normally) few and far between. For example (for the time
+ // being at least) four appleeventsd sandbox violations happen every time
+ // we start the browser in e10s mode. But it makes sense to default to
+ // only tracking "our" violations.
+ if (mozilla::Preferences::GetBool("security.sandbox.mac.track.violations.oursonly", true)) {
+ // This makes each of our processes log its own violations. It might
+ // be better to make the chrome process log all the other processes'
+ // violations.
+ char query_pid[20] = {0};
+ SprintfLiteral(query_pid, "%u", getpid());
+ asl_set_query(query, ASL_KEY_REF_PID, query_pid, ASL_QUERY_OP_EQUAL);
+ }
+
+ aslresponse response = asl_search(nullptr, query);
+
+ // Each time ViolationHandler() is called we grab as many messages as are
+ // available. Otherwise we might not get them all.
+ if (response) {
+ while (true) {
+ aslmsg hit = nullptr;
+ aslmsg found = nullptr;
+ const char* id_str;
+
+ while ((hit = aslresponse_next(response))) {
+ // Record the message id to avoid logging the same violation more
+ // than once.
+ id_str = asl_get(hit, ASL_KEY_MSG_ID);
+ uint64_t id_val = atoll(id_str);
+ if (id_val <= mLastMsgReceived) {
+ continue;
+ }
+ mLastMsgReceived = id_val;
+ found = hit;
+ break;
+ }
+ if (!found) {
+ break;
+ }
+
+ const char* pid_str = asl_get(found, ASL_KEY_REF_PID);
+ const char* message_str = asl_get(found, ASL_KEY_MSG);
+ nsCocoaDebugUtils::DebugLog(
+ "nsSandboxViolationSink::ViolationHandler(): id %s, pid %s, message %s", id_str, pid_str,
+ message_str);
+ }
+ aslresponse_free(response);
+ }
+}
diff --git a/widget/cocoa/nsSound.h b/widget/cocoa/nsSound.h
new file mode 100644
index 0000000000..cf3b02bd77
--- /dev/null
+++ b/widget/cocoa/nsSound.h
@@ -0,0 +1,25 @@
+/* -*- 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 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..cd9e07f019
--- /dev/null
+++ b/widget/cocoa/nsSound.mm
@@ -0,0 +1,69 @@
+/* -*- 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 "nsSound.h"
+#include "nsContentUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsNetUtil.h"
+#include "nsCOMPtr.h"
+#include "nsIURL.h"
+#include "nsString.h"
+
+#import <Cocoa/Cocoa.h>
+
+NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver)
+
+nsSound::nsSound() {}
+
+nsSound::~nsSound() {}
+
+NS_IMETHODIMP
+nsSound::Beep() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSBeep();
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSound::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* context, nsresult aStatus,
+ uint32_t dataLen, const uint8_t* data) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSData* value = [NSData dataWithBytes:data length:dataLen];
+
+ NSSound* sound = [[NSSound alloc] initWithData:value];
+
+ [sound play];
+
+ [sound autorelease];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSound::Play(nsIURL* aURL) {
+ nsCOMPtr<nsIURI> uri(aURL);
+ nsCOMPtr<nsIStreamLoader> loader;
+ return NS_NewStreamLoader(getter_AddRefs(loader), uri,
+ this, // aObserver
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+}
+
+NS_IMETHODIMP
+nsSound::Init() { return NS_OK; }
+
+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..76ec97622d
--- /dev/null
+++ b/widget/cocoa/nsStandaloneNativeMenu.h
@@ -0,0 +1,44 @@
+/* -*- 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..79dcb1add0
--- /dev/null
+++ b/widget/cocoa/nsStandaloneNativeMenu.mm
@@ -0,0 +1,191 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsStandaloneNativeMenu.h"
+#include "nsMenuUtilsX.h"
+#include "nsIMutationObserver.h"
+#include "nsGkAtoms.h"
+#include "nsObjCExceptions.h"
+#include "mozilla/dom/Element.h"
+
+using mozilla::dom::Element;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsStandaloneNativeMenu, nsMenuGroupOwnerX, nsIMutationObserver,
+ nsIStandaloneNativeMenu)
+
+nsStandaloneNativeMenu::nsStandaloneNativeMenu() : mMenu(nullptr), mContainerStatusBarItem(nil) {}
+
+nsStandaloneNativeMenu::~nsStandaloneNativeMenu() {
+ if (mMenu) delete mMenu;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::Init(Element* aElement) {
+ NS_ASSERTION(mMenu == nullptr, "nsNativeMenu::Init - mMenu not null!");
+
+ NS_ENSURE_ARG(aElement);
+
+ if (!aElement->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menupopup)) return NS_ERROR_FAILURE;
+
+ nsresult rv = nsMenuGroupOwnerX::Create(aElement);
+ if (NS_FAILED(rv)) return rv;
+
+ mMenu = new nsMenuX();
+ rv = mMenu->Create(this, this, aElement);
+ if (NS_FAILED(rv)) {
+ delete mMenu;
+ mMenu = nullptr;
+ return rv;
+ }
+
+ mMenu->SetupIcon();
+
+ return NS_OK;
+}
+
+static void UpdateMenu(nsMenuX* aMenu) {
+ aMenu->MenuOpened();
+ aMenu->MenuClosed();
+
+ uint32_t itemCount = aMenu->GetItemCount();
+ for (uint32_t i = 0; i < itemCount; i++) {
+ nsMenuObjectX* menuObject = aMenu->GetItemAt(i);
+ if (menuObject->MenuObjectType() == eSubmenuObjectType) {
+ UpdateMenu(static_cast<nsMenuX*>(menuObject));
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::MenuWillOpen(bool* aResult) {
+ NS_ASSERTION(mMenu != nullptr, "nsStandaloneNativeMenu::OnOpen - mMenu is null!");
+
+ // Force an update on the mMenu by faking an open/close on all of
+ // its submenus.
+ UpdateMenu(mMenu);
+
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::GetNativeMenu(void** aVoidPointer) {
+ if (mMenu) {
+ *aVoidPointer = mMenu->NativeData();
+ [[(NSObject*)(*aVoidPointer) retain] autorelease];
+ return NS_OK;
+ } else {
+ *aVoidPointer = nullptr;
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+}
+
+static NSMenuItem* NativeMenuItemWithLocation(NSMenu* currentSubmenu, NSString* locationString) {
+ NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
+ NSUInteger indexCount = [indexes count];
+ if (indexCount == 0) return nil;
+
+ for (NSUInteger i = 0; i < indexCount; i++) {
+ NSInteger targetIndex = [[indexes objectAtIndex:i] integerValue];
+ NSInteger itemCount = [currentSubmenu numberOfItems];
+ if (targetIndex < itemCount) {
+ NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex];
+
+ // If this is the last index, just return the menu item.
+ if (i == (indexCount - 1)) return menuItem;
+
+ // If this is not the last index, find the submenu and keep going.
+ if ([menuItem hasSubmenu])
+ currentSubmenu = [menuItem submenu];
+ else
+ return nil;
+ }
+ }
+
+ return nil;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::ActivateNativeMenuItemAt(const nsAString& indexString) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mMenu) return NS_ERROR_NOT_INITIALIZED;
+
+ NSString* locationString =
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSMenu* menu = static_cast<NSMenu*>(mMenu->NativeData());
+ NSMenuItem* item = NativeMenuItemWithLocation(menu, locationString);
+
+ // We can't perform an action on an item with a submenu, that will raise
+ // an obj-c exception.
+ if (item && ![item hasSubmenu]) {
+ NSMenu* parent = [item menu];
+ if (parent) {
+ // NSLog(@"Performing action for native menu item titled: %@\n",
+ // [[currentSubmenu itemAtIndex:targetIndex] title]);
+ [parent performActionForItemAtIndex:[parent indexOfItem:item]];
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::ForceUpdateNativeMenuAt(const nsAString& indexString) {
+ if (!mMenu) return NS_ERROR_NOT_INITIALIZED;
+
+ NSString* locationString =
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
+ unsigned int indexCount = [indexes count];
+ if (indexCount == 0) return NS_OK;
+
+ nsMenuX* currentMenu = mMenu;
+
+ // now find the correct submenu
+ for (unsigned int i = 1; currentMenu && i < indexCount; i++) {
+ int targetIndex = [[indexes objectAtIndex:i] intValue];
+ int visible = 0;
+ uint32_t length = currentMenu->GetItemCount();
+ for (unsigned int j = 0; j < length; j++) {
+ nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j);
+ if (!targetMenu) return NS_OK;
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) {
+ visible++;
+ if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) {
+ currentMenu = static_cast<nsMenuX*>(targetMenu);
+ // fake open/close to cause lazy update to happen
+ currentMenu->MenuOpened();
+ currentMenu->MenuClosed();
+ break;
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsStandaloneNativeMenu::IconUpdated() {
+ if (mContainerStatusBarItem) {
+ NSImage* menuImage = [mMenu->NativeMenuItem() image];
+ if (menuImage) {
+ [menuImage setTemplate:true];
+ }
+ [mContainerStatusBarItem setImage:menuImage];
+ }
+}
+
+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..b0c6f93148
--- /dev/null
+++ b/widget/cocoa/nsSystemStatusBarCocoa.h
@@ -0,0 +1,38 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSystemStatusBarCocoa_h_
+#define nsSystemStatusBarCocoa_h_
+
+#include "mozilla/RefPtr.h"
+#include "nsISystemStatusBar.h"
+#include "nsClassHashtable.h"
+
+class nsStandaloneNativeMenu;
+@class NSStatusItem;
+
+class nsSystemStatusBarCocoa : public nsISystemStatusBar {
+ public:
+ nsSystemStatusBarCocoa() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISYSTEMSTATUSBAR
+
+ protected:
+ virtual ~nsSystemStatusBarCocoa() {}
+
+ struct StatusItem {
+ explicit StatusItem(nsStandaloneNativeMenu* aMenu);
+ ~StatusItem();
+
+ private:
+ RefPtr<nsStandaloneNativeMenu> mMenu;
+ NSStatusItem* mStatusItem;
+ };
+
+ nsClassHashtable<nsISupportsHashKey, StatusItem> mItems;
+};
+
+#endif // nsSystemStatusBarCocoa_h_
diff --git a/widget/cocoa/nsSystemStatusBarCocoa.mm b/widget/cocoa/nsSystemStatusBarCocoa.mm
new file mode 100644
index 0000000000..81a0be7d56
--- /dev/null
+++ b/widget/cocoa/nsSystemStatusBarCocoa.mm
@@ -0,0 +1,72 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsComponentManagerUtils.h"
+#include "nsSystemStatusBarCocoa.h"
+#include "nsStandaloneNativeMenu.h"
+#include "nsObjCExceptions.h"
+#include "mozilla/dom/Element.h"
+
+using mozilla::dom::Element;
+
+NS_IMPL_ISUPPORTS(nsSystemStatusBarCocoa, nsISystemStatusBar)
+
+NS_IMETHODIMP
+nsSystemStatusBarCocoa::AddItem(Element* aElement) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ RefPtr<nsStandaloneNativeMenu> menu = new nsStandaloneNativeMenu();
+ nsresult rv = menu->Init(aElement);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISupports> keyPtr = aElement;
+ mItems.Put(keyPtr, new StatusItem(menu));
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSystemStatusBarCocoa::RemoveItem(Element* aElement) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mItems.Remove(aElement);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsSystemStatusBarCocoa::StatusItem::StatusItem(nsStandaloneNativeMenu* aMenu) : mMenu(aMenu) {
+ MOZ_COUNT_CTOR(nsSystemStatusBarCocoa::StatusItem);
+
+ NSMenu* nativeMenu = nil;
+ mMenu->GetNativeMenu(reinterpret_cast<void**>(&nativeMenu));
+
+ mStatusItem =
+ [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
+ [mStatusItem setMenu:nativeMenu];
+ [mStatusItem setHighlightMode:YES];
+
+ // We want the status item to get its image from menu item that mMenu was
+ // initialized with. Icon loads are asynchronous, so we need to let the menu
+ // know about the item so that it can update its icon as soon as it has
+ // loaded.
+ mMenu->SetContainerStatusBarItem(mStatusItem);
+}
+
+nsSystemStatusBarCocoa::StatusItem::~StatusItem() {
+ mMenu->SetContainerStatusBarItem(nil);
+ [[NSStatusBar systemStatusBar] removeStatusItem:mStatusItem];
+ [mStatusItem release];
+ mStatusItem = nil;
+
+ MOZ_COUNT_DTOR(nsSystemStatusBarCocoa::StatusItem);
+}
diff --git a/widget/cocoa/nsToolkit.h b/widget/cocoa/nsToolkit.h
new file mode 100644
index 0000000000..46b08d0ebe
--- /dev/null
+++ b/widget/cocoa/nsToolkit.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsToolkit_h_
+#define nsToolkit_h_
+
+#include "nscore.h"
+
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+#import <objc/Object.h>
+#import <IOKit/IOKitLib.h>
+
+class nsToolkit {
+ public:
+ nsToolkit();
+ virtual ~nsToolkit();
+
+ static nsToolkit* GetToolkit();
+
+ static void Shutdown() {
+ delete gToolkit;
+ gToolkit = nullptr;
+ }
+
+ static void PostSleepWakeNotification(const char* aNotification);
+
+ static nsresult SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod,
+ bool classMethods = false);
+
+ void MonitorAllProcessMouseEvents();
+ void StopMonitoringAllProcessMouseEvents();
+
+ protected:
+ nsresult RegisterForSleepWakeNotifications();
+ void RemoveSleepWakeNotifications();
+
+ protected:
+ static nsToolkit* gToolkit;
+
+ CFRunLoopSourceRef mSleepWakeNotificationRLS;
+ io_object_t mPowerNotifier;
+
+ id mAllProcessMouseMonitor;
+};
+
+#endif // nsToolkit_h_
diff --git a/widget/cocoa/nsToolkit.mm b/widget/cocoa/nsToolkit.mm
new file mode 100644
index 0000000000..3e5907f4ad
--- /dev/null
+++ b/widget/cocoa/nsToolkit.mm
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsToolkit.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <mach/mach_port.h>
+#include <mach/mach_interface.h>
+#include <mach/mach_init.h>
+
+extern "C" {
+#include <mach-o/getsect.h>
+}
+#include <unistd.h>
+#include <dlfcn.h>
+
+#import <Cocoa/Cocoa.h>
+#import <IOKit/pwr_mgt/IOPMLib.h>
+#import <IOKit/IOMessage.h>
+
+#include "nsCocoaUtils.h"
+#include "nsObjCExceptions.h"
+
+#include "nsGkAtoms.h"
+#include "nsIRollupListener.h"
+#include "nsIWidget.h"
+#include "nsBaseWidget.h"
+
+#include "nsIObserverService.h"
+
+#include "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), mPowerNotifier{0}, mAllProcessMouseMonitor(nil) {
+ MOZ_COUNT_CTOR(nsToolkit);
+ RegisterForSleepWakeNotifications();
+}
+
+nsToolkit::~nsToolkit() {
+ MOZ_COUNT_DTOR(nsToolkit);
+ RemoveSleepWakeNotifications();
+ StopMonitoringAllProcessMouseEvents();
+}
+
+void nsToolkit::PostSleepWakeNotification(const char* aNotification) {
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) observerService->NotifyObservers(nullptr, aNotification, nullptr);
+}
+
+// http://developer.apple.com/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/PowerMgmt/chapter_10_section_3.html
+static void ToolkitSleepWakeCallback(void* refCon, io_service_t service, natural_t messageType,
+ void* messageArgument) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ switch (messageType) {
+ case kIOMessageSystemWillSleep:
+ // System is going to sleep now.
+ nsToolkit::PostSleepWakeNotification(NS_WIDGET_SLEEP_OBSERVER_TOPIC);
+ ::IOAllowPowerChange(gRootPort, (long)messageArgument);
+ break;
+
+ case kIOMessageCanSystemSleep:
+ // In this case, the computer has been idle for several minutes
+ // and will sleep soon so you must either allow or cancel
+ // this notification. Important: if you don’t respond, there will
+ // be a 30-second timeout before the computer sleeps.
+ // In Mozilla's case, we always allow sleep.
+ ::IOAllowPowerChange(gRootPort, (long)messageArgument);
+ break;
+
+ case kIOMessageSystemHasPoweredOn:
+ // Handle wakeup.
+ nsToolkit::PostSleepWakeNotification(NS_WIDGET_WAKE_OBSERVER_TOPIC);
+ break;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult nsToolkit::RegisterForSleepWakeNotifications() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ IONotificationPortRef notifyPortRef;
+
+ NS_ASSERTION(!mSleepWakeNotificationRLS, "Already registered for sleep/wake");
+
+ gRootPort =
+ ::IORegisterForSystemPower(0, &notifyPortRef, ToolkitSleepWakeCallback, &mPowerNotifier);
+ if (gRootPort == MACH_PORT_NULL) {
+ NS_ERROR("IORegisterForSystemPower failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ mSleepWakeNotificationRLS = ::IONotificationPortGetRunLoopSource(notifyPortRef);
+ ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), mSleepWakeNotificationRLS, kCFRunLoopDefaultMode);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsToolkit::RemoveSleepWakeNotifications() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mSleepWakeNotificationRLS) {
+ ::IODeregisterForSystemPower(&mPowerNotifier);
+ ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mSleepWakeNotificationRLS,
+ kCFRunLoopDefaultMode);
+
+ mSleepWakeNotificationRLS = nullptr;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// 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::MonitorAllProcessMouseEvents() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // 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 (getenv("MOZ_NO_GLOBAL_MOUSE_MONITOR")) return;
+
+ if (mAllProcessMouseMonitor == nil) {
+ mAllProcessMouseMonitor = [NSEvent
+ addGlobalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown | NSEventMaskLeftMouseDown
+ handler:^(NSEvent* evt) {
+ if ([NSApp isActive]) {
+ return;
+ }
+
+ nsIRollupListener* rollupListener =
+ nsBaseWidget::GetActiveRollupListener();
+ if (!rollupListener) {
+ return;
+ }
+
+ nsCOMPtr<nsIWidget> rollupWidget =
+ rollupListener->GetRollupWidget();
+ if (!rollupWidget) {
+ return;
+ }
+
+ NSWindow* ctxMenuWindow =
+ (NSWindow*)rollupWidget->GetNativeData(
+ NS_NATIVE_WINDOW);
+ if (!ctxMenuWindow) {
+ return;
+ }
+
+ // Don't roll up the rollup widget if our mouseDown happens
+ // over it (doing so would break the corresponding context
+ // menu).
+ NSPoint screenLocation = [NSEvent mouseLocation];
+ if (NSPointInRect(screenLocation, [ctxMenuWindow frame])) {
+ return;
+ }
+
+ rollupListener->Rollup(0, false, nullptr, nullptr);
+ }];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsToolkit::StopMonitoringAllProcessMouseEvents() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mAllProcessMouseMonitor != nil) {
+ [NSEvent removeMonitor:mAllProcessMouseMonitor];
+ mAllProcessMouseMonitor = nil;
+ }
+
+ 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/nsTouchBar.h b/widget/cocoa/nsTouchBar.h
new file mode 100644
index 0000000000..4432b05c39
--- /dev/null
+++ b/widget/cocoa/nsTouchBar.h
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsTouchBar_h_
+#define nsTouchBar_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsITouchBarHelper.h"
+#include "nsTouchBarInput.h"
+#include "nsTouchBarNativeAPIDefines.h"
+
+const NSTouchBarItemIdentifier kTouchBarBaseIdentifier = @"com.mozilla.firefox.touchbar";
+
+/**
+ * Our TouchBar is its own delegate. This is adequate for our purposes,
+ * since the current implementation only defines Touch Bar buttons for the
+ * main Firefox window. If modals and other windows were to have custom
+ * Touch Bar views, each window would have to be a NSTouchBarDelegate so
+ * they could define their own custom sets of buttons.
+ */
+@interface nsTouchBar : NSTouchBar <NSTouchBarDelegate,
+ NSSharingServicePickerTouchBarItemDelegate,
+ NSSharingServiceDelegate> {
+ /**
+ * Link to the frontend API that determines which buttons appear
+ * in the Touch Bar
+ */
+ nsCOMPtr<nsITouchBarHelper> mTouchBarHelper;
+}
+
+/**
+ * Contains TouchBarInput representations of the inputs currently in
+ * the Touch Bar. Populated in `init` and updated by nsITouchBarUpdater.
+ */
+@property(strong) NSMutableDictionary<NSTouchBarItemIdentifier, TouchBarInput*>* mappedLayoutItems;
+
+/**
+ * Stores buttons displayed in a NSScrollView. They must be stored separately
+ * because they are untethered from the nsTouchBar. As such, they
+ * cannot be retrieved with [NSTouchBar itemForIdentifier].
+ */
+@property(strong)
+ NSMutableDictionary<NSTouchBarItemIdentifier, NSCustomTouchBarItem*>* scrollViewButtons;
+
+/**
+ * Returns an instance of nsTouchBar based on implementation details
+ * fetched from the frontend through nsTouchBarHelper.
+ */
+- (instancetype)init;
+
+/**
+ * If aInputs is not nil, a nsTouchBar containing the inputs specified is
+ * initialized. Otherwise, a nsTouchBar is initialized containing a default set
+ * of inputs.
+ */
+- (instancetype)initWithInputs:(NSMutableArray<TouchBarInput*>*)aInputs;
+
+- (void)dealloc;
+
+/**
+ * Creates a new NSTouchBarItem and adds it to the Touch Bar.
+ * Reads the passed identifier and creates the
+ * appropriate item type (eg. NSCustomTouchBarItem).
+ * Required as a member of NSTouchBarDelegate.
+ */
+- (NSTouchBarItem*)touchBar:(NSTouchBar*)aTouchBar
+ makeItemForIdentifier:(NSTouchBarItemIdentifier)aIdentifier;
+
+/**
+ * Updates an input on the Touch Bar by redirecting to one of the specific
+ * TouchBarItem types updaters.
+ * Returns true if the input was successfully updated.
+ */
+- (bool)updateItem:(TouchBarInput*)aInput;
+
+/**
+ * Helper function for updateItem. Checks to see if a given input exists within
+ * any of this Touch Bar's popovers and updates it if it exists.
+ */
+- (bool)maybeUpdatePopoverChild:(TouchBarInput*)aInput;
+
+/**
+ * Helper function for updateItem. Checks to see if a given input exists within
+ * any of this Touch Bar's scroll views and updates it if it exists.
+ */
+- (bool)maybeUpdateScrollViewChild:(TouchBarInput*)aInput;
+
+/**
+ * Helper function for updateItem. Replaces an item in the
+ * self.mappedLayoutItems dictionary.
+ */
+- (void)replaceMappedLayoutItem:(TouchBarInput*)aItem;
+
+/**
+ * Update or create various subclasses of TouchBarItem.
+ */
+- (void)updateButton:(NSCustomTouchBarItem*)aButton
+ withIdentifier:(NSTouchBarItemIdentifier)aIdentifier;
+- (void)updateMainButton:(NSCustomTouchBarItem*)aMainButton
+ withIdentifier:(NSTouchBarItemIdentifier)aIdentifier;
+- (void)updatePopover:(NSPopoverTouchBarItem*)aPopoverItem
+ withIdentifier:(NSTouchBarItemIdentifier)aIdentifier;
+- (void)updateScrollView:(NSCustomTouchBarItem*)aScrollViewItem
+ withIdentifier:(NSTouchBarItemIdentifier)aIdentifier;
+- (void)updateLabel:(NSTextField*)aLabel withIdentifier:(NSTouchBarItemIdentifier)aIdentifier;
+- (NSTouchBarItem*)makeShareScrubberForIdentifier:(NSTouchBarItemIdentifier)aIdentifier;
+
+/**
+ * If aShowing is true, aPopover is shown. Otherwise, it is hidden.
+ */
+- (void)showPopover:(TouchBarInput*)aPopover showing:(bool)aShowing;
+
+/**
+ * Redirects button actions to the appropriate handler.
+ */
+- (void)touchBarAction:(id)aSender;
+
+/**
+ * Helper function to initialize a new nsTouchBarInputIcon and load an icon.
+ */
+- (void)loadIconForInput:(TouchBarInput*)aInput forItem:(NSTouchBarItem*)aItem;
+
+- (NSArray*)itemsForSharingServicePickerTouchBarItem:
+ (NSSharingServicePickerTouchBarItem*)aPickerTouchBarItem;
+
+- (NSArray<NSSharingService*>*)sharingServicePicker:(NSSharingServicePicker*)aSharingServicePicker
+ sharingServicesForItems:(NSArray*)aItems
+ proposedSharingServices:(NSArray<NSSharingService*>*)aProposedServices;
+
+- (void)releaseJSObjects;
+
+@end // nsTouchBar
+
+#endif // nsTouchBar_h_
diff --git a/widget/cocoa/nsTouchBar.mm b/widget/cocoa/nsTouchBar.mm
new file mode 100644
index 0000000000..98c2344395
--- /dev/null
+++ b/widget/cocoa/nsTouchBar.mm
@@ -0,0 +1,604 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsTouchBar.h"
+
+#include <objc/runtime.h>
+
+#include "mozilla/MacStringHelpers.h"
+#include "nsArrayUtils.h"
+#include "nsCocoaUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIArray.h"
+#include "nsTouchBarInputIcon.h"
+#include "nsWidgetsCID.h"
+
+@implementation nsTouchBar
+
+// Used to tie action strings to buttons.
+static char sIdentifierAssociationKey;
+
+// The default space between inputs, used where layout is not automatic.
+static const uint32_t kInputSpacing = 8;
+// The width of buttons in Apple's Share ScrollView. We use this in our
+// ScrollViews to give them a native appearance.
+static const uint32_t kScrollViewButtonWidth = 144;
+static const uint32_t kInputIconSize = 16;
+
+// The system default width for Touch Bar inputs is 128px. This is double.
+#define MAIN_BUTTON_WIDTH 256
+
+#pragma mark - NSTouchBarDelegate
+
+- (instancetype)init {
+ return [self initWithInputs:nil];
+}
+
+- (instancetype)initWithInputs:(NSMutableArray<TouchBarInput*>*)aInputs {
+ if ((self = [super init])) {
+ mTouchBarHelper = do_GetService(NS_TOUCHBARHELPER_CID);
+ if (!mTouchBarHelper) {
+ NS_ERROR("Unable to create Touch Bar Helper.");
+ return nil;
+ }
+
+ self.delegate = self;
+ self.mappedLayoutItems = [NSMutableDictionary dictionary];
+ self.customizationAllowedItemIdentifiers = @[];
+
+ if (!aInputs) {
+ // This customization identifier is how users' custom layouts are saved by macOS.
+ // If this changes, all users' layouts would be reset to the default layout.
+ self.customizationIdentifier =
+ [kTouchBarBaseIdentifier stringByAppendingPathExtension:@"defaultbar"];
+ nsCOMPtr<nsIArray> allItems;
+
+ nsresult rv = mTouchBarHelper->GetAllItems(getter_AddRefs(allItems));
+ if (NS_FAILED(rv) || !allItems) {
+ return nil;
+ }
+
+ uint32_t itemCount = 0;
+ allItems->GetLength(&itemCount);
+ // This is copied to self.customizationAllowedItemIdentifiers.
+ // Required since [self.mappedItems allKeys] does not preserve order.
+ // One slot is added for the spacer item.
+ NSMutableArray* orderedIdentifiers = [NSMutableArray arrayWithCapacity:itemCount + 1];
+ for (uint32_t i = 0; i < itemCount; ++i) {
+ nsCOMPtr<nsITouchBarInput> input = do_QueryElementAt(allItems, i);
+ if (!input) {
+ continue;
+ }
+
+ TouchBarInput* convertedInput;
+ NSTouchBarItemIdentifier newInputIdentifier =
+ [TouchBarInput nativeIdentifierWithXPCOM:input];
+ if (!newInputIdentifier) {
+ continue;
+ }
+
+ // If there is already an input in mappedLayoutItems with this identifier,
+ // that means updateItem fired before this initialization. The input
+ // cached by updateItem is more current, so we should use that one.
+ if (self.mappedLayoutItems[newInputIdentifier]) {
+ convertedInput = self.mappedLayoutItems[newInputIdentifier];
+ } else {
+ convertedInput = [[TouchBarInput alloc] initWithXPCOM:input];
+ // Add new input to dictionary for lookup of properties in delegate.
+ self.mappedLayoutItems[[convertedInput nativeIdentifier]] = convertedInput;
+ }
+
+ orderedIdentifiers[i] = [convertedInput nativeIdentifier];
+ }
+ [orderedIdentifiers addObject:@"NSTouchBarItemIdentifierFlexibleSpace"];
+ self.customizationAllowedItemIdentifiers = [orderedIdentifiers copy];
+
+ NSArray* defaultItemIdentifiers = @[
+ [TouchBarInput nativeIdentifierWithType:@"button" withKey:@"back"],
+ [TouchBarInput nativeIdentifierWithType:@"button" withKey:@"forward"],
+ [TouchBarInput nativeIdentifierWithType:@"button" withKey:@"reload"],
+ [TouchBarInput nativeIdentifierWithType:@"mainButton" withKey:@"open-location"],
+ [TouchBarInput nativeIdentifierWithType:@"button" withKey:@"new-tab"],
+ [TouchBarInput shareScrubberIdentifier], [TouchBarInput searchPopoverIdentifier]
+ ];
+ self.defaultItemIdentifiers = [defaultItemIdentifiers copy];
+ } else {
+ NSMutableArray* defaultItemIdentifiers = [NSMutableArray arrayWithCapacity:[aInputs count]];
+ for (TouchBarInput* input in aInputs) {
+ self.mappedLayoutItems[[input nativeIdentifier]] = input;
+ [defaultItemIdentifiers addObject:[input nativeIdentifier]];
+ }
+ self.defaultItemIdentifiers = [defaultItemIdentifiers copy];
+ }
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
+ NSTouchBarItem* item = [self itemForIdentifier:identifier];
+ if (!item) {
+ continue;
+ }
+ if ([item isKindOfClass:[NSPopoverTouchBarItem class]]) {
+ [(NSPopoverTouchBarItem*)item setCollapsedRepresentationImage:nil];
+ [(nsTouchBar*)[(NSPopoverTouchBarItem*)item popoverTouchBar] release];
+ } else if ([[item view] isKindOfClass:[NSScrollView class]]) {
+ [[(NSScrollView*)[item view] documentView] release];
+ [(NSScrollView*)[item view] release];
+ }
+
+ [item release];
+ }
+
+ [self.defaultItemIdentifiers release];
+ [self.customizationAllowedItemIdentifiers release];
+ [self.scrollViewButtons removeAllObjects];
+ [self.scrollViewButtons release];
+ [self.mappedLayoutItems removeAllObjects];
+ [self.mappedLayoutItems release];
+ [super dealloc];
+}
+
+- (NSTouchBarItem*)touchBar:(NSTouchBar*)aTouchBar
+ makeItemForIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
+ if (!mTouchBarHelper) {
+ return nil;
+ }
+
+ TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
+ if (!input) {
+ return nil;
+ }
+
+ if ([input baseType] == TouchBarInputBaseType::kScrubber) {
+ // We check the identifier rather than the baseType here as a special case.
+ if (![aIdentifier isEqualToString:[TouchBarInput shareScrubberIdentifier]]) {
+ // We're only supporting the Share scrubber for now.
+ return nil;
+ }
+ return [self makeShareScrubberForIdentifier:aIdentifier];
+ }
+
+ if ([input baseType] == TouchBarInputBaseType::kPopover) {
+ NSPopoverTouchBarItem* newPopoverItem =
+ [[NSPopoverTouchBarItem alloc] initWithIdentifier:aIdentifier];
+ [newPopoverItem setCustomizationLabel:[input title]];
+ // We initialize popoverTouchBar here because we only allow setting this
+ // property on popover creation. Updating popoverTouchBar for every update
+ // of the popover item would be very expensive.
+ newPopoverItem.popoverTouchBar = [[nsTouchBar alloc] initWithInputs:[input children]];
+ [self updatePopover:newPopoverItem withIdentifier:[input nativeIdentifier]];
+ return newPopoverItem;
+ }
+
+ // Our new item, which will be initialized depending on aIdentifier.
+ NSCustomTouchBarItem* newItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:aIdentifier];
+ [newItem setCustomizationLabel:[input title]];
+
+ if ([input baseType] == TouchBarInputBaseType::kScrollView) {
+ [self updateScrollView:newItem withIdentifier:[input nativeIdentifier]];
+ return newItem;
+ } else if ([input baseType] == TouchBarInputBaseType::kLabel) {
+ NSTextField* label = [NSTextField labelWithString:@""];
+ [self updateLabel:label withIdentifier:[input nativeIdentifier]];
+ newItem.view = label;
+ return newItem;
+ }
+
+ // The cases of a button or main button require the same setup.
+ NSButton* button = [NSButton buttonWithTitle:@"" target:self action:@selector(touchBarAction:)];
+ newItem.view = button;
+
+ if ([input baseType] == TouchBarInputBaseType::kButton &&
+ ![[input type] hasPrefix:@"scrollView"]) {
+ [self updateButton:newItem withIdentifier:[input nativeIdentifier]];
+ } else if ([input baseType] == TouchBarInputBaseType::kMainButton) {
+ [self updateMainButton:newItem withIdentifier:[input nativeIdentifier]];
+ }
+ return newItem;
+}
+
+- (bool)updateItem:(TouchBarInput*)aInput {
+ if (!mTouchBarHelper) {
+ return false;
+ }
+
+ NSTouchBarItem* item = [self itemForIdentifier:[aInput nativeIdentifier]];
+
+ // If we can't immediately find item, there are three possibilities:
+ // * It is a button in a ScrollView, or
+ // * It is contained within a popover, or
+ // * It simply does not exist.
+ // We check for each possibility here.
+ if (!self.mappedLayoutItems[[aInput nativeIdentifier]]) {
+ if ([self maybeUpdateScrollViewChild:aInput]) {
+ return true;
+ }
+ if ([self maybeUpdatePopoverChild:aInput]) {
+ return true;
+ }
+ return false;
+ }
+
+ // Update our canonical copy of the input.
+ [self replaceMappedLayoutItem:aInput];
+
+ if ([aInput baseType] == TouchBarInputBaseType::kButton) {
+ [(NSCustomTouchBarItem*)item setCustomizationLabel:[aInput title]];
+ [self updateButton:(NSCustomTouchBarItem*)item withIdentifier:[aInput nativeIdentifier]];
+ } else if ([aInput baseType] == TouchBarInputBaseType::kMainButton) {
+ [(NSCustomTouchBarItem*)item setCustomizationLabel:[aInput title]];
+ [self updateMainButton:(NSCustomTouchBarItem*)item withIdentifier:[aInput nativeIdentifier]];
+ } else if ([aInput baseType] == TouchBarInputBaseType::kScrollView) {
+ [(NSCustomTouchBarItem*)item setCustomizationLabel:[aInput title]];
+ [self updateScrollView:(NSCustomTouchBarItem*)item withIdentifier:[aInput nativeIdentifier]];
+ } else if ([aInput baseType] == TouchBarInputBaseType::kPopover) {
+ [(NSPopoverTouchBarItem*)item setCustomizationLabel:[aInput title]];
+ [self updatePopover:(NSPopoverTouchBarItem*)item withIdentifier:[aInput nativeIdentifier]];
+ for (TouchBarInput* child in [aInput children]) {
+ [(nsTouchBar*)[(NSPopoverTouchBarItem*)item popoverTouchBar] updateItem:child];
+ }
+ } else if ([aInput baseType] == TouchBarInputBaseType::kLabel) {
+ [self updateLabel:(NSTextField*)item.view withIdentifier:[aInput nativeIdentifier]];
+ }
+
+ return true;
+}
+
+- (bool)maybeUpdatePopoverChild:(TouchBarInput*)aInput {
+ for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
+ TouchBarInput* potentialPopover = self.mappedLayoutItems[identifier];
+ if ([potentialPopover baseType] != TouchBarInputBaseType::kPopover) {
+ continue;
+ }
+ NSTouchBarItem* popover = [self itemForIdentifier:[potentialPopover nativeIdentifier]];
+ if (popover) {
+ if ([(nsTouchBar*)[(NSPopoverTouchBarItem*)popover popoverTouchBar] updateItem:aInput]) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+- (bool)maybeUpdateScrollViewChild:(TouchBarInput*)aInput {
+ NSCustomTouchBarItem* scrollViewButton = self.scrollViewButtons[[aInput nativeIdentifier]];
+ if (scrollViewButton) {
+ // ScrollView buttons are similar to mainButtons except for their width.
+ [self updateMainButton:scrollViewButton withIdentifier:[aInput nativeIdentifier]];
+ NSButton* button = (NSButton*)scrollViewButton.view;
+ uint32_t buttonSize = MAX(button.attributedTitle.size.width + kInputIconSize + kInputSpacing,
+ kScrollViewButtonWidth);
+ [[button widthAnchor] constraintGreaterThanOrEqualToConstant:buttonSize].active = YES;
+ }
+ // Updating the TouchBarInput* in the ScrollView's mChildren array.
+ for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
+ TouchBarInput* potentialScrollView = self.mappedLayoutItems[identifier];
+ if ([potentialScrollView baseType] != TouchBarInputBaseType::kScrollView) {
+ continue;
+ }
+ for (uint32_t i = 0; i < [[potentialScrollView children] count]; ++i) {
+ TouchBarInput* child = [potentialScrollView children][i];
+ if (![[child nativeIdentifier] isEqualToString:[aInput nativeIdentifier]]) {
+ continue;
+ }
+ [[potentialScrollView children] replaceObjectAtIndex:i withObject:aInput];
+ [child release];
+ return true;
+ }
+ }
+ return false;
+}
+
+- (void)replaceMappedLayoutItem:(TouchBarInput*)aItem {
+ [self.mappedLayoutItems[[aItem nativeIdentifier]] release];
+ self.mappedLayoutItems[[aItem nativeIdentifier]] = aItem;
+}
+
+- (void)updateButton:(NSCustomTouchBarItem*)aButton
+ withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
+ if (!aButton || !aIdentifier) {
+ return;
+ }
+
+ TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
+ if (!input) {
+ return;
+ }
+
+ NSButton* button = (NSButton*)[aButton view];
+ button.title = [input title];
+ if ([input imageURI]) {
+ [button setImagePosition:NSImageOnly];
+ [self loadIconForInput:input forItem:aButton];
+ // Because we are hiding the title, NSAccessibility also does not get it.
+ // Therefore, set an accessibility label as alternative text for image-only buttons.
+ [button setAccessibilityLabel:[input title]];
+ }
+
+ [button setEnabled:![input isDisabled]];
+ if ([input color]) {
+ button.bezelColor = [input color];
+ }
+
+ objc_setAssociatedObject(button, &sIdentifierAssociationKey, aIdentifier,
+ OBJC_ASSOCIATION_RETAIN);
+}
+
+- (void)updateMainButton:(NSCustomTouchBarItem*)aMainButton
+ withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
+ if (!aMainButton || !aIdentifier) {
+ return;
+ }
+
+ TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
+ if (!input) {
+ return;
+ }
+
+ [self updateButton:aMainButton withIdentifier:aIdentifier];
+ NSButton* button = (NSButton*)[aMainButton view];
+
+ // If empty, string is still being localized. Display a blank input instead.
+ if ([[input title] isEqualToString:@""]) {
+ [button setImagePosition:NSNoImage];
+ } else {
+ [button setImagePosition:NSImageLeft];
+ }
+ button.imageHugsTitle = YES;
+ [button.widthAnchor constraintGreaterThanOrEqualToConstant:MAIN_BUTTON_WIDTH].active = YES;
+ [button setContentHuggingPriority:1.0 forOrientation:NSLayoutConstraintOrientationHorizontal];
+}
+
+- (void)updatePopover:(NSPopoverTouchBarItem*)aPopoverItem
+ withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
+ if (!aPopoverItem || !aIdentifier) {
+ return;
+ }
+
+ TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
+ if (!input) {
+ return;
+ }
+
+ aPopoverItem.showsCloseButton = YES;
+ if ([input imageURI]) {
+ [self loadIconForInput:input forItem:aPopoverItem];
+ } else if ([input title]) {
+ aPopoverItem.collapsedRepresentationLabel = [input title];
+ }
+
+ // Special handling to show/hide the search popover if the Urlbar is focused.
+ if ([[input nativeIdentifier] isEqualToString:[TouchBarInput searchPopoverIdentifier]]) {
+ // We can reach this code during window shutdown. We only want to toggle
+ // showPopover if we are in a normal running state.
+ if (!mTouchBarHelper) {
+ return;
+ }
+ bool urlbarIsFocused = false;
+ mTouchBarHelper->GetIsUrlbarFocused(&urlbarIsFocused);
+ if (urlbarIsFocused) {
+ [aPopoverItem showPopover:self];
+ }
+ }
+}
+
+- (void)updateScrollView:(NSCustomTouchBarItem*)aScrollViewItem
+ withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
+ if (!aScrollViewItem || !aIdentifier) {
+ return;
+ }
+
+ TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
+ if (!input || ![input children]) {
+ return;
+ }
+
+ NSMutableDictionary* constraintViews = [NSMutableDictionary dictionary];
+ NSView* documentView = [[NSView alloc] initWithFrame:NSZeroRect];
+ NSString* layoutFormat = @"H:|-8-";
+ NSSize size = NSMakeSize(kInputSpacing, 30);
+ // Layout strings allow only alphanumeric characters. We will use this
+ // NSCharacterSet to strip illegal characters.
+ NSCharacterSet* charactersToRemove = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
+
+ for (TouchBarInput* childInput in [input children]) {
+ if ([childInput baseType] != TouchBarInputBaseType::kButton) {
+ continue;
+ }
+ [self replaceMappedLayoutItem:childInput];
+ NSCustomTouchBarItem* newItem =
+ [[NSCustomTouchBarItem alloc] initWithIdentifier:[childInput nativeIdentifier]];
+ NSButton* button = [NSButton buttonWithTitle:[childInput title]
+ target:self
+ action:@selector(touchBarAction:)];
+ newItem.view = button;
+ // ScrollView buttons are similar to mainButtons except for their width.
+ [self updateMainButton:newItem withIdentifier:[childInput nativeIdentifier]];
+ uint32_t buttonSize = MAX(button.attributedTitle.size.width + kInputIconSize + kInputSpacing,
+ kScrollViewButtonWidth);
+ [[button widthAnchor] constraintGreaterThanOrEqualToConstant:buttonSize].active = YES;
+
+ NSCustomTouchBarItem* tempItem = self.scrollViewButtons[[childInput nativeIdentifier]];
+ self.scrollViewButtons[[childInput nativeIdentifier]] = newItem;
+ [tempItem release];
+
+ button.translatesAutoresizingMaskIntoConstraints = NO;
+ [documentView addSubview:button];
+ NSString* layoutKey = [[[childInput nativeIdentifier]
+ componentsSeparatedByCharactersInSet:charactersToRemove] componentsJoinedByString:@""];
+
+ // Iteratively create our layout string.
+ layoutFormat =
+ [layoutFormat stringByAppendingString:[NSString stringWithFormat:@"[%@]-8-", layoutKey]];
+ [constraintViews setObject:button forKey:layoutKey];
+ size.width += kInputSpacing + buttonSize;
+ }
+ layoutFormat = [layoutFormat stringByAppendingString:[NSString stringWithFormat:@"|"]];
+ NSArray* hConstraints =
+ [NSLayoutConstraint constraintsWithVisualFormat:layoutFormat
+ options:NSLayoutFormatAlignAllCenterY
+ metrics:nil
+ views:constraintViews];
+ NSScrollView* scrollView =
+ [[NSScrollView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
+ [documentView setFrame:NSMakeRect(0, 0, size.width, size.height)];
+ [NSLayoutConstraint activateConstraints:hConstraints];
+ scrollView.documentView = documentView;
+
+ aScrollViewItem.view = scrollView;
+}
+
+- (void)updateLabel:(NSTextField*)aLabel withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
+ if (!aLabel || !aIdentifier) {
+ return;
+ }
+
+ TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
+ if (!input || ![input title]) {
+ return;
+ }
+ [aLabel setStringValue:[input title]];
+}
+
+- (NSTouchBarItem*)makeShareScrubberForIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
+ TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
+ // System-default share menu
+ NSSharingServicePickerTouchBarItem* servicesItem =
+ [[NSSharingServicePickerTouchBarItem alloc] initWithIdentifier:aIdentifier];
+
+ // buttonImage needs to be set to nil while we wait for our icon to load.
+ // Otherwise, the default Apple share icon is automatically loaded.
+ servicesItem.buttonImage = nil;
+
+ [self loadIconForInput:input forItem:servicesItem];
+
+ servicesItem.delegate = self;
+ return servicesItem;
+}
+
+- (void)showPopover:(TouchBarInput*)aPopover showing:(bool)aShowing {
+ if (!aPopover) {
+ return;
+ }
+ NSPopoverTouchBarItem* popoverItem =
+ (NSPopoverTouchBarItem*)[self itemForIdentifier:[aPopover nativeIdentifier]];
+ if (!popoverItem) {
+ return;
+ }
+ if (aShowing) {
+ [popoverItem showPopover:self];
+ } else {
+ [popoverItem dismissPopover:self];
+ }
+}
+
+- (void)touchBarAction:(id)aSender {
+ NSTouchBarItemIdentifier identifier =
+ objc_getAssociatedObject(aSender, &sIdentifierAssociationKey);
+ if (!identifier || [identifier isEqualToString:@""]) {
+ return;
+ }
+
+ TouchBarInput* input = self.mappedLayoutItems[identifier];
+ if (!input) {
+ return;
+ }
+
+ nsCOMPtr<nsITouchBarInputCallback> callback = [input callback];
+ if (!callback) {
+ NSLog(@"Touch Bar action attempted with no valid callback! Identifier: %@",
+ [input nativeIdentifier]);
+ return;
+ }
+ callback->OnCommand();
+}
+
+- (void)loadIconForInput:(TouchBarInput*)aInput forItem:(NSTouchBarItem*)aItem {
+ if (!aInput || ![aInput imageURI] || !aItem || !mTouchBarHelper) {
+ return;
+ }
+
+ RefPtr<nsTouchBarInputIcon> icon = [aInput icon];
+
+ if (!icon) {
+ RefPtr<Document> document;
+ nsresult rv = mTouchBarHelper->GetDocument(getter_AddRefs(document));
+ if (NS_FAILED(rv) || !document) {
+ return;
+ }
+ icon = new nsTouchBarInputIcon(document, aInput, aItem);
+ [aInput setIcon:icon];
+ }
+ icon->SetupIcon([aInput imageURI]);
+}
+
+- (void)releaseJSObjects {
+ mTouchBarHelper = nil;
+
+ for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
+ TouchBarInput* input = self.mappedLayoutItems[identifier];
+ if (!input) {
+ continue;
+ }
+
+ // Childless popovers contain the default Touch Bar as its popoverTouchBar.
+ // We check for [input children] since the default Touch Bar contains a
+ // popover (search-popover), so this would infinitely loop if there was no check.
+ if ([input baseType] == TouchBarInputBaseType::kPopover && [input children]) {
+ NSTouchBarItem* item = [self itemForIdentifier:identifier];
+ [(nsTouchBar*)[(NSPopoverTouchBarItem*)item popoverTouchBar] releaseJSObjects];
+ }
+
+ [input releaseJSObjects];
+ }
+}
+
+#pragma mark - NSSharingServicePickerTouchBarItemDelegate
+
+- (NSArray*)itemsForSharingServicePickerTouchBarItem:
+ (NSSharingServicePickerTouchBarItem*)aPickerTouchBarItem {
+ NSURL* urlToShare = nil;
+ NSString* titleToShare = @"";
+ nsAutoString url;
+ nsAutoString title;
+ if (mTouchBarHelper) {
+ nsresult rv = mTouchBarHelper->GetActiveUrl(url);
+ if (!NS_FAILED(rv)) {
+ urlToShare = [NSURL URLWithString:nsCocoaUtils::ToNSString(url)];
+ // NSURL URLWithString returns nil if the URL is invalid. At this point,
+ // it is too late to simply shut down the share menu, so we default to
+ // about:blank if the share button is clicked when the URL is invalid.
+ if (urlToShare == nil) {
+ urlToShare = [NSURL URLWithString:@"about:blank"];
+ }
+ }
+
+ rv = mTouchBarHelper->GetActiveTitle(title);
+ if (!NS_FAILED(rv)) {
+ titleToShare = nsCocoaUtils::ToNSString(title);
+ }
+ }
+
+ return @[ urlToShare, titleToShare ];
+}
+
+- (NSArray<NSSharingService*>*)sharingServicePicker:(NSSharingServicePicker*)aSharingServicePicker
+ sharingServicesForItems:(NSArray*)aItems
+ proposedSharingServices:(NSArray<NSSharingService*>*)aProposedServices {
+ // redundant services
+ NSArray* excludedServices = @[
+ @"com.apple.share.System.add-to-safari-reading-list",
+ ];
+
+ NSArray* sharingServices = [aProposedServices
+ filteredArrayUsingPredicate:[NSPredicate
+ predicateWithFormat:@"NOT (name IN %@)", excludedServices]];
+
+ return sharingServices;
+}
+
+@end
diff --git a/widget/cocoa/nsTouchBarInput.h b/widget/cocoa/nsTouchBarInput.h
new file mode 100644
index 0000000000..4853660d79
--- /dev/null
+++ b/widget/cocoa/nsTouchBarInput.h
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTouchBarInput_h_
+#define nsTouchBarInput_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsITouchBarInput.h"
+#include "nsTouchBarNativeAPIDefines.h"
+#include "nsCOMPtr.h"
+
+using namespace mozilla::dom;
+
+enum class TouchBarInputBaseType : uint8_t {
+ kButton,
+ kLabel,
+ kMainButton,
+ kPopover,
+ kScrollView,
+ kScrubber
+};
+
+class nsTouchBarInputIcon;
+
+/**
+ * NSObject representation of nsITouchBarInput.
+ */
+@interface TouchBarInput : NSObject {
+ nsCOMPtr<nsIURI> mImageURI;
+ RefPtr<nsTouchBarInputIcon> mIcon;
+ TouchBarInputBaseType mBaseType;
+ NSString* mType;
+ nsCOMPtr<nsITouchBarInputCallback> mCallback;
+ NSMutableArray<TouchBarInput*>* mChildren;
+}
+
+@property(strong) NSString* key;
+@property(strong) NSString* type;
+@property(strong) NSString* title;
+@property(strong) NSColor* color;
+@property(nonatomic, getter=isDisabled) BOOL disabled;
+
+- (nsCOMPtr<nsIURI>)imageURI;
+- (RefPtr<nsTouchBarInputIcon>)icon;
+- (TouchBarInputBaseType)baseType;
+- (NSTouchBarItemIdentifier)nativeIdentifier;
+- (nsCOMPtr<nsITouchBarInputCallback>)callback;
+- (NSMutableArray<TouchBarInput*>*)children;
+- (void)setImageURI:(nsCOMPtr<nsIURI>)aImageURI;
+- (void)setIcon:(RefPtr<nsTouchBarInputIcon>)aIcon;
+- (void)setCallback:(nsCOMPtr<nsITouchBarInputCallback>)aCallback;
+- (void)setChildren:(NSMutableArray<TouchBarInput*>*)aChildren;
+
+- (id)initWithKey:(NSString*)aKey
+ title:(NSString*)aTitle
+ imageURI:(nsCOMPtr<nsIURI>)aImageURI
+ type:(NSString*)aType
+ callback:(nsCOMPtr<nsITouchBarInputCallback>)aCallback
+ color:(uint32_t)aColor
+ disabled:(BOOL)aDisabled
+ children:(nsCOMPtr<nsIArray>)aChildren;
+
+- (TouchBarInput*)initWithXPCOM:(nsCOMPtr<nsITouchBarInput>)aInput;
+
+- (void)releaseJSObjects;
+
+- (void)dealloc;
+
+/**
+ * We make these helper methods static so that other classes can query a
+ * TouchBarInput's nativeIdentifier (e.g. nsTouchBarUpdater looking up a
+ * popover in mappedLayoutItems).
+ */
++ (NSTouchBarItemIdentifier)nativeIdentifierWithType:(NSString*)aType withKey:(NSString*)aKey;
++ (NSTouchBarItemIdentifier)nativeIdentifierWithXPCOM:(nsCOMPtr<nsITouchBarInput>)aInput;
+
+// Non-JS scrubber implemention for the Share Scrubber,
+// since it is defined by an Apple API.
++ (NSTouchBarItemIdentifier)shareScrubberIdentifier;
+
+// The search popover needs to show/hide depending on if the Urlbar is focused
+// when it is created. We keep track of its identifier to accommodate this
+// special handling.
++ (NSTouchBarItemIdentifier)searchPopoverIdentifier;
+
+@end
+
+#endif // nsTouchBarInput_h_
diff --git a/widget/cocoa/nsTouchBarInput.mm b/widget/cocoa/nsTouchBarInput.mm
new file mode 100644
index 0000000000..dc50c64e1b
--- /dev/null
+++ b/widget/cocoa/nsTouchBarInput.mm
@@ -0,0 +1,245 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsTouchBarInput.h"
+
+#include "mozilla/MacStringHelpers.h"
+#include "nsArrayUtils.h"
+#include "nsCocoaUtils.h"
+#include "nsTouchBar.h"
+#include "nsTouchBarInputIcon.h"
+
+@implementation TouchBarInput
+
+- (nsCOMPtr<nsIURI>)imageURI {
+ return mImageURI;
+}
+
+- (void)setImageURI:(nsCOMPtr<nsIURI>)aImageURI {
+ mImageURI = aImageURI;
+}
+
+- (RefPtr<nsTouchBarInputIcon>)icon {
+ return mIcon;
+}
+
+- (void)setIcon:(RefPtr<nsTouchBarInputIcon>)aIcon {
+ mIcon = aIcon;
+}
+
+- (TouchBarInputBaseType)baseType {
+ return mBaseType;
+}
+
+- (NSString*)type {
+ return mType;
+}
+
+- (void)setType:(NSString*)aType {
+ [aType retain];
+ [mType release];
+ if ([aType hasSuffix:@"button"]) {
+ mBaseType = TouchBarInputBaseType::kButton;
+ } else if ([aType hasSuffix:@"label"]) {
+ mBaseType = TouchBarInputBaseType::kLabel;
+ } else if ([aType hasSuffix:@"mainButton"]) {
+ mBaseType = TouchBarInputBaseType::kMainButton;
+ } else if ([aType hasSuffix:@"popover"]) {
+ mBaseType = TouchBarInputBaseType::kPopover;
+ } else if ([aType hasSuffix:@"scrollView"]) {
+ mBaseType = TouchBarInputBaseType::kScrollView;
+ } else if ([aType hasSuffix:@"scrubber"]) {
+ mBaseType = TouchBarInputBaseType::kScrubber;
+ }
+ mType = aType;
+}
+
+- (NSTouchBarItemIdentifier)nativeIdentifier {
+ return [TouchBarInput nativeIdentifierWithType:mType withKey:self.key];
+}
+
+- (nsCOMPtr<nsITouchBarInputCallback>)callback {
+ return mCallback;
+}
+
+- (void)setCallback:(nsCOMPtr<nsITouchBarInputCallback>)aCallback {
+ mCallback = aCallback;
+}
+
+- (NSMutableArray<TouchBarInput*>*)children {
+ return mChildren;
+}
+
+- (void)setChildren:(NSMutableArray<TouchBarInput*>*)aChildren {
+ [aChildren retain];
+ for (TouchBarInput* child in mChildren) {
+ [child releaseJSObjects];
+ }
+ [mChildren removeAllObjects];
+ [mChildren release];
+ mChildren = aChildren;
+}
+
+- (id)initWithKey:(NSString*)aKey
+ title:(NSString*)aTitle
+ imageURI:(nsCOMPtr<nsIURI>)aImageURI
+ type:(NSString*)aType
+ callback:(nsCOMPtr<nsITouchBarInputCallback>)aCallback
+ color:(uint32_t)aColor
+ disabled:(BOOL)aDisabled
+ children:(nsCOMPtr<nsIArray>)aChildren {
+ if (self = [super init]) {
+ mType = nil;
+
+ self.key = aKey;
+ self.title = aTitle;
+ self.type = aType;
+ self.disabled = aDisabled;
+ [self setImageURI:aImageURI];
+ [self setCallback:aCallback];
+ if (aColor) {
+ [self setColor:[NSColor colorWithDisplayP3Red:((aColor >> 16) & 0xFF) / 255.0
+ green:((aColor >> 8) & 0xFF) / 255.0
+ blue:((aColor)&0xFF) / 255.0
+ alpha:1.0]];
+ }
+ if (aChildren) {
+ uint32_t itemCount = 0;
+ aChildren->GetLength(&itemCount);
+ NSMutableArray* orderedChildren = [NSMutableArray arrayWithCapacity:itemCount];
+ for (uint32_t i = 0; i < itemCount; ++i) {
+ nsCOMPtr<nsITouchBarInput> child = do_QueryElementAt(aChildren, i);
+ if (!child) {
+ continue;
+ }
+ TouchBarInput* convertedChild = [[TouchBarInput alloc] initWithXPCOM:child];
+ if (convertedChild) {
+ orderedChildren[i] = convertedChild;
+ }
+ }
+ [self setChildren:orderedChildren];
+ }
+ }
+
+ return self;
+}
+
+- (TouchBarInput*)initWithXPCOM:(nsCOMPtr<nsITouchBarInput>)aInput {
+ nsAutoString keyStr;
+ nsresult rv = aInput->GetKey(keyStr);
+ if (NS_FAILED(rv)) {
+ return nil;
+ }
+
+ nsAutoString titleStr;
+ rv = aInput->GetTitle(titleStr);
+ if (NS_FAILED(rv)) {
+ return nil;
+ }
+
+ nsCOMPtr<nsIURI> imageURI;
+ rv = aInput->GetImage(getter_AddRefs(imageURI));
+ if (NS_FAILED(rv)) {
+ return nil;
+ }
+
+ nsAutoString typeStr;
+ rv = aInput->GetType(typeStr);
+ if (NS_FAILED(rv)) {
+ return nil;
+ }
+
+ nsCOMPtr<nsITouchBarInputCallback> callback;
+ rv = aInput->GetCallback(getter_AddRefs(callback));
+ if (NS_FAILED(rv)) {
+ return nil;
+ }
+
+ uint32_t colorInt;
+ rv = aInput->GetColor(&colorInt);
+ if (NS_FAILED(rv)) {
+ return nil;
+ }
+
+ bool disabled = false;
+ rv = aInput->GetDisabled(&disabled);
+ if (NS_FAILED(rv)) {
+ return nil;
+ }
+
+ nsCOMPtr<nsIArray> children;
+ rv = aInput->GetChildren(getter_AddRefs(children));
+ if (NS_FAILED(rv)) {
+ return nil;
+ }
+
+ return [self initWithKey:nsCocoaUtils::ToNSString(keyStr)
+ title:nsCocoaUtils::ToNSString(titleStr)
+ imageURI:imageURI
+ type:nsCocoaUtils::ToNSString(typeStr)
+ callback:callback
+ color:colorInt
+ disabled:(BOOL)disabled
+ children:children];
+}
+
+- (void)releaseJSObjects {
+ if (mIcon) {
+ mIcon->Destroy();
+ mIcon = nil;
+ }
+ [self setCallback:nil];
+ [self setImageURI:nil];
+ for (TouchBarInput* child in mChildren) {
+ [child releaseJSObjects];
+ }
+}
+
+- (void)dealloc {
+ if (mIcon) {
+ mIcon->Destroy();
+ mIcon = nil;
+ }
+ [mType release];
+ [mChildren removeAllObjects];
+ [mChildren release];
+ [super dealloc];
+}
+
++ (NSTouchBarItemIdentifier)nativeIdentifierWithType:(NSString*)aType withKey:(NSString*)aKey {
+ NSTouchBarItemIdentifier identifier;
+ identifier = [kTouchBarBaseIdentifier stringByAppendingPathExtension:aType];
+ if (aKey) {
+ identifier = [identifier stringByAppendingPathExtension:aKey];
+ }
+ return identifier;
+}
+
++ (NSTouchBarItemIdentifier)nativeIdentifierWithXPCOM:(nsCOMPtr<nsITouchBarInput>)aInput {
+ nsAutoString keyStr;
+ nsresult rv = aInput->GetKey(keyStr);
+ if (NS_FAILED(rv)) {
+ return nil;
+ }
+ NSString* key = nsCocoaUtils::ToNSString(keyStr);
+
+ nsAutoString typeStr;
+ rv = aInput->GetType(typeStr);
+ if (NS_FAILED(rv)) {
+ return nil;
+ }
+ NSString* type = nsCocoaUtils::ToNSString(typeStr);
+
+ return [TouchBarInput nativeIdentifierWithType:type withKey:key];
+}
+
++ (NSTouchBarItemIdentifier)shareScrubberIdentifier {
+ return [TouchBarInput nativeIdentifierWithType:@"scrubber" withKey:@"share"];
+}
+
++ (NSTouchBarItemIdentifier)searchPopoverIdentifier {
+ return [TouchBarInput nativeIdentifierWithType:@"popover" withKey:@"search-popover"];
+}
+
+@end
diff --git a/widget/cocoa/nsTouchBarInputIcon.h b/widget/cocoa/nsTouchBarInputIcon.h
new file mode 100644
index 0000000000..719e743598
--- /dev/null
+++ b/widget/cocoa/nsTouchBarInputIcon.h
@@ -0,0 +1,71 @@
+/* -*- 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 on the macOS Touch Bar.
+ */
+
+#ifndef nsTouchBarInputIcon_h_
+#define nsTouchBarInputIcon_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsTouchBarInput.h"
+#include "nsTouchBarNativeAPIDefines.h"
+#include "IconLoaderHelperCocoa.h"
+
+using namespace mozilla::dom;
+
+class nsIURI;
+class nsIPrincipal;
+class imgRequestProxy;
+
+namespace mozilla::dom {
+class Document;
+}
+
+class nsTouchBarInputIcon : public mozilla::widget::IconLoaderListenerCocoa {
+ public:
+ explicit nsTouchBarInputIcon(RefPtr<Document> aDocument,
+ TouchBarInput* aInput, NSTouchBarItem* aItem);
+
+ private:
+ virtual ~nsTouchBarInputIcon();
+
+ public:
+ // 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(nsCOMPtr<nsIURI> aIconURI);
+
+ // Implements this method for mozilla::widget::IconLoaderListenerCocoa.
+ // Called once the icon load is complete.
+ nsresult OnComplete();
+
+ // Unless we take precautions, we may outlive the object that created us
+ // (mTouchBar, which owns our native menu item (mTouchBarInput)).
+ // Destroy() should be called from mTouchBar's destructor to prevent
+ // this from happening.
+ void Destroy();
+
+ void ReleaseJSObjects();
+
+ protected:
+ RefPtr<Document> mDocument;
+ nsIntRect mImageRegionRect;
+ bool mSetIcon;
+ NSButton* mButton;
+ // We accept a mShareScrubber only as a special case since
+ // NSSharingServicePickerTouchBarItem does not expose an NSButton* on which we
+ // can set the `image` property.
+ NSSharingServicePickerTouchBarItem* mShareScrubber;
+ // We accept a popover only as a special case.
+ NSPopoverTouchBarItem* mPopoverItem;
+ // The icon loader object should never outlive its creating
+ // nsTouchBarInputIcon object.
+ RefPtr<mozilla::widget::IconLoader> mIconLoader;
+ RefPtr<mozilla::widget::IconLoaderHelperCocoa> mIconLoaderHelper;
+};
+
+#endif // nsTouchBarInputIcon_h_
diff --git a/widget/cocoa/nsTouchBarInputIcon.mm b/widget/cocoa/nsTouchBarInputIcon.mm
new file mode 100644
index 0000000000..0f91c59a9b
--- /dev/null
+++ b/widget/cocoa/nsTouchBarInputIcon.mm
@@ -0,0 +1,138 @@
+/* -*- 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 on the macOS Touch Bar.
+ */
+
+#include "nsTouchBarInputIcon.h"
+
+#include "mozilla/dom/Document.h"
+#include "nsCocoaUtils.h"
+#include "nsComputedDOMStyle.h"
+#include "nsContentUtils.h"
+#include "nsGkAtoms.h"
+#include "nsINode.h"
+#include "nsNameSpaceManager.h"
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+using mozilla::widget::IconLoader;
+using mozilla::widget::IconLoaderHelperCocoa;
+
+static const uint32_t kIconSize = 16;
+static const CGFloat kHiDPIScalingFactor = 2.0f;
+
+nsTouchBarInputIcon::nsTouchBarInputIcon(RefPtr<Document> aDocument, TouchBarInput* aInput,
+ NSTouchBarItem* aItem)
+ : mDocument(aDocument), mSetIcon(false), mButton(nil), mShareScrubber(nil), mPopoverItem(nil) {
+ if ([[aInput nativeIdentifier] isEqualToString:[TouchBarInput shareScrubberIdentifier]]) {
+ mShareScrubber = (NSSharingServicePickerTouchBarItem*)aItem;
+ } else if ([aInput baseType] == TouchBarInputBaseType::kPopover) {
+ mPopoverItem = (NSPopoverTouchBarItem*)aItem;
+ } else if ([aInput baseType] == TouchBarInputBaseType::kButton ||
+ [aInput baseType] == TouchBarInputBaseType::kMainButton) {
+ mButton = (NSButton*)[aItem view];
+ } else {
+ NS_ERROR("Incompatible Touch Bar input passed to nsTouchBarInputIcon.");
+ }
+ aInput = nil;
+ MOZ_COUNT_CTOR(nsTouchBarInputIcon);
+}
+
+nsTouchBarInputIcon::~nsTouchBarInputIcon() {
+ Destroy();
+ MOZ_COUNT_DTOR(nsTouchBarInputIcon);
+}
+
+// Called from nsTouchBar's destructor, to prevent us from outliving it
+// (as might otherwise happen if calls to our imgINotificationObserver methods
+// are still outstanding). nsTouchBar owns our mTouchBarInput.
+void nsTouchBarInputIcon::Destroy() {
+ ReleaseJSObjects();
+ if (mIconLoader) {
+ mIconLoader = nullptr;
+ }
+ if (mIconLoaderHelper) {
+ mIconLoaderHelper = nullptr;
+ }
+
+ mButton = nil;
+ mShareScrubber = nil;
+ mPopoverItem = nil;
+}
+
+nsresult nsTouchBarInputIcon::SetupIcon(nsCOMPtr<nsIURI> aIconURI) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // We might not have a document if the Touch Bar tries to update when the main
+ // window is closed.
+ if (!mDocument) {
+ return NS_OK;
+ }
+
+ if (!(mButton || mShareScrubber || mPopoverItem)) {
+ NS_ERROR("No Touch Bar input provided.");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mIconLoader) {
+ // We ask only for the HiDPI images since all Touch Bars are Retina
+ // displays and we have no need for icons @1x.
+ mIconLoaderHelper = new IconLoaderHelperCocoa(this, kIconSize, kIconSize, kHiDPIScalingFactor);
+ mIconLoader = new IconLoader(mIconLoaderHelper, mDocument, mImageRegionRect);
+ if (!mIconLoader) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ if (!mSetIcon) {
+ // Load placeholder icon.
+ [mButton setImage:mIconLoaderHelper->GetNativeIconImage()];
+ [mShareScrubber setButtonImage:mIconLoaderHelper->GetNativeIconImage()];
+ [mPopoverItem setCollapsedRepresentationImage:mIconLoaderHelper->GetNativeIconImage()];
+ }
+
+ nsresult rv = mIconLoader->LoadIcon(aIconURI, true /* aIsInternalIcon */);
+ 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.
+ [mButton setImage:nil];
+ [mShareScrubber setButtonImage:nil];
+ [mPopoverItem setCollapsedRepresentationImage:nil];
+ }
+
+ mSetIcon = true;
+
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsTouchBarInputIcon::ReleaseJSObjects() {
+ if (mIconLoader) {
+ mIconLoader->ReleaseJSObjects();
+ }
+ mDocument = nil;
+}
+
+//
+// mozilla::widget::IconLoaderListenerCocoa
+//
+
+nsresult nsTouchBarInputIcon::OnComplete() {
+ if (!mIconLoaderHelper) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSImage* image = mIconLoaderHelper->GetNativeIconImage();
+ [mButton setImage:image];
+ [mShareScrubber setButtonImage:image];
+ [mPopoverItem setCollapsedRepresentationImage:image];
+
+ mIconLoaderHelper->Destroy();
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsTouchBarNativeAPIDefines.h b/widget/cocoa/nsTouchBarNativeAPIDefines.h
new file mode 100644
index 0000000000..9e0b689a7d
--- /dev/null
+++ b/widget/cocoa/nsTouchBarNativeAPIDefines.h
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsTouchBarNativeAPIDefines_h
+#define nsTouchBarNativeAPIDefines_h
+
+#import <Cocoa/Cocoa.h>
+
+#if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
+@interface NSButton (NewConstructors)
+@property(nonatomic) BOOL imageHugsTitle;
++ (NSButton*)buttonWithTitle:(NSString*)title target:(id)target action:(SEL)action;
+@end
+
+@interface NSColor (DisplayP3Colors)
++ (NSColor*)colorWithDisplayP3Red:(CGFloat)red
+ green:(CGFloat)green
+ blue:(CGFloat)blue
+ alpha:(CGFloat)alpha;
+@end
+
+@interface NSTextField (NewConstructors)
++ (NSTextField*)labelWithString:(NSString*)stringValue;
+@end
+#endif // !defined(MAC_OS_X_VERSION_10_12)
+
+#if !defined(MAC_OS_X_VERSION_10_12_2) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12_2
+@interface NSApplication (TouchBarMenu)
+- (IBAction)toggleTouchBarCustomizationPalette:(id)sender;
+@end
+
+typedef NSString* NSTouchBarItemIdentifier;
+__attribute__((weak_import)) @interface NSTouchBarItem : NSObject
+@property(readonly) NSView* view;
+@property(readonly) NSString* customizationLabel;
+- (instancetype)initWithIdentifier:(NSTouchBarItemIdentifier)aIdentifier;
+@end
+
+@protocol NSSharingServicePickerTouchBarItemDelegate
+@end
+
+__attribute__((weak_import)) @interface NSSharingServicePickerTouchBarItem : NSTouchBarItem
+@property(strong) id<NSSharingServicePickerTouchBarItemDelegate> delegate;
+@property(strong) NSImage* buttonImage;
+@end
+
+__attribute__((weak_import)) @interface NSCustomTouchBarItem : NSTouchBarItem
+@property(strong) NSView* view;
+@property(strong) NSString* customizationLabel;
+@end
+
+@protocol NSTouchBarDelegate
+@end
+
+typedef NSString* NSTouchBarCustomizationIdentifier;
+__attribute__((weak_import)) @interface NSTouchBar : NSObject
+@property(strong) NSArray<NSTouchBarItemIdentifier>* defaultItemIdentifiers;
+@property(strong) id<NSTouchBarDelegate> delegate;
+@property(strong) NSTouchBarCustomizationIdentifier customizationIdentifier;
+@property(strong) NSArray<NSTouchBarItemIdentifier>* customizationAllowedItemIdentifiers;
+- (NSTouchBarItem*)itemForIdentifier:(NSTouchBarItemIdentifier)aIdentifier;
+@end
+
+__attribute__((weak_import)) @interface NSPopoverTouchBarItem : NSTouchBarItem
+@property(strong) NSString* customizationLabel;
+@property(strong) NSView* collapsedRepresentation;
+@property(strong) NSImage* collapsedRepresentationImage;
+@property(strong) NSString* collapsedRepresentationLabel;
+@property(strong) NSTouchBar* popoverTouchBar;
+@property BOOL showsCloseButton;
+- (void)showPopover:(id)sender;
+- (void)dismissPopover:(id)sender;
+@end
+
+@interface NSButton (TouchBarButton)
+@property(strong) NSColor* bezelColor;
+@end
+#endif // !defined(MAC_OS_X_VERSION_10_12_2)
+#endif // nsTouchBarNativeAPIDefines_h
diff --git a/widget/cocoa/nsTouchBarUpdater.h b/widget/cocoa/nsTouchBarUpdater.h
new file mode 100644
index 0000000000..38039f69a0
--- /dev/null
+++ b/widget/cocoa/nsTouchBarUpdater.h
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsTouchBarUpdater_h_
+#define nsTouchBarUpdater_h_
+
+#include "nsITouchBarUpdater.h"
+#include "nsCocoaWindow.h"
+
+class nsTouchBarUpdater : public nsITouchBarUpdater {
+ public:
+ nsTouchBarUpdater() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITOUCHBARUPDATER
+
+ protected:
+ virtual ~nsTouchBarUpdater() {}
+ BaseWindow* GetCocoaWindow(nsIBaseWindow* aWindow);
+};
+
+#endif // nsTouchBarUpdater_h_
diff --git a/widget/cocoa/nsTouchBarUpdater.mm b/widget/cocoa/nsTouchBarUpdater.mm
new file mode 100644
index 0000000000..6b4b25f992
--- /dev/null
+++ b/widget/cocoa/nsTouchBarUpdater.mm
@@ -0,0 +1,116 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsTouchBar.h"
+#include "nsTouchBarInput.h"
+#include "nsTouchBarUpdater.h"
+#include "nsTouchBarNativeAPIDefines.h"
+
+#include "nsIBaseWindow.h"
+#include "nsIWidget.h"
+
+// defined in nsCocoaWindow.mm.
+extern BOOL sTouchBarIsInitialized;
+
+#if !defined(MAC_OS_X_VERSION_10_12_2) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12_2
+@interface BaseWindow (NSTouchBarProvider)
+@property(strong) NSTouchBar* touchBar;
+@end
+#endif
+
+NS_IMPL_ISUPPORTS(nsTouchBarUpdater, nsITouchBarUpdater);
+
+NS_IMETHODIMP
+nsTouchBarUpdater::UpdateTouchBarInputs(nsIBaseWindow* aWindow,
+ const nsTArray<RefPtr<nsITouchBarInput>>& aInputs) {
+ if (!sTouchBarIsInitialized || !aWindow) {
+ return NS_OK;
+ }
+
+ BaseWindow* cocoaWin = nsTouchBarUpdater::GetCocoaWindow(aWindow);
+ if (!cocoaWin) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if ([cocoaWin respondsToSelector:@selector(touchBar)]) {
+ size_t itemCount = aInputs.Length();
+ for (size_t i = 0; i < itemCount; ++i) {
+ nsCOMPtr<nsITouchBarInput> input(aInputs.ElementAt(i));
+ if (!input) {
+ continue;
+ }
+
+ NSTouchBarItemIdentifier newIdentifier = [TouchBarInput nativeIdentifierWithXPCOM:input];
+ // We don't support updating the Share scrubber since it's a special
+ // Apple-made component that behaves differently from the other inputs.
+ if ([newIdentifier isEqualToString:[TouchBarInput nativeIdentifierWithType:@"scrubber"
+ withKey:@"share"]]) {
+ continue;
+ }
+
+ TouchBarInput* convertedInput = [[TouchBarInput alloc] initWithXPCOM:input];
+ [(nsTouchBar*)cocoaWin.touchBar updateItem:convertedInput];
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTouchBarUpdater::ShowPopover(nsIBaseWindow* aWindow, nsITouchBarInput* aPopover, bool aShowing) {
+ if (!sTouchBarIsInitialized || !aPopover || !aWindow) {
+ return NS_OK;
+ }
+
+ BaseWindow* cocoaWin = nsTouchBarUpdater::GetCocoaWindow(aWindow);
+ if (!cocoaWin) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if ([cocoaWin respondsToSelector:@selector(touchBar)]) {
+ // We don't need to completely reinitialize the popover. We only need its
+ // identifier to look it up in [nsTouchBar mappedLayoutItems].
+ NSTouchBarItemIdentifier popoverIdentifier = [TouchBarInput nativeIdentifierWithXPCOM:aPopover];
+
+ TouchBarInput* popoverItem =
+ [[(nsTouchBar*)cocoaWin.touchBar mappedLayoutItems] objectForKey:popoverIdentifier];
+
+ [(nsTouchBar*)cocoaWin.touchBar showPopover:popoverItem showing:aShowing];
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTouchBarUpdater::EnterCustomizeMode() {
+ [NSApp toggleTouchBarCustomizationPalette:(id)this];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTouchBarUpdater::IsTouchBarInitialized(bool* aResult) {
+ *aResult = sTouchBarIsInitialized;
+ return NS_OK;
+}
+
+BaseWindow* nsTouchBarUpdater::GetCocoaWindow(nsIBaseWindow* aWindow) {
+ nsCOMPtr<nsIWidget> widget = nullptr;
+ aWindow->GetMainWidget(getter_AddRefs(widget));
+ if (!widget) {
+ return nil;
+ }
+ BaseWindow* cocoaWin = (BaseWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
+ if (!cocoaWin) {
+ return nil;
+ }
+ return cocoaWin;
+}
+
+// NOTE: This method is for internal unit tests only.
+NS_IMETHODIMP
+nsTouchBarUpdater::SetTouchBarInitialized(bool aIsInitialized) {
+ sTouchBarIsInitialized = aIsInitialized;
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsUserIdleServiceX.h b/widget/cocoa/nsUserIdleServiceX.h
new file mode 100644
index 0000000000..43efbb2591
--- /dev/null
+++ b/widget/cocoa/nsUserIdleServiceX.h
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsUserIdleServiceX_h_
+#define nsUserIdleServiceX_h_
+
+#include "nsUserIdleService.h"
+
+class nsUserIdleServiceX : public nsUserIdleService {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsUserIdleServiceX, nsUserIdleService)
+
+ bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsUserIdleServiceX> GetInstance() {
+ RefPtr<nsUserIdleService> idleService = nsUserIdleService::GetInstance();
+ if (!idleService) {
+ idleService = new nsUserIdleServiceX();
+ }
+
+ return idleService.forget().downcast<nsUserIdleServiceX>();
+ }
+
+ protected:
+ nsUserIdleServiceX() {}
+ virtual ~nsUserIdleServiceX() {}
+ bool UsePollMode() override;
+};
+
+#endif // nsUserIdleServiceX_h_
diff --git a/widget/cocoa/nsUserIdleServiceX.mm b/widget/cocoa/nsUserIdleServiceX.mm
new file mode 100644
index 0000000000..28ef7b2b95
--- /dev/null
+++ b/widget/cocoa/nsUserIdleServiceX.mm
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsUserIdleServiceX.h"
+#include "nsObjCExceptions.h"
+#import <Foundation/Foundation.h>
+
+bool nsUserIdleServiceX::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 length:sizeof(time)];
+ else
+ time = [idleObj unsignedLongLongValue];
+
+ IOObjectRelease(entry);
+
+ // convert to ms from ns
+ time /= 1000000;
+ if (time > UINT32_MAX) // Overflow will occur
+ return false;
+
+ *aIdleTime = static_cast<uint32_t>(time);
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+bool nsUserIdleServiceX::UsePollMode() { return true; }
diff --git a/widget/cocoa/nsWidgetFactory.mm b/widget/cocoa/nsWidgetFactory.mm
new file mode 100644
index 0000000000..590227f93e
--- /dev/null
+++ b/widget/cocoa/nsWidgetFactory.mm
@@ -0,0 +1,213 @@
+/* -*- 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 "nsISupports.h"
+#include "mozilla/Components.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/WidgetUtils.h"
+
+#include "nsWidgetsCID.h"
+
+#include "nsChildView.h"
+#include "nsAppShell.h"
+#include "nsAppShellSingleton.h"
+#include "nsFilePicker.h"
+#include "nsColorPicker.h"
+
+#include "nsClipboard.h"
+#include "nsClipboardHelper.h"
+#include "HeadlessClipboard.h"
+#include "gfxPlatform.h"
+#include "nsTransferable.h"
+#include "nsHTMLFormatConverter.h"
+#include "nsDragService.h"
+#include "nsToolkit.h"
+
+#include "nsLookAndFeel.h"
+
+#include "nsSound.h"
+#include "nsUserIdleServiceX.h"
+#include "NativeKeyBindings.h"
+#include "OSXNotificationCenter.h"
+
+#include "nsDeviceContextSpecX.h"
+#include "nsPrinterListCUPS.h"
+#include "nsPrintSettingsServiceX.h"
+#include "nsPrintDialogX.h"
+#include "nsPrintSession.h"
+#include "nsToolkitCompsCID.h"
+
+#include "mozilla/widget/ScreenManager.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+NS_IMPL_COMPONENT_FACTORY(nsIClipboard) {
+ nsCOMPtr<nsIClipboard> inst;
+ if (gfxPlatform::IsHeadless()) {
+ inst = new HeadlessClipboard();
+ } else {
+ inst = new nsClipboard();
+ }
+
+ return inst.forget();
+}
+
+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(nsClipboardHelper)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDragService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecX)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsPrinterListCUPS)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSettingsServiceX, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintDialogServiceX, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsUserIdleServiceX, nsUserIdleServiceX::GetInstance)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(ScreenManager, ScreenManager::GetAddRefedSingleton)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(OSXNotificationCenter, Init)
+
+#include "nsMenuBarX.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNativeMenuServiceX)
+
+#include "nsMacDockSupport.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacDockSupport)
+
+#include "nsMacFinderProgress.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacFinderProgress)
+
+#include "nsMacSharingService.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacSharingService)
+
+#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 "nsTouchBarUpdater.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTouchBarUpdater)
+
+#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_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_CLIPBOARDHELPER_CID);
+NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTER_LIST_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_MACFINDERPROGRESS_CID);
+NS_DEFINE_NAMED_CID(NS_MACSHARINGSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_MACWEBAPPUTILS_CID);
+NS_DEFINE_NAMED_CID(NS_STANDALONENATIVEMENU_CID);
+NS_DEFINE_NAMED_CID(NS_SYSTEMSTATUSBAR_CID);
+NS_DEFINE_NAMED_CID(NS_TOUCHBARUPDATER_CID);
+NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ {&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_RDD_VR_AND_SOCKET_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_CLIPBOARDHELPER_CID, false, NULL, nsClipboardHelperConstructor},
+ {&kNS_DRAGSERVICE_CID, false, NULL, nsDragServiceConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY},
+ {&kNS_SCREENMANAGER_CID, false, NULL, ScreenManagerConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY},
+ {&kNS_DEVICE_CONTEXT_SPEC_CID, false, NULL, nsDeviceContextSpecXConstructor},
+ {&kNS_PRINTER_LIST_CID, false, NULL, nsPrinterListCUPSConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY},
+ {&kNS_PRINTSESSION_CID, false, NULL, nsPrintSessionConstructor},
+ {&kNS_PRINTSETTINGSSERVICE_CID, false, NULL, nsPrintSettingsServiceXConstructor},
+ {&kNS_PRINTDIALOGSERVICE_CID, false, NULL, nsPrintDialogServiceXConstructor},
+ {&kNS_IDLE_SERVICE_CID, false, NULL, nsUserIdleServiceXConstructor},
+ {&kNS_SYSTEMALERTSSERVICE_CID, false, NULL, OSXNotificationCenterConstructor},
+ {&kNS_NATIVEMENUSERVICE_CID, false, NULL, nsNativeMenuServiceXConstructor},
+ {&kNS_MACDOCKSUPPORT_CID, false, NULL, nsMacDockSupportConstructor},
+ {&kNS_MACFINDERPROGRESS_CID, false, NULL, nsMacFinderProgressConstructor},
+ {&kNS_MACSHARINGSERVICE_CID, false, NULL, nsMacSharingServiceConstructor},
+ {&kNS_MACWEBAPPUTILS_CID, false, NULL, nsMacWebAppUtilsConstructor},
+ {&kNS_STANDALONENATIVEMENU_CID, false, NULL, nsStandaloneNativeMenuConstructor},
+ {&kNS_SYSTEMSTATUSBAR_CID, false, NULL, nsSystemStatusBarCocoaConstructor},
+ {&kNS_TOUCHBARUPDATER_CID, false, NULL, nsTouchBarUpdaterConstructor},
+ {&kNS_GFXINFO_CID, false, NULL, mozilla::widget::GfxInfoConstructor},
+ {NULL}};
+
+static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
+ {"@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_RDD_VR_AND_SOCKET_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/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID},
+ {"@mozilla.org/widget/dragservice;1", &kNS_DRAGSERVICE_CID, mozilla::Module::MAIN_PROCESS_ONLY},
+ {"@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/printerlist;1", &kNS_PRINTER_LIST_CID, mozilla::Module::MAIN_PROCESS_ONLY},
+ {"@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/useridleservice;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/macfinderprogress;1", &kNS_MACFINDERPROGRESS_CID},
+ {"@mozilla.org/widget/macsharingservice;1", &kNS_MACSHARINGSERVICE_CID},
+ {"@mozilla.org/widget/mac-web-app-utils;1", &kNS_MACWEBAPPUTILS_CID},
+ {"@mozilla.org/widget/standalonenativemenu;1", &kNS_STANDALONENATIVEMENU_CID},
+ {"@mozilla.org/widget/systemstatusbar;1", &kNS_SYSTEMSTATUSBAR_CID},
+ {"@mozilla.org/widget/touchbarupdater;1", &kNS_TOUCHBARUPDATER_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();
+}
+
+extern const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion,
+ kWidgetCIDs,
+ kWidgetContracts,
+ NULL,
+ NULL,
+ nsAppShellInit,
+ nsWidgetCocoaModuleDtor,
+ mozilla::Module::ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS};
diff --git a/widget/cocoa/nsWindowMap.h b/widget/cocoa/nsWindowMap.h
new file mode 100644
index 0000000000..fe7a2259d1
--- /dev/null
+++ b/widget/cocoa/nsWindowMap.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWindowMap_h_
+#define nsWindowMap_h_
+
+#import <Cocoa/Cocoa.h>
+
+// WindowDataMap
+//
+// In both mozilla and embedding apps, we need to have a place to put
+// per-top-level-window logic and data, to handle such things as IME
+// commit when the window gains/loses focus. We can't use a window
+// delegate, because an embeddor probably already has one. Nor can we
+// subclass NSWindow, again because we can't impose that burden on the
+// embeddor.
+//
+// So we have a global map of NSWindow -> TopLevelWindowData, and set
+// up TopLevelWindowData as a notification observer etc.
+
+@interface WindowDataMap : NSObject {
+ @private
+ NSMutableDictionary* mWindowMap; // dict of TopLevelWindowData keyed by address of NSWindow
+}
+
++ (WindowDataMap*)sharedWindowDataMap;
+
+- (void)ensureDataForWindow:(NSWindow*)inWindow;
+- (id)dataForWindow:(NSWindow*)inWindow;
+
+// set data for a given window. inData is retained (and any previously set data
+// is released).
+- (void)setData:(id)inData forWindow:(NSWindow*)inWindow;
+
+// remove the data for the given window. the data is released.
+- (void)removeDataForWindow:(NSWindow*)inWindow;
+
+@end
+
+@class ChildView;
+
+// TopLevelWindowData
+//
+// Class to hold per-window data, and handle window state changes.
+
+@interface TopLevelWindowData : NSObject {
+ @private
+}
+
+- (id)initWithWindow:(NSWindow*)inWindow;
++ (void)activateInWindow:(NSWindow*)aWindow;
++ (void)deactivateInWindow:(NSWindow*)aWindow;
++ (void)activateInWindowViews:(NSWindow*)aWindow;
++ (void)deactivateInWindowViews:(NSWindow*)aWindow;
+
+@end
+
+#endif // nsWindowMap_h_
diff --git a/widget/cocoa/nsWindowMap.mm b/widget/cocoa/nsWindowMap.mm
new file mode 100644
index 0000000000..b1c4cf3494
--- /dev/null
+++ b/widget/cocoa/nsWindowMap.mm
@@ -0,0 +1,280 @@
+/* -*- 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];
+ }
+}
+
+- (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];
+ }
+}
+
+// The appearance of a top-level window depends on its main state (not its key
+// state). So (for non-embedders) we need to ensure that a top-level window
+// is main when an NS_ACTIVATE event is sent to Gecko for it.
+- (void)windowBecameMain:(NSNotification*)inNotification {
+ NSWindow* window = (NSWindow*)[inNotification object];
+
+ id delegate = [window delegate];
+ // Don't send events to a top-level window that has a sheet open above it --
+ // as far as Gecko is concerned, it's inactive, and stays so until the sheet
+ // closes.
+ if (delegate && [delegate isKindOfClass:[WindowDelegate class]] && ![window attachedSheet])
+ [TopLevelWindowData activateInWindow:window];
+}
+
+- (void)windowResignedMain:(NSNotification*)inNotification {
+ NSWindow* window = (NSWindow*)[inNotification object];
+
+ id delegate = [window delegate];
+ if (delegate && [delegate isKindOfClass:[WindowDelegate class]] && ![window attachedSheet])
+ [TopLevelWindowData deactivateInWindow:window];
+}
+
+- (void)windowWillClose:(NSNotification*)inNotification {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // postpone our destruction
+ [[self retain] autorelease];
+
+ // remove ourselves from the window map (which owns us)
+ [[WindowDataMap sharedWindowDataMap] removeDataForWindow:[inNotification object]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
diff --git a/widget/cocoa/resources/MainMenu.nib/classes.nib b/widget/cocoa/resources/MainMenu.nib/classes.nib
new file mode 100644
index 0000000000..b9b4b09f6b
--- /dev/null
+++ b/widget/cocoa/resources/MainMenu.nib/classes.nib
@@ -0,0 +1,4 @@
+{
+ IBClasses = ({CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; });
+ IBVersion = 1;
+} \ No newline at end of file
diff --git a/widget/cocoa/resources/MainMenu.nib/info.nib b/widget/cocoa/resources/MainMenu.nib/info.nib
new file mode 100644
index 0000000000..bcf3ace841
--- /dev/null
+++ b/widget/cocoa/resources/MainMenu.nib/info.nib
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBDocumentLocation</key>
+ <string>159 127 356 240 0 0 1920 1178 </string>
+ <key>IBEditorPositions</key>
+ <dict>
+ <key>29</key>
+ <string>413 971 130 44 0 0 1920 1178 </string>
+ </dict>
+ <key>IBFramework Version</key>
+ <string>443.0</string>
+ <key>IBOpenObjects</key>
+ <array>
+ <integer>29</integer>
+ </array>
+ <key>IBSystem Version</key>
+ <string>8F46</string>
+</dict>
+</plist>
diff --git a/widget/cocoa/resources/MainMenu.nib/keyedobjects.nib b/widget/cocoa/resources/MainMenu.nib/keyedobjects.nib
new file mode 100644
index 0000000000..16b3f7e523
--- /dev/null
+++ b/widget/cocoa/resources/MainMenu.nib/keyedobjects.nib
Binary files differ
diff --git a/widget/crashtests/1128214.html b/widget/crashtests/1128214.html
new file mode 100644
index 0000000000..749871c9e6
--- /dev/null
+++ b/widget/crashtests/1128214.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 1128214</title>
+ <style type="text/css">
+ html,body {
+ color:black;
+ background-color:white;
+ font-size:16px;
+ padding:10px;
+ margin:0;
+ }
+ </style>
+</head>
+<body>
+<div style="-moz-appearance:-moz-window-button-close; background-color: #ffd800; width:20px; height:20px;"></div>
+</body>
+</html>
diff --git a/widget/crashtests/303901-1.html b/widget/crashtests/303901-1.html
new file mode 100644
index 0000000000..51511ba182
--- /dev/null
+++ b/widget/crashtests/303901-1.html
@@ -0,0 +1,29 @@
+<HEAD>
+<IFRAME SRC=? SRC=808080 MARGINHEIGHT=https: WIDTH=808080 MARGINWIDTH=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.html onLoad=* HEIGHT=
+ MARGINWIDTH=$ SRC=& ADDRESS=^ onLoad=.gif SRC=ffff ADDRESS=? FRAMEBORDER=0xfffffff STYLE=QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ.rdf MARGINHEIGHT=? MARGINHEIGHT=89000 NAME=808080 WIDTH=-1 SCROLLING=chrome: >
+<TD onLoad=chrome: STYLE=22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222.jpg ALIGN=$ ROWSPAN=.html VALIGN=https:iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii VALIGN=127 STYLE=about: onLoad=chrome: STYLE=7897 onLoad=" COLSPAN=90928345 COLSPAN=.bmp ROWSPAN=7897 BGCOLOR=ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt.jpg VALIGN=^ ALIGN=89000 VALIGN=7897 ROWSPAN=* ALIGN=? ROWSPAN=% >
+<FORM SCRIPT=90928345 METHOD=nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn.png STYLE=-1 SCRIPT=about:&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& ACTION=.xul TARGET=& SCRIPT=.rdf TARGET=A TARGET=%n%n%n%n%n%n%n%n%n%n%n%n TARGET=\ TARGET=-1 ACTION=VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV.rdf onLoad=about:OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO METHOD=74326794236234 TARGET=-1 STYLE=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.xul METHOD=" ACTION=0xfffffff SCRIPT=* METHOD== >
+<OPTION STYLE=
+ VALUE=-1 onLoad=89000 SHAPE=90928345 SHAPE=HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH.xpt VALUE=https: onLoad=https: STYLE=.txt STYLE=tttttttttttttttttttttt.xbm SHAPE=" VALUE=%n%n%n%n%n%n%n%n%n%n%n%n SHAPE=%n%n%n%n%n%n%n%n%n%n%n%n STYLE=chrome: onLoad== VALUE=.ico onLoad=" VALUE=74326794236234 SHAPE=A onLoad=89000 STYLE=%s%n%s%n%s%n%s%n%s%n%s%n >
+<TFOOT VALIGN=? BGCOLOR=-1 VALIGN== VALIGN=about: ALIGN=0xfffffff BGCOLOR=127 COLSPAN=0 BGCOLOR=& COLSPAN=A ROWSPAN=file: VALIGN=0 onLoad=: STYLE=: VALIGN=7897 ALIGN=7897 ALIGN=file:uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu VALIGN=$ VALIGN=90928345 onLoad=-1 VALIGN=.xpi >
+<FORM onLoad=
+ TARGET=-1 ACTION== ENCTYPE=chrome: onLoad=.xbm ACTION=.gif STYLE=-1 SCRIPT== ACTION=chrome:BBBBBBBBBB ACTION=ffff METHOD=%n%n%n%n%n%n%n%n%n%n%n%n STYLE=
+ onLoad=% TARGET=%s%n%s%n%s%n%s%n%s%n%s%n SCRIPT=74326794236234 onLoad=
+ ACTION=oooooooooooooooooooooooo.png ENCTYPE=^ STYLE=http: ACTION=" >
+<CENTER STYLE=wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.png onLoad=0xfffffff onLoad=https: STYLE=????????????????????????????????????????????????????????????????????????????.ico onLoad=127 STYLE=127 onLoad=-1 STYLE=ffff STYLE=.xbm STYLE=http:mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm STYLE=& onLoad=: onLoad=0 STYLE=89000 STYLE=about: STYLE=chrome:ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss onLoad=$ onLoad=%s%n%s%n%s%n%s%n%s%n%s%n onLoad=44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444.html onLoad=about: >
+<B onLoad=.swf STYLE=74326794236234 STYLE=chrome: onLoad=? onLoad=ffff onLoad=0 STYLE=0xfffffff STYLE=89000 onLoad=^ onLoad=0 STYLE=http: onLoad=about:rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr STYLE=90928345 STYLE=ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd.xml onLoad=127 STYLE=90928345 STYLE=//////////////////////////////////////////////////////////////////////////////////////.xbm STYLE=-1 onLoad=% STYLE=$ >
+<FRAMESET onLoad=127 COLS=* STYLE=\ onLoad=7897 STYLE=? onLoad== STYLE=%n%n%n%n%n%n%n%n%n%n%n%n onLoad=74326794236234 STYLE=https:"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" onLoad=sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss.rdf COLS=808080 ROWS=0 ROWS=.xpi COLS=A STYLE=%s%n%s%n%s%n%s%n%s%n%s%n ROWS=90928345 COLS=? COLS=SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS.xml STYLE=: ROWS=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.dtd >
+<BQ CLEAR=about:kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk CLEAR=74326794236234 STYLE=%n%n%n%n%n%n%n%n%n%n%n%n NOWRAP=http: NOWRAP=gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg.rdf onLoad=.swf NOWRAP=90928345 STYLE=89000 onLoad=: onLoad=http: NOWRAP=chrome: STYLE=89000 NOWRAP=90928345 CLEAR=89000 CLEAR=ffff onLoad=https:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy CLEAR=.png STYLE=74326794236234 onLoad=https: NOWRAP=http: >
+<A HREF=https: TITLE=https: SHAPE=7897 HREF=% HREF=127 SHAPE=.rdf REV=http: STYLE=-1 STYLE=? HREF=& REV=%s%n%s%n%s%n%s%n%s%n%s%n REF=\ REV=89000 TITLE=0 NAME=90928345 REV=808080 HREF=808080 REF=808080 STYLE=(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((.png onLoad=.bmp >
+<SPACER TYPE=%n%n%n%n%n%n%n%n%n%n%n%n ALIGN=.gif SIZE=0 TYPE=.bmp WIDTH=$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$.xul SIZE=0 SIZE=& SIZE=7897 HEIGHT=.xpt TYPE=89000 SIZE=ffff WIDTH=file: WIDTH=90928345 STYLE=-1 WIDTH=7897 onLoad=0xfffffff ALIGN=-1 TYPE=about:9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 TYPE=89000 SIZE=UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU.swf >
+<COLGROUP onLoad=file:IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII onLoad=7897 WIDTH=.xpt WIDTH=
+ ALIGN== VALIGN=about:DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD HALIGN=90928345 HALIGN=file:lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll VALIGN=$ HALIGN=0xfffffff WIDTH=74326794236234 WIDTH=0xfffffff VALIGN=89000 STYLE=7897 WIDTH=^ HALIGN=% onLoad=74326794236234 ALIGN=127 HALIGN=* VALIGN=vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv.txt >
+<SPACER TYPE=.xpi ALIGN=.png WIDTH=127 SIZE=%s%n%s%n%s%n%s%n%s%n%s%n TYPE=& onLoad=89000 STYLE=808080 HEIGHT=chrome:``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` WIDTH=KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK.swf WIDTH=$ TYPE=http:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TYPE=" HEIGHT=% TYPE=0 TYPE=127 STYLE=90928345 SIZE=
+ STYLE=\ SIZE=https: HEIGHT=https: >
+<BODY BGPROPERTIES=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.jpg LEFTMARGIN=about:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq TEXT=ffff BGPROPERTIES=89000 TOPMARGIN=.png TEXT=about: VLINK=^ TOPMARGIN=0xfffffff TOPMARGIN=% onLoad=90928345 onLoad=999999999999999999999999999999999999999999999999999999999999999.xpi BGCOLOR=`````````````````````````````````````````````````````````````````````````````````````.xpt TEXT=% LINK=: TOPMARGIN=%s%n%s%n%s%n%s%n%s%n%s%n LEFTMARGIN=? TOPMARGIN=chrome:............................................................................................... BGPROPERTIES=& LEFTMARGIN=111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111.xpi onLoad=" >
+<BGSOUND STYLE=% LOOP=about:MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM STYLE=90928345 SRC=7897 onLoad=90928345 SRC=.xul onLoad=ffff onLoad=0xfffffff SRC=^ SRC=222222222222222222222222222222222.tmpl SRC=0xfffffff LOOP=%s%n%s%n%s%n%s%n%s%n%s%n SRC=7897 STYLE=about: LOOP=0 STYLE=74326794236234 SRC=90928345 onLoad=-1 SRC=74326794236234 LOOP=" >
+<HEAD STYLE=http: onLoad=.swf STYLE=http:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> onLoad=: onLoad=chrome: STYLE=89000 STYLE=http:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy STYLE=? STYLE=ffff onLoad=90928345 onLoad=https: STYLE=file: STYLE=\ onLoad=.xul onLoad=about:3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 onLoad=.swf STYLE=? onLoad=chrome:GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG STYLE=.ico STYLE=& >
+<SCRIPT LANGUAGE=% onLoad=808080 STYLE=.xbm LANGUAGE=file:666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 onLoad=about: onLoad=.xpi STYLE=ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss.ico onLoad=$ onLoad=.ico LANGUAGE=% onLoad=.xul onLoad=0xfffffff LANGUAGE=808080 onLoad=.ico STYLE=0 STYLE=%s%n%s%n%s%n%s%n%s%n%s%n STYLE=ffff STYLE=* LANGUAGE=* LANGUAGE=.gif >
+<OVERLAY X=hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh.rdf WIDTH=0xfffffff UNITS=0 Y=74326794236234 WIDTH=.png STYLE=about:TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT IMAGEMAP=^ WIDTH=ffff onLoad=A X=-1 IMAGEMAP=0xfffffff SRC=https:oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo SRC=7897 HEIGHT=.xpi HEIGHT=0xfffffff UNITS=* WIDTH=file: Y=fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff.xpi IMAGEMAP=.dtd WIDTH=% >
+<B STYLE=^ STYLE=? STYLE=74326794236234 onLoad=https: onLoad=http: STYLE=& onLoad=7897 onLoad=
+ onLoad=808080 STYLE=https:QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ onLoad=74326794236234 onLoad=%s%n%s%n%s%n%s%n%s%n%s%n STYLE=7897 onLoad=: onLoad=90928345 onLoad=ffff onLoad=0xfffffff onLoad=%n%n%n%n%n%n%n%n%n%n%n%n STYLE=^ onLoad=$ >
diff --git a/widget/crashtests/303901-2.html b/widget/crashtests/303901-2.html
new file mode 100644
index 0000000000..00a70945d5
--- /dev/null
+++ b/widget/crashtests/303901-2.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug </title>
+ <style type="text/css">
+
+ html,body {
+ color:black; background-color:white; font-size:16px; padding:0; margin:0;
+ }
+
+
+ </style>
+</head>
+<body>
+
+<div style="overflow:scroll; height:200000px;"></div>
+
+
+</body>
+</html>
diff --git a/widget/crashtests/380359-1.xhtml b/widget/crashtests/380359-1.xhtml
new file mode 100644
index 0000000000..00b60506a1
--- /dev/null
+++ b/widget/crashtests/380359-1.xhtml
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<body>
+
+<xul:hbox style="display: block; position: fixed; -moz-appearance: checkbox;" />
+
+</body>
+</html>
diff --git a/widget/crashtests/crashtests.list b/widget/crashtests/crashtests.list
new file mode 100644
index 0000000000..957f3f005c
--- /dev/null
+++ b/widget/crashtests/crashtests.list
@@ -0,0 +1,4 @@
+load 303901-1.html
+load 303901-2.html
+load 380359-1.xhtml
+load 1128214.html
diff --git a/widget/generic/PCompositorWidget.ipdl b/widget/generic/PCompositorWidget.ipdl
new file mode 100644
index 0000000000..f8ffd8dfff
--- /dev/null
+++ b/widget/generic/PCompositorWidget.ipdl
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 protocol PCompositorBridge;
+
+// This file is a stub, for platforms that do not yet support out-of-process
+// compositing or do not need specialized types to do so.
+
+namespace mozilla {
+namespace widget {
+
+sync protocol PCompositorWidget
+{
+ manager PCompositorBridge;
+
+parent:
+ async __delete__();
+
+child:
+ async ObserveVsync();
+ async UnobserveVsync();
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/generic/PlatformWidgetTypes.ipdlh b/widget/generic/PlatformWidgetTypes.ipdlh
new file mode 100644
index 0000000000..c4b6d2ca4f
--- /dev/null
+++ b/widget/generic/PlatformWidgetTypes.ipdlh
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 is a stub, for platforms that do not yet support out-of-process
+// compositing or do not need specialized types to do so.
+
+include HeadlessWidgetTypes;
+
+namespace mozilla {
+namespace widget {
+
+union CompositorWidgetInitData
+{
+ HeadlessCompositorWidgetInitData;
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/CompositorWidgetChild.cpp b/widget/gtk/CompositorWidgetChild.cpp
new file mode 100644
index 0000000000..ba51dda7a5
--- /dev/null
+++ b/widget/gtk/CompositorWidgetChild.cpp
@@ -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 "CompositorWidgetChild.h"
+#include "mozilla/Unused.h"
+#include "gfxPlatform.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidgetChild::CompositorWidgetChild(
+ RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver,
+ const CompositorWidgetInitData&)
+ : mVsyncDispatcher(aVsyncDispatcher), mVsyncObserver(aVsyncObserver) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!gfxPlatform::IsHeadless());
+}
+
+CompositorWidgetChild::~CompositorWidgetChild() = default;
+
+bool CompositorWidgetChild::Initialize() { return true; }
+
+mozilla::ipc::IPCResult CompositorWidgetChild::RecvObserveVsync() {
+ mVsyncDispatcher->SetCompositorVsyncObserver(mVsyncObserver);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetChild::RecvUnobserveVsync() {
+ mVsyncDispatcher->SetCompositorVsyncObserver(nullptr);
+ return IPC_OK();
+}
+
+void CompositorWidgetChild::NotifyClientSizeChanged(
+ const LayoutDeviceIntSize& aClientSize) {
+ Unused << SendNotifyClientSizeChanged(aClientSize);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/CompositorWidgetChild.h b/widget/gtk/CompositorWidgetChild.h
new file mode 100644
index 0000000000..76adc95baf
--- /dev/null
+++ b/widget/gtk/CompositorWidgetChild.h
@@ -0,0 +1,39 @@
+/* -*- 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 widget_gtk_CompositorWidgetChild_h
+#define widget_gtk_CompositorWidgetChild_h
+
+#include "GtkCompositorWidget.h"
+#include "mozilla/widget/PCompositorWidgetChild.h"
+#include "mozilla/widget/CompositorWidgetVsyncObserver.h"
+
+namespace mozilla {
+namespace widget {
+
+class CompositorWidgetChild final : public PCompositorWidgetChild,
+ public PlatformCompositorWidgetDelegate {
+ public:
+ CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver,
+ const CompositorWidgetInitData&);
+ ~CompositorWidgetChild() override;
+
+ bool Initialize();
+
+ mozilla::ipc::IPCResult RecvObserveVsync() override;
+ mozilla::ipc::IPCResult RecvUnobserveVsync() override;
+
+ void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override;
+
+ private:
+ RefPtr<CompositorVsyncDispatcher> mVsyncDispatcher;
+ RefPtr<CompositorWidgetVsyncObserver> mVsyncObserver;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_CompositorWidgetChild_h
diff --git a/widget/gtk/CompositorWidgetParent.cpp b/widget/gtk/CompositorWidgetParent.cpp
new file mode 100644
index 0000000000..adf49f3f13
--- /dev/null
+++ b/widget/gtk/CompositorWidgetParent.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CompositorWidgetParent.h"
+#include "mozilla/Unused.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidgetParent::CompositorWidgetParent(
+ const CompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions)
+ : GtkCompositorWidget(aInitData.get_GtkCompositorWidgetInitData(), aOptions,
+ nullptr) {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+}
+
+CompositorWidgetParent::~CompositorWidgetParent() = default;
+
+void CompositorWidgetParent::ObserveVsync(VsyncObserver* aObserver) {
+ if (aObserver) {
+ Unused << SendObserveVsync();
+ } else {
+ Unused << SendUnobserveVsync();
+ }
+ mVsyncObserver = aObserver;
+}
+
+RefPtr<VsyncObserver> CompositorWidgetParent::GetVsyncObserver() const {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+ return mVsyncObserver;
+}
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvNotifyClientSizeChanged(
+ const LayoutDeviceIntSize& aClientSize) {
+ NotifyClientSizeChanged(aClientSize);
+ return IPC_OK();
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/CompositorWidgetParent.h b/widget/gtk/CompositorWidgetParent.h
new file mode 100644
index 0000000000..87e8d31d69
--- /dev/null
+++ b/widget/gtk/CompositorWidgetParent.h
@@ -0,0 +1,38 @@
+/* -*- 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 widget_gtk_CompositorWidgetParent_h
+#define widget_gtk_CompositorWidgetParent_h
+
+#include "GtkCompositorWidget.h"
+#include "mozilla/VsyncDispatcher.h"
+#include "mozilla/widget/PCompositorWidgetParent.h"
+
+namespace mozilla {
+namespace widget {
+
+class CompositorWidgetParent final : public PCompositorWidgetParent,
+ public GtkCompositorWidget {
+ public:
+ explicit CompositorWidgetParent(const CompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions);
+ ~CompositorWidgetParent() override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override {}
+
+ void ObserveVsync(VsyncObserver* aObserver) override;
+ RefPtr<VsyncObserver> GetVsyncObserver() const override;
+
+ mozilla::ipc::IPCResult RecvNotifyClientSizeChanged(
+ const LayoutDeviceIntSize& aClientSize) override;
+
+ private:
+ RefPtr<VsyncObserver> mVsyncObserver;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_CompositorWidgetParent_h
diff --git a/widget/gtk/DMABufLibWrapper.cpp b/widget/gtk/DMABufLibWrapper.cpp
new file mode 100644
index 0000000000..7a9ae98ff8
--- /dev/null
+++ b/widget/gtk/DMABufLibWrapper.cpp
@@ -0,0 +1,300 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsWaylandDisplay.h"
+#include "DMABufLibWrapper.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/gfx/gfxVars.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dlfcn.h>
+
+namespace mozilla {
+namespace widget {
+
+#define GBMLIB_NAME "libgbm.so.1"
+#define DRMLIB_NAME "libdrm.so.2"
+
+void* nsGbmLib::sGbmLibHandle = nullptr;
+void* nsGbmLib::sXf86DrmLibHandle = nullptr;
+bool nsGbmLib::sLibLoaded = false;
+CreateDeviceFunc nsGbmLib::sCreateDevice;
+CreateFunc nsGbmLib::sCreate;
+CreateWithModifiersFunc nsGbmLib::sCreateWithModifiers;
+GetModifierFunc nsGbmLib::sGetModifier;
+GetStrideFunc nsGbmLib::sGetStride;
+GetFdFunc nsGbmLib::sGetFd;
+DestroyFunc nsGbmLib::sDestroy;
+MapFunc nsGbmLib::sMap;
+UnmapFunc nsGbmLib::sUnmap;
+GetPlaneCountFunc nsGbmLib::sGetPlaneCount;
+GetHandleForPlaneFunc nsGbmLib::sGetHandleForPlane;
+GetStrideForPlaneFunc nsGbmLib::sGetStrideForPlane;
+GetOffsetFunc nsGbmLib::sGetOffset;
+DeviceIsFormatSupportedFunc nsGbmLib::sDeviceIsFormatSupported;
+DrmPrimeHandleToFDFunc nsGbmLib::sDrmPrimeHandleToFD;
+
+bool nsGbmLib::IsLoaded() {
+ return sCreateDevice != nullptr && sCreate != nullptr &&
+ sCreateWithModifiers != nullptr && sGetModifier != nullptr &&
+ sGetStride != nullptr && sGetFd != nullptr && sDestroy != nullptr &&
+ sMap != nullptr && sUnmap != nullptr && sGetPlaneCount != nullptr &&
+ sGetHandleForPlane != nullptr && sGetStrideForPlane != nullptr &&
+ sGetOffset != nullptr && sDeviceIsFormatSupported != nullptr &&
+ sDrmPrimeHandleToFD != nullptr;
+}
+
+bool nsGbmLib::IsAvailable() {
+ if (!Load()) {
+ return false;
+ }
+ return IsLoaded();
+}
+
+bool nsGbmLib::Load() {
+ if (!sGbmLibHandle && !sLibLoaded) {
+ sLibLoaded = true;
+
+ sGbmLibHandle = dlopen(GBMLIB_NAME, RTLD_LAZY | RTLD_LOCAL);
+ if (!sGbmLibHandle) {
+ LOGDMABUF(("Failed to load %s, dmabuf isn't available.\n", GBMLIB_NAME));
+ return false;
+ }
+
+ sCreateDevice = (CreateDeviceFunc)dlsym(sGbmLibHandle, "gbm_create_device");
+ sCreate = (CreateFunc)dlsym(sGbmLibHandle, "gbm_bo_create");
+ sCreateWithModifiers = (CreateWithModifiersFunc)dlsym(
+ sGbmLibHandle, "gbm_bo_create_with_modifiers");
+ sGetModifier = (GetModifierFunc)dlsym(sGbmLibHandle, "gbm_bo_get_modifier");
+ sGetStride = (GetStrideFunc)dlsym(sGbmLibHandle, "gbm_bo_get_stride");
+ sGetFd = (GetFdFunc)dlsym(sGbmLibHandle, "gbm_bo_get_fd");
+ sDestroy = (DestroyFunc)dlsym(sGbmLibHandle, "gbm_bo_destroy");
+ sMap = (MapFunc)dlsym(sGbmLibHandle, "gbm_bo_map");
+ sUnmap = (UnmapFunc)dlsym(sGbmLibHandle, "gbm_bo_unmap");
+ sGetPlaneCount =
+ (GetPlaneCountFunc)dlsym(sGbmLibHandle, "gbm_bo_get_plane_count");
+ sGetHandleForPlane = (GetHandleForPlaneFunc)dlsym(
+ sGbmLibHandle, "gbm_bo_get_handle_for_plane");
+ sGetStrideForPlane = (GetStrideForPlaneFunc)dlsym(
+ sGbmLibHandle, "gbm_bo_get_stride_for_plane");
+ sGetOffset = (GetOffsetFunc)dlsym(sGbmLibHandle, "gbm_bo_get_offset");
+ sDeviceIsFormatSupported = (DeviceIsFormatSupportedFunc)dlsym(
+ sGbmLibHandle, "gbm_device_is_format_supported");
+
+ sXf86DrmLibHandle = dlopen(DRMLIB_NAME, RTLD_LAZY | RTLD_LOCAL);
+ if (!sXf86DrmLibHandle) {
+ LOGDMABUF(("Failed to load %s, dmabuf isn't available.\n", DRMLIB_NAME));
+ return false;
+ }
+ sDrmPrimeHandleToFD =
+ (DrmPrimeHandleToFDFunc)dlsym(sXf86DrmLibHandle, "drmPrimeHandleToFD");
+ if (!IsLoaded()) {
+ LOGDMABUF(("Failed to load all symbols from %s\n", GBMLIB_NAME));
+ }
+ }
+
+ return sGbmLibHandle;
+}
+
+gbm_device* nsDMABufDevice::GetGbmDevice() {
+ return IsDMABufEnabled() ? mGbmDevice : nullptr;
+}
+
+int nsDMABufDevice::GetGbmDeviceFd() { return IsDMABufEnabled() ? mGbmFd : -1; }
+
+static void dmabuf_modifiers(void* data,
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf,
+ uint32_t format, uint32_t modifier_hi,
+ uint32_t modifier_lo) {
+ auto* device = static_cast<nsDMABufDevice*>(data);
+ switch (format) {
+ case GBM_FORMAT_ARGB8888:
+ device->AddFormatModifier(true, format, modifier_hi, modifier_lo);
+ break;
+ case GBM_FORMAT_XRGB8888:
+ device->AddFormatModifier(false, format, modifier_hi, modifier_lo);
+ break;
+ default:
+ break;
+ }
+}
+
+static void dmabuf_format(void* data,
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf,
+ uint32_t format) {
+ // XXX: deprecated
+}
+
+static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = {
+ dmabuf_format, dmabuf_modifiers};
+
+static void global_registry_handler(void* data, wl_registry* registry,
+ uint32_t id, const char* interface,
+ uint32_t version) {
+ auto* device = static_cast<nsDMABufDevice*>(data);
+ if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0 && version > 2) {
+ auto* dmabuf = WaylandRegistryBind<zwp_linux_dmabuf_v1>(
+ registry, id, &zwp_linux_dmabuf_v1_interface, 3);
+ LOGDMABUF(("zwp_linux_dmabuf_v1 is available."));
+ device->ResetFormatsModifiers();
+ zwp_linux_dmabuf_v1_add_listener(dmabuf, &dmabuf_listener, data);
+ } else if (strcmp(interface, "wl_drm") == 0) {
+ LOGDMABUF(("wl_drm is available."));
+ }
+}
+
+static void global_registry_remover(void* data, wl_registry* registry,
+ uint32_t id) {}
+
+static const struct wl_registry_listener registry_listener = {
+ global_registry_handler, global_registry_remover};
+
+nsDMABufDevice::nsDMABufDevice()
+ : mXRGBFormat({true, false, GBM_FORMAT_XRGB8888, nullptr, 0}),
+ mARGBFormat({true, true, GBM_FORMAT_ARGB8888, nullptr, 0}),
+ mGbmDevice(nullptr),
+ mGbmFd(-1) {
+ if (gdk_display_get_default() &&
+ !GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ wl_display* display = WaylandDisplayGetWLDisplay();
+ mRegistry = (void*)wl_display_get_registry(display);
+ wl_registry_add_listener((wl_registry*)mRegistry, &registry_listener, this);
+ wl_display_roundtrip(display);
+ wl_display_roundtrip(display);
+ }
+}
+
+nsDMABufDevice::~nsDMABufDevice() {
+ if (mRegistry) {
+ wl_registry_destroy((wl_registry*)mRegistry);
+ mRegistry = nullptr;
+ }
+}
+
+bool nsDMABufDevice::Configure() {
+ bool isDMABufUsed = (
+#ifdef NIGHTLY_BUILD
+ StaticPrefs::widget_dmabuf_textures_enabled() ||
+#endif
+ StaticPrefs::widget_dmabuf_webgl_enabled() ||
+ StaticPrefs::media_ffmpeg_vaapi_enabled() ||
+ StaticPrefs::media_ffmpeg_vaapi_drm_display_enabled());
+
+ if (!isDMABufUsed) {
+ // Disabled by user, just quit.
+ LOGDMABUF(("IsDMABufEnabled(): Disabled by preferences."));
+ return false;
+ }
+
+ if (!nsGbmLib::IsAvailable()) {
+ LOGDMABUF(("nsGbmLib is not available!"));
+ return false;
+ }
+
+ nsAutoCString drm_render_node(getenv("MOZ_WAYLAND_DRM_DEVICE"));
+ if (drm_render_node.IsEmpty()) {
+ drm_render_node.Assign(gfx::gfxVars::DrmRenderDevice());
+ if (drm_render_node.IsEmpty()) {
+ return false;
+ }
+ }
+
+ mGbmFd = open(drm_render_node.get(), O_RDWR);
+ if (mGbmFd < 0) {
+ LOGDMABUF(("Failed to open drm render node %s\n", drm_render_node.get()));
+ return false;
+ }
+
+ mGbmDevice = nsGbmLib::CreateDevice(mGbmFd);
+ if (!mGbmDevice) {
+ LOGDMABUF(
+ ("Failed to create drm render device %s\n", drm_render_node.get()));
+ close(mGbmFd);
+ mGbmFd = -1;
+ return false;
+ }
+
+ LOGDMABUF(("GBM device initialized"));
+ return true;
+}
+
+bool nsDMABufDevice::IsDMABufEnabled() {
+ static bool isDMABufEnabled = Configure();
+ return isDMABufEnabled;
+}
+
+#ifdef NIGHTLY_BUILD
+bool nsDMABufDevice::IsDMABufTexturesEnabled() {
+ return gfx::gfxVars::UseEGL() && IsDMABufEnabled() &&
+ StaticPrefs::widget_dmabuf_textures_enabled();
+}
+#else
+bool nsDMABufDevice::IsDMABufTexturesEnabled() { return false; }
+#endif
+bool nsDMABufDevice::IsDMABufVAAPIEnabled() {
+ return gfx::gfxVars::UseEGL() && IsDMABufEnabled() &&
+ StaticPrefs::media_ffmpeg_vaapi_enabled() &&
+ gfx::gfxVars::CanUseHardwareVideoDecoding() && !XRE_IsRDDProcess();
+}
+bool nsDMABufDevice::IsDMABufWebGLEnabled() {
+ return gfx::gfxVars::UseEGL() && IsDMABufEnabled() &&
+ StaticPrefs::widget_dmabuf_webgl_enabled();
+}
+
+GbmFormat* nsDMABufDevice::GetGbmFormat(bool aHasAlpha) {
+ GbmFormat* format = aHasAlpha ? &mARGBFormat : &mXRGBFormat;
+ return format->mIsSupported ? format : nullptr;
+}
+
+GbmFormat* nsDMABufDevice::GetExactGbmFormat(int aFormat) {
+ if (aFormat == mARGBFormat.mFormat) {
+ return &mARGBFormat;
+ } else if (aFormat == mXRGBFormat.mFormat) {
+ return &mXRGBFormat;
+ }
+
+ return nullptr;
+}
+
+void nsDMABufDevice::AddFormatModifier(bool aHasAlpha, int aFormat,
+ uint32_t mModifierHi,
+ uint32_t mModifierLo) {
+ GbmFormat* format = aHasAlpha ? &mARGBFormat : &mXRGBFormat;
+ format->mIsSupported = true;
+ format->mHasAlpha = aHasAlpha;
+ format->mFormat = aFormat;
+ format->mModifiersCount++;
+ format->mModifiers =
+ (uint64_t*)realloc(format->mModifiers,
+ format->mModifiersCount * sizeof(*format->mModifiers));
+ format->mModifiers[format->mModifiersCount - 1] =
+ ((uint64_t)mModifierHi << 32) | mModifierLo;
+}
+
+void nsDMABufDevice::ResetFormatsModifiers() {
+ mARGBFormat.mModifiersCount = 0;
+ free(mARGBFormat.mModifiers);
+ mARGBFormat.mModifiers = nullptr;
+
+ mXRGBFormat.mModifiersCount = 0;
+ free(mXRGBFormat.mModifiers);
+ mXRGBFormat.mModifiers = nullptr;
+}
+
+nsDMABufDevice* GetDMABufDevice() {
+ static nsDMABufDevice dmaBufDevice;
+ return &dmaBufDevice;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/DMABufLibWrapper.h b/widget/gtk/DMABufLibWrapper.h
new file mode 100644
index 0000000000..99e82609ef
--- /dev/null
+++ b/widget/gtk/DMABufLibWrapper.h
@@ -0,0 +1,168 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __MOZ_DMABUF_LIB_WRAPPER_H__
+#define __MOZ_DMABUF_LIB_WRAPPER_H__
+
+#include "mozilla/widget/gbm.h"
+
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gDmabufLog;
+# define LOGDMABUF(args) MOZ_LOG(gDmabufLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOGDMABUF(args)
+#endif /* MOZ_LOGGING */
+
+namespace mozilla {
+namespace widget {
+
+typedef struct gbm_device* (*CreateDeviceFunc)(int);
+typedef struct gbm_bo* (*CreateFunc)(struct gbm_device*, uint32_t, uint32_t,
+ uint32_t, uint32_t);
+typedef struct gbm_bo* (*CreateWithModifiersFunc)(struct gbm_device*, uint32_t,
+ uint32_t, uint32_t,
+ const uint64_t*,
+ const unsigned int);
+typedef uint64_t (*GetModifierFunc)(struct gbm_bo*);
+typedef uint32_t (*GetStrideFunc)(struct gbm_bo*);
+typedef int (*GetFdFunc)(struct gbm_bo*);
+typedef void (*DestroyFunc)(struct gbm_bo*);
+typedef void* (*MapFunc)(struct gbm_bo*, uint32_t, uint32_t, uint32_t, uint32_t,
+ uint32_t, uint32_t*, void**);
+typedef void (*UnmapFunc)(struct gbm_bo*, void*);
+typedef int (*GetPlaneCountFunc)(struct gbm_bo*);
+typedef union gbm_bo_handle (*GetHandleForPlaneFunc)(struct gbm_bo*, int);
+typedef uint32_t (*GetStrideForPlaneFunc)(struct gbm_bo*, int);
+typedef uint32_t (*GetOffsetFunc)(struct gbm_bo*, int);
+typedef int (*DeviceIsFormatSupportedFunc)(struct gbm_device*, uint32_t,
+ uint32_t);
+typedef int (*DrmPrimeHandleToFDFunc)(int, uint32_t, uint32_t, int*);
+
+class nsGbmLib {
+ public:
+ static bool Load();
+ static bool IsLoaded();
+ static bool IsAvailable();
+ static bool IsModifierAvailable();
+
+ static struct gbm_device* CreateDevice(int fd) { return sCreateDevice(fd); };
+ static struct gbm_bo* Create(struct gbm_device* gbm, uint32_t width,
+ uint32_t height, uint32_t format,
+ uint32_t flags) {
+ return sCreate(gbm, width, height, format, flags);
+ }
+ static void Destroy(struct gbm_bo* bo) { sDestroy(bo); }
+ static uint32_t GetStride(struct gbm_bo* bo) { return sGetStride(bo); }
+ static int GetFd(struct gbm_bo* bo) { return sGetFd(bo); }
+ static void* Map(struct gbm_bo* bo, uint32_t x, uint32_t y, uint32_t width,
+ uint32_t height, uint32_t flags, uint32_t* stride,
+ void** map_data) {
+ return sMap(bo, x, y, width, height, flags, stride, map_data);
+ }
+ static void Unmap(struct gbm_bo* bo, void* map_data) { sUnmap(bo, map_data); }
+ static struct gbm_bo* CreateWithModifiers(struct gbm_device* gbm,
+ uint32_t width, uint32_t height,
+ uint32_t format,
+ const uint64_t* modifiers,
+ const unsigned int count) {
+ return sCreateWithModifiers(gbm, width, height, format, modifiers, count);
+ }
+ static uint64_t GetModifier(struct gbm_bo* bo) { return sGetModifier(bo); }
+ static int GetPlaneCount(struct gbm_bo* bo) { return sGetPlaneCount(bo); }
+ static union gbm_bo_handle GetHandleForPlane(struct gbm_bo* bo, int plane) {
+ return sGetHandleForPlane(bo, plane);
+ }
+ static uint32_t GetStrideForPlane(struct gbm_bo* bo, int plane) {
+ return sGetStrideForPlane(bo, plane);
+ }
+ static uint32_t GetOffset(struct gbm_bo* bo, int plane) {
+ return sGetOffset(bo, plane);
+ }
+ static int DeviceIsFormatSupported(struct gbm_device* gbm, uint32_t format,
+ uint32_t usage) {
+ return sDeviceIsFormatSupported(gbm, format, usage);
+ }
+
+ static int DrmPrimeHandleToFD(int fd, uint32_t handle, uint32_t flags,
+ int* prime_fd) {
+ return sDrmPrimeHandleToFD(fd, handle, flags, prime_fd);
+ }
+
+ private:
+ static CreateDeviceFunc sCreateDevice;
+ static CreateFunc sCreate;
+ static CreateWithModifiersFunc sCreateWithModifiers;
+ static GetModifierFunc sGetModifier;
+ static GetStrideFunc sGetStride;
+ static GetFdFunc sGetFd;
+ static DestroyFunc sDestroy;
+ static MapFunc sMap;
+ static UnmapFunc sUnmap;
+ static GetPlaneCountFunc sGetPlaneCount;
+ static GetHandleForPlaneFunc sGetHandleForPlane;
+ static GetStrideForPlaneFunc sGetStrideForPlane;
+ static GetOffsetFunc sGetOffset;
+ static DeviceIsFormatSupportedFunc sDeviceIsFormatSupported;
+ static DrmPrimeHandleToFDFunc sDrmPrimeHandleToFD;
+
+ static void* sGbmLibHandle;
+ static void* sXf86DrmLibHandle;
+ static bool sLibLoaded;
+};
+
+struct GbmFormat {
+ bool mIsSupported;
+ bool mHasAlpha;
+ int mFormat;
+ uint64_t* mModifiers;
+ int mModifiersCount;
+};
+
+class nsDMABufDevice {
+ public:
+ nsDMABufDevice();
+ ~nsDMABufDevice();
+
+ gbm_device* GetGbmDevice();
+ // Returns -1 if we fails to gbm device file descriptor.
+ int GetGbmDeviceFd();
+
+ // Use dmabuf for WebRender general web content
+ bool IsDMABufTexturesEnabled();
+ // Use dmabuf for VA-API video playback
+ bool IsDMABufVAAPIEnabled();
+ // Use dmabuf for WebGL content
+ bool IsDMABufWebGLEnabled();
+
+ GbmFormat* GetGbmFormat(bool aHasAlpha);
+ GbmFormat* GetExactGbmFormat(int aFormat);
+ void ResetFormatsModifiers();
+ void AddFormatModifier(bool aHasAlpha, int aFormat, uint32_t mModifierHi,
+ uint32_t mModifierLo);
+
+ private:
+ bool IsDMABufEnabled();
+ bool Configure();
+
+ void* mRegistry;
+
+ GbmFormat mXRGBFormat;
+ GbmFormat mARGBFormat;
+
+ gbm_device* mGbmDevice;
+ int mGbmFd;
+};
+
+nsDMABufDevice* GetDMABufDevice();
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // __MOZ_DMABUF_LIB_WRAPPER_H__
diff --git a/widget/gtk/DMABufSurface.cpp b/widget/gtk/DMABufSurface.cpp
new file mode 100644
index 0000000000..1e1719780f
--- /dev/null
+++ b/widget/gtk/DMABufSurface.cpp
@@ -0,0 +1,1002 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "DMABufSurface.h"
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <dlfcn.h>
+#include <sys/mman.h>
+#include <sys/eventfd.h>
+#include <poll.h>
+
+#include "mozilla/widget/gbm.h"
+#include "mozilla/widget/va_drmcommon.h"
+#include "GLContextTypes.h" // for GLContext, etc
+#include "GLContextEGL.h"
+#include "GLContextProvider.h"
+#include "ScopedGLHelpers.h"
+
+#include "mozilla/layers/LayersSurfaces.h"
+#include "mozilla/ScopeExit.h"
+
+/*
+TODO:
+DRM device selection:
+https://lists.freedesktop.org/archives/wayland-devel/2018-November/039660.html
+*/
+
+/* C++ / C typecast macros for special EGL handle values */
+#if defined(__cplusplus)
+# define EGL_CAST(type, value) (static_cast<type>(value))
+#else
+# define EGL_CAST(type, value) ((type)(value))
+#endif
+
+using namespace mozilla;
+using namespace mozilla::widget;
+using namespace mozilla::gl;
+using namespace mozilla::layers;
+
+#ifndef DRM_FORMAT_MOD_INVALID
+# define DRM_FORMAT_MOD_INVALID ((1ULL << 56) - 1)
+#endif
+#define BUFFER_FLAGS 0
+
+#ifndef GBM_BO_USE_TEXTURING
+# define GBM_BO_USE_TEXTURING (1 << 5)
+#endif
+
+#ifndef VA_FOURCC_NV12
+# define VA_FOURCC_NV12 0x3231564E
+#endif
+
+#ifndef VA_FOURCC_YV12
+# define VA_FOURCC_YV12 0x32315659
+#endif
+
+static Atomic<int> gNewSurfaceUID(1);
+
+bool DMABufSurface::IsGlobalRefSet() const {
+ if (!mGlobalRefCountFd) {
+ return false;
+ }
+ struct pollfd pfd;
+ pfd.fd = mGlobalRefCountFd;
+ pfd.events = POLLIN;
+ return poll(&pfd, 1, 0) == 1;
+}
+
+void DMABufSurface::GlobalRefRelease() {
+ MOZ_ASSERT(mGlobalRefCountFd);
+ uint64_t counter;
+ if (read(mGlobalRefCountFd, &counter, sizeof(counter)) != sizeof(counter)) {
+ // EAGAIN means the refcount is already zero. It happens when we release
+ // last reference to the surface.
+ if (errno != EAGAIN) {
+ NS_WARNING("Failed to unref dmabuf global ref count!");
+ }
+ }
+}
+
+void DMABufSurface::GlobalRefAdd() {
+ MOZ_ASSERT(mGlobalRefCountFd);
+ uint64_t counter = 1;
+ if (write(mGlobalRefCountFd, &counter, sizeof(counter)) != sizeof(counter)) {
+ NS_WARNING("Failed to ref dmabuf global ref count!");
+ }
+}
+
+void DMABufSurface::GlobalRefCountCreate() {
+ MOZ_ASSERT(!mGlobalRefCountFd);
+ mGlobalRefCountFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE);
+ if (mGlobalRefCountFd < 0) {
+ NS_WARNING("Failed to create dmabuf global ref count!");
+ mGlobalRefCountFd = 0;
+ return;
+ }
+}
+
+void DMABufSurface::GlobalRefCountImport(int aFd) {
+ MOZ_ASSERT(!mGlobalRefCountFd);
+ mGlobalRefCountFd = aFd;
+ GlobalRefAdd();
+}
+
+void DMABufSurface::GlobalRefCountDelete() {
+ if (mGlobalRefCountFd) {
+ GlobalRefRelease();
+ close(mGlobalRefCountFd);
+ mGlobalRefCountFd = 0;
+ }
+}
+
+void DMABufSurface::ReleaseDMABuf() {
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ Unmap(i);
+
+ if (mDmabufFds[i] >= 0) {
+ close(mDmabufFds[i]);
+ mDmabufFds[i] = -1;
+ }
+ }
+
+ if (mGbmBufferObject[0]) {
+ nsGbmLib::Destroy(mGbmBufferObject[0]);
+ mGbmBufferObject[0] = nullptr;
+ }
+}
+
+DMABufSurface::DMABufSurface(SurfaceType aSurfaceType)
+ : mSurfaceType(aSurfaceType),
+ mBufferModifier(DRM_FORMAT_MOD_INVALID),
+ mBufferPlaneCount(0),
+ mDrmFormats(),
+ mStrides(),
+ mOffsets(),
+ mGbmBufferObject(),
+ mMappedRegion(),
+ mMappedRegionStride(),
+ mSyncFd(-1),
+ mSync(0),
+ mGlobalRefCountFd(0),
+ mUID(gNewSurfaceUID++) {
+ for (auto& slot : mDmabufFds) {
+ slot = -1;
+ }
+}
+
+DMABufSurface::~DMABufSurface() {
+ FenceDelete();
+ GlobalRefCountDelete();
+}
+
+already_AddRefed<DMABufSurface> DMABufSurface::CreateDMABufSurface(
+ const mozilla::layers::SurfaceDescriptor& aDesc) {
+ const SurfaceDescriptorDMABuf& desc = aDesc.get_SurfaceDescriptorDMABuf();
+ RefPtr<DMABufSurface> surf;
+
+ switch (desc.bufferType()) {
+ case SURFACE_RGBA:
+ surf = new DMABufSurfaceRGBA();
+ break;
+ case SURFACE_NV12:
+ case SURFACE_YUV420:
+ surf = new DMABufSurfaceYUV();
+ break;
+ default:
+ return nullptr;
+ }
+
+ if (!surf->Create(desc)) {
+ return nullptr;
+ }
+ return surf.forget();
+}
+
+void DMABufSurface::FenceDelete() {
+ if (!mGL) return;
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ if (mSyncFd > 0) {
+ close(mSyncFd);
+ mSyncFd = -1;
+ }
+
+ if (mSync) {
+ egl->fDestroySync(mSync);
+ mSync = nullptr;
+ }
+}
+
+void DMABufSurface::FenceSet() {
+ if (!mGL || !mGL->MakeCurrent()) {
+ return;
+ }
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ if (egl->IsExtensionSupported(EGLExtension::KHR_fence_sync) &&
+ egl->IsExtensionSupported(EGLExtension::ANDROID_native_fence_sync)) {
+ FenceDelete();
+
+ mSync = egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
+ if (mSync) {
+ mSyncFd = egl->fDupNativeFenceFDANDROID(mSync);
+ mGL->fFlush();
+ return;
+ }
+ }
+
+ // ANDROID_native_fence_sync may not be supported so call glFinish()
+ // as a slow path.
+ mGL->fFinish();
+}
+
+void DMABufSurface::FenceWait() {
+ if (!mGL) return;
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ if (!mSync && mSyncFd > 0) {
+ FenceImportFromFd();
+ }
+
+ // Wait on the fence, because presumably we're going to want to read this
+ // surface
+ if (mSync) {
+ egl->fClientWaitSync(mSync, 0, LOCAL_EGL_FOREVER);
+ }
+}
+
+bool DMABufSurface::FenceImportFromFd() {
+ if (!mGL) return false;
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ const EGLint attribs[] = {LOCAL_EGL_SYNC_NATIVE_FENCE_FD_ANDROID, mSyncFd,
+ LOCAL_EGL_NONE};
+ mSync = egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
+ close(mSyncFd);
+ mSyncFd = -1;
+
+ if (!mSync) {
+ MOZ_ASSERT(false, "Failed to create GLFence!");
+ return false;
+ }
+
+ return true;
+}
+
+DMABufSurfaceRGBA::DMABufSurfaceRGBA()
+ : DMABufSurface(SURFACE_RGBA),
+ mSurfaceFlags(0),
+ mWidth(0),
+ mHeight(0),
+ mGmbFormat(nullptr),
+ mEGLImage(LOCAL_EGL_NO_IMAGE),
+ mTexture(0),
+ mGbmBufferFlags(0) {}
+
+DMABufSurfaceRGBA::~DMABufSurfaceRGBA() { ReleaseSurface(); }
+
+bool DMABufSurfaceRGBA::Create(int aWidth, int aHeight,
+ int aDMABufSurfaceFlags) {
+ MOZ_ASSERT(mGbmBufferObject[0] == nullptr, "Already created?");
+
+ mSurfaceFlags = aDMABufSurfaceFlags;
+ mWidth = aWidth;
+ mHeight = aHeight;
+
+ LOGDMABUF(("DMABufSurfaceRGBA::Create() UID %d size %d x %d\n", mUID, mWidth,
+ mHeight));
+
+ mGmbFormat = GetDMABufDevice()->GetGbmFormat(mSurfaceFlags & DMABUF_ALPHA);
+ if (!mGmbFormat) {
+ // Requested DRM format is not supported.
+ return false;
+ }
+
+ bool useModifiers = (aDMABufSurfaceFlags & DMABUF_USE_MODIFIERS) &&
+ mGmbFormat->mModifiersCount > 0;
+ if (useModifiers) {
+ LOGDMABUF((" Creating with modifiers\n"));
+ mGbmBufferObject[0] = nsGbmLib::CreateWithModifiers(
+ GetDMABufDevice()->GetGbmDevice(), mWidth, mHeight, mGmbFormat->mFormat,
+ mGmbFormat->mModifiers, mGmbFormat->mModifiersCount);
+ if (mGbmBufferObject[0]) {
+ mBufferModifier = nsGbmLib::GetModifier(mGbmBufferObject[0]);
+ }
+ }
+
+ // Create without modifiers - use plain/linear format.
+ if (!mGbmBufferObject[0]) {
+ LOGDMABUF((" Creating without modifiers\n"));
+ mGbmBufferFlags = (GBM_BO_USE_SCANOUT | GBM_BO_USE_LINEAR);
+ if (mSurfaceFlags & DMABUF_TEXTURE) {
+ mGbmBufferFlags |= GBM_BO_USE_TEXTURING;
+ }
+
+ if (!nsGbmLib::DeviceIsFormatSupported(GetDMABufDevice()->GetGbmDevice(),
+ mGmbFormat->mFormat,
+ mGbmBufferFlags)) {
+ mGbmBufferFlags &= ~GBM_BO_USE_SCANOUT;
+ }
+
+ mGbmBufferObject[0] =
+ nsGbmLib::Create(GetDMABufDevice()->GetGbmDevice(), mWidth, mHeight,
+ mGmbFormat->mFormat, mGbmBufferFlags);
+
+ mBufferModifier = DRM_FORMAT_MOD_INVALID;
+ }
+
+ if (!mGbmBufferObject[0]) {
+ LOGDMABUF((" Failed to create GbmBufferObject\n"));
+ return false;
+ }
+
+ if (mBufferModifier != DRM_FORMAT_MOD_INVALID) {
+ mBufferPlaneCount = nsGbmLib::GetPlaneCount(mGbmBufferObject[0]);
+ if (mBufferPlaneCount > DMABUF_BUFFER_PLANES) {
+ NS_WARNING("There's too many dmabuf planes!");
+ ReleaseSurface();
+ return false;
+ }
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ uint32_t handle = nsGbmLib::GetHandleForPlane(mGbmBufferObject[0], i).u32;
+ int ret = nsGbmLib::DrmPrimeHandleToFD(
+ GetDMABufDevice()->GetGbmDeviceFd(), handle, 0, &mDmabufFds[i]);
+ if (ret < 0 || mDmabufFds[i] < 0) {
+ ReleaseSurface();
+ return false;
+ }
+ mStrides[i] = nsGbmLib::GetStrideForPlane(mGbmBufferObject[0], i);
+ mOffsets[i] = nsGbmLib::GetOffset(mGbmBufferObject[0], i);
+ }
+ } else {
+ mBufferPlaneCount = 1;
+ mStrides[0] = nsGbmLib::GetStride(mGbmBufferObject[0]);
+ mDmabufFds[0] = nsGbmLib::GetFd(mGbmBufferObject[0]);
+ if (mDmabufFds[0] < 0) {
+ ReleaseSurface();
+ return false;
+ }
+ }
+
+ LOGDMABUF((" Success\n"));
+ return true;
+}
+
+void DMABufSurfaceRGBA::ImportSurfaceDescriptor(
+ const SurfaceDescriptor& aDesc) {
+ const SurfaceDescriptorDMABuf& desc = aDesc.get_SurfaceDescriptorDMABuf();
+
+ mWidth = desc.width()[0];
+ mHeight = desc.height()[0];
+ mBufferModifier = desc.modifier();
+ if (mBufferModifier != DRM_FORMAT_MOD_INVALID) {
+ mGmbFormat = GetDMABufDevice()->GetExactGbmFormat(desc.format()[0]);
+ } else {
+ mDrmFormats[0] = desc.format()[0];
+ }
+ mBufferPlaneCount = desc.fds().Length();
+ mGbmBufferFlags = desc.flags();
+ MOZ_RELEASE_ASSERT(mBufferPlaneCount <= DMABUF_BUFFER_PLANES);
+ mUID = desc.uid();
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ mDmabufFds[i] = desc.fds()[i].ClonePlatformHandle().release();
+ mStrides[i] = desc.strides()[i];
+ mOffsets[i] = desc.offsets()[i];
+ }
+
+ if (desc.fence().Length() > 0) {
+ mSyncFd = desc.fence()[0].ClonePlatformHandle().release();
+ }
+
+ if (desc.refCount().Length() > 0) {
+ GlobalRefCountImport(desc.refCount()[0].ClonePlatformHandle().release());
+ }
+
+ LOGDMABUF(("DMABufSurfaceRGBA::Import() UID %d\n", mUID));
+}
+
+bool DMABufSurfaceRGBA::Create(const SurfaceDescriptor& aDesc) {
+ ImportSurfaceDescriptor(aDesc);
+ return true;
+}
+
+bool DMABufSurfaceRGBA::Serialize(
+ mozilla::layers::SurfaceDescriptor& aOutDescriptor) {
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> width;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> height;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> format;
+ AutoTArray<ipc::FileDescriptor, DMABUF_BUFFER_PLANES> fds;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> strides;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> offsets;
+ AutoTArray<uintptr_t, DMABUF_BUFFER_PLANES> images;
+ AutoTArray<ipc::FileDescriptor, 1> fenceFDs;
+ AutoTArray<ipc::FileDescriptor, 1> refCountFDs;
+
+ LOGDMABUF(("DMABufSurfaceRGBA::Serialize() UID %d\n", mUID));
+
+ width.AppendElement(mWidth);
+ height.AppendElement(mHeight);
+ format.AppendElement(mGmbFormat->mFormat);
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ fds.AppendElement(ipc::FileDescriptor(mDmabufFds[i]));
+ strides.AppendElement(mStrides[i]);
+ offsets.AppendElement(mOffsets[i]);
+ }
+
+ if (mSync) {
+ fenceFDs.AppendElement(ipc::FileDescriptor(mSyncFd));
+ }
+
+ if (mGlobalRefCountFd) {
+ refCountFDs.AppendElement(ipc::FileDescriptor(mGlobalRefCountFd));
+ }
+
+ aOutDescriptor =
+ SurfaceDescriptorDMABuf(mSurfaceType, mBufferModifier, mGbmBufferFlags,
+ fds, width, height, format, strides, offsets,
+ GetYUVColorSpace(), fenceFDs, mUID, refCountFDs);
+ return true;
+}
+
+bool DMABufSurfaceRGBA::CreateTexture(GLContext* aGLContext, int aPlane) {
+ MOZ_ASSERT(!mEGLImage && !mTexture, "EGLImage is already created!");
+
+ nsTArray<EGLint> attribs;
+ attribs.AppendElement(LOCAL_EGL_WIDTH);
+ attribs.AppendElement(mWidth);
+ attribs.AppendElement(LOCAL_EGL_HEIGHT);
+ attribs.AppendElement(mHeight);
+ attribs.AppendElement(LOCAL_EGL_LINUX_DRM_FOURCC_EXT);
+ if (mGmbFormat) {
+ attribs.AppendElement(mGmbFormat->mFormat);
+ } else {
+ attribs.AppendElement(mDrmFormats[0]);
+ }
+#define ADD_PLANE_ATTRIBS(plane_idx) \
+ { \
+ attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_FD_EXT); \
+ attribs.AppendElement(mDmabufFds[plane_idx]); \
+ attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_OFFSET_EXT); \
+ attribs.AppendElement((int)mOffsets[plane_idx]); \
+ attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_PITCH_EXT); \
+ attribs.AppendElement((int)mStrides[plane_idx]); \
+ if (mBufferModifier != DRM_FORMAT_MOD_INVALID) { \
+ attribs.AppendElement( \
+ LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_MODIFIER_LO_EXT); \
+ attribs.AppendElement(mBufferModifier & 0xFFFFFFFF); \
+ attribs.AppendElement( \
+ LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_MODIFIER_HI_EXT); \
+ attribs.AppendElement(mBufferModifier >> 32); \
+ } \
+ }
+ ADD_PLANE_ATTRIBS(0);
+ if (mBufferPlaneCount > 1) ADD_PLANE_ATTRIBS(1);
+ if (mBufferPlaneCount > 2) ADD_PLANE_ATTRIBS(2);
+ if (mBufferPlaneCount > 3) ADD_PLANE_ATTRIBS(3);
+#undef ADD_PLANE_ATTRIBS
+ attribs.AppendElement(LOCAL_EGL_NONE);
+
+ if (!aGLContext) return false;
+ const auto& gle = gl::GLContextEGL::Cast(aGLContext);
+ const auto& egl = gle->mEgl;
+ mEGLImage =
+ egl->fCreateImage(LOCAL_EGL_NO_CONTEXT, LOCAL_EGL_LINUX_DMA_BUF_EXT,
+ nullptr, attribs.Elements());
+ if (mEGLImage == LOCAL_EGL_NO_IMAGE) {
+ NS_WARNING("EGLImageKHR creation failed");
+ return false;
+ }
+
+ aGLContext->MakeCurrent();
+ aGLContext->fGenTextures(1, &mTexture);
+ const ScopedBindTexture savedTex(aGLContext, mTexture);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
+ LOCAL_GL_LINEAR);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
+ LOCAL_GL_LINEAR);
+ aGLContext->fEGLImageTargetTexture2D(LOCAL_GL_TEXTURE_2D, mEGLImage);
+ mGL = aGLContext;
+
+ return true;
+}
+
+void DMABufSurfaceRGBA::ReleaseTextures() {
+ FenceDelete();
+
+ if (!mGL) return;
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ if (mTexture && mGL->MakeCurrent()) {
+ mGL->fDeleteTextures(1, &mTexture);
+ mTexture = 0;
+ mGL = nullptr;
+ }
+
+ if (mEGLImage) {
+ egl->fDestroyImage(mEGLImage);
+ mEGLImage = nullptr;
+ }
+}
+
+void DMABufSurfaceRGBA::ReleaseSurface() {
+ MOZ_ASSERT(!IsMapped(), "We can't release mapped buffer!");
+
+ ReleaseTextures();
+ ReleaseDMABuf();
+}
+
+void* DMABufSurface::MapInternal(uint32_t aX, uint32_t aY, uint32_t aWidth,
+ uint32_t aHeight, uint32_t* aStride,
+ int aGbmFlags, int aPlane) {
+ NS_ASSERTION(!IsMapped(aPlane), "Already mapped!");
+ if (!mGbmBufferObject[aPlane]) {
+ NS_WARNING("We can't map DMABufSurfaceRGBA without mGbmBufferObject");
+ return nullptr;
+ }
+
+ LOGDMABUF(
+ ("DMABufSurfaceRGBA::MapInternal() UID %d size %d x %d -> %d x %d\n",
+ mUID, aX, aY, aWidth, aHeight));
+
+ mMappedRegionStride[aPlane] = 0;
+ mMappedRegionData[aPlane] = nullptr;
+ mMappedRegion[aPlane] = nsGbmLib::Map(
+ mGbmBufferObject[aPlane], aX, aY, aWidth, aHeight, aGbmFlags,
+ &mMappedRegionStride[aPlane], &mMappedRegionData[aPlane]);
+ if (aStride) {
+ *aStride = mMappedRegionStride[aPlane];
+ }
+ return mMappedRegion[aPlane];
+}
+
+void* DMABufSurfaceRGBA::MapReadOnly(uint32_t aX, uint32_t aY, uint32_t aWidth,
+ uint32_t aHeight, uint32_t* aStride) {
+ return MapInternal(aX, aY, aWidth, aHeight, aStride, GBM_BO_TRANSFER_READ);
+}
+
+void* DMABufSurfaceRGBA::MapReadOnly(uint32_t* aStride) {
+ return MapInternal(0, 0, mWidth, mHeight, aStride, GBM_BO_TRANSFER_READ);
+}
+
+void* DMABufSurfaceRGBA::Map(uint32_t aX, uint32_t aY, uint32_t aWidth,
+ uint32_t aHeight, uint32_t* aStride) {
+ return MapInternal(aX, aY, aWidth, aHeight, aStride,
+ GBM_BO_TRANSFER_READ_WRITE);
+}
+
+void* DMABufSurfaceRGBA::Map(uint32_t* aStride) {
+ return MapInternal(0, 0, mWidth, mHeight, aStride,
+ GBM_BO_TRANSFER_READ_WRITE);
+}
+
+void DMABufSurface::Unmap(int aPlane) {
+ if (mMappedRegion[aPlane]) {
+ nsGbmLib::Unmap(mGbmBufferObject[aPlane], mMappedRegionData[aPlane]);
+ mMappedRegion[aPlane] = nullptr;
+ mMappedRegionData[aPlane] = nullptr;
+ mMappedRegionStride[aPlane] = 0;
+ }
+}
+
+#ifdef DEBUG
+void DMABufSurfaceRGBA::DumpToFile(const char* pFile) {
+ uint32_t stride;
+
+ if (!MapReadOnly(&stride)) {
+ return;
+ }
+ cairo_surface_t* surface = nullptr;
+
+ auto unmap = MakeScopeExit([&] {
+ if (surface) {
+ cairo_surface_destroy(surface);
+ }
+ Unmap();
+ });
+
+ surface = cairo_image_surface_create_for_data(
+ (unsigned char*)mMappedRegion[0], CAIRO_FORMAT_ARGB32, mWidth, mHeight,
+ stride);
+ if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) {
+ cairo_surface_write_to_png(surface, pFile);
+ }
+}
+#endif
+
+#if 0
+// Copy from source surface by GL
+# include "GLBlitHelper.h"
+
+bool DMABufSurfaceRGBA::CopyFrom(class DMABufSurface* aSourceSurface,
+ GLContext* aGLContext) {
+ MOZ_ASSERT(aSourceSurface->GetTexture());
+ MOZ_ASSERT(GetTexture());
+
+ gfx::IntSize size(GetWidth(), GetHeight());
+ aGLContext->BlitHelper()->BlitTextureToTexture(aSourceSurface->GetTexture(),
+ GetTexture(), size, size);
+ return true;
+}
+#endif
+
+// TODO - Clear the surface by EGL
+void DMABufSurfaceRGBA::Clear() {
+ uint32_t destStride;
+ void* destData = Map(&destStride);
+ memset(destData, 0, GetHeight() * destStride);
+ Unmap();
+}
+
+bool DMABufSurfaceRGBA::HasAlpha() {
+ return mGmbFormat ? mGmbFormat->mHasAlpha : true;
+}
+
+gfx::SurfaceFormat DMABufSurfaceRGBA::GetFormat() {
+ return HasAlpha() ? gfx::SurfaceFormat::B8G8R8A8
+ : gfx::SurfaceFormat::B8G8R8X8;
+}
+
+// GL uses swapped R and B components so report accordingly.
+gfx::SurfaceFormat DMABufSurfaceRGBA::GetFormatGL() {
+ return HasAlpha() ? gfx::SurfaceFormat::R8G8B8A8
+ : gfx::SurfaceFormat::R8G8B8X8;
+}
+
+already_AddRefed<DMABufSurfaceRGBA> DMABufSurfaceRGBA::CreateDMABufSurface(
+ int aWidth, int aHeight, int aDMABufSurfaceFlags) {
+ RefPtr<DMABufSurfaceRGBA> surf = new DMABufSurfaceRGBA();
+ if (!surf->Create(aWidth, aHeight, aDMABufSurfaceFlags)) {
+ return nullptr;
+ }
+ return surf.forget();
+}
+
+already_AddRefed<DMABufSurfaceYUV> DMABufSurfaceYUV::CreateYUVSurface(
+ const VADRMPRIMESurfaceDescriptor& aDesc) {
+ RefPtr<DMABufSurfaceYUV> surf = new DMABufSurfaceYUV();
+ if (!surf->UpdateYUVData(aDesc)) {
+ return nullptr;
+ }
+ return surf.forget();
+}
+
+already_AddRefed<DMABufSurfaceYUV> DMABufSurfaceYUV::CreateYUVSurface(
+ int aWidth, int aHeight, void** aPixelData, int* aLineSizes) {
+ RefPtr<DMABufSurfaceYUV> surf = new DMABufSurfaceYUV();
+ if (!surf->Create(aWidth, aHeight, aPixelData, aLineSizes)) {
+ return nullptr;
+ }
+ return surf.forget();
+}
+
+DMABufSurfaceYUV::DMABufSurfaceYUV()
+ : DMABufSurface(SURFACE_NV12),
+ mWidth(),
+ mHeight(),
+ mTexture(),
+ mColorSpace(mozilla::gfx::YUVColorSpace::UNKNOWN) {
+ for (int i = 0; i < DMABUF_BUFFER_PLANES; i++) {
+ mEGLImage[i] = LOCAL_EGL_NO_IMAGE;
+ }
+}
+
+DMABufSurfaceYUV::~DMABufSurfaceYUV() { ReleaseSurface(); }
+
+bool DMABufSurfaceYUV::UpdateYUVData(const VADRMPRIMESurfaceDescriptor& aDesc) {
+ if (aDesc.num_layers > DMABUF_BUFFER_PLANES ||
+ aDesc.num_objects > DMABUF_BUFFER_PLANES) {
+ return false;
+ }
+ if (mDmabufFds[0] >= 0) {
+ NS_WARNING("DMABufSurfaceYUV is already created!");
+ return false;
+ }
+ if (aDesc.fourcc == VA_FOURCC_NV12) {
+ mSurfaceType = SURFACE_NV12;
+ } else if (aDesc.fourcc == VA_FOURCC_YV12) {
+ mSurfaceType = SURFACE_YUV420;
+ } else {
+ NS_WARNING(
+ nsPrintfCString(
+ "UpdateYUVData(): Can't import surface data of 0x%x format\n",
+ aDesc.fourcc)
+ .get());
+ return false;
+ }
+
+ mBufferPlaneCount = aDesc.num_layers;
+ mBufferModifier = aDesc.objects[0].drm_format_modifier;
+
+ for (unsigned int i = 0; i < aDesc.num_layers; i++) {
+ // Intel exports VA-API surfaces in one object,planes have the same FD.
+ // AMD exports surfaces in two objects with different FDs.
+ bool dupFD = (aDesc.layers[i].object_index[0] != i);
+ int fd = aDesc.objects[aDesc.layers[i].object_index[0]].fd;
+ mDmabufFds[i] = dupFD ? dup(fd) : fd;
+
+ mDrmFormats[i] = aDesc.layers[i].drm_format;
+ mOffsets[i] = aDesc.layers[i].offset[0];
+ mStrides[i] = aDesc.layers[i].pitch[0];
+ mWidth[i] = aDesc.width >> i;
+ mHeight[i] = aDesc.height >> i;
+ }
+
+ return true;
+}
+
+bool DMABufSurfaceYUV::CreateYUVPlane(int aPlane, int aWidth, int aHeight,
+ int aDrmFormat) {
+ mWidth[aPlane] = aWidth;
+ mHeight[aPlane] = aHeight;
+ mDrmFormats[aPlane] = aDrmFormat;
+
+ mGbmBufferObject[aPlane] =
+ nsGbmLib::Create(GetDMABufDevice()->GetGbmDevice(), aWidth, aHeight,
+ aDrmFormat, GBM_BO_USE_LINEAR | GBM_BO_USE_TEXTURING);
+ if (!mGbmBufferObject[aPlane]) {
+ NS_WARNING("Failed to create GbmBufferObject!");
+ return false;
+ }
+
+ mStrides[aPlane] = nsGbmLib::GetStride(mGbmBufferObject[aPlane]);
+ mDmabufFds[aPlane] = nsGbmLib::GetFd(mGbmBufferObject[aPlane]);
+
+ return true;
+}
+
+void DMABufSurfaceYUV::UpdateYUVPlane(int aPlane, void* aPixelData,
+ int aLineSize) {
+ if (aLineSize == mWidth[aPlane] &&
+ (int)mMappedRegionStride[aPlane] == mWidth[aPlane]) {
+ memcpy(mMappedRegion[aPlane], aPixelData, aLineSize * mHeight[aPlane]);
+ } else {
+ char* src = (char*)aPixelData;
+ char* dest = (char*)mMappedRegion[aPlane];
+ for (int i = 0; i < mHeight[aPlane]; i++) {
+ memcpy(dest, src, mWidth[aPlane]);
+ src += aLineSize;
+ dest += mMappedRegionStride[aPlane];
+ }
+ }
+}
+
+bool DMABufSurfaceYUV::UpdateYUVData(void** aPixelData, int* aLineSizes) {
+ if (mSurfaceType != SURFACE_YUV420) {
+ NS_WARNING("UpdateYUVData can upload YUV420 surface type only!");
+ return false;
+ }
+
+ if (mBufferPlaneCount != 3) {
+ NS_WARNING("DMABufSurfaceYUV planes does not match!");
+ return false;
+ }
+
+ auto unmapBuffers = MakeScopeExit([&] {
+ Unmap(0);
+ Unmap(1);
+ Unmap(2);
+ });
+
+ // Map planes
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ MapInternal(0, 0, mWidth[i], mHeight[i], nullptr, GBM_BO_TRANSFER_WRITE, i);
+ if (!mMappedRegion[i]) {
+ NS_WARNING("DMABufSurfaceYUV plane can't be mapped!");
+ return false;
+ }
+ if ((int)mMappedRegionStride[i] < mWidth[i]) {
+ NS_WARNING("DMABufSurfaceYUV plane size stride does not match!");
+ return false;
+ }
+ }
+
+ // Copy planes
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ UpdateYUVPlane(i, aPixelData[i], aLineSizes[i]);
+ }
+
+ return true;
+}
+
+bool DMABufSurfaceYUV::Create(int aWidth, int aHeight, void** aPixelData,
+ int* aLineSizes) {
+ mSurfaceType = SURFACE_YUV420;
+ mBufferPlaneCount = 3;
+
+ if (!CreateYUVPlane(0, aWidth, aHeight, GBM_FORMAT_R8)) {
+ return false;
+ }
+ if (!CreateYUVPlane(1, aWidth >> 1, aHeight >> 1, GBM_FORMAT_R8)) {
+ return false;
+ }
+ if (!CreateYUVPlane(2, aWidth >> 1, aHeight >> 1, GBM_FORMAT_R8)) {
+ return false;
+ }
+
+ return aPixelData != nullptr && aLineSizes != nullptr
+ ? UpdateYUVData(aPixelData, aLineSizes)
+ : true;
+}
+
+bool DMABufSurfaceYUV::Create(const SurfaceDescriptor& aDesc) {
+ ImportSurfaceDescriptor(aDesc);
+ return true;
+}
+
+void DMABufSurfaceYUV::ImportSurfaceDescriptor(
+ const SurfaceDescriptorDMABuf& aDesc) {
+ mBufferPlaneCount = aDesc.fds().Length();
+ mSurfaceType = (mBufferPlaneCount == 2) ? SURFACE_NV12 : SURFACE_YUV420;
+ mBufferModifier = aDesc.modifier();
+ mColorSpace = aDesc.yUVColorSpace();
+ mUID = aDesc.uid();
+
+ MOZ_RELEASE_ASSERT(mBufferPlaneCount <= DMABUF_BUFFER_PLANES);
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ mDmabufFds[i] = aDesc.fds()[i].ClonePlatformHandle().release();
+ mWidth[i] = aDesc.width()[i];
+ mHeight[i] = aDesc.height()[i];
+ mDrmFormats[i] = aDesc.format()[i];
+ mStrides[i] = aDesc.strides()[i];
+ mOffsets[i] = aDesc.offsets()[i];
+ }
+
+ if (aDesc.fence().Length() > 0) {
+ mSyncFd = aDesc.fence()[0].ClonePlatformHandle().release();
+ }
+
+ if (aDesc.refCount().Length() > 0) {
+ GlobalRefCountImport(aDesc.refCount()[0].ClonePlatformHandle().release());
+ }
+}
+
+bool DMABufSurfaceYUV::Serialize(
+ mozilla::layers::SurfaceDescriptor& aOutDescriptor) {
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> width;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> height;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> format;
+ AutoTArray<ipc::FileDescriptor, DMABUF_BUFFER_PLANES> fds;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> strides;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> offsets;
+ AutoTArray<ipc::FileDescriptor, 1> fenceFDs;
+ AutoTArray<ipc::FileDescriptor, 1> refCountFDs;
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ width.AppendElement(mWidth[i]);
+ height.AppendElement(mHeight[i]);
+ format.AppendElement(mDrmFormats[i]);
+ fds.AppendElement(ipc::FileDescriptor(mDmabufFds[i]));
+ strides.AppendElement(mStrides[i]);
+ offsets.AppendElement(mOffsets[i]);
+ }
+
+ if (mSync) {
+ fenceFDs.AppendElement(ipc::FileDescriptor(mSyncFd));
+ }
+
+ if (mGlobalRefCountFd) {
+ refCountFDs.AppendElement(ipc::FileDescriptor(mGlobalRefCountFd));
+ }
+
+ aOutDescriptor = SurfaceDescriptorDMABuf(
+ mSurfaceType, mBufferModifier, 0, fds, width, height, format, strides,
+ offsets, GetYUVColorSpace(), fenceFDs, mUID, refCountFDs);
+ return true;
+}
+
+bool DMABufSurfaceYUV::CreateTexture(GLContext* aGLContext, int aPlane) {
+ MOZ_ASSERT(!mEGLImage[aPlane] && !mTexture[aPlane],
+ "EGLImage/Texture is already created!");
+
+ if (!aGLContext) return false;
+ const auto& gle = gl::GLContextEGL::Cast(aGLContext);
+ const auto& egl = gle->mEgl;
+
+ nsTArray<EGLint> attribs;
+ attribs.AppendElement(LOCAL_EGL_WIDTH);
+ attribs.AppendElement(mWidth[aPlane]);
+ attribs.AppendElement(LOCAL_EGL_HEIGHT);
+ attribs.AppendElement(mHeight[aPlane]);
+ attribs.AppendElement(LOCAL_EGL_LINUX_DRM_FOURCC_EXT);
+ attribs.AppendElement(mDrmFormats[aPlane]);
+#define ADD_PLANE_ATTRIBS_NV12(plane_idx) \
+ attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_FD_EXT); \
+ attribs.AppendElement(mDmabufFds[aPlane]); \
+ attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_OFFSET_EXT); \
+ attribs.AppendElement((int)mOffsets[aPlane]); \
+ attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_PITCH_EXT); \
+ attribs.AppendElement((int)mStrides[aPlane]);
+ ADD_PLANE_ATTRIBS_NV12(0);
+#undef ADD_PLANE_ATTRIBS_NV12
+ attribs.AppendElement(LOCAL_EGL_NONE);
+
+ mEGLImage[aPlane] =
+ egl->fCreateImage(LOCAL_EGL_NO_CONTEXT, LOCAL_EGL_LINUX_DMA_BUF_EXT,
+ nullptr, attribs.Elements());
+ if (mEGLImage[aPlane] == LOCAL_EGL_NO_IMAGE) {
+ NS_WARNING("EGLImageKHR creation failed");
+ return false;
+ }
+
+ aGLContext->MakeCurrent();
+ aGLContext->fGenTextures(1, &mTexture[aPlane]);
+ const ScopedBindTexture savedTex(aGLContext, mTexture[aPlane]);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
+ LOCAL_GL_LINEAR);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
+ LOCAL_GL_LINEAR);
+ aGLContext->fEGLImageTargetTexture2D(LOCAL_GL_TEXTURE_2D, mEGLImage[aPlane]);
+ mGL = aGLContext;
+ return true;
+}
+
+void DMABufSurfaceYUV::ReleaseTextures() {
+ FenceDelete();
+
+ bool textureActive = false;
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ if (mTexture[i]) {
+ textureActive = true;
+ break;
+ }
+ }
+
+ if (!mGL) return;
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ if (textureActive && mGL->MakeCurrent()) {
+ mGL->fDeleteTextures(DMABUF_BUFFER_PLANES, mTexture);
+ for (int i = 0; i < DMABUF_BUFFER_PLANES; i++) {
+ mTexture[i] = 0;
+ }
+ mGL = nullptr;
+ }
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ if (mEGLImage[i]) {
+ egl->fDestroyImage(mEGLImage[i]);
+ mEGLImage[i] = nullptr;
+ }
+ }
+}
+
+gfx::SurfaceFormat DMABufSurfaceYUV::GetFormat() {
+ switch (mSurfaceType) {
+ case SURFACE_NV12:
+ return gfx::SurfaceFormat::NV12;
+ case SURFACE_YUV420:
+ return gfx::SurfaceFormat::YUV;
+ default:
+ NS_WARNING("DMABufSurfaceYUV::GetFormat(): Wrong surface format!");
+ return gfx::SurfaceFormat::UNKNOWN;
+ }
+}
+
+// GL uses swapped R and B components so report accordingly.
+gfx::SurfaceFormat DMABufSurfaceYUV::GetFormatGL() { return GetFormat(); }
+
+uint32_t DMABufSurfaceYUV::GetTextureCount() {
+ switch (mSurfaceType) {
+ case SURFACE_NV12:
+ return 2;
+ case SURFACE_YUV420:
+ return 3;
+ default:
+ NS_WARNING("DMABufSurfaceYUV::GetTextureCount(): Wrong surface format!");
+ return 1;
+ }
+}
+
+void DMABufSurfaceYUV::ReleaseSurface() {
+ ReleaseTextures();
+ ReleaseDMABuf();
+}
diff --git a/widget/gtk/DMABufSurface.h b/widget/gtk/DMABufSurface.h
new file mode 100644
index 0000000000..936c3c75a8
--- /dev/null
+++ b/widget/gtk/DMABufSurface.h
@@ -0,0 +1,290 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 DMABufSurface_h__
+#define DMABufSurface_h__
+
+#include <stdint.h>
+#include "mozilla/widget/nsWaylandDisplay.h"
+#include "mozilla/widget/va_drmcommon.h"
+#include "GLTypes.h"
+
+typedef void* EGLImageKHR;
+typedef void* EGLSyncKHR;
+
+#define DMABUF_BUFFER_PLANES 4
+
+namespace mozilla {
+namespace layers {
+class SurfaceDescriptor;
+class SurfaceDescriptorDMABuf;
+} // namespace layers
+namespace gl {
+class GLContext;
+}
+} // namespace mozilla
+
+typedef enum {
+ // Use alpha pixel format
+ DMABUF_ALPHA = 1 << 0,
+ // Surface is used as texture and may be also shared
+ DMABUF_TEXTURE = 1 << 1,
+ // Use modifiers. Such dmabuf surface may have more planes
+ // and complex internal structure (tiling/compression/etc.)
+ // so we can't do direct rendering to it.
+ DMABUF_USE_MODIFIERS = 1 << 3,
+} DMABufSurfaceFlags;
+
+class DMABufSurfaceRGBA;
+class DMABufSurfaceYUV;
+
+class DMABufSurface {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DMABufSurface)
+
+ enum SurfaceType {
+ SURFACE_RGBA,
+ SURFACE_NV12,
+ SURFACE_YUV420,
+ };
+
+ // Import surface from SurfaceDescriptor. This is usually
+ // used to copy surface from another process over IPC.
+ // When a global reference counter was created for the surface
+ // (see bellow) it's automatically referenced.
+ static already_AddRefed<DMABufSurface> CreateDMABufSurface(
+ const mozilla::layers::SurfaceDescriptor& aDesc);
+
+ // Export surface to another process via. SurfaceDescriptor.
+ virtual bool Serialize(
+ mozilla::layers::SurfaceDescriptor& aOutDescriptor) = 0;
+
+ virtual int GetWidth(int aPlane = 0) = 0;
+ virtual int GetHeight(int aPlane = 0) = 0;
+ virtual mozilla::gfx::SurfaceFormat GetFormat() = 0;
+ virtual mozilla::gfx::SurfaceFormat GetFormatGL() = 0;
+
+ virtual bool CreateTexture(mozilla::gl::GLContext* aGLContext,
+ int aPlane = 0) = 0;
+ virtual void ReleaseTextures() = 0;
+ virtual GLuint GetTexture(int aPlane = 0) = 0;
+ virtual EGLImageKHR GetEGLImage(int aPlane = 0) = 0;
+
+ SurfaceType GetSurfaceType() { return mSurfaceType; };
+ virtual uint32_t GetTextureCount() = 0;
+
+ bool IsMapped(int aPlane = 0) { return (mMappedRegion[aPlane] != nullptr); };
+ void Unmap(int aPlane = 0);
+
+ virtual DMABufSurfaceRGBA* GetAsDMABufSurfaceRGBA() { return nullptr; }
+ virtual DMABufSurfaceYUV* GetAsDMABufSurfaceYUV() { return nullptr; }
+
+ virtual mozilla::gfx::YUVColorSpace GetYUVColorSpace() {
+ return mozilla::gfx::YUVColorSpace::UNKNOWN;
+ };
+ virtual bool IsFullRange() { return false; };
+
+ void FenceSet();
+ void FenceWait();
+ void FenceDelete();
+
+ // Set and get a global surface UID. The UID is shared across process
+ // and it's used to track surface lifetime in various parts of rendering
+ // engine.
+ uint32_t GetUID() const { return mUID; };
+
+ // Creates a global reference counter objects attached to the surface.
+ // It's created as unreferenced, i.e. IsGlobalRefSet() returns false
+ // right after GlobalRefCountCreate() call.
+ //
+ // The counter is shared by all surface instances across processes
+ // so it tracks global surface usage.
+ //
+ // The counter is automatically referenced when a new surface instance is
+ // created with SurfaceDescriptor (usually copied to another process over IPC)
+ // and it's unreferenced when surface is deleted.
+ //
+ // So without any additional GlobalRefAdd()/GlobalRefRelease() calls
+ // the IsGlobalRefSet() returns true if any other process use the surface.
+ void GlobalRefCountCreate();
+
+ // If global reference counter was created by GlobalRefCountCreate()
+ // returns true when there's an active surface reference.
+ bool IsGlobalRefSet() const;
+
+ // Add/Remove additional reference to the surface global reference counter.
+ void GlobalRefAdd();
+ void GlobalRefRelease();
+
+ // Release all underlying data.
+ virtual void ReleaseSurface() = 0;
+
+#ifdef DEBUG
+ virtual void DumpToFile(const char* pFile){};
+#endif
+
+ DMABufSurface(SurfaceType aSurfaceType);
+
+ protected:
+ virtual bool Create(const mozilla::layers::SurfaceDescriptor& aDesc) = 0;
+ bool FenceImportFromFd();
+
+ void GlobalRefCountImport(int aFd);
+ void GlobalRefCountDelete();
+
+ void ReleaseDMABuf();
+
+ void* MapInternal(uint32_t aX, uint32_t aY, uint32_t aWidth, uint32_t aHeight,
+ uint32_t* aStride, int aGbmFlags, int aPlane = 0);
+
+ virtual ~DMABufSurface();
+
+ SurfaceType mSurfaceType;
+ uint64_t mBufferModifier;
+
+ int mBufferPlaneCount;
+ int mDmabufFds[DMABUF_BUFFER_PLANES];
+ uint32_t mDrmFormats[DMABUF_BUFFER_PLANES];
+ uint32_t mStrides[DMABUF_BUFFER_PLANES];
+ uint32_t mOffsets[DMABUF_BUFFER_PLANES];
+
+ struct gbm_bo* mGbmBufferObject[DMABUF_BUFFER_PLANES];
+ void* mMappedRegion[DMABUF_BUFFER_PLANES];
+ void* mMappedRegionData[DMABUF_BUFFER_PLANES];
+ uint32_t mMappedRegionStride[DMABUF_BUFFER_PLANES];
+
+ int mSyncFd;
+ EGLSyncKHR mSync;
+ RefPtr<mozilla::gl::GLContext> mGL;
+
+ int mGlobalRefCountFd;
+ uint32_t mUID;
+};
+
+class DMABufSurfaceRGBA : public DMABufSurface {
+ public:
+ static already_AddRefed<DMABufSurfaceRGBA> CreateDMABufSurface(
+ int aWidth, int aHeight, int aDMABufSurfaceFlags);
+
+ bool Serialize(mozilla::layers::SurfaceDescriptor& aOutDescriptor);
+
+ DMABufSurfaceRGBA* GetAsDMABufSurfaceRGBA() { return this; }
+
+ void Clear();
+
+ void ReleaseSurface();
+
+ bool CopyFrom(class DMABufSurface* aSourceSurface);
+
+ int GetWidth(int aPlane = 0) { return mWidth; };
+ int GetHeight(int aPlane = 0) { return mHeight; };
+ mozilla::gfx::SurfaceFormat GetFormat();
+ mozilla::gfx::SurfaceFormat GetFormatGL();
+ bool HasAlpha();
+
+ void* MapReadOnly(uint32_t aX, uint32_t aY, uint32_t aWidth, uint32_t aHeight,
+ uint32_t* aStride = nullptr);
+ void* MapReadOnly(uint32_t* aStride = nullptr);
+ void* Map(uint32_t aX, uint32_t aY, uint32_t aWidth, uint32_t aHeight,
+ uint32_t* aStride = nullptr);
+ void* Map(uint32_t* aStride = nullptr);
+ void* GetMappedRegion(int aPlane = 0) { return mMappedRegion[aPlane]; };
+ uint32_t GetMappedRegionStride(int aPlane = 0) {
+ return mMappedRegionStride[aPlane];
+ };
+
+ bool CreateTexture(mozilla::gl::GLContext* aGLContext, int aPlane = 0);
+ void ReleaseTextures();
+ GLuint GetTexture(int aPlane = 0) { return mTexture; };
+ EGLImageKHR GetEGLImage(int aPlane = 0) { return mEGLImage; };
+
+ uint32_t GetTextureCount() { return 1; };
+
+#ifdef DEBUG
+ virtual void DumpToFile(const char* pFile);
+#endif
+
+ DMABufSurfaceRGBA();
+
+ private:
+ ~DMABufSurfaceRGBA();
+
+ bool Create(int aWidth, int aHeight, int aDMABufSurfaceFlags);
+ bool Create(const mozilla::layers::SurfaceDescriptor& aDesc);
+
+ void ImportSurfaceDescriptor(const mozilla::layers::SurfaceDescriptor& aDesc);
+
+ private:
+ int mSurfaceFlags;
+
+ int mWidth;
+ int mHeight;
+ mozilla::widget::GbmFormat* mGmbFormat;
+
+ EGLImageKHR mEGLImage;
+ GLuint mTexture;
+ uint32_t mGbmBufferFlags;
+};
+
+class DMABufSurfaceYUV : public DMABufSurface {
+ public:
+ static already_AddRefed<DMABufSurfaceYUV> CreateYUVSurface(
+ int aWidth, int aHeight, void** aPixelData = nullptr,
+ int* aLineSizes = nullptr);
+
+ static already_AddRefed<DMABufSurfaceYUV> CreateYUVSurface(
+ const VADRMPRIMESurfaceDescriptor& aDesc);
+
+ bool Serialize(mozilla::layers::SurfaceDescriptor& aOutDescriptor);
+
+ DMABufSurfaceYUV* GetAsDMABufSurfaceYUV() { return this; };
+
+ int GetWidth(int aPlane = 0) { return mWidth[aPlane]; }
+ int GetHeight(int aPlane = 0) { return mHeight[aPlane]; }
+ mozilla::gfx::SurfaceFormat GetFormat();
+ mozilla::gfx::SurfaceFormat GetFormatGL();
+
+ bool CreateTexture(mozilla::gl::GLContext* aGLContext, int aPlane = 0);
+ void ReleaseTextures();
+
+ void ReleaseSurface();
+
+ GLuint GetTexture(int aPlane = 0) { return mTexture[aPlane]; };
+ EGLImageKHR GetEGLImage(int aPlane = 0) { return mEGLImage[aPlane]; };
+
+ uint32_t GetTextureCount();
+
+ void SetYUVColorSpace(mozilla::gfx::YUVColorSpace aColorSpace) {
+ mColorSpace = aColorSpace;
+ }
+ mozilla::gfx::YUVColorSpace GetYUVColorSpace() { return mColorSpace; }
+
+ bool IsFullRange() { return true; }
+
+ DMABufSurfaceYUV();
+
+ bool UpdateYUVData(void** aPixelData, int* aLineSizes);
+ bool UpdateYUVData(const VADRMPRIMESurfaceDescriptor& aDesc);
+
+ private:
+ ~DMABufSurfaceYUV();
+
+ bool Create(const mozilla::layers::SurfaceDescriptor& aDesc);
+ bool Create(int aWidth, int aHeight, void** aPixelData, int* aLineSizes);
+ bool CreateYUVPlane(int aPlane, int aWidth, int aHeight, int aDrmFormat);
+ void UpdateYUVPlane(int aPlane, void* aPixelData, int aLineSize);
+
+ void ImportSurfaceDescriptor(
+ const mozilla::layers::SurfaceDescriptorDMABuf& aDesc);
+
+ int mWidth[DMABUF_BUFFER_PLANES];
+ int mHeight[DMABUF_BUFFER_PLANES];
+ EGLImageKHR mEGLImage[DMABUF_BUFFER_PLANES];
+ GLuint mTexture[DMABUF_BUFFER_PLANES];
+ mozilla::gfx::YUVColorSpace mColorSpace;
+};
+
+#endif
diff --git a/widget/gtk/GRefPtr.h b/widget/gtk/GRefPtr.h
new file mode 100644
index 0000000000..3705d5354d
--- /dev/null
+++ b/widget/gtk/GRefPtr.h
@@ -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/. */
+
+#ifndef GRefPtr_h_
+#define GRefPtr_h_
+
+// Allows to use RefPtr<T> with various kinds of GObjects
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+
+template <typename T>
+struct GObjectRefPtrTraits {
+ static void AddRef(T* aObject) { g_object_ref(aObject); }
+ static void Release(T* aObject) { g_object_unref(aObject); }
+};
+
+template <>
+struct RefPtrTraits<GtkWidget> : public GObjectRefPtrTraits<GtkWidget> {};
+
+template <>
+struct RefPtrTraits<GdkDragContext>
+ : public GObjectRefPtrTraits<GdkDragContext> {};
+
+} // namespace mozilla
+
+#endif
diff --git a/widget/gtk/GtkCompositorWidget.cpp b/widget/gtk/GtkCompositorWidget.cpp
new file mode 100644
index 0000000000..bfb970ec47
--- /dev/null
+++ b/widget/gtk/GtkCompositorWidget.cpp
@@ -0,0 +1,147 @@
+/* -*- 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 "GtkCompositorWidget.h"
+
+#include "gfxPlatformGtk.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/widget/InProcessCompositorWidget.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsWindow.h"
+
+namespace mozilla {
+namespace widget {
+
+GtkCompositorWidget::GtkCompositorWidget(
+ const GtkCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions, nsWindow* aWindow)
+ : CompositorWidget(aOptions),
+ mWidget(aWindow),
+ mClientSize("GtkCompositorWidget::mClientSize") {
+#if defined(MOZ_WAYLAND)
+ if (!aInitData.IsX11Display()) {
+ if (!aWindow) {
+ NS_WARNING("GtkCompositorWidget: We're missing nsWindow!");
+ }
+ mProvider.Initialize(aWindow);
+ }
+#endif
+#if defined(MOZ_X11)
+ if (aInitData.IsX11Display()) {
+ // If we have a nsWindow, then grab the already existing display connection
+ // If we don't, then use the init data to connect to the display
+ if (aWindow) {
+ mXDisplay = aWindow->XDisplay();
+ } else {
+ mXDisplay = XOpenDisplay(aInitData.XDisplayString().get());
+ }
+ mXWindow = (Window)aInitData.XWindow();
+
+ // Grab the window's visual and depth
+ XWindowAttributes windowAttrs;
+ if (!XGetWindowAttributes(mXDisplay, mXWindow, &windowAttrs)) {
+ NS_WARNING("GtkCompositorWidget(): XGetWindowAttributes() failed!");
+ }
+
+ Visual* visual = windowAttrs.visual;
+ mDepth = windowAttrs.depth;
+
+ // Initialize the window surface provider
+ mProvider.Initialize(mXDisplay, mXWindow, visual, mDepth,
+ aInitData.Shaped());
+ }
+#endif
+ auto size = mClientSize.Lock();
+ *size = aInitData.InitialClientSize();
+}
+
+GtkCompositorWidget::~GtkCompositorWidget() {
+ mProvider.CleanupResources();
+
+#if defined(MOZ_X11)
+ // If we created our own display connection, we need to destroy it
+ if (!mWidget && mXDisplay) {
+ XCloseDisplay(mXDisplay);
+ mXDisplay = nullptr;
+ }
+#endif
+}
+
+already_AddRefed<gfx::DrawTarget> GtkCompositorWidget::StartRemoteDrawing() {
+ return nullptr;
+}
+void GtkCompositorWidget::EndRemoteDrawing() {}
+
+already_AddRefed<gfx::DrawTarget>
+GtkCompositorWidget::StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion, layers::BufferMode* aBufferMode) {
+ return mProvider.StartRemoteDrawingInRegion(aInvalidRegion, aBufferMode);
+}
+
+void GtkCompositorWidget::EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
+ mProvider.EndRemoteDrawingInRegion(aDrawTarget, aInvalidRegion);
+}
+
+nsIWidget* GtkCompositorWidget::RealWidget() { return mWidget; }
+
+void GtkCompositorWidget::NotifyClientSizeChanged(
+ const LayoutDeviceIntSize& aClientSize) {
+ auto size = mClientSize.Lock();
+ *size = aClientSize;
+}
+
+LayoutDeviceIntSize GtkCompositorWidget::GetClientSize() {
+ auto size = mClientSize.Lock();
+ return *size;
+}
+
+uintptr_t GtkCompositorWidget::GetWidgetKey() {
+ return reinterpret_cast<uintptr_t>(mWidget);
+}
+
+EGLNativeWindowType GtkCompositorWidget::GetEGLNativeWindow() {
+ if (mWidget) {
+ return (EGLNativeWindowType)mWidget->GetNativeData(NS_NATIVE_EGL_WINDOW);
+ }
+#if defined(MOZ_X11)
+ if (mXWindow) {
+ return (EGLNativeWindowType)mXWindow;
+ }
+#endif
+ return nullptr;
+}
+
+int32_t GtkCompositorWidget::GetDepth() { return mDepth; }
+
+#if defined(MOZ_WAYLAND)
+void GtkCompositorWidget::SetEGLNativeWindowSize(
+ const LayoutDeviceIntSize& aEGLWindowSize) {
+ if (mWidget) {
+ mWidget->SetEGLNativeWindowSize(aEGLWindowSize);
+ }
+}
+#endif
+
+void GtkCompositorWidget::ClearBeforePaint(
+ RefPtr<gfx::DrawTarget> aTarget, const LayoutDeviceIntRegion& aRegion) {
+ // We need to clear target buffer alpha values of popup windows as
+ // SW-WR paints with alpha blending (see Bug 1674473).
+ if (mWidget->IsPopup()) {
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ aTarget->ClearRect(gfx::Rect(iter.Get().ToUnknownRect()));
+ }
+ }
+
+ // Clear background of titlebar area to render titlebar
+ // transparent corners correctly.
+ gfx::Rect rect;
+ if (mWidget->GetTitlebarRect(rect)) {
+ aTarget->ClearRect(rect);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/GtkCompositorWidget.h b/widget/gtk/GtkCompositorWidget.h
new file mode 100644
index 0000000000..f0cb15fbfc
--- /dev/null
+++ b/widget/gtk/GtkCompositorWidget.h
@@ -0,0 +1,103 @@
+/* -*- 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 widget_gtk_GtkCompositorWidget_h
+#define widget_gtk_GtkCompositorWidget_h
+
+#include "GLDefs.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/widget/CompositorWidget.h"
+#include "WindowSurfaceProvider.h"
+
+class nsIWidget;
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class PlatformCompositorWidgetDelegate : public CompositorWidgetDelegate {
+ public:
+ virtual void NotifyClientSizeChanged(
+ const LayoutDeviceIntSize& aClientSize) = 0;
+
+ // CompositorWidgetDelegate Overrides
+
+ PlatformCompositorWidgetDelegate* AsPlatformSpecificDelegate() override {
+ return this;
+ }
+};
+
+class GtkCompositorWidgetInitData;
+
+class GtkCompositorWidget : public CompositorWidget,
+ public PlatformCompositorWidgetDelegate {
+ public:
+ GtkCompositorWidget(const GtkCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions,
+ nsWindow* aWindow /* = nullptr*/);
+ ~GtkCompositorWidget();
+
+ // CompositorWidget Overrides
+
+ already_AddRefed<gfx::DrawTarget> StartRemoteDrawing() override;
+ void EndRemoteDrawing() override;
+
+ already_AddRefed<gfx::DrawTarget> StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode) override;
+ void EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget,
+ const LayoutDeviceIntRegion& aInvalidRegion) override;
+ uintptr_t GetWidgetKey() override;
+
+ LayoutDeviceIntSize GetClientSize() override;
+
+ nsIWidget* RealWidget() override;
+ GtkCompositorWidget* AsX11() override { return this; }
+ CompositorWidgetDelegate* AsDelegate() override { return this; }
+
+ EGLNativeWindowType GetEGLNativeWindow();
+ int32_t GetDepth();
+
+ void ClearBeforePaint(RefPtr<gfx::DrawTarget> aTarget,
+ const LayoutDeviceIntRegion& aRegion) override;
+
+#if defined(MOZ_X11)
+ Display* XDisplay() const { return mXDisplay; }
+ Window XWindow() const { return mXWindow; }
+#endif
+#if defined(MOZ_WAYLAND)
+ void SetEGLNativeWindowSize(const LayoutDeviceIntSize& aEGLWindowSize);
+#endif
+
+ // PlatformCompositorWidgetDelegate Overrides
+
+ void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override;
+
+ protected:
+ nsWindow* mWidget;
+
+ private:
+ // This field is written to on the main thread and read from on the compositor
+ // or renderer thread. During window resizing, this is subject to a (largely
+ // benign) read/write race, see bug 1665726. The DataMutex doesn't prevent the
+ // read/write race, but it does make it Not Undefined Behaviour, and also
+ // ensures we only ever use the old or new size, and not some weird synthesis
+ // of the two.
+ DataMutex<LayoutDeviceIntSize> mClientSize;
+
+ WindowSurfaceProvider mProvider;
+
+#if defined(MOZ_X11)
+ Display* mXDisplay = {};
+ Window mXWindow = {};
+#endif
+ int32_t mDepth = {};
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_GtkCompositorWidget_h
diff --git a/widget/gtk/IMContextWrapper.cpp b/widget/gtk/IMContextWrapper.cpp
new file mode 100644
index 0000000000..8600efb4c3
--- /dev/null
+++ b/widget/gtk/IMContextWrapper.cpp
@@ -0,0 +1,3169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "prtime.h"
+
+#include "IMContextWrapper.h"
+#include "nsGtkKeyUtils.h"
+#include "nsWindow.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Likely.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/ToString.h"
+#include "WritingModes.h"
+
+namespace mozilla {
+namespace widget {
+
+LazyLogModule gGtkIMLog("nsGtkIMModuleWidgets");
+
+static inline const char* ToChar(bool aBool) {
+ return aBool ? "true" : "false";
+}
+
+static const char* GetEventType(GdkEventKey* aKeyEvent) {
+ switch (aKeyEvent->type) {
+ case GDK_KEY_PRESS:
+ return "GDK_KEY_PRESS";
+ case GDK_KEY_RELEASE:
+ return "GDK_KEY_RELEASE";
+ default:
+ return "Unknown";
+ }
+}
+
+class GetEventStateName : public nsAutoCString {
+ public:
+ explicit GetEventStateName(guint aState,
+ IMContextWrapper::IMContextID aIMContextID =
+ IMContextWrapper::IMContextID::Unknown) {
+ if (aState & GDK_SHIFT_MASK) {
+ AppendModifier("shift");
+ }
+ if (aState & GDK_CONTROL_MASK) {
+ AppendModifier("control");
+ }
+ if (aState & GDK_MOD1_MASK) {
+ AppendModifier("mod1");
+ }
+ if (aState & GDK_MOD2_MASK) {
+ AppendModifier("mod2");
+ }
+ if (aState & GDK_MOD3_MASK) {
+ AppendModifier("mod3");
+ }
+ if (aState & GDK_MOD4_MASK) {
+ AppendModifier("mod4");
+ }
+ if (aState & GDK_MOD4_MASK) {
+ AppendModifier("mod5");
+ }
+ if (aState & GDK_MOD4_MASK) {
+ AppendModifier("mod5");
+ }
+ switch (aIMContextID) {
+ case IMContextWrapper::IMContextID::IBus:
+ static const guint IBUS_HANDLED_MASK = 1 << 24;
+ static const guint IBUS_IGNORED_MASK = 1 << 25;
+ if (aState & IBUS_HANDLED_MASK) {
+ AppendModifier("IBUS_HANDLED_MASK");
+ }
+ if (aState & IBUS_IGNORED_MASK) {
+ AppendModifier("IBUS_IGNORED_MASK");
+ }
+ break;
+ case IMContextWrapper::IMContextID::Fcitx:
+ case IMContextWrapper::IMContextID::Fcitx5:
+ static const guint FcitxKeyState_HandledMask = 1 << 24;
+ static const guint FcitxKeyState_IgnoredMask = 1 << 25;
+ if (aState & FcitxKeyState_HandledMask) {
+ AppendModifier("FcitxKeyState_HandledMask");
+ }
+ if (aState & FcitxKeyState_IgnoredMask) {
+ AppendModifier("FcitxKeyState_IgnoredMask");
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private:
+ void AppendModifier(const char* aModifierName) {
+ if (!IsEmpty()) {
+ AppendLiteral(" + ");
+ }
+ Append(aModifierName);
+ }
+};
+
+class GetWritingModeName : public nsAutoCString {
+ public:
+ explicit GetWritingModeName(const WritingMode& aWritingMode) {
+ if (!aWritingMode.IsVertical()) {
+ AssignLiteral("Horizontal");
+ return;
+ }
+ if (aWritingMode.IsVerticalLR()) {
+ AssignLiteral("Vertical (LTR)");
+ return;
+ }
+ AssignLiteral("Vertical (RTL)");
+ }
+ virtual ~GetWritingModeName() = default;
+};
+
+class GetTextRangeStyleText final : public nsAutoCString {
+ public:
+ explicit GetTextRangeStyleText(const TextRangeStyle& aStyle) {
+ if (!aStyle.IsDefined()) {
+ AssignLiteral("{ IsDefined()=false }");
+ return;
+ }
+
+ if (aStyle.IsLineStyleDefined()) {
+ AppendLiteral("{ mLineStyle=");
+ AppendLineStyle(aStyle.mLineStyle);
+ if (aStyle.IsUnderlineColorDefined()) {
+ AppendLiteral(", mUnderlineColor=");
+ AppendColor(aStyle.mUnderlineColor);
+ } else {
+ AppendLiteral(", IsUnderlineColorDefined=false");
+ }
+ } else {
+ AppendLiteral("{ IsLineStyleDefined()=false");
+ }
+
+ if (aStyle.IsForegroundColorDefined()) {
+ AppendLiteral(", mForegroundColor=");
+ AppendColor(aStyle.mForegroundColor);
+ } else {
+ AppendLiteral(", IsForegroundColorDefined()=false");
+ }
+
+ if (aStyle.IsBackgroundColorDefined()) {
+ AppendLiteral(", mBackgroundColor=");
+ AppendColor(aStyle.mBackgroundColor);
+ } else {
+ AppendLiteral(", IsBackgroundColorDefined()=false");
+ }
+
+ AppendLiteral(" }");
+ }
+ void AppendLineStyle(TextRangeStyle::LineStyle aLineStyle) {
+ switch (aLineStyle) {
+ case TextRangeStyle::LineStyle::None:
+ AppendLiteral("LineStyle::None");
+ break;
+ case TextRangeStyle::LineStyle::Solid:
+ AppendLiteral("LineStyle::Solid");
+ break;
+ case TextRangeStyle::LineStyle::Dotted:
+ AppendLiteral("LineStyle::Dotted");
+ break;
+ case TextRangeStyle::LineStyle::Dashed:
+ AppendLiteral("LineStyle::Dashed");
+ break;
+ case TextRangeStyle::LineStyle::Double:
+ AppendLiteral("LineStyle::Double");
+ break;
+ case TextRangeStyle::LineStyle::Wavy:
+ AppendLiteral("LineStyle::Wavy");
+ break;
+ default:
+ AppendPrintf("Invalid(0x%02X)",
+ static_cast<TextRangeStyle::LineStyleType>(aLineStyle));
+ break;
+ }
+ }
+ void AppendColor(nscolor aColor) {
+ AppendPrintf("{ R=0x%02X, G=0x%02X, B=0x%02X, A=0x%02X }", NS_GET_R(aColor),
+ NS_GET_G(aColor), NS_GET_B(aColor), NS_GET_A(aColor));
+ }
+ virtual ~GetTextRangeStyleText() = default;
+};
+
+const static bool kUseSimpleContextDefault = false;
+
+/******************************************************************************
+ * SelectionStyleProvider
+ *
+ * IME (e.g., fcitx, ~4.2.8.3) may look up selection colors of widget, which
+ * is related to the window associated with the IM context, to support any
+ * colored widgets. Our editor (like <input type="text">) is rendered as
+ * native GtkTextView as far as possible by default and if editor color is
+ * changed by web apps, nsTextFrame may swap background color of foreground
+ * color of composition string for making composition string is always
+ * visually distinct in normal text.
+ *
+ * So, we would like IME to set style of composition string to good colors
+ * in GtkTextView. Therefore, this class overwrites selection colors of
+ * our widget with selection colors of GtkTextView so that it's possible IME
+ * to refer selection colors of GtkTextView via our widget.
+ ******************************************************************************/
+
+class SelectionStyleProvider final {
+ public:
+ static SelectionStyleProvider* GetInstance() {
+ if (sHasShutDown) {
+ return nullptr;
+ }
+ if (!sInstance) {
+ sInstance = new SelectionStyleProvider();
+ }
+ return sInstance;
+ }
+
+ static void Shutdown() {
+ if (sInstance) {
+ g_object_unref(sInstance->mProvider);
+ }
+ delete sInstance;
+ sInstance = nullptr;
+ sHasShutDown = true;
+ }
+
+ // aGDKWindow is a GTK window which will be associated with an IM context.
+ void AttachTo(GdkWindow* aGDKWindow) {
+ GtkWidget* widget = nullptr;
+ // gdk_window_get_user_data() typically returns pointer to widget that
+ // window belongs to. If it's widget, fcitx retrieves selection colors
+ // of them. So, we need to overwrite its style.
+ gdk_window_get_user_data(aGDKWindow, (gpointer*)&widget);
+ if (GTK_IS_WIDGET(widget)) {
+ gtk_style_context_add_provider(gtk_widget_get_style_context(widget),
+ GTK_STYLE_PROVIDER(mProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ }
+
+ void OnThemeChanged() {
+ // fcitx refers GtkStyle::text[GTK_STATE_SELECTED] and
+ // GtkStyle::bg[GTK_STATE_SELECTED] (although pair of text and *base*
+ // or *fg* and bg is correct). gtk_style_update_from_context() will
+ // set these colors using the widget's GtkStyleContext and so the
+ // colors can be controlled by a ":selected" CSS rule.
+ nsAutoCString style(":selected{");
+ // FYI: LookAndFeel always returns selection colors of GtkTextView.
+ nscolor selectionForegroundColor;
+ if (NS_SUCCEEDED(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::TextSelectForeground,
+ &selectionForegroundColor))) {
+ double alpha =
+ static_cast<double>(NS_GET_A(selectionForegroundColor)) / 0xFF;
+ style.AppendPrintf("color:rgba(%u,%u,%u,",
+ NS_GET_R(selectionForegroundColor),
+ NS_GET_G(selectionForegroundColor),
+ NS_GET_B(selectionForegroundColor));
+ // We can't use AppendPrintf here, because it does locale-specific
+ // formatting of floating-point values.
+ style.AppendFloat(alpha);
+ style.AppendPrintf(");");
+ }
+ nscolor selectionBackgroundColor;
+ if (NS_SUCCEEDED(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::TextSelectBackground,
+ &selectionBackgroundColor))) {
+ double alpha =
+ static_cast<double>(NS_GET_A(selectionBackgroundColor)) / 0xFF;
+ style.AppendPrintf("background-color:rgba(%u,%u,%u,",
+ NS_GET_R(selectionBackgroundColor),
+ NS_GET_G(selectionBackgroundColor),
+ NS_GET_B(selectionBackgroundColor));
+ style.AppendFloat(alpha);
+ style.AppendPrintf(");");
+ }
+ style.AppendLiteral("}");
+ gtk_css_provider_load_from_data(mProvider, style.get(), -1, nullptr);
+ }
+
+ private:
+ static SelectionStyleProvider* sInstance;
+ static bool sHasShutDown;
+ GtkCssProvider* const mProvider;
+
+ SelectionStyleProvider() : mProvider(gtk_css_provider_new()) {
+ OnThemeChanged();
+ }
+};
+
+SelectionStyleProvider* SelectionStyleProvider::sInstance = nullptr;
+bool SelectionStyleProvider::sHasShutDown = false;
+
+/******************************************************************************
+ * IMContextWrapper
+ ******************************************************************************/
+
+IMContextWrapper* IMContextWrapper::sLastFocusedContext = nullptr;
+guint16 IMContextWrapper::sWaitingSynthesizedKeyPressHardwareKeyCode = 0;
+bool IMContextWrapper::sUseSimpleContext;
+
+NS_IMPL_ISUPPORTS(IMContextWrapper, TextEventDispatcherListener,
+ nsISupportsWeakReference)
+
+IMContextWrapper::IMContextWrapper(nsWindow* aOwnerWindow)
+ : mOwnerWindow(aOwnerWindow),
+ mLastFocusedWindow(nullptr),
+ mContext(nullptr),
+ mSimpleContext(nullptr),
+ mDummyContext(nullptr),
+ mComposingContext(nullptr),
+ mCompositionStart(UINT32_MAX),
+ mProcessingKeyEvent(nullptr),
+ mCompositionState(eCompositionState_NotComposing),
+ mIMContextID(IMContextID::Unknown),
+ mIsIMFocused(false),
+ mFallbackToKeyEvent(false),
+ mKeyboardEventWasDispatched(false),
+ mKeyboardEventWasConsumed(false),
+ mIsDeletingSurrounding(false),
+ mLayoutChanged(false),
+ mSetCursorPositionOnKeyEvent(true),
+ mPendingResettingIMContext(false),
+ mRetrieveSurroundingSignalReceived(false),
+ mMaybeInDeadKeySequence(false),
+ mIsIMInAsyncKeyHandlingMode(false) {
+ static bool sFirstInstance = true;
+ if (sFirstInstance) {
+ sFirstInstance = false;
+ sUseSimpleContext =
+ Preferences::GetBool("intl.ime.use_simple_context_on_password_field",
+ kUseSimpleContextDefault);
+ }
+ Init();
+}
+
+static bool IsIBusInSyncMode() {
+ // See ibus_im_context_class_init() in client/gtk2/ibusimcontext.c
+ // https://github.com/ibus/ibus/blob/86963f2f94d1e4fc213b01c2bc2ba9dcf4b22219/client/gtk2/ibusimcontext.c#L610
+ const char* env = PR_GetEnv("IBUS_ENABLE_SYNC_MODE");
+
+ // See _get_boolean_env() in client/gtk2/ibusimcontext.c
+ // https://github.com/ibus/ibus/blob/86963f2f94d1e4fc213b01c2bc2ba9dcf4b22219/client/gtk2/ibusimcontext.c#L520-L537
+ if (!env) {
+ return false;
+ }
+ nsDependentCString envStr(env);
+ if (envStr.IsEmpty() || envStr.EqualsLiteral("0") ||
+ envStr.EqualsLiteral("false") || envStr.EqualsLiteral("False") ||
+ envStr.EqualsLiteral("FALSE")) {
+ return false;
+ }
+ return true;
+}
+
+static bool GetFcitxBoolEnv(const char* aEnv) {
+ // See fcitx_utils_get_boolean_env in src/lib/fcitx-utils/utils.c
+ // https://github.com/fcitx/fcitx/blob/0c87840dc7d9460c2cb5feaeefec299d0d3d62ec/src/lib/fcitx-utils/utils.c#L721-L736
+ const char* env = PR_GetEnv(aEnv);
+ if (!env) {
+ return false;
+ }
+ nsDependentCString envStr(env);
+ if (envStr.IsEmpty() || envStr.EqualsLiteral("0") ||
+ envStr.EqualsLiteral("false")) {
+ return false;
+ }
+ return true;
+}
+
+static bool IsFcitxInSyncMode() {
+ // See fcitx_im_context_class_init() in src/frontend/gtk2/fcitximcontext.c
+ // https://github.com/fcitx/fcitx/blob/78b98d9230dc9630e99d52e3172bdf440ffd08c4/src/frontend/gtk2/fcitximcontext.c#L395-L398
+ return GetFcitxBoolEnv("IBUS_ENABLE_SYNC_MODE") ||
+ GetFcitxBoolEnv("FCITX_ENABLE_SYNC_MODE");
+}
+
+nsDependentCSubstring IMContextWrapper::GetIMName() const {
+ const char* contextIDChar =
+ gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext));
+ if (!contextIDChar) {
+ return nsDependentCSubstring();
+ }
+
+ nsDependentCSubstring im(contextIDChar, strlen(contextIDChar));
+
+ // If the context is XIM, actual engine must be specified with
+ // |XMODIFIERS=@im=foo|.
+ const char* xmodifiersChar = PR_GetEnv("XMODIFIERS");
+ if (!xmodifiersChar || !im.EqualsLiteral("xim")) {
+ return im;
+ }
+
+ nsDependentCString xmodifiers(xmodifiersChar);
+ int32_t atIMValueStart = xmodifiers.Find("@im=") + 4;
+ if (atIMValueStart < 4 ||
+ xmodifiers.Length() <= static_cast<size_t>(atIMValueStart)) {
+ return im;
+ }
+
+ int32_t atIMValueEnd = xmodifiers.Find("@", false, atIMValueStart);
+ if (atIMValueEnd > atIMValueStart) {
+ return nsDependentCSubstring(xmodifiersChar + atIMValueStart,
+ atIMValueEnd - atIMValueStart);
+ }
+
+ if (atIMValueEnd == kNotFound) {
+ return nsDependentCSubstring(xmodifiersChar + atIMValueStart,
+ strlen(xmodifiersChar) - atIMValueStart);
+ }
+
+ return im;
+}
+
+void IMContextWrapper::Init() {
+ MozContainer* container = mOwnerWindow->GetMozContainer();
+ MOZ_ASSERT(container, "container is null");
+ GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container));
+
+ // Overwrite selection colors of the window before associating the window
+ // with IM context since IME may look up selection colors via IM context
+ // to support any colored widgets.
+ SelectionStyleProvider::GetInstance()->AttachTo(gdkWindow);
+
+ // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails.
+ // So, we don't need to check the result.
+
+ // Normal context.
+ mContext = gtk_im_multicontext_new();
+ gtk_im_context_set_client_window(mContext, gdkWindow);
+ g_signal_connect(mContext, "preedit_changed",
+ G_CALLBACK(IMContextWrapper::OnChangeCompositionCallback),
+ this);
+ g_signal_connect(mContext, "retrieve_surrounding",
+ G_CALLBACK(IMContextWrapper::OnRetrieveSurroundingCallback),
+ this);
+ g_signal_connect(mContext, "delete_surrounding",
+ G_CALLBACK(IMContextWrapper::OnDeleteSurroundingCallback),
+ this);
+ g_signal_connect(mContext, "commit",
+ G_CALLBACK(IMContextWrapper::OnCommitCompositionCallback),
+ this);
+ g_signal_connect(mContext, "preedit_start",
+ G_CALLBACK(IMContextWrapper::OnStartCompositionCallback),
+ this);
+ g_signal_connect(mContext, "preedit_end",
+ G_CALLBACK(IMContextWrapper::OnEndCompositionCallback),
+ this);
+ nsDependentCSubstring im = GetIMName();
+ if (im.EqualsLiteral("ibus")) {
+ mIMContextID = IMContextID::IBus;
+ mIsIMInAsyncKeyHandlingMode = !IsIBusInSyncMode();
+ // Although ibus has key snooper mode, it's forcibly disabled on Firefox
+ // in default settings by its whitelist since we always send key events
+ // to IME before handling shortcut keys. The whitelist can be
+ // customized with env, IBUS_NO_SNOOPER_APPS, but we don't need to
+ // support such rare cases for reducing maintenance cost.
+ mIsKeySnooped = false;
+ } else if (im.EqualsLiteral("fcitx")) {
+ mIMContextID = IMContextID::Fcitx;
+ mIsIMInAsyncKeyHandlingMode = !IsFcitxInSyncMode();
+ // Although Fcitx has key snooper mode similar to ibus, it's also
+ // disabled on Firefox in default settings by its whitelist. The
+ // whitelist can be customized with env, IBUS_NO_SNOOPER_APPS or
+ // FCITX_NO_SNOOPER_APPS, but we don't need to support such rare cases
+ // for reducing maintenance cost.
+ mIsKeySnooped = false;
+ } else if (im.EqualsLiteral("fcitx5")) {
+ mIMContextID = IMContextID::Fcitx5;
+ mIsIMInAsyncKeyHandlingMode = true; // does not have sync mode.
+ mIsKeySnooped = false; // never use key snooper.
+ } else if (im.EqualsLiteral("uim")) {
+ mIMContextID = IMContextID::Uim;
+ mIsIMInAsyncKeyHandlingMode = false;
+ // We cannot know if uim uses key snooper since it's build option of
+ // uim. Therefore, we need to retrieve the consideration from the
+ // pref for making users and distributions allowed to choose their
+ // preferred value.
+ mIsKeySnooped =
+ Preferences::GetBool("intl.ime.hack.uim.using_key_snooper", true);
+ } else if (im.EqualsLiteral("scim")) {
+ mIMContextID = IMContextID::Scim;
+ mIsIMInAsyncKeyHandlingMode = false;
+ mIsKeySnooped = false;
+ } else if (im.EqualsLiteral("iiim")) {
+ mIMContextID = IMContextID::IIIMF;
+ mIsIMInAsyncKeyHandlingMode = false;
+ mIsKeySnooped = false;
+ } else if (im.EqualsLiteral("wayland")) {
+ mIMContextID = IMContextID::Wayland;
+ mIsIMInAsyncKeyHandlingMode = false;
+ mIsKeySnooped = true;
+ } else {
+ mIMContextID = IMContextID::Unknown;
+ mIsIMInAsyncKeyHandlingMode = false;
+ mIsKeySnooped = false;
+ }
+
+ // Simple context
+ if (sUseSimpleContext) {
+ mSimpleContext = gtk_im_context_simple_new();
+ gtk_im_context_set_client_window(mSimpleContext, gdkWindow);
+ g_signal_connect(mSimpleContext, "preedit_changed",
+ G_CALLBACK(&IMContextWrapper::OnChangeCompositionCallback),
+ this);
+ g_signal_connect(
+ mSimpleContext, "retrieve_surrounding",
+ G_CALLBACK(&IMContextWrapper::OnRetrieveSurroundingCallback), this);
+ g_signal_connect(mSimpleContext, "delete_surrounding",
+ G_CALLBACK(&IMContextWrapper::OnDeleteSurroundingCallback),
+ this);
+ g_signal_connect(mSimpleContext, "commit",
+ G_CALLBACK(&IMContextWrapper::OnCommitCompositionCallback),
+ this);
+ g_signal_connect(mSimpleContext, "preedit_start",
+ G_CALLBACK(IMContextWrapper::OnStartCompositionCallback),
+ this);
+ g_signal_connect(mSimpleContext, "preedit_end",
+ G_CALLBACK(IMContextWrapper::OnEndCompositionCallback),
+ this);
+ }
+
+ // Dummy context
+ mDummyContext = gtk_im_multicontext_new();
+ gtk_im_context_set_client_window(mDummyContext, gdkWindow);
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p Init(), mOwnerWindow=%p, mContext=%p (im=\"%s\"), "
+ "mIsIMInAsyncKeyHandlingMode=%s, mIsKeySnooped=%s, "
+ "mSimpleContext=%p, mDummyContext=%p, "
+ "gtk_im_multicontext_get_context_id()=\"%s\", "
+ "PR_GetEnv(\"XMODIFIERS\")=\"%s\"",
+ this, mOwnerWindow, mContext, nsAutoCString(im).get(),
+ ToChar(mIsIMInAsyncKeyHandlingMode), ToChar(mIsKeySnooped),
+ mSimpleContext, mDummyContext,
+ gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext)),
+ PR_GetEnv("XMODIFIERS")));
+}
+
+/* static */
+void IMContextWrapper::Shutdown() { SelectionStyleProvider::Shutdown(); }
+
+IMContextWrapper::~IMContextWrapper() {
+ if (this == sLastFocusedContext) {
+ sLastFocusedContext = nullptr;
+ }
+ MOZ_LOG(gGtkIMLog, LogLevel::Info, ("0x%p ~IMContextWrapper()", this));
+}
+
+NS_IMETHODIMP
+IMContextWrapper::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) {
+ switch (aNotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ case REQUEST_TO_CANCEL_COMPOSITION: {
+ nsWindow* window =
+ static_cast<nsWindow*>(aTextEventDispatcher->GetWidget());
+ return EndIMEComposition(window);
+ }
+ case NOTIFY_IME_OF_FOCUS:
+ OnFocusChangeInGecko(true);
+ return NS_OK;
+ case NOTIFY_IME_OF_BLUR:
+ OnFocusChangeInGecko(false);
+ return NS_OK;
+ case NOTIFY_IME_OF_POSITION_CHANGE:
+ OnLayoutChange();
+ return NS_OK;
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
+ OnUpdateComposition();
+ return NS_OK;
+ case NOTIFY_IME_OF_SELECTION_CHANGE: {
+ nsWindow* window =
+ static_cast<nsWindow*>(aTextEventDispatcher->GetWidget());
+ OnSelectionChange(window, aNotification);
+ return NS_OK;
+ }
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+}
+
+NS_IMETHODIMP_(void)
+IMContextWrapper::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) {
+ // XXX When input transaction is being stolen by add-on, what should we do?
+}
+
+NS_IMETHODIMP_(void)
+IMContextWrapper::WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
+ void* aData) {
+ KeymapWrapper::WillDispatchKeyboardEvent(aKeyboardEvent,
+ static_cast<GdkEventKey*>(aData));
+}
+
+TextEventDispatcher* IMContextWrapper::GetTextEventDispatcher() {
+ if (NS_WARN_IF(!mLastFocusedWindow)) {
+ return nullptr;
+ }
+ TextEventDispatcher* dispatcher =
+ mLastFocusedWindow->GetTextEventDispatcher();
+ // nsIWidget::GetTextEventDispatcher() shouldn't return nullptr.
+ MOZ_RELEASE_ASSERT(dispatcher);
+ return dispatcher;
+}
+
+NS_IMETHODIMP_(IMENotificationRequests)
+IMContextWrapper::GetIMENotificationRequests() {
+ IMENotificationRequests::Notifications notifications =
+ IMENotificationRequests::NOTIFY_NOTHING;
+ // If it's not enabled, we don't need position change notification.
+ if (IsEnabled()) {
+ notifications |= IMENotificationRequests::NOTIFY_POSITION_CHANGE;
+ }
+ return IMENotificationRequests(notifications);
+}
+
+void IMContextWrapper::OnDestroyWindow(nsWindow* aWindow) {
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Info,
+ ("0x%p OnDestroyWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
+ "mOwnerWindow=0x%p, mLastFocusedModule=0x%p",
+ this, aWindow, mLastFocusedWindow, mOwnerWindow, sLastFocusedContext));
+
+ MOZ_ASSERT(aWindow, "aWindow must not be null");
+
+ if (mLastFocusedWindow == aWindow) {
+ EndIMEComposition(aWindow);
+ if (mIsIMFocused) {
+ Blur();
+ }
+ mLastFocusedWindow = nullptr;
+ }
+
+ if (mOwnerWindow != aWindow) {
+ return;
+ }
+
+ if (sLastFocusedContext == this) {
+ sLastFocusedContext = nullptr;
+ }
+
+ /**
+ * NOTE:
+ * The given window is the owner of this, so, we must release the
+ * contexts now. But that might be referred from other nsWindows
+ * (they are children of this. But we don't know why there are the
+ * cases). So, we need to clear the pointers that refers to contexts
+ * and this if the other referrers are still alive. See bug 349727.
+ */
+ if (mContext) {
+ PrepareToDestroyContext(mContext);
+ gtk_im_context_set_client_window(mContext, nullptr);
+ g_object_unref(mContext);
+ mContext = nullptr;
+ }
+
+ if (mSimpleContext) {
+ gtk_im_context_set_client_window(mSimpleContext, nullptr);
+ g_object_unref(mSimpleContext);
+ mSimpleContext = nullptr;
+ }
+
+ if (mDummyContext) {
+ // mContext and mDummyContext have the same slaveType and signal_data
+ // so no need for another workaround_gtk_im_display_closed.
+ gtk_im_context_set_client_window(mDummyContext, nullptr);
+ g_object_unref(mDummyContext);
+ mDummyContext = nullptr;
+ }
+
+ if (NS_WARN_IF(mComposingContext)) {
+ g_object_unref(mComposingContext);
+ mComposingContext = nullptr;
+ }
+
+ mOwnerWindow = nullptr;
+ mLastFocusedWindow = nullptr;
+ mInputContext.mIMEState.mEnabled = IMEEnabled::Disabled;
+ mPostingKeyEvents.Clear();
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p OnDestroyWindow(), succeeded, Completely destroyed", this));
+}
+
+void IMContextWrapper::PrepareToDestroyContext(GtkIMContext* aContext) {
+ if (mIMContextID == IMContextID::IIIMF) {
+ // IIIM module registers handlers for the "closed" signal on the
+ // display, but the signal handler is not disconnected when the module
+ // is unloaded. To prevent the module from being unloaded, use static
+ // variable to hold reference of slave context class declared by IIIM.
+ // Note that this does not grab any instance, it grabs the "class".
+ static gpointer sGtkIIIMContextClass = nullptr;
+ if (!sGtkIIIMContextClass) {
+ // We retrieved slave context class with g_type_name() and actual
+ // slave context instance when our widget was GTK2. That must be
+ // _GtkIMContext::priv::slave in GTK3. However, _GtkIMContext::priv
+ // is an opacity struct named _GtkIMMulticontextPrivate, i.e., it's
+ // not exposed by GTK3. Therefore, we cannot access the instance
+ // safely. So, we need to retrieve the slave context class with
+ // g_type_from_name("GtkIMContextIIIM") directly (anyway, we needed
+ // to compare the class name with "GtkIMContextIIIM").
+ GType IIMContextType = g_type_from_name("GtkIMContextIIIM");
+ if (IIMContextType) {
+ sGtkIIIMContextClass = g_type_class_ref(IIMContextType);
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p PrepareToDestroyContext(), added to reference to "
+ "GtkIMContextIIIM class to prevent it from being unloaded",
+ this));
+ } else {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p PrepareToDestroyContext(), FAILED to prevent the "
+ "IIIM module from being uploaded",
+ this));
+ }
+ }
+ }
+}
+
+void IMContextWrapper::OnFocusWindow(nsWindow* aWindow) {
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnFocusWindow(aWindow=0x%p), mLastFocusedWindow=0x%p", this,
+ aWindow, mLastFocusedWindow));
+ mLastFocusedWindow = aWindow;
+ Focus();
+}
+
+void IMContextWrapper::OnBlurWindow(nsWindow* aWindow) {
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnBlurWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
+ "mIsIMFocused=%s",
+ this, aWindow, mLastFocusedWindow, ToChar(mIsIMFocused)));
+
+ if (!mIsIMFocused || mLastFocusedWindow != aWindow) {
+ return;
+ }
+
+ Blur();
+}
+
+KeyHandlingState IMContextWrapper::OnKeyEvent(
+ nsWindow* aCaller, GdkEventKey* aEvent,
+ bool aKeyboardEventWasDispatched /* = false */) {
+ MOZ_ASSERT(aEvent, "aEvent must be non-null");
+
+ if (!mInputContext.mIMEState.IsEditable() || MOZ_UNLIKELY(IsDestroyed())) {
+ return KeyHandlingState::eNotHandled;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info, (">>>>>>>>>>>>>>>>"));
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Info,
+ ("0x%p OnKeyEvent(aCaller=0x%p, "
+ "aEvent(0x%p): { type=%s, keyval=%s, unicode=0x%X, state=%s, "
+ "time=%u, hardware_keycode=%u, group=%u }, "
+ "aKeyboardEventWasDispatched=%s)",
+ this, aCaller, aEvent, GetEventType(aEvent),
+ gdk_keyval_name(aEvent->keyval), gdk_keyval_to_unicode(aEvent->keyval),
+ GetEventStateName(aEvent->state, mIMContextID).get(), aEvent->time,
+ aEvent->hardware_keycode, aEvent->group,
+ ToChar(aKeyboardEventWasDispatched)));
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Info,
+ ("0x%p OnKeyEvent(), mMaybeInDeadKeySequence=%s, "
+ "mCompositionState=%s, current context=%p, active context=%p, "
+ "mIMContextID=%s, mIsIMInAsyncKeyHandlingMode=%s",
+ this, ToChar(mMaybeInDeadKeySequence), GetCompositionStateName(),
+ GetCurrentContext(), GetActiveContext(), ToString(mIMContextID).c_str(),
+ ToChar(mIsIMInAsyncKeyHandlingMode)));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnKeyEvent(), FAILED, the caller isn't focused "
+ "window, mLastFocusedWindow=0x%p",
+ this, mLastFocusedWindow));
+ return KeyHandlingState::eNotHandled;
+ }
+
+ // Even if old IM context has composition, key event should be sent to
+ // current context since the user expects so.
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (MOZ_UNLIKELY(!currentContext)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnKeyEvent(), FAILED, there are no context", this));
+ return KeyHandlingState::eNotHandled;
+ }
+
+ if (mSetCursorPositionOnKeyEvent) {
+ SetCursorPosition(currentContext);
+ mSetCursorPositionOnKeyEvent = false;
+ }
+
+ // Let's support dead key event even if active keyboard layout also
+ // supports complicated composition like CJK IME.
+ bool isDeadKey =
+ KeymapWrapper::ComputeDOMKeyNameIndex(aEvent) == KEY_NAME_INDEX_Dead;
+ mMaybeInDeadKeySequence |= isDeadKey;
+
+ // If current context is mSimpleContext, both ibus and fcitx handles key
+ // events synchronously. So, only when current context is mContext which
+ // is GtkIMMulticontext, the key event may be handled by IME asynchronously.
+ bool probablyHandledAsynchronously =
+ mIsIMInAsyncKeyHandlingMode && currentContext == mContext;
+
+ // If we're not sure whether the event is handled asynchronously, this is
+ // set to true.
+ bool maybeHandledAsynchronously = false;
+
+ // If aEvent is a synthesized event for async handling, this will be set to
+ // true.
+ bool isHandlingAsyncEvent = false;
+
+ // If we've decided that the event won't be synthesized asyncrhonously
+ // by IME, but actually IME did it, this is set to true.
+ bool isUnexpectedAsyncEvent = false;
+
+ // If IM is ibus or fcitx and it handles key events asynchronously,
+ // they mark aEvent->state as "handled by me" when they post key event
+ // to another process. Unfortunately, we need to check this hacky
+ // flag because it's difficult to store all pending key events by
+ // an array or a hashtable.
+ if (probablyHandledAsynchronously) {
+ switch (mIMContextID) {
+ case IMContextID::IBus: {
+ // See src/ibustypes.h
+ static const guint IBUS_IGNORED_MASK = 1 << 25;
+ // If IBUS_IGNORED_MASK was set to aEvent->state, the event
+ // has already been handled by another process and it wasn't
+ // used by IME.
+ isHandlingAsyncEvent = !!(aEvent->state & IBUS_IGNORED_MASK);
+ if (!isHandlingAsyncEvent) {
+ // On some environments, IBUS_IGNORED_MASK flag is not set as
+ // expected. In such case, we keep pusing all events into the queue.
+ // I.e., that causes eating a lot of memory until it's blurred.
+ // Therefore, we need to check whether there is same timestamp event
+ // in the queue. This redundant cost should be low because in most
+ // causes, key events in the queue should be 2 or 4.
+ isHandlingAsyncEvent =
+ mPostingKeyEvents.IndexOf(aEvent) != GdkEventKeyQueue::NoIndex();
+ if (isHandlingAsyncEvent) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnKeyEvent(), aEvent->state does not have "
+ "IBUS_IGNORED_MASK but "
+ "same event in the queue. So, assuming it's a "
+ "synthesized event",
+ this));
+ }
+ }
+
+ // If it's a synthesized event, let's remove it from the posting
+ // event queue first. Otherwise the following blocks cannot use
+ // `break`.
+ if (isHandlingAsyncEvent) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnKeyEvent(), aEvent->state has IBUS_IGNORED_MASK "
+ "or aEvent is in the "
+ "posting event queue, so, it won't be handled "
+ "asynchronously anymore. Removing "
+ "the posted events from the queue",
+ this));
+ probablyHandledAsynchronously = false;
+ mPostingKeyEvents.RemoveEvent(aEvent);
+ }
+
+ // ibus won't send back key press events in a dead key sequcne.
+ if (mMaybeInDeadKeySequence && aEvent->type == GDK_KEY_PRESS) {
+ probablyHandledAsynchronously = false;
+ if (isHandlingAsyncEvent) {
+ isUnexpectedAsyncEvent = true;
+ break;
+ }
+ // Some keyboard layouts which have dead keys may send
+ // "empty" key event to make us call
+ // gtk_im_context_filter_keypress() to commit composed
+ // character during a GDK_KEY_PRESS event dispatching.
+ if (!gdk_keyval_to_unicode(aEvent->keyval) &&
+ !aEvent->hardware_keycode) {
+ isUnexpectedAsyncEvent = true;
+ break;
+ }
+ break;
+ }
+ // ibus may handle key events synchronously if focused editor is
+ // <input type="password"> or |ime-mode: disabled;|. However, in
+ // some environments, not so actually. Therefore, we need to check
+ // the result of gtk_im_context_filter_keypress() later.
+ if (mInputContext.mIMEState.mEnabled == IMEEnabled::Password) {
+ probablyHandledAsynchronously = false;
+ maybeHandledAsynchronously = !isHandlingAsyncEvent;
+ break;
+ }
+ break;
+ }
+ case IMContextID::Fcitx:
+ case IMContextID::Fcitx5: {
+ // See src/lib/fcitx-utils/keysym.h
+ static const guint FcitxKeyState_IgnoredMask = 1 << 25;
+ // If FcitxKeyState_IgnoredMask was set to aEvent->state,
+ // the event has already been handled by another process and
+ // it wasn't used by IME.
+ isHandlingAsyncEvent = !!(aEvent->state & FcitxKeyState_IgnoredMask);
+ if (!isHandlingAsyncEvent) {
+ // On some environments, FcitxKeyState_IgnoredMask flag *might* be not
+ // set as expected. If there were such cases, we'd keep pusing all
+ // events into the queue. I.e., that would cause eating a lot of
+ // memory until it'd be blurred. Therefore, we should check whether
+ // there is same timestamp event in the queue. This redundant cost
+ // should be low because in most causes, key events in the queue
+ // should be 2 or 4.
+ isHandlingAsyncEvent =
+ mPostingKeyEvents.IndexOf(aEvent) != GdkEventKeyQueue::NoIndex();
+ if (isHandlingAsyncEvent) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnKeyEvent(), aEvent->state does not have "
+ "FcitxKeyState_IgnoredMask "
+ "but same event in the queue. So, assuming it's a "
+ "synthesized event",
+ this));
+ }
+ }
+
+ // fcitx won't send back key press events in a dead key sequcne.
+ if (mMaybeInDeadKeySequence && aEvent->type == GDK_KEY_PRESS) {
+ probablyHandledAsynchronously = false;
+ if (isHandlingAsyncEvent) {
+ isUnexpectedAsyncEvent = true;
+ break;
+ }
+ // Some keyboard layouts which have dead keys may send
+ // "empty" key event to make us call
+ // gtk_im_context_filter_keypress() to commit composed
+ // character during a GDK_KEY_PRESS event dispatching.
+ if (!gdk_keyval_to_unicode(aEvent->keyval) &&
+ !aEvent->hardware_keycode) {
+ isUnexpectedAsyncEvent = true;
+ break;
+ }
+ }
+
+ // fcitx handles key events asynchronously even if focused
+ // editor cannot use IME actually.
+
+ if (isHandlingAsyncEvent) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnKeyEvent(), aEvent->state has "
+ "FcitxKeyState_IgnoredMask or aEvent is in "
+ "the posting event queue, so, it won't be handled "
+ "asynchronously anymore. "
+ "Removing the posted events from the queue",
+ this));
+ probablyHandledAsynchronously = false;
+ mPostingKeyEvents.RemoveEvent(aEvent);
+ break;
+ }
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "IME may handle key event "
+ "asyncrhonously, but not yet confirmed if it comes agian "
+ "actually");
+ }
+ }
+
+ if (!isUnexpectedAsyncEvent) {
+ mKeyboardEventWasDispatched = aKeyboardEventWasDispatched;
+ mKeyboardEventWasConsumed = false;
+ } else {
+ // If we didn't expect this event, we've alreday dispatched eKeyDown
+ // event or eKeyUp event for that.
+ mKeyboardEventWasDispatched = true;
+ // And in this case, we need to assume that another key event hasn't
+ // been receivied and mKeyboardEventWasConsumed keeps storing the
+ // dispatched eKeyDown or eKeyUp event's state.
+ }
+ mFallbackToKeyEvent = false;
+ mProcessingKeyEvent = aEvent;
+ gboolean isFiltered = gtk_im_context_filter_keypress(currentContext, aEvent);
+
+ // If we're not sure whether the event is handled by IME asynchronously or
+ // synchronously, we need to trust the result of
+ // gtk_im_context_filter_keypress(). If it consumed and but did nothing,
+ // we can assume that another event will be synthesized.
+ if (!isHandlingAsyncEvent && maybeHandledAsynchronously) {
+ probablyHandledAsynchronously |=
+ isFiltered && !mFallbackToKeyEvent && !mKeyboardEventWasDispatched;
+ }
+
+ if (aEvent->type == GDK_KEY_PRESS) {
+ if (isFiltered && probablyHandledAsynchronously) {
+ sWaitingSynthesizedKeyPressHardwareKeyCode = aEvent->hardware_keycode;
+ } else {
+ sWaitingSynthesizedKeyPressHardwareKeyCode = 0;
+ }
+ }
+
+ // The caller of this shouldn't handle aEvent anymore if we've dispatched
+ // composition events or modified content with other events.
+ bool filterThisEvent = isFiltered && !mFallbackToKeyEvent;
+
+ if (IsComposingOnCurrentContext() && !isFiltered &&
+ aEvent->type == GDK_KEY_PRESS && mDispatchedCompositionString.IsEmpty()) {
+ // A Hangul input engine for SCIM doesn't emit preedit_end
+ // signal even when composition string becomes empty. On the
+ // other hand, we should allow to make composition with empty
+ // string for other languages because there *might* be such
+ // IM. For compromising this issue, we should dispatch
+ // compositionend event, however, we don't need to reset IM
+ // actually.
+ // NOTE: Don't dispatch key events as "processed by IME" since
+ // we need to dispatch keyboard events as IME wasn't handled it.
+ mProcessingKeyEvent = nullptr;
+ DispatchCompositionCommitEvent(currentContext, &EmptyString());
+ mProcessingKeyEvent = aEvent;
+ // In this case, even though we handle the keyboard event here,
+ // but we should dispatch keydown event as
+ filterThisEvent = false;
+ }
+
+ if (filterThisEvent && !mKeyboardEventWasDispatched) {
+ // If IME handled the key event but we've not dispatched eKeyDown nor
+ // eKeyUp event yet, we need to dispatch here unless the key event is
+ // now being handled by other IME process.
+ if (!probablyHandledAsynchronously) {
+ MaybeDispatchKeyEventAsProcessedByIME(eVoidEvent);
+ // Be aware, the widget might have been gone here.
+ }
+ // If we need to wait reply from IM, IM may send some signals to us
+ // without sending the key event again. In such case, we need to
+ // dispatch keyboard events with a copy of aEvent. Therefore, we
+ // need to use information of this key event to dispatch an KeyDown
+ // or eKeyUp event later.
+ else {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnKeyEvent(), putting aEvent into the queue...", this));
+ mPostingKeyEvents.PutEvent(aEvent);
+ }
+ }
+
+ mProcessingKeyEvent = nullptr;
+
+ if (aEvent->type == GDK_KEY_PRESS && !filterThisEvent) {
+ // If the key event hasn't been handled by active IME nor keyboard
+ // layout, we can assume that the dead key sequence has been or was
+ // ended. Note that we should not reset it when the key event is
+ // GDK_KEY_RELEASE since it may not be filtered by active keyboard
+ // layout even in composition.
+ mMaybeInDeadKeySequence = false;
+ }
+
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Debug,
+ ("0x%p OnKeyEvent(), succeeded, filterThisEvent=%s "
+ "(isFiltered=%s, mFallbackToKeyEvent=%s, "
+ "probablyHandledAsynchronously=%s, maybeHandledAsynchronously=%s), "
+ "mPostingKeyEvents.Length()=%zu, mCompositionState=%s, "
+ "mMaybeInDeadKeySequence=%s, mKeyboardEventWasDispatched=%s, "
+ "mKeyboardEventWasConsumed=%s",
+ this, ToChar(filterThisEvent), ToChar(isFiltered),
+ ToChar(mFallbackToKeyEvent), ToChar(probablyHandledAsynchronously),
+ ToChar(maybeHandledAsynchronously), mPostingKeyEvents.Length(),
+ GetCompositionStateName(), ToChar(mMaybeInDeadKeySequence),
+ ToChar(mKeyboardEventWasDispatched), ToChar(mKeyboardEventWasConsumed)));
+ MOZ_LOG(gGtkIMLog, LogLevel::Info, ("<<<<<<<<<<<<<<<<\n\n"));
+
+ if (filterThisEvent) {
+ return KeyHandlingState::eHandled;
+ }
+ // If another call of this method has already dispatched eKeyDown event,
+ // we should return KeyHandlingState::eNotHandledButEventDispatched because
+ // the caller should've stopped handling the event if preceding eKeyDown
+ // event was consumed.
+ if (aKeyboardEventWasDispatched) {
+ return KeyHandlingState::eNotHandledButEventDispatched;
+ }
+ if (!mKeyboardEventWasDispatched) {
+ return KeyHandlingState::eNotHandled;
+ }
+ return mKeyboardEventWasConsumed
+ ? KeyHandlingState::eNotHandledButEventConsumed
+ : KeyHandlingState::eNotHandledButEventDispatched;
+}
+
+void IMContextWrapper::OnFocusChangeInGecko(bool aFocus) {
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Info,
+ ("0x%p OnFocusChangeInGecko(aFocus=%s), "
+ "mCompositionState=%s, mIsIMFocused=%s",
+ this, ToChar(aFocus), GetCompositionStateName(), ToChar(mIsIMFocused)));
+
+ // We shouldn't carry over the removed string to another editor.
+ mSelectedStringRemovedByComposition.Truncate();
+ mSelection.Clear();
+
+ // When the focus changes, we need to inform IM about the new cursor
+ // position. Chinese input methods generally rely on this because they
+ // usually don't start composition until a character is picked.
+ if (aFocus && EnsureToCacheSelection()) {
+ SetCursorPosition(GetActiveContext());
+ }
+}
+
+void IMContextWrapper::ResetIME() {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p ResetIME(), mCompositionState=%s, mIsIMFocused=%s", this,
+ GetCompositionStateName(), ToChar(mIsIMFocused)));
+
+ GtkIMContext* activeContext = GetActiveContext();
+ if (MOZ_UNLIKELY(!activeContext)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p ResetIME(), FAILED, there are no context", this));
+ return;
+ }
+
+ RefPtr<IMContextWrapper> kungFuDeathGrip(this);
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+
+ mPendingResettingIMContext = false;
+ gtk_im_context_reset(activeContext);
+
+ // The last focused window might have been destroyed by a DOM event handler
+ // which was called by us during a call of gtk_im_context_reset().
+ if (!lastFocusedWindow ||
+ NS_WARN_IF(lastFocusedWindow != mLastFocusedWindow) ||
+ lastFocusedWindow->Destroyed()) {
+ return;
+ }
+
+ nsAutoString compositionString;
+ GetCompositionString(activeContext, compositionString);
+
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Debug,
+ ("0x%p ResetIME() called gtk_im_context_reset(), "
+ "activeContext=0x%p, mCompositionState=%s, compositionString=%s, "
+ "mIsIMFocused=%s",
+ this, activeContext, GetCompositionStateName(),
+ NS_ConvertUTF16toUTF8(compositionString).get(), ToChar(mIsIMFocused)));
+
+ // XXX IIIMF (ATOK X3 which is one of the Language Engine of it is still
+ // used in Japan!) sends only "preedit_changed" signal with empty
+ // composition string synchronously. Therefore, if composition string
+ // is now empty string, we should assume that the IME won't send
+ // "commit" signal.
+ if (IsComposing() && compositionString.IsEmpty()) {
+ // WARNING: The widget might have been gone after this.
+ DispatchCompositionCommitEvent(activeContext, &EmptyString());
+ }
+}
+
+nsresult IMContextWrapper::EndIMEComposition(nsWindow* aCaller) {
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return NS_OK;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p EndIMEComposition(aCaller=0x%p), "
+ "mCompositionState=%s",
+ this, aCaller, GetCompositionStateName()));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p EndIMEComposition(), FAILED, the caller isn't "
+ "focused window, mLastFocusedWindow=0x%p",
+ this, mLastFocusedWindow));
+ return NS_OK;
+ }
+
+ if (!IsComposing()) {
+ return NS_OK;
+ }
+
+ // Currently, GTK has API neither to commit nor to cancel composition
+ // forcibly. Therefore, TextComposition will recompute commit string for
+ // the request even if native IME will cause unexpected commit string.
+ // So, we don't need to emulate commit or cancel composition with
+ // proper composition events.
+ // XXX ResetIME() might not enough for finishing compositoin on some
+ // environments. We should emulate focus change too because some IMEs
+ // may commit or cancel composition at blur.
+ ResetIME();
+
+ return NS_OK;
+}
+
+void IMContextWrapper::OnLayoutChange() {
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ if (IsComposing()) {
+ SetCursorPosition(GetActiveContext());
+ } else {
+ // If not composing, candidate window position is updated before key
+ // down
+ mSetCursorPositionOnKeyEvent = true;
+ }
+ mLayoutChanged = true;
+}
+
+void IMContextWrapper::OnUpdateComposition() {
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ if (!IsComposing()) {
+ // Composition has been committed. So we need update selection for
+ // caret later
+ mSelection.Clear();
+ EnsureToCacheSelection();
+ mSetCursorPositionOnKeyEvent = true;
+ }
+
+ // If we've already set candidate window position, we don't need to update
+ // the position with update composition notification.
+ if (!mLayoutChanged) {
+ SetCursorPosition(GetActiveContext());
+ }
+}
+
+void IMContextWrapper::SetInputContext(nsWindow* aCaller,
+ const InputContext* aContext,
+ const InputContextAction* aAction) {
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p SetInputContext(aCaller=0x%p, aContext={ mIMEState={ "
+ "mEnabled=%s }, mHTMLInputType=%s })",
+ this, aCaller, ToString(aContext->mIMEState.mEnabled).c_str(),
+ NS_ConvertUTF16toUTF8(aContext->mHTMLInputType).get()));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetInputContext(), FAILED, "
+ "the caller isn't focused window, mLastFocusedWindow=0x%p",
+ this, mLastFocusedWindow));
+ return;
+ }
+
+ if (!mContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetInputContext(), FAILED, "
+ "there are no context",
+ this));
+ return;
+ }
+
+ if (sLastFocusedContext != this) {
+ mInputContext = *aContext;
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p SetInputContext(), succeeded, "
+ "but we're not active",
+ this));
+ return;
+ }
+
+ bool changingEnabledState =
+ aContext->mIMEState.mEnabled != mInputContext.mIMEState.mEnabled ||
+ aContext->mHTMLInputType != mInputContext.mHTMLInputType;
+
+ // Release current IME focus if IME is enabled.
+ if (changingEnabledState && mInputContext.mIMEState.IsEditable()) {
+ EndIMEComposition(mLastFocusedWindow);
+ Blur();
+ }
+
+ mInputContext = *aContext;
+
+ if (changingEnabledState) {
+ if (mInputContext.mIMEState.IsEditable()) {
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (currentContext) {
+ GtkInputPurpose purpose = GTK_INPUT_PURPOSE_FREE_FORM;
+ const nsString& inputType = mInputContext.mHTMLInputType;
+ // Password case has difficult issue. Desktop IMEs disable
+ // composition if input-purpose is password.
+ // For disabling IME on |ime-mode: disabled;|, we need to check
+ // mEnabled value instead of inputType value. This hack also
+ // enables composition on
+ // <input type="password" style="ime-mode: enabled;">.
+ // This is right behavior of ime-mode on desktop.
+ //
+ // On the other hand, IME for tablet devices may provide a
+ // specific software keyboard for password field. If so,
+ // the behavior might look strange on both:
+ // <input type="text" style="ime-mode: disabled;">
+ // <input type="password" style="ime-mode: enabled;">
+ //
+ // Temporarily, we should focus on desktop environment for now.
+ // I.e., let's ignore tablet devices for now. When somebody
+ // reports actual trouble on tablet devices, we should try to
+ // look for a way to solve actual problem.
+ if (mInputContext.mIMEState.mEnabled == IMEEnabled::Password) {
+ purpose = GTK_INPUT_PURPOSE_PASSWORD;
+ } else if (inputType.EqualsLiteral("email")) {
+ purpose = GTK_INPUT_PURPOSE_EMAIL;
+ } else if (inputType.EqualsLiteral("url")) {
+ purpose = GTK_INPUT_PURPOSE_URL;
+ } else if (inputType.EqualsLiteral("tel")) {
+ purpose = GTK_INPUT_PURPOSE_PHONE;
+ } else if (inputType.EqualsLiteral("number")) {
+ purpose = GTK_INPUT_PURPOSE_NUMBER;
+ } else if (mInputContext.mHTMLInputInputmode.EqualsLiteral("decimal")) {
+ purpose = GTK_INPUT_PURPOSE_NUMBER;
+ } else if (mInputContext.mHTMLInputInputmode.EqualsLiteral("email")) {
+ purpose = GTK_INPUT_PURPOSE_EMAIL;
+ } else if (mInputContext.mHTMLInputInputmode.EqualsLiteral("numeric")) {
+ purpose = GTK_INPUT_PURPOSE_DIGITS;
+ } else if (mInputContext.mHTMLInputInputmode.EqualsLiteral("tel")) {
+ purpose = GTK_INPUT_PURPOSE_PHONE;
+ } else if (mInputContext.mHTMLInputInputmode.EqualsLiteral("url")) {
+ purpose = GTK_INPUT_PURPOSE_URL;
+ }
+ // Search by type and inputmode isn't supported on GTK.
+
+ g_object_set(currentContext, "input-purpose", purpose, nullptr);
+
+ // Although GtkInputHints is enum type, value is bit field.
+ gint hints = GTK_INPUT_HINT_NONE;
+ if (mInputContext.mHTMLInputInputmode.EqualsLiteral("none")) {
+ hints |= GTK_INPUT_HINT_INHIBIT_OSK;
+ }
+
+ if (mInputContext.mAutocapitalize.EqualsLiteral("characters")) {
+ hints |= GTK_INPUT_HINT_UPPERCASE_CHARS;
+ } else if (mInputContext.mAutocapitalize.EqualsLiteral("sentences")) {
+ hints |= GTK_INPUT_HINT_UPPERCASE_SENTENCES;
+ } else if (mInputContext.mAutocapitalize.EqualsLiteral("words")) {
+ hints |= GTK_INPUT_HINT_UPPERCASE_WORDS;
+ }
+
+ g_object_set(currentContext, "input-hints", hints, nullptr);
+ }
+ }
+
+ // Even when aState is not enabled state, we need to set IME focus.
+ // Because some IMs are updating the status bar of them at this time.
+ // Be aware, don't use aWindow here because this method shouldn't move
+ // focus actually.
+ Focus();
+
+ // XXX Should we call Blur() when it's not editable? E.g., it might be
+ // better to close VKB automatically.
+ }
+}
+
+InputContext IMContextWrapper::GetInputContext() {
+ mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
+ return mInputContext;
+}
+
+GtkIMContext* IMContextWrapper::GetCurrentContext() const {
+ if (IsEnabled()) {
+ return mContext;
+ }
+ if (mInputContext.mIMEState.mEnabled == IMEEnabled::Password) {
+ return mSimpleContext;
+ }
+ return mDummyContext;
+}
+
+bool IMContextWrapper::IsValidContext(GtkIMContext* aContext) const {
+ if (!aContext) {
+ return false;
+ }
+ return aContext == mContext || aContext == mSimpleContext ||
+ aContext == mDummyContext;
+}
+
+bool IMContextWrapper::IsEnabled() const {
+ return mInputContext.mIMEState.mEnabled == IMEEnabled::Enabled ||
+ (!sUseSimpleContext &&
+ mInputContext.mIMEState.mEnabled == IMEEnabled::Password);
+}
+
+void IMContextWrapper::Focus() {
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Info,
+ ("0x%p Focus(), sLastFocusedContext=0x%p", this, sLastFocusedContext));
+
+ if (mIsIMFocused) {
+ NS_ASSERTION(sLastFocusedContext == this,
+ "We're not active, but the IM was focused?");
+ return;
+ }
+
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (!currentContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p Focus(), FAILED, there are no context", this));
+ return;
+ }
+
+ if (sLastFocusedContext && sLastFocusedContext != this) {
+ sLastFocusedContext->Blur();
+ }
+
+ sLastFocusedContext = this;
+
+ // Forget all posted key events when focus is moved since they shouldn't
+ // be fired in different editor.
+ sWaitingSynthesizedKeyPressHardwareKeyCode = 0;
+ mPostingKeyEvents.Clear();
+
+ gtk_im_context_focus_in(currentContext);
+ mIsIMFocused = true;
+ mSetCursorPositionOnKeyEvent = true;
+
+ if (!IsEnabled()) {
+ // We should release IME focus for uim and scim.
+ // These IMs are using snooper that is released at losing focus.
+ Blur();
+ }
+}
+
+void IMContextWrapper::Blur() {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p Blur(), mIsIMFocused=%s", this, ToChar(mIsIMFocused)));
+
+ if (!mIsIMFocused) {
+ return;
+ }
+
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (!currentContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p Blur(), FAILED, there are no context", this));
+ return;
+ }
+
+ gtk_im_context_focus_out(currentContext);
+ mIsIMFocused = false;
+}
+
+void IMContextWrapper::OnSelectionChange(
+ nsWindow* aCaller, const IMENotification& aIMENotification) {
+ mSelection.Assign(aIMENotification);
+ bool retrievedSurroundingSignalReceived = mRetrieveSurroundingSignalReceived;
+ mRetrieveSurroundingSignalReceived = false;
+
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ const IMENotification::SelectionChangeDataBase& selectionChangeData =
+ aIMENotification.mSelectionChangeData;
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnSelectionChange(aCaller=0x%p, aIMENotification={ "
+ "mSelectionChangeData=%s }), "
+ "mCompositionState=%s, mIsDeletingSurrounding=%s, "
+ "mRetrieveSurroundingSignalReceived=%s",
+ this, aCaller, ToString(selectionChangeData).c_str(),
+ GetCompositionStateName(), ToChar(mIsDeletingSurrounding),
+ ToChar(retrievedSurroundingSignalReceived)));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnSelectionChange(), FAILED, "
+ "the caller isn't focused window, mLastFocusedWindow=0x%p",
+ this, mLastFocusedWindow));
+ return;
+ }
+
+ if (!IsComposing()) {
+ // Now we have no composition (mostly situation on calling this method)
+ // If we have it, it will set by
+ // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED.
+ mSetCursorPositionOnKeyEvent = true;
+ }
+
+ // The focused editor might have placeholder text with normal text node.
+ // In such case, the text node must be removed from a compositionstart
+ // event handler. So, we're dispatching eCompositionStart,
+ // we should ignore selection change notification.
+ if (mCompositionState == eCompositionState_CompositionStartDispatched) {
+ if (NS_WARN_IF(!mSelection.IsValid())) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnSelectionChange(), FAILED, "
+ "new offset is too large, cannot keep composing",
+ this));
+ } else {
+ // Modify the selection start offset with new offset.
+ mCompositionStart = mSelection.mOffset;
+ // XXX We should modify mSelectedStringRemovedByComposition?
+ // But how?
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p OnSelectionChange(), ignored, mCompositionStart "
+ "is updated to %u, the selection change doesn't cause "
+ "resetting IM context",
+ this, mCompositionStart));
+ // And don't reset the IM context.
+ return;
+ }
+ // Otherwise, reset the IM context due to impossible to keep composing.
+ }
+
+ // If the selection change is caused by deleting surrounding text,
+ // we shouldn't need to notify IME of selection change.
+ if (mIsDeletingSurrounding) {
+ return;
+ }
+
+ bool occurredBeforeComposition =
+ IsComposing() && !selectionChangeData.mOccurredDuringComposition &&
+ !selectionChangeData.mCausedByComposition;
+ if (occurredBeforeComposition) {
+ mPendingResettingIMContext = true;
+ }
+
+ // When the selection change is caused by dispatching composition event,
+ // selection set event and/or occurred before starting current composition,
+ // we shouldn't notify IME of that and commit existing composition.
+ if (!selectionChangeData.mCausedByComposition &&
+ !selectionChangeData.mCausedBySelectionEvent &&
+ !occurredBeforeComposition) {
+ // Hack for ibus-pinyin. ibus-pinyin will synthesize a set of
+ // composition which commits with empty string after calling
+ // gtk_im_context_reset(). Therefore, selecting text causes
+ // unexpectedly removing it. For preventing it but not breaking the
+ // other IMEs which use surrounding text, we should call it only when
+ // surrounding text has been retrieved after last selection range was
+ // set. If it's not retrieved, that means that current IME doesn't
+ // have any content cache, so, it must not need the notification of
+ // selection change.
+ if (IsComposing() || retrievedSurroundingSignalReceived) {
+ ResetIME();
+ }
+ }
+}
+
+/* static */
+void IMContextWrapper::OnThemeChanged() {
+ if (!SelectionStyleProvider::GetInstance()) {
+ return;
+ }
+ SelectionStyleProvider::GetInstance()->OnThemeChanged();
+}
+
+/* static */
+void IMContextWrapper::OnStartCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule) {
+ aModule->OnStartCompositionNative(aContext);
+}
+
+void IMContextWrapper::OnStartCompositionNative(GtkIMContext* aContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnStartCompositionNative(aContext=0x%p), "
+ "current context=0x%p, mComposingContext=0x%p",
+ this, aContext, GetCurrentContext(), mComposingContext));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ if (GetCurrentContext() != aContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnStartCompositionNative(), FAILED, "
+ "given context doesn't match",
+ this));
+ return;
+ }
+
+ if (mComposingContext && aContext != mComposingContext) {
+ // XXX For now, we should ignore this odd case, just logging.
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p OnStartCompositionNative(), Warning, "
+ "there is already a composing context but starting new "
+ "composition with different context",
+ this));
+ }
+
+ // IME may start composition without "preedit_start" signal. Therefore,
+ // mComposingContext will be initialized in DispatchCompositionStart().
+
+ if (!DispatchCompositionStart(aContext)) {
+ return;
+ }
+ mCompositionTargetRange.mOffset = mCompositionStart;
+ mCompositionTargetRange.mLength = 0;
+}
+
+/* static */
+void IMContextWrapper::OnEndCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule) {
+ aModule->OnEndCompositionNative(aContext);
+}
+
+void IMContextWrapper::OnEndCompositionNative(GtkIMContext* aContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnEndCompositionNative(aContext=0x%p), mComposingContext=0x%p",
+ this, aContext, mComposingContext));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ // Note that if this is called after focus move, the context may different
+ // from any our owning context.
+ if (!IsValidContext(aContext)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnEndCompositionNative(), FAILED, "
+ "given context doesn't match with any context",
+ this));
+ return;
+ }
+
+ // If we've not started composition with aContext, we should ignore it.
+ if (aContext != mComposingContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p OnEndCompositionNative(), Warning, "
+ "given context doesn't match with mComposingContext",
+ this));
+ return;
+ }
+
+ g_object_unref(mComposingContext);
+ mComposingContext = nullptr;
+
+ // If we already handled the commit event, we should do nothing here.
+ if (IsComposing()) {
+ if (!DispatchCompositionCommitEvent(aContext)) {
+ // If the widget is destroyed, we should do nothing anymore.
+ return;
+ }
+ }
+
+ if (mPendingResettingIMContext) {
+ ResetIME();
+ }
+}
+
+/* static */
+void IMContextWrapper::OnChangeCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule) {
+ aModule->OnChangeCompositionNative(aContext);
+}
+
+void IMContextWrapper::OnChangeCompositionNative(GtkIMContext* aContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnChangeCompositionNative(aContext=0x%p), "
+ "mComposingContext=0x%p",
+ this, aContext, mComposingContext));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ // Note that if this is called after focus move, the context may different
+ // from any our owning context.
+ if (!IsValidContext(aContext)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnChangeCompositionNative(), FAILED, "
+ "given context doesn't match with any context",
+ this));
+ return;
+ }
+
+ if (mComposingContext && aContext != mComposingContext) {
+ // XXX For now, we should ignore this odd case, just logging.
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p OnChangeCompositionNative(), Warning, "
+ "given context doesn't match with composing context",
+ this));
+ }
+
+ nsAutoString compositionString;
+ GetCompositionString(aContext, compositionString);
+ if (!IsComposing() && compositionString.IsEmpty()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p OnChangeCompositionNative(), Warning, does nothing "
+ "because has not started composition and composing string is "
+ "empty",
+ this));
+ mDispatchedCompositionString.Truncate();
+ return; // Don't start the composition with empty string.
+ }
+
+ // Be aware, widget can be gone
+ DispatchCompositionChangeEvent(aContext, compositionString);
+}
+
+/* static */
+gboolean IMContextWrapper::OnRetrieveSurroundingCallback(
+ GtkIMContext* aContext, IMContextWrapper* aModule) {
+ return aModule->OnRetrieveSurroundingNative(aContext);
+}
+
+gboolean IMContextWrapper::OnRetrieveSurroundingNative(GtkIMContext* aContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnRetrieveSurroundingNative(aContext=0x%p), "
+ "current context=0x%p",
+ this, aContext, GetCurrentContext()));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ if (GetCurrentContext() != aContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnRetrieveSurroundingNative(), FAILED, "
+ "given context doesn't match",
+ this));
+ return FALSE;
+ }
+
+ nsAutoString uniStr;
+ uint32_t cursorPos;
+ if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) {
+ return FALSE;
+ }
+
+ // Despite taking a pointer and a length, IBus wants the string to be
+ // zero-terminated and doesn't like U+0000 within the string.
+ uniStr.ReplaceChar(char16_t(0), char16_t(0xFFFD));
+
+ NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(uniStr, 0, cursorPos));
+ uint32_t cursorPosInUTF8 = utf8Str.Length();
+ AppendUTF16toUTF8(nsDependentSubstring(uniStr, cursorPos), utf8Str);
+ gtk_im_context_set_surrounding(aContext, utf8Str.get(), utf8Str.Length(),
+ cursorPosInUTF8);
+ mRetrieveSurroundingSignalReceived = true;
+ return TRUE;
+}
+
+/* static */
+gboolean IMContextWrapper::OnDeleteSurroundingCallback(
+ GtkIMContext* aContext, gint aOffset, gint aNChars,
+ IMContextWrapper* aModule) {
+ return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars);
+}
+
+gboolean IMContextWrapper::OnDeleteSurroundingNative(GtkIMContext* aContext,
+ gint aOffset,
+ gint aNChars) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnDeleteSurroundingNative(aContext=0x%p, aOffset=%d, "
+ "aNChar=%d), current context=0x%p",
+ this, aContext, aOffset, aNChars, GetCurrentContext()));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ if (GetCurrentContext() != aContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnDeleteSurroundingNative(), FAILED, "
+ "given context doesn't match",
+ this));
+ return FALSE;
+ }
+
+ AutoRestore<bool> saveDeletingSurrounding(mIsDeletingSurrounding);
+ mIsDeletingSurrounding = true;
+ if (NS_SUCCEEDED(DeleteText(aContext, aOffset, (uint32_t)aNChars))) {
+ return TRUE;
+ }
+
+ // failed
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnDeleteSurroundingNative(), FAILED, "
+ "cannot delete text",
+ this));
+ return FALSE;
+}
+
+/* static */
+void IMContextWrapper::OnCommitCompositionCallback(GtkIMContext* aContext,
+ const gchar* aString,
+ IMContextWrapper* aModule) {
+ aModule->OnCommitCompositionNative(aContext, aString);
+}
+
+void IMContextWrapper::OnCommitCompositionNative(GtkIMContext* aContext,
+ const gchar* aUTF8Char) {
+ const gchar emptyStr = 0;
+ const gchar* commitString = aUTF8Char ? aUTF8Char : &emptyStr;
+ NS_ConvertUTF8toUTF16 utf16CommitString(commitString);
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnCommitCompositionNative(aContext=0x%p), "
+ "current context=0x%p, active context=0x%p, commitString=\"%s\", "
+ "mProcessingKeyEvent=0x%p, IsComposingOn(aContext)=%s",
+ this, aContext, GetCurrentContext(), GetActiveContext(),
+ commitString, mProcessingKeyEvent, ToChar(IsComposingOn(aContext))));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ if (!IsValidContext(aContext)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnCommitCompositionNative(), FAILED, "
+ "given context doesn't match",
+ this));
+ return;
+ }
+
+ // If we are not in composition and committing with empty string,
+ // we need to do nothing because if we continued to handle this
+ // signal, we would dispatch compositionstart, text, compositionend
+ // events with empty string. Of course, they are unnecessary events
+ // for Web applications and our editor.
+ if (!IsComposingOn(aContext) && utf16CommitString.IsEmpty()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p OnCommitCompositionNative(), Warning, does nothing "
+ "because has not started composition and commit string is empty",
+ this));
+ return;
+ }
+
+ // If IME doesn't change their keyevent that generated this commit,
+ // we should treat that IME didn't handle the key event because
+ // web applications want to receive "keydown" and "keypress" event
+ // in such case.
+ // NOTE: While a key event is being handled, this might be caused on
+ // current context. Otherwise, this may be caused on active context.
+ if (!IsComposingOn(aContext) && mProcessingKeyEvent &&
+ mProcessingKeyEvent->type == GDK_KEY_PRESS &&
+ aContext == GetCurrentContext()) {
+ char keyval_utf8[8]; /* should have at least 6 bytes of space */
+ gint keyval_utf8_len;
+ guint32 keyval_unicode;
+
+ keyval_unicode = gdk_keyval_to_unicode(mProcessingKeyEvent->keyval);
+ keyval_utf8_len = g_unichar_to_utf8(keyval_unicode, keyval_utf8);
+ keyval_utf8[keyval_utf8_len] = '\0';
+
+ // If committing string is exactly same as a character which is
+ // produced by the key, eKeyDown and eKeyPress event should be
+ // dispatched by the caller of OnKeyEvent() normally. Note that
+ // mMaybeInDeadKeySequence will be set to false by OnKeyEvent()
+ // since we set mFallbackToKeyEvent to true here.
+ if (!strcmp(commitString, keyval_utf8)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnCommitCompositionNative(), "
+ "we'll send normal key event",
+ this));
+ mFallbackToKeyEvent = true;
+ return;
+ }
+
+ // If we're in a dead key sequence, commit string is a character in
+ // the BMP and mProcessingKeyEvent produces some characters but it's
+ // not same as committing string, we should dispatch an eKeyPress
+ // event from here.
+ WidgetKeyboardEvent keyDownEvent(true, eKeyDown, mLastFocusedWindow);
+ KeymapWrapper::InitKeyEvent(keyDownEvent, mProcessingKeyEvent, false);
+ if (mMaybeInDeadKeySequence && utf16CommitString.Length() == 1 &&
+ keyDownEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
+ mKeyboardEventWasDispatched = true;
+ // Anyway, we're not in dead key sequence anymore.
+ mMaybeInDeadKeySequence = false;
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnCommitCompositionNative(), FAILED, "
+ "due to BeginNativeInputTransaction() failure",
+ this));
+ return;
+ }
+
+ // First, dispatch eKeyDown event.
+ keyDownEvent.mKeyValue = utf16CommitString;
+ nsEventStatus status = nsEventStatus_eIgnore;
+ bool dispatched = dispatcher->DispatchKeyboardEvent(
+ eKeyDown, keyDownEvent, status, mProcessingKeyEvent);
+ if (!dispatched || status == nsEventStatus_eConsumeNoDefault) {
+ mKeyboardEventWasConsumed = true;
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnCommitCompositionNative(), "
+ "doesn't dispatch eKeyPress event because the preceding "
+ "eKeyDown event was not dispatched or was consumed",
+ this));
+ return;
+ }
+ if (mLastFocusedWindow != keyDownEvent.mWidget ||
+ mLastFocusedWindow->Destroyed()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p OnCommitCompositionNative(), Warning, "
+ "stop dispatching eKeyPress event because the preceding "
+ "eKeyDown event caused changing focused widget or "
+ "destroyed",
+ this));
+ return;
+ }
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnCommitCompositionNative(), "
+ "dispatched eKeyDown event for the committed character",
+ this));
+
+ // Next, dispatch eKeyPress event.
+ dispatcher->MaybeDispatchKeypressEvents(keyDownEvent, status,
+ mProcessingKeyEvent);
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnCommitCompositionNative(), "
+ "dispatched eKeyPress event for the committed character",
+ this));
+ return;
+ }
+ }
+
+ NS_ConvertUTF8toUTF16 str(commitString);
+ // Be aware, widget can be gone
+ DispatchCompositionCommitEvent(aContext, &str);
+}
+
+void IMContextWrapper::GetCompositionString(GtkIMContext* aContext,
+ nsAString& aCompositionString) {
+ gchar* preedit_string;
+ gint cursor_pos;
+ PangoAttrList* feedback_list;
+ gtk_im_context_get_preedit_string(aContext, &preedit_string, &feedback_list,
+ &cursor_pos);
+ if (preedit_string && *preedit_string) {
+ CopyUTF8toUTF16(MakeStringSpan(preedit_string), aCompositionString);
+ } else {
+ aCompositionString.Truncate();
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p GetCompositionString(aContext=0x%p), "
+ "aCompositionString=\"%s\"",
+ this, aContext, preedit_string));
+
+ pango_attr_list_unref(feedback_list);
+ g_free(preedit_string);
+}
+
+bool IMContextWrapper::MaybeDispatchKeyEventAsProcessedByIME(
+ EventMessage aFollowingEvent) {
+ if (!mLastFocusedWindow) {
+ return false;
+ }
+
+ if (!mIsKeySnooped &&
+ ((!mProcessingKeyEvent && mPostingKeyEvents.IsEmpty()) ||
+ (mProcessingKeyEvent && mKeyboardEventWasDispatched))) {
+ return true;
+ }
+
+ // A "keydown" or "keyup" event handler may change focus with the
+ // following event. In such case, we need to cancel this composition.
+ // So, we need to store IM context now because mComposingContext may be
+ // overwritten with different context if calling this method recursively.
+ // Note that we don't need to grab the context here because |context|
+ // will be used only for checking if it's same as mComposingContext.
+ GtkIMContext* oldCurrentContext = GetCurrentContext();
+ GtkIMContext* oldComposingContext = mComposingContext;
+
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+
+ if (mProcessingKeyEvent || !mPostingKeyEvents.IsEmpty()) {
+ if (mProcessingKeyEvent) {
+ mKeyboardEventWasDispatched = true;
+ }
+ // If we're not handling a key event synchronously, the signal may be
+ // sent by IME without sending key event to us. In such case, we
+ // should dispatch keyboard event for the last key event which was
+ // posted to other IME process.
+ GdkEventKey* sourceEvent = mProcessingKeyEvent
+ ? mProcessingKeyEvent
+ : mPostingKeyEvents.GetFirstEvent();
+
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME("
+ "aFollowingEvent=%s), dispatch %s %s "
+ "event: { type=%s, keyval=%s, unicode=0x%X, state=%s, "
+ "time=%u, hardware_keycode=%u, group=%u }",
+ this, ToChar(aFollowingEvent),
+ ToChar(sourceEvent->type == GDK_KEY_PRESS ? eKeyDown : eKeyUp),
+ mProcessingKeyEvent ? "processing" : "posted",
+ GetEventType(sourceEvent), gdk_keyval_name(sourceEvent->keyval),
+ gdk_keyval_to_unicode(sourceEvent->keyval),
+ GetEventStateName(sourceEvent->state, mIMContextID).get(),
+ sourceEvent->time, sourceEvent->hardware_keycode, sourceEvent->group));
+
+ // Let's dispatch eKeyDown event or eKeyUp event now. Note that only
+ // when we're not in a dead key composition, we should mark the
+ // eKeyDown and eKeyUp event as "processed by IME" since we should
+ // expose raw keyCode and key value to web apps the key event is a
+ // part of a dead key sequence.
+ // FYI: We should ignore if default of preceding keydown or keyup
+ // event is prevented since even on the other browsers, web
+ // applications cannot cancel the following composition event.
+ // Spec bug: https://github.com/w3c/uievents/issues/180
+ KeymapWrapper::DispatchKeyDownOrKeyUpEvent(lastFocusedWindow, sourceEvent,
+ !mMaybeInDeadKeySequence,
+ &mKeyboardEventWasConsumed);
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), keydown or keyup "
+ "event is dispatched",
+ this));
+
+ if (!mProcessingKeyEvent) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), removing first "
+ "event from the queue",
+ this));
+ mPostingKeyEvents.RemoveEvent(sourceEvent);
+ }
+ } else {
+ MOZ_ASSERT(mIsKeySnooped);
+ // Currently, we support key snooper mode of uim and wayland only.
+ MOZ_ASSERT(mIMContextID == IMContextID::Uim ||
+ mIMContextID == IMContextID::Wayland);
+ // uim sends "preedit_start" signal and "preedit_changed" separately
+ // at starting composition, "commit" and "preedit_end" separately at
+ // committing composition.
+
+ // Currently, we should dispatch only fake eKeyDown event because
+ // we cannot decide which is the last signal of each key operation
+ // and Chromium also dispatches only "keydown" event in this case.
+ bool dispatchFakeKeyDown = false;
+ switch (aFollowingEvent) {
+ case eCompositionStart:
+ case eCompositionCommit:
+ case eCompositionCommitAsIs:
+ dispatchFakeKeyDown = true;
+ break;
+ // XXX Unfortunately, I don't have a good idea to prevent to
+ // dispatch redundant eKeyDown event for eCompositionStart
+ // immediately after "delete_surrounding" signal. However,
+ // not dispatching eKeyDown event is worse than dispatching
+ // redundant eKeyDown events.
+ case eContentCommandDelete:
+ dispatchFakeKeyDown = true;
+ break;
+ // We need to prevent to dispatch redundant eKeyDown event for
+ // eCompositionChange immediately after eCompositionStart. So,
+ // We should not dispatch eKeyDown event if dispatched composition
+ // string is still empty string.
+ case eCompositionChange:
+ dispatchFakeKeyDown = !mDispatchedCompositionString.IsEmpty();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Do you forget to handle the case?");
+ break;
+ }
+
+ if (dispatchFakeKeyDown) {
+ WidgetKeyboardEvent fakeKeyDownEvent(true, eKeyDown, lastFocusedWindow);
+ fakeKeyDownEvent.mKeyCode = NS_VK_PROCESSKEY;
+ fakeKeyDownEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
+ // It's impossible to get physical key information in this case but
+ // this should be okay since web apps shouldn't do anything with
+ // physical key information during composition.
+ fakeKeyDownEvent.mCodeNameIndex = CODE_NAME_INDEX_UNKNOWN;
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME("
+ "aFollowingEvent=%s), dispatch fake eKeyDown event",
+ this, ToChar(aFollowingEvent)));
+
+ KeymapWrapper::DispatchKeyDownOrKeyUpEvent(
+ lastFocusedWindow, fakeKeyDownEvent, &mKeyboardEventWasConsumed);
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), "
+ "fake keydown event is dispatched",
+ this));
+ }
+ }
+
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), Warning, the "
+ "focused widget was destroyed/changed by a key event",
+ this));
+ return false;
+ }
+
+ // If the dispatched keydown event caused moving focus and that also
+ // caused changing active context, we need to cancel composition here.
+ if (GetCurrentContext() != oldCurrentContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), Warning, the key "
+ "event causes changing active IM context",
+ this));
+ if (mComposingContext == oldComposingContext) {
+ // Only when the context is still composing, we should call
+ // ResetIME() here. Otherwise, it should've already been
+ // cleaned up.
+ ResetIME();
+ }
+ return false;
+ }
+
+ return true;
+}
+
+bool IMContextWrapper::DispatchCompositionStart(GtkIMContext* aContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p DispatchCompositionStart(aContext=0x%p)", this, aContext));
+
+ if (IsComposing()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "we're already in composition",
+ this));
+ return true;
+ }
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "there are no focused window in this module",
+ this));
+ return false;
+ }
+
+ if (NS_WARN_IF(!EnsureToCacheSelection())) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "cannot query the selection offset",
+ this));
+ return false;
+ }
+
+ mComposingContext = static_cast<GtkIMContext*>(g_object_ref(aContext));
+ MOZ_ASSERT(mComposingContext);
+
+ // Keep the last focused window alive
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+
+ // XXX The composition start point might be changed by composition events
+ // even though we strongly hope it doesn't happen.
+ // Every composition event should have the start offset for the result
+ // because it may high cost if we query the offset every time.
+ mCompositionStart = mSelection.mOffset;
+ mDispatchedCompositionString.Truncate();
+
+ // If this composition is started by a key press, we need to dispatch
+ // eKeyDown or eKeyUp event before dispatching eCompositionStart event.
+ // Note that dispatching a keyboard event which is marked as "processed
+ // by IME" is okay since Chromium also dispatches keyboard event as so.
+ if (!MaybeDispatchKeyEventAsProcessedByIME(eCompositionStart)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p DispatchCompositionStart(), Warning, "
+ "MaybeDispatchKeyEventAsProcessedByIME() returned false",
+ this));
+ return false;
+ }
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "due to BeginNativeInputTransaction() failure",
+ this));
+ return false;
+ }
+
+ static bool sHasSetTelemetry = false;
+ if (!sHasSetTelemetry) {
+ sHasSetTelemetry = true;
+ NS_ConvertUTF8toUTF16 im(GetIMName());
+ // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
+ if (im.Length() > 72) {
+ if (NS_IS_SURROGATE_PAIR(im[72 - 2], im[72 - 1])) {
+ im.Truncate(72 - 2);
+ } else {
+ im.Truncate(72 - 1);
+ }
+ // U+2026 is "..."
+ im.Append(char16_t(0x2026));
+ }
+ Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_LINUX, im,
+ true);
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p DispatchCompositionStart(), dispatching "
+ "compositionstart... (mCompositionStart=%u)",
+ this, mCompositionStart));
+ mCompositionState = eCompositionState_CompositionStartDispatched;
+ nsEventStatus status;
+ dispatcher->StartComposition(status);
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, the focused "
+ "widget was destroyed/changed by compositionstart event",
+ this));
+ return false;
+ }
+
+ return true;
+}
+
+bool IMContextWrapper::DispatchCompositionChangeEvent(
+ GtkIMContext* aContext, const nsAString& aCompositionString) {
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Info,
+ ("0x%p DispatchCompositionChangeEvent(aContext=0x%p)", this, aContext));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "there are no focused window in this module",
+ this));
+ return false;
+ }
+
+ if (!IsComposing()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p DispatchCompositionChangeEvent(), the composition "
+ "wasn't started, force starting...",
+ this));
+ if (!DispatchCompositionStart(aContext)) {
+ return false;
+ }
+ }
+ // If this composition string change caused by a key press, we need to
+ // dispatch eKeyDown or eKeyUp before dispatching eCompositionChange event.
+ else if (!MaybeDispatchKeyEventAsProcessedByIME(eCompositionChange)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p DispatchCompositionChangeEvent(), Warning, "
+ "MaybeDispatchKeyEventAsProcessedByIME() returned false",
+ this));
+ return false;
+ }
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "due to BeginNativeInputTransaction() failure",
+ this));
+ return false;
+ }
+
+ // Store the selected string which will be removed by following
+ // compositionchange event.
+ if (mCompositionState == eCompositionState_CompositionStartDispatched) {
+ if (NS_WARN_IF(
+ !EnsureToCacheSelection(&mSelectedStringRemovedByComposition))) {
+ // XXX How should we behave in this case??
+ } else {
+ // XXX We should assume, for now, any web applications don't change
+ // selection at handling this compositionchange event.
+ mCompositionStart = mSelection.mOffset;
+ }
+ }
+
+ RefPtr<TextRangeArray> rangeArray =
+ CreateTextRangeArray(aContext, aCompositionString);
+
+ rv = dispatcher->SetPendingComposition(aCompositionString, rangeArray);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "due to SetPendingComposition() failure",
+ this));
+ return false;
+ }
+
+ mCompositionState = eCompositionState_CompositionChangeEventDispatched;
+
+ // We cannot call SetCursorPosition for e10s-aware.
+ // DispatchEvent is async on e10s, so composition rect isn't updated now
+ // on tab parent.
+ mDispatchedCompositionString = aCompositionString;
+ mLayoutChanged = false;
+ mCompositionTargetRange.mOffset =
+ mCompositionStart + rangeArray->TargetClauseOffset();
+ mCompositionTargetRange.mLength = rangeArray->TargetClauseLength();
+
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+ nsEventStatus status;
+ rv = dispatcher->FlushPendingComposition(status);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "due to FlushPendingComposition() failure",
+ this));
+ return false;
+ }
+
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, the "
+ "focused widget was destroyed/changed by "
+ "compositionchange event",
+ this));
+ return false;
+ }
+ return true;
+}
+
+bool IMContextWrapper::DispatchCompositionCommitEvent(
+ GtkIMContext* aContext, const nsAString* aCommitString) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p DispatchCompositionCommitEvent(aContext=0x%p, "
+ "aCommitString=0x%p, (\"%s\"))",
+ this, aContext, aCommitString,
+ aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : ""));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "there are no focused window in this module",
+ this));
+ return false;
+ }
+
+ // TODO: We need special care to handle request to commit composition
+ // by content while we're committing composition because we have
+ // commit string information now but IME may not have composition
+ // anymore. Therefore, we may not be able to handle commit as
+ // expected. However, this is rare case because this situation
+ // never occurs with remote content. So, it's okay to fix this
+ // issue later. (Perhaps, TextEventDisptcher should do it for
+ // all platforms. E.g., creating WillCommitComposition()?)
+ if (!IsComposing()) {
+ if (!aCommitString || aCommitString->IsEmpty()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "there is no composition and empty commit string",
+ this));
+ return true;
+ }
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p DispatchCompositionCommitEvent(), "
+ "the composition wasn't started, force starting...",
+ this));
+ if (!DispatchCompositionStart(aContext)) {
+ return false;
+ }
+ }
+ // If this commit caused by a key press, we need to dispatch eKeyDown or
+ // eKeyUp before dispatching composition events.
+ else if (!MaybeDispatchKeyEventAsProcessedByIME(
+ aCommitString ? eCompositionCommit : eCompositionCommitAsIs)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p DispatchCompositionCommitEvent(), Warning, "
+ "MaybeDispatchKeyEventAsProcessedByIME() returned false",
+ this));
+ mCompositionState = eCompositionState_NotComposing;
+ return false;
+ }
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "due to BeginNativeInputTransaction() failure",
+ this));
+ return false;
+ }
+
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+
+ // Emulate selection until receiving actual selection range.
+ mSelection.CollapseTo(
+ mCompositionStart + (aCommitString
+ ? aCommitString->Length()
+ : mDispatchedCompositionString.Length()),
+ mSelection.mWritingMode);
+
+ mCompositionState = eCompositionState_NotComposing;
+ // Reset dead key sequence too because GTK doesn't support dead key chain
+ // (i.e., a key press doesn't cause both producing some characters and
+ // restarting new dead key sequence at one time). So, committing
+ // composition means end of a dead key sequence.
+ mMaybeInDeadKeySequence = false;
+ mCompositionStart = UINT32_MAX;
+ mCompositionTargetRange.Clear();
+ mDispatchedCompositionString.Truncate();
+ mSelectedStringRemovedByComposition.Truncate();
+
+ nsEventStatus status;
+ rv = dispatcher->CommitComposition(status, aCommitString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "due to CommitComposition() failure",
+ this));
+ return false;
+ }
+
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "the focused widget was destroyed/changed by "
+ "compositioncommit event",
+ this));
+ return false;
+ }
+
+ return true;
+}
+
+already_AddRefed<TextRangeArray> IMContextWrapper::CreateTextRangeArray(
+ GtkIMContext* aContext, const nsAString& aCompositionString) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p CreateTextRangeArray(aContext=0x%p, "
+ "aCompositionString=\"%s\" (Length()=%u))",
+ this, aContext, NS_ConvertUTF16toUTF8(aCompositionString).get(),
+ aCompositionString.Length()));
+
+ RefPtr<TextRangeArray> textRangeArray = new TextRangeArray();
+
+ gchar* preedit_string;
+ gint cursor_pos_in_chars;
+ PangoAttrList* feedback_list;
+ gtk_im_context_get_preedit_string(aContext, &preedit_string, &feedback_list,
+ &cursor_pos_in_chars);
+ if (!preedit_string || !*preedit_string) {
+ if (!aCompositionString.IsEmpty()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p CreateTextRangeArray(), FAILED, due to "
+ "preedit_string is null",
+ this));
+ }
+ pango_attr_list_unref(feedback_list);
+ g_free(preedit_string);
+ return textRangeArray.forget();
+ }
+
+ // Convert caret offset from offset in characters to offset in UTF-16
+ // string. If we couldn't proper offset in UTF-16 string, we should
+ // assume that the caret is at the end of the composition string.
+ uint32_t caretOffsetInUTF16 = aCompositionString.Length();
+ if (NS_WARN_IF(cursor_pos_in_chars < 0)) {
+ // Note that this case is undocumented. We should assume that the
+ // caret is at the end of the composition string.
+ } else if (cursor_pos_in_chars == 0) {
+ caretOffsetInUTF16 = 0;
+ } else {
+ gchar* charAfterCaret =
+ g_utf8_offset_to_pointer(preedit_string, cursor_pos_in_chars);
+ if (NS_WARN_IF(!charAfterCaret)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p CreateTextRangeArray(), failed to get UTF-8 "
+ "string before the caret (cursor_pos_in_chars=%d)",
+ this, cursor_pos_in_chars));
+ } else {
+ glong caretOffset = 0;
+ gunichar2* utf16StrBeforeCaret =
+ g_utf8_to_utf16(preedit_string, charAfterCaret - preedit_string,
+ nullptr, &caretOffset, nullptr);
+ if (NS_WARN_IF(!utf16StrBeforeCaret) || NS_WARN_IF(caretOffset < 0)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p CreateTextRangeArray(), WARNING, failed to "
+ "convert to UTF-16 string before the caret "
+ "(cursor_pos_in_chars=%d, caretOffset=%ld)",
+ this, cursor_pos_in_chars, caretOffset));
+ } else {
+ caretOffsetInUTF16 = static_cast<uint32_t>(caretOffset);
+ uint32_t compositionStringLength = aCompositionString.Length();
+ if (NS_WARN_IF(caretOffsetInUTF16 > compositionStringLength)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p CreateTextRangeArray(), WARNING, "
+ "caretOffsetInUTF16=%u is larger than "
+ "compositionStringLength=%u",
+ this, caretOffsetInUTF16, compositionStringLength));
+ caretOffsetInUTF16 = compositionStringLength;
+ }
+ }
+ if (utf16StrBeforeCaret) {
+ g_free(utf16StrBeforeCaret);
+ }
+ }
+ }
+
+ PangoAttrIterator* iter;
+ iter = pango_attr_list_get_iterator(feedback_list);
+ if (!iter) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p CreateTextRangeArray(), FAILED, iterator couldn't "
+ "be allocated",
+ this));
+ pango_attr_list_unref(feedback_list);
+ g_free(preedit_string);
+ return textRangeArray.forget();
+ }
+
+ uint32_t minOffsetOfClauses = aCompositionString.Length();
+ uint32_t maxOffsetOfClauses = 0;
+ do {
+ TextRange range;
+ if (!SetTextRange(iter, preedit_string, caretOffsetInUTF16, range)) {
+ continue;
+ }
+ MOZ_ASSERT(range.Length());
+ minOffsetOfClauses = std::min(minOffsetOfClauses, range.mStartOffset);
+ maxOffsetOfClauses = std::max(maxOffsetOfClauses, range.mEndOffset);
+ textRangeArray->AppendElement(range);
+ } while (pango_attr_iterator_next(iter));
+
+ // If the IME doesn't define clause from the start of the composition,
+ // we should insert dummy clause information since TextRangeArray assumes
+ // that there must be a clause whose start is 0 when there is one or
+ // more clauses.
+ if (minOffsetOfClauses) {
+ TextRange dummyClause;
+ dummyClause.mStartOffset = 0;
+ dummyClause.mEndOffset = minOffsetOfClauses;
+ dummyClause.mRangeType = TextRangeType::eRawClause;
+ textRangeArray->InsertElementAt(0, dummyClause);
+ maxOffsetOfClauses = std::max(maxOffsetOfClauses, dummyClause.mEndOffset);
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p CreateTextRangeArray(), inserting a dummy clause "
+ "at the beginning of the composition string mStartOffset=%u, "
+ "mEndOffset=%u, mRangeType=%s",
+ this, dummyClause.mStartOffset, dummyClause.mEndOffset,
+ ToChar(dummyClause.mRangeType)));
+ }
+
+ // If the IME doesn't define clause at end of the composition, we should
+ // insert dummy clause information since TextRangeArray assumes that there
+ // must be a clase whose end is the length of the composition string when
+ // there is one or more clauses.
+ if (!textRangeArray->IsEmpty() &&
+ maxOffsetOfClauses < aCompositionString.Length()) {
+ TextRange dummyClause;
+ dummyClause.mStartOffset = maxOffsetOfClauses;
+ dummyClause.mEndOffset = aCompositionString.Length();
+ dummyClause.mRangeType = TextRangeType::eRawClause;
+ textRangeArray->AppendElement(dummyClause);
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p CreateTextRangeArray(), inserting a dummy clause "
+ "at the end of the composition string mStartOffset=%u, "
+ "mEndOffset=%u, mRangeType=%s",
+ this, dummyClause.mStartOffset, dummyClause.mEndOffset,
+ ToChar(dummyClause.mRangeType)));
+ }
+
+ TextRange range;
+ range.mStartOffset = range.mEndOffset = caretOffsetInUTF16;
+ range.mRangeType = TextRangeType::eCaret;
+ textRangeArray->AppendElement(range);
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Debug,
+ ("0x%p CreateTextRangeArray(), mStartOffset=%u, "
+ "mEndOffset=%u, mRangeType=%s",
+ this, range.mStartOffset, range.mEndOffset, ToChar(range.mRangeType)));
+
+ pango_attr_iterator_destroy(iter);
+ pango_attr_list_unref(feedback_list);
+ g_free(preedit_string);
+
+ return textRangeArray.forget();
+}
+
+/* static */
+nscolor IMContextWrapper::ToNscolor(PangoAttrColor* aPangoAttrColor) {
+ PangoColor& pangoColor = aPangoAttrColor->color;
+ uint8_t r = pangoColor.red / 0x100;
+ uint8_t g = pangoColor.green / 0x100;
+ uint8_t b = pangoColor.blue / 0x100;
+ return NS_RGB(r, g, b);
+}
+
+bool IMContextWrapper::SetTextRange(PangoAttrIterator* aPangoAttrIter,
+ const gchar* aUTF8CompositionString,
+ uint32_t aUTF16CaretOffset,
+ TextRange& aTextRange) const {
+ // Set the range offsets in UTF-16 string.
+ gint utf8ClauseStart, utf8ClauseEnd;
+ pango_attr_iterator_range(aPangoAttrIter, &utf8ClauseStart, &utf8ClauseEnd);
+ if (utf8ClauseStart == utf8ClauseEnd) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetTextRange(), FAILED, due to collapsed range", this));
+ return false;
+ }
+
+ if (!utf8ClauseStart) {
+ aTextRange.mStartOffset = 0;
+ } else {
+ glong utf16PreviousClausesLength;
+ gunichar2* utf16PreviousClausesString =
+ g_utf8_to_utf16(aUTF8CompositionString, utf8ClauseStart, nullptr,
+ &utf16PreviousClausesLength, nullptr);
+
+ if (NS_WARN_IF(!utf16PreviousClausesString)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetTextRange(), FAILED, due to g_utf8_to_utf16() "
+ "failure (retrieving previous string of current clause)",
+ this));
+ return false;
+ }
+
+ aTextRange.mStartOffset = utf16PreviousClausesLength;
+ g_free(utf16PreviousClausesString);
+ }
+
+ glong utf16CurrentClauseLength;
+ gunichar2* utf16CurrentClauseString = g_utf8_to_utf16(
+ aUTF8CompositionString + utf8ClauseStart, utf8ClauseEnd - utf8ClauseStart,
+ nullptr, &utf16CurrentClauseLength, nullptr);
+
+ if (NS_WARN_IF(!utf16CurrentClauseString)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetTextRange(), FAILED, due to g_utf8_to_utf16() "
+ "failure (retrieving current clause)",
+ this));
+ return false;
+ }
+
+ // iBus Chewing IME tells us that there is an empty clause at the end of
+ // the composition string but we should ignore it since our code doesn't
+ // assume that there is an empty clause.
+ if (!utf16CurrentClauseLength) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p SetTextRange(), FAILED, due to current clause length "
+ "is 0",
+ this));
+ return false;
+ }
+
+ aTextRange.mEndOffset = aTextRange.mStartOffset + utf16CurrentClauseLength;
+ g_free(utf16CurrentClauseString);
+ utf16CurrentClauseString = nullptr;
+
+ // Set styles
+ TextRangeStyle& style = aTextRange.mRangeStyle;
+
+ // Underline
+ PangoAttrInt* attrUnderline = reinterpret_cast<PangoAttrInt*>(
+ pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_UNDERLINE));
+ if (attrUnderline) {
+ switch (attrUnderline->value) {
+ case PANGO_UNDERLINE_NONE:
+ style.mLineStyle = TextRangeStyle::LineStyle::None;
+ break;
+ case PANGO_UNDERLINE_DOUBLE:
+ style.mLineStyle = TextRangeStyle::LineStyle::Double;
+ break;
+ case PANGO_UNDERLINE_ERROR:
+ style.mLineStyle = TextRangeStyle::LineStyle::Wavy;
+ break;
+ case PANGO_UNDERLINE_SINGLE:
+ case PANGO_UNDERLINE_LOW:
+ style.mLineStyle = TextRangeStyle::LineStyle::Solid;
+ break;
+ default:
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p SetTextRange(), retrieved unknown underline "
+ "style: %d",
+ this, attrUnderline->value));
+ style.mLineStyle = TextRangeStyle::LineStyle::Solid;
+ break;
+ }
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_LINESTYLE;
+
+ // Underline color
+ PangoAttrColor* attrUnderlineColor = reinterpret_cast<PangoAttrColor*>(
+ pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_UNDERLINE_COLOR));
+ if (attrUnderlineColor) {
+ style.mUnderlineColor = ToNscolor(attrUnderlineColor);
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_UNDERLINE_COLOR;
+ }
+ } else {
+ style.mLineStyle = TextRangeStyle::LineStyle::None;
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_LINESTYLE;
+ }
+
+ // Don't set colors if they are not specified. They should be computed by
+ // textframe if only one of the colors are specified.
+
+ // Foreground color (text color)
+ PangoAttrColor* attrForeground = reinterpret_cast<PangoAttrColor*>(
+ pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_FOREGROUND));
+ if (attrForeground) {
+ style.mForegroundColor = ToNscolor(attrForeground);
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_FOREGROUND_COLOR;
+ }
+
+ // Background color
+ PangoAttrColor* attrBackground = reinterpret_cast<PangoAttrColor*>(
+ pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_BACKGROUND));
+ if (attrBackground) {
+ style.mBackgroundColor = ToNscolor(attrBackground);
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_BACKGROUND_COLOR;
+ }
+
+ /**
+ * We need to judge the meaning of the clause for a11y. Before we support
+ * IME specific composition string style, we used following rules:
+ *
+ * 1: If attrUnderline and attrForground are specified, we assumed the
+ * clause is TextRangeType::eSelectedClause.
+ * 2: If only attrUnderline is specified, we assumed the clause is
+ * TextRangeType::eConvertedClause.
+ * 3: If only attrForground is specified, we assumed the clause is
+ * TextRangeType::eSelectedRawClause.
+ * 4: If neither attrUnderline nor attrForeground is specified, we assumed
+ * the clause is TextRangeType::eRawClause.
+ *
+ * However, this rules are odd since there can be two or more selected
+ * clauses. Additionally, our old rules caused that IME developers/users
+ * cannot specify composition string style as they want.
+ *
+ * So, we shouldn't guess the meaning from its visual style.
+ */
+
+ // If the range covers whole of composition string and the caret is at
+ // the end of the composition string, the range is probably not converted.
+ if (!utf8ClauseStart &&
+ utf8ClauseEnd == static_cast<gint>(strlen(aUTF8CompositionString)) &&
+ aTextRange.mEndOffset == aUTF16CaretOffset) {
+ aTextRange.mRangeType = TextRangeType::eRawClause;
+ }
+ // Typically, the caret is set at the start of the selected clause.
+ // So, if the caret is in the clause, we can assume that the clause is
+ // selected.
+ else if (aTextRange.mStartOffset <= aUTF16CaretOffset &&
+ aTextRange.mEndOffset > aUTF16CaretOffset) {
+ aTextRange.mRangeType = TextRangeType::eSelectedClause;
+ }
+ // Otherwise, we should assume that the clause is converted but not
+ // selected.
+ else {
+ aTextRange.mRangeType = TextRangeType::eConvertedClause;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p SetTextRange(), succeeded, aTextRange= { "
+ "mStartOffset=%u, mEndOffset=%u, mRangeType=%s, mRangeStyle=%s }",
+ this, aTextRange.mStartOffset, aTextRange.mEndOffset,
+ ToChar(aTextRange.mRangeType),
+ GetTextRangeStyleText(aTextRange.mRangeStyle).get()));
+
+ return true;
+}
+
+void IMContextWrapper::SetCursorPosition(GtkIMContext* aContext) {
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Info,
+ ("0x%p SetCursorPosition(aContext=0x%p), "
+ "mCompositionTargetRange={ mOffset=%u, mLength=%u }"
+ "mSelection={ mOffset=%u, Length()=%u, mWritingMode=%s }",
+ this, aContext, mCompositionTargetRange.mOffset,
+ mCompositionTargetRange.mLength, mSelection.mOffset, mSelection.Length(),
+ GetWritingModeName(mSelection.mWritingMode).get()));
+
+ bool useCaret = false;
+ if (!mCompositionTargetRange.IsValid()) {
+ if (!mSelection.IsValid()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, "
+ "mCompositionTargetRange and mSelection are invalid",
+ this));
+ return;
+ }
+ useCaret = true;
+ }
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, due to no focused "
+ "window",
+ this));
+ return;
+ }
+
+ if (MOZ_UNLIKELY(!aContext)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, due to no context", this));
+ return;
+ }
+
+ WidgetQueryContentEvent queryCaretOrTextRectEvent(
+ true, useCaret ? eQueryCaretRect : eQueryTextRect, mLastFocusedWindow);
+ if (useCaret) {
+ queryCaretOrTextRectEvent.InitForQueryCaretRect(mSelection.mOffset);
+ } else {
+ if (mSelection.mWritingMode.IsVertical()) {
+ // For preventing the candidate window to overlap the target
+ // clause, we should set fake (typically, very tall) caret rect.
+ uint32_t length =
+ mCompositionTargetRange.mLength ? mCompositionTargetRange.mLength : 1;
+ queryCaretOrTextRectEvent.InitForQueryTextRect(
+ mCompositionTargetRange.mOffset, length);
+ } else {
+ queryCaretOrTextRectEvent.InitForQueryTextRect(
+ mCompositionTargetRange.mOffset, 1);
+ }
+ }
+ InitEvent(queryCaretOrTextRectEvent);
+ nsEventStatus status;
+ mLastFocusedWindow->DispatchEvent(&queryCaretOrTextRectEvent, status);
+ if (queryCaretOrTextRectEvent.Failed()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, %s was failed", this,
+ useCaret ? "eQueryCaretRect" : "eQueryTextRect"));
+ return;
+ }
+
+ nsWindow* rootWindow =
+ static_cast<nsWindow*>(mLastFocusedWindow->GetTopLevelWidget());
+
+ // Get the position of the rootWindow in screen.
+ LayoutDeviceIntPoint root = rootWindow->WidgetToScreenOffset();
+
+ // Get the position of IM context owner window in screen.
+ LayoutDeviceIntPoint owner = mOwnerWindow->WidgetToScreenOffset();
+
+ // Compute the caret position in the IM owner window.
+ LayoutDeviceIntRect rect =
+ queryCaretOrTextRectEvent.mReply->mRect + root - owner;
+ rect.width = 0;
+ GdkRectangle area = rootWindow->DevicePixelsToGdkRectRoundOut(rect);
+
+ gtk_im_context_set_cursor_location(aContext, &area);
+}
+
+nsresult IMContextWrapper::GetCurrentParagraph(nsAString& aText,
+ uint32_t& aCursorPos) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p GetCurrentParagraph(), mCompositionState=%s", this,
+ GetCompositionStateName()));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p GetCurrentParagraph(), FAILED, there are no "
+ "focused window in this module",
+ this));
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsEventStatus status;
+
+ uint32_t selOffset = mCompositionStart;
+ uint32_t selLength = mSelectedStringRemovedByComposition.Length();
+
+ // If focused editor doesn't have composition string, we should use
+ // current selection.
+ if (!EditorHasCompositionString()) {
+ // Query cursor position & selection
+ if (NS_WARN_IF(!EnsureToCacheSelection())) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p GetCurrentParagraph(), FAILED, due to no "
+ "valid selection information",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ selOffset = mSelection.mOffset;
+ selLength = mSelection.Length();
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p GetCurrentParagraph(), selOffset=%u, selLength=%u", this,
+ selOffset, selLength));
+
+ // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
+ // we cannot support this request when the current offset is larger
+ // than INT32_MAX.
+ if (selOffset > INT32_MAX || selLength > INT32_MAX ||
+ selOffset + selLength > INT32_MAX) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p GetCurrentParagraph(), FAILED, The selection is "
+ "out of range",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get all text contents of the focused editor
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
+ mLastFocusedWindow);
+ queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
+ mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
+ if (NS_WARN_IF(queryTextContentEvent.Failed())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (selOffset + selLength > queryTextContentEvent.mReply->DataLength()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p GetCurrentParagraph(), FAILED, The selection is "
+ "invalid, queryTextContentEvent={ mReply=%s }",
+ this, ToString(queryTextContentEvent.mReply).c_str()));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Remove composing string and restore the selected string because
+ // GtkEntry doesn't remove selected string until committing, however,
+ // our editor does it. We should emulate the behavior for IME.
+ nsAutoString textContent(queryTextContentEvent.mReply->DataRef());
+ if (EditorHasCompositionString() &&
+ mDispatchedCompositionString != mSelectedStringRemovedByComposition) {
+ textContent.Replace(mCompositionStart,
+ mDispatchedCompositionString.Length(),
+ mSelectedStringRemovedByComposition);
+ }
+
+ // Get only the focused paragraph, by looking for newlines
+ int32_t parStart =
+ (selOffset == 0) ? 0
+ : textContent.RFind("\n", false, selOffset - 1, -1) + 1;
+ int32_t parEnd = textContent.Find("\n", false, selOffset + selLength, -1);
+ if (parEnd < 0) {
+ parEnd = textContent.Length();
+ }
+ aText = nsDependentSubstring(textContent, parStart, parEnd - parStart);
+ aCursorPos = selOffset - uint32_t(parStart);
+
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Debug,
+ ("0x%p GetCurrentParagraph(), succeeded, aText=%s, "
+ "aText.Length()=%u, aCursorPos=%u",
+ this, NS_ConvertUTF16toUTF8(aText).get(), aText.Length(), aCursorPos));
+
+ return NS_OK;
+}
+
+nsresult IMContextWrapper::DeleteText(GtkIMContext* aContext, int32_t aOffset,
+ uint32_t aNChars) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p DeleteText(aContext=0x%p, aOffset=%d, aNChars=%u), "
+ "mCompositionState=%s",
+ this, aContext, aOffset, aNChars, GetCompositionStateName()));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, there are no focused window "
+ "in this module",
+ this));
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (!aNChars) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, aNChars must not be zero", this));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+ nsEventStatus status;
+
+ // First, we should cancel current composition because editor cannot
+ // handle changing selection and deleting text.
+ uint32_t selOffset;
+ bool wasComposing = IsComposing();
+ bool editorHadCompositionString = EditorHasCompositionString();
+ if (wasComposing) {
+ selOffset = mCompositionStart;
+ if (!DispatchCompositionCommitEvent(aContext,
+ &mSelectedStringRemovedByComposition)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, quitting from DeletText", this));
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ if (NS_WARN_IF(!EnsureToCacheSelection())) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, due to no valid selection "
+ "information",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+ selOffset = mSelection.mOffset;
+ }
+
+ // Get all text contents of the focused editor
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
+ mLastFocusedWindow);
+ queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
+ mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
+ if (NS_WARN_IF(queryTextContentEvent.Failed())) {
+ return NS_ERROR_FAILURE;
+ }
+ if (queryTextContentEvent.mReply->IsDataEmpty()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, there is no contents", this));
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(
+ queryTextContentEvent.mReply->DataRef(), 0, selOffset));
+ glong offsetInUTF8Characters =
+ g_utf8_strlen(utf8Str.get(), utf8Str.Length()) + aOffset;
+ if (offsetInUTF8Characters < 0) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, aOffset is too small for "
+ "current cursor pos (computed offset: %ld)",
+ this, offsetInUTF8Characters));
+ return NS_ERROR_FAILURE;
+ }
+
+ AppendUTF16toUTF8(
+ nsDependentSubstring(queryTextContentEvent.mReply->DataRef(), selOffset),
+ utf8Str);
+ glong countOfCharactersInUTF8 =
+ g_utf8_strlen(utf8Str.get(), utf8Str.Length());
+ glong endInUTF8Characters = offsetInUTF8Characters + aNChars;
+ if (countOfCharactersInUTF8 < endInUTF8Characters) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, aNChars is too large for "
+ "current contents (content length: %ld, computed end offset: %ld)",
+ this, countOfCharactersInUTF8, endInUTF8Characters));
+ return NS_ERROR_FAILURE;
+ }
+
+ gchar* charAtOffset =
+ g_utf8_offset_to_pointer(utf8Str.get(), offsetInUTF8Characters);
+ gchar* charAtEnd =
+ g_utf8_offset_to_pointer(utf8Str.get(), endInUTF8Characters);
+
+ // Set selection to delete
+ WidgetSelectionEvent selectionEvent(true, eSetSelection, mLastFocusedWindow);
+
+ nsDependentCSubstring utf8StrBeforeOffset(utf8Str, 0,
+ charAtOffset - utf8Str.get());
+ selectionEvent.mOffset = NS_ConvertUTF8toUTF16(utf8StrBeforeOffset).Length();
+
+ nsDependentCSubstring utf8DeletingStr(utf8Str, utf8StrBeforeOffset.Length(),
+ charAtEnd - charAtOffset);
+ selectionEvent.mLength = NS_ConvertUTF8toUTF16(utf8DeletingStr).Length();
+
+ selectionEvent.mReversed = false;
+ selectionEvent.mExpandToClusterBoundary = false;
+ lastFocusedWindow->DispatchEvent(&selectionEvent, status);
+
+ if (!selectionEvent.mSucceeded || lastFocusedWindow != mLastFocusedWindow ||
+ lastFocusedWindow->Destroyed()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, setting selection caused "
+ "focus change or window destroyed",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this deleting text caused by a key press, we need to dispatch
+ // eKeyDown or eKeyUp before dispatching eContentCommandDelete event.
+ if (!MaybeDispatchKeyEventAsProcessedByIME(eContentCommandDelete)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p DeleteText(), Warning, "
+ "MaybeDispatchKeyEventAsProcessedByIME() returned false",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Delete the selection
+ WidgetContentCommandEvent contentCommandEvent(true, eContentCommandDelete,
+ mLastFocusedWindow);
+ mLastFocusedWindow->DispatchEvent(&contentCommandEvent, status);
+
+ if (!contentCommandEvent.mSucceeded ||
+ lastFocusedWindow != mLastFocusedWindow ||
+ lastFocusedWindow->Destroyed()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, deleting the selection caused "
+ "focus change or window destroyed",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!wasComposing) {
+ return NS_OK;
+ }
+
+ // Restore the composition at new caret position.
+ if (!DispatchCompositionStart(aContext)) {
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, resterting composition start", this));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!editorHadCompositionString) {
+ return NS_OK;
+ }
+
+ nsAutoString compositionString;
+ GetCompositionString(aContext, compositionString);
+ if (!DispatchCompositionChangeEvent(aContext, compositionString)) {
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, restoring composition string", this));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void IMContextWrapper::InitEvent(WidgetGUIEvent& aEvent) {
+ aEvent.mTime = PR_Now() / 1000;
+}
+
+bool IMContextWrapper::EnsureToCacheSelection(nsAString* aSelectedString) {
+ if (aSelectedString) {
+ aSelectedString->Truncate();
+ }
+
+ if (mSelection.IsValid()) {
+ if (aSelectedString) {
+ *aSelectedString = mSelection.mString;
+ }
+ return true;
+ }
+
+ if (NS_WARN_IF(!mLastFocusedWindow)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p EnsureToCacheSelection(), FAILED, due to "
+ "no focused window",
+ this));
+ return false;
+ }
+
+ nsEventStatus status;
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ mLastFocusedWindow);
+ InitEvent(querySelectedTextEvent);
+ mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
+ if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p EnsureToCacheSelection(), FAILED, due to "
+ "failure of query selection event",
+ this));
+ return false;
+ }
+
+ mSelection.Assign(querySelectedTextEvent);
+ if (!mSelection.IsValid()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p EnsureToCacheSelection(), FAILED, due to "
+ "failure of query selection event (invalid result)",
+ this));
+ return false;
+ }
+
+ if (!mSelection.Collapsed() && aSelectedString) {
+ aSelectedString->Assign(querySelectedTextEvent.mReply->DataRef());
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p EnsureToCacheSelection(), Succeeded, mSelection="
+ "{ mOffset=%u, Length()=%u, mWritingMode=%s }",
+ this, mSelection.mOffset, mSelection.Length(),
+ GetWritingModeName(mSelection.mWritingMode).get()));
+ return true;
+}
+
+/******************************************************************************
+ * IMContextWrapper::Selection
+ ******************************************************************************/
+
+void IMContextWrapper::Selection::Assign(
+ const IMENotification& aIMENotification) {
+ MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE);
+ mString = aIMENotification.mSelectionChangeData.String();
+ mOffset = aIMENotification.mSelectionChangeData.mOffset;
+ mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
+}
+
+void IMContextWrapper::Selection::Assign(
+ const WidgetQueryContentEvent& aEvent) {
+ MOZ_ASSERT(aEvent.mMessage == eQuerySelectedText);
+ MOZ_ASSERT(aEvent.Succeeded());
+ MOZ_ASSERT(aEvent.mReply->mOffsetAndData.isSome());
+ mString = aEvent.mReply->DataRef();
+ mOffset = aEvent.mReply->StartOffset();
+ mWritingMode = aEvent.mReply->WritingModeRef();
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/IMContextWrapper.h b/widget/gtk/IMContextWrapper.h
new file mode 100644
index 0000000000..9ebb22fc3d
--- /dev/null
+++ b/widget/gtk/IMContextWrapper.h
@@ -0,0 +1,685 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 IMContextWrapper_h_
+#define IMContextWrapper_h_
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsIWidget.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "WritingModes.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * KeyHandlingState is result of IMContextWrapper::OnKeyEvent().
+ */
+enum class KeyHandlingState {
+ // The native key event has not been handled by IMContextWrapper.
+ eNotHandled,
+ // The native key event was handled by IMContextWrapper.
+ eHandled,
+ // The native key event has not been handled by IMContextWrapper,
+ // but eKeyDown or eKeyUp event has been dispatched.
+ eNotHandledButEventDispatched,
+ // The native key event has not been handled by IMContextWrapper,
+ // but eKeyDown or eKeyUp event has been dispatched and consumed.
+ eNotHandledButEventConsumed,
+};
+
+class IMContextWrapper final : public TextEventDispatcherListener {
+ public:
+ // TextEventDispatcherListener implementation
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+ NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;
+ NS_IMETHOD_(void)
+ OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override;
+ NS_IMETHOD_(void)
+ WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress, void* aData) override;
+
+ public:
+ // aOwnerWindow is a pointer of the owner window. When aOwnerWindow is
+ // destroyed, the related IME contexts are released (i.e., IME cannot be
+ // used with the instance after that).
+ explicit IMContextWrapper(nsWindow* aOwnerWindow);
+
+ // Called when the process is being shut down.
+ static void Shutdown();
+
+ // "Enabled" means the users can use all IMEs.
+ // I.e., the focus is in the normal editors.
+ bool IsEnabled() const;
+
+ // OnFocusWindow is a notification that aWindow is going to be focused.
+ void OnFocusWindow(nsWindow* aWindow);
+ // OnBlurWindow is a notification that aWindow is going to be unfocused.
+ void OnBlurWindow(nsWindow* aWindow);
+ // OnDestroyWindow is a notification that aWindow is going to be destroyed.
+ void OnDestroyWindow(nsWindow* aWindow);
+ // OnFocusChangeInGecko is a notification that an editor gets focus.
+ void OnFocusChangeInGecko(bool aFocus);
+ // OnSelectionChange is a notification that selection (caret) is changed
+ // in the focused editor.
+ void OnSelectionChange(nsWindow* aCaller,
+ const IMENotification& aIMENotification);
+ // OnThemeChanged is called when desktop theme is changed.
+ static void OnThemeChanged();
+
+ /**
+ * OnKeyEvent() is called when aWindow gets a native key press event or a
+ * native key release event. If this returns true, the key event was
+ * filtered by IME. Otherwise, this returns false.
+ * NOTE: When the native key press event starts composition, this returns
+ * true but dispatches an eKeyDown event or eKeyUp event before
+ * dispatching composition events or content command event.
+ *
+ * @param aWindow A window on which user operate the
+ * key.
+ * @param aEvent A native key press or release
+ * event.
+ * @param aKeyboardEventWasDispatched true if eKeyDown or eKeyUp event
+ * for aEvent has already been
+ * dispatched. In this case,
+ * this class doesn't dispatch
+ * keyboard event anymore.
+ */
+ KeyHandlingState OnKeyEvent(nsWindow* aWindow, GdkEventKey* aEvent,
+ bool aKeyboardEventWasDispatched = false);
+
+ // IME related nsIWidget methods.
+ nsresult EndIMEComposition(nsWindow* aCaller);
+ void SetInputContext(nsWindow* aCaller, const InputContext* aContext,
+ const InputContextAction* aAction);
+ InputContext GetInputContext();
+ void OnUpdateComposition();
+ void OnLayoutChange();
+
+ TextEventDispatcher* GetTextEventDispatcher();
+
+ // TODO: Typically, new IM comes every several years. And now, our code
+ // becomes really IM behavior dependent. So, perhaps, we need prefs
+ // to control related flags for IM developers.
+ enum class IMContextID : uint8_t {
+ Fcitx, // 4.x or earlier
+ Fcitx5,
+ IBus,
+ IIIMF,
+ Scim,
+ Uim,
+ Wayland,
+ Unknown,
+ };
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const IMContextID& aIMContextID) {
+ switch (aIMContextID) {
+ case IMContextID::Fcitx:
+ return aStream << "Fcitx";
+ case IMContextID::Fcitx5:
+ return aStream << "Fcitx5";
+ case IMContextID::IBus:
+ return aStream << "IBus";
+ case IMContextID::IIIMF:
+ return aStream << "IIIMF";
+ case IMContextID::Scim:
+ return aStream << "Scim";
+ case IMContextID::Uim:
+ return aStream << "Uim";
+ case IMContextID::Wayland:
+ return aStream << "Wayland";
+ case IMContextID::Unknown:
+ return aStream << "Unknown";
+ }
+ MOZ_ASSERT_UNREACHABLE("Add new case for the new IM support");
+ return aStream << "Unknown";
+ }
+
+ /**
+ * GetIMName() returns IM name associated with mContext. If the context is
+ * xim, this look for actual engine from XMODIFIERS environment variable.
+ */
+ nsDependentCSubstring GetIMName() const;
+
+ /**
+ * GetWaitingSynthesizedKeyPressHardwareKeyCode() returns hardware_keycode
+ * value of last handled GDK_KEY_PRESS event which is probable handled by
+ * IME asynchronously and we have not received synthesized GDK_KEY_PRESS
+ * event yet.
+ */
+ static guint16 GetWaitingSynthesizedKeyPressHardwareKeyCode() {
+ return sWaitingSynthesizedKeyPressHardwareKeyCode;
+ }
+
+ protected:
+ ~IMContextWrapper();
+
+ // Owner of an instance of this class. This should be top level window.
+ // The owner window must release the contexts when it's destroyed because
+ // the IME contexts need the native window. If OnDestroyWindow() is called
+ // with the owner window, it'll release IME contexts. Otherwise, it'll
+ // just clean up any existing composition if it's related to the destroying
+ // child window.
+ nsWindow* mOwnerWindow;
+
+ // A last focused window in this class's context.
+ nsWindow* mLastFocusedWindow;
+
+ // Actual context. This is used for handling the user's input.
+ GtkIMContext* mContext;
+
+ // mSimpleContext is used for the password field and
+ // the |ime-mode: disabled;| editors if sUseSimpleContext is true.
+ // These editors disable IME. But dead keys should work. Fortunately,
+ // the simple IM context of GTK2 support only them.
+ GtkIMContext* mSimpleContext;
+
+ // mDummyContext is a dummy context and will be used in Focus()
+ // when the state of mEnabled means disabled. This context's IME state is
+ // always "closed", so it closes IME forcedly.
+ GtkIMContext* mDummyContext;
+
+ // mComposingContext is not nullptr while one of mContext, mSimpleContext
+ // and mDummyContext has composition.
+ // XXX: We don't assume that two or more context have composition same time.
+ GtkIMContext* mComposingContext;
+
+ // IME enabled state and other things defined in InputContext.
+ // Use following helper methods if you don't need the detail of the status.
+ InputContext mInputContext;
+
+ // mCompositionStart is the start offset of the composition string in the
+ // current content. When <textarea> or <input> have focus, it means offset
+ // from the first character of them. When a HTML editor has focus, it
+ // means offset from the first character of the root element of the editor.
+ uint32_t mCompositionStart;
+
+ // mDispatchedCompositionString is the latest composition string which
+ // was dispatched by compositionupdate event.
+ nsString mDispatchedCompositionString;
+
+ // mSelectedStringRemovedByComposition is the selected string which was
+ // removed by first compositionchange event.
+ nsString mSelectedStringRemovedByComposition;
+
+ // OnKeyEvent() temporarily sets mProcessingKeyEvent to the given native
+ // event.
+ GdkEventKey* mProcessingKeyEvent;
+
+ /**
+ * GdkEventKeyQueue stores *copy* of GdkEventKey instances. However, this
+ * must be safe to our usecase since it has |time| and the value should not
+ * be same as older event.
+ */
+ class GdkEventKeyQueue final {
+ public:
+ ~GdkEventKeyQueue() { Clear(); }
+
+ void Clear() {
+ if (!mEvents.IsEmpty()) {
+ RemoveEventsAt(0, mEvents.Length());
+ }
+ }
+
+ /**
+ * PutEvent() puts new event into the queue.
+ */
+ void PutEvent(const GdkEventKey* aEvent) {
+ GdkEventKey* newEvent = reinterpret_cast<GdkEventKey*>(
+ gdk_event_copy(reinterpret_cast<const GdkEvent*>(aEvent)));
+ newEvent->state &= GDK_MODIFIER_MASK;
+ mEvents.AppendElement(newEvent);
+ }
+
+ /**
+ * RemoveEvent() removes oldest same event and its preceding events
+ * from the queue.
+ */
+ void RemoveEvent(const GdkEventKey* aEvent) {
+ size_t index = IndexOf(aEvent);
+ if (NS_WARN_IF(index == GdkEventKeyQueue::NoIndex())) {
+ return;
+ }
+ RemoveEventsAt(0, index + 1);
+ }
+
+ /**
+ * FirstEvent() returns oldest event in the queue.
+ */
+ GdkEventKey* GetFirstEvent() const {
+ if (mEvents.IsEmpty()) {
+ return nullptr;
+ }
+ return mEvents[0];
+ }
+
+ bool IsEmpty() const { return mEvents.IsEmpty(); }
+
+ static size_t NoIndex() { return nsTArray<GdkEventKey*>::NoIndex; }
+ size_t Length() const { return mEvents.Length(); }
+ size_t IndexOf(const GdkEventKey* aEvent) const {
+ static_assert(!(GDK_MODIFIER_MASK & (1 << 24)),
+ "We assumes 25th bit is used by some IM, but used by GDK");
+ static_assert(!(GDK_MODIFIER_MASK & (1 << 25)),
+ "We assumes 26th bit is used by some IM, but used by GDK");
+ for (size_t i = 0; i < mEvents.Length(); i++) {
+ GdkEventKey* event = mEvents[i];
+ // It must be enough to compare only type, time, keyval and
+ // part of state. Note that we cannot compaire two events
+ // simply since IME may have changed unused bits of state.
+ if (event->time == aEvent->time) {
+ if (NS_WARN_IF(event->type != aEvent->type) ||
+ NS_WARN_IF(event->keyval != aEvent->keyval) ||
+ NS_WARN_IF(event->state != (aEvent->state & GDK_MODIFIER_MASK))) {
+ continue;
+ }
+ }
+ return i;
+ }
+ return GdkEventKeyQueue::NoIndex();
+ }
+
+ private:
+ nsTArray<GdkEventKey*> mEvents;
+
+ void RemoveEventsAt(size_t aStart, size_t aCount) {
+ for (size_t i = aStart; i < aStart + aCount; i++) {
+ gdk_event_free(reinterpret_cast<GdkEvent*>(mEvents[i]));
+ }
+ mEvents.RemoveElementsAt(aStart, aCount);
+ }
+ };
+ // OnKeyEvent() append mPostingKeyEvents when it believes that a key event
+ // is posted to other IME process.
+ GdkEventKeyQueue mPostingKeyEvents;
+
+ static guint16 sWaitingSynthesizedKeyPressHardwareKeyCode;
+
+ struct Range {
+ uint32_t mOffset;
+ uint32_t mLength;
+
+ Range() : mOffset(UINT32_MAX), mLength(UINT32_MAX) {}
+
+ bool IsValid() const { return mOffset != UINT32_MAX; }
+ void Clear() {
+ mOffset = UINT32_MAX;
+ mLength = UINT32_MAX;
+ }
+ };
+
+ // current target offset and length of IME composition
+ Range mCompositionTargetRange;
+
+ // mCompositionState indicates current status of composition.
+ enum eCompositionState : uint8_t {
+ eCompositionState_NotComposing,
+ eCompositionState_CompositionStartDispatched,
+ eCompositionState_CompositionChangeEventDispatched
+ };
+ eCompositionState mCompositionState;
+
+ bool IsComposing() const {
+ return (mCompositionState != eCompositionState_NotComposing);
+ }
+
+ bool IsComposingOn(GtkIMContext* aContext) const {
+ return IsComposing() && mComposingContext == aContext;
+ }
+
+ bool IsComposingOnCurrentContext() const {
+ return IsComposingOn(GetCurrentContext());
+ }
+
+ bool EditorHasCompositionString() {
+ return (mCompositionState ==
+ eCompositionState_CompositionChangeEventDispatched);
+ }
+
+ /**
+ * Checks if aContext is valid context for handling composition.
+ *
+ * @param aContext An IM context which is specified by native
+ * composition events.
+ * @return true if the context is valid context for
+ * handling composition. Otherwise, false.
+ */
+ bool IsValidContext(GtkIMContext* aContext) const;
+
+ const char* GetCompositionStateName() {
+ switch (mCompositionState) {
+ case eCompositionState_NotComposing:
+ return "NotComposing";
+ case eCompositionState_CompositionStartDispatched:
+ return "CompositionStartDispatched";
+ case eCompositionState_CompositionChangeEventDispatched:
+ return "CompositionChangeEventDispatched";
+ default:
+ return "InvaildState";
+ }
+ }
+
+ // mIMContextID indicates the ID of mContext. This is actually indicates
+ // IM which user selected.
+ IMContextID mIMContextID;
+
+ struct Selection final {
+ nsString mString;
+ uint32_t mOffset;
+ WritingMode mWritingMode;
+
+ Selection() : mOffset(UINT32_MAX) {}
+
+ void Clear() {
+ mString.Truncate();
+ mOffset = UINT32_MAX;
+ mWritingMode = WritingMode();
+ }
+ void CollapseTo(uint32_t aOffset, const WritingMode& aWritingMode) {
+ mWritingMode = aWritingMode;
+ mOffset = aOffset;
+ mString.Truncate();
+ }
+
+ void Assign(const IMENotification& aIMENotification);
+ void Assign(const WidgetQueryContentEvent& aSelectedTextEvent);
+
+ bool IsValid() const { return mOffset != UINT32_MAX; }
+ bool Collapsed() const { return mString.IsEmpty(); }
+ uint32_t Length() const { return mString.Length(); }
+ uint32_t EndOffset() const {
+ if (NS_WARN_IF(!IsValid())) {
+ return UINT32_MAX;
+ }
+ CheckedInt<uint32_t> endOffset =
+ CheckedInt<uint32_t>(mOffset) + mString.Length();
+ if (NS_WARN_IF(!endOffset.isValid())) {
+ return UINT32_MAX;
+ }
+ return endOffset.value();
+ }
+ } mSelection;
+ bool EnsureToCacheSelection(nsAString* aSelectedString = nullptr);
+
+ // mIsIMFocused is set to TRUE when we call gtk_im_context_focus_in(). And
+ // it's set to FALSE when we call gtk_im_context_focus_out().
+ bool mIsIMFocused;
+ // mFallbackToKeyEvent is set to false when this class starts to handle
+ // a native key event (at that time, mProcessingKeyEvent is set to the
+ // native event). If active IME just commits composition with a character
+ // which is produced by the key with current keyboard layout, this is set
+ // to true.
+ bool mFallbackToKeyEvent;
+ // mKeyboardEventWasDispatched is used by OnKeyEvent() and
+ // MaybeDispatchKeyEventAsProcessedByIME().
+ // MaybeDispatchKeyEventAsProcessedByIME() dispatches an eKeyDown or
+ // eKeyUp event event if the composition is caused by a native
+ // key press event. If this is true, a keyboard event has been dispatched
+ // for the native event. If so, MaybeDispatchKeyEventAsProcessedByIME()
+ // won't dispatch keyboard event anymore.
+ bool mKeyboardEventWasDispatched;
+ // Whether the keyboard event which as dispatched at setting
+ // mKeyboardEventWasDispatched to true was consumed or not.
+ bool mKeyboardEventWasConsumed;
+ // mIsDeletingSurrounding is true while OnDeleteSurroundingNative() is
+ // trying to delete the surrounding text.
+ bool mIsDeletingSurrounding;
+ // mLayoutChanged is true after OnLayoutChange() is called. This is reset
+ // when eCompositionChange is being dispatched.
+ bool mLayoutChanged;
+ // mSetCursorPositionOnKeyEvent true when caret rect or position is updated
+ // with no composition. If true, we update candidate window position
+ // before key down
+ bool mSetCursorPositionOnKeyEvent;
+ // mPendingResettingIMContext becomes true if selection change notification
+ // is received during composition but the selection change occurred before
+ // starting the composition. In such case, we cannot notify IME of
+ // selection change during composition because we don't want to commit
+ // the composition in such case. However, we should notify IME of the
+ // selection change after the composition is committed.
+ bool mPendingResettingIMContext;
+ // mRetrieveSurroundingSignalReceived is true after "retrieve_surrounding"
+ // signal is received until selection is changed in Gecko.
+ bool mRetrieveSurroundingSignalReceived;
+ // mMaybeInDeadKeySequence is set to true when we detect a dead key press
+ // and set to false when we're sure dead key sequence has been finished.
+ // Note that we cannot detect which key event causes ending a dead key
+ // sequence. For example, when you press dead key grave with ibus Spanish
+ // keyboard layout, it just consumes the key event when we call
+ // gtk_im_context_filter_keypress(). Then, pressing "Escape" key cancels
+ // the dead key sequence but we don't receive any signal and it's consumed
+ // by gtk_im_context_filter_keypress() normally. On the other hand, when
+ // pressing "Shift" key causes exactly same behavior but dead key sequence
+ // isn't finished yet.
+ bool mMaybeInDeadKeySequence;
+ // mIsIMInAsyncKeyHandlingMode is set to true if we know that IM handles
+ // key events asynchronously. I.e., filtered key event may come again
+ // later.
+ bool mIsIMInAsyncKeyHandlingMode;
+ // mIsKeySnooped is set to true if IM uses key snooper to listen key events.
+ // In such case, we won't receive key events if IME consumes the event.
+ bool mIsKeySnooped;
+
+ // sLastFocusedContext is a pointer to the last focused instance of this
+ // class. When a instance is destroyed and sLastFocusedContext refers it,
+ // this is cleared. So, this refers valid pointer always.
+ static IMContextWrapper* sLastFocusedContext;
+
+ // sUseSimpleContext indeicates if password editors and editors with
+ // |ime-mode: disabled;| should use GtkIMContextSimple.
+ // If true, they use GtkIMContextSimple. Otherwise, not.
+ static bool sUseSimpleContext;
+
+ // Callback methods for native IME events. These methods should call
+ // the related instance methods simply.
+ static gboolean OnRetrieveSurroundingCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+ static gboolean OnDeleteSurroundingCallback(GtkIMContext* aContext,
+ gint aOffset, gint aNChars,
+ IMContextWrapper* aModule);
+ static void OnCommitCompositionCallback(GtkIMContext* aContext,
+ const gchar* aString,
+ IMContextWrapper* aModule);
+ static void OnChangeCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+ static void OnStartCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+ static void OnEndCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+
+ // The instance methods for the native IME events.
+ gboolean OnRetrieveSurroundingNative(GtkIMContext* aContext);
+ gboolean OnDeleteSurroundingNative(GtkIMContext* aContext, gint aOffset,
+ gint aNChars);
+ void OnCommitCompositionNative(GtkIMContext* aContext, const gchar* aString);
+ void OnChangeCompositionNative(GtkIMContext* aContext);
+ void OnStartCompositionNative(GtkIMContext* aContext);
+ void OnEndCompositionNative(GtkIMContext* aContext);
+
+ /**
+ * GetCurrentContext() returns current IM context which is chosen with the
+ * enabled state.
+ * WARNING:
+ * When this class receives some signals for a composition after focus
+ * is moved in Gecko, the result of this may be different from given
+ * context by the signals.
+ */
+ GtkIMContext* GetCurrentContext() const;
+
+ /**
+ * GetActiveContext() returns a composing context or current context.
+ */
+ GtkIMContext* GetActiveContext() const {
+ return mComposingContext ? mComposingContext : GetCurrentContext();
+ }
+
+ // If the owner window and IM context have been destroyed, returns TRUE.
+ bool IsDestroyed() { return !mOwnerWindow; }
+
+ // Sets focus to the instance of this class.
+ void Focus();
+
+ // Steals focus from the instance of this class.
+ void Blur();
+
+ // Initializes the instance.
+ void Init();
+
+ /**
+ * Reset the active context, i.e., if there is mComposingContext, reset it.
+ * Otherwise, reset current context. Note that all native composition
+ * events during calling this will be ignored.
+ */
+ void ResetIME();
+
+ // Gets the current composition string by the native APIs.
+ void GetCompositionString(GtkIMContext* aContext,
+ nsAString& aCompositionString);
+
+ /**
+ * Generates our text range array from current composition string.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aCompositionString The data to be dispatched with
+ * compositionchange event.
+ */
+ already_AddRefed<TextRangeArray> CreateTextRangeArray(
+ GtkIMContext* aContext, const nsAString& aCompositionString);
+
+ /**
+ * SetTextRange() initializes aTextRange with aPangoAttrIter.
+ *
+ * @param aPangoAttrIter An iter which represents a clause of the
+ * composition string.
+ * @param aUTF8CompositionString The whole composition string (UTF-8).
+ * @param aUTF16CaretOffset The caret offset in the composition
+ * string encoded as UTF-16.
+ * @param aTextRange The result.
+ * @return true if this initializes aTextRange.
+ * Otherwise, false.
+ */
+ bool SetTextRange(PangoAttrIterator* aPangoAttrIter,
+ const gchar* aUTF8CompositionString,
+ uint32_t aUTF16CaretOffset, TextRange& aTextRange) const;
+
+ /**
+ * ToNscolor() converts the PangoColor in aPangoAttrColor to nscolor.
+ */
+ static nscolor ToNscolor(PangoAttrColor* aPangoAttrColor);
+
+ /**
+ * Move the candidate window with "fake" cursor position.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ */
+ void SetCursorPosition(GtkIMContext* aContext);
+
+ // Queries the current selection offset of the window.
+ uint32_t GetSelectionOffset(nsWindow* aWindow);
+
+ // Get current paragraph text content and cursor position
+ nsresult GetCurrentParagraph(nsAString& aText, uint32_t& aCursorPos);
+
+ /**
+ * Delete text portion
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aOffset Start offset of the range to delete.
+ * @param aNChars Count of characters to delete. It depends
+ * on |g_utf8_strlen()| what is one character.
+ */
+ nsresult DeleteText(GtkIMContext* aContext, int32_t aOffset,
+ uint32_t aNChars);
+
+ // Initializes the GUI event.
+ void InitEvent(WidgetGUIEvent& aEvent);
+
+ // Called before destroying the context to work around some platform bugs.
+ void PrepareToDestroyContext(GtkIMContext* aContext);
+
+ /**
+ * WARNING:
+ * Following methods dispatch gecko events. Then, the focused widget
+ * can be destroyed, and also it can be stolen focus. If they returns
+ * FALSE, callers cannot continue the composition.
+ * - MaybeDispatchKeyEventAsProcessedByIME
+ * - DispatchCompositionStart
+ * - DispatchCompositionChangeEvent
+ * - DispatchCompositionCommitEvent
+ */
+
+ /**
+ * Dispatch an eKeyDown or eKeyUp event whose mKeyCode value is
+ * NS_VK_PROCESSKEY and mKeyNameIndex is KEY_NAME_INDEX_Process if
+ * we're not in a dead key sequence, mProcessingKeyEvent is nullptr
+ * but mPostingKeyEvents is not empty or mProcessingKeyEvent is not
+ * nullptr and mKeyboardEventWasDispatched is still false. If this
+ * dispatches a keyboard event, this sets mKeyboardEventWasDispatched
+ * to true.
+ *
+ * @param aFollowingEvent The following event message.
+ * @return If the caller can continue to handle
+ * composition, returns true. Otherwise,
+ * false. For example, if focus is moved
+ * by dispatched keyboard event, returns
+ * false.
+ */
+ bool MaybeDispatchKeyEventAsProcessedByIME(EventMessage aFollowingEvent);
+
+ /**
+ * Dispatches a composition start event.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @return true if the focused widget is neither
+ * destroyed nor changed. Otherwise, false.
+ */
+ bool DispatchCompositionStart(GtkIMContext* aContext);
+
+ /**
+ * Dispatches a compositionchange event.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aCompositionString New composition string.
+ * @return true if the focused widget is neither
+ * destroyed nor changed. Otherwise, false.
+ */
+ bool DispatchCompositionChangeEvent(GtkIMContext* aContext,
+ const nsAString& aCompositionString);
+
+ /**
+ * Dispatches a compositioncommit event or compositioncommitasis event.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aCommitString If this is nullptr, the composition will
+ * be committed with last dispatched data.
+ * Otherwise, the composition will be
+ * committed with this value.
+ * @return true if the focused widget is neither
+ * destroyed nor changed. Otherwise, false.
+ */
+ bool DispatchCompositionCommitEvent(GtkIMContext* aContext,
+ const nsAString* aCommitString = nullptr);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef IMContextWrapper_h_
diff --git a/widget/gtk/InProcessGtkCompositorWidget.cpp b/widget/gtk/InProcessGtkCompositorWidget.cpp
new file mode 100644
index 0000000000..41581184fb
--- /dev/null
+++ b/widget/gtk/InProcessGtkCompositorWidget.cpp
@@ -0,0 +1,45 @@
+/* -*- 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 "HeadlessCompositorWidget.h"
+#include "HeadlessWidget.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+
+#include "InProcessGtkCompositorWidget.h"
+#include "nsWindow.h"
+
+namespace mozilla {
+namespace widget {
+
+/* static */
+RefPtr<CompositorWidget> CompositorWidget::CreateLocal(
+ const CompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions, nsIWidget* aWidget) {
+ if (aInitData.type() ==
+ CompositorWidgetInitData::THeadlessCompositorWidgetInitData) {
+ return new HeadlessCompositorWidget(
+ aInitData.get_HeadlessCompositorWidgetInitData(), aOptions,
+ static_cast<HeadlessWidget*>(aWidget));
+ } else {
+ return new InProcessGtkCompositorWidget(
+ aInitData.get_GtkCompositorWidgetInitData(), aOptions,
+ static_cast<nsWindow*>(aWidget));
+ }
+}
+
+InProcessGtkCompositorWidget::InProcessGtkCompositorWidget(
+ const GtkCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions, nsWindow* aWindow)
+ : GtkCompositorWidget(aInitData, aOptions, aWindow) {}
+
+void InProcessGtkCompositorWidget::ObserveVsync(VsyncObserver* aObserver) {
+ if (RefPtr<CompositorVsyncDispatcher> cvd =
+ mWidget->GetCompositorVsyncDispatcher()) {
+ cvd->SetCompositorVsyncObserver(aObserver);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/InProcessGtkCompositorWidget.h b/widget/gtk/InProcessGtkCompositorWidget.h
new file mode 100644
index 0000000000..bd7e68d6fb
--- /dev/null
+++ b/widget/gtk/InProcessGtkCompositorWidget.h
@@ -0,0 +1,30 @@
+/* -*- 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 widget_gtk_InProcessGtkCompositorWidget_h
+#define widget_gtk_InProcessGtkCompositorWidget_h
+
+#include "GtkCompositorWidget.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class InProcessGtkCompositorWidget final : public GtkCompositorWidget {
+ public:
+ InProcessGtkCompositorWidget(const GtkCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions,
+ nsWindow* aWindow);
+
+ // CompositorWidgetDelegate
+
+ void ObserveVsync(VsyncObserver* aObserver) override;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_InProcessGtkCompositorWidget_h
diff --git a/widget/gtk/MPRISInterfaceDescription.h b/widget/gtk/MPRISInterfaceDescription.h
new file mode 100644
index 0000000000..28e719e651
--- /dev/null
+++ b/widget/gtk/MPRISInterfaceDescription.h
@@ -0,0 +1,91 @@
+/* -*- 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 WIDGET_GTK_MPRIS_INTERFACE_DESCRIPTION_H_
+#define WIDGET_GTK_MPRIS_INTERFACE_DESCRIPTION_H_
+
+#include <gio/gio.h>
+
+extern const gchar introspection_xml[] =
+ // adopted from https://github.com/freedesktop/mpris-spec/blob/master/spec/org.mpris.MediaPlayer2.xml
+ // everything starting with tp can be removed, as it is used for HTML Spec Documentation Generation
+ "<node>"
+ "<interface name=\"org.mpris.MediaPlayer2\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "<method name=\"Raise\"/>"
+ "<method name=\"Quit\"/>"
+ "<property name=\"CanQuit\" type=\"b\" access=\"read\"/>"
+ "<property name=\"CanRaise\" type=\"b\" access=\"read\"/>"
+ "<property name=\"HasTrackList\" type=\"b\" access=\"read\"/>"
+ "<property name=\"Identity\" type=\"s\" access=\"read\"/>"
+ "<property name=\"DesktopEntry\" type=\"s\" access=\"read\"/>"
+ "<property name=\"SupportedUriSchemes\" type=\"as\" access=\"read\"/>"
+ "<property name=\"SupportedMimeTypes\" type=\"as\" access=\"read\"/>"
+ "</interface>"
+ // Note that every property emits a changed signal (which is default) apart from Position.
+ "<interface name=\"org.mpris.MediaPlayer2.Player\">"
+ "<method name=\"Next\"/>"
+ "<method name=\"Previous\"/>"
+ "<method name=\"Pause\"/>"
+ "<method name=\"PlayPause\"/>"
+ "<method name=\"Stop\"/>"
+ "<method name=\"Play\"/>"
+ "<method name=\"Seek\">"
+ "<arg direction=\"in\" type=\"x\" name=\"Offset\"/>"
+ "</method>"
+ "<method name=\"SetPosition\">"
+ "<arg direction=\"in\" type=\"o\" name=\"TrackId\"/>"
+ "<arg direction=\"in\" type=\"x\" name=\"Position\"/>"
+ "</method>"
+ "<method name=\"OpenUri\">"
+ "<arg direction=\"in\" type=\"s\" name=\"Uri\"/>"
+ "</method>"
+ "<property name=\"PlaybackStatus\" type=\"s\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"Rate\" type=\"d\" access=\"readwrite\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"Metadata\" type=\"a{sv}\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"Volume\" type=\"d\" access=\"readwrite\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"Position\" type=\"x\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"false\"/>"
+ "</property>"
+ "<property name=\"MinimumRate\" type=\"d\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"MaximumRate\" type=\"d\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"CanGoNext\" type=\"b\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"CanGoPrevious\" type=\"b\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"CanPlay\" type=\"b\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"CanPause\" type=\"b\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"CanSeek\" type=\"b\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"CanControl\" type=\"b\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"false\"/>"
+ "</property>"
+ "<signal name=\"Seeked\">"
+ "<arg name=\"Position\" type=\"x\"/>"
+ "</signal>"
+ "</interface>"
+ "</node>";
+
+#endif // WIDGET_GTK_MPRIS_INTERFACE_DESCRIPTION_H_
diff --git a/widget/gtk/MPRISServiceHandler.cpp b/widget/gtk/MPRISServiceHandler.cpp
new file mode 100644
index 0000000000..c21b705b16
--- /dev/null
+++ b/widget/gtk/MPRISServiceHandler.cpp
@@ -0,0 +1,851 @@
+/* -*- 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 "MPRISServiceHandler.h"
+
+#include <stdint.h>
+#include <inttypes.h>
+#include <unordered_map>
+
+#include "MPRISInterfaceDescription.h"
+#include "mozilla/dom/MediaControlUtils.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Sprintf.h"
+#include "nsIXULAppInfo.h"
+#include "nsIOutputStream.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+
+// avoid redefined macro in unified build
+#undef LOG
+#define LOG(msg, ...) \
+ MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
+ ("MPRISServiceHandler=%p, " msg, this, ##__VA_ARGS__))
+
+namespace mozilla {
+namespace widget {
+
+// A global counter tracking the total images saved in the system and it will be
+// used to form a unique image file name.
+static uint32_t gImageNumber = 0;
+
+static inline Maybe<mozilla::dom::MediaControlKey> GetMediaControlKey(
+ const gchar* aMethodName) {
+ const std::unordered_map<std::string, mozilla::dom::MediaControlKey> map = {
+ {"Raise", mozilla::dom::MediaControlKey::Focus},
+ {"Next", mozilla::dom::MediaControlKey::Nexttrack},
+ {"Previous", mozilla::dom::MediaControlKey::Previoustrack},
+ {"Pause", mozilla::dom::MediaControlKey::Pause},
+ {"PlayPause", mozilla::dom::MediaControlKey::Playpause},
+ {"Stop", mozilla::dom::MediaControlKey::Stop},
+ {"Play", mozilla::dom::MediaControlKey::Play}};
+
+ auto it = map.find(aMethodName);
+ return (it == map.end() ? Nothing() : Some(it->second));
+}
+
+static void HandleMethodCall(GDBusConnection* aConnection, const gchar* aSender,
+ const gchar* aObjectPath,
+ const gchar* aInterfaceName,
+ const gchar* aMethodName, GVariant* aParameters,
+ GDBusMethodInvocation* aInvocation,
+ gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Maybe<mozilla::dom::MediaControlKey> key = GetMediaControlKey(aMethodName);
+ if (key.isNothing()) {
+ g_dbus_method_invocation_return_error(
+ aInvocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED,
+ "Method %s.%s.%s not supported", aObjectPath, aInterfaceName,
+ aMethodName);
+ return;
+ }
+
+ MPRISServiceHandler* handler = static_cast<MPRISServiceHandler*>(aUserData);
+ if (handler->PressKey(key.value())) {
+ g_dbus_method_invocation_return_value(aInvocation, nullptr);
+ } else {
+ g_dbus_method_invocation_return_error(
+ aInvocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "%s.%s.%s is not available now", aObjectPath, aInterfaceName,
+ aMethodName);
+ }
+}
+
+enum class Property : uint8_t {
+ eIdentity,
+ eDesktopEntry,
+ eHasTrackList,
+ eCanRaise,
+ eCanQuit,
+ eSupportedUriSchemes,
+ eSupportedMimeTypes,
+ eCanGoNext,
+ eCanGoPrevious,
+ eCanPlay,
+ eCanPause,
+ eCanSeek,
+ eCanControl,
+ eGetPlaybackStatus,
+ eGetMetadata,
+};
+
+static inline Maybe<mozilla::dom::MediaControlKey> GetPairedKey(
+ Property aProperty) {
+ switch (aProperty) {
+ case Property::eCanRaise:
+ return Some(mozilla::dom::MediaControlKey::Focus);
+ case Property::eCanGoNext:
+ return Some(mozilla::dom::MediaControlKey::Nexttrack);
+ case Property::eCanGoPrevious:
+ return Some(mozilla::dom::MediaControlKey::Previoustrack);
+ case Property::eCanPlay:
+ return Some(mozilla::dom::MediaControlKey::Play);
+ case Property::eCanPause:
+ return Some(mozilla::dom::MediaControlKey::Pause);
+ default:
+ return Nothing();
+ }
+}
+
+static inline Maybe<Property> GetProperty(const gchar* aPropertyName) {
+ const std::unordered_map<std::string, Property> map = {
+ // org.mpris.MediaPlayer2 properties
+ {"Identity", Property::eIdentity},
+ {"DesktopEntry", Property::eDesktopEntry},
+ {"HasTrackList", Property::eHasTrackList},
+ {"CanRaise", Property::eCanRaise},
+ {"CanQuit", Property::eCanQuit},
+ {"SupportedUriSchemes", Property::eSupportedUriSchemes},
+ {"SupportedMimeTypes", Property::eSupportedMimeTypes},
+ // org.mpris.MediaPlayer2.Player properties
+ {"CanGoNext", Property::eCanGoNext},
+ {"CanGoPrevious", Property::eCanGoPrevious},
+ {"CanPlay", Property::eCanPlay},
+ {"CanPause", Property::eCanPause},
+ {"CanSeek", Property::eCanSeek},
+ {"CanControl", Property::eCanControl},
+ {"PlaybackStatus", Property::eGetPlaybackStatus},
+ {"Metadata", Property::eGetMetadata}};
+
+ auto it = map.find(aPropertyName);
+ return (it == map.end() ? Nothing() : Some(it->second));
+}
+
+static GVariant* HandleGetProperty(GDBusConnection* aConnection,
+ const gchar* aSender,
+ const gchar* aObjectPath,
+ const gchar* aInterfaceName,
+ const gchar* aPropertyName, GError** aError,
+ gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Maybe<Property> property = GetProperty(aPropertyName);
+ if (property.isNothing()) {
+ g_set_error(aError, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED,
+ "%s.%s %s is not supported", aObjectPath, aInterfaceName,
+ aPropertyName);
+ return nullptr;
+ }
+
+ MPRISServiceHandler* handler = static_cast<MPRISServiceHandler*>(aUserData);
+ switch (property.value()) {
+ case Property::eSupportedUriSchemes:
+ case Property::eSupportedMimeTypes:
+ // No plan to implement OpenUri for now
+ return g_variant_new_strv(nullptr, 0);
+ case Property::eGetPlaybackStatus:
+ return handler->GetPlaybackStatus();
+ case Property::eGetMetadata:
+ return handler->GetMetadataAsGVariant();
+ case Property::eIdentity:
+ return g_variant_new_string(handler->Identity());
+ case Property::eDesktopEntry:
+ return g_variant_new_string(handler->DesktopEntry());
+ case Property::eHasTrackList:
+ case Property::eCanQuit:
+ case Property::eCanSeek:
+ return g_variant_new_boolean(false);
+ // Play/Pause would be blocked if CanControl is false
+ case Property::eCanControl:
+ return g_variant_new_boolean(true);
+ case Property::eCanRaise:
+ case Property::eCanGoNext:
+ case Property::eCanGoPrevious:
+ case Property::eCanPlay:
+ case Property::eCanPause:
+ Maybe<mozilla::dom::MediaControlKey> key = GetPairedKey(property.value());
+ MOZ_ASSERT(key.isSome());
+ return g_variant_new_boolean(handler->IsMediaKeySupported(key.value()));
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Switch statement is incomplete");
+ return nullptr;
+}
+
+static gboolean HandleSetProperty(GDBusConnection* aConnection,
+ const gchar* aSender,
+ const gchar* aObjectPath,
+ const gchar* aInterfaceName,
+ const gchar* aPropertyName, GVariant* aValue,
+ GError** aError, gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ MOZ_ASSERT(NS_IsMainThread());
+ g_set_error(aError, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "%s:%s setting is not supported", aInterfaceName, aPropertyName);
+ return false;
+}
+
+static const GDBusInterfaceVTable gInterfaceVTable = {
+ HandleMethodCall, HandleGetProperty, HandleSetProperty};
+
+void MPRISServiceHandler::OnNameAcquiredStatic(GDBusConnection* aConnection,
+ const gchar* aName,
+ gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ static_cast<MPRISServiceHandler*>(aUserData)->OnNameAcquired(aConnection,
+ aName);
+}
+
+void MPRISServiceHandler::OnNameLostStatic(GDBusConnection* aConnection,
+ const gchar* aName,
+ gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ static_cast<MPRISServiceHandler*>(aUserData)->OnNameLost(aConnection, aName);
+}
+
+void MPRISServiceHandler::OnBusAcquiredStatic(GDBusConnection* aConnection,
+ const gchar* aName,
+ gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ static_cast<MPRISServiceHandler*>(aUserData)->OnBusAcquired(aConnection,
+ aName);
+}
+
+void MPRISServiceHandler::OnNameAcquired(GDBusConnection* aConnection,
+ const gchar* aName) {
+ LOG("OnNameAcquired: %s", aName);
+ mConnection = aConnection;
+}
+
+void MPRISServiceHandler::OnNameLost(GDBusConnection* aConnection,
+ const gchar* aName) {
+ LOG("OnNameLost: %s", aName);
+ mConnection = nullptr;
+ if (!mRootRegistrationId) {
+ return;
+ }
+
+ if (g_dbus_connection_unregister_object(aConnection, mRootRegistrationId)) {
+ mRootRegistrationId = 0;
+ } else {
+ // Note: Most code examples in the internet probably dont't even check the
+ // result here, but
+ // according to the spec it _can_ return false.
+ LOG("Unable to unregister root object from within onNameLost!");
+ }
+
+ if (!mPlayerRegistrationId) {
+ return;
+ }
+
+ if (g_dbus_connection_unregister_object(aConnection, mPlayerRegistrationId)) {
+ mPlayerRegistrationId = 0;
+ } else {
+ // Note: Most code examples in the internet probably dont't even check the
+ // result here, but
+ // according to the spec it _can_ return false.
+ LOG("Unable to unregister object from within onNameLost!");
+ }
+}
+
+void MPRISServiceHandler::OnBusAcquired(GDBusConnection* aConnection,
+ const gchar* aName) {
+ GError* error = nullptr;
+ LOG("OnBusAcquired: %s", aName);
+
+ mRootRegistrationId = g_dbus_connection_register_object(
+ aConnection, DBUS_MPRIS_OBJECT_PATH, mIntrospectionData->interfaces[0],
+ &gInterfaceVTable, this, /* user_data */
+ nullptr, /* user_data_free_func */
+ &error); /* GError** */
+
+ if (mRootRegistrationId == 0) {
+ LOG("Failed at root registration: %s",
+ error ? error->message : "Unknown Error");
+ if (error) {
+ g_error_free(error);
+ }
+ return;
+ }
+
+ mPlayerRegistrationId = g_dbus_connection_register_object(
+ aConnection, DBUS_MPRIS_OBJECT_PATH, mIntrospectionData->interfaces[1],
+ &gInterfaceVTable, this, /* user_data */
+ nullptr, /* user_data_free_func */
+ &error); /* GError** */
+
+ if (mPlayerRegistrationId == 0) {
+ LOG("Failed at object registration: %s",
+ error ? error->message : "Unknown Error");
+ if (error) {
+ g_error_free(error);
+ }
+ }
+}
+
+bool MPRISServiceHandler::Open() {
+ MOZ_ASSERT(!mInitialized);
+ MOZ_ASSERT(NS_IsMainThread());
+ GError* error = nullptr;
+ gchar serviceName[256];
+
+ InitIdentity();
+ SprintfLiteral(serviceName, DBUS_MPRIS_SERVICE_NAME ".instance%d", getpid());
+ mOwnerId =
+ g_bus_own_name(G_BUS_TYPE_SESSION, serviceName,
+ // Enter a waiting queue until this service name is free
+ // (likely another FF instance is running/has been crashed)
+ G_BUS_NAME_OWNER_FLAGS_NONE, OnBusAcquiredStatic,
+ OnNameAcquiredStatic, OnNameLostStatic, this, nullptr);
+
+ /* parse introspection data */
+ mIntrospectionData = g_dbus_node_info_new_for_xml(introspection_xml, &error);
+
+ if (!mIntrospectionData) {
+ LOG("Failed at parsing XML Interface definition: %s",
+ error ? error->message : "Unknown Error");
+ if (error) {
+ g_error_free(error);
+ }
+ return false;
+ }
+
+ mInitialized = true;
+ return true;
+}
+
+MPRISServiceHandler::~MPRISServiceHandler() {
+ MOZ_ASSERT(!mInitialized); // Close hasn't been called!
+}
+
+void MPRISServiceHandler::Close() {
+ gchar serviceName[256];
+ SprintfLiteral(serviceName, DBUS_MPRIS_SERVICE_NAME ".instance%d", getpid());
+
+ // Reset playback state and metadata before disconnect from dbus.
+ SetPlaybackState(dom::MediaSessionPlaybackState::None);
+ ClearMetadata();
+
+ OnNameLost(mConnection, serviceName);
+
+ if (mOwnerId != 0) {
+ g_bus_unown_name(mOwnerId);
+ }
+ if (mIntrospectionData) {
+ g_dbus_node_info_unref(mIntrospectionData);
+ }
+
+ mInitialized = false;
+ MediaControlKeySource::Close();
+}
+
+bool MPRISServiceHandler::IsOpened() const { return mInitialized; }
+
+void MPRISServiceHandler::InitIdentity() {
+ nsresult rv;
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1", &rv);
+
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = appInfo->GetVendor(mIdentity);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = appInfo->GetName(mDesktopEntry);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ mIdentity.Append(' ');
+ mIdentity.Append(mDesktopEntry);
+
+ // Compute the desktop entry name like nsAppRunner does for g_set_prgname
+ ToLowerCase(mDesktopEntry);
+}
+
+const char* MPRISServiceHandler::Identity() const {
+ MOZ_ASSERT(mInitialized);
+ return mIdentity.get();
+}
+
+const char* MPRISServiceHandler::DesktopEntry() const {
+ MOZ_ASSERT(mInitialized);
+ return mDesktopEntry.get();
+}
+
+bool MPRISServiceHandler::PressKey(mozilla::dom::MediaControlKey aKey) const {
+ MOZ_ASSERT(mInitialized);
+ if (!IsMediaKeySupported(aKey)) {
+ LOG("%s is not supported", ToMediaControlKeyStr(aKey));
+ return false;
+ }
+ LOG("Press %s", ToMediaControlKeyStr(aKey));
+ EmitEvent(aKey);
+ return true;
+}
+
+void MPRISServiceHandler::SetPlaybackState(
+ dom::MediaSessionPlaybackState aState) {
+ LOG("SetPlaybackState");
+ if (mPlaybackState == aState) {
+ return;
+ }
+
+ MediaControlKeySource::SetPlaybackState(aState);
+
+ GVariant* state = GetPlaybackStatus();
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(&builder, "{sv}", "PlaybackStatus", state);
+
+ GVariant* parameters = g_variant_new(
+ "(sa{sv}as)", DBUS_MPRIS_PLAYER_INTERFACE, &builder, nullptr);
+
+ LOG("Emitting MPRIS property changes for 'PlaybackStatus'");
+ Unused << EmitPropertiesChangedSignal(parameters);
+}
+
+GVariant* MPRISServiceHandler::GetPlaybackStatus() const {
+ switch (GetPlaybackState()) {
+ case dom::MediaSessionPlaybackState::Playing:
+ return g_variant_new_string("Playing");
+ case dom::MediaSessionPlaybackState::Paused:
+ return g_variant_new_string("Paused");
+ case dom::MediaSessionPlaybackState::None:
+ return g_variant_new_string("Stopped");
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid Playback State");
+ return nullptr;
+ }
+}
+
+void MPRISServiceHandler::SetMediaMetadata(
+ const dom::MediaMetadataBase& aMetadata) {
+ // Reset the index of the next available image to be fetched in the artwork,
+ // before checking the fetching process should be started or not. The image
+ // fetching process could be skipped if the image being fetching currently is
+ // in the artwork. If the current image fetching fails, the next availabe
+ // candidate should be the first image in the latest artwork
+ mNextImageIndex = 0;
+
+ // No need to fetch a MPRIS image if
+ // 1) MPRIS image is being fetched, and the one in fetching is in the artwork
+ // 2) MPRIS image is not being fetched, and the one in use is in the artwork
+ if (!mFetchingUrl.IsEmpty()) {
+ if (mozilla::dom::IsImageIn(aMetadata.mArtwork, mFetchingUrl)) {
+ LOG("No need to load MPRIS image. The one being processed is in the "
+ "artwork");
+ // Set MPRIS without the image first. The image will be loaded to MPRIS
+ // asynchronously once it's fetched and saved into a local file
+ SetMediaMetadataInternal(aMetadata);
+ return;
+ }
+ } else if (!mCurrentImageUrl.IsEmpty()) {
+ if (mozilla::dom::IsImageIn(aMetadata.mArtwork, mCurrentImageUrl)) {
+ LOG("No need to load MPRIS image. The one in use is in the artwork");
+ SetMediaMetadataInternal(aMetadata, false);
+ return;
+ }
+ }
+
+ // Set MPRIS without the image first then load the image to MPRIS
+ // asynchronously
+ SetMediaMetadataInternal(aMetadata);
+ LoadImageAtIndex(mNextImageIndex++);
+}
+
+bool MPRISServiceHandler::EmitMetadataChanged() const {
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(&builder, "{sv}", "Metadata", GetMetadataAsGVariant());
+
+ GVariant* parameters = g_variant_new(
+ "(sa{sv}as)", DBUS_MPRIS_PLAYER_INTERFACE, &builder, nullptr);
+
+ LOG("Emit MPRIS property changes for 'Metadata'");
+ return EmitPropertiesChangedSignal(parameters);
+}
+
+void MPRISServiceHandler::SetMediaMetadataInternal(
+ const dom::MediaMetadataBase& aMetadata, bool aClearArtUrl) {
+ mMPRISMetadata.UpdateFromMetadataBase(aMetadata);
+ if (aClearArtUrl) {
+ mMPRISMetadata.mArtUrl.Truncate();
+ }
+ EmitMetadataChanged();
+}
+
+void MPRISServiceHandler::ClearMetadata() {
+ mMPRISMetadata.Clear();
+ mImageFetchRequest.DisconnectIfExists();
+ RemoveAllLocalImages();
+ mCurrentImageUrl.Truncate();
+ mFetchingUrl.Truncate();
+ mNextImageIndex = 0;
+ mSupportedKeys = 0;
+ EmitMetadataChanged();
+}
+
+void MPRISServiceHandler::LoadImageAtIndex(const size_t aIndex) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aIndex >= mMPRISMetadata.mArtwork.Length()) {
+ LOG("Stop loading image to MPRIS. No available image");
+ mImageFetchRequest.DisconnectIfExists();
+ return;
+ }
+
+ const mozilla::dom::MediaImage& image = mMPRISMetadata.mArtwork[aIndex];
+
+ if (!mozilla::dom::IsValidImageUrl(image.mSrc)) {
+ LOG("Skip the image with invalid URL. Try next image");
+ LoadImageAtIndex(mNextImageIndex++);
+ return;
+ }
+
+ mImageFetchRequest.DisconnectIfExists();
+ mFetchingUrl = image.mSrc;
+
+ mImageFetcher = mozilla::MakeUnique<mozilla::dom::FetchImageHelper>(image);
+ RefPtr<MPRISServiceHandler> self = this;
+ mImageFetcher->FetchImage()
+ ->Then(
+ AbstractThread::MainThread(), __func__,
+ [this, self](const nsCOMPtr<imgIContainer>& aImage) {
+ LOG("The image is fetched successfully");
+ mImageFetchRequest.Complete();
+
+ uint32_t size = 0;
+ char* data = nullptr;
+ // Only used to hold the image data
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = mozilla::dom::GetEncodedImageBuffer(
+ aImage, mMimeType, getter_AddRefs(inputStream), &size, &data);
+ if (NS_FAILED(rv) || !inputStream || size == 0 || !data) {
+ LOG("Failed to get the image buffer info. Try next image");
+ LoadImageAtIndex(mNextImageIndex++);
+ return;
+ }
+
+ if (SetImageToDisplay(data, size)) {
+ mCurrentImageUrl = mFetchingUrl;
+ LOG("The MPRIS image is updated to the image from: %s",
+ NS_ConvertUTF16toUTF8(mCurrentImageUrl).get());
+ } else {
+ LOG("Failed to set image to MPRIS");
+ mCurrentImageUrl.Truncate();
+ }
+
+ mFetchingUrl.Truncate();
+ },
+ [this, self](bool) {
+ LOG("Failed to fetch image. Try next image");
+ mImageFetchRequest.Complete();
+ mFetchingUrl.Truncate();
+ LoadImageAtIndex(mNextImageIndex++);
+ })
+ ->Track(mImageFetchRequest);
+}
+
+bool MPRISServiceHandler::SetImageToDisplay(const char* aImageData,
+ uint32_t aDataSize) {
+ if (!RenewLocalImageFile(aImageData, aDataSize)) {
+ return false;
+ }
+ MOZ_ASSERT(mLocalImageFile);
+
+ mMPRISMetadata.mArtUrl = nsCString("file://");
+ mMPRISMetadata.mArtUrl.Append(mLocalImageFile->NativePath());
+
+ LOG("The image file is created at %s", mMPRISMetadata.mArtUrl.get());
+ return EmitMetadataChanged();
+}
+
+bool MPRISServiceHandler::RenewLocalImageFile(const char* aImageData,
+ uint32_t aDataSize) {
+ MOZ_ASSERT(aImageData);
+ MOZ_ASSERT(aDataSize != 0);
+
+ if (!InitLocalImageFile()) {
+ LOG("Failed to create a new image");
+ return false;
+ }
+
+ MOZ_ASSERT(mLocalImageFile);
+ nsCOMPtr<nsIOutputStream> out;
+ NS_NewLocalFileOutputStream(getter_AddRefs(out), mLocalImageFile,
+ PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+ uint32_t written;
+ nsresult rv = out->Write(aImageData, aDataSize, &written);
+ if (NS_FAILED(rv) || written != aDataSize) {
+ LOG("Failed to write an image file");
+ RemoveAllLocalImages();
+ return false;
+ }
+
+ return true;
+}
+
+static const char* GetImageFileExtension(const char* aMimeType) {
+ MOZ_ASSERT(strcmp(aMimeType, IMAGE_PNG) == 0);
+ return "png";
+}
+
+bool MPRISServiceHandler::InitLocalImageFile() {
+ RemoveAllLocalImages();
+
+ if (!InitLocalImageFolder()) {
+ return false;
+ }
+
+ MOZ_ASSERT(mLocalImageFolder);
+ MOZ_ASSERT(!mLocalImageFile);
+ nsresult rv = mLocalImageFolder->Clone(getter_AddRefs(mLocalImageFile));
+ if (NS_FAILED(rv)) {
+ LOG("Failed to get the image folder");
+ return false;
+ }
+
+ auto cleanup =
+ MakeScopeExit([this, self = RefPtr<MPRISServiceHandler>(this)] {
+ mLocalImageFile = nullptr;
+ });
+
+ // Create an unique file name to work around the file caching mechanism in the
+ // Ubuntu. Once the image X specified by the filename Y is used in Ubuntu's
+ // MPRIS, this pair will be cached. As long as the filename is same, even the
+ // file content specified by Y is changed to Z, the image will stay unchanged.
+ // The image shown in the Ubuntu's notification is still X instead of Z.
+ // Changing the filename constantly works around this problem
+ char filename[64];
+ SprintfLiteral(filename, "%d_%d.%s", getpid(), gImageNumber++,
+ GetImageFileExtension(mMimeType.get()));
+
+ rv = mLocalImageFile->Append(NS_ConvertUTF8toUTF16(filename));
+ if (NS_FAILED(rv)) {
+ LOG("Failed to create an image filename");
+ return false;
+ }
+
+ rv = mLocalImageFile->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
+ if (NS_FAILED(rv)) {
+ LOG("Failed to create an image file");
+ return false;
+ }
+
+ cleanup.release();
+ return true;
+}
+
+bool MPRISServiceHandler::InitLocalImageFolder() {
+ if (mLocalImageFolder && LocalImageFolderExists()) {
+ return true;
+ }
+
+ nsresult rv = NS_GetSpecialDirectory(XRE_USER_APP_DATA_DIR,
+ getter_AddRefs(mLocalImageFolder));
+ if (NS_FAILED(rv) || !mLocalImageFolder) {
+ LOG("Failed to get the image folder");
+ return false;
+ }
+
+ auto cleanup =
+ MakeScopeExit([this, self = RefPtr<MPRISServiceHandler>(this)] {
+ mLocalImageFolder = nullptr;
+ });
+
+ rv = mLocalImageFolder->Append(u"firefox-mpris"_ns);
+ if (NS_FAILED(rv)) {
+ LOG("Failed to name an image folder");
+ return false;
+ }
+
+ if (!LocalImageFolderExists()) {
+ rv = mLocalImageFolder->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv)) {
+ LOG("Failed to create an image folder");
+ return false;
+ }
+ }
+
+ cleanup.release();
+ return true;
+}
+
+void MPRISServiceHandler::RemoveAllLocalImages() {
+ if (!mLocalImageFolder || !LocalImageFolderExists()) {
+ return;
+ }
+
+ nsresult rv = mLocalImageFolder->Remove(/* aRecursive */ true);
+ if (NS_FAILED(rv)) {
+ // It's ok to fail. The next removal is called when updating the
+ // media-session image, or closing the MPRIS.
+ LOG("Failed to remove images");
+ }
+
+ LOG("Abandon %s",
+ mLocalImageFile ? mLocalImageFile->NativePath().get() : "nothing");
+ mMPRISMetadata.mArtUrl.Truncate();
+ mLocalImageFile = nullptr;
+ mLocalImageFolder = nullptr;
+}
+
+bool MPRISServiceHandler::LocalImageFolderExists() {
+ MOZ_ASSERT(mLocalImageFolder);
+
+ bool exists;
+ nsresult rv = mLocalImageFolder->Exists(&exists);
+ return NS_SUCCEEDED(rv) && exists;
+}
+
+GVariant* MPRISServiceHandler::GetMetadataAsGVariant() const {
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(&builder, "{sv}", "mpris:trackid",
+ g_variant_new("o", DBUS_MPRIS_TRACK_PATH));
+
+ g_variant_builder_add(
+ &builder, "{sv}", "xesam:title",
+ g_variant_new_string(static_cast<const gchar*>(
+ NS_ConvertUTF16toUTF8(mMPRISMetadata.mTitle).get())));
+
+ g_variant_builder_add(
+ &builder, "{sv}", "xesam:album",
+ g_variant_new_string(static_cast<const gchar*>(
+ NS_ConvertUTF16toUTF8(mMPRISMetadata.mAlbum).get())));
+
+ GVariantBuilder artistBuilder;
+ g_variant_builder_init(&artistBuilder, G_VARIANT_TYPE("as"));
+ g_variant_builder_add(
+ &artistBuilder, "s",
+ static_cast<const gchar*>(
+ NS_ConvertUTF16toUTF8(mMPRISMetadata.mArtist).get()));
+ g_variant_builder_add(&builder, "{sv}", "xesam:artist",
+ g_variant_builder_end(&artistBuilder));
+
+ if (!mMPRISMetadata.mArtUrl.IsEmpty()) {
+ g_variant_builder_add(&builder, "{sv}", "mpris:artUrl",
+ g_variant_new_string(static_cast<const gchar*>(
+ mMPRISMetadata.mArtUrl.get())));
+ }
+
+ return g_variant_builder_end(&builder);
+}
+
+void MPRISServiceHandler::EmitEvent(mozilla::dom::MediaControlKey aKey) const {
+ for (const auto& listener : mListeners) {
+ listener->OnActionPerformed(mozilla::dom::MediaControlAction(aKey));
+ }
+}
+
+struct InterfaceProperty {
+ const char* interface;
+ const char* property;
+};
+static const std::unordered_map<mozilla::dom::MediaControlKey,
+ InterfaceProperty>
+ gKeyProperty = {{mozilla::dom::MediaControlKey::Focus,
+ {DBUS_MPRIS_INTERFACE, "CanRaise"}},
+ {mozilla::dom::MediaControlKey::Nexttrack,
+ {DBUS_MPRIS_PLAYER_INTERFACE, "CanGoNext"}},
+ {mozilla::dom::MediaControlKey::Previoustrack,
+ {DBUS_MPRIS_PLAYER_INTERFACE, "CanGoPrevious"}},
+ {mozilla::dom::MediaControlKey::Play,
+ {DBUS_MPRIS_PLAYER_INTERFACE, "CanPlay"}},
+ {mozilla::dom::MediaControlKey::Pause,
+ {DBUS_MPRIS_PLAYER_INTERFACE, "CanPause"}}};
+
+void MPRISServiceHandler::SetSupportedMediaKeys(
+ const MediaKeysArray& aSupportedKeys) {
+ uint32_t supportedKeys = 0;
+ for (const mozilla::dom::MediaControlKey& key : aSupportedKeys) {
+ supportedKeys |= GetMediaKeyMask(key);
+ }
+
+ if (mSupportedKeys == supportedKeys) {
+ LOG("Supported keys stay the same");
+ return;
+ }
+
+ uint32_t oldSupportedKeys = mSupportedKeys;
+ mSupportedKeys = supportedKeys;
+
+ // Emit related property changes
+ for (auto it : gKeyProperty) {
+ bool keyWasSupported = oldSupportedKeys & GetMediaKeyMask(it.first);
+ bool keyIsSupported = mSupportedKeys & GetMediaKeyMask(it.first);
+ if (keyWasSupported != keyIsSupported) {
+ LOG("Emit PropertiesChanged signal: %s.%s=%s", it.second.interface,
+ it.second.property, keyIsSupported ? "true" : "false");
+ EmitSupportedKeyChanged(it.first, keyIsSupported);
+ }
+ }
+}
+
+bool MPRISServiceHandler::IsMediaKeySupported(
+ mozilla::dom::MediaControlKey aKey) const {
+ return mSupportedKeys & GetMediaKeyMask(aKey);
+}
+
+bool MPRISServiceHandler::EmitSupportedKeyChanged(
+ mozilla::dom::MediaControlKey aKey, bool aSupported) const {
+ auto it = gKeyProperty.find(aKey);
+ if (it == gKeyProperty.end()) {
+ LOG("No property for %s", ToMediaControlKeyStr(aKey));
+ return false;
+ }
+
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(&builder, "{sv}",
+ static_cast<const gchar*>(it->second.property),
+ g_variant_new_boolean(aSupported));
+
+ GVariant* parameters = g_variant_new(
+ "(sa{sv}as)", static_cast<const gchar*>(it->second.interface), &builder,
+ nullptr);
+
+ LOG("Emit MPRIS property changes for '%s.%s'", it->second.interface,
+ it->second.property);
+ return EmitPropertiesChangedSignal(parameters);
+}
+
+bool MPRISServiceHandler::EmitPropertiesChangedSignal(
+ GVariant* aParameters) const {
+ if (!mConnection) {
+ LOG("No D-Bus Connection. Cannot emit properties changed signal");
+ return false;
+ }
+
+ GError* error = nullptr;
+ if (!g_dbus_connection_emit_signal(
+ mConnection, nullptr, DBUS_MPRIS_OBJECT_PATH,
+ "org.freedesktop.DBus.Properties", "PropertiesChanged", aParameters,
+ &error)) {
+ LOG("Failed to emit MPRIS property changes: %s",
+ error ? error->message : "Unknown Error");
+ if (error) {
+ g_error_free(error);
+ }
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/MPRISServiceHandler.h b/widget/gtk/MPRISServiceHandler.h
new file mode 100644
index 0000000000..6d6c898088
--- /dev/null
+++ b/widget/gtk/MPRISServiceHandler.h
@@ -0,0 +1,188 @@
+/* -*- 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 WIDGET_GTK_MPRIS_SERVICE_HANDLER_H_
+#define WIDGET_GTK_MPRIS_SERVICE_HANDLER_H_
+
+#include <gio/gio.h>
+#include "mozilla/dom/FetchImageHelper.h"
+#include "mozilla/dom/MediaControlKeySource.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIFile.h"
+#include "nsMimeTypes.h"
+#include "nsString.h"
+
+#define DBUS_MPRIS_SERVICE_NAME "org.mpris.MediaPlayer2.firefox"
+#define DBUS_MPRIS_OBJECT_PATH "/org/mpris/MediaPlayer2"
+#define DBUS_MPRIS_INTERFACE "org.mpris.MediaPlayer2"
+#define DBUS_MPRIS_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player"
+#define DBUS_MPRIS_TRACK_PATH "/org/mpris/MediaPlayer2/firefox"
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * This class implements the "MPRIS" D-Bus Service
+ * (https://specifications.freedesktop.org/mpris-spec/2.2),
+ * which is used to communicate with the Desktop Environment about the
+ * Multimedia playing in Gecko.
+ * Note that this interface requires many methods which may not be supported by
+ * Gecko, the interface
+ * however provides CanXYZ properties for these methods, so the method is
+ * defined but won't be executed.
+ *
+ * Also note that the following defines are for parts that the MPRIS Spec
+ * defines optional. The code won't
+ * compile with any of the defines set, yet, as those aren't implemented yet and
+ * probably never will be of
+ * use for gecko. For sake of completeness, they have been added until the
+ * decision about their implementation
+ * is finally made.
+ *
+ * The constexpr'ed methods are capabilities of the user agent known at compile
+ * time, e.g. we decided at
+ * compile time whether we ever want to support closing the user agent via MPRIS
+ * (Quit() and CanQuit()).
+ *
+ * Other properties like CanPlay() might depend on the runtime state (is there
+ * media available for playback?)
+ * and thus aren't a constexpr but merely a const method.
+ */
+class MPRISServiceHandler final : public dom::MediaControlKeySource {
+ NS_INLINE_DECL_REFCOUNTING(MPRISServiceHandler, override)
+ public:
+ // Note that this constructor does NOT initialize the MPRIS Service but only
+ // this class. The method Open() is responsible for registering and MAY FAIL.
+
+ // The image format used in MPRIS is based on the mMimeType here. Although
+ // IMAGE_JPEG or IMAGE_BMP are valid types as well but a png image with
+ // transparent background will be converted into a jpeg/bmp file with a
+ // colored background IMAGE_PNG format seems to be the best choice for now.
+ MPRISServiceHandler() : mMimeType(IMAGE_PNG){};
+ bool Open() override;
+ void Close() override;
+ bool IsOpened() const override;
+
+ // From the EventSource.
+ void SetPlaybackState(dom::MediaSessionPlaybackState aState) override;
+
+ // GetPlaybackState returns dom::PlaybackState. GetPlaybackStatus returns this
+ // state converted into d-bus variants.
+ GVariant* GetPlaybackStatus() const;
+
+ const char* Identity() const;
+ const char* DesktopEntry() const;
+ bool PressKey(dom::MediaControlKey aKey) const;
+
+ void SetMediaMetadata(const dom::MediaMetadataBase& aMetadata) override;
+ GVariant* GetMetadataAsGVariant() const;
+
+ void SetSupportedMediaKeys(const MediaKeysArray& aSupportedKeys) override;
+
+ bool IsMediaKeySupported(dom::MediaControlKey aKey) const;
+
+ private:
+ ~MPRISServiceHandler();
+
+ // Note: Registration Ids for the D-Bus start with 1, so a value of 0
+ // indicates an error (or an object which wasn't initialized yet)
+
+ // a handle to our bus registration/ownership
+ guint mOwnerId = 0;
+ // This is for the interface org.mpris.MediaPlayer2
+ guint mRootRegistrationId = 0;
+ // This is for the interface org.mpris.MediaPlayer2.Player
+ guint mPlayerRegistrationId = 0;
+ GDBusNodeInfo* mIntrospectionData = nullptr;
+ GDBusConnection* mConnection = nullptr;
+ bool mInitialized = false;
+ nsAutoCString mIdentity;
+ nsAutoCString mDesktopEntry;
+
+ nsCString mMimeType;
+
+ // A bitmask indicating what keys are enabled
+ uint32_t mSupportedKeys = 0;
+
+ class MPRISMetadata : public dom::MediaMetadataBase {
+ public:
+ MPRISMetadata() = default;
+ ~MPRISMetadata() = default;
+
+ void UpdateFromMetadataBase(const dom::MediaMetadataBase& aMetadata) {
+ mTitle = aMetadata.mTitle;
+ mArtist = aMetadata.mArtist;
+ mAlbum = aMetadata.mAlbum;
+ mArtwork = aMetadata.mArtwork;
+ }
+ void Clear() {
+ UpdateFromMetadataBase(MediaMetadataBase::EmptyData());
+ mArtUrl.Truncate();
+ }
+
+ nsCString mArtUrl;
+ };
+ MPRISMetadata mMPRISMetadata;
+
+ // The saved image file fetched from the URL
+ nsCOMPtr<nsIFile> mLocalImageFile;
+ nsCOMPtr<nsIFile> mLocalImageFolder;
+
+ mozilla::UniquePtr<mozilla::dom::FetchImageHelper> mImageFetcher;
+ mozilla::MozPromiseRequestHolder<mozilla::dom::ImagePromise>
+ mImageFetchRequest;
+
+ nsString mFetchingUrl;
+ nsString mCurrentImageUrl;
+
+ size_t mNextImageIndex = 0;
+
+ // Load the image at index aIndex of the metadta's artwork to MPRIS
+ // asynchronously
+ void LoadImageAtIndex(const size_t aIndex);
+ bool SetImageToDisplay(const char* aImageData, uint32_t aDataSize);
+
+ bool RenewLocalImageFile(const char* aImageData, uint32_t aDataSize);
+ bool InitLocalImageFile();
+ bool InitLocalImageFolder();
+ void RemoveAllLocalImages();
+ bool LocalImageFolderExists();
+
+ // Queries nsAppInfo to get the branded browser name and vendor
+ void InitIdentity();
+
+ // non-public API, called from events
+ void OnNameAcquired(GDBusConnection* aConnection, const gchar* aName);
+ void OnNameLost(GDBusConnection* aConnection, const gchar* aName);
+ void OnBusAcquired(GDBusConnection* aConnection, const gchar* aName);
+
+ static void OnNameAcquiredStatic(GDBusConnection* aConnection,
+ const gchar* aName, gpointer aUserData);
+ static void OnNameLostStatic(GDBusConnection* aConnection, const gchar* aName,
+ gpointer aUserData);
+ static void OnBusAcquiredStatic(GDBusConnection* aConnection,
+ const gchar* aName, gpointer aUserData);
+
+ void EmitEvent(dom::MediaControlKey aKey) const;
+
+ bool EmitMetadataChanged() const;
+
+ void SetMediaMetadataInternal(const dom::MediaMetadataBase& aMetadata,
+ bool aClearArtUrl = true);
+
+ bool EmitSupportedKeyChanged(mozilla::dom::MediaControlKey aKey,
+ bool aSupported) const;
+
+ bool EmitPropertiesChangedSignal(GVariant* aParameters) const;
+
+ void ClearMetadata();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // WIDGET_GTK_MPRIS_SERVICE_HANDLER_H_
diff --git a/widget/gtk/MediaKeysEventSourceFactory.cpp b/widget/gtk/MediaKeysEventSourceFactory.cpp
new file mode 100644
index 0000000000..b9a3dfde41
--- /dev/null
+++ b/widget/gtk/MediaKeysEventSourceFactory.cpp
@@ -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 "MediaKeysEventSourceFactory.h"
+#include "MPRISServiceHandler.h"
+
+namespace mozilla::widget {
+
+mozilla::dom::MediaControlKeySource* CreateMediaControlKeySource() {
+ return new MPRISServiceHandler();
+}
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/MozContainer.cpp b/widget/gtk/MozContainer.cpp
new file mode 100644
index 0000000000..334592eae1
--- /dev/null
+++ b/widget/gtk/MozContainer.cpp
@@ -0,0 +1,376 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "MozContainer.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <stdio.h>
+#ifdef MOZ_WAYLAND
+# include "gfxPlatformGtk.h"
+#endif
+
+#ifdef ACCESSIBILITY
+# include <atk/atk.h>
+# include "maiRedundantObjectFactory.h"
+#endif
+
+#undef LOG
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gWidgetLog;
+# define LOG(args) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOG(args)
+#endif /* MOZ_LOGGING */
+
+/* init methods */
+static void moz_container_class_init(MozContainerClass* klass);
+static void moz_container_init(MozContainer* container);
+
+/* widget class methods */
+static void moz_container_map(GtkWidget* widget);
+static void moz_container_unmap(GtkWidget* widget);
+static void moz_container_size_allocate(GtkWidget* widget,
+ GtkAllocation* allocation);
+void moz_container_realize(GtkWidget* widget);
+
+/* container class methods */
+static void moz_container_remove(GtkContainer* container,
+ GtkWidget* child_widget);
+static void moz_container_forall(GtkContainer* container,
+ gboolean include_internals,
+ GtkCallback callback, gpointer callback_data);
+static void moz_container_add(GtkContainer* container, GtkWidget* widget);
+
+typedef struct _MozContainerChild MozContainerChild;
+
+struct _MozContainerChild {
+ GtkWidget* widget;
+ gint x;
+ gint y;
+};
+
+static void moz_container_allocate_child(MozContainer* container,
+ MozContainerChild* child);
+static MozContainerChild* moz_container_get_child(MozContainer* container,
+ GtkWidget* child);
+
+/* public methods */
+
+GType moz_container_get_type(void) {
+ static GType moz_container_type = 0;
+
+ if (!moz_container_type) {
+ static GTypeInfo moz_container_info = {
+ sizeof(MozContainerClass), /* class_size */
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc)moz_container_class_init, /* class_init */
+ NULL, /* class_destroy */
+ NULL, /* class_data */
+ sizeof(MozContainer), /* instance_size */
+ 0, /* n_preallocs */
+ (GInstanceInitFunc)moz_container_init, /* instance_init */
+ NULL, /* value_table */
+ };
+
+#ifdef MOZ_WAYLAND
+ if (gfxPlatformGtk::GetPlatform()->IsWaylandDisplay()) {
+ moz_container_info.class_init =
+ (GClassInitFunc)moz_container_wayland_class_init;
+ }
+#endif
+
+ moz_container_type =
+ g_type_register_static(GTK_TYPE_CONTAINER, "MozContainer",
+ &moz_container_info, static_cast<GTypeFlags>(0));
+#ifdef ACCESSIBILITY
+ /* Set a factory to return accessible object with ROLE_REDUNDANT for
+ * MozContainer, so that gail won't send focus notification for it */
+ atk_registry_set_factory_type(atk_get_default_registry(),
+ moz_container_type,
+ mai_redundant_object_factory_get_type());
+#endif
+ }
+
+ return moz_container_type;
+}
+
+GtkWidget* moz_container_new(void) {
+ MozContainer* container;
+
+ container =
+ static_cast<MozContainer*>(g_object_new(MOZ_CONTAINER_TYPE, nullptr));
+
+ return GTK_WIDGET(container);
+}
+
+void moz_container_put(MozContainer* container, GtkWidget* child_widget, gint x,
+ gint y) {
+ MozContainerChild* child;
+
+ child = g_new(MozContainerChild, 1);
+
+ child->widget = child_widget;
+ child->x = x;
+ child->y = y;
+
+ /* printf("moz_container_put %p %p %d %d\n", (void *)container,
+ (void *)child_widget, x, y); */
+
+ container->children = g_list_append(container->children, child);
+
+ /* we assume that the caller of this function will have already set
+ the parent GdkWindow because we can have many anonymous children. */
+ gtk_widget_set_parent(child_widget, GTK_WIDGET(container));
+}
+
+void moz_container_class_init(MozContainerClass* klass) {
+ /*GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass); */
+ GtkContainerClass* container_class = GTK_CONTAINER_CLASS(klass);
+ GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
+
+ widget_class->map = moz_container_map;
+ widget_class->unmap = moz_container_unmap;
+ widget_class->realize = moz_container_realize;
+ widget_class->size_allocate = moz_container_size_allocate;
+
+ container_class->remove = moz_container_remove;
+ container_class->forall = moz_container_forall;
+ container_class->add = moz_container_add;
+}
+
+void moz_container_init(MozContainer* container) {
+ gtk_widget_set_can_focus(GTK_WIDGET(container), TRUE);
+ gtk_container_set_resize_mode(GTK_CONTAINER(container), GTK_RESIZE_IMMEDIATE);
+ gtk_widget_set_redraw_on_allocate(GTK_WIDGET(container), FALSE);
+#ifdef MOZ_WAYLAND
+ if (gfxPlatformGtk::GetPlatform()->IsWaylandDisplay()) {
+ moz_container_wayland_init(&container->wl_container);
+ }
+#endif
+ LOG(("%s [%p]\n", __FUNCTION__, (void*)container));
+}
+
+void moz_container_map(GtkWidget* widget) {
+ MozContainer* container;
+ GList* tmp_list;
+ GtkWidget* tmp_child;
+
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+ container = MOZ_CONTAINER(widget);
+
+ gtk_widget_set_mapped(widget, TRUE);
+
+ tmp_list = container->children;
+ while (tmp_list) {
+ tmp_child = ((MozContainerChild*)tmp_list->data)->widget;
+
+ if (gtk_widget_get_visible(tmp_child)) {
+ if (!gtk_widget_get_mapped(tmp_child)) gtk_widget_map(tmp_child);
+ }
+ tmp_list = tmp_list->next;
+ }
+
+ if (gtk_widget_get_has_window(widget)) {
+ gdk_window_show(gtk_widget_get_window(widget));
+ }
+}
+
+void moz_container_unmap(GtkWidget* widget) {
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+
+ gtk_widget_set_mapped(widget, FALSE);
+
+ if (gtk_widget_get_has_window(widget)) {
+ gdk_window_hide(gtk_widget_get_window(widget));
+ }
+}
+
+void moz_container_realize(GtkWidget* widget) {
+ GdkWindow* parent = gtk_widget_get_parent_window(widget);
+ GdkWindow* window;
+
+ gtk_widget_set_realized(widget, TRUE);
+
+ if (gtk_widget_get_has_window(widget)) {
+ GdkWindowAttr attributes;
+ gint attributes_mask = GDK_WA_VISUAL | GDK_WA_X | GDK_WA_Y;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation(widget, &allocation);
+ attributes.event_mask = gtk_widget_get_events(widget);
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ MozContainer* container = MOZ_CONTAINER(widget);
+ attributes.visual =
+ container->force_default_visual
+ ? gdk_screen_get_system_visual(gtk_widget_get_screen(widget))
+ : gtk_widget_get_visual(widget);
+
+ window = gdk_window_new(parent, &attributes, attributes_mask);
+
+ LOG(("moz_container_realize() [%p] GdkWindow %p\n", (void*)container,
+ (void*)window));
+
+ gdk_window_set_user_data(window, widget);
+ } else {
+ window = parent;
+ g_object_ref(window);
+ }
+
+ gtk_widget_set_window(widget, window);
+}
+
+void moz_container_size_allocate(GtkWidget* widget, GtkAllocation* allocation) {
+ MozContainer* container;
+ GList* tmp_list;
+ GtkAllocation tmp_allocation;
+
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+
+ LOG(("moz_container_size_allocate [%p] %d,%d -> %d x %d\n", (void*)widget,
+ allocation->x, allocation->y, allocation->width, allocation->height));
+
+ /* short circuit if you can */
+ container = MOZ_CONTAINER(widget);
+ gtk_widget_get_allocation(widget, &tmp_allocation);
+ if (!container->children && tmp_allocation.x == allocation->x &&
+ tmp_allocation.y == allocation->y &&
+ tmp_allocation.width == allocation->width &&
+ tmp_allocation.height == allocation->height) {
+ return;
+ }
+
+ gtk_widget_set_allocation(widget, allocation);
+
+ tmp_list = container->children;
+
+ while (tmp_list) {
+ MozContainerChild* child = static_cast<MozContainerChild*>(tmp_list->data);
+
+ moz_container_allocate_child(container, child);
+
+ tmp_list = tmp_list->next;
+ }
+
+ if (gtk_widget_get_has_window(widget) && gtk_widget_get_realized(widget)) {
+ gdk_window_move_resize(gtk_widget_get_window(widget), allocation->x,
+ allocation->y, allocation->width,
+ allocation->height);
+ }
+}
+
+void moz_container_remove(GtkContainer* container, GtkWidget* child_widget) {
+ MozContainerChild* child;
+ MozContainer* moz_container;
+ GdkWindow* parent_window;
+
+ g_return_if_fail(IS_MOZ_CONTAINER(container));
+ g_return_if_fail(GTK_IS_WIDGET(child_widget));
+
+ moz_container = MOZ_CONTAINER(container);
+
+ child = moz_container_get_child(moz_container, child_widget);
+ g_return_if_fail(child);
+
+ /* gtk_widget_unparent will remove the parent window (as well as the
+ * parent widget), but, in Mozilla's window hierarchy, the parent window
+ * may need to be kept because it may be part of a GdkWindow sub-hierarchy
+ * that is being moved to another MozContainer.
+ *
+ * (In a conventional GtkWidget hierarchy, GdkWindows being reparented
+ * would have their own GtkWidget and that widget would be the one being
+ * reparented. In Mozilla's hierarchy, the parent_window needs to be
+ * retained so that the GdkWindow sub-hierarchy is maintained.)
+ */
+ parent_window = gtk_widget_get_parent_window(child_widget);
+ if (parent_window) g_object_ref(parent_window);
+
+ gtk_widget_unparent(child_widget);
+
+ if (parent_window) {
+ /* The child_widget will always still exist because g_signal_emit,
+ * which invokes this function, holds a reference.
+ *
+ * If parent_window is the container's root window then it will not be
+ * the parent_window if the child_widget is placed in another
+ * container.
+ */
+ if (parent_window != gtk_widget_get_window(GTK_WIDGET(container)))
+ gtk_widget_set_parent_window(child_widget, parent_window);
+
+ g_object_unref(parent_window);
+ }
+
+ moz_container->children = g_list_remove(moz_container->children, child);
+ g_free(child);
+}
+
+void moz_container_forall(GtkContainer* container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data) {
+ MozContainer* moz_container;
+ GList* tmp_list;
+
+ g_return_if_fail(IS_MOZ_CONTAINER(container));
+ g_return_if_fail(callback != NULL);
+
+ moz_container = MOZ_CONTAINER(container);
+
+ tmp_list = moz_container->children;
+ while (tmp_list) {
+ MozContainerChild* child;
+ child = static_cast<MozContainerChild*>(tmp_list->data);
+ tmp_list = tmp_list->next;
+ (*callback)(child->widget, callback_data);
+ }
+}
+
+static void moz_container_allocate_child(MozContainer* container,
+ MozContainerChild* child) {
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation(child->widget, &allocation);
+ allocation.x = child->x;
+ allocation.y = child->y;
+
+ gtk_widget_size_allocate(child->widget, &allocation);
+}
+
+MozContainerChild* moz_container_get_child(MozContainer* container,
+ GtkWidget* child_widget) {
+ GList* tmp_list;
+
+ tmp_list = container->children;
+ while (tmp_list) {
+ MozContainerChild* child;
+
+ child = static_cast<MozContainerChild*>(tmp_list->data);
+ tmp_list = tmp_list->next;
+
+ if (child->widget == child_widget) return child;
+ }
+
+ return NULL;
+}
+
+static void moz_container_add(GtkContainer* container, GtkWidget* widget) {
+ moz_container_put(MOZ_CONTAINER(container), widget, 0, 0);
+}
+
+void moz_container_force_default_visual(MozContainer* container) {
+ container->force_default_visual = true;
+}
diff --git a/widget/gtk/MozContainer.h b/widget/gtk/MozContainer.h
new file mode 100644
index 0000000000..9faf909179
--- /dev/null
+++ b/widget/gtk/MozContainer.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __MOZ_CONTAINER_H__
+#define __MOZ_CONTAINER_H__
+
+#ifdef MOZ_WAYLAND
+# include "MozContainerWayland.h"
+#endif
+
+#include <gtk/gtk.h>
+#include <functional>
+
+/*
+ * MozContainer
+ *
+ * This class serves three purposes in the nsIWidget implementation.
+ *
+ * - It provides objects to receive signals from GTK for events on native
+ * windows.
+ *
+ * - It provides GdkWindow to draw content on Wayland or when Gtk+ renders
+ * client side decorations to mShell.
+ *
+ * - It provides a container parent for GtkWidgets. The only GtkWidgets
+ * that need this in Mozilla are the GtkSockets for windowed plugins (Xt
+ * and XEmbed).
+ *
+ * Note that the window hierarchy in Mozilla differs from conventional
+ * GtkWidget hierarchies.
+ *
+ * Mozilla's hierarchy exists through the GdkWindow hierarchy, and all child
+ * GdkWindows (within a child nsIWidget hierarchy) belong to one MozContainer
+ * GtkWidget. If the MozContainer is unrealized or its GdkWindows are
+ * destroyed for some other reason, then the hierarchy no longer exists. (In
+ * conventional GTK clients, the hierarchy is recorded by the GtkWidgets, and
+ * so can be re-established after destruction of the GdkWindows.)
+ *
+ * One consequence of this is that the MozContainer does not know which of its
+ * GdkWindows should parent child GtkWidgets. (Conventional GtkContainers
+ * determine which GdkWindow to assign child GtkWidgets.)
+ *
+ * Therefore, when adding a child GtkWidget to a MozContainer,
+ * gtk_widget_set_parent_window should be called on the child GtkWidget before
+ * it is realized.
+ */
+
+#define MOZ_CONTAINER_TYPE (moz_container_get_type())
+#define MOZ_CONTAINER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), MOZ_CONTAINER_TYPE, MozContainer))
+#define MOZ_CONTAINER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), MOZ_CONTAINER_TYPE, MozContainerClass))
+#define IS_MOZ_CONTAINER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), MOZ_CONTAINER_TYPE))
+#define IS_MOZ_CONTAINER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), MOZ_CONTAINER_TYPE))
+#define MOZ_CONTAINER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), MOZ_CONTAINER_TYPE, MozContainerClass))
+
+// We need to shape only a few pixels of the titlebar as we care about
+// the corners only
+#define TITLEBAR_SHAPE_MASK_HEIGHT 10
+
+typedef struct _MozContainer MozContainer;
+typedef struct _MozContainerClass MozContainerClass;
+
+struct _MozContainer {
+ GtkContainer container;
+ GList* children;
+ gboolean force_default_visual;
+#ifdef MOZ_WAYLAND
+ MozContainerWayland wl_container;
+#endif
+};
+
+struct _MozContainerClass {
+ GtkContainerClass parent_class;
+};
+
+GType moz_container_get_type(void);
+GtkWidget* moz_container_new(void);
+void moz_container_put(MozContainer* container, GtkWidget* child_widget, gint x,
+ gint y);
+void moz_container_force_default_visual(MozContainer* container);
+
+#endif /* __MOZ_CONTAINER_H__ */
diff --git a/widget/gtk/MozContainerWayland.cpp b/widget/gtk/MozContainerWayland.cpp
new file mode 100644
index 0000000000..88fbef7277
--- /dev/null
+++ b/widget/gtk/MozContainerWayland.cpp
@@ -0,0 +1,514 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "MozContainer.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include "nsWaylandDisplay.h"
+#include "gfxPlatformGtk.h"
+#include <wayland-egl.h>
+#include <stdio.h>
+#include <dlfcn.h>
+
+#undef LOG
+#ifdef MOZ_LOGGING
+
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+# include "nsWindow.h"
+extern mozilla::LazyLogModule gWidgetWaylandLog;
+# define LOGWAYLAND(args) \
+ MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOGWAYLAND(args)
+#endif /* MOZ_LOGGING */
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+/* init methods */
+static void moz_container_wayland_destroy(GtkWidget* widget);
+
+/* widget class methods */
+static void moz_container_wayland_map(GtkWidget* widget);
+static gboolean moz_container_wayland_map_event(GtkWidget* widget,
+ GdkEventAny* event);
+static void moz_container_wayland_unmap(GtkWidget* widget);
+static void moz_container_wayland_size_allocate(GtkWidget* widget,
+ GtkAllocation* allocation);
+
+// Imlemented in MozContainer.cpp
+void moz_container_realize(GtkWidget* widget);
+
+static void moz_container_wayland_move_locked(MozContainer* container, int dx,
+ int dy) {
+ LOGWAYLAND(("moz_container_wayland_move_locked [%p] %d,%d\n",
+ (void*)container, dx, dy));
+
+ MozContainerWayland* wl_container = &container->wl_container;
+
+ wl_container->subsurface_dx = dx;
+ wl_container->subsurface_dy = dy;
+ wl_container->surface_position_needs_update = true;
+
+ // Wayland subsurface is not created yet.
+ if (!wl_container->subsurface) {
+ return;
+ }
+
+ // wl_subsurface_set_position is actually property of parent surface
+ // which is effective when parent surface is commited.
+ wl_subsurface_set_position(wl_container->subsurface,
+ wl_container->subsurface_dx,
+ wl_container->subsurface_dy);
+ wl_container->surface_position_needs_update = false;
+
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container));
+ if (window) {
+ GdkRectangle rect = (GdkRectangle){0, 0, gdk_window_get_width(window),
+ gdk_window_get_height(window)};
+ gdk_window_invalidate_rect(window, &rect, false);
+ }
+}
+
+static void moz_container_wayland_move(MozContainer* container, int dx,
+ int dy) {
+ MutexAutoLock lock(*container->wl_container.container_lock);
+ LOGWAYLAND(
+ ("moz_container_wayland_move [%p] %d,%d\n", (void*)container, dx, dy));
+ moz_container_wayland_move_locked(container, dx, dy);
+}
+
+// This is called from layout/compositor code only with
+// size equal to GL rendering context. Otherwise there are
+// rendering artifacts as wl_egl_window size does not match
+// GL rendering pipeline setup.
+void moz_container_wayland_egl_window_set_size(MozContainer* container,
+ int width, int height) {
+ MozContainerWayland* wl_container = &container->wl_container;
+ MutexAutoLock lock(*wl_container->container_lock);
+ if (wl_container->eglwindow) {
+ wl_egl_window_resize(wl_container->eglwindow, width, height, 0, 0);
+ }
+}
+
+void moz_container_wayland_class_init(MozContainerClass* klass) {
+ /*GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass); */
+ GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
+
+ widget_class->map = moz_container_wayland_map;
+ widget_class->map_event = moz_container_wayland_map_event;
+ widget_class->destroy = moz_container_wayland_destroy;
+ widget_class->unmap = moz_container_wayland_unmap;
+ widget_class->realize = moz_container_realize;
+ widget_class->size_allocate = moz_container_wayland_size_allocate;
+}
+
+void moz_container_wayland_init(MozContainerWayland* container) {
+ container->surface = nullptr;
+ container->subsurface = nullptr;
+ container->eglwindow = nullptr;
+ container->frame_callback_handler = nullptr;
+ container->frame_callback_handler_surface_id = -1;
+ container->ready_to_draw = false;
+ container->opaque_region_needs_update = false;
+ container->opaque_region_subtract_corners = false;
+ container->surface_needs_clear = true;
+ container->subsurface_dx = 0;
+ container->subsurface_dy = 0;
+ container->surface_position_needs_update = 0;
+ container->initial_draw_cbs.clear();
+ container->container_lock = new mozilla::Mutex("MozContainer lock");
+}
+
+static void moz_container_wayland_destroy(GtkWidget* widget) {
+ MozContainerWayland* container = &MOZ_CONTAINER(widget)->wl_container;
+ delete container->container_lock;
+ container->container_lock = nullptr;
+}
+
+void moz_container_wayland_add_initial_draw_callback(
+ MozContainer* container, const std::function<void(void)>& initial_draw_cb) {
+ container->wl_container.initial_draw_cbs.push_back(initial_draw_cb);
+}
+
+wl_surface* moz_gtk_widget_get_wl_surface(GtkWidget* aWidget) {
+ GdkWindow* window = gtk_widget_get_window(aWidget);
+ wl_surface* surface = gdk_wayland_window_get_wl_surface(window);
+
+ LOGWAYLAND(("moz_gtk_widget_get_wl_surface [%p] wl_surface %p ID %d\n",
+ (void*)aWidget, (void*)surface,
+ surface ? wl_proxy_get_id((struct wl_proxy*)surface) : -1));
+
+ return surface;
+}
+
+static void moz_container_wayland_frame_callback_handler(
+ void* data, struct wl_callback* callback, uint32_t time) {
+ MozContainerWayland* wl_container = &MOZ_CONTAINER(data)->wl_container;
+
+ LOGWAYLAND(
+ ("%s [%p] frame_callback_handler %p ready_to_draw %d (set to true)"
+ " initial_draw callback %zd\n",
+ __FUNCTION__, (void*)MOZ_CONTAINER(data),
+ (void*)wl_container->frame_callback_handler, wl_container->ready_to_draw,
+ wl_container->initial_draw_cbs.size()));
+
+ g_clear_pointer(&wl_container->frame_callback_handler, wl_callback_destroy);
+ wl_container->frame_callback_handler_surface_id = -1;
+
+ if (!wl_container->ready_to_draw) {
+ wl_container->ready_to_draw = true;
+ for (auto const& cb : wl_container->initial_draw_cbs) {
+ cb();
+ }
+ wl_container->initial_draw_cbs.clear();
+ }
+}
+
+static const struct wl_callback_listener moz_container_frame_listener = {
+ moz_container_wayland_frame_callback_handler};
+
+static void moz_container_wayland_request_parent_frame_callback(
+ MozContainer* container) {
+ MozContainerWayland* wl_container = &container->wl_container;
+
+ wl_surface* gtk_container_surface =
+ moz_gtk_widget_get_wl_surface(GTK_WIDGET(container));
+ int gtk_container_surface_id =
+ gtk_container_surface
+ ? wl_proxy_get_id((struct wl_proxy*)gtk_container_surface)
+ : -1;
+
+ LOGWAYLAND(
+ ("%s [%p] frame_callback_handler %p "
+ "frame_callback_handler_surface_id %d\n",
+ __FUNCTION__, (void*)container, wl_container->frame_callback_handler,
+ wl_container->frame_callback_handler_surface_id));
+
+ if (wl_container->frame_callback_handler &&
+ wl_container->frame_callback_handler_surface_id ==
+ gtk_container_surface_id) {
+ return;
+ }
+
+ // If there's pending frame callback, delete it.
+ if (wl_container->frame_callback_handler) {
+ g_clear_pointer(&wl_container->frame_callback_handler, wl_callback_destroy);
+ }
+
+ if (gtk_container_surface) {
+ wl_container->frame_callback_handler_surface_id = gtk_container_surface_id;
+ wl_container->frame_callback_handler =
+ wl_surface_frame(gtk_container_surface);
+ wl_callback_add_listener(wl_container->frame_callback_handler,
+ &moz_container_frame_listener, container);
+ } else {
+ wl_container->frame_callback_handler_surface_id = -1;
+ }
+}
+
+static gboolean moz_container_wayland_map_event(GtkWidget* widget,
+ GdkEventAny* event) {
+ MozContainerWayland* wl_container = &MOZ_CONTAINER(widget)->wl_container;
+
+ LOGWAYLAND(("%s begin [%p] ready_to_draw %d\n", __FUNCTION__,
+ (void*)MOZ_CONTAINER(widget), wl_container->ready_to_draw));
+
+ if (wl_container->ready_to_draw) {
+ return FALSE;
+ }
+
+ moz_container_wayland_request_parent_frame_callback(MOZ_CONTAINER(widget));
+ return FALSE;
+}
+
+static void moz_container_wayland_unmap_internal(MozContainer* container) {
+ MozContainerWayland* wl_container = &container->wl_container;
+ MutexAutoLock lock(*wl_container->container_lock);
+
+ g_clear_pointer(&wl_container->eglwindow, wl_egl_window_destroy);
+ g_clear_pointer(&wl_container->subsurface, wl_subsurface_destroy);
+ g_clear_pointer(&wl_container->surface, wl_surface_destroy);
+ g_clear_pointer(&wl_container->frame_callback_handler, wl_callback_destroy);
+ wl_container->frame_callback_handler_surface_id = -1;
+
+ wl_container->surface_needs_clear = true;
+ wl_container->ready_to_draw = false;
+
+ LOGWAYLAND(("%s [%p]\n", __FUNCTION__, (void*)container));
+}
+
+void moz_container_wayland_map(GtkWidget* widget) {
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+ gtk_widget_set_mapped(widget, TRUE);
+
+ if (gtk_widget_get_has_window(widget)) {
+ gdk_window_show(gtk_widget_get_window(widget));
+ moz_container_wayland_map_event(widget, nullptr);
+ }
+}
+
+void moz_container_wayland_unmap(GtkWidget* widget) {
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+
+ gtk_widget_set_mapped(widget, FALSE);
+
+ if (gtk_widget_get_has_window(widget)) {
+ gdk_window_hide(gtk_widget_get_window(widget));
+ moz_container_wayland_unmap_internal(MOZ_CONTAINER(widget));
+ }
+}
+
+void moz_container_wayland_size_allocate(GtkWidget* widget,
+ GtkAllocation* allocation) {
+ MozContainer* container;
+ GtkAllocation tmp_allocation;
+
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+
+ LOGWAYLAND(("moz_container_wayland_size_allocate [%p] %d,%d -> %d x %d\n",
+ (void*)widget, allocation->x, allocation->y, allocation->width,
+ allocation->height));
+
+ /* short circuit if you can */
+ container = MOZ_CONTAINER(widget);
+ gtk_widget_get_allocation(widget, &tmp_allocation);
+ if (!container->children && tmp_allocation.x == allocation->x &&
+ tmp_allocation.y == allocation->y &&
+ tmp_allocation.width == allocation->width &&
+ tmp_allocation.height == allocation->height) {
+ return;
+ }
+
+ gtk_widget_set_allocation(widget, allocation);
+
+ if (gtk_widget_get_has_window(widget) && gtk_widget_get_realized(widget)) {
+ gdk_window_move_resize(gtk_widget_get_window(widget), allocation->x,
+ allocation->y, allocation->width,
+ allocation->height);
+ // We need to position our subsurface according to GdkWindow
+ // when offset changes (GdkWindow is maximized for instance).
+ // see gtk-clutter-embed.c for reference.
+ if (gfxPlatformGtk::GetPlatform()->IsWaylandDisplay()) {
+ moz_container_wayland_move(MOZ_CONTAINER(widget), allocation->x,
+ allocation->y);
+ }
+ }
+}
+
+static wl_region* moz_container_wayland_create_opaque_region(
+ int aX, int aY, int aWidth, int aHeight, bool aSubtractCorners) {
+ struct wl_compositor* compositor = WaylandDisplayGet()->GetCompositor();
+ wl_region* region = wl_compositor_create_region(compositor);
+ wl_region_add(region, aX, aY, aWidth, aHeight);
+ if (aSubtractCorners) {
+ wl_region_subtract(region, aX, aY, TITLEBAR_SHAPE_MASK_HEIGHT,
+ TITLEBAR_SHAPE_MASK_HEIGHT);
+ wl_region_subtract(region, aX + aWidth - TITLEBAR_SHAPE_MASK_HEIGHT, aY,
+ TITLEBAR_SHAPE_MASK_HEIGHT, TITLEBAR_SHAPE_MASK_HEIGHT);
+ }
+ return region;
+}
+
+static void moz_container_wayland_set_opaque_region_locked(
+ MozContainer* container) {
+ MozContainerWayland* wl_container = &container->wl_container;
+
+ if (!wl_container->opaque_region_needs_update || !wl_container->surface) {
+ return;
+ }
+
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(GTK_WIDGET(container), &allocation);
+
+ wl_region* region = moz_container_wayland_create_opaque_region(
+ 0, 0, allocation.width, allocation.height,
+ wl_container->opaque_region_subtract_corners);
+ wl_surface_set_opaque_region(wl_container->surface, region);
+ wl_region_destroy(region);
+
+ wl_container->opaque_region_needs_update = false;
+}
+
+static void moz_container_wayland_set_opaque_region(MozContainer* container) {
+ MutexAutoLock lock(*container->wl_container.container_lock);
+ moz_container_wayland_set_opaque_region_locked(container);
+}
+
+static void moz_container_wayland_set_scale_factor_locked(
+ MozContainer* container) {
+ if (!container->wl_container.surface) {
+ return;
+ }
+ gpointer user_data = g_object_get_data(G_OBJECT(container), "nsWindow");
+ nsWindow* wnd = static_cast<nsWindow*>(user_data);
+
+ int scale = 1;
+ if (wnd) {
+ scale = wnd->GdkScaleFactor();
+ }
+ wl_surface_set_buffer_scale(container->wl_container.surface, scale);
+}
+
+void moz_container_wayland_set_scale_factor(MozContainer* container) {
+ MutexAutoLock lock(*container->wl_container.container_lock);
+ moz_container_wayland_set_scale_factor_locked(container);
+}
+
+static struct wl_surface* moz_container_wayland_get_surface_locked(
+ MozContainer* container, nsWaylandDisplay* aWaylandDisplay) {
+ MozContainerWayland* wl_container = &container->wl_container;
+
+ LOGWAYLAND(("%s [%p] surface %p ready_to_draw %d\n", __FUNCTION__,
+ (void*)container, (void*)wl_container->surface,
+ wl_container->ready_to_draw));
+
+ if (!wl_container->surface) {
+ if (!wl_container->ready_to_draw) {
+ moz_container_wayland_request_parent_frame_callback(container);
+ return nullptr;
+ }
+ wl_surface* parent_surface =
+ moz_gtk_widget_get_wl_surface(GTK_WIDGET(container));
+ if (!parent_surface) {
+ return nullptr;
+ }
+
+ // Available as of GTK 3.8+
+ struct wl_compositor* compositor = aWaylandDisplay->GetCompositor();
+ wl_container->surface = wl_compositor_create_surface(compositor);
+ if (!wl_container->surface) {
+ return nullptr;
+ }
+
+ wl_container->subsurface =
+ wl_subcompositor_get_subsurface(aWaylandDisplay->GetSubcompositor(),
+ wl_container->surface, parent_surface);
+ if (!wl_container->subsurface) {
+ g_clear_pointer(&wl_container->surface, wl_surface_destroy);
+ return nullptr;
+ }
+
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container));
+ gint x, y;
+ gdk_window_get_position(window, &x, &y);
+ moz_container_wayland_move_locked(container, x, y);
+ wl_subsurface_set_desync(wl_container->subsurface);
+
+ // Route input to parent wl_surface owned by Gtk+ so we get input
+ // events from Gtk+.
+ wl_region* region = wl_compositor_create_region(compositor);
+ wl_surface_set_input_region(wl_container->surface, region);
+ wl_region_destroy(region);
+
+ wl_surface_commit(wl_container->surface);
+ wl_display_flush(aWaylandDisplay->GetDisplay());
+
+ LOGWAYLAND(("%s [%p] created surface %p\n", __FUNCTION__, (void*)container,
+ (void*)wl_container->surface));
+ }
+
+ if (wl_container->surface_position_needs_update) {
+ moz_container_wayland_move_locked(container, wl_container->subsurface_dx,
+ wl_container->subsurface_dy);
+ }
+
+ moz_container_wayland_set_opaque_region_locked(container);
+ moz_container_wayland_set_scale_factor_locked(container);
+
+ return wl_container->surface;
+}
+
+struct wl_surface* moz_container_wayland_surface_lock(MozContainer* container) {
+ GdkDisplay* display = gtk_widget_get_display(GTK_WIDGET(container));
+ RefPtr<nsWaylandDisplay> waylandDisplay = WaylandDisplayGet(display);
+
+ LOGWAYLAND(("%s [%p] surface %p\n", __FUNCTION__, (void*)container,
+ (void*)container->wl_container.surface));
+
+ container->wl_container.container_lock->Lock();
+ struct wl_surface* surface =
+ moz_container_wayland_get_surface_locked(container, waylandDisplay);
+ if (surface == nullptr) {
+ container->wl_container.container_lock->Unlock();
+ }
+ return surface;
+}
+
+void moz_container_wayland_surface_unlock(MozContainer* container,
+ struct wl_surface** surface) {
+ LOGWAYLAND(("%s [%p] surface %p\n", __FUNCTION__, (void*)container,
+ (void*)container->wl_container.surface));
+ if (*surface) {
+ container->wl_container.container_lock->Unlock();
+ *surface = nullptr;
+ }
+}
+
+struct wl_egl_window* moz_container_wayland_get_egl_window(
+ MozContainer* container, int scale) {
+ GdkDisplay* display = gtk_widget_get_display(GTK_WIDGET(container));
+ RefPtr<nsWaylandDisplay> waylandDisplay = WaylandDisplayGet(display);
+ MozContainerWayland* wl_container = &container->wl_container;
+
+ LOGWAYLAND(("%s [%p] eglwindow %p\n", __FUNCTION__, (void*)container,
+ (void*)wl_container->eglwindow));
+
+ MutexAutoLock lock(*wl_container->container_lock);
+
+ // Always call moz_container_get_wl_surface() to ensure underlying
+ // container->surface has correct scale and position.
+ wl_surface* surface =
+ moz_container_wayland_get_surface_locked(container, waylandDisplay);
+ if (!surface) {
+ return nullptr;
+ }
+ if (!wl_container->eglwindow) {
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container));
+ wl_container->eglwindow =
+ wl_egl_window_create(surface, gdk_window_get_width(window) * scale,
+ gdk_window_get_height(window) * scale);
+
+ LOGWAYLAND(("%s [%p] created eglwindow %p\n", __FUNCTION__,
+ (void*)container, (void*)wl_container->eglwindow));
+ }
+
+ return wl_container->eglwindow;
+}
+
+gboolean moz_container_wayland_has_egl_window(MozContainer* container) {
+ return container->wl_container.eglwindow ? true : false;
+}
+
+gboolean moz_container_wayland_surface_needs_clear(MozContainer* container) {
+ int ret = container->wl_container.surface_needs_clear;
+ container->wl_container.surface_needs_clear = false;
+ return ret;
+}
+
+void moz_container_wayland_update_opaque_region(MozContainer* container,
+ bool aSubtractCorners) {
+ MozContainerWayland* wl_container = &container->wl_container;
+ wl_container->opaque_region_needs_update = true;
+ wl_container->opaque_region_subtract_corners = aSubtractCorners;
+
+ // When GL compositor / WebRender is used,
+ // moz_container_wayland_get_egl_window() is called only once when window
+ // is created or resized so update opaque region now.
+ if (moz_container_wayland_has_egl_window(container)) {
+ moz_container_wayland_set_opaque_region(container);
+ }
+}
+
+gboolean moz_container_wayland_can_draw(MozContainer* container) {
+ return container ? container->wl_container.ready_to_draw : false;
+}
diff --git a/widget/gtk/MozContainerWayland.h b/widget/gtk/MozContainerWayland.h
new file mode 100644
index 0000000000..6decd5646c
--- /dev/null
+++ b/widget/gtk/MozContainerWayland.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __MOZ_CONTAINER_WAYLAND_H__
+#define __MOZ_CONTAINER_WAYLAND_H__
+
+#include <gtk/gtk.h>
+#include <functional>
+#include <vector>
+#include "mozilla/Mutex.h"
+
+/*
+ * MozContainer
+ *
+ * This class serves three purposes in the nsIWidget implementation.
+ *
+ * - It provides objects to receive signals from GTK for events on native
+ * windows.
+ *
+ * - It provides GdkWindow to draw content on Wayland or when Gtk+ renders
+ * client side decorations to mShell.
+ */
+
+/* Workaround for bug at wayland-util.h,
+ * present in wayland-devel < 1.12
+ */
+struct wl_surface;
+struct wl_subsurface;
+
+struct MozContainerWayland {
+ struct wl_surface* surface;
+ struct wl_subsurface* subsurface;
+ int subsurface_dx, subsurface_dy;
+ struct wl_egl_window* eglwindow;
+ struct wl_callback* frame_callback_handler;
+ int frame_callback_handler_surface_id;
+ gboolean opaque_region_needs_update;
+ gboolean opaque_region_subtract_corners;
+ gboolean opaque_region_fullscreen;
+ gboolean surface_position_needs_update;
+ gboolean surface_needs_clear;
+ gboolean ready_to_draw;
+ std::vector<std::function<void(void)>> initial_draw_cbs;
+ // mozcontainer is used from Compositor and Rendering threads
+ // so we need to control access to mozcontainer where wayland internals
+ // are used directly.
+ mozilla::Mutex* container_lock;
+};
+
+struct _MozContainer;
+struct _MozContainerClass;
+typedef struct _MozContainer MozContainer;
+typedef struct _MozContainerClass MozContainerClass;
+
+void moz_container_wayland_class_init(MozContainerClass* klass);
+void moz_container_wayland_init(MozContainerWayland* container);
+
+struct wl_surface* moz_container_wayland_surface_lock(MozContainer* container);
+void moz_container_wayland_surface_unlock(MozContainer* container,
+ struct wl_surface** surface);
+
+struct wl_egl_window* moz_container_wayland_get_egl_window(
+ MozContainer* container, int scale);
+
+gboolean moz_container_wayland_has_egl_window(MozContainer* container);
+gboolean moz_container_wayland_surface_needs_clear(MozContainer* container);
+void moz_container_wayland_move_resize(MozContainer* container, int dx, int dy,
+ int width, int height);
+void moz_container_wayland_egl_window_set_size(MozContainer* container,
+ int width, int height);
+void moz_container_wayland_set_scale_factor(MozContainer* container);
+void moz_container_wayland_add_initial_draw_callback(
+ MozContainer* container, const std::function<void(void)>& initial_draw_cb);
+wl_surface* moz_gtk_widget_get_wl_surface(GtkWidget* aWidget);
+void moz_container_wayland_update_opaque_region(MozContainer* container,
+ bool aSubtractCorners);
+gboolean moz_container_wayland_can_draw(MozContainer* container);
+
+#endif /* __MOZ_CONTAINER_WAYLAND_H__ */
diff --git a/widget/gtk/NativeKeyBindings.cpp b/widget/gtk/NativeKeyBindings.cpp
new file mode 100644
index 0000000000..65843d7768
--- /dev/null
+++ b/widget/gtk/NativeKeyBindings.cpp
@@ -0,0 +1,346 @@
+/* -*- 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 "mozilla/MathAlgorithms.h"
+#include "mozilla/TextEvents.h"
+
+#include "NativeKeyBindings.h"
+#include "nsString.h"
+#include "nsMemory.h"
+#include "nsGtkKeyUtils.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdk.h>
+
+namespace mozilla {
+namespace widget {
+
+static nsTArray<CommandInt>* gCurrentCommands = nullptr;
+static bool gHandled = false;
+
+inline void AddCommand(Command aCommand) {
+ MOZ_ASSERT(gCurrentCommands);
+ gCurrentCommands->AppendElement(static_cast<CommandInt>(aCommand));
+}
+
+// Common GtkEntry and GtkTextView signals
+static void copy_clipboard_cb(GtkWidget* w, gpointer user_data) {
+ AddCommand(Command::Copy);
+ g_signal_stop_emission_by_name(w, "copy_clipboard");
+ gHandled = true;
+}
+
+static void cut_clipboard_cb(GtkWidget* w, gpointer user_data) {
+ AddCommand(Command::Cut);
+ g_signal_stop_emission_by_name(w, "cut_clipboard");
+ gHandled = true;
+}
+
+// GTK distinguishes between display lines (wrapped, as they appear on the
+// screen) and paragraphs, which are runs of text terminated by a newline.
+// We don't have this distinction, so we always use editor's notion of
+// lines, which are newline-terminated.
+
+static const Command sDeleteCommands[][2] = {
+ // backward, forward
+ // CHARS
+ {Command::DeleteCharBackward, Command::DeleteCharForward},
+ // WORD_ENDS
+ {Command::DeleteWordBackward, Command::DeleteWordForward},
+ // WORDS
+ {Command::DeleteWordBackward, Command::DeleteWordForward},
+ // LINES
+ {Command::DeleteToBeginningOfLine, Command::DeleteToEndOfLine},
+ // LINE_ENDS
+ {Command::DeleteToBeginningOfLine, Command::DeleteToEndOfLine},
+ // PARAGRAPH_ENDS
+ {Command::DeleteToBeginningOfLine, Command::DeleteToEndOfLine},
+ // PARAGRAPHS
+ {Command::DeleteToBeginningOfLine, Command::DeleteToEndOfLine},
+ // This deletes from the end of the previous word to the beginning of the
+ // next word, but only if the caret is not in a word.
+ // XXX need to implement in editor
+ {Command::DoNothing, Command::DoNothing} // WHITESPACE
+};
+
+static void delete_from_cursor_cb(GtkWidget* w, GtkDeleteType del_type,
+ gint count, gpointer user_data) {
+ g_signal_stop_emission_by_name(w, "delete_from_cursor");
+ if (count == 0) {
+ // Nothing to do.
+ return;
+ }
+
+ bool forward = count > 0;
+
+ // Ignore GTK's Ctrl-K keybinding introduced in GTK 3.14 and removed in
+ // 3.18 if the user has custom bindings set. See bug 1176929.
+ if (del_type == GTK_DELETE_PARAGRAPH_ENDS && forward && GTK_IS_ENTRY(w) &&
+ !gtk_check_version(3, 14, 1) && gtk_check_version(3, 17, 9)) {
+ GtkStyleContext* context = gtk_widget_get_style_context(w);
+ GtkStateFlags flags = gtk_widget_get_state_flags(w);
+
+ GPtrArray* array;
+ gtk_style_context_get(context, flags, "gtk-key-bindings", &array, nullptr);
+ if (!array) return;
+ g_ptr_array_unref(array);
+ }
+
+ gHandled = true;
+ if (uint32_t(del_type) >= ArrayLength(sDeleteCommands)) {
+ // unsupported deletion type
+ return;
+ }
+
+ if (del_type == GTK_DELETE_WORDS) {
+ // This works like word_ends, except we first move the caret to the
+ // beginning/end of the current word.
+ if (forward) {
+ AddCommand(Command::WordNext);
+ AddCommand(Command::WordPrevious);
+ } else {
+ AddCommand(Command::WordPrevious);
+ AddCommand(Command::WordNext);
+ }
+ } else if (del_type == GTK_DELETE_DISPLAY_LINES ||
+ del_type == GTK_DELETE_PARAGRAPHS) {
+ // This works like display_line_ends, except we first move the caret to the
+ // beginning/end of the current line.
+ if (forward) {
+ AddCommand(Command::BeginLine);
+ } else {
+ AddCommand(Command::EndLine);
+ }
+ }
+
+ Command command = sDeleteCommands[del_type][forward];
+ if (command == Command::DoNothing) {
+ return;
+ }
+
+ unsigned int absCount = Abs(count);
+ for (unsigned int i = 0; i < absCount; ++i) {
+ AddCommand(command);
+ }
+}
+
+static const Command sMoveCommands[][2][2] = {
+ // non-extend { backward, forward }, extend { backward, forward }
+ // GTK differentiates between logical position, which is prev/next,
+ // and visual position, which is always left/right.
+ // We should fix this to work the same way for RTL text input.
+ {// LOGICAL_POSITIONS
+ {Command::CharPrevious, Command::CharNext},
+ {Command::SelectCharPrevious, Command::SelectCharNext}},
+ {// VISUAL_POSITIONS
+ {Command::CharPrevious, Command::CharNext},
+ {Command::SelectCharPrevious, Command::SelectCharNext}},
+ {// WORDS
+ {Command::WordPrevious, Command::WordNext},
+ {Command::SelectWordPrevious, Command::SelectWordNext}},
+ {// DISPLAY_LINES
+ {Command::LinePrevious, Command::LineNext},
+ {Command::SelectLinePrevious, Command::SelectLineNext}},
+ {// DISPLAY_LINE_ENDS
+ {Command::BeginLine, Command::EndLine},
+ {Command::SelectBeginLine, Command::SelectEndLine}},
+ {// PARAGRAPHS
+ {Command::LinePrevious, Command::LineNext},
+ {Command::SelectLinePrevious, Command::SelectLineNext}},
+ {// PARAGRAPH_ENDS
+ {Command::BeginLine, Command::EndLine},
+ {Command::SelectBeginLine, Command::SelectEndLine}},
+ {// PAGES
+ {Command::MovePageUp, Command::MovePageDown},
+ {Command::SelectPageUp, Command::SelectPageDown}},
+ {// BUFFER_ENDS
+ {Command::MoveTop, Command::MoveBottom},
+ {Command::SelectTop, Command::SelectBottom}},
+ {// HORIZONTAL_PAGES (unsupported)
+ {Command::DoNothing, Command::DoNothing},
+ {Command::DoNothing, Command::DoNothing}}};
+
+static void move_cursor_cb(GtkWidget* w, GtkMovementStep step, gint count,
+ gboolean extend_selection, gpointer user_data) {
+ g_signal_stop_emission_by_name(w, "move_cursor");
+ if (count == 0) {
+ // Nothing to do.
+ return;
+ }
+
+ gHandled = true;
+ bool forward = count > 0;
+ if (uint32_t(step) >= ArrayLength(sMoveCommands)) {
+ // unsupported movement type
+ return;
+ }
+
+ Command command = sMoveCommands[step][extend_selection][forward];
+ if (command == Command::DoNothing) {
+ return;
+ }
+
+ unsigned int absCount = Abs(count);
+ for (unsigned int i = 0; i < absCount; ++i) {
+ AddCommand(command);
+ }
+}
+
+static void paste_clipboard_cb(GtkWidget* w, gpointer user_data) {
+ AddCommand(Command::Paste);
+ g_signal_stop_emission_by_name(w, "paste_clipboard");
+ gHandled = true;
+}
+
+// GtkTextView-only signals
+static void select_all_cb(GtkWidget* w, gboolean select, gpointer user_data) {
+ AddCommand(Command::SelectAll);
+ g_signal_stop_emission_by_name(w, "select_all");
+ gHandled = true;
+}
+
+NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr;
+NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr;
+
+// static
+NativeKeyBindings* NativeKeyBindings::GetInstance(NativeKeyBindingsType aType) {
+ switch (aType) {
+ case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+ if (!sInstanceForSingleLineEditor) {
+ sInstanceForSingleLineEditor = new NativeKeyBindings();
+ sInstanceForSingleLineEditor->Init(aType);
+ }
+ return sInstanceForSingleLineEditor;
+
+ default:
+ // fallback to multiline editor case in release build
+ MOZ_FALLTHROUGH_ASSERT("aType is invalid or not yet implemented");
+ case nsIWidget::NativeKeyBindingsForMultiLineEditor:
+ case nsIWidget::NativeKeyBindingsForRichTextEditor:
+ if (!sInstanceForMultiLineEditor) {
+ sInstanceForMultiLineEditor = new NativeKeyBindings();
+ sInstanceForMultiLineEditor->Init(aType);
+ }
+ return sInstanceForMultiLineEditor;
+ }
+}
+
+// static
+void NativeKeyBindings::Shutdown() {
+ delete sInstanceForSingleLineEditor;
+ sInstanceForSingleLineEditor = nullptr;
+ delete sInstanceForMultiLineEditor;
+ sInstanceForMultiLineEditor = nullptr;
+}
+
+void NativeKeyBindings::Init(NativeKeyBindingsType aType) {
+ switch (aType) {
+ case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+ mNativeTarget = gtk_entry_new();
+ break;
+ default:
+ mNativeTarget = gtk_text_view_new();
+ if (gtk_major_version > 2 ||
+ (gtk_major_version == 2 &&
+ (gtk_minor_version > 2 ||
+ (gtk_minor_version == 2 && gtk_micro_version >= 2)))) {
+ // select_all only exists in gtk >= 2.2.2. Prior to that,
+ // ctrl+a is bound to (move to beginning, select to end).
+ g_signal_connect(mNativeTarget, "select_all", G_CALLBACK(select_all_cb),
+ this);
+ }
+ break;
+ }
+
+ g_object_ref_sink(mNativeTarget);
+
+ g_signal_connect(mNativeTarget, "copy_clipboard",
+ G_CALLBACK(copy_clipboard_cb), this);
+ g_signal_connect(mNativeTarget, "cut_clipboard", G_CALLBACK(cut_clipboard_cb),
+ this);
+ g_signal_connect(mNativeTarget, "delete_from_cursor",
+ G_CALLBACK(delete_from_cursor_cb), this);
+ g_signal_connect(mNativeTarget, "move_cursor", G_CALLBACK(move_cursor_cb),
+ this);
+ g_signal_connect(mNativeTarget, "paste_clipboard",
+ G_CALLBACK(paste_clipboard_cb), this);
+}
+
+NativeKeyBindings::~NativeKeyBindings() {
+ gtk_widget_destroy(mNativeTarget);
+ g_object_unref(mNativeTarget);
+}
+
+void NativeKeyBindings::GetEditCommands(const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands) {
+ // If the native key event is set, it must be synthesized for tests.
+ // We just ignore such events because this behavior depends on system
+ // settings.
+ if (!aEvent.mNativeKeyEvent) {
+ // It must be synthesized event or dispatched DOM event from chrome.
+ return;
+ }
+
+ guint keyval;
+
+ if (aEvent.mCharCode) {
+ keyval = gdk_unicode_to_keyval(aEvent.mCharCode);
+ } else {
+ keyval = static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->keyval;
+ }
+
+ if (GetEditCommandsInternal(aEvent, aCommands, keyval)) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < aEvent.mAlternativeCharCodes.Length(); ++i) {
+ uint32_t ch = aEvent.IsShift()
+ ? aEvent.mAlternativeCharCodes[i].mShiftedCharCode
+ : aEvent.mAlternativeCharCodes[i].mUnshiftedCharCode;
+ if (ch && ch != aEvent.mCharCode) {
+ keyval = gdk_unicode_to_keyval(ch);
+ if (GetEditCommandsInternal(aEvent, aCommands, keyval)) {
+ return;
+ }
+ }
+ }
+
+ /*
+ gtk_bindings_activate_event is preferable, but it has unresolved bug:
+ http://bugzilla.gnome.org/show_bug.cgi?id=162726
+ The bug was already marked as FIXED. However, somebody reports that the
+ bug still exists.
+ Also gtk_bindings_activate may work with some non-shortcuts operations
+ (todo: check it). See bug 411005 and bug 406407.
+
+ Code, which should be used after fixing GNOME bug 162726:
+
+ gtk_bindings_activate_event(GTK_OBJECT(mNativeTarget),
+ static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent));
+ */
+}
+
+bool NativeKeyBindings::GetEditCommandsInternal(
+ const WidgetKeyboardEvent& aEvent, nsTArray<CommandInt>& aCommands,
+ guint aKeyval) {
+ guint modifiers = static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->state;
+
+ gCurrentCommands = &aCommands;
+
+ gHandled = false;
+ gtk_bindings_activate(G_OBJECT(mNativeTarget), aKeyval,
+ GdkModifierType(modifiers));
+
+ gCurrentCommands = nullptr;
+
+ MOZ_ASSERT(!gHandled || !aCommands.IsEmpty());
+
+ return gHandled;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/NativeKeyBindings.h b/widget/gtk/NativeKeyBindings.h
new file mode 100644
index 0000000000..ead3f350d5
--- /dev/null
+++ b/widget/gtk/NativeKeyBindings.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 mozilla_widget_NativeKeyBindings_h_
+#define mozilla_widget_NativeKeyBindings_h_
+
+#include <gtk/gtk.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "nsIWidget.h"
+
+namespace mozilla {
+namespace widget {
+
+class NativeKeyBindings final {
+ typedef nsIWidget::NativeKeyBindingsType NativeKeyBindingsType;
+
+ public:
+ static NativeKeyBindings* GetInstance(NativeKeyBindingsType aType);
+ static void Shutdown();
+
+ void Init(NativeKeyBindingsType aType);
+
+ void GetEditCommands(const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands);
+
+ private:
+ ~NativeKeyBindings();
+
+ bool GetEditCommandsInternal(const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands, guint aKeyval);
+
+ GtkWidget* mNativeTarget;
+
+ static NativeKeyBindings* sInstanceForSingleLineEditor;
+ static NativeKeyBindings* sInstanceForMultiLineEditor;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_NativeKeyBindings_h_
diff --git a/widget/gtk/PCompositorWidget.ipdl b/widget/gtk/PCompositorWidget.ipdl
new file mode 100644
index 0000000000..178fe78e4d
--- /dev/null
+++ b/widget/gtk/PCompositorWidget.ipdl
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 protocol PCompositorBridge;
+
+using mozilla::LayoutDeviceIntSize from "Units.h";
+
+namespace mozilla {
+namespace widget {
+
+sync protocol PCompositorWidget
+{
+ manager PCompositorBridge;
+
+parent:
+ async __delete__();
+
+ async NotifyClientSizeChanged(LayoutDeviceIntSize aClientSize);
+
+child:
+
+ async ObserveVsync();
+ async UnobserveVsync();
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/PlatformWidgetTypes.ipdlh b/widget/gtk/PlatformWidgetTypes.ipdlh
new file mode 100644
index 0000000000..2fe3ef148c
--- /dev/null
+++ b/widget/gtk/PlatformWidgetTypes.ipdlh
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 HeadlessWidgetTypes;
+
+using mozilla::LayoutDeviceIntSize from "Units.h";
+
+namespace mozilla {
+namespace widget {
+
+struct GtkCompositorWidgetInitData
+{
+ uintptr_t XWindow;
+ nsCString XDisplayString;
+ bool Shaped;
+ bool IsX11Display;
+
+ LayoutDeviceIntSize InitialClientSize;
+};
+
+union CompositorWidgetInitData
+{
+ GtkCompositorWidgetInitData;
+ HeadlessCompositorWidgetInitData;
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/ScreenHelperGTK.cpp b/widget/gtk/ScreenHelperGTK.cpp
new file mode 100644
index 0000000000..67410b25a1
--- /dev/null
+++ b/widget/gtk/ScreenHelperGTK.cpp
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "ScreenHelperGTK.h"
+
+#ifdef MOZ_X11
+# include <gdk/gdkx.h>
+#endif /* MOZ_X11 */
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkwayland.h>
+#endif /* MOZ_WAYLAND */
+#include <dlfcn.h>
+#include <gtk/gtk.h>
+
+#include "gfxPlatformGtk.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/Logging.h"
+#include "nsGtkUtils.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace widget {
+
+static LazyLogModule sScreenLog("WidgetScreen");
+
+static void monitors_changed(GdkScreen* aScreen, gpointer aClosure) {
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("Received monitors-changed event"));
+ ScreenHelperGTK* self = static_cast<ScreenHelperGTK*>(aClosure);
+ self->RefreshScreens();
+}
+
+static void screen_resolution_changed(GdkScreen* aScreen, GParamSpec* aPspec,
+ ScreenHelperGTK* self) {
+ self->RefreshScreens();
+}
+
+static GdkFilterReturn root_window_event_filter(GdkXEvent* aGdkXEvent,
+ GdkEvent* aGdkEvent,
+ gpointer aClosure) {
+#ifdef MOZ_X11
+ ScreenHelperGTK* self = static_cast<ScreenHelperGTK*>(aClosure);
+ XEvent* xevent = static_cast<XEvent*>(aGdkXEvent);
+
+ switch (xevent->type) {
+ case PropertyNotify: {
+ XPropertyEvent* propertyEvent = &xevent->xproperty;
+ if (propertyEvent->atom == self->NetWorkareaAtom()) {
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("Work area size changed"));
+ self->RefreshScreens();
+ }
+ } break;
+ default:
+ break;
+ }
+#endif
+
+ return GDK_FILTER_CONTINUE;
+}
+
+ScreenHelperGTK::ScreenHelperGTK()
+ : mRootWindow(nullptr)
+#ifdef MOZ_X11
+ ,
+ mNetWorkareaAtom(0)
+#endif
+{
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("ScreenHelperGTK created"));
+ GdkScreen* defaultScreen = gdk_screen_get_default();
+ if (!defaultScreen) {
+ // Sometimes we don't initial X (e.g., xpcshell)
+ MOZ_LOG(sScreenLog, LogLevel::Debug,
+ ("defaultScreen is nullptr, running headless"));
+ return;
+ }
+ mRootWindow = gdk_get_default_root_window();
+ MOZ_ASSERT(mRootWindow);
+
+ g_object_ref(mRootWindow);
+
+ // GDK_PROPERTY_CHANGE_MASK ==> PropertyChangeMask, for PropertyNotify
+ gdk_window_set_events(mRootWindow,
+ GdkEventMask(gdk_window_get_events(mRootWindow) |
+ GDK_PROPERTY_CHANGE_MASK));
+
+ g_signal_connect(defaultScreen, "monitors-changed",
+ G_CALLBACK(monitors_changed), this);
+ // Use _after to ensure this callback is run after gfxPlatformGtk.cpp's
+ // handler.
+ g_signal_connect_after(defaultScreen, "notify::resolution",
+ G_CALLBACK(screen_resolution_changed), this);
+#ifdef MOZ_X11
+ gdk_window_add_filter(mRootWindow, root_window_event_filter, this);
+ if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ mNetWorkareaAtom = XInternAtom(GDK_WINDOW_XDISPLAY(mRootWindow),
+ "_NET_WORKAREA", X11False);
+ }
+#endif
+ RefreshScreens();
+}
+
+ScreenHelperGTK::~ScreenHelperGTK() {
+ if (mRootWindow) {
+ g_signal_handlers_disconnect_by_data(gdk_screen_get_default(), this);
+
+ gdk_window_remove_filter(mRootWindow, root_window_event_filter, this);
+ g_object_unref(mRootWindow);
+ mRootWindow = nullptr;
+ }
+}
+
+gint ScreenHelperGTK::GetGTKMonitorScaleFactor(gint aMonitorNum) {
+ // Since GDK 3.10
+ static auto sGdkScreenGetMonitorScaleFactorPtr =
+ (gint(*)(GdkScreen*, gint))dlsym(RTLD_DEFAULT,
+ "gdk_screen_get_monitor_scale_factor");
+ if (sGdkScreenGetMonitorScaleFactorPtr) {
+ GdkScreen* screen = gdk_screen_get_default();
+ return sGdkScreenGetMonitorScaleFactorPtr(screen, aMonitorNum);
+ }
+ return 1;
+}
+
+static uint32_t GetGTKPixelDepth() {
+ GdkVisual* visual = gdk_screen_get_system_visual(gdk_screen_get_default());
+ return gdk_visual_get_depth(visual);
+}
+
+static already_AddRefed<Screen> MakeScreen(GdkScreen* aScreen,
+ gint aMonitorNum) {
+ GdkRectangle monitor;
+ GdkRectangle workarea;
+ gdk_screen_get_monitor_geometry(aScreen, aMonitorNum, &monitor);
+ gdk_screen_get_monitor_workarea(aScreen, aMonitorNum, &workarea);
+ gint gdkScaleFactor = ScreenHelperGTK::GetGTKMonitorScaleFactor(aMonitorNum);
+
+ // gdk_screen_get_monitor_geometry / workarea returns application pixels
+ // (desktop pixels), so we need to convert it to device pixels with
+ // gdkScaleFactor.
+ LayoutDeviceIntRect rect(
+ monitor.x * gdkScaleFactor, monitor.y * gdkScaleFactor,
+ monitor.width * gdkScaleFactor, monitor.height * gdkScaleFactor);
+ LayoutDeviceIntRect availRect(
+ workarea.x * gdkScaleFactor, workarea.y * gdkScaleFactor,
+ workarea.width * gdkScaleFactor, workarea.height * gdkScaleFactor);
+ uint32_t pixelDepth = GetGTKPixelDepth();
+
+ // Use per-monitor scaling factor in gtk/wayland, or 1.0 otherwise.
+ DesktopToLayoutDeviceScale contentsScale(1.0);
+#ifdef MOZ_WAYLAND
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ if (!GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ contentsScale.scale = gdkScaleFactor;
+ }
+#endif
+
+ CSSToLayoutDeviceScale defaultCssScale(gdkScaleFactor *
+ gfxPlatformGtk::GetFontScaleFactor());
+
+ float dpi = 96.0f;
+ gint heightMM = gdk_screen_get_monitor_height_mm(aScreen, aMonitorNum);
+ if (heightMM > 0) {
+ dpi = rect.height / (heightMM / MM_PER_INCH_FLOAT);
+ }
+
+ MOZ_LOG(sScreenLog, LogLevel::Debug,
+ ("New screen [%d %d %d %d (%d %d %d %d) %d %f %f %f]", rect.x, rect.y,
+ rect.width, rect.height, availRect.x, availRect.y, availRect.width,
+ availRect.height, pixelDepth, contentsScale.scale,
+ defaultCssScale.scale, dpi));
+ RefPtr<Screen> screen = new Screen(rect, availRect, pixelDepth, pixelDepth,
+ contentsScale, defaultCssScale, dpi);
+ return screen.forget();
+}
+
+void ScreenHelperGTK::RefreshScreens() {
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens"));
+ AutoTArray<RefPtr<Screen>, 4> screenList;
+
+ GdkScreen* defaultScreen = gdk_screen_get_default();
+ gint numScreens = gdk_screen_get_n_monitors(defaultScreen);
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("GDK reports %d screens", numScreens));
+
+ for (gint i = 0; i < numScreens; i++) {
+ screenList.AppendElement(MakeScreen(defaultScreen, i));
+ }
+
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ screenManager.Refresh(std::move(screenList));
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/ScreenHelperGTK.h b/widget/gtk/ScreenHelperGTK.h
new file mode 100644
index 0000000000..78b4d7d823
--- /dev/null
+++ b/widget/gtk/ScreenHelperGTK.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_gtk_ScreenHelperGTK_h
+#define mozilla_widget_gtk_ScreenHelperGTK_h
+
+#include "mozilla/widget/ScreenManager.h"
+
+#include "gdk/gdk.h"
+#ifdef MOZ_X11
+# include <X11/Xlib.h>
+# include "X11UndefineNone.h"
+#endif
+
+namespace mozilla {
+namespace widget {
+
+class ScreenHelperGTK final : public ScreenManager::Helper {
+ public:
+ ScreenHelperGTK();
+ ~ScreenHelperGTK() override;
+
+ static gint GetGTKMonitorScaleFactor(gint aMonitorNum = 0);
+
+#ifdef MOZ_X11
+ Atom NetWorkareaAtom() { return mNetWorkareaAtom; }
+#endif
+
+ // For internal use from signal callback functions
+ void RefreshScreens();
+
+ private:
+ GdkWindow* mRootWindow;
+#ifdef MOZ_X11
+ Atom mNetWorkareaAtom;
+#endif
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_gtk_ScreenHelperGTK_h
diff --git a/widget/gtk/TaskbarProgress.cpp b/widget/gtk/TaskbarProgress.cpp
new file mode 100644
index 0000000000..2aad109eab
--- /dev/null
+++ b/widget/gtk/TaskbarProgress.cpp
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+
+#include "TaskbarProgress.h"
+#include "nsWindow.h"
+#include "WidgetUtils.h"
+#include "nsPIDOMWindow.h"
+
+using mozilla::LogLevel;
+static mozilla::LazyLogModule gGtkTaskbarProgressLog("nsIGtkTaskbarProgress");
+
+/******************************************************************************
+ * TaskbarProgress
+ ******************************************************************************/
+
+NS_IMPL_ISUPPORTS(TaskbarProgress, nsIGtkTaskbarProgress, nsITaskbarProgress)
+
+TaskbarProgress::TaskbarProgress() : mPrimaryWindow(nullptr) {
+ MOZ_LOG(gGtkTaskbarProgressLog, LogLevel::Info,
+ ("%p TaskbarProgress()", this));
+}
+
+TaskbarProgress::~TaskbarProgress() {
+ MOZ_LOG(gGtkTaskbarProgressLog, LogLevel::Info,
+ ("%p ~TaskbarProgress()", this));
+}
+
+NS_IMETHODIMP
+TaskbarProgress::SetProgressState(nsTaskbarProgressState aState,
+ uint64_t aCurrentValue, uint64_t aMaxValue) {
+#ifdef MOZ_X11
+ 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);
+ }
+
+ NS_ENSURE_TRUE((aCurrentValue <= aMaxValue), NS_ERROR_ILLEGAL_VALUE);
+
+ // See TaskbarProgress::SetPrimaryWindow: if we're running in headless
+ // mode, mPrimaryWindow will be null.
+ if (!mPrimaryWindow) {
+ return NS_OK;
+ }
+
+ gulong progress;
+
+ if (aMaxValue == 0) {
+ progress = 0;
+ } else {
+ // Rounding down to ensure we don't set to 'full' until the operation
+ // is completely finished.
+ progress = (gulong)(((double)aCurrentValue / aMaxValue) * 100.0);
+ }
+
+ // Check if the resultant value is the same as the previous call, and
+ // ignore this update if it is.
+
+ if (progress == mCurrentProgress) {
+ return NS_OK;
+ }
+
+ mCurrentProgress = progress;
+
+ MOZ_LOG(gGtkTaskbarProgressLog, LogLevel::Debug,
+ ("GtkTaskbarProgress::SetProgressState progress: %lu", progress));
+
+ mPrimaryWindow->SetProgress(progress);
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarProgress::SetPrimaryWindow(mozIDOMWindowProxy* aWindow) {
+ NS_ENSURE_TRUE(aWindow != nullptr, NS_ERROR_ILLEGAL_VALUE);
+
+ auto* parent = nsPIDOMWindowOuter::From(aWindow);
+ RefPtr<nsIWidget> widget =
+ mozilla::widget::WidgetUtils::DOMWindowToWidget(parent);
+
+ // Only nsWindows have a native window, HeadlessWidgets do not. Stop here if
+ // the window does not have one.
+ if (!widget->GetNativeData(NS_NATIVE_WINDOW)) {
+ return NS_OK;
+ }
+
+ mPrimaryWindow = static_cast<nsWindow*>(widget.get());
+
+ // Clear our current progress. We get a forced update from the
+ // DownloadsTaskbar after returning from this function - zeroing out our
+ // progress will make sure the new window gets the property set on it
+ // immediately, rather than waiting for the progress value to change (which
+ // could be a while depending on size.)
+ mCurrentProgress = 0;
+
+ MOZ_LOG(gGtkTaskbarProgressLog, LogLevel::Debug,
+ ("GtkTaskbarProgress::SetPrimaryWindow window: %p",
+ mPrimaryWindow.get()));
+
+ return NS_OK;
+}
diff --git a/widget/gtk/TaskbarProgress.h b/widget/gtk/TaskbarProgress.h
new file mode 100644
index 0000000000..819df0c089
--- /dev/null
+++ b/widget/gtk/TaskbarProgress.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 TaskbarProgress_h_
+#define TaskbarProgress_h_
+
+#include "nsIGtkTaskbarProgress.h"
+
+class nsWindow;
+
+class TaskbarProgress final : public nsIGtkTaskbarProgress {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGTKTASKBARPROGRESS
+ NS_DECL_NSITASKBARPROGRESS
+
+ TaskbarProgress();
+
+ protected:
+ ~TaskbarProgress();
+
+ // We track the progress value so we can avoid updating the X window property
+ // unnecessarily.
+ unsigned long mCurrentProgress;
+
+ RefPtr<nsWindow> mPrimaryWindow;
+};
+
+#endif // #ifndef TaskbarProgress_h_
diff --git a/widget/gtk/WakeLockListener.cpp b/widget/gtk/WakeLockListener.cpp
new file mode 100644
index 0000000000..2192e3f492
--- /dev/null
+++ b/widget/gtk/WakeLockListener.cpp
@@ -0,0 +1,500 @@
+/* -*- 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/. */
+
+#ifdef MOZ_ENABLE_DBUS
+
+# include "WakeLockListener.h"
+
+# include <dbus/dbus.h>
+# include <dbus/dbus-glib-lowlevel.h>
+
+# if defined(MOZ_X11)
+# include "gfxPlatformGtk.h"
+# include "prlink.h"
+# include <gdk/gdk.h>
+# include <gdk/gdkx.h>
+# endif
+
+# if defined(MOZ_WAYLAND)
+# include "mozilla/widget/nsWaylandDisplay.h"
+# include "nsWindow.h"
+# include "mozilla/dom/power/PowerManagerService.h"
+# endif
+
+# define FREEDESKTOP_SCREENSAVER_TARGET "org.freedesktop.ScreenSaver"
+# define FREEDESKTOP_SCREENSAVER_OBJECT "/ScreenSaver"
+# define FREEDESKTOP_SCREENSAVER_INTERFACE "org.freedesktop.ScreenSaver"
+
+# define SESSION_MANAGER_TARGET "org.gnome.SessionManager"
+# define SESSION_MANAGER_OBJECT "/org/gnome/SessionManager"
+# define SESSION_MANAGER_INTERFACE "org.gnome.SessionManager"
+
+# define DBUS_TIMEOUT (-1)
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+NS_IMPL_ISUPPORTS(WakeLockListener, nsIDOMMozWakeLockListener)
+
+StaticRefPtr<WakeLockListener> WakeLockListener::sSingleton;
+
+# define WAKE_LOCK_LOG(...) \
+ MOZ_LOG(gLinuxWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+static mozilla::LazyLogModule gLinuxWakeLockLog("LinuxWakeLock");
+
+enum DesktopEnvironment {
+ FreeDesktop,
+ GNOME,
+# if defined(MOZ_X11)
+ XScreenSaver,
+# endif
+# if defined(MOZ_WAYLAND)
+ WaylandIdleInhibit,
+# endif
+ Unsupported,
+};
+
+class WakeLockTopic {
+ public:
+ WakeLockTopic(const nsAString& aTopic, DBusConnection* aConnection)
+ :
+# if defined(MOZ_WAYLAND)
+ mWaylandInhibitor(nullptr),
+# endif
+ mTopic(NS_ConvertUTF16toUTF8(aTopic)),
+ mConnection(aConnection),
+ mDesktopEnvironment(FreeDesktop),
+ mInhibitRequest(0),
+ mShouldInhibit(false),
+ mWaitingForReply(false) {
+ }
+
+ nsresult InhibitScreensaver(void);
+ nsresult UninhibitScreensaver(void);
+
+ private:
+ bool SendInhibit();
+ bool SendUninhibit();
+
+ bool SendFreeDesktopInhibitMessage();
+ bool SendGNOMEInhibitMessage();
+ bool SendMessage(DBusMessage* aMessage);
+
+# if defined(MOZ_X11)
+ static bool CheckXScreenSaverSupport();
+ static bool InhibitXScreenSaver(bool inhibit);
+# endif
+
+# if defined(MOZ_WAYLAND)
+ zwp_idle_inhibitor_v1* mWaylandInhibitor;
+ static bool CheckWaylandIdleInhibitSupport();
+ bool InhibitWaylandIdle();
+ bool UninhibitWaylandIdle();
+# endif
+
+ static void ReceiveInhibitReply(DBusPendingCall* aPending, void* aUserData);
+ void InhibitFailed();
+ void InhibitSucceeded(uint32_t aInhibitRequest);
+
+ nsCString mTopic;
+ RefPtr<DBusConnection> mConnection;
+
+ DesktopEnvironment mDesktopEnvironment;
+
+ uint32_t mInhibitRequest;
+
+ bool mShouldInhibit;
+ bool mWaitingForReply;
+};
+
+bool WakeLockTopic::SendMessage(DBusMessage* aMessage) {
+ // send message and get a handle for a reply
+ RefPtr<DBusPendingCall> reply;
+ dbus_connection_send_with_reply(mConnection, aMessage,
+ reply.StartAssignment(), DBUS_TIMEOUT);
+ if (!reply) {
+ return false;
+ }
+
+ dbus_pending_call_set_notify(reply, &ReceiveInhibitReply, this, NULL);
+
+ return true;
+}
+
+bool WakeLockTopic::SendFreeDesktopInhibitMessage() {
+ RefPtr<DBusMessage> message =
+ already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ FREEDESKTOP_SCREENSAVER_TARGET, FREEDESKTOP_SCREENSAVER_OBJECT,
+ FREEDESKTOP_SCREENSAVER_INTERFACE, "Inhibit"));
+
+ if (!message) {
+ return false;
+ }
+
+ const char* app = g_get_prgname();
+ const char* topic = mTopic.get();
+ dbus_message_append_args(message, DBUS_TYPE_STRING, &app, DBUS_TYPE_STRING,
+ &topic, DBUS_TYPE_INVALID);
+
+ return SendMessage(message);
+}
+
+bool WakeLockTopic::SendGNOMEInhibitMessage() {
+ RefPtr<DBusMessage> message =
+ already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ SESSION_MANAGER_TARGET, SESSION_MANAGER_OBJECT,
+ SESSION_MANAGER_INTERFACE, "Inhibit"));
+
+ if (!message) {
+ return false;
+ }
+
+ static const uint32_t xid = 0;
+ static const uint32_t flags = (1 << 3); // Inhibit idle
+ const char* app = g_get_prgname();
+ const char* topic = mTopic.get();
+ dbus_message_append_args(message, DBUS_TYPE_STRING, &app, DBUS_TYPE_UINT32,
+ &xid, DBUS_TYPE_STRING, &topic, DBUS_TYPE_UINT32,
+ &flags, DBUS_TYPE_INVALID);
+
+ return SendMessage(message);
+}
+
+# if defined(MOZ_X11)
+
+typedef Bool (*_XScreenSaverQueryExtension_fn)(Display* dpy, int* event_base,
+ int* error_base);
+typedef Bool (*_XScreenSaverQueryVersion_fn)(Display* dpy, int* major,
+ int* minor);
+typedef void (*_XScreenSaverSuspend_fn)(Display* dpy, Bool suspend);
+
+static PRLibrary* sXssLib = nullptr;
+static _XScreenSaverQueryExtension_fn _XSSQueryExtension = nullptr;
+static _XScreenSaverQueryVersion_fn _XSSQueryVersion = nullptr;
+static _XScreenSaverSuspend_fn _XSSSuspend = nullptr;
+
+/* static */
+bool WakeLockTopic::CheckXScreenSaverSupport() {
+ if (!sXssLib) {
+ sXssLib = PR_LoadLibrary("libXss.so.1");
+ if (!sXssLib) {
+ return false;
+ }
+ }
+
+ _XSSQueryExtension = (_XScreenSaverQueryExtension_fn)PR_FindFunctionSymbol(
+ sXssLib, "XScreenSaverQueryExtension");
+ _XSSQueryVersion = (_XScreenSaverQueryVersion_fn)PR_FindFunctionSymbol(
+ sXssLib, "XScreenSaverQueryVersion");
+ _XSSSuspend = (_XScreenSaverSuspend_fn)PR_FindFunctionSymbol(
+ sXssLib, "XScreenSaverSuspend");
+ if (!_XSSQueryExtension || !_XSSQueryVersion || !_XSSSuspend) {
+ return false;
+ }
+
+ GdkDisplay* gDisplay = gdk_display_get_default();
+ if (!gDisplay || !GDK_IS_X11_DISPLAY(gDisplay)) {
+ return false;
+ }
+ Display* display = GDK_DISPLAY_XDISPLAY(gDisplay);
+
+ int throwaway;
+ if (!_XSSQueryExtension(display, &throwaway, &throwaway)) return false;
+
+ int major, minor;
+ if (!_XSSQueryVersion(display, &major, &minor)) return false;
+ // Needs to be compatible with version 1.1
+ if (major != 1) return false;
+ if (minor < 1) return false;
+
+ return true;
+}
+
+/* static */
+bool WakeLockTopic::InhibitXScreenSaver(bool inhibit) {
+ // Should only be called if CheckXScreenSaverSupport returns true.
+ // There's a couple of safety checks here nonetheless.
+ if (!_XSSSuspend) {
+ return false;
+ }
+ GdkDisplay* gDisplay = gdk_display_get_default();
+ if (!gDisplay || !GDK_IS_X11_DISPLAY(gDisplay)) {
+ return false;
+ }
+ Display* display = GDK_DISPLAY_XDISPLAY(gDisplay);
+ _XSSSuspend(display, inhibit);
+ return true;
+}
+
+# endif
+
+# if defined(MOZ_WAYLAND)
+
+/* static */
+bool WakeLockTopic::CheckWaylandIdleInhibitSupport() {
+ RefPtr<nsWaylandDisplay> waylandDisplay = WaylandDisplayGet();
+ return waylandDisplay && waylandDisplay->GetIdleInhibitManager() != nullptr;
+}
+
+bool WakeLockTopic::InhibitWaylandIdle() {
+ RefPtr<nsWaylandDisplay> waylandDisplay = WaylandDisplayGet();
+ if (!waylandDisplay) {
+ return false;
+ }
+
+ nsWindow* focusedWindow = nsWindow::GetFocusedWindow();
+ if (!focusedWindow) {
+ return false;
+ }
+
+ UninhibitWaylandIdle();
+
+ MozContainer* container = focusedWindow->GetMozContainer();
+ wl_surface* waylandSurface = moz_container_wayland_surface_lock(container);
+ if (waylandSurface) {
+ mWaylandInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(
+ waylandDisplay->GetIdleInhibitManager(), waylandSurface);
+ moz_container_wayland_surface_unlock(container, &waylandSurface);
+ }
+ return true;
+}
+
+bool WakeLockTopic::UninhibitWaylandIdle() {
+ if (mWaylandInhibitor == nullptr) return false;
+
+ zwp_idle_inhibitor_v1_destroy(mWaylandInhibitor);
+ mWaylandInhibitor = nullptr;
+
+ return true;
+}
+
+# endif
+
+bool WakeLockTopic::SendInhibit() {
+ bool sendOk = false;
+
+ switch (mDesktopEnvironment) {
+ case FreeDesktop:
+ sendOk = SendFreeDesktopInhibitMessage();
+ break;
+ case GNOME:
+ sendOk = SendGNOMEInhibitMessage();
+ break;
+# if defined(MOZ_X11)
+ case XScreenSaver:
+ return InhibitXScreenSaver(true);
+# endif
+# if defined(MOZ_WAYLAND)
+ case WaylandIdleInhibit:
+ return InhibitWaylandIdle();
+# endif
+ case Unsupported:
+ return false;
+ }
+
+ if (sendOk) {
+ mWaitingForReply = true;
+ }
+
+ return sendOk;
+}
+
+bool WakeLockTopic::SendUninhibit() {
+ RefPtr<DBusMessage> message;
+
+ if (mDesktopEnvironment == FreeDesktop) {
+ message = already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ FREEDESKTOP_SCREENSAVER_TARGET, FREEDESKTOP_SCREENSAVER_OBJECT,
+ FREEDESKTOP_SCREENSAVER_INTERFACE, "UnInhibit"));
+ } else if (mDesktopEnvironment == GNOME) {
+ message = already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ SESSION_MANAGER_TARGET, SESSION_MANAGER_OBJECT,
+ SESSION_MANAGER_INTERFACE, "Uninhibit"));
+ }
+# if defined(MOZ_X11)
+ else if (mDesktopEnvironment == XScreenSaver) {
+ return InhibitXScreenSaver(false);
+ }
+# endif
+# if defined(MOZ_WAYLAND)
+ else if (mDesktopEnvironment == WaylandIdleInhibit) {
+ return UninhibitWaylandIdle();
+ }
+# endif
+
+ if (!message) {
+ return false;
+ }
+
+ dbus_message_append_args(message, DBUS_TYPE_UINT32, &mInhibitRequest,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send(mConnection, message, nullptr);
+ dbus_connection_flush(mConnection);
+
+ mInhibitRequest = 0;
+
+ return true;
+}
+
+nsresult WakeLockTopic::InhibitScreensaver() {
+ if (mShouldInhibit) {
+ // Screensaver is inhibited. Nothing to do here.
+ return NS_OK;
+ }
+
+ mShouldInhibit = true;
+
+ if (mWaitingForReply) {
+ // We already have a screensaver inhibit request pending. This can happen
+ // if InhibitScreensaver is called, then UninhibitScreensaver, then
+ // InhibitScreensaver again quickly.
+ return NS_OK;
+ }
+
+ return SendInhibit() ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult WakeLockTopic::UninhibitScreensaver() {
+ if (!mShouldInhibit) {
+ // Screensaver isn't inhibited. Nothing to do here.
+ return NS_OK;
+ }
+
+ mShouldInhibit = false;
+
+ if (mWaitingForReply) {
+ // If we're still waiting for a response to our inhibit request, we can't
+ // do anything until we get a dbus message back. The callbacks below will
+ // check |mShouldInhibit| and act accordingly.
+ return NS_OK;
+ }
+
+ return SendUninhibit() ? NS_OK : NS_ERROR_FAILURE;
+}
+
+void WakeLockTopic::InhibitFailed() {
+ mWaitingForReply = false;
+
+ if (mDesktopEnvironment == FreeDesktop) {
+ mDesktopEnvironment = GNOME;
+# if defined(MOZ_X11)
+ } else if (mDesktopEnvironment == GNOME && CheckXScreenSaverSupport()) {
+ mDesktopEnvironment = XScreenSaver;
+# endif
+# if defined(MOZ_WAYLAND)
+ } else if (mDesktopEnvironment == GNOME && CheckWaylandIdleInhibitSupport()) {
+ mDesktopEnvironment = WaylandIdleInhibit;
+# endif
+ } else {
+ mDesktopEnvironment = Unsupported;
+ mShouldInhibit = false;
+ }
+
+ if (!mShouldInhibit) {
+ // We were interrupted by UninhibitScreensaver() before we could find the
+ // correct desktop environment.
+ return;
+ }
+
+ SendInhibit();
+}
+
+void WakeLockTopic::InhibitSucceeded(uint32_t aInhibitRequest) {
+ mWaitingForReply = false;
+ mInhibitRequest = aInhibitRequest;
+
+ if (!mShouldInhibit) {
+ // We successfully inhibited the screensaver, but UninhibitScreensaver()
+ // was called while we were waiting for a reply.
+ SendUninhibit();
+ }
+}
+
+/* static */
+void WakeLockTopic::ReceiveInhibitReply(DBusPendingCall* pending,
+ void* user_data) {
+ if (!WakeLockListener::GetSingleton(false)) {
+ // The WakeLockListener (and therefore our topic) was deleted while we were
+ // waiting for a reply.
+ return;
+ }
+
+ WakeLockTopic* self = static_cast<WakeLockTopic*>(user_data);
+
+ RefPtr<DBusMessage> msg =
+ already_AddRefed<DBusMessage>(dbus_pending_call_steal_reply(pending));
+ if (!msg) {
+ return;
+ }
+
+ if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+ uint32_t inhibitRequest;
+
+ if (dbus_message_get_args(msg, nullptr, DBUS_TYPE_UINT32, &inhibitRequest,
+ DBUS_TYPE_INVALID)) {
+ self->InhibitSucceeded(inhibitRequest);
+ }
+ } else {
+ self->InhibitFailed();
+ }
+}
+
+WakeLockListener::WakeLockListener() : mConnection(nullptr) {}
+
+/* static */
+WakeLockListener* WakeLockListener::GetSingleton(bool aCreate) {
+ if (!sSingleton && aCreate) {
+ sSingleton = new WakeLockListener();
+ }
+
+ return sSingleton;
+}
+
+/* static */
+void WakeLockListener::Shutdown() { sSingleton = nullptr; }
+
+bool WakeLockListener::EnsureDBusConnection() {
+ if (!mConnection) {
+ mConnection = already_AddRefed<DBusConnection>(
+ dbus_bus_get(DBUS_BUS_SESSION, nullptr));
+
+ if (mConnection) {
+ dbus_connection_set_exit_on_disconnect(mConnection, false);
+ dbus_connection_setup_with_g_main(mConnection, nullptr);
+ }
+ }
+
+ return mConnection != nullptr;
+}
+
+nsresult WakeLockListener::Callback(const nsAString& topic,
+ const nsAString& state) {
+ if (!EnsureDBusConnection()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!topic.Equals(u"screen"_ns) && !topic.Equals(u"audio-playing"_ns) &&
+ !topic.Equals(u"video-playing"_ns))
+ return NS_OK;
+
+ WakeLockTopic* topicLock = mTopics.Get(topic);
+ if (!topicLock) {
+ topicLock = new WakeLockTopic(topic, mConnection);
+ mTopics.Put(topic, topicLock);
+ }
+
+ // Treat "locked-background" the same as "unlocked" on desktop linux.
+ bool shouldLock = state.EqualsLiteral("locked-foreground");
+ WAKE_LOCK_LOG("topic=%s, shouldLock=%d", NS_ConvertUTF16toUTF8(topic).get(),
+ shouldLock);
+
+ return shouldLock ? topicLock->InhibitScreensaver()
+ : topicLock->UninhibitScreensaver();
+}
+
+#endif
diff --git a/widget/gtk/WakeLockListener.h b/widget/gtk/WakeLockListener.h
new file mode 100644
index 0000000000..4f163434df
--- /dev/null
+++ b/widget/gtk/WakeLockListener.h
@@ -0,0 +1,55 @@
+/* -*- 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 __WakeLockListener_h__
+#define __WakeLockListener_h__
+
+#include <unistd.h>
+
+#include "mozilla/StaticPtr.h"
+#include "nsHashKeys.h"
+#include "nsClassHashtable.h"
+
+#include "nsIDOMWakeLockListener.h"
+
+#ifdef MOZ_ENABLE_DBUS
+# include "mozilla/DBusHelpers.h"
+#endif
+
+class WakeLockTopic;
+
+/**
+ * Receives WakeLock events and simply passes it on to the right WakeLockTopic
+ * to inhibit the screensaver.
+ */
+class WakeLockListener final : public nsIDOMMozWakeLockListener {
+ public:
+ NS_DECL_ISUPPORTS;
+
+ static WakeLockListener* GetSingleton(bool aCreate = true);
+ static void Shutdown();
+
+ virtual nsresult Callback(const nsAString& topic,
+ const nsAString& state) override;
+
+ private:
+ WakeLockListener();
+ ~WakeLockListener() = default;
+
+ bool EnsureDBusConnection();
+
+ static mozilla::StaticRefPtr<WakeLockListener> sSingleton;
+
+#ifdef MOZ_ENABLE_DBUS
+ RefPtr<DBusConnection> mConnection;
+#endif
+ // Map of topic names to |WakeLockTopic|s.
+ // We assume a small, finite-sized set of topics.
+ nsClassHashtable<nsStringHashKey, WakeLockTopic> mTopics;
+};
+
+#endif // __WakeLockListener_h__
diff --git a/widget/gtk/WaylandVsyncSource.cpp b/widget/gtk/WaylandVsyncSource.cpp
new file mode 100644
index 0000000000..02f9d9c38b
--- /dev/null
+++ b/widget/gtk/WaylandVsyncSource.cpp
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifdef MOZ_WAYLAND
+
+# include "WaylandVsyncSource.h"
+# include "nsThreadUtils.h"
+# include "nsISupportsImpl.h"
+# include "MainThreadUtils.h"
+
+# include <gdk/gdkwayland.h>
+
+using namespace mozilla::widget;
+
+namespace mozilla {
+
+static void WaylandVsyncSourceCallbackHandler(void* data,
+ struct wl_callback* callback,
+ uint32_t time) {
+ WaylandVsyncSource::WaylandDisplay* context =
+ (WaylandVsyncSource::WaylandDisplay*)data;
+ wl_callback_destroy(callback);
+ context->FrameCallback(time);
+}
+
+static const struct wl_callback_listener WaylandVsyncSourceCallbackListener = {
+ WaylandVsyncSourceCallbackHandler};
+
+WaylandVsyncSource::WaylandDisplay::WaylandDisplay(MozContainer* container)
+ : mEnabledLock("WaylandVsyncEnabledLock"),
+ mIsShutdown(false),
+ mVsyncEnabled(false),
+ mMonitorEnabled(false),
+ mCallback(nullptr),
+ mContainer(container),
+ mLastVsyncTimeStamp(TimeStamp::Now()) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We store the display here so all the frame callbacks won't have to look it
+ // up all the time.
+ mDisplay = WaylandDisplayGetWLDisplay();
+
+ mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0);
+}
+
+void WaylandVsyncSource::WaylandDisplay::ClearFrameCallback() {
+ if (mCallback) {
+ wl_callback_destroy(mCallback);
+ mCallback = nullptr;
+ }
+}
+
+void WaylandVsyncSource::WaylandDisplay::Refresh() {
+ TimeStamp outputTimestamp;
+ {
+ MutexAutoLock lock(mEnabledLock);
+ if (!mMonitorEnabled || !mVsyncEnabled || mCallback) {
+ // We don't need to do anything because:
+ // * We are unwanted by our widget or monitor, or
+ // * The last frame callback hasn't yet run to see that it had been shut
+ // down, so we can reuse it after having set mVsyncEnabled to true.
+ return;
+ }
+
+ struct wl_surface* surface = moz_container_wayland_surface_lock(mContainer);
+ if (!surface) {
+ // The surface hasn't been created yet. Try again when the surface is
+ // ready.
+ RefPtr<WaylandVsyncSource::WaylandDisplay> self(this);
+ moz_container_wayland_add_initial_draw_callback(
+ mContainer, [self]() -> void { self->Refresh(); });
+ return;
+ }
+ moz_container_wayland_surface_unlock(mContainer, &surface);
+
+ // Vsync is enabled, but we don't have a callback configured. Set one up so
+ // we can get to work.
+ SetupFrameCallback();
+ mLastVsyncTimeStamp = TimeStamp::Now();
+ outputTimestamp = mLastVsyncTimeStamp + GetVsyncRate();
+ }
+ NotifyVsync(mLastVsyncTimeStamp, outputTimestamp);
+}
+
+void WaylandVsyncSource::WaylandDisplay::EnableMonitor() {
+ {
+ MutexAutoLock lock(mEnabledLock);
+ if (mMonitorEnabled) {
+ return;
+ }
+ mMonitorEnabled = true;
+ }
+ Refresh();
+}
+
+void WaylandVsyncSource::WaylandDisplay::DisableMonitor() {
+ MutexAutoLock lock(mEnabledLock);
+ if (!mMonitorEnabled) {
+ return;
+ }
+ mMonitorEnabled = false;
+ ClearFrameCallback();
+}
+
+void WaylandVsyncSource::WaylandDisplay::SetupFrameCallback() {
+ MOZ_ASSERT(mCallback == nullptr);
+ struct wl_surface* surface = moz_container_wayland_surface_lock(mContainer);
+ if (!surface) {
+ // We don't have a surface, either due to being called before it was made
+ // available in the mozcontainer, or after it was destroyed. We're all done
+ // regardless.
+ ClearFrameCallback();
+ return;
+ }
+
+ mCallback = wl_surface_frame(surface);
+ wl_callback_add_listener(mCallback, &WaylandVsyncSourceCallbackListener,
+ this);
+ wl_surface_commit(surface);
+ wl_display_flush(mDisplay);
+ moz_container_wayland_surface_unlock(mContainer, &surface);
+}
+
+void WaylandVsyncSource::WaylandDisplay::FrameCallback(uint32_t timestampTime) {
+ TimeStamp outputTimestamp;
+ {
+ MutexAutoLock lock(mEnabledLock);
+ mCallback = nullptr;
+
+ if (!mVsyncEnabled || !mMonitorEnabled) {
+ // We are unwanted by either our creator or our consumer, so we just stop
+ // here without setting up a new frame callback.
+ return;
+ }
+
+ // Configure our next frame callback.
+ SetupFrameCallback();
+
+ int64_t tick =
+ BaseTimeDurationPlatformUtils::TicksFromMilliseconds(timestampTime);
+ TimeStamp callbackTimeStamp = TimeStamp::FromSystemTime(tick);
+ double duration = (TimeStamp::Now() - callbackTimeStamp).ToMilliseconds();
+
+ TimeStamp vsyncTimestamp;
+ if (duration < 50 && duration > -50) {
+ vsyncTimestamp = callbackTimeStamp;
+ } else {
+ vsyncTimestamp = TimeStamp::Now();
+ }
+
+ CalculateVsyncRate(vsyncTimestamp);
+ mLastVsyncTimeStamp = vsyncTimestamp;
+ outputTimestamp = vsyncTimestamp + GetVsyncRate();
+ }
+ NotifyVsync(mLastVsyncTimeStamp, outputTimestamp);
+}
+
+TimeDuration WaylandVsyncSource::WaylandDisplay::GetVsyncRate() {
+ return mVsyncRate;
+}
+
+void WaylandVsyncSource::WaylandDisplay::CalculateVsyncRate(
+ TimeStamp vsyncTimestamp) {
+ double duration = (vsyncTimestamp - mLastVsyncTimeStamp).ToMilliseconds();
+ double curVsyncRate = mVsyncRate.ToMilliseconds();
+ double correction;
+
+ if (duration > curVsyncRate) {
+ correction = fmin(curVsyncRate, (duration - curVsyncRate) / 10);
+ mVsyncRate += TimeDuration::FromMilliseconds(correction);
+ } else {
+ correction = fmin(curVsyncRate / 2, (curVsyncRate - duration) / 10);
+ mVsyncRate -= TimeDuration::FromMilliseconds(correction);
+ }
+}
+
+void WaylandVsyncSource::WaylandDisplay::EnableVsync() {
+ MOZ_ASSERT(NS_IsMainThread());
+ {
+ MutexAutoLock lock(mEnabledLock);
+ if (mVsyncEnabled || mIsShutdown) {
+ return;
+ }
+ mVsyncEnabled = true;
+ }
+ Refresh();
+}
+
+void WaylandVsyncSource::WaylandDisplay::DisableVsync() {
+ MutexAutoLock lock(mEnabledLock);
+ mVsyncEnabled = false;
+ ClearFrameCallback();
+}
+
+bool WaylandVsyncSource::WaylandDisplay::IsVsyncEnabled() {
+ MutexAutoLock lock(mEnabledLock);
+ return mVsyncEnabled;
+}
+
+void WaylandVsyncSource::WaylandDisplay::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MutexAutoLock lock(mEnabledLock);
+ mIsShutdown = true;
+ mVsyncEnabled = false;
+ ClearFrameCallback();
+ wl_display_roundtrip(mDisplay);
+}
+
+} // namespace mozilla
+
+#endif // MOZ_WAYLAND
diff --git a/widget/gtk/WaylandVsyncSource.h b/widget/gtk/WaylandVsyncSource.h
new file mode 100644
index 0000000000..fc6fcc10cf
--- /dev/null
+++ b/widget/gtk/WaylandVsyncSource.h
@@ -0,0 +1,96 @@
+/* -*- 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 _WaylandVsyncSource_h_
+#define _WaylandVsyncSource_h_
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Monitor.h"
+#include "MozContainer.h"
+#include "VsyncSource.h"
+#include "base/thread.h"
+#include "nsWaylandDisplay.h"
+
+namespace mozilla {
+
+/*
+ * WaylandVsyncSource
+ *
+ * This class provides a per-widget VsyncSource under Wayland, emulated using
+ * frame callbacks on the widget surface with empty surface commits.
+ *
+ * Wayland does not expose vsync/vblank, as it considers that an implementation
+ * detail the clients should not concern themselves with. Instead, frame
+ * callbacks are provided whenever the compositor believes it is a good time to
+ * start drawing the next frame for a particular surface, giving us as much
+ * time as possible to do so.
+ *
+ * Note that the compositor sends frame callbacks only when it sees fit, and
+ * when that may be is entirely up to the compositor. One cannot expect a
+ * certain rate of callbacks, or any callbacks at all. Examples of common
+ * variations would be surfaces moved between outputs with different refresh
+ * rates, and surfaces that are hidden and therefore do not receieve any
+ * callbacks at all. Other hypothetical scenarios of variation could be
+ * throttling to conserve power, or because a user has requested it.
+ *
+ */
+class WaylandVsyncSource final : public gfx::VsyncSource {
+ public:
+ explicit WaylandVsyncSource(MozContainer* container) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mGlobalDisplay = new WaylandDisplay(container);
+ }
+
+ virtual ~WaylandVsyncSource() { MOZ_ASSERT(NS_IsMainThread()); }
+
+ virtual Display& GetGlobalDisplay() override { return *mGlobalDisplay; }
+
+ class WaylandDisplay final : public mozilla::gfx::VsyncSource::Display {
+ public:
+ explicit WaylandDisplay(MozContainer* container);
+
+ void EnableMonitor();
+ void DisableMonitor();
+
+ void FrameCallback(uint32_t timestampTime);
+ void Notify();
+
+ TimeDuration GetVsyncRate() override;
+
+ virtual void EnableVsync() override;
+
+ virtual void DisableVsync() override;
+
+ virtual bool IsVsyncEnabled() override;
+
+ virtual void Shutdown() override;
+
+ private:
+ virtual ~WaylandDisplay() = default;
+ void Refresh();
+ void SetupFrameCallback();
+ void ClearFrameCallback();
+ void CalculateVsyncRate(TimeStamp vsyncTimestamp);
+
+ Mutex mEnabledLock;
+ bool mIsShutdown;
+ bool mVsyncEnabled;
+ bool mMonitorEnabled;
+ struct wl_display* mDisplay;
+ struct wl_callback* mCallback;
+ MozContainer* mContainer;
+ TimeDuration mVsyncRate;
+ TimeStamp mLastVsyncTimeStamp;
+ };
+
+ private:
+ // We need a refcounted VsyncSource::Display to use chromium IPC runnables.
+ RefPtr<WaylandDisplay> mGlobalDisplay;
+};
+
+} // namespace mozilla
+
+#endif // _WaylandVsyncSource_h_
diff --git a/widget/gtk/WidgetStyleCache.cpp b/widget/gtk/WidgetStyleCache.cpp
new file mode 100644
index 0000000000..15f66f4876
--- /dev/null
+++ b/widget/gtk/WidgetStyleCache.cpp
@@ -0,0 +1,1464 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <dlfcn.h>
+#include <gtk/gtk.h>
+#include "WidgetStyleCache.h"
+#include "gtkdrawing.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/PodOperations.h"
+#include "nsDebug.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+
+#define STATE_FLAG_DIR_LTR (1U << 7)
+#define STATE_FLAG_DIR_RTL (1U << 8)
+static_assert(GTK_STATE_FLAG_DIR_LTR == STATE_FLAG_DIR_LTR &&
+ GTK_STATE_FLAG_DIR_RTL == STATE_FLAG_DIR_RTL,
+ "incorrect direction state flags");
+
+static GtkWidget* sWidgetStorage[MOZ_GTK_WIDGET_NODE_COUNT];
+static GtkStyleContext* sStyleStorage[MOZ_GTK_WIDGET_NODE_COUNT];
+
+static GtkStyleContext* GetWidgetRootStyle(WidgetNodeType aNodeType);
+static GtkStyleContext* GetCssNodeStyleInternal(WidgetNodeType aNodeType);
+
+static GtkWidget* CreateWindowWidget() {
+ GtkWidget* widget = gtk_window_new(GTK_WINDOW_POPUP);
+ MOZ_RELEASE_ASSERT(widget, "We're missing GtkWindow widget!");
+ gtk_widget_set_name(widget, "MozillaGtkWidget");
+ return widget;
+}
+
+static GtkWidget* CreateWindowContainerWidget() {
+ GtkWidget* widget = gtk_fixed_new();
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW)), widget);
+ return widget;
+}
+
+static void AddToWindowContainer(GtkWidget* widget) {
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW_CONTAINER)), widget);
+}
+
+static GtkWidget* CreateScrollbarWidget(WidgetNodeType aAppearance,
+ GtkOrientation aOrientation) {
+ GtkWidget* widget = gtk_scrollbar_new(aOrientation, nullptr);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateCheckboxWidget() {
+ GtkWidget* widget = gtk_check_button_new_with_label("M");
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateRadiobuttonWidget() {
+ GtkWidget* widget = gtk_radio_button_new_with_label(nullptr, "M");
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateMenuBarWidget() {
+ GtkWidget* widget = gtk_menu_bar_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateMenuPopupWidget() {
+ GtkWidget* widget = gtk_menu_new();
+ gtk_menu_attach_to_widget(GTK_MENU(widget), GetWidget(MOZ_GTK_WINDOW),
+ nullptr);
+ return widget;
+}
+
+static GtkWidget* CreateProgressWidget() {
+ GtkWidget* widget = gtk_progress_bar_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateTooltipWidget() {
+ MOZ_ASSERT(gtk_check_version(3, 20, 0) != nullptr,
+ "CreateTooltipWidget should be used for Gtk < 3.20 only.");
+ GtkWidget* widget = CreateWindowWidget();
+ GtkStyleContext* style = gtk_widget_get_style_context(widget);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOOLTIP);
+ return widget;
+}
+
+static GtkWidget* CreateExpanderWidget() {
+ GtkWidget* widget = gtk_expander_new("M");
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateFrameWidget() {
+ GtkWidget* widget = gtk_frame_new(nullptr);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateGripperWidget() {
+ GtkWidget* widget = gtk_handle_box_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateToolbarWidget() {
+ GtkWidget* widget = gtk_toolbar_new();
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_GRIPPER)), widget);
+ return widget;
+}
+
+static GtkWidget* CreateToolbarSeparatorWidget() {
+ GtkWidget* widget = GTK_WIDGET(gtk_separator_tool_item_new());
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateInfoBarWidget() {
+ GtkWidget* widget = gtk_info_bar_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateButtonWidget() {
+ GtkWidget* widget = gtk_button_new_with_label("M");
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateToggleButtonWidget() {
+ GtkWidget* widget = gtk_toggle_button_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateButtonArrowWidget() {
+ GtkWidget* widget = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_TOGGLE_BUTTON)), widget);
+ gtk_widget_show(widget);
+ return widget;
+}
+
+static GtkWidget* CreateSpinWidget() {
+ GtkWidget* widget = gtk_spin_button_new(nullptr, 1, 0);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateEntryWidget() {
+ GtkWidget* widget = gtk_entry_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateComboBoxWidget() {
+ GtkWidget* widget = gtk_combo_box_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+typedef struct {
+ GType type;
+ GtkWidget** widget;
+} GtkInnerWidgetInfo;
+
+static void GetInnerWidget(GtkWidget* widget, gpointer client_data) {
+ auto info = static_cast<GtkInnerWidgetInfo*>(client_data);
+
+ if (G_TYPE_CHECK_INSTANCE_TYPE(widget, info->type)) {
+ *info->widget = widget;
+ }
+}
+
+static GtkWidget* CreateComboBoxButtonWidget() {
+ GtkWidget* comboBox = GetWidget(MOZ_GTK_COMBOBOX);
+ GtkWidget* comboBoxButton = nullptr;
+
+ /* Get its inner Button */
+ GtkInnerWidgetInfo info = {GTK_TYPE_TOGGLE_BUTTON, &comboBoxButton};
+ gtk_container_forall(GTK_CONTAINER(comboBox), GetInnerWidget, &info);
+
+ if (!comboBoxButton) {
+ /* Shouldn't be reached with current internal gtk implementation; we
+ * use a generic toggle button as last resort fallback to avoid
+ * crashing. */
+ comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON);
+ } else {
+ /* We need to have pointers to the inner widgets (button, separator, arrow)
+ * of the ComboBox to get the correct rendering from theme engines which
+ * special cases their look. Since the inner layout can change, we ask GTK
+ * to NULL our pointers when they are about to become invalid because the
+ * corresponding widgets don't exist anymore. It's the role of
+ * g_object_add_weak_pointer().
+ * Note that if we don't find the inner widgets (which shouldn't happen), we
+ * fallback to use generic "non-inner" widgets, and they don't need that
+ * kind of weak pointer since they are explicit children of gProtoLayout and
+ * as such GTK holds a strong reference to them. */
+ g_object_add_weak_pointer(
+ G_OBJECT(comboBoxButton),
+ reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_BUTTON);
+ }
+
+ return comboBoxButton;
+}
+
+static GtkWidget* CreateComboBoxArrowWidget() {
+ GtkWidget* comboBoxButton = GetWidget(MOZ_GTK_COMBOBOX_BUTTON);
+ GtkWidget* comboBoxArrow = nullptr;
+
+ /* Get the widgets inside the Button */
+ GtkWidget* buttonChild = gtk_bin_get_child(GTK_BIN(comboBoxButton));
+ if (GTK_IS_BOX(buttonChild)) {
+ /* appears-as-list = FALSE, cell-view = TRUE; the button
+ * contains an hbox. This hbox is there because the ComboBox
+ * needs to place a cell renderer, a separator, and an arrow in
+ * the button when appears-as-list is FALSE. */
+ GtkInnerWidgetInfo info = {GTK_TYPE_ARROW, &comboBoxArrow};
+ gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
+ } else if (GTK_IS_ARROW(buttonChild)) {
+ /* appears-as-list = TRUE, or cell-view = FALSE;
+ * the button only contains an arrow */
+ comboBoxArrow = buttonChild;
+ }
+
+ if (!comboBoxArrow) {
+ /* Shouldn't be reached with current internal gtk implementation;
+ * we gButtonArrowWidget as last resort fallback to avoid
+ * crashing. */
+ comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW);
+ } else {
+ g_object_add_weak_pointer(
+ G_OBJECT(comboBoxArrow),
+ reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_ARROW);
+ }
+
+ return comboBoxArrow;
+}
+
+static GtkWidget* CreateComboBoxSeparatorWidget() {
+ // Ensure to search for separator only once as it can fail
+ // TODO - it won't initialize after ResetWidgetCache() call
+ static bool isMissingSeparator = false;
+ if (isMissingSeparator) return nullptr;
+
+ /* Get the widgets inside the Button */
+ GtkWidget* comboBoxSeparator = nullptr;
+ GtkWidget* buttonChild =
+ gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_BUTTON)));
+ if (GTK_IS_BOX(buttonChild)) {
+ /* appears-as-list = FALSE, cell-view = TRUE; the button
+ * contains an hbox. This hbox is there because the ComboBox
+ * needs to place a cell renderer, a separator, and an arrow in
+ * the button when appears-as-list is FALSE. */
+ GtkInnerWidgetInfo info = {GTK_TYPE_SEPARATOR, &comboBoxSeparator};
+ gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
+ }
+
+ if (comboBoxSeparator) {
+ g_object_add_weak_pointer(G_OBJECT(comboBoxSeparator),
+ reinterpret_cast<gpointer*>(sWidgetStorage) +
+ MOZ_GTK_COMBOBOX_SEPARATOR);
+ } else {
+ /* comboBoxSeparator may be NULL
+ * when "appears-as-list" = TRUE or "cell-view" = FALSE;
+ * if there is no separator, then we just won't paint it. */
+ isMissingSeparator = true;
+ }
+
+ return comboBoxSeparator;
+}
+
+static GtkWidget* CreateComboBoxEntryWidget() {
+ GtkWidget* widget = gtk_combo_box_new_with_entry();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateComboBoxEntryTextareaWidget() {
+ GtkWidget* comboBoxTextarea = nullptr;
+
+ /* Get its inner Entry and Button */
+ GtkInnerWidgetInfo info = {GTK_TYPE_ENTRY, &comboBoxTextarea};
+ gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)),
+ GetInnerWidget, &info);
+
+ if (!comboBoxTextarea) {
+ comboBoxTextarea = GetWidget(MOZ_GTK_ENTRY);
+ } else {
+ g_object_add_weak_pointer(
+ G_OBJECT(comboBoxTextarea),
+ reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_ENTRY);
+ }
+
+ return comboBoxTextarea;
+}
+
+static GtkWidget* CreateComboBoxEntryButtonWidget() {
+ GtkWidget* comboBoxButton = nullptr;
+
+ /* Get its inner Entry and Button */
+ GtkInnerWidgetInfo info = {GTK_TYPE_TOGGLE_BUTTON, &comboBoxButton};
+ gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)),
+ GetInnerWidget, &info);
+
+ if (!comboBoxButton) {
+ comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON);
+ } else {
+ g_object_add_weak_pointer(G_OBJECT(comboBoxButton),
+ reinterpret_cast<gpointer*>(sWidgetStorage) +
+ MOZ_GTK_COMBOBOX_ENTRY_BUTTON);
+ }
+
+ return comboBoxButton;
+}
+
+static GtkWidget* CreateComboBoxEntryArrowWidget() {
+ GtkWidget* comboBoxArrow = nullptr;
+
+ /* Get the Arrow inside the Button */
+ GtkWidget* buttonChild =
+ gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON)));
+
+ if (GTK_IS_BOX(buttonChild)) {
+ /* appears-as-list = FALSE, cell-view = TRUE; the button
+ * contains an hbox. This hbox is there because the ComboBox
+ * needs to place a cell renderer, a separator, and an arrow in
+ * the button when appears-as-list is FALSE. */
+ GtkInnerWidgetInfo info = {GTK_TYPE_ARROW, &comboBoxArrow};
+ gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
+ } else if (GTK_IS_ARROW(buttonChild)) {
+ /* appears-as-list = TRUE, or cell-view = FALSE;
+ * the button only contains an arrow */
+ comboBoxArrow = buttonChild;
+ }
+
+ if (!comboBoxArrow) {
+ /* Shouldn't be reached with current internal gtk implementation;
+ * we gButtonArrowWidget as last resort fallback to avoid
+ * crashing. */
+ comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW);
+ } else {
+ g_object_add_weak_pointer(G_OBJECT(comboBoxArrow),
+ reinterpret_cast<gpointer*>(sWidgetStorage) +
+ MOZ_GTK_COMBOBOX_ENTRY_ARROW);
+ }
+
+ return comboBoxArrow;
+}
+
+static GtkWidget* CreateScrolledWindowWidget() {
+ GtkWidget* widget = gtk_scrolled_window_new(nullptr, nullptr);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateMenuSeparatorWidget() {
+ GtkWidget* widget = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(GetWidget(MOZ_GTK_MENUPOPUP)), widget);
+ return widget;
+}
+
+static GtkWidget* CreateTreeViewWidget() {
+ GtkWidget* widget = gtk_tree_view_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateTreeHeaderCellWidget() {
+ /*
+ * Some GTK engines paint the first and last cell
+ * of a TreeView header with a highlight.
+ * Since we do not know where our widget will be relative
+ * to the other buttons in the TreeView header, we must
+ * paint it as a button that is between two others,
+ * thus ensuring it is neither the first or last button
+ * in the header.
+ * GTK doesn't give us a way to do this explicitly,
+ * so we must paint with a button that is between two
+ * others.
+ */
+ GtkTreeViewColumn* firstTreeViewColumn;
+ GtkTreeViewColumn* middleTreeViewColumn;
+ GtkTreeViewColumn* lastTreeViewColumn;
+
+ GtkWidget* treeView = GetWidget(MOZ_GTK_TREEVIEW);
+
+ /* Create and append our three columns */
+ firstTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(firstTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), firstTreeViewColumn);
+
+ middleTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(middleTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), middleTreeViewColumn);
+
+ lastTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(lastTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), lastTreeViewColumn);
+
+ /* Use the middle column's header for our button */
+ return gtk_tree_view_column_get_button(middleTreeViewColumn);
+}
+
+static GtkWidget* CreateTreeHeaderSortArrowWidget() {
+ /* TODO, but it can't be NULL */
+ GtkWidget* widget = gtk_button_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateHPanedWidget() {
+ GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateVPanedWidget() {
+ GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateScaleWidget(GtkOrientation aOrientation) {
+ GtkWidget* widget = gtk_scale_new(aOrientation, nullptr);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateNotebookWidget() {
+ GtkWidget* widget = gtk_notebook_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+void GtkWindowSetTitlebar(GtkWindow* aWindow, GtkWidget* aWidget) {
+ static auto sGtkWindowSetTitlebar = (void (*)(GtkWindow*, GtkWidget*))dlsym(
+ RTLD_DEFAULT, "gtk_window_set_titlebar");
+ sGtkWindowSetTitlebar(aWindow, aWidget);
+}
+
+GtkWidget* GtkHeaderBarNew() {
+ static auto sGtkHeaderBarNewPtr =
+ (GtkWidget * (*)()) dlsym(RTLD_DEFAULT, "gtk_header_bar_new");
+ return sGtkHeaderBarNewPtr();
+}
+
+bool IsSolidCSDStyleUsed() {
+ static bool isSolidCSDStyleUsed = []() {
+ GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ GtkWindowSetTitlebar(GTK_WINDOW(window), GtkHeaderBarNew());
+ gtk_widget_realize(window);
+ GtkStyleContext* windowStyle = gtk_widget_get_style_context(window);
+ bool ret = gtk_style_context_has_class(windowStyle, "solid-csd");
+ gtk_widget_destroy(window);
+ return ret;
+ }();
+ return isSolidCSDStyleUsed;
+}
+
+static void CreateHeaderBarWidget(WidgetNodeType aAppearance) {
+ sWidgetStorage[aAppearance] = GtkHeaderBarNew();
+
+ GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ GtkStyleContext* style = gtk_widget_get_style_context(window);
+
+ if (aAppearance == MOZ_GTK_HEADER_BAR_MAXIMIZED) {
+ gtk_style_context_add_class(style, "maximized");
+ MOZ_ASSERT(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED] == nullptr,
+ "Window widget is already created!");
+ sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED] = window;
+ } else {
+ MOZ_ASSERT(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW] == nullptr,
+ "Window widget is already created!");
+ sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW] = window;
+ }
+
+ // Headerbar has to be placed to window with csd or solid-csd style
+ // to properly draw the decorated.
+ gtk_style_context_add_class(style,
+ IsSolidCSDStyleUsed() ? "solid-csd" : "csd");
+
+ GtkWidget* fixed = gtk_fixed_new();
+ gtk_container_add(GTK_CONTAINER(window), fixed);
+ gtk_container_add(GTK_CONTAINER(fixed), sWidgetStorage[aAppearance]);
+
+ // Emulate what create_titlebar() at gtkwindow.c does.
+ style = gtk_widget_get_style_context(sWidgetStorage[aAppearance]);
+ gtk_style_context_add_class(style, "titlebar");
+
+ // TODO: Define default-decoration titlebar style as workaround
+ // to ensure the titlebar buttons does not overflow outside.
+ // Recently the titlebar size is calculated as
+ // tab size + titlebar border/padding (default-decoration has 6px padding
+ // at default Adwaita theme).
+ // We need to fix titlebar size calculation to also include
+ // titlebar button sizes. (Bug 1419442)
+ gtk_style_context_add_class(style, "default-decoration");
+}
+
+#define ICON_SCALE_VARIANTS 2
+
+static void LoadWidgetIconPixbuf(GtkWidget* aWidgetIcon) {
+ GtkStyleContext* style = gtk_widget_get_style_context(aWidgetIcon);
+
+ const gchar* iconName;
+ GtkIconSize gtkIconSize;
+ gtk_image_get_icon_name(GTK_IMAGE(aWidgetIcon), &iconName, &gtkIconSize);
+
+ gint iconWidth, iconHeight;
+ gtk_icon_size_lookup(gtkIconSize, &iconWidth, &iconHeight);
+
+ /* Those are available since Gtk+ 3.10 as well as GtkHeaderBar */
+ static auto sGtkIconThemeLookupIconForScalePtr =
+ (GtkIconInfo *
+ (*)(GtkIconTheme*, const gchar*, gint, gint, GtkIconLookupFlags))
+ dlsym(RTLD_DEFAULT, "gtk_icon_theme_lookup_icon_for_scale");
+ static auto sGdkCairoSurfaceCreateFromPixbufPtr =
+ (cairo_surface_t * (*)(const GdkPixbuf*, int, GdkWindow*))
+ dlsym(RTLD_DEFAULT, "gdk_cairo_surface_create_from_pixbuf");
+
+ for (int scale = 1; scale < ICON_SCALE_VARIANTS + 1; scale++) {
+ GtkIconInfo* gtkIconInfo = sGtkIconThemeLookupIconForScalePtr(
+ gtk_icon_theme_get_default(), iconName, iconWidth, scale,
+ (GtkIconLookupFlags)0);
+
+ if (!gtkIconInfo) {
+ // We miss the icon, nothing to do here.
+ return;
+ }
+
+ gboolean unused;
+ GdkPixbuf* iconPixbuf = gtk_icon_info_load_symbolic_for_context(
+ gtkIconInfo, style, &unused, nullptr);
+ g_object_unref(G_OBJECT(gtkIconInfo));
+
+ cairo_surface_t* iconSurface =
+ sGdkCairoSurfaceCreateFromPixbufPtr(iconPixbuf, scale, nullptr);
+ g_object_unref(iconPixbuf);
+
+ nsAutoCString surfaceName;
+ surfaceName = nsPrintfCString("MozillaIconSurface%d", scale);
+ g_object_set_data_full(G_OBJECT(aWidgetIcon), surfaceName.get(),
+ iconSurface, (GDestroyNotify)cairo_surface_destroy);
+ }
+}
+
+cairo_surface_t* GetWidgetIconSurface(GtkWidget* aWidgetIcon, int aScale) {
+ if (aScale > ICON_SCALE_VARIANTS) aScale = ICON_SCALE_VARIANTS;
+
+ nsAutoCString surfaceName;
+ surfaceName = nsPrintfCString("MozillaIconSurface%d", aScale);
+ return (cairo_surface_t*)g_object_get_data(G_OBJECT(aWidgetIcon),
+ surfaceName.get());
+}
+
+static void CreateHeaderBarButton(GtkWidget* aParentWidget,
+ WidgetNodeType aAppearance) {
+ GtkWidget* widget = gtk_button_new();
+
+ // We have to add button to widget hierarchy now to pick
+ // right icon style at LoadWidgetIconPixbuf().
+ if (GTK_IS_BOX(aParentWidget)) {
+ gtk_box_pack_start(GTK_BOX(aParentWidget), widget, FALSE, FALSE, 0);
+ } else {
+ gtk_container_add(GTK_CONTAINER(aParentWidget), widget);
+ }
+
+ // We bypass GetWidget() here because we create all titlebar
+ // buttons at once when a first one is requested.
+ NS_ASSERTION(sWidgetStorage[aAppearance] == nullptr,
+ "Titlebar button is already created!");
+ sWidgetStorage[aAppearance] = widget;
+
+ // We need to show the button widget now as GtkBox does not
+ // place invisible widgets and we'll miss first-child/last-child
+ // css selectors at the buttons otherwise.
+ gtk_widget_show(widget);
+
+ GtkStyleContext* style = gtk_widget_get_style_context(widget);
+ gtk_style_context_add_class(style, "titlebutton");
+
+ GtkWidget* image = nullptr;
+ switch (aAppearance) {
+ case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
+ gtk_style_context_add_class(style, "close");
+ image = gtk_image_new_from_icon_name("window-close-symbolic",
+ GTK_ICON_SIZE_MENU);
+ break;
+ case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
+ gtk_style_context_add_class(style, "minimize");
+ image = gtk_image_new_from_icon_name("window-minimize-symbolic",
+ GTK_ICON_SIZE_MENU);
+ break;
+
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
+ gtk_style_context_add_class(style, "maximize");
+ image = gtk_image_new_from_icon_name("window-maximize-symbolic",
+ GTK_ICON_SIZE_MENU);
+ break;
+
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE:
+ gtk_style_context_add_class(style, "maximize");
+ image = gtk_image_new_from_icon_name("window-restore-symbolic",
+ GTK_ICON_SIZE_MENU);
+ break;
+ default:
+ break;
+ }
+
+ gtk_widget_set_valign(widget, GTK_ALIGN_CENTER);
+ g_object_set(image, "use-fallback", TRUE, NULL);
+ gtk_container_add(GTK_CONTAINER(widget), image);
+
+ // We bypass GetWidget() here by explicit sWidgetStorage[] update so
+ // invalidate the style as well as GetWidget() does.
+ style = gtk_widget_get_style_context(image);
+ gtk_style_context_invalidate(style);
+
+ LoadWidgetIconPixbuf(image);
+}
+
+static bool IsToolbarButtonEnabled(ButtonLayout* aButtonLayout,
+ size_t aButtonNums,
+ WidgetNodeType aAppearance) {
+ for (size_t i = 0; i < aButtonNums; i++) {
+ if (aButtonLayout[i].mType == aAppearance) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void CreateHeaderBarButtons() {
+ GtkWidget* headerBar = sWidgetStorage[MOZ_GTK_HEADER_BAR];
+ MOZ_ASSERT(headerBar != nullptr, "We're missing header bar widget!");
+
+ gint buttonSpacing = 6;
+ g_object_get(headerBar, "spacing", &buttonSpacing, nullptr);
+
+ GtkWidget* buttonBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, buttonSpacing);
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_HEADER_BAR)), buttonBox);
+ // We support only LTR headerbar layout for now.
+ gtk_style_context_add_class(gtk_widget_get_style_context(buttonBox),
+ GTK_STYLE_CLASS_LEFT);
+
+ ButtonLayout buttonLayout[TOOLBAR_BUTTONS];
+
+ size_t activeButtons =
+ GetGtkHeaderBarButtonLayout(mozilla::Span(buttonLayout), nullptr);
+
+ if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
+ MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE)) {
+ CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE);
+ }
+ if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
+ MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE)) {
+ CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE);
+ // We don't pack "restore" headerbar button to box as it's an icon
+ // placeholder. Pack it only to header bar to get correct style.
+ CreateHeaderBarButton(GetWidget(MOZ_GTK_HEADER_BAR),
+ MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE);
+ }
+ if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
+ MOZ_GTK_HEADER_BAR_BUTTON_CLOSE)) {
+ CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
+ }
+}
+
+static void CreateHeaderBar() {
+ CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR);
+ CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR_MAXIMIZED);
+ CreateHeaderBarButtons();
+}
+
+static GtkWidget* CreateWidget(WidgetNodeType aAppearance) {
+ switch (aAppearance) {
+ case MOZ_GTK_WINDOW:
+ return CreateWindowWidget();
+ case MOZ_GTK_WINDOW_CONTAINER:
+ return CreateWindowContainerWidget();
+ case MOZ_GTK_CHECKBUTTON_CONTAINER:
+ return CreateCheckboxWidget();
+ case MOZ_GTK_PROGRESSBAR:
+ return CreateProgressWidget();
+ case MOZ_GTK_RADIOBUTTON_CONTAINER:
+ return CreateRadiobuttonWidget();
+ case MOZ_GTK_SCROLLBAR_HORIZONTAL:
+ return CreateScrollbarWidget(aAppearance, GTK_ORIENTATION_HORIZONTAL);
+ case MOZ_GTK_SCROLLBAR_VERTICAL:
+ return CreateScrollbarWidget(aAppearance, GTK_ORIENTATION_VERTICAL);
+ case MOZ_GTK_MENUBAR:
+ return CreateMenuBarWidget();
+ case MOZ_GTK_MENUPOPUP:
+ return CreateMenuPopupWidget();
+ case MOZ_GTK_MENUSEPARATOR:
+ return CreateMenuSeparatorWidget();
+ case MOZ_GTK_EXPANDER:
+ return CreateExpanderWidget();
+ case MOZ_GTK_FRAME:
+ return CreateFrameWidget();
+ case MOZ_GTK_GRIPPER:
+ return CreateGripperWidget();
+ case MOZ_GTK_TOOLBAR:
+ return CreateToolbarWidget();
+ case MOZ_GTK_TOOLBAR_SEPARATOR:
+ return CreateToolbarSeparatorWidget();
+ case MOZ_GTK_INFO_BAR:
+ return CreateInfoBarWidget();
+ case MOZ_GTK_SPINBUTTON:
+ return CreateSpinWidget();
+ case MOZ_GTK_BUTTON:
+ return CreateButtonWidget();
+ case MOZ_GTK_TOGGLE_BUTTON:
+ return CreateToggleButtonWidget();
+ case MOZ_GTK_BUTTON_ARROW:
+ return CreateButtonArrowWidget();
+ case MOZ_GTK_ENTRY:
+ case MOZ_GTK_DROPDOWN_ENTRY:
+ return CreateEntryWidget();
+ case MOZ_GTK_SCROLLED_WINDOW:
+ return CreateScrolledWindowWidget();
+ case MOZ_GTK_TREEVIEW:
+ return CreateTreeViewWidget();
+ case MOZ_GTK_TREE_HEADER_CELL:
+ return CreateTreeHeaderCellWidget();
+ case MOZ_GTK_TREE_HEADER_SORTARROW:
+ return CreateTreeHeaderSortArrowWidget();
+ case MOZ_GTK_SPLITTER_HORIZONTAL:
+ return CreateHPanedWidget();
+ case MOZ_GTK_SPLITTER_VERTICAL:
+ return CreateVPanedWidget();
+ case MOZ_GTK_SCALE_HORIZONTAL:
+ return CreateScaleWidget(GTK_ORIENTATION_HORIZONTAL);
+ case MOZ_GTK_SCALE_VERTICAL:
+ return CreateScaleWidget(GTK_ORIENTATION_VERTICAL);
+ case MOZ_GTK_NOTEBOOK:
+ return CreateNotebookWidget();
+ case MOZ_GTK_COMBOBOX:
+ return CreateComboBoxWidget();
+ case MOZ_GTK_COMBOBOX_BUTTON:
+ return CreateComboBoxButtonWidget();
+ case MOZ_GTK_COMBOBOX_ARROW:
+ return CreateComboBoxArrowWidget();
+ case MOZ_GTK_COMBOBOX_SEPARATOR:
+ return CreateComboBoxSeparatorWidget();
+ case MOZ_GTK_COMBOBOX_ENTRY:
+ return CreateComboBoxEntryWidget();
+ case MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA:
+ return CreateComboBoxEntryTextareaWidget();
+ case MOZ_GTK_COMBOBOX_ENTRY_BUTTON:
+ return CreateComboBoxEntryButtonWidget();
+ case MOZ_GTK_COMBOBOX_ENTRY_ARROW:
+ return CreateComboBoxEntryArrowWidget();
+ case MOZ_GTK_HEADERBAR_WINDOW:
+ case MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED:
+ case MOZ_GTK_HEADER_BAR:
+ case MOZ_GTK_HEADER_BAR_MAXIMIZED:
+ case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE:
+ /* Create header bar widgets once and fill with child elements as we need
+ the header bar fully configured to get a correct style */
+ CreateHeaderBar();
+ return sWidgetStorage[aAppearance];
+ default:
+ /* Not implemented */
+ return nullptr;
+ }
+}
+
+GtkWidget* GetWidget(WidgetNodeType aAppearance) {
+ GtkWidget* widget = sWidgetStorage[aAppearance];
+ if (!widget) {
+ widget = CreateWidget(aAppearance);
+ // Some widgets (MOZ_GTK_COMBOBOX_SEPARATOR for instance) may not be
+ // available or implemented.
+ if (!widget) {
+ return nullptr;
+ }
+ // In GTK versions prior to 3.18, automatic invalidation of style contexts
+ // for widgets was delayed until the next resize event. Gecko however,
+ // typically uses the style context before the resize event runs and so an
+ // explicit invalidation may be required. This is necessary if a style
+ // property was retrieved before all changes were made to the style
+ // context. One such situation is where gtk_button_construct_child()
+ // retrieves the style property "image-spacing" during construction of the
+ // GtkButton, before its parent is set to provide inheritance of ancestor
+ // properties. More recent GTK versions do not need this, but do not
+ // re-resolve until required and so invalidation does not trigger
+ // unnecessary resolution in general.
+ GtkStyleContext* style = gtk_widget_get_style_context(widget);
+ gtk_style_context_invalidate(style);
+
+ sWidgetStorage[aAppearance] = widget;
+ }
+ return widget;
+}
+
+static void AddStyleClassesFromStyle(GtkStyleContext* aDest,
+ GtkStyleContext* aSrc) {
+ GList* classes = gtk_style_context_list_classes(aSrc);
+ for (GList* link = classes; link; link = link->next) {
+ gtk_style_context_add_class(aDest, static_cast<gchar*>(link->data));
+ }
+ g_list_free(classes);
+}
+
+GtkStyleContext* CreateStyleForWidget(GtkWidget* aWidget,
+ GtkStyleContext* aParentStyle) {
+ static auto sGtkWidgetClassGetCSSName =
+ reinterpret_cast<const char* (*)(GtkWidgetClass*)>(
+ dlsym(RTLD_DEFAULT, "gtk_widget_class_get_css_name"));
+
+ GtkWidgetClass* widgetClass = GTK_WIDGET_GET_CLASS(aWidget);
+ const gchar* name = sGtkWidgetClassGetCSSName
+ ? sGtkWidgetClassGetCSSName(widgetClass)
+ : nullptr;
+
+ GtkStyleContext* context =
+ CreateCSSNode(name, aParentStyle, G_TYPE_FROM_CLASS(widgetClass));
+
+ // Classes are stored on the style context instead of the path so that any
+ // future gtk_style_context_save() will inherit classes on the head CSS
+ // node, in the same way as happens when called on a style context owned by
+ // a widget.
+ //
+ // Classes can be stored on a GtkCssNodeDeclaration and/or the path.
+ // gtk_style_context_save() reuses the GtkCssNodeDeclaration, and appends a
+ // new object to the path, without copying the classes from the old path
+ // head. The new head picks up classes from the GtkCssNodeDeclaration, but
+ // not the path. GtkWidgets store their classes on the
+ // GtkCssNodeDeclaration, so make sure to add classes there.
+ //
+ // Picking up classes from the style context also means that
+ // https://bugzilla.gnome.org/show_bug.cgi?id=767312, which can stop
+ // gtk_widget_path_append_for_widget() from finding classes in GTK 3.20,
+ // is not a problem.
+ GtkStyleContext* widgetStyle = gtk_widget_get_style_context(aWidget);
+ AddStyleClassesFromStyle(context, widgetStyle);
+
+ // Release any floating reference on aWidget.
+ g_object_ref_sink(aWidget);
+ g_object_unref(aWidget);
+
+ return context;
+}
+
+static GtkStyleContext* CreateStyleForWidget(GtkWidget* aWidget,
+ WidgetNodeType aParentType) {
+ return CreateStyleForWidget(aWidget, GetWidgetRootStyle(aParentType));
+}
+
+GtkStyleContext* CreateCSSNode(const char* aName, GtkStyleContext* aParentStyle,
+ GType aType) {
+ static auto sGtkWidgetPathIterSetObjectName =
+ reinterpret_cast<void (*)(GtkWidgetPath*, gint, const char*)>(
+ dlsym(RTLD_DEFAULT, "gtk_widget_path_iter_set_object_name"));
+
+ GtkWidgetPath* path;
+ if (aParentStyle) {
+ path = gtk_widget_path_copy(gtk_style_context_get_path(aParentStyle));
+ // Copy classes from the parent style context to its corresponding node in
+ // the path, because GTK will only match against ancestor classes if they
+ // are on the path.
+ GList* classes = gtk_style_context_list_classes(aParentStyle);
+ for (GList* link = classes; link; link = link->next) {
+ gtk_widget_path_iter_add_class(path, -1, static_cast<gchar*>(link->data));
+ }
+ g_list_free(classes);
+ } else {
+ path = gtk_widget_path_new();
+ }
+
+ gtk_widget_path_append_type(path, aType);
+
+ if (sGtkWidgetPathIterSetObjectName) {
+ (*sGtkWidgetPathIterSetObjectName)(path, -1, aName);
+ }
+
+ GtkStyleContext* context = gtk_style_context_new();
+ gtk_style_context_set_path(context, path);
+ gtk_style_context_set_parent(context, aParentStyle);
+ gtk_widget_path_unref(path);
+
+ // In GTK 3.4, gtk_render_* functions use |theming_engine| on the style
+ // context without ensuring any style resolution sets it appropriately
+ // in style_data_lookup(). e.g.
+ // https://git.gnome.org/browse/gtk+/tree/gtk/gtkstylecontext.c?h=3.4.4#n3847
+ //
+ // That can result in incorrect drawing on first draw. To work around this,
+ // force a style look-up to set |theming_engine|. It is sufficient to do
+ // this only on context creation, instead of after every modification to the
+ // context, because themes typically (Ambiance and oxygen-gtk, at least) set
+ // the "engine" property with the '*' selector.
+ if (GTK_MAJOR_VERSION == 3 && gtk_get_minor_version() < 6) {
+ GdkRGBA unused;
+ gtk_style_context_get_color(context, GTK_STATE_FLAG_NORMAL, &unused);
+ }
+
+ return context;
+}
+
+// Return a style context matching that of the root CSS node of a widget.
+// This is used by all GTK versions.
+static GtkStyleContext* GetWidgetRootStyle(WidgetNodeType aNodeType) {
+ GtkStyleContext* style = sStyleStorage[aNodeType];
+ if (style) return style;
+
+ switch (aNodeType) {
+ case MOZ_GTK_MENUBARITEM:
+ style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUBAR);
+ break;
+ case MOZ_GTK_MENUITEM:
+ style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUPOPUP);
+ break;
+ case MOZ_GTK_CHECKMENUITEM:
+ style =
+ CreateStyleForWidget(gtk_check_menu_item_new(), MOZ_GTK_MENUPOPUP);
+ break;
+ case MOZ_GTK_RADIOMENUITEM:
+ style = CreateStyleForWidget(gtk_radio_menu_item_new(nullptr),
+ MOZ_GTK_MENUPOPUP);
+ break;
+ case MOZ_GTK_TEXT_VIEW:
+ style =
+ CreateStyleForWidget(gtk_text_view_new(), MOZ_GTK_SCROLLED_WINDOW);
+ break;
+ case MOZ_GTK_TOOLTIP:
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ // The tooltip style class is added first in CreateTooltipWidget()
+ // and transfered to style in CreateStyleForWidget().
+ GtkWidget* tooltipWindow = CreateTooltipWidget();
+ style = CreateStyleForWidget(tooltipWindow, nullptr);
+ gtk_widget_destroy(tooltipWindow); // Release GtkWindow self-reference.
+ } else {
+ // We create this from the path because GtkTooltipWindow is not public.
+ style = CreateCSSNode("tooltip", nullptr, GTK_TYPE_TOOLTIP);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND);
+ }
+ break;
+ case MOZ_GTK_TOOLTIP_BOX:
+ style = CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0),
+ MOZ_GTK_TOOLTIP);
+ break;
+ case MOZ_GTK_TOOLTIP_BOX_LABEL:
+ style = CreateStyleForWidget(gtk_label_new(nullptr), MOZ_GTK_TOOLTIP_BOX);
+ break;
+ default:
+ GtkWidget* widget = GetWidget(aNodeType);
+ MOZ_ASSERT(widget);
+ return gtk_widget_get_style_context(widget);
+ }
+
+ MOZ_ASSERT(style);
+ sStyleStorage[aNodeType] = style;
+ return style;
+}
+
+static GtkStyleContext* CreateChildCSSNode(const char* aName,
+ WidgetNodeType aParentNodeType) {
+ return CreateCSSNode(aName, GetCssNodeStyleInternal(aParentNodeType));
+}
+
+// Create a style context equivalent to a saved root style context of
+// |aAppearance| with |aStyleClass| as an additional class. This is used to
+// produce a context equivalent to what GTK versions < 3.20 use for many
+// internal parts of widgets.
+static GtkStyleContext* CreateSubStyleWithClass(WidgetNodeType aAppearance,
+ const gchar* aStyleClass) {
+ static auto sGtkWidgetPathIterGetObjectName =
+ reinterpret_cast<const char* (*)(const GtkWidgetPath*, gint)>(
+ dlsym(RTLD_DEFAULT, "gtk_widget_path_iter_get_object_name"));
+
+ GtkStyleContext* parentStyle = GetWidgetRootStyle(aAppearance);
+
+ // Create a new context that behaves like |parentStyle| would after
+ // gtk_style_context_save(parentStyle).
+ //
+ // Avoiding gtk_style_context_save() avoids the need to manage the
+ // restore, and a new context permits caching style resolution.
+ //
+ // gtk_style_context_save(context) changes the node hierarchy of |context|
+ // to add a new GtkCssNodeDeclaration that is a copy of its original node.
+ // The new node is a child of the original node, and so the new heirarchy is
+ // one level deeper. The new node receives the same classes as the
+ // original, but any changes to the classes on |context| will change only
+ // the new node. The new node inherits properties from the original node
+ // (which retains the original heirarchy and classes) and matches CSS rules
+ // with the new heirarchy and any changes to the classes.
+ //
+ // The change in hierarchy can produce some surprises in matching theme CSS
+ // rules (e.g. https://bugzilla.gnome.org/show_bug.cgi?id=761870#c2), but it
+ // is important here to produce the same behavior so that rules match the
+ // same widget parts in Gecko as they do in GTK.
+ //
+ // When using public GTK API to construct style contexts, a widget path is
+ // required. CSS rules are not matched against the style context heirarchy
+ // but according to the heirarchy in the widget path. The path that matches
+ // the same CSS rules as a saved context is like the path of |parentStyle|
+ // but with an extra copy of the head (last) object appended. Setting
+ // |parentStyle| as the parent context provides the same inheritance of
+ // properties from the widget root node.
+ const GtkWidgetPath* parentPath = gtk_style_context_get_path(parentStyle);
+ const gchar* name = sGtkWidgetPathIterGetObjectName
+ ? sGtkWidgetPathIterGetObjectName(parentPath, -1)
+ : nullptr;
+ GType objectType = gtk_widget_path_get_object_type(parentPath);
+
+ GtkStyleContext* style = CreateCSSNode(name, parentStyle, objectType);
+
+ // Start with the same classes on the new node as were on |parentStyle|.
+ // GTK puts no regions or junction_sides on widget root nodes, and so there
+ // is no need to copy these.
+ AddStyleClassesFromStyle(style, parentStyle);
+
+ gtk_style_context_add_class(style, aStyleClass);
+ return style;
+}
+
+/* GetCssNodeStyleInternal is used by Gtk >= 3.20 */
+static GtkStyleContext* GetCssNodeStyleInternal(WidgetNodeType aNodeType) {
+ GtkStyleContext* style = sStyleStorage[aNodeType];
+ if (style) return style;
+
+ switch (aNodeType) {
+ case MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL:
+ style = CreateChildCSSNode("contents", MOZ_GTK_SCROLLBAR_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
+ MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
+ MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL:
+ style = CreateChildCSSNode("contents", MOZ_GTK_SCROLLBAR_VERTICAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
+ MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
+ MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_BUTTON:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_BUTTON,
+ MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL);
+ break;
+ case MOZ_GTK_RADIOBUTTON:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO,
+ MOZ_GTK_RADIOBUTTON_CONTAINER);
+ break;
+ case MOZ_GTK_CHECKBUTTON:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK,
+ MOZ_GTK_CHECKBUTTON_CONTAINER);
+ break;
+ case MOZ_GTK_RADIOMENUITEM_INDICATOR:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO, MOZ_GTK_RADIOMENUITEM);
+ break;
+ case MOZ_GTK_CHECKMENUITEM_INDICATOR:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK, MOZ_GTK_CHECKMENUITEM);
+ break;
+ case MOZ_GTK_PROGRESS_TROUGH:
+ /* Progress bar background (trough) */
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, MOZ_GTK_PROGRESSBAR);
+ break;
+ case MOZ_GTK_PROGRESS_CHUNK:
+ style = CreateChildCSSNode("progress", MOZ_GTK_PROGRESS_TROUGH);
+ break;
+ case MOZ_GTK_GRIPPER:
+ // TODO - create from CSS node
+ style = CreateSubStyleWithClass(MOZ_GTK_GRIPPER, GTK_STYLE_CLASS_GRIP);
+ break;
+ case MOZ_GTK_INFO_BAR:
+ // TODO - create from CSS node
+ style = CreateSubStyleWithClass(MOZ_GTK_INFO_BAR, GTK_STYLE_CLASS_INFO);
+ break;
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ // TODO - create from CSS node
+ style =
+ CreateSubStyleWithClass(MOZ_GTK_SPINBUTTON, GTK_STYLE_CLASS_ENTRY);
+ break;
+ case MOZ_GTK_SCROLLED_WINDOW:
+ // TODO - create from CSS node
+ style = CreateSubStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
+ GTK_STYLE_CLASS_FRAME);
+ break;
+ case MOZ_GTK_TEXT_VIEW_TEXT_SELECTION:
+ style = CreateChildCSSNode("selection", MOZ_GTK_TEXT_VIEW_TEXT);
+ break;
+ case MOZ_GTK_TEXT_VIEW_TEXT:
+ case MOZ_GTK_RESIZER:
+ style = CreateChildCSSNode("text", MOZ_GTK_TEXT_VIEW);
+ if (aNodeType == MOZ_GTK_RESIZER) {
+ // The "grip" class provides the correct builtin icon from
+ // gtk_render_handle(). The icon is drawn with shaded variants of
+ // the background color, and so a transparent background would lead to
+ // a transparent resizer. gtk_render_handle() also uses the
+ // background color to draw a background, and so this style otherwise
+ // matches what is used in GtkTextView to match the background with
+ // textarea elements.
+ GdkRGBA color;
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
+ &color);
+ if (color.alpha == 0.0) {
+ g_object_unref(style);
+ style = CreateStyleForWidget(gtk_text_view_new(),
+ MOZ_GTK_SCROLLED_WINDOW);
+ }
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_GRIP);
+ }
+ break;
+ case MOZ_GTK_FRAME_BORDER:
+ style = CreateChildCSSNode("border", MOZ_GTK_FRAME);
+ break;
+ case MOZ_GTK_TREEVIEW_VIEW:
+ // TODO - create from CSS node
+ style = CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_VIEW);
+ break;
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ // TODO - create from CSS node
+ style =
+ CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_EXPANDER);
+ break;
+ case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL:
+ style = CreateChildCSSNode("separator", MOZ_GTK_SPLITTER_HORIZONTAL);
+ break;
+ case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL:
+ style = CreateChildCSSNode("separator", MOZ_GTK_SPLITTER_VERTICAL);
+ break;
+ case MOZ_GTK_SCALE_CONTENTS_HORIZONTAL:
+ style = CreateChildCSSNode("contents", MOZ_GTK_SCALE_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCALE_CONTENTS_VERTICAL:
+ style = CreateChildCSSNode("contents", MOZ_GTK_SCALE_VERTICAL);
+ break;
+ case MOZ_GTK_SCALE_TROUGH_HORIZONTAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
+ MOZ_GTK_SCALE_CONTENTS_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCALE_TROUGH_VERTICAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
+ MOZ_GTK_SCALE_CONTENTS_VERTICAL);
+ break;
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
+ MOZ_GTK_SCALE_TROUGH_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
+ MOZ_GTK_SCALE_TROUGH_VERTICAL);
+ break;
+ case MOZ_GTK_TAB_TOP: {
+ // TODO - create from CSS node
+ style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_TOP);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ break;
+ }
+ case MOZ_GTK_TAB_BOTTOM: {
+ // TODO - create from CSS node
+ style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_BOTTOM);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ break;
+ }
+ case MOZ_GTK_NOTEBOOK:
+ case MOZ_GTK_NOTEBOOK_HEADER:
+ case MOZ_GTK_TABPANELS:
+ case MOZ_GTK_TAB_SCROLLARROW: {
+ // TODO - create from CSS node
+ GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK);
+ return gtk_widget_get_style_context(widget);
+ }
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE: {
+ NS_ASSERTION(
+ false, "MOZ_GTK_HEADER_BAR_BUTTON_RESTORE is used as an icon only!");
+ return nullptr;
+ }
+ case MOZ_GTK_WINDOW_DECORATION: {
+ GtkStyleContext* parentStyle =
+ CreateSubStyleWithClass(MOZ_GTK_WINDOW, "csd");
+ style = CreateCSSNode("decoration", parentStyle);
+ g_object_unref(parentStyle);
+ break;
+ }
+ case MOZ_GTK_WINDOW_DECORATION_SOLID: {
+ GtkStyleContext* parentStyle =
+ CreateSubStyleWithClass(MOZ_GTK_WINDOW, "solid-csd");
+ style = CreateCSSNode("decoration", parentStyle);
+ g_object_unref(parentStyle);
+ break;
+ }
+ default:
+ return GetWidgetRootStyle(aNodeType);
+ }
+
+ MOZ_ASSERT(style, "missing style context for node type");
+ sStyleStorage[aNodeType] = style;
+ return style;
+}
+
+/* GetWidgetStyleInternal is used by Gtk < 3.20 */
+static GtkStyleContext* GetWidgetStyleInternal(WidgetNodeType aNodeType) {
+ GtkStyleContext* style = sStyleStorage[aNodeType];
+ if (style) return style;
+
+ switch (aNodeType) {
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL,
+ GTK_STYLE_CLASS_TROUGH);
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL,
+ GTK_STYLE_CLASS_SLIDER);
+ break;
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL,
+ GTK_STYLE_CLASS_TROUGH);
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL,
+ GTK_STYLE_CLASS_SLIDER);
+ break;
+ case MOZ_GTK_RADIOBUTTON:
+ style = CreateSubStyleWithClass(MOZ_GTK_RADIOBUTTON_CONTAINER,
+ GTK_STYLE_CLASS_RADIO);
+ break;
+ case MOZ_GTK_CHECKBUTTON:
+ style = CreateSubStyleWithClass(MOZ_GTK_CHECKBUTTON_CONTAINER,
+ GTK_STYLE_CLASS_CHECK);
+ break;
+ case MOZ_GTK_RADIOMENUITEM_INDICATOR:
+ style =
+ CreateSubStyleWithClass(MOZ_GTK_RADIOMENUITEM, GTK_STYLE_CLASS_RADIO);
+ break;
+ case MOZ_GTK_CHECKMENUITEM_INDICATOR:
+ style =
+ CreateSubStyleWithClass(MOZ_GTK_CHECKMENUITEM, GTK_STYLE_CLASS_CHECK);
+ break;
+ case MOZ_GTK_PROGRESS_TROUGH:
+ style =
+ CreateSubStyleWithClass(MOZ_GTK_PROGRESSBAR, GTK_STYLE_CLASS_TROUGH);
+ break;
+ case MOZ_GTK_PROGRESS_CHUNK:
+ style = CreateSubStyleWithClass(MOZ_GTK_PROGRESSBAR,
+ GTK_STYLE_CLASS_PROGRESSBAR);
+ gtk_style_context_remove_class(style, GTK_STYLE_CLASS_TROUGH);
+ break;
+ case MOZ_GTK_GRIPPER:
+ style = CreateSubStyleWithClass(MOZ_GTK_GRIPPER, GTK_STYLE_CLASS_GRIP);
+ break;
+ case MOZ_GTK_INFO_BAR:
+ style = CreateSubStyleWithClass(MOZ_GTK_INFO_BAR, GTK_STYLE_CLASS_INFO);
+ break;
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ style =
+ CreateSubStyleWithClass(MOZ_GTK_SPINBUTTON, GTK_STYLE_CLASS_ENTRY);
+ break;
+ case MOZ_GTK_SCROLLED_WINDOW:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
+ GTK_STYLE_CLASS_FRAME);
+ break;
+ case MOZ_GTK_TEXT_VIEW_TEXT:
+ case MOZ_GTK_RESIZER:
+ // GTK versions prior to 3.20 do not have the view class on the root
+ // node, but add this to determine the background for the text window.
+ style = CreateSubStyleWithClass(MOZ_GTK_TEXT_VIEW, GTK_STYLE_CLASS_VIEW);
+ if (aNodeType == MOZ_GTK_RESIZER) {
+ // The "grip" class provides the correct builtin icon from
+ // gtk_render_handle(). The icon is drawn with shaded variants of
+ // the background color, and so a transparent background would lead to
+ // a transparent resizer. gtk_render_handle() also uses the
+ // background color to draw a background, and so this style otherwise
+ // matches MOZ_GTK_TEXT_VIEW_TEXT to match the background with
+ // textarea elements. GtkTextView creates a separate text window and
+ // so the background should not be transparent.
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_GRIP);
+ }
+ break;
+ case MOZ_GTK_FRAME_BORDER:
+ return GetWidgetRootStyle(MOZ_GTK_FRAME);
+ case MOZ_GTK_TREEVIEW_VIEW:
+ style = CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_VIEW);
+ break;
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ style =
+ CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_EXPANDER);
+ break;
+ case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SPLITTER_HORIZONTAL,
+ GTK_STYLE_CLASS_PANE_SEPARATOR);
+ break;
+ case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SPLITTER_VERTICAL,
+ GTK_STYLE_CLASS_PANE_SEPARATOR);
+ break;
+ case MOZ_GTK_SCALE_TROUGH_HORIZONTAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL,
+ GTK_STYLE_CLASS_TROUGH);
+ break;
+ case MOZ_GTK_SCALE_TROUGH_VERTICAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCALE_VERTICAL,
+ GTK_STYLE_CLASS_TROUGH);
+ break;
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL,
+ GTK_STYLE_CLASS_SLIDER);
+ break;
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCALE_VERTICAL,
+ GTK_STYLE_CLASS_SLIDER);
+ break;
+ case MOZ_GTK_TAB_TOP:
+ style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_TOP);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ break;
+ case MOZ_GTK_TAB_BOTTOM:
+ style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_BOTTOM);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ break;
+ case MOZ_GTK_NOTEBOOK:
+ case MOZ_GTK_NOTEBOOK_HEADER:
+ case MOZ_GTK_TABPANELS:
+ case MOZ_GTK_TAB_SCROLLARROW: {
+ GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK);
+ return gtk_widget_get_style_context(widget);
+ }
+ default:
+ return GetWidgetRootStyle(aNodeType);
+ }
+
+ MOZ_ASSERT(style);
+ sStyleStorage[aNodeType] = style;
+ return style;
+}
+
+void ResetWidgetCache(void) {
+ for (int i = 0; i < MOZ_GTK_WIDGET_NODE_COUNT; i++) {
+ if (sStyleStorage[i]) g_object_unref(sStyleStorage[i]);
+ }
+ mozilla::PodArrayZero(sStyleStorage);
+
+ /* This will destroy all of our widgets */
+ if (sWidgetStorage[MOZ_GTK_WINDOW]) {
+ gtk_widget_destroy(sWidgetStorage[MOZ_GTK_WINDOW]);
+ }
+ if (sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW]) {
+ gtk_widget_destroy(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW]);
+ }
+ if (sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED]) {
+ gtk_widget_destroy(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED]);
+ }
+
+ /* Clear already freed arrays */
+ mozilla::PodArrayZero(sWidgetStorage);
+}
+
+GtkStyleContext* GetStyleContext(WidgetNodeType aNodeType, int aScale,
+ GtkTextDirection aDirection,
+ GtkStateFlags aStateFlags) {
+ GtkStyleContext* style;
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ style = GetWidgetStyleInternal(aNodeType);
+ } else {
+ style = GetCssNodeStyleInternal(aNodeType);
+ StyleContextSetScale(style, aScale);
+ }
+ bool stateChanged = false;
+ bool stateHasDirection = gtk_get_minor_version() >= 8;
+ GtkStateFlags oldState = gtk_style_context_get_state(style);
+ MOZ_ASSERT(!(aStateFlags & (STATE_FLAG_DIR_LTR | STATE_FLAG_DIR_RTL)));
+ unsigned newState = aStateFlags;
+ if (stateHasDirection) {
+ switch (aDirection) {
+ case GTK_TEXT_DIR_LTR:
+ newState |= STATE_FLAG_DIR_LTR;
+ break;
+ case GTK_TEXT_DIR_RTL:
+ newState |= STATE_FLAG_DIR_RTL;
+ break;
+ default:
+ MOZ_FALLTHROUGH_ASSERT("Bad GtkTextDirection");
+ case GTK_TEXT_DIR_NONE:
+ // GtkWidget uses a default direction if neither is explicitly
+ // specified, but here DIR_NONE is interpreted as meaning the
+ // direction is not important, so don't change the direction
+ // unnecessarily.
+ newState |= oldState & (STATE_FLAG_DIR_LTR | STATE_FLAG_DIR_RTL);
+ }
+ } else if (aDirection != GTK_TEXT_DIR_NONE) {
+ GtkTextDirection oldDirection = gtk_style_context_get_direction(style);
+ if (aDirection != oldDirection) {
+ gtk_style_context_set_direction(style, aDirection);
+ stateChanged = true;
+ }
+ }
+ if (oldState != newState) {
+ gtk_style_context_set_state(style, static_cast<GtkStateFlags>(newState));
+ stateChanged = true;
+ }
+ // This invalidate is necessary for unsaved style contexts from GtkWidgets
+ // in pre-3.18 GTK, because automatic invalidation of such contexts
+ // was delayed until a resize event runs.
+ //
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1272194#c7
+ //
+ // Avoid calling invalidate on contexts that are not owned and constructed
+ // by widgets to avoid performing build_properties() (in 3.16 stylecontext.c)
+ // unnecessarily early.
+ if (stateChanged && sWidgetStorage[aNodeType]) {
+ gtk_style_context_invalidate(style);
+ }
+ return style;
+}
+
+GtkStyleContext* CreateStyleContextWithStates(WidgetNodeType aNodeType,
+ int aScale,
+ GtkTextDirection aDirection,
+ GtkStateFlags aStateFlags) {
+ GtkStyleContext* style =
+ GetStyleContext(aNodeType, aScale, aDirection, aStateFlags);
+ GtkWidgetPath* path = gtk_widget_path_copy(gtk_style_context_get_path(style));
+
+ static auto sGtkWidgetPathIterGetState =
+ (GtkStateFlags(*)(const GtkWidgetPath*, gint))dlsym(
+ RTLD_DEFAULT, "gtk_widget_path_iter_get_state");
+ static auto sGtkWidgetPathIterSetState =
+ (void (*)(GtkWidgetPath*, gint, GtkStateFlags))dlsym(
+ RTLD_DEFAULT, "gtk_widget_path_iter_set_state");
+
+ int pathLength = gtk_widget_path_length(path);
+ for (int i = 0; i < pathLength; i++) {
+ unsigned state = aStateFlags | sGtkWidgetPathIterGetState(path, i);
+ sGtkWidgetPathIterSetState(path, i, GtkStateFlags(state));
+ }
+
+ style = gtk_style_context_new();
+ gtk_style_context_set_path(style, path);
+ gtk_widget_path_unref(path);
+
+ return style;
+}
+
+void StyleContextSetScale(GtkStyleContext* style, gint aScaleFactor) {
+ // Support HiDPI styles on Gtk 3.20+
+ static auto sGtkStyleContextSetScalePtr =
+ (void (*)(GtkStyleContext*, gint))dlsym(RTLD_DEFAULT,
+ "gtk_style_context_set_scale");
+ if (sGtkStyleContextSetScalePtr && style) {
+ sGtkStyleContextSetScalePtr(style, aScaleFactor);
+ }
+}
diff --git a/widget/gtk/WidgetStyleCache.h b/widget/gtk/WidgetStyleCache.h
new file mode 100644
index 0000000000..13fa53d4b4
--- /dev/null
+++ b/widget/gtk/WidgetStyleCache.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 WidgetStyleCache_h
+#define WidgetStyleCache_h
+
+#include <gtk/gtk.h>
+#include "gtkdrawing.h"
+
+GtkWidget* GetWidget(WidgetNodeType aNodeType);
+
+cairo_surface_t* GetWidgetIconSurface(GtkWidget* aWidgetIcon, int aScale);
+
+/*
+ * Return a new style context based on aWidget, as a child of aParentStyle.
+ * If aWidget still has a floating reference, then it is sunk and released.
+ */
+GtkStyleContext* CreateStyleForWidget(GtkWidget* aWidget,
+ GtkStyleContext* aParentStyle);
+
+GtkStyleContext* CreateCSSNode(const char* aName, GtkStyleContext* aParentStyle,
+ GType aType = G_TYPE_NONE);
+
+/*
+ * Returns a pointer to a style context for the specified node and state.
+ * aStateFlags is applied only to last widget in css style path,
+ * for instance GetStyleContext(MOZ_GTK_BUTTON, .., GTK_STATE_FLAG_HOVER)
+ * you get "window button:hover" css selector.
+ * If you want aStateFlags applied to all path elements use
+ * CreateStyleContextWithStates().
+ *
+ * The context is owned by WidgetStyleCache. Do not unref.
+ */
+GtkStyleContext* GetStyleContext(
+ WidgetNodeType aNodeType, int aScale = 1,
+ GtkTextDirection aDirection = GTK_TEXT_DIR_NONE,
+ GtkStateFlags aStateFlags = GTK_STATE_FLAG_NORMAL);
+
+/*
+ * Returns a pointer to a style context for the specified node
+ * and state applied to all elements at widget style path.
+ *
+ * The context is owned by caller and must be released by g_object_unref().
+ */
+GtkStyleContext* CreateStyleContextWithStates(
+ WidgetNodeType aNodeType, int aScale = 1,
+ GtkTextDirection aDirection = GTK_TEXT_DIR_NONE,
+ GtkStateFlags aStateFlags = GTK_STATE_FLAG_NORMAL);
+
+void ResetWidgetCache(void);
+
+bool IsSolidCSDStyleUsed();
+
+void StyleContextSetScale(GtkStyleContext* style, gint aScaleFactor);
+
+#endif // WidgetStyleCache_h
diff --git a/widget/gtk/WidgetTraceEvent.cpp b/widget/gtk/WidgetTraceEvent.cpp
new file mode 100644
index 0000000000..161e5302cc
--- /dev/null
+++ b/widget/gtk/WidgetTraceEvent.cpp
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/WidgetTraceEvent.h"
+
+#include <glib.h>
+#include <mozilla/CondVar.h>
+#include <mozilla/Mutex.h>
+#include <stdio.h>
+
+using mozilla::CondVar;
+using mozilla::Mutex;
+using mozilla::MutexAutoLock;
+
+namespace {
+
+Mutex* sMutex = nullptr;
+CondVar* sCondVar = nullptr;
+bool sTracerProcessed = false;
+
+// This function is called from the main (UI) thread.
+gboolean TracerCallback(gpointer data) {
+ mozilla::SignalTracerThread();
+ return FALSE;
+}
+
+} // namespace
+
+namespace mozilla {
+
+bool InitWidgetTracing() {
+ sMutex = new Mutex("Event tracer thread mutex");
+ sCondVar = new CondVar(*sMutex, "Event tracer thread condvar");
+ return true;
+}
+
+void CleanUpWidgetTracing() {
+ delete sMutex;
+ delete sCondVar;
+ sMutex = nullptr;
+ sCondVar = nullptr;
+}
+
+// This function is called from the background tracer thread.
+bool FireAndWaitForTracerEvent() {
+ MOZ_ASSERT(sMutex && sCondVar, "Tracing not initialized!");
+
+ // Send a default-priority idle event through the
+ // event loop, and wait for it to finish.
+ MutexAutoLock lock(*sMutex);
+ MOZ_ASSERT(!sTracerProcessed, "Tracer synchronization state is wrong");
+ g_idle_add_full(G_PRIORITY_DEFAULT, TracerCallback, nullptr, nullptr);
+ while (!sTracerProcessed) sCondVar->Wait();
+ sTracerProcessed = false;
+ return true;
+}
+
+void SignalTracerThread() {
+ if (!sMutex || !sCondVar) return;
+ MutexAutoLock lock(*sMutex);
+ if (!sTracerProcessed) {
+ sTracerProcessed = true;
+ sCondVar->Notify();
+ }
+}
+
+} // namespace mozilla
diff --git a/widget/gtk/WidgetUtilsGtk.cpp b/widget/gtk/WidgetUtilsGtk.cpp
new file mode 100644
index 0000000000..a0299bdf14
--- /dev/null
+++ b/widget/gtk/WidgetUtilsGtk.cpp
@@ -0,0 +1,52 @@
+/* -*- 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 "WidgetUtilsGtk.h"
+#include "nsWindow.h"
+#include <gtk/gtk.h>
+
+namespace mozilla {
+
+namespace widget {
+
+int32_t WidgetUtilsGTK::IsTouchDeviceSupportPresent() {
+ int32_t result = 0;
+ GdkDisplay* display = gdk_display_get_default();
+ if (!display) {
+ return 0;
+ }
+
+ GdkDeviceManager* manager = gdk_display_get_device_manager(display);
+ if (!manager) {
+ return 0;
+ }
+
+ GList* devices =
+ gdk_device_manager_list_devices(manager, GDK_DEVICE_TYPE_SLAVE);
+ GList* list = devices;
+
+ while (devices) {
+ GdkDevice* device = static_cast<GdkDevice*>(devices->data);
+ if (gdk_device_get_source(device) == GDK_SOURCE_TOUCHSCREEN) {
+ result = 1;
+ break;
+ }
+ devices = devices->next;
+ }
+
+ if (list) {
+ g_list_free(list);
+ }
+
+ return result;
+}
+
+bool IsMainWindowTransparent() {
+ return nsWindow::IsToplevelWindowTransparent();
+}
+
+} // namespace widget
+
+} // namespace mozilla
diff --git a/widget/gtk/WidgetUtilsGtk.h b/widget/gtk/WidgetUtilsGtk.h
new file mode 100644
index 0000000000..5ede9aa520
--- /dev/null
+++ b/widget/gtk/WidgetUtilsGtk.h
@@ -0,0 +1,26 @@
+/* -*- 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 WidgetUtilsGtk_h__
+#define WidgetUtilsGtk_h__
+
+#include <stdint.h>
+
+namespace mozilla {
+namespace widget {
+
+class WidgetUtilsGTK {
+ public:
+ /* See WidgetUtils::IsTouchDeviceSupportPresent(). */
+ static int32_t IsTouchDeviceSupportPresent();
+};
+
+bool IsMainWindowTransparent();
+
+} // namespace widget
+
+} // namespace mozilla
+
+#endif // WidgetUtilsGtk_h__
diff --git a/widget/gtk/WindowSurfaceProvider.cpp b/widget/gtk/WindowSurfaceProvider.cpp
new file mode 100644
index 0000000000..282847a512
--- /dev/null
+++ b/widget/gtk/WindowSurfaceProvider.cpp
@@ -0,0 +1,131 @@
+/* -*- 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 "WindowSurfaceProvider.h"
+
+#include "gfxPlatformGtk.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "nsWindow.h"
+#include "WindowSurfaceX11Image.h"
+#include "WindowSurfaceX11SHM.h"
+#include "WindowSurfaceXRender.h"
+#ifdef MOZ_WAYLAND
+# include "WindowSurfaceWayland.h"
+#endif
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::layers;
+
+WindowSurfaceProvider::WindowSurfaceProvider()
+ : mIsX11Display(false),
+ mXDisplay(nullptr),
+ mXWindow(0),
+ mXVisual(nullptr),
+ mXDepth(0),
+ mWindowSurface(nullptr)
+#ifdef MOZ_WAYLAND
+ ,
+ mWidget(nullptr)
+#endif
+ ,
+ mIsShaped(false) {
+}
+
+void WindowSurfaceProvider::Initialize(Display* aDisplay, Window aWindow,
+ Visual* aVisual, int aDepth,
+ bool aIsShaped) {
+ // We should not be initialized
+ MOZ_ASSERT(!mXDisplay);
+
+ // This should also be a valid initialization
+ MOZ_ASSERT(aDisplay && aWindow != X11None && aVisual);
+
+ mXDisplay = aDisplay;
+ mXWindow = aWindow;
+ mXVisual = aVisual;
+ mXDepth = aDepth;
+ mIsShaped = aIsShaped;
+ mIsX11Display = true;
+}
+
+#ifdef MOZ_WAYLAND
+void WindowSurfaceProvider::Initialize(nsWindow* aWidget) {
+ mWidget = aWidget;
+ mIsX11Display = false;
+}
+#endif
+
+void WindowSurfaceProvider::CleanupResources() { mWindowSurface = nullptr; }
+
+UniquePtr<WindowSurface> WindowSurfaceProvider::CreateWindowSurface() {
+#ifdef MOZ_WAYLAND
+ if (!mIsX11Display) {
+ LOGDRAW(("Drawing to nsWindow %p will use wl_surface\n", mWidget));
+ return MakeUnique<WindowSurfaceWayland>(mWidget);
+ }
+#endif
+
+ // We should be initialized
+ MOZ_ASSERT(mXDisplay);
+
+ // Blit to the window with the following priority:
+ // 1. XRender (iff XRender is enabled && we are in-process)
+ // 2. MIT-SHM
+ // 3. XPutImage
+ if (!mIsShaped && gfx::gfxVars::UseXRender()) {
+ LOGDRAW(("Drawing to Window 0x%lx will use XRender\n", mXWindow));
+ return MakeUnique<WindowSurfaceXRender>(mXDisplay, mXWindow, mXVisual,
+ mXDepth);
+ }
+
+#ifdef MOZ_HAVE_SHMIMAGE
+ if (!mIsShaped && nsShmImage::UseShm()) {
+ LOGDRAW(("Drawing to Window 0x%lx will use MIT-SHM\n", mXWindow));
+ return MakeUnique<WindowSurfaceX11SHM>(mXDisplay, mXWindow, mXVisual,
+ mXDepth);
+ }
+#endif // MOZ_HAVE_SHMIMAGE
+
+ LOGDRAW(("Drawing to Window 0x%lx will use XPutImage\n", mXWindow));
+ return MakeUnique<WindowSurfaceX11Image>(mXDisplay, mXWindow, mXVisual,
+ mXDepth, mIsShaped);
+}
+
+already_AddRefed<gfx::DrawTarget>
+WindowSurfaceProvider::StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion, layers::BufferMode* aBufferMode) {
+ if (aInvalidRegion.IsEmpty()) return nullptr;
+
+ if (!mWindowSurface) {
+ mWindowSurface = CreateWindowSurface();
+ if (!mWindowSurface) return nullptr;
+ }
+
+ *aBufferMode = BufferMode::BUFFER_NONE;
+ RefPtr<gfx::DrawTarget> dt = nullptr;
+ if (!(dt = mWindowSurface->Lock(aInvalidRegion)) && mIsX11Display &&
+ !mWindowSurface->IsFallback()) {
+ // We can't use WindowSurfaceX11Image fallback on Wayland but
+ // Lock() call on WindowSurfaceWayland should never fail.
+ gfxWarningOnce()
+ << "Failed to lock WindowSurface, falling back to XPutImage backend.";
+ mWindowSurface = MakeUnique<WindowSurfaceX11Image>(
+ mXDisplay, mXWindow, mXVisual, mXDepth, mIsShaped);
+ dt = mWindowSurface->Lock(aInvalidRegion);
+ }
+ return dt.forget();
+}
+
+void WindowSurfaceProvider::EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
+ if (mWindowSurface) mWindowSurface->Commit(aInvalidRegion);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WindowSurfaceProvider.h b/widget/gtk/WindowSurfaceProvider.h
new file mode 100644
index 0000000000..cbd36fa10b
--- /dev/null
+++ b/widget/gtk/WindowSurfaceProvider.h
@@ -0,0 +1,81 @@
+/* -*- 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 _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/widget/WindowSurface.h"
+#include "Units.h"
+
+#include <gdk/gdk.h>
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkwayland.h>
+#endif
+#include <X11/Xlib.h> // for Window, Display, Visual, etc.
+#include "X11UndefineNone.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+/*
+ * Holds the logic for creating WindowSurface's for a GTK nsWindow.
+ * The main purpose of this class is to allow sharing of logic between
+ * nsWindow and GtkCompositorWidget, for when OMTC is enabled or disabled.
+ */
+class WindowSurfaceProvider final {
+ public:
+ WindowSurfaceProvider();
+
+ /**
+ * Initializes the WindowSurfaceProvider by giving it the window
+ * handle and display to attach to. WindowSurfaceProvider doesn't
+ * own the Display, Window, etc, and they must continue to exist
+ * while WindowSurfaceProvider is used.
+ */
+ void Initialize(Display* aDisplay, Window aWindow, Visual* aVisual,
+ int aDepth, bool aIsShaped);
+
+#ifdef MOZ_WAYLAND
+ void Initialize(nsWindow* aWidget);
+#endif
+
+ /**
+ * Releases any surfaces created by this provider.
+ * This is used by GtkCompositorWidget to get rid
+ * of resources before we close the display connection.
+ */
+ void CleanupResources();
+
+ already_AddRefed<gfx::DrawTarget> StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion, layers::BufferMode* aBufferMode);
+ void EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget,
+ const LayoutDeviceIntRegion& aInvalidRegion);
+
+ private:
+ UniquePtr<WindowSurface> CreateWindowSurface();
+
+ // Can we access X?
+ bool mIsX11Display;
+ Display* mXDisplay;
+ Window mXWindow;
+ Visual* mXVisual;
+ int mXDepth;
+ UniquePtr<WindowSurface> mWindowSurface;
+#ifdef MOZ_WAYLAND
+ nsWindow* mWidget;
+#endif
+ bool mIsShaped;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H
diff --git a/widget/gtk/WindowSurfaceWayland.cpp b/widget/gtk/WindowSurfaceWayland.cpp
new file mode 100644
index 0000000000..2855ff7121
--- /dev/null
+++ b/widget/gtk/WindowSurfaceWayland.cpp
@@ -0,0 +1,1116 @@
+/* -*- 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 "nsWaylandDisplay.h"
+#include "WindowSurfaceWayland.h"
+
+#include "nsPrintfCString.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Tools.h"
+#include "gfx2DGlue.h"
+#include "gfxPlatform.h"
+#include "MozContainer.h"
+#include "nsTArray.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_widget.h"
+
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#undef LOG
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gWidgetWaylandLog;
+# define LOGWAYLAND(args) \
+ MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOGWAYLAND(args)
+#endif /* MOZ_LOGGING */
+
+// Maximal compositing timeout it miliseconds
+#define COMPOSITING_TIMEOUT 200
+
+namespace mozilla {
+namespace widget {
+
+/*
+ Wayland multi-thread rendering scheme
+
+ Every rendering thread (main thread, compositor thread) contains its own
+ nsWaylandDisplay object connected to Wayland compositor (Mutter, Weston, etc.)
+
+ WindowSurfaceWayland implements WindowSurface class and draws nsWindow by
+ WindowSurface interface (Lock, Commit) to screen through nsWaylandDisplay.
+
+ ----------------------
+ | Wayland compositor |
+ ----------------------
+ ^
+ |
+ ----------------------
+ | nsWaylandDisplay |
+ ----------------------
+ ^ ^
+ | |
+ | |
+ | --------------------------------- ------------------
+ | | WindowSurfaceWayland |<------>| nsWindow |
+ | | | ------------------
+ | | ----------------------- |
+ | | | WindowBackBuffer | |
+ | | | | |
+ | | | ------------------- | |
+ | | | | WaylandShmPool | | |
+ | | | ------------------- | |
+ | | ----------------------- |
+ | | |
+ | | ----------------------- |
+ | | | WindowBackBuffer | |
+ | | | | |
+ | | | ------------------- | |
+ | | | | WaylandShmPool | | |
+ | | | ------------------- | |
+ | | ----------------------- |
+ | ---------------------------------
+ |
+ |
+ --------------------------------- ------------------
+ | WindowSurfaceWayland |<------>| nsWindow |
+ | | ------------------
+ | ----------------------- |
+ | | WindowBackBuffer | |
+ | | | |
+ | | ------------------- | |
+ | | | WaylandShmPool | | |
+ | | ------------------- | |
+ | ----------------------- |
+ | |
+ | ----------------------- |
+ | | WindowBackBuffer | |
+ | | | |
+ | | ------------------- | |
+ | | | WaylandShmPool | | |
+ | | ------------------- | |
+ | ----------------------- |
+ ---------------------------------
+
+
+nsWaylandDisplay
+
+Is our connection to Wayland display server,
+holds our display connection (wl_display) and event queue (wl_event_queue).
+
+nsWaylandDisplay is created for every thread which sends data to Wayland
+compositor. Wayland events for main thread is served by default Gtk+ loop,
+for other threads (compositor) we must create wl_event_queue and run event loop.
+
+
+WindowSurfaceWayland
+
+Is a Wayland implementation of WindowSurface class for WindowSurfaceProvider,
+we implement Lock() and Commit() interfaces from WindowSurface
+for actual drawing.
+
+One WindowSurfaceWayland draws one nsWindow so those are tied 1:1.
+At Wayland level it holds one wl_surface object.
+
+To perform visualiation of nsWindow, WindowSurfaceWayland contains one
+wl_surface and two wl_buffer objects (owned by WindowBackBuffer)
+as we use double buffering. When nsWindow drawing is finished to wl_buffer,
+the wl_buffer is attached to wl_surface and it's sent to Wayland compositor.
+
+When there's no wl_buffer available for drawing (all wl_buffers are locked in
+compositor for instance) we store the drawing to WindowImageSurface object
+and draw later when wl_buffer becomes availabe or discard the
+WindowImageSurface cache when whole screen is invalidated.
+
+WindowBackBuffer
+
+Is a class which provides a wl_buffer for drawing.
+Wl_buffer is a main Wayland object with actual graphics data.
+Wl_buffer basically represent one complete window screen.
+When double buffering is involved every window (GdkWindow for instance)
+utilises two wl_buffers which are cycled. One is filed with data by application
+and one is rendered by compositor.
+
+WindowBackBuffer is implemented by shared memory (shm).
+It owns wl_buffer object, owns WaylandShmPool
+(which provides the shared memory) and ties them together.
+
+WaylandShmPool
+
+WaylandShmPool acts as a manager of shared memory for WindowBackBuffer.
+Allocates it, holds reference to it and releases it.
+
+We allocate shared memory (shm) by mmap(..., MAP_SHARED,...) as an interface
+between us and wayland compositor. We draw our graphics data to the shm and
+handle to wayland compositor by WindowBackBuffer/WindowSurfaceWayland
+(wl_buffer/wl_surface).
+*/
+
+#define EVENT_LOOP_DELAY (1000 / 240)
+
+#define BUFFER_BPP 4
+gfx::SurfaceFormat WindowBackBuffer::mFormat = gfx::SurfaceFormat::B8G8R8A8;
+
+int WindowBackBuffer::mDumpSerial =
+ PR_GetEnv("MOZ_WAYLAND_DUMP_WL_BUFFERS") ? 1 : 0;
+char* WindowBackBuffer::mDumpDir = PR_GetEnv("MOZ_WAYLAND_DUMP_DIR");
+
+RefPtr<nsWaylandDisplay> WindowBackBuffer::GetWaylandDisplay() {
+ return mWindowSurfaceWayland->GetWaylandDisplay();
+}
+
+static int WaylandAllocateShmMemory(int aSize) {
+ int fd = -1;
+ do {
+ static int counter = 0;
+ nsPrintfCString shmName("/wayland.mozilla.ipc.%d", counter++);
+ fd = shm_open(shmName.get(), O_CREAT | O_RDWR | O_EXCL, 0600);
+ if (fd >= 0) {
+ // We don't want to use leaked file
+ if (shm_unlink(shmName.get()) != 0) {
+ NS_WARNING("shm_unlink failed");
+ return -1;
+ }
+ }
+ } while (fd < 0 && errno == EEXIST);
+
+ if (fd < 0) {
+ NS_WARNING(nsPrintfCString("shm_open failed: %s", strerror(errno)).get());
+ return -1;
+ }
+
+ int ret = 0;
+#ifdef HAVE_POSIX_FALLOCATE
+ do {
+ ret = posix_fallocate(fd, 0, aSize);
+ } while (ret == EINTR);
+ if (ret != 0) {
+ NS_WARNING(
+ nsPrintfCString("posix_fallocate() fails to allocate shm memory: %s",
+ strerror(ret))
+ .get());
+ close(fd);
+ return -1;
+ }
+#else
+ do {
+ ret = ftruncate(fd, aSize);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0) {
+ NS_WARNING(nsPrintfCString("ftruncate() fails to allocate shm memory: %s",
+ strerror(ret))
+ .get());
+ close(fd);
+ fd = -1;
+ }
+#endif
+
+ return fd;
+}
+
+static bool WaylandReAllocateShmMemory(int aFd, int aSize) {
+ if (ftruncate(aFd, aSize) < 0) {
+ return false;
+ }
+#ifdef HAVE_POSIX_FALLOCATE
+ do {
+ errno = posix_fallocate(aFd, 0, aSize);
+ } while (errno == EINTR);
+ if (errno != 0) {
+ return false;
+ }
+#endif
+ return true;
+}
+
+WaylandShmPool::WaylandShmPool()
+ : mShmPool(nullptr),
+ mShmPoolFd(-1),
+ mAllocatedSize(0),
+ mImageData(MAP_FAILED){};
+
+void WaylandShmPool::Release() {
+ if (mImageData != MAP_FAILED) {
+ munmap(mImageData, mAllocatedSize);
+ mImageData = MAP_FAILED;
+ }
+ if (mShmPool) {
+ wl_shm_pool_destroy(mShmPool);
+ mShmPool = 0;
+ }
+ if (mShmPoolFd >= 0) {
+ close(mShmPoolFd);
+ mShmPoolFd = -1;
+ }
+}
+
+bool WaylandShmPool::Create(RefPtr<nsWaylandDisplay> aWaylandDisplay,
+ int aSize) {
+ // We do size increase only
+ if (aSize <= mAllocatedSize) {
+ return true;
+ }
+
+ if (mShmPoolFd < 0) {
+ mShmPoolFd = WaylandAllocateShmMemory(aSize);
+ if (mShmPoolFd < 0) {
+ return false;
+ }
+ } else {
+ if (!WaylandReAllocateShmMemory(mShmPoolFd, aSize)) {
+ Release();
+ return false;
+ }
+ }
+
+ if (mImageData != MAP_FAILED) {
+ munmap(mImageData, mAllocatedSize);
+ }
+ mImageData =
+ mmap(nullptr, aSize, PROT_READ | PROT_WRITE, MAP_SHARED, mShmPoolFd, 0);
+ if (mImageData == MAP_FAILED) {
+ NS_WARNING("Unable to map drawing surface!");
+ Release();
+ return false;
+ }
+
+ if (mShmPool) {
+ wl_shm_pool_resize(mShmPool, aSize);
+ } else {
+ mShmPool = wl_shm_create_pool(aWaylandDisplay->GetShm(), mShmPoolFd, aSize);
+ // We set our queue to get mShmPool events at compositor thread.
+ wl_proxy_set_queue((struct wl_proxy*)mShmPool,
+ aWaylandDisplay->GetEventQueue());
+ }
+
+ mAllocatedSize = aSize;
+ return true;
+}
+
+void WaylandShmPool::SetImageDataFromPool(class WaylandShmPool* aSourcePool,
+ int aImageDataSize) {
+ MOZ_ASSERT(mAllocatedSize >= aImageDataSize, "WaylandShmPool overflows!");
+ memcpy(mImageData, aSourcePool->GetImageData(), aImageDataSize);
+}
+
+WaylandShmPool::~WaylandShmPool() { Release(); }
+
+static void buffer_release(void* data, wl_buffer* buffer) {
+ auto surface = reinterpret_cast<WindowBackBuffer*>(data);
+ surface->Detach(buffer);
+}
+
+static const struct wl_buffer_listener buffer_listener = {buffer_release};
+
+bool WindowBackBuffer::Create(int aWidth, int aHeight) {
+ MOZ_ASSERT(!IsAttached(), "We can't create attached buffers.");
+
+ ReleaseWLBuffer();
+
+ int size = aWidth * aHeight * BUFFER_BPP;
+ if (!mShmPool.Create(GetWaylandDisplay(), size)) {
+ return false;
+ }
+
+ mWLBuffer =
+ wl_shm_pool_create_buffer(mShmPool.GetShmPool(), 0, aWidth, aHeight,
+ aWidth * BUFFER_BPP, WL_SHM_FORMAT_ARGB8888);
+ wl_proxy_set_queue((struct wl_proxy*)mWLBuffer,
+ GetWaylandDisplay()->GetEventQueue());
+ wl_buffer_add_listener(mWLBuffer, &buffer_listener, this);
+
+ mWidth = aWidth;
+ mHeight = aHeight;
+
+ LOGWAYLAND(("WindowBackBuffer::Create [%p] wl_buffer %p ID %d\n", (void*)this,
+ (void*)mWLBuffer,
+ mWLBuffer ? wl_proxy_get_id((struct wl_proxy*)mWLBuffer) : -1));
+ return true;
+}
+
+void WindowBackBuffer::ReleaseWLBuffer() {
+ LOGWAYLAND(("WindowBackBuffer::Release [%p]\n", (void*)this));
+ if (mWLBuffer) {
+ wl_buffer_destroy(mWLBuffer);
+ mWLBuffer = nullptr;
+ }
+ mWidth = mHeight = 0;
+}
+
+void WindowBackBuffer::Clear() {
+ memset(mShmPool.GetImageData(), 0, mHeight * mWidth * BUFFER_BPP);
+}
+
+WindowBackBuffer::WindowBackBuffer(WindowSurfaceWayland* aWindowSurfaceWayland)
+ : mWindowSurfaceWayland(aWindowSurfaceWayland),
+ mShmPool(),
+ mWLBuffer(nullptr),
+ mWidth(0),
+ mHeight(0),
+ mAttached(false) {}
+
+WindowBackBuffer::~WindowBackBuffer() { ReleaseWLBuffer(); }
+
+bool WindowBackBuffer::Resize(int aWidth, int aHeight) {
+ if (aWidth == mWidth && aHeight == mHeight) {
+ return true;
+ }
+ LOGWAYLAND(
+ ("WindowBackBuffer::Resize [%p] %d %d\n", (void*)this, aWidth, aHeight));
+ Create(aWidth, aHeight);
+ return (mWLBuffer != nullptr);
+}
+
+void WindowBackBuffer::Attach(wl_surface* aSurface) {
+ LOGWAYLAND(
+ ("WindowBackBuffer::Attach [%p] wl_surface %p ID %d wl_buffer %p ID %d\n",
+ (void*)this, (void*)aSurface,
+ aSurface ? wl_proxy_get_id((struct wl_proxy*)aSurface) : -1,
+ (void*)GetWlBuffer(),
+ GetWlBuffer() ? wl_proxy_get_id((struct wl_proxy*)GetWlBuffer()) : -1));
+
+ wl_buffer* buffer = GetWlBuffer();
+ if (buffer) {
+ mAttached = true;
+ wl_surface_attach(aSurface, buffer, 0, 0);
+ wl_surface_commit(aSurface);
+ wl_display_flush(WaylandDisplayGetWLDisplay());
+ }
+}
+
+void WindowBackBuffer::Detach(wl_buffer* aBuffer) {
+ LOGWAYLAND(("WindowBackBuffer::Detach [%p] wl_buffer %p ID %d\n", (void*)this,
+ (void*)aBuffer,
+ aBuffer ? wl_proxy_get_id((struct wl_proxy*)aBuffer) : -1));
+ mAttached = false;
+
+ // Commit any potential cached drawings from latest Lock()/Commit() cycle.
+ mWindowSurfaceWayland->FlushPendingCommits();
+}
+
+bool WindowBackBuffer::SetImageDataFromBuffer(
+ class WindowBackBuffer* aSourceBuffer) {
+ auto sourceBuffer = static_cast<class WindowBackBuffer*>(aSourceBuffer);
+ if (!IsMatchingSize(sourceBuffer)) {
+ if (!Resize(sourceBuffer->mWidth, sourceBuffer->mHeight)) {
+ return false;
+ }
+ }
+
+ mShmPool.SetImageDataFromPool(
+ &sourceBuffer->mShmPool,
+ sourceBuffer->mWidth * sourceBuffer->mHeight * BUFFER_BPP);
+ return true;
+}
+
+already_AddRefed<gfx::DrawTarget> WindowBackBuffer::Lock() {
+ LOGWAYLAND(("WindowBackBuffer::Lock [%p] [%d x %d] wl_buffer %p ID %d\n",
+ (void*)this, mWidth, mHeight, (void*)mWLBuffer,
+ mWLBuffer ? wl_proxy_get_id((struct wl_proxy*)mWLBuffer) : -1));
+
+ gfx::IntSize lockSize(mWidth, mHeight);
+ mIsLocked = true;
+ return gfxPlatform::CreateDrawTargetForData(
+ static_cast<unsigned char*>(mShmPool.GetImageData()), lockSize,
+ BUFFER_BPP * mWidth, GetSurfaceFormat());
+}
+
+#ifdef MOZ_LOGGING
+void WindowBackBuffer::DumpToFile(const char* aHint) {
+ if (!mDumpSerial) {
+ return;
+ }
+
+ cairo_surface_t* surface = nullptr;
+ auto unmap = MakeScopeExit([&] {
+ if (surface) {
+ cairo_surface_destroy(surface);
+ }
+ });
+ surface = cairo_image_surface_create_for_data(
+ (unsigned char*)mShmPool.GetImageData(), CAIRO_FORMAT_ARGB32, mWidth,
+ mHeight, BUFFER_BPP * mWidth);
+ if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) {
+ nsCString filename;
+ if (mDumpDir) {
+ filename.Append(mDumpDir);
+ filename.Append('/');
+ }
+ filename.Append(
+ nsPrintfCString("firefox-wl-buffer-%.5d-%s.png", mDumpSerial++, aHint));
+ cairo_surface_write_to_png(surface, filename.get());
+ LOGWAYLAND(("Dumped wl_buffer to %s\n", filename.get()));
+ }
+}
+#endif
+
+static void frame_callback_handler(void* data, struct wl_callback* callback,
+ uint32_t time) {
+ auto surface = reinterpret_cast<WindowSurfaceWayland*>(data);
+ surface->FrameCallbackHandler();
+}
+
+static const struct wl_callback_listener frame_listener = {
+ frame_callback_handler};
+
+WindowSurfaceWayland::WindowSurfaceWayland(nsWindow* aWindow)
+ : mWindow(aWindow),
+ mWaylandDisplay(WaylandDisplayGet()),
+ mWaylandBuffer(nullptr),
+ mWaylandFullscreenDamage(false),
+ mFrameCallback(nullptr),
+ mLastCommittedSurface(nullptr),
+ mLastCommitTime(0),
+ mDrawToWaylandBufferDirectly(true),
+ mCanSwitchWaylandBuffer(true),
+ mBufferPendingCommit(false),
+ mBufferCommitAllowed(false),
+ mBufferNeedsClear(false),
+ mSmoothRendering(StaticPrefs::widget_wayland_smooth_rendering()),
+ mSurfaceReadyTimerID(),
+ mSurfaceLock("WindowSurfaceWayland lock") {
+ for (int i = 0; i < BACK_BUFFER_NUM; i++) {
+ mShmBackupBuffer[i] = nullptr;
+ }
+}
+
+WindowSurfaceWayland::~WindowSurfaceWayland() {
+ MutexAutoLock lock(mSurfaceLock);
+
+ if (mSurfaceReadyTimerID) {
+ g_source_remove(mSurfaceReadyTimerID);
+ mSurfaceReadyTimerID = 0;
+ }
+
+ if (mBufferPendingCommit) {
+ NS_WARNING("Deleted WindowSurfaceWayland with a pending commit!");
+ }
+
+ if (mFrameCallback) {
+ wl_callback_destroy(mFrameCallback);
+ }
+
+ mWaylandBuffer = nullptr;
+
+ for (int i = 0; i < BACK_BUFFER_NUM; i++) {
+ if (mShmBackupBuffer[i]) {
+ delete mShmBackupBuffer[i];
+ }
+ }
+}
+
+WindowBackBuffer* WindowSurfaceWayland::CreateWaylandBuffer(int aWidth,
+ int aHeight) {
+ int availableBuffer;
+ for (availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
+ availableBuffer++) {
+ if (!mShmBackupBuffer[availableBuffer]) {
+ break;
+ }
+ }
+
+ // There isn't any free slot for additional buffer.
+ if (availableBuffer == BACK_BUFFER_NUM) {
+ return nullptr;
+ }
+
+ WindowBackBuffer* buffer = new WindowBackBuffer(this);
+ if (!buffer->Create(aWidth, aHeight)) {
+ delete buffer;
+ return nullptr;
+ }
+
+ mShmBackupBuffer[availableBuffer] = buffer;
+ return buffer;
+}
+
+WindowBackBuffer* WindowSurfaceWayland::WaylandBufferFindAvailable(
+ int aWidth, int aHeight) {
+ int availableBuffer;
+ // Try to find a buffer which matches the size
+ for (availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
+ availableBuffer++) {
+ WindowBackBuffer* buffer = mShmBackupBuffer[availableBuffer];
+ if (buffer && !buffer->IsAttached() &&
+ buffer->IsMatchingSize(aWidth, aHeight)) {
+ return buffer;
+ }
+ }
+
+ // Try to find any buffer
+ for (availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
+ availableBuffer++) {
+ WindowBackBuffer* buffer = mShmBackupBuffer[availableBuffer];
+ if (buffer && !buffer->IsAttached()) {
+ return buffer;
+ }
+ }
+
+ return nullptr;
+}
+
+WindowBackBuffer* WindowSurfaceWayland::SetNewWaylandBuffer() {
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::NewWaylandBuffer [%p] Requested buffer [%d "
+ "x %d]\n",
+ (void*)this, mWLBufferRect.width, mWLBufferRect.height));
+
+ mWaylandBuffer =
+ WaylandBufferFindAvailable(mWLBufferRect.width, mWLBufferRect.height);
+ if (mWaylandBuffer) {
+ if (!mWaylandBuffer->Resize(mWLBufferRect.width, mWLBufferRect.height)) {
+ return nullptr;
+ }
+ return mWaylandBuffer;
+ }
+
+ mWaylandBuffer =
+ CreateWaylandBuffer(mWLBufferRect.width, mWLBufferRect.height);
+ return mWaylandBuffer;
+}
+
+// Recent
+WindowBackBuffer* WindowSurfaceWayland::GetWaylandBuffer() {
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::GetWaylandBuffer [%p] Requested buffer [%d "
+ "x %d] can switch %d\n",
+ (void*)this, mWLBufferRect.width, mWLBufferRect.height,
+ mCanSwitchWaylandBuffer));
+
+ // There's no buffer created yet, create a new one for partial screen updates.
+ if (!mWaylandBuffer) {
+ return SetNewWaylandBuffer();
+ }
+
+ if (mWaylandBuffer->IsAttached()) {
+ if (mCanSwitchWaylandBuffer) {
+ return SetNewWaylandBuffer();
+ }
+ LOGWAYLAND((" Buffer is attached and we can't switch, return null\n"));
+ return nullptr;
+ }
+
+ if (mWaylandBuffer->IsMatchingSize(mWLBufferRect.width,
+ mWLBufferRect.height)) {
+ LOGWAYLAND((" Size is ok, use the buffer [%d x %d]\n",
+ mWLBufferRect.width, mWLBufferRect.height));
+ return mWaylandBuffer;
+ }
+
+ if (mCanSwitchWaylandBuffer) {
+ // Reuse existing buffer
+ LOGWAYLAND((" Reuse buffer with resize [%d x %d]\n", mWLBufferRect.width,
+ mWLBufferRect.height));
+ if (mWaylandBuffer->Resize(mWLBufferRect.width, mWLBufferRect.height)) {
+ return mWaylandBuffer;
+ }
+ // OOM here, just return null to skip this frame.
+ return nullptr;
+ }
+
+ LOGWAYLAND(
+ (" Buffer size does not match, requested %d x %d got %d x%d, return "
+ "null.\n",
+ mWaylandBuffer->GetWidth(), mWaylandBuffer->GetHeight(),
+ mWLBufferRect.width, mWLBufferRect.height));
+ return nullptr;
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceWayland::LockWaylandBuffer() {
+ // Allocated wayland buffer can't be bigger than mozilla widget size.
+ LayoutDeviceIntRegion region;
+ region.And(mLockedScreenRect, mWindow->GetMozContainerSize());
+ mWLBufferRect = LayoutDeviceIntRect(region.GetBounds());
+
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::LockWaylandBuffer [%p] Requesting buffer %d x "
+ "%d\n",
+ (void*)this, mWLBufferRect.width, mWLBufferRect.height));
+
+ WindowBackBuffer* buffer = GetWaylandBuffer();
+ LOGWAYLAND(("WindowSurfaceWayland::LockWaylandBuffer [%p] Got buffer %p\n",
+ (void*)this, (void*)buffer));
+
+ if (!buffer) {
+ if (mLastCommitTime && (g_get_monotonic_time() / 1000) - mLastCommitTime >
+ COMPOSITING_TIMEOUT) {
+ NS_WARNING(
+ "Slow response from Wayland compositor, visual glitches ahead.");
+ }
+ return nullptr;
+ }
+
+ mCanSwitchWaylandBuffer = false;
+
+ if (mBufferNeedsClear) {
+ buffer->Clear();
+ mBufferNeedsClear = false;
+ }
+
+ return buffer->Lock();
+}
+
+void WindowSurfaceWayland::UnlockWaylandBuffer() {
+ LOGWAYLAND(("WindowSurfaceWayland::UnlockWaylandBuffer [%p]\n", (void*)this));
+ mWaylandBuffer->Unlock();
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceWayland::LockImageSurface(
+ const gfx::IntSize& aLockSize) {
+ if (!mImageSurface || !(aLockSize <= mImageSurface->GetSize())) {
+ mImageSurface = gfx::Factory::CreateDataSourceSurface(
+ aLockSize, WindowBackBuffer::GetSurfaceFormat());
+ }
+ gfx::DataSourceSurface::MappedSurface map = {nullptr, 0};
+ if (!mImageSurface->Map(gfx::DataSourceSurface::READ_WRITE, &map)) {
+ return nullptr;
+ }
+ return gfxPlatform::CreateDrawTargetForData(
+ map.mData, mImageSurface->GetSize(), map.mStride,
+ WindowBackBuffer::GetSurfaceFormat());
+}
+
+static bool IsWindowFullScreenUpdate(
+ LayoutDeviceIntRect& aScreenRect,
+ const LayoutDeviceIntRegion& aUpdatedRegion) {
+ if (aUpdatedRegion.GetNumRects() > 1) return false;
+
+ gfx::IntRect rect = aUpdatedRegion.RectIter().Get().ToUnknownRect();
+ return (rect.x == 0 && rect.y == 0 && aScreenRect.width == rect.width &&
+ aScreenRect.height == rect.height);
+}
+
+static bool IsPopupFullScreenUpdate(
+ LayoutDeviceIntRect& aScreenRect,
+ const LayoutDeviceIntRegion& aUpdatedRegion) {
+ // We know that popups can be drawn from two parts; a panel and an arrow.
+ // Assume we redraw whole popups when we have two rects and bounding
+ // box is equal to window borders.
+ if (aUpdatedRegion.GetNumRects() > 2) return false;
+
+ gfx::IntRect lockSize = aUpdatedRegion.GetBounds().ToUnknownRect();
+ return (lockSize.x == 0 && lockSize.y == 0 &&
+ aScreenRect.width == lockSize.width &&
+ aScreenRect.height == lockSize.height);
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceWayland::Lock(
+ const LayoutDeviceIntRegion& aRegion) {
+ if (mWindow->WindowType() == eWindowType_invisible) {
+ return nullptr;
+ }
+
+ // Wait until all pending events are processed. There may be queued
+ // wl_buffer release event which releases our wl_buffer for further rendering.
+ mWaylandDisplay->WaitForSyncEnd();
+
+ // Lock the surface *after* WaitForSyncEnd() call as is can fire
+ // FlushPendingCommits().
+ MutexAutoLock lock(mSurfaceLock);
+
+ // Disable all commits (from potential frame callback/delayed handlers)
+ // until next WindowSurfaceWayland::Commit() call.
+ mBufferCommitAllowed = false;
+
+ LayoutDeviceIntRect lockedScreenRect = mWindow->GetBounds();
+ // The window bounds of popup windows contains relative position to
+ // the transient window. We need to remove that effect because by changing
+ // position of the popup window the buffer has not changed its size.
+ lockedScreenRect.x = lockedScreenRect.y = 0;
+ gfx::IntRect lockSize = aRegion.GetBounds().ToUnknownRect();
+
+ bool isTransparentPopup =
+ mWindow->IsWaylandPopup() &&
+ (eTransparencyTransparent == mWindow->GetTransparencyMode());
+
+ bool windowRedraw = isTransparentPopup
+ ? IsPopupFullScreenUpdate(lockedScreenRect, aRegion)
+ : IsWindowFullScreenUpdate(lockedScreenRect, aRegion);
+ if (windowRedraw) {
+ // Clear buffer when we (re)draw new transparent popup window,
+ // otherwise leave it as-is, mBufferNeedsClear can be set from previous
+ // (already pending) commits which are cached now.
+ mBufferNeedsClear =
+ mWindow->WaylandSurfaceNeedsClear() || isTransparentPopup;
+
+ // Store info that we can switch WaylandBuffer when we flush
+ // mImageSurface / mDelayedImageCommits. Don't clear it - it's cleared
+ // at LockWaylandBuffer() when we actualy switch the buffer.
+ mCanSwitchWaylandBuffer = true;
+
+ // We do full buffer repaint so clear our cached drawings.
+ mDelayedImageCommits.Clear();
+ mWaylandBufferDamage.SetEmpty();
+
+ // Store info that we can safely invalidate whole screen.
+ mWaylandFullscreenDamage = true;
+ } else {
+ // We can switch buffer if there isn't any content committed
+ // to active buffer.
+ mCanSwitchWaylandBuffer = !mBufferPendingCommit;
+ }
+
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::Lock [%p] [%d,%d] -> [%d x %d] rects %d "
+ "windowSize [%d x %d]\n",
+ (void*)this, lockSize.x, lockSize.y, lockSize.width, lockSize.height,
+ aRegion.GetNumRects(), lockedScreenRect.width, lockedScreenRect.height));
+ LOGWAYLAND((" nsWindow = %p\n", mWindow));
+ LOGWAYLAND((" isPopup = %d\n", mWindow->IsWaylandPopup()));
+ LOGWAYLAND((" isTransparentPopup = %d\n", isTransparentPopup));
+ LOGWAYLAND((" IsPopupFullScreenUpdate = %d\n",
+ IsPopupFullScreenUpdate(lockedScreenRect, aRegion)));
+ LOGWAYLAND((" IsWindowFullScreenUpdate = %d\n",
+ IsWindowFullScreenUpdate(lockedScreenRect, aRegion)));
+ LOGWAYLAND((" mBufferNeedsClear = %d\n", mBufferNeedsClear));
+ LOGWAYLAND((" mBufferPendingCommit = %d\n", mBufferPendingCommit));
+ LOGWAYLAND((" mCanSwitchWaylandBuffer = %d\n", mCanSwitchWaylandBuffer));
+ LOGWAYLAND((" windowRedraw = %d\n", windowRedraw));
+
+ if (!(mLockedScreenRect == lockedScreenRect)) {
+ LOGWAYLAND((" screen size changed\n"));
+
+ // Screen (window) size changed and we still have some painting pending
+ // for the last window size. That can happen when window is resized.
+ // We can't commit them any more as they're for former window size, so
+ // scratch them.
+ mDelayedImageCommits.Clear();
+ mWaylandBufferDamage.SetEmpty();
+
+ if (!windowRedraw) {
+ NS_WARNING("Partial screen update when window is resized!");
+ // This should not happen. Screen size changed but we got only
+ // partal screen update instead of whole screen. Discard this painting
+ // as it produces artifacts.
+ return nullptr;
+ }
+ mLockedScreenRect = lockedScreenRect;
+ }
+
+ // We can draw directly only when widget has the same size as wl_buffer
+ LayoutDeviceIntRect size = mWindow->GetMozContainerSize();
+ mDrawToWaylandBufferDirectly = (size.width >= mLockedScreenRect.width &&
+ size.height >= mLockedScreenRect.height);
+
+ // We can draw directly only when we redraw significant part of the window
+ // to avoid flickering or do only fullscreen updates in smooth mode.
+ if (mDrawToWaylandBufferDirectly) {
+ mDrawToWaylandBufferDirectly =
+ mSmoothRendering
+ ? windowRedraw
+ : (windowRedraw || (lockSize.width * 2 > lockedScreenRect.width &&
+ lockSize.height * 2 > lockedScreenRect.height));
+ }
+ if (!mDrawToWaylandBufferDirectly) {
+ // Don't switch wl_buffers when we cache drawings.
+ mCanSwitchWaylandBuffer = false;
+ LOGWAYLAND((" Indirect drawing, mCanSwitchWaylandBuffer = %d\n",
+ mCanSwitchWaylandBuffer));
+ }
+
+ if (mDrawToWaylandBufferDirectly) {
+ LOGWAYLAND((" Direct drawing\n"));
+ RefPtr<gfx::DrawTarget> dt = LockWaylandBuffer();
+ if (dt) {
+#if MOZ_LOGGING
+ mWaylandBuffer->DumpToFile("Lock");
+#endif
+ if (!windowRedraw) {
+ DrawDelayedImageCommits(dt, mWaylandBufferDamage);
+#if MOZ_LOGGING
+ mWaylandBuffer->DumpToFile("Lock-after-commit");
+#endif
+ }
+ mBufferPendingCommit = true;
+ return dt.forget();
+ }
+ }
+
+ // We do indirect drawing because there isn't any front buffer available.
+ // Do indirect drawing to mImageSurface which is commited to wayland
+ // wl_buffer by DrawDelayedImageCommits() later.
+ mDrawToWaylandBufferDirectly = false;
+
+ LOGWAYLAND((" Indirect drawing.\n"));
+ return LockImageSurface(gfx::IntSize(lockSize.XMost(), lockSize.YMost()));
+}
+
+bool WindowImageSurface::OverlapsSurface(
+ class WindowImageSurface& aBottomSurface) {
+ return mUpdateRegion.Contains(aBottomSurface.mUpdateRegion);
+}
+
+void WindowImageSurface::DrawToTarget(
+ gfx::DrawTarget* aDest, LayoutDeviceIntRegion& aWaylandBufferDamage) {
+#ifdef MOZ_LOGGING
+ gfx::IntRect bounds = mUpdateRegion.GetBounds().ToUnknownRect();
+ LOGWAYLAND(("WindowImageSurface::DrawToTarget\n"));
+ LOGWAYLAND((" rects num %d\n", mUpdateRegion.GetNumRects()));
+ LOGWAYLAND((" bounds [ %d, %d] -> [%d x %d]\n", bounds.x, bounds.y,
+ bounds.width, bounds.height));
+#endif
+ for (auto iter = mUpdateRegion.RectIter(); !iter.Done(); iter.Next()) {
+ gfx::IntRect r(iter.Get().ToUnknownRect());
+ LOGWAYLAND(
+ (" draw rect [%d,%d] -> [%d x %d]\n", r.x, r.y, r.width, r.height));
+ aDest->CopySurface(mImageSurface, r, gfx::IntPoint(r.x, r.y));
+ }
+ aWaylandBufferDamage.OrWith(mUpdateRegion);
+}
+
+WindowImageSurface::WindowImageSurface(
+ gfx::DataSourceSurface* aImageSurface,
+ const LayoutDeviceIntRegion& aUpdateRegion)
+ : mImageSurface(aImageSurface), mUpdateRegion(aUpdateRegion) {}
+
+void WindowSurfaceWayland::DrawDelayedImageCommits(
+ gfx::DrawTarget* aDrawTarget, LayoutDeviceIntRegion& aWaylandBufferDamage) {
+ unsigned int imagesNum = mDelayedImageCommits.Length();
+ LOGWAYLAND(("WindowSurfaceWayland::DrawDelayedImageCommits [%p] len %d\n",
+ (void*)this, imagesNum));
+ for (unsigned int i = 0; i < imagesNum; i++) {
+ mDelayedImageCommits[i].DrawToTarget(aDrawTarget, aWaylandBufferDamage);
+ }
+ mDelayedImageCommits.Clear();
+}
+
+void WindowSurfaceWayland::CacheImageSurface(
+ const LayoutDeviceIntRegion& aRegion) {
+#ifdef MOZ_LOGGING
+ gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+ LOGWAYLAND(("WindowSurfaceWayland::CacheImageSurface [%p]\n", (void*)this));
+ LOGWAYLAND((" rects num %d\n", aRegion.GetNumRects()));
+ LOGWAYLAND((" bounds [ %d, %d] -> [%d x %d]\n", bounds.x, bounds.y,
+ bounds.width, bounds.height));
+#endif
+
+ mImageSurface->Unmap();
+ WindowImageSurface surf = WindowImageSurface(mImageSurface, aRegion);
+
+ if (mDelayedImageCommits.Length()) {
+ auto lastSurf = mDelayedImageCommits.PopLastElement();
+ if (surf.OverlapsSurface(lastSurf)) {
+#ifdef MOZ_LOGGING
+ {
+ gfx::IntRect size =
+ lastSurf.GetUpdateRegion()->GetBounds().ToUnknownRect();
+ LOGWAYLAND((" removing [ %d, %d] -> [%d x %d]\n", size.x, size.y,
+ size.width, size.height));
+ }
+#endif
+ } else {
+ mDelayedImageCommits.AppendElement(lastSurf);
+ }
+ }
+
+ mDelayedImageCommits.AppendElement(surf);
+ // mImageSurface is owned by mDelayedImageCommits
+ mImageSurface = nullptr;
+
+ LOGWAYLAND(
+ (" There's %d cached images\n", int(mDelayedImageCommits.Length())));
+}
+
+bool WindowSurfaceWayland::CommitImageCacheToWaylandBuffer() {
+ if (!mDelayedImageCommits.Length()) {
+ return false;
+ }
+
+ MOZ_ASSERT(!mDrawToWaylandBufferDirectly);
+
+ RefPtr<gfx::DrawTarget> dt = LockWaylandBuffer();
+ if (!dt) {
+ return false;
+ }
+
+ LOGWAYLAND((" Flushing %ld cached WindowImageSurfaces to Wayland buffer\n",
+ long(mDelayedImageCommits.Length())));
+
+ DrawDelayedImageCommits(dt, mWaylandBufferDamage);
+ UnlockWaylandBuffer();
+
+ return true;
+}
+
+void WindowSurfaceWayland::FlushPendingCommits() {
+ MutexAutoLock lock(mSurfaceLock);
+ if (FlushPendingCommitsLocked()) {
+ mWaylandDisplay->QueueSyncBegin();
+ }
+}
+
+// When a new window is created we may not have a valid wl_surface
+// for drawing (Gtk haven't created it yet). All commits are queued
+// and FlushPendingCommitsLocked() is called by timer when wl_surface is ready
+// for drawing.
+static int WaylandBufferFlushPendingCommits(void* data) {
+ WindowSurfaceWayland* aSurface = static_cast<WindowSurfaceWayland*>(data);
+ aSurface->FlushPendingCommits();
+ return true;
+}
+
+bool WindowSurfaceWayland::FlushPendingCommitsLocked() {
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::FlushPendingCommitsLocked [%p]\n", (void*)this));
+ LOGWAYLAND(
+ (" mDrawToWaylandBufferDirectly = %d\n", mDrawToWaylandBufferDirectly));
+ LOGWAYLAND((" mCanSwitchWaylandBuffer = %d\n", mCanSwitchWaylandBuffer));
+ LOGWAYLAND((" mFrameCallback = %p\n", mFrameCallback));
+ LOGWAYLAND((" mLastCommittedSurface = %p\n", mLastCommittedSurface));
+ LOGWAYLAND((" mBufferPendingCommit = %d\n", mBufferPendingCommit));
+ LOGWAYLAND((" mBufferCommitAllowed = %d\n", mBufferCommitAllowed));
+
+ if (!mBufferCommitAllowed) {
+ return false;
+ }
+
+ if (CommitImageCacheToWaylandBuffer()) {
+ mBufferPendingCommit = true;
+ }
+
+ // There's nothing to do here
+ if (!mBufferPendingCommit) {
+ return false;
+ }
+
+ MOZ_ASSERT(!mWaylandBuffer->IsAttached(),
+ "We can't draw to attached wayland buffer!");
+
+ MozContainer* container = mWindow->GetMozContainer();
+ wl_surface* waylandSurface = moz_container_wayland_surface_lock(container);
+ if (!waylandSurface) {
+ LOGWAYLAND((" [%p] mWindow->GetWaylandSurface() failed, delay commit.\n",
+ (void*)this));
+
+ // Target window is not created yet - delay the commit. This can happen only
+ // when the window is newly created and there's no active
+ // frame callback pending.
+ MOZ_ASSERT(!mFrameCallback || waylandSurface != mLastCommittedSurface,
+ "Missing wayland surface at frame callback!");
+
+ if (!mSurfaceReadyTimerID) {
+ mSurfaceReadyTimerID = g_timeout_add(
+ EVENT_LOOP_DELAY, &WaylandBufferFlushPendingCommits, this);
+ }
+ return true;
+ }
+ if (mSurfaceReadyTimerID) {
+ g_source_remove(mSurfaceReadyTimerID);
+ mSurfaceReadyTimerID = 0;
+ }
+
+ auto unlockContainer = MakeScopeExit([&] {
+ moz_container_wayland_surface_unlock(container, &waylandSurface);
+ });
+
+ wl_proxy_set_queue((struct wl_proxy*)waylandSurface,
+ mWaylandDisplay->GetEventQueue());
+
+ // We have an active frame callback request so handle it.
+ if (mFrameCallback) {
+ if (waylandSurface == mLastCommittedSurface) {
+ LOGWAYLAND((" [%p] wait for frame callback.\n", (void*)this));
+ // We have an active frame callback pending from our recent surface.
+ // It means we should defer the commit to FrameCallbackHandler().
+ return true;
+ }
+ // If our stored wl_surface does not match the actual one it means the frame
+ // callback is no longer active and we should release it.
+ wl_callback_destroy(mFrameCallback);
+ mFrameCallback = nullptr;
+ mLastCommittedSurface = nullptr;
+ }
+
+ if (mWaylandFullscreenDamage) {
+ LOGWAYLAND((" wl_surface_damage full screen\n"));
+ wl_surface_damage(waylandSurface, 0, 0, INT_MAX, INT_MAX);
+ } else {
+ for (auto iter = mWaylandBufferDamage.RectIter(); !iter.Done();
+ iter.Next()) {
+ mozilla::LayoutDeviceIntRect r = iter.Get();
+ LOGWAYLAND((" wl_surface_damage_buffer [%d, %d] -> [%d, %d]\n", r.x,
+ r.y, r.width, r.height));
+ wl_surface_damage_buffer(waylandSurface, r.x, r.y, r.width, r.height);
+ }
+ }
+
+#if MOZ_LOGGING
+ mWaylandBuffer->DumpToFile("Commit");
+#endif
+
+ // Clear all back buffer damage as we're committing
+ // all requested regions.
+ mWaylandFullscreenDamage = false;
+ mWaylandBufferDamage.SetEmpty();
+
+ mFrameCallback = wl_surface_frame(waylandSurface);
+ wl_callback_add_listener(mFrameCallback, &frame_listener, this);
+
+ mWaylandBuffer->Attach(waylandSurface);
+ mLastCommittedSurface = waylandSurface;
+ mLastCommitTime = g_get_monotonic_time() / 1000;
+
+ // There's no pending commit, all changes are sent to compositor.
+ mBufferPendingCommit = false;
+
+ return true;
+}
+
+void WindowSurfaceWayland::Commit(const LayoutDeviceIntRegion& aInvalidRegion) {
+#ifdef MOZ_LOGGING
+ {
+ gfx::IntRect lockSize = aInvalidRegion.GetBounds().ToUnknownRect();
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::Commit [%p] damage size [%d, %d] -> [%d x %d]"
+ "screenSize [%d x %d]\n",
+ (void*)this, lockSize.x, lockSize.y, lockSize.width, lockSize.height,
+ mLockedScreenRect.width, mLockedScreenRect.height));
+ LOGWAYLAND((" mDrawToWaylandBufferDirectly = %d\n",
+ mDrawToWaylandBufferDirectly));
+ }
+#endif
+
+ MutexAutoLock lock(mSurfaceLock);
+
+ if (mDrawToWaylandBufferDirectly) {
+ MOZ_ASSERT(mWaylandBuffer->IsLocked());
+ mWaylandBufferDamage.OrWith(aInvalidRegion);
+ UnlockWaylandBuffer();
+ } else {
+ CacheImageSurface(aInvalidRegion);
+ }
+
+ mBufferCommitAllowed = true;
+ if (FlushPendingCommitsLocked()) {
+ mWaylandDisplay->QueueSyncBegin();
+ }
+}
+
+void WindowSurfaceWayland::FrameCallbackHandler() {
+ MOZ_ASSERT(mFrameCallback != nullptr,
+ "FrameCallbackHandler() called without valid frame callback!");
+ MOZ_ASSERT(mLastCommittedSurface != nullptr,
+ "FrameCallbackHandler() called without valid wl_surface!");
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::FrameCallbackHandler [%p]\n", (void*)this));
+
+ MutexAutoLock lock(mSurfaceLock);
+
+ wl_callback_destroy(mFrameCallback);
+ mFrameCallback = nullptr;
+
+ if (FlushPendingCommitsLocked()) {
+ mWaylandDisplay->QueueSyncBegin();
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WindowSurfaceWayland.h b/widget/gtk/WindowSurfaceWayland.h
new file mode 100644
index 0000000000..b34a9a7036
--- /dev/null
+++ b/widget/gtk/WindowSurfaceWayland.h
@@ -0,0 +1,269 @@
+/* -*- 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 _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_WAYLAND_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_WAYLAND_H
+
+#include <prthread.h>
+#include "gfxImageSurface.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "nsWaylandDisplay.h"
+#include "nsWindow.h"
+#include "WindowSurface.h"
+#include "mozilla/Mutex.h"
+
+#define BACK_BUFFER_NUM 3
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceWayland;
+
+// Allocates and owns shared memory for Wayland drawing surface
+class WaylandShmPool {
+ public:
+ bool Create(RefPtr<nsWaylandDisplay> aWaylandDisplay, int aSize);
+ void Release();
+ wl_shm_pool* GetShmPool() { return mShmPool; };
+ void* GetImageData() { return mImageData; };
+ void SetImageDataFromPool(class WaylandShmPool* aSourcePool,
+ int aImageDataSize);
+ WaylandShmPool();
+ ~WaylandShmPool();
+
+ private:
+ wl_shm_pool* mShmPool;
+ int mShmPoolFd;
+ int mAllocatedSize;
+ void* mImageData;
+};
+
+// Holds actual graphics data for wl_surface
+class WindowBackBuffer {
+ public:
+ explicit WindowBackBuffer(WindowSurfaceWayland* aWindowSurfaceWayland);
+ ~WindowBackBuffer();
+
+ already_AddRefed<gfx::DrawTarget> Lock();
+ bool IsLocked() { return mIsLocked; };
+ void Unlock() { mIsLocked = false; };
+
+ void Attach(wl_surface* aSurface);
+ void Detach(wl_buffer* aBuffer);
+ bool IsAttached() { return mAttached; }
+
+ void Clear();
+ bool Create(int aWidth, int aHeight);
+ bool Resize(int aWidth, int aHeight);
+ bool SetImageDataFromBuffer(class WindowBackBuffer* aSourceBuffer);
+
+ int GetWidth() { return mWidth; };
+ int GetHeight() { return mHeight; };
+
+ wl_buffer* GetWlBuffer() { return mWLBuffer; };
+
+ bool IsMatchingSize(int aWidth, int aHeight) {
+ return aWidth == GetWidth() && aHeight == GetHeight();
+ }
+ bool IsMatchingSize(class WindowBackBuffer* aBuffer) {
+ return aBuffer->IsMatchingSize(GetWidth(), GetHeight());
+ }
+ static gfx::SurfaceFormat GetSurfaceFormat() { return mFormat; }
+
+#ifdef MOZ_LOGGING
+ void DumpToFile(const char* aHint);
+#endif
+
+ RefPtr<nsWaylandDisplay> GetWaylandDisplay();
+
+ private:
+ void ReleaseWLBuffer();
+
+ static gfx::SurfaceFormat mFormat;
+ WindowSurfaceWayland* mWindowSurfaceWayland;
+
+ // WaylandShmPool provides actual shared memory we draw into
+ WaylandShmPool mShmPool;
+
+#ifdef MOZ_LOGGING
+ static int mDumpSerial;
+ static char* mDumpDir;
+#endif
+
+ // wl_buffer is a wayland object that encapsulates the shared memory
+ // and passes it to wayland compositor by wl_surface object.
+ wl_buffer* mWLBuffer;
+ int mWidth;
+ int mHeight;
+ bool mAttached;
+ bool mIsLocked;
+};
+
+class WindowImageSurface {
+ public:
+ void DrawToTarget(gfx::DrawTarget* aDest,
+ LayoutDeviceIntRegion& aWaylandBufferDamage);
+ WindowImageSurface(gfx::DataSourceSurface* aImageSurface,
+ const LayoutDeviceIntRegion& aUpdateRegion);
+ bool OverlapsSurface(class WindowImageSurface& aBottomSurface);
+
+ const LayoutDeviceIntRegion* GetUpdateRegion() { return &mUpdateRegion; };
+
+ private:
+ RefPtr<gfx::DataSourceSurface> mImageSurface;
+ const LayoutDeviceIntRegion mUpdateRegion;
+};
+
+// WindowSurfaceWayland is an abstraction for wl_surface
+// and related management
+class WindowSurfaceWayland : public WindowSurface {
+ public:
+ explicit WindowSurfaceWayland(nsWindow* aWindow);
+ ~WindowSurfaceWayland();
+
+ // Lock() / Commit() are called by gecko when Firefox
+ // wants to display something. Lock() returns a DrawTarget
+ // where gecko paints. When gecko is done it calls Commit()
+ // and we try to send the DrawTarget (backed by wl_buffer)
+ // to wayland compositor.
+ //
+ // If we fail (wayland compositor is busy,
+ // wl_surface is not created yet) we queue the painting
+ // and we send it to wayland compositor in FrameCallbackHandler()/
+ // FlushPendingCommits().
+ already_AddRefed<gfx::DrawTarget> Lock(
+ const LayoutDeviceIntRegion& aRegion) override;
+ void Commit(const LayoutDeviceIntRegion& aInvalidRegion) final;
+
+ // It's called from wayland compositor when there's the right
+ // time to send wl_buffer to display. It's no-op if there's no
+ // queued commits.
+ void FrameCallbackHandler();
+
+ // Try to commit all queued drawings to Wayland compositor. This is usually
+ // called from other routines but can be used to explicitly flush
+ // all drawings as we do when wl_buffer is released
+ // (see WindowBackBufferShm::Detach() for instance).
+ void FlushPendingCommits();
+
+ RefPtr<nsWaylandDisplay> GetWaylandDisplay() { return mWaylandDisplay; };
+
+ // Image cache mode can be set by widget.wayland_cache_mode
+ typedef enum {
+ // Cache and clip all drawings, default. It's slowest
+ // but also without any rendered artifacts.
+ CACHE_ALL = 0,
+ // Cache drawing only when back buffer is missing. May produce
+ // some rendering artifacts and flickering when partial screen update
+ // is rendered.
+ CACHE_MISSING = 1,
+ // Don't cache anything, draw only when back buffer is available.
+ CACHE_NONE = 2
+ } RenderingCacheMode;
+
+ private:
+ WindowBackBuffer* GetWaylandBuffer();
+ WindowBackBuffer* SetNewWaylandBuffer();
+ WindowBackBuffer* CreateWaylandBuffer(int aWidth, int aHeight);
+ WindowBackBuffer* WaylandBufferFindAvailable(int aWidth, int aHeight);
+
+ already_AddRefed<gfx::DrawTarget> LockWaylandBuffer();
+ void UnlockWaylandBuffer();
+
+ already_AddRefed<gfx::DrawTarget> LockImageSurface(
+ const gfx::IntSize& aLockSize);
+
+ void CacheImageSurface(const LayoutDeviceIntRegion& aRegion);
+ bool CommitImageCacheToWaylandBuffer();
+
+ void DrawDelayedImageCommits(gfx::DrawTarget* aDrawTarget,
+ LayoutDeviceIntRegion& aWaylandBufferDamage);
+ // Return true if we need to sync Wayland events after this call.
+ bool FlushPendingCommitsLocked();
+
+ // TODO: Do we need to hold a reference to nsWindow object?
+ nsWindow* mWindow;
+ // Buffer screen rects helps us understand if we operate on
+ // the same window size as we're called on WindowSurfaceWayland::Lock().
+ // mLockedScreenRect is window size when our wayland buffer was allocated.
+ LayoutDeviceIntRect mLockedScreenRect;
+
+ // mWLBufferRect is an intersection of mozcontainer widgetsize and
+ // mLockedScreenRect size. It can be different than mLockedScreenRect
+ // during resize when mBounds are updated immediately but actual
+ // GtkWidget size is updated asynchronously (see Bug 1489463).
+ LayoutDeviceIntRect mWLBufferRect;
+ RefPtr<nsWaylandDisplay> mWaylandDisplay;
+
+ // Actual buffer (backed by wl_buffer) where all drawings go into.
+ // Drawn areas are stored at mWaylandBufferDamage and if there's
+ // any uncommited drawings which needs to be send to wayland compositor
+ // the mBufferPendingCommit is set.
+ WindowBackBuffer* mWaylandBuffer;
+ WindowBackBuffer* mShmBackupBuffer[BACK_BUFFER_NUM];
+
+ // When mWaylandFullscreenDamage we invalidate whole surface,
+ // otherwise partial screen updates (mWaylandBufferDamage) are used.
+ bool mWaylandFullscreenDamage;
+ LayoutDeviceIntRegion mWaylandBufferDamage;
+
+ // After every commit to wayland compositor a frame callback is requested.
+ // Any next commit to wayland compositor will happen when frame callback
+ // comes from wayland compositor back as it's the best time to do the commit.
+ wl_callback* mFrameCallback;
+ wl_surface* mLastCommittedSurface;
+
+ // Cached drawings. If we can't get WaylandBuffer (wl_buffer) at
+ // WindowSurfaceWayland::Lock() we direct gecko rendering to
+ // mImageSurface.
+ // If we can't get WaylandBuffer at WindowSurfaceWayland::Commit()
+ // time, mImageSurface is moved to mDelayedImageCommits which
+ // holds all cached drawings.
+ // mDelayedImageCommits can be drawn by FrameCallbackHandler()
+ // or when WaylandBuffer is detached.
+ RefPtr<gfx::DataSourceSurface> mImageSurface;
+ AutoTArray<WindowImageSurface, 30> mDelayedImageCommits;
+
+ int64_t mLastCommitTime;
+
+ // Indicates that we don't have any cached drawings at mDelayedImageCommits
+ // and WindowSurfaceWayland::Lock() returned WaylandBuffer to gecko
+ // to draw into.
+ bool mDrawToWaylandBufferDirectly;
+
+ // Set when our cached drawings (mDelayedImageCommits) contains
+ // full screen damage. That means we can safely switch WaylandBuffer
+ // at LockWaylandBuffer().
+ bool mCanSwitchWaylandBuffer;
+
+ // Set when actual WaylandBuffer contains drawings which are not send to
+ // wayland compositor yet.
+ bool mBufferPendingCommit;
+
+ // We can't send WaylandBuffer (wl_buffer) to compositor when gecko
+ // is rendering into it (i.e. between WindowSurfaceWayland::Lock() /
+ // WindowSurfaceWayland::Commit()).
+ // Thus we use mBufferCommitAllowed to disable commit by
+ // FlushPendingCommits().
+ bool mBufferCommitAllowed;
+
+ // We need to clear WaylandBuffer when entire transparent window is repainted.
+ // This typically apply to popup windows.
+ bool mBufferNeedsClear;
+
+ // Cache all drawings except fullscreen updates.
+ // Avoid any rendering artifacts for significant performance penality.
+ bool mSmoothRendering;
+
+ gint mSurfaceReadyTimerID;
+ mozilla::Mutex mSurfaceLock;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_WAYLAND_H
diff --git a/widget/gtk/WindowSurfaceX11.cpp b/widget/gtk/WindowSurfaceX11.cpp
new file mode 100644
index 0000000000..a32cc12e18
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "WindowSurfaceX11.h"
+#include "gfxPlatform.h"
+#include "X11UndefineNone.h"
+
+namespace mozilla {
+namespace widget {
+
+WindowSurfaceX11::WindowSurfaceX11(Display* aDisplay, Window aWindow,
+ Visual* aVisual, unsigned int aDepth)
+ : mDisplay(aDisplay),
+ mWindow(aWindow),
+ mVisual(aVisual),
+ mDepth(aDepth),
+ mFormat(GetVisualFormat(aVisual, aDepth)) {}
+
+/* static */
+gfx::SurfaceFormat WindowSurfaceX11::GetVisualFormat(const Visual* aVisual,
+ unsigned int aDepth) {
+ switch (aDepth) {
+ case 32:
+ if (aVisual->red_mask == 0xff0000 && aVisual->green_mask == 0xff00 &&
+ aVisual->blue_mask == 0xff) {
+ return gfx::SurfaceFormat::B8G8R8A8;
+ }
+ break;
+ case 24:
+ if (aVisual->red_mask == 0xff0000 && aVisual->green_mask == 0xff00 &&
+ aVisual->blue_mask == 0xff) {
+ return gfx::SurfaceFormat::B8G8R8X8;
+ }
+ break;
+ case 16:
+ if (aVisual->red_mask == 0xf800 && aVisual->green_mask == 0x07e0 &&
+ aVisual->blue_mask == 0x1f) {
+ return gfx::SurfaceFormat::R5G6B5_UINT16;
+ }
+ break;
+ }
+
+ return gfx::SurfaceFormat::UNKNOWN;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WindowSurfaceX11.h b/widget/gtk/WindowSurfaceX11.h
new file mode 100644
index 0000000000..d297ec6b66
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11.h
@@ -0,0 +1,40 @@
+/* -*- 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 _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_H
+
+#ifdef MOZ_X11
+
+# include "mozilla/widget/WindowSurface.h"
+# include "mozilla/gfx/Types.h"
+
+# include <X11/Xlib.h>
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceX11 : public WindowSurface {
+ public:
+ WindowSurfaceX11(Display* aDisplay, Window aWindow, Visual* aVisual,
+ unsigned int aDepth);
+
+ protected:
+ static gfx::SurfaceFormat GetVisualFormat(const Visual* aVisual,
+ unsigned int aDepth);
+
+ Display* const mDisplay;
+ const Window mWindow;
+ Visual* const mVisual;
+ const unsigned int mDepth;
+ const gfx::SurfaceFormat mFormat;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_H
diff --git a/widget/gtk/WindowSurfaceX11Image.cpp b/widget/gtk/WindowSurfaceX11Image.cpp
new file mode 100644
index 0000000000..1e4d28915f
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11Image.cpp
@@ -0,0 +1,263 @@
+/* -*- 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 "WindowSurfaceX11Image.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "gfxPlatform.h"
+#include "gfx2DGlue.h"
+
+#include <X11/extensions/shape.h>
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::gfx;
+
+// gfxImageSurface pixel format configuration.
+#define SHAPED_IMAGE_SURFACE_BPP 4
+#ifdef IS_BIG_ENDIAN
+# define SHAPED_IMAGE_SURFACE_ALPHA_INDEX 0
+#else
+# define SHAPED_IMAGE_SURFACE_ALPHA_INDEX 3
+#endif
+
+WindowSurfaceX11Image::WindowSurfaceX11Image(Display* aDisplay, Window aWindow,
+ Visual* aVisual,
+ unsigned int aDepth,
+ bool aIsShaped)
+ : WindowSurfaceX11(aDisplay, aWindow, aVisual, aDepth),
+ mTransparencyBitmap(nullptr),
+ mTransparencyBitmapWidth(0),
+ mTransparencyBitmapHeight(0),
+ mIsShaped(aIsShaped) {}
+
+WindowSurfaceX11Image::~WindowSurfaceX11Image() {
+ if (mTransparencyBitmap) {
+ delete[] mTransparencyBitmap;
+
+ Display* xDisplay = mWindowSurface->XDisplay();
+ Window xDrawable = mWindowSurface->XDrawable();
+
+ XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, X11None,
+ ShapeSet);
+ }
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceX11Image::Lock(
+ const LayoutDeviceIntRegion& aRegion) {
+ gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+ gfx::IntSize size(bounds.XMost(), bounds.YMost());
+
+ if (!mWindowSurface || mWindowSurface->CairoStatus() ||
+ !(size <= mWindowSurface->GetSize())) {
+ mWindowSurface = new gfxXlibSurface(mDisplay, mWindow, mVisual, size);
+ }
+ if (mWindowSurface->CairoStatus()) {
+ return nullptr;
+ }
+
+ if (!mImageSurface || mImageSurface->CairoStatus() ||
+ !(size <= mImageSurface->GetSize())) {
+ gfxImageFormat format = SurfaceFormatToImageFormat(mFormat);
+ if (format == gfx::SurfaceFormat::UNKNOWN) {
+ format = mDepth == 32 ? gfx::SurfaceFormat::A8R8G8B8_UINT32
+ : gfx::SurfaceFormat::X8R8G8B8_UINT32;
+ }
+
+ // Use alpha image format for shaped window as we derive
+ // the shape bitmap from alpha channel. Must match SHAPED_IMAGE_SURFACE_BPP
+ // and SHAPED_IMAGE_SURFACE_ALPHA_INDEX.
+ if (mIsShaped) {
+ format = gfx::SurfaceFormat::A8R8G8B8_UINT32;
+ }
+
+ mImageSurface = new gfxImageSurface(size, format);
+ if (mImageSurface->CairoStatus()) {
+ return nullptr;
+ }
+ }
+
+ gfxImageFormat format = mImageSurface->Format();
+ // Cairo prefers compositing to BGRX instead of BGRA where possible.
+ // Cairo/pixman lacks some fast paths for compositing BGRX onto BGRA, so
+ // just report it as BGRX directly in that case.
+ // Otherwise, for Skia, report it as BGRA to the compositor. The alpha
+ // channel will be discarded when we put the image.
+ if (format == gfx::SurfaceFormat::X8R8G8B8_UINT32) {
+ gfx::BackendType backend = gfxVars::ContentBackend();
+ if (!gfx::Factory::DoesBackendSupportDataDrawtarget(backend)) {
+#ifdef USE_SKIA
+ backend = gfx::BackendType::SKIA;
+#else
+ backend = gfx::BackendType::CAIRO;
+#endif
+ }
+ if (backend != gfx::BackendType::CAIRO) {
+ format = gfx::SurfaceFormat::A8R8G8B8_UINT32;
+ }
+ }
+
+ return gfxPlatform::CreateDrawTargetForData(
+ mImageSurface->Data(), mImageSurface->GetSize(), mImageSurface->Stride(),
+ ImageFormatToSurfaceFormat(format));
+}
+
+// The transparency bitmap routines are derived form the ones at nsWindow.cpp.
+// The difference here is that we compose to RGBA image and then create
+// the shape mask from final image alpha channel.
+static inline int32_t GetBitmapStride(int32_t width) { return (width + 7) / 8; }
+
+static bool ChangedMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
+ int32_t aMaskHeight, const nsIntRect& aRect,
+ uint8_t* aImageData) {
+ int32_t stride = aMaskWidth * SHAPED_IMAGE_SURFACE_BPP;
+ int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
+ int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
+ for (y = aRect.y; y < yMax; y++) {
+ gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
+ uint8_t* alphas = aImageData;
+ for (x = aRect.x; x < xMax; x++) {
+ bool newBit = *(alphas + SHAPED_IMAGE_SURFACE_ALPHA_INDEX) > 0x7f;
+ alphas += SHAPED_IMAGE_SURFACE_BPP;
+
+ gchar maskByte = maskBytes[x >> 3];
+ bool maskBit = (maskByte & (1 << (x & 7))) != 0;
+
+ if (maskBit != newBit) {
+ return true;
+ }
+ }
+ aImageData += stride;
+ }
+
+ return false;
+}
+
+static void UpdateMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
+ int32_t aMaskHeight, const nsIntRect& aRect,
+ uint8_t* aImageData) {
+ int32_t stride = aMaskWidth * SHAPED_IMAGE_SURFACE_BPP;
+ int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
+ int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
+ for (y = aRect.y; y < yMax; y++) {
+ gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
+ uint8_t* alphas = aImageData;
+ for (x = aRect.x; x < xMax; x++) {
+ bool newBit = *(alphas + SHAPED_IMAGE_SURFACE_ALPHA_INDEX) > 0x7f;
+ alphas += SHAPED_IMAGE_SURFACE_BPP;
+
+ gchar mask = 1 << (x & 7);
+ gchar maskByte = maskBytes[x >> 3];
+ // Note: '-newBit' turns 0 into 00...00 and 1 into 11...11
+ maskBytes[x >> 3] = (maskByte & ~mask) | (-newBit & mask);
+ }
+ aImageData += stride;
+ }
+}
+
+void WindowSurfaceX11Image::ResizeTransparencyBitmap(int aWidth, int aHeight) {
+ int32_t actualSize =
+ GetBitmapStride(mTransparencyBitmapWidth) * mTransparencyBitmapHeight;
+ int32_t newSize = GetBitmapStride(aWidth) * aHeight;
+
+ if (actualSize < newSize) {
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = new gchar[newSize];
+ }
+
+ mTransparencyBitmapWidth = aWidth;
+ mTransparencyBitmapHeight = aHeight;
+}
+
+void WindowSurfaceX11Image::ApplyTransparencyBitmap() {
+ gfx::IntSize size = mWindowSurface->GetSize();
+ bool maskChanged = true;
+
+ if (!mTransparencyBitmap) {
+ mTransparencyBitmapWidth = size.width;
+ mTransparencyBitmapHeight = size.height;
+
+ int32_t byteSize =
+ GetBitmapStride(mTransparencyBitmapWidth) * mTransparencyBitmapHeight;
+ mTransparencyBitmap = new gchar[byteSize];
+ } else {
+ bool sizeChanged = (size.width != mTransparencyBitmapWidth ||
+ size.height != mTransparencyBitmapHeight);
+
+ if (sizeChanged) {
+ ResizeTransparencyBitmap(size.width, size.height);
+ } else {
+ maskChanged = ChangedMaskBits(
+ mTransparencyBitmap, mTransparencyBitmapWidth,
+ mTransparencyBitmapHeight, nsIntRect(0, 0, size.width, size.height),
+ (uint8_t*)mImageSurface->Data());
+ }
+ }
+
+ if (maskChanged) {
+ UpdateMaskBits(mTransparencyBitmap, mTransparencyBitmapWidth,
+ mTransparencyBitmapHeight,
+ nsIntRect(0, 0, size.width, size.height),
+ (uint8_t*)mImageSurface->Data());
+
+ // We use X11 calls where possible, because GDK handles expose events
+ // for shaped windows in a way that's incompatible with us (Bug 635903).
+ // It doesn't occur when the shapes are set through X.
+ Display* xDisplay = mWindowSurface->XDisplay();
+ Window xDrawable = mWindowSurface->XDrawable();
+ Pixmap maskPixmap = XCreateBitmapFromData(
+ xDisplay, xDrawable, mTransparencyBitmap, mTransparencyBitmapWidth,
+ mTransparencyBitmapHeight);
+ XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
+ ShapeSet);
+ XFreePixmap(xDisplay, maskPixmap);
+ }
+}
+
+void WindowSurfaceX11Image::Commit(
+ const LayoutDeviceIntRegion& aInvalidRegion) {
+ RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateDrawTargetForCairoSurface(
+ mWindowSurface->CairoSurface(), mWindowSurface->GetSize());
+ RefPtr<gfx::SourceSurface> surf =
+ gfx::Factory::CreateSourceSurfaceForCairoSurface(
+ mImageSurface->CairoSurface(), mImageSurface->GetSize(),
+ mImageSurface->Format());
+ if (!dt || !surf) {
+ return;
+ }
+
+ gfx::IntRect bounds = aInvalidRegion.GetBounds().ToUnknownRect();
+ gfx::Rect rect(bounds);
+ if (rect.IsEmpty()) {
+ return;
+ }
+
+ uint32_t numRects = aInvalidRegion.GetNumRects();
+ if (numRects != 1) {
+ AutoTArray<IntRect, 32> rects;
+ rects.SetCapacity(numRects);
+ for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
+ rects.AppendElement(iter.Get().ToUnknownRect());
+ }
+ dt->PushDeviceSpaceClipRects(rects.Elements(), rects.Length());
+ }
+
+ if (mIsShaped) {
+ ApplyTransparencyBitmap();
+ }
+
+ dt->DrawSurface(surf, rect, rect);
+
+ if (numRects != 1) {
+ dt->PopClip();
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WindowSurfaceX11Image.h b/widget/gtk/WindowSurfaceX11Image.h
new file mode 100644
index 0000000000..b8b2a33f0e
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11Image.h
@@ -0,0 +1,48 @@
+/* -*- 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 _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
+
+#ifdef MOZ_X11
+
+# include <glib.h>
+# include "WindowSurfaceX11.h"
+# include "gfxXlibSurface.h"
+# include "gfxImageSurface.h"
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceX11Image : public WindowSurfaceX11 {
+ public:
+ WindowSurfaceX11Image(Display* aDisplay, Window aWindow, Visual* aVisual,
+ unsigned int aDepth, bool aIsShaped);
+ ~WindowSurfaceX11Image();
+
+ already_AddRefed<gfx::DrawTarget> Lock(
+ const LayoutDeviceIntRegion& aRegion) override;
+ void Commit(const LayoutDeviceIntRegion& aInvalidRegion) override;
+ bool IsFallback() const override { return true; }
+
+ private:
+ void ResizeTransparencyBitmap(int aWidth, int aHeight);
+ void ApplyTransparencyBitmap();
+
+ RefPtr<gfxXlibSurface> mWindowSurface;
+ RefPtr<gfxImageSurface> mImageSurface;
+
+ gchar* mTransparencyBitmap;
+ int32_t mTransparencyBitmapWidth;
+ int32_t mTransparencyBitmapHeight;
+ bool mIsShaped;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
diff --git a/widget/gtk/WindowSurfaceXRender.cpp b/widget/gtk/WindowSurfaceXRender.cpp
new file mode 100644
index 0000000000..9f040d9ce3
--- /dev/null
+++ b/widget/gtk/WindowSurfaceXRender.cpp
@@ -0,0 +1,75 @@
+/* -*- 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 "WindowSurfaceXRender.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "gfxPlatform.h"
+
+namespace mozilla {
+namespace widget {
+
+WindowSurfaceXRender::WindowSurfaceXRender(Display* aDisplay, Window aWindow,
+ Visual* aVisual, unsigned int aDepth)
+ : WindowSurfaceX11(aDisplay, aWindow, aVisual, aDepth),
+ mXlibSurface(nullptr),
+ mGC(X11None) {}
+
+WindowSurfaceXRender::~WindowSurfaceXRender() {
+ if (mGC != X11None) {
+ XFreeGC(mDisplay, mGC);
+ }
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceXRender::Lock(
+ const LayoutDeviceIntRegion& aRegion) {
+ gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+ gfx::IntSize size(bounds.XMost(), bounds.YMost());
+ if (!mXlibSurface || mXlibSurface->CairoStatus() ||
+ !(size <= mXlibSurface->GetSize())) {
+ mXlibSurface = gfxXlibSurface::Create(DefaultScreenOfDisplay(mDisplay),
+ mVisual, size, mWindow);
+ }
+ if (!mXlibSurface || mXlibSurface->CairoStatus()) {
+ return nullptr;
+ }
+
+ return gfxPlatform::CreateDrawTargetForSurface(mXlibSurface, size);
+}
+
+void WindowSurfaceXRender::Commit(const LayoutDeviceIntRegion& aInvalidRegion) {
+ AutoTArray<XRectangle, 32> xrects;
+ xrects.SetCapacity(aInvalidRegion.GetNumRects());
+
+ for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const LayoutDeviceIntRect& r = iter.Get();
+ XRectangle xrect = {(short)r.x, (short)r.y, (unsigned short)r.width,
+ (unsigned short)r.height};
+ xrects.AppendElement(xrect);
+ }
+
+ if (!mGC) {
+ mGC = XCreateGC(mDisplay, mWindow, 0, nullptr);
+ if (!mGC) {
+ NS_WARNING("Couldn't create X11 graphics context for window!");
+ return;
+ }
+ }
+
+ XSetClipRectangles(mDisplay, mGC, 0, 0, xrects.Elements(), xrects.Length(),
+ YXBanded);
+
+ MOZ_ASSERT(mXlibSurface && mXlibSurface->CairoStatus() == 0,
+ "Attempted to commit invalid surface!");
+ gfx::IntRect bounds = aInvalidRegion.GetBounds().ToUnknownRect();
+ gfx::IntSize size(bounds.XMost(), bounds.YMost());
+ XCopyArea(mDisplay, mXlibSurface->XDrawable(), mWindow, mGC, bounds.x,
+ bounds.y, size.width, size.height, bounds.x, bounds.y);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WindowSurfaceXRender.h b/widget/gtk/WindowSurfaceXRender.h
new file mode 100644
index 0000000000..8c8e2745eb
--- /dev/null
+++ b/widget/gtk/WindowSurfaceXRender.h
@@ -0,0 +1,37 @@
+/* -*- 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 _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_XRENDER_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_XRENDER_H
+
+#ifdef MOZ_X11
+
+# include "WindowSurfaceX11.h"
+# include "gfxXlibSurface.h"
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceXRender : public WindowSurfaceX11 {
+ public:
+ WindowSurfaceXRender(Display* aDisplay, Window aWindow, Visual* aVisual,
+ unsigned int aDepth);
+ ~WindowSurfaceXRender();
+
+ already_AddRefed<gfx::DrawTarget> Lock(
+ const LayoutDeviceIntRegion& aRegion) override;
+ void Commit(const LayoutDeviceIntRegion& aInvalidRegion) override;
+
+ private:
+ RefPtr<gfxXlibSurface> mXlibSurface;
+ GC mGC;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_XRENDER_H
diff --git a/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h b/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h
new file mode 100644
index 0000000000..4cf3b68f62
--- /dev/null
+++ b/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 GDKVERSIONMACROS_WRAPPER_H
+#define GDKVERSIONMACROS_WRAPPER_H
+
+/**
+ * Suppress all GTK3 deprecated warnings as deprecated functions are often
+ * used for GTK2 compatibility.
+ *
+ * GDK_VERSION_MIN_REQUIRED cannot be used to suppress warnings for functions
+ * deprecated in 3.0, but still needs to be set because gdkversionmacros.h
+ * asserts that GDK_VERSION_MAX_ALLOWED >= GDK_VERSION_MIN_REQUIRED and
+ * GDK_VERSION_MIN_REQUIRED >= GDK_VERSION_3_0.
+ *
+ * Setting GDK_DISABLE_DEPRECATION_WARNINGS would also disable
+ * GDK_UNAVAILABLE() warnings, which are useful.
+ */
+
+#define GDK_VERSION_MIN_REQUIRED GDK_VERSION_3_14
+
+#include_next <gdk/gdkversionmacros.h>
+
+#undef GDK_DEPRECATED
+#define GDK_DEPRECATED GDK_AVAILABLE_IN_ALL
+#undef GDK_DEPRECATED_FOR
+#define GDK_DEPRECATED_FOR(f) GDK_AVAILABLE_IN_ALL
+
+#endif /* GDKVERSIONMACROS_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkdnd.h b/widget/gtk/compat/gdk/gdkdnd.h
new file mode 100644
index 0000000000..bf9888d84f
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkdnd.h
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GDKDND_WRAPPER_H
+#define GDKDND_WRAPPER_H
+
+#define gdk_drag_context_get_actions gdk_drag_context_get_actions_
+#define gdk_drag_context_list_targets gdk_drag_context_list_targets_
+#define gdk_drag_context_get_dest_window gdk_drag_context_get_dest_window_
+#include_next <gdk/gdkdnd.h>
+#undef gdk_drag_context_get_actions
+#undef gdk_drag_context_list_targets
+#undef gdk_drag_context_get_dest_window
+
+static inline GdkDragAction gdk_drag_context_get_actions(
+ GdkDragContext* context) {
+ return context->actions;
+}
+
+static inline GList* gdk_drag_context_list_targets(GdkDragContext* context) {
+ return context->targets;
+}
+
+static inline GdkWindow* gdk_drag_context_get_dest_window(
+ GdkDragContext* context) {
+ return context->dest_window;
+}
+#endif /* GDKDND_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkkeysyms.h b/widget/gtk/compat/gdk/gdkkeysyms.h
new file mode 100644
index 0000000000..2f48d61fcc
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkkeysyms.h
@@ -0,0 +1,266 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 GDKKEYSYMS_WRAPPER_H
+#define GDKKEYSYMS_WRAPPER_H
+
+#include_next <gdk/gdkkeysyms.h>
+
+#ifndef GDK_ISO_Level5_Shift
+# define GDK_ISO_Level5_Shift 0xFE11
+#endif
+
+#ifndef GDK_ISO_Level5_Latch
+# define GDK_ISO_Level5_Latch 0xFE12
+#endif
+
+#ifndef GDK_ISO_Level5_Lock
+# define GDK_ISO_Level5_Lock 0xFE13
+#endif
+
+#ifndef GDK_dead_greek
+# define GDK_dead_greek 0xFE8C
+#endif
+
+#ifndef GDK_ch
+# define GDK_ch 0xFEA0
+#endif
+
+#ifndef GDK_Ch
+# define GDK_Ch 0xFEA1
+#endif
+
+#ifndef GDK_CH
+# define GDK_CH 0xFEA2
+#endif
+
+#ifndef GDK_c_h
+# define GDK_c_h 0xFEA3
+#endif
+
+#ifndef GDK_C_h
+# define GDK_C_h 0xFEA4
+#endif
+
+#ifndef GDK_C_H
+# define GDK_C_H 0xFEA5
+#endif
+
+#ifndef GDK_MonBrightnessUp
+# define GDK_MonBrightnessUp 0x1008FF02
+#endif
+
+#ifndef GDK_MonBrightnessDown
+# define GDK_MonBrightnessDown 0x1008FF03
+#endif
+
+#ifndef GDK_AudioLowerVolume
+# define GDK_AudioLowerVolume 0x1008FF11
+#endif
+
+#ifndef GDK_AudioMute
+# define GDK_AudioMute 0x1008FF12
+#endif
+
+#ifndef GDK_AudioRaiseVolume
+# define GDK_AudioRaiseVolume 0x1008FF13
+#endif
+
+#ifndef GDK_AudioPlay
+# define GDK_AudioPlay 0x1008FF14
+#endif
+
+#ifndef GDK_AudioStop
+# define GDK_AudioStop 0x1008FF15
+#endif
+
+#ifndef GDK_AudioPrev
+# define GDK_AudioPrev 0x1008FF16
+#endif
+
+#ifndef GDK_AudioNext
+# define GDK_AudioNext 0x1008FF17
+#endif
+
+#ifndef GDK_HomePage
+# define GDK_HomePage 0x1008FF18
+#endif
+
+#ifndef GDK_Mail
+# define GDK_Mail 0x1008FF19
+#endif
+
+#ifndef GDK_Search
+# define GDK_Search 0x1008FF1B
+#endif
+
+#ifndef GDK_AudioRecord
+# define GDK_AudioRecord 0x1008FF1C
+#endif
+
+#ifndef GDK_Back
+# define GDK_Back 0x1008FF26
+#endif
+
+#ifndef GDK_Forward
+# define GDK_Forward 0x1008FF27
+#endif
+
+#ifndef GDK_Stop
+# define GDK_Stop 0x1008FF28
+#endif
+
+#ifndef GDK_Refresh
+# define GDK_Refresh 0x1008FF29
+#endif
+
+#ifndef GDK_PowerOff
+# define GDK_PowerOff 0x1008FF2A
+#endif
+
+#ifndef GDK_Eject
+# define GDK_Eject 0x1008FF2C
+#endif
+
+#ifndef GDK_AudioPause
+# define GDK_AudioPause 0x1008FF31
+#endif
+
+#ifndef GDK_BrightnessAdjust
+# define GDK_BrightnessAdjust 0x1008FF3B
+#endif
+
+#ifndef GDK_AudioRewind
+# define GDK_AudioRewind 0x1008FF3E
+#endif
+
+#ifndef GDK_Launch0
+# define GDK_Launch0 0x1008FF40
+#endif
+
+#ifndef GDK_Launch1
+# define GDK_Launch1 0x1008FF41
+#endif
+
+#ifndef GDK_Launch2
+# define GDK_Launch2 0x1008FF42
+#endif
+
+#ifndef GDK_Launch3
+# define GDK_Launch3 0x1008FF43
+#endif
+
+#ifndef GDK_Launch4
+# define GDK_Launch4 0x1008FF44
+#endif
+
+#ifndef GDK_Launch5
+# define GDK_Launch5 0x1008FF45
+#endif
+
+#ifndef GDK_Launch6
+# define GDK_Launch6 0x1008FF46
+#endif
+
+#ifndef GDK_Launch7
+# define GDK_Launch7 0x1008FF47
+#endif
+
+#ifndef GDK_Launch8
+# define GDK_Launch8 0x1008FF48
+#endif
+
+#ifndef GDK_Launch9
+# define GDK_Launch9 0x1008FF49
+#endif
+
+#ifndef GDK_LaunchA
+# define GDK_LaunchA 0x1008FF4A
+#endif
+
+#ifndef GDK_LaunchB
+# define GDK_LaunchB 0x1008FF4B
+#endif
+
+#ifndef GDK_LaunchC
+# define GDK_LaunchC 0x1008FF4C
+#endif
+
+#ifndef GDK_LaunchD
+# define GDK_LaunchD 0x1008FF4D
+#endif
+
+#ifndef GDK_LaunchE
+# define GDK_LaunchE 0x1008FF4E
+#endif
+
+#ifndef GDK_LaunchF
+# define GDK_LaunchF 0x1008FF4F
+#endif
+
+#ifndef GDK_Copy
+# define GDK_Copy 0x1008FF57
+#endif
+
+#ifndef GDK_Cut
+# define GDK_Cut 0x1008FF58
+#endif
+
+#ifndef GDK_Paste
+# define GDK_Paste 0x1008FF6D
+#endif
+
+#ifndef GDK_Reload
+# define GDK_Reload 0x1008FF73
+#endif
+
+#ifndef GDK_AudioRandomPlay
+# define GDK_AudioRandomPlay 0x1008FF99
+#endif
+
+#ifndef GDK_Subtitle
+# define GDK_Subtitle 0x1008FF9A
+#endif
+
+#ifndef GDK_Red
+# define GDK_Red 0x1008FFA3
+#endif
+
+#ifndef GDK_Green
+# define GDK_Green 0x1008FFA4
+#endif
+
+#ifndef GDK_Yellow
+# define GDK_Yellow 0x1008FFA5
+#endif
+
+#ifndef GDK_Blue
+# define GDK_Blue 0x1008FFA6
+#endif
+
+#ifndef GDK_TouchpadToggle
+# define GDK_TouchpadToggle 0x1008FFA9
+#endif
+
+#ifndef GDK_TouchpadOn
+# define GDK_TouchpadOn 0x1008FFB0
+#endif
+
+#ifndef GDK_TouchpadOff
+# define GDK_TouchpadOff 0x1008ffb1
+#endif
+
+#ifndef GDK_LogWindowTree
+# define GDK_LogWindowTree 0x1008FE24
+#endif
+
+#ifndef GDK_LogGrabInfo
+# define GDK_LogGrabInfo 0x1008FE25
+#endif
+
+#ifndef GDK_Sleep
+# define GDK_Sleep 0x1008FF2F
+#endif
+
+#endif /* GDKKEYSYMS_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkvisual.h b/widget/gtk/compat/gdk/gdkvisual.h
new file mode 100644
index 0000000000..e5187a78da
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkvisual.h
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 GDKVISUAL_WRAPPER_H
+#define GDKVISUAL_WRAPPER_H
+
+#define gdk_visual_get_depth gdk_visual_get_depth_
+#include_next <gdk/gdkvisual.h>
+#undef gdk_visual_get_depth
+
+static inline gint gdk_visual_get_depth(GdkVisual* visual) {
+ return visual->depth;
+}
+#endif /* GDKVISUAL_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkwindow.h b/widget/gtk/compat/gdk/gdkwindow.h
new file mode 100644
index 0000000000..a4d2efbc89
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkwindow.h
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GDKWINDOW_WRAPPER_H
+#define GDKWINDOW_WRAPPER_H
+
+#define gdk_window_get_display gdk_window_get_display_
+#define gdk_window_get_screen gdk_window_get_screen_
+#include_next <gdk/gdkwindow.h>
+#undef gdk_window_get_display
+#undef gdk_window_get_screen
+
+static inline GdkDisplay* gdk_window_get_display(GdkWindow* window) {
+ return gdk_drawable_get_display(GDK_DRAWABLE(window));
+}
+
+static inline GdkScreen* gdk_window_get_screen(GdkWindow* window) {
+ return gdk_drawable_get_screen(window);
+}
+
+#if GDK_PIXBUF_MAJOR == 2 && GDK_PIXBUF_MINOR < 18
+static inline gboolean gdk_window_is_destroyed(GdkWindow* window) {
+ return GDK_WINDOW_OBJECT(window)->destroyed;
+}
+#endif
+#endif /* GDKWINDOW_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkx.h b/widget/gtk/compat/gdk/gdkx.h
new file mode 100644
index 0000000000..3d13c88b4d
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkx.h
@@ -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/. */
+
+#ifndef GDKX_WRAPPER_H
+#define GDKX_WRAPPER_H
+
+#include <gtk/gtkversion.h>
+
+#define gdk_x11_window_foreign_new_for_display \
+ gdk_x11_window_foreign_new_for_display_
+#define gdk_x11_window_lookup_for_display gdk_x11_window_lookup_for_display_
+#define gdk_x11_window_get_xid gdk_x11_window_get_xid_
+#if !GTK_CHECK_VERSION(2, 24, 0)
+# define gdk_x11_set_sm_client_id gdk_x11_set_sm_client_id_
+#endif
+#include_next <gdk/gdkx.h>
+#undef gdk_x11_window_foreign_new_for_display
+#undef gdk_x11_window_lookup_for_display
+#undef gdk_x11_window_get_xid
+
+static inline GdkWindow* gdk_x11_window_foreign_new_for_display(
+ GdkDisplay* display, Window window) {
+ return gdk_window_foreign_new_for_display(display, window);
+}
+
+static inline GdkWindow* gdk_x11_window_lookup_for_display(GdkDisplay* display,
+ Window window) {
+ return gdk_window_lookup_for_display(display, window);
+}
+
+static inline Window gdk_x11_window_get_xid(GdkWindow* window) {
+ return (GDK_WINDOW_XWINDOW(window));
+}
+
+#ifndef GDK_IS_X11_DISPLAY
+# define GDK_IS_X11_DISPLAY(a) (true)
+#endif
+
+#if !GTK_CHECK_VERSION(2, 24, 0)
+# undef gdk_x11_set_sm_client_id
+static inline void gdk_x11_set_sm_client_id(const gchar* sm_client_id) {
+ gdk_set_sm_client_id(sm_client_id);
+}
+#endif
+#endif /* GDKX_WRAPPER_H */
diff --git a/widget/gtk/compat/glib/gmem.h b/widget/gtk/compat/glib/gmem.h
new file mode 100644
index 0000000000..78f24bbd8d
--- /dev/null
+++ b/widget/gtk/compat/glib/gmem.h
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 GMEM_WRAPPER_H
+#define GMEM_WRAPPER_H
+
+#define g_malloc_n g_malloc_n_
+#define g_malloc0_n g_malloc0_n_
+#define g_realloc_n g_realloc_n_
+#include_next <glib/gmem.h>
+#undef g_malloc_n
+#undef g_malloc0_n
+#undef g_realloc_n
+
+#include <glib/gmessages.h>
+
+#undef g_new
+#define g_new(type, num) ((type*)g_malloc_n((num), sizeof(type)))
+
+#undef g_new0
+#define g_new0(type, num) ((type*)g_malloc0_n((num), sizeof(type)))
+
+#undef g_renew
+#define g_renew(type, ptr, num) ((type*)g_realloc_n(ptr, (num), sizeof(type)))
+
+#define _CHECK_OVERFLOW(num, type_size) \
+ if (G_UNLIKELY(type_size > 0 && num > G_MAXSIZE / type_size)) { \
+ g_error("%s: overflow allocating %" G_GSIZE_FORMAT "*%" G_GSIZE_FORMAT \
+ " bytes", \
+ G_STRLOC, num, type_size); \
+ }
+
+static inline gpointer g_malloc_n(gsize num, gsize type_size) {
+ _CHECK_OVERFLOW(num, type_size)
+ return g_malloc(num * type_size);
+}
+
+static inline gpointer g_malloc0_n(gsize num, gsize type_size) {
+ _CHECK_OVERFLOW(num, type_size)
+ return g_malloc0(num * type_size);
+}
+
+static inline gpointer g_realloc_n(gpointer ptr, gsize num, gsize type_size) {
+ _CHECK_OVERFLOW(num, type_size)
+ return g_realloc(ptr, num * type_size);
+}
+#endif /* GMEM_WRAPPER_H */
diff --git a/widget/gtk/compat/gtk/gtkwidget.h b/widget/gtk/compat/gtk/gtkwidget.h
new file mode 100644
index 0000000000..21165d61fa
--- /dev/null
+++ b/widget/gtk/compat/gtk/gtkwidget.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GTKWIDGET_WRAPPER_H
+#define GTKWIDGET_WRAPPER_H
+
+#define gtk_widget_set_mapped gtk_widget_set_mapped_
+#define gtk_widget_get_mapped gtk_widget_get_mapped_
+#define gtk_widget_set_realized gtk_widget_set_realized_
+#define gtk_widget_get_realized gtk_widget_get_realized_
+#include_next <gtk/gtkwidget.h>
+#undef gtk_widget_set_mapped
+#undef gtk_widget_get_mapped
+#undef gtk_widget_set_realized
+#undef gtk_widget_get_realized
+
+#include <gtk/gtkversion.h>
+
+static inline void gtk_widget_set_mapped(GtkWidget* widget, gboolean mapped) {
+ if (mapped)
+ GTK_WIDGET_SET_FLAGS(widget, GTK_MAPPED);
+ else
+ GTK_WIDGET_UNSET_FLAGS(widget, GTK_MAPPED);
+}
+
+static inline gboolean gtk_widget_get_mapped(GtkWidget* widget) {
+ return GTK_WIDGET_MAPPED(widget);
+}
+
+static inline void gtk_widget_set_realized(GtkWidget* widget,
+ gboolean realized) {
+ if (realized)
+ GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
+ else
+ GTK_WIDGET_UNSET_FLAGS(widget, GTK_REALIZED);
+}
+
+static inline gboolean gtk_widget_get_realized(GtkWidget* widget) {
+ return GTK_WIDGET_REALIZED(widget);
+}
+
+#endif /* GTKWIDGET_WRAPPER_H */
diff --git a/widget/gtk/compat/gtk/gtkwindow.h b/widget/gtk/compat/gtk/gtkwindow.h
new file mode 100644
index 0000000000..7c3d5873bd
--- /dev/null
+++ b/widget/gtk/compat/gtk/gtkwindow.h
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GTKWINDOW_WRAPPER_H
+#define GTKWINDOW_WRAPPER_H
+
+#define gtk_window_group_get_current_grab gtk_window_group_get_current_grab_
+#define gtk_window_get_window_type gtk_window_get_window_type_
+#include_next <gtk/gtkwindow.h>
+#undef gtk_window_group_get_current_grab
+#undef gtk_window_get_window_type
+
+static inline GtkWidget* gtk_window_group_get_current_grab(
+ GtkWindowGroup* window_group) {
+ if (!window_group->grabs) return NULL;
+
+ return GTK_WIDGET(window_group->grabs->data);
+}
+
+static inline GtkWindowType gtk_window_get_window_type(GtkWindow* window) {
+ gint type;
+ g_object_get(window, "type", &type, (void*)NULL);
+ return (GtkWindowType)type;
+}
+#endif /* GTKWINDOW_WRAPPER_H */
diff --git a/widget/gtk/components.conf b/widget/gtk/components.conf
new file mode 100644
index 0000000000..a65c8e02b0
--- /dev/null
+++ b/widget/gtk/components.conf
@@ -0,0 +1,166 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Headers = [
+ '/widget/gtk/nsWidgetFactory.h',
+]
+
+InitFunc = 'nsWidgetGtk2ModuleCtor'
+UnloadFunc = 'nsWidgetGtk2ModuleDtor'
+
+Classes = [
+ {
+ 'cid': '{2d96b3df-c051-11d1-a827-0040959a28c9}',
+ 'contract_ids': ['@mozilla.org/widget/appshell/gtk;1'],
+ 'legacy_constructor': 'nsAppShellConstructor',
+ 'headers': ['/widget/gtk/nsWidgetFactory.h'],
+ 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{c401eb80-f9ea-11d3-bb6f-e732b73ebe7c}',
+ 'contract_ids': ['@mozilla.org/gfx/screenmanager;1'],
+ 'singleton': True,
+ 'type': 'mozilla::widget::ScreenManager',
+ 'headers': ['mozilla/StaticPtr.h', 'mozilla/widget/ScreenManager.h'],
+ 'constructor': 'mozilla::widget::ScreenManager::GetAddRefedSingleton',
+ 'processes': ProcessSelector.ALLOW_IN_GPU_AND_MAIN_PROCESS,
+ },
+ {
+ 'cid': '{a9339876-0027-430f-b953-84c9c11c2da3}',
+ 'contract_ids': ['@mozilla.org/widget/taskbarprogress/gtk;1'],
+ 'type': 'TaskbarProgress',
+ 'headers': ['/widget/gtk/TaskbarProgress.h'],
+ },
+ {
+ 'cid': '{0f872c8c-3ee6-46bd-92a2-69652c6b474e}',
+ 'contract_ids': ['@mozilla.org/colorpicker;1'],
+ 'type': 'nsColorPicker',
+ 'headers': ['/widget/gtk/nsColorPicker.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{bd57cee8-1dd1-11b2-9fe7-95cf4709aea3}',
+ 'contract_ids': ['@mozilla.org/filepicker;1'],
+ 'type': 'nsFilePicker',
+ 'headers': ['/widget/gtk/nsFilePicker.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{948a0023-e3a7-11d2-96cf-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/htmlformatconverter;1'],
+ 'type': 'nsHTMLFormatConverter',
+ 'headers': ['/widget/nsHTMLFormatConverter.h'],
+ },
+ {
+ 'cid': '{b148eed2-236d-11d3-b35c-00a0cc3c1cde}',
+ 'contract_ids': ['@mozilla.org/sound;1'],
+ 'singleton': True,
+ 'type': 'nsISound',
+ 'constructor': 'nsSound::GetInstance',
+ 'headers': ['/widget/gtk/nsSound.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{fc2389b8-c650-4093-9e42-b05e5f0685b7}',
+ 'contract_ids': ['@mozilla.org/widget/image-to-gdk-pixbuf;1'],
+ 'type': 'nsImageToPixbuf',
+ 'headers': ['/widget/gtk/nsImageToPixbuf.h'],
+ },
+ {
+ 'cid': '{8b5314bc-db01-11d2-96ce-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/transferable;1'],
+ 'type': 'nsTransferable',
+ 'headers': ['/widget/nsTransferable.h'],
+ },
+ {
+ 'cid': '{e221df9b-3d66-4045-9a66-5720949f8d10}',
+ 'contract_ids': ['@mozilla.org/applicationchooser;1'],
+ 'type': 'nsApplicationChooser',
+ 'headers': ['/widget/gtk/nsApplicationChooser.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+]
+
+if defined('MOZ_X11'):
+ Classes += [
+ {
+ 'js_name': 'clipboard',
+ 'cid': '{8b5314ba-db01-11d2-96ce-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/clipboard;1'],
+ 'interfaces': ['nsIClipboard'],
+ 'type': 'nsIClipboard',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ 'overridable': True,
+ },
+ {
+ 'cid': '{77221d5a-1dd2-11b2-8c69-c710f15d2ed5}',
+ 'contract_ids': ['@mozilla.org/widget/clipboardhelper;1'],
+ 'type': 'nsClipboardHelper',
+ 'headers': ['/widget/nsClipboardHelper.h'],
+ },
+ {
+ 'cid': '{8b5314bb-db01-11d2-96ce-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/dragservice;1'],
+ 'singleton': True,
+ 'type': 'nsDragService',
+ 'headers': ['/widget/gtk/nsDragService.h'],
+ 'constructor': 'nsDragService::GetInstance',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{d755a760-9f27-11df-0800-200c9a664242}',
+ 'contract_ids': ['@mozilla.org/gfx/info;1'],
+ 'type': 'mozilla::widget::GfxInfo',
+ 'headers': ['/widget/GfxInfoX11.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.ALLOW_IN_GPU_PROCESS,
+ },
+ {
+ 'cid': '{6987230e-0098-4e78-bc5f-1493ee7519fa}',
+ 'contract_ids': ['@mozilla.org/widget/useridleservice;1'],
+ 'singleton': True,
+ 'type': 'nsUserIdleService',
+ 'headers': ['/widget/gtk/nsUserIdleServiceGTK.h'],
+ 'constructor': 'nsUserIdleServiceGTK::GetInstance',
+ },
+ ]
+
+if defined('NS_PRINTING'):
+ Classes += [
+ {
+ 'cid': '{d3f69889-e13a-4321-980c-a39332e21f34}',
+ 'contract_ids': ['@mozilla.org/gfx/devicecontextspec;1'],
+ 'type': 'nsDeviceContextSpecGTK',
+ 'headers': ['/widget/gtk/nsDeviceContextSpecG.h'],
+ },
+ {
+ 'cid': '{06beec76-a183-4d9f-85dd-085f26da565a}',
+ 'contract_ids': ['@mozilla.org/widget/printdialog-service;1'],
+ 'type': 'nsPrintDialogServiceGTK',
+ 'headers': ['/widget/gtk/nsPrintDialogGTK.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{2f977d53-5485-11d4-87e2-0010a4e75ef2}',
+ 'contract_ids': ['@mozilla.org/gfx/printsession;1'],
+ 'type': 'nsPrintSession',
+ 'headers': ['/widget/nsPrintSession.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{841387c8-72e6-484b-9296-bf6eea80d58a}',
+ 'contract_ids': ['@mozilla.org/gfx/printsettings-service;1'],
+ 'type': 'nsPrintSettingsServiceGTK',
+ 'headers': ['/widget/gtk/nsPrintSettingsServiceGTK.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{a6cf9129-15b3-11d2-932e-00805f8add32}',
+ 'contract_ids': ['@mozilla.org/gfx/printerlist;1'],
+ 'type': 'nsPrinterListCUPS',
+ 'headers': ['/widget/nsPrinterListCUPS.h'],
+ },
+ ]
diff --git a/widget/gtk/crashtests/540078-1.xhtml b/widget/gtk/crashtests/540078-1.xhtml
new file mode 100644
index 0000000000..f5de1b9aee
--- /dev/null
+++ b/widget/gtk/crashtests/540078-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><scrollcorner class="zebra"/></hbox><style style="display: none;">.zebra { -moz-appearance: checkbox; }</style></html>
diff --git a/widget/gtk/crashtests/673390-1.html b/widget/gtk/crashtests/673390-1.html
new file mode 100644
index 0000000000..8463f67f05
--- /dev/null
+++ b/widget/gtk/crashtests/673390-1.html
@@ -0,0 +1 @@
+<div style="-moz-appearance: progresschunk; position: fixed"></div>
diff --git a/widget/gtk/crashtests/crashtests.list b/widget/gtk/crashtests/crashtests.list
new file mode 100644
index 0000000000..9ae47c2233
--- /dev/null
+++ b/widget/gtk/crashtests/crashtests.list
@@ -0,0 +1,2 @@
+load 540078-1.xhtml
+load 673390-1.html
diff --git a/widget/gtk/gtk3drawing.cpp b/widget/gtk/gtk3drawing.cpp
new file mode 100644
index 0000000000..c8a39bfd5c
--- /dev/null
+++ b/widget/gtk/gtk3drawing.cpp
@@ -0,0 +1,3214 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file contains painting functions for each of the gtk2 widgets.
+ * Adapted from the gtkdrawing.c, and gtk+2.0 source.
+ */
+
+#include <gtk/gtk.h>
+#include <gdk/gdkprivate.h>
+#include <string.h>
+#include "gtkdrawing.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ScopeExit.h"
+#include "prinrval.h"
+#include "WidgetStyleCache.h"
+#include "nsString.h"
+#include "nsDebug.h"
+
+#include <math.h>
+#include <dlfcn.h>
+
+static gboolean checkbox_check_state;
+static gboolean notebook_has_tab_gap;
+
+static ScrollbarGTKMetrics sScrollbarMetrics[2];
+static ScrollbarGTKMetrics sActiveScrollbarMetrics[2];
+static ToggleGTKMetrics sCheckboxMetrics;
+static ToggleGTKMetrics sRadioMetrics;
+static ToggleGTKMetrics sMenuRadioMetrics;
+static ToggleGTKMetrics sMenuCheckboxMetrics;
+static ToolbarGTKMetrics sToolbarMetrics;
+static CSDWindowDecorationSize sToplevelWindowDecorationSize;
+static CSDWindowDecorationSize sPopupWindowDecorationSize;
+
+using mozilla::Span;
+
+#define ARROW_UP 0
+#define ARROW_DOWN G_PI
+#define ARROW_RIGHT G_PI_2
+#define ARROW_LEFT (G_PI + G_PI_2)
+
+#if 0
+// It's used for debugging only to compare Gecko widget style with
+// the ones used by Gtk+ applications.
+static void
+style_path_print(GtkStyleContext *context)
+{
+ const GtkWidgetPath* path = gtk_style_context_get_path(context);
+
+ static auto sGtkWidgetPathToStringPtr =
+ (char * (*)(const GtkWidgetPath *))
+ dlsym(RTLD_DEFAULT, "gtk_widget_path_to_string");
+
+ fprintf(stderr, "Style path:\n%s\n\n", sGtkWidgetPathToStringPtr(path));
+}
+#endif
+
+static GtkBorder operator-(const GtkBorder& first, const GtkBorder& second) {
+ GtkBorder result;
+ result.left = first.left - second.left;
+ result.right = first.right - second.right;
+ result.top = first.top - second.top;
+ result.bottom = first.bottom - second.bottom;
+ return result;
+}
+
+static GtkBorder operator+(const GtkBorder& first, const GtkBorder& second) {
+ GtkBorder result;
+ result.left = first.left + second.left;
+ result.right = first.right + second.right;
+ result.top = first.top + second.top;
+ result.bottom = first.bottom + second.bottom;
+ return result;
+}
+
+static GtkBorder operator+=(GtkBorder& first, const GtkBorder& second) {
+ first.left += second.left;
+ first.right += second.right;
+ first.top += second.top;
+ first.bottom += second.bottom;
+ return first;
+}
+
+static gint moz_gtk_get_tab_thickness(GtkStyleContext* style);
+
+static gint moz_gtk_menu_item_paint(WidgetNodeType widget, cairo_t* cr,
+ GdkRectangle* rect, GtkWidgetState* state,
+ GtkTextDirection direction);
+
+static GtkBorder GetMarginBorderPadding(GtkStyleContext* aStyle);
+
+static void Inset(GdkRectangle*, const GtkBorder&);
+
+static void InsetByMargin(GdkRectangle*, GtkStyleContext* style);
+
+static void moz_gtk_add_style_margin(GtkStyleContext* style, gint* left,
+ gint* top, gint* right, gint* bottom) {
+ GtkBorder margin;
+
+ gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+ &margin);
+ *left += margin.left;
+ *right += margin.right;
+ *top += margin.top;
+ *bottom += margin.bottom;
+}
+
+static void moz_gtk_add_style_border(GtkStyleContext* style, gint* left,
+ gint* top, gint* right, gint* bottom) {
+ GtkBorder border;
+
+ gtk_style_context_get_border(style, gtk_style_context_get_state(style),
+ &border);
+
+ *left += border.left;
+ *right += border.right;
+ *top += border.top;
+ *bottom += border.bottom;
+}
+
+static void moz_gtk_add_style_padding(GtkStyleContext* style, gint* left,
+ gint* top, gint* right, gint* bottom) {
+ GtkBorder padding;
+
+ gtk_style_context_get_padding(style, gtk_style_context_get_state(style),
+ &padding);
+
+ *left += padding.left;
+ *right += padding.right;
+ *top += padding.top;
+ *bottom += padding.bottom;
+}
+
+static void moz_gtk_add_margin_border_padding(GtkStyleContext* style,
+ gint* left, gint* top,
+ gint* right, gint* bottom) {
+ moz_gtk_add_style_margin(style, left, top, right, bottom);
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+}
+
+static void moz_gtk_add_border_padding(GtkStyleContext* style, gint* left,
+ gint* top, gint* right, gint* bottom) {
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+}
+
+// In case there's an error in Gtk theme and preferred size is zero,
+// return some sane values to pass mozilla automation tests.
+// It should not happen in real-life.
+#define MIN_WIDGET_SIZE 10
+static void moz_gtk_sanity_preferred_size(GtkRequisition* requisition) {
+ if (requisition->width <= 0) {
+ requisition->width = MIN_WIDGET_SIZE;
+ }
+ if (requisition->height <= 0) {
+ requisition->height = MIN_WIDGET_SIZE;
+ }
+}
+
+// GetStateFlagsFromGtkWidgetState() can be safely used for the specific
+// GtkWidgets that set both prelight and active flags. For other widgets,
+// either the GtkStateFlags or Gecko's GtkWidgetState need to be carefully
+// adjusted to match GTK behavior. Although GTK sets insensitive and focus
+// flags in the generic GtkWidget base class, GTK adds prelight and active
+// flags only to widgets that are expected to demonstrate prelight or active
+// states. This contrasts with HTML where any element may have :active and
+// :hover states, and so Gecko's GtkStateFlags do not necessarily map to GTK
+// flags. Failure to restrict the flags in the same way as GTK can cause
+// generic CSS selectors from some themes to unintentionally match elements
+// that are not expected to change appearance on hover or mouse-down.
+static GtkStateFlags GetStateFlagsFromGtkWidgetState(GtkWidgetState* state) {
+ GtkStateFlags stateFlags = GTK_STATE_FLAG_NORMAL;
+
+ if (state->disabled)
+ stateFlags = GTK_STATE_FLAG_INSENSITIVE;
+ else {
+ if (state->depressed || state->active)
+ stateFlags =
+ static_cast<GtkStateFlags>(stateFlags | GTK_STATE_FLAG_ACTIVE);
+ if (state->inHover)
+ stateFlags =
+ static_cast<GtkStateFlags>(stateFlags | GTK_STATE_FLAG_PRELIGHT);
+ if (state->focused)
+ stateFlags =
+ static_cast<GtkStateFlags>(stateFlags | GTK_STATE_FLAG_FOCUSED);
+ if (state->backdrop)
+ stateFlags =
+ static_cast<GtkStateFlags>(stateFlags | GTK_STATE_FLAG_BACKDROP);
+ }
+
+ return stateFlags;
+}
+
+static GtkStateFlags GetStateFlagsFromGtkTabFlags(GtkTabFlags flags) {
+ return ((flags & MOZ_GTK_TAB_SELECTED) == 0) ? GTK_STATE_FLAG_NORMAL
+ : GTK_STATE_FLAG_ACTIVE;
+}
+
+gint moz_gtk_init() {
+ if (gtk_major_version > 3 ||
+ (gtk_major_version == 3 && gtk_minor_version >= 14))
+ checkbox_check_state = GTK_STATE_FLAG_CHECKED;
+ else
+ checkbox_check_state = GTK_STATE_FLAG_ACTIVE;
+
+ moz_gtk_refresh();
+
+ return MOZ_GTK_SUCCESS;
+}
+
+void moz_gtk_refresh() {
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ // Deprecated for Gtk >= 3.20+
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_TAB_TOP);
+ gtk_style_context_get_style(style, "has-tab-gap", &notebook_has_tab_gap,
+ NULL);
+ } else {
+ notebook_has_tab_gap = true;
+ }
+
+ sScrollbarMetrics[GTK_ORIENTATION_HORIZONTAL].initialized = false;
+ sScrollbarMetrics[GTK_ORIENTATION_VERTICAL].initialized = false;
+ sActiveScrollbarMetrics[GTK_ORIENTATION_HORIZONTAL].initialized = false;
+ sActiveScrollbarMetrics[GTK_ORIENTATION_VERTICAL].initialized = false;
+ sCheckboxMetrics.initialized = false;
+ sRadioMetrics.initialized = false;
+ sMenuCheckboxMetrics.initialized = false;
+ sMenuRadioMetrics.initialized = false;
+ sToolbarMetrics.initialized = false;
+ sToplevelWindowDecorationSize.initialized = false;
+ sPopupWindowDecorationSize.initialized = false;
+
+ /* This will destroy all of our widgets */
+ ResetWidgetCache();
+}
+
+static gint moz_gtk_get_focus_outline_size(GtkStyleContext* style,
+ gint* focus_h_width,
+ gint* focus_v_width) {
+ GtkBorder border;
+ gtk_style_context_get_border(style, gtk_style_context_get_state(style),
+ &border);
+ *focus_h_width = border.left;
+ *focus_v_width = border.top;
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_get_focus_outline_size(gint* focus_h_width, gint* focus_v_width) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_ENTRY);
+ moz_gtk_get_focus_outline_size(style, focus_h_width, focus_v_width);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_menuitem_get_horizontal_padding(gint* horizontal_padding) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_MENUITEM);
+ gtk_style_context_get_style(style, "horizontal-padding", horizontal_padding,
+ nullptr);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_checkmenuitem_get_horizontal_padding(gint* horizontal_padding) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_CHECKMENUITEM);
+ gtk_style_context_get_style(style, "horizontal-padding", horizontal_padding,
+ nullptr);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_button_get_default_overflow(gint* border_top, gint* border_left,
+ gint* border_bottom,
+ gint* border_right) {
+ GtkBorder* default_outside_border;
+
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_BUTTON);
+ gtk_style_context_get_style(style, "default-outside-border",
+ &default_outside_border, NULL);
+
+ if (default_outside_border) {
+ *border_top = default_outside_border->top;
+ *border_left = default_outside_border->left;
+ *border_bottom = default_outside_border->bottom;
+ *border_right = default_outside_border->right;
+ gtk_border_free(default_outside_border);
+ } else {
+ *border_top = *border_left = *border_bottom = *border_right = 0;
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_button_get_default_border(gint* border_top,
+ gint* border_left,
+ gint* border_bottom,
+ gint* border_right) {
+ GtkBorder* default_border;
+
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_BUTTON);
+ gtk_style_context_get_style(style, "default-border", &default_border, NULL);
+
+ if (default_border) {
+ *border_top = default_border->top;
+ *border_left = default_border->left;
+ *border_bottom = default_border->bottom;
+ *border_right = default_border->right;
+ gtk_border_free(default_border);
+ } else {
+ /* see gtkbutton.c */
+ *border_top = *border_left = *border_bottom = *border_right = 1;
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_splitter_get_metrics(gint orientation, gint* size) {
+ GtkStyleContext* style;
+ if (orientation == GTK_ORIENTATION_HORIZONTAL) {
+ style = GetStyleContext(MOZ_GTK_SPLITTER_HORIZONTAL);
+ } else {
+ style = GetStyleContext(MOZ_GTK_SPLITTER_VERTICAL);
+ }
+ gtk_style_context_get_style(style, "handle_size", size, NULL);
+ return MOZ_GTK_SUCCESS;
+}
+
+static void CalculateToolbarButtonMetrics(WidgetNodeType aAppearance,
+ ToolbarButtonGTKMetrics* aMetrics) {
+ gint iconWidth, iconHeight;
+ if (!gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &iconWidth, &iconHeight)) {
+ NS_WARNING("Failed to get Gtk+ icon size for titlebar button!");
+
+ // Use some reasonable fallback size
+ iconWidth = 16;
+ iconHeight = 16;
+ }
+
+ GtkStyleContext* style = GetStyleContext(aAppearance);
+ gint width = 0, height = 0;
+ if (gtk_check_version(3, 20, 0) == nullptr) {
+ gtk_style_context_get(style, gtk_style_context_get_state(style),
+ "min-width", &width, "min-height", &height, NULL);
+ }
+
+ // Cover cases when min-width/min-height is not set, it's invalid
+ // or we're running on Gtk+ < 3.20.
+ if (width < iconWidth) width = iconWidth;
+ if (height < iconHeight) height = iconHeight;
+
+ gint left = 0, top = 0, right = 0, bottom = 0;
+ moz_gtk_add_border_padding(style, &left, &top, &right, &bottom);
+
+ // Button size is calculated as min-width/height + border/padding.
+ width += left + right;
+ height += top + bottom;
+
+ // Place icon at button center.
+ aMetrics->iconXPosition = (width - iconWidth) / 2;
+ aMetrics->iconYPosition = (height - iconHeight) / 2;
+
+ aMetrics->minSizeWithBorderMargin.width = width;
+ aMetrics->minSizeWithBorderMargin.height = height;
+}
+
+// We support LTR layout only here for now.
+static void CalculateToolbarButtonSpacing(WidgetNodeType aAppearance,
+ ToolbarButtonGTKMetrics* aMetrics) {
+ GtkStyleContext* style = GetStyleContext(aAppearance);
+ gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+ &aMetrics->buttonMargin);
+
+ // Get titlebar spacing, a default one is 6 pixels (gtk/gtkheaderbar.c)
+ gint buttonSpacing = 6;
+ g_object_get(GetWidget(MOZ_GTK_HEADER_BAR), "spacing", &buttonSpacing,
+ nullptr);
+
+ // We apply spacing as a margin equaly to both adjacent buttons.
+ buttonSpacing /= 2;
+
+ if (!aMetrics->firstButton) {
+ aMetrics->buttonMargin.left += buttonSpacing;
+ }
+ if (!aMetrics->lastButton) {
+ aMetrics->buttonMargin.right += buttonSpacing;
+ }
+
+ aMetrics->minSizeWithBorderMargin.width +=
+ aMetrics->buttonMargin.right + aMetrics->buttonMargin.left;
+ aMetrics->minSizeWithBorderMargin.height +=
+ aMetrics->buttonMargin.top + aMetrics->buttonMargin.bottom;
+}
+
+size_t GetGtkHeaderBarButtonLayout(Span<ButtonLayout> aButtonLayout,
+ bool* aReversedButtonsPlacement) {
+ gchar* decorationLayoutSetting = nullptr;
+ GtkSettings* settings = gtk_settings_get_default();
+ g_object_get(settings, "gtk-decoration-layout", &decorationLayoutSetting,
+ nullptr);
+ auto free = mozilla::MakeScopeExit([&] { g_free(decorationLayoutSetting); });
+
+ // Use a default layout
+ const gchar* decorationLayout = "menu:minimize,maximize,close";
+ if (decorationLayoutSetting) {
+ decorationLayout = decorationLayoutSetting;
+ }
+
+ // "minimize,maximize,close:" layout means buttons are on the opposite
+ // titlebar side. close button is always there.
+ if (aReversedButtonsPlacement) {
+ const char* closeButton = strstr(decorationLayout, "close");
+ const char* separator = strchr(decorationLayout, ':');
+ *aReversedButtonsPlacement =
+ closeButton && separator && closeButton < separator;
+ }
+
+ // We check what position a button string is stored in decorationLayout.
+ //
+ // decorationLayout gets its value from the GNOME preference:
+ // org.gnome.desktop.vm.preferences.button-layout via the
+ // gtk-decoration-layout property.
+ //
+ // Documentation of the gtk-decoration-layout property can be found here:
+ // https://developer.gnome.org/gtk3/stable/GtkSettings.html#GtkSettings--gtk-decoration-layout
+ if (aButtonLayout.IsEmpty()) {
+ return 0;
+ }
+
+ nsDependentCSubstring layout(decorationLayout, strlen(decorationLayout));
+
+ bool right = false;
+ size_t activeButtons = 0;
+ for (const auto& part : layout.Split(':')) {
+ for (const auto& button : part.Split(',')) {
+ if (button.EqualsLiteral("close")) {
+ aButtonLayout[activeButtons++] = {MOZ_GTK_HEADER_BAR_BUTTON_CLOSE,
+ right};
+ } else if (button.EqualsLiteral("minimize")) {
+ aButtonLayout[activeButtons++] = {MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE,
+ right};
+ } else if (button.EqualsLiteral("maximize")) {
+ aButtonLayout[activeButtons++] = {MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE,
+ right};
+ }
+ if (activeButtons == aButtonLayout.Length()) {
+ return activeButtons;
+ }
+ }
+ right = true;
+ }
+ return activeButtons;
+}
+
+static void EnsureToolbarMetrics(void) {
+ if (!sToolbarMetrics.initialized) {
+ // Make sure we have clean cache after theme reset, etc.
+ memset(&sToolbarMetrics, 0, sizeof(sToolbarMetrics));
+
+ // Calculate titlebar button visibility and positions.
+ ButtonLayout aButtonLayout[TOOLBAR_BUTTONS];
+ size_t activeButtonNums =
+ GetGtkHeaderBarButtonLayout(mozilla::Span(aButtonLayout), nullptr);
+
+ for (size_t i = 0; i < activeButtonNums; i++) {
+ int buttonIndex =
+ (aButtonLayout[i].mType - MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
+ ToolbarButtonGTKMetrics* metrics = sToolbarMetrics.button + buttonIndex;
+ metrics->visible = true;
+ // Mark first button
+ if (!i) {
+ metrics->firstButton = true;
+ }
+ // Mark last button.
+ if (i == (activeButtonNums - 1)) {
+ metrics->lastButton = true;
+ }
+
+ CalculateToolbarButtonMetrics(aButtonLayout[i].mType, metrics);
+ CalculateToolbarButtonSpacing(aButtonLayout[i].mType, metrics);
+ }
+
+ sToolbarMetrics.initialized = true;
+ }
+}
+
+const ToolbarButtonGTKMetrics* GetToolbarButtonMetrics(
+ WidgetNodeType aAppearance) {
+ EnsureToolbarMetrics();
+
+ int buttonIndex = (aAppearance - MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
+ NS_ASSERTION(buttonIndex >= 0 && buttonIndex <= TOOLBAR_BUTTONS,
+ "GetToolbarButtonMetrics(): Wrong titlebar button!");
+ return sToolbarMetrics.button + buttonIndex;
+}
+
+static gint moz_gtk_window_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkTextDirection direction) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW, direction);
+
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_style_context_restore(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_button_paint(cairo_t* cr, const GdkRectangle* rect,
+ GtkWidgetState* state, GtkReliefStyle relief,
+ GtkWidget* widget,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style = gtk_widget_get_style_context(widget);
+ gint x = rect->x, y = rect->y, width = rect->width, height = rect->height;
+
+ gtk_widget_set_direction(widget, direction);
+
+ gtk_style_context_save(style);
+ StyleContextSetScale(style, state->scale);
+ gtk_style_context_set_state(style, state_flags);
+
+ if (state->isDefault && relief == GTK_RELIEF_NORMAL) {
+ /* handle default borders both outside and inside the button */
+ gint default_top, default_left, default_bottom, default_right;
+ moz_gtk_button_get_default_overflow(&default_top, &default_left,
+ &default_bottom, &default_right);
+ x -= default_left;
+ y -= default_top;
+ width += default_left + default_right;
+ height += default_top + default_bottom;
+ gtk_render_background(style, cr, x, y, width, height);
+ gtk_render_frame(style, cr, x, y, width, height);
+ moz_gtk_button_get_default_border(&default_top, &default_left,
+ &default_bottom, &default_right);
+ x += default_left;
+ y += default_top;
+ width -= (default_left + default_right);
+ height -= (default_top + default_bottom);
+ } else if (relief != GTK_RELIEF_NONE || state->depressed ||
+ (state_flags & GTK_STATE_FLAG_PRELIGHT)) {
+ /* the following line can trigger an assertion (Crux theme)
+ file ../../gdk/gdkwindow.c: line 1846 (gdk_window_clear_area):
+ assertion `GDK_IS_WINDOW (window)' failed */
+ gtk_render_background(style, cr, x, y, width, height);
+ gtk_render_frame(style, cr, x, y, width, height);
+ }
+
+ if (state->focused) {
+ GtkBorder border;
+ gtk_style_context_get_border(style, state_flags, &border);
+ x += border.left;
+ y += border.top;
+ width -= (border.left + border.right);
+ height -= (border.top + border.bottom);
+ gtk_render_focus(style, cr, x, y, width, height);
+ }
+ gtk_style_context_restore(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_header_bar_button_paint(cairo_t* cr,
+ const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkReliefStyle relief,
+ WidgetNodeType aIconWidgetType,
+ GtkTextDirection direction) {
+ WidgetNodeType buttonWidgetType =
+ (aIconWidgetType == MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE)
+ ? MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE
+ : aIconWidgetType;
+
+ GdkRectangle rect = *aRect;
+ // We need to inset our calculated margin because it also
+ // contains titlebar button spacing.
+ const ToolbarButtonGTKMetrics* metrics =
+ GetToolbarButtonMetrics(buttonWidgetType);
+ Inset(&rect, metrics->buttonMargin);
+
+ GtkWidget* buttonWidget = GetWidget(buttonWidgetType);
+ moz_gtk_button_paint(cr, &rect, state, relief, buttonWidget, direction);
+
+ GtkWidget* iconWidget =
+ gtk_bin_get_child(GTK_BIN(GetWidget(aIconWidgetType)));
+ cairo_surface_t* surface = GetWidgetIconSurface(iconWidget, state->scale);
+
+ if (surface) {
+ GtkStyleContext* style = gtk_widget_get_style_context(buttonWidget);
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+
+ gtk_style_context_save(style);
+ StyleContextSetScale(style, state->scale);
+ gtk_style_context_set_state(style, state_flags);
+
+ const ToolbarButtonGTKMetrics* metrics =
+ GetToolbarButtonMetrics(buttonWidgetType);
+
+ /* This is available since Gtk+ 3.10 as well as GtkHeaderBar */
+ static auto sGtkRenderIconSurfacePtr =
+ (void (*)(GtkStyleContext*, cairo_t*, cairo_surface_t*, gdouble,
+ gdouble))dlsym(RTLD_DEFAULT, "gtk_render_icon_surface");
+
+ sGtkRenderIconSurfacePtr(style, cr, surface,
+ rect.x + metrics->iconXPosition,
+ rect.y + metrics->iconYPosition);
+ gtk_style_context_restore(style);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_toggle_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state, gboolean selected,
+ gboolean inconsistent, gboolean isradio,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ gint x, y, width, height;
+ GtkStyleContext* style;
+
+ // We need to call this before GetStyleContext, because otherwise we would
+ // reset state flags
+ const ToggleGTKMetrics* metrics =
+ GetToggleMetrics(isradio ? MOZ_GTK_RADIOBUTTON : MOZ_GTK_CHECKBUTTON);
+ // Clamp the rect and paint it center aligned in the rect.
+ x = rect->x;
+ y = rect->y;
+ width = rect->width;
+ height = rect->height;
+
+ if (rect->width < rect->height) {
+ y = rect->y + (rect->height - rect->width) / 2;
+ height = rect->width;
+ }
+
+ if (rect->height < rect->width) {
+ x = rect->x + (rect->width - rect->height) / 2;
+ width = rect->height;
+ }
+
+ if (selected)
+ state_flags =
+ static_cast<GtkStateFlags>(state_flags | checkbox_check_state);
+
+ if (inconsistent)
+ state_flags =
+ static_cast<GtkStateFlags>(state_flags | GTK_STATE_FLAG_INCONSISTENT);
+
+ style = GetStyleContext(isradio ? MOZ_GTK_RADIOBUTTON : MOZ_GTK_CHECKBUTTON,
+ state->scale, direction, state_flags);
+
+ if (gtk_check_version(3, 20, 0) == nullptr) {
+ gtk_render_background(style, cr, x, y, width, height);
+ gtk_render_frame(style, cr, x, y, width, height);
+ // Indicator is inset by the toggle's padding and border.
+ gint indicator_x = x + metrics->borderAndPadding.left;
+ gint indicator_y = y + metrics->borderAndPadding.top;
+ gint indicator_width = metrics->minSizeWithBorder.width -
+ metrics->borderAndPadding.left -
+ metrics->borderAndPadding.right;
+ gint indicator_height = metrics->minSizeWithBorder.height -
+ metrics->borderAndPadding.top -
+ metrics->borderAndPadding.bottom;
+ if (isradio) {
+ gtk_render_option(style, cr, indicator_x, indicator_y, indicator_width,
+ indicator_height);
+ } else {
+ gtk_render_check(style, cr, indicator_x, indicator_y, indicator_width,
+ indicator_height);
+ }
+ } else {
+ if (isradio) {
+ gtk_render_option(style, cr, x, y, width, height);
+ } else {
+ gtk_render_check(style, cr, x, y, width, height);
+ }
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint calculate_button_inner_rect(GtkWidget* button,
+ const GdkRectangle* rect,
+ GdkRectangle* inner_rect,
+ GtkTextDirection direction) {
+ GtkStyleContext* style;
+ GtkBorder border;
+ GtkBorder padding = {0, 0, 0, 0};
+
+ style = gtk_widget_get_style_context(button);
+
+ /* This mirrors gtkbutton's child positioning */
+ gtk_style_context_get_border(style, gtk_style_context_get_state(style),
+ &border);
+ gtk_style_context_get_padding(style, gtk_style_context_get_state(style),
+ &padding);
+
+ inner_rect->x = rect->x + border.left + padding.left;
+ inner_rect->y = rect->y + padding.top + border.top;
+ inner_rect->width =
+ MAX(1, rect->width - padding.left - padding.right - border.left * 2);
+ inner_rect->height =
+ MAX(1, rect->height - padding.top - padding.bottom - border.top * 2);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint calculate_arrow_rect(GtkWidget* arrow, GdkRectangle* rect,
+ GdkRectangle* arrow_rect,
+ GtkTextDirection direction) {
+ /* defined in gtkarrow.c */
+ gfloat arrow_scaling = 0.7;
+ gfloat xalign, xpad;
+ gint extent;
+ gint mxpad, mypad;
+ gfloat mxalign, myalign;
+ GtkMisc* misc = GTK_MISC(arrow);
+
+ gtk_style_context_get_style(gtk_widget_get_style_context(arrow),
+ "arrow_scaling", &arrow_scaling, NULL);
+
+ gtk_misc_get_padding(misc, &mxpad, &mypad);
+ extent = MIN((rect->width - mxpad * 2), (rect->height - mypad * 2)) *
+ arrow_scaling;
+
+ gtk_misc_get_alignment(misc, &mxalign, &myalign);
+
+ xalign = direction == GTK_TEXT_DIR_LTR ? mxalign : 1.0 - mxalign;
+ xpad = mxpad + (rect->width - extent) * xalign;
+
+ arrow_rect->x = direction == GTK_TEXT_DIR_LTR ? floor(rect->x + xpad)
+ : ceil(rect->x + xpad);
+ arrow_rect->y = floor(rect->y + mypad + ((rect->height - extent) * myalign));
+
+ arrow_rect->width = arrow_rect->height = extent;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static MozGtkSize GetMinContentBox(GtkStyleContext* style) {
+ GtkStateFlags state_flags = gtk_style_context_get_state(style);
+ gint width, height;
+ gtk_style_context_get(style, state_flags, "min-width", &width, "min-height",
+ &height, nullptr);
+ return {width, height};
+}
+
+/**
+ * Get minimum widget size as sum of margin, padding, border and
+ * min-width/min-height.
+ */
+static void moz_gtk_get_widget_min_size(GtkStyleContext* style, int* width,
+ int* height) {
+ GtkStateFlags state_flags = gtk_style_context_get_state(style);
+ gtk_style_context_get(style, state_flags, "min-height", height, "min-width",
+ width, nullptr);
+
+ GtkBorder border, padding, margin;
+ gtk_style_context_get_border(style, state_flags, &border);
+ gtk_style_context_get_padding(style, state_flags, &padding);
+ gtk_style_context_get_margin(style, state_flags, &margin);
+
+ *width += border.left + border.right + margin.left + margin.right +
+ padding.left + padding.right;
+ *height += border.top + border.bottom + margin.top + margin.bottom +
+ padding.top + padding.bottom;
+}
+
+static MozGtkSize GetMinMarginBox(GtkStyleContext* style) {
+ gint width, height;
+ moz_gtk_get_widget_min_size(style, &width, &height);
+ return {width, height};
+}
+
+static void Inset(GdkRectangle* rect, const GtkBorder& aBorder) {
+ rect->x += aBorder.left;
+ rect->y += aBorder.top;
+ rect->width -= aBorder.left + aBorder.right;
+ rect->height -= aBorder.top + aBorder.bottom;
+}
+
+// Inset a rectangle by the margins specified in a style context.
+static void InsetByMargin(GdkRectangle* rect, GtkStyleContext* style) {
+ GtkBorder margin;
+ gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+ &margin);
+ Inset(rect, margin);
+}
+
+// Inset a rectangle by the border and padding specified in a style context.
+static void InsetByBorderPadding(GdkRectangle* rect, GtkStyleContext* style) {
+ GtkStateFlags state = gtk_style_context_get_state(style);
+ GtkBorder padding, border;
+
+ gtk_style_context_get_padding(style, state, &padding);
+ Inset(rect, padding);
+ gtk_style_context_get_border(style, state, &border);
+ Inset(rect, border);
+}
+
+static gint moz_gtk_scrollbar_button_paint(cairo_t* cr,
+ const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkScrollbarButtonFlags flags,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GdkRectangle arrow_rect;
+ gdouble arrow_angle;
+ GtkStyleContext* style;
+ gint arrow_displacement_x, arrow_displacement_y;
+
+ GtkWidget* scrollbar = GetWidget(flags & MOZ_GTK_STEPPER_VERTICAL
+ ? MOZ_GTK_SCROLLBAR_VERTICAL
+ : MOZ_GTK_SCROLLBAR_HORIZONTAL);
+
+ gtk_widget_set_direction(scrollbar, direction);
+
+ if (flags & MOZ_GTK_STEPPER_VERTICAL) {
+ arrow_angle = (flags & MOZ_GTK_STEPPER_DOWN) ? ARROW_DOWN : ARROW_UP;
+ } else {
+ arrow_angle = (flags & MOZ_GTK_STEPPER_DOWN) ? ARROW_RIGHT : ARROW_LEFT;
+ }
+
+ style = gtk_widget_get_style_context(scrollbar);
+
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BUTTON);
+ StyleContextSetScale(style, state->scale);
+ gtk_style_context_set_state(style, state_flags);
+ if (arrow_angle == ARROW_RIGHT) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_RIGHT);
+ } else if (arrow_angle == ARROW_DOWN) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BOTTOM);
+ } else if (arrow_angle == ARROW_LEFT) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_LEFT);
+ } else {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOP);
+ }
+
+ GdkRectangle rect = *aRect;
+ if (gtk_check_version(3, 20, 0) == nullptr) {
+ // The "trough-border" is not used since GTK 3.20. The stepper margin
+ // box occupies the full width of the "contents" gadget content box.
+ InsetByMargin(&rect, style);
+ } else {
+ // Scrollbar button has to be inset by trough_border because its DOM
+ // element is filling width of vertical scrollbar's track (or height
+ // in case of horizontal scrollbars).
+ GtkOrientation orientation = flags & MOZ_GTK_STEPPER_VERTICAL
+ ? GTK_ORIENTATION_VERTICAL
+ : GTK_ORIENTATION_HORIZONTAL;
+
+ const ScrollbarGTKMetrics* metrics = GetScrollbarMetrics(orientation);
+ if (flags & MOZ_GTK_STEPPER_VERTICAL) {
+ rect.x += metrics->border.track.left;
+ rect.width = metrics->size.thumb.width;
+ } else {
+ rect.y += metrics->border.track.top;
+ rect.height = metrics->size.thumb.height;
+ }
+ }
+
+ gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+ gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height);
+
+ arrow_rect.width = rect.width / 2;
+ arrow_rect.height = rect.height / 2;
+
+ gfloat arrow_scaling;
+ gtk_style_context_get_style(style, "arrow-scaling", &arrow_scaling, NULL);
+
+ gdouble arrow_size = MIN(rect.width, rect.height) * arrow_scaling;
+ arrow_rect.x = rect.x + (rect.width - arrow_size) / 2;
+ arrow_rect.y = rect.y + (rect.height - arrow_size) / 2;
+
+ if (state_flags & GTK_STATE_FLAG_ACTIVE) {
+ gtk_style_context_get_style(style, "arrow-displacement-x",
+ &arrow_displacement_x, "arrow-displacement-y",
+ &arrow_displacement_y, NULL);
+
+ arrow_rect.x += arrow_displacement_x;
+ arrow_rect.y += arrow_displacement_y;
+ }
+
+ gtk_render_arrow(style, cr, arrow_angle, arrow_rect.x, arrow_rect.y,
+ arrow_size);
+
+ gtk_style_context_restore(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static void moz_gtk_update_scrollbar_style(GtkStyleContext* style,
+ WidgetNodeType widget,
+ GtkTextDirection direction) {
+ if (widget == MOZ_GTK_SCROLLBAR_HORIZONTAL) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BOTTOM);
+ } else {
+ if (direction == GTK_TEXT_DIR_LTR) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_RIGHT);
+ gtk_style_context_remove_class(style, GTK_STYLE_CLASS_LEFT);
+ } else {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_LEFT);
+ gtk_style_context_remove_class(style, GTK_STYLE_CLASS_RIGHT);
+ }
+ }
+}
+
+static void moz_gtk_draw_styled_frame(GtkStyleContext* style, cairo_t* cr,
+ const GdkRectangle* aRect,
+ bool drawFocus) {
+ GdkRectangle rect = *aRect;
+
+ InsetByMargin(&rect, style);
+
+ gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+ gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height);
+ if (drawFocus) {
+ gtk_render_focus(style, cr, rect.x, rect.y, rect.width, rect.height);
+ }
+}
+
+static gint moz_gtk_scrollbar_trough_paint(WidgetNodeType widget, cairo_t* cr,
+ const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GdkRectangle rect = *aRect;
+ GtkStyleContext* style;
+
+ if (gtk_get_minor_version() >= 20) {
+ WidgetNodeType thumb = widget == MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL
+ ? MOZ_GTK_SCROLLBAR_THUMB_VERTICAL
+ : MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
+ MozGtkSize thumbSize = GetMinMarginBox(GetStyleContext(thumb));
+ style = GetStyleContext(widget, state->scale, direction, state_flags);
+ MozGtkSize trackSize = GetMinContentBox(style);
+ trackSize.Include(thumbSize);
+ trackSize += GetMarginBorderPadding(style);
+ // Gecko's trough |aRect| fills available breadth, but GTK's trough is
+ // centered in the contents_gadget. The centering here round left
+ // and up, like gtk_box_gadget_allocate_child().
+ if (widget == MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL) {
+ rect.x += (rect.width - trackSize.width) / 2;
+ rect.width = trackSize.width;
+ } else {
+ rect.y += (rect.height - trackSize.height) / 2;
+ rect.height = trackSize.height;
+ }
+ } else {
+ style = GetStyleContext(widget, state->scale, direction, state_flags);
+ }
+
+ moz_gtk_draw_styled_frame(style, cr, &rect, state->focused);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_scrollbar_paint(WidgetNodeType widget, cairo_t* cr,
+ const GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style =
+ GetStyleContext(widget, state->scale, direction, state_flags);
+
+ moz_gtk_update_scrollbar_style(style, widget, direction);
+
+ moz_gtk_draw_styled_frame(style, cr, rect, state->focused);
+
+ style = GetStyleContext((widget == MOZ_GTK_SCROLLBAR_HORIZONTAL)
+ ? MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL
+ : MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL,
+ state->scale, direction, state_flags);
+ moz_gtk_draw_styled_frame(style, cr, rect, state->focused);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_scrollbar_thumb_paint(WidgetNodeType widget, cairo_t* cr,
+ const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style =
+ GetStyleContext(widget, state->scale, direction, state_flags);
+
+ GtkOrientation orientation = (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL)
+ ? GTK_ORIENTATION_HORIZONTAL
+ : GTK_ORIENTATION_VERTICAL;
+
+ GdkRectangle rect = *aRect;
+
+ const ScrollbarGTKMetrics* metrics =
+ (state->depressed || state->active || state->inHover)
+ ? GetActiveScrollbarMetrics(orientation)
+ : GetScrollbarMetrics(orientation);
+ Inset(&rect, metrics->margin.thumb);
+
+ gtk_render_slider(style, cr, rect.x, rect.y, rect.width, rect.height,
+ orientation);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_inner_spin_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_SPINBUTTON, state->scale, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ /* hard code these values */
+ GdkRectangle arrow_rect;
+ arrow_rect.width = 6;
+ arrow_rect.height = 6;
+
+ // align spin to the left
+ arrow_rect.x = rect->x;
+
+ // up button
+ arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2 - 3;
+ gtk_render_arrow(style, cr, ARROW_UP, arrow_rect.x, arrow_rect.y,
+ arrow_rect.width);
+
+ // down button
+ arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2 + 3;
+ gtk_render_arrow(style, cr, ARROW_DOWN, arrow_rect.x, arrow_rect.y,
+ arrow_rect.width);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_spin_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_SPINBUTTON, state->scale, direction);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_spin_updown_paint(cairo_t* cr, GdkRectangle* rect,
+ gboolean isDown, GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_SPINBUTTON, state->scale, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ /* hard code these values */
+ GdkRectangle arrow_rect;
+ arrow_rect.width = 6;
+ arrow_rect.height = 6;
+ arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2;
+ arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2;
+ arrow_rect.y += isDown ? -1 : 1;
+
+ gtk_render_arrow(style, cr, isDown ? ARROW_DOWN : ARROW_UP, arrow_rect.x,
+ arrow_rect.y, arrow_rect.width);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+/* See gtk_range_draw() for reference.
+ */
+static gint moz_gtk_scale_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state, GtkOrientation flags,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ gint x, y, width, height, min_width, min_height;
+ GtkStyleContext* style;
+ GtkBorder margin;
+
+ moz_gtk_get_scale_metrics(flags, &min_width, &min_height);
+
+ WidgetNodeType widget = (flags == GTK_ORIENTATION_HORIZONTAL)
+ ? MOZ_GTK_SCALE_TROUGH_HORIZONTAL
+ : MOZ_GTK_SCALE_TROUGH_VERTICAL;
+ style = GetStyleContext(widget, state->scale, direction, state_flags);
+ gtk_style_context_get_margin(style, state_flags, &margin);
+
+ // Clamp the dimension perpendicular to the direction that the slider crosses
+ // to the minimum size.
+ if (flags == GTK_ORIENTATION_HORIZONTAL) {
+ width = rect->width - (margin.left + margin.right);
+ height = min_height - (margin.top + margin.bottom);
+ x = rect->x + margin.left;
+ y = rect->y + (rect->height - height) / 2;
+ } else {
+ width = min_width - (margin.left + margin.right);
+ height = rect->height - (margin.top + margin.bottom);
+ x = rect->x + (rect->width - width) / 2;
+ y = rect->y + margin.top;
+ }
+
+ gtk_render_background(style, cr, x, y, width, height);
+ gtk_render_frame(style, cr, x, y, width, height);
+
+ if (state->focused)
+ gtk_render_focus(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_scale_thumb_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkOrientation flags,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style;
+ gint thumb_width, thumb_height, x, y;
+
+ /* determine the thumb size, and position the thumb in the center in the
+ * opposite axis
+ */
+ if (flags == GTK_ORIENTATION_HORIZONTAL) {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_width,
+ &thumb_height);
+ x = rect->x;
+ y = rect->y + (rect->height - thumb_height) / 2;
+ } else {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_height,
+ &thumb_width);
+ x = rect->x + (rect->width - thumb_width) / 2;
+ y = rect->y;
+ }
+
+ WidgetNodeType widget = (flags == GTK_ORIENTATION_HORIZONTAL)
+ ? MOZ_GTK_SCALE_THUMB_HORIZONTAL
+ : MOZ_GTK_SCALE_THUMB_VERTICAL;
+ style = GetStyleContext(widget, state->scale, direction, state_flags);
+ gtk_render_slider(style, cr, x, y, thumb_width, thumb_height, flags);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_gripper_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_GRIPPER, state->scale, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_hpaned_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL, state->scale,
+ GTK_TEXT_DIR_LTR, GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_handle(style, cr, rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_vpaned_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL, state->scale,
+ GTK_TEXT_DIR_LTR, GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_handle(style, cr, rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+// See gtk_entry_draw() for reference.
+static gint moz_gtk_entry_paint(cairo_t* cr, const GdkRectangle* aRect,
+ GtkWidgetState* state, GtkStyleContext* style,
+ WidgetNodeType widget) {
+ // StyleAppearance::FocusOutline
+ int draw_focus_outline_only = state->depressed;
+ GdkRectangle rect = *aRect;
+
+ if (draw_focus_outline_only) {
+ // Inflate the given 'rect' with the focus outline size.
+ gint h, v;
+ moz_gtk_get_focus_outline_size(style, &h, &v);
+ rect.x -= h;
+ rect.width += 2 * h;
+ rect.y -= v;
+ rect.height += 2 * v;
+ } else {
+ gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+ }
+
+ // Paint the border, except for 'menulist-textfield' that isn't focused:
+ if (widget != MOZ_GTK_DROPDOWN_ENTRY || state->focused) {
+ gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_text_view_paint(cairo_t* cr, GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ // GtkTextView and GtkScrolledWindow do not set active and prelight flags.
+ // The use of focus with MOZ_GTK_SCROLLED_WINDOW here is questionable
+ // because a parent widget will not have focus when its child GtkTextView
+ // has focus, but perhaps this may help identify a focused textarea with
+ // some themes as GtkTextView backgrounds do not typically render
+ // differently with focus.
+ GtkStateFlags state_flags = state->disabled ? GTK_STATE_FLAG_INSENSITIVE
+ : state->focused ? GTK_STATE_FLAG_FOCUSED
+ : GTK_STATE_FLAG_NORMAL;
+
+ GtkStyleContext* style_frame = GetStyleContext(
+ MOZ_GTK_SCROLLED_WINDOW, state->scale, direction, state_flags);
+ gtk_render_frame(style_frame, cr, aRect->x, aRect->y, aRect->width,
+ aRect->height);
+
+ GdkRectangle rect = *aRect;
+ InsetByBorderPadding(&rect, style_frame);
+
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_TEXT_VIEW, state->scale, direction, state_flags);
+ gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+ // There is a separate "text" window, which usually provides the
+ // background behind the text. However, this is transparent in Ambiance
+ // for GTK 3.20, in which case the MOZ_GTK_TEXT_VIEW background is
+ // visible.
+ style = GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT, state->scale, direction,
+ state_flags);
+ gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_treeview_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ gint xthickness, ythickness;
+ GtkStyleContext* style;
+ GtkStyleContext* style_tree;
+ GtkStateFlags state_flags;
+ GtkBorder border;
+
+ /* only handle disabled and normal states, otherwise the whole background
+ * area will be painted differently with other states */
+ state_flags =
+ state->disabled ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL;
+
+ style = GetStyleContext(MOZ_GTK_SCROLLED_WINDOW, state->scale, direction);
+ gtk_style_context_get_border(style, state_flags, &border);
+ xthickness = border.left;
+ ythickness = border.top;
+
+ style_tree = GetStyleContext(MOZ_GTK_TREEVIEW_VIEW, state->scale, direction);
+ gtk_render_background(style_tree, cr, rect->x + xthickness,
+ rect->y + ythickness, rect->width - 2 * xthickness,
+ rect->height - 2 * ythickness);
+
+ style = GetStyleContext(MOZ_GTK_SCROLLED_WINDOW, state->scale, direction);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_tree_header_cell_paint(cairo_t* cr,
+ const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ gboolean isSorted,
+ GtkTextDirection direction) {
+ moz_gtk_button_paint(cr, aRect, state, GTK_RELIEF_NORMAL,
+ GetWidget(MOZ_GTK_TREE_HEADER_CELL), direction);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_tree_header_sort_arrow_paint(cairo_t* cr,
+ GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkArrowType arrow_type,
+ GtkTextDirection direction) {
+ GdkRectangle arrow_rect;
+ gdouble arrow_angle;
+ GtkStyleContext* style;
+
+ /* hard code these values */
+ arrow_rect.width = 11;
+ arrow_rect.height = 11;
+ arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2;
+ arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2;
+ style = GetStyleContext(MOZ_GTK_TREE_HEADER_SORTARROW, state->scale,
+ direction, GetStateFlagsFromGtkWidgetState(state));
+ switch (arrow_type) {
+ case GTK_ARROW_LEFT:
+ arrow_angle = ARROW_LEFT;
+ break;
+ case GTK_ARROW_RIGHT:
+ arrow_angle = ARROW_RIGHT;
+ break;
+ case GTK_ARROW_DOWN:
+ arrow_angle = ARROW_DOWN;
+ break;
+ default:
+ arrow_angle = ARROW_UP;
+ break;
+ }
+ if (arrow_type != GTK_ARROW_NONE)
+ gtk_render_arrow(style, cr, arrow_angle, arrow_rect.x, arrow_rect.y,
+ arrow_rect.width);
+ return MOZ_GTK_SUCCESS;
+}
+
+/* See gtk_expander_paint() for reference.
+ */
+static gint moz_gtk_treeview_expander_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkExpanderStyle expander_state,
+ GtkTextDirection direction) {
+ /* Because the frame we get is of the entire treeview, we can't get the
+ * precise event state of one expander, thus rendering hover and active
+ * feedback useless. */
+ GtkStateFlags state_flags =
+ state->disabled ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL;
+
+ if (state->inHover)
+ state_flags =
+ static_cast<GtkStateFlags>(state_flags | GTK_STATE_FLAG_PRELIGHT);
+ if (state->selected)
+ state_flags =
+ static_cast<GtkStateFlags>(state_flags | GTK_STATE_FLAG_SELECTED);
+
+ /* GTK_STATE_FLAG_ACTIVE controls expanded/colapsed state rendering
+ * in gtk_render_expander()
+ */
+ if (expander_state == GTK_EXPANDER_EXPANDED)
+ state_flags =
+ static_cast<GtkStateFlags>(state_flags | checkbox_check_state);
+ else
+ state_flags =
+ static_cast<GtkStateFlags>(state_flags & ~(checkbox_check_state));
+
+ GtkStyleContext* style = GetStyleContext(
+ MOZ_GTK_TREEVIEW_EXPANDER, state->scale, direction, state_flags);
+ gtk_render_expander(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+/* See gtk_separator_draw() for reference.
+ */
+static gint moz_gtk_combo_box_paint(cairo_t* cr, const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GdkRectangle arrow_rect, real_arrow_rect;
+ gint separator_width;
+ gboolean wide_separators;
+ GtkStyleContext* style;
+ GtkRequisition arrow_req;
+
+ GtkWidget* comboBoxButton = GetWidget(MOZ_GTK_COMBOBOX_BUTTON);
+ GtkWidget* comboBoxArrow = GetWidget(MOZ_GTK_COMBOBOX_ARROW);
+
+ /* Also sets the direction on gComboBoxButtonWidget, which is then
+ * inherited by the separator and arrow */
+ moz_gtk_button_paint(cr, aRect, state, GTK_RELIEF_NORMAL, comboBoxButton,
+ direction);
+
+ calculate_button_inner_rect(comboBoxButton, aRect, &arrow_rect, direction);
+ /* Now arrow_rect contains the inner rect ; we want to correct the width
+ * to what the arrow needs (see gtk_combo_box_size_allocate) */
+ gtk_widget_get_preferred_size(comboBoxArrow, NULL, &arrow_req);
+ moz_gtk_sanity_preferred_size(&arrow_req);
+
+ if (direction == GTK_TEXT_DIR_LTR)
+ arrow_rect.x += arrow_rect.width - arrow_req.width;
+ arrow_rect.width = arrow_req.width;
+
+ calculate_arrow_rect(comboBoxArrow, &arrow_rect, &real_arrow_rect, direction);
+
+ style = GetStyleContext(MOZ_GTK_COMBOBOX_ARROW, state->scale);
+ gtk_render_arrow(style, cr, ARROW_DOWN, real_arrow_rect.x, real_arrow_rect.y,
+ real_arrow_rect.width);
+
+ /* If there is no separator in the theme, there's nothing left to do. */
+ GtkWidget* widget = GetWidget(MOZ_GTK_COMBOBOX_SEPARATOR);
+ if (!widget) return MOZ_GTK_SUCCESS;
+ style = gtk_widget_get_style_context(widget);
+ StyleContextSetScale(style, state->scale);
+ gtk_style_context_get_style(style, "wide-separators", &wide_separators,
+ "separator-width", &separator_width, NULL);
+
+ if (wide_separators) {
+ if (direction == GTK_TEXT_DIR_LTR)
+ arrow_rect.x -= separator_width;
+ else
+ arrow_rect.x += arrow_rect.width;
+
+ gtk_render_frame(style, cr, arrow_rect.x, arrow_rect.y, separator_width,
+ arrow_rect.height);
+ } else {
+ if (direction == GTK_TEXT_DIR_LTR) {
+ GtkBorder padding;
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ gtk_style_context_get_padding(style, state_flags, &padding);
+ arrow_rect.x -= padding.left;
+ } else
+ arrow_rect.x += arrow_rect.width;
+
+ gtk_render_line(style, cr, arrow_rect.x, arrow_rect.y, arrow_rect.x,
+ arrow_rect.y + arrow_rect.height);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_arrow_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state, GtkArrowType arrow_type,
+ GtkTextDirection direction) {
+ GdkRectangle arrow_rect;
+ gdouble arrow_angle;
+
+ if (direction == GTK_TEXT_DIR_RTL) {
+ if (arrow_type == GTK_ARROW_LEFT) {
+ arrow_type = GTK_ARROW_RIGHT;
+ } else if (arrow_type == GTK_ARROW_RIGHT) {
+ arrow_type = GTK_ARROW_LEFT;
+ }
+ }
+ switch (arrow_type) {
+ case GTK_ARROW_LEFT:
+ arrow_angle = ARROW_LEFT;
+ break;
+ case GTK_ARROW_RIGHT:
+ arrow_angle = ARROW_RIGHT;
+ break;
+ case GTK_ARROW_DOWN:
+ arrow_angle = ARROW_DOWN;
+ break;
+ default:
+ arrow_angle = ARROW_UP;
+ break;
+ }
+ if (arrow_type == GTK_ARROW_NONE) return MOZ_GTK_SUCCESS;
+
+ calculate_arrow_rect(GetWidget(MOZ_GTK_BUTTON_ARROW), rect, &arrow_rect,
+ direction);
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_BUTTON_ARROW, state->scale,
+ direction, state_flags);
+ gtk_render_arrow(style, cr, arrow_angle, arrow_rect.x, arrow_rect.y,
+ arrow_rect.width);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_combo_box_entry_button_paint(cairo_t* cr,
+ GdkRectangle* rect,
+ GtkWidgetState* state,
+ gboolean input_focus,
+ GtkTextDirection direction) {
+ gint x_displacement, y_displacement;
+ GdkRectangle arrow_rect, real_arrow_rect;
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style;
+
+ GtkWidget* comboBoxEntry = GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON);
+ moz_gtk_button_paint(cr, rect, state, GTK_RELIEF_NORMAL, comboBoxEntry,
+ direction);
+ calculate_button_inner_rect(comboBoxEntry, rect, &arrow_rect, direction);
+
+ if (state_flags & GTK_STATE_FLAG_ACTIVE) {
+ style = gtk_widget_get_style_context(comboBoxEntry);
+ StyleContextSetScale(style, state->scale);
+ gtk_style_context_get_style(style, "child-displacement-x", &x_displacement,
+ "child-displacement-y", &y_displacement, NULL);
+ arrow_rect.x += x_displacement;
+ arrow_rect.y += y_displacement;
+ }
+
+ calculate_arrow_rect(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_ARROW), &arrow_rect,
+ &real_arrow_rect, direction);
+
+ style = GetStyleContext(MOZ_GTK_COMBOBOX_ENTRY_ARROW, state->scale);
+ gtk_render_arrow(style, cr, ARROW_DOWN, real_arrow_rect.x, real_arrow_rect.y,
+ real_arrow_rect.width);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_container_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ WidgetNodeType widget_type,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style =
+ GetStyleContext(widget_type, state->scale, direction, state_flags);
+ /* this is for drawing a prelight box */
+ if (state_flags & GTK_STATE_FLAG_PRELIGHT) {
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width,
+ rect->height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_toggle_label_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state, gboolean isradio,
+ GtkTextDirection direction) {
+ if (!state->focused) return MOZ_GTK_SUCCESS;
+
+ GtkStyleContext* style = GetStyleContext(
+ isradio ? MOZ_GTK_RADIOBUTTON_CONTAINER : MOZ_GTK_CHECKBUTTON_CONTAINER,
+ state->scale, direction, GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_focus(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_toolbar_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_TOOLBAR, state->scale, direction);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+/* See _gtk_toolbar_paint_space_line() for reference.
+ */
+static gint moz_gtk_toolbar_separator_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ gint separator_width;
+ gint paint_width;
+ gboolean wide_separators;
+
+ /* Defined as constants in GTK+ 2.10.14 */
+ const double start_fraction = 0.2;
+ const double end_fraction = 0.8;
+
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_TOOLBAR, state->scale);
+ gtk_style_context_get_style(style, "wide-separators", &wide_separators,
+ "separator-width", &separator_width, NULL);
+
+ style = GetStyleContext(MOZ_GTK_TOOLBAR_SEPARATOR, state->scale, direction);
+ if (wide_separators) {
+ if (separator_width > rect->width) separator_width = rect->width;
+
+ gtk_render_frame(style, cr, rect->x + (rect->width - separator_width) / 2,
+ rect->y + rect->height * start_fraction, separator_width,
+ rect->height * (end_fraction - start_fraction));
+ } else {
+ GtkBorder padding;
+ gtk_style_context_get_padding(style, gtk_style_context_get_state(style),
+ &padding);
+
+ paint_width = padding.left;
+ if (paint_width > rect->width) paint_width = rect->width;
+
+ gtk_render_line(style, cr, rect->x + (rect->width - paint_width) / 2,
+ rect->y + rect->height * start_fraction,
+ rect->x + (rect->width - paint_width) / 2,
+ rect->y + rect->height * end_fraction);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_tooltip_paint(cairo_t* cr, const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ // Tooltip widget is made in GTK3 as following tree:
+ // Tooltip window
+ // Horizontal Box
+ // Icon (not supported by Firefox)
+ // Label
+ // Each element can be fully styled by CSS of GTK theme.
+ // We have to draw all elements with appropriate offset and right dimensions.
+
+ // Tooltip drawing
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_TOOLTIP, state->scale, direction);
+ GdkRectangle rect = *aRect;
+ gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+ gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height);
+
+ // Horizontal Box drawing
+ //
+ // The box element has hard-coded 6px margin-* GtkWidget properties, which
+ // are added between the window dimensions and the CSS margin box of the
+ // horizontal box. The frame of the tooltip window is drawn in the
+ // 6px margin.
+ // For drawing Horizontal Box we have to inset drawing area by that 6px
+ // plus its CSS margin.
+ GtkStyleContext* boxStyle =
+ GetStyleContext(MOZ_GTK_TOOLTIP_BOX, state->scale, direction);
+
+ rect.x += 6;
+ rect.y += 6;
+ rect.width -= 12;
+ rect.height -= 12;
+
+ InsetByMargin(&rect, boxStyle);
+ gtk_render_background(boxStyle, cr, rect.x, rect.y, rect.width, rect.height);
+ gtk_render_frame(boxStyle, cr, rect.x, rect.y, rect.width, rect.height);
+
+ // Label drawing
+ InsetByBorderPadding(&rect, boxStyle);
+
+ GtkStyleContext* labelStyle =
+ GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL, state->scale, direction);
+ moz_gtk_draw_styled_frame(labelStyle, cr, &rect, false);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_resizer_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_RESIZER, state->scale, GTK_TEXT_DIR_LTR,
+ GetStateFlagsFromGtkWidgetState(state));
+
+ // Workaround unico not respecting the text direction for resizers.
+ // See bug 1174248.
+ cairo_save(cr);
+ if (direction == GTK_TEXT_DIR_RTL) {
+ cairo_matrix_t mat;
+ cairo_matrix_init_translate(&mat, 2 * rect->x + rect->width, 0);
+ cairo_matrix_scale(&mat, -1, 1);
+ cairo_transform(cr, &mat);
+ }
+
+ gtk_render_handle(style, cr, rect->x, rect->y, rect->width, rect->height);
+ cairo_restore(cr);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_frame_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_FRAME, state->scale, direction);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_progressbar_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_PROGRESS_TROUGH, state->scale, direction);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_progress_chunk_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction,
+ WidgetNodeType widget) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_PROGRESS_CHUNK, state->scale, direction);
+
+ if (widget == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE ||
+ widget == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) {
+ /**
+ * The bar's size and the bar speed are set depending of the progress'
+ * size. These could also be constant for all progress bars easily.
+ */
+ gboolean vertical =
+ (widget == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE);
+
+ /* The size of the dimension we are going to use for the animation. */
+ const gint progressSize = vertical ? rect->height : rect->width;
+
+ /* The bar is using a fifth of the element size, based on GtkProgressBar
+ * activity-blocks property. */
+ const gint barSize = MAX(1, progressSize / 5);
+
+ /* Represents the travel that has to be done for a complete cycle. */
+ const gint travel = 2 * (progressSize - barSize);
+
+ /* period equals to travel / pixelsPerMillisecond
+ * where pixelsPerMillisecond equals progressSize / 1000.0.
+ * This is equivalent to 1600. */
+ static const guint period = 1600;
+ const gint t = PR_IntervalToMilliseconds(PR_IntervalNow()) % period;
+ const gint dx = travel * t / period;
+
+ if (vertical) {
+ rect->y += (dx < travel / 2) ? dx : travel - dx;
+ rect->height = barSize;
+ } else {
+ rect->x += (dx < travel / 2) ? dx : travel - dx;
+ rect->width = barSize;
+ }
+ }
+
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_get_tab_thickness(GtkStyleContext* style) {
+ if (!notebook_has_tab_gap)
+ return 0; /* tabs do not overdraw the tabpanel border with "no gap" style */
+
+ GtkBorder border;
+ gtk_style_context_get_border(style, gtk_style_context_get_state(style),
+ &border);
+ if (border.top < 2) return 2; /* some themes don't set ythickness correctly */
+
+ return border.top;
+}
+
+gint moz_gtk_get_tab_thickness(WidgetNodeType aNodeType) {
+ GtkStyleContext* style = GetStyleContext(aNodeType);
+ int thickness = moz_gtk_get_tab_thickness(style);
+ return thickness;
+}
+
+/* actual small tabs */
+static gint moz_gtk_tab_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state, GtkTabFlags flags,
+ GtkTextDirection direction,
+ WidgetNodeType widget) {
+ /* When the tab isn't selected, we just draw a notebook extension.
+ * When it is selected, we overwrite the adjacent border of the tabpanel
+ * touching the tab with a pierced border (called "the gap") to make the
+ * tab appear physically attached to the tabpanel; see details below. */
+
+ GtkStyleContext* style;
+ GdkRectangle tabRect;
+ GdkRectangle focusRect;
+ GdkRectangle backRect;
+ int initial_gap = 0;
+ bool isBottomTab = (widget == MOZ_GTK_TAB_BOTTOM);
+
+ style = GetStyleContext(widget, state->scale, direction,
+ GetStateFlagsFromGtkTabFlags(flags));
+ tabRect = *rect;
+
+ if (flags & MOZ_GTK_TAB_FIRST) {
+ gtk_style_context_get_style(style, "initial-gap", &initial_gap, NULL);
+ tabRect.width -= initial_gap;
+
+ if (direction != GTK_TEXT_DIR_RTL) {
+ tabRect.x += initial_gap;
+ }
+ }
+
+ focusRect = backRect = tabRect;
+
+ if (notebook_has_tab_gap) {
+ if ((flags & MOZ_GTK_TAB_SELECTED) == 0) {
+ /* Only draw the tab */
+ gtk_render_extension(style, cr, tabRect.x, tabRect.y, tabRect.width,
+ tabRect.height,
+ isBottomTab ? GTK_POS_TOP : GTK_POS_BOTTOM);
+ } else {
+ /* Draw the tab and the gap
+ * We want the gap to be positioned exactly on the tabpanel top
+ * border; since tabbox.css may set a negative margin so that the tab
+ * frame rect already overlaps the tabpanel frame rect, we need to take
+ * that into account when drawing. To that effect, nsNativeThemeGTK
+ * passes us this negative margin (bmargin in the graphic below) in the
+ * lowest bits of |flags|. We use it to set gap_voffset, the distance
+ * between the top of the gap and the bottom of the tab (resp. the
+ * bottom of the gap and the top of the tab when we draw a bottom tab),
+ * while ensuring that the gap always touches the border of the tab,
+ * i.e. 0 <= gap_voffset <= gap_height, to avoid surprinsing results
+ * with big negative or positive margins.
+ * Here is a graphical explanation in the case of top tabs:
+ * ___________________________
+ * / \
+ * | T A B |
+ * ----------|. . . . . . . . . . . . . . .|----- top of tabpanel
+ * : ^ bmargin : ^
+ * : | (-negative margin, : |
+ * bottom : v passed in flags) : | gap_height
+ * of -> :.............................: | (the size of the
+ * the tab . part of the gap . | tabpanel top border)
+ * . outside of the tab . v
+ * ----------------------------------------------
+ *
+ * To draw the gap, we use gtk_render_frame_gap(), see comment in
+ * moz_gtk_tabpanels_paint(). This gap is made 3 * gap_height tall,
+ * which should suffice to ensure that the only visible border is the
+ * pierced one. If the tab is in the middle, we make the box_gap begin
+ * a bit to the left of the tab and end a bit to the right, adjusting
+ * the gap position so it still is under the tab, because we want the
+ * rendering of a gap in the middle of a tabpanel. This is the role of
+ * the gints gap_{l,r}_offset. On the contrary, if the tab is the
+ * first, we align the start border of the box_gap with the start
+ * border of the tab (left if LTR, right if RTL), by setting the
+ * appropriate offset to 0.*/
+ gint gap_loffset, gap_roffset, gap_voffset, gap_height;
+
+ /* Get height needed by the gap */
+ gap_height = moz_gtk_get_tab_thickness(style);
+
+ /* Extract gap_voffset from the first bits of flags */
+ gap_voffset = flags & MOZ_GTK_TAB_MARGIN_MASK;
+ if (gap_voffset > gap_height) gap_voffset = gap_height;
+
+ /* Set gap_{l,r}_offset to appropriate values */
+ gap_loffset = gap_roffset = 20; /* should be enough */
+ if (flags & MOZ_GTK_TAB_FIRST) {
+ if (direction == GTK_TEXT_DIR_RTL)
+ gap_roffset = initial_gap;
+ else
+ gap_loffset = initial_gap;
+ }
+
+ GtkStyleContext* panelStyle =
+ GetStyleContext(MOZ_GTK_TABPANELS, state->scale, direction);
+
+ if (isBottomTab) {
+ /* Draw the tab on bottom */
+ focusRect.y += gap_voffset;
+ focusRect.height -= gap_voffset;
+
+ gtk_render_extension(style, cr, tabRect.x, tabRect.y + gap_voffset,
+ tabRect.width, tabRect.height - gap_voffset,
+ GTK_POS_TOP);
+
+ backRect.y += (gap_voffset - gap_height);
+ backRect.height = gap_height;
+
+ /* Draw the gap; erase with background color before painting in
+ * case theme does not */
+ gtk_render_background(panelStyle, cr, backRect.x, backRect.y,
+ backRect.width, backRect.height);
+ cairo_save(cr);
+ cairo_rectangle(cr, backRect.x, backRect.y, backRect.width,
+ backRect.height);
+ cairo_clip(cr);
+
+ gtk_render_frame_gap(panelStyle, cr, tabRect.x - gap_loffset,
+ tabRect.y + gap_voffset - 3 * gap_height,
+ tabRect.width + gap_loffset + gap_roffset,
+ 3 * gap_height, GTK_POS_BOTTOM, gap_loffset,
+ gap_loffset + tabRect.width);
+ cairo_restore(cr);
+ } else {
+ /* Draw the tab on top */
+ focusRect.height -= gap_voffset;
+ gtk_render_extension(style, cr, tabRect.x, tabRect.y, tabRect.width,
+ tabRect.height - gap_voffset, GTK_POS_BOTTOM);
+
+ backRect.y += (tabRect.height - gap_voffset);
+ backRect.height = gap_height;
+
+ /* Draw the gap; erase with background color before painting in
+ * case theme does not */
+ gtk_render_background(panelStyle, cr, backRect.x, backRect.y,
+ backRect.width, backRect.height);
+
+ cairo_save(cr);
+ cairo_rectangle(cr, backRect.x, backRect.y, backRect.width,
+ backRect.height);
+ cairo_clip(cr);
+
+ gtk_render_frame_gap(panelStyle, cr, tabRect.x - gap_loffset,
+ tabRect.y + tabRect.height - gap_voffset,
+ tabRect.width + gap_loffset + gap_roffset,
+ 3 * gap_height, GTK_POS_TOP, gap_loffset,
+ gap_loffset + tabRect.width);
+ cairo_restore(cr);
+ }
+ }
+ } else {
+ gtk_render_background(style, cr, tabRect.x, tabRect.y, tabRect.width,
+ tabRect.height);
+ gtk_render_frame(style, cr, tabRect.x, tabRect.y, tabRect.width,
+ tabRect.height);
+ }
+
+ if (state->focused) {
+ /* Paint the focus ring */
+ GtkBorder padding;
+ gtk_style_context_get_padding(style, GetStateFlagsFromGtkWidgetState(state),
+ &padding);
+
+ focusRect.x += padding.left;
+ focusRect.width -= (padding.left + padding.right);
+ focusRect.y += padding.top;
+ focusRect.height -= (padding.top + padding.bottom);
+
+ gtk_render_focus(style, cr, focusRect.x, focusRect.y, focusRect.width,
+ focusRect.height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+/* tab area*/
+static gint moz_gtk_tabpanels_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_TABPANELS, state->scale, direction);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ /*
+ * The gap size is not needed in moz_gtk_tabpanels_paint because
+ * the gap will be painted with the foreground tab in moz_gtk_tab_paint.
+ *
+ * However, if moz_gtk_tabpanels_paint just uses gtk_render_frame(),
+ * the theme will think that there are no tabs and may draw something
+ * different.Hence the trick of using two clip regions, and drawing the
+ * gap outside each clip region, to get the correct frame for
+ * a tabpanel with tabs.
+ */
+ /* left side */
+ cairo_save(cr);
+ cairo_rectangle(cr, rect->x, rect->y, rect->x + rect->width / 2,
+ rect->y + rect->height);
+ cairo_clip(cr);
+ gtk_render_frame_gap(style, cr, rect->x, rect->y, rect->width, rect->height,
+ GTK_POS_TOP, rect->width - 1, rect->width);
+ cairo_restore(cr);
+
+ /* right side */
+ cairo_save(cr);
+ cairo_rectangle(cr, rect->x + rect->width / 2, rect->y, rect->x + rect->width,
+ rect->y + rect->height);
+ cairo_clip(cr);
+ gtk_render_frame_gap(style, cr, rect->x, rect->y, rect->width, rect->height,
+ GTK_POS_TOP, 0, 1);
+ cairo_restore(cr);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_tab_scroll_arrow_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkArrowType arrow_type,
+ GtkTextDirection direction) {
+ GtkStyleContext* style;
+ gdouble arrow_angle;
+ gint arrow_size = MIN(rect->width, rect->height);
+ gint x = rect->x + (rect->width - arrow_size) / 2;
+ gint y = rect->y + (rect->height - arrow_size) / 2;
+
+ if (direction == GTK_TEXT_DIR_RTL) {
+ arrow_type =
+ (arrow_type == GTK_ARROW_LEFT) ? GTK_ARROW_RIGHT : GTK_ARROW_LEFT;
+ }
+ switch (arrow_type) {
+ case GTK_ARROW_LEFT:
+ arrow_angle = ARROW_LEFT;
+ break;
+ case GTK_ARROW_RIGHT:
+ arrow_angle = ARROW_RIGHT;
+ break;
+ case GTK_ARROW_DOWN:
+ arrow_angle = ARROW_DOWN;
+ break;
+ default:
+ arrow_angle = ARROW_UP;
+ break;
+ }
+ if (arrow_type != GTK_ARROW_NONE) {
+ style = GetStyleContext(MOZ_GTK_TAB_SCROLLARROW, state->scale, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_arrow(style, cr, arrow_angle, x, y, arrow_size);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_menu_bar_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style;
+
+ GtkWidget* widget = GetWidget(MOZ_GTK_MENUBAR);
+ gtk_widget_set_direction(widget, direction);
+
+ style = gtk_widget_get_style_context(widget);
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_MENUBAR);
+ StyleContextSetScale(style, state->scale);
+
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_style_context_restore(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_menu_popup_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style;
+
+ GtkWidget* widget = GetWidget(MOZ_GTK_MENUPOPUP);
+ gtk_widget_set_direction(widget, direction);
+
+ // Draw a backing toplevel. This fixes themes that don't provide a menu
+ // background, and depend on the GtkMenu's implementation window to provide
+ // it.
+ moz_gtk_window_paint(cr, rect, direction);
+
+ style = gtk_widget_get_style_context(widget);
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_MENU);
+ StyleContextSetScale(style, state->scale);
+
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_style_context_restore(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+// See gtk_menu_item_draw() for reference.
+static gint moz_gtk_menu_separator_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkWidgetState defaultState = {0};
+ moz_gtk_menu_item_paint(MOZ_GTK_MENUSEPARATOR, cr, rect, &defaultState,
+ direction);
+
+ if (gtk_get_minor_version() >= 20) return MOZ_GTK_SUCCESS;
+
+ GtkStyleContext* style;
+ gboolean wide_separators;
+ gint separator_height;
+ gint x, y, w;
+ GtkBorder padding;
+
+ style = GetStyleContext(MOZ_GTK_MENUSEPARATOR, state->scale, direction);
+ gtk_style_context_get_padding(style, gtk_style_context_get_state(style),
+ &padding);
+
+ x = rect->x;
+ y = rect->y;
+ w = rect->width;
+
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_SEPARATOR);
+
+ gtk_style_context_get_style(style, "wide-separators", &wide_separators,
+ "separator-height", &separator_height, NULL);
+
+ if (wide_separators) {
+ gtk_render_frame(style, cr, x + padding.left, y + padding.top,
+ w - padding.left - padding.right, separator_height);
+ } else {
+ gtk_render_line(style, cr, x + padding.left, y + padding.top,
+ x + w - padding.right - 1, y + padding.top);
+ }
+
+ gtk_style_context_restore(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+// See gtk_menu_item_draw() for reference.
+static gint moz_gtk_menu_item_paint(WidgetNodeType widget, cairo_t* cr,
+ GdkRectangle* rect, GtkWidgetState* state,
+ GtkTextDirection direction) {
+ gint x, y, w, h;
+ guint minorVersion = gtk_get_minor_version();
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+
+ // GTK versions prior to 3.8 render the background and frame only when not
+ // a separator and in hover prelight.
+ if (minorVersion < 8 && (widget == MOZ_GTK_MENUSEPARATOR ||
+ !(state_flags & GTK_STATE_FLAG_PRELIGHT)))
+ return MOZ_GTK_SUCCESS;
+
+ GtkStyleContext* style =
+ GetStyleContext(widget, state->scale, direction, state_flags);
+
+ if (minorVersion < 6) {
+ // GTK+ 3.4 saves the style context and adds the menubar class to
+ // menubar children, but does each of these only when drawing, not
+ // during layout.
+ gtk_style_context_save(style);
+ if (widget == MOZ_GTK_MENUBARITEM) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_MENUBAR);
+ }
+ }
+
+ x = rect->x;
+ y = rect->y;
+ w = rect->width;
+ h = rect->height;
+
+ gtk_render_background(style, cr, x, y, w, h);
+ gtk_render_frame(style, cr, x, y, w, h);
+
+ if (minorVersion < 6) {
+ gtk_style_context_restore(style);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_menu_arrow_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_MENUITEM, state->scale, direction, state_flags);
+ gtk_render_arrow(style, cr,
+ (direction == GTK_TEXT_DIR_LTR) ? ARROW_RIGHT : ARROW_LEFT,
+ rect->x, rect->y, rect->width);
+ return MOZ_GTK_SUCCESS;
+}
+
+// For reference, see gtk_check_menu_item_size_allocate() in GTK versions after
+// 3.20 and gtk_real_check_menu_item_draw_indicator() in earlier versions.
+static gint moz_gtk_check_menu_item_paint(WidgetNodeType widgetType,
+ cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ gboolean checked,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style;
+ gint indicator_size, horizontal_padding;
+ gint x, y;
+
+ moz_gtk_menu_item_paint(MOZ_GTK_MENUITEM, cr, rect, state, direction);
+
+ if (checked) {
+ state_flags =
+ static_cast<GtkStateFlags>(state_flags | checkbox_check_state);
+ }
+
+ bool pre_3_20 = gtk_get_minor_version() < 20;
+ gint offset;
+ style = GetStyleContext(widgetType, state->scale, direction);
+ gtk_style_context_get_style(style, "indicator-size", &indicator_size,
+ "horizontal-padding", &horizontal_padding, NULL);
+ if (pre_3_20) {
+ GtkBorder padding;
+ gtk_style_context_get_padding(style, state_flags, &padding);
+ offset = horizontal_padding + padding.left + 2;
+ } else {
+ GdkRectangle r = {0};
+ InsetByMargin(&r, style);
+ InsetByBorderPadding(&r, style);
+ offset = r.x;
+ }
+
+ bool isRadio = (widgetType == MOZ_GTK_RADIOMENUITEM);
+ WidgetNodeType indicatorType = isRadio ? MOZ_GTK_RADIOMENUITEM_INDICATOR
+ : MOZ_GTK_CHECKMENUITEM_INDICATOR;
+ const ToggleGTKMetrics* metrics = GetToggleMetrics(indicatorType);
+ style = GetStyleContext(indicatorType, state->scale, direction, state_flags);
+
+ if (direction == GTK_TEXT_DIR_RTL) {
+ x = rect->width - indicator_size - offset;
+ } else {
+ x = rect->x + offset;
+ }
+ y = rect->y + (rect->height - indicator_size) / 2;
+
+ gint indicator_width, indicator_height;
+ indicator_width = indicator_height = indicator_size;
+ if (!pre_3_20) {
+ gtk_render_background(style, cr, x, y, indicator_size, indicator_size);
+ gtk_render_frame(style, cr, x, y, indicator_size, indicator_size);
+ x = x + metrics->borderAndPadding.left;
+ y = y + metrics->borderAndPadding.top;
+ indicator_width = metrics->minSizeWithBorder.width -
+ metrics->borderAndPadding.left -
+ metrics->borderAndPadding.right;
+ indicator_height = metrics->minSizeWithBorder.height -
+ metrics->borderAndPadding.top -
+ metrics->borderAndPadding.bottom;
+ }
+
+ if (isRadio) {
+ gtk_render_option(style, cr, x, y, indicator_width, indicator_height);
+ } else {
+ gtk_render_check(style, cr, x, y, indicator_width, indicator_height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_info_bar_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_INFO_BAR, state->scale, GTK_TEXT_DIR_LTR,
+ GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_header_bar_paint(WidgetNodeType widgetType, cairo_t* cr,
+ GdkRectangle* rect,
+ GtkWidgetState* state) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style =
+ GetStyleContext(widgetType, state->scale, GTK_TEXT_DIR_NONE, state_flags);
+
+// Some themes (Adwaita for instance) draws bold dark line at
+// titlebar bottom. It does not fit well with Firefox tabs so
+// draw with some extent to make the titlebar bottom part invisible.
+#define TITLEBAR_EXTENT 4
+
+ // We don't need to draw window decoration for MOZ_GTK_HEADER_BAR_MAXIMIZED,
+ // i.e. when main window is maximized.
+ if (widgetType == MOZ_GTK_HEADER_BAR) {
+ GtkStyleContext* windowStyle =
+ GetStyleContext(MOZ_GTK_HEADERBAR_WINDOW, state->scale);
+ bool solidDecorations =
+ gtk_style_context_has_class(windowStyle, "solid-csd");
+ GtkStyleContext* decorationStyle =
+ GetStyleContext(solidDecorations ? MOZ_GTK_WINDOW_DECORATION_SOLID
+ : MOZ_GTK_WINDOW_DECORATION,
+ state->scale, GTK_TEXT_DIR_LTR, state_flags);
+
+ gtk_render_background(decorationStyle, cr, rect->x, rect->y, rect->width,
+ rect->height + TITLEBAR_EXTENT);
+ gtk_render_frame(decorationStyle, cr, rect->x, rect->y, rect->width,
+ rect->height + TITLEBAR_EXTENT);
+ }
+
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width,
+ rect->height + TITLEBAR_EXTENT);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width,
+ rect->height + TITLEBAR_EXTENT);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static GtkBorder GetMarginBorderPadding(GtkStyleContext* aStyle) {
+ gint left = 0, top = 0, right = 0, bottom = 0;
+ moz_gtk_add_margin_border_padding(aStyle, &left, &top, &right, &bottom);
+ // narrowing conversions to gint16:
+ GtkBorder result;
+ result.left = left;
+ result.right = right;
+ result.top = top;
+ result.bottom = bottom;
+ return result;
+}
+
+gint moz_gtk_get_widget_border(WidgetNodeType widget, gint* left, gint* top,
+ gint* right, gint* bottom,
+ // NOTE: callers depend on direction being used
+ // only for MOZ_GTK_DROPDOWN widgets.
+ GtkTextDirection direction) {
+ GtkWidget* w;
+ GtkStyleContext* style;
+ *left = *top = *right = *bottom = 0;
+
+ switch (widget) {
+ case MOZ_GTK_BUTTON:
+ case MOZ_GTK_TOOLBAR_BUTTON: {
+ style = GetStyleContext(MOZ_GTK_BUTTON);
+
+ *left = *top = *right = *bottom = gtk_container_get_border_width(
+ GTK_CONTAINER(GetWidget(MOZ_GTK_BUTTON)));
+
+ if (widget == MOZ_GTK_TOOLBAR_BUTTON) {
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, "image-button");
+ }
+
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+
+ if (widget == MOZ_GTK_TOOLBAR_BUTTON) gtk_style_context_restore(style);
+
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_ENTRY:
+ case MOZ_GTK_DROPDOWN_ENTRY: {
+ style = GetStyleContext(widget);
+
+ // XXX: Subtract 1 pixel from the padding to account for the default
+ // padding in forms.css. See bug 1187385.
+ *left = *top = *right = *bottom = -1;
+ moz_gtk_add_border_padding(style, left, top, right, bottom);
+
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TEXT_VIEW:
+ case MOZ_GTK_TREEVIEW: {
+ style = GetStyleContext(MOZ_GTK_SCROLLED_WINDOW);
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TREE_HEADER_CELL: {
+ /* A Tree Header in GTK is just a different styled button
+ * It must be placed in a TreeView for getting the correct style
+ * assigned.
+ * That is why the following code is the same as for MOZ_GTK_BUTTON.
+ * */
+ *left = *top = *right = *bottom = gtk_container_get_border_width(
+ GTK_CONTAINER(GetWidget(MOZ_GTK_TREE_HEADER_CELL)));
+ style = GetStyleContext(MOZ_GTK_TREE_HEADER_CELL);
+ moz_gtk_add_border_padding(style, left, top, right, bottom);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TREE_HEADER_SORTARROW:
+ w = GetWidget(MOZ_GTK_TREE_HEADER_SORTARROW);
+ break;
+ case MOZ_GTK_DROPDOWN_ARROW:
+ w = GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON);
+ break;
+ case MOZ_GTK_DROPDOWN: {
+ /* We need to account for the arrow on the dropdown, so text
+ * doesn't come too close to the arrow, or in some cases spill
+ * into the arrow. */
+ gboolean wide_separators;
+ gint separator_width;
+ GtkRequisition arrow_req;
+ GtkBorder border;
+
+ *left = *top = *right = *bottom = gtk_container_get_border_width(
+ GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_BUTTON)));
+ style = GetStyleContext(MOZ_GTK_COMBOBOX_BUTTON);
+ moz_gtk_add_border_padding(style, left, top, right, bottom);
+
+ /* If there is no separator, don't try to count its width. */
+ separator_width = 0;
+ GtkWidget* comboBoxSeparator = GetWidget(MOZ_GTK_COMBOBOX_SEPARATOR);
+ if (comboBoxSeparator) {
+ style = gtk_widget_get_style_context(comboBoxSeparator);
+ gtk_style_context_get_style(style, "wide-separators", &wide_separators,
+ "separator-width", &separator_width, NULL);
+
+ if (!wide_separators) {
+ gtk_style_context_get_border(
+ style, gtk_style_context_get_state(style), &border);
+ separator_width = border.left;
+ }
+ }
+
+ gtk_widget_get_preferred_size(GetWidget(MOZ_GTK_COMBOBOX_ARROW), NULL,
+ &arrow_req);
+ moz_gtk_sanity_preferred_size(&arrow_req);
+
+ if (direction == GTK_TEXT_DIR_RTL)
+ *left += separator_width + arrow_req.width;
+ else
+ *right += separator_width + arrow_req.width;
+
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TABPANELS:
+ w = GetWidget(MOZ_GTK_TABPANELS);
+ break;
+ case MOZ_GTK_PROGRESSBAR:
+ w = GetWidget(MOZ_GTK_PROGRESSBAR);
+ break;
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ case MOZ_GTK_SPINBUTTON_UP:
+ case MOZ_GTK_SPINBUTTON_DOWN:
+ w = GetWidget(MOZ_GTK_SPINBUTTON);
+ break;
+ case MOZ_GTK_SCALE_HORIZONTAL:
+ case MOZ_GTK_SCALE_VERTICAL:
+ w = GetWidget(widget);
+ break;
+ case MOZ_GTK_FRAME:
+ w = GetWidget(MOZ_GTK_FRAME);
+ break;
+ case MOZ_GTK_CHECKBUTTON_CONTAINER:
+ case MOZ_GTK_RADIOBUTTON_CONTAINER: {
+ w = GetWidget(widget);
+ style = gtk_widget_get_style_context(w);
+
+ *left = *top = *right = *bottom =
+ gtk_container_get_border_width(GTK_CONTAINER(w));
+ moz_gtk_add_border_padding(style, left, top, right, bottom);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_MENUPOPUP:
+ w = GetWidget(MOZ_GTK_MENUPOPUP);
+ break;
+ case MOZ_GTK_MENUBARITEM:
+ case MOZ_GTK_MENUITEM:
+ case MOZ_GTK_CHECKMENUITEM:
+ case MOZ_GTK_RADIOMENUITEM: {
+ // Bug 1274143 for MOZ_GTK_MENUBARITEM
+ WidgetNodeType type =
+ widget == MOZ_GTK_MENUBARITEM ? MOZ_GTK_MENUITEM : widget;
+ style = GetStyleContext(type);
+
+ if (gtk_get_minor_version() < 20) {
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+ } else {
+ moz_gtk_add_margin_border_padding(style, left, top, right, bottom);
+ }
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_INFO_BAR:
+ w = GetWidget(MOZ_GTK_INFO_BAR);
+ break;
+ case MOZ_GTK_TOOLTIP: {
+ // In GTK 3 there are 6 pixels of additional margin around the box.
+ // See details there:
+ // https://github.com/GNOME/gtk/blob/5ea69a136bd7e4970b3a800390e20314665aaed2/gtk/ui/gtktooltipwindow.ui#L11
+ *left = *right = *top = *bottom = 6;
+
+ // We also need to add margin/padding/borders from Tooltip content.
+ // Tooltip contains horizontal box, where icon and label is put.
+ // We ignore icon as long as we don't have support for it.
+ GtkStyleContext* boxStyle = GetStyleContext(MOZ_GTK_TOOLTIP_BOX);
+ moz_gtk_add_margin_border_padding(boxStyle, left, top, right, bottom);
+
+ GtkStyleContext* labelStyle = GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL);
+ moz_gtk_add_margin_border_padding(labelStyle, left, top, right, bottom);
+
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_HEADER_BAR_BUTTON_BOX: {
+ style = GetStyleContext(MOZ_GTK_HEADER_BAR);
+ moz_gtk_add_border_padding(style, left, top, right, bottom);
+ *top = *bottom = 0;
+ bool leftButtonsPlacement = false;
+ GetGtkHeaderBarButtonLayout({}, &leftButtonsPlacement);
+ if (direction == GTK_TEXT_DIR_RTL) {
+ leftButtonsPlacement = !leftButtonsPlacement;
+ }
+ if (leftButtonsPlacement) {
+ *right = 0;
+ } else {
+ *left = 0;
+ }
+ return MOZ_GTK_SUCCESS;
+ }
+ /* These widgets have no borders, since they are not containers. */
+ case MOZ_GTK_CHECKBUTTON_LABEL:
+ case MOZ_GTK_RADIOBUTTON_LABEL:
+ case MOZ_GTK_SPLITTER_HORIZONTAL:
+ case MOZ_GTK_SPLITTER_VERTICAL:
+ case MOZ_GTK_CHECKBUTTON:
+ case MOZ_GTK_RADIOBUTTON:
+ case MOZ_GTK_SCROLLBAR_BUTTON:
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ case MOZ_GTK_GRIPPER:
+ case MOZ_GTK_PROGRESS_CHUNK:
+ case MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE:
+ case MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE:
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ case MOZ_GTK_TOOLBAR_SEPARATOR:
+ case MOZ_GTK_MENUSEPARATOR:
+ case MOZ_GTK_HEADER_BAR:
+ case MOZ_GTK_HEADER_BAR_MAXIMIZED:
+ case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE:
+ /* These widgets have no borders.*/
+ case MOZ_GTK_INNER_SPIN_BUTTON:
+ case MOZ_GTK_SPINBUTTON:
+ case MOZ_GTK_WINDOW:
+ case MOZ_GTK_RESIZER:
+ case MOZ_GTK_MENUARROW:
+ case MOZ_GTK_TOOLBARBUTTON_ARROW:
+ case MOZ_GTK_TOOLBAR:
+ case MOZ_GTK_MENUBAR:
+ case MOZ_GTK_TAB_SCROLLARROW:
+ return MOZ_GTK_SUCCESS;
+ default:
+ g_warning("Unsupported widget type: %d", widget);
+ return MOZ_GTK_UNKNOWN_WIDGET;
+ }
+ /* TODO - we're still missing some widget implementations */
+ if (w) {
+ moz_gtk_add_style_border(gtk_widget_get_style_context(w), left, top, right,
+ bottom);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_get_tab_border(gint* left, gint* top, gint* right, gint* bottom,
+ GtkTextDirection direction, GtkTabFlags flags,
+ WidgetNodeType widget) {
+ GtkStyleContext* style = GetStyleContext(widget, 1, direction,
+ GetStateFlagsFromGtkTabFlags(flags));
+
+ *left = *top = *right = *bottom = 0;
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+
+ // Gtk >= 3.20 does not use those styles
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ int tab_curvature;
+
+ gtk_style_context_get_style(style, "tab-curvature", &tab_curvature, NULL);
+ *left += tab_curvature;
+ *right += tab_curvature;
+
+ if (flags & MOZ_GTK_TAB_FIRST) {
+ int initial_gap = 0;
+ gtk_style_context_get_style(style, "initial-gap", &initial_gap, NULL);
+ if (direction == GTK_TEXT_DIR_RTL)
+ *right += initial_gap;
+ else
+ *left += initial_gap;
+ }
+ } else {
+ GtkBorder margin;
+
+ gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+ &margin);
+ *left += margin.left;
+ *right += margin.right;
+
+ if (flags & MOZ_GTK_TAB_FIRST) {
+ style = GetStyleContext(MOZ_GTK_NOTEBOOK_HEADER, direction);
+ gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+ &margin);
+ *left += margin.left;
+ *right += margin.right;
+ }
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_get_combo_box_entry_button_size(gint* width, gint* height) {
+ /*
+ * We get the requisition of the drop down button, which includes
+ * all padding, border and focus line widths the button uses,
+ * as well as the minimum arrow size and its padding
+ * */
+ GtkRequisition requisition;
+
+ gtk_widget_get_preferred_size(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON), NULL,
+ &requisition);
+ moz_gtk_sanity_preferred_size(&requisition);
+
+ *width = requisition.width;
+ *height = requisition.height;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_get_tab_scroll_arrow_size(gint* width, gint* height) {
+ gint arrow_size;
+
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_TABPANELS);
+ gtk_style_context_get_style(style, "scroll-arrow-hlength", &arrow_size, NULL);
+
+ *height = *width = arrow_size;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+void moz_gtk_get_arrow_size(WidgetNodeType widgetType, gint* width,
+ gint* height) {
+ GtkWidget* widget;
+ switch (widgetType) {
+ case MOZ_GTK_DROPDOWN:
+ widget = GetWidget(MOZ_GTK_COMBOBOX_ARROW);
+ break;
+ default:
+ widget = GetWidget(MOZ_GTK_BUTTON_ARROW);
+ break;
+ }
+
+ GtkRequisition requisition;
+ gtk_widget_get_preferred_size(widget, NULL, &requisition);
+ moz_gtk_sanity_preferred_size(&requisition);
+
+ *width = requisition.width;
+ *height = requisition.height;
+}
+
+gint moz_gtk_get_toolbar_separator_width(gint* size) {
+ gboolean wide_separators;
+ gint separator_width;
+ GtkBorder border;
+
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_TOOLBAR);
+ gtk_style_context_get_style(style, "space-size", size, "wide-separators",
+ &wide_separators, "separator-width",
+ &separator_width, NULL);
+ /* Just in case... */
+ gtk_style_context_get_border(style, gtk_style_context_get_state(style),
+ &border);
+ *size = MAX(*size, (wide_separators ? separator_width : border.left));
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_get_expander_size(gint* size) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_EXPANDER);
+ gtk_style_context_get_style(style, "expander-size", size, NULL);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_get_treeview_expander_size(gint* size) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_TREEVIEW);
+ gtk_style_context_get_style(style, "expander-size", size, NULL);
+ return MOZ_GTK_SUCCESS;
+}
+
+// See gtk_menu_item_draw() for reference.
+gint moz_gtk_get_menu_separator_height(gint* size) {
+ gboolean wide_separators;
+ gint separator_height;
+ GtkBorder padding;
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_MENUSEPARATOR);
+ gtk_style_context_get_padding(style, gtk_style_context_get_state(style),
+ &padding);
+
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_SEPARATOR);
+
+ gtk_style_context_get_style(style, "wide-separators", &wide_separators,
+ "separator-height", &separator_height, NULL);
+
+ gtk_style_context_restore(style);
+
+ *size = padding.top + padding.bottom;
+ *size += (wide_separators) ? separator_height : 1;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+void moz_gtk_get_entry_min_height(gint* min_content_height,
+ gint* border_padding_height) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_ENTRY);
+ if (!gtk_check_version(3, 20, 0)) {
+ gtk_style_context_get(style, gtk_style_context_get_state(style),
+ "min-height", min_content_height, nullptr);
+ } else {
+ *min_content_height = 0;
+ }
+
+ GtkBorder border;
+ GtkBorder padding;
+ gtk_style_context_get_border(style, gtk_style_context_get_state(style),
+ &border);
+ gtk_style_context_get_padding(style, gtk_style_context_get_state(style),
+ &padding);
+
+ *border_padding_height =
+ (border.top + border.bottom + padding.top + padding.bottom);
+}
+
+void moz_gtk_get_scale_metrics(GtkOrientation orient, gint* scale_width,
+ gint* scale_height) {
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL)
+ ? MOZ_GTK_SCALE_HORIZONTAL
+ : MOZ_GTK_SCALE_VERTICAL;
+
+ gint thumb_length, thumb_height, trough_border;
+ moz_gtk_get_scalethumb_metrics(orient, &thumb_length, &thumb_height);
+
+ GtkStyleContext* style = GetStyleContext(widget);
+ gtk_style_context_get_style(style, "trough-border", &trough_border, NULL);
+
+ if (orient == GTK_ORIENTATION_HORIZONTAL) {
+ *scale_width = thumb_length + trough_border * 2;
+ *scale_height = thumb_height + trough_border * 2;
+ } else {
+ *scale_width = thumb_height + trough_border * 2;
+ *scale_height = thumb_length + trough_border * 2;
+ }
+ } else {
+ WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL)
+ ? MOZ_GTK_SCALE_TROUGH_HORIZONTAL
+ : MOZ_GTK_SCALE_TROUGH_VERTICAL;
+ moz_gtk_get_widget_min_size(GetStyleContext(widget), scale_width,
+ scale_height);
+ }
+}
+
+gint moz_gtk_get_scalethumb_metrics(GtkOrientation orient, gint* thumb_length,
+ gint* thumb_height) {
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL)
+ ? MOZ_GTK_SCALE_HORIZONTAL
+ : MOZ_GTK_SCALE_VERTICAL;
+ GtkStyleContext* style = GetStyleContext(widget);
+ gtk_style_context_get_style(style, "slider_length", thumb_length,
+ "slider_width", thumb_height, NULL);
+ } else {
+ WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL)
+ ? MOZ_GTK_SCALE_THUMB_HORIZONTAL
+ : MOZ_GTK_SCALE_THUMB_VERTICAL;
+ GtkStyleContext* style = GetStyleContext(widget);
+
+ gint min_width, min_height;
+ GtkStateFlags state = gtk_style_context_get_state(style);
+ gtk_style_context_get(style, state, "min-width", &min_width, "min-height",
+ &min_height, nullptr);
+ GtkBorder margin;
+ gtk_style_context_get_margin(style, state, &margin);
+ gint margin_width = margin.left + margin.right;
+ gint margin_height = margin.top + margin.bottom;
+
+ // Negative margin of slider element also determines its minimal size
+ // so use bigger of those two values.
+ if (min_width < -margin_width) min_width = -margin_width;
+ if (min_height < -margin_height) min_height = -margin_height;
+
+ *thumb_length = min_width;
+ *thumb_height = min_height;
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static MozGtkSize SizeFromLengthAndBreadth(GtkOrientation aOrientation,
+ gint aLength, gint aBreadth) {
+ return aOrientation == GTK_ORIENTATION_HORIZONTAL
+ ? MozGtkSize({aLength, aBreadth})
+ : MozGtkSize({aBreadth, aLength});
+}
+
+const ToggleGTKMetrics* GetToggleMetrics(WidgetNodeType aWidgetType) {
+ ToggleGTKMetrics* metrics;
+
+ switch (aWidgetType) {
+ case MOZ_GTK_RADIOBUTTON:
+ metrics = &sRadioMetrics;
+ break;
+ case MOZ_GTK_CHECKBUTTON:
+ metrics = &sCheckboxMetrics;
+ break;
+ case MOZ_GTK_RADIOMENUITEM_INDICATOR:
+ metrics = &sMenuRadioMetrics;
+ break;
+ case MOZ_GTK_CHECKMENUITEM_INDICATOR:
+ metrics = &sMenuCheckboxMetrics;
+ break;
+ default:
+ MOZ_CRASH("Unsupported widget type for getting metrics");
+ return nullptr;
+ }
+
+ metrics->initialized = true;
+ if (gtk_check_version(3, 20, 0) == nullptr) {
+ GtkStyleContext* style = GetStyleContext(aWidgetType);
+ GtkStateFlags state_flags = gtk_style_context_get_state(style);
+ gtk_style_context_get(style, state_flags, "min-height",
+ &(metrics->minSizeWithBorder.height), "min-width",
+ &(metrics->minSizeWithBorder.width), nullptr);
+ // Fallback to indicator size if min dimensions are zero
+ if (metrics->minSizeWithBorder.height == 0 ||
+ metrics->minSizeWithBorder.width == 0) {
+ gint indicator_size;
+ gtk_widget_style_get(GetWidget(MOZ_GTK_CHECKBUTTON_CONTAINER),
+ "indicator_size", &indicator_size, nullptr);
+ if (metrics->minSizeWithBorder.height == 0) {
+ metrics->minSizeWithBorder.height = indicator_size;
+ }
+ if (metrics->minSizeWithBorder.width == 0) {
+ metrics->minSizeWithBorder.width = indicator_size;
+ }
+ }
+
+ GtkBorder border, padding;
+ gtk_style_context_get_border(style, state_flags, &border);
+ gtk_style_context_get_padding(style, state_flags, &padding);
+ metrics->borderAndPadding.left = border.left + padding.left;
+ metrics->borderAndPadding.right = border.right + padding.right;
+ metrics->borderAndPadding.top = border.top + padding.top;
+ metrics->borderAndPadding.bottom = border.bottom + padding.bottom;
+ metrics->minSizeWithBorder.width +=
+ metrics->borderAndPadding.left + metrics->borderAndPadding.right;
+ metrics->minSizeWithBorder.height +=
+ metrics->borderAndPadding.top + metrics->borderAndPadding.bottom;
+ } else {
+ gint indicator_size, indicator_spacing;
+ gtk_widget_style_get(GetWidget(MOZ_GTK_CHECKBUTTON_CONTAINER),
+ "indicator_size", &indicator_size, "indicator_spacing",
+ &indicator_spacing, nullptr);
+ metrics->minSizeWithBorder.width = metrics->minSizeWithBorder.height =
+ indicator_size;
+ }
+ return metrics;
+}
+
+static void InitScrollbarMetrics(ScrollbarGTKMetrics* aMetrics,
+ GtkOrientation aOrientation,
+ GtkStateFlags aStateFlags) {
+ WidgetNodeType scrollbar = aOrientation == GTK_ORIENTATION_HORIZONTAL
+ ? MOZ_GTK_SCROLLBAR_HORIZONTAL
+ : MOZ_GTK_SCROLLBAR_VERTICAL;
+
+ gboolean backward, forward, secondary_backward, secondary_forward;
+ GtkStyleContext* style =
+ GetStyleContext(scrollbar, 1, GTK_TEXT_DIR_NONE, aStateFlags);
+ gtk_style_context_get_style(
+ style, "has-backward-stepper", &backward, "has-forward-stepper", &forward,
+ "has-secondary-backward-stepper", &secondary_backward,
+ "has-secondary-forward-stepper", &secondary_forward, nullptr);
+ bool hasButtons =
+ backward || forward || secondary_backward || secondary_forward;
+
+ if (gtk_get_minor_version() < 20) {
+ gint slider_width, trough_border, stepper_size, min_slider_size;
+
+ gtk_style_context_get_style(style, "slider-width", &slider_width,
+ "trough-border", &trough_border, "stepper-size",
+ &stepper_size, "min-slider-length",
+ &min_slider_size, nullptr);
+
+ aMetrics->size.thumb =
+ SizeFromLengthAndBreadth(aOrientation, min_slider_size, slider_width);
+ aMetrics->size.button =
+ SizeFromLengthAndBreadth(aOrientation, stepper_size, slider_width);
+ // overall scrollbar
+ gint breadth = slider_width + 2 * trough_border;
+ // Require room for the slider in the track if we don't have buttons.
+ gint length = hasButtons ? 0 : min_slider_size + 2 * trough_border;
+ aMetrics->size.scrollbar =
+ SizeFromLengthAndBreadth(aOrientation, length, breadth);
+
+ // Borders on the major axis are set on the outermost scrollbar
+ // element to correctly position the buttons when
+ // trough-under-steppers is true.
+ // Borders on the minor axis are set on the track element so that it
+ // receives mouse events, as in GTK.
+ // Other borders have been zero-initialized.
+ if (aOrientation == GTK_ORIENTATION_HORIZONTAL) {
+ aMetrics->border.scrollbar.left = aMetrics->border.scrollbar.right =
+ aMetrics->border.track.top = aMetrics->border.track.bottom =
+ trough_border;
+ } else {
+ aMetrics->border.scrollbar.top = aMetrics->border.scrollbar.bottom =
+ aMetrics->border.track.left = aMetrics->border.track.right =
+ trough_border;
+ }
+
+ // We're done here for Gtk+ < 3.20...
+ return;
+ }
+
+ // GTK version > 3.20
+ // scrollbar
+ aMetrics->border.scrollbar = GetMarginBorderPadding(style);
+
+ WidgetNodeType contents, track, thumb;
+ if (aOrientation == GTK_ORIENTATION_HORIZONTAL) {
+ contents = MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL;
+ track = MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL;
+ thumb = MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
+ } else {
+ contents = MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL;
+ track = MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL;
+ thumb = MOZ_GTK_SCROLLBAR_THUMB_VERTICAL;
+ }
+
+ /* GetStyleContext() sets GtkStateFlags to the latest widget name
+ * in css selector string. When we call:
+ *
+ * GetStyleContext(thumb, GTK_STATE_FLAG_PRELIGHT)
+ *
+ * we get:
+ *
+ * "scrollbar contents trough slider:hover"
+ *
+ * Some themes (Ubuntu Ambiance) styles trough/thumb by scrollbar,
+ * the Gtk+ css rule looks like:
+ *
+ * "scrollbar:hover contents trough slider"
+ *
+ * So we need to apply GtkStateFlags to each widgets in style path.
+ */
+
+ // thumb
+ style =
+ CreateStyleContextWithStates(thumb, 1, GTK_TEXT_DIR_NONE, aStateFlags);
+ aMetrics->size.thumb = GetMinMarginBox(style);
+ gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+ &aMetrics->margin.thumb);
+ g_object_unref(style);
+
+ // track
+ style =
+ CreateStyleContextWithStates(track, 1, GTK_TEXT_DIR_NONE, aStateFlags);
+ aMetrics->border.track = GetMarginBorderPadding(style);
+ MozGtkSize trackMinSize = GetMinContentBox(style) + aMetrics->border.track;
+ MozGtkSize trackSizeForThumb = aMetrics->size.thumb + aMetrics->border.track;
+ g_object_unref(style);
+
+ // button
+ if (hasButtons) {
+ style = CreateStyleContextWithStates(MOZ_GTK_SCROLLBAR_BUTTON, 1,
+ GTK_TEXT_DIR_NONE, aStateFlags);
+ aMetrics->size.button = GetMinMarginBox(style);
+ g_object_unref(style);
+ } else {
+ aMetrics->size.button = {0, 0};
+ }
+ if (aOrientation == GTK_ORIENTATION_HORIZONTAL) {
+ aMetrics->size.button.Rotate();
+ // If the track is wider than necessary for the thumb, including when
+ // the buttons will cause Gecko to expand the track to fill
+ // available breadth, then add to the track border to prevent Gecko
+ // from expanding the thumb to fill available breadth.
+ gint extra = std::max(trackMinSize.height, aMetrics->size.button.height) -
+ trackSizeForThumb.height;
+ if (extra > 0) {
+ // If extra is odd, then the thumb is 0.5 pixels above
+ // center as in gtk_range_compute_slider_position().
+ aMetrics->border.track.top += extra / 2;
+ aMetrics->border.track.bottom += extra - extra / 2;
+ // Update size for change in border.
+ trackSizeForThumb.height += extra;
+ }
+ } else {
+ gint extra = std::max(trackMinSize.width, aMetrics->size.button.width) -
+ trackSizeForThumb.width;
+ if (extra > 0) {
+ // If extra is odd, then the thumb is 0.5 pixels to the left
+ // of center as in gtk_range_compute_slider_position().
+ aMetrics->border.track.left += extra / 2;
+ aMetrics->border.track.right += extra - extra / 2;
+ trackSizeForThumb.width += extra;
+ }
+ }
+
+ style =
+ CreateStyleContextWithStates(contents, 1, GTK_TEXT_DIR_NONE, aStateFlags);
+ GtkBorder contentsBorder = GetMarginBorderPadding(style);
+ g_object_unref(style);
+
+ aMetrics->size.scrollbar =
+ trackSizeForThumb + contentsBorder + aMetrics->border.scrollbar;
+}
+
+const ScrollbarGTKMetrics* GetScrollbarMetrics(GtkOrientation aOrientation) {
+ auto metrics = &sScrollbarMetrics[aOrientation];
+ if (!metrics->initialized) {
+ InitScrollbarMetrics(metrics, aOrientation, GTK_STATE_FLAG_NORMAL);
+
+ // We calculate thumb margin here because it's composited from
+ // thumb class margin + difference margin between active and inactive
+ // scrollbars. It's a workaround which alows us to emulate
+ // overlay scrollbars for some Gtk+ themes (Ubuntu/Ambiance),
+ // when an inactive scrollbar thumb is smaller than the active one.
+ const ScrollbarGTKMetrics* metricsActive =
+ GetActiveScrollbarMetrics(aOrientation);
+
+ if (metrics->size.thumb < metricsActive->size.thumb) {
+ metrics->margin.thumb +=
+ (metrics->border.scrollbar + metrics->border.track) -
+ (metricsActive->border.scrollbar + metricsActive->border.track);
+ }
+
+ metrics->initialized = true;
+ }
+ return metrics;
+}
+
+const ScrollbarGTKMetrics* GetActiveScrollbarMetrics(
+ GtkOrientation aOrientation) {
+ auto metrics = &sActiveScrollbarMetrics[aOrientation];
+ if (!metrics->initialized) {
+ InitScrollbarMetrics(metrics, aOrientation, GTK_STATE_FLAG_PRELIGHT);
+ metrics->initialized = true;
+ }
+ return metrics;
+}
+
+/*
+ * get_shadow_width() from gtkwindow.c is not public so we need
+ * to implement it.
+ */
+void InitWindowDecorationSize(CSDWindowDecorationSize* sWindowDecorationSize,
+ bool aPopupWindow) {
+ bool solidDecorations = gtk_style_context_has_class(
+ GetStyleContext(MOZ_GTK_HEADERBAR_WINDOW, 1), "solid-csd");
+ // solid-csd does not use frame extents, quit now.
+ if (solidDecorations) {
+ sWindowDecorationSize->decorationSize = {0, 0, 0, 0};
+ return;
+ }
+
+ // Scale factor is applied later when decoration size is used for actual
+ // gtk windows.
+ GtkStyleContext* context = GetStyleContext(MOZ_GTK_WINDOW_DECORATION);
+
+ /* Always sum border + padding */
+ GtkBorder padding;
+ GtkStateFlags state = gtk_style_context_get_state(context);
+ gtk_style_context_get_border(context, state,
+ &sWindowDecorationSize->decorationSize);
+ gtk_style_context_get_padding(context, state, &padding);
+ sWindowDecorationSize->decorationSize += padding;
+
+ // Available on GTK 3.20+.
+ static auto sGtkRenderBackgroundGetClip = (void (*)(
+ GtkStyleContext*, gdouble, gdouble, gdouble, gdouble,
+ GdkRectangle*))dlsym(RTLD_DEFAULT, "gtk_render_background_get_clip");
+
+ if (!sGtkRenderBackgroundGetClip) {
+ return;
+ }
+
+ GdkRectangle clip;
+ sGtkRenderBackgroundGetClip(context, 0, 0, 0, 0, &clip);
+
+ GtkBorder extents;
+ extents.top = -clip.y;
+ extents.right = clip.width + clip.x;
+ extents.bottom = clip.height + clip.y;
+ extents.left = -clip.x;
+
+ // Get shadow extents but combine with style margin; use the bigger value.
+ // Margin is used for resize grip size - it's not present on
+ // popup windows.
+ if (!aPopupWindow) {
+ GtkBorder margin;
+ gtk_style_context_get_margin(context, state, &margin);
+
+ extents.top = MAX(extents.top, margin.top);
+ extents.right = MAX(extents.right, margin.right);
+ extents.bottom = MAX(extents.bottom, margin.bottom);
+ extents.left = MAX(extents.left, margin.left);
+ }
+
+ sWindowDecorationSize->decorationSize += extents;
+}
+
+GtkBorder GetCSDDecorationSize(bool aIsPopup) {
+ auto metrics =
+ aIsPopup ? &sPopupWindowDecorationSize : &sToplevelWindowDecorationSize;
+ if (!metrics->initialized) {
+ InitWindowDecorationSize(metrics, aIsPopup);
+ metrics->initialized = true;
+ }
+ return metrics->decorationSize;
+}
+
+/* cairo_t *cr argument has to be a system-cairo. */
+gint moz_gtk_widget_paint(WidgetNodeType widget, cairo_t* cr,
+ GdkRectangle* rect, GtkWidgetState* state, gint flags,
+ GtkTextDirection direction) {
+ /* A workaround for https://bugzilla.gnome.org/show_bug.cgi?id=694086
+ */
+ cairo_new_path(cr);
+
+ switch (widget) {
+ case MOZ_GTK_BUTTON:
+ case MOZ_GTK_TOOLBAR_BUTTON:
+ if (state->depressed) {
+ return moz_gtk_button_paint(cr, rect, state, (GtkReliefStyle)flags,
+ GetWidget(MOZ_GTK_TOGGLE_BUTTON),
+ direction);
+ }
+ return moz_gtk_button_paint(cr, rect, state, (GtkReliefStyle)flags,
+ GetWidget(MOZ_GTK_BUTTON), direction);
+ case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE:
+ return moz_gtk_header_bar_button_paint(
+ cr, rect, state, (GtkReliefStyle)flags, widget, direction);
+ case MOZ_GTK_CHECKBUTTON:
+ case MOZ_GTK_RADIOBUTTON:
+ return moz_gtk_toggle_paint(cr, rect, state,
+ !!(flags & MOZ_GTK_WIDGET_CHECKED),
+ !!(flags & MOZ_GTK_WIDGET_INCONSISTENT),
+ (widget == MOZ_GTK_RADIOBUTTON), direction);
+ case MOZ_GTK_SCROLLBAR_BUTTON:
+ return moz_gtk_scrollbar_button_paint(
+ cr, rect, state, (GtkScrollbarButtonFlags)flags, direction);
+ case MOZ_GTK_SCROLLBAR_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_VERTICAL: {
+ if (flags & MOZ_GTK_TRACK_OPAQUE) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW, direction);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width,
+ rect->height);
+ }
+ if (gtk_check_version(3, 20, 0) == nullptr) {
+ return moz_gtk_scrollbar_paint(widget, cr, rect, state, direction);
+ }
+ WidgetNodeType trough_widget = (widget == MOZ_GTK_SCROLLBAR_HORIZONTAL)
+ ? MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL
+ : MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL;
+ return moz_gtk_scrollbar_trough_paint(trough_widget, cr, rect, state,
+ direction);
+ }
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ if (gtk_check_version(3, 20, 0) == nullptr) {
+ return moz_gtk_scrollbar_trough_paint(widget, cr, rect, state,
+ direction);
+ }
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ return moz_gtk_scrollbar_thumb_paint(widget, cr, rect, state, direction);
+ case MOZ_GTK_SCALE_HORIZONTAL:
+ case MOZ_GTK_SCALE_VERTICAL:
+ return moz_gtk_scale_paint(cr, rect, state, (GtkOrientation)flags,
+ direction);
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ return moz_gtk_scale_thumb_paint(cr, rect, state, (GtkOrientation)flags,
+ direction);
+ case MOZ_GTK_INNER_SPIN_BUTTON:
+ return moz_gtk_inner_spin_paint(cr, rect, state, direction);
+ case MOZ_GTK_SPINBUTTON:
+ return moz_gtk_spin_paint(cr, rect, state, direction);
+ case MOZ_GTK_SPINBUTTON_UP:
+ case MOZ_GTK_SPINBUTTON_DOWN:
+ return moz_gtk_spin_updown_paint(
+ cr, rect, (widget == MOZ_GTK_SPINBUTTON_DOWN), state, direction);
+ case MOZ_GTK_SPINBUTTON_ENTRY: {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_SPINBUTTON_ENTRY, state->scale, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+ return moz_gtk_entry_paint(cr, rect, state, style, widget);
+ }
+ case MOZ_GTK_GRIPPER:
+ return moz_gtk_gripper_paint(cr, rect, state, direction);
+ case MOZ_GTK_TREEVIEW:
+ return moz_gtk_treeview_paint(cr, rect, state, direction);
+ case MOZ_GTK_TREE_HEADER_CELL:
+ return moz_gtk_tree_header_cell_paint(cr, rect, state, flags, direction);
+ case MOZ_GTK_TREE_HEADER_SORTARROW:
+ return moz_gtk_tree_header_sort_arrow_paint(
+ cr, rect, state, (GtkArrowType)flags, direction);
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ return moz_gtk_treeview_expander_paint(
+ cr, rect, state, (GtkExpanderStyle)flags, direction);
+ case MOZ_GTK_ENTRY:
+ case MOZ_GTK_DROPDOWN_ENTRY: {
+ GtkStyleContext* style =
+ GetStyleContext(widget, state->scale, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+ gint ret = moz_gtk_entry_paint(cr, rect, state, style, widget);
+ return ret;
+ }
+ case MOZ_GTK_TEXT_VIEW:
+ return moz_gtk_text_view_paint(cr, rect, state, direction);
+ case MOZ_GTK_DROPDOWN:
+ return moz_gtk_combo_box_paint(cr, rect, state, direction);
+ case MOZ_GTK_DROPDOWN_ARROW:
+ return moz_gtk_combo_box_entry_button_paint(cr, rect, state, flags,
+ direction);
+ case MOZ_GTK_CHECKBUTTON_CONTAINER:
+ case MOZ_GTK_RADIOBUTTON_CONTAINER:
+ return moz_gtk_container_paint(cr, rect, state, widget, direction);
+ case MOZ_GTK_CHECKBUTTON_LABEL:
+ case MOZ_GTK_RADIOBUTTON_LABEL:
+ return moz_gtk_toggle_label_paint(
+ cr, rect, state, (widget == MOZ_GTK_RADIOBUTTON_LABEL), direction);
+ case MOZ_GTK_TOOLBAR:
+ return moz_gtk_toolbar_paint(cr, rect, state, direction);
+ case MOZ_GTK_TOOLBAR_SEPARATOR:
+ return moz_gtk_toolbar_separator_paint(cr, rect, state, direction);
+ case MOZ_GTK_TOOLTIP:
+ return moz_gtk_tooltip_paint(cr, rect, state, direction);
+ case MOZ_GTK_FRAME:
+ return moz_gtk_frame_paint(cr, rect, state, direction);
+ case MOZ_GTK_RESIZER:
+ return moz_gtk_resizer_paint(cr, rect, state, direction);
+ case MOZ_GTK_PROGRESSBAR:
+ return moz_gtk_progressbar_paint(cr, rect, state, direction);
+ case MOZ_GTK_PROGRESS_CHUNK:
+ case MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE:
+ case MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE:
+ return moz_gtk_progress_chunk_paint(cr, rect, state, direction, widget);
+ case MOZ_GTK_TAB_TOP:
+ case MOZ_GTK_TAB_BOTTOM:
+ return moz_gtk_tab_paint(cr, rect, state, (GtkTabFlags)flags, direction,
+ widget);
+ case MOZ_GTK_TABPANELS:
+ return moz_gtk_tabpanels_paint(cr, rect, state, direction);
+ case MOZ_GTK_TAB_SCROLLARROW:
+ return moz_gtk_tab_scroll_arrow_paint(cr, rect, state,
+ (GtkArrowType)flags, direction);
+ case MOZ_GTK_MENUBAR:
+ return moz_gtk_menu_bar_paint(cr, rect, state, direction);
+ case MOZ_GTK_MENUPOPUP:
+ return moz_gtk_menu_popup_paint(cr, rect, state, direction);
+ case MOZ_GTK_MENUSEPARATOR:
+ return moz_gtk_menu_separator_paint(cr, rect, state, direction);
+ case MOZ_GTK_MENUBARITEM:
+ case MOZ_GTK_MENUITEM:
+ return moz_gtk_menu_item_paint(widget, cr, rect, state, direction);
+ case MOZ_GTK_MENUARROW:
+ return moz_gtk_menu_arrow_paint(cr, rect, state, direction);
+ case MOZ_GTK_TOOLBARBUTTON_ARROW:
+ return moz_gtk_arrow_paint(cr, rect, state, (GtkArrowType)flags,
+ direction);
+ case MOZ_GTK_CHECKMENUITEM:
+ case MOZ_GTK_RADIOMENUITEM:
+ return moz_gtk_check_menu_item_paint(widget, cr, rect, state,
+ (gboolean)flags, direction);
+ case MOZ_GTK_SPLITTER_HORIZONTAL:
+ return moz_gtk_vpaned_paint(cr, rect, state);
+ case MOZ_GTK_SPLITTER_VERTICAL:
+ return moz_gtk_hpaned_paint(cr, rect, state);
+ case MOZ_GTK_WINDOW:
+ return moz_gtk_window_paint(cr, rect, direction);
+ case MOZ_GTK_INFO_BAR:
+ return moz_gtk_info_bar_paint(cr, rect, state);
+ case MOZ_GTK_HEADER_BAR:
+ case MOZ_GTK_HEADER_BAR_MAXIMIZED:
+ return moz_gtk_header_bar_paint(widget, cr, rect, state);
+ default:
+ g_warning("Unknown widget type: %d", widget);
+ }
+
+ return MOZ_GTK_UNKNOWN_WIDGET;
+}
+
+gint moz_gtk_shutdown() {
+ /* This will destroy all of our widgets */
+ ResetWidgetCache();
+
+ return MOZ_GTK_SUCCESS;
+}
diff --git a/widget/gtk/gtkdrawing.h b/widget/gtk/gtkdrawing.h
new file mode 100644
index 0000000000..38dc1587fc
--- /dev/null
+++ b/widget/gtk/gtkdrawing.h
@@ -0,0 +1,634 @@
+/* -*- 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/. */
+
+/**
+ * gtkdrawing.h: GTK widget rendering utilities
+ *
+ * gtkdrawing provides an API for rendering GTK widgets in the
+ * current theme to a pixmap or window, without requiring an actual
+ * widget instantiation, similar to the Macintosh Appearance Manager
+ * or Windows XP's DrawThemeBackground() API.
+ */
+
+#ifndef _GTK_DRAWING_H_
+#define _GTK_DRAWING_H_
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <algorithm>
+#include "mozilla/Span.h"
+
+/*** type definitions ***/
+typedef struct {
+ guint8 active;
+ guint8 focused;
+ guint8 selected;
+ guint8 inHover;
+ guint8 disabled;
+ guint8 isDefault;
+ guint8 canDefault;
+ /* The depressed state is for buttons which remain active for a longer period:
+ * activated toggle buttons or buttons showing a popup menu. */
+ guint8 depressed;
+ guint8 backdrop;
+ gint32 curpos; /* curpos and maxpos are used for scrollbars */
+ gint32 maxpos;
+ gint32 scale; /* actual widget scale */
+} GtkWidgetState;
+
+/**
+ * A size in the same GTK pixel units as GtkBorder and GdkRectangle.
+ */
+struct MozGtkSize {
+ gint width;
+ gint height;
+
+ MozGtkSize& operator+=(const GtkBorder& aBorder) {
+ width += aBorder.left + aBorder.right;
+ height += aBorder.top + aBorder.bottom;
+ return *this;
+ }
+ MozGtkSize operator+(const GtkBorder& aBorder) const {
+ MozGtkSize result = *this;
+ return result += aBorder;
+ }
+ bool operator<(const MozGtkSize& aOther) const {
+ return (width < aOther.width && height <= aOther.height) ||
+ (width <= aOther.width && height < aOther.height);
+ }
+ void Include(MozGtkSize aOther) {
+ width = std::max(width, aOther.width);
+ height = std::max(height, aOther.height);
+ }
+ void Rotate() {
+ gint tmp = width;
+ width = height;
+ height = tmp;
+ }
+};
+
+typedef struct {
+ bool initialized;
+ struct {
+ MozGtkSize scrollbar;
+ MozGtkSize thumb;
+ MozGtkSize button;
+ } size;
+ struct {
+ GtkBorder scrollbar;
+ GtkBorder track;
+ } border;
+ struct {
+ GtkBorder thumb;
+ } margin;
+} ScrollbarGTKMetrics;
+
+typedef struct {
+ bool initialized;
+ MozGtkSize minSizeWithBorder;
+ GtkBorder borderAndPadding;
+} ToggleGTKMetrics;
+
+typedef struct {
+ MozGtkSize minSizeWithBorderMargin;
+ GtkBorder buttonMargin;
+ gint iconXPosition;
+ gint iconYPosition;
+ bool visible;
+ bool firstButton;
+ bool lastButton;
+} ToolbarButtonGTKMetrics;
+
+#define TOOLBAR_BUTTONS 3
+typedef struct {
+ bool initialized;
+ ToolbarButtonGTKMetrics button[TOOLBAR_BUTTONS];
+} ToolbarGTKMetrics;
+
+typedef struct {
+ bool initialized;
+ GtkBorder decorationSize;
+} CSDWindowDecorationSize;
+
+typedef enum {
+ MOZ_GTK_STEPPER_DOWN = 1 << 0,
+ MOZ_GTK_STEPPER_BOTTOM = 1 << 1,
+ MOZ_GTK_STEPPER_VERTICAL = 1 << 2
+} GtkScrollbarButtonFlags;
+
+typedef enum { MOZ_GTK_TRACK_OPAQUE = 1 << 0 } GtkScrollbarTrackFlags;
+
+/** flags for tab state **/
+typedef enum {
+ /* first eight bits are used to pass a margin */
+ MOZ_GTK_TAB_MARGIN_MASK = 0xFF,
+ /* the first tab in the group */
+ MOZ_GTK_TAB_FIRST = 1 << 9,
+ /* the selected tab */
+ MOZ_GTK_TAB_SELECTED = 1 << 10
+} GtkTabFlags;
+
+/*** result/error codes ***/
+#define MOZ_GTK_SUCCESS 0
+#define MOZ_GTK_UNKNOWN_WIDGET -1
+#define MOZ_GTK_UNSAFE_THEME -2
+
+/*** checkbox/radio flags ***/
+#define MOZ_GTK_WIDGET_CHECKED 1
+#define MOZ_GTK_WIDGET_INCONSISTENT (1 << 1)
+
+/*** widget type constants ***/
+enum WidgetNodeType : int {
+ /* Paints a GtkButton. flags is a GtkReliefStyle. */
+ MOZ_GTK_BUTTON,
+ /* Paints a button with image and no text */
+ MOZ_GTK_TOOLBAR_BUTTON,
+ /* Paints a toggle button */
+ MOZ_GTK_TOGGLE_BUTTON,
+ /* Paints a button arrow */
+ MOZ_GTK_BUTTON_ARROW,
+
+ /* Paints the container part of a GtkCheckButton. */
+ MOZ_GTK_CHECKBUTTON_CONTAINER,
+ /* Paints a GtkCheckButton. flags is a boolean, 1=checked, 0=not checked. */
+ MOZ_GTK_CHECKBUTTON,
+ /* Paints the label of a GtkCheckButton (focus outline) */
+ MOZ_GTK_CHECKBUTTON_LABEL,
+
+ /* Paints the container part of a GtkRadioButton. */
+ MOZ_GTK_RADIOBUTTON_CONTAINER,
+ /* Paints a GtkRadioButton. flags is a boolean, 1=checked, 0=not checked. */
+ MOZ_GTK_RADIOBUTTON,
+ /* Paints the label of a GtkRadioButton (focus outline) */
+ MOZ_GTK_RADIOBUTTON_LABEL,
+ /**
+ * Paints the button of a GtkScrollbar. flags is a GtkArrowType giving
+ * the arrow direction.
+ */
+ MOZ_GTK_SCROLLBAR_BUTTON,
+
+ /* Horizontal GtkScrollbar counterparts */
+ MOZ_GTK_SCROLLBAR_HORIZONTAL,
+ MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL,
+ /* Paints the trough (track) of a GtkScrollbar. */
+ MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL,
+ /* Paints the slider (thumb) of a GtkScrollbar. */
+ MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL,
+
+ /* Vertical GtkScrollbar counterparts */
+ MOZ_GTK_SCROLLBAR_VERTICAL,
+ MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL,
+ MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL,
+ MOZ_GTK_SCROLLBAR_THUMB_VERTICAL,
+
+ /* Paints a GtkScale. */
+ MOZ_GTK_SCALE_HORIZONTAL,
+ MOZ_GTK_SCALE_VERTICAL,
+ /* Paints a GtkScale trough. */
+ MOZ_GTK_SCALE_CONTENTS_HORIZONTAL,
+ MOZ_GTK_SCALE_CONTENTS_VERTICAL,
+ MOZ_GTK_SCALE_TROUGH_HORIZONTAL,
+ MOZ_GTK_SCALE_TROUGH_VERTICAL,
+ /* Paints a GtkScale thumb. */
+ MOZ_GTK_SCALE_THUMB_HORIZONTAL,
+ MOZ_GTK_SCALE_THUMB_VERTICAL,
+ /* Paints a GtkSpinButton */
+ MOZ_GTK_INNER_SPIN_BUTTON,
+ MOZ_GTK_SPINBUTTON,
+ MOZ_GTK_SPINBUTTON_UP,
+ MOZ_GTK_SPINBUTTON_DOWN,
+ MOZ_GTK_SPINBUTTON_ENTRY,
+ /* Paints the gripper of a GtkHandleBox. */
+ MOZ_GTK_GRIPPER,
+ /* Paints a GtkEntry. */
+ MOZ_GTK_ENTRY,
+ /* Paints a GtkExpander. */
+ MOZ_GTK_EXPANDER,
+ /* Paints a GtkTextView or gets the style context corresponding to the
+ root node of a GtkTextView. */
+ MOZ_GTK_TEXT_VIEW,
+ /* The "text" window or node of a GtkTextView */
+ MOZ_GTK_TEXT_VIEW_TEXT,
+ /* The "selection" node of a GtkTextView.text */
+ MOZ_GTK_TEXT_VIEW_TEXT_SELECTION,
+ /* Paints a GtkOptionMenu. */
+ MOZ_GTK_DROPDOWN,
+ /* Paints a dropdown arrow (a GtkButton containing a down GtkArrow). */
+ MOZ_GTK_DROPDOWN_ARROW,
+ /* Paints an entry in an editable option menu */
+ MOZ_GTK_DROPDOWN_ENTRY,
+
+ /* Paints the background of a GtkHandleBox. */
+ MOZ_GTK_TOOLBAR,
+ /* Paints a toolbar separator */
+ MOZ_GTK_TOOLBAR_SEPARATOR,
+ /* Paints a GtkToolTip */
+ MOZ_GTK_TOOLTIP,
+ /* Paints a GtkBox from GtkToolTip */
+ MOZ_GTK_TOOLTIP_BOX,
+ /* Paints a GtkLabel of GtkToolTip */
+ MOZ_GTK_TOOLTIP_BOX_LABEL,
+ /* Paints a GtkFrame (e.g. a status bar panel). */
+ MOZ_GTK_FRAME,
+ /* Paints the border of a GtkFrame */
+ MOZ_GTK_FRAME_BORDER,
+ /* Paints a resize grip for a GtkTextView */
+ MOZ_GTK_RESIZER,
+ /* Paints a GtkProgressBar. */
+ MOZ_GTK_PROGRESSBAR,
+ /* Paints a trough (track) of a GtkProgressBar */
+ MOZ_GTK_PROGRESS_TROUGH,
+ /* Paints a progress chunk of a GtkProgressBar. */
+ MOZ_GTK_PROGRESS_CHUNK,
+ /* Paints a progress chunk of an indeterminated GtkProgressBar. */
+ MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE,
+ /* Paints a progress chunk of a vertical indeterminated GtkProgressBar. */
+ MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE,
+ /* Used as root style of whole GtkNotebook widget */
+ MOZ_GTK_NOTEBOOK,
+ /* Used as root style of active GtkNotebook area which contains tabs and
+ arrows. */
+ MOZ_GTK_NOTEBOOK_HEADER,
+ /* Paints a tab of a GtkNotebook. flags is a GtkTabFlags, defined above. */
+ MOZ_GTK_TAB_TOP,
+ /* Paints a tab of a GtkNotebook. flags is a GtkTabFlags, defined above. */
+ MOZ_GTK_TAB_BOTTOM,
+ /* Paints the background and border of a GtkNotebook. */
+ MOZ_GTK_TABPANELS,
+ /* Paints a GtkArrow for a GtkNotebook. flags is a GtkArrowType. */
+ MOZ_GTK_TAB_SCROLLARROW,
+ /* Paints the expander and border of a GtkTreeView */
+ MOZ_GTK_TREEVIEW,
+ /* Paints the border of a GtkTreeView */
+ MOZ_GTK_TREEVIEW_VIEW,
+ /* Paints treeheader cells */
+ MOZ_GTK_TREE_HEADER_CELL,
+ /* Paints sort arrows in treeheader cells */
+ MOZ_GTK_TREE_HEADER_SORTARROW,
+ /* Paints an expander for a GtkTreeView */
+ MOZ_GTK_TREEVIEW_EXPANDER,
+ /* Paints the background of the menu bar. */
+ MOZ_GTK_MENUBAR,
+ /* Paints the background of menus, context menus. */
+ MOZ_GTK_MENUPOPUP,
+ /* Paints the arrow of menuitems that contain submenus */
+ MOZ_GTK_MENUARROW,
+ /* Paints an arrow in a toolbar button. flags is a GtkArrowType. */
+ MOZ_GTK_TOOLBARBUTTON_ARROW,
+ /* Paints items of menubar. */
+ MOZ_GTK_MENUBARITEM,
+ /* Paints items of popup menus. */
+ MOZ_GTK_MENUITEM,
+ /* Paints a menuitem with check indicator, or the gets the style context for
+ a menuitem that contains a checkbox. */
+ MOZ_GTK_CHECKMENUITEM,
+ /* Gets the style context for a checkbox in a check menuitem. */
+ MOZ_GTK_CHECKMENUITEM_INDICATOR,
+ MOZ_GTK_RADIOMENUITEM,
+ MOZ_GTK_RADIOMENUITEM_INDICATOR,
+ MOZ_GTK_MENUSEPARATOR,
+ /* GtkVPaned base class */
+ MOZ_GTK_SPLITTER_HORIZONTAL,
+ /* GtkHPaned base class */
+ MOZ_GTK_SPLITTER_VERTICAL,
+ /* Paints a GtkVPaned separator */
+ MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL,
+ /* Paints a GtkHPaned separator */
+ MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL,
+ /* Paints the background of a window, dialog or page. */
+ MOZ_GTK_WINDOW,
+ /* Used only as a container for MOZ_GTK_HEADER_BAR. */
+ MOZ_GTK_HEADERBAR_WINDOW,
+ /* Used only as a container for MOZ_GTK_HEADER_BAR_MAXIMIZED. */
+ MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED,
+ /* Window container for all widgets */
+ MOZ_GTK_WINDOW_CONTAINER,
+ /* Paints a GtkInfoBar, for notifications. */
+ MOZ_GTK_INFO_BAR,
+ /* Used for widget tree construction. */
+ MOZ_GTK_COMBOBOX,
+ /* Paints a GtkComboBox button widget. */
+ MOZ_GTK_COMBOBOX_BUTTON,
+ /* Paints a GtkComboBox arrow widget. */
+ MOZ_GTK_COMBOBOX_ARROW,
+ /* Paints a GtkComboBox separator widget. */
+ MOZ_GTK_COMBOBOX_SEPARATOR,
+ /* Used for widget tree construction. */
+ MOZ_GTK_COMBOBOX_ENTRY,
+ /* Paints a GtkComboBox entry widget. */
+ MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA,
+ /* Paints a GtkComboBox entry button widget. */
+ MOZ_GTK_COMBOBOX_ENTRY_BUTTON,
+ /* Paints a GtkComboBox entry arrow widget. */
+ MOZ_GTK_COMBOBOX_ENTRY_ARROW,
+ /* Used for scrolled window shell. */
+ MOZ_GTK_SCROLLED_WINDOW,
+ /* Paints a GtkHeaderBar */
+ MOZ_GTK_HEADER_BAR,
+ /* Paints a GtkHeaderBar in maximized state */
+ MOZ_GTK_HEADER_BAR_MAXIMIZED,
+ /* Container for GtkHeaderBar buttons */
+ MOZ_GTK_HEADER_BAR_BUTTON_BOX,
+ /* Paints GtkHeaderBar title buttons.
+ * Keep the order here as MOZ_GTK_HEADER_BAR_BUTTON_* are processed
+ * as an array from MOZ_GTK_HEADER_BAR_BUTTON_CLOSE to the last one.
+ */
+ MOZ_GTK_HEADER_BAR_BUTTON_CLOSE,
+ MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE,
+ MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE,
+
+ /* MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE is a state of
+ * MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE button and it's used as
+ * an icon placeholder only.
+ */
+ MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE,
+
+ /* Client-side window decoration node. Available on GTK 3.20+. */
+ MOZ_GTK_WINDOW_DECORATION,
+ MOZ_GTK_WINDOW_DECORATION_SOLID,
+
+ MOZ_GTK_WIDGET_NODE_COUNT
+};
+
+/* ButtonLayout represents a GTK CSD button and whether its on the left or
+ * right side of the tab bar */
+struct ButtonLayout {
+ WidgetNodeType mType;
+ bool mAtRight;
+};
+
+/*** General library functions ***/
+/**
+ * Initializes the drawing library. You must call this function
+ * prior to using any other functionality.
+ * returns: MOZ_GTK_SUCCESS if there were no errors
+ * MOZ_GTK_UNSAFE_THEME if the current theme engine is known
+ * to crash with gtkdrawing.
+ */
+gint moz_gtk_init();
+
+/**
+ * Updates the drawing library when the theme changes.
+ */
+void moz_gtk_refresh();
+
+/**
+ * Perform cleanup of the drawing library. You should call this function
+ * when your program exits, or you no longer need the library.
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_shutdown();
+
+/*** Widget drawing ***/
+/**
+ * Paint a widget in the current theme.
+ * widget: a constant giving the widget to paint
+ * drawable: the drawable to paint to;
+ * it's colormap must be moz_gtk_widget_get_colormap().
+ * rect: the bounding rectangle for the widget
+ * state: the state of the widget. ignored for some widgets.
+ * flags: widget-dependant flags; see the WidgetNodeType definition.
+ * direction: the text direction, to draw the widget correctly LTR and RTL.
+ */
+gint moz_gtk_widget_paint(WidgetNodeType widget, cairo_t* cr,
+ GdkRectangle* rect, GtkWidgetState* state, gint flags,
+ GtkTextDirection direction);
+
+/*** Widget metrics ***/
+/**
+ * Get the border size of a widget
+ * left/right: [OUT] the widget's left/right border
+ * top/bottom: [OUT] the widget's top/bottom border
+ * direction: the text direction for the widget. Callers depend on this
+ * being used only for MOZ_GTK_DROPDOWN widgets, and cache
+ * results for other widget types across direction values.
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_widget_border(WidgetNodeType widget, gint* left, gint* top,
+ gint* right, gint* bottom,
+ GtkTextDirection direction);
+
+/**
+ * Get the border size of a notebook tab
+ * left/right: [OUT] the tab's left/right border
+ * top/bottom: [OUT] the tab's top/bottom border
+ * direction: the text direction for the widget
+ * flags: tab-dependant flags; see the GtkTabFlags definition.
+ * widget: tab widget
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_tab_border(gint* left, gint* top, gint* right, gint* bottom,
+ GtkTextDirection direction, GtkTabFlags flags,
+ WidgetNodeType widget);
+
+/**
+ * Get the desired size of a GtkCheckButton
+ * indicator_size: [OUT] the indicator size
+ * indicator_spacing: [OUT] the spacing between the indicator and its
+ * container
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_checkbox_get_metrics(gint* indicator_size,
+ gint* indicator_spacing);
+
+/**
+ * Get metrics of the toggle (radio or checkbox)
+ * isRadio: [IN] true when requesting metrics for the radio button
+ * returns: pointer to ToggleGTKMetrics struct
+ */
+const ToggleGTKMetrics* GetToggleMetrics(WidgetNodeType aWidgetType);
+
+/**
+ * Get the desired size of a GtkRadioButton
+ * indicator_size: [OUT] the indicator size
+ * indicator_spacing: [OUT] the spacing between the indicator and its
+ * container
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_radio_get_metrics(gint* indicator_size, gint* indicator_spacing);
+
+/** Returns the size of the focus ring for outline:auto.
+ * focus_h_width: [OUT] the horizontal width
+ * focus_v_width: [OUT] the vertical width
+ *
+ * returns: MOZ_GTK_SUCCESS
+ */
+gint moz_gtk_get_focus_outline_size(gint* focus_h_width, gint* focus_v_width);
+
+/** Get the horizontal padding for the menuitem widget or checkmenuitem widget.
+ * horizontal_padding: [OUT] The left and right padding of the menuitem or
+ * checkmenuitem
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_menuitem_get_horizontal_padding(gint* horizontal_padding);
+
+gint moz_gtk_checkmenuitem_get_horizontal_padding(gint* horizontal_padding);
+
+/**
+ * Some GTK themes draw their indication for the default button outside
+ * the button (e.g. the glow in New Wave). This gets the extra space necessary.
+ *
+ * border_top: [OUT] extra space to add above
+ * border_left: [OUT] extra space to add to the left
+ * border_bottom: [OUT] extra space to add underneath
+ * border_right: [OUT] extra space to add to the right
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_button_get_default_overflow(gint* border_top, gint* border_left,
+ gint* border_bottom,
+ gint* border_right);
+
+/**
+ * Gets the minimum size of a GtkScale.
+ * orient: [IN] the scale orientation
+ * scale_width: [OUT] the width of the scale
+ * scale_height: [OUT] the height of the scale
+ */
+void moz_gtk_get_scale_metrics(GtkOrientation orient, gint* scale_width,
+ gint* scale_height);
+
+/**
+ * Get the desired size of a GtkScale thumb
+ * orient: [IN] the scale orientation
+ * thumb_length: [OUT] the length of the thumb
+ * thumb_height: [OUT] the height of the thumb
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_scalethumb_metrics(GtkOrientation orient, gint* thumb_length,
+ gint* thumb_height);
+
+/**
+ * Get the metrics in GTK pixels for a scrollbar.
+ * aOrientation: [IN] the scrollbar orientation
+ */
+const ScrollbarGTKMetrics* GetScrollbarMetrics(GtkOrientation aOrientation);
+
+/**
+ * Get the metrics in GTK pixels for a scrollbar which is active
+ * (selected by mouse pointer).
+ * aOrientation: [IN] the scrollbar orientation
+ */
+const ScrollbarGTKMetrics* GetActiveScrollbarMetrics(
+ GtkOrientation aOrientation);
+
+/**
+ * Get the desired size of a dropdown arrow button
+ * width: [OUT] the desired width
+ * height: [OUT] the desired height
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_combo_box_entry_button_size(gint* width, gint* height);
+
+/**
+ * Get the desired size of a scroll arrow widget
+ * width: [OUT] the desired width
+ * height: [OUT] the desired height
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_tab_scroll_arrow_size(gint* width, gint* height);
+
+/**
+ * Get the desired size of an arrow in a button
+ *
+ * widgetType: [IN] the widget for which to get the arrow size
+ * width: [OUT] the desired width
+ * height: [OUT] the desired height
+ */
+void moz_gtk_get_arrow_size(WidgetNodeType widgetType, gint* width,
+ gint* height);
+
+/**
+ * Get the minimum height of a entry widget
+ * min_content_height: [OUT] the minimum height of the content box.
+ * border_padding_height: [OUT] the size of borders and paddings.
+ */
+void moz_gtk_get_entry_min_height(gint* min_content_height,
+ gint* border_padding_height);
+
+/**
+ * Get the desired size of a toolbar separator
+ * size: [OUT] the desired width
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_toolbar_separator_width(gint* size);
+
+/**
+ * Get the size of a regular GTK expander that shows/hides content
+ * size: [OUT] the size of the GTK expander, size = width = height.
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_expander_size(gint* size);
+
+/**
+ * Get the size of a treeview's expander (we call them twisties)
+ * size: [OUT] the size of the GTK expander, size = width = height.
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_treeview_expander_size(gint* size);
+
+/**
+ * Get the desired height of a menu separator
+ * size: [OUT] the desired height
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_menu_separator_height(gint* size);
+
+/**
+ * Get the desired size of a splitter
+ * orientation: [IN] GTK_ORIENTATION_HORIZONTAL or GTK_ORIENTATION_VERTICAL
+ * size: [OUT] width or height of the splitter handle
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_splitter_get_metrics(gint orientation, gint* size);
+
+/**
+ * Get the YTHICKNESS of a tab (notebook extension).
+ */
+gint moz_gtk_get_tab_thickness(WidgetNodeType aNodeType);
+
+/**
+ * Get ToolbarButtonGTKMetrics for recent theme.
+ */
+const ToolbarButtonGTKMetrics* GetToolbarButtonMetrics(
+ WidgetNodeType aAppearance);
+
+/**
+ * Get toolbar button layout.
+ * aButtonLayout: [OUT] An array which will be filled by ButtonLayout
+ * references to visible titlebar buttons. Must contain at
+ * least TOOLBAR_BUTTONS entries if non-empty.
+ * aReversedButtonsPlacement: [OUT] True if the buttons are placed in opposite
+ * titlebar corner.
+ *
+ * returns: Number of returned entries at aButtonLayout.
+ */
+size_t GetGtkHeaderBarButtonLayout(mozilla::Span<ButtonLayout>,
+ bool* aReversedButtonsPlacement);
+
+/**
+ * Get size of CSD window extents.
+ *
+ * aIsPopup: [IN] Get decoration size for popup or toplevel window.
+ *
+ * returns: Calculated (or estimated) decoration size of given aGtkWindow.
+ */
+GtkBorder GetCSDDecorationSize(bool aIsPopup);
+
+#endif
diff --git a/widget/gtk/maiRedundantObjectFactory.c b/widget/gtk/maiRedundantObjectFactory.c
new file mode 100644
index 0000000000..ce086e20af
--- /dev/null
+++ b/widget/gtk/maiRedundantObjectFactory.c
@@ -0,0 +1,81 @@
+/* -*- 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/. */
+
+#include <atk/atk.h>
+#include "maiRedundantObjectFactory.h"
+
+static void mai_redundant_object_factory_class_init(
+ maiRedundantObjectFactoryClass* klass);
+
+static AtkObject* mai_redundant_object_factory_create_accessible(GObject* obj);
+static GType mai_redundant_object_factory_get_accessible_type(void);
+
+GType mai_redundant_object_factory_get_type(void) {
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo tinfo = {
+ sizeof(maiRedundantObjectFactoryClass),
+ (GBaseInitFunc)NULL, /* base init */
+ (GBaseFinalizeFunc)NULL, /* base finalize */
+ (GClassInitFunc)
+ mai_redundant_object_factory_class_init, /* class init */
+ (GClassFinalizeFunc)NULL, /* class finalize */
+ NULL, /* class data */
+ sizeof(maiRedundantObjectFactory), /* instance size */
+ 0, /* nb preallocs */
+ (GInstanceInitFunc)NULL, /* instance init */
+ NULL /* value table */
+ };
+ type = g_type_register_static(ATK_TYPE_OBJECT_FACTORY,
+ "MaiRedundantObjectFactory", &tinfo, 0);
+ }
+
+ return type;
+}
+
+static void mai_redundant_object_factory_class_init(
+ maiRedundantObjectFactoryClass* klass) {
+ AtkObjectFactoryClass* class = ATK_OBJECT_FACTORY_CLASS(klass);
+
+ class->create_accessible = mai_redundant_object_factory_create_accessible;
+ class->get_accessible_type = mai_redundant_object_factory_get_accessible_type;
+}
+
+/**
+ * mai_redundant_object_factory_new:
+ *
+ * Creates an instance of an #AtkObjectFactory which generates primitive
+ * (non-functioning) #AtkObjects.
+ *
+ * Returns: an instance of an #AtkObjectFactory
+ **/
+AtkObjectFactory* mai_redundant_object_factory_new() {
+ GObject* factory;
+
+ factory = g_object_new(mai_redundant_object_factory_get_type(), NULL);
+
+ g_return_val_if_fail(factory != NULL, NULL);
+ return ATK_OBJECT_FACTORY(factory);
+}
+
+static AtkObject* mai_redundant_object_factory_create_accessible(GObject* obj) {
+ AtkObject* accessible;
+
+ g_return_val_if_fail(obj != NULL, NULL);
+
+ accessible = g_object_new(ATK_TYPE_OBJECT, NULL);
+ g_return_val_if_fail(accessible != NULL, NULL);
+
+ accessible->role = ATK_ROLE_REDUNDANT_OBJECT;
+
+ return accessible;
+}
+
+static GType mai_redundant_object_factory_get_accessible_type() {
+ return mai_redundant_object_factory_get_type();
+}
diff --git a/widget/gtk/maiRedundantObjectFactory.h b/widget/gtk/maiRedundantObjectFactory.h
new file mode 100644
index 0000000000..931adffb6b
--- /dev/null
+++ b/widget/gtk/maiRedundantObjectFactory.h
@@ -0,0 +1,30 @@
+/* -*- 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 __MAI_REDUNDANT_OBJECT_FACTORY_H__
+#define __MAI_REDUNDANT_OBJECT_FACTORY_H__
+
+G_BEGIN_DECLS
+
+typedef struct _maiRedundantObjectFactory maiRedundantObjectFactory;
+typedef struct _maiRedundantObjectFactoryClass maiRedundantObjectFactoryClass;
+
+struct _maiRedundantObjectFactory {
+ AtkObjectFactory parent;
+};
+
+struct _maiRedundantObjectFactoryClass {
+ AtkObjectFactoryClass parent_class;
+};
+
+GType mai_redundant_object_factory_get_type();
+
+AtkObjectFactory* mai_redundant_object_factory_new();
+
+G_END_DECLS
+
+#endif /* __NS_MAI_REDUNDANT_OBJECT_FACTORY_H__ */
diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build
new file mode 100644
index 0000000000..cddd45e49f
--- /dev/null
+++ b/widget/gtk/moz.build
@@ -0,0 +1,178 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget: Gtk")
+
+with Files("*CompositorWidget*"):
+ BUG_COMPONENT = ("Core", "Graphics")
+
+with Files("*WindowSurface*"):
+ BUG_COMPONENT = ("Core", "Graphics")
+
+with Files("*IMContextWrapper*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*nsGtkKeyUtils*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ DIRS += ["mozgtk"]
+
+if CONFIG["MOZ_WAYLAND"]:
+ DIRS += ["wayland", "mozwayland"]
+
+EXPORTS += [
+ "MozContainer.h",
+ "nsGTKToolkit.h",
+ "nsIImageToPixbuf.h",
+]
+
+EXPORTS.mozilla += ["WidgetUtilsGtk.h"]
+
+UNIFIED_SOURCES += [
+ "IMContextWrapper.cpp",
+ "MozContainer.cpp",
+ "MPRISServiceHandler.cpp",
+ "NativeKeyBindings.cpp",
+ "nsAppShell.cpp",
+ "nsBidiKeyboard.cpp",
+ "nsColorPicker.cpp",
+ "nsFilePicker.cpp",
+ "nsGtkKeyUtils.cpp",
+ "nsImageToPixbuf.cpp",
+ "nsLookAndFeel.cpp",
+ "nsNativeBasicThemeGTK.cpp",
+ "nsNativeThemeGTK.cpp",
+ "nsSound.cpp",
+ "nsToolkit.cpp",
+ "nsWidgetFactory.cpp",
+ "ScreenHelperGTK.cpp",
+ "TaskbarProgress.cpp",
+ "WakeLockListener.cpp",
+ "WidgetTraceEvent.cpp",
+ "WidgetUtilsGtk.cpp",
+]
+
+SOURCES += [
+ "MediaKeysEventSourceFactory.cpp",
+ "nsWindow.cpp", # conflicts with X11 headers
+ "WaylandVsyncSource.cpp", # conflicts with X11 headers
+]
+
+if CONFIG["MOZ_X11"]:
+ UNIFIED_SOURCES += [
+ "CompositorWidgetChild.cpp",
+ "CompositorWidgetParent.cpp",
+ "GtkCompositorWidget.cpp",
+ "InProcessGtkCompositorWidget.cpp",
+ "nsUserIdleServiceGTK.cpp",
+ ]
+ EXPORTS.mozilla.widget += [
+ "CompositorWidgetChild.h",
+ "CompositorWidgetParent.h",
+ "GtkCompositorWidget.h",
+ "InProcessGtkCompositorWidget.h",
+ ]
+
+if CONFIG["NS_PRINTING"]:
+ UNIFIED_SOURCES += [
+ "nsDeviceContextSpecG.cpp",
+ "nsPrintDialogGTK.cpp",
+ "nsPrintSettingsGTK.cpp",
+ "nsPrintSettingsServiceGTK.cpp",
+ ]
+
+if CONFIG["MOZ_X11"]:
+ UNIFIED_SOURCES += [
+ "nsClipboard.cpp",
+ "nsClipboardX11.cpp",
+ "nsDragService.cpp",
+ "WindowSurfaceProvider.cpp",
+ "WindowSurfaceX11.cpp",
+ "WindowSurfaceX11Image.cpp",
+ "WindowSurfaceXRender.cpp",
+ ]
+ EXPORTS.mozilla.widget += [
+ "WindowSurfaceProvider.h",
+ ]
+
+if CONFIG["MOZ_WAYLAND"]:
+ UNIFIED_SOURCES += [
+ "DMABufLibWrapper.cpp",
+ "DMABufSurface.cpp",
+ "MozContainerWayland.cpp",
+ "nsClipboardWayland.cpp",
+ "nsWaylandDisplay.cpp",
+ "WindowSurfaceWayland.cpp",
+ ]
+ EXPORTS.mozilla.widget += [
+ "DMABufLibWrapper.h",
+ "DMABufSurface.h",
+ "MozContainerWayland.h",
+ "nsWaylandDisplay.h",
+ ]
+
+if CONFIG["ACCESSIBILITY"]:
+ UNIFIED_SOURCES += [
+ "maiRedundantObjectFactory.c",
+ ]
+
+UNIFIED_SOURCES += [
+ "gtk3drawing.cpp",
+ "nsApplicationChooser.cpp",
+ "WidgetStyleCache.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/layout/base",
+ "/layout/forms",
+ "/layout/generic",
+ "/layout/xul",
+ "/other-licenses/atk-1.0",
+ "/third_party/cups/include",
+ "/widget",
+ "/widget/headless",
+]
+
+if CONFIG["MOZ_X11"]:
+ LOCAL_INCLUDES += [
+ "/widget/x11",
+ ]
+
+DEFINES["CAIRO_GFX"] = True
+
+DEFINES["MOZ_APP_NAME"] = '"%s"' % CONFIG["MOZ_APP_NAME"]
+
+# When building with GTK3, the widget code always needs to use
+# system Cairo headers, regardless of whether we are also linked
+# against and using in-tree Cairo. By not using in-tree Cairo
+# headers, we avoid picking up our renamed symbols, and instead
+# use only system Cairo symbols that GTK3 uses. This allows that
+# any Cairo objects created can be freely passed back and forth
+# between the widget code and GTK3.
+if not (CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk" and CONFIG["MOZ_TREE_CAIRO"]):
+ CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"]
+
+CFLAGS += CONFIG["TK_CFLAGS"]
+CXXFLAGS += CONFIG["TK_CFLAGS"]
+
+if CONFIG["MOZ_WAYLAND"]:
+ CFLAGS += CONFIG["MOZ_WAYLAND_CFLAGS"]
+ CXXFLAGS += CONFIG["MOZ_WAYLAND_CFLAGS"]
+
+if CONFIG["MOZ_ENABLE_DBUS"]:
+ CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"]
+
+CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/widget/gtk/mozgtk/gtk2/moz.build b/widget/gtk/mozgtk/gtk2/moz.build
new file mode 100644
index 0000000000..93e43c3957
--- /dev/null
+++ b/widget/gtk/mozgtk/gtk2/moz.build
@@ -0,0 +1,40 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 += [
+ "../mozgtk.c",
+]
+
+DEFINES["GTK3_SYMBOLS"] = True
+
+SharedLibrary("mozgtk2")
+
+SHARED_LIBRARY_NAME = "mozgtk"
+
+FINAL_TARGET = "dist/bin/gtk2"
+
+# If LDFLAGS contains -Wl,--as-needed or if it's the default for the toolchain,
+# we need to add -Wl,--no-as-needed before the gtk libraries, otherwise the
+# linker will drop those dependencies because no symbols are used from them.
+# But those dependencies need to be kept for things to work properly.
+# Ideally, we'd only add -Wl,--no-as-needed if necessary, but it's just simpler
+# to add it unconditionally. This library is also simple enough that forcing
+# -Wl,--as-needed after the gtk libraries is not going to make a significant
+# difference.
+if CONFIG["GCC_USE_GNU_LD"]:
+ no_as_needed = ["-Wl,--no-as-needed"]
+ as_needed = ["-Wl,--as-needed"]
+else:
+ no_as_needed = []
+ as_needed = []
+
+OS_LIBS += [f for f in CONFIG["MOZ_GTK2_LIBS"] if f.startswith("-L")]
+OS_LIBS += no_as_needed
+OS_LIBS += [
+ "gtk-x11-2.0",
+ "gdk-x11-2.0",
+]
+OS_LIBS += as_needed
diff --git a/widget/gtk/mozgtk/gtk3/moz.build b/widget/gtk/mozgtk/gtk3/moz.build
new file mode 100644
index 0000000000..b4ab68ecb1
--- /dev/null
+++ b/widget/gtk/mozgtk/gtk3/moz.build
@@ -0,0 +1,38 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 += [
+ "../mozgtk.c",
+]
+
+DEFINES["GTK2_SYMBOLS"] = True
+
+SharedLibrary("mozgtk")
+
+SONAME = "mozgtk"
+
+# If LDFLAGS contains -Wl,--as-needed or if it's the default for the toolchain,
+# we need to add -Wl,--no-as-needed before the gtk libraries, otherwise the
+# linker will drop those dependencies because no symbols are used from them.
+# But those dependencies need to be kept for things to work properly.
+# Ideally, we'd only add -Wl,--no-as-needed if necessary, but it's just simpler
+# to add it unconditionally. This library is also simple enough that forcing
+# -Wl,--as-needed after the gtk libraries is not going to make a significant
+# difference.
+if CONFIG["GCC_USE_GNU_LD"]:
+ no_as_needed = ["-Wl,--no-as-needed"]
+ as_needed = ["-Wl,--as-needed"]
+else:
+ no_as_needed = []
+ as_needed = []
+
+OS_LIBS += [f for f in CONFIG["MOZ_GTK3_LIBS"] if f.startswith("-L")]
+OS_LIBS += no_as_needed
+OS_LIBS += [
+ "gtk-3",
+ "gdk-3",
+]
+OS_LIBS += as_needed
diff --git a/widget/gtk/mozgtk/moz.build b/widget/gtk/mozgtk/moz.build
new file mode 100644
index 0000000000..8288583745
--- /dev/null
+++ b/widget/gtk/mozgtk/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 += ["stub", "gtk2", "gtk3"]
diff --git a/widget/gtk/mozgtk/mozgtk.c b/widget/gtk/mozgtk/mozgtk.c
new file mode 100644
index 0000000000..0b2e3fd494
--- /dev/null
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -0,0 +1,676 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Types.h"
+#include "mozilla/Assertions.h"
+
+#define STUB(symbol) \
+ MOZ_EXPORT void symbol(void) { MOZ_CRASH(); }
+
+#ifdef COMMON_SYMBOLS
+STUB(gdk_atom_intern)
+STUB(gdk_atom_name)
+STUB(gdk_beep)
+STUB(gdk_cairo_create)
+STUB(gdk_color_free)
+STUB(gdk_color_parse)
+STUB(gdk_cursor_new_for_display)
+STUB(gdk_cursor_new_from_name)
+STUB(gdk_cursor_new_from_pixbuf)
+STUB(gdk_display_close)
+STUB(gdk_display_get_default)
+STUB(gdk_display_get_default_screen)
+STUB(gdk_display_get_pointer)
+STUB(gdk_display_get_window_at_pointer)
+STUB(gdk_display_manager_get)
+STUB(gdk_display_manager_set_default_display)
+STUB(gdk_display_open)
+STUB(gdk_display_sync)
+STUB(gdk_display_warp_pointer)
+STUB(gdk_drag_context_get_actions)
+STUB(gdk_drag_context_get_dest_window)
+STUB(gdk_drag_context_get_source_window)
+STUB(gdk_drag_context_list_targets)
+STUB(gdk_drag_status)
+STUB(gdk_error_trap_pop)
+STUB(gdk_error_trap_push)
+STUB(gdk_event_copy)
+STUB(gdk_event_free)
+STUB(gdk_event_get_axis)
+STUB(gdk_event_get_time)
+STUB(gdk_event_handler_set)
+STUB(gdk_event_peek)
+STUB(gdk_event_put)
+STUB(gdk_flush)
+STUB(gdk_get_default_root_window)
+STUB(gdk_get_display)
+STUB(gdk_get_display_arg_name)
+STUB(gdk_get_program_class)
+STUB(gdk_keymap_get_default)
+STUB(gdk_keymap_get_direction)
+STUB(gdk_keymap_get_entries_for_keyval)
+STUB(gdk_keymap_get_for_display)
+STUB(gdk_keymap_have_bidi_layouts)
+STUB(gdk_keymap_translate_keyboard_state)
+STUB(gdk_keyval_name)
+STUB(gdk_keyval_to_unicode)
+STUB(gdk_pango_context_get)
+STUB(gdk_pointer_grab)
+STUB(gdk_pointer_ungrab)
+STUB(gdk_property_change)
+STUB(gdk_property_get)
+STUB(gdk_property_delete)
+STUB(gdk_screen_get_default)
+STUB(gdk_screen_get_display)
+STUB(gdk_screen_get_font_options)
+STUB(gdk_screen_get_height)
+STUB(gdk_screen_get_height_mm)
+STUB(gdk_screen_get_n_monitors)
+STUB(gdk_screen_get_monitor_at_window)
+STUB(gdk_screen_get_monitor_geometry)
+STUB(gdk_screen_get_monitor_height_mm)
+STUB(gdk_screen_get_number)
+STUB(gdk_screen_get_resolution)
+STUB(gdk_screen_get_rgba_visual)
+STUB(gdk_screen_get_root_window)
+STUB(gdk_screen_get_system_visual)
+STUB(gdk_screen_get_width)
+STUB(gdk_screen_height)
+STUB(gdk_screen_is_composited)
+STUB(gdk_screen_width)
+STUB(gdk_selection_owner_get)
+STUB(gdk_set_program_class)
+STUB(gdk_unicode_to_keyval)
+STUB(gdk_visual_get_depth)
+STUB(gdk_visual_get_system)
+STUB(gdk_window_add_filter)
+STUB(gdk_window_begin_move_drag)
+STUB(gdk_window_begin_resize_drag)
+STUB(gdk_window_destroy)
+STUB(gdk_window_focus)
+STUB(gdk_window_get_children)
+STUB(gdk_window_get_display)
+STUB(gdk_window_get_events)
+STUB(gdk_window_get_geometry)
+STUB(gdk_window_get_height)
+STUB(gdk_window_get_origin)
+STUB(gdk_window_get_parent)
+STUB(gdk_window_get_position)
+STUB(gdk_window_get_root_origin)
+STUB(gdk_window_get_screen)
+STUB(gtk_window_get_size)
+STUB(gdk_window_get_state)
+STUB(gdk_window_get_toplevel)
+STUB(gdk_window_get_update_area)
+STUB(gdk_window_get_user_data)
+STUB(gdk_window_get_visual)
+STUB(gdk_window_get_width)
+STUB(gdk_window_get_window_type)
+STUB(gdk_window_hide)
+STUB(gdk_window_input_shape_combine_region)
+STUB(gdk_window_invalidate_rect)
+STUB(gdk_window_invalidate_region)
+STUB(gdk_window_is_destroyed)
+STUB(gdk_window_is_visible)
+STUB(gdk_window_lower)
+STUB(gdk_window_move)
+STUB(gdk_window_move_resize)
+STUB(gdk_window_new)
+STUB(gdk_window_peek_children)
+STUB(gdk_window_process_updates)
+STUB(gdk_window_raise)
+STUB(gdk_window_remove_filter)
+STUB(gdk_window_reparent)
+STUB(gdk_window_resize)
+STUB(gdk_window_set_cursor)
+STUB(gdk_window_set_debug_updates)
+STUB(gdk_window_set_decorations)
+STUB(gdk_window_set_events)
+STUB(gdk_window_set_role)
+STUB(gdk_window_set_urgency_hint)
+STUB(gdk_window_set_user_data)
+STUB(gdk_window_shape_combine_region)
+STUB(gdk_window_show)
+STUB(gdk_window_show_unraised)
+STUB(gdk_x11_atom_to_xatom)
+STUB(gdk_x11_display_get_user_time)
+STUB(gdk_x11_display_get_xdisplay)
+STUB(gdk_x11_get_default_root_xwindow)
+STUB(gdk_x11_get_default_xdisplay)
+STUB(gdk_x11_get_server_time)
+STUB(gdk_x11_get_xatom_by_name)
+STUB(gdk_x11_get_xatom_by_name_for_display)
+STUB(gdk_x11_lookup_xdisplay)
+STUB(gdk_x11_screen_get_xscreen)
+STUB(gdk_x11_screen_get_screen_number)
+STUB(gdk_x11_screen_lookup_visual)
+STUB(gdk_x11_screen_supports_net_wm_hint)
+STUB(gdk_x11_visual_get_xvisual)
+STUB(gdk_x11_window_foreign_new_for_display)
+STUB(gdk_x11_window_lookup_for_display)
+STUB(gdk_x11_window_set_user_time)
+STUB(gdk_x11_xatom_to_atom)
+STUB(gdk_x11_set_sm_client_id)
+STUB(gtk_accel_label_new)
+STUB(gtk_alignment_get_type)
+STUB(gtk_alignment_new)
+STUB(gtk_alignment_set_padding)
+STUB(gtk_arrow_get_type)
+STUB(gtk_arrow_new)
+STUB(gtk_bindings_activate)
+STUB(gtk_bin_get_child)
+STUB(gtk_bin_get_type)
+STUB(gtk_border_free)
+STUB(gtk_box_get_type)
+STUB(gtk_box_pack_start)
+STUB(gtk_button_new)
+STUB(gtk_button_new_with_label)
+STUB(gtk_check_button_new_with_label)
+STUB(gtk_check_button_new_with_mnemonic)
+STUB(gtk_check_menu_item_new)
+STUB(gtk_check_version)
+STUB(gtk_clipboard_clear)
+STUB(gtk_clipboard_get)
+STUB(gtk_clipboard_request_contents)
+STUB(gtk_clipboard_request_text)
+STUB(gtk_clipboard_set_can_store)
+STUB(gtk_clipboard_set_with_data)
+STUB(gtk_clipboard_store)
+STUB(gtk_color_selection_dialog_get_color_selection)
+STUB(gtk_color_selection_dialog_get_type)
+STUB(gtk_color_selection_dialog_new)
+STUB(gtk_color_selection_get_current_color)
+STUB(gtk_color_selection_get_type)
+STUB(gtk_color_selection_set_current_color)
+STUB(gtk_combo_box_get_active)
+STUB(gtk_combo_box_get_type)
+STUB(gtk_combo_box_new)
+STUB(gtk_combo_box_new_with_entry)
+STUB(gtk_combo_box_set_active)
+STUB(gtk_combo_box_text_get_type)
+STUB(gtk_combo_box_text_new)
+STUB(gtk_container_add)
+STUB(gtk_container_forall)
+STUB(gtk_container_get_border_width)
+STUB(gtk_container_get_type)
+STUB(gtk_container_set_border_width)
+STUB(gtk_container_set_resize_mode)
+STUB(gtk_dialog_get_content_area)
+STUB(gtk_dialog_get_type)
+STUB(gtk_dialog_new_with_buttons)
+STUB(gtk_dialog_run)
+STUB(gtk_dialog_set_alternative_button_order)
+STUB(gtk_dialog_set_default_response)
+STUB(gtk_drag_begin)
+STUB(gtk_drag_dest_set)
+STUB(gtk_drag_finish)
+STUB(gtk_drag_get_data)
+STUB(gtk_drag_get_source_widget)
+STUB(gtk_drag_set_icon_pixbuf)
+STUB(gtk_drag_set_icon_widget)
+STUB(gtk_editable_get_type)
+STUB(gtk_editable_select_region)
+STUB(gtk_entry_get_text)
+STUB(gtk_entry_get_type)
+STUB(gtk_entry_new)
+STUB(gtk_entry_set_activates_default)
+STUB(gtk_entry_set_text)
+STUB(gtk_enumerate_printers)
+STUB(gtk_expander_new)
+STUB(gtk_file_chooser_add_filter)
+STUB(gtk_file_chooser_dialog_new)
+STUB(gtk_file_chooser_get_filenames)
+STUB(gtk_file_chooser_get_filter)
+STUB(gtk_file_chooser_get_preview_filename)
+STUB(gtk_file_chooser_get_type)
+STUB(gtk_file_chooser_get_uri)
+STUB(gtk_file_chooser_list_filters)
+STUB(gtk_file_chooser_set_current_folder)
+STUB(gtk_file_chooser_set_current_name)
+STUB(gtk_file_chooser_set_do_overwrite_confirmation)
+STUB(gtk_file_chooser_set_filename)
+STUB(gtk_file_chooser_set_filter)
+STUB(gtk_file_chooser_set_local_only)
+STUB(gtk_file_chooser_set_preview_widget)
+STUB(gtk_file_chooser_set_preview_widget_active)
+STUB(gtk_file_chooser_set_select_multiple)
+STUB(gtk_file_chooser_widget_get_type)
+STUB(gtk_file_filter_add_pattern)
+STUB(gtk_file_filter_new)
+STUB(gtk_file_filter_set_name)
+STUB(gtk_fixed_new)
+STUB(gtk_frame_new)
+STUB(gtk_get_current_event_time)
+STUB(gtk_grab_add)
+STUB(gtk_grab_remove)
+STUB(gtk_handle_box_new)
+STUB(gtk_hbox_new)
+STUB(gtk_icon_info_free)
+STUB(gtk_icon_info_load_icon)
+STUB(gtk_icon_set_add_source)
+STUB(gtk_icon_set_new)
+STUB(gtk_icon_set_render_icon)
+STUB(gtk_icon_set_unref)
+STUB(gtk_icon_size_lookup)
+STUB(gtk_icon_source_free)
+STUB(gtk_icon_source_new)
+STUB(gtk_icon_source_set_icon_name)
+STUB(gtk_icon_theme_add_builtin_icon)
+STUB(gtk_icon_theme_get_default)
+STUB(gtk_icon_theme_get_icon_sizes)
+STUB(gtk_icon_theme_lookup_by_gicon)
+STUB(gtk_icon_theme_lookup_icon)
+STUB(gtk_image_get_icon_name)
+STUB(gtk_image_get_type)
+STUB(gtk_image_new)
+STUB(gtk_image_new_from_icon_name)
+STUB(gtk_image_new_from_stock)
+STUB(gtk_image_set_from_pixbuf)
+STUB(gtk_im_context_filter_keypress)
+STUB(gtk_im_context_focus_in)
+STUB(gtk_im_context_focus_out)
+STUB(gtk_im_context_get_preedit_string)
+STUB(gtk_im_context_reset)
+STUB(gtk_im_context_set_client_window)
+STUB(gtk_im_context_set_cursor_location)
+STUB(gtk_im_context_set_surrounding)
+STUB(gtk_im_context_simple_new)
+STUB(gtk_im_multicontext_get_context_id)
+STUB(gtk_im_multicontext_get_type)
+STUB(gtk_im_multicontext_new)
+STUB(gtk_info_bar_get_type)
+STUB(gtk_info_bar_get_content_area)
+STUB(gtk_info_bar_new)
+STUB(gtk_init)
+STUB(gtk_invisible_new)
+STUB(gtk_key_snooper_install)
+STUB(gtk_key_snooper_remove)
+STUB(gtk_label_get_type)
+STUB(gtk_label_new)
+STUB(gtk_label_set_markup)
+STUB(gtk_link_button_new)
+STUB(gtk_main_do_event)
+STUB(gtk_main_iteration)
+STUB(gtk_menu_attach_to_widget)
+STUB(gtk_menu_bar_new)
+STUB(gtk_menu_get_type)
+STUB(gtk_menu_item_get_type)
+STUB(gtk_menu_item_new)
+STUB(gtk_menu_item_set_submenu)
+STUB(gtk_menu_new)
+STUB(gtk_menu_shell_append)
+STUB(gtk_menu_shell_get_type)
+STUB(gtk_misc_get_alignment)
+STUB(gtk_misc_get_padding)
+STUB(gtk_misc_get_type)
+STUB(gtk_misc_set_alignment)
+STUB(gtk_misc_set_padding)
+STUB(gtk_notebook_new)
+STUB(gtk_page_setup_copy)
+STUB(gtk_page_setup_get_bottom_margin)
+STUB(gtk_page_setup_get_left_margin)
+STUB(gtk_page_setup_get_orientation)
+STUB(gtk_page_setup_get_paper_size)
+STUB(gtk_page_setup_get_right_margin)
+STUB(gtk_page_setup_get_top_margin)
+STUB(gtk_page_setup_new)
+STUB(gtk_page_setup_set_bottom_margin)
+STUB(gtk_page_setup_set_left_margin)
+STUB(gtk_page_setup_set_orientation)
+STUB(gtk_page_setup_set_paper_size)
+STUB(gtk_page_setup_set_paper_size_and_default_margins)
+STUB(gtk_page_setup_set_right_margin)
+STUB(gtk_page_setup_set_top_margin)
+STUB(gtk_page_setup_to_key_file)
+STUB(gtk_paper_size_free)
+STUB(gtk_paper_size_get_display_name)
+STUB(gtk_paper_size_get_height)
+STUB(gtk_paper_size_get_name)
+STUB(gtk_paper_size_get_width)
+STUB(gtk_paper_size_is_custom)
+STUB(gtk_paper_size_is_equal)
+STUB(gtk_paper_size_new)
+STUB(gtk_paper_size_new_custom)
+STUB(gtk_paper_size_set_size)
+STUB(gtk_parse_args)
+STUB(gtk_plug_get_socket_window)
+STUB(gtk_plug_get_type)
+STUB(gtk_printer_accepts_pdf)
+STUB(gtk_printer_get_name)
+STUB(gtk_printer_get_type)
+STUB(gtk_printer_is_default)
+STUB(gtk_print_job_new)
+STUB(gtk_print_job_send)
+STUB(gtk_print_job_set_source_file)
+STUB(gtk_print_run_page_setup_dialog)
+STUB(gtk_print_settings_copy)
+STUB(gtk_print_settings_foreach)
+STUB(gtk_print_settings_get)
+STUB(gtk_print_settings_get_duplex)
+STUB(gtk_print_settings_get_n_copies)
+STUB(gtk_print_settings_get_page_ranges)
+STUB(gtk_print_settings_get_paper_size)
+STUB(gtk_print_settings_get_printer)
+STUB(gtk_print_settings_get_print_pages)
+STUB(gtk_print_settings_get_resolution)
+STUB(gtk_print_settings_get_reverse)
+STUB(gtk_print_settings_get_scale)
+STUB(gtk_print_settings_get_use_color)
+STUB(gtk_print_settings_has_key)
+STUB(gtk_print_settings_new)
+STUB(gtk_print_settings_set)
+STUB(gtk_print_settings_set_duplex)
+STUB(gtk_print_settings_set_n_copies)
+STUB(gtk_print_settings_set_orientation)
+STUB(gtk_print_settings_set_page_ranges)
+STUB(gtk_print_settings_set_paper_size)
+STUB(gtk_print_settings_set_printer)
+STUB(gtk_print_settings_set_print_pages)
+STUB(gtk_print_settings_set_resolution)
+STUB(gtk_print_settings_set_reverse)
+STUB(gtk_print_settings_set_scale)
+STUB(gtk_print_settings_set_use_color)
+STUB(gtk_print_unix_dialog_add_custom_tab)
+STUB(gtk_print_unix_dialog_get_page_setup)
+STUB(gtk_print_unix_dialog_get_selected_printer)
+STUB(gtk_print_unix_dialog_get_settings)
+STUB(gtk_print_unix_dialog_get_type)
+STUB(gtk_print_unix_dialog_new)
+STUB(gtk_print_unix_dialog_set_manual_capabilities)
+STUB(gtk_print_unix_dialog_set_page_setup)
+STUB(gtk_print_unix_dialog_set_settings)
+STUB(gtk_progress_bar_new)
+STUB(gtk_propagate_event)
+STUB(gtk_radio_button_get_type)
+STUB(gtk_radio_button_new_with_label)
+STUB(gtk_radio_button_new_with_mnemonic)
+STUB(gtk_radio_button_new_with_mnemonic_from_widget)
+STUB(gtk_range_get_min_slider_size)
+STUB(gtk_range_get_type)
+STUB(gtk_recent_manager_add_item)
+STUB(gtk_recent_manager_get_default)
+STUB(gtk_scrollbar_get_type)
+STUB(gtk_scrolled_window_new)
+STUB(gtk_selection_data_copy)
+STUB(gtk_selection_data_free)
+STUB(gtk_selection_data_get_data)
+STUB(gtk_selection_data_get_length)
+STUB(gtk_selection_data_get_selection)
+STUB(gtk_selection_data_get_target)
+STUB(gtk_selection_data_get_targets)
+STUB(gtk_selection_data_set)
+STUB(gtk_selection_data_set_pixbuf)
+STUB(gtk_selection_data_set_text)
+STUB(gtk_selection_data_targets_include_text)
+STUB(gtk_separator_get_type)
+STUB(gtk_separator_menu_item_new)
+STUB(gtk_separator_tool_item_new)
+STUB(gtk_settings_get_default)
+STUB(gtk_settings_get_for_screen)
+STUB(gtk_show_uri)
+STUB(gtk_socket_add_id)
+STUB(gtk_socket_get_id)
+STUB(gtk_socket_get_type)
+STUB(gtk_socket_get_plug_window)
+STUB(gtk_socket_new)
+STUB(gtk_spin_button_new)
+STUB(gtk_statusbar_new)
+STUB(gtk_style_lookup_icon_set)
+STUB(gtk_table_attach)
+STUB(gtk_table_get_type)
+STUB(gtk_table_new)
+STUB(gtk_target_list_add)
+STUB(gtk_target_list_add_image_targets)
+STUB(gtk_target_list_add_text_targets)
+STUB(gtk_target_list_new)
+STUB(gtk_target_list_unref)
+STUB(gtk_targets_include_image)
+STUB(gtk_targets_include_text)
+STUB(gtk_target_table_free)
+STUB(gtk_target_table_new_from_list)
+STUB(gtk_text_view_new)
+STUB(gtk_toggle_button_get_active)
+STUB(gtk_toggle_button_get_type)
+STUB(gtk_toggle_button_new)
+STUB(gtk_toggle_button_set_active)
+STUB(gtk_toggle_button_set_inconsistent)
+STUB(gtk_toolbar_new)
+STUB(gtk_tooltip_get_type)
+STUB(gtk_tree_view_append_column)
+STUB(gtk_tree_view_column_new)
+STUB(gtk_tree_view_column_set_title)
+STUB(gtk_tree_view_get_type)
+STUB(gtk_tree_view_new)
+STUB(gtk_vbox_new)
+STUB(gtk_widget_add_events)
+STUB(gtk_widget_class_find_style_property)
+STUB(gtk_widget_destroy)
+STUB(gtk_widget_destroyed)
+STUB(gtk_widget_ensure_style)
+STUB(gtk_widget_event)
+STUB(gtk_widget_get_accessible)
+STUB(gtk_widget_get_allocation)
+STUB(gtk_widget_get_default_direction)
+STUB(gtk_widget_get_display)
+STUB(gtk_widget_get_events)
+STUB(gtk_widget_get_has_window)
+STUB(gtk_widget_get_mapped)
+STUB(gtk_widget_get_parent)
+STUB(gtk_widget_get_parent_window)
+STUB(gtk_widget_get_realized)
+STUB(gtk_widget_get_screen)
+STUB(gtk_widget_get_style)
+STUB(gtk_widget_get_toplevel)
+STUB(gtk_widget_get_type)
+STUB(gtk_widget_get_visible)
+STUB(gtk_widget_get_visual)
+STUB(gtk_widget_get_window)
+STUB(gtk_widget_grab_focus)
+STUB(gtk_widget_has_focus)
+STUB(gtk_widget_has_grab)
+STUB(gtk_widget_hide)
+STUB(gtk_widget_is_focus)
+STUB(gtk_widget_is_toplevel)
+STUB(gtk_widget_map)
+STUB(gtk_widget_modify_bg)
+STUB(gtk_widget_realize)
+STUB(gtk_widget_reparent)
+STUB(gtk_widget_set_allocation)
+STUB(gtk_widget_set_app_paintable)
+STUB(gtk_window_set_auto_startup_notification)
+STUB(gtk_window_set_keep_above)
+STUB(gtk_window_set_opacity)
+STUB(gtk_window_set_screen)
+STUB(gtk_widget_set_can_focus)
+STUB(gtk_widget_set_direction)
+STUB(gtk_widget_set_double_buffered)
+STUB(gtk_widget_set_has_window)
+STUB(gtk_widget_set_mapped)
+STUB(gtk_widget_set_name)
+STUB(gtk_widget_set_parent)
+STUB(gtk_widget_set_parent_window)
+STUB(gtk_widget_set_realized)
+STUB(gtk_widget_set_redraw_on_allocate)
+STUB(gtk_widget_set_sensitive)
+STUB(gtk_widget_set_window)
+STUB(gtk_widget_show)
+STUB(gtk_widget_show_all)
+STUB(gtk_widget_size_allocate)
+STUB(gtk_widget_style_get)
+STUB(gtk_widget_unparent)
+STUB(gtk_widget_unrealize)
+STUB(gtk_window_deiconify)
+STUB(gtk_window_fullscreen)
+STUB(gtk_window_get_group)
+STUB(gtk_window_get_modal)
+STUB(gtk_window_get_transient_for)
+STUB(gtk_window_get_type)
+STUB(gtk_window_get_type_hint)
+STUB(gtk_window_get_window_type)
+STUB(gtk_window_group_add_window)
+STUB(gtk_window_group_get_current_grab)
+STUB(gtk_window_group_new)
+STUB(gtk_window_iconify)
+STUB(gtk_window_is_active)
+STUB(gtk_window_maximize)
+STUB(gtk_window_move)
+STUB(gtk_window_new)
+STUB(gtk_window_present_with_time)
+STUB(gtk_window_resize)
+STUB(gtk_window_set_accept_focus)
+STUB(gtk_window_set_decorated)
+STUB(gtk_window_set_deletable)
+STUB(gtk_window_set_destroy_with_parent)
+STUB(gtk_window_set_focus_on_map)
+STUB(gtk_window_set_geometry_hints)
+STUB(gtk_window_set_icon_name)
+STUB(gtk_window_set_modal)
+STUB(gtk_window_set_skip_taskbar_hint)
+STUB(gtk_window_set_startup_id)
+STUB(gtk_window_set_title)
+STUB(gtk_window_set_transient_for)
+STUB(gtk_window_set_type_hint)
+STUB(gtk_window_set_wmclass)
+STUB(gtk_window_unfullscreen)
+STUB(gtk_window_unmaximize)
+#endif
+
+#ifdef GTK3_SYMBOLS
+STUB(gtk_css_provider_load_from_data)
+STUB(gtk_css_provider_new)
+STUB(gdk_device_get_source)
+STUB(gdk_device_manager_get_client_pointer)
+STUB(gdk_disable_multidevice)
+STUB(gdk_device_manager_list_devices)
+STUB(gdk_display_get_device_manager)
+STUB(gdk_display_manager_open_display)
+STUB(gdk_error_trap_pop_ignored)
+STUB(gdk_event_get_source_device)
+STUB(gdk_screen_get_monitor_workarea)
+STUB(gdk_window_get_type)
+STUB(gdk_window_set_opaque_region)
+STUB(gdk_x11_window_get_xid)
+STUB(gdk_x11_display_get_type)
+STUB(gdk_wayland_display_get_type)
+STUB(gdk_wayland_display_get_wl_compositor)
+STUB(gdk_wayland_display_get_wl_display)
+STUB(gdk_wayland_window_get_wl_surface)
+STUB(gtk_box_new)
+STUB(gtk_cairo_should_draw_window)
+STUB(gtk_cairo_transform_to_window)
+STUB(gtk_combo_box_text_append)
+STUB(gtk_drag_set_icon_surface)
+STUB(gtk_get_major_version)
+STUB(gtk_get_micro_version)
+STUB(gtk_get_minor_version)
+STUB(gtk_icon_info_load_symbolic_for_context)
+STUB(gtk_menu_button_new)
+STUB(gtk_offscreen_window_new)
+STUB(gtk_paned_new)
+STUB(gtk_radio_menu_item_new)
+STUB(gtk_render_activity)
+STUB(gtk_render_arrow)
+STUB(gtk_render_background)
+STUB(gtk_render_check)
+STUB(gtk_render_expander)
+STUB(gtk_render_extension)
+STUB(gtk_render_focus)
+STUB(gtk_render_frame)
+STUB(gtk_render_frame_gap)
+STUB(gtk_render_handle)
+STUB(gtk_render_icon)
+STUB(gtk_render_line)
+STUB(gtk_render_option)
+STUB(gtk_render_slider)
+STUB(gtk_scale_new)
+STUB(gtk_scrollbar_new)
+STUB(gtk_style_context_add_class)
+STUB(gtk_style_context_add_provider)
+STUB(gtk_style_context_add_region)
+STUB(gtk_style_context_get)
+STUB(gtk_style_context_get_background_color)
+STUB(gtk_style_context_get_border)
+STUB(gtk_style_context_get_border_color)
+STUB(gtk_style_context_get_color)
+STUB(gtk_style_context_get_direction)
+STUB(gtk_style_context_get_margin)
+STUB(gtk_style_context_get_padding)
+STUB(gtk_style_context_get_path)
+STUB(gtk_style_context_get_property)
+STUB(gtk_style_context_get_state)
+STUB(gtk_style_context_get_style)
+STUB(gtk_style_context_has_class)
+STUB(gtk_style_context_invalidate)
+STUB(gtk_style_context_list_classes)
+STUB(gtk_style_context_new)
+STUB(gtk_style_context_remove_class)
+STUB(gtk_style_context_remove_region)
+STUB(gtk_style_context_restore)
+STUB(gtk_style_context_save)
+STUB(gtk_style_context_set_direction)
+STUB(gtk_style_context_set_path)
+STUB(gtk_style_context_set_parent)
+STUB(gtk_style_context_set_state)
+STUB(gtk_style_properties_lookup_property)
+STUB(gtk_style_provider_get_type)
+STUB(gtk_tree_view_column_get_button)
+STUB(gtk_widget_get_preferred_size)
+STUB(gtk_widget_get_preferred_width)
+STUB(gtk_widget_get_preferred_height)
+STUB(gtk_widget_get_state_flags)
+STUB(gtk_widget_get_style_context)
+STUB(gtk_widget_path_append_type)
+STUB(gtk_widget_path_copy)
+STUB(gtk_widget_path_free)
+STUB(gtk_widget_path_iter_add_class)
+STUB(gtk_widget_path_get_object_type)
+STUB(gtk_widget_path_length)
+STUB(gtk_widget_path_new)
+STUB(gtk_widget_path_unref)
+STUB(gtk_widget_set_valign)
+STUB(gtk_widget_set_visual)
+STUB(gtk_window_set_titlebar)
+STUB(gtk_app_chooser_dialog_new_for_content_type)
+STUB(gtk_app_chooser_get_type)
+STUB(gtk_app_chooser_get_app_info)
+STUB(gtk_app_chooser_dialog_get_type)
+STUB(gtk_app_chooser_dialog_set_heading)
+STUB(gtk_color_chooser_dialog_new)
+STUB(gtk_color_chooser_dialog_get_type)
+STUB(gtk_color_chooser_get_type)
+STUB(gtk_color_chooser_set_rgba)
+STUB(gtk_color_chooser_get_rgba)
+STUB(gtk_color_chooser_set_use_alpha)
+#endif
+
+#ifdef GTK2_SYMBOLS
+STUB(gdk_drawable_get_screen)
+STUB(gdk_rgb_get_colormap)
+STUB(gdk_rgb_get_visual)
+STUB(gdk_window_lookup)
+STUB(gdk_window_set_back_pixmap)
+STUB(gdk_x11_colormap_foreign_new)
+STUB(gdk_x11_colormap_get_xcolormap)
+STUB(gdk_x11_drawable_get_xdisplay)
+STUB(gdk_x11_drawable_get_xid)
+STUB(gdk_x11_window_get_drawable_impl)
+STUB(gdkx_visual_get)
+STUB(gtk_object_get_type)
+#endif
+
+#ifndef GTK3_SYMBOLS
+// Only define the following workaround when using GTK3, which we detect
+// by checking if GTK3 stubs are not provided.
+# include <X11/Xlib.h>
+// Bug 1271100
+// We need to trick system Cairo into not using the XShm extension due to
+// a race condition in it that results in frequent BadAccess errors. Cairo
+// relies upon XShmQueryExtension to initially detect if XShm is available.
+// So we define our own stub that always indicates XShm not being present.
+// mozgtk loads before libXext/libcairo and so this stub will take priority.
+// Our tree usage goes through xcb and remains unaffected by this.
+MOZ_EXPORT Bool XShmQueryExtension(Display* aDisplay) { return False; }
+#endif
diff --git a/widget/gtk/mozgtk/stub/moz.build b/widget/gtk/mozgtk/stub/moz.build
new file mode 100644
index 0000000000..8af0cc1cdf
--- /dev/null
+++ b/widget/gtk/mozgtk/stub/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 += [
+ "../mozgtk.c",
+]
+
+for var in ("COMMON_SYMBOLS", "GTK2_SYMBOLS", "GTK3_SYMBOLS"):
+ DEFINES[var] = True
+
+SharedLibrary("mozgtk_stub")
+
+SONAME = "mozgtk"
diff --git a/widget/gtk/mozwayland/moz.build b/widget/gtk/mozwayland/moz.build
new file mode 100644
index 0000000000..94eca8a785
--- /dev/null
+++ b/widget/gtk/mozwayland/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 += [
+ "mozwayland.c",
+]
+EXPORTS.mozilla.widget += [
+ "mozwayland.h",
+]
+
+SharedLibrary("mozwayland")
+
+CFLAGS += CONFIG["TK_CFLAGS"]
diff --git a/widget/gtk/mozwayland/mozwayland.c b/widget/gtk/mozwayland/mozwayland.c
new file mode 100644
index 0000000000..8a79d87865
--- /dev/null
+++ b/widget/gtk/mozwayland/mozwayland.c
@@ -0,0 +1,201 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include <stdlib.h>
+#include "mozilla/Types.h"
+#include <gtk/gtk.h>
+#include <gtk/gtkx.h>
+#include <gdk/gdkwayland.h>
+
+union wl_argument;
+
+/* Those strucures are just placeholders and will be replaced by
+ * real symbols from libwayland during run-time linking. We need to make
+ * them explicitly visible.
+ */
+#pragma GCC visibility push(default)
+const struct wl_interface wl_buffer_interface;
+const struct wl_interface wl_callback_interface;
+const struct wl_interface wl_data_device_interface;
+const struct wl_interface wl_data_device_manager_interface;
+const struct wl_interface wl_keyboard_interface;
+const struct wl_interface wl_region_interface;
+const struct wl_interface wl_registry_interface;
+const struct wl_interface wl_shm_interface;
+const struct wl_interface wl_shm_pool_interface;
+const struct wl_interface wl_seat_interface;
+const struct wl_interface wl_surface_interface;
+const struct wl_interface wl_subsurface_interface;
+const struct wl_interface wl_compositor_interface;
+const struct wl_interface wl_subcompositor_interface;
+const struct wl_interface wl_output_interface;
+#pragma GCC visibility pop
+
+MOZ_EXPORT void wl_event_queue_destroy(struct wl_event_queue* queue) {}
+
+MOZ_EXPORT void wl_proxy_marshal(struct wl_proxy* p, uint32_t opcode, ...) {}
+
+MOZ_EXPORT void wl_proxy_marshal_array(struct wl_proxy* p, uint32_t opcode,
+ union wl_argument* args) {}
+
+MOZ_EXPORT struct wl_proxy* wl_proxy_create(
+ struct wl_proxy* factory, const struct wl_interface* interface) {
+ return NULL;
+}
+
+MOZ_EXPORT void* wl_proxy_create_wrapper(void* proxy) { return NULL; }
+
+MOZ_EXPORT void wl_proxy_wrapper_destroy(void* proxy_wrapper) {}
+
+MOZ_EXPORT struct wl_proxy* wl_proxy_marshal_constructor(
+ struct wl_proxy* proxy, uint32_t opcode,
+ const struct wl_interface* interface, ...) {
+ return NULL;
+}
+
+MOZ_EXPORT struct wl_proxy* wl_proxy_marshal_constructor_versioned(
+ struct wl_proxy* proxy, uint32_t opcode,
+ const struct wl_interface* interface, uint32_t version, ...) {
+ return NULL;
+}
+
+MOZ_EXPORT struct wl_proxy* wl_proxy_marshal_array_constructor(
+ struct wl_proxy* proxy, uint32_t opcode, union wl_argument* args,
+ const struct wl_interface* interface) {
+ return NULL;
+}
+
+MOZ_EXPORT struct wl_proxy* wl_proxy_marshal_array_constructor_versioned(
+ struct wl_proxy* proxy, uint32_t opcode, union wl_argument* args,
+ const struct wl_interface* interface, uint32_t version) {
+ return NULL;
+}
+
+MOZ_EXPORT void wl_proxy_destroy(struct wl_proxy* proxy) {}
+
+MOZ_EXPORT int wl_proxy_add_listener(struct wl_proxy* proxy,
+ void (**implementation)(void),
+ void* data) {
+ return -1;
+}
+
+MOZ_EXPORT const void* wl_proxy_get_listener(struct wl_proxy* proxy) {
+ return NULL;
+}
+
+typedef int (*wl_dispatcher_func_t)(const void*, void*, uint32_t,
+ const struct wl_message*,
+ union wl_argument*);
+
+MOZ_EXPORT int wl_proxy_add_dispatcher(struct wl_proxy* proxy,
+ wl_dispatcher_func_t dispatcher_func,
+ const void* dispatcher_data,
+ void* data) {
+ return -1;
+}
+
+MOZ_EXPORT void wl_proxy_set_user_data(struct wl_proxy* proxy,
+ void* user_data) {}
+
+MOZ_EXPORT void* wl_proxy_get_user_data(struct wl_proxy* proxy) { return NULL; }
+
+MOZ_EXPORT uint32_t wl_proxy_get_version(struct wl_proxy* proxy) { return -1; }
+
+MOZ_EXPORT uint32_t wl_proxy_get_id(struct wl_proxy* proxy) { return -1; }
+
+MOZ_EXPORT const char* wl_proxy_get_class(struct wl_proxy* proxy) {
+ return NULL;
+}
+
+MOZ_EXPORT void wl_proxy_set_queue(struct wl_proxy* proxy,
+ struct wl_event_queue* queue) {}
+
+MOZ_EXPORT struct wl_display* wl_display_connect(const char* name) {
+ return NULL;
+}
+
+MOZ_EXPORT struct wl_display* wl_display_connect_to_fd(int fd) { return NULL; }
+
+MOZ_EXPORT void wl_display_disconnect(struct wl_display* display) {}
+
+MOZ_EXPORT int wl_display_get_fd(struct wl_display* display) { return -1; }
+
+MOZ_EXPORT int wl_display_dispatch(struct wl_display* display) { return -1; }
+
+MOZ_EXPORT int wl_display_dispatch_queue(struct wl_display* display,
+ struct wl_event_queue* queue) {
+ return -1;
+}
+
+MOZ_EXPORT int wl_display_dispatch_queue_pending(struct wl_display* display,
+ struct wl_event_queue* queue) {
+ return -1;
+}
+
+MOZ_EXPORT int wl_display_dispatch_pending(struct wl_display* display) {
+ return -1;
+}
+
+MOZ_EXPORT int wl_display_get_error(struct wl_display* display) { return -1; }
+
+MOZ_EXPORT uint32_t wl_display_get_protocol_error(
+ struct wl_display* display, const struct wl_interface** interface,
+ uint32_t* id) {
+ return -1;
+}
+
+MOZ_EXPORT int wl_display_flush(struct wl_display* display) { return -1; }
+
+MOZ_EXPORT int wl_display_roundtrip_queue(struct wl_display* display,
+ struct wl_event_queue* queue) {
+ return -1;
+}
+
+MOZ_EXPORT int wl_display_roundtrip(struct wl_display* display) { return -1; }
+
+MOZ_EXPORT struct wl_event_queue* wl_display_create_queue(
+ struct wl_display* display) {
+ return NULL;
+}
+
+MOZ_EXPORT int wl_display_prepare_read_queue(struct wl_display* display,
+ struct wl_event_queue* queue) {
+ return -1;
+}
+
+MOZ_EXPORT int wl_display_prepare_read(struct wl_display* display) {
+ return -1;
+}
+
+MOZ_EXPORT void wl_display_cancel_read(struct wl_display* display) {}
+
+MOZ_EXPORT int wl_display_read_events(struct wl_display* display) { return -1; }
+
+MOZ_EXPORT void wl_log_set_handler_client(wl_log_func_t handler) {}
+
+MOZ_EXPORT struct wl_egl_window* wl_egl_window_create(
+ struct wl_surface* surface, int width, int height) {
+ return NULL;
+}
+
+MOZ_EXPORT void wl_egl_window_destroy(struct wl_egl_window* egl_window) {}
+
+MOZ_EXPORT void wl_egl_window_resize(struct wl_egl_window* egl_window,
+ int width, int height, int dx, int dy) {}
+
+MOZ_EXPORT void wl_list_init(struct wl_list* list) {}
+
+MOZ_EXPORT void wl_list_insert(struct wl_list* list, struct wl_list* elm) {}
+
+MOZ_EXPORT void wl_list_remove(struct wl_list* elm) {}
+
+MOZ_EXPORT int wl_list_length(const struct wl_list* list) { return -1; }
+
+MOZ_EXPORT int wl_list_empty(const struct wl_list* list) { return -1; }
+
+MOZ_EXPORT void wl_list_insert_list(struct wl_list* list,
+ struct wl_list* other) {}
diff --git a/widget/gtk/mozwayland/mozwayland.h b/widget/gtk/mozwayland/mozwayland.h
new file mode 100644
index 0000000000..22f80d6315
--- /dev/null
+++ b/widget/gtk/mozwayland/mozwayland.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/* Wayland compatibility header, it makes Firefox build with
+ wayland-1.2 and Gtk+ 3.10.
+*/
+
+#ifndef __MozWayland_h_
+#define __MozWayland_h_
+
+#include "mozilla/Types.h"
+#include <gtk/gtk.h>
+#include <gdk/gdkwayland.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+MOZ_EXPORT struct wl_display* wl_display_connect(const char* name);
+MOZ_EXPORT int wl_display_roundtrip_queue(struct wl_display* display,
+ struct wl_event_queue* queue);
+MOZ_EXPORT uint32_t wl_proxy_get_version(struct wl_proxy* proxy);
+MOZ_EXPORT void wl_proxy_marshal(struct wl_proxy* p, uint32_t opcode, ...);
+MOZ_EXPORT struct wl_proxy* wl_proxy_marshal_constructor(
+ struct wl_proxy* proxy, uint32_t opcode,
+ const struct wl_interface* interface, ...);
+MOZ_EXPORT struct wl_proxy* wl_proxy_marshal_constructor_versioned(
+ struct wl_proxy* proxy, uint32_t opcode,
+ const struct wl_interface* interface, uint32_t version, ...);
+MOZ_EXPORT void wl_proxy_destroy(struct wl_proxy* proxy);
+MOZ_EXPORT void* wl_proxy_create_wrapper(void* proxy);
+MOZ_EXPORT void wl_proxy_wrapper_destroy(void* proxy_wrapper);
+
+/* We need implement some missing functions from wayland-client-protocol.h
+ */
+#ifndef WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM
+enum wl_data_device_manager_dnd_action {
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE = 0,
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY = 1,
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE = 2,
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK = 4
+};
+#endif
+
+#ifndef WL_DATA_OFFER_SET_ACTIONS
+# define WL_DATA_OFFER_SET_ACTIONS 4
+
+struct moz_wl_data_offer_listener {
+ void (*offer)(void* data, struct wl_data_offer* wl_data_offer,
+ const char* mime_type);
+ void (*source_actions)(void* data, struct wl_data_offer* wl_data_offer,
+ uint32_t source_actions);
+ void (*action)(void* data, struct wl_data_offer* wl_data_offer,
+ uint32_t dnd_action);
+};
+
+static inline void wl_data_offer_set_actions(
+ struct wl_data_offer* wl_data_offer, uint32_t dnd_actions,
+ uint32_t preferred_action) {
+ wl_proxy_marshal((struct wl_proxy*)wl_data_offer, WL_DATA_OFFER_SET_ACTIONS,
+ dnd_actions, preferred_action);
+}
+#else
+typedef struct wl_data_offer_listener moz_wl_data_offer_listener;
+#endif
+
+#ifndef WL_SUBCOMPOSITOR_GET_SUBSURFACE
+# define WL_SUBCOMPOSITOR_GET_SUBSURFACE 1
+struct wl_subcompositor;
+
+// Emulate what mozilla header wrapper does - make the
+// wl_subcompositor_interface always visible.
+# pragma GCC visibility push(default)
+extern const struct wl_interface wl_subsurface_interface;
+extern const struct wl_interface wl_subcompositor_interface;
+# pragma GCC visibility pop
+
+# define WL_SUBSURFACE_DESTROY 0
+# define WL_SUBSURFACE_SET_POSITION 1
+# define WL_SUBSURFACE_PLACE_ABOVE 2
+# define WL_SUBSURFACE_PLACE_BELOW 3
+# define WL_SUBSURFACE_SET_SYNC 4
+# define WL_SUBSURFACE_SET_DESYNC 5
+
+static inline struct wl_subsurface* wl_subcompositor_get_subsurface(
+ struct wl_subcompositor* wl_subcompositor, struct wl_surface* surface,
+ struct wl_surface* parent) {
+ struct wl_proxy* id;
+
+ id = wl_proxy_marshal_constructor(
+ (struct wl_proxy*)wl_subcompositor, WL_SUBCOMPOSITOR_GET_SUBSURFACE,
+ &wl_subsurface_interface, NULL, surface, parent);
+
+ return (struct wl_subsurface*)id;
+}
+
+static inline void wl_subsurface_set_position(
+ struct wl_subsurface* wl_subsurface, int32_t x, int32_t y) {
+ wl_proxy_marshal((struct wl_proxy*)wl_subsurface, WL_SUBSURFACE_SET_POSITION,
+ x, y);
+}
+
+static inline void wl_subsurface_set_desync(
+ struct wl_subsurface* wl_subsurface) {
+ wl_proxy_marshal((struct wl_proxy*)wl_subsurface, WL_SUBSURFACE_SET_DESYNC);
+}
+
+static inline void wl_subsurface_destroy(struct wl_subsurface* wl_subsurface) {
+ wl_proxy_marshal((struct wl_proxy*)wl_subsurface, WL_SUBSURFACE_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)wl_subsurface);
+}
+#endif
+
+#ifndef WL_SURFACE_DAMAGE_BUFFER
+# define WL_SURFACE_DAMAGE_BUFFER 9
+
+static inline void wl_surface_damage_buffer(struct wl_surface* wl_surface,
+ int32_t x, int32_t y, int32_t width,
+ int32_t height) {
+ wl_proxy_marshal((struct wl_proxy*)wl_surface, WL_SURFACE_DAMAGE_BUFFER, x, y,
+ width, height);
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MozWayland_h_ */
diff --git a/widget/gtk/nsAppShell.cpp b/widget/gtk/nsAppShell.cpp
new file mode 100644
index 0000000000..24bdd50833
--- /dev/null
+++ b/widget/gtk/nsAppShell.cpp
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <gdk/gdk.h>
+#include "nsAppShell.h"
+#include "nsWindow.h"
+#include "mozilla/Logging.h"
+#include "prenv.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/Hal.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WidgetUtils.h"
+#include "GeckoProfiler.h"
+#include "nsIPowerManagerService.h"
+#ifdef MOZ_ENABLE_DBUS
+# include "WakeLockListener.h"
+#endif
+#include "gfxPlatform.h"
+#include "ScreenHelperGTK.h"
+#include "HeadlessScreenHelper.h"
+#include "mozilla/widget/ScreenManager.h"
+#ifdef MOZ_WAYLAND
+# include "nsWaylandDisplay.h"
+#endif
+
+using mozilla::LazyLogModule;
+using mozilla::Unused;
+using mozilla::widget::HeadlessScreenHelper;
+using mozilla::widget::ScreenHelperGTK;
+using mozilla::widget::ScreenManager;
+
+#define NOTIFY_TOKEN 0xFA
+
+LazyLogModule gWidgetLog("Widget");
+LazyLogModule gWidgetFocusLog("WidgetFocus");
+LazyLogModule gWidgetDragLog("WidgetDrag");
+LazyLogModule gWidgetDrawLog("WidgetDraw");
+LazyLogModule gWidgetWaylandLog("WidgetWayland");
+LazyLogModule gDmabufLog("Dmabuf");
+LazyLogModule gClipboardLog("WidgetClipboard");
+
+static GPollFunc sPollFunc;
+
+// Wrapper function to disable hang monitoring while waiting in poll().
+static gint PollWrapper(GPollFD* ufds, guint nfsd, gint timeout_) {
+ mozilla::BackgroundHangMonitor().NotifyWait();
+ gint result;
+ {
+ AUTO_PROFILER_LABEL("PollWrapper", IDLE);
+ AUTO_PROFILER_THREAD_SLEEP;
+ result = (*sPollFunc)(ufds, nfsd, timeout_);
+ }
+ mozilla::BackgroundHangMonitor().NotifyActivity();
+ return result;
+}
+
+// Emit resume-events on GdkFrameClock if flush-events has not been
+// balanced by resume-events at dispose.
+// For https://bugzilla.gnome.org/show_bug.cgi?id=742636
+static decltype(GObjectClass::constructed) sRealGdkFrameClockConstructed;
+static decltype(GObjectClass::dispose) sRealGdkFrameClockDispose;
+static GQuark sPendingResumeQuark;
+
+static void OnFlushEvents(GObject* clock, gpointer) {
+ g_object_set_qdata(clock, sPendingResumeQuark, GUINT_TO_POINTER(1));
+}
+
+static void OnResumeEvents(GObject* clock, gpointer) {
+ g_object_set_qdata(clock, sPendingResumeQuark, nullptr);
+}
+
+static void WrapGdkFrameClockConstructed(GObject* object) {
+ sRealGdkFrameClockConstructed(object);
+
+ g_signal_connect(object, "flush-events", G_CALLBACK(OnFlushEvents), nullptr);
+ g_signal_connect(object, "resume-events", G_CALLBACK(OnResumeEvents),
+ nullptr);
+}
+
+static void WrapGdkFrameClockDispose(GObject* object) {
+ if (g_object_get_qdata(object, sPendingResumeQuark)) {
+ g_signal_emit_by_name(object, "resume-events");
+ }
+
+ sRealGdkFrameClockDispose(object);
+}
+
+/*static*/
+gboolean nsAppShell::EventProcessorCallback(GIOChannel* source,
+ GIOCondition condition,
+ gpointer data) {
+ nsAppShell* self = static_cast<nsAppShell*>(data);
+
+ unsigned char c;
+ Unused << read(self->mPipeFDs[0], &c, 1);
+ NS_ASSERTION(c == (unsigned char)NOTIFY_TOKEN, "wrong token");
+
+ self->NativeEventCallback();
+ return TRUE;
+}
+
+nsAppShell::~nsAppShell() {
+ mozilla::hal::Shutdown();
+
+ if (mTag) g_source_remove(mTag);
+ if (mPipeFDs[0]) close(mPipeFDs[0]);
+ if (mPipeFDs[1]) close(mPipeFDs[1]);
+}
+
+nsresult nsAppShell::Init() {
+ mozilla::hal::Init();
+
+#ifdef MOZ_ENABLE_DBUS
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIPowerManagerService> powerManagerService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+
+ if (powerManagerService) {
+ powerManagerService->AddWakeLockListener(
+ WakeLockListener::GetSingleton());
+ } else {
+ NS_WARNING(
+ "Failed to retrieve PowerManagerService, wakelocks will be broken!");
+ }
+ }
+#endif
+
+ if (!sPollFunc) {
+ sPollFunc = g_main_context_get_poll_func(nullptr);
+ g_main_context_set_poll_func(nullptr, &PollWrapper);
+ }
+
+ if (XRE_IsParentProcess()) {
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ if (gfxPlatform::IsHeadless()) {
+ screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
+ } else {
+ screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperGTK>());
+ }
+
+ if (gtk_check_version(3, 16, 3) == nullptr) {
+ // Before 3.16.3, GDK cannot override classname by --class command line
+ // option when program uses gdk_set_program_class().
+ //
+ // See https://bugzilla.gnome.org/show_bug.cgi?id=747634
+ //
+ // Only bother doing this for the parent process, since it's the one
+ // creating top-level windows. (At this point, a child process hasn't
+ // received the list of registered chrome packages, so the
+ // GetBrandShortName call would fail anyway.)
+ nsAutoString brandName;
+ mozilla::widget::WidgetUtils::GetBrandShortName(brandName);
+ if (!brandName.IsEmpty()) {
+ gdk_set_program_class(NS_ConvertUTF16toUTF8(brandName).get());
+ }
+ }
+ }
+
+ if (!sPendingResumeQuark &&
+ gtk_check_version(3, 14, 7) != nullptr) { // GTK 3.0 to GTK 3.14.7.
+ // GTK 3.8 - 3.14 registered this type when creating the frame clock
+ // for the root window of the display when the display was opened.
+ GType gdkFrameClockIdleType = g_type_from_name("GdkFrameClockIdle");
+ if (gdkFrameClockIdleType) { // not in versions prior to 3.8
+ sPendingResumeQuark = g_quark_from_string("moz-resume-is-pending");
+ auto gdk_frame_clock_idle_class =
+ G_OBJECT_CLASS(g_type_class_peek_static(gdkFrameClockIdleType));
+ auto constructed = &gdk_frame_clock_idle_class->constructed;
+ sRealGdkFrameClockConstructed = *constructed;
+ *constructed = WrapGdkFrameClockConstructed;
+ auto dispose = &gdk_frame_clock_idle_class->dispose;
+ sRealGdkFrameClockDispose = *dispose;
+ *dispose = WrapGdkFrameClockDispose;
+ }
+ }
+
+ // Workaround for bug 1209659 which is fixed by Gtk3.20
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ unsetenv("GTK_CSD");
+ }
+
+ if (PR_GetEnv("MOZ_DEBUG_PAINTS")) {
+ gdk_window_set_debug_updates(TRUE);
+ }
+
+ // Whitelist of only common, stable formats - see bugs 1197059 and 1203078
+ GSList* pixbufFormats = gdk_pixbuf_get_formats();
+ for (GSList* iter = pixbufFormats; iter; iter = iter->next) {
+ GdkPixbufFormat* format = static_cast<GdkPixbufFormat*>(iter->data);
+ gchar* name = gdk_pixbuf_format_get_name(format);
+ if (strcmp(name, "jpeg") && strcmp(name, "png") && strcmp(name, "gif") &&
+ strcmp(name, "bmp") && strcmp(name, "ico") && strcmp(name, "xpm") &&
+ strcmp(name, "svg")) {
+ gdk_pixbuf_format_set_disabled(format, TRUE);
+ }
+ g_free(name);
+ }
+ g_slist_free(pixbufFormats);
+
+ int err = pipe(mPipeFDs);
+ if (err) return NS_ERROR_OUT_OF_MEMORY;
+
+ GIOChannel* ioc;
+ GSource* source;
+
+ // make the pipe nonblocking
+
+ int flags = fcntl(mPipeFDs[0], F_GETFL, 0);
+ if (flags == -1) goto failed;
+ err = fcntl(mPipeFDs[0], F_SETFL, flags | O_NONBLOCK);
+ if (err == -1) goto failed;
+ flags = fcntl(mPipeFDs[1], F_GETFL, 0);
+ if (flags == -1) goto failed;
+ err = fcntl(mPipeFDs[1], F_SETFL, flags | O_NONBLOCK);
+ if (err == -1) goto failed;
+
+ ioc = g_io_channel_unix_new(mPipeFDs[0]);
+ source = g_io_create_watch(ioc, G_IO_IN);
+ g_io_channel_unref(ioc);
+ g_source_set_callback(source, (GSourceFunc)EventProcessorCallback, this,
+ nullptr);
+ g_source_set_can_recurse(source, TRUE);
+ mTag = g_source_attach(source, nullptr);
+ g_source_unref(source);
+
+ return nsBaseAppShell::Init();
+failed:
+ close(mPipeFDs[0]);
+ close(mPipeFDs[1]);
+ mPipeFDs[0] = mPipeFDs[1] = 0;
+ return NS_ERROR_FAILURE;
+}
+
+void nsAppShell::ScheduleNativeEventCallback() {
+ unsigned char buf[] = {NOTIFY_TOKEN};
+ Unused << write(mPipeFDs[1], buf, 1);
+}
+
+bool nsAppShell::ProcessNextNativeEvent(bool mayWait) {
+ bool ret = g_main_context_iteration(nullptr, mayWait);
+#ifdef MOZ_WAYLAND
+ mozilla::widget::WaylandDispatchDisplays();
+#endif
+ return ret;
+}
diff --git a/widget/gtk/nsAppShell.h b/widget/gtk/nsAppShell.h
new file mode 100644
index 0000000000..06543ee955
--- /dev/null
+++ b/widget/gtk/nsAppShell.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsAppShell_h__
+#define nsAppShell_h__
+
+#include <glib.h>
+#include "nsBaseAppShell.h"
+#include "nsCOMPtr.h"
+
+class nsAppShell : public nsBaseAppShell {
+ public:
+ nsAppShell() : mTag(0) { mPipeFDs[0] = mPipeFDs[1] = 0; }
+
+ // nsBaseAppShell overrides:
+ nsresult Init();
+ virtual void ScheduleNativeEventCallback() override;
+ virtual bool ProcessNextNativeEvent(bool mayWait) override;
+
+ private:
+ virtual ~nsAppShell();
+
+ static gboolean EventProcessorCallback(GIOChannel* source,
+ GIOCondition condition, gpointer data);
+
+ int mPipeFDs[2];
+ unsigned mTag;
+};
+
+#endif /* nsAppShell_h__ */
diff --git a/widget/gtk/nsApplicationChooser.cpp b/widget/gtk/nsApplicationChooser.cpp
new file mode 100644
index 0000000000..2ebb62fd1d
--- /dev/null
+++ b/widget/gtk/nsApplicationChooser.cpp
@@ -0,0 +1,131 @@
+/* -*- 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/Types.h"
+
+#include <gtk/gtk.h>
+
+#include "nsApplicationChooser.h"
+#include "WidgetUtils.h"
+#include "nsIMIMEInfo.h"
+#include "nsIWidget.h"
+#include "nsCExternalHandlerService.h"
+#include "nsComponentManagerUtils.h"
+#include "nsGtkUtils.h"
+#include "nsPIDOMWindow.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsApplicationChooser, nsIApplicationChooser)
+
+nsApplicationChooser::nsApplicationChooser() = default;
+
+nsApplicationChooser::~nsApplicationChooser() = default;
+
+NS_IMETHODIMP
+nsApplicationChooser::Init(mozIDOMWindowProxy* aParent,
+ const nsACString& aTitle) {
+ NS_ENSURE_TRUE(aParent, NS_ERROR_FAILURE);
+ auto* parent = nsPIDOMWindowOuter::From(aParent);
+ mParentWidget = widget::WidgetUtils::DOMWindowToWidget(parent);
+ mWindowTitle.Assign(aTitle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationChooser::Open(const nsACString& aContentType,
+ nsIApplicationChooserFinishedCallback* aCallback) {
+ MOZ_ASSERT(aCallback);
+ if (mCallback) {
+ NS_WARNING("Chooser is already in progress.");
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+ mCallback = aCallback;
+ NS_ENSURE_TRUE(mParentWidget, NS_ERROR_FAILURE);
+ GtkWindow* parent_widget =
+ GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
+
+ GtkWidget* chooser = gtk_app_chooser_dialog_new_for_content_type(
+ parent_widget,
+ (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
+ PromiseFlatCString(aContentType).get());
+ gtk_app_chooser_dialog_set_heading(GTK_APP_CHOOSER_DIALOG(chooser),
+ mWindowTitle.BeginReading());
+ NS_ADDREF_THIS();
+ g_signal_connect(chooser, "response", G_CALLBACK(OnResponse), this);
+ g_signal_connect(chooser, "destroy", G_CALLBACK(OnDestroy), this);
+ gtk_widget_show(chooser);
+ return NS_OK;
+}
+
+/* static */
+void nsApplicationChooser::OnResponse(GtkWidget* chooser, gint response_id,
+ gpointer user_data) {
+ static_cast<nsApplicationChooser*>(user_data)->Done(chooser, response_id);
+}
+
+/* static */
+void nsApplicationChooser::OnDestroy(GtkWidget* chooser, gpointer user_data) {
+ static_cast<nsApplicationChooser*>(user_data)->Done(chooser,
+ GTK_RESPONSE_CANCEL);
+}
+
+void nsApplicationChooser::Done(GtkWidget* chooser, gint response) {
+ nsCOMPtr<nsILocalHandlerApp> localHandler;
+ nsresult rv;
+ switch (response) {
+ case GTK_RESPONSE_OK:
+ case GTK_RESPONSE_ACCEPT: {
+ localHandler = do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Out of memory.");
+ break;
+ }
+ GAppInfo* app_info =
+ gtk_app_chooser_get_app_info(GTK_APP_CHOOSER(chooser));
+
+ nsCOMPtr<nsIFile> localExecutable;
+ gchar* fileWithFullPath =
+ g_find_program_in_path(g_app_info_get_executable(app_info));
+ if (!fileWithFullPath) {
+ g_object_unref(app_info);
+ NS_WARNING("Cannot find program in path.");
+ break;
+ }
+ rv = NS_NewNativeLocalFile(nsDependentCString(fileWithFullPath), false,
+ getter_AddRefs(localExecutable));
+ g_free(fileWithFullPath);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Cannot create local filename.");
+ localHandler = nullptr;
+ } else {
+ localHandler->SetExecutable(localExecutable);
+ localHandler->SetName(
+ NS_ConvertUTF8toUTF16(g_app_info_get_display_name(app_info)));
+ }
+ g_object_unref(app_info);
+ }
+
+ break;
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_CLOSE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ break;
+ default:
+ NS_WARNING("Unexpected response");
+ break;
+ }
+
+ // A "response" signal won't be sent again but "destroy" will be.
+ g_signal_handlers_disconnect_by_func(chooser, FuncToGpointer(OnDestroy),
+ this);
+ gtk_widget_destroy(chooser);
+
+ if (mCallback) {
+ mCallback->Done(localHandler);
+ mCallback = nullptr;
+ }
+ NS_RELEASE_THIS();
+}
diff --git a/widget/gtk/nsApplicationChooser.h b/widget/gtk/nsApplicationChooser.h
new file mode 100644
index 0000000000..22f9a808c0
--- /dev/null
+++ b/widget/gtk/nsApplicationChooser.h
@@ -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/. */
+
+#ifndef nsApplicationChooser_h__
+#define nsApplicationChooser_h__
+
+#include <gtk/gtk.h>
+#include "nsCOMPtr.h"
+#include "nsIApplicationChooser.h"
+#include "nsString.h"
+
+class nsIWidget;
+
+class nsApplicationChooser final : public nsIApplicationChooser {
+ public:
+ nsApplicationChooser();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAPPLICATIONCHOOSER
+ void Done(GtkWidget* chooser, gint response);
+
+ private:
+ ~nsApplicationChooser();
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsCString mWindowTitle;
+ nsCOMPtr<nsIApplicationChooserFinishedCallback> mCallback;
+ static void OnResponse(GtkWidget* chooser, gint response_id,
+ gpointer user_data);
+ static void OnDestroy(GtkWidget* chooser, gpointer user_data);
+};
+#endif
diff --git a/widget/gtk/nsBidiKeyboard.cpp b/widget/gtk/nsBidiKeyboard.cpp
new file mode 100644
index 0000000000..a57235195b
--- /dev/null
+++ b/widget/gtk/nsBidiKeyboard.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "prlink.h"
+
+#include "nsBidiKeyboard.h"
+#include "nsIWidget.h"
+#include <gtk/gtk.h>
+
+NS_IMPL_ISUPPORTS(nsBidiKeyboard, nsIBidiKeyboard)
+
+nsBidiKeyboard::nsBidiKeyboard() { Reset(); }
+
+NS_IMETHODIMP
+nsBidiKeyboard::Reset() {
+ // NB: The default keymap can be null (e.g. in xpcshell). In that case,
+ // simply assume that we don't have bidi keyboards.
+ mHaveBidiKeyboards = false;
+
+ GdkDisplay* display = gdk_display_get_default();
+ if (!display) return NS_OK;
+
+ GdkKeymap* keymap = gdk_keymap_get_for_display(display);
+ mHaveBidiKeyboards = keymap && gdk_keymap_have_bidi_layouts(keymap);
+ return NS_OK;
+}
+
+nsBidiKeyboard::~nsBidiKeyboard() = default;
+
+NS_IMETHODIMP
+nsBidiKeyboard::IsLangRTL(bool* aIsRTL) {
+ if (!mHaveBidiKeyboards) return NS_ERROR_FAILURE;
+
+ *aIsRTL = (gdk_keymap_get_direction(gdk_keymap_get_default()) ==
+ PANGO_DIRECTION_RTL);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(bool* aResult) {
+ // not implemented yet
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// static
+already_AddRefed<nsIBidiKeyboard> nsIWidget::CreateBidiKeyboardInner() {
+ return do_AddRef(new nsBidiKeyboard());
+}
diff --git a/widget/gtk/nsBidiKeyboard.h b/widget/gtk/nsBidiKeyboard.h
new file mode 100644
index 0000000000..1bc1c7e0a2
--- /dev/null
+++ b/widget/gtk/nsBidiKeyboard.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __nsBidiKeyboard
+#define __nsBidiKeyboard
+#include "nsIBidiKeyboard.h"
+
+class nsBidiKeyboard : public nsIBidiKeyboard {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIBIDIKEYBOARD
+
+ nsBidiKeyboard();
+
+ protected:
+ virtual ~nsBidiKeyboard();
+
+ bool mHaveBidiKeyboards;
+};
+
+#endif // __nsBidiKeyboard
diff --git a/widget/gtk/nsClipboard.cpp b/widget/gtk/nsClipboard.cpp
new file mode 100644
index 0000000000..42807845fb
--- /dev/null
+++ b/widget/gtk/nsClipboard.cpp
@@ -0,0 +1,822 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#include "nsClipboardX11.h"
+#if defined(MOZ_WAYLAND)
+# include "nsClipboardWayland.h"
+#endif
+#include "nsContentUtils.h"
+#include "HeadlessClipboard.h"
+#include "nsSupportsPrimitives.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsImageToPixbuf.h"
+#include "nsStringStream.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/TimeStamp.h"
+#include "gfxPlatformGtk.h"
+
+#include "imgIContainer.h"
+
+#include <gtk/gtk.h>
+#include <gtk/gtkx.h>
+
+#include "mozilla/Encoding.h"
+
+using namespace mozilla;
+
+// Idle timeout for receiving selection and property notify events (microsec)
+const int kClipboardTimeout = 500000;
+
+// We add this prefix to HTML markup, so that GetHTMLCharset can correctly
+// detect the HTML as UTF-8 encoded.
+static const char kHTMLMarkupPrefix[] =
+ R"(<meta http-equiv="content-type" content="text/html; charset=utf-8">)";
+
+// Callback when someone asks us for the data
+void clipboard_get_cb(GtkClipboard* aGtkClipboard,
+ GtkSelectionData* aSelectionData, guint info,
+ gpointer user_data);
+
+// Callback when someone asks us to clear a clipboard
+void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data);
+
+static bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength,
+ nsCString& charset, char16_t** unicodeData,
+ int32_t& outUnicodeLen);
+
+static bool GetHTMLCharset(const char* data, int32_t dataLength,
+ nsCString& str);
+
+GdkAtom GetSelectionAtom(int32_t aWhichClipboard) {
+ if (aWhichClipboard == nsIClipboard::kGlobalClipboard)
+ return GDK_SELECTION_CLIPBOARD;
+
+ return GDK_SELECTION_PRIMARY;
+}
+
+int GetGeckoClipboardType(GtkClipboard* aGtkClipboard) {
+ if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
+ return nsClipboard::kSelectionClipboard;
+ else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
+ return nsClipboard::kGlobalClipboard;
+ else
+ return -1; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
+}
+
+nsClipboard::nsClipboard() = default;
+
+nsClipboard::~nsClipboard() {
+ // We have to clear clipboard before gdk_display_close() call.
+ // See bug 531580 for details.
+ if (mGlobalTransferable) {
+ gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
+ }
+ if (mSelectionTransferable) {
+ gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard, nsIObserver)
+
+nsresult nsClipboard::Init(void) {
+ if (gfxPlatformGtk::GetPlatform()->IsX11Display()) {
+ mContext = MakeUnique<nsRetrievalContextX11>();
+#if defined(MOZ_WAYLAND)
+ } else {
+ mContext = MakeUnique<nsRetrievalContextWayland>();
+#endif
+ }
+ NS_ASSERTION(mContext, "Missing nsRetrievalContext for nsClipboard!");
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->AddObserver(this, "xpcom-shutdown", false);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ // Save global clipboard content to CLIPBOARD_MANAGER.
+ // gtk_clipboard_store() can run an event loop, so call from a dedicated
+ // runnable.
+ return SchedulerGroup::Dispatch(
+ TaskCategory::Other,
+ NS_NewRunnableFunction("gtk_clipboard_store()", []() {
+ gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
+ }));
+}
+
+NS_IMETHODIMP
+nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* aOwner,
+ int32_t aWhichClipboard) {
+ // See if we can short cut
+ if ((aWhichClipboard == kGlobalClipboard &&
+ aTransferable == mGlobalTransferable.get() &&
+ aOwner == mGlobalOwner.get()) ||
+ (aWhichClipboard == kSelectionClipboard &&
+ aTransferable == mSelectionTransferable.get() &&
+ aOwner == mSelectionOwner.get())) {
+ return NS_OK;
+ }
+
+ LOGCLIP(("nsClipboard::SetData (%s)\n",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
+
+ // List of suported targets
+ GtkTargetList* list = gtk_target_list_new(nullptr, 0);
+
+ // Get the types of supported flavors
+ nsTArray<nsCString> flavors;
+ nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors);
+ if (NS_FAILED(rv)) {
+ LOGCLIP((" FlavorsTransferableCanExport failed!\n"));
+ // Fall through. |gtkTargets| will be null below.
+ }
+
+ // Add all the flavors to this widget's supported type.
+ bool imagesAdded = false;
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+
+ // Special case text/unicode since we can handle all of the string types.
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ LOGCLIP((" text targets\n"));
+ gtk_target_list_add_text_targets(list, 0);
+ continue;
+ }
+
+ if (nsContentUtils::IsFlavorImage(flavorStr)) {
+ // Don't bother adding image targets twice
+ if (!imagesAdded) {
+ // accept any writable image type
+ LOGCLIP((" image targets\n"));
+ gtk_target_list_add_image_targets(list, 0, TRUE);
+ imagesAdded = true;
+ }
+ continue;
+ }
+
+ // Add this to our list of valid targets
+ GdkAtom atom = gdk_atom_intern(flavorStr.get(), FALSE);
+ gtk_target_list_add(list, atom, 0, 0);
+ }
+
+ // Get GTK clipboard (CLIPBOARD or PRIMARY)
+ GtkClipboard* gtkClipboard =
+ gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+ gint numTargets;
+ GtkTargetEntry* gtkTargets =
+ gtk_target_table_new_from_list(list, &numTargets);
+
+ LOGCLIP((" gtk_target_table_new_from_list() = %p\n", (void*)gtkTargets));
+
+ // Set getcallback and request to store data after an application exit
+ if (gtkTargets &&
+ gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
+ clipboard_get_cb, clipboard_clear_cb, this)) {
+ LOGCLIP((" gtk_clipboard_set_with_data() is ok\n"));
+ // We managed to set-up the clipboard so update internal state
+ // We have to set it now because gtk_clipboard_set_with_data() calls
+ // clipboard_clear_cb() which reset our internal state
+ if (aWhichClipboard == kSelectionClipboard) {
+ mSelectionOwner = aOwner;
+ mSelectionTransferable = aTransferable;
+ } else {
+ mGlobalOwner = aOwner;
+ mGlobalTransferable = aTransferable;
+ gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
+ }
+
+ rv = NS_OK;
+ } else {
+ LOGCLIP((" gtk_clipboard_set_with_data() failed!\n"));
+ // Clear references to the any old data and let GTK know that it is no
+ // longer available.
+ EmptyClipboard(aWhichClipboard);
+ rv = NS_ERROR_FAILURE;
+ }
+
+ gtk_target_table_free(gtkTargets, numTargets);
+ gtk_target_list_unref(list);
+
+ return rv;
+}
+
+void nsClipboard::SetTransferableData(nsITransferable* aTransferable,
+ nsCString& aFlavor,
+ const char* aClipboardData,
+ uint32_t aClipboardDataLength) {
+ LOGCLIP(("nsClipboard::SetTransferableData MIME %s\n", aFlavor.get()));
+
+ nsCOMPtr<nsISupports> wrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(
+ aFlavor, aClipboardData, aClipboardDataLength, getter_AddRefs(wrapper));
+ aTransferable->SetTransferData(aFlavor.get(), wrapper);
+}
+
+NS_IMETHODIMP
+nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
+ if (!aTransferable) return NS_ERROR_FAILURE;
+
+ LOGCLIP(("nsClipboard::GetData (%s)\n",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
+
+ // Get a list of flavors this transferable can import
+ nsTArray<nsCString> flavors;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
+ if (NS_FAILED(rv)) {
+ LOGCLIP((" FlavorsTransferableCanImport falied!\n"));
+ return rv;
+ }
+
+#ifdef MOZ_LOGGING
+ LOGCLIP(("Flavors which can be imported:\n"));
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ LOGCLIP((" %s\n", flavors[i].get()));
+ }
+#endif
+
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+
+ if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kGIFImageMime)) {
+ // Emulate support for image/jpg
+ if (flavorStr.EqualsLiteral(kJPGImageMime)) {
+ flavorStr.Assign(kJPEGImageMime);
+ }
+
+ LOGCLIP((" Getting image %s MIME clipboard data\n", flavorStr.get()));
+
+ uint32_t clipboardDataLength;
+ const char* clipboardData = mContext->GetClipboardData(
+ flavorStr.get(), aWhichClipboard, &clipboardDataLength);
+ if (!clipboardData) {
+ LOGCLIP((" %s type is missing\n", flavorStr.get()));
+ continue;
+ }
+
+ nsCOMPtr<nsIInputStream> byteStream;
+ NS_NewByteInputStream(getter_AddRefs(byteStream),
+ Span(clipboardData, clipboardDataLength),
+ NS_ASSIGNMENT_COPY);
+ aTransferable->SetTransferData(flavorStr.get(), byteStream);
+ LOGCLIP((" got %s MIME data\n", flavorStr.get()));
+
+ mContext->ReleaseClipboardData(clipboardData);
+ return NS_OK;
+ }
+
+ // Special case text/unicode since we can convert any
+ // string into text/unicode
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ LOGCLIP(
+ (" Getting unicode %s MIME clipboard data\n", flavorStr.get()));
+
+ const char* clipboardData = mContext->GetClipboardText(aWhichClipboard);
+ if (!clipboardData) {
+ LOGCLIP((" failed to get unicode data\n"));
+ // If the type was text/unicode and we couldn't get
+ // text off the clipboard, run the next loop
+ // iteration.
+ continue;
+ }
+
+ // Convert utf-8 into our unicode format.
+ NS_ConvertUTF8toUTF16 ucs2string(clipboardData);
+ const char* unicodeData = (const char*)ToNewUnicode(ucs2string);
+ uint32_t unicodeDataLength = ucs2string.Length() * 2;
+ SetTransferableData(aTransferable, flavorStr, unicodeData,
+ unicodeDataLength);
+ free((void*)unicodeData);
+
+ LOGCLIP((" got unicode data, length %d\n", ucs2string.Length()));
+
+ mContext->ReleaseClipboardData(clipboardData);
+ return NS_OK;
+ }
+
+ LOGCLIP((" Getting %s MIME clipboard data\n", flavorStr.get()));
+
+ uint32_t clipboardDataLength;
+ const char* clipboardData = mContext->GetClipboardData(
+ flavorStr.get(), aWhichClipboard, &clipboardDataLength);
+
+#ifdef MOZ_LOGGING
+ if (!clipboardData) {
+ LOGCLIP((" %s type is missing\n", flavorStr.get()));
+ }
+#endif
+
+ if (clipboardData) {
+ LOGCLIP((" got %s mime type data.\n", flavorStr.get()));
+
+ // Special case text/html since we can convert into UCS2
+ if (flavorStr.EqualsLiteral(kHTMLMime)) {
+ char16_t* htmlBody = nullptr;
+ int32_t htmlBodyLen = 0;
+ // Convert text/html into our unicode format
+ nsAutoCString charset;
+ if (!GetHTMLCharset(clipboardData, clipboardDataLength, charset)) {
+ // Fall back to utf-8 in case html/data is missing kHTMLMarkupPrefix.
+ LOGCLIP(("Failed to get html/text encoding, fall back to utf-8.\n"));
+ charset.AssignLiteral("utf-8");
+ }
+ if (!ConvertHTMLtoUCS2(clipboardData, clipboardDataLength, charset,
+ &htmlBody, htmlBodyLen)) {
+ LOGCLIP((" failed to convert text/html to UCS2.\n"));
+ mContext->ReleaseClipboardData(clipboardData);
+ continue;
+ }
+
+ SetTransferableData(aTransferable, flavorStr, (const char*)htmlBody,
+ htmlBodyLen * 2);
+ free(htmlBody);
+ } else {
+ SetTransferableData(aTransferable, flavorStr, clipboardData,
+ clipboardDataLength);
+ }
+
+ mContext->ReleaseClipboardData(clipboardData);
+ return NS_OK;
+ }
+ }
+
+ LOGCLIP((" failed to get clipboard content.\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
+ LOGCLIP(("nsClipboard::EmptyClipboard (%s)\n",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
+ if (aWhichClipboard == kSelectionClipboard) {
+ if (mSelectionTransferable) {
+ gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
+ MOZ_ASSERT(!mSelectionTransferable);
+ }
+ } else {
+ if (mGlobalTransferable) {
+ gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
+ MOZ_ASSERT(!mGlobalTransferable);
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsClipboard::ClearTransferable(int32_t aWhichClipboard) {
+ if (aWhichClipboard == kSelectionClipboard) {
+ if (mSelectionOwner) {
+ mSelectionOwner->LosingOwnership(mSelectionTransferable);
+ mSelectionOwner = nullptr;
+ }
+ mSelectionTransferable = nullptr;
+ } else {
+ if (mGlobalOwner) {
+ mGlobalOwner->LosingOwnership(mGlobalTransferable);
+ mGlobalOwner = nullptr;
+ }
+ mGlobalTransferable = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
+ int32_t aWhichClipboard, bool* _retval) {
+ if (!_retval) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ LOGCLIP(("nsClipboard::HasDataMatchingFlavors (%s)\n",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
+
+ *_retval = false;
+
+ int targetNums;
+ GdkAtom* targets = mContext->GetTargets(aWhichClipboard, &targetNums);
+ if (!targets) {
+ LOGCLIP((" no targes at clipboard (null)\n"));
+ return NS_OK;
+ }
+
+ // Walk through the provided types and try to match it to a
+ // provided type.
+ for (auto& flavor : aFlavorList) {
+ // We special case text/unicode here.
+ if (flavor.EqualsLiteral(kUnicodeMime) &&
+ gtk_targets_include_text(targets, targetNums)) {
+ *_retval = true;
+ LOGCLIP((" has kUnicodeMime\n"));
+ break;
+ }
+
+ for (int32_t j = 0; j < targetNums; j++) {
+ gchar* atom_name = gdk_atom_name(targets[j]);
+ if (!atom_name) continue;
+
+ if (flavor.Equals(atom_name)) {
+ *_retval = true;
+ LOGCLIP((" has %s\n", atom_name));
+ }
+ // X clipboard supports image/jpeg, but we want to emulate support
+ // for image/jpg as well
+ else if (flavor.EqualsLiteral(kJPGImageMime) &&
+ !strcmp(atom_name, kJPEGImageMime)) {
+ *_retval = true;
+ LOGCLIP((" has image/jpg\n"));
+ }
+
+ g_free(atom_name);
+
+ if (*_retval) break;
+ }
+ }
+
+#ifdef MOZ_LOGGING
+ if (!(*_retval)) {
+ LOGCLIP((" no targes at clipboard (bad match)\n"));
+ }
+#endif
+
+ g_free(targets);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsSelectionClipboard(bool* _retval) {
+ *_retval = mContext->HasSelectionSupport();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsFindClipboard(bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+nsITransferable* nsClipboard::GetTransferable(int32_t aWhichClipboard) {
+ nsITransferable* retval;
+
+ if (aWhichClipboard == kSelectionClipboard)
+ retval = mSelectionTransferable.get();
+ else
+ retval = mGlobalTransferable.get();
+
+ return retval;
+}
+
+void nsClipboard::SelectionGetEvent(GtkClipboard* aClipboard,
+ GtkSelectionData* aSelectionData) {
+ // Someone has asked us to hand them something. The first thing
+ // that we want to do is see if that something includes text. If
+ // it does, try to give it text/unicode after converting it to
+ // utf-8.
+
+ int32_t whichClipboard;
+
+ // which clipboard?
+ GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
+ if (selection == GDK_SELECTION_PRIMARY)
+ whichClipboard = kSelectionClipboard;
+ else if (selection == GDK_SELECTION_CLIPBOARD)
+ whichClipboard = kGlobalClipboard;
+ else
+ return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
+
+ LOGCLIP(("nsClipboard::SelectionGetEvent (%s)\n",
+ whichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
+
+ nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
+ if (!trans) {
+ // We have nothing to serve
+ LOGCLIP(("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
+ whichClipboard == kSelectionClipboard ? "Primary" : "Clipboard"));
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> item;
+
+ GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
+ LOGCLIP((" selection target %s\n", gdk_atom_name(selectionTarget)));
+
+ // Check to see if the selection data is some text type.
+ if (gtk_targets_include_text(&selectionTarget, 1)) {
+ LOGCLIP((" providing text/unicode data\n"));
+ // Try to convert our internal type into a text string. Get
+ // the transferable for this clipboard and try to get the
+ // text/unicode type for it.
+ rv = trans->GetTransferData("text/unicode", getter_AddRefs(item));
+ if (NS_FAILED(rv) || !item) {
+ LOGCLIP((" GetTransferData() failed to get text/unicode!\n"));
+ return;
+ }
+
+ nsCOMPtr<nsISupportsString> wideString;
+ wideString = do_QueryInterface(item);
+ if (!wideString) return;
+
+ nsAutoString ucs2string;
+ wideString->GetData(ucs2string);
+ NS_ConvertUTF16toUTF8 utf8string(ucs2string);
+
+ LOGCLIP((" sent %d bytes of utf-8 data\n", utf8string.Length()));
+ if (selectionTarget == gdk_atom_intern("text/plain;charset=utf-8", FALSE)) {
+ LOGCLIP(
+ (" using gtk_selection_data_set for 'text/plain;charset=utf-8'\n"));
+ // Bypass gtk_selection_data_set_text, which will convert \n to \r\n
+ // in some versions of GTK.
+ gtk_selection_data_set(aSelectionData, selectionTarget, 8,
+ reinterpret_cast<const guchar*>(utf8string.get()),
+ utf8string.Length());
+ } else {
+ gtk_selection_data_set_text(aSelectionData, utf8string.get(),
+ utf8string.Length());
+ }
+ return;
+ }
+
+ // Check to see if the selection data is an image type
+ if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
+ LOGCLIP((" providing image data\n"));
+ // Look through our transfer data for the image
+ static const char* const imageMimeTypes[] = {kNativeImageMime,
+ kPNGImageMime, kJPEGImageMime,
+ kJPGImageMime, kGIFImageMime};
+ nsCOMPtr<nsISupports> imageItem;
+ nsCOMPtr<imgIContainer> image;
+ for (uint32_t i = 0; i < ArrayLength(imageMimeTypes); i++) {
+ rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem));
+ if (NS_FAILED(rv)) {
+ LOGCLIP(
+ (" %s is missing at GetTransferData()\n", imageMimeTypes[i]));
+ continue;
+ }
+
+ image = do_QueryInterface(imageItem);
+ if (image) {
+ LOGCLIP(
+ (" %s is available at GetTransferData()\n", imageMimeTypes[i]));
+ break;
+ }
+ }
+
+ if (!image) { // Not getting an image for an image mime type!?
+ LOGCLIP((" Failed to get any image mime from GetTransferData()!\n"));
+ return;
+ }
+
+ GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
+ if (!pixbuf) {
+ LOGCLIP((" nsImageToPixbuf::ImageToPixbuf() failed!\n"));
+ return;
+ }
+
+ LOGCLIP((" Setting pixbuf image data as %s\n",
+ gdk_atom_name(selectionTarget)));
+ gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
+ g_object_unref(pixbuf);
+ return;
+ }
+
+ if (selectionTarget == gdk_atom_intern(kHTMLMime, FALSE)) {
+ LOGCLIP((" providing %s data\n", kHTMLMime));
+ rv = trans->GetTransferData(kHTMLMime, getter_AddRefs(item));
+ if (NS_FAILED(rv) || !item) {
+ LOGCLIP((" failed to get %s data by GetTransferData()!\n", kHTMLMime));
+ return;
+ }
+
+ nsCOMPtr<nsISupportsString> wideString;
+ wideString = do_QueryInterface(item);
+ if (!wideString) {
+ LOGCLIP((" failed to get wideString interface!"));
+ return;
+ }
+
+ nsAutoString ucs2string;
+ wideString->GetData(ucs2string);
+
+ nsAutoCString html;
+ // Add the prefix so the encoding is correctly detected.
+ html.AppendLiteral(kHTMLMarkupPrefix);
+ AppendUTF16toUTF8(ucs2string, html);
+
+ LOGCLIP((" Setting %d bytest of %s data\n", html.Length(),
+ gdk_atom_name(selectionTarget)));
+ gtk_selection_data_set(aSelectionData, selectionTarget, 8,
+ (const guchar*)html.get(), html.Length());
+ return;
+ }
+
+ LOGCLIP((" Try if we have anything at GetTransferData() for %s\n",
+ gdk_atom_name(selectionTarget)));
+
+ // Try to match up the selection data target to something our
+ // transferable provides.
+ gchar* target_name = gdk_atom_name(selectionTarget);
+ if (!target_name) {
+ LOGCLIP((" Failed to get target name!\n"));
+ return;
+ }
+
+ rv = trans->GetTransferData(target_name, getter_AddRefs(item));
+ // nothing found?
+ if (NS_FAILED(rv) || !item) {
+ LOGCLIP((" Failed to get anything from GetTransferData()!\n"));
+ g_free(target_name);
+ return;
+ }
+
+ void* primitive_data = nullptr;
+ uint32_t dataLen = 0;
+ nsPrimitiveHelpers::CreateDataFromPrimitive(nsDependentCString(target_name),
+ item, &primitive_data, &dataLen);
+
+ if (primitive_data) {
+ LOGCLIP((" Setting %s as a primitive data type, %d bytes\n", target_name,
+ dataLen));
+ gtk_selection_data_set(aSelectionData, selectionTarget,
+ 8, /* 8 bits in a unit */
+ (const guchar*)primitive_data, dataLen);
+ free(primitive_data);
+ } else {
+ LOGCLIP((" Failed to get primitive data!\n"));
+ }
+
+ g_free(target_name);
+}
+
+void nsClipboard::SelectionClearEvent(GtkClipboard* aGtkClipboard) {
+ int32_t whichClipboard = GetGeckoClipboardType(aGtkClipboard);
+ if (whichClipboard < 0) {
+ return;
+ }
+
+ LOGCLIP(("nsClipboard::SelectionClearEvent (%s)\n",
+ whichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
+
+ ClearTransferable(whichClipboard);
+}
+
+void clipboard_get_cb(GtkClipboard* aGtkClipboard,
+ GtkSelectionData* aSelectionData, guint info,
+ gpointer user_data) {
+ LOGCLIP(("clipboard_get_cb() callback\n"));
+ nsClipboard* aClipboard = static_cast<nsClipboard*>(user_data);
+ aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
+}
+
+void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data) {
+ LOGCLIP(("clipboard_clear_cb() callback\n"));
+ nsClipboard* aClipboard = static_cast<nsClipboard*>(user_data);
+ aClipboard->SelectionClearEvent(aGtkClipboard);
+}
+
+/*
+ * when copy-paste, mozilla wants data encoded using UCS2,
+ * other app such as StarOffice use "text/html"(RFC2854).
+ * This function convert data(got from GTK clipboard)
+ * to data mozilla wanted.
+ *
+ * data from GTK clipboard can be 3 forms:
+ * 1. From current mozilla
+ * "text/html", charset = utf-16
+ * 2. From old version mozilla or mozilla-based app
+ * content("body" only), charset = utf-16
+ * 3. From other app who use "text/html" when copy-paste
+ * "text/html", has "charset" info
+ *
+ * data : got from GTK clipboard
+ * dataLength: got from GTK clipboard
+ * body : pass to Mozilla
+ * bodyLength: pass to Mozilla
+ */
+bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength, nsCString& charset,
+ char16_t** unicodeData, int32_t& outUnicodeLen) {
+ if (charset.EqualsLiteral("UTF-16")) { // current mozilla
+ outUnicodeLen = (dataLength / 2) - 1;
+ *unicodeData = reinterpret_cast<char16_t*>(
+ moz_xmalloc((outUnicodeLen + sizeof('\0')) * sizeof(char16_t)));
+ memcpy(*unicodeData, data + sizeof(char16_t),
+ outUnicodeLen * sizeof(char16_t));
+ (*unicodeData)[outUnicodeLen] = '\0';
+ return true;
+ } else if (charset.EqualsLiteral("UNKNOWN")) {
+ outUnicodeLen = 0;
+ return false;
+ } else {
+ // app which use "text/html" to copy&paste
+ // get the decoder
+ auto encoding = Encoding::ForLabelNoReplacement(charset);
+ if (!encoding) {
+ LOGCLIP(("ConvertHTMLtoUCS2: get unicode decoder error\n"));
+ outUnicodeLen = 0;
+ return false;
+ }
+
+ auto dataSpan = Span(data, dataLength);
+ // Remove kHTMLMarkupPrefix again, it won't necessarily cause any
+ // issues, but might confuse other users.
+ const size_t prefixLen = ArrayLength(kHTMLMarkupPrefix) - 1;
+ if (dataSpan.Length() >= prefixLen &&
+ Substring(data, prefixLen).EqualsLiteral(kHTMLMarkupPrefix)) {
+ dataSpan = dataSpan.From(prefixLen);
+ }
+
+ auto decoder = encoding->NewDecoder();
+ CheckedInt<size_t> needed =
+ decoder->MaxUTF16BufferLength(dataSpan.Length());
+ if (!needed.isValid() || needed.value() > INT32_MAX) {
+ outUnicodeLen = 0;
+ return false;
+ }
+
+ outUnicodeLen = 0;
+ if (needed.value()) {
+ *unicodeData = reinterpret_cast<char16_t*>(
+ moz_xmalloc((needed.value() + 1) * sizeof(char16_t)));
+ uint32_t result;
+ size_t read;
+ size_t written;
+ bool hadErrors;
+ Tie(result, read, written, hadErrors) = decoder->DecodeToUTF16(
+ AsBytes(dataSpan), Span(*unicodeData, needed.value()), true);
+ MOZ_ASSERT(result == kInputEmpty);
+ MOZ_ASSERT(read == size_t(dataSpan.Length()));
+ MOZ_ASSERT(written <= needed.value());
+ Unused << hadErrors;
+ outUnicodeLen = written;
+ // null terminate.
+ (*unicodeData)[outUnicodeLen] = '\0';
+ return true;
+ } // if valid length
+ }
+ return false;
+}
+
+/*
+ * get "charset" information from clipboard data
+ * return value can be:
+ * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16"
+ * 2. "UNKNOWN": mozilla can't detect what encode it use
+ * 3. other: "text/html" with other charset than utf-16
+ */
+bool GetHTMLCharset(const char* data, int32_t dataLength, nsCString& str) {
+ // if detect "FFFE" or "FEFF", assume UTF-16
+ char16_t* beginChar = (char16_t*)data;
+ if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
+ str.AssignLiteral("UTF-16");
+ LOGCLIP(("GetHTMLCharset: Charset of HTML is UTF-16\n"));
+ return true;
+ }
+ // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
+ const nsDependentCSubstring htmlStr(data, dataLength);
+ nsACString::const_iterator start, end;
+ htmlStr.BeginReading(start);
+ htmlStr.EndReading(end);
+ nsACString::const_iterator valueStart(start), valueEnd(start);
+
+ if (CaseInsensitiveFindInReadable("CONTENT=\"text/html;"_ns, start, end)) {
+ start = end;
+ htmlStr.EndReading(end);
+
+ if (CaseInsensitiveFindInReadable("charset="_ns, start, end)) {
+ valueStart = end;
+ start = end;
+ htmlStr.EndReading(end);
+
+ if (FindCharInReadable('"', start, end)) valueEnd = start;
+ }
+ }
+ // find "charset" in HTML
+ if (valueStart != valueEnd) {
+ str = Substring(valueStart, valueEnd);
+ ToUpperCase(str);
+ LOGCLIP(("GetHTMLCharset: Charset of HTML = %s\n", str.get()));
+ return true;
+ }
+ str.AssignLiteral("UNKNOWN");
+ LOGCLIP(("GetHTMLCharset: Failed to get HTML Charset!\n"));
+ return false;
+}
diff --git a/widget/gtk/nsClipboard.h b/widget/gtk/nsClipboard.h
new file mode 100644
index 0000000000..95b6eb828a
--- /dev/null
+++ b/widget/gtk/nsClipboard.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __nsClipboard_h_
+#define __nsClipboard_h_
+
+#include "mozilla/UniquePtr.h"
+#include "nsIClipboard.h"
+#include "nsIObserver.h"
+#include <gtk/gtk.h>
+
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gClipboardLog;
+# define LOGCLIP(args) MOZ_LOG(gClipboardLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOGCLIP(args)
+#endif /* MOZ_LOGGING */
+
+class nsRetrievalContext {
+ public:
+ // Get actual clipboard content (GetClipboardData/GetClipboardText)
+ // which has to be released by ReleaseClipboardData().
+ virtual const char* GetClipboardData(const char* aMimeType,
+ int32_t aWhichClipboard,
+ uint32_t* aContentLength) = 0;
+ virtual const char* GetClipboardText(int32_t aWhichClipboard) = 0;
+ virtual void ReleaseClipboardData(const char* aClipboardData) = 0;
+
+ // Get data mime types which can be obtained from clipboard.
+ // The returned array has to be released by g_free().
+ virtual GdkAtom* GetTargets(int32_t aWhichClipboard, int* aTargetNum) = 0;
+
+ virtual bool HasSelectionSupport(void) = 0;
+
+ virtual ~nsRetrievalContext() = default;
+};
+
+class nsClipboard : public nsIClipboard, public nsIObserver {
+ public:
+ nsClipboard();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSICLIPBOARD
+
+ // Make sure we are initialized, called from the factory
+ // constructor
+ nsresult Init(void);
+
+ // Someone requested the selection
+ void SelectionGetEvent(GtkClipboard* aGtkClipboard,
+ GtkSelectionData* aSelectionData);
+ void SelectionClearEvent(GtkClipboard* aGtkClipboard);
+
+ private:
+ virtual ~nsClipboard();
+
+ // Get our hands on the correct transferable, given a specific
+ // clipboard
+ nsITransferable* GetTransferable(int32_t aWhichClipboard);
+
+ // Send clipboard data by nsITransferable
+ void SetTransferableData(nsITransferable* aTransferable, nsCString& aFlavor,
+ const char* aClipboardData,
+ uint32_t aClipboardDataLength);
+
+ void ClearTransferable(int32_t aWhichClipboard);
+
+ // Hang on to our owners and transferables so we can transfer data
+ // when asked.
+ nsCOMPtr<nsIClipboardOwner> mSelectionOwner;
+ nsCOMPtr<nsIClipboardOwner> mGlobalOwner;
+ nsCOMPtr<nsITransferable> mSelectionTransferable;
+ nsCOMPtr<nsITransferable> mGlobalTransferable;
+ mozilla::UniquePtr<nsRetrievalContext> mContext;
+};
+
+extern const int kClipboardTimeout;
+
+GdkAtom GetSelectionAtom(int32_t aWhichClipboard);
+int GetGeckoClipboardType(GtkClipboard* aGtkClipboard);
+
+#endif /* __nsClipboard_h_ */
diff --git a/widget/gtk/nsClipboardWayland.cpp b/widget/gtk/nsClipboardWayland.cpp
new file mode 100644
index 0000000000..f8e1031e9e
--- /dev/null
+++ b/widget/gtk/nsClipboardWayland.cpp
@@ -0,0 +1,915 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#include "nsClipboardWayland.h"
+#include "nsSupportsPrimitives.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsImageToPixbuf.h"
+#include "nsStringStream.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsDragService.h"
+#include "mozwayland/mozwayland.h"
+#include "nsWaylandDisplay.h"
+
+#include <gtk/gtk.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+const char* nsRetrievalContextWayland::sTextMimeTypes[TEXT_MIME_TYPES_NUM] = {
+ "text/plain;charset=utf-8", "UTF8_STRING", "COMPOUND_TEXT"};
+
+static inline GdkDragAction wl_to_gdk_actions(uint32_t dnd_actions) {
+ GdkDragAction actions = GdkDragAction(0);
+
+ if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
+ actions = GdkDragAction(actions | GDK_ACTION_COPY);
+ if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
+ actions = GdkDragAction(actions | GDK_ACTION_MOVE);
+
+ return actions;
+}
+
+static inline uint32_t gdk_to_wl_actions(GdkDragAction action) {
+ uint32_t dnd_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
+
+ if (action & (GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_PRIVATE))
+ dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
+ if (action & GDK_ACTION_MOVE)
+ dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
+
+ return dnd_actions;
+}
+
+static GtkWidget* get_gtk_widget_for_wl_surface(struct wl_surface* surface) {
+ GdkWindow* gdkParentWindow =
+ static_cast<GdkWindow*>(wl_surface_get_user_data(surface));
+
+ gpointer user_data = nullptr;
+ gdk_window_get_user_data(gdkParentWindow, &user_data);
+
+ return GTK_WIDGET(user_data);
+}
+
+void DataOffer::AddMIMEType(const char* aMimeType) {
+ GdkAtom atom = gdk_atom_intern(aMimeType, FALSE);
+ mTargetMIMETypes.AppendElement(atom);
+}
+
+GdkAtom* DataOffer::GetTargets(int* aTargetNum) {
+ int length = mTargetMIMETypes.Length();
+ if (!length) {
+ *aTargetNum = 0;
+ return nullptr;
+ }
+
+ GdkAtom* targetList =
+ reinterpret_cast<GdkAtom*>(g_malloc(sizeof(GdkAtom) * length));
+ for (int32_t j = 0; j < length; j++) {
+ targetList[j] = mTargetMIMETypes[j];
+ }
+
+ *aTargetNum = length;
+ return targetList;
+}
+
+bool DataOffer::HasTarget(const char* aMimeType) {
+ int length = mTargetMIMETypes.Length();
+ for (int32_t j = 0; j < length; j++) {
+ if (mTargetMIMETypes[j] == gdk_atom_intern(aMimeType, FALSE)) {
+ LOGCLIP(("DataOffer::HasTarget() we have mime %s\n", aMimeType));
+ return true;
+ }
+ }
+ LOGCLIP(("DataOffer::HasTarget() missing mime %s\n", aMimeType));
+ return false;
+}
+
+char* DataOffer::GetData(wl_display* aDisplay, const char* aMimeType,
+ uint32_t* aContentLength) {
+ LOGCLIP(("DataOffer::GetData() mime %s\n", aMimeType));
+
+ int pipe_fd[2];
+ if (pipe(pipe_fd) == -1) return nullptr;
+
+ if (!RequestDataTransfer(aMimeType, pipe_fd[1])) {
+ NS_WARNING("DataOffer::RequestDataTransfer() failed!");
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ return nullptr;
+ }
+
+ close(pipe_fd[1]);
+ wl_display_flush(aDisplay);
+
+ struct pollfd fds;
+ fds.fd = pipe_fd[0];
+ fds.events = POLLIN;
+ int pollReturn = -1;
+
+#define MAX_CLIPBOARD_POLL_ATTEMPTS 10
+ for (int i = 0; i < MAX_CLIPBOARD_POLL_ATTEMPTS; i++) {
+ pollReturn = poll(&fds, 1, kClipboardTimeout / 1000);
+ // ret > 0 means we have data available
+ // ret = 0 means poll timeout expired
+ // ret < 0 means poll failed with error
+ if (pollReturn >= 0) {
+ break;
+ }
+ // We should try again for EINTR/EAGAIN errors,
+ // quit for all other ones.
+ if (errno != EINTR && errno != EAGAIN) {
+ break;
+ }
+ }
+ // Quit for poll error() and timeout
+ if (pollReturn <= 0) {
+ NS_WARNING("DataOffer::RequestDataTransfer() poll timeout!");
+ close(pipe_fd[0]);
+ return nullptr;
+ }
+
+ GIOChannel* channel = g_io_channel_unix_new(pipe_fd[0]);
+ GError* error = nullptr;
+ char* clipboardData = nullptr;
+
+ g_io_channel_set_encoding(channel, nullptr, &error);
+ if (!error) {
+ gsize length = 0;
+ g_io_channel_read_to_end(channel, &clipboardData, &length, &error);
+ if (length == 0) {
+ // We don't have valid clipboard data although
+ // g_io_channel_read_to_end() allocated clipboardData for us.
+ // Release it now and return nullptr to indicate
+ // we don't have reqested data flavour.
+ g_free((void*)clipboardData);
+ clipboardData = nullptr;
+ }
+ *aContentLength = length;
+ }
+
+ if (error) {
+ NS_WARNING(
+ nsPrintfCString("Unexpected error when reading clipboard data: %s",
+ error->message)
+ .get());
+ g_error_free(error);
+ }
+
+ g_io_channel_unref(channel);
+ close(pipe_fd[0]);
+
+ LOGCLIP((" Got clipboard data length %d\n", *aContentLength));
+ return clipboardData;
+}
+
+bool WaylandDataOffer::RequestDataTransfer(const char* aMimeType, int fd) {
+ if (mWaylandDataOffer) {
+ wl_data_offer_receive(mWaylandDataOffer, aMimeType, fd);
+ return true;
+ }
+
+ return false;
+}
+
+void WaylandDataOffer::DragOfferAccept(const char* aMimeType, uint32_t aTime) {
+ wl_data_offer_accept(mWaylandDataOffer, aTime, aMimeType);
+}
+
+/* We follow logic of gdk_wayland_drag_context_commit_status()/gdkdnd-wayland.c
+ * here.
+ */
+void WaylandDataOffer::SetDragStatus(GdkDragAction aPreferredAction,
+ uint32_t aTime) {
+ uint32_t preferredAction = gdk_to_wl_actions(aPreferredAction);
+ uint32_t allActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
+
+ /* We only don't choose a preferred action if we don't accept any.
+ * If we do accept any, it is currently alway copy and move
+ */
+ if (preferredAction != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE) {
+ allActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
+ }
+
+ wl_data_offer_set_actions(mWaylandDataOffer, allActions, preferredAction);
+
+ /* Workaround Wayland D&D architecture here. To get the data_device_drop()
+ signal (which routes to nsDragService::GetData() call) we need to
+ accept at least one mime type before data_device_leave().
+
+ Real wl_data_offer_accept() for actualy requested data mime type is
+ called from nsDragService::GetData().
+ */
+ if (mTargetMIMETypes[0]) {
+ wl_data_offer_accept(mWaylandDataOffer, aTime,
+ gdk_atom_name(mTargetMIMETypes[0]));
+ }
+}
+
+void WaylandDataOffer::SetSelectedDragAction(uint32_t aWaylandAction) {
+ mSelectedDragAction = aWaylandAction;
+}
+
+GdkDragAction WaylandDataOffer::GetSelectedDragAction() {
+ return wl_to_gdk_actions(mSelectedDragAction);
+}
+
+void WaylandDataOffer::SetAvailableDragActions(uint32_t aWaylandActions) {
+ mAvailableDragActions = aWaylandActions;
+}
+
+GdkDragAction WaylandDataOffer::GetAvailableDragActions() {
+ return wl_to_gdk_actions(mAvailableDragActions);
+}
+
+void WaylandDataOffer::SetWaylandDragContext(
+ nsWaylandDragContext* aDragContext) {
+ mDragContext = aDragContext;
+}
+
+nsWaylandDragContext* WaylandDataOffer::GetWaylandDragContext() {
+ return mDragContext;
+}
+
+static void data_offer_offer(void* data, struct wl_data_offer* wl_data_offer,
+ const char* type) {
+ auto* offer = static_cast<DataOffer*>(data);
+ offer->AddMIMEType(type);
+}
+
+/* Advertise all available drag and drop actions from source.
+ * We don't use that but follow gdk_wayland_drag_context_commit_status()
+ * from gdkdnd-wayland.c here.
+ */
+static void data_offer_source_actions(void* data,
+ struct wl_data_offer* wl_data_offer,
+ uint32_t source_actions) {
+ auto* offer = static_cast<WaylandDataOffer*>(data);
+ offer->SetAvailableDragActions(source_actions);
+}
+
+/* Advertise recently selected drag and drop action by compositor, based
+ * on source actions and user choice (key modifiers, etc.).
+ */
+static void data_offer_action(void* data, struct wl_data_offer* wl_data_offer,
+ uint32_t dnd_action) {
+ auto* offer = static_cast<WaylandDataOffer*>(data);
+ offer->SetSelectedDragAction(dnd_action);
+
+ /* Mimic GTK which triggers the motion event callback */
+ nsWaylandDragContext* dropContext = offer->GetWaylandDragContext();
+ if (dropContext) {
+ uint32_t time;
+ nscoord x, y;
+ dropContext->GetLastDropInfo(&time, &x, &y);
+
+ WindowDragMotionHandler(dropContext->GetWidget(), nullptr, dropContext, x,
+ y, time);
+ }
+}
+
+/* wl_data_offer callback description:
+ *
+ * data_offer_offer - Is called for each MIME type available at wl_data_offer.
+ * data_offer_source_actions - This event indicates the actions offered by
+ * the data source.
+ * data_offer_action - This event indicates the action selected by
+ * the compositor after matching the source/destination
+ * side actions.
+ */
+static const moz_wl_data_offer_listener data_offer_listener = {
+ data_offer_offer, data_offer_source_actions, data_offer_action};
+
+WaylandDataOffer::WaylandDataOffer(wl_data_offer* aWaylandDataOffer)
+ : mWaylandDataOffer(aWaylandDataOffer),
+ mDragContext(nullptr),
+ mSelectedDragAction(0),
+ mAvailableDragActions(0) {
+ wl_data_offer_add_listener(
+ mWaylandDataOffer, (struct wl_data_offer_listener*)&data_offer_listener,
+ this);
+}
+
+WaylandDataOffer::~WaylandDataOffer(void) {
+ if (mWaylandDataOffer) {
+ wl_data_offer_destroy(mWaylandDataOffer);
+ }
+}
+
+bool PrimaryDataOffer::RequestDataTransfer(const char* aMimeType, int fd) {
+ if (mPrimaryDataOfferGtk) {
+ gtk_primary_selection_offer_receive(mPrimaryDataOfferGtk, aMimeType, fd);
+ return true;
+ }
+ if (mPrimaryDataOfferZwpV1) {
+ zwp_primary_selection_offer_v1_receive(mPrimaryDataOfferZwpV1, aMimeType,
+ fd);
+ return true;
+ }
+ return false;
+}
+
+static void primary_data_offer(
+ void* data, gtk_primary_selection_offer* primary_selection_offer,
+ const char* mime_type) {
+ auto* offer = static_cast<DataOffer*>(data);
+ offer->AddMIMEType(mime_type);
+}
+
+static void primary_data_offer(
+ void* data, zwp_primary_selection_offer_v1* primary_selection_offer,
+ const char* mime_type) {
+ auto* offer = static_cast<DataOffer*>(data);
+ offer->AddMIMEType(mime_type);
+}
+
+/* gtk_primary_selection_offer_listener callback description:
+ *
+ * primary_data_offer - Is called for each MIME type available at
+ * gtk_primary_selection_offer.
+ */
+static const struct gtk_primary_selection_offer_listener
+ primary_selection_offer_listener_gtk = {primary_data_offer};
+
+static const struct zwp_primary_selection_offer_v1_listener
+ primary_selection_offer_listener_zwp_v1 = {primary_data_offer};
+
+PrimaryDataOffer::PrimaryDataOffer(
+ gtk_primary_selection_offer* aPrimaryDataOffer)
+ : mPrimaryDataOfferGtk(aPrimaryDataOffer), mPrimaryDataOfferZwpV1(nullptr) {
+ gtk_primary_selection_offer_add_listener(
+ aPrimaryDataOffer, &primary_selection_offer_listener_gtk, this);
+}
+
+PrimaryDataOffer::PrimaryDataOffer(
+ zwp_primary_selection_offer_v1* aPrimaryDataOffer)
+ : mPrimaryDataOfferGtk(nullptr), mPrimaryDataOfferZwpV1(aPrimaryDataOffer) {
+ zwp_primary_selection_offer_v1_add_listener(
+ aPrimaryDataOffer, &primary_selection_offer_listener_zwp_v1, this);
+}
+
+PrimaryDataOffer::~PrimaryDataOffer(void) {
+ if (mPrimaryDataOfferGtk) {
+ gtk_primary_selection_offer_destroy(mPrimaryDataOfferGtk);
+ }
+ if (mPrimaryDataOfferZwpV1) {
+ zwp_primary_selection_offer_v1_destroy(mPrimaryDataOfferZwpV1);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsWaylandDragContext, nsISupports);
+
+nsWaylandDragContext::nsWaylandDragContext(WaylandDataOffer* aDataOffer,
+ wl_display* aDisplay)
+ : mDataOffer(aDataOffer),
+ mDisplay(aDisplay),
+ mTime(0),
+ mGtkWidget(nullptr),
+ mX(0),
+ mY(0) {
+ aDataOffer->SetWaylandDragContext(this);
+}
+
+void nsWaylandDragContext::DropDataEnter(GtkWidget* aGtkWidget, uint32_t aTime,
+ nscoord aX, nscoord aY) {
+ mTime = aTime;
+ mGtkWidget = aGtkWidget;
+ mX = aX;
+ mY = aY;
+}
+
+void nsWaylandDragContext::DropMotion(uint32_t aTime, nscoord aX, nscoord aY) {
+ mTime = aTime;
+ mX = aX;
+ mY = aY;
+}
+
+void nsWaylandDragContext::GetLastDropInfo(uint32_t* aTime, nscoord* aX,
+ nscoord* aY) {
+ *aTime = mTime;
+ *aX = mX;
+ *aY = mY;
+}
+
+void nsWaylandDragContext::SetDragStatus(GdkDragAction aPreferredAction) {
+ mDataOffer->SetDragStatus(aPreferredAction, mTime);
+}
+
+GdkDragAction nsWaylandDragContext::GetAvailableDragActions() {
+ GdkDragAction gdkAction = mDataOffer->GetSelectedDragAction();
+
+ // We emulate gdk_drag_context_get_actions() here.
+ if (!gdkAction) {
+ gdkAction = mDataOffer->GetAvailableDragActions();
+ }
+
+ return gdkAction;
+}
+
+GList* nsWaylandDragContext::GetTargets() {
+ int targetNums;
+ GdkAtom* atoms = mDataOffer->GetTargets(&targetNums);
+
+ GList* targetList = nullptr;
+ for (int i = 0; i < targetNums; i++) {
+ targetList = g_list_append(targetList, GDK_ATOM_TO_POINTER(atoms[i]));
+ }
+
+ return targetList;
+}
+
+char* nsWaylandDragContext::GetData(const char* aMimeType,
+ uint32_t* aContentLength) {
+ mDataOffer->DragOfferAccept(aMimeType, mTime);
+ return mDataOffer->GetData(mDisplay, aMimeType, aContentLength);
+}
+
+void nsRetrievalContextWayland::RegisterNewDataOffer(
+ wl_data_offer* aWaylandDataOffer) {
+ DataOffer* dataOffer = static_cast<DataOffer*>(
+ g_hash_table_lookup(mActiveOffers, aWaylandDataOffer));
+ MOZ_ASSERT(
+ dataOffer == nullptr,
+ "Registered WaylandDataOffer already exists. Wayland protocol error?");
+
+ if (!dataOffer) {
+ dataOffer = new WaylandDataOffer(aWaylandDataOffer);
+ g_hash_table_insert(mActiveOffers, aWaylandDataOffer, dataOffer);
+ }
+}
+
+void nsRetrievalContextWayland::RegisterNewDataOffer(
+ gtk_primary_selection_offer* aPrimaryDataOffer) {
+ DataOffer* dataOffer = static_cast<DataOffer*>(
+ g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
+ MOZ_ASSERT(
+ dataOffer == nullptr,
+ "Registered PrimaryDataOffer already exists. Wayland protocol error?");
+
+ if (!dataOffer) {
+ dataOffer = new PrimaryDataOffer(aPrimaryDataOffer);
+ g_hash_table_insert(mActiveOffers, aPrimaryDataOffer, dataOffer);
+ }
+}
+
+void nsRetrievalContextWayland::RegisterNewDataOffer(
+ zwp_primary_selection_offer_v1* aPrimaryDataOffer) {
+ DataOffer* dataOffer = static_cast<DataOffer*>(
+ g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
+ MOZ_ASSERT(
+ dataOffer == nullptr,
+ "Registered PrimaryDataOffer already exists. Wayland protocol error?");
+
+ if (!dataOffer) {
+ dataOffer = new PrimaryDataOffer(aPrimaryDataOffer);
+ g_hash_table_insert(mActiveOffers, aPrimaryDataOffer, dataOffer);
+ }
+}
+
+void nsRetrievalContextWayland::SetClipboardDataOffer(
+ wl_data_offer* aWaylandDataOffer) {
+ // Delete existing clipboard data offer
+ mClipboardOffer = nullptr;
+
+ // null aWaylandDataOffer indicates that our clipboard content
+ // is no longer valid and should be release.
+ if (aWaylandDataOffer != nullptr) {
+ DataOffer* dataOffer = static_cast<DataOffer*>(
+ g_hash_table_lookup(mActiveOffers, aWaylandDataOffer));
+ NS_ASSERTION(dataOffer, "We're missing stored clipboard data offer!");
+ if (dataOffer) {
+ g_hash_table_remove(mActiveOffers, aWaylandDataOffer);
+ mClipboardOffer = WrapUnique(dataOffer);
+ }
+ }
+}
+
+void nsRetrievalContextWayland::SetPrimaryDataOffer(
+ gtk_primary_selection_offer* aPrimaryDataOffer) {
+ // Release any primary offer we have.
+ mPrimaryOffer = nullptr;
+
+ // aPrimaryDataOffer can be null which means we lost
+ // the mouse selection.
+ if (aPrimaryDataOffer) {
+ DataOffer* dataOffer = static_cast<DataOffer*>(
+ g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
+ NS_ASSERTION(dataOffer, "We're missing primary data offer!");
+ if (dataOffer) {
+ g_hash_table_remove(mActiveOffers, aPrimaryDataOffer);
+ mPrimaryOffer = WrapUnique(dataOffer);
+ }
+ }
+}
+
+void nsRetrievalContextWayland::SetPrimaryDataOffer(
+ zwp_primary_selection_offer_v1* aPrimaryDataOffer) {
+ // Release any primary offer we have.
+ mPrimaryOffer = nullptr;
+
+ // aPrimaryDataOffer can be null which means we lost
+ // the mouse selection.
+ if (aPrimaryDataOffer) {
+ DataOffer* dataOffer = static_cast<DataOffer*>(
+ g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
+ NS_ASSERTION(dataOffer, "We're missing primary data offer!");
+ if (dataOffer) {
+ g_hash_table_remove(mActiveOffers, aPrimaryDataOffer);
+ mPrimaryOffer = WrapUnique(dataOffer);
+ }
+ }
+}
+
+void nsRetrievalContextWayland::AddDragAndDropDataOffer(
+ wl_data_offer* aDropDataOffer) {
+ // Remove any existing D&D contexts.
+ mDragContext = nullptr;
+
+ WaylandDataOffer* dataOffer = static_cast<WaylandDataOffer*>(
+ g_hash_table_lookup(mActiveOffers, aDropDataOffer));
+ NS_ASSERTION(dataOffer, "We're missing drag and drop data offer!");
+ if (dataOffer) {
+ g_hash_table_remove(mActiveOffers, aDropDataOffer);
+ mDragContext = new nsWaylandDragContext(dataOffer, mDisplay->GetDisplay());
+ }
+}
+
+nsWaylandDragContext* nsRetrievalContextWayland::GetDragContext(void) {
+ return mDragContext;
+}
+
+void nsRetrievalContextWayland::ClearDragAndDropDataOffer(void) {
+ mDragContext = nullptr;
+}
+
+// We have a new fresh data content.
+// We should attach listeners to it and save for further use.
+static void data_device_data_offer(void* data,
+ struct wl_data_device* data_device,
+ struct wl_data_offer* offer) {
+ LOGCLIP(("data_device_data_offer() callback\n"));
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ context->RegisterNewDataOffer(offer);
+}
+
+// The new fresh data content is clipboard.
+static void data_device_selection(void* data,
+ struct wl_data_device* wl_data_device,
+ struct wl_data_offer* offer) {
+ LOGCLIP(("data_device_selection() callback\n"));
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ context->SetClipboardDataOffer(offer);
+}
+
+// The new fresh wayland data content is drag and drop.
+static void data_device_enter(void* data, struct wl_data_device* data_device,
+ uint32_t time, struct wl_surface* surface,
+ int32_t x_fixed, int32_t y_fixed,
+ struct wl_data_offer* offer) {
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ context->AddDragAndDropDataOffer(offer);
+
+ nsWaylandDragContext* dragContext = context->GetDragContext();
+
+ GtkWidget* gtkWidget = get_gtk_widget_for_wl_surface(surface);
+ if (!gtkWidget) {
+ NS_WARNING("DragAndDrop: Unable to get GtkWidget for wl_surface!");
+ return;
+ }
+
+ LOGDRAG(("nsWindow data_device_enter for GtkWidget %p\n", (void*)gtkWidget));
+ dragContext->DropDataEnter(gtkWidget, time, wl_fixed_to_int(x_fixed),
+ wl_fixed_to_int(y_fixed));
+}
+
+static void data_device_leave(void* data, struct wl_data_device* data_device) {
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+
+ nsWaylandDragContext* dropContext = context->GetDragContext();
+ WindowDragLeaveHandler(dropContext->GetWidget());
+
+ LOGDRAG(("nsWindow data_device_leave for GtkWidget %p\n",
+ (void*)dropContext->GetWidget()));
+ context->ClearDragAndDropDataOffer();
+}
+
+static void data_device_motion(void* data, struct wl_data_device* data_device,
+ uint32_t time, int32_t x_fixed,
+ int32_t y_fixed) {
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+
+ nsWaylandDragContext* dropContext = context->GetDragContext();
+
+ nscoord x = wl_fixed_to_int(x_fixed);
+ nscoord y = wl_fixed_to_int(y_fixed);
+ dropContext->DropMotion(time, x, y);
+
+ LOGDRAG(("nsWindow data_device_motion for GtkWidget %p\n",
+ (void*)dropContext->GetWidget()));
+ WindowDragMotionHandler(dropContext->GetWidget(), nullptr, dropContext, x, y,
+ time);
+}
+
+static void data_device_drop(void* data, struct wl_data_device* data_device) {
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ nsWaylandDragContext* dropContext = context->GetDragContext();
+
+ uint32_t time;
+ nscoord x, y;
+ dropContext->GetLastDropInfo(&time, &x, &y);
+
+ LOGDRAG(("nsWindow data_device_drop GtkWidget %p\n",
+ (void*)dropContext->GetWidget()));
+ WindowDragDropHandler(dropContext->GetWidget(), nullptr, dropContext, x, y,
+ time);
+}
+
+/* wl_data_device callback description:
+ *
+ * data_device_data_offer - It's called when there's a new wl_data_offer
+ * available. We need to attach wl_data_offer_listener
+ * to it to get available MIME types.
+ *
+ * data_device_selection - It's called when the new wl_data_offer
+ * is a clipboard content.
+ *
+ * data_device_enter - It's called when the new wl_data_offer is a drag & drop
+ * content and it's tied to actual wl_surface.
+ * data_device_leave - It's called when the wl_data_offer (drag & dop) is not
+ * valid any more.
+ * data_device_motion - It's called when the drag and drop selection moves
+ * across wl_surface.
+ * data_device_drop - It's called when D&D operation is sucessfully finished
+ * and we can read the data from D&D.
+ * It's generated only if we call wl_data_offer_accept() and
+ * wl_data_offer_set_actions() from data_device_motion
+ * callback.
+ */
+static const struct wl_data_device_listener data_device_listener = {
+ data_device_data_offer, data_device_enter, data_device_leave,
+ data_device_motion, data_device_drop, data_device_selection};
+
+static void primary_selection_data_offer(
+ void* data, struct gtk_primary_selection_device* primary_selection_device,
+ struct gtk_primary_selection_offer* primary_offer) {
+ LOGCLIP(("primary_selection_data_offer() callback\n"));
+ // create and add listener
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ context->RegisterNewDataOffer(primary_offer);
+}
+
+static void primary_selection_data_offer(
+ void* data,
+ struct zwp_primary_selection_device_v1* primary_selection_device,
+ struct zwp_primary_selection_offer_v1* primary_offer) {
+ LOGCLIP(("primary_selection_data_offer() callback\n"));
+ // create and add listener
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ context->RegisterNewDataOffer(primary_offer);
+}
+
+static void primary_selection_selection(
+ void* data, struct gtk_primary_selection_device* primary_selection_device,
+ struct gtk_primary_selection_offer* primary_offer) {
+ LOGCLIP(("primary_selection_selection() callback\n"));
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ context->SetPrimaryDataOffer(primary_offer);
+}
+
+static void primary_selection_selection(
+ void* data,
+ struct zwp_primary_selection_device_v1* primary_selection_device,
+ struct zwp_primary_selection_offer_v1* primary_offer) {
+ LOGCLIP(("primary_selection_selection() callback\n"));
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ context->SetPrimaryDataOffer(primary_offer);
+}
+
+/* gtk_primary_selection_device callback description:
+ *
+ * primary_selection_data_offer - It's called when there's a new
+ * gtk_primary_selection_offer available. We need to
+ * attach gtk_primary_selection_offer_listener to it
+ * to get available MIME types.
+ *
+ * primary_selection_selection - It's called when the new
+ * gtk_primary_selection_offer is a primary selection
+ * content. It can be also called with
+ * gtk_primary_selection_offer = null which means
+ * there's no primary selection.
+ */
+static const struct gtk_primary_selection_device_listener
+ primary_selection_device_listener_gtk = {
+ primary_selection_data_offer,
+ primary_selection_selection,
+};
+
+static const struct zwp_primary_selection_device_v1_listener
+ primary_selection_device_listener_zwp_v1 = {
+ primary_selection_data_offer,
+ primary_selection_selection,
+};
+
+bool nsRetrievalContextWayland::HasSelectionSupport(void) {
+ return (mDisplay->GetPrimarySelectionDeviceManagerZwpV1() != nullptr ||
+ mDisplay->GetPrimarySelectionDeviceManagerGtk() != nullptr);
+}
+
+nsRetrievalContextWayland::nsRetrievalContextWayland(void)
+ : mInitialized(false),
+ mDisplay(WaylandDisplayGet()),
+ mActiveOffers(g_hash_table_new(NULL, NULL)),
+ mClipboardOffer(nullptr),
+ mPrimaryOffer(nullptr),
+ mDragContext(nullptr),
+ mClipboardRequestNumber(0),
+ mClipboardData(nullptr),
+ mClipboardDataLength(0) {
+ wl_data_device* dataDevice = wl_data_device_manager_get_data_device(
+ mDisplay->GetDataDeviceManager(), mDisplay->GetSeat());
+ wl_data_device_add_listener(dataDevice, &data_device_listener, this);
+
+ if (mDisplay->GetPrimarySelectionDeviceManagerZwpV1()) {
+ zwp_primary_selection_device_v1* primaryDataDevice =
+ zwp_primary_selection_device_manager_v1_get_device(
+ mDisplay->GetPrimarySelectionDeviceManagerZwpV1(),
+ mDisplay->GetSeat());
+ zwp_primary_selection_device_v1_add_listener(
+ primaryDataDevice, &primary_selection_device_listener_zwp_v1, this);
+ } else if (mDisplay->GetPrimarySelectionDeviceManagerGtk()) {
+ gtk_primary_selection_device* primaryDataDevice =
+ gtk_primary_selection_device_manager_get_device(
+ mDisplay->GetPrimarySelectionDeviceManagerGtk(),
+ mDisplay->GetSeat());
+ gtk_primary_selection_device_add_listener(
+ primaryDataDevice, &primary_selection_device_listener_gtk, this);
+ }
+
+ mInitialized = true;
+}
+
+static gboolean offer_hash_remove(gpointer wl_offer, gpointer aDataOffer,
+ gpointer user_data) {
+#ifdef DEBUG
+ nsPrintfCString msg("nsRetrievalContextWayland(): leaked nsDataOffer %p\n",
+ aDataOffer);
+ NS_WARNING(msg.get());
+#endif
+ delete static_cast<DataOffer*>(aDataOffer);
+ return true;
+}
+
+nsRetrievalContextWayland::~nsRetrievalContextWayland(void) {
+ g_hash_table_foreach_remove(mActiveOffers, offer_hash_remove, nullptr);
+ g_hash_table_destroy(mActiveOffers);
+}
+
+GdkAtom* nsRetrievalContextWayland::GetTargets(int32_t aWhichClipboard,
+ int* aTargetNum) {
+ if (GetSelectionAtom(aWhichClipboard) == GDK_SELECTION_CLIPBOARD) {
+ if (mClipboardOffer) {
+ return mClipboardOffer->GetTargets(aTargetNum);
+ }
+ } else {
+ if (mPrimaryOffer) {
+ return mPrimaryOffer->GetTargets(aTargetNum);
+ }
+ }
+
+ *aTargetNum = 0;
+ return nullptr;
+}
+
+struct FastTrackClipboard {
+ FastTrackClipboard(int aClipboardRequestNumber,
+ nsRetrievalContextWayland* aRetrievalContex)
+ : mClipboardRequestNumber(aClipboardRequestNumber),
+ mRetrievalContex(aRetrievalContex) {}
+
+ int mClipboardRequestNumber;
+ nsRetrievalContextWayland* mRetrievalContex;
+};
+
+static void wayland_clipboard_contents_received(
+ GtkClipboard* clipboard, GtkSelectionData* selection_data, gpointer data) {
+ LOGCLIP(("wayland_clipboard_contents_received() callback\n"));
+ FastTrackClipboard* fastTrack = static_cast<FastTrackClipboard*>(data);
+ fastTrack->mRetrievalContex->TransferFastTrackClipboard(
+ fastTrack->mClipboardRequestNumber, selection_data);
+ delete fastTrack;
+}
+
+void nsRetrievalContextWayland::TransferFastTrackClipboard(
+ int aClipboardRequestNumber, GtkSelectionData* aSelectionData) {
+ if (mClipboardRequestNumber == aClipboardRequestNumber) {
+ int dataLength = gtk_selection_data_get_length(aSelectionData);
+ if (dataLength > 0) {
+ mClipboardDataLength = dataLength;
+ mClipboardData = reinterpret_cast<char*>(
+ g_malloc(sizeof(char) * (mClipboardDataLength + 1)));
+ memcpy(mClipboardData, gtk_selection_data_get_data(aSelectionData),
+ sizeof(char) * mClipboardDataLength);
+ mClipboardData[mClipboardDataLength] = '\0';
+ }
+ } else {
+ NS_WARNING("Received obsoleted clipboard data!");
+ }
+}
+
+const char* nsRetrievalContextWayland::GetClipboardData(
+ const char* aMimeType, int32_t aWhichClipboard, uint32_t* aContentLength) {
+ NS_ASSERTION(mClipboardData == nullptr && mClipboardDataLength == 0,
+ "Looks like we're leaking clipboard data here!");
+
+ LOGCLIP(("nsRetrievalContextWayland::GetClipboardData [%p] mime %s\n", this,
+ aMimeType));
+
+ /* If actual clipboard data is owned by us we don't need to go
+ * through Wayland but we ask Gtk+ to directly call data
+ * getter callback nsClipboard::SelectionGetEvent().
+ * see gtk_selection_convert() at gtk+/gtkselection.c.
+ */
+ GdkAtom selection = GetSelectionAtom(aWhichClipboard);
+ if (gdk_selection_owner_get(selection)) {
+ LOGCLIP((" Internal clipboard content\n"));
+ mClipboardRequestNumber++;
+ gtk_clipboard_request_contents(
+ gtk_clipboard_get(selection), gdk_atom_intern(aMimeType, FALSE),
+ wayland_clipboard_contents_received,
+ new FastTrackClipboard(mClipboardRequestNumber, this));
+ } else {
+ LOGCLIP((" Remote clipboard content\n"));
+ const auto& dataOffer =
+ (selection == GDK_SELECTION_PRIMARY) ? mPrimaryOffer : mClipboardOffer;
+ if (!dataOffer) {
+ // Something went wrong. We're requested to provide clipboard data
+ // but we haven't got any from wayland.
+ NS_WARNING("Requested data without valid DataOffer!");
+ mClipboardData = nullptr;
+ mClipboardDataLength = 0;
+ } else {
+ mClipboardData = dataOffer->GetData(mDisplay->GetDisplay(), aMimeType,
+ &mClipboardDataLength);
+ }
+ }
+
+ *aContentLength = mClipboardDataLength;
+ return reinterpret_cast<const char*>(mClipboardData);
+}
+
+const char* nsRetrievalContextWayland::GetClipboardText(
+ int32_t aWhichClipboard) {
+ LOGCLIP(("nsRetrievalContextWayland::GetClipboardText [%p]\n", this));
+
+ GdkAtom selection = GetSelectionAtom(aWhichClipboard);
+ const auto& dataOffer =
+ (selection == GDK_SELECTION_PRIMARY) ? mPrimaryOffer : mClipboardOffer;
+ if (!dataOffer) return nullptr;
+
+ for (unsigned int i = 0; i < TEXT_MIME_TYPES_NUM; i++) {
+ if (dataOffer->HasTarget(sTextMimeTypes[i])) {
+ uint32_t unused;
+ return GetClipboardData(sTextMimeTypes[i], aWhichClipboard, &unused);
+ }
+ }
+ return nullptr;
+}
+
+void nsRetrievalContextWayland::ReleaseClipboardData(
+ const char* aClipboardData) {
+ LOGCLIP(("nsRetrievalContextWayland::ReleaseClipboardData [%p]\n", this));
+
+ NS_ASSERTION(aClipboardData == mClipboardData,
+ "Releasing unknown clipboard data!");
+ g_free((void*)aClipboardData);
+
+ mClipboardData = nullptr;
+ mClipboardDataLength = 0;
+}
diff --git a/widget/gtk/nsClipboardWayland.h b/widget/gtk/nsClipboardWayland.h
new file mode 100644
index 0000000000..ddf5d40dc6
--- /dev/null
+++ b/widget/gtk/nsClipboardWayland.h
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __nsClipboardWayland_h_
+#define __nsClipboardWayland_h_
+
+#include <gtk/gtk.h>
+#include <gdk/gdkwayland.h>
+#include <nsTArray.h>
+
+#include "mozilla/UniquePtr.h"
+#include "nsClipboard.h"
+#include "nsWaylandDisplay.h"
+
+struct FastTrackClipboard;
+
+class DataOffer {
+ public:
+ void AddMIMEType(const char* aMimeType);
+
+ GdkAtom* GetTargets(int* aTargetNum);
+ bool HasTarget(const char* aMimeType);
+
+ char* GetData(wl_display* aDisplay, const char* aMimeType,
+ uint32_t* aContentLength);
+
+ virtual ~DataOffer() = default;
+
+ private:
+ virtual bool RequestDataTransfer(const char* aMimeType, int fd) = 0;
+
+ protected:
+ nsTArray<GdkAtom> mTargetMIMETypes;
+};
+
+class WaylandDataOffer : public DataOffer {
+ public:
+ explicit WaylandDataOffer(wl_data_offer* aWaylandDataOffer);
+
+ void DragOfferAccept(const char* aMimeType, uint32_t aTime);
+ void SetDragStatus(GdkDragAction aPreferredAction, uint32_t aTime);
+
+ GdkDragAction GetSelectedDragAction();
+ void SetSelectedDragAction(uint32_t aWaylandAction);
+
+ void SetAvailableDragActions(uint32_t aWaylandActions);
+ GdkDragAction GetAvailableDragActions();
+
+ void SetWaylandDragContext(nsWaylandDragContext* aDragContext);
+ nsWaylandDragContext* GetWaylandDragContext();
+
+ virtual ~WaylandDataOffer();
+
+ private:
+ bool RequestDataTransfer(const char* aMimeType, int fd) override;
+
+ wl_data_offer* mWaylandDataOffer;
+ RefPtr<nsWaylandDragContext> mDragContext;
+ uint32_t mSelectedDragAction;
+ uint32_t mAvailableDragActions;
+};
+
+class PrimaryDataOffer : public DataOffer {
+ public:
+ explicit PrimaryDataOffer(gtk_primary_selection_offer* aPrimaryDataOffer);
+ explicit PrimaryDataOffer(zwp_primary_selection_offer_v1* aPrimaryDataOffer);
+ void SetAvailableDragActions(uint32_t aWaylandActions){};
+
+ virtual ~PrimaryDataOffer();
+
+ private:
+ bool RequestDataTransfer(const char* aMimeType, int fd) override;
+
+ gtk_primary_selection_offer* mPrimaryDataOfferGtk;
+ zwp_primary_selection_offer_v1* mPrimaryDataOfferZwpV1;
+};
+
+class nsWaylandDragContext : public nsISupports {
+ NS_DECL_ISUPPORTS
+
+ public:
+ nsWaylandDragContext(WaylandDataOffer* aWaylandDataOffer,
+ wl_display* aDisplay);
+
+ void DropDataEnter(GtkWidget* aGtkWidget, uint32_t aTime, nscoord aX,
+ nscoord aY);
+ void DropMotion(uint32_t aTime, nscoord aX, nscoord aY);
+ void GetLastDropInfo(uint32_t* aTime, nscoord* aX, nscoord* aY);
+
+ void SetDragStatus(GdkDragAction aPreferredAction);
+ GdkDragAction GetAvailableDragActions();
+
+ GtkWidget* GetWidget() { return mGtkWidget; }
+ GList* GetTargets();
+ char* GetData(const char* aMimeType, uint32_t* aContentLength);
+
+ private:
+ virtual ~nsWaylandDragContext() = default;
+
+ mozilla::UniquePtr<WaylandDataOffer> mDataOffer;
+ wl_display* mDisplay;
+ uint32_t mTime;
+ GtkWidget* mGtkWidget;
+ nscoord mX, mY;
+};
+
+class nsRetrievalContextWayland : public nsRetrievalContext {
+ public:
+ nsRetrievalContextWayland();
+
+ virtual const char* GetClipboardData(const char* aMimeType,
+ int32_t aWhichClipboard,
+ uint32_t* aContentLength) override;
+ virtual const char* GetClipboardText(int32_t aWhichClipboard) override;
+ virtual void ReleaseClipboardData(const char* aClipboardData) override;
+
+ virtual GdkAtom* GetTargets(int32_t aWhichClipboard,
+ int* aTargetNum) override;
+ virtual bool HasSelectionSupport(void) override;
+
+ void RegisterNewDataOffer(wl_data_offer* aWaylandDataOffer);
+ void RegisterNewDataOffer(gtk_primary_selection_offer* aPrimaryDataOffer);
+ void RegisterNewDataOffer(zwp_primary_selection_offer_v1* aPrimaryDataOffer);
+
+ void SetClipboardDataOffer(wl_data_offer* aWaylandDataOffer);
+ void SetPrimaryDataOffer(gtk_primary_selection_offer* aPrimaryDataOffer);
+ void SetPrimaryDataOffer(zwp_primary_selection_offer_v1* aPrimaryDataOffer);
+ void AddDragAndDropDataOffer(wl_data_offer* aWaylandDataOffer);
+ nsWaylandDragContext* GetDragContext();
+
+ void ClearDragAndDropDataOffer();
+
+ void TransferFastTrackClipboard(int aClipboardRequestNumber,
+ GtkSelectionData* aSelectionData);
+
+ virtual ~nsRetrievalContextWayland() override;
+
+ private:
+ bool mInitialized;
+ RefPtr<mozilla::widget::nsWaylandDisplay> mDisplay;
+
+ // Data offers provided by Wayland data device
+ GHashTable* mActiveOffers;
+ mozilla::UniquePtr<DataOffer> mClipboardOffer;
+ mozilla::UniquePtr<DataOffer> mPrimaryOffer;
+ RefPtr<nsWaylandDragContext> mDragContext;
+
+ int mClipboardRequestNumber;
+ char* mClipboardData;
+ uint32_t mClipboardDataLength;
+
+// Mime types used for text data at Gtk+, see request_text_received_func()
+// at gtkclipboard.c.
+#define TEXT_MIME_TYPES_NUM 3
+ static const char* sTextMimeTypes[TEXT_MIME_TYPES_NUM];
+};
+
+#endif /* __nsClipboardWayland_h_ */
diff --git a/widget/gtk/nsClipboardX11.cpp b/widget/gtk/nsClipboardX11.cpp
new file mode 100644
index 0000000000..0439c2e68c
--- /dev/null
+++ b/widget/gtk/nsClipboardX11.cpp
@@ -0,0 +1,340 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#include "nsClipboardX11.h"
+#include "nsSupportsPrimitives.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsImageToPixbuf.h"
+#include "nsStringStream.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+
+#include <gtk/gtk.h>
+
+// For manipulation of the X event queue
+#include <X11/Xlib.h>
+#include <poll.h>
+#include <gdk/gdkx.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+#include "X11UndefineNone.h"
+
+using namespace mozilla;
+
+bool nsRetrievalContextX11::HasSelectionSupport(void) {
+ // yeah, unix supports the selection clipboard on X11.
+ return true;
+}
+
+nsRetrievalContextX11::nsRetrievalContextX11()
+ : mState(INITIAL),
+ mClipboardRequestNumber(0),
+ mClipboardData(nullptr),
+ mClipboardDataLength(0),
+ mTargetMIMEType(gdk_atom_intern("TARGETS", FALSE)) {}
+
+static void DispatchSelectionNotifyEvent(GtkWidget* widget, XEvent* xevent) {
+ GdkEvent event = {};
+ event.selection.type = GDK_SELECTION_NOTIFY;
+ event.selection.window = gtk_widget_get_window(widget);
+ event.selection.selection =
+ gdk_x11_xatom_to_atom(xevent->xselection.selection);
+ event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
+ event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property);
+ event.selection.time = xevent->xselection.time;
+
+ gtk_widget_event(widget, &event);
+}
+
+static void DispatchPropertyNotifyEvent(GtkWidget* widget, XEvent* xevent) {
+ GdkWindow* window = gtk_widget_get_window(widget);
+ if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) {
+ GdkEvent event = {};
+ event.property.type = GDK_PROPERTY_NOTIFY;
+ event.property.window = window;
+ event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
+ event.property.time = xevent->xproperty.time;
+ event.property.state = xevent->xproperty.state;
+
+ gtk_widget_event(widget, &event);
+ }
+}
+
+struct checkEventContext {
+ GtkWidget* cbWidget;
+ Atom selAtom;
+};
+
+static Bool checkEventProc(Display* display, XEvent* event, XPointer arg) {
+ checkEventContext* context = (checkEventContext*)arg;
+
+ if (event->xany.type == SelectionNotify ||
+ (event->xany.type == PropertyNotify &&
+ event->xproperty.atom == context->selAtom)) {
+ GdkWindow* cbWindow = gdk_x11_window_lookup_for_display(
+ gdk_x11_lookup_xdisplay(display), event->xany.window);
+ if (cbWindow) {
+ GtkWidget* cbWidget = nullptr;
+ gdk_window_get_user_data(cbWindow, (gpointer*)&cbWidget);
+ if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
+ context->cbWidget = cbWidget;
+ return X11True;
+ }
+ }
+ }
+
+ return X11False;
+}
+
+bool nsRetrievalContextX11::WaitForX11Content() {
+ if (mState == COMPLETED) { // the request completed synchronously
+ return true;
+ }
+
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ // gdk_display_get_default() returns null on headless
+ if (gdkDisplay && GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ Display* xDisplay = GDK_DISPLAY_XDISPLAY(gdkDisplay);
+ checkEventContext context;
+ context.cbWidget = nullptr;
+ context.selAtom =
+ gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION", FALSE));
+
+ // Send X events which are relevant to the ongoing selection retrieval
+ // to the clipboard widget. Wait until either the operation completes, or
+ // we hit our timeout. All other X events remain queued.
+
+ int poll_result;
+
+ struct pollfd pfd;
+ pfd.fd = ConnectionNumber(xDisplay);
+ pfd.events = POLLIN;
+ TimeStamp start = TimeStamp::Now();
+
+ do {
+ XEvent xevent;
+
+ while (XCheckIfEvent(xDisplay, &xevent, checkEventProc,
+ (XPointer)&context)) {
+ if (xevent.xany.type == SelectionNotify)
+ DispatchSelectionNotifyEvent(context.cbWidget, &xevent);
+ else
+ DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
+
+ if (mState == COMPLETED) {
+ return true;
+ }
+ }
+
+ TimeStamp now = TimeStamp::Now();
+ int timeout = std::max<int>(
+ 0, kClipboardTimeout / 1000 - (now - start).ToMilliseconds());
+ poll_result = poll(&pfd, 1, timeout);
+ } while ((poll_result == 1 && (pfd.revents & (POLLHUP | POLLERR)) == 0) ||
+ (poll_result == -1 && errno == EINTR));
+ }
+#ifdef DEBUG_CLIPBOARD
+ printf("exceeded clipboard timeout\n");
+#endif
+ mState = TIMED_OUT;
+ return false;
+}
+
+// Call this when data has been retrieved.
+void nsRetrievalContextX11::Complete(ClipboardDataType aDataType,
+ const void* aData,
+ int aDataRequestNumber) {
+ LOGCLIP(("nsRetrievalContextX11::Complete\n"));
+
+ if (mClipboardRequestNumber != aDataRequestNumber) {
+ NS_WARNING(
+ "nsRetrievalContextX11::Complete() got obsoleted clipboard data.");
+ return;
+ }
+
+ if (mState == INITIAL) {
+ mState = COMPLETED;
+
+ MOZ_ASSERT(mClipboardData == nullptr && mClipboardDataLength == 0,
+ "We're leaking clipboard data!");
+
+ switch (aDataType) {
+ case CLIPBOARD_TEXT: {
+ const char* text = static_cast<const char*>(aData);
+ if (text) {
+ mClipboardDataLength = sizeof(char) * (strlen(text) + 1);
+ mClipboardData = moz_xmalloc(mClipboardDataLength);
+ memcpy(mClipboardData, text, mClipboardDataLength);
+ }
+ } break;
+ case CLIPBOARD_TARGETS: {
+ const GtkSelectionData* selection =
+ static_cast<const GtkSelectionData*>(aData);
+
+ gint n_targets = 0;
+ GdkAtom* targets = nullptr;
+
+ if (!gtk_selection_data_get_targets(selection, &targets, &n_targets) ||
+ !n_targets) {
+ return;
+ }
+
+ mClipboardData = targets;
+ mClipboardDataLength = n_targets;
+ } break;
+ case CLIPBOARD_DATA: {
+ const GtkSelectionData* selection =
+ static_cast<const GtkSelectionData*>(aData);
+
+ gint dataLength = gtk_selection_data_get_length(selection);
+ if (dataLength > 0) {
+ mClipboardDataLength = dataLength;
+ mClipboardData = moz_xmalloc(dataLength);
+ memcpy(mClipboardData, gtk_selection_data_get_data(selection),
+ dataLength);
+ }
+ } break;
+ }
+ } else {
+ // Already timed out
+ MOZ_ASSERT(mState == TIMED_OUT);
+ }
+}
+
+static void clipboard_contents_received(GtkClipboard* clipboard,
+ GtkSelectionData* selection_data,
+ gpointer data) {
+ int whichClipboard = GetGeckoClipboardType(clipboard);
+ LOGCLIP(("clipboard_contents_received (%s) callback\n",
+ whichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard"));
+
+ ClipboardRequestHandler* handler =
+ static_cast<ClipboardRequestHandler*>(data);
+ handler->Complete(selection_data);
+ delete handler;
+}
+
+static void clipboard_text_received(GtkClipboard* clipboard, const gchar* text,
+ gpointer data) {
+ int whichClipboard = GetGeckoClipboardType(clipboard);
+ LOGCLIP(("clipboard_text_received (%s) callback\n",
+ whichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard"));
+
+ ClipboardRequestHandler* handler =
+ static_cast<ClipboardRequestHandler*>(data);
+ handler->Complete(text);
+ delete handler;
+}
+
+bool nsRetrievalContextX11::WaitForClipboardData(ClipboardDataType aDataType,
+ GtkClipboard* clipboard,
+ const char* aMimeType) {
+ LOGCLIP(("nsRetrievalContextX11::WaitForClipboardData\n"));
+
+ mState = INITIAL;
+ NS_ASSERTION(!mClipboardData, "Leaking clipboard content!");
+
+ // Call ClipboardRequestHandler() with unique clipboard request number.
+ // The request number pairs gtk_clipboard_request_contents() data request
+ // with clipboard_contents_received() callback where the data
+ // is provided by Gtk.
+ mClipboardRequestNumber++;
+ ClipboardRequestHandler* handler =
+ new ClipboardRequestHandler(this, aDataType, mClipboardRequestNumber);
+
+ switch (aDataType) {
+ case CLIPBOARD_DATA:
+ gtk_clipboard_request_contents(clipboard,
+ gdk_atom_intern(aMimeType, FALSE),
+ clipboard_contents_received, handler);
+ break;
+ case CLIPBOARD_TEXT:
+ gtk_clipboard_request_text(clipboard, clipboard_text_received, handler);
+ break;
+ case CLIPBOARD_TARGETS:
+ gtk_clipboard_request_contents(clipboard, mTargetMIMEType,
+ clipboard_contents_received, handler);
+ break;
+ }
+
+ return WaitForX11Content();
+}
+
+GdkAtom* nsRetrievalContextX11::GetTargets(int32_t aWhichClipboard,
+ int* aTargetNums) {
+ LOGCLIP(("nsRetrievalContextX11::GetTargets(%s)\n",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard"));
+
+ GtkClipboard* clipboard =
+ gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+ if (!WaitForClipboardData(CLIPBOARD_TARGETS, clipboard)) {
+ LOGCLIP((" WaitForClipboardData() failed!\n"));
+ return nullptr;
+ }
+
+ *aTargetNums = mClipboardDataLength;
+ GdkAtom* targets = static_cast<GdkAtom*>(mClipboardData);
+
+ // We don't hold the target list internally but we transfer the ownership.
+ mClipboardData = nullptr;
+ mClipboardDataLength = 0;
+
+ LOGCLIP((" returned %d targets\n", *aTargetNums));
+ return targets;
+}
+
+const char* nsRetrievalContextX11::GetClipboardData(const char* aMimeType,
+ int32_t aWhichClipboard,
+ uint32_t* aContentLength) {
+ LOGCLIP(("nsRetrievalContextX11::GetClipboardData(%s)\n",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard"));
+
+ GtkClipboard* clipboard;
+ clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+ if (!WaitForClipboardData(CLIPBOARD_DATA, clipboard, aMimeType))
+ return nullptr;
+
+ *aContentLength = mClipboardDataLength;
+ return static_cast<const char*>(mClipboardData);
+}
+
+const char* nsRetrievalContextX11::GetClipboardText(int32_t aWhichClipboard) {
+ LOGCLIP(("nsRetrievalContextX11::GetClipboardText(%s)\n",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard"));
+
+ GtkClipboard* clipboard;
+ clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+ if (!WaitForClipboardData(CLIPBOARD_TEXT, clipboard)) return nullptr;
+
+ return static_cast<const char*>(mClipboardData);
+}
+
+void nsRetrievalContextX11::ReleaseClipboardData(const char* aClipboardData) {
+ LOGCLIP(("nsRetrievalContextX11::ReleaseClipboardData\n"));
+ NS_ASSERTION(aClipboardData == mClipboardData,
+ "Releasing unknown clipboard data!");
+ free((void*)aClipboardData);
+
+ mClipboardData = nullptr;
+ mClipboardDataLength = 0;
+}
diff --git a/widget/gtk/nsClipboardX11.h b/widget/gtk/nsClipboardX11.h
new file mode 100644
index 0000000000..2363111f74
--- /dev/null
+++ b/widget/gtk/nsClipboardX11.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __nsClipboardX11_h_
+#define __nsClipboardX11_h_
+
+#include <gtk/gtk.h>
+
+enum ClipboardDataType { CLIPBOARD_DATA, CLIPBOARD_TEXT, CLIPBOARD_TARGETS };
+
+class nsRetrievalContextX11 : public nsRetrievalContext {
+ public:
+ enum State { INITIAL, COMPLETED, TIMED_OUT };
+
+ virtual const char* GetClipboardData(const char* aMimeType,
+ int32_t aWhichClipboard,
+ uint32_t* aContentLength) override;
+ virtual const char* GetClipboardText(int32_t aWhichClipboard) override;
+ virtual void ReleaseClipboardData(const char* aClipboardData) override;
+
+ virtual GdkAtom* GetTargets(int32_t aWhichClipboard,
+ int* aTargetNums) override;
+
+ virtual bool HasSelectionSupport(void) override;
+
+ // Call this when data or text has been retrieved.
+ void Complete(ClipboardDataType aDataType, const void* aData,
+ int aDataRequestNumber);
+
+ nsRetrievalContextX11();
+
+ private:
+ bool WaitForClipboardData(ClipboardDataType aDataType,
+ GtkClipboard* clipboard,
+ const char* aMimeType = nullptr);
+
+ /**
+ * Spins X event loop until timing out or being completed. Returns
+ * null if we time out, otherwise returns the completed data (passing
+ * ownership to caller).
+ */
+ bool WaitForX11Content();
+
+ State mState;
+ int mClipboardRequestNumber;
+ void* mClipboardData;
+ uint32_t mClipboardDataLength;
+ GdkAtom mTargetMIMEType;
+};
+
+class ClipboardRequestHandler {
+ public:
+ ClipboardRequestHandler(nsRetrievalContextX11* aContext,
+ ClipboardDataType aDataType, int aDataRequestNumber)
+ : mContext(aContext),
+ mDataRequestNumber(aDataRequestNumber),
+ mDataType(aDataType) {}
+
+ void Complete(const void* aData) {
+ mContext->Complete(mDataType, aData, mDataRequestNumber);
+ }
+
+ private:
+ nsRetrievalContextX11* mContext;
+ int mDataRequestNumber;
+ ClipboardDataType mDataType;
+};
+
+#endif /* __nsClipboardX11_h_ */
diff --git a/widget/gtk/nsColorPicker.cpp b/widget/gtk/nsColorPicker.cpp
new file mode 100644
index 0000000000..21341e552f
--- /dev/null
+++ b/widget/gtk/nsColorPicker.cpp
@@ -0,0 +1,242 @@
+/* -*- 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 <gtk/gtk.h>
+
+#include "nsColor.h"
+#include "nsColorPicker.h"
+#include "nsGtkUtils.h"
+#include "nsIWidget.h"
+#include "WidgetUtils.h"
+#include "nsPIDOMWindow.h"
+
+NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
+
+#if defined(ACTIVATE_GTK3_COLOR_PICKER)
+int nsColorPicker::convertGdkRgbaComponent(gdouble color_component) {
+ // GdkRGBA value is in range [0.0..1.0]. We need something in range [0..255]
+ return color_component * 255 + 0.5;
+}
+
+gdouble nsColorPicker::convertToGdkRgbaComponent(int color_component) {
+ return color_component / 255.0;
+}
+
+GdkRGBA nsColorPicker::convertToRgbaColor(nscolor color) {
+ GdkRGBA result = {convertToGdkRgbaComponent(NS_GET_R(color)),
+ convertToGdkRgbaComponent(NS_GET_G(color)),
+ convertToGdkRgbaComponent(NS_GET_B(color)),
+ convertToGdkRgbaComponent(NS_GET_A(color))};
+
+ return result;
+}
+#else
+int nsColorPicker::convertGdkColorComponent(guint16 color_component) {
+ // GdkColor value is in range [0..65535]. We need something in range [0..255]
+ return (color_component * 255 + 127) / 65535;
+}
+
+guint16 nsColorPicker::convertToGdkColorComponent(int color_component) {
+ return color_component * 65535 / 255;
+}
+
+GdkColor nsColorPicker::convertToGdkColor(nscolor color) {
+ GdkColor result = {0 /* obsolete, unused 'pixel' value */,
+ convertToGdkColorComponent(NS_GET_R(color)),
+ convertToGdkColorComponent(NS_GET_G(color)),
+ convertToGdkColorComponent(NS_GET_B(color))};
+
+ return result;
+}
+
+GtkColorSelection* nsColorPicker::WidgetGetColorSelection(GtkWidget* widget) {
+ return GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(
+ GTK_COLOR_SELECTION_DIALOG(widget)));
+}
+#endif
+
+NS_IMETHODIMP nsColorPicker::Init(mozIDOMWindowProxy* aParent,
+ const nsAString& title,
+ const nsAString& initialColor) {
+ auto* parent = nsPIDOMWindowOuter::From(aParent);
+ mParentWidget = mozilla::widget::WidgetUtils::DOMWindowToWidget(parent);
+ mTitle = title;
+ mInitialColor = initialColor;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsColorPicker::Open(
+ nsIColorPickerShownCallback* aColorPickerShownCallback) {
+ // Input color string should be 7 length (i.e. a string representing a valid
+ // simple color)
+ if (mInitialColor.Length() != 7) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const nsAString& withoutHash = StringTail(mInitialColor, 6);
+ nscolor color;
+ if (!NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mCallback) {
+ // It means Open has already been called: this is not allowed
+ NS_WARNING("mCallback is already set. Open called twice?");
+ return NS_ERROR_FAILURE;
+ }
+ mCallback = aColorPickerShownCallback;
+
+ NS_ConvertUTF16toUTF8 title(mTitle);
+ GtkWindow* parent_window =
+ GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
+
+#if defined(ACTIVATE_GTK3_COLOR_PICKER)
+ GtkWidget* color_chooser = gtk_color_chooser_dialog_new(title, parent_window);
+
+ if (parent_window) {
+ GtkWindow* window = GTK_WINDOW(color_chooser);
+ gtk_window_set_destroy_with_parent(window, TRUE);
+ if (gtk_window_get_modal(parent_window)) {
+ gtk_window_set_modal(window, TRUE);
+ }
+ }
+
+ gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(color_chooser), FALSE);
+ GdkRGBA color_rgba = convertToRgbaColor(color);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(color_chooser), &color_rgba);
+
+ g_signal_connect(GTK_COLOR_CHOOSER(color_chooser), "color-activated",
+ G_CALLBACK(OnColorChanged), this);
+#else
+ GtkWidget* color_chooser = gtk_color_selection_dialog_new(title.get());
+
+ if (parent_window) {
+ GtkWindow* window = GTK_WINDOW(color_chooser);
+ gtk_window_set_transient_for(window, parent_window);
+ gtk_window_set_destroy_with_parent(window, TRUE);
+ if (gtk_window_get_modal(parent_window)) {
+ gtk_window_set_modal(window, TRUE);
+ }
+ }
+
+ GdkColor color_gdk = convertToGdkColor(color);
+ gtk_color_selection_set_current_color(WidgetGetColorSelection(color_chooser),
+ &color_gdk);
+
+ g_signal_connect(WidgetGetColorSelection(color_chooser), "color-changed",
+ G_CALLBACK(OnColorChanged), this);
+#endif
+
+ NS_ADDREF_THIS();
+
+ g_signal_connect(color_chooser, "response", G_CALLBACK(OnResponse), this);
+ g_signal_connect(color_chooser, "destroy", G_CALLBACK(OnDestroy), this);
+ gtk_widget_show(color_chooser);
+
+ return NS_OK;
+}
+
+#if defined(ACTIVATE_GTK3_COLOR_PICKER)
+/* static */
+void nsColorPicker::OnColorChanged(GtkColorChooser* color_chooser,
+ GdkRGBA* color, gpointer user_data) {
+ static_cast<nsColorPicker*>(user_data)->Update(color);
+}
+
+void nsColorPicker::Update(GdkRGBA* color) {
+ SetColor(color);
+ if (mCallback) {
+ mCallback->Update(mColor);
+ }
+}
+
+void nsColorPicker::SetColor(const GdkRGBA* color) {
+ mColor.Assign('#');
+ mColor += ToHexString(convertGdkRgbaComponent(color->red));
+ mColor += ToHexString(convertGdkRgbaComponent(color->green));
+ mColor += ToHexString(convertGdkRgbaComponent(color->blue));
+}
+#else
+/* static */
+void nsColorPicker::OnColorChanged(GtkColorSelection* colorselection,
+ gpointer user_data) {
+ static_cast<nsColorPicker*>(user_data)->Update(colorselection);
+}
+
+void nsColorPicker::Update(GtkColorSelection* colorselection) {
+ ReadValueFromColorSelection(colorselection);
+ if (mCallback) {
+ mCallback->Update(mColor);
+ }
+}
+
+void nsColorPicker::ReadValueFromColorSelection(
+ GtkColorSelection* colorselection) {
+ GdkColor rgba;
+ gtk_color_selection_get_current_color(colorselection, &rgba);
+
+ mColor.Assign('#');
+ mColor += ToHexString(convertGdkColorComponent(rgba.red));
+ mColor += ToHexString(convertGdkColorComponent(rgba.green));
+ mColor += ToHexString(convertGdkColorComponent(rgba.blue));
+}
+#endif
+
+/* static */
+void nsColorPicker::OnResponse(GtkWidget* color_chooser, gint response_id,
+ gpointer user_data) {
+ static_cast<nsColorPicker*>(user_data)->Done(color_chooser, response_id);
+}
+
+/* static */
+void nsColorPicker::OnDestroy(GtkWidget* color_chooser, gpointer user_data) {
+ static_cast<nsColorPicker*>(user_data)->Done(color_chooser,
+ GTK_RESPONSE_CANCEL);
+}
+
+void nsColorPicker::Done(GtkWidget* color_chooser, gint response) {
+ switch (response) {
+ case GTK_RESPONSE_OK:
+ case GTK_RESPONSE_ACCEPT:
+#if defined(ACTIVATE_GTK3_COLOR_PICKER)
+ GdkRGBA color;
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(color_chooser), &color);
+ SetColor(&color);
+#else
+ ReadValueFromColorSelection(WidgetGetColorSelection(color_chooser));
+#endif
+ break;
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_CLOSE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ mColor = mInitialColor;
+ break;
+ default:
+ NS_WARNING("Unexpected response");
+ break;
+ }
+
+ // A "response" signal won't be sent again but "destroy" will be.
+ g_signal_handlers_disconnect_by_func(color_chooser, FuncToGpointer(OnDestroy),
+ this);
+
+ gtk_widget_destroy(color_chooser);
+ if (mCallback) {
+ mCallback->Done(mColor);
+ mCallback = nullptr;
+ }
+
+ NS_RELEASE_THIS();
+}
+
+nsString nsColorPicker::ToHexString(int n) {
+ nsString result;
+ if (n <= 0x0F) {
+ result.Append('0');
+ }
+ result.AppendInt(n, 16);
+ return result;
+}
diff --git a/widget/gtk/nsColorPicker.h b/widget/gtk/nsColorPicker.h
new file mode 100644
index 0000000000..c1f108f5c3
--- /dev/null
+++ b/widget/gtk/nsColorPicker.h
@@ -0,0 +1,72 @@
+/* -*- 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 <gtk/gtk.h>
+
+#include "nsCOMPtr.h"
+#include "nsIColorPicker.h"
+#include "nsString.h"
+
+// Don't activate the GTK3 color picker for now, because it is missing a few
+// things, mainly the ability to let the user select a color on the screen.
+// See bug 1198256.
+#undef ACTIVATE_GTK3_COLOR_PICKER
+
+class nsIWidget;
+
+class nsColorPicker final : public nsIColorPicker {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOLORPICKER
+
+ nsColorPicker() = default;
+
+ private:
+ ~nsColorPicker() = default;
+
+ static nsString ToHexString(int n);
+
+ static void OnResponse(GtkWidget* dialog, gint response_id,
+ gpointer user_data);
+ static void OnDestroy(GtkWidget* dialog, gpointer user_data);
+
+#if defined(ACTIVATE_GTK3_COLOR_PICKER)
+ static void OnColorChanged(GtkColorChooser* color_chooser, GdkRGBA* color,
+ gpointer user_data);
+
+ static int convertGdkRgbaComponent(gdouble color_component);
+ static gdouble convertToGdkRgbaComponent(int color_component);
+ static GdkRGBA convertToRgbaColor(nscolor color);
+
+ void Update(GdkRGBA* color);
+ void SetColor(const GdkRGBA* color);
+#else
+ static void OnColorChanged(GtkColorSelection* colorselection,
+ gpointer user_data);
+
+ // Conversion functions for color
+ static int convertGdkColorComponent(guint16 color_component);
+ static guint16 convertToGdkColorComponent(int color_component);
+ static GdkColor convertToGdkColor(nscolor color);
+
+ static GtkColorSelection* WidgetGetColorSelection(GtkWidget* widget);
+
+ void Update(GtkColorSelection* colorselection);
+ void ReadValueFromColorSelection(GtkColorSelection* colorselection);
+#endif
+
+ void Done(GtkWidget* dialog, gint response_id);
+
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsCOMPtr<nsIColorPickerShownCallback> mCallback;
+ nsString mTitle;
+ nsString mColor;
+ nsString mInitialColor;
+};
+
+#endif // nsColorPicker_h__
diff --git a/widget/gtk/nsDeviceContextSpecG.cpp b/widget/gtk/nsDeviceContextSpecG.cpp
new file mode 100644
index 0000000000..631ce9d3d4
--- /dev/null
+++ b/widget/gtk/nsDeviceContextSpecG.cpp
@@ -0,0 +1,341 @@
+/* -*- 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 "nsDeviceContextSpecG.h"
+
+#include "mozilla/gfx/PrintTargetPDF.h"
+#include "mozilla/gfx/PrintTargetPS.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Services.h"
+
+#include "plstr.h"
+#include "prenv.h" /* for PR_GetEnv */
+
+#include "nsComponentManagerUtils.h"
+#include "nsIObserverService.h"
+#include "nsPrintfCString.h"
+#include "nsReadableUtils.h"
+#include "nsThreadUtils.h"
+
+#include "nsCUPSShim.h"
+#include "nsPrinterCUPS.h"
+
+#include "nsPrintSettingsGTK.h"
+
+#include "nsIFileStreams.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_print.h"
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+// To check if we need to use flatpak portal for printing
+#include "nsIGIOService.h"
+
+using namespace mozilla;
+
+using mozilla::gfx::IntSize;
+using mozilla::gfx::PrintTarget;
+using mozilla::gfx::PrintTargetPDF;
+using mozilla::gfx::PrintTargetPS;
+
+nsDeviceContextSpecGTK::nsDeviceContextSpecGTK()
+ : mGtkPrintSettings(nullptr), mGtkPageSetup(nullptr) {}
+
+nsDeviceContextSpecGTK::~nsDeviceContextSpecGTK() {
+ if (mGtkPageSetup) {
+ g_object_unref(mGtkPageSetup);
+ }
+
+ if (mGtkPrintSettings) {
+ g_object_unref(mGtkPrintSettings);
+ }
+
+ if (mSpoolFile) {
+ mSpoolFile->Remove(false);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsDeviceContextSpecGTK, nsIDeviceContextSpec)
+
+already_AddRefed<PrintTarget> nsDeviceContextSpecGTK::MakePrintTarget() {
+ double width, height;
+ mPrintSettings->GetEffectiveSheetSize(&width, &height);
+
+ // convert twips to points
+ width /= TWIPS_PER_POINT_FLOAT;
+ height /= TWIPS_PER_POINT_FLOAT;
+
+ nsresult rv;
+
+ // We shouldn't be attempting to get a surface if we've already got a spool
+ // file.
+ MOZ_ASSERT(!mSpoolFile);
+
+ // Spool file. Use Glib's temporary file function since we're
+ // already dependent on the gtk software stack.
+ gchar* buf;
+ gint fd = g_file_open_tmp("XXXXXX.tmp", &buf, nullptr);
+ if (-1 == fd) return nullptr;
+ close(fd);
+
+ rv = NS_NewNativeLocalFile(nsDependentCString(buf), false,
+ getter_AddRefs(mSpoolFile));
+ if (NS_FAILED(rv)) {
+ unlink(buf);
+ g_free(buf);
+ return nullptr;
+ }
+
+ mSpoolName = buf;
+ g_free(buf);
+
+ mSpoolFile->SetPermissions(0600);
+
+ nsCOMPtr<nsIFileOutputStream> stream =
+ do_CreateInstance("@mozilla.org/network/file-output-stream;1");
+ rv = stream->Init(mSpoolFile, -1, -1, 0);
+ if (NS_FAILED(rv)) return nullptr;
+
+ int16_t format;
+ mPrintSettings->GetOutputFormat(&format);
+
+ // We assume PDF output if asked for native output.
+ if (format == nsIPrintSettings::kOutputFormatNative) {
+ format = nsIPrintSettings::kOutputFormatPDF;
+ }
+
+ IntSize size = IntSize::Ceil(width, height);
+ if (format == nsIPrintSettings::kOutputFormatPDF) {
+ return PrintTargetPDF::CreateOrNull(stream, size);
+ }
+
+ int32_t orientation = mPrintSettings->GetSheetOrientation();
+ return PrintTargetPS::CreateOrNull(
+ stream, size,
+ orientation == nsIPrintSettings::kPortraitOrientation
+ ? PrintTargetPS::PORTRAIT
+ : PrintTargetPS::LANDSCAPE);
+}
+
+#define DECLARE_KNOWN_MONOCHROME_SETTING(key_, value_) {"cups-" key_, value_},
+
+struct {
+ const char* mKey;
+ const char* mValue;
+} kKnownMonochromeSettings[] = {
+ CUPS_EACH_MONOCHROME_PRINTER_SETTING(DECLARE_KNOWN_MONOCHROME_SETTING)};
+
+#undef DECLARE_KNOWN_MONOCHROME_SETTING
+
+/** -------------------------------------------------------
+ * Initialize the nsDeviceContextSpecGTK
+ * @update dc 2/15/98
+ * @update syd 3/2/99
+ */
+NS_IMETHODIMP nsDeviceContextSpecGTK::Init(nsIWidget* aWidget,
+ nsIPrintSettings* aPS,
+ bool aIsPrintPreview) {
+ if (gtk_major_version < 2 ||
+ (gtk_major_version == 2 && gtk_minor_version < 10))
+ return NS_ERROR_NOT_AVAILABLE; // I'm so sorry bz
+
+ mPrintSettings = do_QueryInterface(aPS);
+ if (!mPrintSettings) return NS_ERROR_NO_INTERFACE;
+
+ // This is only set by embedders
+ bool toFile;
+ aPS->GetPrintToFile(&toFile);
+
+ mToPrinter = !toFile && !aIsPrintPreview;
+
+ mGtkPrintSettings = mPrintSettings->GetGtkPrintSettings();
+ mGtkPageSetup = mPrintSettings->GetGtkPageSetup();
+
+ // This is a horrible workaround for some printer driver bugs that treat
+ // custom page sizes different to standard ones. If our paper object matches
+ // one of a standard one, use a standard paper size object instead. See bug
+ // 414314 for more info.
+ GtkPaperSize* geckosHackishPaperSize =
+ gtk_page_setup_get_paper_size(mGtkPageSetup);
+ GtkPaperSize* standardGtkPaperSize =
+ gtk_paper_size_new(gtk_paper_size_get_name(geckosHackishPaperSize));
+
+ mGtkPageSetup = gtk_page_setup_copy(mGtkPageSetup);
+ mGtkPrintSettings = gtk_print_settings_copy(mGtkPrintSettings);
+
+ if (!aPS->GetPrintInColor() && StaticPrefs::print_cups_monochrome_enabled()) {
+ for (const auto& setting : kKnownMonochromeSettings) {
+ gtk_print_settings_set(mGtkPrintSettings, setting.mKey, setting.mValue);
+ }
+ auto applySetting = [&](const nsACString& aKey, const nsACString& aVal) {
+ nsAutoCString extra;
+ extra.AppendASCII("cups-");
+ extra.Append(aKey);
+ gtk_print_settings_set(mGtkPrintSettings, extra.get(),
+ nsAutoCString(aVal).get());
+ };
+ nsPrinterCUPS::ForEachExtraMonochromeSetting(applySetting);
+ }
+
+ GtkPaperSize* properPaperSize;
+ if (gtk_paper_size_is_equal(geckosHackishPaperSize, standardGtkPaperSize)) {
+ properPaperSize = standardGtkPaperSize;
+ } else {
+ properPaperSize = geckosHackishPaperSize;
+ }
+ gtk_print_settings_set_paper_size(mGtkPrintSettings, properPaperSize);
+ gtk_page_setup_set_paper_size_and_default_margins(mGtkPageSetup,
+ properPaperSize);
+ gtk_paper_size_free(standardGtkPaperSize);
+
+ return NS_OK;
+}
+
+static void print_callback(GtkPrintJob* aJob, gpointer aData,
+ const GError* aError) {
+ g_object_unref(aJob);
+ ((nsIFile*)aData)->Remove(false);
+}
+
+/* static */
+gboolean nsDeviceContextSpecGTK::PrinterEnumerator(GtkPrinter* aPrinter,
+ gpointer aData) {
+ nsDeviceContextSpecGTK* spec = (nsDeviceContextSpecGTK*)aData;
+
+ // Find the printer whose name matches the one inside the settings.
+ nsString printerName;
+ nsresult rv = spec->mPrintSettings->GetPrinterName(printerName);
+ if (NS_SUCCEEDED(rv) && !printerName.IsVoid()) {
+ NS_ConvertUTF16toUTF8 requestedName(printerName);
+ const char* currentName = gtk_printer_get_name(aPrinter);
+ if (requestedName.Equals(currentName)) {
+ spec->mPrintSettings->SetGtkPrinter(aPrinter);
+
+ // Bug 1145916 - attempting to kick off a print job for this printer
+ // during this tick of the event loop will result in the printer backend
+ // misunderstanding what the capabilities of the printer are due to a
+ // GTK bug (https://bugzilla.gnome.org/show_bug.cgi?id=753041). We
+ // sidestep this by deferring the print to the next tick.
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("nsDeviceContextSpecGTK::StartPrintJob", spec,
+ &nsDeviceContextSpecGTK::StartPrintJob));
+ return TRUE;
+ }
+ }
+
+ // We haven't found it yet - keep searching...
+ return FALSE;
+}
+
+void nsDeviceContextSpecGTK::StartPrintJob() {
+ GtkPrintJob* job =
+ gtk_print_job_new(mTitle.get(), mPrintSettings->GetGtkPrinter(),
+ mGtkPrintSettings, mGtkPageSetup);
+
+ if (!gtk_print_job_set_source_file(job, mSpoolName.get(), nullptr)) return;
+
+ // Now gtk owns the print job, and will be released via our callback.
+ gtk_print_job_send(job, print_callback, mSpoolFile.forget().take(),
+ [](gpointer aData) {
+ auto* spoolFile = static_cast<nsIFile*>(aData);
+ NS_RELEASE(spoolFile);
+ });
+}
+
+void nsDeviceContextSpecGTK::EnumeratePrinters() {
+ gtk_enumerate_printers(&nsDeviceContextSpecGTK::PrinterEnumerator, this,
+ nullptr, TRUE);
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecGTK::BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) {
+ // Print job names exceeding 255 bytes are safe with GTK version 3.18.2 or
+ // newer. This is a workaround for old GTK.
+ if (gtk_check_version(3, 18, 2) != nullptr) {
+ PrintTarget::AdjustPrintJobNameForIPP(aTitle, mTitle);
+ } else {
+ CopyUTF16toUTF8(aTitle, mTitle);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecGTK::EndDocument() {
+ if (mToPrinter) {
+ // At this point, we might have a GtkPrinter set up in nsPrintSettingsGTK,
+ // or we might not. In the single-process case, we probably will, as this
+ // is populated by the print settings dialog, or set to the default
+ // printer.
+ // In the multi-process case, we proxy the print settings dialog over to
+ // the parent process, and only get the name of the printer back on the
+ // content process side. In that case, we need to enumerate the printers
+ // on the content side, and find a printer with a matching name.
+
+ if (mPrintSettings->GetGtkPrinter()) {
+ // We have a printer, so we can print right away.
+ StartPrintJob();
+ } else {
+ // We don't have a printer. We have to enumerate the printers and find
+ // one with a matching name.
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("nsDeviceContextSpecGTK::EnumeratePrinters", this,
+ &nsDeviceContextSpecGTK::EnumeratePrinters));
+ }
+ } else {
+ // Handle print-to-file ourselves for the benefit of embedders
+ nsString targetPath;
+ nsCOMPtr<nsIFile> destFile;
+ mPrintSettings->GetToFileName(targetPath);
+
+ nsresult rv = NS_NewLocalFile(targetPath, false, getter_AddRefs(destFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString destLeafName;
+ rv = destFile->GetLeafName(destLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> destDir;
+ rv = destFile->GetParent(getter_AddRefs(destDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mSpoolFile->MoveTo(destDir, destLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSpoolFile = nullptr;
+
+ // This is the standard way to get the UNIX umask. Ugh.
+ mode_t mask = umask(0);
+ umask(mask);
+ // If you're not familiar with umasks, they contain the bits of what NOT
+ // to set in the permissions (thats because files and directories have
+ // different numbers of bits for their permissions)
+ destFile->SetPermissions(0666 & ~(mask));
+
+ // Notify flatpak printing portal that file is completely written
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ bool shouldUsePortal;
+ if (giovfs) {
+ giovfs->ShouldUseFlatpakPortal(&shouldUsePortal);
+ if (shouldUsePortal) {
+ // Use the name of the file for printing to match with
+ // nsFlatpakPrintPortal
+ nsCOMPtr<nsIObserverService> os =
+ mozilla::services::GetObserverService();
+ // Pass filename to be sure that observer process the right data
+ os->NotifyObservers(nullptr, "print-to-file-finished",
+ targetPath.get());
+ }
+ }
+ }
+ return NS_OK;
+}
diff --git a/widget/gtk/nsDeviceContextSpecG.h b/widget/gtk/nsDeviceContextSpecG.h
new file mode 100644
index 0000000000..e3b0537782
--- /dev/null
+++ b/widget/gtk/nsDeviceContextSpecG.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 nsDeviceContextSpecGTK_h___
+#define nsDeviceContextSpecGTK_h___
+
+struct JSContext;
+
+#include "nsIDeviceContextSpec.h"
+#include "nsIPrinterList.h"
+#include "nsIPrintSettings.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+#include "nsCRT.h" /* should be <limits.h>? */
+
+#include <gtk/gtk.h>
+#include <gtk/gtkunixprint.h>
+
+#define NS_PORTRAIT 0
+#define NS_LANDSCAPE 1
+
+class nsPrintSettingsGTK;
+
+class nsDeviceContextSpecGTK : public nsIDeviceContextSpec {
+ public:
+ nsDeviceContextSpecGTK();
+
+ NS_DECL_ISUPPORTS
+
+ already_AddRefed<PrintTarget> MakePrintTarget() final;
+
+ NS_IMETHOD Init(nsIWidget* aWidget, nsIPrintSettings* aPS,
+ bool aIsPrintPreview) override;
+ NS_IMETHOD BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) override;
+ NS_IMETHOD EndDocument() override;
+ NS_IMETHOD BeginPage() override { return NS_OK; }
+ NS_IMETHOD EndPage() override { return NS_OK; }
+
+ protected:
+ virtual ~nsDeviceContextSpecGTK();
+ nsCOMPtr<nsPrintSettingsGTK> mPrintSettings;
+ bool mToPrinter : 1; /* If true, print to printer */
+ GtkPrintSettings* mGtkPrintSettings;
+ GtkPageSetup* mGtkPageSetup;
+
+ nsCString mSpoolName;
+ nsCOMPtr<nsIFile> mSpoolFile;
+ nsCString mTitle;
+
+ private:
+ void EnumeratePrinters();
+ void StartPrintJob();
+ static gboolean PrinterEnumerator(GtkPrinter* aPrinter, gpointer aData);
+};
+
+#endif /* !nsDeviceContextSpecGTK_h___ */
diff --git a/widget/gtk/nsDragService.cpp b/widget/gtk/nsDragService.cpp
new file mode 100644
index 0000000000..7d57c35df9
--- /dev/null
+++ b/widget/gtk/nsDragService.cpp
@@ -0,0 +1,2122 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsDragService.h"
+#include "nsArrayUtils.h"
+#include "nsIObserverService.h"
+#include "nsWidgetsCID.h"
+#include "nsWindow.h"
+#include "nsSystemInfo.h"
+#include "nsXPCOM.h"
+#include "nsICookieJarSettings.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIIOService.h"
+#include "nsIFileURL.h"
+#include "nsNetUtil.h"
+#include "mozilla/Logging.h"
+#include "nsTArray.h"
+#include "nsPrimitiveHelpers.h"
+#include "prtime.h"
+#include "prthread.h"
+#include <dlfcn.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include "nsCRT.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Services.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ScopeExit.h"
+#include "GRefPtr.h"
+
+#include "gfxXlibSurface.h"
+#include "gfxContext.h"
+#include "nsImageToPixbuf.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsViewManager.h"
+#include "nsIFrame.h"
+#include "nsGtkUtils.h"
+#include "nsGtkKeyUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxPlatform.h"
+#include "ScreenHelperGTK.h"
+#include "nsArrayUtils.h"
+#ifdef MOZ_WAYLAND
+# include "nsClipboardWayland.h"
+# include "gfxPlatformGtk.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+#define NS_SYSTEMINFO_CONTRACTID "@mozilla.org/system-info;1"
+
+// This sets how opaque the drag image is
+#define DRAG_IMAGE_ALPHA_LEVEL 0.5
+
+// These values are copied from GtkDragResult (rather than using GtkDragResult
+// directly) so that this code can be compiled against versions of GTK+ that
+// do not have GtkDragResult.
+// GtkDragResult is available from GTK+ version 2.12.
+enum {
+ MOZ_GTK_DRAG_RESULT_SUCCESS,
+ MOZ_GTK_DRAG_RESULT_NO_TARGET,
+ MOZ_GTK_DRAG_RESULT_USER_CANCELLED,
+ MOZ_GTK_DRAG_RESULT_TIMEOUT_EXPIRED,
+ MOZ_GTK_DRAG_RESULT_GRAB_BROKEN,
+ MOZ_GTK_DRAG_RESULT_ERROR
+};
+
+static LazyLogModule sDragLm("nsDragService");
+
+// data used for synthetic periodic motion events sent to the source widget
+// grabbing real events for the drag.
+static guint sMotionEventTimerID;
+static GdkEvent* sMotionEvent;
+static GtkWidget* sGrabWidget;
+
+static const char gMimeListType[] = "application/x-moz-internal-item-list";
+static const char gMozUrlType[] = "_NETSCAPE_URL";
+static const char gTextUriListType[] = "text/uri-list";
+static const char gTextPlainUTF8Type[] = "text/plain;charset=utf-8";
+static const char gXdndDirectSaveType[] = "XdndDirectSave0";
+static const char gTabDropType[] = "application/x-moz-tabbrowser-tab";
+
+static void invisibleSourceDragBegin(GtkWidget* aWidget,
+ GdkDragContext* aContext, gpointer aData);
+
+static void invisibleSourceDragEnd(GtkWidget* aWidget, GdkDragContext* aContext,
+ gpointer aData);
+
+static gboolean invisibleSourceDragFailed(GtkWidget* aWidget,
+ GdkDragContext* aContext,
+ gint aResult, gpointer aData);
+
+static void invisibleSourceDragDataGet(GtkWidget* aWidget,
+ GdkDragContext* aContext,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint32 aTime,
+ gpointer aData);
+
+nsDragService::nsDragService()
+ : mScheduledTask(eDragTaskNone),
+ mTaskSource(0)
+#ifdef MOZ_WAYLAND
+ ,
+ mPendingWaylandDragContext(nullptr),
+ mTargetWaylandDragContext(nullptr)
+#endif
+{
+ // We have to destroy the hidden widget before the event loop stops
+ // running.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ obsServ->AddObserver(this, "quit-application", false);
+
+ // our hidden source widget
+ // Using an offscreen window works around bug 983843.
+ mHiddenWidget = gtk_offscreen_window_new();
+ // make sure that the widget is realized so that
+ // we can use it as a drag source.
+ gtk_widget_realize(mHiddenWidget);
+ // hook up our internal signals so that we can get some feedback
+ // from our drag source
+ g_signal_connect(mHiddenWidget, "drag_begin",
+ G_CALLBACK(invisibleSourceDragBegin), this);
+ g_signal_connect(mHiddenWidget, "drag_data_get",
+ G_CALLBACK(invisibleSourceDragDataGet), this);
+ g_signal_connect(mHiddenWidget, "drag_end",
+ G_CALLBACK(invisibleSourceDragEnd), this);
+ // drag-failed is available from GTK+ version 2.12
+ guint dragFailedID =
+ g_signal_lookup("drag-failed", G_TYPE_FROM_INSTANCE(mHiddenWidget));
+ if (dragFailedID) {
+ g_signal_connect_closure_by_id(
+ mHiddenWidget, dragFailedID, 0,
+ g_cclosure_new(G_CALLBACK(invisibleSourceDragFailed), this, nullptr),
+ FALSE);
+ }
+
+ // set up our logging module
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::nsDragService"));
+ mCanDrop = false;
+ mTargetDragDataReceived = false;
+ mTargetDragData = 0;
+ mTargetDragDataLen = 0;
+}
+
+nsDragService::~nsDragService() {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::~nsDragService"));
+ if (mTaskSource) g_source_remove(mTaskSource);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsDragService, nsBaseDragService, nsIObserver)
+
+mozilla::StaticRefPtr<nsDragService> sDragServiceInstance;
+/* static */
+already_AddRefed<nsDragService> nsDragService::GetInstance() {
+ if (gfxPlatform::IsHeadless()) {
+ return nullptr;
+ }
+ if (!sDragServiceInstance) {
+ sDragServiceInstance = new nsDragService();
+ ClearOnShutdown(&sDragServiceInstance);
+ }
+
+ RefPtr<nsDragService> service = sDragServiceInstance.get();
+ return service.forget();
+}
+
+// nsIObserver
+
+NS_IMETHODIMP
+nsDragService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!nsCRT::strcmp(aTopic, "quit-application")) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService::Observe(\"quit-application\")"));
+ if (mHiddenWidget) {
+ gtk_widget_destroy(mHiddenWidget);
+ mHiddenWidget = 0;
+ }
+ TargetResetData();
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected topic");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+// Support for periodic drag events
+
+// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
+// and the Xdnd protocol both recommend that drag events are sent periodically,
+// but GTK does not normally provide this.
+//
+// Here GTK is periodically stimulated by copies of the most recent mouse
+// motion events so as to send drag position messages to the destination when
+// appropriate (after it has received a status event from the previous
+// message).
+//
+// (If events were sent only on the destination side then the destination
+// would have no message to which it could reply with a drag status. Without
+// sending a drag status to the source, the destination would not be able to
+// change its feedback re whether it could accept the drop, and so the
+// source's behavior on drop will not be consistent.)
+
+static gboolean DispatchMotionEventCopy(gpointer aData) {
+ // Clear the timer id before OnSourceGrabEventAfter is called during event
+ // dispatch.
+ sMotionEventTimerID = 0;
+
+ GdkEvent* event = sMotionEvent;
+ sMotionEvent = nullptr;
+ // If there is no longer a grab on the widget, then the drag is over and
+ // there is no need to continue drag motion.
+ if (gtk_widget_has_grab(sGrabWidget)) {
+ gtk_propagate_event(sGrabWidget, event);
+ }
+ gdk_event_free(event);
+
+ // Cancel this timer;
+ // We've already started another if the motion event was dispatched.
+ return FALSE;
+}
+
+static void OnSourceGrabEventAfter(GtkWidget* widget, GdkEvent* event,
+ gpointer user_data) {
+ // If there is no longer a grab on the widget, then the drag motion is
+ // over (though the data may not be fetched yet).
+ if (!gtk_widget_has_grab(sGrabWidget)) return;
+
+ if (event->type == GDK_MOTION_NOTIFY) {
+ if (sMotionEvent) {
+ gdk_event_free(sMotionEvent);
+ }
+ sMotionEvent = gdk_event_copy(event);
+
+ // Update the cursor position. The last of these recorded gets used for
+ // the eDragEnd event.
+ nsDragService* dragService = static_cast<nsDragService*>(user_data);
+ gint scale = mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor();
+ auto p = LayoutDeviceIntPoint::Round(event->motion.x_root * scale,
+ event->motion.y_root * scale);
+ dragService->SetDragEndPoint(p);
+ } else if (sMotionEvent &&
+ (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE)) {
+ // Update modifier state from key events.
+ sMotionEvent->motion.state = event->key.state;
+ } else {
+ return;
+ }
+
+ if (sMotionEventTimerID) {
+ g_source_remove(sMotionEventTimerID);
+ }
+
+ // G_PRIORITY_DEFAULT_IDLE is lower priority than GDK's redraw idle source
+ // and lower than GTK's idle source that sends drag position messages after
+ // motion-notify signals.
+ //
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
+ // recommends an interval of 350ms +/- 200ms.
+ sMotionEventTimerID = g_timeout_add_full(
+ G_PRIORITY_DEFAULT_IDLE, 350, DispatchMotionEventCopy, nullptr, nullptr);
+}
+
+static GtkWindow* GetGtkWindow(dom::Document* aDocument) {
+ if (!aDocument) return nullptr;
+
+ PresShell* presShell = aDocument->GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+
+ RefPtr<nsViewManager> vm = presShell->GetViewManager();
+ if (!vm) return nullptr;
+
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (!widget) return nullptr;
+
+ GtkWidget* gtkWidget =
+ static_cast<nsWindow*>(widget.get())->GetMozContainerWidget();
+ if (!gtkWidget) return nullptr;
+
+ GtkWidget* toplevel = nullptr;
+ toplevel = gtk_widget_get_toplevel(gtkWidget);
+ if (!GTK_IS_WINDOW(toplevel)) return nullptr;
+
+ return GTK_WINDOW(toplevel);
+}
+
+// nsIDragService
+
+NS_IMETHODIMP
+nsDragService::InvokeDragSession(
+ nsINode* aDOMNode, nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp,
+ nsICookieJarSettings* aCookieJarSettings, nsIArray* aArrayTransferables,
+ uint32_t aActionType,
+ nsContentPolicyType aContentPolicyType = nsIContentPolicy::TYPE_OTHER) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::InvokeDragSession"));
+
+ // If the previous source drag has not yet completed, signal handlers need
+ // to be removed from sGrabWidget and dragend needs to be dispatched to
+ // the source node, but we can't call EndDragSession yet because we don't
+ // know whether or not the drag succeeded.
+ if (mSourceNode) return NS_ERROR_NOT_AVAILABLE;
+
+ return nsBaseDragService::InvokeDragSession(
+ aDOMNode, aPrincipal, aCsp, aCookieJarSettings, aArrayTransferables,
+ aActionType, aContentPolicyType);
+}
+
+// nsBaseDragService
+nsresult nsDragService::InvokeDragSessionImpl(
+ nsIArray* aArrayTransferables, const Maybe<CSSIntRegion>& aRegion,
+ uint32_t aActionType) {
+ // make sure that we have an array of transferables to use
+ if (!aArrayTransferables) return NS_ERROR_INVALID_ARG;
+ // set our reference to the transferables. this will also addref
+ // the transferables since we're going to hang onto this beyond the
+ // length of this call
+ mSourceDataItems = aArrayTransferables;
+ // get the list of items we offer for drags
+ GtkTargetList* sourceList = GetSourceList();
+
+ if (!sourceList) return NS_OK;
+
+ // save our action type
+ GdkDragAction action = GDK_ACTION_DEFAULT;
+
+ if (aActionType & DRAGDROP_ACTION_COPY)
+ action = (GdkDragAction)(action | GDK_ACTION_COPY);
+ if (aActionType & DRAGDROP_ACTION_MOVE)
+ action = (GdkDragAction)(action | GDK_ACTION_MOVE);
+ if (aActionType & DRAGDROP_ACTION_LINK)
+ action = (GdkDragAction)(action | GDK_ACTION_LINK);
+
+ // Create a fake event for the drag so we can pass the time (so to speak).
+ // If we don't do this, then, when the timestamp for the pending button
+ // release event is used for the ungrab, the ungrab can fail due to the
+ // timestamp being _earlier_ than CurrentTime.
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+ event.type = GDK_BUTTON_PRESS;
+ event.button.window = gtk_widget_get_window(mHiddenWidget);
+ event.button.time = nsWindow::GetLastUserInputTime();
+
+ // Put the drag widget in the window group of the source node so that the
+ // gtk_grab_add during gtk_drag_begin is effective.
+ // gtk_window_get_group(nullptr) returns the default window group.
+ GtkWindowGroup* window_group =
+ gtk_window_get_group(GetGtkWindow(mSourceDocument));
+ gtk_window_group_add_window(window_group, GTK_WINDOW(mHiddenWidget));
+
+ // Get device for event source
+ GdkDisplay* display = gdk_display_get_default();
+ GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
+ event.button.device = gdk_device_manager_get_client_pointer(device_manager);
+
+ // start our drag.
+ GdkDragContext* context =
+ gtk_drag_begin(mHiddenWidget, sourceList, action, 1, &event);
+
+ nsresult rv;
+ if (context) {
+ StartDragSession();
+
+ // GTK uses another hidden window for receiving mouse events.
+ sGrabWidget = gtk_window_group_get_current_grab(window_group);
+ if (sGrabWidget) {
+ g_object_ref(sGrabWidget);
+ // Only motion and key events are required but connect to
+ // "event-after" as this is never blocked by other handlers.
+ g_signal_connect(sGrabWidget, "event-after",
+ G_CALLBACK(OnSourceGrabEventAfter), this);
+ }
+ // We don't have a drag end point yet.
+ mEndDragPoint = LayoutDeviceIntPoint(-1, -1);
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ gtk_target_list_unref(sourceList);
+
+ return rv;
+}
+
+bool nsDragService::SetAlphaPixmap(SourceSurface* aSurface,
+ GdkDragContext* aContext, int32_t aXOffset,
+ int32_t aYOffset,
+ const LayoutDeviceIntRect& dragRect) {
+ GdkScreen* screen = gtk_widget_get_screen(mHiddenWidget);
+
+ // Transparent drag icons need, like a lot of transparency-related things,
+ // a compositing X window manager
+ if (!gdk_screen_is_composited(screen)) return false;
+
+#ifdef cairo_image_surface_create
+# error "Looks like we're including Mozilla's cairo instead of system cairo"
+#endif
+
+ // TODO: grab X11 pixmap or image data instead of expensive readback.
+ cairo_surface_t* surf = cairo_image_surface_create(
+ CAIRO_FORMAT_ARGB32, dragRect.width, dragRect.height);
+ if (!surf) return false;
+
+ RefPtr<DrawTarget> dt = gfxPlatform::CreateDrawTargetForData(
+ cairo_image_surface_get_data(surf),
+ nsIntSize(dragRect.width, dragRect.height),
+ cairo_image_surface_get_stride(surf), SurfaceFormat::B8G8R8A8);
+ if (!dt) return false;
+
+ dt->ClearRect(Rect(0, 0, dragRect.width, dragRect.height));
+ dt->DrawSurface(
+ aSurface, Rect(0, 0, dragRect.width, dragRect.height),
+ Rect(0, 0, dragRect.width, dragRect.height), DrawSurfaceOptions(),
+ DrawOptions(DRAG_IMAGE_ALPHA_LEVEL, CompositionOp::OP_SOURCE));
+
+ cairo_surface_mark_dirty(surf);
+ cairo_surface_set_device_offset(surf, -aXOffset, -aYOffset);
+
+ // Ensure that the surface is drawn at the correct scale on HiDPI displays.
+ static auto sCairoSurfaceSetDeviceScalePtr =
+ (void (*)(cairo_surface_t*, double, double))dlsym(
+ RTLD_DEFAULT, "cairo_surface_set_device_scale");
+ if (sCairoSurfaceSetDeviceScalePtr) {
+ gint scale = mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor();
+ sCairoSurfaceSetDeviceScalePtr(surf, scale, scale);
+ }
+
+ gtk_drag_set_icon_surface(aContext, surf);
+ cairo_surface_destroy(surf);
+ return true;
+}
+
+NS_IMETHODIMP
+nsDragService::StartDragSession() {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::StartDragSession"));
+ return nsBaseDragService::StartDragSession();
+}
+
+NS_IMETHODIMP
+nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService::EndDragSession %d", aDoneDrag));
+
+ if (sGrabWidget) {
+ g_signal_handlers_disconnect_by_func(
+ sGrabWidget, FuncToGpointer(OnSourceGrabEventAfter), this);
+ g_object_unref(sGrabWidget);
+ sGrabWidget = nullptr;
+
+ if (sMotionEventTimerID) {
+ g_source_remove(sMotionEventTimerID);
+ sMotionEventTimerID = 0;
+ }
+ if (sMotionEvent) {
+ gdk_event_free(sMotionEvent);
+ sMotionEvent = nullptr;
+ }
+ }
+
+ // unset our drag action
+ SetDragAction(DRAGDROP_ACTION_NONE);
+
+ // We're done with the drag context.
+ mTargetDragContextForRemote = nullptr;
+#ifdef MOZ_WAYLAND
+ mTargetWaylandDragContextForRemote = nullptr;
+#endif
+
+ return nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
+}
+
+// nsIDragSession
+NS_IMETHODIMP
+nsDragService::SetCanDrop(bool aCanDrop) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::SetCanDrop %d", aCanDrop));
+ mCanDrop = aCanDrop;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDragService::GetCanDrop(bool* aCanDrop) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetCanDrop"));
+ *aCanDrop = mCanDrop;
+ return NS_OK;
+}
+
+static void UTF16ToNewUTF8(const char16_t* aUTF16, uint32_t aUTF16Len,
+ char** aUTF8, uint32_t* aUTF8Len) {
+ nsDependentSubstring utf16(aUTF16, aUTF16Len);
+ *aUTF8 = ToNewUTF8String(utf16, aUTF8Len);
+}
+
+static void UTF8ToNewUTF16(const char* aUTF8, uint32_t aUTF8Len,
+ char16_t** aUTF16, uint32_t* aUTF16Len) {
+ nsDependentCSubstring utf8(aUTF8, aUTF8Len);
+ *aUTF16 = UTF8ToNewUnicode(utf8, aUTF16Len);
+}
+
+// count the number of URIs in some text/uri-list format data.
+static uint32_t CountTextUriListItems(const char* data, uint32_t datalen) {
+ const char* p = data;
+ const char* endPtr = p + datalen;
+ uint32_t count = 0;
+
+ while (p < endPtr) {
+ // skip whitespace (if any)
+ while (p < endPtr && *p != '\0' && isspace(*p)) p++;
+ // if we aren't at the end of the line ...
+ if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r') count++;
+ // skip to the end of the line
+ while (p < endPtr && *p != '\0' && *p != '\n') p++;
+ p++; // skip the actual newline as well.
+ }
+ return count;
+}
+
+// extract an item from text/uri-list formatted data and convert it to
+// unicode.
+static void GetTextUriListItem(const char* data, uint32_t datalen,
+ uint32_t aItemIndex, char16_t** convertedText,
+ uint32_t* convertedTextLen) {
+ const char* p = data;
+ const char* endPtr = p + datalen;
+ unsigned int count = 0;
+
+ *convertedText = nullptr;
+ while (p < endPtr) {
+ // skip whitespace (if any)
+ while (p < endPtr && *p != '\0' && isspace(*p)) p++;
+ // if we aren't at the end of the line, we have a url
+ if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r') count++;
+ // this is the item we are after ...
+ if (aItemIndex + 1 == count) {
+ const char* q = p;
+ while (q < endPtr && *q != '\0' && *q != '\n' && *q != '\r') q++;
+ UTF8ToNewUTF16(p, q - p, convertedText, convertedTextLen);
+ break;
+ }
+ // skip to the end of the line
+ while (p < endPtr && *p != '\0' && *p != '\n') p++;
+ p++; // skip the actual newline as well.
+ }
+
+ // didn't find the desired item, so just pass the whole lot
+ if (!*convertedText) {
+ UTF8ToNewUTF16(data, datalen, convertedText, convertedTextLen);
+ }
+}
+
+NS_IMETHODIMP
+nsDragService::GetNumDropItems(uint32_t* aNumItems) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetNumDropItems"));
+
+ if (!mTargetWidget) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("*** warning: GetNumDropItems \
+ called without a valid target widget!\n"));
+ *aNumItems = 0;
+ return NS_OK;
+ }
+
+ bool isList = IsTargetContextList();
+ if (isList)
+ mSourceDataItems->GetLength(aNumItems);
+ else {
+ GdkAtom gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ const char* data = reinterpret_cast<char*>(mTargetDragData);
+ *aNumItems = CountTextUriListItems(data, mTargetDragDataLen);
+ } else
+ *aNumItems = 1;
+ }
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("%d items", *aNumItems));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetData %d", aItemIndex));
+
+ // make sure that we have a transferable
+ if (!aTransferable) return NS_ERROR_INVALID_ARG;
+
+ if (!mTargetWidget) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("*** warning: GetData \
+ called without a valid target widget!\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // get flavor list that includes all acceptable flavors (including
+ // ones obtained through conversion).
+ nsTArray<nsCString> flavors;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
+ if (NS_FAILED(rv)) return rv;
+
+ // check to see if this is an internal list
+ bool isList = IsTargetContextList();
+
+ if (isList) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("it's a list..."));
+ // find a matching flavor
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ nsCString& flavorStr = flavors[i];
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("flavor is %s\n", flavorStr.get()));
+ // get the item with the right index
+ nsCOMPtr<nsITransferable> item =
+ do_QueryElementAt(mSourceDataItems, aItemIndex);
+ if (!item) continue;
+
+ nsCOMPtr<nsISupports> data;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("trying to get transfer data for %s\n", flavorStr.get()));
+ rv = item->GetTransferData(flavorStr.get(), getter_AddRefs(data));
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("failed.\n"));
+ continue;
+ }
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("succeeded.\n"));
+ rv = aTransferable->SetTransferData(flavorStr.get(), data);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("fail to set transfer data into transferable!\n"));
+ continue;
+ }
+ // ok, we got the data
+ return NS_OK;
+ }
+ // if we got this far, we failed
+ return NS_ERROR_FAILURE;
+ }
+
+ // Now walk down the list of flavors. When we find one that is
+ // actually present, copy out the data into the transferable in that
+ // format. SetTransferData() implicitly handles conversions.
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ nsCString& flavorStr = flavors[i];
+ GdkAtom gdkFlavor = gdk_atom_intern(flavorStr.get(), FALSE);
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("looking for data in type %s, gdk flavor %p\n", flavorStr.get(),
+ gdkFlavor));
+ bool dataFound = false;
+ if (gdkFlavor) {
+ GetTargetDragData(gdkFlavor);
+ }
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound = true\n"));
+ dataFound = true;
+ } else {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound = false\n"));
+
+ // Dragging and dropping from the file manager would cause us
+ // to parse the source text as a nsIFile URL.
+ if (flavorStr.EqualsLiteral(kFileMime)) {
+ gdkFlavor = gdk_atom_intern(kTextMime, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (!mTargetDragData) {
+ gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ }
+ if (mTargetDragData) {
+ const char* text = static_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+
+ GetTextUriListItem(text, mTargetDragDataLen, aItemIndex,
+ &convertedText, &convertedTextLen);
+
+ if (convertedText) {
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ nsCOMPtr<nsIURI> fileURI;
+ rv = ioService->NewURI(NS_ConvertUTF16toUTF8(convertedText),
+ nullptr, nullptr, getter_AddRefs(fileURI));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv)) {
+ // The common wrapping code at the end of
+ // this function assumes the data is text
+ // and calls text-specific operations.
+ // Make a secret hideout here for nsIFile
+ // objects and return early.
+ aTransferable->SetTransferData(flavorStr.get(), file);
+ g_free(convertedText);
+ return NS_OK;
+ }
+ }
+ }
+ g_free(convertedText);
+ }
+ continue;
+ }
+ }
+
+ // if we are looking for text/unicode and we fail to find it
+ // on the clipboard first, try again with text/plain. If that
+ // is present, convert it to unicode.
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/unicode... \
+ trying with text/plain;charset=utf-8\n"));
+ gdkFlavor = gdk_atom_intern(gTextPlainUTF8Type, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Got textplain data\n"));
+ const char* castedText = reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ NS_ConvertUTF8toUTF16 ucs2string(castedText, mTargetDragDataLen);
+ convertedText = ToNewUnicode(ucs2string, mozilla::fallible);
+ if (convertedText) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("successfully converted plain text \
+ to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = ucs2string.Length() * 2;
+ dataFound = true;
+ } // if plain text data on clipboard
+ } else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/unicode... \
+ trying again with text/plain\n"));
+ gdkFlavor = gdk_atom_intern(kTextMime, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Got textplain data\n"));
+ const char* castedText = reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+ UTF8ToNewUTF16(castedText, mTargetDragDataLen, &convertedText,
+ &convertedTextLen);
+ if (convertedText) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("successfully converted plain text \
+ to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = convertedTextLen * 2;
+ dataFound = true;
+ } // if plain text data on clipboard
+ } // if plain text flavor present
+ } // if plain text charset=utf-8 flavor present
+ } // if looking for text/unicode
+
+ // if we are looking for text/x-moz-url and we failed to find
+ // it on the clipboard, try again with text/uri-list, and then
+ // _NETSCAPE_URL
+ if (flavorStr.EqualsLiteral(kURLMime)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/x-moz-url...\
+ trying again with text/uri-list\n"));
+ gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Got text/uri-list data\n"));
+ const char* data = reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+
+ GetTextUriListItem(data, mTargetDragDataLen, aItemIndex,
+ &convertedText, &convertedTextLen);
+
+ if (convertedText) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("successfully converted \
+ _NETSCAPE_URL to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = convertedTextLen * 2;
+ dataFound = true;
+ }
+ } else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("failed to get text/uri-list data\n"));
+ }
+ if (!dataFound) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/x-moz-url...\
+ trying again with _NETSCAP_URL\n"));
+ gdkFlavor = gdk_atom_intern(gMozUrlType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Got _NETSCAPE_URL data\n"));
+ const char* castedText = reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+ UTF8ToNewUTF16(castedText, mTargetDragDataLen, &convertedText,
+ &convertedTextLen);
+ if (convertedText) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("successfully converted _NETSCAPE_URL \
+ to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = convertedTextLen * 2;
+ dataFound = true;
+ }
+ } else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("failed to get _NETSCAPE_URL data\n"));
+ }
+ }
+ }
+
+ } // else we try one last ditch effort to find our data
+
+ if (dataFound) {
+ if (!flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ // the DOM only wants LF, so convert from MacOS line endings
+ // to DOM line endings.
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(
+ flavorStr, &mTargetDragData,
+ reinterpret_cast<int*>(&mTargetDragDataLen));
+ }
+
+ // put it into the transferable.
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(
+ flavorStr, mTargetDragData, mTargetDragDataLen,
+ getter_AddRefs(genericDataWrapper));
+ aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
+ // we found one, get out of this loop!
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound and converted!\n"));
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService::IsDataFlavorSupported %s", aDataFlavor));
+ if (!_retval) return NS_ERROR_INVALID_ARG;
+
+ // set this to no by default
+ *_retval = false;
+
+ // check to make sure that we have a drag object set, here
+ if (!mTargetWidget) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("*** warning: IsDataFlavorSupported \
+ called without a valid target widget!\n"));
+ return NS_OK;
+ }
+
+ // check to see if the target context is a list.
+ bool isList = IsTargetContextList();
+ // if it is, just look in the internal data since we are the source
+ // for it.
+ if (isList) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("It's a list.."));
+ uint32_t numDragItems = 0;
+ // if we don't have mDataItems we didn't start this drag so it's
+ // an external client trying to fool us.
+ if (!mSourceDataItems) return NS_OK;
+ mSourceDataItems->GetLength(&numDragItems);
+ for (uint32_t itemIndex = 0; itemIndex < numDragItems; ++itemIndex) {
+ nsCOMPtr<nsITransferable> currItem =
+ do_QueryElementAt(mSourceDataItems, itemIndex);
+ if (currItem) {
+ nsTArray<nsCString> flavors;
+ currItem->FlavorsTransferableCanExport(flavors);
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("checking %s against %s\n", flavors[i].get(), aDataFlavor));
+ if (flavors[i].Equals(aDataFlavor)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("boioioioiooioioioing!\n"));
+ *_retval = true;
+ }
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ // check the target context vs. this flavor, one at a time
+ GList* tmp = nullptr;
+ if (mTargetDragContext) {
+ tmp = gdk_drag_context_list_targets(mTargetDragContext);
+ }
+#ifdef MOZ_WAYLAND
+ else if (mTargetWaylandDragContext) {
+ tmp = mTargetWaylandDragContext->GetTargets();
+ }
+ GList* tmp_head = tmp;
+#endif
+
+ for (; tmp; tmp = tmp->next) {
+ /* Bug 331198 */
+ GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
+ gchar* name = nullptr;
+ name = gdk_atom_name(atom);
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("checking %s against %s\n", name, aDataFlavor));
+ if (name && (strcmp(name, aDataFlavor) == 0)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("good!\n"));
+ *_retval = true;
+ }
+ // check for automatic text/uri-list -> text/x-moz-url mapping
+ if (!*_retval && name && (strcmp(name, gTextUriListType) == 0) &&
+ (strcmp(aDataFlavor, kURLMime) == 0 ||
+ strcmp(aDataFlavor, kFileMime) == 0)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("good! ( it's text/uri-list and \
+ we're checking against text/x-moz-url )\n"));
+ *_retval = true;
+ }
+ // check for automatic _NETSCAPE_URL -> text/x-moz-url mapping
+ if (!*_retval && name && (strcmp(name, gMozUrlType) == 0) &&
+ (strcmp(aDataFlavor, kURLMime) == 0)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("good! ( it's _NETSCAPE_URL and \
+ we're checking against text/x-moz-url )\n"));
+ *_retval = true;
+ }
+ // check for auto text/plain -> text/unicode mapping
+ if (!*_retval && name && (strcmp(name, kTextMime) == 0) &&
+ ((strcmp(aDataFlavor, kUnicodeMime) == 0) ||
+ (strcmp(aDataFlavor, kFileMime) == 0))) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("good! ( it's text plain and we're checking \
+ against text/unicode or application/x-moz-file)\n"));
+ *_retval = true;
+ }
+ g_free(name);
+ }
+
+#ifdef MOZ_WAYLAND
+ // mTargetWaylandDragContext->GetTargets allocates the list
+ // so we need to free it here.
+ if (!mTargetDragContext && tmp_head) {
+ g_list_free(tmp_head);
+ }
+#endif
+
+ return NS_OK;
+}
+
+void nsDragService::ReplyToDragMotion(GdkDragContext* aDragContext) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService::ReplyToDragMotion %d", mCanDrop));
+
+ GdkDragAction action = (GdkDragAction)0;
+ if (mCanDrop) {
+ // notify the dragger if we can drop
+ switch (mDragAction) {
+ case DRAGDROP_ACTION_COPY:
+ action = GDK_ACTION_COPY;
+ break;
+ case DRAGDROP_ACTION_LINK:
+ action = GDK_ACTION_LINK;
+ break;
+ case DRAGDROP_ACTION_NONE:
+ action = (GdkDragAction)0;
+ break;
+ default:
+ action = GDK_ACTION_MOVE;
+ break;
+ }
+ }
+
+ gdk_drag_status(aDragContext, action, mTargetTime);
+}
+
+#ifdef MOZ_WAYLAND
+void nsDragService::ReplyToDragMotion(nsWaylandDragContext* aDragContext) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService::ReplyToDragMotion %d", mCanDrop));
+
+ GdkDragAction action = (GdkDragAction)0;
+ if (mCanDrop) {
+ // notify the dragger if we can drop
+ switch (mDragAction) {
+ case DRAGDROP_ACTION_COPY:
+ action = GDK_ACTION_COPY;
+ break;
+ case DRAGDROP_ACTION_LINK:
+ action = GDK_ACTION_LINK;
+ break;
+ case DRAGDROP_ACTION_NONE:
+ action = (GdkDragAction)0;
+ break;
+ default:
+ action = GDK_ACTION_MOVE;
+ break;
+ }
+ }
+
+ aDragContext->SetDragStatus(action);
+}
+#endif
+
+void nsDragService::TargetDataReceived(GtkWidget* aWidget,
+ GdkDragContext* aContext, gint aX,
+ gint aY,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint32 aTime) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::TargetDataReceived"));
+ TargetResetData();
+
+ mTargetDragDataReceived = true;
+ gint len = gtk_selection_data_get_length(aSelectionData);
+ const guchar* data = gtk_selection_data_get_data(aSelectionData);
+
+ GdkAtom target = gtk_selection_data_get_target(aSelectionData);
+ char* name = gdk_atom_name(target);
+ nsCString flavor(name);
+ g_free(name);
+
+ if (len > 0 && data) {
+ mTargetDragDataLen = len;
+ mTargetDragData = g_malloc(mTargetDragDataLen);
+ memcpy(mTargetDragData, data, mTargetDragDataLen);
+
+ nsTArray<uint8_t> copy;
+ if (!copy.SetLength(len, fallible)) {
+ return;
+ }
+ memcpy(copy.Elements(), data, len);
+
+ mCachedData.Put(flavor, std::move(copy));
+ } else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("Failed to get data. selection data len was %d\n",
+ mTargetDragDataLen));
+
+ mCachedData.Put(flavor, nsTArray<uint8_t>());
+ }
+}
+
+bool nsDragService::IsTargetContextList(void) {
+ bool retval = false;
+
+ // gMimeListType drags only work for drags within a single process. The
+ // gtk_drag_get_source_widget() function will return nullptr if the source
+ // of the drag is another app, so we use it to check if a gMimeListType
+ // drop will work or not.
+ if (mTargetDragContext &&
+ gtk_drag_get_source_widget(mTargetDragContext) == nullptr) {
+ return retval;
+ }
+
+ GList* tmp = nullptr;
+ if (mTargetDragContext) {
+ tmp = gdk_drag_context_list_targets(mTargetDragContext);
+ }
+#ifdef MOZ_WAYLAND
+ GList* tmp_head = nullptr;
+ if (mTargetWaylandDragContext) {
+ tmp_head = tmp = mTargetWaylandDragContext->GetTargets();
+ }
+#endif
+
+ // walk the list of context targets and see if one of them is a list
+ // of items.
+ for (; tmp; tmp = tmp->next) {
+ /* Bug 331198 */
+ GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
+ gchar* name = nullptr;
+ name = gdk_atom_name(atom);
+ if (name && strcmp(name, gMimeListType) == 0) retval = true;
+ g_free(name);
+ if (retval) break;
+ }
+
+#ifdef MOZ_WAYLAND
+ // mTargetWaylandDragContext->GetTargets allocates the list
+ // so we need to free it here.
+ if (mTargetWaylandDragContext && tmp_head) {
+ g_list_free(tmp_head);
+ }
+#endif
+
+ return retval;
+}
+
+// Maximum time to wait for a "drag_received" arrived, in microseconds
+#define NS_DND_TIMEOUT 500000
+
+void nsDragService::GetTargetDragData(GdkAtom aFlavor) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("getting data flavor %p\n", aFlavor));
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("mLastWidget is %p and mLastContext is %p\n", mTargetWidget.get(),
+ mTargetDragContext.get()));
+ // reset our target data areas
+ TargetResetData();
+
+ if (mTargetDragContext) {
+ char* name = gdk_atom_name(aFlavor);
+ nsCString flavor(name);
+ g_free(name);
+
+ // We keep a copy of the requested data with the same life-time
+ // as mTargetDragContext.
+ // Especially with multiple items the same data is requested
+ // very often.
+ if (nsTArray<uint8_t>* cached = mCachedData.GetValue(flavor)) {
+ mTargetDragDataLen = cached->Length();
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("Using cached data for %s, length is %d", flavor.get(),
+ mTargetDragDataLen));
+
+ if (mTargetDragDataLen) {
+ mTargetDragData = g_malloc(mTargetDragDataLen);
+ memcpy(mTargetDragData, cached->Elements(), mTargetDragDataLen);
+ }
+
+ mTargetDragDataReceived = true;
+ return;
+ }
+
+ gtk_drag_get_data(mTargetWidget, mTargetDragContext, aFlavor, mTargetTime);
+
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("about to start inner iteration."));
+ PRTime entryTime = PR_Now();
+ while (!mTargetDragDataReceived && mDoingDrag) {
+ // check the number of iterations
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("doing iteration...\n"));
+ PR_Sleep(20 * PR_TicksPerSecond() / 1000); /* sleep for 20 ms/iteration */
+ if (PR_Now() - entryTime > NS_DND_TIMEOUT) break;
+ gtk_main_iteration();
+ }
+ }
+#ifdef MOZ_WAYLAND
+ else {
+ mTargetDragData = mTargetWaylandDragContext->GetData(gdk_atom_name(aFlavor),
+ &mTargetDragDataLen);
+ mTargetDragDataReceived = true;
+ }
+#endif
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("finished inner iteration\n"));
+}
+
+void nsDragService::TargetResetData(void) {
+ mTargetDragDataReceived = false;
+ // make sure to free old data if we have to
+ g_free(mTargetDragData);
+ mTargetDragData = 0;
+ mTargetDragDataLen = 0;
+}
+
+GtkTargetList* nsDragService::GetSourceList(void) {
+ if (!mSourceDataItems) return nullptr;
+ nsTArray<GtkTargetEntry*> targetArray;
+ GtkTargetEntry* targets;
+ GtkTargetList* targetList = 0;
+ uint32_t targetCount = 0;
+ unsigned int numDragItems = 0;
+
+ mSourceDataItems->GetLength(&numDragItems);
+
+ // Check to see if we're dragging > 1 item.
+ if (numDragItems > 1) {
+ // as the Xdnd protocol only supports a single item (or is it just
+ // gtk's implementation?), we don't advertise all flavours listed
+ // in the nsITransferable.
+
+ // the application/x-moz-internal-item-list format, which preserves
+ // all information for drags within the same mozilla instance.
+ GtkTargetEntry* listTarget =
+ (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ listTarget->target = g_strdup(gMimeListType);
+ listTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", listTarget->target));
+ targetArray.AppendElement(listTarget);
+
+ // check what flavours are supported so we can decide what other
+ // targets to advertise.
+ nsCOMPtr<nsITransferable> currItem = do_QueryElementAt(mSourceDataItems, 0);
+
+ if (currItem) {
+ nsTArray<nsCString> flavors;
+ currItem->FlavorsTransferableCanExport(flavors);
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ // check if text/x-moz-url is supported.
+ // If so, advertise
+ // text/uri-list.
+ if (flavors[i].EqualsLiteral(kURLMime)) {
+ listTarget = (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ listTarget->target = g_strdup(gTextUriListType);
+ listTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", listTarget->target));
+ targetArray.AppendElement(listTarget);
+ }
+ }
+ } // if item is a transferable
+ } else if (numDragItems == 1) {
+ nsCOMPtr<nsITransferable> currItem = do_QueryElementAt(mSourceDataItems, 0);
+ if (currItem) {
+ nsTArray<nsCString> flavors;
+ currItem->FlavorsTransferableCanExport(flavors);
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ nsCString& flavorStr = flavors[i];
+
+ GtkTargetEntry* target =
+ (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ target->target = g_strdup(flavorStr.get());
+ target->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("adding target %s\n", target->target));
+ targetArray.AppendElement(target);
+
+ // If there is a file, add the text/uri-list type.
+ if (flavorStr.EqualsLiteral(kFileMime)) {
+ GtkTargetEntry* urilistTarget =
+ (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ urilistTarget->target = g_strdup(gTextUriListType);
+ urilistTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", urilistTarget->target));
+ targetArray.AppendElement(urilistTarget);
+ }
+ // Check to see if this is text/unicode.
+ // If it is, add text/plain
+ // since we automatically support text/plain
+ // if we support text/unicode.
+ else if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ GtkTargetEntry* plainUTF8Target =
+ (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ plainUTF8Target->target = g_strdup(gTextPlainUTF8Type);
+ plainUTF8Target->flags = 0;
+ MOZ_LOG(
+ sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", plainUTF8Target->target));
+ targetArray.AppendElement(plainUTF8Target);
+
+ GtkTargetEntry* plainTarget =
+ (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ plainTarget->target = g_strdup(kTextMime);
+ plainTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", plainTarget->target));
+ targetArray.AppendElement(plainTarget);
+ }
+ // Check to see if this is the x-moz-url type.
+ // If it is, add _NETSCAPE_URL
+ // this is a type used by everybody.
+ else if (flavorStr.EqualsLiteral(kURLMime)) {
+ GtkTargetEntry* urlTarget =
+ (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ urlTarget->target = g_strdup(gMozUrlType);
+ urlTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", urlTarget->target));
+ targetArray.AppendElement(urlTarget);
+ }
+ // XdndDirectSave
+ else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
+ GtkTargetEntry* directsaveTarget =
+ (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ directsaveTarget->target = g_strdup(gXdndDirectSaveType);
+ directsaveTarget->flags = 0;
+ MOZ_LOG(
+ sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", directsaveTarget->target));
+ targetArray.AppendElement(directsaveTarget);
+ }
+ }
+ }
+ }
+
+ // get all the elements that we created.
+ targetCount = targetArray.Length();
+ if (targetCount) {
+ // allocate space to create the list of valid targets
+ targets = (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry) * targetCount);
+ uint32_t targetIndex;
+ for (targetIndex = 0; targetIndex < targetCount; ++targetIndex) {
+ GtkTargetEntry* disEntry = targetArray.ElementAt(targetIndex);
+ // this is a string reference but it will be freed later.
+ targets[targetIndex].target = disEntry->target;
+ targets[targetIndex].flags = disEntry->flags;
+ targets[targetIndex].info = 0;
+ }
+ targetList = gtk_target_list_new(targets, targetCount);
+ // clean up the target list
+ for (uint32_t cleanIndex = 0; cleanIndex < targetCount; ++cleanIndex) {
+ GtkTargetEntry* thisTarget = targetArray.ElementAt(cleanIndex);
+ g_free(thisTarget->target);
+ g_free(thisTarget);
+ }
+ g_free(targets);
+ } else {
+ // We need to create a dummy target list to be able initialize dnd.
+ targetList = gtk_target_list_new(nullptr, 0);
+ }
+ return targetList;
+}
+
+void nsDragService::SourceEndDragSession(GdkDragContext* aContext,
+ gint aResult) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("SourceEndDragSession result %d\n", aResult));
+
+ // this just releases the list of data items that we provide
+ mSourceDataItems = nullptr;
+
+ // Remove this property, if it exists, to satisfy the Direct Save Protocol.
+ GdkAtom property = gdk_atom_intern(gXdndDirectSaveType, FALSE);
+ gdk_property_delete(gdk_drag_context_get_source_window(aContext), property);
+
+ if (!mDoingDrag || mScheduledTask == eDragTaskSourceEnd)
+ // EndDragSession() was already called on drop
+ // or SourceEndDragSession on drag-failed
+ return;
+
+ if (mEndDragPoint.x < 0) {
+ // We don't have a drag end point, so guess
+ gint x, y;
+ GdkDisplay* display = gdk_display_get_default();
+ if (display) {
+ gint scale = mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor();
+ gdk_display_get_pointer(display, nullptr, &x, &y, nullptr);
+ SetDragEndPoint(LayoutDeviceIntPoint(x * scale, y * scale));
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("guess drag end point %d %d\n", x * scale, y * scale));
+ }
+ }
+
+ // Either the drag was aborted or the drop occurred outside the app.
+ // The dropEffect of mDataTransfer is not updated for motion outside the
+ // app, but is needed for the dragend event, so set it now.
+
+ uint32_t dropEffect;
+
+ if (aResult == MOZ_GTK_DRAG_RESULT_SUCCESS) {
+ // With GTK+ versions 2.10.x and prior the drag may have been
+ // cancelled (but no drag-failed signal would have been sent).
+ // aContext->dest_window will be non-nullptr only if the drop was
+ // sent.
+ GdkDragAction action = gdk_drag_context_get_dest_window(aContext)
+ ? gdk_drag_context_get_actions(aContext)
+ : (GdkDragAction)0;
+
+ // Only one bit of action should be set, but, just in case someone
+ // does something funny, erring away from MOVE, and not recording
+ // unusual action combinations as NONE.
+ if (!action)
+ dropEffect = DRAGDROP_ACTION_NONE;
+ else if (action & GDK_ACTION_COPY)
+ dropEffect = DRAGDROP_ACTION_COPY;
+ else if (action & GDK_ACTION_LINK)
+ dropEffect = DRAGDROP_ACTION_LINK;
+ else if (action & GDK_ACTION_MOVE)
+ dropEffect = DRAGDROP_ACTION_MOVE;
+ else
+ dropEffect = DRAGDROP_ACTION_COPY;
+
+ } else {
+ dropEffect = DRAGDROP_ACTION_NONE;
+
+ bool isWaylandTabDrop = false;
+#ifdef MOZ_WAYLAND
+ // Bug 1527976. Wayland protocol does not have any way how to handle
+ // MOZ_GTK_DRAG_RESULT_NO_TARGET drop result so consider all tab
+ // drops as not cancelled on wayland.
+ if (gfxPlatformGtk::GetPlatform()->IsWaylandDisplay() &&
+ aResult == MOZ_GTK_DRAG_RESULT_ERROR) {
+ for (GList* tmp = gdk_drag_context_list_targets(aContext); tmp;
+ tmp = tmp->next) {
+ GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
+ gchar* name = gdk_atom_name(atom);
+ if (name && (strcmp(name, gTabDropType) == 0)) {
+ isWaylandTabDrop = true;
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("is wayland tab drop\n"));
+ break;
+ }
+ }
+ }
+#endif
+ if (aResult != MOZ_GTK_DRAG_RESULT_NO_TARGET && !isWaylandTabDrop) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("drop is user chancelled\n"));
+ mUserCancelled = true;
+ }
+ }
+
+ if (mDataTransfer) {
+ mDataTransfer->SetDropEffectInt(dropEffect);
+ }
+
+ // Schedule the appropriate drag end dom events.
+ Schedule(eDragTaskSourceEnd, nullptr, nullptr, nullptr,
+ LayoutDeviceIntPoint(), 0);
+}
+
+static void CreateURIList(nsIArray* aItems, nsACString& aURIList) {
+ uint32_t length = 0;
+ aItems->GetLength(&length);
+
+ for (uint32_t i = 0; i < length; ++i) {
+ nsCOMPtr<nsITransferable> item = do_QueryElementAt(aItems, i);
+ if (!item) {
+ continue;
+ }
+
+ nsCOMPtr<nsISupports> data;
+ nsresult rv = item->GetTransferData(kURLMime, getter_AddRefs(data));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsISupportsString> string = do_QueryInterface(data);
+
+ nsAutoString text;
+ if (string) {
+ string->GetData(text);
+ }
+
+ // text/x-moz-url is of form url + "\n" + title.
+ // We just want the url.
+ int32_t separatorPos = text.FindChar(u'\n');
+ if (separatorPos >= 0) {
+ text.Truncate(separatorPos);
+ }
+
+ AppendUTF16toUTF8(text, aURIList);
+ aURIList.AppendLiteral("\r\n");
+ continue;
+ }
+
+ // There is no URI available. If there is a file available, create
+ // a URI from the file.
+ rv = item->GetTransferData(kFileMime, getter_AddRefs(data));
+ if (NS_SUCCEEDED(rv)) {
+ if (nsCOMPtr<nsIFile> file = do_QueryInterface(data)) {
+ nsCOMPtr<nsIURI> fileURI;
+ NS_NewFileURI(getter_AddRefs(fileURI), file);
+ if (fileURI) {
+ nsAutoCString spec;
+ fileURI->GetSpec(spec);
+
+ aURIList.Append(spec);
+ aURIList.AppendLiteral("\r\n");
+ }
+ }
+ }
+ }
+}
+
+void nsDragService::SourceDataGet(GtkWidget* aWidget, GdkDragContext* aContext,
+ GtkSelectionData* aSelectionData,
+ guint32 aTime) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::SourceDataGet"));
+ GdkAtom target = gtk_selection_data_get_target(aSelectionData);
+ gchar* typeName = gdk_atom_name(target);
+ if (!typeName) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("failed to get atom name.\n"));
+ return;
+ }
+
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Type is %s\n", typeName));
+ auto freeTypeName = mozilla::MakeScopeExit([&] { g_free(typeName); });
+ // check to make sure that we have data items to return.
+ if (!mSourceDataItems) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Failed to get our data items\n"));
+ return;
+ }
+
+ nsDependentCSubstring mimeFlavor(typeName, strlen(typeName));
+ nsCOMPtr<nsITransferable> item;
+ item = do_QueryElementAt(mSourceDataItems, 0);
+ if (item) {
+ // if someone was asking for text/plain, lookup unicode instead so
+ // we can convert it.
+ bool needToDoConversionToPlainText = false;
+ const char* actualFlavor;
+ if (mimeFlavor.EqualsLiteral(kTextMime) ||
+ mimeFlavor.EqualsLiteral(gTextPlainUTF8Type)) {
+ actualFlavor = kUnicodeMime;
+ needToDoConversionToPlainText = true;
+ }
+ // if someone was asking for _NETSCAPE_URL we need to convert to
+ // plain text but we also need to look for x-moz-url
+ else if (mimeFlavor.EqualsLiteral(gMozUrlType)) {
+ actualFlavor = kURLMime;
+ needToDoConversionToPlainText = true;
+ }
+ // if someone was asking for text/uri-list we need to convert to
+ // plain text.
+ else if (mimeFlavor.EqualsLiteral(gTextUriListType)) {
+ actualFlavor = gTextUriListType;
+ needToDoConversionToPlainText = true;
+ }
+ // Someone is asking for the special Direct Save Protocol type.
+ else if (mimeFlavor.EqualsLiteral(gXdndDirectSaveType)) {
+ // Indicate failure by default.
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)"E", 1);
+
+ GdkAtom property = gdk_atom_intern(gXdndDirectSaveType, FALSE);
+ GdkAtom type = gdk_atom_intern(kTextMime, FALSE);
+
+ guchar* data;
+ gint length;
+ if (!gdk_property_get(gdk_drag_context_get_source_window(aContext),
+ property, type, 0, INT32_MAX, FALSE, nullptr,
+ nullptr, &length, &data)) {
+ return;
+ }
+
+ // Zero-terminate the string.
+ data = (guchar*)g_realloc(data, length + 1);
+ if (!data) return;
+ data[length] = '\0';
+
+ gchar* hostname;
+ char* gfullpath =
+ g_filename_from_uri((const gchar*)data, &hostname, nullptr);
+ g_free(data);
+ if (!gfullpath) return;
+
+ nsCString fullpath(gfullpath);
+ g_free(gfullpath);
+
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("XdndDirectSave filepath is %s\n", fullpath.get()));
+
+ // If there is no hostname in the URI, NULL will be stored.
+ // We should not accept uris with from a different host.
+ if (hostname) {
+ nsCOMPtr<nsIPropertyBag2> infoService =
+ do_GetService(NS_SYSTEMINFO_CONTRACTID);
+ if (!infoService) return;
+
+ nsAutoCString host;
+ if (NS_SUCCEEDED(
+ infoService->GetPropertyAsACString(u"host"_ns, host))) {
+ if (!host.Equals(hostname)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("ignored drag because of different host.\n"));
+
+ // Special error code "F" for this case.
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)"F", 1);
+ g_free(hostname);
+ return;
+ }
+ }
+
+ g_free(hostname);
+ }
+
+ nsCOMPtr<nsIFile> file;
+ if (NS_FAILED(
+ NS_NewNativeLocalFile(fullpath, false, getter_AddRefs(file)))) {
+ return;
+ }
+
+ // We have to split the path into a directory and filename,
+ // because our internal file-promise API is based on these.
+
+ nsCOMPtr<nsIFile> directory;
+ file->GetParent(getter_AddRefs(directory));
+
+ item->SetTransferData(kFilePromiseDirectoryMime, directory);
+
+ nsCOMPtr<nsISupportsString> filenamePrimitive =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
+ if (!filenamePrimitive) return;
+
+ nsAutoString leafName;
+ file->GetLeafName(leafName);
+ filenamePrimitive->SetData(leafName);
+
+ item->SetTransferData(kFilePromiseDestFilename, filenamePrimitive);
+
+ // Request a different type in GetTransferData.
+ actualFlavor = kFilePromiseMime;
+ } else {
+ actualFlavor = typeName;
+ }
+ nsresult rv;
+ nsCOMPtr<nsISupports> data;
+ rv = item->GetTransferData(actualFlavor, getter_AddRefs(data));
+
+ if (strcmp(actualFlavor, kFilePromiseMime) == 0) {
+ if (NS_SUCCEEDED(rv)) {
+ // Indicate success.
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)"S", 1);
+ }
+ return;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ void* tmpData = nullptr;
+ uint32_t tmpDataLen = 0;
+ nsPrimitiveHelpers::CreateDataFromPrimitive(
+ nsDependentCString(actualFlavor), data, &tmpData, &tmpDataLen);
+ // if required, do the extra work to convert unicode to plain
+ // text and replace the output values with the plain text.
+ if (needToDoConversionToPlainText) {
+ char* plainTextData = nullptr;
+ char16_t* castedUnicode = reinterpret_cast<char16_t*>(tmpData);
+ uint32_t plainTextLen = 0;
+ UTF16ToNewUTF8(castedUnicode, tmpDataLen / 2, &plainTextData,
+ &plainTextLen);
+ if (tmpData) {
+ // this was not allocated using glib
+ free(tmpData);
+ tmpData = plainTextData;
+ tmpDataLen = plainTextLen;
+ }
+ }
+ if (tmpData) {
+ // this copies the data
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)tmpData,
+ tmpDataLen);
+ // this wasn't allocated with glib
+ free(tmpData);
+ }
+ } else {
+ if (mimeFlavor.EqualsLiteral(gTextUriListType)) {
+ // fall back for text/uri-list
+ nsAutoCString list;
+ CreateURIList(mSourceDataItems, list);
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)list.get(),
+ list.Length());
+ return;
+ }
+ }
+ }
+}
+
+void nsDragService::SourceBeginDrag(GdkDragContext* aContext) {
+ nsCOMPtr<nsITransferable> transferable =
+ do_QueryElementAt(mSourceDataItems, 0);
+ if (!transferable) return;
+
+ nsTArray<nsCString> flavors;
+ nsresult rv = transferable->FlavorsTransferableCanImport(flavors);
+ NS_ENSURE_SUCCESS(rv, );
+
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ if (flavors[i].EqualsLiteral(kFilePromiseDestFilename)) {
+ nsCOMPtr<nsISupports> data;
+ rv = transferable->GetTransferData(kFilePromiseDestFilename,
+ getter_AddRefs(data));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsISupportsString> fileName = do_QueryInterface(data);
+ if (!fileName) {
+ return;
+ }
+
+ nsAutoString fileNameStr;
+ fileName->GetData(fileNameStr);
+
+ nsCString fileNameCStr;
+ CopyUTF16toUTF8(fileNameStr, fileNameCStr);
+
+ GdkAtom property = gdk_atom_intern(gXdndDirectSaveType, FALSE);
+ GdkAtom type = gdk_atom_intern(kTextMime, FALSE);
+
+ gdk_property_change(gdk_drag_context_get_source_window(aContext),
+ property, type, 8, GDK_PROP_MODE_REPLACE,
+ (const guchar*)fileNameCStr.get(),
+ fileNameCStr.Length());
+ }
+ }
+}
+
+void nsDragService::SetDragIcon(GdkDragContext* aContext) {
+ if (!mHasImage && !mSelection) return;
+
+ LayoutDeviceIntRect dragRect;
+ nsPresContext* pc;
+ RefPtr<SourceSurface> surface;
+ DrawDrag(mSourceNode, mRegion, mScreenPosition, &dragRect, &surface, &pc);
+ if (!pc) return;
+
+ LayoutDeviceIntPoint screenPoint =
+ ConvertToUnscaledDevPixels(pc, mScreenPosition);
+ int32_t offsetX = screenPoint.x - dragRect.x;
+ int32_t offsetY = screenPoint.y - dragRect.y;
+
+ // If a popup is set as the drag image, use its widget. Otherwise, use
+ // the surface that DrawDrag created.
+ //
+ // XXX: Disable drag popups on GTK 3.19.4 and above: see bug 1264454.
+ // Fix this once a new GTK version ships that does not destroy our
+ // widget in gtk_drag_set_icon_widget.
+ if (mDragPopup && gtk_check_version(3, 19, 4)) {
+ GtkWidget* gtkWidget = nullptr;
+ nsIFrame* frame = mDragPopup->GetPrimaryFrame();
+ if (frame) {
+ // DrawDrag ensured that this is a popup frame.
+ nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget();
+ if (widget) {
+ gtkWidget = (GtkWidget*)widget->GetNativeData(NS_NATIVE_SHELLWIDGET);
+ if (gtkWidget) {
+ OpenDragPopup();
+ gtk_drag_set_icon_widget(aContext, gtkWidget, offsetX, offsetY);
+ }
+ }
+ }
+ } else if (surface) {
+ if (!SetAlphaPixmap(surface, aContext, offsetX, offsetY, dragRect)) {
+ GdkPixbuf* dragPixbuf = nsImageToPixbuf::SourceSurfaceToPixbuf(
+ surface, dragRect.width, dragRect.height);
+ if (dragPixbuf) {
+ gtk_drag_set_icon_pixbuf(aContext, dragPixbuf, offsetX, offsetY);
+ g_object_unref(dragPixbuf);
+ }
+ }
+ }
+}
+
+static void invisibleSourceDragBegin(GtkWidget* aWidget,
+ GdkDragContext* aContext, gpointer aData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragBegin"));
+ nsDragService* dragService = (nsDragService*)aData;
+
+ dragService->SourceBeginDrag(aContext);
+ dragService->SetDragIcon(aContext);
+}
+
+static void invisibleSourceDragDataGet(GtkWidget* aWidget,
+ GdkDragContext* aContext,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint32 aTime,
+ gpointer aData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragDataGet"));
+ nsDragService* dragService = (nsDragService*)aData;
+ dragService->SourceDataGet(aWidget, aContext, aSelectionData, aTime);
+}
+
+static gboolean invisibleSourceDragFailed(GtkWidget* aWidget,
+ GdkDragContext* aContext,
+ gint aResult, gpointer aData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragFailed %i", aResult));
+ nsDragService* dragService = (nsDragService*)aData;
+ // End the drag session now (rather than waiting for the drag-end signal)
+ // so that operations performed on dropEffect == none can start immediately
+ // rather than waiting for the drag-failed animation to finish.
+ dragService->SourceEndDragSession(aContext, aResult);
+
+ // We should return TRUE to disable the drag-failed animation iff the
+ // source performed an operation when dropEffect was none, but the handler
+ // of the dragend DOM event doesn't provide this information.
+ return FALSE;
+}
+
+static void invisibleSourceDragEnd(GtkWidget* aWidget, GdkDragContext* aContext,
+ gpointer aData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragEnd"));
+ nsDragService* dragService = (nsDragService*)aData;
+
+ // The drag has ended. Release the hostages!
+ dragService->SourceEndDragSession(aContext, MOZ_GTK_DRAG_RESULT_SUCCESS);
+}
+
+// The following methods handle responding to GTK drag signals and
+// tracking state between these signals.
+//
+// In general, GTK does not expect us to run the event loop while handling its
+// drag signals, however our drag event handlers may run the
+// event loop, most often to fetch information about the drag data.
+//
+// GTK, for example, uses the return value from drag-motion signals to
+// determine whether drag-leave signals should be sent. If an event loop is
+// run during drag-motion the XdndLeave message can get processed but when GTK
+// receives the message it does not yet know that it needs to send the
+// drag-leave signal to our widget.
+//
+// After a drag-drop signal, we need to reply with gtk_drag_finish().
+// However, gtk_drag_finish should happen after the drag-drop signal handler
+// returns so that when the Motif drag protocol is used, the
+// XmTRANSFER_SUCCESS during gtk_drag_finish is sent after the XmDROP_START
+// reply sent on return from the drag-drop signal handler.
+//
+// Similarly drag-end for a successful drag and drag-failed are not good
+// times to run a nested event loop as gtk_drag_drop_finished() and
+// gtk_drag_source_info_destroy() don't gtk_drag_clear_source_info() or remove
+// drop_timeout until after at least the first of these signals is sent.
+// Processing other events (e.g. a slow GDK_DROP_FINISHED reply, or the drop
+// timeout) could cause gtk_drag_drop_finished to be called again with the
+// same GtkDragSourceInfo, which won't like being destroyed twice.
+//
+// Therefore we reply to the signals immediately and schedule a task to
+// dispatch the Gecko events, which may run the event loop.
+//
+// Action in response to drag-leave signals is also delayed until the event
+// loop runs again so that we find out whether a drag-drop signal follows.
+//
+// A single task is scheduled to manage responses to all three GTK signals.
+// If further signals are received while the task is scheduled, the scheduled
+// response is updated, sometimes effectively compressing successive signals.
+//
+// No Gecko drag events are dispatched (during nested event loops) while other
+// Gecko drag events are in flight. This helps event handlers that may not
+// expect nested events, while accessing an event's dataTransfer for example.
+
+gboolean nsDragService::ScheduleMotionEvent(
+ nsWindow* aWindow, GdkDragContext* aDragContext,
+ nsWaylandDragContext* aWaylandDragContext,
+ LayoutDeviceIntPoint aWindowPoint, guint aTime) {
+ if (aDragContext && mScheduledTask == eDragTaskMotion) {
+ // The drag source has sent another motion message before we've
+ // replied to the previous. That shouldn't happen with Xdnd. The
+ // spec for Motif drags is less clear, but we'll just update the
+ // scheduled task with the new position reply only to the most
+ // recent message.
+ NS_WARNING("Drag Motion message received before previous reply was sent");
+ }
+
+ // Returning TRUE means we'll reply with a status message, unless we first
+ // get a leave.
+ return Schedule(eDragTaskMotion, aWindow, aDragContext, aWaylandDragContext,
+ aWindowPoint, aTime);
+}
+
+void nsDragService::ScheduleLeaveEvent() {
+ // We don't know at this stage whether a drop signal will immediately
+ // follow. If the drop signal gets sent it will happen before we return
+ // to the main loop and the scheduled leave task will be replaced.
+ if (!Schedule(eDragTaskLeave, nullptr, nullptr, nullptr,
+ LayoutDeviceIntPoint(), 0)) {
+ NS_WARNING("Drag leave after drop");
+ }
+}
+
+gboolean nsDragService::ScheduleDropEvent(
+ nsWindow* aWindow, GdkDragContext* aDragContext,
+ nsWaylandDragContext* aWaylandDragContext,
+ LayoutDeviceIntPoint aWindowPoint, guint aTime) {
+ if (!Schedule(eDragTaskDrop, aWindow, aDragContext, aWaylandDragContext,
+ aWindowPoint, aTime)) {
+ NS_WARNING("Additional drag drop ignored");
+ return FALSE;
+ }
+
+ SetDragEndPoint(aWindowPoint + aWindow->WidgetToScreenOffset());
+
+ // We'll reply with gtk_drag_finish().
+ return TRUE;
+}
+
+gboolean nsDragService::Schedule(DragTask aTask, nsWindow* aWindow,
+ GdkDragContext* aDragContext,
+ nsWaylandDragContext* aWaylandDragContext,
+ LayoutDeviceIntPoint aWindowPoint,
+ guint aTime) {
+ // If there is an existing leave or motion task scheduled, then that
+ // will be replaced. When the new task is run, it will dispatch
+ // any necessary leave or motion events.
+
+ // If aTask is eDragTaskSourceEnd, then it will replace even a scheduled
+ // drop event (which could happen if the drop event has not been processed
+ // within the allowed time). Otherwise, if we haven't yet run a scheduled
+ // drop or end task, just say that we are not ready to receive another
+ // drop.
+ if (mScheduledTask == eDragTaskSourceEnd ||
+ (mScheduledTask == eDragTaskDrop && aTask != eDragTaskSourceEnd))
+ return FALSE;
+
+ mScheduledTask = aTask;
+ mPendingWindow = aWindow;
+ mPendingDragContext = aDragContext;
+#ifdef MOZ_WAYLAND
+ mPendingWaylandDragContext = aWaylandDragContext;
+#endif
+ mPendingWindowPoint = aWindowPoint;
+ mPendingTime = aTime;
+
+ if (!mTaskSource) {
+ // High priority is used here because the native events involved have
+ // already waited at default priority. Perhaps a lower than default
+ // priority could be used for motion tasks because there is a chance
+ // that a leave or drop is waiting, but managing different priorities
+ // may not be worth the effort. Motion tasks shouldn't queue up as
+ // they should be throttled based on replies.
+ mTaskSource =
+ g_idle_add_full(G_PRIORITY_HIGH, TaskDispatchCallback, this, nullptr);
+ }
+ return TRUE;
+}
+
+gboolean nsDragService::TaskDispatchCallback(gpointer data) {
+ RefPtr<nsDragService> dragService = static_cast<nsDragService*>(data);
+ return dragService->RunScheduledTask();
+}
+
+gboolean nsDragService::RunScheduledTask() {
+ if (mTargetWindow && mTargetWindow != mPendingWindow) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService: dispatch drag leave (%p)\n", mTargetWindow.get()));
+ mTargetWindow->DispatchDragEvent(eDragExit, mTargetWindowPoint, 0);
+
+ if (!mSourceNode) {
+ // The 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 this app).
+ EndDragSession(false, GetCurrentModifiers());
+ }
+ }
+
+ // It is possible that the pending state has been updated during dispatch
+ // of the leave event. That's fine.
+
+ // Now we collect the pending state because, from this point on, we want
+ // to use the same state for all events dispatched. All state is updated
+ // so that when other tasks are scheduled during dispatch here, this
+ // task is considered to have already been run.
+ bool positionHasChanged = mPendingWindow != mTargetWindow ||
+ mPendingWindowPoint != mTargetWindowPoint;
+ DragTask task = mScheduledTask;
+ mScheduledTask = eDragTaskNone;
+ mTargetWindow = std::move(mPendingWindow);
+ mTargetWindowPoint = mPendingWindowPoint;
+
+ if (task == eDragTaskLeave || task == eDragTaskSourceEnd) {
+ if (task == eDragTaskSourceEnd) {
+ // Dispatch drag end events.
+ EndDragSession(true, GetCurrentModifiers());
+ }
+
+ // Nothing more to do
+ // Returning false removes the task source from the event loop.
+ mTaskSource = 0;
+ return FALSE;
+ }
+
+ // This may be the start of a destination drag session.
+ StartDragSession();
+
+ // mTargetWidget may be nullptr if the window has been destroyed.
+ // (The leave event is not scheduled if a drop task is still scheduled.)
+ // We still reply appropriately to indicate that the drop will or didn't
+ // succeeed.
+ mTargetWidget = mTargetWindow->GetMozContainerWidget();
+ mTargetDragContext = std::move(mPendingDragContext);
+#ifdef MOZ_WAYLAND
+ mTargetWaylandDragContext = std::move(mPendingWaylandDragContext);
+#endif
+ mTargetTime = mPendingTime;
+
+ mCachedData.Clear();
+
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
+ // (as at 27 December 2010) indicates that a "drop" event should only be
+ // fired (at the current target element) if the current drag operation is
+ // not none. The current drag operation will only be set to a non-none
+ // value during a "dragover" event.
+ //
+ // If the user has ended the drag before any dragover events have been
+ // sent, then the spec recommends skipping the drop (because the current
+ // drag operation is none). However, here we assume that, by releasing
+ // the mouse button, the user has indicated that they want to drop, so we
+ // proceed with the drop where possible.
+ //
+ // In order to make the events appear to content in the same way as if the
+ // spec is being followed we make sure to dispatch a "dragover" event with
+ // appropriate coordinates and check canDrop before the "drop" event.
+ //
+ // When the Xdnd protocol is used for source/destination communication (as
+ // should be the case with GTK source applications) a dragover event
+ // should have already been sent during the drag-motion signal, which
+ // would have already been received because XdndDrop messages do not
+ // contain a position. However, we can't assume the same when the Motif
+ // protocol is used.
+ if (task == eDragTaskMotion || positionHasChanged) {
+ UpdateDragAction();
+ TakeDragEventDispatchedToChildProcess(); // Clear the old value.
+ DispatchMotionEvents();
+ if (task == eDragTaskMotion) {
+ if (TakeDragEventDispatchedToChildProcess()) {
+ mTargetDragContextForRemote = mTargetDragContext;
+#ifdef MOZ_WAYLAND
+ mTargetWaylandDragContextForRemote = mTargetWaylandDragContext;
+#endif
+ } else {
+ // Reply to tell the source whether we can drop and what
+ // action would be taken.
+ if (mTargetDragContext) {
+ ReplyToDragMotion(mTargetDragContext);
+ }
+#ifdef MOZ_WAYLAND
+ else if (mTargetWaylandDragContext) {
+ ReplyToDragMotion(mTargetWaylandDragContext);
+ }
+#endif
+ }
+ }
+ }
+
+ if (task == eDragTaskDrop) {
+ gboolean success = DispatchDropEvent();
+
+ // Perhaps we should set the del parameter to TRUE when the drag
+ // action is move, but we don't know whether the data was successfully
+ // transferred.
+ if (mTargetDragContext) {
+ gtk_drag_finish(mTargetDragContext, success,
+ /* del = */ FALSE, mTargetTime);
+ }
+
+ // This drag is over, so clear out our reference to the previous
+ // window.
+ mTargetWindow = nullptr;
+ // Make sure to end the drag session. If this drag started in a
+ // different app, we won't get a drag_end signal to end it from.
+ EndDragSession(true, GetCurrentModifiers());
+ }
+
+ // We're done with the drag context.
+ mTargetWidget = nullptr;
+ mTargetDragContext = nullptr;
+#ifdef MOZ_WAYLAND
+ mTargetWaylandDragContext = nullptr;
+#endif
+
+ mCachedData.Clear();
+
+ // If we got another drag signal while running the sheduled task, that
+ // must have happened while running a nested event loop. Leave the task
+ // source on the event loop.
+ if (mScheduledTask != eDragTaskNone) return TRUE;
+
+ // We have no task scheduled.
+ // Returning false removes the task source from the event loop.
+ mTaskSource = 0;
+ return FALSE;
+}
+
+// This will update the drag action based on the information in the
+// drag context. Gtk gets this from a combination of the key settings
+// and what the source is offering.
+
+void nsDragService::UpdateDragAction() {
+ // This doesn't look right. dragSession.dragAction is used by
+ // nsContentUtils::SetDataTransferInEvent() to set the initial
+ // dataTransfer.dropEffect, so GdkDragContext::suggested_action would be
+ // more appropriate. GdkDragContext::actions should be used to set
+ // dataTransfer.effectAllowed, which doesn't currently happen with
+ // external sources.
+
+ // default is to do nothing
+ int action = nsIDragService::DRAGDROP_ACTION_NONE;
+ GdkDragAction gdkAction = GDK_ACTION_DEFAULT;
+ if (mTargetDragContext) {
+ gdkAction = gdk_drag_context_get_actions(mTargetDragContext);
+ }
+#ifdef MOZ_WAYLAND
+ else if (mTargetWaylandDragContext) {
+ gdkAction = mTargetWaylandDragContext->GetAvailableDragActions();
+ }
+#endif
+
+ // set the default just in case nothing matches below
+ if (gdkAction & GDK_ACTION_DEFAULT)
+ action = nsIDragService::DRAGDROP_ACTION_MOVE;
+
+ // first check to see if move is set
+ if (gdkAction & GDK_ACTION_MOVE)
+ action = nsIDragService::DRAGDROP_ACTION_MOVE;
+
+ // then fall to the others
+ else if (gdkAction & GDK_ACTION_LINK)
+ action = nsIDragService::DRAGDROP_ACTION_LINK;
+
+ // copy is ctrl
+ else if (gdkAction & GDK_ACTION_COPY)
+ action = nsIDragService::DRAGDROP_ACTION_COPY;
+
+ // update the drag information
+ SetDragAction(action);
+}
+
+NS_IMETHODIMP
+nsDragService::UpdateDragEffect() {
+ if (mTargetDragContextForRemote) {
+ ReplyToDragMotion(mTargetDragContextForRemote);
+ mTargetDragContextForRemote = nullptr;
+ }
+#ifdef MOZ_WAYLAND
+ else if (mTargetWaylandDragContextForRemote) {
+ ReplyToDragMotion(mTargetWaylandDragContextForRemote);
+ mTargetWaylandDragContextForRemote = nullptr;
+ }
+#endif
+ return NS_OK;
+}
+
+void nsDragService::DispatchMotionEvents() {
+ mCanDrop = false;
+
+ FireDragEventAtSource(eDrag, GetCurrentModifiers());
+
+ mTargetWindow->DispatchDragEvent(eDragOver, mTargetWindowPoint, mTargetTime);
+}
+
+// Returns true if the drop was successful
+gboolean nsDragService::DispatchDropEvent() {
+ // We need to check IsDestroyed here because the nsRefPtr
+ // only protects this from being deleted, it does NOT protect
+ // against nsView::~nsView() calling Destroy() on it, bug 378273.
+ if (mTargetWindow->IsDestroyed()) return FALSE;
+
+ EventMessage msg = mCanDrop ? eDrop : eDragExit;
+
+ mTargetWindow->DispatchDragEvent(msg, mTargetWindowPoint, mTargetTime);
+
+ return mCanDrop;
+}
+
+/* static */
+uint32_t nsDragService::GetCurrentModifiers() {
+ return mozilla::widget::KeymapWrapper::ComputeCurrentKeyModifiers();
+}
diff --git a/widget/gtk/nsDragService.h b/widget/gtk/nsDragService.h
new file mode 100644
index 0000000000..6072824f49
--- /dev/null
+++ b/widget/gtk/nsDragService.h
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/RefPtr.h"
+#include "nsBaseDragService.h"
+#include "nsIObserver.h"
+#include <gtk/gtk.h>
+
+class nsICookieJarSettings;
+class nsWindow;
+class nsWaylandDragContext;
+
+namespace mozilla {
+namespace gfx {
+class SourceSurface;
+}
+} // namespace mozilla
+
+/**
+ * Native GTK DragService wrapper
+ */
+
+class nsDragService final : public nsBaseDragService, public nsIObserver {
+ public:
+ nsDragService();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIOBSERVER
+
+ // nsBaseDragService
+ MOZ_CAN_RUN_SCRIPT virtual nsresult InvokeDragSessionImpl(
+ nsIArray* anArrayTransferables,
+ const mozilla::Maybe<mozilla::CSSIntRegion>& aRegion,
+ uint32_t aActionType) override;
+ // nsIDragService
+ MOZ_CAN_RUN_SCRIPT NS_IMETHOD InvokeDragSession(
+ nsINode* aDOMNode, nsIPrincipal* aPrincipal,
+ nsIContentSecurityPolicy* aCsp, nsICookieJarSettings* aCookieJarSettings,
+ nsIArray* anArrayTransferables, uint32_t aActionType,
+ nsContentPolicyType aContentPolicyType) override;
+ NS_IMETHOD StartDragSession() override;
+ MOZ_CAN_RUN_SCRIPT NS_IMETHOD EndDragSession(bool aDoneDrag,
+ uint32_t aKeyModifiers) override;
+
+ // nsIDragSession
+ NS_IMETHOD SetCanDrop(bool aCanDrop) override;
+ NS_IMETHOD GetCanDrop(bool* aCanDrop) override;
+ NS_IMETHOD GetNumDropItems(uint32_t* aNumItems) override;
+ NS_IMETHOD GetData(nsITransferable* aTransferable,
+ uint32_t aItemIndex) override;
+ NS_IMETHOD IsDataFlavorSupported(const char* aDataFlavor,
+ bool* _retval) override;
+
+ NS_IMETHOD UpdateDragEffect() override;
+
+ // Methods called from nsWindow to handle responding to GTK drag
+ // destination signals
+
+ static already_AddRefed<nsDragService> GetInstance();
+
+ void TargetDataReceived(GtkWidget* aWidget, GdkDragContext* aContext, gint aX,
+ gint aY, GtkSelectionData* aSelection_data,
+ guint aInfo, guint32 aTime);
+
+ gboolean ScheduleMotionEvent(nsWindow* aWindow, GdkDragContext* aDragContext,
+ nsWaylandDragContext* aPendingWaylandDragContext,
+ mozilla::LayoutDeviceIntPoint aWindowPoint,
+ guint aTime);
+ void ScheduleLeaveEvent();
+ gboolean ScheduleDropEvent(nsWindow* aWindow, GdkDragContext* aDragContext,
+ nsWaylandDragContext* aPendingWaylandDragContext,
+ mozilla::LayoutDeviceIntPoint aWindowPoint,
+ guint aTime);
+
+ nsWindow* GetMostRecentDestWindow() {
+ return mScheduledTask == eDragTaskNone ? mTargetWindow : mPendingWindow;
+ }
+
+ // END PUBLIC API
+
+ // These methods are public only so that they can be called from functions
+ // with C calling conventions. They are called for drags started with the
+ // invisible widget.
+ void SourceEndDragSession(GdkDragContext* aContext, gint aResult);
+ void SourceDataGet(GtkWidget* widget, GdkDragContext* context,
+ GtkSelectionData* selection_data, guint32 aTime);
+
+ void SourceBeginDrag(GdkDragContext* aContext);
+
+ // set the drag icon during drag-begin
+ void SetDragIcon(GdkDragContext* aContext);
+
+ protected:
+ virtual ~nsDragService();
+
+ private:
+ // mScheduledTask indicates what signal has been received from GTK and
+ // so what needs to be dispatched when the scheduled task is run. It is
+ // eDragTaskNone when there is no task scheduled (but the
+ // previous task may still not have finished running).
+ enum DragTask {
+ eDragTaskNone,
+ eDragTaskMotion,
+ eDragTaskLeave,
+ eDragTaskDrop,
+ eDragTaskSourceEnd
+ };
+ DragTask mScheduledTask;
+ // mTaskSource is the GSource id for the task that is either scheduled
+ // or currently running. It is 0 if no task is scheduled or running.
+ guint mTaskSource;
+
+ // target/destination side vars
+ // These variables keep track of the state of the current drag.
+
+ // mPendingWindow, mPendingWindowPoint, mPendingDragContext, and
+ // mPendingTime, carry information from the GTK signal that will be used
+ // when the scheduled task is run. mPendingWindow and mPendingDragContext
+ // will be nullptr if the scheduled task is eDragTaskLeave.
+ RefPtr<nsWindow> mPendingWindow;
+ mozilla::LayoutDeviceIntPoint mPendingWindowPoint;
+ RefPtr<GdkDragContext> mPendingDragContext;
+
+ // We cache all data for the current drag context,
+ // because waiting for the data in GetTargetDragData can be very slow.
+ nsDataHashtable<nsCStringHashKey, nsTArray<uint8_t>> mCachedData;
+
+#ifdef MOZ_WAYLAND
+ RefPtr<nsWaylandDragContext> mPendingWaylandDragContext;
+#endif
+ guint mPendingTime;
+
+ // mTargetWindow and mTargetWindowPoint record the position of the last
+ // eDragTaskMotion or eDragTaskDrop task that was run or is still running.
+ // mTargetWindow is cleared once the drag has completed or left.
+ RefPtr<nsWindow> mTargetWindow;
+ mozilla::LayoutDeviceIntPoint mTargetWindowPoint;
+ // mTargetWidget and mTargetDragContext are set only while dispatching
+ // motion or drop events. mTime records the corresponding timestamp.
+ RefPtr<GtkWidget> mTargetWidget;
+ RefPtr<GdkDragContext> mTargetDragContext;
+#ifdef MOZ_WAYLAND
+ RefPtr<nsWaylandDragContext> mTargetWaylandDragContext;
+#endif
+ // mTargetDragContextForRemote is set while waiting for a reply from
+ // a child process.
+ RefPtr<GdkDragContext> mTargetDragContextForRemote;
+#ifdef MOZ_WAYLAND
+ RefPtr<nsWaylandDragContext> mTargetWaylandDragContextForRemote;
+#endif
+ guint mTargetTime;
+
+ // is it OK to drop on us?
+ bool mCanDrop;
+
+ // have we received our drag data?
+ bool mTargetDragDataReceived;
+ // last data received and its length
+ void* mTargetDragData;
+ uint32_t mTargetDragDataLen;
+ // is the current target drag context contain a list?
+ bool IsTargetContextList(void);
+ // this will get the native data from the last target given a
+ // specific flavor
+ void GetTargetDragData(GdkAtom aFlavor);
+ // this will reset all of the target vars
+ void TargetResetData(void);
+
+ // source side vars
+
+ // the source of our drags
+ GtkWidget* mHiddenWidget;
+ // our source data items
+ nsCOMPtr<nsIArray> mSourceDataItems;
+
+ // get a list of the sources in gtk's format
+ GtkTargetList* GetSourceList(void);
+
+ // attempts to create a semi-transparent drag image. Returns TRUE if
+ // successful, FALSE if not
+ bool SetAlphaPixmap(SourceSurface* aPixbuf, GdkDragContext* aContext,
+ int32_t aXOffset, int32_t aYOffset,
+ const mozilla::LayoutDeviceIntRect& dragRect);
+
+ gboolean Schedule(DragTask aTask, nsWindow* aWindow,
+ GdkDragContext* aDragContext,
+ nsWaylandDragContext* aPendingWaylandDragContext,
+ mozilla::LayoutDeviceIntPoint aWindowPoint, guint aTime);
+
+ // Callback for g_idle_add_full() to run mScheduledTask.
+ MOZ_CAN_RUN_SCRIPT static gboolean TaskDispatchCallback(gpointer data);
+ MOZ_CAN_RUN_SCRIPT gboolean RunScheduledTask();
+ void UpdateDragAction();
+ MOZ_CAN_RUN_SCRIPT void DispatchMotionEvents();
+ void ReplyToDragMotion(GdkDragContext* aDragContext);
+#ifdef MOZ_WAYLAND
+ void ReplyToDragMotion(nsWaylandDragContext* aDragContext);
+#endif
+ gboolean DispatchDropEvent();
+ static uint32_t GetCurrentModifiers();
+};
+
+#endif // nsDragService_h__
diff --git a/widget/gtk/nsFilePicker.cpp b/widget/gtk/nsFilePicker.cpp
new file mode 100644
index 0000000000..b1f9ed3961
--- /dev/null
+++ b/widget/gtk/nsFilePicker.cpp
@@ -0,0 +1,646 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <dlfcn.h>
+#include <gtk/gtk.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "mozilla/Types.h"
+#include "nsGtkUtils.h"
+#include "nsIFileURL.h"
+#include "nsIGIOService.h"
+#include "nsIURI.h"
+#include "nsIWidget.h"
+#include "nsIFile.h"
+#include "mozilla/Preferences.h"
+
+#include "nsArrayEnumerator.h"
+#include "nsMemory.h"
+#include "nsEnumeratorUtils.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "MozContainer.h"
+#include "gfxPlatformGtk.h"
+
+#include "nsFilePicker.h"
+
+using namespace mozilla;
+
+#define MAX_PREVIEW_SIZE 180
+// bug 1184009
+#define MAX_PREVIEW_SOURCE_SIZE 4096
+
+nsIFile* nsFilePicker::mPrevDisplayDirectory = nullptr;
+
+void nsFilePicker::Shutdown() { NS_IF_RELEASE(mPrevDisplayDirectory); }
+
+static GtkFileChooserAction GetGtkFileChooserAction(int16_t aMode) {
+ GtkFileChooserAction action;
+
+ switch (aMode) {
+ case nsIFilePicker::modeSave:
+ action = GTK_FILE_CHOOSER_ACTION_SAVE;
+ break;
+
+ case nsIFilePicker::modeGetFolder:
+ action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
+ break;
+
+ case nsIFilePicker::modeOpen:
+ case nsIFilePicker::modeOpenMultiple:
+ action = GTK_FILE_CHOOSER_ACTION_OPEN;
+ break;
+
+ default:
+ NS_WARNING("Unknown nsIFilePicker mode");
+ action = GTK_FILE_CHOOSER_ACTION_OPEN;
+ break;
+ }
+
+ return action;
+}
+
+static void UpdateFilePreviewWidget(GtkFileChooser* file_chooser,
+ gpointer preview_widget_voidptr) {
+ GtkImage* preview_widget = GTK_IMAGE(preview_widget_voidptr);
+ char* image_filename = gtk_file_chooser_get_preview_filename(file_chooser);
+ struct stat st_buf;
+
+ if (!image_filename) {
+ gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
+ return;
+ }
+
+ gint preview_width = 0;
+ gint preview_height = 0;
+ /* check type of file
+ * if file is named pipe, Open is blocking which may lead to UI
+ * nonresponsiveness; if file is directory/socket, it also isn't
+ * likely to get preview */
+ if (stat(image_filename, &st_buf) || (!S_ISREG(st_buf.st_mode))) {
+ g_free(image_filename);
+ gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
+ return; /* stat failed or file is not regular */
+ }
+
+ GdkPixbufFormat* preview_format =
+ gdk_pixbuf_get_file_info(image_filename, &preview_width, &preview_height);
+ if (!preview_format || preview_width <= 0 || preview_height <= 0 ||
+ preview_width > MAX_PREVIEW_SOURCE_SIZE ||
+ preview_height > MAX_PREVIEW_SOURCE_SIZE) {
+ g_free(image_filename);
+ gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
+ return;
+ }
+
+ GdkPixbuf* preview_pixbuf = nullptr;
+ // Only scale down images that are too big
+ if (preview_width > MAX_PREVIEW_SIZE || preview_height > MAX_PREVIEW_SIZE) {
+ preview_pixbuf = gdk_pixbuf_new_from_file_at_size(
+ image_filename, MAX_PREVIEW_SIZE, MAX_PREVIEW_SIZE, nullptr);
+ } else {
+ preview_pixbuf = gdk_pixbuf_new_from_file(image_filename, nullptr);
+ }
+
+ g_free(image_filename);
+
+ if (!preview_pixbuf) {
+ gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
+ return;
+ }
+
+ GdkPixbuf* preview_pixbuf_temp = preview_pixbuf;
+ preview_pixbuf = gdk_pixbuf_apply_embedded_orientation(preview_pixbuf_temp);
+ g_object_unref(preview_pixbuf_temp);
+
+ // This is the easiest way to do center alignment without worrying about
+ // containers Minimum 3px padding each side (hence the 6) just to make things
+ // nice
+ gint x_padding =
+ (MAX_PREVIEW_SIZE + 6 - gdk_pixbuf_get_width(preview_pixbuf)) / 2;
+ gtk_misc_set_padding(GTK_MISC(preview_widget), x_padding, 0);
+
+ gtk_image_set_from_pixbuf(preview_widget, preview_pixbuf);
+ g_object_unref(preview_pixbuf);
+ gtk_file_chooser_set_preview_widget_active(file_chooser, TRUE);
+}
+
+static nsAutoCString MakeCaseInsensitiveShellGlob(const char* aPattern) {
+ // aPattern is UTF8
+ nsAutoCString result;
+ unsigned int len = strlen(aPattern);
+
+ for (unsigned int i = 0; i < len; i++) {
+ if (!g_ascii_isalpha(aPattern[i])) {
+ // non-ASCII characters will also trigger this path, so unicode
+ // is safely handled albeit case-sensitively
+ result.Append(aPattern[i]);
+ continue;
+ }
+
+ // add the lowercase and uppercase version of a character to a bracket
+ // match, so it matches either the lowercase or uppercase char.
+ result.Append('[');
+ result.Append(g_ascii_tolower(aPattern[i]));
+ result.Append(g_ascii_toupper(aPattern[i]));
+ result.Append(']');
+ }
+
+ return result;
+}
+
+NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
+
+nsFilePicker::nsFilePicker()
+ : mSelectedType(0),
+ mRunning(false),
+ mAllowURLs(false),
+ mFileChooserDelegate(nullptr) {
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ // Due to Bug 1635718 always use portal for file dialog on Wayland.
+ if (gfxPlatformGtk::GetPlatform()->IsWaylandDisplay()) {
+ mUseNativeFileChooser =
+ Preferences::GetBool("widget.use-xdg-desktop-portal", true);
+ } else {
+ giovfs->ShouldUseFlatpakPortal(&mUseNativeFileChooser);
+ }
+}
+
+nsFilePicker::~nsFilePicker() = default;
+
+void ReadMultipleFiles(gpointer filename, gpointer array) {
+ nsCOMPtr<nsIFile> localfile;
+ nsresult rv =
+ NS_NewNativeLocalFile(nsDependentCString(static_cast<char*>(filename)),
+ false, getter_AddRefs(localfile));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMArray<nsIFile>& files = *static_cast<nsCOMArray<nsIFile>*>(array);
+ files.AppendObject(localfile);
+ }
+
+ g_free(filename);
+}
+
+void nsFilePicker::ReadValuesFromFileChooser(void* file_chooser) {
+ mFiles.Clear();
+
+ if (mMode == nsIFilePicker::modeOpenMultiple) {
+ mFileURL.Truncate();
+
+ GSList* list =
+ gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(file_chooser));
+ g_slist_foreach(list, ReadMultipleFiles, static_cast<gpointer>(&mFiles));
+ g_slist_free(list);
+ } else {
+ gchar* filename = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(file_chooser));
+ mFileURL.Assign(filename);
+ g_free(filename);
+ }
+
+ GtkFileFilter* filter =
+ gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(file_chooser));
+ GSList* filter_list =
+ gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(file_chooser));
+
+ mSelectedType = static_cast<int16_t>(g_slist_index(filter_list, filter));
+ g_slist_free(filter_list);
+
+ // Remember last used directory.
+ nsCOMPtr<nsIFile> file;
+ GetFile(getter_AddRefs(file));
+ if (file) {
+ nsCOMPtr<nsIFile> dir;
+ file->GetParent(getter_AddRefs(dir));
+ if (dir) {
+ dir.swap(mPrevDisplayDirectory);
+ }
+ }
+}
+
+void nsFilePicker::InitNative(nsIWidget* aParent, const nsAString& aTitle) {
+ mParentWidget = aParent;
+ mTitle.Assign(aTitle);
+}
+
+NS_IMETHODIMP
+nsFilePicker::AppendFilters(int32_t aFilterMask) {
+ mAllowURLs = !!(aFilterMask & filterAllowURLs);
+ return nsBaseFilePicker::AppendFilters(aFilterMask);
+}
+
+NS_IMETHODIMP
+nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) {
+ if (aFilter.EqualsLiteral("..apps")) {
+ // No platform specific thing we can do here, really....
+ return NS_OK;
+ }
+
+ nsAutoCString filter, name;
+ CopyUTF16toUTF8(aFilter, filter);
+ CopyUTF16toUTF8(aTitle, name);
+
+ mFilters.AppendElement(filter);
+ mFilterNames.AppendElement(name);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::SetDefaultString(const nsAString& aString) {
+ mDefault = aString;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetDefaultString(nsAString& aString) {
+ // Per API...
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsFilePicker::SetDefaultExtension(const nsAString& aExtension) {
+ mDefaultExtension = aExtension;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetDefaultExtension(nsAString& aExtension) {
+ aExtension = mDefaultExtension;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFilterIndex(int32_t* aFilterIndex) {
+ *aFilterIndex = mSelectedType;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::SetFilterIndex(int32_t aFilterIndex) {
+ mSelectedType = aFilterIndex;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFile(nsIFile** aFile) {
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ *aFile = nullptr;
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetFileURL(getter_AddRefs(uri));
+ if (!uri) return rv;
+
+ nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(uri, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ file.forget(aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFileURL(nsIURI** aFileURL) {
+ *aFileURL = nullptr;
+ return NS_NewURI(aFileURL, mFileURL);
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFiles(nsISimpleEnumerator** aFiles) {
+ NS_ENSURE_ARG_POINTER(aFiles);
+
+ if (mMode == nsIFilePicker::modeOpenMultiple) {
+ return NS_NewArrayEnumerator(aFiles, mFiles, NS_GET_IID(nsIFile));
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsFilePicker::Show(int16_t* aReturn) {
+ NS_ENSURE_ARG_POINTER(aReturn);
+
+ nsresult rv = Open(nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ while (mRunning) {
+ g_main_context_iteration(nullptr, TRUE);
+ }
+
+ *aReturn = mResult;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::Open(nsIFilePickerShownCallback* aCallback) {
+ // Can't show two dialogs concurrently with the same filepicker
+ if (mRunning) return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ConvertUTF16toUTF8 title(mTitle);
+
+ GtkWindow* parent_widget =
+ GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
+
+ GtkFileChooserAction action = GetGtkFileChooserAction(mMode);
+
+ const gchar* accept_button;
+ NS_ConvertUTF16toUTF8 buttonLabel(mOkButtonLabel);
+ if (!mOkButtonLabel.IsEmpty()) {
+ accept_button = buttonLabel.get();
+ } else {
+ accept_button = nullptr;
+ }
+
+ void* file_chooser =
+ GtkFileChooserNew(title.get(), parent_widget, action, accept_button);
+
+ // If we have --enable-proxy-bypass-protection, then don't allow
+ // remote URLs to be used.
+#ifndef MOZ_PROXY_BYPASS_PROTECTION
+ if (mAllowURLs) {
+ gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(file_chooser), FALSE);
+ }
+#endif
+
+ if (action == GTK_FILE_CHOOSER_ACTION_OPEN ||
+ action == GTK_FILE_CHOOSER_ACTION_SAVE) {
+ GtkWidget* img_preview = gtk_image_new();
+ gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(file_chooser),
+ img_preview);
+ g_signal_connect(file_chooser, "update-preview",
+ G_CALLBACK(UpdateFilePreviewWidget), img_preview);
+ }
+
+ GtkFileChooserSetModal(file_chooser, parent_widget, TRUE);
+
+ NS_ConvertUTF16toUTF8 defaultName(mDefault);
+ switch (mMode) {
+ case nsIFilePicker::modeOpenMultiple:
+ gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(file_chooser),
+ TRUE);
+ break;
+ case nsIFilePicker::modeSave:
+ gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(file_chooser),
+ defaultName.get());
+ break;
+ }
+
+ nsCOMPtr<nsIFile> defaultPath;
+ if (mDisplayDirectory) {
+ mDisplayDirectory->Clone(getter_AddRefs(defaultPath));
+ } else if (mPrevDisplayDirectory) {
+ mPrevDisplayDirectory->Clone(getter_AddRefs(defaultPath));
+ }
+
+ if (defaultPath) {
+ if (!defaultName.IsEmpty() && mMode != nsIFilePicker::modeSave) {
+ // Try to select the intended file. Even if it doesn't exist, GTK still
+ // switches directories.
+ defaultPath->AppendNative(defaultName);
+ nsAutoCString path;
+ defaultPath->GetNativePath(path);
+ gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(file_chooser), path.get());
+ } else {
+ nsAutoCString directory;
+ defaultPath->GetNativePath(directory);
+
+ // Workaround for problematic refcounting in GTK3 before 3.16.
+ // We need to keep a reference to the dialog's internal delegate.
+ // Otherwise, if our dialog gets destroyed, we'll lose the dialog's
+ // delegate by the time this gets processed in the event loop.
+ // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1166741
+ if (GTK_IS_DIALOG(file_chooser)) {
+ GtkDialog* dialog = GTK_DIALOG(file_chooser);
+ GtkContainer* area = GTK_CONTAINER(gtk_dialog_get_content_area(dialog));
+ gtk_container_forall(
+ area,
+ [](GtkWidget* widget, gpointer data) {
+ if (GTK_IS_FILE_CHOOSER_WIDGET(widget)) {
+ auto result = static_cast<GtkFileChooserWidget**>(data);
+ *result = GTK_FILE_CHOOSER_WIDGET(widget);
+ }
+ },
+ &mFileChooserDelegate);
+
+ if (mFileChooserDelegate != nullptr) {
+ g_object_ref(mFileChooserDelegate);
+ }
+ }
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_chooser),
+ directory.get());
+ }
+ }
+
+ if (GTK_IS_DIALOG(file_chooser)) {
+ gtk_dialog_set_default_response(GTK_DIALOG(file_chooser),
+ GTK_RESPONSE_ACCEPT);
+ }
+
+ int32_t count = mFilters.Length();
+ for (int32_t i = 0; i < count; ++i) {
+ // This is fun... the GTK file picker does not accept a list of filters
+ // so we need to split out each string, and add it manually.
+
+ char** patterns = g_strsplit(mFilters[i].get(), ";", -1);
+ if (!patterns) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ GtkFileFilter* filter = gtk_file_filter_new();
+ for (int j = 0; patterns[j] != nullptr; ++j) {
+ nsAutoCString caseInsensitiveFilter =
+ MakeCaseInsensitiveShellGlob(g_strstrip(patterns[j]));
+ gtk_file_filter_add_pattern(filter, caseInsensitiveFilter.get());
+ }
+
+ g_strfreev(patterns);
+
+ if (!mFilterNames[i].IsEmpty()) {
+ // If we have a name for our filter, let's use that.
+ const char* filter_name = mFilterNames[i].get();
+ gtk_file_filter_set_name(filter, filter_name);
+ } else {
+ // If we don't have a name, let's just use the filter pattern.
+ const char* filter_pattern = mFilters[i].get();
+ gtk_file_filter_set_name(filter, filter_pattern);
+ }
+
+ gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser), filter);
+
+ // Set the initially selected filter
+ if (mSelectedType == i) {
+ gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(file_chooser), filter);
+ }
+ }
+
+ gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_chooser),
+ TRUE);
+
+ mRunning = true;
+ mCallback = aCallback;
+ NS_ADDREF_THIS();
+ g_signal_connect(file_chooser, "response", G_CALLBACK(OnResponse), this);
+ GtkFileChooserShow(file_chooser);
+
+ return NS_OK;
+}
+
+/* static */
+void nsFilePicker::OnResponse(void* file_chooser, gint response_id,
+ gpointer user_data) {
+ static_cast<nsFilePicker*>(user_data)->Done(file_chooser, response_id);
+}
+
+/* static */
+void nsFilePicker::OnDestroy(GtkWidget* file_chooser, gpointer user_data) {
+ static_cast<nsFilePicker*>(user_data)->Done(file_chooser,
+ GTK_RESPONSE_CANCEL);
+}
+
+void nsFilePicker::Done(void* file_chooser, gint response) {
+ mRunning = false;
+
+ int16_t result;
+ switch (response) {
+ case GTK_RESPONSE_OK:
+ case GTK_RESPONSE_ACCEPT:
+ ReadValuesFromFileChooser(file_chooser);
+ result = nsIFilePicker::returnOK;
+ if (mMode == nsIFilePicker::modeSave) {
+ nsCOMPtr<nsIFile> file;
+ GetFile(getter_AddRefs(file));
+ if (file) {
+ bool exists = false;
+ file->Exists(&exists);
+ if (exists) result = nsIFilePicker::returnReplace;
+ }
+ }
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_CLOSE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ result = nsIFilePicker::returnCancel;
+ break;
+
+ default:
+ NS_WARNING("Unexpected response");
+ result = nsIFilePicker::returnCancel;
+ break;
+ }
+
+ // A "response" signal won't be sent again but "destroy" will be.
+ g_signal_handlers_disconnect_by_func(file_chooser, FuncToGpointer(OnDestroy),
+ this);
+
+ // When response_id is GTK_RESPONSE_DELETE_EVENT or when called from
+ // OnDestroy, the widget would be destroyed anyway but it is fine if
+ // gtk_widget_destroy is called more than once. gtk_widget_destroy has
+ // requests that any remaining references be released, but the reference
+ // count will not be decremented again if GtkWindow's reference has already
+ // been released.
+ GtkFileChooserDestroy(file_chooser);
+
+ if (mFileChooserDelegate) {
+ // Properly deref our acquired reference. We call this after
+ // gtk_widget_destroy() to try and ensure that pending file info
+ // queries caused by updating the current folder have been cancelled.
+ // However, we do not know for certain when the callback will run after
+ // cancelled.
+ g_idle_add(
+ [](gpointer data) -> gboolean {
+ g_object_unref(data);
+ return G_SOURCE_REMOVE;
+ },
+ mFileChooserDelegate);
+ mFileChooserDelegate = nullptr;
+ }
+
+ if (mCallback) {
+ mCallback->Done(result);
+ mCallback = nullptr;
+ } else {
+ mResult = result;
+ }
+ NS_RELEASE_THIS();
+}
+
+// All below functions available as of GTK 3.20+
+void* nsFilePicker::GtkFileChooserNew(const gchar* title, GtkWindow* parent,
+ GtkFileChooserAction action,
+ const gchar* accept_label) {
+ static auto sGtkFileChooserNativeNewPtr =
+ (void* (*)(const gchar*, GtkWindow*, GtkFileChooserAction, const gchar*,
+ const gchar*))dlsym(RTLD_DEFAULT,
+ "gtk_file_chooser_native_new");
+ if (mUseNativeFileChooser && sGtkFileChooserNativeNewPtr != nullptr) {
+ return (*sGtkFileChooserNativeNewPtr)(title, parent, action, accept_label,
+ nullptr);
+ }
+ if (accept_label == nullptr) {
+ accept_label = (action == GTK_FILE_CHOOSER_ACTION_SAVE) ? GTK_STOCK_SAVE
+ : GTK_STOCK_OPEN;
+ }
+ GtkWidget* file_chooser = gtk_file_chooser_dialog_new(
+ title, parent, action, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ accept_label, GTK_RESPONSE_ACCEPT, nullptr);
+ gtk_dialog_set_alternative_button_order(
+ GTK_DIALOG(file_chooser), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_CANCEL, -1);
+ return file_chooser;
+}
+
+void nsFilePicker::GtkFileChooserShow(void* file_chooser) {
+ static auto sGtkNativeDialogShowPtr =
+ (void (*)(void*))dlsym(RTLD_DEFAULT, "gtk_native_dialog_show");
+ if (mUseNativeFileChooser && sGtkNativeDialogShowPtr != nullptr) {
+ const char* portalEnvString = g_getenv("GTK_USE_PORTAL");
+ bool setPortalEnv =
+ (portalEnvString && atoi(portalEnvString) == 0) || !portalEnvString;
+ if (setPortalEnv) {
+ setenv("GTK_USE_PORTAL", "1", true);
+ }
+ (*sGtkNativeDialogShowPtr)(file_chooser);
+ if (setPortalEnv) {
+ unsetenv("GTK_USE_PORTAL");
+ }
+ } else {
+ g_signal_connect(file_chooser, "destroy", G_CALLBACK(OnDestroy), this);
+ gtk_widget_show(GTK_WIDGET(file_chooser));
+ }
+}
+
+void nsFilePicker::GtkFileChooserDestroy(void* file_chooser) {
+ static auto sGtkNativeDialogDestroyPtr =
+ (void (*)(void*))dlsym(RTLD_DEFAULT, "gtk_native_dialog_destroy");
+ if (mUseNativeFileChooser && sGtkNativeDialogDestroyPtr != nullptr) {
+ (*sGtkNativeDialogDestroyPtr)(file_chooser);
+ } else {
+ gtk_widget_destroy(GTK_WIDGET(file_chooser));
+ }
+}
+
+void nsFilePicker::GtkFileChooserSetModal(void* file_chooser,
+ GtkWindow* parent_widget,
+ gboolean modal) {
+ static auto sGtkNativeDialogSetModalPtr = (void (*)(void*, gboolean))dlsym(
+ RTLD_DEFAULT, "gtk_native_dialog_set_modal");
+ if (mUseNativeFileChooser && sGtkNativeDialogSetModalPtr != nullptr) {
+ (*sGtkNativeDialogSetModalPtr)(file_chooser, modal);
+ } else {
+ GtkWindow* window = GTK_WINDOW(file_chooser);
+ gtk_window_set_modal(window, modal);
+ if (parent_widget != nullptr) {
+ gtk_window_set_destroy_with_parent(window, modal);
+ }
+ }
+}
diff --git a/widget/gtk/nsFilePicker.h b/widget/gtk/nsFilePicker.h
new file mode 100644
index 0000000000..9b3110aa00
--- /dev/null
+++ b/widget/gtk/nsFilePicker.h
@@ -0,0 +1,87 @@
+/* -*- 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 <gtk/gtk.h>
+
+#include "nsBaseFilePicker.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+
+class nsIWidget;
+class nsIFile;
+
+class nsFilePicker : public nsBaseFilePicker {
+ public:
+ nsFilePicker();
+
+ NS_DECL_ISUPPORTS
+
+ // nsIFilePicker (less what's in nsBaseFilePicker)
+ NS_IMETHOD Open(nsIFilePickerShownCallback* aCallback) override;
+ NS_IMETHOD AppendFilters(int32_t aFilterMask) override;
+ NS_IMETHOD AppendFilter(const nsAString& aTitle,
+ const nsAString& aFilter) override;
+ NS_IMETHOD SetDefaultString(const nsAString& aString) override;
+ NS_IMETHOD GetDefaultString(nsAString& aString) override;
+ NS_IMETHOD SetDefaultExtension(const nsAString& aExtension) override;
+ NS_IMETHOD GetDefaultExtension(nsAString& aExtension) override;
+ NS_IMETHOD GetFilterIndex(int32_t* aFilterIndex) override;
+ NS_IMETHOD SetFilterIndex(int32_t aFilterIndex) override;
+ NS_IMETHOD GetFile(nsIFile** aFile) override;
+ NS_IMETHOD GetFileURL(nsIURI** aFileURL) override;
+ NS_IMETHOD GetFiles(nsISimpleEnumerator** aFiles) override;
+
+ // nsBaseFilePicker
+ virtual void InitNative(nsIWidget* aParent, const nsAString& aTitle) override;
+
+ static void Shutdown();
+
+ protected:
+ virtual ~nsFilePicker();
+
+ nsresult Show(int16_t* aReturn) override;
+ void ReadValuesFromFileChooser(void* file_chooser);
+
+ static void OnResponse(void* file_chooser, gint response_id,
+ gpointer user_data);
+ static void OnDestroy(GtkWidget* file_chooser, gpointer user_data);
+ void Done(void* file_chooser, gint response_id);
+
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsCOMPtr<nsIFilePickerShownCallback> mCallback;
+ nsCOMArray<nsIFile> mFiles;
+
+ int16_t mSelectedType;
+ int16_t mResult;
+ bool mRunning;
+ bool mAllowURLs;
+ nsCString mFileURL;
+ nsString mTitle;
+ nsString mDefault;
+ nsString mDefaultExtension;
+
+ nsTArray<nsCString> mFilters;
+ nsTArray<nsCString> mFilterNames;
+
+ private:
+ static nsIFile* mPrevDisplayDirectory;
+
+ void* GtkFileChooserNew(const gchar* title, GtkWindow* parent,
+ GtkFileChooserAction action,
+ const gchar* accept_label);
+ void GtkFileChooserShow(void* file_chooser);
+ void GtkFileChooserDestroy(void* file_chooser);
+ void GtkFileChooserSetModal(void* file_chooser, GtkWindow* parent_widget,
+ gboolean modal);
+
+ GtkFileChooserWidget* mFileChooserDelegate;
+ bool mUseNativeFileChooser;
+};
+
+#endif
diff --git a/widget/gtk/nsGTKToolkit.h b/widget/gtk/nsGTKToolkit.h
new file mode 100644
index 0000000000..a4708d5466
--- /dev/null
+++ b/widget/gtk/nsGTKToolkit.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 GTKTOOLKIT_H
+#define GTKTOOLKIT_H
+
+#include "nsString.h"
+#include <gtk/gtk.h>
+
+/**
+ * Wrapper around the thread running the message pump.
+ * The toolkit abstraction is necessary because the message pump must
+ * execute within the same thread that created the widget under Win32.
+ */
+
+class nsGTKToolkit {
+ public:
+ nsGTKToolkit();
+
+ static nsGTKToolkit* GetToolkit();
+
+ static void Shutdown() {
+ delete gToolkit;
+ gToolkit = nullptr;
+ }
+
+ /**
+ * Get/set our value of DESKTOP_STARTUP_ID. When non-empty, this is applied
+ * to the next toplevel window to be shown or focused (and then immediately
+ * cleared).
+ */
+ void SetDesktopStartupID(const nsACString& aID) { mDesktopStartupID = aID; }
+ void GetDesktopStartupID(nsACString* aID) { *aID = mDesktopStartupID; }
+
+ /**
+ * Get/set the timestamp value to be used, if non-zero, to focus the
+ * next top-level window to be shown or focused (upon which it is cleared).
+ */
+ void SetFocusTimestamp(uint32_t aTimestamp) { mFocusTimestamp = aTimestamp; }
+ uint32_t GetFocusTimestamp() { return mFocusTimestamp; }
+
+ private:
+ static nsGTKToolkit* gToolkit;
+
+ nsCString mDesktopStartupID;
+ uint32_t mFocusTimestamp;
+};
+
+#endif // GTKTOOLKIT_H
diff --git a/widget/gtk/nsGtkCursors.h b/widget/gtk/nsGtkCursors.h
new file mode 100644
index 0000000000..f30ed593a9
--- /dev/null
+++ b/widget/gtk/nsGtkCursors.h
@@ -0,0 +1,416 @@
+/* -*- 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 nsGtkCursors_h__
+#define nsGtkCursors_h__
+
+typedef struct {
+ const unsigned char* bits;
+ const unsigned char* mask_bits;
+ int hot_x;
+ int hot_y;
+ const char* hash;
+} nsGtkCursor;
+
+/* MOZ_CURSOR_HAND_GRAB */
+static const unsigned char moz_hand_grab_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00,
+ 0x60, 0x39, 0x00, 0x00, 0x90, 0x49, 0x00, 0x00, 0x90, 0x49, 0x01, 0x00,
+ 0x20, 0xc9, 0x02, 0x00, 0x20, 0x49, 0x02, 0x00, 0x58, 0x40, 0x02, 0x00,
+ 0x64, 0x00, 0x02, 0x00, 0x44, 0x00, 0x01, 0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x10, 0x80, 0x00, 0x00, 0x20, 0x80, 0x00, 0x00,
+ 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_hand_grab_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x60, 0x3f, 0x00, 0x00,
+ 0xf0, 0x7f, 0x00, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf8, 0xff, 0x03, 0x00,
+ 0xf0, 0xff, 0x07, 0x00, 0xf8, 0xff, 0x07, 0x00, 0xfc, 0xff, 0x07, 0x00,
+ 0xfe, 0xff, 0x07, 0x00, 0xfe, 0xff, 0x03, 0x00, 0xfc, 0xff, 0x03, 0x00,
+ 0xf8, 0xff, 0x03, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x01, 0x00,
+ 0xe0, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_HAND_GRABBING */
+static const unsigned char moz_hand_grabbing_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xc0, 0x36, 0x00, 0x00, 0x20, 0xc9, 0x00, 0x00, 0x20, 0x40, 0x01, 0x00,
+ 0x40, 0x00, 0x01, 0x00, 0x60, 0x00, 0x01, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x10, 0x80, 0x00, 0x00, 0x20, 0x80, 0x00, 0x00,
+ 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_hand_grabbing_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x36, 0x00, 0x00,
+ 0xe0, 0xff, 0x00, 0x00, 0xf0, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x03, 0x00,
+ 0xe0, 0xff, 0x03, 0x00, 0xf0, 0xff, 0x03, 0x00, 0xf8, 0xff, 0x03, 0x00,
+ 0xf8, 0xff, 0x03, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x01, 0x00,
+ 0xe0, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_COPY */
+static const unsigned char moz_copy_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00,
+ 0xfc, 0x03, 0x00, 0x00, 0x7c, 0x30, 0x00, 0x00, 0x6c, 0x30, 0x00, 0x00,
+ 0xc4, 0xfc, 0x00, 0x00, 0xc0, 0xfc, 0x00, 0x00, 0x80, 0x31, 0x00, 0x00,
+ 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_copy_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00,
+ 0xfe, 0x37, 0x00, 0x00, 0xfe, 0x7b, 0x00, 0x00, 0xfe, 0xfc, 0x00, 0x00,
+ 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x00, 0x00,
+ 0xc0, 0x7b, 0x00, 0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_ALIAS */
+static const unsigned char moz_alias_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00,
+ 0xfc, 0x03, 0x00, 0x00, 0x7c, 0xf0, 0x00, 0x00, 0x6c, 0xe0, 0x00, 0x00,
+ 0xc4, 0xf0, 0x00, 0x00, 0xc0, 0xb0, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00,
+ 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_alias_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00,
+ 0xfe, 0xf7, 0x00, 0x00, 0xfe, 0xfb, 0x01, 0x00, 0xfe, 0xf0, 0x01, 0x00,
+ 0xee, 0xf9, 0x01, 0x00, 0xe4, 0xf9, 0x01, 0x00, 0xc0, 0xbf, 0x00, 0x00,
+ 0xc0, 0x3f, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_CONTEXT_MENU */
+static const unsigned char moz_menu_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0xfd, 0x00, 0x00,
+ 0xfc, 0xff, 0x00, 0x00, 0x7c, 0x84, 0x00, 0x00, 0x6c, 0xfc, 0x00, 0x00,
+ 0xc4, 0x84, 0x00, 0x00, 0xc0, 0xfc, 0x00, 0x00, 0x80, 0x85, 0x00, 0x00,
+ 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_menu_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0xfd, 0x00, 0x00, 0xfe, 0xff, 0x01, 0x00,
+ 0xfe, 0xff, 0x01, 0x00, 0xfe, 0xff, 0x01, 0x00, 0xfe, 0xfe, 0x01, 0x00,
+ 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00,
+ 0xc0, 0xff, 0x01, 0x00, 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_SPINNING */
+static const unsigned char moz_spinning_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00,
+ 0xfc, 0x3b, 0x00, 0x00, 0x7c, 0x38, 0x00, 0x00, 0x6c, 0x54, 0x00, 0x00,
+ 0xc4, 0xdc, 0x00, 0x00, 0xc0, 0x44, 0x00, 0x00, 0x80, 0x39, 0x00, 0x00,
+ 0x80, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_spinning_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x3b, 0x00, 0x00,
+ 0xfe, 0x7f, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0xfe, 0xfe, 0x00, 0x00,
+ 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00,
+ 0xc0, 0x7f, 0x00, 0x00, 0x80, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_ZOOM_IN */
+static const unsigned char moz_zoom_in_bits[] = {
+ 0xf0, 0x00, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00,
+ 0x62, 0x04, 0x00, 0x00, 0x61, 0x08, 0x00, 0x00, 0xf9, 0x09, 0x00, 0x00,
+ 0xf9, 0x09, 0x00, 0x00, 0x61, 0x08, 0x00, 0x00, 0x62, 0x04, 0x00, 0x00,
+ 0x02, 0x04, 0x00, 0x00, 0x0c, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_zoom_in_mask_bits[] = {
+ 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xfc, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_ZOOM_OUT */
+static const unsigned char moz_zoom_out_bits[] = {
+ 0xf0, 0x00, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00,
+ 0x02, 0x04, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0xf9, 0x09, 0x00, 0x00,
+ 0xf9, 0x09, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00,
+ 0x02, 0x04, 0x00, 0x00, 0x0c, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_zoom_out_mask_bits[] = {
+ 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xfc, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_NOT_ALLOWED */
+static const unsigned char moz_not_allowed_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x1f, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00,
+ 0xf0, 0xf0, 0x00, 0x00, 0x38, 0xc0, 0x01, 0x00, 0x7c, 0x80, 0x03, 0x00,
+ 0xec, 0x00, 0x03, 0x00, 0xce, 0x01, 0x07, 0x00, 0x86, 0x03, 0x06, 0x00,
+ 0x06, 0x07, 0x06, 0x00, 0x06, 0x0e, 0x06, 0x00, 0x06, 0x1c, 0x06, 0x00,
+ 0x0e, 0x38, 0x07, 0x00, 0x0c, 0x70, 0x03, 0x00, 0x1c, 0xe0, 0x03, 0x00,
+ 0x38, 0xc0, 0x01, 0x00, 0xf0, 0xf0, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00,
+ 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_not_allowed_mask_bits[] = {
+ 0x80, 0x1f, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, 0xf0, 0xff, 0x00, 0x00,
+ 0xf8, 0xff, 0x01, 0x00, 0xfc, 0xf0, 0x03, 0x00, 0xfe, 0xc0, 0x07, 0x00,
+ 0xfe, 0x81, 0x07, 0x00, 0xff, 0x83, 0x0f, 0x00, 0xcf, 0x07, 0x0f, 0x00,
+ 0x8f, 0x0f, 0x0f, 0x00, 0x0f, 0x1f, 0x0f, 0x00, 0x0f, 0x3e, 0x0f, 0x00,
+ 0x1f, 0xfc, 0x0f, 0x00, 0x1e, 0xf8, 0x07, 0x00, 0x3e, 0xf0, 0x07, 0x00,
+ 0xfc, 0xf0, 0x03, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x00, 0x00,
+ 0xe0, 0x7f, 0x00, 0x00, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_VERTICAL_TEXT */
+static const unsigned char moz_vertical_text_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00,
+ 0x06, 0x60, 0x00, 0x00, 0xfc, 0x3f, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00,
+ 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_vertical_text_mask_bits[] = {
+ 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+ 0x0f, 0xf0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_NESW_RESIZE */
+static const unsigned char moz_nesw_resize_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0xbe, 0x00, 0x00, 0x00, 0xbc, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00,
+ 0x02, 0xb4, 0x00, 0x00, 0x02, 0xa2, 0x00, 0x00, 0x02, 0x81, 0x00, 0x00,
+ 0x8a, 0x80, 0x00, 0x00, 0x5a, 0x80, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
+ 0x7a, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_nesw_resize_mask_bits[] = {
+ 0xc0, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00,
+ 0x00, 0xff, 0x01, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfc, 0x01, 0x00,
+ 0x07, 0xfe, 0x01, 0x00, 0x07, 0xf7, 0x01, 0x00, 0x8f, 0xe3, 0x01, 0x00,
+ 0xdf, 0xc1, 0x01, 0x00, 0xff, 0xc0, 0x01, 0x00, 0x7f, 0x00, 0x00, 0x00,
+ 0xff, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_NWSE_RESIZE */
+static const unsigned char moz_nwse_resize_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0xfa, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
+ 0x5a, 0x80, 0x00, 0x00, 0x8a, 0x80, 0x00, 0x00, 0x02, 0x81, 0x00, 0x00,
+ 0x02, 0xa2, 0x00, 0x00, 0x02, 0xb4, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00,
+ 0x00, 0xbc, 0x00, 0x00, 0x00, 0xbe, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0xc0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_nwse_resize_mask_bits[] = {
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
+ 0xff, 0xc0, 0x01, 0x00, 0xdf, 0xc1, 0x01, 0x00, 0x8f, 0xe3, 0x01, 0x00,
+ 0x07, 0xf7, 0x01, 0x00, 0x07, 0xfe, 0x01, 0x00, 0x00, 0xfc, 0x01, 0x00,
+ 0x00, 0xfe, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00,
+ 0xc0, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_NONE */
+static const unsigned char moz_none_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_none_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+enum {
+ MOZ_CURSOR_HAND_GRAB,
+ MOZ_CURSOR_HAND_GRABBING,
+ MOZ_CURSOR_COPY,
+ MOZ_CURSOR_ALIAS,
+ MOZ_CURSOR_CONTEXT_MENU,
+ MOZ_CURSOR_SPINNING,
+ MOZ_CURSOR_ZOOM_IN,
+ MOZ_CURSOR_ZOOM_OUT,
+ MOZ_CURSOR_NOT_ALLOWED,
+ MOZ_CURSOR_VERTICAL_TEXT,
+ MOZ_CURSOR_NESW_RESIZE,
+ MOZ_CURSOR_NWSE_RESIZE,
+ MOZ_CURSOR_NONE
+};
+
+// create custom pixmap cursor. The hash values must stay in sync with the
+// bitmap data above. To see the hash function, have a look at XcursorImageHash
+// in libXcursor
+static const nsGtkCursor GtkCursors[] = {
+ {moz_hand_grab_bits, moz_hand_grab_mask_bits, 10, 10,
+ "5aca4d189052212118709018842178c0"},
+ {moz_hand_grabbing_bits, moz_hand_grabbing_mask_bits, 10, 10,
+ "208530c400c041818281048008011002"},
+ {moz_copy_bits, moz_copy_mask_bits, 2, 2,
+ "08ffe1cb5fe6fc01f906f1c063814ccf"},
+ {moz_alias_bits, moz_alias_mask_bits, 2, 2,
+ "0876e1c15ff2fc01f906f1c363074c0f"},
+ {moz_menu_bits, moz_menu_mask_bits, 2, 2,
+ "08ffe1e65f80fcfdf9fff11263e74c48"},
+ {moz_spinning_bits, moz_spinning_mask_bits, 2, 2,
+ "08e8e1c95fe2fc01f976f1e063a24ccd"},
+ {moz_zoom_in_bits, moz_zoom_in_mask_bits, 6, 6,
+ "f41c0e382c94c0958e07017e42b00462"},
+ {moz_zoom_out_bits, moz_zoom_out_mask_bits, 6, 6,
+ "f41c0e382c97c0938e07017e42800402"},
+ {moz_not_allowed_bits, moz_not_allowed_mask_bits, 9, 9,
+ "03b6e0fcb3499374a867d041f52298f0"},
+ {moz_vertical_text_bits, moz_vertical_text_mask_bits, 8, 4,
+ "048008013003cff3c00c801001200000"},
+ {moz_nesw_resize_bits, moz_nesw_resize_mask_bits, 8, 8,
+ "50585d75b494802d0151028115016902"},
+ {moz_nwse_resize_bits, moz_nwse_resize_mask_bits, 8, 8,
+ "38c5dff7c7b8962045400281044508d2"},
+ {moz_none_bits, moz_none_mask_bits, 0, 0, nullptr}};
+
+#endif /* nsGtkCursors_h__ */
diff --git a/widget/gtk/nsGtkKeyUtils.cpp b/widget/gtk/nsGtkKeyUtils.cpp
new file mode 100644
index 0000000000..41a679eab7
--- /dev/null
+++ b/widget/gtk/nsGtkKeyUtils.cpp
@@ -0,0 +1,2377 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "mozilla/Logging.h"
+
+#include "nsGtkKeyUtils.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <algorithm>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <dlfcn.h>
+#include <gdk/gdkkeysyms-compat.h>
+#include <X11/XKBlib.h>
+#include "X11UndefineNone.h"
+#include "IMContextWrapper.h"
+#include "WidgetUtils.h"
+#include "keysym2ucs.h"
+#include "nsContentUtils.h"
+#include "nsGtkUtils.h"
+#include "nsIBidiKeyboard.h"
+#include "nsPrintfCString.h"
+#include "nsReadableUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWindow.h"
+#include "gfxPlatformGtk.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+
+#ifdef MOZ_WAYLAND
+# include <sys/mman.h>
+# include "nsWaylandDisplay.h"
+#endif
+
+namespace mozilla {
+namespace widget {
+
+LazyLogModule gKeymapWrapperLog("KeymapWrapperWidgets");
+
+#define IS_ASCII_ALPHABETICAL(key) \
+ ((('a' <= key) && (key <= 'z')) || (('A' <= key) && (key <= 'Z')))
+
+#define MOZ_MODIFIER_KEYS "MozKeymapWrapper"
+
+KeymapWrapper* KeymapWrapper::sInstance = nullptr;
+guint KeymapWrapper::sLastRepeatableHardwareKeyCode = 0;
+Time KeymapWrapper::sLastRepeatableKeyTime = 0;
+KeymapWrapper::RepeatState KeymapWrapper::sRepeatState =
+ KeymapWrapper::NOT_PRESSED;
+
+static const char* GetBoolName(bool aBool) { return aBool ? "TRUE" : "FALSE"; }
+
+static const char* GetStatusName(nsEventStatus aStatus) {
+ switch (aStatus) {
+ case nsEventStatus_eConsumeDoDefault:
+ return "nsEventStatus_eConsumeDoDefault";
+ case nsEventStatus_eConsumeNoDefault:
+ return "nsEventStatus_eConsumeNoDefault";
+ case nsEventStatus_eIgnore:
+ return "nsEventStatus_eIgnore";
+ case nsEventStatus_eSentinel:
+ return "nsEventStatus_eSentinel";
+ default:
+ return "Illegal value";
+ }
+}
+
+static const nsCString GetKeyLocationName(uint32_t aLocation) {
+ switch (aLocation) {
+ case eKeyLocationLeft:
+ return "KEY_LOCATION_LEFT"_ns;
+ case eKeyLocationRight:
+ return "KEY_LOCATION_RIGHT"_ns;
+ case eKeyLocationStandard:
+ return "KEY_LOCATION_STANDARD"_ns;
+ case eKeyLocationNumpad:
+ return "KEY_LOCATION_NUMPAD"_ns;
+ default:
+ return nsPrintfCString("Unknown (0x%04X)", aLocation);
+ }
+}
+
+static const nsCString GetCharacterCodeName(char16_t aChar) {
+ switch (aChar) {
+ case 0x0000:
+ return "NULL (0x0000)"_ns;
+ case 0x0008:
+ return "BACKSPACE (0x0008)"_ns;
+ case 0x0009:
+ return "CHARACTER TABULATION (0x0009)"_ns;
+ case 0x000A:
+ return "LINE FEED (0x000A)"_ns;
+ case 0x000B:
+ return "LINE TABULATION (0x000B)"_ns;
+ case 0x000C:
+ return "FORM FEED (0x000C)"_ns;
+ case 0x000D:
+ return "CARRIAGE RETURN (0x000D)"_ns;
+ case 0x0018:
+ return "CANCEL (0x0018)"_ns;
+ case 0x001B:
+ return "ESCAPE (0x001B)"_ns;
+ case 0x0020:
+ return "SPACE (0x0020)"_ns;
+ case 0x007F:
+ return "DELETE (0x007F)"_ns;
+ case 0x00A0:
+ return "NO-BREAK SPACE (0x00A0)"_ns;
+ case 0x00AD:
+ return "SOFT HYPHEN (0x00AD)"_ns;
+ case 0x2000:
+ return "EN QUAD (0x2000)"_ns;
+ case 0x2001:
+ return "EM QUAD (0x2001)"_ns;
+ case 0x2002:
+ return "EN SPACE (0x2002)"_ns;
+ case 0x2003:
+ return "EM SPACE (0x2003)"_ns;
+ case 0x2004:
+ return "THREE-PER-EM SPACE (0x2004)"_ns;
+ case 0x2005:
+ return "FOUR-PER-EM SPACE (0x2005)"_ns;
+ case 0x2006:
+ return "SIX-PER-EM SPACE (0x2006)"_ns;
+ case 0x2007:
+ return "FIGURE SPACE (0x2007)"_ns;
+ case 0x2008:
+ return "PUNCTUATION SPACE (0x2008)"_ns;
+ case 0x2009:
+ return "THIN SPACE (0x2009)"_ns;
+ case 0x200A:
+ return "HAIR SPACE (0x200A)"_ns;
+ case 0x200B:
+ return "ZERO WIDTH SPACE (0x200B)"_ns;
+ case 0x200C:
+ return "ZERO WIDTH NON-JOINER (0x200C)"_ns;
+ case 0x200D:
+ return "ZERO WIDTH JOINER (0x200D)"_ns;
+ case 0x200E:
+ return "LEFT-TO-RIGHT MARK (0x200E)"_ns;
+ case 0x200F:
+ return "RIGHT-TO-LEFT MARK (0x200F)"_ns;
+ case 0x2029:
+ return "PARAGRAPH SEPARATOR (0x2029)"_ns;
+ case 0x202A:
+ return "LEFT-TO-RIGHT EMBEDDING (0x202A)"_ns;
+ case 0x202B:
+ return "RIGHT-TO-LEFT EMBEDDING (0x202B)"_ns;
+ case 0x202D:
+ return "LEFT-TO-RIGHT OVERRIDE (0x202D)"_ns;
+ case 0x202E:
+ return "RIGHT-TO-LEFT OVERRIDE (0x202E)"_ns;
+ case 0x202F:
+ return "NARROW NO-BREAK SPACE (0x202F)"_ns;
+ case 0x205F:
+ return "MEDIUM MATHEMATICAL SPACE (0x205F)"_ns;
+ case 0x2060:
+ return "WORD JOINER (0x2060)"_ns;
+ case 0x2066:
+ return "LEFT-TO-RIGHT ISOLATE (0x2066)"_ns;
+ case 0x2067:
+ return "RIGHT-TO-LEFT ISOLATE (0x2067)"_ns;
+ case 0x3000:
+ return "IDEOGRAPHIC SPACE (0x3000)"_ns;
+ case 0xFEFF:
+ return "ZERO WIDTH NO-BREAK SPACE (0xFEFF)"_ns;
+ default: {
+ if (aChar < ' ' || (aChar >= 0x80 && aChar < 0xA0)) {
+ return nsPrintfCString("control (0x%04X)", aChar);
+ }
+ if (NS_IS_HIGH_SURROGATE(aChar)) {
+ return nsPrintfCString("high surrogate (0x%04X)", aChar);
+ }
+ if (NS_IS_LOW_SURROGATE(aChar)) {
+ return nsPrintfCString("low surrogate (0x%04X)", aChar);
+ }
+ return nsPrintfCString("'%s' (0x%04X)",
+ NS_ConvertUTF16toUTF8(nsAutoString(aChar)).get(),
+ aChar);
+ }
+ }
+}
+
+static const nsCString GetCharacterCodeNames(const char16_t* aChars,
+ uint32_t aLength) {
+ if (!aLength) {
+ return "\"\""_ns;
+ }
+ nsCString result;
+ result.AssignLiteral("\"");
+ StringJoinAppend(result, ", "_ns, Span{aChars, aLength},
+ [](nsACString& dest, const char16_t charValue) {
+ dest.Append(GetCharacterCodeName(charValue));
+ });
+ result.AppendLiteral("\"");
+ return result;
+}
+
+static const nsCString GetCharacterCodeNames(const nsAString& aString) {
+ return GetCharacterCodeNames(aString.BeginReading(), aString.Length());
+}
+
+/* static */ const char* KeymapWrapper::GetModifierName(Modifier aModifier) {
+ switch (aModifier) {
+ case CAPS_LOCK:
+ return "CapsLock";
+ case NUM_LOCK:
+ return "NumLock";
+ case SCROLL_LOCK:
+ return "ScrollLock";
+ case SHIFT:
+ return "Shift";
+ case CTRL:
+ return "Ctrl";
+ case ALT:
+ return "Alt";
+ case SUPER:
+ return "Super";
+ case HYPER:
+ return "Hyper";
+ case META:
+ return "Meta";
+ case LEVEL3:
+ return "Level3";
+ case LEVEL5:
+ return "Level5";
+ case NOT_MODIFIER:
+ return "NotModifier";
+ default:
+ return "InvalidValue";
+ }
+}
+
+/* static */ KeymapWrapper::Modifier KeymapWrapper::GetModifierForGDKKeyval(
+ guint aGdkKeyval) {
+ switch (aGdkKeyval) {
+ case GDK_Caps_Lock:
+ return CAPS_LOCK;
+ case GDK_Num_Lock:
+ return NUM_LOCK;
+ case GDK_Scroll_Lock:
+ return SCROLL_LOCK;
+ case GDK_Shift_Lock:
+ case GDK_Shift_L:
+ case GDK_Shift_R:
+ return SHIFT;
+ case GDK_Control_L:
+ case GDK_Control_R:
+ return CTRL;
+ case GDK_Alt_L:
+ case GDK_Alt_R:
+ return ALT;
+ case GDK_Super_L:
+ case GDK_Super_R:
+ return SUPER;
+ case GDK_Hyper_L:
+ case GDK_Hyper_R:
+ return HYPER;
+ case GDK_Meta_L:
+ case GDK_Meta_R:
+ return META;
+ case GDK_ISO_Level3_Shift:
+ case GDK_Mode_switch:
+ return LEVEL3;
+ case GDK_ISO_Level5_Shift:
+ return LEVEL5;
+ default:
+ return NOT_MODIFIER;
+ }
+}
+
+guint KeymapWrapper::GetModifierMask(Modifier aModifier) const {
+ switch (aModifier) {
+ case CAPS_LOCK:
+ return GDK_LOCK_MASK;
+ case NUM_LOCK:
+ return mModifierMasks[INDEX_NUM_LOCK];
+ case SCROLL_LOCK:
+ return mModifierMasks[INDEX_SCROLL_LOCK];
+ case SHIFT:
+ return GDK_SHIFT_MASK;
+ case CTRL:
+ return GDK_CONTROL_MASK;
+ case ALT:
+ return mModifierMasks[INDEX_ALT];
+ case SUPER:
+ return mModifierMasks[INDEX_SUPER];
+ case HYPER:
+ return mModifierMasks[INDEX_HYPER];
+ case META:
+ return mModifierMasks[INDEX_META];
+ case LEVEL3:
+ return mModifierMasks[INDEX_LEVEL3];
+ case LEVEL5:
+ return mModifierMasks[INDEX_LEVEL5];
+ default:
+ return 0;
+ }
+}
+
+KeymapWrapper::ModifierKey* KeymapWrapper::GetModifierKey(
+ guint aHardwareKeycode) {
+ for (uint32_t i = 0; i < mModifierKeys.Length(); i++) {
+ ModifierKey& key = mModifierKeys[i];
+ if (key.mHardwareKeycode == aHardwareKeycode) {
+ return &key;
+ }
+ }
+ return nullptr;
+}
+
+/* static */
+KeymapWrapper* KeymapWrapper::GetInstance() {
+ if (sInstance) {
+ sInstance->Init();
+ return sInstance;
+ }
+
+ sInstance = new KeymapWrapper();
+ return sInstance;
+}
+
+/* static */
+void KeymapWrapper::Shutdown() {
+ if (sInstance) {
+ delete sInstance;
+ sInstance = nullptr;
+ }
+}
+
+KeymapWrapper::KeymapWrapper()
+ : mInitialized(false),
+ mGdkKeymap(gdk_keymap_get_default()),
+ mXKBBaseEventCode(0),
+ mOnKeysChangedSignalHandle(0),
+ mOnDirectionChangedSignalHandle(0) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p Constructor, mGdkKeymap=%p", this, mGdkKeymap));
+
+ g_object_ref(mGdkKeymap);
+
+ if (gfxPlatformGtk::GetPlatform()->IsX11Display()) {
+ InitXKBExtension();
+ }
+
+ Init();
+}
+
+void KeymapWrapper::Init() {
+ if (mInitialized) {
+ return;
+ }
+ mInitialized = true;
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p Init, mGdkKeymap=%p", this, mGdkKeymap));
+
+ mModifierKeys.Clear();
+ memset(mModifierMasks, 0, sizeof(mModifierMasks));
+
+ if (gfxPlatformGtk::GetPlatform()->IsX11Display()) {
+ InitBySystemSettingsX11();
+ }
+#ifdef MOZ_WAYLAND
+ else {
+ InitBySystemSettingsWayland();
+ }
+#endif
+
+ gdk_window_add_filter(nullptr, FilterEvents, this);
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p Init, CapsLock=0x%X, NumLock=0x%X, "
+ "ScrollLock=0x%X, Level3=0x%X, Level5=0x%X, "
+ "Shift=0x%X, Ctrl=0x%X, Alt=0x%X, Meta=0x%X, Super=0x%X, Hyper=0x%X",
+ this, GetModifierMask(CAPS_LOCK), GetModifierMask(NUM_LOCK),
+ GetModifierMask(SCROLL_LOCK), GetModifierMask(LEVEL3),
+ GetModifierMask(LEVEL5), GetModifierMask(SHIFT),
+ GetModifierMask(CTRL), GetModifierMask(ALT), GetModifierMask(META),
+ GetModifierMask(SUPER), GetModifierMask(HYPER)));
+}
+
+void KeymapWrapper::InitXKBExtension() {
+ PodZero(&mKeyboardState);
+
+ int xkbMajorVer = XkbMajorVersion;
+ int xkbMinorVer = XkbMinorVersion;
+ if (!XkbLibraryVersion(&xkbMajorVer, &xkbMinorVer)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XkbLibraryVersion()",
+ this));
+ return;
+ }
+
+ Display* display = gdk_x11_display_get_xdisplay(gdk_display_get_default());
+
+ // XkbLibraryVersion() set xkbMajorVer and xkbMinorVer to that of the
+ // library, which may be newer than what is required of the server in
+ // XkbQueryExtension(), so these variables should be reset to
+ // XkbMajorVersion and XkbMinorVersion before the XkbQueryExtension call.
+ xkbMajorVer = XkbMajorVersion;
+ xkbMinorVer = XkbMinorVersion;
+ int opcode, baseErrorCode;
+ if (!XkbQueryExtension(display, &opcode, &mXKBBaseEventCode, &baseErrorCode,
+ &xkbMajorVer, &xkbMinorVer)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XkbQueryExtension(), display=0x%p",
+ this, display));
+ return;
+ }
+
+ if (!XkbSelectEventDetails(display, XkbUseCoreKbd, XkbStateNotify,
+ XkbModifierStateMask, XkbModifierStateMask)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XkbSelectEventDetails() for XModifierStateMask, display=0x%p",
+ this, display));
+ return;
+ }
+
+ if (!XkbSelectEventDetails(display, XkbUseCoreKbd, XkbControlsNotify,
+ XkbPerKeyRepeatMask, XkbPerKeyRepeatMask)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XkbSelectEventDetails() for XkbControlsNotify, display=0x%p",
+ this, display));
+ return;
+ }
+
+ if (!XGetKeyboardControl(display, &mKeyboardState)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XGetKeyboardControl(), display=0x%p",
+ this, display));
+ return;
+ }
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension, Succeeded", this));
+}
+
+void KeymapWrapper::InitBySystemSettingsX11() {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettingsX11, mGdkKeymap=%p", this, mGdkKeymap));
+
+ if (!mOnKeysChangedSignalHandle) {
+ mOnKeysChangedSignalHandle = g_signal_connect(
+ mGdkKeymap, "keys-changed", (GCallback)OnKeysChanged, this);
+ }
+ if (!mOnDirectionChangedSignalHandle) {
+ mOnDirectionChangedSignalHandle = g_signal_connect(
+ mGdkKeymap, "direction-changed", (GCallback)OnDirectionChanged, this);
+ }
+
+ Display* display = gdk_x11_display_get_xdisplay(gdk_display_get_default());
+
+ int min_keycode = 0;
+ int max_keycode = 0;
+ XDisplayKeycodes(display, &min_keycode, &max_keycode);
+
+ int keysyms_per_keycode = 0;
+ KeySym* xkeymap =
+ XGetKeyboardMapping(display, min_keycode, max_keycode - min_keycode + 1,
+ &keysyms_per_keycode);
+ if (!xkeymap) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettings, "
+ "Failed due to null xkeymap",
+ this));
+ return;
+ }
+
+ XModifierKeymap* xmodmap = XGetModifierMapping(display);
+ if (!xmodmap) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettings, "
+ "Failed due to null xmodmap",
+ this));
+ XFree(xkeymap);
+ return;
+ }
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettings, min_keycode=%d, "
+ "max_keycode=%d, keysyms_per_keycode=%d, max_keypermod=%d",
+ this, min_keycode, max_keycode, keysyms_per_keycode,
+ xmodmap->max_keypermod));
+
+ // The modifiermap member of the XModifierKeymap structure contains 8 sets
+ // of max_keypermod KeyCodes, one for each modifier in the order Shift,
+ // Lock, Control, Mod1, Mod2, Mod3, Mod4, and Mod5.
+ // Only nonzero KeyCodes have meaning in each set, and zero KeyCodes are
+ // ignored.
+
+ // Note that two or more modifiers may use one modifier flag. E.g.,
+ // on Ubuntu 10.10, Alt and Meta share the Mod1 in default settings.
+ // And also Super and Hyper share the Mod4. In such cases, we need to
+ // decide which modifier flag means one of DOM modifiers.
+
+ // mod[0] is Modifier introduced by Mod1.
+ Modifier mod[5];
+ int32_t foundLevel[5];
+ for (uint32_t i = 0; i < ArrayLength(mod); i++) {
+ mod[i] = NOT_MODIFIER;
+ foundLevel[i] = INT32_MAX;
+ }
+ const uint32_t map_size = 8 * xmodmap->max_keypermod;
+ for (uint32_t i = 0; i < map_size; i++) {
+ KeyCode keycode = xmodmap->modifiermap[i];
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettings, "
+ " i=%d, keycode=0x%08X",
+ this, i, keycode));
+ if (!keycode || keycode < min_keycode || keycode > max_keycode) {
+ continue;
+ }
+
+ ModifierKey* modifierKey = GetModifierKey(keycode);
+ if (!modifierKey) {
+ modifierKey = mModifierKeys.AppendElement(ModifierKey(keycode));
+ }
+
+ const KeySym* syms =
+ xkeymap + (keycode - min_keycode) * keysyms_per_keycode;
+ const uint32_t bit = i / xmodmap->max_keypermod;
+ modifierKey->mMask |= 1 << bit;
+
+ // We need to know the meaning of Mod1, Mod2, Mod3, Mod4 and Mod5.
+ // Let's skip if current map is for others.
+ if (bit < 3) {
+ continue;
+ }
+
+ const int32_t modIndex = bit - 3;
+ for (int32_t j = 0; j < keysyms_per_keycode; j++) {
+ Modifier modifier = GetModifierForGDKKeyval(syms[j]);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettings, "
+ " Mod%d, j=%d, syms[j]=%s(0x%lX), modifier=%s",
+ this, modIndex + 1, j, gdk_keyval_name(syms[j]), syms[j],
+ GetModifierName(modifier)));
+
+ switch (modifier) {
+ case NOT_MODIFIER:
+ // Don't overwrite the stored information with
+ // NOT_MODIFIER.
+ break;
+ case CAPS_LOCK:
+ case SHIFT:
+ case CTRL:
+ // Ignore the modifiers defined in GDK spec. They shouldn't
+ // be mapped to Mod1-5 because they must not work on native
+ // GTK applications.
+ break;
+ default:
+ // If new modifier is found in higher level than stored
+ // value, we don't need to overwrite it.
+ if (j > foundLevel[modIndex]) {
+ break;
+ }
+ // If new modifier is more important than stored value,
+ // we should overwrite it with new modifier.
+ if (j == foundLevel[modIndex]) {
+ mod[modIndex] = std::min(modifier, mod[modIndex]);
+ break;
+ }
+ foundLevel[modIndex] = j;
+ mod[modIndex] = modifier;
+ break;
+ }
+ }
+ }
+
+ for (uint32_t i = 0; i < COUNT_OF_MODIFIER_INDEX; i++) {
+ Modifier modifier;
+ switch (i) {
+ case INDEX_NUM_LOCK:
+ modifier = NUM_LOCK;
+ break;
+ case INDEX_SCROLL_LOCK:
+ modifier = SCROLL_LOCK;
+ break;
+ case INDEX_ALT:
+ modifier = ALT;
+ break;
+ case INDEX_META:
+ modifier = META;
+ break;
+ case INDEX_SUPER:
+ modifier = SUPER;
+ break;
+ case INDEX_HYPER:
+ modifier = HYPER;
+ break;
+ case INDEX_LEVEL3:
+ modifier = LEVEL3;
+ break;
+ case INDEX_LEVEL5:
+ modifier = LEVEL5;
+ break;
+ default:
+ MOZ_CRASH("All indexes must be handled here");
+ }
+ for (uint32_t j = 0; j < ArrayLength(mod); j++) {
+ if (modifier == mod[j]) {
+ mModifierMasks[i] |= 1 << (j + 3);
+ }
+ }
+ }
+
+ XFreeModifiermap(xmodmap);
+ XFree(xkeymap);
+}
+
+#ifdef MOZ_WAYLAND
+void KeymapWrapper::SetModifierMask(xkb_keymap* aKeymap,
+ ModifierIndex aModifierIndex,
+ const char* aModifierName) {
+ static auto sXkbKeymapModGetIndex =
+ (xkb_mod_index_t(*)(struct xkb_keymap*, const char*))dlsym(
+ RTLD_DEFAULT, "xkb_keymap_mod_get_index");
+
+ xkb_mod_index_t index = sXkbKeymapModGetIndex(aKeymap, aModifierName);
+ if (index != XKB_MOD_INVALID) {
+ mModifierMasks[aModifierIndex] = (1 << index);
+ }
+}
+
+void KeymapWrapper::SetModifierMasks(xkb_keymap* aKeymap) {
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ // This mapping is derived from get_xkb_modifiers() at gdkkeys-wayland.c
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_NUM_LOCK, XKB_MOD_NAME_NUM);
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_ALT, XKB_MOD_NAME_ALT);
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_META, "Meta");
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_SUPER, "Super");
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_HYPER, "Hyper");
+
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_SCROLL_LOCK, "ScrollLock");
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_LEVEL3, "Level3");
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_LEVEL5, "Level5");
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p KeymapWrapper::SetModifierMasks, CapsLock=0x%X, NumLock=0x%X, "
+ "ScrollLock=0x%X, Level3=0x%X, Level5=0x%X, "
+ "Shift=0x%X, Ctrl=0x%X, Alt=0x%X, Meta=0x%X, Super=0x%X, Hyper=0x%X",
+ keymapWrapper, keymapWrapper->GetModifierMask(CAPS_LOCK),
+ keymapWrapper->GetModifierMask(NUM_LOCK),
+ keymapWrapper->GetModifierMask(SCROLL_LOCK),
+ keymapWrapper->GetModifierMask(LEVEL3),
+ keymapWrapper->GetModifierMask(LEVEL5),
+ keymapWrapper->GetModifierMask(SHIFT),
+ keymapWrapper->GetModifierMask(CTRL),
+ keymapWrapper->GetModifierMask(ALT),
+ keymapWrapper->GetModifierMask(META),
+ keymapWrapper->GetModifierMask(SUPER),
+ keymapWrapper->GetModifierMask(HYPER)));
+}
+
+/* This keymap routine is derived from weston-2.0.0/clients/simple-im.c
+ */
+static void keyboard_handle_keymap(void* data, struct wl_keyboard* wl_keyboard,
+ uint32_t format, int fd, uint32_t size) {
+ KeymapWrapper::ResetKeyboard();
+
+ if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
+ close(fd);
+ return;
+ }
+
+ char* mapString = (char*)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+ if (mapString == MAP_FAILED) {
+ close(fd);
+ return;
+ }
+
+ static auto sXkbContextNew =
+ (struct xkb_context * (*)(enum xkb_context_flags))
+ dlsym(RTLD_DEFAULT, "xkb_context_new");
+ static auto sXkbKeymapNewFromString =
+ (struct xkb_keymap * (*)(struct xkb_context*, const char*,
+ enum xkb_keymap_format,
+ enum xkb_keymap_compile_flags))
+ dlsym(RTLD_DEFAULT, "xkb_keymap_new_from_string");
+
+ struct xkb_context* xkb_context = sXkbContextNew(XKB_CONTEXT_NO_FLAGS);
+ struct xkb_keymap* keymap =
+ sXkbKeymapNewFromString(xkb_context, mapString, XKB_KEYMAP_FORMAT_TEXT_V1,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+
+ munmap(mapString, size);
+ close(fd);
+
+ if (!keymap) {
+ NS_WARNING("keyboard_handle_keymap(): Failed to compile keymap!\n");
+ return;
+ }
+
+ KeymapWrapper::SetModifierMasks(keymap);
+
+ static auto sXkbKeymapUnRef =
+ (void (*)(struct xkb_keymap*))dlsym(RTLD_DEFAULT, "xkb_keymap_unref");
+ sXkbKeymapUnRef(keymap);
+
+ static auto sXkbContextUnref =
+ (void (*)(struct xkb_context*))dlsym(RTLD_DEFAULT, "xkb_context_unref");
+ sXkbContextUnref(xkb_context);
+}
+
+static void keyboard_handle_enter(void* data, struct wl_keyboard* keyboard,
+ uint32_t serial, struct wl_surface* surface,
+ struct wl_array* keys) {}
+static void keyboard_handle_leave(void* data, struct wl_keyboard* keyboard,
+ uint32_t serial, struct wl_surface* surface) {
+}
+static void keyboard_handle_key(void* data, struct wl_keyboard* keyboard,
+ uint32_t serial, uint32_t time, uint32_t key,
+ uint32_t state) {}
+static void keyboard_handle_modifiers(void* data, struct wl_keyboard* keyboard,
+ uint32_t serial, uint32_t mods_depressed,
+ uint32_t mods_latched,
+ uint32_t mods_locked, uint32_t group) {}
+
+static const struct wl_keyboard_listener keyboard_listener = {
+ keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave,
+ keyboard_handle_key, keyboard_handle_modifiers,
+};
+
+static void seat_handle_capabilities(void* data, struct wl_seat* seat,
+ unsigned int caps) {
+ static wl_keyboard* keyboard = nullptr;
+
+ if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !keyboard) {
+ keyboard = wl_seat_get_keyboard(seat);
+ wl_keyboard_add_listener(keyboard, &keyboard_listener, nullptr);
+ } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && keyboard) {
+ wl_keyboard_destroy(keyboard);
+ keyboard = nullptr;
+ }
+}
+
+static const struct wl_seat_listener seat_listener = {
+ seat_handle_capabilities,
+};
+
+static void gdk_registry_handle_global(void* data, struct wl_registry* registry,
+ uint32_t id, const char* interface,
+ uint32_t version) {
+ if (strcmp(interface, "wl_seat") == 0) {
+ auto* seat =
+ WaylandRegistryBind<wl_seat>(registry, id, &wl_seat_interface, 1);
+ wl_seat_add_listener(seat, &seat_listener, data);
+ }
+}
+
+static void gdk_registry_handle_global_remove(void* data,
+ struct wl_registry* registry,
+ uint32_t id) {}
+
+static const struct wl_registry_listener keyboard_registry_listener = {
+ gdk_registry_handle_global, gdk_registry_handle_global_remove};
+
+void KeymapWrapper::InitBySystemSettingsWayland() {
+ wl_display* display = WaylandDisplayGetWLDisplay();
+ wl_registry_add_listener(wl_display_get_registry(display),
+ &keyboard_registry_listener, this);
+}
+#endif
+
+KeymapWrapper::~KeymapWrapper() {
+ gdk_window_remove_filter(nullptr, FilterEvents, this);
+ if (mOnKeysChangedSignalHandle) {
+ g_signal_handler_disconnect(mGdkKeymap, mOnKeysChangedSignalHandle);
+ }
+ if (mOnDirectionChangedSignalHandle) {
+ g_signal_handler_disconnect(mGdkKeymap, mOnDirectionChangedSignalHandle);
+ }
+ g_object_unref(mGdkKeymap);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, ("%p Destructor", this));
+}
+
+/* static */
+GdkFilterReturn KeymapWrapper::FilterEvents(GdkXEvent* aXEvent,
+ GdkEvent* aGdkEvent,
+ gpointer aData) {
+ XEvent* xEvent = static_cast<XEvent*>(aXEvent);
+ switch (xEvent->type) {
+ case KeyPress: {
+ // If the key doesn't support auto repeat, ignore the event because
+ // even if such key (e.g., Shift) is pressed during auto repeat of
+ // anoter key, it doesn't stop the auto repeat.
+ KeymapWrapper* self = static_cast<KeymapWrapper*>(aData);
+ if (!self->IsAutoRepeatableKey(xEvent->xkey.keycode)) {
+ break;
+ }
+ if (sRepeatState == NOT_PRESSED) {
+ sRepeatState = FIRST_PRESS;
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("FilterEvents(aXEvent={ type=KeyPress, "
+ "xkey={ keycode=0x%08X, state=0x%08X, time=%lu } }, "
+ "aGdkEvent={ state=0x%08X }), "
+ "detected first keypress",
+ xEvent->xkey.keycode, xEvent->xkey.state, xEvent->xkey.time,
+ reinterpret_cast<GdkEventKey*>(aGdkEvent)->state));
+ } else if (sLastRepeatableHardwareKeyCode == xEvent->xkey.keycode) {
+ if (sLastRepeatableKeyTime == xEvent->xkey.time &&
+ sLastRepeatableHardwareKeyCode ==
+ IMContextWrapper::
+ GetWaitingSynthesizedKeyPressHardwareKeyCode()) {
+ // On some environment, IM may generate duplicated KeyPress event
+ // without any special state flags. In such case, we shouldn't
+ // treat the event as "repeated".
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("FilterEvents(aXEvent={ type=KeyPress, "
+ "xkey={ keycode=0x%08X, state=0x%08X, time=%lu } }, "
+ "aGdkEvent={ state=0x%08X }), "
+ "igored keypress since it must be synthesized by IME",
+ xEvent->xkey.keycode, xEvent->xkey.state, xEvent->xkey.time,
+ reinterpret_cast<GdkEventKey*>(aGdkEvent)->state));
+ break;
+ }
+ sRepeatState = REPEATING;
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("FilterEvents(aXEvent={ type=KeyPress, "
+ "xkey={ keycode=0x%08X, state=0x%08X, time=%lu } }, "
+ "aGdkEvent={ state=0x%08X }), "
+ "detected repeating keypress",
+ xEvent->xkey.keycode, xEvent->xkey.state, xEvent->xkey.time,
+ reinterpret_cast<GdkEventKey*>(aGdkEvent)->state));
+ } else {
+ // If a different key is pressed while another key is pressed,
+ // auto repeat system repeats only the last pressed key.
+ // So, setting new keycode and setting repeat state as first key
+ // press should work fine.
+ sRepeatState = FIRST_PRESS;
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("FilterEvents(aXEvent={ type=KeyPress, "
+ "xkey={ keycode=0x%08X, state=0x%08X, time=%lu } }, "
+ "aGdkEvent={ state=0x%08X }), "
+ "detected different keypress",
+ xEvent->xkey.keycode, xEvent->xkey.state, xEvent->xkey.time,
+ reinterpret_cast<GdkEventKey*>(aGdkEvent)->state));
+ }
+ sLastRepeatableHardwareKeyCode = xEvent->xkey.keycode;
+ sLastRepeatableKeyTime = xEvent->xkey.time;
+ break;
+ }
+ case KeyRelease: {
+ if (sLastRepeatableHardwareKeyCode != xEvent->xkey.keycode) {
+ // This case means the key release event is caused by
+ // a non-repeatable key such as Shift or a repeatable key that
+ // was pressed before sLastRepeatableHardwareKeyCode was
+ // pressed.
+ break;
+ }
+ sRepeatState = NOT_PRESSED;
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("FilterEvents(aXEvent={ type=KeyRelease, "
+ "xkey={ keycode=0x%08X, state=0x%08X, time=%lu } }, "
+ "aGdkEvent={ state=0x%08X }), "
+ "detected key release",
+ xEvent->xkey.keycode, xEvent->xkey.state, xEvent->xkey.time,
+ reinterpret_cast<GdkEventKey*>(aGdkEvent)->state));
+ break;
+ }
+ case FocusOut: {
+ // At moving focus, we should reset keyboard repeat state.
+ // Strictly, this causes incorrect behavior. However, this
+ // correctness must be enough for web applications.
+ sRepeatState = NOT_PRESSED;
+ break;
+ }
+ default: {
+ KeymapWrapper* self = static_cast<KeymapWrapper*>(aData);
+ if (xEvent->type != self->mXKBBaseEventCode) {
+ break;
+ }
+ XkbEvent* xkbEvent = (XkbEvent*)xEvent;
+ if (xkbEvent->any.xkb_type != XkbControlsNotify ||
+ !(xkbEvent->ctrls.changed_ctrls & XkbPerKeyRepeatMask)) {
+ break;
+ }
+ if (!XGetKeyboardControl(xkbEvent->any.display, &self->mKeyboardState)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p FilterEvents failed due to failure "
+ "of XGetKeyboardControl(), display=0x%p",
+ self, xkbEvent->any.display));
+ }
+ break;
+ }
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+static void ResetBidiKeyboard() {
+ // Reset the bidi keyboard settings for the new GdkKeymap
+ nsCOMPtr<nsIBidiKeyboard> bidiKeyboard = nsContentUtils::GetBidiKeyboard();
+ if (bidiKeyboard) {
+ bidiKeyboard->Reset();
+ }
+ WidgetUtils::SendBidiKeyboardInfoToContent();
+}
+
+/* static */
+void KeymapWrapper::ResetKeyboard() {
+ sInstance->mInitialized = false;
+ ResetBidiKeyboard();
+}
+
+/* static */
+void KeymapWrapper::OnKeysChanged(GdkKeymap* aGdkKeymap,
+ KeymapWrapper* aKeymapWrapper) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("OnKeysChanged, aGdkKeymap=%p, aKeymapWrapper=%p", aGdkKeymap,
+ aKeymapWrapper));
+
+ MOZ_ASSERT(sInstance == aKeymapWrapper,
+ "This instance must be the singleton instance");
+
+ // We cannot reintialize here becasue we don't have GdkWindow which is using
+ // the GdkKeymap. We'll reinitialize it when next GetInstance() is called.
+ ResetKeyboard();
+}
+
+// static
+void KeymapWrapper::OnDirectionChanged(GdkKeymap* aGdkKeymap,
+ KeymapWrapper* aKeymapWrapper) {
+ // XXX
+ // A lot of diretion-changed signal might be fired on switching bidi
+ // keyboard when using both ibus (with arabic layout) and fcitx (with IME).
+ // See https://github.com/fcitx/fcitx/issues/257
+ //
+ // Also, when using ibus, switching to IM might not cause this signal.
+ // See https://github.com/ibus/ibus/issues/1848
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("OnDirectionChanged, aGdkKeymap=%p, aKeymapWrapper=%p", aGdkKeymap,
+ aKeymapWrapper));
+
+ ResetBidiKeyboard();
+}
+
+/* static */
+guint KeymapWrapper::GetCurrentModifierState() {
+ GdkModifierType modifiers;
+ gdk_display_get_pointer(gdk_display_get_default(), nullptr, nullptr, nullptr,
+ &modifiers);
+ return static_cast<guint>(modifiers);
+}
+
+/* static */
+bool KeymapWrapper::AreModifiersCurrentlyActive(Modifiers aModifiers) {
+ guint modifierState = GetCurrentModifierState();
+ return AreModifiersActive(aModifiers, modifierState);
+}
+
+/* static */
+bool KeymapWrapper::AreModifiersActive(Modifiers aModifiers,
+ guint aModifierState) {
+ NS_ENSURE_TRUE(aModifiers, false);
+
+ KeymapWrapper* keymapWrapper = GetInstance();
+ for (uint32_t i = 0; i < sizeof(Modifier) * 8 && aModifiers; i++) {
+ Modifier modifier = static_cast<Modifier>(1 << i);
+ if (!(aModifiers & modifier)) {
+ continue;
+ }
+ if (!(aModifierState & keymapWrapper->GetModifierMask(modifier))) {
+ return false;
+ }
+ aModifiers &= ~modifier;
+ }
+ return true;
+}
+
+/* static */
+uint32_t KeymapWrapper::ComputeCurrentKeyModifiers() {
+ return ComputeKeyModifiers(GetCurrentModifierState());
+}
+
+/* static */
+uint32_t KeymapWrapper::ComputeKeyModifiers(guint aModifierState) {
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ uint32_t keyModifiers = 0;
+ // DOM Meta key should be TRUE only on Mac. We need to discuss this
+ // issue later.
+ if (keymapWrapper->AreModifiersActive(SHIFT, aModifierState)) {
+ keyModifiers |= MODIFIER_SHIFT;
+ }
+ if (keymapWrapper->AreModifiersActive(CTRL, aModifierState)) {
+ keyModifiers |= MODIFIER_CONTROL;
+ }
+ if (keymapWrapper->AreModifiersActive(ALT, aModifierState)) {
+ keyModifiers |= MODIFIER_ALT;
+ }
+ if (keymapWrapper->AreModifiersActive(META, aModifierState)) {
+ keyModifiers |= MODIFIER_META;
+ }
+ if (keymapWrapper->AreModifiersActive(SUPER, aModifierState) ||
+ keymapWrapper->AreModifiersActive(HYPER, aModifierState)) {
+ keyModifiers |= MODIFIER_OS;
+ }
+ if (keymapWrapper->AreModifiersActive(LEVEL3, aModifierState) ||
+ keymapWrapper->AreModifiersActive(LEVEL5, aModifierState)) {
+ keyModifiers |= MODIFIER_ALTGRAPH;
+ }
+ if (keymapWrapper->AreModifiersActive(CAPS_LOCK, aModifierState)) {
+ keyModifiers |= MODIFIER_CAPSLOCK;
+ }
+ if (keymapWrapper->AreModifiersActive(NUM_LOCK, aModifierState)) {
+ keyModifiers |= MODIFIER_NUMLOCK;
+ }
+ if (keymapWrapper->AreModifiersActive(SCROLL_LOCK, aModifierState)) {
+ keyModifiers |= MODIFIER_SCROLLLOCK;
+ }
+ return keyModifiers;
+}
+
+/* static */
+void KeymapWrapper::InitInputEvent(WidgetInputEvent& aInputEvent,
+ guint aModifierState) {
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ aInputEvent.mModifiers = ComputeKeyModifiers(aModifierState);
+
+ // Don't log this method for non-important events because e.g., eMouseMove is
+ // just noisy and there is no reason to log it.
+ bool doLog = aInputEvent.mMessage != eMouseMove;
+ if (doLog) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Debug,
+ ("%p InitInputEvent, aModifierState=0x%08X, "
+ "aInputEvent={ mMessage=%s, mModifiers=0x%04X (Shift: %s, "
+ "Control: %s, Alt: %s, "
+ "Meta: %s, OS: %s, AltGr: %s, "
+ "CapsLock: %s, NumLock: %s, ScrollLock: %s })",
+ keymapWrapper, aModifierState, ToChar(aInputEvent.mMessage),
+ aInputEvent.mModifiers,
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_SHIFT),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_CONTROL),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_ALT),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_META),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_OS),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_ALTGRAPH),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_CAPSLOCK),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_NUMLOCK),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_SCROLLLOCK)));
+ }
+
+ switch (aInputEvent.mClass) {
+ case eMouseEventClass:
+ case eMouseScrollEventClass:
+ case eWheelEventClass:
+ case eDragEventClass:
+ case eSimpleGestureEventClass:
+ break;
+ default:
+ return;
+ }
+
+ WidgetMouseEventBase& mouseEvent = *aInputEvent.AsMouseEventBase();
+ mouseEvent.mButtons = 0;
+ if (aModifierState & GDK_BUTTON1_MASK) {
+ mouseEvent.mButtons |= MouseButtonsFlag::ePrimaryFlag;
+ }
+ if (aModifierState & GDK_BUTTON3_MASK) {
+ mouseEvent.mButtons |= MouseButtonsFlag::eSecondaryFlag;
+ }
+ if (aModifierState & GDK_BUTTON2_MASK) {
+ mouseEvent.mButtons |= MouseButtonsFlag::eMiddleFlag;
+ }
+
+ if (doLog) {
+ MOZ_LOG(
+ gKeymapWrapperLog, LogLevel::Debug,
+ ("%p InitInputEvent, aInputEvent has mButtons, "
+ "aInputEvent.mButtons=0x%04X (Left: %s, Right: %s, Middle: %s, "
+ "4th (BACK): %s, 5th (FORWARD): %s)",
+ keymapWrapper, mouseEvent.mButtons,
+ GetBoolName(mouseEvent.mButtons & MouseButtonsFlag::ePrimaryFlag),
+ GetBoolName(mouseEvent.mButtons & MouseButtonsFlag::eSecondaryFlag),
+ GetBoolName(mouseEvent.mButtons & MouseButtonsFlag::eMiddleFlag),
+ GetBoolName(mouseEvent.mButtons & MouseButtonsFlag::e4thFlag),
+ GetBoolName(mouseEvent.mButtons & MouseButtonsFlag::e5thFlag)));
+ }
+}
+
+/* static */
+uint32_t KeymapWrapper::ComputeDOMKeyCode(const GdkEventKey* aGdkKeyEvent) {
+ // If the keyval indicates it's a modifier key, we should use unshifted
+ // key's modifier keyval.
+ guint keyval = aGdkKeyEvent->keyval;
+ if (GetModifierForGDKKeyval(keyval)) {
+ // But if the keyval without modifiers isn't a modifier key, we
+ // shouldn't use it. E.g., Japanese keyboard layout's
+ // Shift + Eisu-Toggle key is CapsLock. This is an actual rare case,
+ // Windows uses different keycode for a physical key for different
+ // shift key state.
+ guint keyvalWithoutModifier = GetGDKKeyvalWithoutModifier(aGdkKeyEvent);
+ if (GetModifierForGDKKeyval(keyvalWithoutModifier)) {
+ keyval = keyvalWithoutModifier;
+ }
+ // Note that the modifier keycode and activating or deactivating
+ // modifier flag may be mismatched, but it's okay. If a DOM key
+ // event handler is testing a keydown event, it's more likely being
+ // used to test which key is being pressed than to test which
+ // modifier will become active. So, if we computed DOM keycode
+ // from modifier flag which were changing by the physical key, then
+ // there would be no other way for the user to generate the original
+ // keycode.
+ uint32_t DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyval);
+ NS_ASSERTION(DOMKeyCode, "All modifier keys must have a DOM keycode");
+ return DOMKeyCode;
+ }
+
+ // If the key isn't printable, let's look at the key pairs.
+ uint32_t charCode = GetCharCodeFor(aGdkKeyEvent);
+ if (!charCode) {
+ // Note that any key may be a function key because of some unusual keyboard
+ // layouts. I.e., even if the pressed key is a printable key of en-US
+ // keyboard layout, we should expose the function key's keyCode value to
+ // web apps because web apps should handle the keydown/keyup events as
+ // inputted by usual keyboard layout. For example, Hatchak keyboard
+ // maps Tab key to "Digit3" key and Level3 Shift makes it "Backspace".
+ // In this case, we should expose DOM_VK_BACK_SPACE (8).
+ uint32_t DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyval);
+ if (DOMKeyCode) {
+ // XXX If DOMKeyCode is a function key's keyCode value, it might be
+ // better to consume necessary modifiers. For example, if there is
+ // no Control Pad section on keyboard like notebook, Delete key is
+ // available only with Level3 Shift+"Backspace" key if using Hatchak.
+ // If web apps accept Delete key operation only when no modifiers are
+ // active, such users cannot use Delete key to do it. However,
+ // Chromium doesn't consume such necessary modifiers. So, our default
+ // behavior should keep not touching modifiers for compatibility, but
+ // it might be better to add a pref to consume necessary modifiers.
+ return DOMKeyCode;
+ }
+ // If aGdkKeyEvent cannot be mapped to a DOM keyCode value, we should
+ // refer keyCode value without modifiers because web apps should be
+ // able to identify the key as far as possible.
+ guint keyvalWithoutModifier = GetGDKKeyvalWithoutModifier(aGdkKeyEvent);
+ return GetDOMKeyCodeFromKeyPairs(keyvalWithoutModifier);
+ }
+
+ // printable numpad keys should be resolved here.
+ switch (keyval) {
+ case GDK_KP_Multiply:
+ return NS_VK_MULTIPLY;
+ case GDK_KP_Add:
+ return NS_VK_ADD;
+ case GDK_KP_Separator:
+ return NS_VK_SEPARATOR;
+ case GDK_KP_Subtract:
+ return NS_VK_SUBTRACT;
+ case GDK_KP_Decimal:
+ return NS_VK_DECIMAL;
+ case GDK_KP_Divide:
+ return NS_VK_DIVIDE;
+ case GDK_KP_0:
+ return NS_VK_NUMPAD0;
+ case GDK_KP_1:
+ return NS_VK_NUMPAD1;
+ case GDK_KP_2:
+ return NS_VK_NUMPAD2;
+ case GDK_KP_3:
+ return NS_VK_NUMPAD3;
+ case GDK_KP_4:
+ return NS_VK_NUMPAD4;
+ case GDK_KP_5:
+ return NS_VK_NUMPAD5;
+ case GDK_KP_6:
+ return NS_VK_NUMPAD6;
+ case GDK_KP_7:
+ return NS_VK_NUMPAD7;
+ case GDK_KP_8:
+ return NS_VK_NUMPAD8;
+ case GDK_KP_9:
+ return NS_VK_NUMPAD9;
+ }
+
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ // Ignore all modifier state except NumLock.
+ guint baseState =
+ (aGdkKeyEvent->state & keymapWrapper->GetModifierMask(NUM_LOCK));
+
+ // Basically, we should use unmodified character for deciding our keyCode.
+ uint32_t unmodifiedChar = keymapWrapper->GetCharCodeFor(
+ aGdkKeyEvent, baseState, aGdkKeyEvent->group);
+ if (IsBasicLatinLetterOrNumeral(unmodifiedChar)) {
+ // If the unmodified character is an ASCII alphabet or an ASCII
+ // numeric, it's the best hint for deciding our keyCode.
+ return WidgetUtils::ComputeKeyCodeFromChar(unmodifiedChar);
+ }
+
+ // If the unmodified character is not an ASCII character, that means we
+ // couldn't find the hint. We should reset it.
+ if (!IsPrintableASCIICharacter(unmodifiedChar)) {
+ unmodifiedChar = 0;
+ }
+
+ // Retry with shifted keycode.
+ guint shiftState = (baseState | keymapWrapper->GetModifierMask(SHIFT));
+ uint32_t shiftedChar = keymapWrapper->GetCharCodeFor(aGdkKeyEvent, shiftState,
+ aGdkKeyEvent->group);
+ if (IsBasicLatinLetterOrNumeral(shiftedChar)) {
+ // A shifted character can be an ASCII alphabet on Hebrew keyboard
+ // layout. And also shifted character can be an ASCII numeric on
+ // AZERTY keyboad layout. Then, it's a good hint for deciding our
+ // keyCode.
+ return WidgetUtils::ComputeKeyCodeFromChar(shiftedChar);
+ }
+
+ // If the shifted unmodified character isn't an ASCII character, we should
+ // discard it too.
+ if (!IsPrintableASCIICharacter(shiftedChar)) {
+ shiftedChar = 0;
+ }
+
+ // If current keyboard layout isn't ASCII alphabet inputtable layout,
+ // look for ASCII alphabet inputtable keyboard layout. If the key
+ // inputs an ASCII alphabet or an ASCII numeric, we should use it
+ // for deciding our keyCode.
+ uint32_t unmodCharLatin = 0;
+ uint32_t shiftedCharLatin = 0;
+ if (!keymapWrapper->IsLatinGroup(aGdkKeyEvent->group)) {
+ gint minGroup = keymapWrapper->GetFirstLatinGroup();
+ if (minGroup >= 0) {
+ unmodCharLatin =
+ keymapWrapper->GetCharCodeFor(aGdkKeyEvent, baseState, minGroup);
+ if (IsBasicLatinLetterOrNumeral(unmodCharLatin)) {
+ // If the unmodified character is an ASCII alphabet or
+ // an ASCII numeric, we should use it for the keyCode.
+ return WidgetUtils::ComputeKeyCodeFromChar(unmodCharLatin);
+ }
+ // If the unmodified character in the alternative ASCII capable
+ // keyboard layout isn't an ASCII character, that means we couldn't
+ // find the hint. We should reset it.
+ if (!IsPrintableASCIICharacter(unmodCharLatin)) {
+ unmodCharLatin = 0;
+ }
+ shiftedCharLatin =
+ keymapWrapper->GetCharCodeFor(aGdkKeyEvent, shiftState, minGroup);
+ if (IsBasicLatinLetterOrNumeral(shiftedCharLatin)) {
+ // If the shifted character is an ASCII alphabet or an ASCII
+ // numeric, we should use it for the keyCode.
+ return WidgetUtils::ComputeKeyCodeFromChar(shiftedCharLatin);
+ }
+ // If the shifted unmodified character in the alternative ASCII
+ // capable keyboard layout isn't an ASCII character, we should
+ // discard it too.
+ if (!IsPrintableASCIICharacter(shiftedCharLatin)) {
+ shiftedCharLatin = 0;
+ }
+ }
+ }
+
+ // If the key itself or with Shift state on active keyboard layout produces
+ // an ASCII punctuation character, we should decide keyCode value with it.
+ if (unmodifiedChar || shiftedChar) {
+ return WidgetUtils::ComputeKeyCodeFromChar(unmodifiedChar ? unmodifiedChar
+ : shiftedChar);
+ }
+
+ // If the key itself or with Shift state on alternative ASCII capable
+ // keyboard layout produces an ASCII punctuation character, we should
+ // decide keyCode value with it. Note that We've returned 0 for long
+ // time if keyCode isn't for an alphabet keys or a numeric key even in
+ // alternative ASCII capable keyboard layout because we decided that we
+ // should avoid setting same keyCode value to 2 or more keys since active
+ // keyboard layout may have a key to input the punctuation with different
+ // key. However, setting keyCode to 0 makes some web applications which
+ // are aware of neither KeyboardEvent.key nor KeyboardEvent.code not work
+ // with Firefox when user selects non-ASCII capable keyboard layout such
+ // as Russian and Thai. So, if alternative ASCII capable keyboard layout
+ // has keyCode value for the key, we should use it. In other words, this
+ // behavior means that non-ASCII capable keyboard layout overrides some
+ // keys' keyCode value only if the key produces ASCII character by itself
+ // or with Shift key.
+ if (unmodCharLatin || shiftedCharLatin) {
+ return WidgetUtils::ComputeKeyCodeFromChar(
+ unmodCharLatin ? unmodCharLatin : shiftedCharLatin);
+ }
+
+ // Otherwise, let's decide keyCode value from the hardware_keycode
+ // value on major keyboard layout.
+ CodeNameIndex code = ComputeDOMCodeNameIndex(aGdkKeyEvent);
+ return WidgetKeyboardEvent::GetFallbackKeyCodeOfPunctuationKey(code);
+}
+
+KeyNameIndex KeymapWrapper::ComputeDOMKeyNameIndex(
+ const GdkEventKey* aGdkKeyEvent) {
+ switch (aGdkKeyEvent->keyval) {
+#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: \
+ return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ break;
+ }
+
+ return KEY_NAME_INDEX_Unidentified;
+}
+
+/* static */
+CodeNameIndex KeymapWrapper::ComputeDOMCodeNameIndex(
+ const GdkEventKey* aGdkKeyEvent) {
+ switch (aGdkKeyEvent->hardware_keycode) {
+#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
+ case aNativeKey: \
+ return aCodeNameIndex;
+
+#include "NativeKeyToDOMCodeName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
+
+ default:
+ break;
+ }
+
+ return CODE_NAME_INDEX_UNKNOWN;
+}
+
+/* static */
+bool KeymapWrapper::DispatchKeyDownOrKeyUpEvent(nsWindow* aWindow,
+ GdkEventKey* aGdkKeyEvent,
+ bool aIsProcessedByIME,
+ bool* aIsCancelled) {
+ MOZ_ASSERT(aIsCancelled, "aIsCancelled must not be nullptr");
+
+ *aIsCancelled = false;
+
+ if (aGdkKeyEvent->type == GDK_KEY_PRESS && aGdkKeyEvent->keyval == GDK_Tab &&
+ AreModifiersActive(CTRL | ALT, aGdkKeyEvent->state)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" DispatchKeyDownOrKeyUpEvent(), didn't dispatch keyboard events "
+ "because it's Ctrl + Alt + Tab"));
+ return false;
+ }
+
+ EventMessage message =
+ aGdkKeyEvent->type == GDK_KEY_PRESS ? eKeyDown : eKeyUp;
+ WidgetKeyboardEvent keyEvent(true, message, aWindow);
+ KeymapWrapper::InitKeyEvent(keyEvent, aGdkKeyEvent, aIsProcessedByIME);
+ return DispatchKeyDownOrKeyUpEvent(aWindow, keyEvent, aIsCancelled);
+}
+
+/* static */
+bool KeymapWrapper::DispatchKeyDownOrKeyUpEvent(
+ nsWindow* aWindow, WidgetKeyboardEvent& aKeyboardEvent,
+ bool* aIsCancelled) {
+ MOZ_ASSERT(aIsCancelled, "aIsCancelled must not be nullptr");
+
+ *aIsCancelled = false;
+
+ RefPtr<TextEventDispatcher> dispatcher = aWindow->GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Error,
+ (" DispatchKeyDownOrKeyUpEvent(), stopped dispatching %s event "
+ "because of failed to initialize TextEventDispatcher",
+ ToChar(aKeyboardEvent.mMessage)));
+ return FALSE;
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ bool dispatched = dispatcher->DispatchKeyboardEvent(
+ aKeyboardEvent.mMessage, aKeyboardEvent, status, nullptr);
+ *aIsCancelled = (status == nsEventStatus_eConsumeNoDefault);
+ return dispatched;
+}
+
+/* static */
+bool KeymapWrapper::MaybeDispatchContextMenuEvent(nsWindow* aWindow,
+ const GdkEventKey* aEvent) {
+ KeyNameIndex keyNameIndex = ComputeDOMKeyNameIndex(aEvent);
+
+ // Shift+F10 and ContextMenu should cause eContextMenu event.
+ if (keyNameIndex != KEY_NAME_INDEX_F10 &&
+ keyNameIndex != KEY_NAME_INDEX_ContextMenu) {
+ return false;
+ }
+
+ WidgetMouseEvent contextMenuEvent(true, eContextMenu, aWindow,
+ WidgetMouseEvent::eReal,
+ WidgetMouseEvent::eContextMenuKey);
+
+ contextMenuEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ contextMenuEvent.AssignEventTime(aWindow->GetWidgetEventTime(aEvent->time));
+ contextMenuEvent.mClickCount = 1;
+ KeymapWrapper::InitInputEvent(contextMenuEvent, aEvent->state);
+
+ if (contextMenuEvent.IsControl() || contextMenuEvent.IsMeta() ||
+ contextMenuEvent.IsAlt()) {
+ return false;
+ }
+
+ // If the key is ContextMenu, then an eContextMenu mouse event is
+ // dispatched regardless of the state of the Shift modifier. When it is
+ // pressed without the Shift modifier, a web page can prevent the default
+ // context menu action. When pressed with the Shift modifier, the web page
+ // cannot prevent the default context menu action.
+ // (PresShell::HandleEventInternal() sets mOnlyChromeDispatch to true.)
+
+ // If the key is F10, it needs Shift state because Shift+F10 is well-known
+ // shortcut key on Linux. However, eContextMenu with Shift state is
+ // special. It won't fire "contextmenu" event in the web content for
+ // blocking web page to prevent its default. Therefore, this combination
+ // should work same as ContextMenu key.
+ // XXX Should we allow to block web page to prevent its default with
+ // Ctrl+Shift+F10 or Alt+Shift+F10 instead?
+ if (keyNameIndex == KEY_NAME_INDEX_F10) {
+ if (!contextMenuEvent.IsShift()) {
+ return false;
+ }
+ contextMenuEvent.mModifiers &= ~MODIFIER_SHIFT;
+ }
+
+ aWindow->DispatchInputEvent(&contextMenuEvent);
+ return true;
+}
+
+/* static*/
+void KeymapWrapper::HandleKeyPressEvent(nsWindow* aWindow,
+ GdkEventKey* aGdkKeyEvent) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("HandleKeyPressEvent(aWindow=%p, aGdkKeyEvent={ type=%s, "
+ "keyval=%s(0x%X), state=0x%08X, hardware_keycode=0x%08X, "
+ "time=%u, is_modifier=%s })",
+ aWindow,
+ ((aGdkKeyEvent->type == GDK_KEY_PRESS) ? "GDK_KEY_PRESS"
+ : "GDK_KEY_RELEASE"),
+ gdk_keyval_name(aGdkKeyEvent->keyval), aGdkKeyEvent->keyval,
+ aGdkKeyEvent->state, aGdkKeyEvent->hardware_keycode,
+ aGdkKeyEvent->time, GetBoolName(aGdkKeyEvent->is_modifier)));
+
+ // if we are in the middle of composing text, XIM gets to see it
+ // before mozilla does.
+ // FYI: Don't dispatch keydown event before notifying IME of the event
+ // because IME may send a key event synchronously and consume the
+ // original event.
+ bool IMEWasEnabled = false;
+ KeyHandlingState handlingState = KeyHandlingState::eNotHandled;
+ RefPtr<IMContextWrapper> imContext = aWindow->GetIMContext();
+ if (imContext) {
+ IMEWasEnabled = imContext->IsEnabled();
+ handlingState = imContext->OnKeyEvent(aWindow, aGdkKeyEvent);
+ if (handlingState == KeyHandlingState::eHandled) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), the event was handled by "
+ "IMContextWrapper"));
+ return;
+ }
+ }
+
+ // work around for annoying things.
+ if (aGdkKeyEvent->keyval == GDK_Tab &&
+ AreModifiersActive(CTRL | ALT, aGdkKeyEvent->state)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), didn't dispatch keyboard events "
+ "because it's Ctrl + Alt + Tab"));
+ return;
+ }
+
+ // Dispatch keydown event always. At auto repeating, we should send
+ // KEYDOWN -> KEYPRESS -> KEYDOWN -> KEYPRESS ... -> KEYUP
+ // However, old distributions (e.g., Ubuntu 9.10) sent native key
+ // release event, so, on such platform, the DOM events will be:
+ // KEYDOWN -> KEYPRESS -> KEYUP -> KEYDOWN -> KEYPRESS -> KEYUP...
+
+ bool isKeyDownCancelled = false;
+ if (handlingState == KeyHandlingState::eNotHandled) {
+ if (DispatchKeyDownOrKeyUpEvent(aWindow, aGdkKeyEvent, false,
+ &isKeyDownCancelled) &&
+ (MOZ_UNLIKELY(aWindow->IsDestroyed()) || isKeyDownCancelled)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched eKeyDown event and "
+ "stopped handling the event because %s",
+ aWindow->IsDestroyed() ? "the window has been destroyed"
+ : "the event was consumed"));
+ return;
+ }
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched eKeyDown event and "
+ "it wasn't consumed"));
+ handlingState = KeyHandlingState::eNotHandledButEventDispatched;
+ }
+
+ // If a keydown event handler causes to enable IME, i.e., it moves
+ // focus from IME unusable content to IME usable editor, we should
+ // send the native key event to IME for the first input on the editor.
+ imContext = aWindow->GetIMContext();
+ if (!IMEWasEnabled && imContext && imContext->IsEnabled()) {
+ // Notice our keydown event was already dispatched. This prevents
+ // unnecessary DOM keydown event in the editor.
+ handlingState = imContext->OnKeyEvent(aWindow, aGdkKeyEvent, true);
+ if (handlingState == KeyHandlingState::eHandled) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), the event was handled by "
+ "IMContextWrapper which was enabled by the preceding eKeyDown "
+ "event"));
+ return;
+ }
+ }
+
+ // Look for specialized app-command keys
+ switch (aGdkKeyEvent->keyval) {
+ case GDK_Back:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Back);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Back\" command event"));
+ return;
+ case GDK_Forward:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Forward);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Forward\" command "
+ "event"));
+ return;
+ case GDK_Reload:
+ case GDK_Refresh:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Reload);
+ return;
+ case GDK_Stop:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Stop);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Stop\" command event"));
+ return;
+ case GDK_Search:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Search);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Search\" command event"));
+ return;
+ case GDK_Favorites:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Bookmarks);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Bookmarks\" command "
+ "event"));
+ return;
+ case GDK_HomePage:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Home);
+ return;
+ case GDK_Copy:
+ case GDK_F16: // F16, F20, F18, F14 are old keysyms for Copy Cut Paste Undo
+ aWindow->DispatchContentCommandEvent(eContentCommandCopy);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Copy\" content command "
+ "event"));
+ return;
+ case GDK_Cut:
+ case GDK_F20:
+ aWindow->DispatchContentCommandEvent(eContentCommandCut);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Cut\" content command "
+ "event"));
+ return;
+ case GDK_Paste:
+ case GDK_F18:
+ aWindow->DispatchContentCommandEvent(eContentCommandPaste);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Paste\" content command "
+ "event"));
+ return;
+ case GDK_Redo:
+ aWindow->DispatchContentCommandEvent(eContentCommandRedo);
+ return;
+ case GDK_Undo:
+ case GDK_F14:
+ aWindow->DispatchContentCommandEvent(eContentCommandUndo);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Undo\" content command "
+ "event"));
+ return;
+ default:
+ break;
+ }
+
+ // before we dispatch a key, check if it's the context menu key.
+ // If so, send a context menu key event instead.
+ if (MaybeDispatchContextMenuEvent(aWindow, aGdkKeyEvent)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), stopped dispatching eKeyPress event "
+ "because eContextMenu event was dispatched"));
+ return;
+ }
+
+ RefPtr<TextEventDispatcher> textEventDispatcher =
+ aWindow->GetTextEventDispatcher();
+ nsresult rv = textEventDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Error,
+ (" HandleKeyPressEvent(), stopped dispatching eKeyPress event "
+ "because of failed to initialize TextEventDispatcher"));
+ return;
+ }
+
+ // If the character code is in the BMP, send the key press event.
+ // Otherwise, send a compositionchange event with the equivalent UTF-16
+ // string.
+ // TODO: Investigate other browser's behavior in this case because
+ // this hack is odd for UI Events.
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, aWindow);
+ KeymapWrapper::InitKeyEvent(keypressEvent, aGdkKeyEvent, false);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ if (keypressEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING ||
+ keypressEvent.mKeyValue.Length() == 1) {
+ if (textEventDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
+ aGdkKeyEvent)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched eKeyPress event "
+ "(status=%s)",
+ GetStatusName(status)));
+ } else {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), didn't dispatch eKeyPress event "
+ "(status=%s)",
+ GetStatusName(status)));
+ }
+ } else {
+ WidgetEventTime eventTime = aWindow->GetWidgetEventTime(aGdkKeyEvent->time);
+ textEventDispatcher->CommitComposition(status, &keypressEvent.mKeyValue,
+ &eventTime);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched a set of composition "
+ "events"));
+ }
+}
+
+/* static */
+bool KeymapWrapper::HandleKeyReleaseEvent(nsWindow* aWindow,
+ GdkEventKey* aGdkKeyEvent) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("HandleKeyReleaseEvent(aWindow=%p, aGdkKeyEvent={ type=%s, "
+ "keyval=%s(0x%X), state=0x%08X, hardware_keycode=0x%08X, "
+ "time=%u, is_modifier=%s })",
+ aWindow,
+ ((aGdkKeyEvent->type == GDK_KEY_PRESS) ? "GDK_KEY_PRESS"
+ : "GDK_KEY_RELEASE"),
+ gdk_keyval_name(aGdkKeyEvent->keyval), aGdkKeyEvent->keyval,
+ aGdkKeyEvent->state, aGdkKeyEvent->hardware_keycode,
+ aGdkKeyEvent->time, GetBoolName(aGdkKeyEvent->is_modifier)));
+
+ RefPtr<IMContextWrapper> imContext = aWindow->GetIMContext();
+ if (imContext) {
+ KeyHandlingState handlingState =
+ imContext->OnKeyEvent(aWindow, aGdkKeyEvent);
+ if (handlingState != KeyHandlingState::eNotHandled) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyReleaseEvent(), the event was handled by "
+ "IMContextWrapper"));
+ return true;
+ }
+ }
+
+ bool isCancelled = false;
+ if (NS_WARN_IF(!DispatchKeyDownOrKeyUpEvent(aWindow, aGdkKeyEvent, false,
+ &isCancelled))) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Error,
+ (" HandleKeyReleaseEvent(), didn't dispatch eKeyUp event"));
+ return false;
+ }
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyReleaseEvent(), dispatched eKeyUp event "
+ "(isCancelled=%s)",
+ GetBoolName(isCancelled)));
+ return true;
+}
+
+/* static */
+void KeymapWrapper::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent,
+ bool aIsProcessedByIME) {
+ MOZ_ASSERT(
+ !aIsProcessedByIME || aKeyEvent.mMessage != eKeyPress,
+ "If the key event is handled by IME, keypress event shouldn't be fired");
+
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ aKeyEvent.mCodeNameIndex = ComputeDOMCodeNameIndex(aGdkKeyEvent);
+ MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
+ aKeyEvent.mKeyNameIndex =
+ aIsProcessedByIME ? KEY_NAME_INDEX_Process
+ : keymapWrapper->ComputeDOMKeyNameIndex(aGdkKeyEvent);
+ if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_Unidentified) {
+ uint32_t charCode = GetCharCodeFor(aGdkKeyEvent);
+ if (!charCode) {
+ charCode = keymapWrapper->GetUnmodifiedCharCodeFor(aGdkKeyEvent);
+ }
+ if (charCode) {
+ aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ MOZ_ASSERT(aKeyEvent.mKeyValue.IsEmpty(),
+ "Uninitialized mKeyValue must be empty");
+ AppendUCS4ToUTF16(charCode, aKeyEvent.mKeyValue);
+ }
+ }
+
+ if (aIsProcessedByIME) {
+ aKeyEvent.mKeyCode = NS_VK_PROCESSKEY;
+ } else if (aKeyEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING ||
+ aKeyEvent.mMessage != eKeyPress) {
+ aKeyEvent.mKeyCode = ComputeDOMKeyCode(aGdkKeyEvent);
+ } else {
+ aKeyEvent.mKeyCode = 0;
+ }
+
+ // NOTE: The state of given key event indicates adjacent state of
+ // modifier keys. E.g., even if the event is Shift key press event,
+ // the bit for Shift is still false. By the same token, even if the
+ // event is Shift key release event, the bit for Shift is still true.
+ // Unfortunately, gdk_keyboard_get_modifiers() returns current modifier
+ // state. It means if there're some pending modifier key press or
+ // key release events, the result isn't what we want.
+ guint modifierState = aGdkKeyEvent->state;
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ if (aGdkKeyEvent->is_modifier && GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ Display* display = gdk_x11_display_get_xdisplay(gdkDisplay);
+ if (XEventsQueued(display, QueuedAfterReading)) {
+ XEvent nextEvent;
+ XPeekEvent(display, &nextEvent);
+ if (nextEvent.type == keymapWrapper->mXKBBaseEventCode) {
+ XkbEvent* XKBEvent = (XkbEvent*)&nextEvent;
+ if (XKBEvent->any.xkb_type == XkbStateNotify) {
+ XkbStateNotifyEvent* stateNotifyEvent =
+ (XkbStateNotifyEvent*)XKBEvent;
+ modifierState &= ~0xFF;
+ modifierState |= stateNotifyEvent->lookup_mods;
+ }
+ }
+ }
+ }
+ InitInputEvent(aKeyEvent, modifierState);
+
+ switch (aGdkKeyEvent->keyval) {
+ case GDK_Shift_L:
+ case GDK_Control_L:
+ case GDK_Alt_L:
+ case GDK_Super_L:
+ case GDK_Hyper_L:
+ case GDK_Meta_L:
+ aKeyEvent.mLocation = eKeyLocationLeft;
+ break;
+
+ case GDK_Shift_R:
+ case GDK_Control_R:
+ case GDK_Alt_R:
+ case GDK_Super_R:
+ case GDK_Hyper_R:
+ case GDK_Meta_R:
+ aKeyEvent.mLocation = eKeyLocationRight;
+ break;
+
+ case GDK_KP_0:
+ case GDK_KP_1:
+ case GDK_KP_2:
+ case GDK_KP_3:
+ case GDK_KP_4:
+ case GDK_KP_5:
+ case GDK_KP_6:
+ case GDK_KP_7:
+ case GDK_KP_8:
+ case GDK_KP_9:
+ case GDK_KP_Space:
+ case GDK_KP_Tab:
+ case GDK_KP_Enter:
+ case GDK_KP_F1:
+ case GDK_KP_F2:
+ case GDK_KP_F3:
+ case GDK_KP_F4:
+ case GDK_KP_Home:
+ case GDK_KP_Left:
+ case GDK_KP_Up:
+ case GDK_KP_Right:
+ case GDK_KP_Down:
+ case GDK_KP_Prior: // same as GDK_KP_Page_Up
+ case GDK_KP_Next: // same as GDK_KP_Page_Down
+ case GDK_KP_End:
+ case GDK_KP_Begin:
+ case GDK_KP_Insert:
+ case GDK_KP_Delete:
+ case GDK_KP_Equal:
+ case GDK_KP_Multiply:
+ case GDK_KP_Add:
+ case GDK_KP_Separator:
+ case GDK_KP_Subtract:
+ case GDK_KP_Decimal:
+ case GDK_KP_Divide:
+ aKeyEvent.mLocation = eKeyLocationNumpad;
+ break;
+
+ default:
+ aKeyEvent.mLocation = eKeyLocationStandard;
+ break;
+ }
+
+ // The transformations above and in gdk for the keyval are not invertible
+ // so link to the GdkEvent (which will vanish soon after return from the
+ // event callback) to give plugins access to hardware_keycode and state.
+ // (An XEvent would be nice but the GdkEvent is good enough.)
+ aKeyEvent.mPluginEvent.Copy(*aGdkKeyEvent);
+ aKeyEvent.mTime = aGdkKeyEvent->time;
+ aKeyEvent.mNativeKeyEvent = static_cast<void*>(aGdkKeyEvent);
+ aKeyEvent.mIsRepeat =
+ sRepeatState == REPEATING &&
+ aGdkKeyEvent->hardware_keycode == sLastRepeatableHardwareKeyCode;
+
+ MOZ_LOG(
+ gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitKeyEvent, modifierState=0x%08X "
+ "aKeyEvent={ mMessage=%s, isShift=%s, isControl=%s, "
+ "isAlt=%s, isMeta=%s , mKeyCode=0x%02X, mCharCode=%s, "
+ "mKeyNameIndex=%s, mKeyValue=%s, mCodeNameIndex=%s, mCodeValue=%s, "
+ "mLocation=%s, mIsRepeat=%s }",
+ keymapWrapper, modifierState, ToChar(aKeyEvent.mMessage),
+ GetBoolName(aKeyEvent.IsShift()), GetBoolName(aKeyEvent.IsControl()),
+ GetBoolName(aKeyEvent.IsAlt()), GetBoolName(aKeyEvent.IsMeta()),
+ aKeyEvent.mKeyCode,
+ GetCharacterCodeName(static_cast<char16_t>(aKeyEvent.mCharCode)).get(),
+ ToString(aKeyEvent.mKeyNameIndex).get(),
+ GetCharacterCodeNames(aKeyEvent.mKeyValue).get(),
+ ToString(aKeyEvent.mCodeNameIndex).get(),
+ GetCharacterCodeNames(aKeyEvent.mCodeValue).get(),
+ GetKeyLocationName(aKeyEvent.mLocation).get(),
+ GetBoolName(aKeyEvent.mIsRepeat)));
+}
+
+/* static */
+uint32_t KeymapWrapper::GetCharCodeFor(const GdkEventKey* aGdkKeyEvent) {
+ // Anything above 0xf000 is considered a non-printable
+ // Exception: directly encoded UCS characters
+ if (aGdkKeyEvent->keyval > 0xf000 &&
+ (aGdkKeyEvent->keyval & 0xff000000) != 0x01000000) {
+ // Keypad keys are an exception: they return a value different
+ // from their non-keypad equivalents, but mozilla doesn't distinguish.
+ switch (aGdkKeyEvent->keyval) {
+ case GDK_KP_Space:
+ return ' ';
+ case GDK_KP_Equal:
+ return '=';
+ case GDK_KP_Multiply:
+ return '*';
+ case GDK_KP_Add:
+ return '+';
+ case GDK_KP_Separator:
+ return ',';
+ case GDK_KP_Subtract:
+ return '-';
+ case GDK_KP_Decimal:
+ return '.';
+ case GDK_KP_Divide:
+ return '/';
+ case GDK_KP_0:
+ return '0';
+ case GDK_KP_1:
+ return '1';
+ case GDK_KP_2:
+ return '2';
+ case GDK_KP_3:
+ return '3';
+ case GDK_KP_4:
+ return '4';
+ case GDK_KP_5:
+ return '5';
+ case GDK_KP_6:
+ return '6';
+ case GDK_KP_7:
+ return '7';
+ case GDK_KP_8:
+ return '8';
+ case GDK_KP_9:
+ return '9';
+ default:
+ return 0; // non-printables
+ }
+ }
+
+ static const long MAX_UNICODE = 0x10FFFF;
+
+ // we're supposedly printable, let's try to convert
+ long ucs = keysym2ucs(aGdkKeyEvent->keyval);
+ if ((ucs != -1) && (ucs < MAX_UNICODE)) {
+ return ucs;
+ }
+
+ // I guess we couldn't convert
+ return 0;
+}
+
+uint32_t KeymapWrapper::GetCharCodeFor(const GdkEventKey* aGdkKeyEvent,
+ guint aModifierState, gint aGroup) {
+ guint keyval;
+ if (!gdk_keymap_translate_keyboard_state(
+ mGdkKeymap, aGdkKeyEvent->hardware_keycode,
+ GdkModifierType(aModifierState), aGroup, &keyval, nullptr, nullptr,
+ nullptr)) {
+ return 0;
+ }
+ GdkEventKey tmpEvent = *aGdkKeyEvent;
+ tmpEvent.state = aModifierState;
+ tmpEvent.keyval = keyval;
+ tmpEvent.group = aGroup;
+ return GetCharCodeFor(&tmpEvent);
+}
+
+uint32_t KeymapWrapper::GetUnmodifiedCharCodeFor(
+ const GdkEventKey* aGdkKeyEvent) {
+ guint state = aGdkKeyEvent->state &
+ (GetModifierMask(SHIFT) | GetModifierMask(CAPS_LOCK) |
+ GetModifierMask(NUM_LOCK) | GetModifierMask(SCROLL_LOCK) |
+ GetModifierMask(LEVEL3) | GetModifierMask(LEVEL5));
+ uint32_t charCode =
+ GetCharCodeFor(aGdkKeyEvent, GdkModifierType(state), aGdkKeyEvent->group);
+ if (charCode) {
+ return charCode;
+ }
+ // If no character is mapped to the key when Level3 Shift or Level5 Shift
+ // is active, let's return a character which is inputted by the key without
+ // Level3 nor Level5 Shift.
+ guint stateWithoutAltGraph =
+ state & ~(GetModifierMask(LEVEL3) | GetModifierMask(LEVEL5));
+ if (state == stateWithoutAltGraph) {
+ return 0;
+ }
+ return GetCharCodeFor(aGdkKeyEvent, GdkModifierType(stateWithoutAltGraph),
+ aGdkKeyEvent->group);
+}
+
+gint KeymapWrapper::GetKeyLevel(GdkEventKey* aGdkKeyEvent) {
+ gint level;
+ if (!gdk_keymap_translate_keyboard_state(
+ mGdkKeymap, aGdkKeyEvent->hardware_keycode,
+ GdkModifierType(aGdkKeyEvent->state), aGdkKeyEvent->group, nullptr,
+ nullptr, &level, nullptr)) {
+ return -1;
+ }
+ return level;
+}
+
+gint KeymapWrapper::GetFirstLatinGroup() {
+ GdkKeymapKey* keys;
+ gint count;
+ gint minGroup = -1;
+ if (gdk_keymap_get_entries_for_keyval(mGdkKeymap, GDK_a, &keys, &count)) {
+ // find the minimum number group for latin inputtable layout
+ for (gint i = 0; i < count && minGroup != 0; ++i) {
+ if (keys[i].level != 0 && keys[i].level != 1) {
+ continue;
+ }
+ if (minGroup >= 0 && keys[i].group > minGroup) {
+ continue;
+ }
+ minGroup = keys[i].group;
+ }
+ g_free(keys);
+ }
+ return minGroup;
+}
+
+bool KeymapWrapper::IsLatinGroup(guint8 aGroup) {
+ GdkKeymapKey* keys;
+ gint count;
+ bool result = false;
+ if (gdk_keymap_get_entries_for_keyval(mGdkKeymap, GDK_a, &keys, &count)) {
+ for (gint i = 0; i < count; ++i) {
+ if (keys[i].level != 0 && keys[i].level != 1) {
+ continue;
+ }
+ if (keys[i].group == aGroup) {
+ result = true;
+ break;
+ }
+ }
+ g_free(keys);
+ }
+ return result;
+}
+
+bool KeymapWrapper::IsAutoRepeatableKey(guint aHardwareKeyCode) {
+ uint8_t indexOfArray = aHardwareKeyCode / 8;
+ MOZ_ASSERT(indexOfArray < ArrayLength(mKeyboardState.auto_repeats),
+ "invalid index");
+ char bitMask = 1 << (aHardwareKeyCode % 8);
+ return (mKeyboardState.auto_repeats[indexOfArray] & bitMask) != 0;
+}
+
+/* static */
+bool KeymapWrapper::IsBasicLatinLetterOrNumeral(uint32_t aCharCode) {
+ return (aCharCode >= 'a' && aCharCode <= 'z') ||
+ (aCharCode >= 'A' && aCharCode <= 'Z') ||
+ (aCharCode >= '0' && aCharCode <= '9');
+}
+
+/* static */
+guint KeymapWrapper::GetGDKKeyvalWithoutModifier(
+ const GdkEventKey* aGdkKeyEvent) {
+ KeymapWrapper* keymapWrapper = GetInstance();
+ guint state =
+ (aGdkKeyEvent->state & keymapWrapper->GetModifierMask(NUM_LOCK));
+ guint keyval;
+ if (!gdk_keymap_translate_keyboard_state(
+ keymapWrapper->mGdkKeymap, aGdkKeyEvent->hardware_keycode,
+ GdkModifierType(state), aGdkKeyEvent->group, &keyval, nullptr,
+ nullptr, nullptr)) {
+ return 0;
+ }
+ return keyval;
+}
+
+/* static */
+uint32_t KeymapWrapper::GetDOMKeyCodeFromKeyPairs(guint aGdkKeyval) {
+ switch (aGdkKeyval) {
+ case GDK_Cancel:
+ return NS_VK_CANCEL;
+ case GDK_BackSpace:
+ return NS_VK_BACK;
+ case GDK_Tab:
+ case GDK_ISO_Left_Tab:
+ return NS_VK_TAB;
+ case GDK_Clear:
+ return NS_VK_CLEAR;
+ case GDK_Return:
+ return NS_VK_RETURN;
+ case GDK_Shift_L:
+ case GDK_Shift_R:
+ case GDK_Shift_Lock:
+ return NS_VK_SHIFT;
+ case GDK_Control_L:
+ case GDK_Control_R:
+ return NS_VK_CONTROL;
+ case GDK_Alt_L:
+ case GDK_Alt_R:
+ return NS_VK_ALT;
+ case GDK_Meta_L:
+ case GDK_Meta_R:
+ return NS_VK_META;
+
+ // Assume that Super or Hyper is always mapped to physical Win key.
+ case GDK_Super_L:
+ case GDK_Super_R:
+ case GDK_Hyper_L:
+ case GDK_Hyper_R:
+ return NS_VK_WIN;
+
+ // GTK's AltGraph key is similar to Mac's Option (Alt) key. However,
+ // unfortunately, browsers on Mac are using NS_VK_ALT for it even though
+ // it's really different from Alt key on Windows.
+ // On the other hand, GTK's AltGrapsh keys are really different from
+ // Alt key. However, there is no AltGrapsh key on Windows. On Windows,
+ // both Ctrl and Alt keys are pressed internally when AltGr key is
+ // pressed. For some languages' users, AltGraph key is important, so,
+ // web applications on such locale may want to know AltGraph key press.
+ // Therefore, we should map AltGr keycode for them only on GTK.
+ case GDK_ISO_Level3_Shift:
+ case GDK_ISO_Level5_Shift:
+ // We assume that Mode_switch is always used for level3 shift.
+ case GDK_Mode_switch:
+ return NS_VK_ALTGR;
+
+ case GDK_Pause:
+ return NS_VK_PAUSE;
+ case GDK_Caps_Lock:
+ return NS_VK_CAPS_LOCK;
+ case GDK_Kana_Lock:
+ case GDK_Kana_Shift:
+ return NS_VK_KANA;
+ case GDK_Hangul:
+ return NS_VK_HANGUL;
+ // case GDK_XXX: return NS_VK_JUNJA;
+ // case GDK_XXX: return NS_VK_FINAL;
+ case GDK_Hangul_Hanja:
+ return NS_VK_HANJA;
+ case GDK_Kanji:
+ return NS_VK_KANJI;
+ case GDK_Escape:
+ return NS_VK_ESCAPE;
+ case GDK_Henkan:
+ return NS_VK_CONVERT;
+ case GDK_Muhenkan:
+ return NS_VK_NONCONVERT;
+ // case GDK_XXX: return NS_VK_ACCEPT;
+ // case GDK_XXX: return NS_VK_MODECHANGE;
+ case GDK_Page_Up:
+ return NS_VK_PAGE_UP;
+ case GDK_Page_Down:
+ return NS_VK_PAGE_DOWN;
+ case GDK_End:
+ return NS_VK_END;
+ case GDK_Home:
+ return NS_VK_HOME;
+ case GDK_Left:
+ return NS_VK_LEFT;
+ case GDK_Up:
+ return NS_VK_UP;
+ case GDK_Right:
+ return NS_VK_RIGHT;
+ case GDK_Down:
+ return NS_VK_DOWN;
+ case GDK_Select:
+ return NS_VK_SELECT;
+ case GDK_Print:
+ return NS_VK_PRINT;
+ case GDK_Execute:
+ return NS_VK_EXECUTE;
+ case GDK_Insert:
+ return NS_VK_INSERT;
+ case GDK_Delete:
+ return NS_VK_DELETE;
+ case GDK_Help:
+ return NS_VK_HELP;
+
+ // keypad keys
+ case GDK_KP_Left:
+ return NS_VK_LEFT;
+ case GDK_KP_Right:
+ return NS_VK_RIGHT;
+ case GDK_KP_Up:
+ return NS_VK_UP;
+ case GDK_KP_Down:
+ return NS_VK_DOWN;
+ case GDK_KP_Page_Up:
+ return NS_VK_PAGE_UP;
+ // Not sure what these are
+ // case GDK_KP_Prior: return NS_VK_;
+ // case GDK_KP_Next: return NS_VK_;
+ case GDK_KP_Begin:
+ return NS_VK_CLEAR; // Num-unlocked 5
+ case GDK_KP_Page_Down:
+ return NS_VK_PAGE_DOWN;
+ case GDK_KP_Home:
+ return NS_VK_HOME;
+ case GDK_KP_End:
+ return NS_VK_END;
+ case GDK_KP_Insert:
+ return NS_VK_INSERT;
+ case GDK_KP_Delete:
+ return NS_VK_DELETE;
+ case GDK_KP_Enter:
+ return NS_VK_RETURN;
+
+ case GDK_Num_Lock:
+ return NS_VK_NUM_LOCK;
+ case GDK_Scroll_Lock:
+ return NS_VK_SCROLL_LOCK;
+
+ // Function keys
+ case GDK_F1:
+ return NS_VK_F1;
+ case GDK_F2:
+ return NS_VK_F2;
+ case GDK_F3:
+ return NS_VK_F3;
+ case GDK_F4:
+ return NS_VK_F4;
+ case GDK_F5:
+ return NS_VK_F5;
+ case GDK_F6:
+ return NS_VK_F6;
+ case GDK_F7:
+ return NS_VK_F7;
+ case GDK_F8:
+ return NS_VK_F8;
+ case GDK_F9:
+ return NS_VK_F9;
+ case GDK_F10:
+ return NS_VK_F10;
+ case GDK_F11:
+ return NS_VK_F11;
+ case GDK_F12:
+ return NS_VK_F12;
+ case GDK_F13:
+ return NS_VK_F13;
+ case GDK_F14:
+ return NS_VK_F14;
+ case GDK_F15:
+ return NS_VK_F15;
+ case GDK_F16:
+ return NS_VK_F16;
+ case GDK_F17:
+ return NS_VK_F17;
+ case GDK_F18:
+ return NS_VK_F18;
+ case GDK_F19:
+ return NS_VK_F19;
+ case GDK_F20:
+ return NS_VK_F20;
+ case GDK_F21:
+ return NS_VK_F21;
+ case GDK_F22:
+ return NS_VK_F22;
+ case GDK_F23:
+ return NS_VK_F23;
+ case GDK_F24:
+ return NS_VK_F24;
+
+ // context menu key, keysym 0xff67, typically keycode 117 on 105-key
+ // (Microsoft) x86 keyboards, located between right 'Windows' key and
+ // right Ctrl key
+ case GDK_Menu:
+ return NS_VK_CONTEXT_MENU;
+ case GDK_Sleep:
+ return NS_VK_SLEEP;
+
+ case GDK_3270_Attn:
+ return NS_VK_ATTN;
+ case GDK_3270_CursorSelect:
+ return NS_VK_CRSEL;
+ case GDK_3270_ExSelect:
+ return NS_VK_EXSEL;
+ case GDK_3270_EraseEOF:
+ return NS_VK_EREOF;
+ case GDK_3270_Play:
+ return NS_VK_PLAY;
+ // case GDK_XXX: return NS_VK_ZOOM;
+ case GDK_3270_PA1:
+ return NS_VK_PA1;
+
+ // map Sun Keyboard special keysyms on to NS_VK keys
+
+ // Sun F11 key generates SunF36(0x1005ff10) keysym
+ case 0x1005ff10:
+ return NS_VK_F11;
+ // Sun F12 key generates SunF37(0x1005ff11) keysym
+ case 0x1005ff11:
+ return NS_VK_F12;
+ default:
+ return 0;
+ }
+}
+
+void KeymapWrapper::WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent) {
+ GetInstance()->WillDispatchKeyboardEventInternal(aKeyEvent, aGdkKeyEvent);
+}
+
+void KeymapWrapper::WillDispatchKeyboardEventInternal(
+ WidgetKeyboardEvent& aKeyEvent, GdkEventKey* aGdkKeyEvent) {
+ if (!aGdkKeyEvent) {
+ // If aGdkKeyEvent is nullptr, we're trying to dispatch a fake keyboard
+ // event in such case, we don't need to set alternative char codes.
+ // So, we don't need to do nothing here. This case is typically we're
+ // dispatching eKeyDown or eKeyUp event during composition.
+ return;
+ }
+
+ uint32_t charCode = GetCharCodeFor(aGdkKeyEvent);
+ if (!charCode) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "mKeyCode=0x%02X, charCode=0x%08X",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode));
+ return;
+ }
+
+ // The mCharCode was set from mKeyValue. However, for example, when Ctrl key
+ // is pressed, its value should indicate an ASCII character for backward
+ // compatibility rather than inputting character without the modifiers.
+ // Therefore, we need to modify mCharCode value here.
+ aKeyEvent.SetCharCode(charCode);
+
+ gint level = GetKeyLevel(aGdkKeyEvent);
+ if (level != 0 && level != 1) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level));
+ return;
+ }
+
+ guint baseState =
+ aGdkKeyEvent->state & ~(GetModifierMask(SHIFT) | GetModifierMask(CTRL) |
+ GetModifierMask(ALT) | GetModifierMask(META) |
+ GetModifierMask(SUPER) | GetModifierMask(HYPER));
+
+ // We shold send both shifted char and unshifted char, all keyboard layout
+ // users can use all keys. Don't change event.mCharCode. On some keyboard
+ // layouts, Ctrl/Alt/Meta keys are used for inputting some characters.
+ AlternativeCharCode altCharCodes(0, 0);
+ // unshifted charcode of current keyboard layout.
+ altCharCodes.mUnshiftedCharCode =
+ GetCharCodeFor(aGdkKeyEvent, baseState, aGdkKeyEvent->group);
+ bool isLatin = (altCharCodes.mUnshiftedCharCode <= 0xFF);
+ // shifted charcode of current keyboard layout.
+ altCharCodes.mShiftedCharCode = GetCharCodeFor(
+ aGdkKeyEvent, baseState | GetModifierMask(SHIFT), aGdkKeyEvent->group);
+ isLatin = isLatin && (altCharCodes.mShiftedCharCode <= 0xFF);
+ if (altCharCodes.mUnshiftedCharCode || altCharCodes.mShiftedCharCode) {
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
+ }
+
+ bool needLatinKeyCodes = !isLatin;
+ if (!needLatinKeyCodes) {
+ needLatinKeyCodes =
+ (IS_ASCII_ALPHABETICAL(altCharCodes.mUnshiftedCharCode) !=
+ IS_ASCII_ALPHABETICAL(altCharCodes.mShiftedCharCode));
+ }
+
+ // If current keyboard layout can input Latin characters, we don't need
+ // more information.
+ if (!needLatinKeyCodes) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d, altCharCodes={ "
+ "mUnshiftedCharCode=0x%08X, mShiftedCharCode=0x%08X }",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level,
+ altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode));
+ return;
+ }
+
+ // Next, find Latin inputtable keyboard layout.
+ gint minGroup = GetFirstLatinGroup();
+ if (minGroup < 0) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "Latin keyboard layout isn't found: "
+ "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d, "
+ "altCharCodes={ mUnshiftedCharCode=0x%08X, "
+ "mShiftedCharCode=0x%08X }",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level,
+ altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode));
+ return;
+ }
+
+ AlternativeCharCode altLatinCharCodes(0, 0);
+ uint32_t unmodifiedCh = aKeyEvent.IsShift() ? altCharCodes.mShiftedCharCode
+ : altCharCodes.mUnshiftedCharCode;
+
+ // unshifted charcode of found keyboard layout.
+ uint32_t ch = GetCharCodeFor(aGdkKeyEvent, baseState, minGroup);
+ altLatinCharCodes.mUnshiftedCharCode =
+ IsBasicLatinLetterOrNumeral(ch) ? ch : 0;
+ // shifted charcode of found keyboard layout.
+ ch = GetCharCodeFor(aGdkKeyEvent, baseState | GetModifierMask(SHIFT),
+ minGroup);
+ altLatinCharCodes.mShiftedCharCode = IsBasicLatinLetterOrNumeral(ch) ? ch : 0;
+ if (altLatinCharCodes.mUnshiftedCharCode ||
+ altLatinCharCodes.mShiftedCharCode) {
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altLatinCharCodes);
+ }
+ // If the mCharCode is not Latin, and the level is 0 or 1, we should
+ // replace the mCharCode to Latin char if Alt and Meta keys are not
+ // pressed. (Alt should be sent the localized char for accesskey
+ // like handling of Web Applications.)
+ ch = aKeyEvent.IsShift() ? altLatinCharCodes.mShiftedCharCode
+ : altLatinCharCodes.mUnshiftedCharCode;
+ if (ch && !(aKeyEvent.IsAlt() || aKeyEvent.IsMeta()) &&
+ charCode == unmodifiedCh) {
+ aKeyEvent.SetCharCode(ch);
+ }
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d, minGroup=%d, "
+ "altCharCodes={ mUnshiftedCharCode=0x%08X, "
+ "mShiftedCharCode=0x%08X } "
+ "altLatinCharCodes={ mUnshiftedCharCode=0x%08X, "
+ "mShiftedCharCode=0x%08X }",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level, minGroup,
+ altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode,
+ altLatinCharCodes.mUnshiftedCharCode,
+ altLatinCharCodes.mShiftedCharCode));
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/nsGtkKeyUtils.h b/widget/gtk/nsGtkKeyUtils.h
new file mode 100644
index 0000000000..3354cf2fee
--- /dev/null
+++ b/widget/gtk/nsGtkKeyUtils.h
@@ -0,0 +1,467 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __nsGdkKeyUtils_h__
+#define __nsGdkKeyUtils_h__
+
+#include "nsTArray.h"
+#include "mozilla/EventForwards.h"
+
+#include <gdk/gdk.h>
+#include <X11/XKBlib.h>
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkwayland.h>
+# include <xkbcommon/xkbcommon.h>
+#endif
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * KeymapWrapper is a wrapper class of GdkKeymap. GdkKeymap doesn't support
+ * all our needs, therefore, we need to access lower level APIs.
+ * But such code is usually complex and might be slow. Against such issues,
+ * we should cache some information.
+ *
+ * This class provides only static methods. The methods is using internal
+ * singleton instance which is initialized by default GdkKeymap. When the
+ * GdkKeymap is destroyed, the singleton instance will be destroyed.
+ */
+
+class KeymapWrapper {
+ public:
+ /**
+ * Compute an our DOM keycode from a GDK keyval.
+ */
+ static uint32_t ComputeDOMKeyCode(const GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * Compute a DOM key name index from aGdkKeyEvent.
+ */
+ static KeyNameIndex ComputeDOMKeyNameIndex(const GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * Compute a DOM code name index from aGdkKeyEvent.
+ */
+ static CodeNameIndex ComputeDOMCodeNameIndex(const GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * Modifier is list of modifiers which we support in widget level.
+ */
+ enum Modifier {
+ NOT_MODIFIER = 0x0000,
+ CAPS_LOCK = 0x0001,
+ NUM_LOCK = 0x0002,
+ SCROLL_LOCK = 0x0004,
+ SHIFT = 0x0008,
+ CTRL = 0x0010,
+ ALT = 0x0020,
+ META = 0x0040,
+ SUPER = 0x0080,
+ HYPER = 0x0100,
+ LEVEL3 = 0x0200,
+ LEVEL5 = 0x0400
+ };
+
+ /**
+ * Modifiers is used for combination of Modifier.
+ * E.g., |Modifiers modifiers = (SHIFT | CTRL);| means Shift and Ctrl.
+ */
+ typedef uint32_t Modifiers;
+
+ /**
+ * GetCurrentModifierState() returns current modifier key state.
+ * The "current" means actual state of hardware keyboard when this is
+ * called. I.e., if some key events are not still dispatched by GDK,
+ * the state may mismatch with GdkEventKey::state.
+ *
+ * @return Current modifier key state.
+ */
+ static guint GetCurrentModifierState();
+
+ /**
+ * AreModifiersCurrentlyActive() checks the "current" modifier state
+ * on aGdkWindow with the keymap of the singleton instance.
+ *
+ * @param aModifiers One or more of Modifier values except
+ * NOT_MODIFIER.
+ * @return TRUE if all of modifieres in aModifiers are
+ * active. Otherwise, FALSE.
+ */
+ static bool AreModifiersCurrentlyActive(Modifiers aModifiers);
+
+ /**
+ * Utility function to compute current keyboard modifiers for
+ * WidgetInputEvent
+ */
+ static uint32_t ComputeCurrentKeyModifiers();
+
+ /**
+ * Utility function to covert platform modifier state to keyboard modifiers
+ * of WidgetInputEvent
+ */
+ static uint32_t ComputeKeyModifiers(guint aModifierState);
+
+ /**
+ * InitInputEvent() initializes the aInputEvent with aModifierState.
+ */
+ static void InitInputEvent(WidgetInputEvent& aInputEvent,
+ guint aModifierState);
+
+ /**
+ * InitKeyEvent() intializes aKeyEvent's modifier key related members
+ * and keycode related values.
+ *
+ * @param aKeyEvent It's an WidgetKeyboardEvent which needs to be
+ * initialized.
+ * @param aGdkKeyEvent A native GDK key event.
+ * @param aIsProcessedByIME true if aGdkKeyEvent is handled by IME.
+ */
+ static void InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent, bool aIsProcessedByIME);
+
+ /**
+ * DispatchKeyDownOrKeyUpEvent() dispatches eKeyDown or eKeyUp event.
+ *
+ * @param aWindow The window to dispatch a keyboard event.
+ * @param aGdkKeyEvent A native GDK_KEY_PRESS or GDK_KEY_RELEASE
+ * event.
+ * @param aIsProcessedByIME true if the event is handled by IME.
+ * @param aIsCancelled [Out] true if the default is prevented.
+ * @return true if eKeyDown event is actually dispatched.
+ * Otherwise, false.
+ */
+ static bool DispatchKeyDownOrKeyUpEvent(nsWindow* aWindow,
+ GdkEventKey* aGdkKeyEvent,
+ bool aIsProcessedByIME,
+ bool* aIsCancelled);
+
+ /**
+ * DispatchKeyDownOrKeyUpEvent() dispatches eKeyDown or eKeyUp event.
+ *
+ * @param aWindow The window to dispatch aKeyboardEvent.
+ * @param aKeyboardEvent An eKeyDown or eKeyUp event. This will be
+ * dispatched as is.
+ * @param aIsCancelled [Out] true if the default is prevented.
+ * @return true if eKeyDown event is actually dispatched.
+ * Otherwise, false.
+ */
+ static bool DispatchKeyDownOrKeyUpEvent(nsWindow* aWindow,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ bool* aIsCancelled);
+
+ /**
+ * GDK_KEY_PRESS event handler.
+ *
+ * @param aWindow The window to dispatch eKeyDown event (and maybe
+ * eKeyPress events).
+ * @param aGdkKeyEvent Receivied GDK_KEY_PRESS event.
+ */
+ static void HandleKeyPressEvent(nsWindow* aWindow, GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * GDK_KEY_RELEASE event handler.
+ *
+ * @param aWindow The window to dispatch eKeyUp event.
+ * @param aGdkKeyEvent Receivied GDK_KEY_RELEASE event.
+ * @return true if an event is dispatched. Otherwise, false.
+ */
+ static bool HandleKeyReleaseEvent(nsWindow* aWindow,
+ GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * WillDispatchKeyboardEvent() is called via
+ * TextEventDispatcherListener::WillDispatchKeyboardEvent().
+ *
+ * @param aKeyEvent An instance of KeyboardEvent which will be
+ * dispatched. This method should set charCode
+ * and alternative char codes if it's necessary.
+ * @param aGdkKeyEvent A GdkEventKey instance which caused the
+ * aKeyEvent.
+ */
+ static void WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent);
+
+#ifdef MOZ_WAYLAND
+ /**
+ * Utility function to set all supported modifier masks
+ * from xkb_keymap. We call that from Wayland backend routines.
+ */
+ static void SetModifierMasks(xkb_keymap* aKeymap);
+#endif
+
+ /**
+ * ResetKeyboard is called on keymap changes from OnKeysChanged and
+ * keyboard_handle_keymap to prepare for keymap changes.
+ */
+ static void ResetKeyboard();
+
+ /**
+ * Destroys the singleton KeymapWrapper instance, if it exists.
+ */
+ static void Shutdown();
+
+ protected:
+ /**
+ * GetInstance() returns a KeymapWrapper instance.
+ *
+ * @return A singleton instance of KeymapWrapper.
+ */
+ static KeymapWrapper* GetInstance();
+
+ KeymapWrapper();
+ ~KeymapWrapper();
+
+ bool mInitialized;
+
+ /**
+ * Initializing methods.
+ */
+ void Init();
+ void InitXKBExtension();
+ void InitBySystemSettingsX11();
+#ifdef MOZ_WAYLAND
+ void InitBySystemSettingsWayland();
+#endif
+
+ /**
+ * mModifierKeys stores each hardware key information.
+ */
+ struct ModifierKey {
+ guint mHardwareKeycode;
+ guint mMask;
+
+ explicit ModifierKey(guint aHardwareKeycode)
+ : mHardwareKeycode(aHardwareKeycode), mMask(0) {}
+ };
+ nsTArray<ModifierKey> mModifierKeys;
+
+ /**
+ * GetModifierKey() returns modifier key information of the hardware
+ * keycode. If the key isn't a modifier key, returns nullptr.
+ */
+ ModifierKey* GetModifierKey(guint aHardwareKeycode);
+
+ /**
+ * mModifierMasks is bit masks for each modifier. The index should be one
+ * of ModifierIndex values.
+ */
+ enum ModifierIndex {
+ INDEX_NUM_LOCK,
+ INDEX_SCROLL_LOCK,
+ INDEX_ALT,
+ INDEX_META,
+ INDEX_SUPER,
+ INDEX_HYPER,
+ INDEX_LEVEL3,
+ INDEX_LEVEL5,
+ COUNT_OF_MODIFIER_INDEX
+ };
+ guint mModifierMasks[COUNT_OF_MODIFIER_INDEX];
+
+ guint GetModifierMask(Modifier aModifier) const;
+
+ /**
+ * @param aGdkKeyval A GDK defined modifier key value such as
+ * GDK_Shift_L.
+ * @return Returns Modifier values for aGdkKeyval.
+ * If the given key code isn't a modifier key,
+ * returns NOT_MODIFIER.
+ */
+ static Modifier GetModifierForGDKKeyval(guint aGdkKeyval);
+
+ static const char* GetModifierName(Modifier aModifier);
+
+ /**
+ * AreModifiersActive() just checks whether aModifierState indicates
+ * all modifiers in aModifiers are active or not.
+ *
+ * @param aModifiers One or more of Modifier values except
+ * NOT_MODIFIER.
+ * @param aModifierState GDK's modifier states.
+ * @return TRUE if aGdkModifierType indecates all of
+ * modifiers in aModifier are active.
+ * Otherwise, FALSE.
+ */
+ static bool AreModifiersActive(Modifiers aModifiers, guint aModifierState);
+
+ /**
+ * mGdkKeymap is a wrapped instance by this class.
+ */
+ GdkKeymap* mGdkKeymap;
+
+ /**
+ * The base event code of XKB extension.
+ */
+ int mXKBBaseEventCode;
+
+ /**
+ * Only auto_repeats[] stores valid value. If you need to use other
+ * members, you need to listen notification events for them.
+ * See a call of XkbSelectEventDetails() with XkbControlsNotify in
+ * InitXKBExtension().
+ */
+ XKeyboardState mKeyboardState;
+
+ /**
+ * Pointer of the singleton instance.
+ */
+ static KeymapWrapper* sInstance;
+
+ /**
+ * Auto key repeat management.
+ */
+ static guint sLastRepeatableHardwareKeyCode;
+ static Time sLastRepeatableKeyTime;
+ enum RepeatState { NOT_PRESSED, FIRST_PRESS, REPEATING };
+ static RepeatState sRepeatState;
+
+ /**
+ * IsAutoRepeatableKey() returns true if the key supports auto repeat.
+ * Otherwise, false.
+ */
+ bool IsAutoRepeatableKey(guint aHardwareKeyCode);
+
+ /**
+ * Signal handlers.
+ */
+ static void OnKeysChanged(GdkKeymap* aKeymap, KeymapWrapper* aKeymapWrapper);
+ static void OnDirectionChanged(GdkKeymap* aGdkKeymap,
+ KeymapWrapper* aKeymapWrapper);
+
+ gulong mOnKeysChangedSignalHandle;
+ gulong mOnDirectionChangedSignalHandle;
+
+ /**
+ * GetCharCodeFor() Computes what character is inputted by the key event
+ * with aModifierState and aGroup.
+ *
+ * @param aGdkKeyEvent Native key event, must not be nullptr.
+ * @param aModifierState Combination of GdkModifierType which you
+ * want to test with aGdkKeyEvent.
+ * @param aGroup Set group in the mGdkKeymap.
+ * @return charCode which is inputted by aGdkKeyEvent.
+ * If failed, this returns 0.
+ */
+ static uint32_t GetCharCodeFor(const GdkEventKey* aGdkKeyEvent);
+ uint32_t GetCharCodeFor(const GdkEventKey* aGdkKeyEvent, guint aModifierState,
+ gint aGroup);
+
+ /**
+ * GetUnmodifiedCharCodeFor() computes what character is inputted by the
+ * key event without Ctrl/Alt/Meta/Super/Hyper modifiers.
+ * If Level3 or Level5 Shift causes no character input, this also ignores
+ * them.
+ *
+ * @param aGdkKeyEvent Native key event, must not be nullptr.
+ * @return charCode which is computed without modifiers
+ * which prevent text input.
+ */
+ uint32_t GetUnmodifiedCharCodeFor(const GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * GetKeyLevel() returns level of the aGdkKeyEvent in mGdkKeymap.
+ *
+ * @param aGdkKeyEvent Native key event, must not be nullptr.
+ * @return Using level. Typically, this is 0 or 1.
+ * If failed, this returns -1.
+ */
+ gint GetKeyLevel(GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * GetFirstLatinGroup() returns group of mGdkKeymap which can input an
+ * ASCII character by GDK_A.
+ *
+ * @return group value of GdkEventKey.
+ */
+ gint GetFirstLatinGroup();
+
+ /**
+ * IsLatinGroup() checkes whether the keyboard layout of aGroup is
+ * ASCII alphabet inputtable or not.
+ *
+ * @param aGroup The group value of GdkEventKey.
+ * @return TRUE if the keyboard layout can input
+ * ASCII alphabet. Otherwise, FALSE.
+ */
+ bool IsLatinGroup(guint8 aGroup);
+
+ /**
+ * IsBasicLatinLetterOrNumeral() Checks whether the aCharCode is an
+ * alphabet or a numeric character in ASCII.
+ *
+ * @param aCharCode Charcode which you want to test.
+ * @return TRUE if aCharCode is an alphabet or a numeric
+ * in ASCII range. Otherwise, FALSE.
+ */
+ static bool IsBasicLatinLetterOrNumeral(uint32_t aCharCode);
+
+ /**
+ * IsPrintableASCIICharacter() checks whether the aCharCode is a printable
+ * ASCII character. I.e., returns false if aCharCode is a control
+ * character even in an ASCII character.
+ */
+ static bool IsPrintableASCIICharacter(uint32_t aCharCode) {
+ return aCharCode >= 0x20 && aCharCode <= 0x7E;
+ }
+
+ /**
+ * GetGDKKeyvalWithoutModifier() returns the keyval for aGdkKeyEvent when
+ * ignoring the modifier state except NumLock. (NumLock is a key to change
+ * some key's meaning.)
+ */
+ static guint GetGDKKeyvalWithoutModifier(const GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * GetDOMKeyCodeFromKeyPairs() returns DOM keycode for aGdkKeyval if
+ * it's in KeyPair table.
+ */
+ static uint32_t GetDOMKeyCodeFromKeyPairs(guint aGdkKeyval);
+
+ /**
+ * FilterEvents() listens all events on all our windows.
+ * Be careful, this may make damage to performance if you add expensive
+ * code in this method.
+ */
+ static GdkFilterReturn FilterEvents(GdkXEvent* aXEvent, GdkEvent* aGdkEvent,
+ gpointer aData);
+
+ /**
+ * MaybeDispatchContextMenuEvent() may dispatch eContextMenu event if
+ * the given key combination should cause opening context menu.
+ *
+ * @param aWindow The window to dispatch a contextmenu event.
+ * @param aEvent The native key event.
+ * @return true if this method dispatched eContextMenu
+ * event. Otherwise, false.
+ * Be aware, when this returns true, the
+ * widget may have been destroyed.
+ */
+ static bool MaybeDispatchContextMenuEvent(nsWindow* aWindow,
+ const GdkEventKey* aEvent);
+
+ /**
+ * See the document of WillDispatchKeyboardEvent().
+ */
+ void WillDispatchKeyboardEventInternal(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent);
+
+#ifdef MOZ_WAYLAND
+ /**
+ * Utility function to set Xkb modifier key mask.
+ */
+ void SetModifierMask(xkb_keymap* aKeymap, ModifierIndex aModifierIndex,
+ const char* aModifierName);
+#endif
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __nsGdkKeyUtils_h__ */
diff --git a/widget/gtk/nsGtkUtils.h b/widget/gtk/nsGtkUtils.h
new file mode 100644
index 0000000000..58cf2eed16
--- /dev/null
+++ b/widget/gtk/nsGtkUtils.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsGtkUtils_h__
+#define nsGtkUtils_h__
+
+#include <glib.h>
+
+// Some gobject functions expect functions for gpointer arguments.
+// gpointer is void* but C++ doesn't like casting functions to void*.
+template <class T>
+static inline gpointer FuncToGpointer(T aFunction) {
+ return reinterpret_cast<gpointer>(
+ reinterpret_cast<uintptr_t>
+ // This cast just provides a warning if T is not a function.
+ (reinterpret_cast<void (*)()>(aFunction)));
+}
+
+#endif // nsGtkUtils_h__
diff --git a/widget/gtk/nsIImageToPixbuf.h b/widget/gtk/nsIImageToPixbuf.h
new file mode 100644
index 0000000000..396d1b98b5
--- /dev/null
+++ b/widget/gtk/nsIImageToPixbuf.h
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSIIMAGETOPIXBUF_H_
+#define NSIIMAGETOPIXBUF_H_
+
+#include "nsISupports.h"
+
+// dfa4ac93-83f2-4ab8-9b2a-0ff7022aebe2
+#define NSIIMAGETOPIXBUF_IID \
+ { \
+ 0xdfa4ac93, 0x83f2, 0x4ab8, { \
+ 0x9b, 0x2a, 0x0f, 0xf7, 0x02, 0x2a, 0xeb, 0xe2 \
+ } \
+ }
+
+class imgIContainer;
+typedef struct _GdkPixbuf GdkPixbuf;
+
+/**
+ * An interface that allows converting the current frame of an imgIContainer to
+ * a GdkPixbuf*.
+ */
+class nsIImageToPixbuf : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NSIIMAGETOPIXBUF_IID)
+
+ /**
+ * The return value, if not null, should be released as needed
+ * by the caller using g_object_unref.
+ */
+ NS_IMETHOD_(GdkPixbuf*) ConvertImageToPixbuf(imgIContainer* aImage) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIImageToPixbuf, NSIIMAGETOPIXBUF_IID)
+
+#endif
diff --git a/widget/gtk/nsImageToPixbuf.cpp b/widget/gtk/nsImageToPixbuf.cpp
new file mode 100644
index 0000000000..c70e589576
--- /dev/null
+++ b/widget/gtk/nsImageToPixbuf.cpp
@@ -0,0 +1,108 @@
+/* vim:set sw=2 sts=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 <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "nsImageToPixbuf.h"
+
+#include "imgIContainer.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
+
+using mozilla::gfx::DataSourceSurface;
+using mozilla::gfx::SurfaceFormat;
+
+NS_IMPL_ISUPPORTS(nsImageToPixbuf, nsIImageToPixbuf)
+
+inline unsigned char unpremultiply(unsigned char color, unsigned char alpha) {
+ if (alpha == 0) return 0;
+ // plus alpha/2 to round instead of truncate
+ return (color * 255 + alpha / 2) / alpha;
+}
+
+NS_IMETHODIMP_(GdkPixbuf*)
+nsImageToPixbuf::ConvertImageToPixbuf(imgIContainer* aImage) {
+ return ImageToPixbuf(aImage);
+}
+
+GdkPixbuf* nsImageToPixbuf::ImageToPixbuf(imgIContainer* aImage) {
+ RefPtr<SourceSurface> surface = aImage->GetFrame(
+ imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+
+ // If the last call failed, it was probably because our call stack originates
+ // in an imgINotificationObserver event, meaning that we're not allowed
+ // request a sync decode. Presumably the originating event is something
+ // sensible like OnStopFrame(), so we can just retry the call without a sync
+ // decode.
+ if (!surface)
+ surface = aImage->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_NONE);
+
+ NS_ENSURE_TRUE(surface, nullptr);
+
+ return SourceSurfaceToPixbuf(surface, surface->GetSize().width,
+ surface->GetSize().height);
+}
+
+GdkPixbuf* nsImageToPixbuf::SourceSurfaceToPixbuf(SourceSurface* aSurface,
+ int32_t aWidth,
+ int32_t aHeight) {
+ MOZ_ASSERT(aWidth <= aSurface->GetSize().width &&
+ aHeight <= aSurface->GetSize().height,
+ "Requested rect is bigger than the supplied surface");
+
+ GdkPixbuf* pixbuf =
+ gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, aWidth, aHeight);
+ if (!pixbuf) return nullptr;
+
+ uint32_t destStride = gdk_pixbuf_get_rowstride(pixbuf);
+ guchar* destPixels = gdk_pixbuf_get_pixels(pixbuf);
+
+ RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) return nullptr;
+
+ uint8_t* srcData = map.mData;
+ int32_t srcStride = map.mStride;
+
+ SurfaceFormat format = dataSurface->GetFormat();
+
+ for (int32_t row = 0; row < aHeight; ++row) {
+ for (int32_t col = 0; col < aWidth; ++col) {
+ guchar* destPixel = destPixels + row * destStride + 4 * col;
+
+ uint32_t* srcPixel =
+ reinterpret_cast<uint32_t*>((srcData + row * srcStride + 4 * col));
+
+ if (format == SurfaceFormat::B8G8R8A8) {
+ const uint8_t a = (*srcPixel >> 24) & 0xFF;
+ const uint8_t r = unpremultiply((*srcPixel >> 16) & 0xFF, a);
+ const uint8_t g = unpremultiply((*srcPixel >> 8) & 0xFF, a);
+ const uint8_t b = unpremultiply((*srcPixel >> 0) & 0xFF, a);
+
+ *destPixel++ = r;
+ *destPixel++ = g;
+ *destPixel++ = b;
+ *destPixel++ = a;
+ } else {
+ MOZ_ASSERT(format == SurfaceFormat::B8G8R8X8);
+
+ const uint8_t r = (*srcPixel >> 16) & 0xFF;
+ const uint8_t g = (*srcPixel >> 8) & 0xFF;
+ const uint8_t b = (*srcPixel >> 0) & 0xFF;
+
+ *destPixel++ = r;
+ *destPixel++ = g;
+ *destPixel++ = b;
+ *destPixel++ = 0xFF; // A
+ }
+ }
+ }
+
+ dataSurface->Unmap();
+
+ return pixbuf;
+}
diff --git a/widget/gtk/nsImageToPixbuf.h b/widget/gtk/nsImageToPixbuf.h
new file mode 100644
index 0000000000..3f30201ac5
--- /dev/null
+++ b/widget/gtk/nsImageToPixbuf.h
@@ -0,0 +1,47 @@
+/* vim:set sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSIMAGETOPIXBUF_H_
+#define NSIMAGETOPIXBUF_H_
+
+#include "nsIImageToPixbuf.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace gfx {
+class SourceSurface;
+}
+} // namespace mozilla
+
+class nsImageToPixbuf final : public nsIImageToPixbuf {
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD_(GdkPixbuf*) ConvertImageToPixbuf(imgIContainer* aImage) override;
+
+ // Friendlier version of ConvertImageToPixbuf for callers inside of
+ // widget
+ /**
+ * The return value of all these, if not null, should be
+ * released as needed by the caller using g_object_unref.
+ */
+ static GdkPixbuf* ImageToPixbuf(imgIContainer* aImage);
+ static GdkPixbuf* SourceSurfaceToPixbuf(SourceSurface* aSurface,
+ int32_t aWidth, int32_t aHeight);
+
+ private:
+ ~nsImageToPixbuf() = default;
+};
+
+// fc2389b8-c650-4093-9e42-b05e5f0685b7
+#define NS_IMAGE_TO_PIXBUF_CID \
+ { \
+ 0xfc2389b8, 0xc650, 0x4093, { \
+ 0x9e, 0x42, 0xb0, 0x5e, 0x5f, 0x06, 0x85, 0xb7 \
+ } \
+ }
+
+#endif
diff --git a/widget/gtk/nsLookAndFeel.cpp b/widget/gtk/nsLookAndFeel.cpp
new file mode 100644
index 0000000000..0fdc7748ce
--- /dev/null
+++ b/widget/gtk/nsLookAndFeel.cpp
@@ -0,0 +1,1536 @@
+/* -*- 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/. */
+
+// for strtod()
+#include <stdlib.h>
+
+#include "nsLookAndFeel.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+
+#include <pango/pango.h>
+#include <pango/pango-fontmap.h>
+
+#include <fontconfig/fontconfig.h>
+#include "gfxPlatformGtk.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/RelativeLuminanceUtils.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/Telemetry.h"
+#include "ScreenHelperGTK.h"
+#include "nsNativeBasicThemeGTK.h"
+
+#include "gtkdrawing.h"
+#include "nsStyleConsts.h"
+#include "gfxFontConstants.h"
+#include "WidgetUtils.h"
+#include "nsWindow.h"
+
+#include "mozilla/gfx/2D.h"
+
+#include <cairo-gobject.h>
+#include "WidgetStyleCache.h"
+#include "prenv.h"
+#include "nsCSSColorUtils.h"
+
+using namespace mozilla;
+using mozilla::LookAndFeel;
+
+#undef LOG
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gWidgetLog;
+# define LOG(args) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOG(args)
+#endif /* MOZ_LOGGING */
+
+#define GDK_COLOR_TO_NS_RGB(c) \
+ ((nscolor)NS_RGB(c.red >> 8, c.green >> 8, c.blue >> 8))
+#define GDK_RGBA_TO_NS_RGBA(c) \
+ ((nscolor)NS_RGBA((int)((c).red * 255), (int)((c).green * 255), \
+ (int)((c).blue * 255), (int)((c).alpha * 255)))
+
+nsLookAndFeel::nsLookAndFeel(const LookAndFeelCache* aCache) {
+ if (aCache) {
+ DoSetCache(*aCache);
+ }
+}
+
+nsLookAndFeel::~nsLookAndFeel() = default;
+
+// Modifies color |*aDest| as if a pattern of color |aSource| was painted with
+// CAIRO_OPERATOR_OVER to a surface with color |*aDest|.
+static void ApplyColorOver(const GdkRGBA& aSource, GdkRGBA* aDest) {
+ gdouble sourceCoef = aSource.alpha;
+ gdouble destCoef = aDest->alpha * (1.0 - sourceCoef);
+ gdouble resultAlpha = sourceCoef + destCoef;
+ if (resultAlpha != 0.0) { // don't divide by zero
+ destCoef /= resultAlpha;
+ sourceCoef /= resultAlpha;
+ aDest->red = sourceCoef * aSource.red + destCoef * aDest->red;
+ aDest->green = sourceCoef * aSource.green + destCoef * aDest->green;
+ aDest->blue = sourceCoef * aSource.blue + destCoef * aDest->blue;
+ aDest->alpha = resultAlpha;
+ }
+}
+
+static void GetLightAndDarkness(const GdkRGBA& aColor, double* aLightness,
+ double* aDarkness) {
+ double sum = aColor.red + aColor.green + aColor.blue;
+ *aLightness = sum * aColor.alpha;
+ *aDarkness = (3.0 - sum) * aColor.alpha;
+}
+
+static bool GetGradientColors(const GValue* aValue, GdkRGBA* aLightColor,
+ GdkRGBA* aDarkColor) {
+ if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN))
+ return false;
+
+ auto pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
+ if (!pattern) return false;
+
+ // Just picking the lightest and darkest colors as simple samples rather
+ // than trying to blend, which could get messy if there are many stops.
+ if (CAIRO_STATUS_SUCCESS !=
+ cairo_pattern_get_color_stop_rgba(pattern, 0, nullptr, &aDarkColor->red,
+ &aDarkColor->green, &aDarkColor->blue,
+ &aDarkColor->alpha))
+ return false;
+
+ double maxLightness, maxDarkness;
+ GetLightAndDarkness(*aDarkColor, &maxLightness, &maxDarkness);
+ *aLightColor = *aDarkColor;
+
+ GdkRGBA stop;
+ for (int index = 1;
+ CAIRO_STATUS_SUCCESS ==
+ cairo_pattern_get_color_stop_rgba(pattern, index, nullptr, &stop.red,
+ &stop.green, &stop.blue, &stop.alpha);
+ ++index) {
+ double lightness, darkness;
+ GetLightAndDarkness(stop, &lightness, &darkness);
+ if (lightness > maxLightness) {
+ maxLightness = lightness;
+ *aLightColor = stop;
+ }
+ if (darkness > maxDarkness) {
+ maxDarkness = darkness;
+ *aDarkColor = stop;
+ }
+ }
+
+ return true;
+}
+
+static bool GetUnicoBorderGradientColors(GtkStyleContext* aContext,
+ GdkRGBA* aLightColor,
+ GdkRGBA* aDarkColor) {
+ // Ubuntu 12.04 has GTK engine Unico-1.0.2, which overrides render_frame,
+ // providing its own border code. Ubuntu 14.04 has
+ // Unico-1.0.3+14.04.20140109, which does not override render_frame, and
+ // so does not need special attention. The earlier Unico can be detected
+ // by the -unico-border-gradient style property it registers.
+ // gtk_style_properties_lookup_property() is checked first to avoid the
+ // warning from gtk_style_context_get_property() when the property does
+ // not exist. (gtk_render_frame() of GTK+ 3.16 no longer uses the
+ // engine.)
+ const char* propertyName = "-unico-border-gradient";
+ if (!gtk_style_properties_lookup_property(propertyName, nullptr, nullptr))
+ return false;
+
+ // -unico-border-gradient is used only when the CSS node's engine is Unico.
+ GtkThemingEngine* engine;
+ GtkStateFlags state = gtk_style_context_get_state(aContext);
+ gtk_style_context_get(aContext, state, "engine", &engine, nullptr);
+ if (strcmp(g_type_name(G_TYPE_FROM_INSTANCE(engine)), "UnicoEngine") != 0)
+ return false;
+
+ // draw_border() of Unico engine uses -unico-border-gradient
+ // in preference to border-color.
+ GValue value = G_VALUE_INIT;
+ gtk_style_context_get_property(aContext, propertyName, state, &value);
+
+ bool result = GetGradientColors(&value, aLightColor, aDarkColor);
+
+ g_value_unset(&value);
+ return result;
+}
+
+// Sets |aLightColor| and |aDarkColor| to colors from |aContext|. Returns
+// true if |aContext| uses these colors to render a visible border.
+// If returning false, then the colors returned are a fallback from the
+// border-color value even though |aContext| does not use these colors to
+// render a border.
+static bool GetBorderColors(GtkStyleContext* aContext, GdkRGBA* aLightColor,
+ GdkRGBA* aDarkColor) {
+ // Determine whether the border on this style context is visible.
+ GtkStateFlags state = gtk_style_context_get_state(aContext);
+ GtkBorderStyle borderStyle;
+ gtk_style_context_get(aContext, state, GTK_STYLE_PROPERTY_BORDER_STYLE,
+ &borderStyle, nullptr);
+ bool visible = borderStyle != GTK_BORDER_STYLE_NONE &&
+ borderStyle != GTK_BORDER_STYLE_HIDDEN;
+ if (visible) {
+ // GTK has an initial value of zero for border-widths, and so themes
+ // need to explicitly set border-widths to make borders visible.
+ GtkBorder border;
+ gtk_style_context_get_border(aContext, state, &border);
+ visible = border.top != 0 || border.right != 0 || border.bottom != 0 ||
+ border.left != 0;
+ }
+
+ if (visible &&
+ GetUnicoBorderGradientColors(aContext, aLightColor, aDarkColor))
+ return true;
+
+ // The initial value for the border-color is the foreground color, and so
+ // this will usually return a color distinct from the background even if
+ // there is no visible border detected.
+ gtk_style_context_get_border_color(aContext, state, aDarkColor);
+ // TODO GTK3 - update aLightColor
+ // for GTK_BORDER_STYLE_INSET/OUTSET/GROVE/RIDGE border styles.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=978172#c25
+ *aLightColor = *aDarkColor;
+ return visible;
+}
+
+static bool GetBorderColors(GtkStyleContext* aContext, nscolor* aLightColor,
+ nscolor* aDarkColor) {
+ GdkRGBA lightColor, darkColor;
+ bool ret = GetBorderColors(aContext, &lightColor, &darkColor);
+ *aLightColor = GDK_RGBA_TO_NS_RGBA(lightColor);
+ *aDarkColor = GDK_RGBA_TO_NS_RGBA(darkColor);
+ return ret;
+}
+
+// Finds ideal cell highlight colors used for unfocused+selected cells distinct
+// from both Highlight, used as focused+selected background, and the listbox
+// background which is assumed to be similar to -moz-field
+nsresult nsLookAndFeel::InitCellHighlightColors() {
+ // NS_SUFFICIENT_LUMINOSITY_DIFFERENCE is the a11y standard for text
+ // on a background. Use 20% of that standard since we have a background
+ // on top of another background
+ int32_t minLuminosityDifference = NS_SUFFICIENT_LUMINOSITY_DIFFERENCE / 5;
+ int32_t backLuminosityDifference =
+ NS_LUMINOSITY_DIFFERENCE(mMozWindowBackground, mFieldBackground);
+ if (backLuminosityDifference >= minLuminosityDifference) {
+ mMozCellHighlightBackground = mMozWindowBackground;
+ mMozCellHighlightText = mMozWindowText;
+ return NS_OK;
+ }
+
+ uint16_t hue, sat, luminance;
+ uint8_t alpha;
+ mMozCellHighlightBackground = mFieldBackground;
+ mMozCellHighlightText = mFieldText;
+
+ NS_RGB2HSV(mMozCellHighlightBackground, hue, sat, luminance, alpha);
+
+ uint16_t step = 30;
+ // Lighten the color if the color is very dark
+ if (luminance <= step) {
+ luminance += step;
+ }
+ // Darken it if it is very light
+ else if (luminance >= 255 - step) {
+ luminance -= step;
+ }
+ // Otherwise, compute what works best depending on the text luminance.
+ else {
+ uint16_t textHue, textSat, textLuminance;
+ uint8_t textAlpha;
+ NS_RGB2HSV(mMozCellHighlightText, textHue, textSat, textLuminance,
+ textAlpha);
+ // Text is darker than background, use a lighter shade
+ if (textLuminance < luminance) {
+ luminance += step;
+ }
+ // Otherwise, use a darker shade
+ else {
+ luminance -= step;
+ }
+ }
+ NS_HSV2RGB(mMozCellHighlightBackground, hue, sat, luminance, alpha);
+ return NS_OK;
+}
+
+void nsLookAndFeel::NativeInit() { EnsureInit(); }
+
+void nsLookAndFeel::RefreshImpl() {
+ nsXPLookAndFeel::RefreshImpl();
+ moz_gtk_refresh();
+
+ mInitialized = false;
+}
+
+widget::LookAndFeelCache nsLookAndFeel::GetCacheImpl() {
+ LookAndFeelCache cache = nsXPLookAndFeel::GetCacheImpl();
+
+ constexpr IntID kIntIdsToCache[] = {IntID::SystemUsesDarkTheme,
+ IntID::PrefersReducedMotion,
+ IntID::UseAccessibilityTheme};
+
+ constexpr ColorID kColorIdsToCache[] = {
+ ColorID::ThemedScrollbar,
+ ColorID::ThemedScrollbarInactive,
+ ColorID::ThemedScrollbarThumb,
+ ColorID::ThemedScrollbarThumbHover,
+ ColorID::ThemedScrollbarThumbActive,
+ ColorID::ThemedScrollbarThumbInactive};
+
+ for (IntID id : kIntIdsToCache) {
+ cache.mInts().AppendElement(LookAndFeelInt(id, GetInt(id)));
+ }
+
+ for (ColorID id : kColorIdsToCache) {
+ cache.mColors().AppendElement(LookAndFeelColor(id, GetColor(id)));
+ }
+
+ return cache;
+}
+
+void nsLookAndFeel::SetCacheImpl(const LookAndFeelCache& aCache) {
+ DoSetCache(aCache);
+}
+
+void nsLookAndFeel::DoSetCache(const LookAndFeelCache& aCache) {
+ for (const auto& entry : aCache.mInts()) {
+ switch (entry.id()) {
+ case IntID::SystemUsesDarkTheme:
+ mSystemUsesDarkTheme = entry.value();
+ break;
+ case IntID::PrefersReducedMotion:
+ mPrefersReducedMotion = entry.value();
+ break;
+ case IntID::UseAccessibilityTheme:
+ mHighContrast = entry.value();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Bogus Int ID in cache");
+ break;
+ }
+ }
+ for (const auto& entry : aCache.mColors()) {
+ switch (entry.id()) {
+ case ColorID::ThemedScrollbar:
+ mThemedScrollbar = entry.color();
+ break;
+ case ColorID::ThemedScrollbarInactive:
+ mThemedScrollbarInactive = entry.color();
+ break;
+ case ColorID::ThemedScrollbarThumb:
+ mThemedScrollbarThumb = entry.color();
+ break;
+ case ColorID::ThemedScrollbarThumbHover:
+ mThemedScrollbarThumbHover = entry.color();
+ break;
+ case ColorID::ThemedScrollbarThumbActive:
+ mThemedScrollbarThumbActive = entry.color();
+ break;
+ case ColorID::ThemedScrollbarThumbInactive:
+ mThemedScrollbarThumbInactive = entry.color();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Bogus Color ID in cache");
+ break;
+ }
+ }
+}
+
+nsresult nsLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor) {
+ EnsureInit();
+
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ // These colors don't seem to be used for anything anymore in Mozilla
+ // (except here at least TextSelectBackground and TextSelectForeground)
+ // The CSS2 colors below are used.
+ case ColorID::WindowBackground:
+ case ColorID::WidgetBackground:
+ case ColorID::TextBackground:
+ case ColorID::Activecaption: // active window caption background
+ case ColorID::Appworkspace: // MDI background color
+ case ColorID::Background: // desktop background
+ case ColorID::Window:
+ case ColorID::Windowframe:
+ case ColorID::MozDialog:
+ case ColorID::MozCombobox:
+ aColor = mMozWindowBackground;
+ break;
+ case ColorID::WindowForeground:
+ case ColorID::WidgetForeground:
+ case ColorID::TextForeground:
+ case ColorID::Captiontext: // text in active window caption, size box, and
+ // scrollbar arrow box (!)
+ case ColorID::Windowtext:
+ case ColorID::MozDialogtext:
+ aColor = mMozWindowText;
+ break;
+ case ColorID::WidgetSelectBackground:
+ case ColorID::TextSelectBackground:
+ case ColorID::IMESelectedRawTextBackground:
+ case ColorID::IMESelectedConvertedTextBackground:
+ case ColorID::MozDragtargetzone:
+ case ColorID::MozHtmlCellhighlight:
+ case ColorID::Highlight: // preference selected item,
+ aColor = mTextSelectedBackground;
+ break;
+ case ColorID::WidgetSelectForeground:
+ case ColorID::TextSelectForeground:
+ case ColorID::IMESelectedRawTextForeground:
+ case ColorID::IMESelectedConvertedTextForeground:
+ case ColorID::Highlighttext:
+ case ColorID::MozHtmlCellhighlighttext:
+ aColor = mTextSelectedText;
+ break;
+ case ColorID::MozCellhighlight:
+ aColor = mMozCellHighlightBackground;
+ break;
+ case ColorID::MozCellhighlighttext:
+ aColor = mMozCellHighlightText;
+ break;
+ case ColorID::Widget3DHighlight:
+ aColor = NS_RGB(0xa0, 0xa0, 0xa0);
+ break;
+ case ColorID::Widget3DShadow:
+ aColor = NS_RGB(0x40, 0x40, 0x40);
+ break;
+ case ColorID::IMERawInputBackground:
+ case ColorID::IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ break;
+ case ColorID::IMERawInputForeground:
+ case ColorID::IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case ColorID::IMERawInputUnderline:
+ case ColorID::IMEConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case ColorID::IMESelectedRawTextUnderline:
+ case ColorID::IMESelectedConvertedTextUnderline:
+ aColor = NS_TRANSPARENT;
+ break;
+ case ColorID::SpellCheckerUnderline:
+ aColor = NS_RGB(0xff, 0, 0);
+ break;
+ case ColorID::ThemedScrollbar:
+ aColor = mThemedScrollbar;
+ break;
+ case ColorID::ThemedScrollbarInactive:
+ aColor = mThemedScrollbarInactive;
+ break;
+ case ColorID::ThemedScrollbarThumb:
+ aColor = mThemedScrollbarThumb;
+ break;
+ case ColorID::ThemedScrollbarThumbHover:
+ aColor = mThemedScrollbarThumbHover;
+ break;
+ case ColorID::ThemedScrollbarThumbActive:
+ aColor = mThemedScrollbarThumbActive;
+ break;
+ case ColorID::ThemedScrollbarThumbInactive:
+ aColor = mThemedScrollbarThumbInactive;
+ break;
+
+ // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ case ColorID::Activeborder:
+ // active window border
+ aColor = mMozWindowActiveBorder;
+ break;
+ case ColorID::Inactiveborder:
+ // inactive window border
+ aColor = mMozWindowInactiveBorder;
+ break;
+ case ColorID::Graytext: // disabled text in windows, menus, etc.
+ case ColorID::Inactivecaptiontext: // text in inactive window caption
+ aColor = mMenuTextInactive;
+ break;
+ case ColorID::Inactivecaption:
+ // inactive window caption
+ aColor = mMozWindowInactiveCaption;
+ break;
+ case ColorID::Infobackground:
+ // tooltip background color
+ aColor = mInfoBackground;
+ break;
+ case ColorID::Infotext:
+ // tooltip text color
+ aColor = mInfoText;
+ break;
+ case ColorID::Menu:
+ // menu background
+ aColor = mMenuBackground;
+ break;
+ case ColorID::Menutext:
+ // menu text
+ aColor = mMenuText;
+ break;
+ case ColorID::Scrollbar:
+ // scrollbar gray area
+ aColor = mMozScrollbar;
+ break;
+
+ case ColorID::Threedface:
+ case ColorID::Buttonface:
+ // 3-D face color
+ aColor = mMozWindowBackground;
+ break;
+
+ case ColorID::Buttontext:
+ // text on push buttons
+ aColor = mButtonText;
+ break;
+
+ case ColorID::Buttonhighlight:
+ // 3-D highlighted edge color
+ case ColorID::Threedhighlight:
+ // 3-D highlighted outer edge color
+ aColor = mFrameOuterLightBorder;
+ break;
+
+ case ColorID::Buttonshadow:
+ // 3-D shadow edge color
+ case ColorID::Threedshadow:
+ // 3-D shadow inner edge color
+ aColor = mFrameInnerDarkBorder;
+ break;
+
+ case ColorID::Threedlightshadow:
+ aColor = NS_RGB(0xE0, 0xE0, 0xE0);
+ break;
+ case ColorID::Threeddarkshadow:
+ aColor = NS_RGB(0xDC, 0xDC, 0xDC);
+ break;
+
+ case ColorID::MozEventreerow:
+ case ColorID::Field:
+ aColor = mFieldBackground;
+ break;
+ case ColorID::Fieldtext:
+ aColor = mFieldText;
+ break;
+ case ColorID::MozButtondefault:
+ // default button border color
+ aColor = mButtonDefault;
+ break;
+ case ColorID::MozButtonhoverface:
+ aColor = mButtonHoverFace;
+ break;
+ case ColorID::MozButtonhovertext:
+ aColor = mButtonHoverText;
+ break;
+ case ColorID::MozGtkButtonactivetext:
+ aColor = mButtonActiveText;
+ break;
+ case ColorID::MozMenuhover:
+ aColor = mMenuHover;
+ break;
+ case ColorID::MozMenuhovertext:
+ aColor = mMenuHoverText;
+ break;
+ case ColorID::MozOddtreerow:
+ aColor = mOddCellBackground;
+ break;
+ case ColorID::MozNativehyperlinktext:
+ aColor = mNativeHyperLinkText;
+ break;
+ case ColorID::MozComboboxtext:
+ aColor = mComboBoxText;
+ break;
+ case ColorID::MozMenubartext:
+ aColor = mMenuBarText;
+ break;
+ case ColorID::MozMenubarhovertext:
+ aColor = mMenuBarHoverText;
+ break;
+ case ColorID::MozGtkInfoBarText:
+ aColor = mInfoBarText;
+ break;
+ case ColorID::MozColheadertext:
+ aColor = mMozColHeaderText;
+ break;
+ case ColorID::MozColheaderhovertext:
+ aColor = mMozColHeaderHoverText;
+ break;
+ default:
+ /* default color is BLACK */
+ aColor = 0;
+ res = NS_ERROR_FAILURE;
+ break;
+ }
+
+ return res;
+}
+
+static int32_t CheckWidgetStyle(GtkWidget* aWidget, const char* aStyle,
+ int32_t aResult) {
+ gboolean value = FALSE;
+ gtk_widget_style_get(aWidget, aStyle, &value, nullptr);
+ return value ? aResult : 0;
+}
+
+static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle(
+ GtkWidget* aWidget) {
+ if (!aWidget) return mozilla::LookAndFeel::eScrollArrowStyle_Single;
+
+ return CheckWidgetStyle(aWidget, "has-backward-stepper",
+ mozilla::LookAndFeel::eScrollArrow_StartBackward) |
+ CheckWidgetStyle(aWidget, "has-forward-stepper",
+ mozilla::LookAndFeel::eScrollArrow_EndForward) |
+ CheckWidgetStyle(aWidget, "has-secondary-backward-stepper",
+ mozilla::LookAndFeel::eScrollArrow_EndBackward) |
+ CheckWidgetStyle(aWidget, "has-secondary-forward-stepper",
+ mozilla::LookAndFeel::eScrollArrow_StartForward);
+}
+
+nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
+ nsresult res = NS_OK;
+
+ // We use delayed initialization by EnsureInit() here
+ // to make sure mozilla::Preferences is available (Bug 115807).
+ // IntID::UseAccessibilityTheme is requested before user preferences
+ // are read, and so EnsureInit(), which depends on preference values,
+ // is deliberately delayed until required.
+ switch (aID) {
+ case IntID::ScrollButtonLeftMouseButtonAction:
+ aResult = 0;
+ break;
+ case IntID::ScrollButtonMiddleMouseButtonAction:
+ aResult = 1;
+ break;
+ case IntID::ScrollButtonRightMouseButtonAction:
+ aResult = 2;
+ break;
+ case IntID::CaretBlinkTime:
+ EnsureInit();
+ aResult = mCaretBlinkTime;
+ break;
+ case IntID::CaretWidth:
+ aResult = 1;
+ break;
+ case IntID::ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case IntID::SelectTextfieldsOnKeyFocus: {
+ GtkSettings* settings;
+ gboolean select_on_focus;
+
+ settings = gtk_settings_get_default();
+ g_object_get(settings, "gtk-entry-select-on-focus", &select_on_focus,
+ nullptr);
+
+ if (select_on_focus)
+ aResult = 1;
+ else
+ aResult = 0;
+
+ } break;
+ case IntID::ScrollToClick: {
+ GtkSettings* settings;
+ gboolean warps_slider = FALSE;
+
+ settings = gtk_settings_get_default();
+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
+ "gtk-primary-button-warps-slider")) {
+ g_object_get(settings, "gtk-primary-button-warps-slider", &warps_slider,
+ nullptr);
+ }
+
+ if (warps_slider)
+ aResult = 1;
+ else
+ aResult = 0;
+ } break;
+ case IntID::SubmenuDelay: {
+ GtkSettings* settings;
+ gint delay;
+
+ settings = gtk_settings_get_default();
+ g_object_get(settings, "gtk-menu-popup-delay", &delay, nullptr);
+ aResult = (int32_t)delay;
+ break;
+ }
+ case IntID::TooltipDelay: {
+ aResult = 500;
+ break;
+ }
+ case IntID::MenusCanOverlapOSBar:
+ // we want XUL popups to be able to overlap the task bar.
+ aResult = 1;
+ break;
+ case IntID::SkipNavigatingDisabledMenuItem:
+ aResult = 1;
+ break;
+ case IntID::DragThresholdX:
+ case IntID::DragThresholdY: {
+ gint threshold = 0;
+ g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold",
+ &threshold, nullptr);
+
+ aResult = threshold;
+ } break;
+ case IntID::ScrollArrowStyle: {
+ GtkWidget* scrollbar = GetWidget(MOZ_GTK_SCROLLBAR_HORIZONTAL);
+ aResult = ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar);
+ break;
+ }
+ case IntID::ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+ case IntID::TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case IntID::TreeCloseDelay:
+ aResult = 1000;
+ break;
+ case IntID::TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case IntID::TreeScrollDelay:
+ aResult = 100;
+ break;
+ case IntID::TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case IntID::DWMCompositor:
+ case IntID::WindowsClassic:
+ case IntID::WindowsDefaultTheme:
+ case IntID::WindowsThemeIdentifier:
+ case IntID::OperatingSystemVersionIdentifier:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case IntID::TouchEnabled:
+ aResult = mozilla::widget::WidgetUtils::IsTouchDeviceSupportPresent();
+ break;
+ case IntID::MacGraphiteTheme:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case IntID::AlertNotificationOrigin:
+ aResult = NS_ALERT_TOP;
+ break;
+ case IntID::IMERawInputUnderlineStyle:
+ case IntID::IMEConvertedTextUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ break;
+ case IntID::IMESelectedRawTextUnderlineStyle:
+ case IntID::IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
+ break;
+ case IntID::SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
+ break;
+ case IntID::MenuBarDrag:
+ EnsureInit();
+ aResult = mMenuSupportsDrag;
+ break;
+ case IntID::ScrollbarButtonAutoRepeatBehavior:
+ aResult = 1;
+ break;
+ case IntID::SwipeAnimationEnabled:
+ aResult = 0;
+ break;
+ case IntID::ContextMenuOffsetVertical:
+ case IntID::ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+ case IntID::GTKCSDAvailable:
+ EnsureInit();
+ aResult = mCSDAvailable;
+ break;
+ case IntID::GTKCSDHideTitlebarByDefault:
+ EnsureInit();
+ aResult = mCSDHideTitlebarByDefault;
+ break;
+ case IntID::GTKCSDMaximizeButton:
+ EnsureInit();
+ aResult = mCSDMaximizeButton;
+ break;
+ case IntID::GTKCSDMinimizeButton:
+ EnsureInit();
+ aResult = mCSDMinimizeButton;
+ break;
+ case IntID::GTKCSDCloseButton:
+ EnsureInit();
+ aResult = mCSDCloseButton;
+ break;
+ case IntID::GTKCSDTransparentBackground: {
+ // Enable transparent titlebar corners for titlebar mode.
+ GdkScreen* screen = gdk_screen_get_default();
+ aResult = gdk_screen_is_composited(screen)
+ ? (nsWindow::GetSystemCSDSupportLevel() !=
+ nsWindow::CSD_SUPPORT_NONE)
+ : false;
+ break;
+ }
+ case IntID::GTKCSDReversedPlacement:
+ EnsureInit();
+ aResult = mCSDReversedPlacement;
+ break;
+ case IntID::PrefersReducedMotion: {
+ aResult = mPrefersReducedMotion;
+ break;
+ }
+ case IntID::SystemUsesDarkTheme: {
+ EnsureInit();
+ aResult = mSystemUsesDarkTheme;
+ break;
+ }
+ case IntID::GTKCSDMaximizeButtonPosition:
+ aResult = mCSDMaximizeButtonPosition;
+ break;
+ case IntID::GTKCSDMinimizeButtonPosition:
+ aResult = mCSDMinimizeButtonPosition;
+ break;
+ case IntID::GTKCSDCloseButtonPosition:
+ aResult = mCSDCloseButtonPosition;
+ break;
+ case IntID::UseAccessibilityTheme: {
+ EnsureInit();
+ aResult = mHighContrast;
+ break;
+ }
+ default:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ }
+
+ return res;
+}
+
+nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
+ nsresult rv = NS_OK;
+ switch (aID) {
+ case FloatID::IMEUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case FloatID::SpellCheckerUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case FloatID::CaretAspectRatio:
+ EnsureInit();
+ aResult = mCaretRatio;
+ break;
+ default:
+ aResult = -1.0;
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+static void GetSystemFontInfo(GtkStyleContext* aStyle, nsString* aFontName,
+ gfxFontStyle* aFontStyle) {
+ aFontStyle->style = FontSlantStyle::Normal();
+
+ // As in
+ // https://git.gnome.org/browse/gtk+/tree/gtk/gtkwidget.c?h=3.22.19#n10333
+ PangoFontDescription* desc;
+ gtk_style_context_get(aStyle, gtk_style_context_get_state(aStyle), "font",
+ &desc, nullptr);
+
+ aFontStyle->systemFont = true;
+
+ constexpr auto quote = u"\""_ns;
+ NS_ConvertUTF8toUTF16 family(pango_font_description_get_family(desc));
+ *aFontName = quote + family + quote;
+
+ aFontStyle->weight = FontWeight(pango_font_description_get_weight(desc));
+
+ // FIXME: Set aFontStyle->stretch correctly!
+ aFontStyle->stretch = FontStretch::Normal();
+
+ float size = float(pango_font_description_get_size(desc)) / PANGO_SCALE;
+
+ // |size| is now either pixels or pango-points (not Mozilla-points!)
+
+ if (!pango_font_description_get_size_is_absolute(desc)) {
+ // |size| is in pango-points, so convert to pixels.
+ size *= float(gfxPlatformGtk::GetFontScaleDPI()) / POINTS_PER_INCH_FLOAT;
+ }
+
+ // |size| is now pixels but not scaled for the hidpi displays,
+ aFontStyle->size = size;
+
+ pango_font_description_free(desc);
+}
+
+bool nsLookAndFeel::NativeGetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) {
+ switch (aID) {
+ case FontID::Menu: // css2
+ case FontID::PullDownMenu: // css3
+ aFontName = mMenuFontName;
+ aFontStyle = mMenuFontStyle;
+ break;
+
+ case FontID::Field: // css3
+ case FontID::List: // css3
+ aFontName = mFieldFontName;
+ aFontStyle = mFieldFontStyle;
+ break;
+
+ case FontID::Button: // css3
+ aFontName = mButtonFontName;
+ aFontStyle = mButtonFontStyle;
+ break;
+
+ case FontID::Caption: // css2
+ case FontID::Icon: // css2
+ case FontID::MessageBox: // css2
+ case FontID::SmallCaption: // css2
+ case FontID::StatusBar: // css2
+ case FontID::Window: // css3
+ case FontID::Document: // css3
+ case FontID::Workspace: // css3
+ case FontID::Desktop: // css3
+ case FontID::Info: // css3
+ case FontID::Dialog: // css3
+ case FontID::Tooltips: // moz
+ case FontID::Widget: // moz
+ default:
+ aFontName = mDefaultFontName;
+ aFontStyle = mDefaultFontStyle;
+ break;
+ }
+
+ // Scale the font for the current monitor
+ double scaleFactor = StaticPrefs::layout_css_devPixelsPerPx();
+ if (scaleFactor > 0) {
+ aFontStyle.size *=
+ widget::ScreenHelperGTK::GetGTKMonitorScaleFactor() / scaleFactor;
+ } else {
+ // Convert gdk pixels to CSS pixels.
+ aFontStyle.size /= gfxPlatformGtk::GetFontScaleFactor();
+ }
+
+ return true;
+}
+
+// Check color contrast according to
+// https://www.w3.org/TR/AERT/#color-contrast
+static bool HasGoodContrastVisibility(GdkRGBA& aColor1, GdkRGBA& aColor2) {
+ int32_t luminosityDifference = NS_LUMINOSITY_DIFFERENCE(
+ GDK_RGBA_TO_NS_RGBA(aColor1), GDK_RGBA_TO_NS_RGBA(aColor2));
+ if (luminosityDifference < NS_SUFFICIENT_LUMINOSITY_DIFFERENCE) {
+ return false;
+ }
+
+ double colorDifference = std::abs(aColor1.red - aColor2.red) +
+ std::abs(aColor1.green - aColor2.green) +
+ std::abs(aColor1.blue - aColor2.blue);
+ return (colorDifference * 255.0 > 500.0);
+}
+
+// Check if the foreground/background colors match with default white/black
+// html page colors.
+static bool IsGtkThemeCompatibleWithHTMLColors() {
+ GdkRGBA white = {1.0, 1.0, 1.0};
+ GdkRGBA black = {0.0, 0.0, 0.0};
+
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW);
+
+ GdkRGBA textColor;
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &textColor);
+
+ // Theme text color and default white html page background
+ if (!HasGoodContrastVisibility(textColor, white)) {
+ return false;
+ }
+
+ GdkRGBA backgroundColor;
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
+ &backgroundColor);
+
+ // Theme background color and default white html page background
+ if (HasGoodContrastVisibility(backgroundColor, white)) {
+ return false;
+ }
+
+ // Theme background color and default black text color
+ return HasGoodContrastVisibility(backgroundColor, black);
+}
+
+static nsCString GetGtkTheme() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ nsCString ret;
+ GtkSettings* settings = gtk_settings_get_default();
+ char* themeName = nullptr;
+ g_object_get(settings, "gtk-theme-name", &themeName, nullptr);
+ if (themeName) {
+ ret.Assign(themeName);
+ g_free(themeName);
+ }
+ return ret;
+}
+
+static bool GetPreferDarkTheme() {
+ GtkSettings* settings = gtk_settings_get_default();
+ gboolean preferDarkTheme = FALSE;
+ g_object_get(settings, "gtk-application-prefer-dark-theme", &preferDarkTheme,
+ nullptr);
+ return preferDarkTheme == TRUE;
+}
+
+void nsLookAndFeel::ConfigureTheme(const LookAndFeelTheme& aTheme) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ GtkSettings* settings = gtk_settings_get_default();
+ g_object_set(settings, "gtk-theme-name", aTheme.themeName().get(),
+ "gtk-application-prefer-dark-theme",
+ aTheme.preferDarkTheme() ? TRUE : FALSE, nullptr);
+}
+
+void nsLookAndFeel::WithThemeConfiguredForContent(
+ const std::function<void(const LookAndFeelTheme& aTheme)>& aFn) {
+ nsWindow::WithSettingsChangesIgnored([&]() {
+ // Available on Gtk 3.20+.
+ static auto sGtkSettingsResetProperty =
+ (void (*)(GtkSettings*, const gchar*))dlsym(
+ RTLD_DEFAULT, "gtk_settings_reset_property");
+
+ nsCString themeName;
+ bool preferDarkTheme = false;
+
+ if (!sGtkSettingsResetProperty) {
+ // When gtk_settings_reset_property is not available, we instead
+ // record the current theme name and variant and explicitly restore
+ // them afterwards. This means we won't respond to any subsequent
+ // theme settings changes, which is unfortunate. (It's possible we
+ // could listen to xsettings changes and update the GtkSettings object
+ // ourselves in response, if we wanted to fix this.)
+ themeName = GetGtkTheme();
+ preferDarkTheme = GetPreferDarkTheme();
+ }
+
+ bool changed = ConfigureContentGtkTheme();
+ if (changed) {
+ RefreshImpl();
+ }
+
+ LookAndFeelTheme theme;
+ theme.themeName() = GetGtkTheme();
+ theme.preferDarkTheme() = GetPreferDarkTheme();
+
+ aFn(theme);
+
+ if (changed) {
+ GtkSettings* settings = gtk_settings_get_default();
+ if (sGtkSettingsResetProperty) {
+ sGtkSettingsResetProperty(settings, "gtk-theme-name");
+ sGtkSettingsResetProperty(settings,
+ "gtk-application-prefer-dark-theme");
+ } else {
+ g_object_set(settings, "gtk-theme-name", themeName.get(),
+ "gtk-application-prefer-dark-theme",
+ preferDarkTheme ? TRUE : FALSE, nullptr);
+ }
+ RefreshImpl();
+ }
+ });
+}
+
+bool nsLookAndFeel::ConfigureContentGtkTheme() {
+ bool changed = false;
+
+ GtkSettings* settings = gtk_settings_get_default();
+
+ nsAutoCString themeOverride;
+ mozilla::Preferences::GetCString("widget.content.gtk-theme-override",
+ themeOverride);
+ if (!themeOverride.IsEmpty()) {
+ g_object_set(settings, "gtk-theme-name", themeOverride.get(), nullptr);
+ changed = true;
+ LOG(("ConfigureContentGtkTheme(%s)\n", themeOverride.get()));
+ } else {
+ LOG(("ConfigureContentGtkTheme(%s)\n", GetGtkTheme().get()));
+ }
+
+ // Dark theme is active but user explicitly enables it, or we're on
+ // high-contrast (in which case we prevent content to mess up with the colors
+ // of the page), so we're done now.
+ if (!themeOverride.IsEmpty() || mHighContrast ||
+ StaticPrefs::widget_content_allow_gtk_dark_theme()) {
+ return changed;
+ }
+
+ // Try to select the light variant of the current theme first...
+ if (GetPreferDarkTheme()) {
+ LOG((" disabling gtk-application-prefer-dark-theme\n"));
+ g_object_set(settings, "gtk-application-prefer-dark-theme", FALSE, nullptr);
+ changed = true;
+ }
+
+ // ...and use a default Gtk theme as a fallback.
+ if (!IsGtkThemeCompatibleWithHTMLColors()) {
+ LOG((" Non-compatible dark theme, default to Adwaita\n"));
+ g_object_set(settings, "gtk-theme-name", "Adwaita", nullptr);
+ changed = true;
+ }
+
+ return changed;
+}
+
+void nsLookAndFeel::EnsureInit() {
+ if (mInitialized) {
+ return;
+ }
+
+ // Gtk manages a screen's CSS in the settings object so we
+ // ask Gtk to create it explicitly. Otherwise we may end up
+ // with wrong color theme, see Bug 972382
+ GtkSettings* settings = gtk_settings_get_default();
+ if (MOZ_UNLIKELY(!settings)) {
+ NS_WARNING("EnsureInit: No settings");
+ return;
+ }
+
+ mInitialized = true;
+
+ // gtk does non threadsafe refcounting
+ MOZ_ASSERT(NS_IsMainThread());
+
+ GtkStyleContext* style;
+ GdkRGBA color;
+
+ if (XRE_IsContentProcess()) {
+ LOG(("nsLookAndFeel::EnsureInit() [%p] Content process\n", (void*)this));
+ // Dark themes interacts poorly with widget styling (see bug 1216658).
+ // We disable dark themes by default for web content
+ // but allow user to overide it by prefs.
+ ConfigureContentGtkTheme();
+ } else {
+ // It seems GTK doesn't have an API to query if the current theme is
+ // "light" or "dark", so we synthesize it from the CSS2 Window/WindowText
+ // colors instead, by comparing their luminosity.
+ GdkRGBA bg, fg;
+ style = GetStyleContext(MOZ_GTK_WINDOW);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &bg);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg);
+ LOG(("nsLookAndFeel::EnsureInit() [%p] Chrome process\n", (void*)this));
+ // Update mSystemUsesDarkTheme only in the parent process since in the child
+ // processes we forcibly set gtk-theme-name so that we can't get correct
+ // results. Instead mSystemUsesDarkTheme in the child processes is updated
+ // via our caching machinery.
+ mSystemUsesDarkTheme =
+ (RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(bg)) <
+ RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(fg)));
+
+ mHighContrast = StaticPrefs::widget_content_gtk_high_contrast_enabled() &&
+ GetGtkTheme().Find("HighContrast"_ns) >= 0;
+
+ gboolean enableAnimations = false;
+ g_object_get(settings, "gtk-enable-animations", &enableAnimations, nullptr);
+ mPrefersReducedMotion = !enableAnimations;
+
+ // Colors that we pass to content processes through the LookAndFeelCache.
+ if (ShouldHonorThemeScrollbarColors()) {
+ // Some themes style the <trough>, while others style the <scrollbar>
+ // itself, so we look at both and compose the colors.
+ style = GetStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
+ &color);
+ mThemedScrollbar = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
+ &color);
+ mThemedScrollbarInactive = GDK_RGBA_TO_NS_RGBA(color);
+
+ style = GetStyleContext(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
+ &color);
+ mThemedScrollbar =
+ NS_ComposeColors(mThemedScrollbar, GDK_RGBA_TO_NS_RGBA(color));
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
+ &color);
+ mThemedScrollbarInactive = NS_ComposeColors(mThemedScrollbarInactive,
+ GDK_RGBA_TO_NS_RGBA(color));
+
+ mMozScrollbar = mThemedScrollbar;
+
+ style = GetStyleContext(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
+ &color);
+ mThemedScrollbarThumb = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
+ &color);
+ mThemedScrollbarThumbHover = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_background_color(
+ style, GtkStateFlags(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE),
+ &color);
+ mThemedScrollbarThumbActive = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
+ &color);
+ mThemedScrollbarThumbInactive = GDK_RGBA_TO_NS_RGBA(color);
+ } else {
+ mMozScrollbar = mThemedScrollbar = widget::sScrollbarColor.ToABGR();
+ mThemedScrollbarInactive = widget::sScrollbarColor.ToABGR();
+ mThemedScrollbarThumb = widget::sScrollbarThumbColor.ToABGR();
+ mThemedScrollbarThumbHover = widget::sScrollbarThumbColorHover.ToABGR();
+ mThemedScrollbarThumbActive = widget::sScrollbarThumbColorActive.ToABGR();
+ mThemedScrollbarThumbInactive = widget::sScrollbarThumbColor.ToABGR();
+ }
+ }
+
+ // The label is not added to a parent widget, but shared for constructing
+ // different style contexts. The node hierarchy is constructed only on
+ // the label style context.
+ GtkWidget* labelWidget = gtk_label_new("M");
+ g_object_ref_sink(labelWidget);
+
+ // Window colors
+ style = GetStyleContext(MOZ_GTK_WINDOW);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mMozWindowBackground = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mMozWindowText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mMozWindowActiveBorder = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_border_color(style, GTK_STATE_FLAG_INSENSITIVE, &color);
+ mMozWindowInactiveBorder = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_INSENSITIVE,
+ &color);
+ mMozWindowInactiveCaption = GDK_RGBA_TO_NS_RGBA(color);
+
+ style = GetStyleContext(MOZ_GTK_WINDOW_CONTAINER);
+ {
+ GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
+ GetSystemFontInfo(labelStyle, &mDefaultFontName, &mDefaultFontStyle);
+ g_object_unref(labelStyle);
+ }
+
+ // tooltip foreground and background
+ style = GetStyleContext(MOZ_GTK_TOOLTIP);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mInfoBackground = GDK_RGBA_TO_NS_RGBA(color);
+
+ style = GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mInfoText = GDK_RGBA_TO_NS_RGBA(color);
+
+ style = GetStyleContext(MOZ_GTK_MENUITEM);
+ {
+ GtkStyleContext* accelStyle =
+ CreateStyleForWidget(gtk_accel_label_new("M"), style);
+
+ GetSystemFontInfo(accelStyle, &mMenuFontName, &mMenuFontStyle);
+
+ gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_NORMAL, &color);
+ mMenuText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_INSENSITIVE, &color);
+ mMenuTextInactive = GDK_RGBA_TO_NS_RGBA(color);
+ g_object_unref(accelStyle);
+ }
+
+ style = GetStyleContext(MOZ_GTK_MENUPOPUP);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mMenuBackground = GDK_RGBA_TO_NS_RGBA(color);
+
+ style = GetStyleContext(MOZ_GTK_MENUITEM);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
+ &color);
+ mMenuHover = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ mMenuHoverText = GDK_RGBA_TO_NS_RGBA(color);
+
+ GtkWidget* parent = gtk_fixed_new();
+ GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
+ GtkWidget* treeView = gtk_tree_view_new();
+ GtkWidget* linkButton = gtk_link_button_new("http://example.com/");
+ GtkWidget* menuBar = gtk_menu_bar_new();
+ GtkWidget* menuBarItem = gtk_menu_item_new();
+ GtkWidget* entry = gtk_entry_new();
+ GtkWidget* textView = gtk_text_view_new();
+
+ gtk_container_add(GTK_CONTAINER(parent), treeView);
+ gtk_container_add(GTK_CONTAINER(parent), linkButton);
+ gtk_container_add(GTK_CONTAINER(parent), menuBar);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menuBar), menuBarItem);
+ gtk_container_add(GTK_CONTAINER(window), parent);
+ gtk_container_add(GTK_CONTAINER(parent), entry);
+ gtk_container_add(GTK_CONTAINER(parent), textView);
+
+ // Text colors
+ GdkRGBA bgColor;
+ // If the text window background is translucent, then the background of
+ // the textview root node is visible.
+ style = GetStyleContext(MOZ_GTK_TEXT_VIEW);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
+ &bgColor);
+
+ style = GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ ApplyColorOver(color, &bgColor);
+ mFieldBackground = GDK_RGBA_TO_NS_RGBA(bgColor);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mFieldText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // Selected text and background
+ {
+ GtkStyleContext* selectionStyle =
+ GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT_SELECTION);
+ auto GrabSelectionColors = [&](GtkStyleContext* style) {
+ gtk_style_context_get_background_color(
+ style,
+ static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
+ GTK_STATE_FLAG_SELECTED),
+ &color);
+ mTextSelectedBackground = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(
+ style,
+ static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
+ GTK_STATE_FLAG_SELECTED),
+ &color);
+ mTextSelectedText = GDK_RGBA_TO_NS_RGBA(color);
+ };
+ GrabSelectionColors(selectionStyle);
+ if (mTextSelectedBackground == mTextSelectedText) {
+ // Some old distros/themes don't properly use the .selection style, so
+ // fall back to the regular text view style.
+ GrabSelectionColors(style);
+ }
+ }
+
+ // Button text color
+ style = GetStyleContext(MOZ_GTK_BUTTON);
+ {
+ GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
+ GetSystemFontInfo(labelStyle, &mButtonFontName, &mButtonFontStyle);
+ g_object_unref(labelStyle);
+ }
+
+ gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mButtonDefault = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mButtonText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ mButtonHoverText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_ACTIVE, &color);
+ mButtonActiveText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
+ &color);
+ mButtonHoverFace = GDK_RGBA_TO_NS_RGBA(color);
+
+ // Combobox text color
+ style = GetStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mComboBoxText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // Menubar text and hover text colors
+ style = GetStyleContext(MOZ_GTK_MENUBARITEM);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mMenuBarText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ mMenuBarHoverText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // GTK's guide to fancy odd row background colors:
+ // 1) Check if a theme explicitly defines an odd row color
+ // 2) If not, check if it defines an even row color, and darken it
+ // slightly by a hardcoded value (gtkstyle.c)
+ // 3) If neither are defined, take the base background color and
+ // darken that by a hardcoded value
+ style = GetStyleContext(MOZ_GTK_TREEVIEW);
+
+ // Get odd row background color
+ gtk_style_context_save(style);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_ROW, GTK_REGION_ODD);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mOddCellBackground = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_restore(style);
+
+ // Column header colors
+ style = GetStyleContext(MOZ_GTK_TREE_HEADER_CELL);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mMozColHeaderText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ mMozColHeaderHoverText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // Compute cell highlight colors
+ InitCellHighlightColors();
+
+ // GtkFrame has a "border" subnode on which Adwaita draws the border.
+ // Some themes do not draw on this node but draw a border on the widget
+ // root node, so check the root node if no border is found on the border
+ // node.
+ style = GetStyleContext(MOZ_GTK_FRAME_BORDER);
+ bool themeUsesColors =
+ GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder);
+ if (!themeUsesColors) {
+ style = GetStyleContext(MOZ_GTK_FRAME);
+ GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder);
+ }
+
+ // GtkInfoBar
+ // TODO - Use WidgetCache for it?
+ GtkWidget* infoBar = gtk_info_bar_new();
+ GtkWidget* infoBarContent =
+ gtk_info_bar_get_content_area(GTK_INFO_BAR(infoBar));
+ GtkWidget* infoBarLabel = gtk_label_new(nullptr);
+ gtk_container_add(GTK_CONTAINER(parent), infoBar);
+ gtk_container_add(GTK_CONTAINER(infoBarContent), infoBarLabel);
+ style = gtk_widget_get_style_context(infoBarLabel);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_INFO);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mInfoBarText = GDK_RGBA_TO_NS_RGBA(color);
+ // Some themes have a unified menu bar, and support window dragging on it
+ gboolean supports_menubar_drag = FALSE;
+ GParamSpec* param_spec = gtk_widget_class_find_style_property(
+ GTK_WIDGET_GET_CLASS(menuBar), "window-dragging");
+ if (param_spec) {
+ if (g_type_is_a(G_PARAM_SPEC_VALUE_TYPE(param_spec), G_TYPE_BOOLEAN)) {
+ gtk_widget_style_get(menuBar, "window-dragging", &supports_menubar_drag,
+ nullptr);
+ }
+ }
+ mMenuSupportsDrag = supports_menubar_drag;
+
+ // TODO: It returns wrong color for themes which
+ // sets link color for GtkLabel only as we query
+ // GtkLinkButton style here.
+ style = gtk_widget_get_style_context(linkButton);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_LINK, &color);
+ mNativeHyperLinkText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // invisible character styles
+ guint value;
+ g_object_get(entry, "invisible-char", &value, nullptr);
+ mInvisibleCharacter = char16_t(value);
+
+ // caret styles
+ gtk_widget_style_get(entry, "cursor-aspect-ratio", &mCaretRatio, nullptr);
+
+ gint blink_time;
+ gboolean blink;
+ g_object_get(settings, "gtk-cursor-blink-time", &blink_time,
+ "gtk-cursor-blink", &blink, nullptr);
+ mCaretBlinkTime = blink ? (int32_t)blink_time : 0;
+
+ GetSystemFontInfo(gtk_widget_get_style_context(entry), &mFieldFontName,
+ &mFieldFontStyle);
+
+ gtk_widget_destroy(window);
+ g_object_unref(labelWidget);
+
+ mCSDAvailable =
+ nsWindow::GetSystemCSDSupportLevel() != nsWindow::CSD_SUPPORT_NONE;
+ mCSDHideTitlebarByDefault = nsWindow::HideTitlebarByDefault();
+
+ mCSDCloseButton = false;
+ mCSDMinimizeButton = false;
+ mCSDMaximizeButton = false;
+ mCSDCloseButtonPosition = 0;
+ mCSDMinimizeButtonPosition = 0;
+ mCSDMaximizeButtonPosition = 0;
+
+ // We need to initialize whole CSD config explicitly because it's queried
+ // as -moz-gtk* media features.
+ ButtonLayout buttonLayout[TOOLBAR_BUTTONS];
+
+ size_t activeButtons =
+ GetGtkHeaderBarButtonLayout(Span(buttonLayout), &mCSDReversedPlacement);
+ for (size_t i = 0; i < activeButtons; i++) {
+ // We check if a button is represented on the right side of the tabbar.
+ // Then we assign it a value from 3 to 5, instead of 0 to 2 when it is on
+ // the left side.
+ const ButtonLayout& layout = buttonLayout[i];
+ int32_t* pos = nullptr;
+ switch (layout.mType) {
+ case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
+ mCSDMinimizeButton = true;
+ pos = &mCSDMinimizeButtonPosition;
+ break;
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
+ mCSDMaximizeButton = true;
+ pos = &mCSDMaximizeButtonPosition;
+ break;
+ case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
+ mCSDCloseButton = true;
+ pos = &mCSDCloseButtonPosition;
+ break;
+ default:
+ break;
+ }
+
+ if (pos) {
+ *pos = i;
+ if (layout.mAtRight) {
+ *pos += TOOLBAR_BUTTONS;
+ }
+ }
+ }
+
+ RecordTelemetry();
+}
+
+// virtual
+char16_t nsLookAndFeel::GetPasswordCharacterImpl() {
+ EnsureInit();
+ return mInvisibleCharacter;
+}
+
+bool nsLookAndFeel::GetEchoPasswordImpl() { return false; }
+
+bool nsLookAndFeel::WidgetUsesImage(WidgetNodeType aNodeType) {
+ static constexpr GtkStateFlags sFlagsToCheck[]{
+ GTK_STATE_FLAG_NORMAL, GTK_STATE_FLAG_PRELIGHT,
+ GtkStateFlags(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE),
+ GTK_STATE_FLAG_BACKDROP, GTK_STATE_FLAG_INSENSITIVE};
+
+ GtkStyleContext* style = GetStyleContext(aNodeType);
+
+ GValue value = G_VALUE_INIT;
+ for (GtkStateFlags state : sFlagsToCheck) {
+ gtk_style_context_get_property(style, "background-image", state, &value);
+ bool hasPattern = G_VALUE_TYPE(&value) == CAIRO_GOBJECT_TYPE_PATTERN &&
+ g_value_get_boxed(&value);
+ g_value_unset(&value);
+ if (hasPattern) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void nsLookAndFeel::RecordLookAndFeelSpecificTelemetry() {
+ // Gtk version we're on.
+ nsString version;
+ version.AppendPrintf("%d.%d", gtk_major_version, gtk_minor_version);
+ Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_GTK_VERSION, version);
+
+ // Whether the current Gtk theme has scrollbar buttons.
+ bool hasScrollbarButtons =
+ GetInt(LookAndFeel::IntID::ScrollArrowStyle) != eScrollArrow_None;
+ mozilla::Telemetry::ScalarSet(
+ mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_HAS_SCROLLBAR_BUTTONS,
+ hasScrollbarButtons);
+
+ // Whether the current Gtk theme uses something other than a solid color
+ // background for scrollbar parts.
+ bool scrollbarUsesImage =
+ WidgetUsesImage(MOZ_GTK_SCROLLBAR_VERTICAL) ||
+ WidgetUsesImage(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL) ||
+ WidgetUsesImage(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL) ||
+ WidgetUsesImage(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL);
+ mozilla::Telemetry::ScalarSet(
+ mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_SCROLLBAR_USES_IMAGES,
+ scrollbarUsesImage);
+}
+
+bool nsLookAndFeel::ShouldHonorThemeScrollbarColors() {
+ // If the Gtk theme uses anything other than solid color backgrounds for Gtk
+ // scrollbar parts, this is a good indication that painting XUL scrollbar part
+ // elements using colors extracted from the theme won't provide good results.
+ return !WidgetUsesImage(MOZ_GTK_SCROLLBAR_VERTICAL) &&
+ !WidgetUsesImage(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL) &&
+ !WidgetUsesImage(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL) &&
+ !WidgetUsesImage(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL);
+}
diff --git a/widget/gtk/nsLookAndFeel.h b/widget/gtk/nsLookAndFeel.h
new file mode 100644
index 0000000000..c8e4ec83b1
--- /dev/null
+++ b/widget/gtk/nsLookAndFeel.h
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __nsLookAndFeel
+#define __nsLookAndFeel
+
+#include "X11UndefineNone.h"
+#include "nsXPLookAndFeel.h"
+#include "nsCOMPtr.h"
+#include "gfxFont.h"
+
+enum WidgetNodeType : int;
+struct _GtkStyle;
+
+class nsLookAndFeel final : public nsXPLookAndFeel {
+ public:
+ explicit nsLookAndFeel(const LookAndFeelCache* aCache);
+ virtual ~nsLookAndFeel();
+
+ void NativeInit() final;
+ void RefreshImpl() override;
+ nsresult NativeGetInt(IntID aID, int32_t& aResult) override;
+ nsresult NativeGetFloat(FloatID aID, float& aResult) override;
+ nsresult NativeGetColor(ColorID aID, nscolor& aResult) override;
+ bool NativeGetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) override;
+
+ char16_t GetPasswordCharacterImpl() override;
+ bool GetEchoPasswordImpl() override;
+
+ LookAndFeelCache GetCacheImpl() override;
+ void SetCacheImpl(const LookAndFeelCache& aCache) override;
+
+ void WithThemeConfiguredForContent(
+ const std::function<void(const LookAndFeelTheme& aTheme)>& aFn) override;
+ static void ConfigureTheme(const LookAndFeelTheme& aTheme);
+
+ bool IsCSDAvailable() const { return mCSDAvailable; }
+
+ static const nscolor kBlack = NS_RGB(0, 0, 0);
+ static const nscolor kWhite = NS_RGB(255, 255, 255);
+
+ protected:
+ void DoSetCache(const LookAndFeelCache& aCache);
+ bool WidgetUsesImage(WidgetNodeType aNodeType);
+ void RecordLookAndFeelSpecificTelemetry() override;
+ bool ShouldHonorThemeScrollbarColors();
+
+ // Cached fonts
+ nsString mDefaultFontName;
+ nsString mButtonFontName;
+ nsString mFieldFontName;
+ nsString mMenuFontName;
+ gfxFontStyle mDefaultFontStyle;
+ gfxFontStyle mButtonFontStyle;
+ gfxFontStyle mFieldFontStyle;
+ gfxFontStyle mMenuFontStyle;
+
+ // Cached colors
+ nscolor mInfoBackground = kWhite;
+ nscolor mInfoText = kBlack;
+ nscolor mMenuBackground = kWhite;
+ nscolor mMenuBarText = kBlack;
+ nscolor mMenuBarHoverText = kBlack;
+ nscolor mMenuText = kBlack;
+ nscolor mMenuTextInactive = kWhite;
+ nscolor mMenuHover = kWhite;
+ nscolor mMenuHoverText = kBlack;
+ nscolor mButtonDefault = kWhite;
+ nscolor mButtonText = kBlack;
+ nscolor mButtonHoverText = kBlack;
+ nscolor mButtonHoverFace = kWhite;
+ nscolor mButtonActiveText = kBlack;
+ nscolor mFrameOuterLightBorder = kBlack;
+ nscolor mFrameInnerDarkBorder = kBlack;
+ nscolor mOddCellBackground = kWhite;
+ nscolor mNativeHyperLinkText = kBlack;
+ nscolor mComboBoxText = kBlack;
+ nscolor mComboBoxBackground = kWhite;
+ nscolor mFieldText = kBlack;
+ nscolor mFieldBackground = kWhite;
+ nscolor mMozWindowText = kBlack;
+ nscolor mMozWindowBackground = kWhite;
+ nscolor mMozWindowActiveBorder = kBlack;
+ nscolor mMozWindowInactiveBorder = kBlack;
+ nscolor mMozWindowInactiveCaption = kWhite;
+ nscolor mMozCellHighlightBackground = kWhite;
+ nscolor mMozCellHighlightText = kBlack;
+ nscolor mTextSelectedText = kBlack;
+ nscolor mTextSelectedBackground = kWhite;
+ nscolor mMozScrollbar = kWhite;
+ nscolor mInfoBarText = kBlack;
+ nscolor mMozColHeaderText = kBlack;
+ nscolor mMozColHeaderHoverText = kBlack;
+ nscolor mThemedScrollbar = kWhite;
+ nscolor mThemedScrollbarInactive = kWhite;
+ nscolor mThemedScrollbarThumb = kBlack;
+ nscolor mThemedScrollbarThumbHover = kBlack;
+ nscolor mThemedScrollbarThumbActive = kBlack;
+ nscolor mThemedScrollbarThumbInactive = kBlack;
+ char16_t mInvisibleCharacter = 0;
+ float mCaretRatio = 0.0f;
+ int32_t mCaretBlinkTime = 0;
+ bool mMenuSupportsDrag = false;
+ bool mCSDAvailable = false;
+ bool mCSDHideTitlebarByDefault = false;
+ bool mCSDMaximizeButton = false;
+ bool mCSDMinimizeButton = false;
+ bool mCSDCloseButton = false;
+ bool mCSDReversedPlacement = false;
+ bool mSystemUsesDarkTheme = false;
+ bool mPrefersReducedMotion = false;
+ bool mHighContrast = false;
+ bool mInitialized = false;
+ int32_t mCSDMaximizeButtonPosition = 0;
+ int32_t mCSDMinimizeButtonPosition = 0;
+ int32_t mCSDCloseButtonPosition = 0;
+
+ void EnsureInit();
+ // Returns whether the current theme or theme variant was changed.
+ bool ConfigureContentGtkTheme();
+
+ private:
+ nsresult InitCellHighlightColors();
+};
+
+#endif
diff --git a/widget/gtk/nsNativeBasicThemeGTK.cpp b/widget/gtk/nsNativeBasicThemeGTK.cpp
new file mode 100644
index 0000000000..cf32161c40
--- /dev/null
+++ b/widget/gtk/nsNativeBasicThemeGTK.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 40; 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 "nsNativeBasicThemeGTK.h"
+
+#include "nsLayoutUtils.h"
+
+using namespace mozilla;
+
+static constexpr CSSCoord kGtkMinimumScrollbarSize = 12;
+static constexpr CSSCoord kGtkMinimumThinScrollbarSize = 6;
+static constexpr CSSCoord kGtkMinimumScrollbarThumbSize = 40;
+
+already_AddRefed<nsITheme> do_GetBasicNativeThemeDoNotUseDirectly() {
+ static mozilla::StaticRefPtr<nsITheme> gInstance;
+ if (MOZ_UNLIKELY(!gInstance)) {
+ gInstance = new nsNativeBasicThemeGTK();
+ ClearOnShutdown(&gInstance);
+ }
+ return do_AddRef(gInstance);
+}
+
+nsITheme::Transparency nsNativeBasicThemeGTK::GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ // Make scrollbar tracks opaque on the window's scroll frame to prevent
+ // leaf layers from overlapping. See bug 1179780.
+ return IsRootScrollbar(aFrame) ? eOpaque : eTransparent;
+ default:
+ return nsNativeBasicTheme::GetWidgetTransparency(aFrame, aAppearance);
+ }
+}
+
+NS_IMETHODIMP
+nsNativeBasicThemeGTK::GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) {
+ DPIRatio dpiRatio = GetDPIRatio(aFrame);
+
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::Scrollcorner: {
+ ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
+ if (style->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin) {
+ aResult->SizeTo(kGtkMinimumThinScrollbarSize * dpiRatio,
+ kGtkMinimumThinScrollbarSize * dpiRatio);
+ } else {
+ aResult->SizeTo(kGtkMinimumScrollbarSize * dpiRatio,
+ kGtkMinimumScrollbarSize * dpiRatio);
+ }
+ break;
+ }
+ default:
+ return nsNativeBasicTheme::GetMinimumWidgetSize(
+ aPresContext, aFrame, aAppearance, aResult, aIsOverridable);
+ }
+
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ aResult->width = kGtkMinimumScrollbarThumbSize * dpiRatio;
+ break;
+ case StyleAppearance::ScrollbarthumbVertical:
+ aResult->height = kGtkMinimumScrollbarThumbSize * dpiRatio;
+ break;
+ default:
+ break;
+ }
+
+ *aIsOverridable = true;
+ return NS_OK;
+}
+
+void nsNativeBasicThemeGTK::PaintScrollbarThumb(
+ DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, bool aHorizontal,
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aElementState, const EventStates& aDocumentState,
+ DPIRatio aDpiRatio) {
+ sRGBColor thumbColor =
+ ComputeScrollbarThumbColor(aFrame, aStyle, aElementState, aDocumentState);
+ LayoutDeviceRect thumbRect(aRect);
+ thumbRect.Deflate(floorf((aHorizontal ? aRect.height : aRect.width) / 4.0f));
+ LayoutDeviceCoord radius =
+ (aHorizontal ? thumbRect.height : thumbRect.width) / 2.0f;
+ PaintRoundedRectWithRadius(aDrawTarget, thumbRect, thumbColor, sRGBColor(), 0,
+ radius / aDpiRatio, aDpiRatio);
+}
+
+void nsNativeBasicThemeGTK::PaintScrollbar(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ bool aHorizontal, nsIFrame* aFrame,
+ const ComputedStyle& aStyle,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio, bool aIsRoot) {
+ auto [trackColor, borderColor] =
+ ComputeScrollbarColors(aFrame, aStyle, aDocumentState, aIsRoot);
+ Unused << borderColor;
+ aDrawTarget->FillRect(aRect.ToUnknownRect(),
+ gfx::ColorPattern(ToDeviceColor(trackColor)));
+}
+
+void nsNativeBasicThemeGTK::PaintScrollCorner(
+ DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, nsIFrame* aFrame,
+ const ComputedStyle& aStyle, const EventStates& aDocumentState,
+ DPIRatio aDpiRatio, bool aIsRoot) {
+ auto [trackColor, borderColor] =
+ ComputeScrollbarColors(aFrame, aStyle, aDocumentState, aIsRoot);
+ Unused << borderColor;
+ aDrawTarget->FillRect(aRect.ToUnknownRect(),
+ gfx::ColorPattern(ToDeviceColor(trackColor)));
+}
diff --git a/widget/gtk/nsNativeBasicThemeGTK.h b/widget/gtk/nsNativeBasicThemeGTK.h
new file mode 100644
index 0000000000..9cad0cbb08
--- /dev/null
+++ b/widget/gtk/nsNativeBasicThemeGTK.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 nsNativeBasicThemeGTK_h
+#define nsNativeBasicThemeGTK_h
+
+#include "nsNativeBasicTheme.h"
+
+class nsNativeBasicThemeGTK : public nsNativeBasicTheme {
+ public:
+ nsNativeBasicThemeGTK() = default;
+
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) override;
+
+ nsITheme::Transparency GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) override;
+ void PaintScrollbarThumb(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect, bool aHorizontal,
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aElementState,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio) override;
+ void PaintScrollbar(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ bool aHorizontal, nsIFrame* aFrame,
+ const ComputedStyle& aStyle,
+ const EventStates& aDocumentState, DPIRatio aDpiRatio,
+ bool aIsRoot) override;
+ void PaintScrollCorner(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aDocumentState, DPIRatio aDpiRatio,
+ bool aIsRoot) override;
+ bool ThemeSupportsScrollbarButtons() override { return false; }
+
+ protected:
+ virtual ~nsNativeBasicThemeGTK() = default;
+};
+
+#endif
diff --git a/widget/gtk/nsNativeThemeGTK.cpp b/widget/gtk/nsNativeThemeGTK.cpp
new file mode 100644
index 0000000000..f392db8013
--- /dev/null
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -0,0 +1,2019 @@
+/* -*- 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 "nsNativeThemeGTK.h"
+#include "HeadlessThemeGTK.h"
+#include "nsStyleConsts.h"
+#include "gtkdrawing.h"
+#include "ScreenHelperGTK.h"
+
+#include "gfx2DGlue.h"
+#include "nsIObserverService.h"
+#include "nsIFrame.h"
+#include "nsIContent.h"
+#include "nsViewManager.h"
+#include "nsNameSpaceManager.h"
+#include "nsGfxCIID.h"
+#include "nsTransform2D.h"
+#include "nsMenuFrame.h"
+#include "tree/nsTreeBodyFrame.h"
+#include "prlink.h"
+#include "nsGkAtoms.h"
+#include "nsAttrValueInlines.h"
+
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/Services.h"
+
+#include <gdk/gdkprivate.h>
+#include <gtk/gtk.h>
+
+#include "gfxContext.h"
+#include "gfxPlatformGtk.h"
+#include "gfxGdkNativeRenderer.h"
+#include "mozilla/gfx/BorrowedContext.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "nsWindow.h"
+#include "nsLayoutUtils.h"
+#include "nsNativeBasicTheme.h"
+
+#ifdef MOZ_X11
+# ifdef CAIRO_HAS_XLIB_SURFACE
+# include "cairo-xlib.h"
+# endif
+# ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
+# include "cairo-xlib-xrender.h"
+# endif
+#endif
+
+#include <algorithm>
+#include <dlfcn.h>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+using mozilla::dom::HTMLInputElement;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeGTK, nsNativeTheme, nsITheme,
+ nsIObserver)
+
+static int gLastGdkError;
+
+// Return scale factor of the monitor where the window is located
+// by the most part or layout.css.devPixelsPerPx pref if set to > 0.
+static inline gint GetMonitorScaleFactor(nsIFrame* aFrame) {
+ // When the layout.css.devPixelsPerPx is set the scale can be < 1,
+ // the real monitor scale cannot go under 1.
+ double scale = StaticPrefs::layout_css_devPixelsPerPx();
+ if (scale <= 0) {
+ nsIWidget* rootWidget = aFrame->PresContext()->GetRootWidget();
+ if (rootWidget) {
+ // We need to use GetDefaultScale() despite it returns monitor scale
+ // factor multiplied by font scale factor because it is the only scale
+ // updated in nsPuppetWidget.
+ // Since we don't want to apply font scale factor for UI elements
+ // (because GTK does not do so) we need to remove that from returned
+ // value. The computed monitor scale factor needs to be rounded before
+ // casting to integer to avoid rounding errors which would lead to
+ // returning 0.
+ int monitorScale = int(round(rootWidget->GetDefaultScale().scale /
+ gfxPlatformGtk::GetFontScaleFactor()));
+ // Monitor scale can be negative if it has not been initialized in the
+ // puppet widget yet. We also make sure that we return positive value.
+ if (monitorScale < 1) {
+ return 1;
+ }
+ return monitorScale;
+ }
+ }
+ // Use monitor scaling factor where devPixelsPerPx is set
+ return ScreenHelperGTK::GetGTKMonitorScaleFactor();
+}
+
+nsNativeThemeGTK::nsNativeThemeGTK() {
+ if (moz_gtk_init() != MOZ_GTK_SUCCESS) {
+ memset(mDisabledWidgetTypes, 0xff, sizeof(mDisabledWidgetTypes));
+ return;
+ }
+
+ // We have to call moz_gtk_shutdown before the event loop stops running.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ obsServ->AddObserver(this, "xpcom-shutdown", false);
+
+ ThemeChanged();
+}
+
+nsNativeThemeGTK::~nsNativeThemeGTK() = default;
+
+NS_IMETHODIMP
+nsNativeThemeGTK::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
+ moz_gtk_shutdown();
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected topic");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+void nsNativeThemeGTK::RefreshWidgetWindow(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+ MOZ_ASSERT(aFrame->PresShell());
+
+ nsViewManager* vm = aFrame->PresShell()->GetViewManager();
+ if (!vm) {
+ return;
+ }
+ vm->InvalidateAllViews();
+}
+
+static bool IsFrameContentNodeInNamespace(nsIFrame* aFrame,
+ uint32_t aNamespace) {
+ nsIContent* content = aFrame ? aFrame->GetContent() : nullptr;
+ if (!content) return false;
+ return content->IsInNamespace(aNamespace);
+}
+
+static bool IsWidgetTypeDisabled(const uint8_t* aDisabledVector,
+ StyleAppearance aAppearance) {
+ auto type = static_cast<size_t>(aAppearance);
+ MOZ_ASSERT(type < static_cast<size_t>(mozilla::StyleAppearance::Count));
+ return (aDisabledVector[type >> 3] & (1 << (type & 7))) != 0;
+}
+
+static void SetWidgetTypeDisabled(uint8_t* aDisabledVector,
+ StyleAppearance aAppearance) {
+ auto type = static_cast<size_t>(aAppearance);
+ MOZ_ASSERT(type < static_cast<size_t>(mozilla::StyleAppearance::Count));
+ aDisabledVector[type >> 3] |= (1 << (type & 7));
+}
+
+static inline uint16_t GetWidgetStateKey(StyleAppearance aAppearance,
+ GtkWidgetState* aWidgetState) {
+ return (aWidgetState->active | aWidgetState->focused << 1 |
+ aWidgetState->inHover << 2 | aWidgetState->disabled << 3 |
+ aWidgetState->isDefault << 4 |
+ static_cast<uint16_t>(aAppearance) << 5);
+}
+
+static bool IsWidgetStateSafe(uint8_t* aSafeVector, StyleAppearance aAppearance,
+ GtkWidgetState* aWidgetState) {
+ MOZ_ASSERT(static_cast<size_t>(aAppearance) <
+ static_cast<size_t>(mozilla::StyleAppearance::Count));
+ uint16_t key = GetWidgetStateKey(aAppearance, aWidgetState);
+ return (aSafeVector[key >> 3] & (1 << (key & 7))) != 0;
+}
+
+static void SetWidgetStateSafe(uint8_t* aSafeVector,
+ StyleAppearance aAppearance,
+ GtkWidgetState* aWidgetState) {
+ MOZ_ASSERT(static_cast<size_t>(aAppearance) <
+ static_cast<size_t>(mozilla::StyleAppearance::Count));
+ uint16_t key = GetWidgetStateKey(aAppearance, aWidgetState);
+ aSafeVector[key >> 3] |= (1 << (key & 7));
+}
+
+/* static */
+GtkTextDirection nsNativeThemeGTK::GetTextDirection(nsIFrame* aFrame) {
+ // IsFrameRTL() treats vertical-rl modes as right-to-left (in addition to
+ // horizontal text with direction=RTL), rather than just considering the
+ // text direction. GtkTextDirection does not have distinct values for
+ // vertical writing modes, but considering the block flow direction is
+ // important for resizers and scrollbar elements, at least.
+ return IsFrameRTL(aFrame) ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR;
+}
+
+// Returns positive for negative margins (otherwise 0).
+gint nsNativeThemeGTK::GetTabMarginPixels(nsIFrame* aFrame) {
+ nscoord margin = IsBottomTab(aFrame) ? aFrame->GetUsedMargin().top
+ : aFrame->GetUsedMargin().bottom;
+
+ return std::min<gint>(
+ MOZ_GTK_TAB_MARGIN_MASK,
+ std::max(0, aFrame->PresContext()->AppUnitsToDevPixels(-margin)));
+}
+
+static bool ShouldScrollbarButtonBeDisabled(int32_t aCurpos, int32_t aMaxpos,
+ StyleAppearance aAppearance) {
+ return (
+ (aCurpos == 0 && (aAppearance == StyleAppearance::ScrollbarbuttonUp ||
+ aAppearance == StyleAppearance::ScrollbarbuttonLeft)) ||
+ (aCurpos == aMaxpos &&
+ (aAppearance == StyleAppearance::ScrollbarbuttonDown ||
+ aAppearance == StyleAppearance::ScrollbarbuttonRight)));
+}
+
+bool nsNativeThemeGTK::GetGtkWidgetAndState(StyleAppearance aAppearance,
+ nsIFrame* aFrame,
+ WidgetNodeType& aGtkWidgetType,
+ GtkWidgetState* aState,
+ gint* aWidgetFlags) {
+ if (aWidgetFlags) {
+ *aWidgetFlags = 0;
+ }
+ if (aState) {
+ memset(aState, 0, sizeof(GtkWidgetState));
+
+ // For XUL checkboxes and radio buttons, the state of the parent
+ // determines our state.
+ nsIFrame* stateFrame = aFrame;
+ if (aFrame && ((aWidgetFlags && (aAppearance == StyleAppearance::Checkbox ||
+ aAppearance == StyleAppearance::Radio)) ||
+ aAppearance == StyleAppearance::CheckboxLabel ||
+ aAppearance == StyleAppearance::RadioLabel)) {
+ nsAtom* atom = nullptr;
+ if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
+ if (aAppearance == StyleAppearance::CheckboxLabel ||
+ aAppearance == StyleAppearance::RadioLabel) {
+ // Adjust stateFrame so GetContentState finds the correct state.
+ stateFrame = aFrame = aFrame->GetParent()->GetParent();
+ } else {
+ // GetContentState knows to look one frame up for radio/checkbox
+ // widgets, so don't adjust stateFrame here.
+ aFrame = aFrame->GetParent();
+ }
+ if (aWidgetFlags) {
+ if (!atom) {
+ atom = (aAppearance == StyleAppearance::Checkbox ||
+ aAppearance == StyleAppearance::CheckboxLabel)
+ ? nsGkAtoms::checked
+ : nsGkAtoms::selected;
+ }
+ *aWidgetFlags = CheckBooleanAttr(aFrame, atom);
+ }
+ } else {
+ if (aWidgetFlags) {
+ *aWidgetFlags = 0;
+ HTMLInputElement* inputElt =
+ HTMLInputElement::FromNode(aFrame->GetContent());
+ if (inputElt && inputElt->Checked())
+ *aWidgetFlags |= MOZ_GTK_WIDGET_CHECKED;
+
+ if (GetIndeterminate(aFrame))
+ *aWidgetFlags |= MOZ_GTK_WIDGET_INCONSISTENT;
+ }
+ }
+ } else if (aAppearance == StyleAppearance::ToolbarbuttonDropdown ||
+ aAppearance == StyleAppearance::Treeheadersortarrow ||
+ aAppearance == StyleAppearance::ButtonArrowPrevious ||
+ aAppearance == StyleAppearance::ButtonArrowNext ||
+ aAppearance == StyleAppearance::ButtonArrowUp ||
+ aAppearance == StyleAppearance::ButtonArrowDown) {
+ // The state of an arrow comes from its parent.
+ stateFrame = aFrame = aFrame->GetParent();
+ }
+
+ EventStates eventState = GetContentState(stateFrame, aAppearance);
+
+ aState->disabled = IsDisabled(aFrame, eventState) || IsReadOnly(aFrame);
+ aState->active = eventState.HasState(NS_EVENT_STATE_ACTIVE);
+ aState->focused = eventState.HasState(NS_EVENT_STATE_FOCUS);
+ aState->inHover = eventState.HasState(NS_EVENT_STATE_HOVER);
+ aState->isDefault = IsDefaultButton(aFrame);
+ aState->canDefault = FALSE; // XXX fix me
+
+ if (aAppearance == StyleAppearance::FocusOutline) {
+ aState->disabled = FALSE;
+ aState->active = FALSE;
+ aState->inHover = FALSE;
+ aState->isDefault = FALSE;
+ aState->canDefault = FALSE;
+
+ aState->focused = TRUE;
+ aState->depressed = TRUE; // see moz_gtk_entry_paint()
+ } else if (aAppearance == StyleAppearance::Button ||
+ aAppearance == StyleAppearance::Toolbarbutton ||
+ aAppearance == StyleAppearance::Dualbutton ||
+ aAppearance == StyleAppearance::ToolbarbuttonDropdown ||
+ aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton ||
+ aAppearance == StyleAppearance::MozMenulistArrowButton) {
+ aState->active &= aState->inHover;
+ } else if (aAppearance == StyleAppearance::Treetwisty ||
+ aAppearance == StyleAppearance::Treetwistyopen) {
+ nsTreeBodyFrame* treeBodyFrame = do_QueryFrame(aFrame);
+ if (treeBodyFrame) {
+ const mozilla::AtomArray& atoms =
+ treeBodyFrame->GetPropertyArrayForCurrentDrawingItem();
+ aState->selected = atoms.Contains((nsStaticAtom*)nsGkAtoms::selected);
+ aState->inHover = atoms.Contains((nsStaticAtom*)nsGkAtoms::hover);
+ }
+ }
+
+ if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
+ // For these widget types, some element (either a child or parent)
+ // actually has element focus, so we check the focused attribute
+ // to see whether to draw in the focused state.
+ if (aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::Textfield ||
+ aAppearance == StyleAppearance::Textarea ||
+ aAppearance == StyleAppearance::SpinnerTextfield ||
+ aAppearance == StyleAppearance::RadioContainer ||
+ aAppearance == StyleAppearance::RadioLabel) {
+ aState->focused = IsFocused(aFrame);
+ } else if (aAppearance == StyleAppearance::Radio ||
+ aAppearance == StyleAppearance::Checkbox) {
+ // In XUL, checkboxes and radios shouldn't have focus rings, their
+ // labels do
+ aState->focused = FALSE;
+ }
+
+ if (aAppearance == StyleAppearance::ScrollbarthumbVertical ||
+ aAppearance == StyleAppearance::ScrollbarthumbHorizontal) {
+ // for scrollbars we need to go up two to go from the thumb to
+ // the slider to the actual scrollbar object
+ nsIFrame* tmpFrame = aFrame->GetParent()->GetParent();
+
+ aState->curpos = CheckIntAttr(tmpFrame, nsGkAtoms::curpos, 0);
+ aState->maxpos = CheckIntAttr(tmpFrame, nsGkAtoms::maxpos, 100);
+
+ if (CheckBooleanAttr(aFrame, nsGkAtoms::active)) {
+ aState->active = TRUE;
+ // Set hover state to emulate Gtk style of active scrollbar thumb
+ aState->inHover = TRUE;
+ }
+ }
+
+ if (aAppearance == StyleAppearance::ScrollbarbuttonUp ||
+ aAppearance == StyleAppearance::ScrollbarbuttonDown ||
+ aAppearance == StyleAppearance::ScrollbarbuttonLeft ||
+ aAppearance == StyleAppearance::ScrollbarbuttonRight) {
+ // set the state to disabled when the scrollbar is scrolled to
+ // the beginning or the end, depending on the button type.
+ int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
+ int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
+ if (ShouldScrollbarButtonBeDisabled(curpos, maxpos, aAppearance)) {
+ aState->disabled = true;
+ }
+
+ // In order to simulate native GTK scrollbar click behavior,
+ // we set the active attribute on the element to true if it's
+ // pressed with any mouse button.
+ // This allows us to show that it's active without setting :active
+ else if (CheckBooleanAttr(aFrame, nsGkAtoms::active))
+ aState->active = true;
+
+ if (aWidgetFlags) {
+ *aWidgetFlags = GetScrollbarButtonType(aFrame);
+ if (static_cast<uint8_t>(aAppearance) -
+ static_cast<uint8_t>(StyleAppearance::ScrollbarbuttonUp) <
+ 2)
+ *aWidgetFlags |= MOZ_GTK_STEPPER_VERTICAL;
+ }
+ }
+
+ // menu item state is determined by the attribute "_moz-menuactive",
+ // and not by the mouse hovering (accessibility). as a special case,
+ // menus which are children of a menu bar are only marked as prelight
+ // if they are open, not on normal hover.
+
+ if (aAppearance == StyleAppearance::Menuitem ||
+ aAppearance == StyleAppearance::Checkmenuitem ||
+ aAppearance == StyleAppearance::Radiomenuitem ||
+ aAppearance == StyleAppearance::Menuseparator ||
+ aAppearance == StyleAppearance::Menuarrow) {
+ bool isTopLevel = false;
+ nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
+ if (menuFrame) {
+ isTopLevel = menuFrame->IsOnMenuBar();
+ }
+
+ if (isTopLevel) {
+ aState->inHover = menuFrame->IsOpen();
+ } else {
+ aState->inHover = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+ }
+
+ aState->active = FALSE;
+
+ if (aAppearance == StyleAppearance::Checkmenuitem ||
+ aAppearance == StyleAppearance::Radiomenuitem) {
+ *aWidgetFlags = 0;
+ if (aFrame && aFrame->GetContent() &&
+ aFrame->GetContent()->IsElement()) {
+ *aWidgetFlags = aFrame->GetContent()->AsElement()->AttrValueIs(
+ kNameSpaceID_None, nsGkAtoms::checked, nsGkAtoms::_true,
+ eIgnoreCase);
+ }
+ }
+ }
+
+ // A button with drop down menu open or an activated toggle button
+ // should always appear depressed.
+ if (aAppearance == StyleAppearance::Button ||
+ aAppearance == StyleAppearance::Toolbarbutton ||
+ aAppearance == StyleAppearance::Dualbutton ||
+ aAppearance == StyleAppearance::ToolbarbuttonDropdown ||
+ aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton ||
+ aAppearance == StyleAppearance::MozMenulistArrowButton) {
+ bool menuOpen = IsOpenButton(aFrame);
+ aState->depressed = IsCheckedButton(aFrame) || menuOpen;
+ // we must not highlight buttons with open drop down menus on hover.
+ aState->inHover = aState->inHover && !menuOpen;
+ }
+
+ // When the input field of the drop down button has focus, some themes
+ // should draw focus for the drop down button as well.
+ if ((aAppearance == StyleAppearance::MenulistButton ||
+ aAppearance == StyleAppearance::MozMenulistArrowButton) &&
+ aWidgetFlags) {
+ *aWidgetFlags = CheckBooleanAttr(aFrame, nsGkAtoms::parentfocused);
+ }
+ }
+
+ if (aAppearance == StyleAppearance::MozWindowTitlebar ||
+ aAppearance == StyleAppearance::MozWindowTitlebarMaximized ||
+ aAppearance == StyleAppearance::MozWindowButtonClose ||
+ aAppearance == StyleAppearance::MozWindowButtonMinimize ||
+ aAppearance == StyleAppearance::MozWindowButtonMaximize ||
+ aAppearance == StyleAppearance::MozWindowButtonRestore) {
+ aState->backdrop = !nsWindow::GetTopLevelWindowActiveState(aFrame);
+ }
+
+ if (aAppearance == StyleAppearance::ScrollbarbuttonUp ||
+ aAppearance == StyleAppearance::ScrollbarbuttonDown ||
+ aAppearance == StyleAppearance::ScrollbarbuttonLeft ||
+ aAppearance == StyleAppearance::ScrollbarbuttonRight ||
+ aAppearance == StyleAppearance::ScrollbarVertical ||
+ aAppearance == StyleAppearance::ScrollbarHorizontal ||
+ aAppearance == StyleAppearance::ScrollbartrackHorizontal ||
+ aAppearance == StyleAppearance::ScrollbartrackVertical ||
+ aAppearance == StyleAppearance::ScrollbarthumbVertical ||
+ aAppearance == StyleAppearance::ScrollbarthumbHorizontal) {
+ EventStates docState =
+ aFrame->GetContent()->OwnerDoc()->GetDocumentState();
+ aState->backdrop = docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
+ }
+ }
+
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ if (aWidgetFlags) *aWidgetFlags = GTK_RELIEF_NORMAL;
+ aGtkWidgetType = MOZ_GTK_BUTTON;
+ break;
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::Dualbutton:
+ if (aWidgetFlags) *aWidgetFlags = GTK_RELIEF_NONE;
+ aGtkWidgetType = MOZ_GTK_TOOLBAR_BUTTON;
+ break;
+ case StyleAppearance::FocusOutline:
+ aGtkWidgetType = MOZ_GTK_ENTRY;
+ break;
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio:
+ aGtkWidgetType = (aAppearance == StyleAppearance::Radio)
+ ? MOZ_GTK_RADIOBUTTON
+ : MOZ_GTK_CHECKBUTTON;
+ break;
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_BUTTON;
+ break;
+ case StyleAppearance::ScrollbarVertical:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_VERTICAL;
+ if (GetWidgetTransparency(aFrame, aAppearance) == eOpaque)
+ *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE;
+ else
+ *aWidgetFlags = 0;
+ break;
+ case StyleAppearance::ScrollbarHorizontal:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_HORIZONTAL;
+ if (GetWidgetTransparency(aFrame, aAppearance) == eOpaque)
+ *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE;
+ else
+ *aWidgetFlags = 0;
+ break;
+ case StyleAppearance::ScrollbartrackHorizontal:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL;
+ break;
+ case StyleAppearance::ScrollbartrackVertical:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL;
+ break;
+ case StyleAppearance::ScrollbarthumbVertical:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_VERTICAL;
+ break;
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
+ break;
+ case StyleAppearance::Spinner:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON;
+ break;
+ case StyleAppearance::SpinnerUpbutton:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_UP;
+ break;
+ case StyleAppearance::SpinnerDownbutton:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_DOWN;
+ break;
+ case StyleAppearance::SpinnerTextfield:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_ENTRY;
+ break;
+ case StyleAppearance::Range: {
+ if (IsRangeHorizontal(aFrame)) {
+ if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL;
+ } else {
+ if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL;
+ }
+ break;
+ }
+ case StyleAppearance::RangeThumb: {
+ if (IsRangeHorizontal(aFrame)) {
+ if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL;
+ } else {
+ if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL;
+ }
+ break;
+ }
+ case StyleAppearance::Separator:
+ aGtkWidgetType = MOZ_GTK_TOOLBAR_SEPARATOR;
+ break;
+ case StyleAppearance::Toolbargripper:
+ aGtkWidgetType = MOZ_GTK_GRIPPER;
+ break;
+ case StyleAppearance::Resizer:
+ aGtkWidgetType = MOZ_GTK_RESIZER;
+ break;
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ aGtkWidgetType = MOZ_GTK_ENTRY;
+ break;
+ case StyleAppearance::Textarea:
+ aGtkWidgetType = MOZ_GTK_TEXT_VIEW;
+ break;
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW;
+ break;
+ case StyleAppearance::Treeheadercell:
+ if (aWidgetFlags) {
+ // In this case, the flag denotes whether the header is the sorted one
+ // or not
+ if (GetTreeSortDirection(aFrame) == eTreeSortDirection_Natural)
+ *aWidgetFlags = false;
+ else
+ *aWidgetFlags = true;
+ }
+ aGtkWidgetType = MOZ_GTK_TREE_HEADER_CELL;
+ break;
+ case StyleAppearance::Treeheadersortarrow:
+ if (aWidgetFlags) {
+ switch (GetTreeSortDirection(aFrame)) {
+ case eTreeSortDirection_Ascending:
+ *aWidgetFlags = GTK_ARROW_DOWN;
+ break;
+ case eTreeSortDirection_Descending:
+ *aWidgetFlags = GTK_ARROW_UP;
+ break;
+ case eTreeSortDirection_Natural:
+ default:
+ /* This prevents the treecolums from getting smaller
+ * and wider when switching sort direction off and on
+ * */
+ *aWidgetFlags = GTK_ARROW_NONE;
+ break;
+ }
+ }
+ aGtkWidgetType = MOZ_GTK_TREE_HEADER_SORTARROW;
+ break;
+ case StyleAppearance::Treetwisty:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
+ if (aWidgetFlags) *aWidgetFlags = GTK_EXPANDER_COLLAPSED;
+ break;
+ case StyleAppearance::Treetwistyopen:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
+ if (aWidgetFlags) *aWidgetFlags = GTK_EXPANDER_EXPANDED;
+ break;
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Menulist:
+ aGtkWidgetType = MOZ_GTK_DROPDOWN;
+ if (aWidgetFlags)
+ *aWidgetFlags =
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML);
+ break;
+ case StyleAppearance::MenulistText:
+ return false; // nothing to do, but prevents the bg from being drawn
+ case StyleAppearance::MozMenulistArrowButton:
+ aGtkWidgetType = MOZ_GTK_DROPDOWN_ARROW;
+ break;
+ case StyleAppearance::ToolbarbuttonDropdown:
+ case StyleAppearance::ButtonArrowDown:
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowNext:
+ case StyleAppearance::ButtonArrowPrevious:
+ aGtkWidgetType = MOZ_GTK_TOOLBARBUTTON_ARROW;
+ if (aWidgetFlags) {
+ *aWidgetFlags = GTK_ARROW_DOWN;
+
+ if (aAppearance == StyleAppearance::ButtonArrowUp)
+ *aWidgetFlags = GTK_ARROW_UP;
+ else if (aAppearance == StyleAppearance::ButtonArrowNext)
+ *aWidgetFlags = GTK_ARROW_RIGHT;
+ else if (aAppearance == StyleAppearance::ButtonArrowPrevious)
+ *aWidgetFlags = GTK_ARROW_LEFT;
+ }
+ break;
+ case StyleAppearance::CheckboxContainer:
+ aGtkWidgetType = MOZ_GTK_CHECKBUTTON_CONTAINER;
+ break;
+ case StyleAppearance::RadioContainer:
+ aGtkWidgetType = MOZ_GTK_RADIOBUTTON_CONTAINER;
+ break;
+ case StyleAppearance::CheckboxLabel:
+ aGtkWidgetType = MOZ_GTK_CHECKBUTTON_LABEL;
+ break;
+ case StyleAppearance::RadioLabel:
+ aGtkWidgetType = MOZ_GTK_RADIOBUTTON_LABEL;
+ break;
+ case StyleAppearance::Toolbar:
+ aGtkWidgetType = MOZ_GTK_TOOLBAR;
+ break;
+ case StyleAppearance::Tooltip:
+ aGtkWidgetType = MOZ_GTK_TOOLTIP;
+ break;
+ case StyleAppearance::Statusbarpanel:
+ case StyleAppearance::Resizerpanel:
+ aGtkWidgetType = MOZ_GTK_FRAME;
+ break;
+ case StyleAppearance::ProgressBar:
+ aGtkWidgetType = MOZ_GTK_PROGRESSBAR;
+ break;
+ case StyleAppearance::Progresschunk: {
+ nsIFrame* stateFrame = aFrame->GetParent();
+ EventStates eventStates = GetContentState(stateFrame, aAppearance);
+
+ aGtkWidgetType = IsIndeterminateProgress(stateFrame, eventStates)
+ ? IsVerticalProgress(stateFrame)
+ ? MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE
+ : MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE
+ : MOZ_GTK_PROGRESS_CHUNK;
+ } break;
+ case StyleAppearance::TabScrollArrowBack:
+ case StyleAppearance::TabScrollArrowForward:
+ if (aWidgetFlags)
+ *aWidgetFlags = aAppearance == StyleAppearance::TabScrollArrowBack
+ ? GTK_ARROW_LEFT
+ : GTK_ARROW_RIGHT;
+ aGtkWidgetType = MOZ_GTK_TAB_SCROLLARROW;
+ break;
+ case StyleAppearance::Tabpanels:
+ aGtkWidgetType = MOZ_GTK_TABPANELS;
+ break;
+ case StyleAppearance::Tab: {
+ if (IsBottomTab(aFrame)) {
+ aGtkWidgetType = MOZ_GTK_TAB_BOTTOM;
+ } else {
+ aGtkWidgetType = MOZ_GTK_TAB_TOP;
+ }
+
+ if (aWidgetFlags) {
+ /* First bits will be used to store max(0,-bmargin) where bmargin
+ * is the bottom margin of the tab in pixels (resp. top margin,
+ * for bottom tabs). */
+ *aWidgetFlags = GetTabMarginPixels(aFrame);
+
+ if (IsSelectedTab(aFrame)) *aWidgetFlags |= MOZ_GTK_TAB_SELECTED;
+
+ if (IsFirstTab(aFrame)) *aWidgetFlags |= MOZ_GTK_TAB_FIRST;
+ }
+ } break;
+ case StyleAppearance::Splitter:
+ if (IsHorizontal(aFrame))
+ aGtkWidgetType = MOZ_GTK_SPLITTER_VERTICAL;
+ else
+ aGtkWidgetType = MOZ_GTK_SPLITTER_HORIZONTAL;
+ break;
+ case StyleAppearance::Menubar:
+ aGtkWidgetType = MOZ_GTK_MENUBAR;
+ break;
+ case StyleAppearance::Menupopup:
+ aGtkWidgetType = MOZ_GTK_MENUPOPUP;
+ break;
+ case StyleAppearance::Menuitem: {
+ nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
+ if (menuFrame && menuFrame->IsOnMenuBar()) {
+ aGtkWidgetType = MOZ_GTK_MENUBARITEM;
+ break;
+ }
+ }
+ aGtkWidgetType = MOZ_GTK_MENUITEM;
+ break;
+ case StyleAppearance::Menuseparator:
+ aGtkWidgetType = MOZ_GTK_MENUSEPARATOR;
+ break;
+ case StyleAppearance::Menuarrow:
+ aGtkWidgetType = MOZ_GTK_MENUARROW;
+ break;
+ case StyleAppearance::Checkmenuitem:
+ aGtkWidgetType = MOZ_GTK_CHECKMENUITEM;
+ break;
+ case StyleAppearance::Radiomenuitem:
+ aGtkWidgetType = MOZ_GTK_RADIOMENUITEM;
+ break;
+ case StyleAppearance::Window:
+ case StyleAppearance::Dialog:
+ aGtkWidgetType = MOZ_GTK_WINDOW;
+ break;
+ case StyleAppearance::MozGtkInfoBar:
+ aGtkWidgetType = MOZ_GTK_INFO_BAR;
+ break;
+ case StyleAppearance::MozWindowTitlebar:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR;
+ break;
+ case StyleAppearance::MozWindowTitlebarMaximized:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR_MAXIMIZED;
+ break;
+ case StyleAppearance::MozWindowButtonBox:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_BOX;
+ break;
+ case StyleAppearance::MozWindowButtonClose:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_CLOSE;
+ break;
+ case StyleAppearance::MozWindowButtonMinimize:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE;
+ break;
+ case StyleAppearance::MozWindowButtonMaximize:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE;
+ break;
+ case StyleAppearance::MozWindowButtonRestore:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+class SystemCairoClipper : public ClipExporter {
+ public:
+ explicit SystemCairoClipper(cairo_t* aContext, gint aScaleFactor = 1)
+ : mContext(aContext), mScaleFactor(aScaleFactor) {}
+
+ void BeginClip(const Matrix& aTransform) override {
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(aTransform, mat);
+ // We also need to remove the scale factor effect from the matrix
+ mat.y0 = mat.y0 / mScaleFactor;
+ mat.x0 = mat.x0 / mScaleFactor;
+ cairo_set_matrix(mContext, &mat);
+
+ cairo_new_path(mContext);
+ }
+
+ void MoveTo(const Point& aPoint) override {
+ cairo_move_to(mContext, aPoint.x / mScaleFactor, aPoint.y / mScaleFactor);
+ mBeginPoint = aPoint;
+ mCurrentPoint = aPoint;
+ }
+
+ void LineTo(const Point& aPoint) override {
+ cairo_line_to(mContext, aPoint.x / mScaleFactor, aPoint.y / mScaleFactor);
+ mCurrentPoint = aPoint;
+ }
+
+ void BezierTo(const Point& aCP1, const Point& aCP2,
+ const Point& aCP3) override {
+ cairo_curve_to(mContext, aCP1.x / mScaleFactor, aCP1.y / mScaleFactor,
+ aCP2.x / mScaleFactor, aCP2.y / mScaleFactor,
+ aCP3.x / mScaleFactor, aCP3.y / mScaleFactor);
+ mCurrentPoint = aCP3;
+ }
+
+ void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) override {
+ Point CP0 = CurrentPoint();
+ Point CP1 = (CP0 + aCP1 * 2.0) / 3.0;
+ Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0;
+ Point CP3 = aCP2;
+ cairo_curve_to(mContext, CP1.x / mScaleFactor, CP1.y / mScaleFactor,
+ CP2.x / mScaleFactor, CP2.y / mScaleFactor,
+ CP3.x / mScaleFactor, CP3.y / mScaleFactor);
+ mCurrentPoint = aCP2;
+ }
+
+ void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
+ float aEndAngle, bool aAntiClockwise) override {
+ ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle,
+ aAntiClockwise);
+ }
+
+ void Close() override {
+ cairo_close_path(mContext);
+ mCurrentPoint = mBeginPoint;
+ }
+
+ void EndClip() override { cairo_clip(mContext); }
+
+ private:
+ cairo_t* mContext;
+ gint mScaleFactor;
+};
+
+static void DrawThemeWithCairo(gfxContext* aContext, DrawTarget* aDrawTarget,
+ GtkWidgetState aState,
+ WidgetNodeType aGTKWidgetType, gint aFlags,
+ GtkTextDirection aDirection, gint aScaleFactor,
+ bool aSnapped, const Point& aDrawOrigin,
+ const nsIntSize& aDrawSize,
+ GdkRectangle& aGDKRect,
+ nsITheme::Transparency aTransparency) {
+ bool isX11Display = gfxPlatformGtk::GetPlatform()->IsX11Display();
+ static auto sCairoSurfaceSetDeviceScalePtr =
+ (void (*)(cairo_surface_t*, double, double))dlsym(
+ RTLD_DEFAULT, "cairo_surface_set_device_scale");
+ bool useHiDPIWidgets =
+ (aScaleFactor != 1) && (sCairoSurfaceSetDeviceScalePtr != nullptr);
+
+ Point drawOffsetScaled;
+ Point drawOffsetOriginal;
+ Matrix transform;
+ if (!aSnapped) {
+ // If we are not snapped, we depend on the DT for translation.
+ drawOffsetOriginal = aDrawOrigin;
+ drawOffsetScaled = useHiDPIWidgets ? drawOffsetOriginal / aScaleFactor
+ : drawOffsetOriginal;
+ transform = aDrawTarget->GetTransform().PreTranslate(drawOffsetScaled);
+ } else {
+ // Otherwise, we only need to take the device offset into account.
+ drawOffsetOriginal = aDrawOrigin - aContext->GetDeviceOffset();
+ drawOffsetScaled = useHiDPIWidgets ? drawOffsetOriginal / aScaleFactor
+ : drawOffsetOriginal;
+ transform = Matrix::Translation(drawOffsetScaled);
+ }
+
+ if (!useHiDPIWidgets && aScaleFactor != 1) {
+ transform.PreScale(aScaleFactor, aScaleFactor);
+ }
+
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(transform, mat);
+
+ nsIntSize clipSize((aDrawSize.width + aScaleFactor - 1) / aScaleFactor,
+ (aDrawSize.height + aScaleFactor - 1) / aScaleFactor);
+
+ // A direct Cairo draw target is not available, so we need to create a
+ // temporary one.
+#if defined(MOZ_X11) && defined(CAIRO_HAS_XLIB_SURFACE)
+ if (isX11Display) {
+ // If using a Cairo xlib surface, then try to reuse it.
+ BorrowedXlibDrawable borrow(aDrawTarget);
+ if (borrow.GetDrawable()) {
+ nsIntSize size = borrow.GetSize();
+ cairo_surface_t* surf = nullptr;
+ // Check if the surface is using XRender.
+# ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
+ if (borrow.GetXRenderFormat()) {
+ surf = cairo_xlib_surface_create_with_xrender_format(
+ borrow.GetDisplay(), borrow.GetDrawable(), borrow.GetScreen(),
+ borrow.GetXRenderFormat(), size.width, size.height);
+ } else {
+# else
+ if (!borrow.GetXRenderFormat()) {
+# endif
+ surf = cairo_xlib_surface_create(
+ borrow.GetDisplay(), borrow.GetDrawable(), borrow.GetVisual(),
+ size.width, size.height);
+ }
+ if (!NS_WARN_IF(!surf)) {
+ Point offset = borrow.GetOffset();
+ if (offset != Point()) {
+ cairo_surface_set_device_offset(surf, offset.x, offset.y);
+ }
+ cairo_t* cr = cairo_create(surf);
+ if (!NS_WARN_IF(!cr)) {
+ RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr);
+ aContext->ExportClip(*clipper);
+
+ cairo_set_matrix(cr, &mat);
+
+ cairo_new_path(cr);
+ cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height);
+ cairo_clip(cr);
+
+ moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
+ aDirection);
+
+ cairo_destroy(cr);
+ }
+ cairo_surface_destroy(surf);
+ }
+ borrow.Finish();
+ return;
+ }
+ }
+#endif
+
+ // Check if the widget requires complex masking that must be composited.
+ // Try to directly write to the draw target's pixels if possible.
+ uint8_t* data;
+ nsIntSize size;
+ int32_t stride;
+ SurfaceFormat format;
+ IntPoint origin;
+ if (aDrawTarget->LockBits(&data, &size, &stride, &format, &origin)) {
+ // Create a Cairo image surface context the device rectangle.
+ cairo_surface_t* surf = cairo_image_surface_create_for_data(
+ data, GfxFormatToCairoFormat(format), size.width, size.height, stride);
+ if (!NS_WARN_IF(!surf)) {
+ if (useHiDPIWidgets) {
+ sCairoSurfaceSetDeviceScalePtr(surf, aScaleFactor, aScaleFactor);
+ }
+ if (origin != IntPoint()) {
+ cairo_surface_set_device_offset(surf, -origin.x, -origin.y);
+ }
+ cairo_t* cr = cairo_create(surf);
+ if (!NS_WARN_IF(!cr)) {
+ RefPtr<SystemCairoClipper> clipper =
+ new SystemCairoClipper(cr, useHiDPIWidgets ? aScaleFactor : 1);
+ aContext->ExportClip(*clipper);
+
+ cairo_set_matrix(cr, &mat);
+
+ cairo_new_path(cr);
+ cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height);
+ cairo_clip(cr);
+
+ moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
+ aDirection);
+
+ cairo_destroy(cr);
+ }
+ cairo_surface_destroy(surf);
+ }
+ aDrawTarget->ReleaseBits(data);
+ } else {
+ // If the widget has any transparency, make sure to choose an alpha format.
+ format = aTransparency != nsITheme::eOpaque ? SurfaceFormat::B8G8R8A8
+ : aDrawTarget->GetFormat();
+ // Create a temporary data surface to render the widget into.
+ RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface(
+ aDrawSize, format, aTransparency != nsITheme::eOpaque);
+ DataSourceSurface::MappedSurface map;
+ if (!NS_WARN_IF(
+ !(dataSurface &&
+ dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)))) {
+ // Create a Cairo image surface wrapping the data surface.
+ cairo_surface_t* surf = cairo_image_surface_create_for_data(
+ map.mData, GfxFormatToCairoFormat(format), aDrawSize.width,
+ aDrawSize.height, map.mStride);
+ cairo_t* cr = nullptr;
+ if (!NS_WARN_IF(!surf)) {
+ cr = cairo_create(surf);
+ if (!NS_WARN_IF(!cr)) {
+ if (aScaleFactor != 1) {
+ if (useHiDPIWidgets) {
+ sCairoSurfaceSetDeviceScalePtr(surf, aScaleFactor, aScaleFactor);
+ } else {
+ cairo_scale(cr, aScaleFactor, aScaleFactor);
+ }
+ }
+
+ moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
+ aDirection);
+ }
+ }
+
+ // Unmap the surface before using it as a source
+ dataSurface->Unmap();
+
+ if (cr) {
+ // The widget either needs to be masked or has transparency, so use the
+ // slower drawing path.
+ aDrawTarget->DrawSurface(
+ dataSurface,
+ Rect(aSnapped ? drawOffsetOriginal -
+ aDrawTarget->GetTransform().GetTranslation()
+ : drawOffsetOriginal,
+ Size(aDrawSize)),
+ Rect(0, 0, aDrawSize.width, aDrawSize.height));
+ cairo_destroy(cr);
+ }
+
+ if (surf) {
+ cairo_surface_destroy(surf);
+ }
+ }
+ }
+}
+
+bool nsNativeThemeGTK::GetExtraSizeForWidget(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsIntMargin* aExtra) {
+ *aExtra = nsIntMargin(0, 0, 0, 0);
+ // Allow an extra one pixel above and below the thumb for certain
+ // GTK2 themes (Ximian Industrial, Bluecurve, Misty, at least);
+ // We modify the frame's overflow area. See bug 297508.
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarthumbVertical:
+ aExtra->top = aExtra->bottom = 1;
+ break;
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ aExtra->left = aExtra->right = 1;
+ break;
+
+ case StyleAppearance::Button: {
+ if (IsDefaultButton(aFrame)) {
+ // Some themes draw a default indicator outside the widget,
+ // include that in overflow
+ gint top, left, bottom, right;
+ moz_gtk_button_get_default_overflow(&top, &left, &bottom, &right);
+ aExtra->top = top;
+ aExtra->right = right;
+ aExtra->bottom = bottom;
+ aExtra->left = left;
+ break;
+ }
+ return false;
+ }
+ case StyleAppearance::FocusOutline: {
+ moz_gtk_get_focus_outline_size(&aExtra->left, &aExtra->top);
+ aExtra->right = aExtra->left;
+ aExtra->bottom = aExtra->top;
+ break;
+ }
+ case StyleAppearance::Tab: {
+ if (!IsSelectedTab(aFrame)) return false;
+
+ gint gap_height = moz_gtk_get_tab_thickness(
+ IsBottomTab(aFrame) ? MOZ_GTK_TAB_BOTTOM : MOZ_GTK_TAB_TOP);
+ if (!gap_height) return false;
+
+ int32_t extra = gap_height - GetTabMarginPixels(aFrame);
+ if (extra <= 0) return false;
+
+ if (IsBottomTab(aFrame)) {
+ aExtra->top = extra;
+ } else {
+ aExtra->bottom = extra;
+ }
+ return false;
+ }
+ default:
+ return false;
+ }
+ gint scale = GetMonitorScaleFactor(aFrame);
+ aExtra->top *= scale;
+ aExtra->right *= scale;
+ aExtra->bottom *= scale;
+ aExtra->left *= scale;
+ return true;
+}
+
+bool nsNativeThemeGTK::IsWidgetVisible(StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::MozWindowButtonBox:
+ return false;
+ default:
+ break;
+ }
+ return true;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) {
+ GtkWidgetState state;
+ WidgetNodeType gtkWidgetType;
+ GtkTextDirection direction = GetTextDirection(aFrame);
+ gint flags;
+
+ if (!IsWidgetVisible(aAppearance) ||
+ !GetGtkWidgetAndState(aAppearance, aFrame, gtkWidgetType, &state,
+ &flags)) {
+ return NS_OK;
+ }
+
+ gfxContext* ctx = aContext;
+ nsPresContext* presContext = aFrame->PresContext();
+
+ gfxRect rect = presContext->AppUnitsToGfxUnits(aRect);
+ gfxRect dirtyRect = presContext->AppUnitsToGfxUnits(aDirtyRect);
+ gint scaleFactor = GetMonitorScaleFactor(aFrame);
+
+ // Align to device pixels where sensible
+ // to provide crisper and faster drawing.
+ // Don't snap if it's a non-unit scale factor. We're going to have to take
+ // slow paths then in any case.
+ bool snapped = ctx->UserToDevicePixelSnapped(rect);
+ if (snapped) {
+ // Leave rect in device coords but make dirtyRect consistent.
+ dirtyRect = ctx->UserToDevice(dirtyRect);
+ }
+
+ // Translate the dirty rect so that it is wrt the widget top-left.
+ dirtyRect.MoveBy(-rect.TopLeft());
+ // Round out the dirty rect to gdk pixels to ensure that gtk draws
+ // enough pixels for interpolation to device pixels.
+ dirtyRect.RoundOut();
+
+ // GTK themes can only draw an integer number of pixels
+ // (even when not snapped).
+ nsIntRect widgetRect(0, 0, NS_lround(rect.Width()), NS_lround(rect.Height()));
+ nsIntRect overflowRect(widgetRect);
+ nsIntMargin extraSize;
+ if (GetExtraSizeForWidget(aFrame, aAppearance, &extraSize)) {
+ overflowRect.Inflate(extraSize);
+ }
+
+ // This is the rectangle that will actually be drawn, in gdk pixels
+ nsIntRect drawingRect(int32_t(dirtyRect.X()), int32_t(dirtyRect.Y()),
+ int32_t(dirtyRect.Width()),
+ int32_t(dirtyRect.Height()));
+ if (widgetRect.IsEmpty() ||
+ !drawingRect.IntersectRect(overflowRect, drawingRect))
+ return NS_OK;
+
+ NS_ASSERTION(!IsWidgetTypeDisabled(mDisabledWidgetTypes, aAppearance),
+ "Trying to render an unsafe widget!");
+
+ bool safeState = IsWidgetStateSafe(mSafeWidgetStates, aAppearance, &state);
+ if (!safeState) {
+ gLastGdkError = 0;
+ gdk_error_trap_push();
+ }
+
+ Transparency transparency = GetWidgetTransparency(aFrame, aAppearance);
+
+ // gdk rectangles are wrt the drawing rect.
+ GdkRectangle gdk_rect = {
+ -drawingRect.x / scaleFactor, -drawingRect.y / scaleFactor,
+ widgetRect.width / scaleFactor, widgetRect.height / scaleFactor};
+
+ // Save actual widget scale to GtkWidgetState as we don't provide
+ // nsFrame to gtk3drawing routines.
+ state.scale = scaleFactor;
+
+ // translate everything so (0,0) is the top left of the drawingRect
+ gfxPoint origin = rect.TopLeft() + drawingRect.TopLeft();
+
+ DrawThemeWithCairo(ctx, aContext->GetDrawTarget(), state, gtkWidgetType,
+ flags, direction, scaleFactor, snapped, ToPoint(origin),
+ drawingRect.Size(), gdk_rect, transparency);
+
+ if (!safeState) {
+ // gdk_flush() call from expose event crashes Gtk+ on Wayland
+ // (Gnome BZ #773307)
+ if (gfxPlatformGtk::GetPlatform()->IsX11Display()) {
+ gdk_flush();
+ }
+ gLastGdkError = gdk_error_trap_pop();
+
+ if (gLastGdkError) {
+#ifdef DEBUG
+ printf(
+ "GTK theme failed for widget type %d, error was %d, state was "
+ "[active=%d,focused=%d,inHover=%d,disabled=%d]\n",
+ static_cast<int>(aAppearance), gLastGdkError, state.active,
+ state.focused, state.inHover, state.disabled);
+#endif
+ NS_WARNING("GTK theme failed; disabling unsafe widget");
+ SetWidgetTypeDisabled(mDisabledWidgetTypes, aAppearance);
+ // force refresh of the window, because the widget was not
+ // successfully drawn it must be redrawn using the default look
+ RefreshWidgetWindow(aFrame);
+ } else {
+ SetWidgetStateSafe(mSafeWidgetStates, aAppearance, &state);
+ }
+ }
+
+ // Indeterminate progress bar are animated.
+ if (gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE ||
+ gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) {
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
+ NS_WARNING("unable to animate widget!");
+ }
+ }
+
+ return NS_OK;
+}
+
+bool nsNativeThemeGTK::CreateWebRenderCommandsForWidget(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
+ StyleAppearance aAppearance, const nsRect& aRect) {
+ nsPresContext* presContext = aFrame->PresContext();
+ wr::LayoutRect bounds = wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(
+ aRect, presContext->AppUnitsPerDevPixel()));
+
+ switch (aAppearance) {
+ case StyleAppearance::Window:
+ case StyleAppearance::Dialog:
+ aBuilder.PushRect(
+ bounds, bounds, true,
+ wr::ToColorF(ToDeviceColor(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::WindowBackground, NS_RGBA(0, 0, 0, 0)))));
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+WidgetNodeType nsNativeThemeGTK::NativeThemeToGtkTheme(
+ StyleAppearance aAppearance, nsIFrame* aFrame) {
+ WidgetNodeType gtkWidgetType;
+ gint unusedFlags;
+
+ if (!GetGtkWidgetAndState(aAppearance, aFrame, gtkWidgetType, nullptr,
+ &unusedFlags)) {
+ MOZ_ASSERT_UNREACHABLE("Unknown native widget to gtk widget mapping");
+ return MOZ_GTK_WINDOW;
+ }
+ return gtkWidgetType;
+}
+
+static void FixupForVerticalWritingMode(WritingMode aWritingMode,
+ LayoutDeviceIntMargin* aResult) {
+ if (aWritingMode.IsVertical()) {
+ bool rtl = aWritingMode.IsBidiRTL();
+ LogicalMargin logical(aWritingMode, aResult->top,
+ rtl ? aResult->left : aResult->right, aResult->bottom,
+ rtl ? aResult->right : aResult->left);
+ nsMargin physical = logical.GetPhysicalMargin(aWritingMode);
+ aResult->top = physical.top;
+ aResult->right = physical.right;
+ aResult->bottom = physical.bottom;
+ aResult->left = physical.left;
+ }
+}
+
+void nsNativeThemeGTK::GetCachedWidgetBorder(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ GtkTextDirection aDirection,
+ LayoutDeviceIntMargin* aResult) {
+ aResult->SizeTo(0, 0, 0, 0);
+
+ WidgetNodeType gtkWidgetType;
+ gint unusedFlags;
+ if (GetGtkWidgetAndState(aAppearance, aFrame, gtkWidgetType, nullptr,
+ &unusedFlags)) {
+ MOZ_ASSERT(0 <= gtkWidgetType && gtkWidgetType < MOZ_GTK_WIDGET_NODE_COUNT);
+ uint8_t cacheIndex = gtkWidgetType / 8;
+ uint8_t cacheBit = 1u << (gtkWidgetType % 8);
+
+ if (mBorderCacheValid[cacheIndex] & cacheBit) {
+ *aResult = mBorderCache[gtkWidgetType];
+ } else {
+ moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top,
+ &aResult->right, &aResult->bottom, aDirection);
+ if (gtkWidgetType != MOZ_GTK_DROPDOWN) { // depends on aDirection
+ mBorderCacheValid[cacheIndex] |= cacheBit;
+ mBorderCache[gtkWidgetType] = *aResult;
+ }
+ }
+ }
+ FixupForVerticalWritingMode(aFrame->GetWritingMode(), aResult);
+}
+
+LayoutDeviceIntMargin nsNativeThemeGTK::GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
+ LayoutDeviceIntMargin result;
+ GtkTextDirection direction = GetTextDirection(aFrame);
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical: {
+ GtkOrientation orientation =
+ aAppearance == StyleAppearance::ScrollbarHorizontal
+ ? GTK_ORIENTATION_HORIZONTAL
+ : GTK_ORIENTATION_VERTICAL;
+ const ScrollbarGTKMetrics* metrics =
+ GetActiveScrollbarMetrics(orientation);
+
+ const GtkBorder& border = metrics->border.scrollbar;
+ result.top = border.top;
+ result.right = border.right;
+ result.bottom = border.bottom;
+ result.left = border.left;
+ } break;
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical: {
+ GtkOrientation orientation =
+ aAppearance == StyleAppearance::ScrollbartrackHorizontal
+ ? GTK_ORIENTATION_HORIZONTAL
+ : GTK_ORIENTATION_VERTICAL;
+ const ScrollbarGTKMetrics* metrics =
+ GetActiveScrollbarMetrics(orientation);
+
+ const GtkBorder& border = metrics->border.track;
+ result.top = border.top;
+ result.right = border.right;
+ result.bottom = border.bottom;
+ result.left = border.left;
+ } break;
+ case StyleAppearance::Toolbox:
+ // gtk has no toolbox equivalent. So, although we map toolbox to
+ // gtk's 'toolbar' for purposes of painting the widget background,
+ // we don't use the toolbar border for toolbox.
+ break;
+ case StyleAppearance::Dualbutton:
+ // TOOLBAR_DUAL_BUTTON is an interesting case. We want a border to draw
+ // around the entire button + dropdown, and also an inner border if you're
+ // over the button part. But, we want the inner button to be right up
+ // against the edge of the outer button so that the borders overlap.
+ // To make this happen, we draw a button border for the outer button,
+ // but don't reserve any space for it.
+ break;
+ case StyleAppearance::Tab: {
+ WidgetNodeType gtkWidgetType;
+ gint flags;
+
+ if (!GetGtkWidgetAndState(aAppearance, aFrame, gtkWidgetType, nullptr,
+ &flags)) {
+ return result;
+ }
+ moz_gtk_get_tab_border(&result.left, &result.top, &result.right,
+ &result.bottom, direction, (GtkTabFlags)flags,
+ gtkWidgetType);
+ } break;
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem:
+ // For regular menuitems, we will be using GetWidgetPadding instead of
+ // GetWidgetBorder to pad up the widget's internals; other menuitems
+ // will need to fall through and use the default case as before.
+ if (IsRegularMenuItem(aFrame)) break;
+ [[fallthrough]];
+ default: {
+ GetCachedWidgetBorder(aFrame, aAppearance, direction, &result);
+ }
+ }
+
+ gint scale = GetMonitorScaleFactor(aFrame);
+ result.top *= scale;
+ result.right *= scale;
+ result.bottom *= scale;
+ result.left *= scale;
+ return result;
+}
+
+bool nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) {
+ switch (aAppearance) {
+ case StyleAppearance::ButtonFocus:
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::MozWindowButtonBox:
+ case StyleAppearance::MozWindowButtonClose:
+ case StyleAppearance::MozWindowButtonMinimize:
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore:
+ case StyleAppearance::Dualbutton:
+ case StyleAppearance::TabScrollArrowBack:
+ case StyleAppearance::TabScrollArrowForward:
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::ToolbarbuttonDropdown:
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowDown:
+ case StyleAppearance::ButtonArrowNext:
+ case StyleAppearance::ButtonArrowPrevious:
+ case StyleAppearance::RangeThumb:
+ // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
+ // and have a meaningful baseline, so they can't have
+ // author-specified padding.
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio:
+ aResult->SizeTo(0, 0, 0, 0);
+ return true;
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem: {
+ // Menubar and menulist have their padding specified in CSS.
+ if (!IsRegularMenuItem(aFrame)) return false;
+
+ GetCachedWidgetBorder(aFrame, aAppearance, GetTextDirection(aFrame),
+ aResult);
+
+ gint horizontal_padding;
+ if (aAppearance == StyleAppearance::Menuitem)
+ moz_gtk_menuitem_get_horizontal_padding(&horizontal_padding);
+ else
+ moz_gtk_checkmenuitem_get_horizontal_padding(&horizontal_padding);
+
+ aResult->left += horizontal_padding;
+ aResult->right += horizontal_padding;
+
+ gint scale = GetMonitorScaleFactor(aFrame);
+ aResult->top *= scale;
+ aResult->right *= scale;
+ aResult->bottom *= scale;
+ aResult->left *= scale;
+
+ return true;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool nsNativeThemeGTK::GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) {
+ nsIntMargin extraSize;
+ if (!GetExtraSizeForWidget(aFrame, aAppearance, &extraSize)) return false;
+
+ int32_t p2a = aContext->AppUnitsPerDevPixel();
+ nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a),
+ NSIntPixelsToAppUnits(extraSize.right, p2a),
+ NSIntPixelsToAppUnits(extraSize.bottom, p2a),
+ NSIntPixelsToAppUnits(extraSize.left, p2a));
+
+ aOverflowRect->Inflate(m);
+ return true;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) {
+ aResult->width = aResult->height = 0;
+ *aIsOverridable = true;
+
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown: {
+ const ScrollbarGTKMetrics* metrics =
+ GetActiveScrollbarMetrics(GTK_ORIENTATION_VERTICAL);
+
+ aResult->width = metrics->size.button.width;
+ aResult->height = metrics->size.button.height;
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight: {
+ const ScrollbarGTKMetrics* metrics =
+ GetActiveScrollbarMetrics(GTK_ORIENTATION_HORIZONTAL);
+
+ aResult->width = metrics->size.button.width;
+ aResult->height = metrics->size.button.height;
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::Splitter: {
+ gint metrics;
+ if (IsHorizontal(aFrame)) {
+ moz_gtk_splitter_get_metrics(GTK_ORIENTATION_HORIZONTAL, &metrics);
+ aResult->width = metrics;
+ aResult->height = 0;
+ } else {
+ moz_gtk_splitter_get_metrics(GTK_ORIENTATION_VERTICAL, &metrics);
+ aResult->width = 0;
+ aResult->height = metrics;
+ }
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::ScrollbarNonDisappearing: {
+ const ScrollbarGTKMetrics* verticalMetrics =
+ GetActiveScrollbarMetrics(GTK_ORIENTATION_VERTICAL);
+ const ScrollbarGTKMetrics* horizontalMetrics =
+ GetActiveScrollbarMetrics(GTK_ORIENTATION_HORIZONTAL);
+ aResult->width = verticalMetrics->size.scrollbar.width;
+ aResult->height = horizontalMetrics->size.scrollbar.height;
+ } break;
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical: {
+ /* While we enforce a minimum size for the thumb, this is ignored
+ * for the some scrollbars if buttons are hidden (bug 513006) because
+ * the thumb isn't a direct child of the scrollbar, unlike the buttons
+ * or track. So add a minimum size to the track as well to prevent a
+ * 0-width scrollbar. */
+ GtkOrientation orientation =
+ aAppearance == StyleAppearance::ScrollbarHorizontal
+ ? GTK_ORIENTATION_HORIZONTAL
+ : GTK_ORIENTATION_VERTICAL;
+ const ScrollbarGTKMetrics* metrics =
+ GetActiveScrollbarMetrics(orientation);
+
+ aResult->width = metrics->size.scrollbar.width;
+ aResult->height = metrics->size.scrollbar.height;
+ } break;
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal: {
+ GtkOrientation orientation =
+ aAppearance == StyleAppearance::ScrollbarthumbHorizontal
+ ? GTK_ORIENTATION_HORIZONTAL
+ : GTK_ORIENTATION_VERTICAL;
+ const ScrollbarGTKMetrics* metrics =
+ GetActiveScrollbarMetrics(orientation);
+
+ aResult->width = metrics->size.thumb.width;
+ aResult->height = metrics->size.thumb.height;
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::RangeThumb: {
+ gint thumb_length, thumb_height;
+
+ if (IsRangeHorizontal(aFrame)) {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL,
+ &thumb_length, &thumb_height);
+ } else {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_height,
+ &thumb_length);
+ }
+ aResult->width = thumb_length;
+ aResult->height = thumb_height;
+
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::TabScrollArrowBack:
+ case StyleAppearance::TabScrollArrowForward: {
+ moz_gtk_get_tab_scroll_arrow_size(&aResult->width, &aResult->height);
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::MozMenulistArrowButton: {
+ moz_gtk_get_combo_box_entry_button_size(&aResult->width,
+ &aResult->height);
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::Menuseparator: {
+ gint separator_height;
+
+ moz_gtk_get_menu_separator_height(&separator_height);
+ aResult->height = separator_height;
+
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio: {
+ const ToggleGTKMetrics* metrics = GetToggleMetrics(
+ aAppearance == StyleAppearance::Radio ? MOZ_GTK_RADIOBUTTON
+ : MOZ_GTK_CHECKBUTTON);
+ aResult->width = metrics->minSizeWithBorder.width;
+ aResult->height = metrics->minSizeWithBorder.height;
+ } break;
+ case StyleAppearance::ToolbarbuttonDropdown:
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowDown:
+ case StyleAppearance::ButtonArrowNext:
+ case StyleAppearance::ButtonArrowPrevious: {
+ moz_gtk_get_arrow_size(MOZ_GTK_TOOLBARBUTTON_ARROW, &aResult->width,
+ &aResult->height);
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::MozWindowButtonClose: {
+ const ToolbarButtonGTKMetrics* metrics =
+ GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
+ aResult->width = metrics->minSizeWithBorderMargin.width;
+ aResult->height = metrics->minSizeWithBorderMargin.height;
+ break;
+ }
+ case StyleAppearance::MozWindowButtonMinimize: {
+ const ToolbarButtonGTKMetrics* metrics =
+ GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE);
+ aResult->width = metrics->minSizeWithBorderMargin.width;
+ aResult->height = metrics->minSizeWithBorderMargin.height;
+ break;
+ }
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore: {
+ const ToolbarButtonGTKMetrics* metrics =
+ GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE);
+ aResult->width = metrics->minSizeWithBorderMargin.width;
+ aResult->height = metrics->minSizeWithBorderMargin.height;
+ break;
+ }
+ case StyleAppearance::CheckboxContainer:
+ case StyleAppearance::RadioContainer:
+ case StyleAppearance::CheckboxLabel:
+ case StyleAppearance::RadioLabel:
+ case StyleAppearance::Button:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::Treeheadercell: {
+ if (aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton) {
+ // Include the arrow size.
+ moz_gtk_get_arrow_size(MOZ_GTK_DROPDOWN, &aResult->width,
+ &aResult->height);
+ }
+ // else the minimum size is missing consideration of container
+ // descendants; the value returned here will not be helpful, but the
+ // box model may consider border and padding with child minimum sizes.
+
+ LayoutDeviceIntMargin border;
+ GetCachedWidgetBorder(aFrame, aAppearance, GetTextDirection(aFrame),
+ &border);
+ aResult->width += border.left + border.right;
+ aResult->height += border.top + border.bottom;
+ } break;
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield: {
+ gint contentHeight = 0;
+ gint borderPaddingHeight = 0;
+ moz_gtk_get_entry_min_height(&contentHeight, &borderPaddingHeight);
+
+ // Scale the min content height proportionately with the font-size if it's
+ // smaller than the default one. This prevents <input type=text
+ // style="font-size: .5em"> from keeping a ridiculously large size, for
+ // example.
+ const gfxFloat fieldFontSizeInCSSPixels = [] {
+ gfxFontStyle fieldFontStyle;
+ nsAutoString unusedFontName;
+ DebugOnly<bool> result = LookAndFeel::GetFont(
+ LookAndFeel::FontID::Field, unusedFontName, fieldFontStyle);
+ MOZ_ASSERT(result, "GTK look and feel supports the field font");
+ // NOTE: GetFont returns font sizes in CSS pixels, and we want just
+ // that.
+ return fieldFontStyle.size;
+ }();
+
+ const gfxFloat fontSize = aFrame->StyleFont()->mFont.size.ToCSSPixels();
+ if (fieldFontSizeInCSSPixels > fontSize) {
+ contentHeight =
+ std::round(contentHeight * fontSize / fieldFontSizeInCSSPixels);
+ }
+
+ gint height = contentHeight + borderPaddingHeight;
+ if (aFrame->GetWritingMode().IsVertical()) {
+ aResult->width = height;
+ } else {
+ aResult->height = height;
+ }
+ } break;
+ case StyleAppearance::Separator: {
+ gint separator_width;
+
+ moz_gtk_get_toolbar_separator_width(&separator_width);
+
+ aResult->width = separator_width;
+ } break;
+ case StyleAppearance::Spinner:
+ // hard code these sizes
+ aResult->width = 14;
+ aResult->height = 26;
+ break;
+ case StyleAppearance::Treeheadersortarrow:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ // hard code these sizes
+ aResult->width = 14;
+ aResult->height = 13;
+ break;
+ case StyleAppearance::Resizer:
+ // same as Windows to make our lives easier
+ aResult->width = aResult->height = 15;
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::Treetwisty:
+ case StyleAppearance::Treetwistyopen: {
+ gint expander_size;
+
+ moz_gtk_get_treeview_expander_size(&expander_size);
+ aResult->width = aResult->height = expander_size;
+ *aIsOverridable = false;
+ } break;
+ default:
+ break;
+ }
+
+ *aResult = *aResult * GetMonitorScaleFactor(aFrame);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) {
+ // Some widget types just never change state.
+ if (aAppearance == StyleAppearance::Toolbox ||
+ aAppearance == StyleAppearance::Toolbar ||
+ aAppearance == StyleAppearance::Statusbar ||
+ aAppearance == StyleAppearance::Statusbarpanel ||
+ aAppearance == StyleAppearance::Resizerpanel ||
+ aAppearance == StyleAppearance::Progresschunk ||
+ aAppearance == StyleAppearance::ProgressBar ||
+ aAppearance == StyleAppearance::Menubar ||
+ aAppearance == StyleAppearance::Menupopup ||
+ aAppearance == StyleAppearance::Tooltip ||
+ aAppearance == StyleAppearance::Menuseparator ||
+ aAppearance == StyleAppearance::Window ||
+ aAppearance == StyleAppearance::Dialog) {
+ *aShouldRepaint = false;
+ return NS_OK;
+ }
+
+ if (aAppearance == StyleAppearance::MozWindowTitlebar ||
+ aAppearance == StyleAppearance::MozWindowTitlebarMaximized ||
+ aAppearance == StyleAppearance::MozWindowButtonClose ||
+ aAppearance == StyleAppearance::MozWindowButtonMinimize ||
+ aAppearance == StyleAppearance::MozWindowButtonMaximize ||
+ aAppearance == StyleAppearance::MozWindowButtonRestore) {
+ *aShouldRepaint = true;
+ return NS_OK;
+ }
+
+ if ((aAppearance == StyleAppearance::ScrollbarthumbVertical ||
+ aAppearance == StyleAppearance::ScrollbarthumbHorizontal) &&
+ aAttribute == nsGkAtoms::active) {
+ *aShouldRepaint = true;
+ return NS_OK;
+ }
+
+ if ((aAppearance == StyleAppearance::ScrollbarbuttonUp ||
+ aAppearance == StyleAppearance::ScrollbarbuttonDown ||
+ aAppearance == StyleAppearance::ScrollbarbuttonLeft ||
+ aAppearance == StyleAppearance::ScrollbarbuttonRight) &&
+ (aAttribute == nsGkAtoms::curpos || aAttribute == nsGkAtoms::maxpos)) {
+ // If 'curpos' has changed and we are passed its old value, we can
+ // determine whether the button's enablement actually needs to change.
+ if (aAttribute == nsGkAtoms::curpos && aOldValue) {
+ int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
+ int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 0);
+ nsAutoString str;
+ aOldValue->ToString(str);
+ nsresult err;
+ int32_t oldCurpos = str.ToInteger(&err);
+ if (str.IsEmpty() || NS_FAILED(err)) {
+ *aShouldRepaint = true;
+ } else {
+ bool disabledBefore =
+ ShouldScrollbarButtonBeDisabled(oldCurpos, maxpos, aAppearance);
+ bool disabledNow =
+ ShouldScrollbarButtonBeDisabled(curpos, maxpos, aAppearance);
+ *aShouldRepaint = (disabledBefore != disabledNow);
+ }
+ } else {
+ *aShouldRepaint = true;
+ }
+ 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::focused || aAttribute == nsGkAtoms::readonly ||
+ aAttribute == nsGkAtoms::_default ||
+ aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::open ||
+ aAttribute == nsGkAtoms::parentfocused)
+ *aShouldRepaint = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::ThemeChanged() {
+ memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
+ memset(mSafeWidgetStates, 0, sizeof(mSafeWidgetStates));
+ memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid));
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ if (IsWidgetTypeDisabled(mDisabledWidgetTypes, aAppearance)) {
+ return false;
+ }
+
+ if (IsWidgetScrollbarPart(aAppearance)) {
+ ComputedStyle* cs = nsLayoutUtils::StyleForScrollbar(aFrame);
+ if (cs->StyleUI()->HasCustomScrollbars() ||
+ // We cannot handle thin scrollbar on GTK+ widget directly as well.
+ cs->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin) {
+ return false;
+ }
+ }
+
+ switch (aAppearance) {
+ // Combobox dropdowns don't support native theming in vertical mode.
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::MenulistText:
+ if (aFrame && aFrame->GetWritingMode().IsVertical()) {
+ return false;
+ }
+ [[fallthrough]];
+
+ case StyleAppearance::Button:
+ case StyleAppearance::ButtonFocus:
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Toolbox: // N/A
+ case StyleAppearance::Toolbar:
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::Dualbutton: // so we can override the border with 0
+ case StyleAppearance::ToolbarbuttonDropdown:
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowDown:
+ case StyleAppearance::ButtonArrowNext:
+ case StyleAppearance::ButtonArrowPrevious:
+ case StyleAppearance::Separator:
+ case StyleAppearance::Toolbargripper:
+ case StyleAppearance::Statusbar:
+ case StyleAppearance::Statusbarpanel:
+ case StyleAppearance::Resizerpanel:
+ case StyleAppearance::Resizer:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ // case StyleAppearance::Treeitem:
+ case StyleAppearance::Treetwisty:
+ // case StyleAppearance::Treeline:
+ // case StyleAppearance::Treeheader:
+ case StyleAppearance::Treeheadercell:
+ case StyleAppearance::Treeheadersortarrow:
+ case StyleAppearance::Treetwistyopen:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Tab:
+ // case StyleAppearance::Tabpanel:
+ case StyleAppearance::Tabpanels:
+ case StyleAppearance::TabScrollArrowBack:
+ case StyleAppearance::TabScrollArrowForward:
+ case StyleAppearance::Tooltip:
+ case StyleAppearance::Spinner:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ case StyleAppearance::SpinnerTextfield:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarNonDisappearing:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ case StyleAppearance::CheckboxContainer:
+ case StyleAppearance::RadioContainer:
+ case StyleAppearance::CheckboxLabel:
+ case StyleAppearance::RadioLabel:
+ case StyleAppearance::Menubar:
+ case StyleAppearance::Menupopup:
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Menuarrow:
+ case StyleAppearance::Menuseparator:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem:
+ case StyleAppearance::Splitter:
+ case StyleAppearance::Window:
+ case StyleAppearance::Dialog:
+ case StyleAppearance::MozGtkInfoBar:
+ case StyleAppearance::MozWindowButtonBox:
+ case StyleAppearance::MozWindowButtonClose:
+ case StyleAppearance::MozWindowButtonMinimize:
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore:
+ case StyleAppearance::MozWindowTitlebar:
+ case StyleAppearance::MozWindowTitlebarMaximized:
+ return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
+
+ case StyleAppearance::MozMenulistArrowButton:
+ if (aFrame && aFrame->GetWritingMode().IsVertical()) {
+ return false;
+ }
+ // "Native" dropdown buttons cause padding and margin problems, but only
+ // in HTML so allow them in XUL.
+ return (!aFrame ||
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) &&
+ !IsWidgetStyled(aPresContext, aFrame, aAppearance);
+
+ case StyleAppearance::FocusOutline:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP_(bool)
+nsNativeThemeGTK::WidgetIsContainer(StyleAppearance aAppearance) {
+ // XXXdwh At some point flesh all of this out.
+ if (aAppearance == StyleAppearance::MozMenulistArrowButton ||
+ aAppearance == StyleAppearance::Radio ||
+ aAppearance == StyleAppearance::RangeThumb ||
+ aAppearance == StyleAppearance::Checkbox ||
+ aAppearance == StyleAppearance::TabScrollArrowBack ||
+ aAppearance == StyleAppearance::TabScrollArrowForward ||
+ aAppearance == StyleAppearance::ButtonArrowUp ||
+ aAppearance == StyleAppearance::ButtonArrowDown ||
+ aAppearance == StyleAppearance::ButtonArrowNext ||
+ aAppearance == StyleAppearance::ButtonArrowPrevious)
+ return false;
+ return true;
+}
+
+bool nsNativeThemeGTK::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Treeheadercell:
+ case StyleAppearance::NumberInput:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool nsNativeThemeGTK::ThemeNeedsComboboxDropmarker() { return false; }
+
+nsITheme::Transparency nsNativeThemeGTK::GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ // These widgets always draw a default background.
+ case StyleAppearance::Menupopup:
+ case StyleAppearance::Window:
+ case StyleAppearance::Dialog:
+ return eOpaque;
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ // Make scrollbar tracks opaque on the window's scroll frame to prevent
+ // leaf layers from overlapping. See bug 1179780.
+ if (!(CheckBooleanAttr(aFrame, nsGkAtoms::root_) &&
+ aFrame->PresContext()->IsRootContentDocument() &&
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL))) {
+ return eTransparent;
+ }
+ return eOpaque;
+ // Tooltips use gtk_paint_flat_box() on Gtk2
+ // but are shaped on Gtk3
+ case StyleAppearance::Tooltip:
+ return eTransparent;
+ default:
+ return eUnknownTransparency;
+ }
+}
+
+bool nsNativeThemeGTK::WidgetAppearanceDependsOnWindowFocus(
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ return true;
+ default:
+ return false;
+ }
+}
+
+already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() {
+ static nsCOMPtr<nsITheme> inst;
+
+ if (!inst) {
+ if (gfxPlatform::IsHeadless()) {
+ inst = new HeadlessThemeGTK();
+ } else {
+ inst = new nsNativeThemeGTK();
+ }
+ ClearOnShutdown(&inst);
+ }
+
+ return do_AddRef(inst);
+}
diff --git a/widget/gtk/nsNativeThemeGTK.h b/widget/gtk/nsNativeThemeGTK.h
new file mode 100644
index 0000000000..8b86625bda
--- /dev/null
+++ b/widget/gtk/nsNativeThemeGTK.h
@@ -0,0 +1,116 @@
+/* -*- 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 _GTK_NSNATIVETHEMEGTK_H_
+#define _GTK_NSNATIVETHEMEGTK_H_
+
+#include "nsITheme.h"
+#include "nsCOMPtr.h"
+#include "nsAtom.h"
+#include "nsIObserver.h"
+#include "nsNativeTheme.h"
+#include "nsStyleConsts.h"
+
+#include <gtk/gtk.h>
+#include "gtkdrawing.h"
+
+class nsNativeThemeGTK final : private nsNativeTheme,
+ public nsITheme,
+ public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIOBSERVER
+
+ // The nsITheme interface.
+ NS_IMETHOD DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) override;
+
+ bool CreateWebRenderCommandsForWidget(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
+ StyleAppearance aAppearance, const nsRect& aRect) override;
+
+ [[nodiscard]] LayoutDeviceIntMargin GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ bool GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) override;
+
+ virtual bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) override;
+
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) override;
+
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) override;
+
+ NS_IMETHOD ThemeChanged() override;
+
+ NS_IMETHOD_(bool)
+ ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ NS_IMETHOD_(bool) WidgetIsContainer(StyleAppearance aAppearance) override;
+
+ NS_IMETHOD_(bool)
+ ThemeDrawsFocusForWidget(StyleAppearance aAppearance) override;
+
+ virtual bool ThemeNeedsComboboxDropmarker() override;
+
+ virtual Transparency GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) override;
+
+ virtual bool WidgetAppearanceDependsOnWindowFocus(
+ StyleAppearance aAppearance) override;
+
+ nsNativeThemeGTK();
+
+ protected:
+ virtual ~nsNativeThemeGTK();
+
+ private:
+ GtkTextDirection GetTextDirection(nsIFrame* aFrame);
+ gint GetTabMarginPixels(nsIFrame* aFrame);
+ bool GetGtkWidgetAndState(StyleAppearance aAppearance, nsIFrame* aFrame,
+ WidgetNodeType& aGtkWidgetType,
+ GtkWidgetState* aState, gint* aWidgetFlags);
+ bool GetExtraSizeForWidget(nsIFrame* aFrame, StyleAppearance aAppearance,
+ nsIntMargin* aExtra);
+ bool IsWidgetVisible(StyleAppearance aAppearance);
+
+ void RefreshWidgetWindow(nsIFrame* aFrame);
+ WidgetNodeType NativeThemeToGtkTheme(StyleAppearance aAppearance,
+ nsIFrame* aFrame);
+
+ uint8_t mDisabledWidgetTypes
+ [(static_cast<size_t>(mozilla::StyleAppearance::Count) + 7) / 8];
+ uint8_t
+ mSafeWidgetStates[static_cast<size_t>(mozilla::StyleAppearance::Count) *
+ 4]; // 32 bits per widget
+ static const char* sDisabledEngines[];
+
+ // Because moz_gtk_get_widget_border can be slow, we cache its results
+ // by widget type. Each bit in mBorderCacheValid says whether the
+ // corresponding entry in mBorderCache is valid.
+ void GetCachedWidgetBorder(nsIFrame* aFrame, StyleAppearance aAppearance,
+ GtkTextDirection aDirection,
+ LayoutDeviceIntMargin* aResult);
+ uint8_t mBorderCacheValid[(MOZ_GTK_WIDGET_NODE_COUNT + 7) / 8];
+ LayoutDeviceIntMargin mBorderCache[MOZ_GTK_WIDGET_NODE_COUNT];
+};
+
+#endif
diff --git a/widget/gtk/nsPrintDialogGTK.cpp b/widget/gtk/nsPrintDialogGTK.cpp
new file mode 100644
index 0000000000..9c1a72a6e3
--- /dev/null
+++ b/widget/gtk/nsPrintDialogGTK.cpp
@@ -0,0 +1,1054 @@
+/* -*- 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 <gtk/gtk.h>
+#include <gtk/gtkunixprint.h>
+#include <stdlib.h>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Services.h"
+
+#include "MozContainer.h"
+#include "nsIPrintSettings.h"
+#include "nsIWidget.h"
+#include "nsPrintDialogGTK.h"
+#include "nsPrintSettingsGTK.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIStringBundle.h"
+#include "nsIPrintSettingsService.h"
+#include "nsPIDOMWindow.h"
+#include "nsIGIOService.h"
+#include "WidgetUtils.h"
+#include "nsIObserverService.h"
+
+// for gdk_x11_window_get_xid
+#include <gdk/gdkx.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <gio/gunixfdlist.h>
+#include "gfxPlatformGtk.h"
+
+// for dlsym
+#include <dlfcn.h>
+#include "MainThreadUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+static const char header_footer_tags[][4] = {"", "&T", "&U", "&D", "&P", "&PT"};
+
+#define CUSTOM_VALUE_INDEX gint(ArrayLength(header_footer_tags))
+
+static GtkWindow* get_gtk_window_for_nsiwidget(nsIWidget* widget) {
+ return GTK_WINDOW(widget->GetNativeData(NS_NATIVE_SHELLWIDGET));
+}
+
+static void ShowCustomDialog(GtkComboBox* changed_box, gpointer user_data) {
+ if (gtk_combo_box_get_active(changed_box) != CUSTOM_VALUE_INDEX) {
+ g_object_set_data(G_OBJECT(changed_box), "previous-active",
+ GINT_TO_POINTER(gtk_combo_box_get_active(changed_box)));
+ return;
+ }
+
+ GtkWindow* printDialog = GTK_WINDOW(user_data);
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+
+ nsCOMPtr<nsIStringBundle> printBundle;
+ bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
+ getter_AddRefs(printBundle));
+ nsAutoString intlString;
+
+ printBundle->GetStringFromName("headerFooterCustom", intlString);
+ GtkWidget* prompt_dialog = gtk_dialog_new_with_buttons(
+ NS_ConvertUTF16toUTF8(intlString).get(), printDialog,
+ (GtkDialogFlags)(GTK_DIALOG_MODAL), GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, nullptr);
+ gtk_dialog_set_default_response(GTK_DIALOG(prompt_dialog),
+ GTK_RESPONSE_ACCEPT);
+ gtk_dialog_set_alternative_button_order(
+ GTK_DIALOG(prompt_dialog), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_REJECT, -1);
+
+ printBundle->GetStringFromName("customHeaderFooterPrompt", intlString);
+ GtkWidget* custom_label =
+ gtk_label_new(NS_ConvertUTF16toUTF8(intlString).get());
+ GtkWidget* custom_entry = gtk_entry_new();
+ GtkWidget* question_icon =
+ gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
+
+ // To be convenient, prefill the textbox with the existing value, if any, and
+ // select it all so they can easily both edit it and type in a new one.
+ const char* current_text =
+ (const char*)g_object_get_data(G_OBJECT(changed_box), "custom-text");
+ if (current_text) {
+ gtk_entry_set_text(GTK_ENTRY(custom_entry), current_text);
+ gtk_editable_select_region(GTK_EDITABLE(custom_entry), 0, -1);
+ }
+ gtk_entry_set_activates_default(GTK_ENTRY(custom_entry), TRUE);
+
+ GtkWidget* custom_vbox = gtk_vbox_new(TRUE, 2);
+ gtk_box_pack_start(GTK_BOX(custom_vbox), custom_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(custom_vbox), custom_entry, FALSE, FALSE,
+ 5); // Make entry 5px underneath label
+ GtkWidget* custom_hbox = gtk_hbox_new(FALSE, 2);
+ gtk_box_pack_start(GTK_BOX(custom_hbox), question_icon, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(custom_hbox), custom_vbox, FALSE, FALSE,
+ 10); // Make question icon 10px away from content
+ gtk_container_set_border_width(GTK_CONTAINER(custom_hbox), 2);
+ gtk_widget_show_all(custom_hbox);
+
+ gtk_box_pack_start(
+ GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(prompt_dialog))),
+ custom_hbox, FALSE, FALSE, 0);
+ gint diag_response = gtk_dialog_run(GTK_DIALOG(prompt_dialog));
+
+ if (diag_response == GTK_RESPONSE_ACCEPT) {
+ const gchar* response_text = gtk_entry_get_text(GTK_ENTRY(custom_entry));
+ g_object_set_data_full(G_OBJECT(changed_box), "custom-text",
+ strdup(response_text), (GDestroyNotify)free);
+ g_object_set_data(G_OBJECT(changed_box), "previous-active",
+ GINT_TO_POINTER(CUSTOM_VALUE_INDEX));
+ } else {
+ // Go back to the previous index
+ gint previous_active = GPOINTER_TO_INT(
+ g_object_get_data(G_OBJECT(changed_box), "previous-active"));
+ gtk_combo_box_set_active(changed_box, previous_active);
+ }
+
+ gtk_widget_destroy(prompt_dialog);
+}
+
+class nsPrintDialogWidgetGTK {
+ public:
+ nsPrintDialogWidgetGTK(nsPIDOMWindowOuter* aParent,
+ nsIPrintSettings* aPrintSettings);
+ ~nsPrintDialogWidgetGTK() { gtk_widget_destroy(dialog); }
+ NS_ConvertUTF16toUTF8 GetUTF8FromBundle(const char* aKey);
+ gint Run();
+
+ nsresult ImportSettings(nsIPrintSettings* aNSSettings);
+ nsresult ExportSettings(nsIPrintSettings* aNSSettings);
+
+ private:
+ GtkWidget* dialog;
+ GtkWidget* shrink_to_fit_toggle;
+ GtkWidget* print_bg_colors_toggle;
+ GtkWidget* print_bg_images_toggle;
+ GtkWidget* selection_only_toggle;
+ GtkWidget* header_dropdown[3]; // {left, center, right}
+ GtkWidget* footer_dropdown[3];
+
+ nsCOMPtr<nsIStringBundle> printBundle;
+
+ bool useNativeSelection;
+
+ GtkWidget* ConstructHeaderFooterDropdown(const char16_t* currentString);
+ const char* OptionWidgetToString(GtkWidget* dropdown);
+
+ /* Code to copy between GTK and NS print settings structures.
+ * In the following,
+ * "Import" means to copy from NS to GTK
+ * "Export" means to copy from GTK to NS
+ */
+ void ExportHeaderFooter(nsIPrintSettings* aNS);
+};
+
+nsPrintDialogWidgetGTK::nsPrintDialogWidgetGTK(nsPIDOMWindowOuter* aParent,
+ nsIPrintSettings* aSettings) {
+ nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
+ NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
+ GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
+ NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
+
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
+ getter_AddRefs(printBundle));
+
+ dialog = gtk_print_unix_dialog_new(GetUTF8FromBundle("printTitleGTK").get(),
+ gtkParent);
+
+ gtk_print_unix_dialog_set_manual_capabilities(
+ GTK_PRINT_UNIX_DIALOG(dialog),
+ GtkPrintCapabilities(
+ GTK_PRINT_CAPABILITY_COPIES | GTK_PRINT_CAPABILITY_COLLATE |
+ GTK_PRINT_CAPABILITY_REVERSE | GTK_PRINT_CAPABILITY_SCALE |
+ GTK_PRINT_CAPABILITY_GENERATE_PDF));
+
+ // The vast majority of magic numbers in this widget construction are padding.
+ // e.g. for the set_border_width below, 12px matches that of just about every
+ // other window.
+ GtkWidget* custom_options_tab = gtk_vbox_new(FALSE, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(custom_options_tab), 12);
+ GtkWidget* tab_label =
+ gtk_label_new(GetUTF8FromBundle("optionsTabLabelGTK").get());
+
+ // Check buttons for shrink-to-fit and print selection
+ GtkWidget* check_buttons_container = gtk_vbox_new(TRUE, 2);
+ shrink_to_fit_toggle = gtk_check_button_new_with_mnemonic(
+ GetUTF8FromBundle("shrinkToFit").get());
+ gtk_box_pack_start(GTK_BOX(check_buttons_container), shrink_to_fit_toggle,
+ FALSE, FALSE, 0);
+
+ // GTK+2.18 and above allow us to add a "Selection" option to the main
+ // settings screen, rather than adding an option on a custom tab like we must
+ // do on older versions.
+ bool canSelectText = aSettings->GetIsPrintSelectionRBEnabled();
+ if (gtk_major_version > 2 ||
+ (gtk_major_version == 2 && gtk_minor_version >= 18)) {
+ useNativeSelection = true;
+ g_object_set(dialog, "support-selection", TRUE, "has-selection",
+ canSelectText, "embed-page-setup", TRUE, nullptr);
+ } else {
+ useNativeSelection = false;
+ selection_only_toggle = gtk_check_button_new_with_mnemonic(
+ GetUTF8FromBundle("selectionOnly").get());
+ gtk_widget_set_sensitive(selection_only_toggle, canSelectText);
+ gtk_box_pack_start(GTK_BOX(check_buttons_container), selection_only_toggle,
+ FALSE, FALSE, 0);
+ }
+
+ // Check buttons for printing background
+ GtkWidget* appearance_buttons_container = gtk_vbox_new(TRUE, 2);
+ print_bg_colors_toggle = gtk_check_button_new_with_mnemonic(
+ GetUTF8FromBundle("printBGColors").get());
+ print_bg_images_toggle = gtk_check_button_new_with_mnemonic(
+ GetUTF8FromBundle("printBGImages").get());
+ gtk_box_pack_start(GTK_BOX(appearance_buttons_container),
+ print_bg_colors_toggle, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(appearance_buttons_container),
+ print_bg_images_toggle, FALSE, FALSE, 0);
+
+ // "Appearance" options label, bold and center-aligned
+ GtkWidget* appearance_label = gtk_label_new(nullptr);
+ char* pangoMarkup = g_markup_printf_escaped(
+ "<b>%s</b>", GetUTF8FromBundle("printBGOptions").get());
+ gtk_label_set_markup(GTK_LABEL(appearance_label), pangoMarkup);
+ g_free(pangoMarkup);
+ gtk_misc_set_alignment(GTK_MISC(appearance_label), 0, 0);
+
+ GtkWidget* appearance_container = gtk_alignment_new(0, 0, 0, 0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(appearance_container), 8, 0, 12, 0);
+ gtk_container_add(GTK_CONTAINER(appearance_container),
+ appearance_buttons_container);
+
+ GtkWidget* appearance_vertical_squasher = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher), appearance_label,
+ FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher),
+ appearance_container, FALSE, FALSE, 0);
+
+ // "Header & Footer" options label, bold and center-aligned
+ GtkWidget* header_footer_label = gtk_label_new(nullptr);
+ pangoMarkup = g_markup_printf_escaped(
+ "<b>%s</b>", GetUTF8FromBundle("headerFooter").get());
+ gtk_label_set_markup(GTK_LABEL(header_footer_label), pangoMarkup);
+ g_free(pangoMarkup);
+ gtk_misc_set_alignment(GTK_MISC(header_footer_label), 0, 0);
+
+ GtkWidget* header_footer_container = gtk_alignment_new(0, 0, 0, 0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(header_footer_container), 8, 0, 12,
+ 0);
+
+ // --- Table for making the header and footer options ---
+ GtkWidget* header_footer_table = gtk_table_new(3, 3, FALSE); // 3x3 table
+ nsString header_footer_str[3];
+
+ aSettings->GetHeaderStrLeft(header_footer_str[0]);
+ aSettings->GetHeaderStrCenter(header_footer_str[1]);
+ aSettings->GetHeaderStrRight(header_footer_str[2]);
+
+ for (unsigned int i = 0; i < ArrayLength(header_dropdown); i++) {
+ header_dropdown[i] =
+ ConstructHeaderFooterDropdown(header_footer_str[i].get());
+ // Those 4 magic numbers in the middle provide the position in the table.
+ // The last two numbers mean 2 px padding on every side.
+ gtk_table_attach(GTK_TABLE(header_footer_table), header_dropdown[i], i,
+ (i + 1), 0, 1, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
+ 2);
+ }
+
+ const char labelKeys[][7] = {"left", "center", "right"};
+ for (unsigned int i = 0; i < ArrayLength(labelKeys); i++) {
+ gtk_table_attach(GTK_TABLE(header_footer_table),
+ gtk_label_new(GetUTF8FromBundle(labelKeys[i]).get()), i,
+ (i + 1), 1, 2, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
+ 2);
+ }
+
+ aSettings->GetFooterStrLeft(header_footer_str[0]);
+ aSettings->GetFooterStrCenter(header_footer_str[1]);
+ aSettings->GetFooterStrRight(header_footer_str[2]);
+
+ for (unsigned int i = 0; i < ArrayLength(footer_dropdown); i++) {
+ footer_dropdown[i] =
+ ConstructHeaderFooterDropdown(header_footer_str[i].get());
+ gtk_table_attach(GTK_TABLE(header_footer_table), footer_dropdown[i], i,
+ (i + 1), 2, 3, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
+ 2);
+ }
+ // ---
+
+ gtk_container_add(GTK_CONTAINER(header_footer_container),
+ header_footer_table);
+
+ GtkWidget* header_footer_vertical_squasher = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher),
+ header_footer_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher),
+ header_footer_container, FALSE, FALSE, 0);
+
+ // Construction of everything
+ gtk_box_pack_start(GTK_BOX(custom_options_tab), check_buttons_container,
+ FALSE, FALSE, 10); // 10px padding
+ gtk_box_pack_start(GTK_BOX(custom_options_tab), appearance_vertical_squasher,
+ FALSE, FALSE, 10);
+ gtk_box_pack_start(GTK_BOX(custom_options_tab),
+ header_footer_vertical_squasher, FALSE, FALSE, 0);
+
+ gtk_print_unix_dialog_add_custom_tab(GTK_PRINT_UNIX_DIALOG(dialog),
+ custom_options_tab, tab_label);
+ gtk_widget_show_all(custom_options_tab);
+}
+
+NS_ConvertUTF16toUTF8 nsPrintDialogWidgetGTK::GetUTF8FromBundle(
+ const char* aKey) {
+ nsAutoString intlString;
+ printBundle->GetStringFromName(aKey, intlString);
+ return NS_ConvertUTF16toUTF8(
+ intlString); // Return the actual object so we don't lose reference
+}
+
+const char* nsPrintDialogWidgetGTK::OptionWidgetToString(GtkWidget* dropdown) {
+ gint index = gtk_combo_box_get_active(GTK_COMBO_BOX(dropdown));
+
+ NS_ASSERTION(index <= CUSTOM_VALUE_INDEX,
+ "Index of dropdown is higher than expected!");
+
+ if (index == CUSTOM_VALUE_INDEX)
+ return (const char*)g_object_get_data(G_OBJECT(dropdown), "custom-text");
+ else
+ return header_footer_tags[index];
+}
+
+gint nsPrintDialogWidgetGTK::Run() {
+ const gint response = gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_hide(dialog);
+ return response;
+}
+
+void nsPrintDialogWidgetGTK::ExportHeaderFooter(nsIPrintSettings* aNS) {
+ const char* header_footer_str;
+ header_footer_str = OptionWidgetToString(header_dropdown[0]);
+ aNS->SetHeaderStrLeft(NS_ConvertUTF8toUTF16(header_footer_str));
+
+ header_footer_str = OptionWidgetToString(header_dropdown[1]);
+ aNS->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(header_footer_str));
+
+ header_footer_str = OptionWidgetToString(header_dropdown[2]);
+ aNS->SetHeaderStrRight(NS_ConvertUTF8toUTF16(header_footer_str));
+
+ header_footer_str = OptionWidgetToString(footer_dropdown[0]);
+ aNS->SetFooterStrLeft(NS_ConvertUTF8toUTF16(header_footer_str));
+
+ header_footer_str = OptionWidgetToString(footer_dropdown[1]);
+ aNS->SetFooterStrCenter(NS_ConvertUTF8toUTF16(header_footer_str));
+
+ header_footer_str = OptionWidgetToString(footer_dropdown[2]);
+ aNS->SetFooterStrRight(NS_ConvertUTF8toUTF16(header_footer_str));
+}
+
+nsresult nsPrintDialogWidgetGTK::ImportSettings(nsIPrintSettings* aNSSettings) {
+ MOZ_ASSERT(aNSSettings, "aSettings must not be null");
+ NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
+ if (!aNSSettingsGTK) return NS_ERROR_FAILURE;
+
+ GtkPrintSettings* settings = aNSSettingsGTK->GetGtkPrintSettings();
+ GtkPageSetup* setup = aNSSettingsGTK->GetGtkPageSetup();
+
+ bool geckoBool;
+ aNSSettings->GetShrinkToFit(&geckoBool);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle),
+ geckoBool);
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_colors_toggle),
+ aNSSettings->GetPrintBGColors());
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_images_toggle),
+ aNSSettings->GetPrintBGImages());
+
+ gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog), settings);
+ gtk_print_unix_dialog_set_page_setup(GTK_PRINT_UNIX_DIALOG(dialog), setup);
+
+ return NS_OK;
+}
+
+nsresult nsPrintDialogWidgetGTK::ExportSettings(nsIPrintSettings* aNSSettings) {
+ MOZ_ASSERT(aNSSettings, "aSettings must not be null");
+ NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
+
+ GtkPrintSettings* settings =
+ gtk_print_unix_dialog_get_settings(GTK_PRINT_UNIX_DIALOG(dialog));
+ GtkPageSetup* setup =
+ gtk_print_unix_dialog_get_page_setup(GTK_PRINT_UNIX_DIALOG(dialog));
+ GtkPrinter* printer =
+ gtk_print_unix_dialog_get_selected_printer(GTK_PRINT_UNIX_DIALOG(dialog));
+ if (settings && setup && printer) {
+ ExportHeaderFooter(aNSSettings);
+
+ aNSSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatNative);
+
+ // Print-to-file is true by default. This must be turned off or else
+ // printing won't occur! (We manually copy the spool file when this flag is
+ // set, because we love our embedders) Even if it is print-to-file in GTK's
+ // case, GTK does The Right Thing when we send the job.
+ aNSSettings->SetPrintToFile(false);
+
+ aNSSettings->SetShrinkToFit(
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle)));
+
+ aNSSettings->SetPrintBGColors(gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(print_bg_colors_toggle)));
+ aNSSettings->SetPrintBGImages(gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(print_bg_images_toggle)));
+
+ // Try to save native settings in the session object
+ nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
+ if (aNSSettingsGTK) {
+ aNSSettingsGTK->SetGtkPrintSettings(settings);
+ aNSSettingsGTK->SetGtkPageSetup(setup);
+ aNSSettingsGTK->SetGtkPrinter(printer);
+ bool printSelectionOnly;
+ if (useNativeSelection) {
+ _GtkPrintPages pageSetting =
+ (_GtkPrintPages)gtk_print_settings_get_print_pages(settings);
+ printSelectionOnly = (pageSetting == _GTK_PRINT_PAGES_SELECTION);
+ } else {
+ printSelectionOnly = gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(selection_only_toggle));
+ }
+ aNSSettingsGTK->SetPrintSelectionOnly(printSelectionOnly);
+ }
+ }
+
+ if (settings) g_object_unref(settings);
+ return NS_OK;
+}
+
+GtkWidget* nsPrintDialogWidgetGTK::ConstructHeaderFooterDropdown(
+ const char16_t* currentString) {
+ GtkWidget* dropdown = gtk_combo_box_text_new();
+ const char hf_options[][22] = {"headerFooterBlank", "headerFooterTitle",
+ "headerFooterURL", "headerFooterDate",
+ "headerFooterPage", "headerFooterPageTotal",
+ "headerFooterCustom"};
+
+ for (unsigned int i = 0; i < ArrayLength(hf_options); i++) {
+ gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(dropdown), nullptr,
+ GetUTF8FromBundle(hf_options[i]).get());
+ }
+
+ bool shouldBeCustom = true;
+ NS_ConvertUTF16toUTF8 currentStringUTF8(currentString);
+
+ for (unsigned int i = 0; i < ArrayLength(header_footer_tags); i++) {
+ if (!strcmp(currentStringUTF8.get(), header_footer_tags[i])) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), i);
+ g_object_set_data(G_OBJECT(dropdown), "previous-active",
+ GINT_TO_POINTER(i));
+ shouldBeCustom = false;
+ break;
+ }
+ }
+
+ if (shouldBeCustom) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), CUSTOM_VALUE_INDEX);
+ g_object_set_data(G_OBJECT(dropdown), "previous-active",
+ GINT_TO_POINTER(CUSTOM_VALUE_INDEX));
+ char* custom_string = strdup(currentStringUTF8.get());
+ g_object_set_data_full(G_OBJECT(dropdown), "custom-text", custom_string,
+ (GDestroyNotify)free);
+ }
+
+ g_signal_connect(dropdown, "changed", (GCallback)ShowCustomDialog, dialog);
+ return dropdown;
+}
+
+NS_IMPL_ISUPPORTS(nsPrintDialogServiceGTK, nsIPrintDialogService)
+
+nsPrintDialogServiceGTK::nsPrintDialogServiceGTK() = default;
+
+nsPrintDialogServiceGTK::~nsPrintDialogServiceGTK() = default;
+
+NS_IMETHODIMP
+nsPrintDialogServiceGTK::Init() { return NS_OK; }
+
+// Used to obtain window handle. The portal use this handle
+// to ensure that print dialog is modal.
+typedef void (*WindowHandleExported)(GtkWindow* window, const char* handle,
+ gpointer user_data);
+
+typedef void (*GtkWindowHandleExported)(GtkWindow* window, const char* handle,
+ gpointer user_data);
+#ifdef MOZ_WAYLAND
+# if !GTK_CHECK_VERSION(3, 22, 0)
+typedef void (*GdkWaylandWindowExported)(GdkWindow* window, const char* handle,
+ gpointer user_data);
+# endif
+
+typedef struct {
+ GtkWindow* window;
+ WindowHandleExported callback;
+ gpointer user_data;
+} WaylandWindowHandleExportedData;
+
+static void wayland_window_handle_exported(GdkWindow* window,
+ const char* wayland_handle_str,
+ gpointer user_data) {
+ WaylandWindowHandleExportedData* data =
+ static_cast<WaylandWindowHandleExportedData*>(user_data);
+ char* handle_str;
+
+ handle_str = g_strdup_printf("wayland:%s", wayland_handle_str);
+ data->callback(data->window, handle_str, data->user_data);
+ g_free(handle_str);
+}
+#endif
+
+// Get window handle for the portal, taken from gtk/gtkwindow.c
+// (currently not exported)
+static gboolean window_export_handle(GtkWindow* window,
+ GtkWindowHandleExported callback,
+ gpointer user_data) {
+ if (gfxPlatformGtk::GetPlatform()->IsX11Display()) {
+ GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
+ char* handle_str;
+ guint32 xid = (guint32)gdk_x11_window_get_xid(gdk_window);
+
+ handle_str = g_strdup_printf("x11:%x", xid);
+ callback(window, handle_str, user_data);
+ g_free(handle_str);
+ return true;
+ }
+#ifdef MOZ_WAYLAND
+ else {
+ GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
+ WaylandWindowHandleExportedData* data;
+
+ data = g_new0(WaylandWindowHandleExportedData, 1);
+ data->window = window;
+ data->callback = callback;
+ data->user_data = user_data;
+
+ static auto s_gdk_wayland_window_export_handle =
+ reinterpret_cast<gboolean (*)(GdkWindow*, GdkWaylandWindowExported,
+ gpointer, GDestroyNotify)>(
+ dlsym(RTLD_DEFAULT, "gdk_wayland_window_export_handle"));
+ if (!s_gdk_wayland_window_export_handle ||
+ !s_gdk_wayland_window_export_handle(
+ gdk_window, wayland_window_handle_exported, data, g_free)) {
+ g_free(data);
+ return false;
+ } else {
+ return true;
+ }
+ }
+#endif
+
+ g_warning("Couldn't export handle, unsupported windowing system");
+
+ return false;
+}
+/**
+ * Communication class with the GTK print portal handler
+ *
+ * To print document from flatpak we need to use print portal because
+ * printers are not directly accessible in the sandboxed environment.
+ *
+ * At first we request portal to show the print dialog to let user choose
+ * printer settings. We use DBUS interface for that (PreparePrint method).
+ *
+ * Next we force application to print to temporary file and after the writing
+ * to the file is finished we pass its file descriptor to the portal.
+ * Portal will pass duplicate of the file descriptor to the printer which
+ * user selected before (by DBUS Print method).
+ *
+ * Since DBUS communication is done async while nsPrintDialogServiceGTK::Show
+ * is expecting sync execution, we need to create a new GMainLoop during the
+ * print portal dialog is running. The loop is stopped after the dialog
+ * is closed.
+ */
+class nsFlatpakPrintPortal : public nsIObserver {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ public:
+ explicit nsFlatpakPrintPortal(nsPrintSettingsGTK* aPrintSettings);
+ nsresult PreparePrintRequest(GtkWindow* aWindow);
+ static void OnWindowExportHandleDone(GtkWindow* aWindow,
+ const char* aWindowHandleStr,
+ gpointer aUserData);
+ void PreparePrint(GtkWindow* aWindow, const char* aWindowHandleStr);
+ static void OnPreparePrintResponse(GDBusConnection* connection,
+ const char* sender_name,
+ const char* object_path,
+ const char* interface_name,
+ const char* signal_name,
+ GVariant* parameters, gpointer data);
+ GtkPrintOperationResult GetResult();
+
+ private:
+ virtual ~nsFlatpakPrintPortal();
+ void FinishPrintDialog(GVariant* parameters);
+ nsCOMPtr<nsPrintSettingsGTK> mPrintAndPageSettings;
+ GDBusProxy* mProxy;
+ guint32 mToken;
+ GMainLoop* mLoop;
+ GtkPrintOperationResult mResult;
+ guint mResponseSignalId;
+ GtkWindow* mParentWindow;
+};
+
+NS_IMPL_ISUPPORTS(nsFlatpakPrintPortal, nsIObserver)
+
+nsFlatpakPrintPortal::nsFlatpakPrintPortal(nsPrintSettingsGTK* aPrintSettings)
+ : mPrintAndPageSettings(aPrintSettings),
+ mProxy(nullptr),
+ mLoop(nullptr),
+ mResponseSignalId(0),
+ mParentWindow(nullptr) {}
+
+/**
+ * Creates GDBusProxy, query for window handle and create a new GMainLoop.
+ *
+ * The GMainLoop is to be run from GetResult() and be quitted during
+ * FinishPrintDialog.
+ *
+ * @param aWindow toplevel application window which is used as parent of print
+ * dialog
+ */
+nsresult nsFlatpakPrintPortal::PreparePrintRequest(GtkWindow* aWindow) {
+ MOZ_ASSERT(aWindow, "aWindow must not be null");
+ MOZ_ASSERT(mPrintAndPageSettings, "mPrintAndPageSettings must not be null");
+
+ GError* error = nullptr;
+ mProxy = g_dbus_proxy_new_for_bus_sync(
+ G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, nullptr,
+ "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.Print", nullptr, &error);
+ if (mProxy == nullptr) {
+ NS_WARNING(
+ nsPrintfCString("Unable to create dbus proxy: %s", error->message)
+ .get());
+ g_error_free(error);
+ return NS_ERROR_FAILURE;
+ }
+
+ // The window handler is returned async, we will continue by PreparePrint
+ // method when it is returned.
+ if (!window_export_handle(
+ aWindow, &nsFlatpakPrintPortal::OnWindowExportHandleDone, this)) {
+ NS_WARNING("Unable to get window handle for creating modal print dialog.");
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoop = g_main_loop_new(NULL, FALSE);
+ return NS_OK;
+}
+
+void nsFlatpakPrintPortal::OnWindowExportHandleDone(
+ GtkWindow* aWindow, const char* aWindowHandleStr, gpointer aUserData) {
+ nsFlatpakPrintPortal* printPortal =
+ static_cast<nsFlatpakPrintPortal*>(aUserData);
+ printPortal->PreparePrint(aWindow, aWindowHandleStr);
+}
+
+/**
+ * Ask print portal to show the print dialog.
+ *
+ * Print and page settings and window handle are passed to the portal to prefill
+ * last used settings.
+ */
+void nsFlatpakPrintPortal::PreparePrint(GtkWindow* aWindow,
+ const char* aWindowHandleStr) {
+ GtkPrintSettings* gtkSettings = mPrintAndPageSettings->GetGtkPrintSettings();
+ GtkPageSetup* pageSetup = mPrintAndPageSettings->GetGtkPageSetup();
+
+ // We need to remember GtkWindow to unexport window handle after it is
+ // no longer needed by the portal dialog (apply only on non-X11 sessions).
+ if (gfxPlatformGtk::GetPlatform()->IsWaylandDisplay()) {
+ mParentWindow = aWindow;
+ }
+
+ GVariantBuilder opt_builder;
+ g_variant_builder_init(&opt_builder, G_VARIANT_TYPE_VARDICT);
+ char* token = g_strdup_printf("mozilla%d", g_random_int_range(0, G_MAXINT));
+ g_variant_builder_add(&opt_builder, "{sv}", "handle_token",
+ g_variant_new_string(token));
+ g_free(token);
+ GVariant* options = g_variant_builder_end(&opt_builder);
+ static auto s_gtk_print_settings_to_gvariant =
+ reinterpret_cast<GVariant* (*)(GtkPrintSettings*)>(
+ dlsym(RTLD_DEFAULT, "gtk_print_settings_to_gvariant"));
+ static auto s_gtk_page_setup_to_gvariant =
+ reinterpret_cast<GVariant* (*)(GtkPageSetup*)>(
+ dlsym(RTLD_DEFAULT, "gtk_page_setup_to_gvariant"));
+ if (!s_gtk_print_settings_to_gvariant || !s_gtk_page_setup_to_gvariant) {
+ mResult = GTK_PRINT_OPERATION_RESULT_ERROR;
+ FinishPrintDialog(nullptr);
+ return;
+ }
+
+ // Get translated window title
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ nsCOMPtr<nsIStringBundle> printBundle;
+ bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
+ getter_AddRefs(printBundle));
+ nsAutoString intlPrintTitle;
+ printBundle->GetStringFromName("printTitleGTK", intlPrintTitle);
+
+ GError* error = nullptr;
+ GVariant* ret = g_dbus_proxy_call_sync(
+ mProxy, "PreparePrint",
+ g_variant_new(
+ "(ss@a{sv}@a{sv}@a{sv})", aWindowHandleStr,
+ NS_ConvertUTF16toUTF8(intlPrintTitle).get(), // Title of the window
+ s_gtk_print_settings_to_gvariant(gtkSettings),
+ s_gtk_page_setup_to_gvariant(pageSetup), options),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
+ if (ret == nullptr) {
+ NS_WARNING(
+ nsPrintfCString("Unable to call dbus proxy: %s", error->message).get());
+ g_error_free(error);
+ mResult = GTK_PRINT_OPERATION_RESULT_ERROR;
+ FinishPrintDialog(nullptr);
+ return;
+ }
+
+ const char* handle = nullptr;
+ g_variant_get(ret, "(&o)", &handle);
+ if (strcmp(aWindowHandleStr, handle) != 0) {
+ aWindowHandleStr = g_strdup(handle);
+ if (mResponseSignalId) {
+ g_dbus_connection_signal_unsubscribe(
+ g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)), mResponseSignalId);
+ }
+ }
+ mResponseSignalId = g_dbus_connection_signal_subscribe(
+ g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)),
+ "org.freedesktop.portal.Desktop", "org.freedesktop.portal.Request",
+ "Response", aWindowHandleStr, NULL, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+ &nsFlatpakPrintPortal::OnPreparePrintResponse, this, NULL);
+}
+
+void nsFlatpakPrintPortal::OnPreparePrintResponse(
+ GDBusConnection* connection, const char* sender_name,
+ const char* object_path, const char* interface_name,
+ const char* signal_name, GVariant* parameters, gpointer data) {
+ nsFlatpakPrintPortal* printPortal = static_cast<nsFlatpakPrintPortal*>(data);
+ printPortal->FinishPrintDialog(parameters);
+}
+
+/**
+ * When the dialog is accepted, read print and page settings and token.
+ *
+ * Token is later used for printing portal as print operation identifier.
+ * Print and page settings are modified in-place and stored to
+ * mPrintAndPageSettings.
+ */
+void nsFlatpakPrintPortal::FinishPrintDialog(GVariant* parameters) {
+ // This ends GetResult() method
+ if (mLoop) {
+ g_main_loop_quit(mLoop);
+ mLoop = nullptr;
+ }
+
+ if (!parameters) {
+ // mResult should be already defined
+ return;
+ }
+
+ guint32 response;
+ GVariant* options;
+
+ g_variant_get(parameters, "(u@a{sv})", &response, &options);
+ mResult = GTK_PRINT_OPERATION_RESULT_CANCEL;
+ if (response == 0) {
+ GVariant* v =
+ g_variant_lookup_value(options, "settings", G_VARIANT_TYPE_VARDICT);
+ static auto s_gtk_print_settings_new_from_gvariant =
+ reinterpret_cast<GtkPrintSettings* (*)(GVariant*)>(
+ dlsym(RTLD_DEFAULT, "gtk_print_settings_new_from_gvariant"));
+
+ GtkPrintSettings* printSettings = s_gtk_print_settings_new_from_gvariant(v);
+ g_variant_unref(v);
+
+ v = g_variant_lookup_value(options, "page-setup", G_VARIANT_TYPE_VARDICT);
+ static auto s_gtk_page_setup_new_from_gvariant =
+ reinterpret_cast<GtkPageSetup* (*)(GVariant*)>(
+ dlsym(RTLD_DEFAULT, "gtk_page_setup_new_from_gvariant"));
+ GtkPageSetup* pageSetup = s_gtk_page_setup_new_from_gvariant(v);
+ g_variant_unref(v);
+
+ g_variant_lookup(options, "token", "u", &mToken);
+
+ // Save native settings in the session object
+ mPrintAndPageSettings->SetGtkPrintSettings(printSettings);
+ mPrintAndPageSettings->SetGtkPageSetup(pageSetup);
+
+ // Portal consumes PDF file
+ mPrintAndPageSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatPDF);
+
+ // We need to set to print to file
+ mPrintAndPageSettings->SetPrintToFile(true);
+
+ mResult = GTK_PRINT_OPERATION_RESULT_APPLY;
+ }
+}
+
+/**
+ * Get result of the print dialog.
+ *
+ * This call blocks until FinishPrintDialog is called.
+ *
+ */
+GtkPrintOperationResult nsFlatpakPrintPortal::GetResult() {
+ // If the mLoop has not been initialized we haven't go thru PreparePrint
+ // method
+ if (!NS_IsMainThread() || !mLoop) {
+ return GTK_PRINT_OPERATION_RESULT_ERROR;
+ }
+ // Calling g_main_loop_run stops current code until g_main_loop_quit is called
+ g_main_loop_run(mLoop);
+
+ // Free resources we've allocated in order to show print dialog.
+#ifdef MOZ_WAYLAND
+ if (mParentWindow) {
+ GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(mParentWindow));
+ static auto s_gdk_wayland_window_unexport_handle =
+ reinterpret_cast<void (*)(GdkWindow*)>(
+ dlsym(RTLD_DEFAULT, "gdk_wayland_window_unexport_handle"));
+ if (s_gdk_wayland_window_unexport_handle) {
+ s_gdk_wayland_window_unexport_handle(gdk_window);
+ }
+ }
+#endif
+ return mResult;
+}
+
+/**
+ * Send file descriptor of the file which contains document to the portal to
+ * finish the print operation.
+ */
+NS_IMETHODIMP
+nsFlatpakPrintPortal::Observe(nsISupports* aObject, const char* aTopic,
+ const char16_t* aData) {
+ // Check that written file match to the stored filename in case multiple
+ // print operations are in progress.
+ nsAutoString filenameStr;
+ mPrintAndPageSettings->GetToFileName(filenameStr);
+ if (!nsDependentString(aData).Equals(filenameStr)) {
+ // Different file is finished, not for this instance
+ return NS_OK;
+ }
+ int fd, idx;
+ fd = open(NS_ConvertUTF16toUTF8(filenameStr).get(), O_RDONLY | O_CLOEXEC);
+ static auto s_g_unix_fd_list_new = reinterpret_cast<GUnixFDList* (*)(void)>(
+ dlsym(RTLD_DEFAULT, "g_unix_fd_list_new"));
+ NS_ASSERTION(s_g_unix_fd_list_new,
+ "Cannot find g_unix_fd_list_new function.");
+
+ GUnixFDList* fd_list = s_g_unix_fd_list_new();
+ static auto s_g_unix_fd_list_append =
+ reinterpret_cast<gint (*)(GUnixFDList*, gint, GError**)>(
+ dlsym(RTLD_DEFAULT, "g_unix_fd_list_append"));
+ idx = s_g_unix_fd_list_append(fd_list, fd, NULL);
+ close(fd);
+
+ GVariantBuilder opt_builder;
+ g_variant_builder_init(&opt_builder, G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add(&opt_builder, "{sv}", "token",
+ g_variant_new_uint32(mToken));
+ g_dbus_proxy_call_with_unix_fd_list(
+ mProxy, "Print",
+ g_variant_new("(ssh@a{sv})", "", /* window */
+ "Print", /* title */
+ idx, g_variant_builder_end(&opt_builder)),
+ G_DBUS_CALL_FLAGS_NONE, -1, fd_list, NULL,
+ NULL, // TODO portal result cb function
+ nullptr); // data
+ g_object_unref(fd_list);
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ // Let the nsFlatpakPrintPortal instance die
+ os->RemoveObserver(this, "print-to-file-finished");
+ return NS_OK;
+}
+
+nsFlatpakPrintPortal::~nsFlatpakPrintPortal() {
+ if (mProxy) {
+ if (mResponseSignalId) {
+ g_dbus_connection_signal_unsubscribe(
+ g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)), mResponseSignalId);
+ }
+ g_object_unref(mProxy);
+ }
+ if (mLoop) g_main_loop_quit(mLoop);
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceGTK::Show(nsPIDOMWindowOuter* aParent,
+ nsIPrintSettings* aSettings) {
+ MOZ_ASSERT(aParent, "aParent must not be null");
+ MOZ_ASSERT(aSettings, "aSettings must not be null");
+
+ // Check for the flatpak portal first
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ bool shouldUsePortal;
+ giovfs->ShouldUseFlatpakPortal(&shouldUsePortal);
+ if (shouldUsePortal && gtk_check_version(3, 22, 0) == nullptr) {
+ nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
+ NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
+ GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
+ NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
+
+ nsCOMPtr<nsPrintSettingsGTK> printSettingsGTK(do_QueryInterface(aSettings));
+ RefPtr<nsFlatpakPrintPortal> fpPrintPortal =
+ new nsFlatpakPrintPortal(printSettingsGTK);
+
+ nsresult rv = fpPrintPortal->PreparePrintRequest(gtkParent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This blocks until nsFlatpakPrintPortal::FinishPrintDialog is called
+ GtkPrintOperationResult printDialogResult = fpPrintPortal->GetResult();
+
+ switch (printDialogResult) {
+ case GTK_PRINT_OPERATION_RESULT_APPLY: {
+ nsCOMPtr<nsIObserverService> os =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(os);
+ // Observer waits until notified that the file with the content
+ // to print has been written.
+ rv = os->AddObserver(fpPrintPortal, "print-to-file-finished", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ case GTK_PRINT_OPERATION_RESULT_CANCEL:
+ rv = NS_ERROR_ABORT;
+ break;
+ default:
+ NS_WARNING("Unexpected response");
+ rv = NS_ERROR_ABORT;
+ }
+ return rv;
+ }
+
+ nsPrintDialogWidgetGTK printDialog(aParent, aSettings);
+ nsresult rv = printDialog.ImportSettings(aSettings);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const gint response = printDialog.Run();
+
+ // Handle the result
+ switch (response) {
+ case GTK_RESPONSE_OK: // Proceed
+ rv = printDialog.ExportSettings(aSettings);
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_CLOSE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ case GTK_RESPONSE_NONE:
+ rv = NS_ERROR_ABORT;
+ break;
+
+ case GTK_RESPONSE_APPLY: // Print preview
+ default:
+ NS_WARNING("Unexpected response");
+ rv = NS_ERROR_ABORT;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceGTK::ShowPageSetup(nsPIDOMWindowOuter* aParent,
+ nsIPrintSettings* aNSSettings) {
+ MOZ_ASSERT(aParent, "aParent must not be null");
+ MOZ_ASSERT(aNSSettings, "aSettings must not be null");
+ NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
+ NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
+ GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
+ NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
+
+ nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
+ if (!aNSSettingsGTK) return NS_ERROR_FAILURE;
+
+ // We need to init the prefs here because aNSSettings in its current form is a
+ // dummy in both uses of the word
+ nsCOMPtr<nsIPrintSettingsService> psService =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ if (psService) {
+ nsString printName;
+ aNSSettings->GetPrinterName(printName);
+ if (printName.IsVoid()) {
+ psService->GetLastUsedPrinterName(printName);
+ aNSSettings->SetPrinterName(printName);
+ }
+ psService->InitPrintSettingsFromPrefs(aNSSettings, true,
+ nsIPrintSettings::kInitSaveAll);
+ }
+
+ // Frustratingly, gtk_print_run_page_setup_dialog doesn't tell us whether
+ // the user cancelled or confirmed the dialog! So to avoid needlessly
+ // refreshing the preview when Page Setup was cancelled, we compare the
+ // serializations of old and new settings; if they're the same, bail out.
+ GtkPrintSettings* gtkSettings = aNSSettingsGTK->GetGtkPrintSettings();
+ GtkPageSetup* oldPageSetup = aNSSettingsGTK->GetGtkPageSetup();
+ GKeyFile* oldKeyFile = g_key_file_new();
+ gtk_page_setup_to_key_file(oldPageSetup, oldKeyFile, nullptr);
+ gsize oldLength;
+ gchar* oldData = g_key_file_to_data(oldKeyFile, &oldLength, nullptr);
+ g_key_file_free(oldKeyFile);
+
+ GtkPageSetup* newPageSetup =
+ gtk_print_run_page_setup_dialog(gtkParent, oldPageSetup, gtkSettings);
+
+ GKeyFile* newKeyFile = g_key_file_new();
+ gtk_page_setup_to_key_file(newPageSetup, newKeyFile, nullptr);
+ gsize newLength;
+ gchar* newData = g_key_file_to_data(newKeyFile, &newLength, nullptr);
+ g_key_file_free(newKeyFile);
+
+ bool unchanged =
+ (oldLength == newLength && !memcmp(oldData, newData, oldLength));
+ g_free(oldData);
+ g_free(newData);
+ if (unchanged) {
+ g_object_unref(newPageSetup);
+ return NS_ERROR_ABORT;
+ }
+
+ aNSSettingsGTK->SetGtkPageSetup(newPageSetup);
+
+ // Now newPageSetup has a refcount of 2 (SetGtkPageSetup will addref), put it
+ // to 1 so if this gets replaced we don't leak.
+ g_object_unref(newPageSetup);
+
+ if (psService)
+ psService->SavePrintSettingsToPrefs(
+ aNSSettings, true,
+ nsIPrintSettings::kInitSaveOrientation |
+ nsIPrintSettings::kInitSavePaperSize |
+ nsIPrintSettings::kInitSaveUnwriteableMargins);
+
+ return NS_OK;
+}
diff --git a/widget/gtk/nsPrintDialogGTK.h b/widget/gtk/nsPrintDialogGTK.h
new file mode 100644
index 0000000000..83cd357992
--- /dev/null
+++ b/widget/gtk/nsPrintDialogGTK.h
@@ -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/. */
+
+#ifndef nsPrintDialog_h__
+#define nsPrintDialog_h__
+
+#include "nsIPrintDialogService.h"
+
+class nsIPrintSettings;
+
+// Copy the print pages enum here because not all versions
+// have SELECTION, which we will use
+typedef enum {
+ _GTK_PRINT_PAGES_ALL,
+ _GTK_PRINT_PAGES_CURRENT,
+ _GTK_PRINT_PAGES_RANGES,
+ _GTK_PRINT_PAGES_SELECTION
+} _GtkPrintPages;
+
+class nsPrintDialogServiceGTK : public nsIPrintDialogService {
+ virtual ~nsPrintDialogServiceGTK();
+
+ public:
+ nsPrintDialogServiceGTK();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init() override;
+ NS_IMETHOD Show(nsPIDOMWindowOuter* aParent,
+ nsIPrintSettings* aSettings) override;
+ NS_IMETHOD ShowPageSetup(nsPIDOMWindowOuter* aParent,
+ nsIPrintSettings* aSettings) override;
+};
+
+#endif
diff --git a/widget/gtk/nsPrintSettingsGTK.cpp b/widget/gtk/nsPrintSettingsGTK.cpp
new file mode 100644
index 0000000000..986e90ee4b
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsGTK.cpp
@@ -0,0 +1,685 @@
+/* -*- 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 "nsPrintSettingsGTK.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include <stdlib.h>
+#include <algorithm>
+
+static gboolean ref_printer(GtkPrinter* aPrinter, gpointer aData) {
+ ((nsPrintSettingsGTK*)aData)->SetGtkPrinter(aPrinter);
+ return TRUE;
+}
+
+static gboolean printer_enumerator(GtkPrinter* aPrinter, gpointer aData) {
+ if (gtk_printer_is_default(aPrinter)) return ref_printer(aPrinter, aData);
+
+ return FALSE; // Keep 'em coming...
+}
+
+static GtkPaperSize* moz_gtk_paper_size_copy_to_new_custom(
+ GtkPaperSize* oldPaperSize) {
+ // We make a "custom-ified" copy of the paper size so it can be changed later.
+ return gtk_paper_size_new_custom(
+ gtk_paper_size_get_name(oldPaperSize),
+ gtk_paper_size_get_display_name(oldPaperSize),
+ gtk_paper_size_get_width(oldPaperSize, GTK_UNIT_INCH),
+ gtk_paper_size_get_height(oldPaperSize, GTK_UNIT_INCH), GTK_UNIT_INCH);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsGTK, nsPrintSettings,
+ nsPrintSettingsGTK)
+
+/** ---------------------------------------------------
+ */
+nsPrintSettingsGTK::nsPrintSettingsGTK()
+ : mPageSetup(nullptr), mPrintSettings(nullptr), mGTKPrinter(nullptr) {
+ // The aim here is to set up the objects enough that silent printing works
+ // well. These will be replaced anyway if the print dialog is used.
+ mPrintSettings = gtk_print_settings_new();
+ GtkPageSetup* pageSetup = gtk_page_setup_new();
+ SetGtkPageSetup(pageSetup);
+ g_object_unref(pageSetup);
+
+ SetOutputFormat(nsIPrintSettings::kOutputFormatNative);
+}
+
+already_AddRefed<nsIPrintSettings> CreatePlatformPrintSettings(
+ const mozilla::PrintSettingsInitializer& aSettings) {
+ RefPtr<nsPrintSettings> settings = new nsPrintSettingsGTK();
+ settings->InitWithInitializer(aSettings);
+ settings->SetDefaultFileName();
+ return settings.forget();
+}
+
+/** ---------------------------------------------------
+ */
+nsPrintSettingsGTK::~nsPrintSettingsGTK() {
+ if (mPageSetup) {
+ g_object_unref(mPageSetup);
+ mPageSetup = nullptr;
+ }
+ if (mPrintSettings) {
+ g_object_unref(mPrintSettings);
+ mPrintSettings = nullptr;
+ }
+ if (mGTKPrinter) {
+ g_object_unref(mGTKPrinter);
+ mGTKPrinter = nullptr;
+ }
+}
+
+/** ---------------------------------------------------
+ */
+nsPrintSettingsGTK::nsPrintSettingsGTK(const nsPrintSettingsGTK& aPS)
+ : mPageSetup(nullptr), mPrintSettings(nullptr), mGTKPrinter(nullptr) {
+ *this = aPS;
+}
+
+/** ---------------------------------------------------
+ */
+nsPrintSettingsGTK& nsPrintSettingsGTK::operator=(
+ const nsPrintSettingsGTK& rhs) {
+ if (this == &rhs) {
+ return *this;
+ }
+
+ nsPrintSettings::operator=(rhs);
+
+ if (mPageSetup) g_object_unref(mPageSetup);
+ mPageSetup = gtk_page_setup_copy(rhs.mPageSetup);
+ // NOTE: No need to re-initialize mUnwriteableMargin here (even
+ // though mPageSetup is changing). It'll be copied correctly by
+ // nsPrintSettings::operator=.
+
+ if (mPrintSettings) g_object_unref(mPrintSettings);
+ mPrintSettings = gtk_print_settings_copy(rhs.mPrintSettings);
+
+ if (mGTKPrinter) g_object_unref(mGTKPrinter);
+ mGTKPrinter = (GtkPrinter*)g_object_ref(rhs.mGTKPrinter);
+
+ return *this;
+}
+
+/** -------------------------------------------
+ */
+nsresult nsPrintSettingsGTK::_Clone(nsIPrintSettings** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ nsPrintSettingsGTK* newSettings = new nsPrintSettingsGTK(*this);
+ if (!newSettings) return NS_ERROR_FAILURE;
+ *_retval = newSettings;
+ NS_ADDREF(*_retval);
+ return NS_OK;
+}
+
+/** -------------------------------------------
+ */
+NS_IMETHODIMP
+nsPrintSettingsGTK::_Assign(nsIPrintSettings* aPS) {
+ nsPrintSettingsGTK* printSettingsGTK = static_cast<nsPrintSettingsGTK*>(aPS);
+ if (!printSettingsGTK) return NS_ERROR_UNEXPECTED;
+ *this = *printSettingsGTK;
+ return NS_OK;
+}
+
+/** ---------------------------------------------------
+ */
+void nsPrintSettingsGTK::SetGtkPageSetup(GtkPageSetup* aPageSetup) {
+ if (mPageSetup) g_object_unref(mPageSetup);
+
+ mPageSetup = (GtkPageSetup*)g_object_ref(aPageSetup);
+ InitUnwriteableMargin();
+
+ // If the paper size is not custom, then we make a custom copy of the
+ // GtkPaperSize, so it can be mutable. If a GtkPaperSize wasn't made as
+ // custom, its properties are immutable.
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(aPageSetup);
+ if (!gtk_paper_size_is_custom(paperSize)) {
+ GtkPaperSize* customPaperSize =
+ moz_gtk_paper_size_copy_to_new_custom(paperSize);
+ gtk_page_setup_set_paper_size(mPageSetup, customPaperSize);
+ gtk_paper_size_free(customPaperSize);
+ }
+ SaveNewPageSize();
+}
+
+/** ---------------------------------------------------
+ */
+void nsPrintSettingsGTK::SetGtkPrintSettings(GtkPrintSettings* aPrintSettings) {
+ if (mPrintSettings) g_object_unref(mPrintSettings);
+
+ mPrintSettings = (GtkPrintSettings*)g_object_ref(aPrintSettings);
+
+ GtkPaperSize* paperSize = gtk_print_settings_get_paper_size(aPrintSettings);
+ if (paperSize) {
+ GtkPaperSize* customPaperSize =
+ moz_gtk_paper_size_copy_to_new_custom(paperSize);
+ gtk_paper_size_free(paperSize);
+ gtk_page_setup_set_paper_size(mPageSetup, customPaperSize);
+ gtk_paper_size_free(customPaperSize);
+ } else {
+ // paperSize was null, and so we add the paper size in the GtkPageSetup to
+ // the settings.
+ SaveNewPageSize();
+ }
+}
+
+/** ---------------------------------------------------
+ */
+void nsPrintSettingsGTK::SetGtkPrinter(GtkPrinter* aPrinter) {
+ if (mGTKPrinter) g_object_unref(mGTKPrinter);
+
+ mGTKPrinter = (GtkPrinter*)g_object_ref(aPrinter);
+}
+
+NS_IMETHODIMP nsPrintSettingsGTK::GetOutputFormat(int16_t* aOutputFormat) {
+ NS_ENSURE_ARG_POINTER(aOutputFormat);
+
+ int16_t format;
+ nsresult rv = nsPrintSettings::GetOutputFormat(&format);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (format == nsIPrintSettings::kOutputFormatNative &&
+ GTK_IS_PRINTER(mGTKPrinter)) {
+ if (gtk_printer_accepts_pdf(mGTKPrinter)) {
+ format = nsIPrintSettings::kOutputFormatPDF;
+ } else {
+ format = nsIPrintSettings::kOutputFormatPS;
+ }
+ }
+
+ *aOutputFormat = format;
+ return NS_OK;
+}
+
+/**
+ * Reimplementation of nsPrintSettings functions so that we get the values
+ * from the GTK objects rather than our own variables.
+ */
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPageRanges(const nsTArray<int32_t>& aRanges) {
+ if (aRanges.Length() % 2 != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ gtk_print_settings_set_print_pages(
+ mPrintSettings,
+ aRanges.IsEmpty() ? GTK_PRINT_PAGES_ALL : GTK_PRINT_PAGES_RANGES);
+
+ nsTArray<GtkPageRange> ranges;
+ ranges.SetCapacity(aRanges.Length() / 2);
+ for (size_t i = 0; i < aRanges.Length(); i += 2) {
+ GtkPageRange* gtkRange = ranges.AppendElement();
+ gtkRange->start = aRanges[i] - 1;
+ gtkRange->end = aRanges[i + 1] - 1;
+ }
+
+ gtk_print_settings_set_page_ranges(mPrintSettings, ranges.Elements(),
+ ranges.Length());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPrintReversed(bool* aPrintReversed) {
+ *aPrintReversed = gtk_print_settings_get_reverse(mPrintSettings);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPrintReversed(bool aPrintReversed) {
+ gtk_print_settings_set_reverse(mPrintSettings, aPrintReversed);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPrintInColor(bool* aPrintInColor) {
+ *aPrintInColor = gtk_print_settings_get_use_color(mPrintSettings);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPrintInColor(bool aPrintInColor) {
+ gtk_print_settings_set_use_color(mPrintSettings, aPrintInColor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetOrientation(int32_t* aOrientation) {
+ NS_ENSURE_ARG_POINTER(aOrientation);
+
+ GtkPageOrientation gtkOrient = gtk_page_setup_get_orientation(mPageSetup);
+ switch (gtkOrient) {
+ case GTK_PAGE_ORIENTATION_LANDSCAPE:
+ case GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE:
+ *aOrientation = kLandscapeOrientation;
+ break;
+
+ case GTK_PAGE_ORIENTATION_PORTRAIT:
+ case GTK_PAGE_ORIENTATION_REVERSE_PORTRAIT:
+ default:
+ *aOrientation = kPortraitOrientation;
+ }
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetOrientation(int32_t aOrientation) {
+ GtkPageOrientation gtkOrient;
+ if (aOrientation == kLandscapeOrientation)
+ gtkOrient = GTK_PAGE_ORIENTATION_LANDSCAPE;
+ else
+ gtkOrient = GTK_PAGE_ORIENTATION_PORTRAIT;
+
+ gtk_print_settings_set_orientation(mPrintSettings, gtkOrient);
+ gtk_page_setup_set_orientation(mPageSetup, gtkOrient);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetToFileName(nsAString& aToFileName) {
+ // Get the gtk output filename
+ const char* gtk_output_uri =
+ gtk_print_settings_get(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_URI);
+ if (!gtk_output_uri) {
+ aToFileName = mToFileName;
+ return NS_OK;
+ }
+
+ // Convert to an nsIFile
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_GetFileFromURLSpec(nsDependentCString(gtk_output_uri),
+ getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ // Extract the path
+ return file->GetPath(aToFileName);
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetToFileName(const nsAString& aToFileName) {
+ if (aToFileName.IsEmpty()) {
+ mToFileName.SetLength(0);
+ gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_URI,
+ nullptr);
+ return NS_OK;
+ }
+
+ gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT,
+ "pdf");
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_NewLocalFile(aToFileName, true, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Convert the nsIFile to a URL
+ nsAutoCString url;
+ rv = NS_GetURLSpecFromFile(file, url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_URI,
+ url.get());
+ mToFileName = aToFileName;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPrinterName(nsAString& aPrinter) {
+ const char* gtkPrintName = gtk_print_settings_get_printer(mPrintSettings);
+ if (!gtkPrintName) {
+ if (GTK_IS_PRINTER(mGTKPrinter)) {
+ gtkPrintName = gtk_printer_get_name(mGTKPrinter);
+ } else {
+ // This mimics what nsPrintSettingsImpl does when we try to Get before we
+ // Set
+ aPrinter.Truncate();
+ return NS_OK;
+ }
+ }
+ CopyUTF8toUTF16(mozilla::MakeStringSpan(gtkPrintName), aPrinter);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPrinterName(const nsAString& aPrinter) {
+ NS_ConvertUTF16toUTF8 gtkPrinter(aPrinter);
+
+ if (StringBeginsWith(gtkPrinter, "CUPS/"_ns)) {
+ // Strip off "CUPS/"; GTK might recognize the rest
+ gtkPrinter.Cut(0, strlen("CUPS/"));
+ }
+
+ // Give mPrintSettings the passed-in printer name if either...
+ // - it has no printer name stored yet
+ // - it has an existing printer name that's different from
+ // the name passed to this function.
+ const char* oldPrinterName = gtk_print_settings_get_printer(mPrintSettings);
+ if (!oldPrinterName || !gtkPrinter.Equals(oldPrinterName)) {
+ mIsInitedFromPrinter = false;
+ mIsInitedFromPrefs = false;
+ gtk_print_settings_set_printer(mPrintSettings, gtkPrinter.get());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetNumCopies(int32_t* aNumCopies) {
+ NS_ENSURE_ARG_POINTER(aNumCopies);
+ *aNumCopies = gtk_print_settings_get_n_copies(mPrintSettings);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetNumCopies(int32_t aNumCopies) {
+ gtk_print_settings_set_n_copies(mPrintSettings, aNumCopies);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetScaling(double* aScaling) {
+ *aScaling = gtk_print_settings_get_scale(mPrintSettings) / 100.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetScaling(double aScaling) {
+ gtk_print_settings_set_scale(mPrintSettings, aScaling * 100.0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPaperId(nsAString& aPaperId) {
+ const gchar* name =
+ gtk_paper_size_get_name(gtk_page_setup_get_paper_size(mPageSetup));
+ CopyUTF8toUTF16(mozilla::MakeStringSpan(name), aPaperId);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPaperId(const nsAString& aPaperId) {
+ NS_ConvertUTF16toUTF8 gtkPaperName(aPaperId);
+
+ // Convert these Gecko names to GTK names
+ // XXX (jfkthame): is this still relevant?
+ if (gtkPaperName.EqualsIgnoreCase("letter"))
+ gtkPaperName.AssignLiteral(GTK_PAPER_NAME_LETTER);
+ else if (gtkPaperName.EqualsIgnoreCase("legal"))
+ gtkPaperName.AssignLiteral(GTK_PAPER_NAME_LEGAL);
+
+ GtkPaperSize* oldPaperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ gdouble width = gtk_paper_size_get_width(oldPaperSize, GTK_UNIT_INCH);
+ gdouble height = gtk_paper_size_get_height(oldPaperSize, GTK_UNIT_INCH);
+
+ // Try to get the display name from the name so our paper size fits in the
+ // Page Setup dialog.
+ GtkPaperSize* paperSize = gtk_paper_size_new(gtkPaperName.get());
+ GtkPaperSize* customPaperSize = gtk_paper_size_new_custom(
+ gtkPaperName.get(), gtk_paper_size_get_display_name(paperSize), width,
+ height, GTK_UNIT_INCH);
+ gtk_paper_size_free(paperSize);
+
+ gtk_page_setup_set_paper_size(mPageSetup, customPaperSize);
+ gtk_paper_size_free(customPaperSize);
+ SaveNewPageSize();
+ return NS_OK;
+}
+
+GtkUnit nsPrintSettingsGTK::GetGTKUnit(int16_t aGeckoUnit) {
+ if (aGeckoUnit == kPaperSizeMillimeters)
+ return GTK_UNIT_MM;
+ else
+ return GTK_UNIT_INCH;
+}
+
+void nsPrintSettingsGTK::SaveNewPageSize() {
+ gtk_print_settings_set_paper_size(mPrintSettings,
+ gtk_page_setup_get_paper_size(mPageSetup));
+}
+
+void nsPrintSettingsGTK::InitUnwriteableMargin() {
+ mUnwriteableMargin.SizeTo(
+ NS_INCHES_TO_INT_TWIPS(
+ gtk_page_setup_get_top_margin(mPageSetup, GTK_UNIT_INCH)),
+ NS_INCHES_TO_INT_TWIPS(
+ gtk_page_setup_get_right_margin(mPageSetup, GTK_UNIT_INCH)),
+ NS_INCHES_TO_INT_TWIPS(
+ gtk_page_setup_get_bottom_margin(mPageSetup, GTK_UNIT_INCH)),
+ NS_INCHES_TO_INT_TWIPS(
+ gtk_page_setup_get_left_margin(mPageSetup, GTK_UNIT_INCH)));
+}
+
+/**
+ * NOTE: Need a custom set of SetUnwriteableMargin functions, because
+ * whenever we change mUnwriteableMargin, we must pass the change
+ * down to our GTKPageSetup object. (This is needed in order for us
+ * to give the correct default values in nsPrintDialogGTK.)
+ *
+ * It's important that the following functions pass
+ * mUnwriteableMargin values rather than aUnwriteableMargin values
+ * to gtk_page_setup_set_[blank]_margin, because the two may not be
+ * the same. (Specifically, negative values of aUnwriteableMargin
+ * are ignored by the nsPrintSettings::SetUnwriteableMargin functions.)
+ */
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginInTwips(
+ nsIntMargin& aUnwriteableMargin) {
+ nsPrintSettings::SetUnwriteableMarginInTwips(aUnwriteableMargin);
+ gtk_page_setup_set_top_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.top), GTK_UNIT_INCH);
+ gtk_page_setup_set_left_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.left), GTK_UNIT_INCH);
+ gtk_page_setup_set_bottom_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.bottom), GTK_UNIT_INCH);
+ gtk_page_setup_set_right_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.right), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginTop(double aUnwriteableMarginTop) {
+ nsPrintSettings::SetUnwriteableMarginTop(aUnwriteableMarginTop);
+ gtk_page_setup_set_top_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.top), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginLeft(double aUnwriteableMarginLeft) {
+ nsPrintSettings::SetUnwriteableMarginLeft(aUnwriteableMarginLeft);
+ gtk_page_setup_set_left_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.left), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginBottom(
+ double aUnwriteableMarginBottom) {
+ nsPrintSettings::SetUnwriteableMarginBottom(aUnwriteableMarginBottom);
+ gtk_page_setup_set_bottom_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.bottom), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginRight(double aUnwriteableMarginRight) {
+ nsPrintSettings::SetUnwriteableMarginRight(aUnwriteableMarginRight);
+ gtk_page_setup_set_right_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.right), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPaperWidth(double* aPaperWidth) {
+ NS_ENSURE_ARG_POINTER(aPaperWidth);
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ *aPaperWidth =
+ gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit));
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPaperWidth(double aPaperWidth) {
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ gtk_paper_size_set_size(
+ paperSize, aPaperWidth,
+ gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit)),
+ GetGTKUnit(mPaperSizeUnit));
+ SaveNewPageSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPaperHeight(double* aPaperHeight) {
+ NS_ENSURE_ARG_POINTER(aPaperHeight);
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ *aPaperHeight =
+ gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit));
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPaperHeight(double aPaperHeight) {
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ gtk_paper_size_set_size(
+ paperSize,
+ gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit)),
+ aPaperHeight, GetGTKUnit(mPaperSizeUnit));
+ SaveNewPageSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPaperSizeUnit(int16_t aPaperSizeUnit) {
+ // Convert units internally. e.g. they might have set the values while we're
+ // still in mm but they change to inch just afterwards, expecting that their
+ // sizes are in inches.
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ gtk_paper_size_set_size(
+ paperSize,
+ gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit)),
+ gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit)),
+ GetGTKUnit(aPaperSizeUnit));
+ SaveNewPageSize();
+
+ mPaperSizeUnit = aPaperSizeUnit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetEffectivePageSize(double* aWidth, double* aHeight) {
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ if (mPaperSizeUnit == kPaperSizeInches) {
+ *aWidth =
+ NS_INCHES_TO_TWIPS(gtk_paper_size_get_width(paperSize, GTK_UNIT_INCH));
+ *aHeight =
+ NS_INCHES_TO_TWIPS(gtk_paper_size_get_height(paperSize, GTK_UNIT_INCH));
+ } else {
+ MOZ_ASSERT(mPaperSizeUnit == kPaperSizeMillimeters,
+ "unexpected paper size unit");
+ *aWidth = NS_MILLIMETERS_TO_TWIPS(
+ gtk_paper_size_get_width(paperSize, GTK_UNIT_MM));
+ *aHeight = NS_MILLIMETERS_TO_TWIPS(
+ gtk_paper_size_get_height(paperSize, GTK_UNIT_MM));
+ }
+ GtkPageOrientation gtkOrient = gtk_page_setup_get_orientation(mPageSetup);
+
+ if (gtkOrient == GTK_PAGE_ORIENTATION_LANDSCAPE ||
+ gtkOrient == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE) {
+ double temp = *aWidth;
+ *aWidth = *aHeight;
+ *aHeight = temp;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetupSilentPrinting() {
+ // We have to get a printer here, rather than when the print settings are
+ // constructed. This is because when we request sync, GTK makes us wait in the
+ // *event loop* while waiting for the enumeration to finish. We must do this
+ // when event loop runs are expected.
+ gtk_enumerate_printers(printer_enumerator, this, nullptr, TRUE);
+
+ // XXX If no default printer set, get the first one.
+ if (!GTK_IS_PRINTER(mGTKPrinter))
+ gtk_enumerate_printers(ref_printer, this, nullptr, TRUE);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPageRanges(nsTArray<int32_t>& aPages) {
+ GtkPrintPages gtkRange = gtk_print_settings_get_print_pages(mPrintSettings);
+ if (gtkRange != GTK_PRINT_PAGES_RANGES) {
+ aPages.Clear();
+ return NS_OK;
+ }
+
+ gint ctRanges;
+ GtkPageRange* lstRanges =
+ gtk_print_settings_get_page_ranges(mPrintSettings, &ctRanges);
+
+ aPages.Clear();
+
+ for (gint i = 0; i < ctRanges; i++) {
+ aPages.AppendElement(lstRanges[i].start + 1);
+ aPages.AppendElement(lstRanges[i].end + 1);
+ }
+
+ g_free(lstRanges);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetResolution(int32_t* aResolution) {
+ if (!gtk_print_settings_has_key(mPrintSettings,
+ GTK_PRINT_SETTINGS_RESOLUTION))
+ return NS_ERROR_FAILURE;
+ *aResolution = gtk_print_settings_get_resolution(mPrintSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetResolution(int32_t aResolution) {
+ gtk_print_settings_set_resolution(mPrintSettings, aResolution);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetDuplex(int32_t* aDuplex) {
+ if (!gtk_print_settings_has_key(mPrintSettings, GTK_PRINT_SETTINGS_DUPLEX)) {
+ *aDuplex = GTK_PRINT_DUPLEX_SIMPLEX;
+ } else {
+ *aDuplex = gtk_print_settings_get_duplex(mPrintSettings);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetDuplex(int32_t aDuplex) {
+ MOZ_ASSERT(aDuplex >= GTK_PRINT_DUPLEX_SIMPLEX &&
+ aDuplex <= GTK_PRINT_DUPLEX_VERTICAL,
+ "value is out of bounds for GtkPrintDuplex enum");
+ gtk_print_settings_set_duplex(mPrintSettings,
+ static_cast<GtkPrintDuplex>(aDuplex));
+
+ // We want to set the GTK CUPS Duplex setting in addition to calling
+ // gtk_print_settings_set_duplex(). Some systems may look for one, or the
+ // other, so it is best to set them both consistently.
+ constexpr char kCupsDuplex[] = "cups-Duplex";
+ switch (aDuplex) {
+ case GTK_PRINT_DUPLEX_SIMPLEX:
+ gtk_print_settings_set(mPrintSettings, kCupsDuplex, "None");
+ break;
+ case GTK_PRINT_DUPLEX_HORIZONTAL:
+ gtk_print_settings_set(mPrintSettings, kCupsDuplex, "DuplexNoTumble");
+ break;
+ case GTK_PRINT_DUPLEX_VERTICAL:
+ gtk_print_settings_set(mPrintSettings, kCupsDuplex, "DuplexTumble");
+ break;
+ }
+
+ return NS_OK;
+}
diff --git a/widget/gtk/nsPrintSettingsGTK.h b/widget/gtk/nsPrintSettingsGTK.h
new file mode 100644
index 0000000000..c32c594587
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsGTK.h
@@ -0,0 +1,145 @@
+/* -*- Mode: IDL; 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 nsPrintSettingsGTK_h_
+#define nsPrintSettingsGTK_h_
+
+#include "nsPrintSettingsImpl.h"
+
+extern "C" {
+#include <gtk/gtk.h>
+#include <gtk/gtkunixprint.h>
+}
+
+#define NS_PRINTSETTINGSGTK_IID \
+ { \
+ 0x758df520, 0xc7c3, 0x11dc, { \
+ 0x95, 0xff, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 \
+ } \
+ }
+
+//*****************************************************************************
+//*** nsPrintSettingsGTK
+//*****************************************************************************
+
+class nsPrintSettingsGTK : public nsPrintSettings {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PRINTSETTINGSGTK_IID)
+
+ nsPrintSettingsGTK();
+ explicit nsPrintSettingsGTK(const PrintSettingsInitializer& aSettings);
+
+ // We're overriding these methods because we want to read/write with GTK
+ // objects, not local variables. This allows a simpler settings implementation
+ // between Gecko and GTK.
+
+ GtkPageSetup* GetGtkPageSetup() { return mPageSetup; };
+ void SetGtkPageSetup(GtkPageSetup* aPageSetup);
+
+ GtkPrintSettings* GetGtkPrintSettings() { return mPrintSettings; };
+ void SetGtkPrintSettings(GtkPrintSettings* aPrintSettings);
+
+ GtkPrinter* GetGtkPrinter() { return mGTKPrinter; };
+ void SetGtkPrinter(GtkPrinter* aPrinter);
+
+ // Reversed, color, orientation and file name are all stored in the
+ // GtkPrintSettings. Orientation is also stored in the GtkPageSetup and its
+ // setting takes priority when getting the orientation.
+ NS_IMETHOD GetPrintReversed(bool* aPrintReversed) override;
+ NS_IMETHOD SetPrintReversed(bool aPrintReversed) override;
+
+ NS_IMETHOD GetPrintInColor(bool* aPrintInColor) override;
+ NS_IMETHOD SetPrintInColor(bool aPrintInColor) override;
+
+ NS_IMETHOD GetOrientation(int32_t* aOrientation) override;
+ NS_IMETHOD SetOrientation(int32_t aOrientation) override;
+
+ NS_IMETHOD GetToFileName(nsAString& aToFileName) override;
+ NS_IMETHOD SetToFileName(const nsAString& aToFileName) override;
+
+ // Gets/Sets the printer name in the GtkPrintSettings. If no printer name is
+ // specified there, you will get back the name of the current internal
+ // GtkPrinter.
+ NS_IMETHOD GetPrinterName(nsAString& Printer) override;
+ NS_IMETHOD SetPrinterName(const nsAString& aPrinter) override;
+
+ // Number of copies is stored/gotten from the GtkPrintSettings.
+ NS_IMETHOD GetNumCopies(int32_t* aNumCopies) override;
+ NS_IMETHOD SetNumCopies(int32_t aNumCopies) override;
+
+ NS_IMETHOD GetScaling(double* aScaling) override;
+ NS_IMETHOD SetScaling(double aScaling) override;
+
+ // A name recognised by GTK is strongly advised here, as this is used to
+ // create a GtkPaperSize.
+ NS_IMETHOD GetPaperId(nsAString& aPaperId) override;
+ NS_IMETHOD SetPaperId(const nsAString& aPaperId) override;
+
+ NS_IMETHOD SetUnwriteableMarginInTwips(
+ nsIntMargin& aUnwriteableMargin) override;
+ NS_IMETHOD SetUnwriteableMarginTop(double aUnwriteableMarginTop) override;
+ NS_IMETHOD SetUnwriteableMarginLeft(double aUnwriteableMarginLeft) override;
+ NS_IMETHOD SetUnwriteableMarginBottom(
+ double aUnwriteableMarginBottom) override;
+ NS_IMETHOD SetUnwriteableMarginRight(double aUnwriteableMarginRight) override;
+
+ NS_IMETHOD GetPaperWidth(double* aPaperWidth) override;
+ NS_IMETHOD SetPaperWidth(double aPaperWidth) override;
+
+ NS_IMETHOD GetPaperHeight(double* aPaperHeight) override;
+ NS_IMETHOD SetPaperHeight(double aPaperHeight) override;
+
+ NS_IMETHOD SetPaperSizeUnit(int16_t aPaperSizeUnit) override;
+
+ NS_IMETHOD GetEffectivePageSize(double* aWidth, double* aHeight) override;
+
+ NS_IMETHOD SetupSilentPrinting() override;
+
+ NS_IMETHOD SetPageRanges(const nsTArray<int32_t>&) override;
+ NS_IMETHOD GetPageRanges(nsTArray<int32_t>&) override;
+
+ NS_IMETHOD GetResolution(int32_t* aResolution) override;
+ NS_IMETHOD SetResolution(int32_t aResolution) override;
+
+ NS_IMETHOD GetDuplex(int32_t* aDuplex) override;
+ NS_IMETHOD SetDuplex(int32_t aDuplex) override;
+
+ NS_IMETHOD GetOutputFormat(int16_t* aOutputFormat) override;
+
+ protected:
+ virtual ~nsPrintSettingsGTK();
+
+ nsPrintSettingsGTK(const nsPrintSettingsGTK& src);
+ nsPrintSettingsGTK& operator=(const nsPrintSettingsGTK& rhs);
+
+ virtual nsresult _Clone(nsIPrintSettings** _retval) override;
+ virtual nsresult _Assign(nsIPrintSettings* aPS) override;
+
+ GtkUnit GetGTKUnit(int16_t aGeckoUnit);
+ void SaveNewPageSize();
+
+ /**
+ * Re-initialize mUnwriteableMargin with values from mPageSetup.
+ * Should be called whenever mPageSetup is initialized or overwritten.
+ */
+ void InitUnwriteableMargin();
+
+ /**
+ * On construction:
+ * - mPrintSettings and mPageSetup are just new objects with defaults
+ * determined by GTK.
+ * - mGTKPrinter is nullptr!!! Remember to be careful when accessing this
+ * property.
+ */
+ GtkPageSetup* mPageSetup;
+ GtkPrintSettings* mPrintSettings;
+ GtkPrinter* mGTKPrinter;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPrintSettingsGTK, NS_PRINTSETTINGSGTK_IID)
+
+#endif // nsPrintSettingsGTK_h_
diff --git a/widget/gtk/nsPrintSettingsServiceGTK.cpp b/widget/gtk/nsPrintSettingsServiceGTK.cpp
new file mode 100644
index 0000000000..7894101831
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsServiceGTK.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPrintSettingsServiceGTK.h"
+#include "nsPrintSettingsGTK.h"
+
+using namespace mozilla::embedding;
+
+static void serialize_gtk_printsettings_to_printdata(const gchar* key,
+ const gchar* value,
+ gpointer aData) {
+ PrintData* data = (PrintData*)aData;
+ CStringKeyValue pair;
+ pair.key() = key;
+ pair.value() = value;
+ data->GTKPrintSettings().AppendElement(pair);
+}
+
+NS_IMETHODIMP
+nsPrintSettingsServiceGTK::SerializeToPrintData(nsIPrintSettings* aSettings,
+ PrintData* data) {
+ nsresult rv = nsPrintSettingsService::SerializeToPrintData(aSettings, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsPrintSettingsGTK> settingsGTK(do_QueryInterface(aSettings));
+ NS_ENSURE_STATE(settingsGTK);
+
+ GtkPrintSettings* gtkPrintSettings = settingsGTK->GetGtkPrintSettings();
+ NS_ENSURE_STATE(gtkPrintSettings);
+
+ gtk_print_settings_foreach(gtkPrintSettings,
+ serialize_gtk_printsettings_to_printdata, data);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsServiceGTK::DeserializeToPrintSettings(
+ const PrintData& data, nsIPrintSettings* settings) {
+ nsCOMPtr<nsPrintSettingsGTK> settingsGTK(do_QueryInterface(settings));
+ NS_ENSURE_STATE(settingsGTK);
+
+ nsresult rv =
+ nsPrintSettingsService::DeserializeToPrintSettings(data, settings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Instead of re-using the GtkPrintSettings that nsIPrintSettings is
+ // wrapping, we'll create a new one to deserialize to and replace it
+ // within nsIPrintSettings.
+ GtkPrintSettings* newGtkPrintSettings = gtk_print_settings_new();
+
+ for (uint32_t i = 0; i < data.GTKPrintSettings().Length(); ++i) {
+ CStringKeyValue pair = data.GTKPrintSettings()[i];
+ gtk_print_settings_set(newGtkPrintSettings, pair.key().get(),
+ pair.value().get());
+ }
+
+ settingsGTK->SetGtkPrintSettings(newGtkPrintSettings);
+
+ // nsPrintSettingsGTK is holding a reference to newGtkPrintSettings
+ g_object_unref(newGtkPrintSettings);
+ newGtkPrintSettings = nullptr;
+ return NS_OK;
+}
+
+nsresult nsPrintSettingsServiceGTK::_CreatePrintSettings(
+ nsIPrintSettings** _retval) {
+ *_retval = nullptr;
+ nsPrintSettingsGTK* printSettings =
+ new nsPrintSettingsGTK(); // does not initially ref count
+ NS_ENSURE_TRUE(printSettings, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ADDREF(*_retval = printSettings); // ref count
+
+ return NS_OK;
+}
diff --git a/widget/gtk/nsPrintSettingsServiceGTK.h b/widget/gtk/nsPrintSettingsServiceGTK.h
new file mode 100644
index 0000000000..d26f543110
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsServiceGTK.h
@@ -0,0 +1,33 @@
+/* -*- Mode: IDL; 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 nsPrintSettingsServiceGTK_h
+#define nsPrintSettingsServiceGTK_h
+
+#include "nsPrintSettingsService.h"
+
+namespace mozilla {
+namespace embedding {
+class PrintData;
+} // namespace embedding
+} // namespace mozilla
+
+class nsPrintSettingsServiceGTK final : public nsPrintSettingsService {
+ public:
+ nsPrintSettingsServiceGTK() = default;
+
+ NS_IMETHODIMP SerializeToPrintData(
+ nsIPrintSettings* aSettings,
+ mozilla::embedding::PrintData* data) override;
+
+ NS_IMETHODIMP DeserializeToPrintSettings(
+ const mozilla::embedding::PrintData& data,
+ nsIPrintSettings* settings) override;
+
+ virtual nsresult _CreatePrintSettings(nsIPrintSettings** _retval) override;
+};
+
+#endif // nsPrintSettingsServiceGTK_h
diff --git a/widget/gtk/nsSound.cpp b/widget/gtk/nsSound.cpp
new file mode 100644
index 0000000000..4d685d8566
--- /dev/null
+++ b/widget/gtk/nsSound.cpp
@@ -0,0 +1,398 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include <string.h>
+
+#include "nscore.h"
+#include "plstr.h"
+#include "prlink.h"
+
+#include "nsSound.h"
+
+#include "HeadlessSound.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsDirectoryService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WidgetUtils.h"
+#include "nsIXULAppInfo.h"
+#include "nsContentUtils.h"
+#include "gfxPlatform.h"
+#include "mozilla/ClearOnShutdown.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <gtk/gtk.h>
+static PRLibrary* libcanberra = nullptr;
+
+/* used to play sounds with libcanberra. */
+typedef struct _ca_context ca_context;
+typedef struct _ca_proplist ca_proplist;
+
+typedef void (*ca_finish_callback_t)(ca_context* c, uint32_t id, int error_code,
+ void* userdata);
+
+typedef int (*ca_context_create_fn)(ca_context**);
+typedef int (*ca_context_destroy_fn)(ca_context*);
+typedef int (*ca_context_play_fn)(ca_context* c, uint32_t id, ...);
+typedef int (*ca_context_change_props_fn)(ca_context* c, ...);
+typedef int (*ca_proplist_create_fn)(ca_proplist**);
+typedef int (*ca_proplist_destroy_fn)(ca_proplist*);
+typedef int (*ca_proplist_sets_fn)(ca_proplist* c, const char* key,
+ const char* value);
+typedef int (*ca_context_play_full_fn)(ca_context* c, uint32_t id,
+ ca_proplist* p, ca_finish_callback_t cb,
+ void* userdata);
+
+static ca_context_create_fn ca_context_create;
+static ca_context_destroy_fn ca_context_destroy;
+static ca_context_play_fn ca_context_play;
+static ca_context_change_props_fn ca_context_change_props;
+static ca_proplist_create_fn ca_proplist_create;
+static ca_proplist_destroy_fn ca_proplist_destroy;
+static ca_proplist_sets_fn ca_proplist_sets;
+static ca_context_play_full_fn ca_context_play_full;
+
+struct ScopedCanberraFile {
+ explicit ScopedCanberraFile(nsIFile* file) : mFile(file){};
+
+ ~ScopedCanberraFile() {
+ if (mFile) {
+ mFile->Remove(false);
+ }
+ }
+
+ void forget() { mozilla::Unused << mFile.forget(); }
+ nsIFile* operator->() { return mFile; }
+ operator nsIFile*() { return mFile; }
+
+ nsCOMPtr<nsIFile> mFile;
+};
+
+static ca_context* ca_context_get_default() {
+ // This allows us to avoid race conditions with freeing the context by handing
+ // that responsibility to Glib, and still use one context at a time
+ static GPrivate ctx_private =
+ G_PRIVATE_INIT((GDestroyNotify)ca_context_destroy);
+
+ ca_context* ctx = (ca_context*)g_private_get(&ctx_private);
+
+ if (ctx) {
+ return ctx;
+ }
+
+ ca_context_create(&ctx);
+ if (!ctx) {
+ return nullptr;
+ }
+
+ g_private_set(&ctx_private, ctx);
+
+ GtkSettings* settings = gtk_settings_get_default();
+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
+ "gtk-sound-theme-name")) {
+ gchar* sound_theme_name = nullptr;
+ g_object_get(settings, "gtk-sound-theme-name", &sound_theme_name, nullptr);
+
+ if (sound_theme_name) {
+ ca_context_change_props(ctx, "canberra.xdg-theme.name", sound_theme_name,
+ nullptr);
+ g_free(sound_theme_name);
+ }
+ }
+
+ nsAutoString wbrand;
+ mozilla::widget::WidgetUtils::GetBrandShortName(wbrand);
+ ca_context_change_props(ctx, "application.name",
+ NS_ConvertUTF16toUTF8(wbrand).get(), nullptr);
+
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1");
+ if (appInfo) {
+ nsAutoCString version;
+ appInfo->GetVersion(version);
+
+ ca_context_change_props(ctx, "application.version", version.get(), nullptr);
+ }
+
+ ca_context_change_props(ctx, "application.icon_name", MOZ_APP_NAME, nullptr);
+
+ return ctx;
+}
+
+static void ca_finish_cb(ca_context* c, uint32_t id, int error_code,
+ void* userdata) {
+ nsIFile* file = reinterpret_cast<nsIFile*>(userdata);
+ if (file) {
+ file->Remove(false);
+ NS_RELEASE(file);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver)
+
+////////////////////////////////////////////////////////////////////////
+nsSound::nsSound() { mInited = false; }
+
+nsSound::~nsSound() = default;
+
+NS_IMETHODIMP
+nsSound::Init() {
+ // This function is designed so that no library is compulsory, and
+ // one library missing doesn't cause the other(s) to not be used.
+ if (mInited) return NS_OK;
+
+ mInited = true;
+
+ if (!libcanberra) {
+ libcanberra = PR_LoadLibrary("libcanberra.so.0");
+ if (libcanberra) {
+ ca_context_create = (ca_context_create_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_context_create");
+ if (!ca_context_create) {
+#ifdef MOZ_TSAN
+ // With TSan, we cannot unload libcanberra once we have loaded it
+ // because TSan does not support unloading libraries that are matched
+ // from its suppression list. Hence we just keep the library loaded in
+ // TSan builds.
+ libcanberra = nullptr;
+ return NS_OK;
+#endif
+ PR_UnloadLibrary(libcanberra);
+ libcanberra = nullptr;
+ } else {
+ // at this point we know we have a good libcanberra library
+ ca_context_destroy = (ca_context_destroy_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_context_destroy");
+ ca_context_play = (ca_context_play_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_context_play");
+ ca_context_change_props =
+ (ca_context_change_props_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_context_change_props");
+ ca_proplist_create = (ca_proplist_create_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_proplist_create");
+ ca_proplist_destroy = (ca_proplist_destroy_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_proplist_destroy");
+ ca_proplist_sets = (ca_proplist_sets_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_proplist_sets");
+ ca_context_play_full = (ca_context_play_full_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_context_play_full");
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+/* static */
+void nsSound::Shutdown() {
+#ifndef MOZ_TSAN
+ if (libcanberra) {
+ PR_UnloadLibrary(libcanberra);
+ libcanberra = nullptr;
+ }
+#endif
+}
+
+namespace mozilla {
+namespace sound {
+StaticRefPtr<nsISound> sInstance;
+}
+} // namespace mozilla
+/* static */
+already_AddRefed<nsISound> nsSound::GetInstance() {
+ using namespace mozilla::sound;
+
+ if (!sInstance) {
+ if (gfxPlatform::IsHeadless()) {
+ sInstance = new mozilla::widget::HeadlessSound();
+ } else {
+ sInstance = new nsSound();
+ }
+ ClearOnShutdown(&sInstance);
+ }
+
+ RefPtr<nsISound> service = sInstance.get();
+ return service.forget();
+}
+
+NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* context, nsresult aStatus,
+ uint32_t dataLen, const uint8_t* data) {
+ // print a load error on bad status, and return
+ if (NS_FAILED(aStatus)) {
+#ifdef DEBUG
+ if (aLoader) {
+ nsCOMPtr<nsIRequest> request;
+ aLoader->GetRequest(getter_AddRefs(request));
+ if (request) {
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel) {
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ printf("Failed to load %s\n", uri->GetSpecOrDefault().get());
+ }
+ }
+ }
+ }
+#endif
+ return aStatus;
+ }
+
+ nsCOMPtr<nsIFile> tmpFile;
+ nsDirectoryService::gService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(tmpFile));
+
+ nsresult rv =
+ tmpFile->AppendNative(nsDependentCString("mozilla_audio_sample"));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, PR_IRUSR | PR_IWUSR);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ ScopedCanberraFile canberraFile(tmpFile);
+
+ mozilla::AutoFDClose fd;
+ rv = canberraFile->OpenNSPRFileDesc(PR_WRONLY, PR_IRUSR | PR_IWUSR,
+ &fd.rwget());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // XXX: Should we do this on another thread?
+ uint32_t length = dataLen;
+ while (length > 0) {
+ int32_t amount = PR_Write(fd, data, length);
+ if (amount < 0) {
+ return NS_ERROR_FAILURE;
+ }
+ length -= amount;
+ data += amount;
+ }
+
+ ca_context* ctx = ca_context_get_default();
+ if (!ctx) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ ca_proplist* p;
+ ca_proplist_create(&p);
+ if (!p) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsAutoCString path;
+ rv = canberraFile->GetNativePath(path);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ ca_proplist_sets(p, "media.filename", path.get());
+ if (ca_context_play_full(ctx, 0, p, ca_finish_cb, canberraFile) >= 0) {
+ // Don't delete the temporary file here if ca_context_play_full succeeds
+ canberraFile.forget();
+ }
+ ca_proplist_destroy(p);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSound::Beep() {
+ ::gdk_beep();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSound::Play(nsIURL* aURL) {
+ if (!mInited) Init();
+
+ if (!libcanberra) return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv;
+ if (aURL->SchemeIs("file")) {
+ ca_context* ctx = ca_context_get_default();
+ if (!ctx) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsAutoCString spec;
+ rv = aURL->GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ gchar* path = g_filename_from_uri(spec.get(), nullptr, nullptr);
+ if (!path) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ ca_context_play(ctx, 0, "media.filename", path, nullptr);
+ g_free(path);
+ } else {
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(
+ getter_AddRefs(loader), aURL,
+ this, // aObserver
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId) {
+ if (!mInited) Init();
+
+ if (!libcanberra) return NS_OK;
+
+ // Do we even want alert sounds?
+ GtkSettings* settings = gtk_settings_get_default();
+
+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
+ "gtk-enable-event-sounds")) {
+ gboolean enable_sounds = TRUE;
+ g_object_get(settings, "gtk-enable-event-sounds", &enable_sounds, nullptr);
+
+ if (!enable_sounds) {
+ return NS_OK;
+ }
+ }
+
+ ca_context* ctx = ca_context_get_default();
+ if (!ctx) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ switch (aEventId) {
+ case EVENT_ALERT_DIALOG_OPEN:
+ ca_context_play(ctx, 0, "event.id", "dialog-warning", nullptr);
+ break;
+ case EVENT_CONFIRM_DIALOG_OPEN:
+ ca_context_play(ctx, 0, "event.id", "dialog-question", nullptr);
+ break;
+ case EVENT_NEW_MAIL_RECEIVED:
+ ca_context_play(ctx, 0, "event.id", "message-new-email", nullptr);
+ break;
+ case EVENT_MENU_EXECUTE:
+ ca_context_play(ctx, 0, "event.id", "menu-click", nullptr);
+ break;
+ case EVENT_MENU_POPUP:
+ ca_context_play(ctx, 0, "event.id", "menu-popup", nullptr);
+ break;
+ }
+ return NS_OK;
+}
diff --git a/widget/gtk/nsSound.h b/widget/gtk/nsSound.h
new file mode 100644
index 0000000000..8f4fe5a04b
--- /dev/null
+++ b/widget/gtk/nsSound.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __nsSound_h__
+#define __nsSound_h__
+
+#include "nsISound.h"
+#include "nsIStreamLoader.h"
+
+#include <gtk/gtk.h>
+
+class nsSound : public nsISound, public nsIStreamLoaderObserver {
+ public:
+ nsSound();
+
+ static void Shutdown();
+ static already_AddRefed<nsISound> GetInstance();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISOUND
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+ private:
+ virtual ~nsSound();
+
+ bool mInited;
+};
+
+#endif /* __nsSound_h__ */
diff --git a/widget/gtk/nsToolkit.cpp b/widget/gtk/nsToolkit.cpp
new file mode 100644
index 0000000000..9b4be6fb93
--- /dev/null
+++ b/widget/gtk/nsToolkit.cpp
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nscore.h" // needed for 'nullptr'
+#include "nsGTKToolkit.h"
+
+nsGTKToolkit* nsGTKToolkit::gToolkit = nullptr;
+
+//-------------------------------------------------------------------------
+//
+// constructor
+//
+//-------------------------------------------------------------------------
+nsGTKToolkit::nsGTKToolkit() : mFocusTimestamp(0) {}
+
+//-------------------------------------------------------------------------------
+// Return the toolkit. If a toolkit does not yet exist, then one will be
+// created.
+//-------------------------------------------------------------------------------
+// static
+nsGTKToolkit* nsGTKToolkit::GetToolkit() {
+ if (!gToolkit) {
+ gToolkit = new nsGTKToolkit();
+ }
+
+ return gToolkit;
+}
diff --git a/widget/gtk/nsUserIdleServiceGTK.cpp b/widget/gtk/nsUserIdleServiceGTK.cpp
new file mode 100644
index 0000000000..882ded8cad
--- /dev/null
+++ b/widget/gtk/nsUserIdleServiceGTK.cpp
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include <gtk/gtk.h>
+
+#include "nsUserIdleServiceGTK.h"
+#include "nsDebug.h"
+#include "prlink.h"
+#include "mozilla/Logging.h"
+
+using mozilla::LogLevel;
+
+static mozilla::LazyLogModule sIdleLog("nsIUserIdleService");
+
+typedef bool (*_XScreenSaverQueryExtension_fn)(Display* dpy, int* event_base,
+ int* error_base);
+
+typedef XScreenSaverInfo* (*_XScreenSaverAllocInfo_fn)(void);
+
+typedef void (*_XScreenSaverQueryInfo_fn)(Display* dpy, Drawable drw,
+ XScreenSaverInfo* info);
+
+static bool sInitialized = false;
+static _XScreenSaverQueryExtension_fn _XSSQueryExtension = nullptr;
+static _XScreenSaverAllocInfo_fn _XSSAllocInfo = nullptr;
+static _XScreenSaverQueryInfo_fn _XSSQueryInfo = nullptr;
+
+static void Initialize() {
+ if (!gdk_display_get_default() ||
+ !GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ return;
+ }
+
+ // This will leak - See comments in ~nsUserIdleServiceGTK().
+ PRLibrary* xsslib = PR_LoadLibrary("libXss.so.1");
+ if (!xsslib) // ouch.
+ {
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to find libXss.so!\n"));
+ return;
+ }
+
+ _XSSQueryExtension = (_XScreenSaverQueryExtension_fn)PR_FindFunctionSymbol(
+ xsslib, "XScreenSaverQueryExtension");
+ _XSSAllocInfo = (_XScreenSaverAllocInfo_fn)PR_FindFunctionSymbol(
+ xsslib, "XScreenSaverAllocInfo");
+ _XSSQueryInfo = (_XScreenSaverQueryInfo_fn)PR_FindFunctionSymbol(
+ xsslib, "XScreenSaverQueryInfo");
+
+ if (!_XSSQueryExtension)
+ MOZ_LOG(sIdleLog, LogLevel::Warning,
+ ("Failed to get XSSQueryExtension!\n"));
+ if (!_XSSAllocInfo)
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to get XSSAllocInfo!\n"));
+ if (!_XSSQueryInfo)
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to get XSSQueryInfo!\n"));
+
+ sInitialized = true;
+}
+
+nsUserIdleServiceGTK::nsUserIdleServiceGTK() : mXssInfo(nullptr) {
+ Initialize();
+}
+
+nsUserIdleServiceGTK::~nsUserIdleServiceGTK() {
+ if (mXssInfo) XFree(mXssInfo);
+
+// It is not safe to unload libXScrnSaver until each display is closed because
+// the library registers callbacks through XESetCloseDisplay (Bug 397607).
+// (Also the library and its functions are scoped for the file not the object.)
+#if 0
+ if (xsslib) {
+ PR_UnloadLibrary(xsslib);
+ xsslib = nullptr;
+ }
+#endif
+}
+
+bool nsUserIdleServiceGTK::PollIdleTime(uint32_t* aIdleTime) {
+ if (!sInitialized) {
+ // For some reason, we could not find xscreensaver.
+ return false;
+ }
+
+ // Ask xscreensaver about idle time:
+ *aIdleTime = 0;
+
+ // We might not have a display (cf. in xpcshell)
+ Display* dplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+ if (!dplay) {
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("No display found!\n"));
+ return false;
+ }
+
+ if (!_XSSQueryExtension || !_XSSAllocInfo || !_XSSQueryInfo) {
+ return false;
+ }
+
+ int event_base, error_base;
+ if (_XSSQueryExtension(dplay, &event_base, &error_base)) {
+ if (!mXssInfo) mXssInfo = _XSSAllocInfo();
+ if (!mXssInfo) return false;
+ _XSSQueryInfo(dplay, GDK_ROOT_WINDOW(), mXssInfo);
+ *aIdleTime = mXssInfo->idle;
+ return true;
+ }
+ // If we get here, we couldn't get to XScreenSaver:
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("XSSQueryExtension returned false!\n"));
+ return false;
+}
+
+bool nsUserIdleServiceGTK::UsePollMode() { return sInitialized; }
diff --git a/widget/gtk/nsUserIdleServiceGTK.h b/widget/gtk/nsUserIdleServiceGTK.h
new file mode 100644
index 0000000000..9b9ba31846
--- /dev/null
+++ b/widget/gtk/nsUserIdleServiceGTK.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsUserIdleServiceGTK_h__
+#define nsUserIdleServiceGTK_h__
+
+#include "nsUserIdleService.h"
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <gdk/gdkx.h>
+
+typedef struct {
+ Window window; // Screen saver window
+ int state; // ScreenSaver(Off,On,Disabled)
+ int kind; // ScreenSaver(Blanked,Internal,External)
+ unsigned long til_or_since; // milliseconds since/til screensaver kicks in
+ unsigned long idle; // milliseconds idle
+ unsigned long event_mask; // event stuff
+} XScreenSaverInfo;
+
+class nsUserIdleServiceGTK : public nsUserIdleService {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsUserIdleServiceGTK, nsUserIdleService)
+
+ virtual bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsUserIdleServiceGTK> GetInstance() {
+ RefPtr<nsUserIdleServiceGTK> idleService =
+ nsUserIdleService::GetInstance().downcast<nsUserIdleServiceGTK>();
+ if (!idleService) {
+ idleService = new nsUserIdleServiceGTK();
+ }
+
+ return idleService.forget();
+ }
+
+ private:
+ ~nsUserIdleServiceGTK();
+ XScreenSaverInfo* mXssInfo;
+
+ protected:
+ nsUserIdleServiceGTK();
+ virtual bool UsePollMode() override;
+};
+
+#endif // nsUserIdleServiceGTK_h__
diff --git a/widget/gtk/nsWaylandDisplay.cpp b/widget/gtk/nsWaylandDisplay.cpp
new file mode 100644
index 0000000000..310917a27b
--- /dev/null
+++ b/widget/gtk/nsWaylandDisplay.cpp
@@ -0,0 +1,309 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsWaylandDisplay.h"
+
+#include "base/message_loop.h" // for MessageLoop
+#include "base/task.h" // for NewRunnableMethod, etc
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPrefs_widget.h"
+
+namespace mozilla {
+namespace widget {
+
+// nsWaylandDisplay needs to be created for each calling thread(main thread,
+// compositor thread and render thread)
+#define MAX_DISPLAY_CONNECTIONS 10
+
+// An array of active wayland displays. We need a display for every thread
+// where is wayland interface used as we need to dispatch waylands events
+// there.
+static RefPtr<nsWaylandDisplay> gWaylandDisplays[MAX_DISPLAY_CONNECTIONS];
+static StaticMutex gWaylandDisplayArrayWriteMutex;
+
+// Dispatch events to Compositor/Render queues
+void WaylandDispatchDisplays() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "WaylandDispatchDisplays() is supposed to run in main thread");
+ for (auto& display : gWaylandDisplays) {
+ if (display) {
+ display->DispatchEventQueue();
+ }
+ }
+}
+
+void WaylandDisplayRelease() {
+ StaticMutexAutoLock lock(gWaylandDisplayArrayWriteMutex);
+ for (auto& display : gWaylandDisplays) {
+ if (display) {
+ display = nullptr;
+ }
+ }
+}
+
+// Get WaylandDisplay for given wl_display and actual calling thread.
+RefPtr<nsWaylandDisplay> WaylandDisplayGet(GdkDisplay* aGdkDisplay) {
+ wl_display* waylandDisplay = WaylandDisplayGetWLDisplay(aGdkDisplay);
+ if (!waylandDisplay) {
+ return nullptr;
+ }
+
+ // Search existing display connections for wl_display:thread combination.
+ for (auto& display : gWaylandDisplays) {
+ if (display && display->Matches(waylandDisplay)) {
+ return display;
+ }
+ }
+
+ StaticMutexAutoLock arrayLock(gWaylandDisplayArrayWriteMutex);
+ for (auto& display : gWaylandDisplays) {
+ if (display == nullptr) {
+ display = new nsWaylandDisplay(waylandDisplay);
+ return display;
+ }
+ }
+
+ MOZ_CRASH("There's too many wayland display conections!");
+ return nullptr;
+}
+
+wl_display* WaylandDisplayGetWLDisplay(GdkDisplay* aGdkDisplay) {
+ if (!aGdkDisplay) {
+ aGdkDisplay = gdk_display_get_default();
+ if (!aGdkDisplay || GDK_IS_X11_DISPLAY(aGdkDisplay)) {
+ return nullptr;
+ }
+ }
+
+ return gdk_wayland_display_get_wl_display(aGdkDisplay);
+}
+
+void nsWaylandDisplay::SetShm(wl_shm* aShm) { mShm = aShm; }
+
+void nsWaylandDisplay::SetCompositor(wl_compositor* aCompositor) {
+ mCompositor = aCompositor;
+}
+
+void nsWaylandDisplay::SetSubcompositor(wl_subcompositor* aSubcompositor) {
+ mSubcompositor = aSubcompositor;
+}
+
+void nsWaylandDisplay::SetDataDeviceManager(
+ wl_data_device_manager* aDataDeviceManager) {
+ mDataDeviceManager = aDataDeviceManager;
+}
+
+void nsWaylandDisplay::SetSeat(wl_seat* aSeat) { mSeat = aSeat; }
+
+void nsWaylandDisplay::SetPrimarySelectionDeviceManager(
+ gtk_primary_selection_device_manager* aPrimarySelectionDeviceManager) {
+ mPrimarySelectionDeviceManagerGtk = aPrimarySelectionDeviceManager;
+}
+
+void nsWaylandDisplay::SetPrimarySelectionDeviceManager(
+ zwp_primary_selection_device_manager_v1* aPrimarySelectionDeviceManager) {
+ mPrimarySelectionDeviceManagerZwpV1 = aPrimarySelectionDeviceManager;
+}
+
+void nsWaylandDisplay::SetIdleInhibitManager(
+ zwp_idle_inhibit_manager_v1* aIdleInhibitManager) {
+ mIdleInhibitManager = aIdleInhibitManager;
+}
+
+static void global_registry_handler(void* data, wl_registry* registry,
+ uint32_t id, const char* interface,
+ uint32_t version) {
+ auto* display = static_cast<nsWaylandDisplay*>(data);
+ if (!display) {
+ return;
+ }
+
+ if (strcmp(interface, "wl_shm") == 0) {
+ auto* shm = WaylandRegistryBind<wl_shm>(registry, id, &wl_shm_interface, 1);
+ wl_proxy_set_queue((struct wl_proxy*)shm, display->GetEventQueue());
+ display->SetShm(shm);
+ } else if (strcmp(interface, "wl_data_device_manager") == 0) {
+ int data_device_manager_version = MIN(version, 3);
+ auto* data_device_manager = WaylandRegistryBind<wl_data_device_manager>(
+ registry, id, &wl_data_device_manager_interface,
+ data_device_manager_version);
+ wl_proxy_set_queue((struct wl_proxy*)data_device_manager,
+ display->GetEventQueue());
+ display->SetDataDeviceManager(data_device_manager);
+ } else if (strcmp(interface, "wl_seat") == 0) {
+ auto* seat =
+ WaylandRegistryBind<wl_seat>(registry, id, &wl_seat_interface, 1);
+ wl_proxy_set_queue((struct wl_proxy*)seat, display->GetEventQueue());
+ display->SetSeat(seat);
+ } else if (strcmp(interface, "gtk_primary_selection_device_manager") == 0) {
+ auto* primary_selection_device_manager =
+ WaylandRegistryBind<gtk_primary_selection_device_manager>(
+ registry, id, &gtk_primary_selection_device_manager_interface, 1);
+ wl_proxy_set_queue((struct wl_proxy*)primary_selection_device_manager,
+ display->GetEventQueue());
+ display->SetPrimarySelectionDeviceManager(primary_selection_device_manager);
+ } else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") ==
+ 0) {
+ auto* primary_selection_device_manager =
+ WaylandRegistryBind<gtk_primary_selection_device_manager>(
+ registry, id, &zwp_primary_selection_device_manager_v1_interface,
+ 1);
+ wl_proxy_set_queue((struct wl_proxy*)primary_selection_device_manager,
+ display->GetEventQueue());
+ display->SetPrimarySelectionDeviceManager(primary_selection_device_manager);
+ } else if (strcmp(interface, "zwp_idle_inhibit_manager_v1") == 0) {
+ auto* idle_inhibit_manager =
+ WaylandRegistryBind<zwp_idle_inhibit_manager_v1>(
+ registry, id, &zwp_idle_inhibit_manager_v1_interface, 1);
+ wl_proxy_set_queue((struct wl_proxy*)idle_inhibit_manager,
+ display->GetEventQueue());
+ display->SetIdleInhibitManager(idle_inhibit_manager);
+ } else if (strcmp(interface, "wl_compositor") == 0) {
+ // Requested wl_compositor version 4 as we need wl_surface_damage_buffer().
+ auto* compositor = WaylandRegistryBind<wl_compositor>(
+ registry, id, &wl_compositor_interface, 4);
+ wl_proxy_set_queue((struct wl_proxy*)compositor, display->GetEventQueue());
+ display->SetCompositor(compositor);
+ } else if (strcmp(interface, "wl_subcompositor") == 0) {
+ auto* subcompositor = WaylandRegistryBind<wl_subcompositor>(
+ registry, id, &wl_subcompositor_interface, 1);
+ wl_proxy_set_queue((struct wl_proxy*)subcompositor,
+ display->GetEventQueue());
+ display->SetSubcompositor(subcompositor);
+ }
+}
+
+static void global_registry_remover(void* data, wl_registry* registry,
+ uint32_t id) {}
+
+static const struct wl_registry_listener registry_listener = {
+ global_registry_handler, global_registry_remover};
+
+bool nsWaylandDisplay::DispatchEventQueue() {
+ if (mEventQueue) {
+ wl_display_dispatch_queue_pending(mDisplay, mEventQueue);
+ }
+ return true;
+}
+
+void nsWaylandDisplay::SyncEnd() {
+ wl_callback_destroy(mSyncCallback);
+ mSyncCallback = nullptr;
+}
+
+static void wayland_sync_callback(void* data, struct wl_callback* callback,
+ uint32_t time) {
+ auto display = static_cast<nsWaylandDisplay*>(data);
+ display->SyncEnd();
+}
+
+static const struct wl_callback_listener sync_callback_listener = {
+ .done = wayland_sync_callback};
+
+void nsWaylandDisplay::SyncBegin() {
+ WaitForSyncEnd();
+
+ // Use wl_display_sync() to synchronize wayland events.
+ // See dri2_wl_swap_buffers_with_damage() from MESA
+ // or wl_display_roundtrip_queue() from wayland-client.
+ struct wl_display* displayWrapper =
+ static_cast<wl_display*>(wl_proxy_create_wrapper((void*)mDisplay));
+ if (!displayWrapper) {
+ NS_WARNING("Failed to create wl_proxy wrapper!");
+ return;
+ }
+
+ wl_proxy_set_queue((struct wl_proxy*)displayWrapper, mEventQueue);
+ mSyncCallback = wl_display_sync(displayWrapper);
+ wl_proxy_wrapper_destroy((void*)displayWrapper);
+
+ if (!mSyncCallback) {
+ NS_WARNING("Failed to create wl_display_sync callback!");
+ return;
+ }
+
+ wl_callback_add_listener(mSyncCallback, &sync_callback_listener, this);
+ wl_display_flush(mDisplay);
+}
+
+void nsWaylandDisplay::QueueSyncBegin() {
+ RefPtr<nsWaylandDisplay> self(this);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("nsWaylandDisplay::QueueSyncBegin",
+ [self]() -> void { self->SyncBegin(); }));
+}
+
+void nsWaylandDisplay::WaitForSyncEnd() {
+ // We're done here
+ if (!mSyncCallback) {
+ return;
+ }
+
+ while (mSyncCallback != nullptr) {
+ // TODO: wl_display_dispatch_queue() should not be called while
+ // glib main loop is iterated at nsAppShell::ProcessNextNativeEvent().
+ if (wl_display_dispatch_queue(mDisplay, mEventQueue) == -1) {
+ NS_WARNING("wl_display_dispatch_queue failed!");
+ SyncEnd();
+ return;
+ }
+ }
+}
+
+bool nsWaylandDisplay::Matches(wl_display* aDisplay) {
+ return mThreadId == PR_GetCurrentThread() && aDisplay == mDisplay;
+}
+
+nsWaylandDisplay::nsWaylandDisplay(wl_display* aDisplay, bool aLighWrapper)
+ : mThreadId(PR_GetCurrentThread()),
+ mDisplay(aDisplay),
+ mEventQueue(nullptr),
+ mDataDeviceManager(nullptr),
+ mCompositor(nullptr),
+ mSubcompositor(nullptr),
+ mSeat(nullptr),
+ mShm(nullptr),
+ mSyncCallback(nullptr),
+ mPrimarySelectionDeviceManagerGtk(nullptr),
+ mPrimarySelectionDeviceManagerZwpV1(nullptr),
+ mIdleInhibitManager(nullptr),
+ mRegistry(nullptr),
+ mExplicitSync(false) {
+ if (!aLighWrapper) {
+ mRegistry = wl_display_get_registry(mDisplay);
+ wl_registry_add_listener(mRegistry, &registry_listener, this);
+ }
+
+ if (!NS_IsMainThread()) {
+ mEventQueue = wl_display_create_queue(mDisplay);
+ wl_proxy_set_queue((struct wl_proxy*)mRegistry, mEventQueue);
+ }
+
+ if (!aLighWrapper) {
+ if (mEventQueue) {
+ wl_display_roundtrip_queue(mDisplay, mEventQueue);
+ wl_display_roundtrip_queue(mDisplay, mEventQueue);
+ } else {
+ wl_display_roundtrip(mDisplay);
+ wl_display_roundtrip(mDisplay);
+ }
+ }
+}
+
+nsWaylandDisplay::~nsWaylandDisplay() {
+ wl_registry_destroy(mRegistry);
+ mRegistry = nullptr;
+
+ if (mEventQueue) {
+ wl_event_queue_destroy(mEventQueue);
+ mEventQueue = nullptr;
+ }
+ mDisplay = nullptr;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/nsWaylandDisplay.h b/widget/gtk/nsWaylandDisplay.h
new file mode 100644
index 0000000000..6beb7ef684
--- /dev/null
+++ b/widget/gtk/nsWaylandDisplay.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __MOZ_WAYLAND_DISPLAY_H__
+#define __MOZ_WAYLAND_DISPLAY_H__
+
+#include "DMABufLibWrapper.h"
+
+#include "mozilla/widget/mozwayland.h"
+#include "mozilla/widget/gbm.h"
+#include "mozilla/widget/gtk-primary-selection-client-protocol.h"
+#include "mozilla/widget/idle-inhibit-unstable-v1-client-protocol.h"
+#include "mozilla/widget/linux-dmabuf-unstable-v1-client-protocol.h"
+#include "mozilla/widget/primary-selection-unstable-v1-client-protocol.h"
+
+namespace mozilla {
+namespace widget {
+
+// Our general connection to Wayland display server,
+// holds our display connection and runs event loop.
+// We have a global nsWaylandDisplay object for each thread.
+class nsWaylandDisplay {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsWaylandDisplay)
+
+ // Create nsWaylandDisplay object on top of native Wayland wl_display
+ // connection. When aLighWrapper is set we don't get wayland registry
+ // objects and only event loop is provided.
+ explicit nsWaylandDisplay(wl_display* aDisplay, bool aLighWrapper = false);
+
+ bool DispatchEventQueue();
+
+ void SyncBegin();
+ void QueueSyncBegin();
+ void SyncEnd();
+ void WaitForSyncEnd();
+
+ bool Matches(wl_display* aDisplay);
+
+ wl_display* GetDisplay() { return mDisplay; };
+ wl_event_queue* GetEventQueue() { return mEventQueue; };
+ wl_compositor* GetCompositor(void) { return mCompositor; };
+ wl_subcompositor* GetSubcompositor(void) { return mSubcompositor; };
+ wl_data_device_manager* GetDataDeviceManager(void) {
+ return mDataDeviceManager;
+ };
+ wl_seat* GetSeat(void) { return mSeat; };
+ wl_shm* GetShm(void) { return mShm; };
+ gtk_primary_selection_device_manager* GetPrimarySelectionDeviceManagerGtk(
+ void) {
+ return mPrimarySelectionDeviceManagerGtk;
+ };
+ zwp_primary_selection_device_manager_v1*
+ GetPrimarySelectionDeviceManagerZwpV1(void) {
+ return mPrimarySelectionDeviceManagerZwpV1;
+ };
+ zwp_idle_inhibit_manager_v1* GetIdleInhibitManager(void) {
+ return mIdleInhibitManager;
+ }
+
+ bool IsMainThreadDisplay() { return mEventQueue == nullptr; }
+
+ void SetShm(wl_shm* aShm);
+ void SetCompositor(wl_compositor* aCompositor);
+ void SetSubcompositor(wl_subcompositor* aSubcompositor);
+ void SetDataDeviceManager(wl_data_device_manager* aDataDeviceManager);
+ void SetSeat(wl_seat* aSeat);
+ void SetPrimarySelectionDeviceManager(
+ gtk_primary_selection_device_manager* aPrimarySelectionDeviceManager);
+ void SetPrimarySelectionDeviceManager(
+ zwp_primary_selection_device_manager_v1* aPrimarySelectionDeviceManager);
+ void SetIdleInhibitManager(zwp_idle_inhibit_manager_v1* aIdleInhibitManager);
+
+ bool IsExplicitSyncEnabled() { return mExplicitSync; }
+
+ private:
+ ~nsWaylandDisplay();
+
+ PRThread* mThreadId;
+ wl_display* mDisplay;
+ wl_event_queue* mEventQueue;
+ wl_data_device_manager* mDataDeviceManager;
+ wl_compositor* mCompositor;
+ wl_subcompositor* mSubcompositor;
+ wl_seat* mSeat;
+ wl_shm* mShm;
+ wl_callback* mSyncCallback;
+ gtk_primary_selection_device_manager* mPrimarySelectionDeviceManagerGtk;
+ zwp_primary_selection_device_manager_v1* mPrimarySelectionDeviceManagerZwpV1;
+ zwp_idle_inhibit_manager_v1* mIdleInhibitManager;
+ wl_registry* mRegistry;
+ bool mExplicitSync;
+};
+
+void WaylandDispatchDisplays();
+void WaylandDisplayRelease();
+
+RefPtr<nsWaylandDisplay> WaylandDisplayGet(GdkDisplay* aGdkDisplay = nullptr);
+wl_display* WaylandDisplayGetWLDisplay(GdkDisplay* aGdkDisplay = nullptr);
+
+} // namespace widget
+} // namespace mozilla
+
+template <class T>
+static inline T* WaylandRegistryBind(struct wl_registry* wl_registry,
+ uint32_t name,
+ const struct wl_interface* interface,
+ uint32_t version) {
+ struct wl_proxy* id;
+
+ // When libwayland-client does not provide this symbol, it will be
+ // linked to the fallback in libmozwayland, which returns NULL.
+ id = wl_proxy_marshal_constructor_versioned(
+ (struct wl_proxy*)wl_registry, WL_REGISTRY_BIND, interface, version, name,
+ interface->name, version, nullptr);
+
+ if (id == nullptr) {
+ id = wl_proxy_marshal_constructor((struct wl_proxy*)wl_registry,
+ WL_REGISTRY_BIND, interface, name,
+ interface->name, version, nullptr);
+ }
+
+ return reinterpret_cast<T*>(id);
+}
+
+#endif // __MOZ_WAYLAND_DISPLAY_H__
diff --git a/widget/gtk/nsWidgetFactory.cpp b/widget/gtk/nsWidgetFactory.cpp
new file mode 100644
index 0000000000..d649c7e0bf
--- /dev/null
+++ b/widget/gtk/nsWidgetFactory.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsWidgetFactory.h"
+
+#include "mozilla/Components.h"
+#include "mozilla/WidgetUtils.h"
+#include "NativeKeyBindings.h"
+#include "nsWidgetsCID.h"
+#include "nsAppShell.h"
+#include "nsAppShellSingleton.h"
+#include "nsBaseWidget.h"
+#include "nsGtkKeyUtils.h"
+#include "nsLookAndFeel.h"
+#include "nsWindow.h"
+#include "nsHTMLFormatConverter.h"
+#include "HeadlessClipboard.h"
+#include "IMContextWrapper.h"
+#ifdef MOZ_X11
+# include "nsClipboard.h"
+#endif
+#include "TaskbarProgress.h"
+#include "nsFilePicker.h"
+#include "nsSound.h"
+#include "nsGTKToolkit.h"
+#include "WakeLockListener.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/widget/ScreenManager.h"
+#include <gtk/gtk.h>
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#ifdef MOZ_X11
+NS_IMPL_COMPONENT_FACTORY(nsIClipboard) {
+ nsCOMPtr<nsIClipboard> inst;
+ if (gfxPlatform::IsHeadless()) {
+ inst = new HeadlessClipboard();
+ } else {
+ auto clipboard = MakeRefPtr<nsClipboard>();
+ if (NS_FAILED(clipboard->Init())) {
+ return nullptr;
+ }
+ inst = std::move(clipboard);
+ }
+
+ return inst.forget().downcast<nsISupports>();
+}
+#endif
+
+nsresult nsWidgetGtk2ModuleCtor() { return nsAppShellInit(); }
+
+void nsWidgetGtk2ModuleDtor() {
+ // Shutdown all XP level widget classes.
+ WidgetUtils::Shutdown();
+
+ NativeKeyBindings::Shutdown();
+ nsLookAndFeel::Shutdown();
+ nsFilePicker::Shutdown();
+ nsSound::Shutdown();
+ nsWindow::ReleaseGlobals();
+ IMContextWrapper::Shutdown();
+ KeymapWrapper::Shutdown();
+ nsGTKToolkit::Shutdown();
+ nsAppShellShutdown();
+#ifdef MOZ_ENABLE_DBUS
+ WakeLockListener::Shutdown();
+#endif
+}
diff --git a/widget/gtk/nsWidgetFactory.h b/widget/gtk/nsWidgetFactory.h
new file mode 100644
index 0000000000..21ea0da667
--- /dev/null
+++ b/widget/gtk/nsWidgetFactory.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 widget_gtk_nsWidgetFactory_h
+#define widget_gtk_nsWidgetFactory_h
+
+#include "nscore.h"
+#include "nsID.h"
+
+class nsISupports;
+
+nsresult nsAppShellConstructor(nsISupports* outer, const nsIID& iid,
+ void** result);
+
+nsresult nsWidgetGtk2ModuleCtor();
+void nsWidgetGtk2ModuleDtor();
+
+#endif // defined widget_gtk_nsWidgetFactory_h
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
new file mode 100644
index 0000000000..f435464f7a
--- /dev/null
+++ b/widget/gtk/nsWindow.cpp
@@ -0,0 +1,8420 @@
+/* -*- 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/. */
+
+#include "nsWindow.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/WidgetUtils.h"
+#include "mozilla/X11Util.h"
+#include "mozilla/XREAppData.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/WheelEventBinding.h"
+#include "InputData.h"
+#include "nsAppRunner.h"
+#include <algorithm>
+
+#include "GeckoProfiler.h"
+
+#include "prlink.h"
+#include "nsGTKToolkit.h"
+#include "nsIRollupListener.h"
+#include "nsINode.h"
+
+#include "nsWidgetsCID.h"
+#include "nsDragService.h"
+#include "nsIWidgetListener.h"
+#include "nsIScreenManager.h"
+#include "SystemTimeConverter.h"
+#include "nsViewManager.h"
+#include "nsMenuPopupFrame.h"
+#include "nsXPLookAndFeel.h"
+
+#include "nsGtkKeyUtils.h"
+#include "nsGtkCursors.h"
+#include "ScreenHelperGTK.h"
+
+#include <gtk/gtk.h>
+#include <gtk/gtkx.h>
+
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkwayland.h>
+#endif /* MOZ_WAYLAND */
+
+#ifdef MOZ_X11
+# include <gdk/gdkx.h>
+# include <X11/Xatom.h>
+# include <X11/extensions/XShm.h>
+# include <X11/extensions/shape.h>
+# include <gdk/gdkkeysyms-compat.h>
+#endif /* MOZ_X11 */
+
+#include <gdk/gdkkeysyms.h>
+
+#if defined(MOZ_WAYLAND)
+# include <gdk/gdkwayland.h>
+# include "nsView.h"
+#endif
+
+#include "nsGkAtoms.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Preferences.h"
+#include "nsGfxCIID.h"
+#include "nsGtkUtils.h"
+#include "nsLayoutUtils.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "nsIUserIdleServiceInternal.h"
+#include "GLContext.h"
+#include "gfx2DGlue.h"
+
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/Accessible.h"
+# include "mozilla/a11y/Platform.h"
+# include "nsAccessibilityService.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+#endif
+
+/* For SetIcon */
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsString.h"
+#include "nsIFile.h"
+
+/* SetCursor(imgIContainer*) */
+#include <gdk/gdk.h>
+#include <wchar.h>
+#include "imgIContainer.h"
+#include "nsGfxCIID.h"
+#include "nsImageToPixbuf.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "ClientLayerManager.h"
+#include "nsIGSettingsService.h"
+
+#include "gfxPlatformGtk.h"
+#include "gfxContext.h"
+#include "gfxImageSurface.h"
+#include "gfxUtils.h"
+#include "Layers.h"
+#include "GLContextProvider.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/KnowsCompositor.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+
+#include "mozilla/layers/APZInputBridge.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+
+#ifdef MOZ_X11
+# include "mozilla/gfx/gfxVars.h"
+# include "GLContextGLX.h" // for GLContextGLX::FindVisual()
+# include "GLContextEGL.h" // for GLContextEGL::FindVisual()
+# include "GtkCompositorWidget.h"
+# include "gfxXlibSurface.h"
+# include "WindowSurfaceX11Image.h"
+# include "WindowSurfaceX11SHM.h"
+# include "WindowSurfaceXRender.h"
+#endif // MOZ_X11
+#ifdef MOZ_WAYLAND
+# include "nsIClipboard.h"
+#endif
+
+#include "nsShmImage.h"
+#include "gtkdrawing.h"
+
+#include "NativeKeyBindings.h"
+
+#include <dlfcn.h>
+#include "nsPresContext.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+using namespace mozilla::layers;
+using mozilla::gl::GLContextEGL;
+using mozilla::gl::GLContextGLX;
+
+// 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
+
+#if !GTK_CHECK_VERSION(3, 18, 0)
+
+struct _GdkEventTouchpadPinch {
+ GdkEventType type;
+ GdkWindow* window;
+ gint8 send_event;
+ gint8 phase;
+ gint8 n_fingers;
+ guint32 time;
+ gdouble x;
+ gdouble y;
+ gdouble dx;
+ gdouble dy;
+ gdouble angle_delta;
+ gdouble scale;
+ gdouble x_root, y_root;
+ guint state;
+};
+
+typedef enum {
+ GDK_TOUCHPAD_GESTURE_PHASE_BEGIN,
+ GDK_TOUCHPAD_GESTURE_PHASE_UPDATE,
+ GDK_TOUCHPAD_GESTURE_PHASE_END,
+ GDK_TOUCHPAD_GESTURE_PHASE_CANCEL
+} GdkTouchpadGesturePhase;
+
+GdkEventMask GDK_TOUCHPAD_GESTURE_MASK = static_cast<GdkEventMask>(1 << 24);
+GdkEventType GDK_TOUCHPAD_PINCH = static_cast<GdkEventType>(42);
+
+#endif
+
+const gint kEvents = GDK_TOUCHPAD_GESTURE_MASK | GDK_EXPOSURE_MASK |
+ GDK_STRUCTURE_MASK | GDK_VISIBILITY_NOTIFY_MASK |
+ GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_SMOOTH_SCROLL_MASK | GDK_TOUCH_MASK | GDK_SCROLL_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_PROPERTY_CHANGE_MASK;
+
+#if !GTK_CHECK_VERSION(3, 22, 0)
+typedef enum {
+ GDK_ANCHOR_FLIP_X = 1 << 0,
+ GDK_ANCHOR_FLIP_Y = 1 << 1,
+ GDK_ANCHOR_SLIDE_X = 1 << 2,
+ GDK_ANCHOR_SLIDE_Y = 1 << 3,
+ GDK_ANCHOR_RESIZE_X = 1 << 4,
+ GDK_ANCHOR_RESIZE_Y = 1 << 5,
+ GDK_ANCHOR_FLIP = GDK_ANCHOR_FLIP_X | GDK_ANCHOR_FLIP_Y,
+ GDK_ANCHOR_SLIDE = GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_SLIDE_Y,
+ GDK_ANCHOR_RESIZE = GDK_ANCHOR_RESIZE_X | GDK_ANCHOR_RESIZE_Y
+} GdkAnchorHints;
+#endif
+
+/* utility functions */
+static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
+ gdouble aMouseY);
+static nsWindow* get_window_for_gtk_widget(GtkWidget* widget);
+static nsWindow* get_window_for_gdk_window(GdkWindow* window);
+static GtkWidget* get_gtk_widget_for_gdk_window(GdkWindow* window);
+static GdkCursor* get_gtk_cursor(nsCursor aCursor);
+
+static GdkWindow* get_inner_gdk_window(GdkWindow* aWindow, gint x, gint y,
+ gint* retx, gint* rety);
+
+static int is_parent_ungrab_enter(GdkEventCrossing* aEvent);
+static int is_parent_grab_leave(GdkEventCrossing* aEvent);
+
+/* callbacks from widgets */
+static gboolean expose_event_cb(GtkWidget* widget, cairo_t* rect);
+static gboolean configure_event_cb(GtkWidget* widget, GdkEventConfigure* event);
+static void container_unrealize_cb(GtkWidget* widget);
+static void size_allocate_cb(GtkWidget* widget, GtkAllocation* allocation);
+static void toplevel_window_size_allocate_cb(GtkWidget* widget,
+ GtkAllocation* allocation);
+static gboolean delete_event_cb(GtkWidget* widget, GdkEventAny* event);
+static gboolean enter_notify_event_cb(GtkWidget* widget,
+ GdkEventCrossing* event);
+static gboolean leave_notify_event_cb(GtkWidget* widget,
+ GdkEventCrossing* event);
+static gboolean motion_notify_event_cb(GtkWidget* widget,
+ GdkEventMotion* event);
+static gboolean button_press_event_cb(GtkWidget* widget, GdkEventButton* event);
+static gboolean button_release_event_cb(GtkWidget* widget,
+ GdkEventButton* event);
+static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event);
+static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event);
+static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event);
+static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event);
+static gboolean property_notify_event_cb(GtkWidget* widget,
+ GdkEventProperty* event);
+static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event);
+
+static void hierarchy_changed_cb(GtkWidget* widget,
+ GtkWidget* previous_toplevel);
+static gboolean window_state_event_cb(GtkWidget* widget,
+ GdkEventWindowState* event);
+static void settings_changed_cb(GtkSettings* settings, GParamSpec* pspec,
+ nsWindow* data);
+static void settings_xft_dpi_changed_cb(GtkSettings* settings,
+ GParamSpec* pspec, nsWindow* data);
+static void check_resize_cb(GtkContainer* container, gpointer user_data);
+static void screen_composited_changed_cb(GdkScreen* screen, gpointer user_data);
+static void widget_composited_changed_cb(GtkWidget* widget, gpointer user_data);
+
+static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
+ gpointer aPointer);
+static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent);
+static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent);
+
+static nsWindow* GetFirstNSWindowForGDKWindow(GdkWindow* aGdkWindow);
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+#ifdef MOZ_X11
+static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
+ GdkEvent* event, gpointer data);
+#endif /* MOZ_X11 */
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+static gboolean drag_motion_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY, guint aTime, gpointer aData);
+static void drag_leave_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, guint aTime,
+ gpointer aData);
+static gboolean drag_drop_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY, guint aTime, gpointer aData);
+static void drag_data_received_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint32 aTime,
+ gpointer aData);
+
+/* initialization static functions */
+static nsresult initialize_prefs(void);
+
+static guint32 sLastUserInputTime = GDK_CURRENT_TIME;
+static guint32 sRetryGrabTime;
+
+static SystemTimeConverter<guint32>& TimeConverter() {
+ static SystemTimeConverter<guint32> sTimeConverterSingleton;
+ return sTimeConverterSingleton;
+}
+
+nsWindow::CSDSupportLevel nsWindow::sCSDSupportLevel = CSD_SUPPORT_UNKNOWN;
+bool nsWindow::sTransparentMainWindow = false;
+static bool sIgnoreChangedSettings = false;
+
+void nsWindow::WithSettingsChangesIgnored(const std::function<void()>& aFn) {
+ AutoRestore ar(sIgnoreChangedSettings);
+ sIgnoreChangedSettings = true;
+ aFn();
+}
+
+namespace mozilla {
+
+class CurrentX11TimeGetter {
+ public:
+ explicit CurrentX11TimeGetter(GdkWindow* aWindow)
+ : mWindow(aWindow), mAsyncUpdateStart() {}
+
+ guint32 GetCurrentTime() const { return gdk_x11_get_server_time(mWindow); }
+
+ void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
+ // Check for in-flight request
+ if (!mAsyncUpdateStart.IsNull()) {
+ return;
+ }
+ mAsyncUpdateStart = aNow;
+
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mWindow);
+ Window xWindow = GDK_WINDOW_XID(mWindow);
+ unsigned char c = 'a';
+ Atom timeStampPropAtom = TimeStampPropAtom();
+ XChangeProperty(xDisplay, xWindow, timeStampPropAtom, timeStampPropAtom, 8,
+ PropModeReplace, &c, 1);
+ XFlush(xDisplay);
+ }
+
+ gboolean PropertyNotifyHandler(GtkWidget* aWidget, GdkEventProperty* aEvent) {
+ if (aEvent->atom != gdk_x11_xatom_to_atom(TimeStampPropAtom())) {
+ return FALSE;
+ }
+
+ guint32 eventTime = aEvent->time;
+ TimeStamp lowerBound = mAsyncUpdateStart;
+
+ TimeConverter().CompensateForBackwardsSkew(eventTime, lowerBound);
+ mAsyncUpdateStart = TimeStamp();
+ return TRUE;
+ }
+
+ private:
+ static Atom TimeStampPropAtom() {
+ return gdk_x11_get_xatom_by_name_for_display(gdk_display_get_default(),
+ "GDK_TIMESTAMP_PROP");
+ }
+
+ // This is safe because this class is stored as a member of mWindow and
+ // won't outlive it.
+ GdkWindow* mWindow;
+ TimeStamp mAsyncUpdateStart;
+};
+
+} // namespace mozilla
+
+static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID);
+
+// The window from which the focus manager asks us to dispatch key events.
+static nsWindow* gFocusWindow = nullptr;
+static bool gBlockActivateEvent = false;
+static bool gGlobalsInitialized = false;
+static bool gRaiseWindows = true;
+static bool gUseWaylandVsync = true;
+static bool gUseWaylandUseOpaqueRegion = true;
+static bool gUseAspectRatio = true;
+static GList* gVisibleWaylandPopupWindows = nullptr;
+static uint32_t gLastTouchID = 0;
+
+#define NS_WINDOW_TITLE_MAX_LENGTH 4095
+
+// If after selecting profile window, the startup fail, please refer to
+// http://bugzilla.gnome.org/show_bug.cgi?id=88940
+
+// needed for imgIContainer cursors
+// GdkDisplay* was added in 2.2
+typedef struct _GdkDisplay GdkDisplay;
+
+#define kWindowPositionSlop 20
+
+// cursor cache
+static GdkCursor* gCursorCache[eCursorCount];
+
+static GtkWidget* gInvisibleContainer = nullptr;
+
+// Sometimes this actually also includes the state of the modifier keys, but
+// only the button state bits are used.
+static guint gButtonState;
+
+static inline int32_t GetBitmapStride(int32_t width) {
+#if defined(MOZ_X11)
+ return (width + 7) / 8;
+#else
+ return cairo_format_stride_for_width(CAIRO_FORMAT_A1, width);
+#endif
+}
+
+static inline bool TimestampIsNewerThan(guint32 a, guint32 b) {
+ // Timestamps are just the least significant bits of a monotonically
+ // increasing function, and so the use of unsigned overflow arithmetic.
+ return a - b <= G_MAXUINT32 / 2;
+}
+
+static void UpdateLastInputEventTime(void* aGdkEvent) {
+ nsCOMPtr<nsIUserIdleServiceInternal> idleService =
+ do_GetService("@mozilla.org/widget/useridleservice;1");
+ if (idleService) {
+ idleService->ResetIdleTimeOut(0);
+ }
+
+ guint timestamp = gdk_event_get_time(static_cast<GdkEvent*>(aGdkEvent));
+ if (timestamp == GDK_CURRENT_TIME) return;
+
+ sLastUserInputTime = timestamp;
+}
+
+void GetWindowOrigin(GdkWindow* aWindow, int* aX, int* aY) {
+ if (aWindow) {
+ gdk_window_get_origin(aWindow, aX, aY);
+ }
+
+ // TODO(bug 1655924): gdk_window_get_origin is can block waiting for the x
+ // server for a long time, we would like to use the implementation below
+ // instead. However, removing the synchronous x server queries causes a race
+ // condition to surface, causing issues such as bug 1652743 and bug 1653711.
+#if 0
+ *aX = 0;
+ *aY = 0;
+ if (!aWindow) {
+ return;
+ }
+
+ GdkWindow* current = aWindow;
+ while (GdkWindow* parent = gdk_window_get_parent(current)) {
+ if (parent == current) {
+ break;
+ }
+
+ int x = 0;
+ int y = 0;
+ gdk_window_get_position(current, &x, &y);
+ *aX += x;
+ *aY += y;
+
+ current = parent;
+ }
+#endif
+}
+
+nsWindow::nsWindow() {
+ mIsTopLevel = false;
+ mIsDestroyed = false;
+ mListenForResizes = false;
+ mNeedsDispatchResized = false;
+ mIsShown = false;
+ mNeedsShow = false;
+ mEnabled = true;
+ mCreated = false;
+ mHandleTouchEvent = false;
+ mIsDragPopup = false;
+ mIsX11Display = gfxPlatformGtk::GetPlatform()->IsX11Display();
+
+ mContainer = nullptr;
+ mGdkWindow = nullptr;
+ mShell = nullptr;
+ mCompositorWidgetDelegate = nullptr;
+ mHasMappedToplevel = false;
+ mRetryPointerGrab = false;
+ mWindowType = eWindowType_child;
+ mSizeState = nsSizeMode_Normal;
+ mBoundsAreValid = true;
+ mAspectRatio = 0.0f;
+ mAspectRatioSaved = 0.0f;
+ mLastSizeMode = nsSizeMode_Normal;
+ mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize);
+
+#ifdef MOZ_X11
+ mOldFocusWindow = 0;
+
+ mXDisplay = nullptr;
+ mXWindow = X11None;
+ mXVisual = nullptr;
+ mXDepth = 0;
+#endif /* MOZ_X11 */
+
+#ifdef MOZ_WAYLAND
+ mNeedsCompositorResume = false;
+ mCompositorInitiallyPaused = false;
+#endif
+ mWaitingForMoveToRectCB = false;
+ mPendingSizeRect = LayoutDeviceIntRect(0, 0, 0, 0);
+
+ if (!gGlobalsInitialized) {
+ gGlobalsInitialized = true;
+
+ // It's OK if either of these fail, but it may not be one day.
+ initialize_prefs();
+
+#ifdef MOZ_WAYLAND
+ // Wayland provides clipboard data to application on focus-in event
+ // so we need to init our clipboard hooks before we create window
+ // and get focus.
+ if (!mIsX11Display) {
+ nsCOMPtr<nsIClipboard> clipboard =
+ do_GetService("@mozilla.org/widget/clipboard;1");
+ NS_ASSERTION(clipboard, "Failed to init clipboard!");
+ }
+#endif
+ }
+
+ mLastMotionPressure = 0;
+
+#ifdef ACCESSIBILITY
+ mRootAccessible = nullptr;
+#endif
+
+ mIsTransparent = false;
+ mTransparencyBitmap = nullptr;
+ mTransparencyBitmapForTitlebar = false;
+
+ mTransparencyBitmapWidth = 0;
+ mTransparencyBitmapHeight = 0;
+
+ mLastScrollEventTime = GDK_CURRENT_TIME;
+
+ mPendingConfigures = 0;
+ mCSDSupportLevel = CSD_SUPPORT_NONE;
+ mDrawToContainer = false;
+ mDrawInTitlebar = false;
+ mTitlebarBackdropState = false;
+
+ mHasAlphaVisual = false;
+ mIsPIPWindow = false;
+ mAlwaysOnTop = false;
+
+ mWindowScaleFactorChanged = true;
+ mWindowScaleFactor = 1;
+
+ mCompositedScreen = gdk_screen_is_composited(gdk_screen_get_default());
+}
+
+nsWindow::~nsWindow() {
+ LOG(("nsWindow::~nsWindow() [%p]\n", (void*)this));
+
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = nullptr;
+
+ Destroy();
+}
+
+/* static */
+void nsWindow::ReleaseGlobals() {
+ for (auto& cursor : gCursorCache) {
+ if (cursor) {
+ g_object_unref(cursor);
+ cursor = nullptr;
+ }
+ }
+}
+
+void nsWindow::CommonCreate(nsIWidget* aParent, bool aListenForResizes) {
+ mParent = aParent;
+ mListenForResizes = aListenForResizes;
+ mCreated = true;
+}
+
+void nsWindow::DispatchActivateEvent(void) {
+ NS_ASSERTION(mContainer || mIsDestroyed,
+ "DispatchActivateEvent only intended for container windows");
+
+#ifdef ACCESSIBILITY
+ DispatchActivateEventAccessible();
+#endif // ACCESSIBILITY
+
+ if (mWidgetListener) mWidgetListener->WindowActivated();
+}
+
+void nsWindow::DispatchDeactivateEvent(void) {
+ if (mWidgetListener) mWidgetListener->WindowDeactivated();
+
+#ifdef ACCESSIBILITY
+ DispatchDeactivateEventAccessible();
+#endif // ACCESSIBILITY
+}
+
+void nsWindow::DispatchResized() {
+ mNeedsDispatchResized = false;
+ if (mWidgetListener) {
+ mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
+ }
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
+ }
+}
+
+void nsWindow::MaybeDispatchResized() {
+ if (mNeedsDispatchResized && !mIsDestroyed) {
+ DispatchResized();
+ }
+}
+
+nsIWidgetListener* nsWindow::GetListener() {
+ return mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener;
+}
+
+nsresult nsWindow::DispatchEvent(WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) {
+#ifdef DEBUG
+ debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "something", 0);
+#endif
+ aStatus = nsEventStatus_eIgnore;
+ nsIWidgetListener* listener = GetListener();
+ if (listener) {
+ aStatus = listener->HandleEvent(aEvent, mUseAttachedEvents);
+ }
+
+ return NS_OK;
+}
+
+void nsWindow::OnDestroy(void) {
+ if (mOnDestroyCalled) return;
+
+ mOnDestroyCalled = true;
+
+ // Prevent deletion.
+ nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
+
+ // release references to children, device context, toolkit + app shell
+ nsBaseWidget::OnDestroy();
+
+ // Remove association between this object and its parent and siblings.
+ nsBaseWidget::Destroy();
+ mParent = nullptr;
+
+ NotifyWindowDestroyed();
+}
+
+bool nsWindow::AreBoundsSane() {
+ return mBounds.width > 0 && mBounds.height > 0;
+}
+
+static GtkWidget* EnsureInvisibleContainer() {
+ if (!gInvisibleContainer) {
+ // GtkWidgets need to be anchored to a GtkWindow to be realized (to
+ // have a window). Using GTK_WINDOW_POPUP rather than
+ // GTK_WINDOW_TOPLEVEL in the hope that POPUP results in less
+ // initialization and window manager interaction.
+ GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
+ gInvisibleContainer = moz_container_new();
+ gtk_container_add(GTK_CONTAINER(window), gInvisibleContainer);
+ gtk_widget_realize(gInvisibleContainer);
+ }
+ return gInvisibleContainer;
+}
+
+static void CheckDestroyInvisibleContainer() {
+ MOZ_ASSERT(gInvisibleContainer, "oh, no");
+
+ if (!gdk_window_peek_children(gtk_widget_get_window(gInvisibleContainer))) {
+ // No children, so not in use.
+ // Make sure to destroy the GtkWindow also.
+ gtk_widget_destroy(gtk_widget_get_parent(gInvisibleContainer));
+ gInvisibleContainer = nullptr;
+ }
+}
+
+// Change the containing GtkWidget on a sub-hierarchy of GdkWindows belonging
+// to aOldWidget and rooted at aWindow, and reparent any child GtkWidgets of
+// the GdkWindow hierarchy to aNewWidget.
+static void SetWidgetForHierarchy(GdkWindow* aWindow, GtkWidget* aOldWidget,
+ GtkWidget* aNewWidget) {
+ gpointer data;
+ gdk_window_get_user_data(aWindow, &data);
+
+ if (data != aOldWidget) {
+ if (!GTK_IS_WIDGET(data)) return;
+
+ auto* widget = static_cast<GtkWidget*>(data);
+ if (gtk_widget_get_parent(widget) != aOldWidget) return;
+
+ // This window belongs to a child widget, which will no longer be a
+ // child of aOldWidget.
+ gtk_widget_reparent(widget, aNewWidget);
+
+ return;
+ }
+
+ GList* children = gdk_window_get_children(aWindow);
+ for (GList* list = children; list; list = list->next) {
+ SetWidgetForHierarchy(GDK_WINDOW(list->data), aOldWidget, aNewWidget);
+ }
+ g_list_free(children);
+
+ gdk_window_set_user_data(aWindow, aNewWidget);
+}
+
+// Walk the list of child windows and call destroy on them.
+void nsWindow::DestroyChildWindows() {
+ if (!mGdkWindow) return;
+
+ while (GList* children = gdk_window_peek_children(mGdkWindow)) {
+ GdkWindow* child = GDK_WINDOW(children->data);
+ nsWindow* kid = get_window_for_gdk_window(child);
+ if (kid) {
+ kid->Destroy();
+ } else {
+ // This child is not an nsWindow.
+ // Destroy the child GtkWidget.
+ gpointer data;
+ gdk_window_get_user_data(child, &data);
+ if (GTK_IS_WIDGET(data)) {
+ gtk_widget_destroy(static_cast<GtkWidget*>(data));
+ }
+ }
+ }
+}
+
+void nsWindow::Destroy() {
+ if (mIsDestroyed || !mCreated) return;
+
+ LOG(("nsWindow::Destroy [%p]\n", (void*)this));
+ mIsDestroyed = true;
+ mCreated = false;
+
+ /** Need to clean our LayerManager up while still alive */
+ if (mLayerManager) {
+ mLayerManager->Destroy();
+ }
+ mLayerManager = nullptr;
+
+#ifdef MOZ_WAYLAND
+ // Shut down our local vsync source
+ if (mWaylandVsyncSource) {
+ mWaylandVsyncSource->Shutdown();
+ mWaylandVsyncSource = nullptr;
+ }
+#endif
+
+ // It is safe to call DestroyeCompositor several times (here and
+ // in the parent class) since it will take effect only once.
+ // The reason we call it here is because on gtk platforms we need
+ // to destroy the compositor before we destroy the gdk window (which
+ // destroys the the gl context attached to it).
+ DestroyCompositor();
+
+#ifdef MOZ_X11
+ // Ensure any resources assigned to the window get cleaned up first
+ // to avoid double-freeing.
+ mSurfaceProvider.CleanupResources();
+#endif
+
+ ClearCachedResources();
+
+ g_signal_handlers_disconnect_by_data(gtk_settings_get_default(), this);
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ if (rollupListener) {
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (static_cast<nsIWidget*>(this) == rollupWidget) {
+ rollupListener->Rollup(0, false, nullptr, nullptr);
+ }
+ }
+
+ // dragService will be null after shutdown of the service manager.
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ if (dragService && this == dragService->GetMostRecentDestWindow()) {
+ dragService->ScheduleLeaveEvent();
+ }
+
+ NativeShow(false);
+
+ if (mIMContext) {
+ mIMContext->OnDestroyWindow(this);
+ }
+
+ // make sure that we remove ourself as the focus window
+ if (gFocusWindow == this) {
+ LOGFOCUS(("automatically losing focus...\n"));
+ gFocusWindow = nullptr;
+ }
+
+ GtkWidget* owningWidget = GetMozContainerWidget();
+ if (mShell) {
+ gtk_widget_destroy(mShell);
+ mShell = nullptr;
+ mContainer = nullptr;
+ MOZ_ASSERT(!mGdkWindow,
+ "mGdkWindow should be NULL when mContainer is destroyed");
+ } else if (mContainer) {
+ gtk_widget_destroy(GTK_WIDGET(mContainer));
+ mContainer = nullptr;
+ MOZ_ASSERT(!mGdkWindow,
+ "mGdkWindow should be NULL when mContainer is destroyed");
+ } else if (mGdkWindow) {
+ // Destroy child windows to ensure that their mThebesSurfaces are
+ // released and to remove references from GdkWindows back to their
+ // container widget. (OnContainerUnrealize() does this when the
+ // MozContainer widget is destroyed.)
+ DestroyChildWindows();
+
+ gdk_window_set_user_data(mGdkWindow, nullptr);
+ g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr);
+ gdk_window_destroy(mGdkWindow);
+ mGdkWindow = nullptr;
+ }
+
+ if (gInvisibleContainer && owningWidget == gInvisibleContainer) {
+ CheckDestroyInvisibleContainer();
+ }
+
+#ifdef ACCESSIBILITY
+ if (mRootAccessible) {
+ mRootAccessible = nullptr;
+ }
+#endif
+
+ // Save until last because OnDestroy() may cause us to be deleted.
+ OnDestroy();
+}
+
+nsIWidget* nsWindow::GetParent(void) { return mParent; }
+
+float nsWindow::GetDPI() {
+ float dpi = 96.0f;
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ if (screen) {
+ screen->GetDpi(&dpi);
+ }
+ return dpi;
+}
+
+double nsWindow::GetDefaultScaleInternal() {
+ return GdkScaleFactor() * gfxPlatformGtk::GetFontScaleFactor();
+}
+
+DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScale() {
+#ifdef MOZ_WAYLAND
+ if (!mIsX11Display) {
+ return DesktopToLayoutDeviceScale(GdkScaleFactor());
+ }
+#endif
+
+ // In Gtk/X11, we manage windows using device pixels.
+ return DesktopToLayoutDeviceScale(1.0);
+}
+
+DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScaleByScreen() {
+#ifdef MOZ_WAYLAND
+ // In Wayland there's no way to get absolute position of the window and use it
+ // to determine the screen factor of the monitor on which the window is
+ // placed. The window is notified of the current scale factor but not at this
+ // point, so the GdkScaleFactor can return wrong value which can lead to wrong
+ // popup placement. We need to use parent's window scale factor for the new
+ // one.
+ if (!mIsX11Display) {
+ nsView* view = nsView::GetViewFor(this);
+ if (view) {
+ nsView* parentView = view->GetParent();
+ if (parentView) {
+ nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr);
+ if (parentWidget) {
+ return DesktopToLayoutDeviceScale(
+ parentWidget->RoundsWidgetCoordinatesTo());
+ }
+ NS_WARNING("Widget has no parent");
+ }
+ } else {
+ NS_WARNING("Cannot find widget view");
+ }
+ }
+#endif
+ return nsBaseWidget::GetDesktopToDeviceScale();
+}
+
+void nsWindow::SetParent(nsIWidget* aNewParent) {
+ if (!mGdkWindow) {
+ MOZ_ASSERT_UNREACHABLE("The native window has already been destroyed");
+ return;
+ }
+
+ if (mContainer) {
+ // FIXME bug 1469183
+ NS_ERROR("nsWindow should not have a container here");
+ return;
+ }
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
+ if (mParent) {
+ mParent->RemoveChild(this);
+ }
+ mParent = aNewParent;
+
+ GtkWidget* oldContainer = GetMozContainerWidget();
+ if (!oldContainer) {
+ // The GdkWindows have been destroyed so there is nothing else to
+ // reparent.
+ MOZ_ASSERT(gdk_window_is_destroyed(mGdkWindow),
+ "live GdkWindow with no widget");
+ return;
+ }
+
+ nsWindow* newParent = static_cast<nsWindow*>(aNewParent);
+ GdkWindow* newParentWindow = nullptr;
+ GtkWidget* newContainer = nullptr;
+ if (aNewParent) {
+ aNewParent->AddChild(this);
+ newParentWindow = newParent->mGdkWindow;
+ newContainer = newParent->GetMozContainerWidget();
+ } else {
+ // aNewParent is nullptr, but reparent to a hidden window to avoid
+ // destroying the GdkWindow and its descendants.
+ // An invisible container widget is needed to hold descendant
+ // GtkWidgets.
+ newContainer = EnsureInvisibleContainer();
+ newParentWindow = gtk_widget_get_window(newContainer);
+ }
+
+ if (!newContainer) {
+ // The new parent GdkWindow has been destroyed.
+ MOZ_ASSERT(!newParentWindow || gdk_window_is_destroyed(newParentWindow),
+ "live GdkWindow with no widget");
+ Destroy();
+ } else {
+ if (newContainer != oldContainer) {
+ MOZ_ASSERT(!gdk_window_is_destroyed(newParentWindow),
+ "destroyed GdkWindow with widget");
+ SetWidgetForHierarchy(mGdkWindow, oldContainer, newContainer);
+
+ if (oldContainer == gInvisibleContainer) {
+ CheckDestroyInvisibleContainer();
+ }
+ }
+
+ gdk_window_reparent(mGdkWindow, newParentWindow,
+ DevicePixelsToGdkCoordRoundDown(mBounds.x),
+ DevicePixelsToGdkCoordRoundDown(mBounds.y));
+ }
+
+ bool parentHasMappedToplevel = newParent && newParent->mHasMappedToplevel;
+ if (mHasMappedToplevel != parentHasMappedToplevel) {
+ SetHasMappedToplevel(parentHasMappedToplevel);
+ }
+}
+
+bool nsWindow::WidgetTypeSupportsAcceleration() {
+ if (IsSmallPopup()) {
+ return false;
+ }
+ // Workaround for Bug 1479135
+ // We draw transparent popups on non-compositing screens by SW as we don't
+ // implement X shape masks in WebRender.
+ if (mWindowType == eWindowType_popup) {
+ return mCompositedScreen;
+ }
+ return true;
+}
+
+void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
+ MOZ_ASSERT(aNewParent, "null widget");
+ MOZ_ASSERT(!mIsDestroyed, "");
+ MOZ_ASSERT(!static_cast<nsWindow*>(aNewParent)->mIsDestroyed, "");
+ MOZ_ASSERT(!gdk_window_is_destroyed(mGdkWindow),
+ "destroyed GdkWindow with widget");
+
+ MOZ_ASSERT(
+ !mParent,
+ "nsWindow::ReparentNativeWidget() works on toplevel windows only.");
+
+ auto* newParent = static_cast<nsWindow*>(aNewParent);
+ GtkWindow* newParentWidget = GTK_WINDOW(newParent->GetGtkWidget());
+ GtkWindow* shell = GTK_WINDOW(mShell);
+
+ if (shell && gtk_window_get_transient_for(shell)) {
+ gtk_window_set_transient_for(shell, newParentWidget);
+ }
+}
+
+void nsWindow::SetModal(bool aModal) {
+ LOG(("nsWindow::SetModal [%p] %d\n", (void*)this, aModal));
+ if (mIsDestroyed) return;
+ if (!mIsTopLevel || !mShell) return;
+ gtk_window_set_modal(GTK_WINDOW(mShell), aModal ? TRUE : FALSE);
+}
+
+// nsIWidget method, which means IsShown.
+bool nsWindow::IsVisible() const { return mIsShown; }
+
+void nsWindow::RegisterTouchWindow() {
+ mHandleTouchEvent = true;
+ mTouches.Clear();
+}
+
+void nsWindow::ConstrainPosition(bool aAllowSlop, int32_t* aX, int32_t* aY) {
+ if (!mIsTopLevel || !mShell) return;
+
+ double dpiScale = GetDefaultScale().scale;
+
+ // we need to use the window size in logical screen pixels
+ int32_t logWidth = std::max(NSToIntRound(mBounds.width / dpiScale), 1);
+ int32_t logHeight = std::max(NSToIntRound(mBounds.height / dpiScale), 1);
+
+ /* get our playing field. use the current screen, or failing that
+ for any reason, use device caps for the default screen. */
+ nsCOMPtr<nsIScreen> screen;
+ nsCOMPtr<nsIScreenManager> screenmgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (screenmgr) {
+ screenmgr->ScreenForRect(*aX, *aY, logWidth, logHeight,
+ getter_AddRefs(screen));
+ }
+
+ // We don't have any screen so leave the coordinates as is
+ if (!screen) return;
+
+ nsIntRect screenRect;
+ if (mSizeMode != nsSizeMode_Fullscreen) {
+ // For normalized windows, use the desktop work area.
+ screen->GetAvailRectDisplayPix(&screenRect.x, &screenRect.y,
+ &screenRect.width, &screenRect.height);
+ } else {
+ // For full screen windows, use the desktop.
+ screen->GetRectDisplayPix(&screenRect.x, &screenRect.y, &screenRect.width,
+ &screenRect.height);
+ }
+
+ if (aAllowSlop) {
+ if (*aX < screenRect.x - logWidth + kWindowPositionSlop)
+ *aX = screenRect.x - logWidth + kWindowPositionSlop;
+ else if (*aX >= screenRect.XMost() - kWindowPositionSlop)
+ *aX = screenRect.XMost() - kWindowPositionSlop;
+
+ if (*aY < screenRect.y - logHeight + kWindowPositionSlop)
+ *aY = screenRect.y - logHeight + kWindowPositionSlop;
+ else if (*aY >= screenRect.YMost() - kWindowPositionSlop)
+ *aY = screenRect.YMost() - kWindowPositionSlop;
+ } else {
+ if (*aX < screenRect.x)
+ *aX = screenRect.x;
+ else if (*aX >= screenRect.XMost() - logWidth)
+ *aX = screenRect.XMost() - logWidth;
+
+ if (*aY < screenRect.y)
+ *aY = screenRect.y;
+ else if (*aY >= screenRect.YMost() - logHeight)
+ *aY = screenRect.YMost() - logHeight;
+ }
+}
+
+void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
+ mSizeConstraints.mMinSize = GetSafeWindowSize(aConstraints.mMinSize);
+ mSizeConstraints.mMaxSize = GetSafeWindowSize(aConstraints.mMaxSize);
+
+ ApplySizeConstraints();
+}
+
+void nsWindow::AddCSDDecorationSize(int* aWidth, int* aHeight) {
+ if (mCSDSupportLevel == CSD_SUPPORT_CLIENT && mDrawInTitlebar) {
+ GtkBorder decorationSize = GetCSDDecorationSize(!mIsTopLevel);
+ *aWidth += decorationSize.left + decorationSize.right;
+ *aHeight += decorationSize.top + decorationSize.bottom;
+ }
+}
+
+void nsWindow::ApplySizeConstraints(void) {
+ if (mShell) {
+ GdkGeometry geometry;
+ geometry.min_width =
+ DevicePixelsToGdkCoordRoundUp(mSizeConstraints.mMinSize.width);
+ geometry.min_height =
+ DevicePixelsToGdkCoordRoundUp(mSizeConstraints.mMinSize.height);
+ geometry.max_width =
+ DevicePixelsToGdkCoordRoundDown(mSizeConstraints.mMaxSize.width);
+ geometry.max_height =
+ DevicePixelsToGdkCoordRoundDown(mSizeConstraints.mMaxSize.height);
+
+ uint32_t hints = 0;
+ if (mSizeConstraints.mMinSize != LayoutDeviceIntSize(0, 0)) {
+ AddCSDDecorationSize(&geometry.min_width, &geometry.min_height);
+ hints |= GDK_HINT_MIN_SIZE;
+ LOG(("nsWindow::ApplySizeConstraints [%p] min size %d %d\n", (void*)this,
+ geometry.min_width, geometry.min_height));
+ }
+ if (mSizeConstraints.mMaxSize !=
+ LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) {
+ AddCSDDecorationSize(&geometry.max_width, &geometry.max_height);
+ hints |= GDK_HINT_MAX_SIZE;
+ LOG(("nsWindow::ApplySizeConstraints [%p] max size %d %d\n", (void*)this,
+ geometry.max_width, geometry.max_height));
+ }
+
+ if (mAspectRatio != 0.0f) {
+ geometry.min_aspect = mAspectRatio;
+ geometry.max_aspect = mAspectRatio;
+ hints |= GDK_HINT_ASPECT;
+ }
+
+ gtk_window_set_geometry_hints(GTK_WINDOW(mShell), nullptr, &geometry,
+ GdkWindowHints(hints));
+ }
+}
+
+void nsWindow::Show(bool aState) {
+ if (aState == mIsShown) return;
+
+ // Clear our cached resources when the window is hidden.
+ if (mIsShown && !aState) {
+ ClearCachedResources();
+ }
+
+ mIsShown = aState;
+
+ LOG(("nsWindow::Show [%p] state %d\n", (void*)this, aState));
+
+ if (aState) {
+ // Now that this window is shown, mHasMappedToplevel needs to be
+ // tracked on viewable descendants.
+ SetHasMappedToplevel(mHasMappedToplevel);
+ }
+
+ // Ok, someone called show on a window that isn't sized to a sane
+ // value. Mark this window as needing to have Show() called on it
+ // and return.
+ if ((aState && !AreBoundsSane()) || !mCreated) {
+ LOG(("\tbounds are insane or window hasn't been created yet\n"));
+ mNeedsShow = true;
+ return;
+ }
+
+ // If someone is hiding this widget, clear any needing show flag.
+ if (!aState) mNeedsShow = false;
+
+#ifdef ACCESSIBILITY
+ if (aState && a11y::ShouldA11yBeEnabled()) CreateRootAccessible();
+#endif
+
+ NativeShow(aState);
+}
+
+void nsWindow::ResizeInt(int aX, int aY, int aWidth, int aHeight, bool aMove,
+ bool aRepaint) {
+ LOG(("nsWindow::ResizeInt [%p] x:%d y:%d -> w:%d h:%d repaint %d aMove %d\n",
+ (void*)this, aX, aY, aWidth, aHeight, aRepaint, aMove));
+
+ ConstrainSize(&aWidth, &aHeight);
+
+ LOG((" ConstrainSize: w:%d h;%d\n", aWidth, aHeight));
+
+ // If we used to have insane bounds, we may have skipped actually positioning
+ // the widget in NativeMoveResizeWaylandPopup, in which case we need to
+ // actually position it now as well.
+ const bool hadInsaneWaylandPopupDimensions =
+ !AreBoundsSane() && IsWaylandPopup();
+
+ if (aMove) {
+ mBounds.x = aX;
+ mBounds.y = aY;
+ }
+
+ // For top-level windows, aWidth and aHeight should possibly be
+ // interpreted as frame bounds, but NativeResize treats these as window
+ // bounds (Bug 581866).
+ mBounds.SizeTo(aWidth, aHeight);
+
+ // We set correct mBounds in advance here. This can be invalided by state
+ // event.
+ mBoundsAreValid = true;
+
+ // Recalculate aspect ratio when resized from DOM
+ if (mAspectRatio != 0.0) {
+ LockAspectRatio(true);
+ }
+
+ if (!mCreated) return;
+
+ if (aMove || mPreferredPopupRectFlushed || hadInsaneWaylandPopupDimensions) {
+ LOG((" Need also to move, flushed? %d, bounds were insane: %d\n",
+ mPreferredPopupRectFlushed, hadInsaneWaylandPopupDimensions));
+ NativeMoveResize();
+ } else {
+ NativeResize();
+ }
+
+ NotifyRollupGeometryChange();
+
+ // send a resize notification if this is a toplevel
+ if (mIsTopLevel || mListenForResizes) {
+ DispatchResized();
+ }
+}
+
+void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
+ LOG(("nsWindow::Resize [%p] %f %f\n", (void*)this, aWidth, aHeight));
+
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t width = NSToIntRound(scale * aWidth);
+ int32_t height = NSToIntRound(scale * aHeight);
+
+ ResizeInt(0, 0, width, height, /* aMove */ false, aRepaint);
+}
+
+void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) {
+ LOG(("nsWindow::Resize [%p] %f %f repaint %d\n", (void*)this, aWidth, aHeight,
+ aRepaint));
+
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t width = NSToIntRound(scale * aWidth);
+ int32_t height = NSToIntRound(scale * aHeight);
+
+ int32_t x = NSToIntRound(scale * aX);
+ int32_t y = NSToIntRound(scale * aY);
+
+ ResizeInt(x, y, width, height, /* aMove */ true, aRepaint);
+}
+
+void nsWindow::Enable(bool aState) { mEnabled = aState; }
+
+bool nsWindow::IsEnabled() const { return mEnabled; }
+
+void nsWindow::Move(double aX, double aY) {
+ LOG(("nsWindow::Move [%p] %f %f\n", (void*)this, aX, aY));
+
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t x = NSToIntRound(aX * scale);
+ int32_t y = NSToIntRound(aY * scale);
+
+ if (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog) {
+ SetSizeMode(nsSizeMode_Normal);
+ }
+
+ // Since a popup window's x/y coordinates are in relation to to
+ // the parent, the parent might have moved so we always move a
+ // popup window.
+ if (x == mBounds.x && y == mBounds.y && mWindowType != eWindowType_popup)
+ return;
+
+ // XXX Should we do some AreBoundsSane check here?
+
+ mBounds.x = x;
+ mBounds.y = y;
+
+ if (!mCreated) return;
+
+ if (IsWaylandPopup()) {
+ int32_t p2a = AppUnitsPerCSSPixel() / gfxPlatformGtk::GetFontScaleFactor();
+ if (mPreferredPopupRect.x != mBounds.x * p2a &&
+ mPreferredPopupRect.y != mBounds.y * p2a) {
+ NativeMove();
+ NotifyRollupGeometryChange();
+ } else {
+ LOG((" mBounds same as mPreferredPopupRect, no need to move"));
+ }
+ } else {
+ NativeMove();
+ NotifyRollupGeometryChange();
+ }
+}
+
+bool nsWindow::IsPopup() {
+ return mIsTopLevel && mWindowType == eWindowType_popup;
+}
+
+bool nsWindow::IsWaylandPopup() { return !mIsX11Display && IsPopup(); }
+
+void nsWindow::HideWaylandTooltips() {
+ while (gVisibleWaylandPopupWindows) {
+ nsWindow* window =
+ static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data);
+ if (window->mPopupType != ePopupTypeTooltip) break;
+ LOG(("nsWindow::HideWaylandTooltips [%p] hidding tooltip [%p].\n",
+ (void*)this, window));
+ window->HideWaylandWindow();
+ }
+}
+
+void nsWindow::HideWaylandOpenedPopups() {
+ while (gVisibleWaylandPopupWindows) {
+ nsWindow* window =
+ static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data);
+ window->HideWaylandWindow();
+ }
+}
+
+// Hide popup nsWindows which are no longer in the nsXULPopupManager widget
+// chain list.
+void nsWindow::CleanupWaylandPopups() {
+ LOG(("nsWindow::CleanupWaylandPopups...\n"));
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ AutoTArray<nsIWidget*, 5> widgetChain;
+ pm->GetSubmenuWidgetChain(&widgetChain);
+ GList* popupList = gVisibleWaylandPopupWindows;
+ while (popupList) {
+ LOG((" Looking for %p [nsWindow]\n", popupList->data));
+ nsWindow* waylandWnd = static_cast<nsWindow*>(popupList->data);
+ // Remove only menu popups or empty frames - they are most likely
+ // already rolledup popups
+ if (waylandWnd->IsMainMenuWindow() || !waylandWnd->GetFrame()) {
+ bool popupFound = false;
+ for (unsigned long i = 0; i < widgetChain.Length(); i++) {
+ if (waylandWnd == widgetChain[i]) {
+ popupFound = true;
+ break;
+ }
+ }
+ if (!popupFound) {
+ LOG((" nsWindow [%p] not found in PopupManager, hiding it.\n",
+ waylandWnd));
+ waylandWnd->HideWaylandWindow();
+ popupList = gVisibleWaylandPopupWindows;
+ } else {
+ LOG((" nsWindow [%p] is still open.\n", waylandWnd));
+ popupList = popupList->next;
+ }
+ } else {
+ popupList = popupList->next;
+ }
+ }
+}
+
+static nsMenuPopupFrame* GetMenuPopupFrame(nsIFrame* aFrame) {
+ if (aFrame) {
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame);
+ return menuPopupFrame;
+ }
+ return nullptr;
+}
+
+// The MenuList popups are used as dropdown menus for example in WebRTC
+// microphone/camera chooser or autocomplete widgets.
+bool nsWindow::IsMainMenuWindow() {
+ nsMenuPopupFrame* menuPopupFrame = GetMenuPopupFrame(GetFrame());
+ if (menuPopupFrame) {
+ LOG((" nsMenuPopupFrame [%p] type: %d IsMenu: %d, IsMenuList: %d\n",
+ menuPopupFrame, menuPopupFrame->PopupType(), menuPopupFrame->IsMenu(),
+ menuPopupFrame->IsMenuList()));
+ return mPopupType == ePopupTypeMenu && !menuPopupFrame->IsMenuList();
+ }
+ return false;
+}
+
+GtkWindow* nsWindow::GetTopmostWindow() {
+ nsView* view = nsView::GetViewFor(this);
+ if (view) {
+ nsView* parentView = view->GetParent();
+ if (parentView) {
+ nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr);
+ if (parentWidget) {
+ nsWindow* parentnsWindow = static_cast<nsWindow*>(parentWidget);
+ LOG((" Topmost window: %p [nsWindow]\n", parentnsWindow));
+ return GTK_WINDOW(parentnsWindow->mShell);
+ }
+ }
+ }
+ return nullptr;
+}
+
+GtkWindow* nsWindow::GetCurrentWindow() {
+ GtkWindow* parentGtkWindow = nullptr;
+ // get the last opened window from gVisibleWaylandPopupWindows
+ if (gVisibleWaylandPopupWindows) {
+ nsWindow* parentnsWindow =
+ static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data);
+ if (parentnsWindow) {
+ LOG((" Setting parent to last opened window: %p [nsWindow]\n",
+ parentnsWindow));
+ parentGtkWindow = GTK_WINDOW(parentnsWindow->GetGtkWidget());
+ }
+ }
+ // get the topmost window if the last opened windows are empty
+ if (!parentGtkWindow) {
+ parentGtkWindow = GetTopmostWindow();
+ }
+ if (parentGtkWindow && GTK_IS_WINDOW(parentGtkWindow)) {
+ return GTK_WINDOW(parentGtkWindow);
+ } else {
+ LOG((" Failed to get current window for %p: %p\n", this, parentGtkWindow));
+ }
+ return nullptr;
+}
+
+bool nsWindow::IsWidgetOverflowWindow() {
+ if (this->GetFrame() && this->GetFrame()->GetContent()->GetID()) {
+ nsCString nodeId;
+ this->GetFrame()->GetContent()->GetID()->ToUTF8String(nodeId);
+ return nodeId.Equals("widget-overflow");
+ }
+ return false;
+}
+
+// Wayland keeps strong popup window hierarchy. We need to track active
+// (visible) popup windows and make sure we hide popup on the same level
+// before we open another one on that level. It means that every open
+// popup needs to have an unique parent.
+GtkWidget* nsWindow::ConfigureWaylandPopupWindows() {
+ MOZ_ASSERT(this->mWindowType == eWindowType_popup);
+ LOG(
+ ("nsWindow::ConfigureWaylandPopupWindows [%p], frame %p hasRemoteContent "
+ "%d\n",
+ (void*)this, this->GetFrame(), this->HasRemoteContent()));
+#if DEBUG
+ if (this->GetFrame() && this->GetFrame()->GetContent()->GetID()) {
+ nsCString nodeId;
+ this->GetFrame()->GetContent()->GetID()->ToUTF8String(nodeId);
+ LOG((" [%p] popup node id=%s\n", this, nodeId.get()));
+ }
+#endif
+
+ if (!GetFrame()) {
+ LOG((" Window without frame cannot be configured.\n"));
+ return nullptr;
+ }
+
+ // Check if we're already configured.
+ if (gVisibleWaylandPopupWindows &&
+ g_list_find(gVisibleWaylandPopupWindows, this)) {
+ LOG((" [%p] is already configured.\n", (void*)this));
+ return GTK_WIDGET(gtk_window_get_transient_for(GTK_WINDOW(mShell)));
+ }
+
+ // If we're opening a new window we don't want to attach it to a tooltip
+ // as it's short lived temporary window.
+ HideWaylandTooltips();
+
+ // Cleanup already closed menus
+ CleanupWaylandPopups();
+
+ if (gVisibleWaylandPopupWindows &&
+ (HasRemoteContent() || IsWidgetOverflowWindow())) {
+ nsWindow* openedWindow =
+ static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data);
+ LOG((" this [%p], lastOpenedWindow [%p]", this, openedWindow));
+ if (openedWindow != this) {
+ LOG(
+ (" Hiding all opened popups because the window is remote content or "
+ "overflow-widget"));
+ HideWaylandOpenedPopups();
+ }
+ }
+
+ GtkWindow* parentGtkWindow = GetCurrentWindow();
+ if (parentGtkWindow) {
+ MOZ_ASSERT(parentGtkWindow != GTK_WINDOW(this->GetGtkWidget()),
+ "Cannot set self as parent");
+ gtk_window_set_transient_for(GTK_WINDOW(mShell),
+ GTK_WINDOW(parentGtkWindow));
+ // Add current window to the visible popup list
+ gVisibleWaylandPopupWindows =
+ g_list_prepend(gVisibleWaylandPopupWindows, this);
+ LOG((" Parent window for %p: %p [GtkWindow]", this, parentGtkWindow));
+ }
+
+ MOZ_ASSERT(parentGtkWindow, "NO parent window for %p: expect popup glitches");
+ return GTK_WIDGET(parentGtkWindow);
+}
+
+static void NativeMoveResizeWaylandPopupCallback(
+ GdkWindow* window, const GdkRectangle* flipped_rect,
+ const GdkRectangle* final_rect, gboolean flipped_x, gboolean flipped_y,
+ void* aWindow) {
+ LOG(("NativeMoveResizeWaylandPopupCallback [%p] flipped_x %d flipped_y %d\n",
+ aWindow, flipped_x, flipped_y));
+
+ LOG((" flipped_rect x=%d y=%d width=%d height=%d\n", flipped_rect->x,
+ flipped_rect->y, flipped_rect->width, flipped_rect->height));
+ LOG((" final_rect x=%d y=%d width=%d height=%d\n", final_rect->x,
+ final_rect->y, final_rect->width, final_rect->height));
+ nsWindow* wnd = get_window_for_gdk_window(window);
+
+ wnd->NativeMoveResizeWaylandPopupCB(final_rect, flipped_x, flipped_y);
+}
+
+void nsWindow::NativeMoveResizeWaylandPopupCB(const GdkRectangle* aFinalSize,
+ bool aFlippedX, bool aFlippedY) {
+ LOG((" orig mBounds x=%d y=%d width=%d height=%d\n", mBounds.x, mBounds.y,
+ mBounds.width, mBounds.height));
+
+ // Remove signal handler because it can also be called from
+ // xdg_popup_configure
+ GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mShell));
+ if (g_signal_handler_find(
+ gdkWindow, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
+ FuncToGpointer(NativeMoveResizeWaylandPopupCallback), this)) {
+ LOG((" Disconnecting NativeMoveResizeWaylandPopupCallback"));
+ g_signal_handlers_disconnect_by_func(
+ gdkWindow, FuncToGpointer(NativeMoveResizeWaylandPopupCallback), this);
+ }
+ mWaitingForMoveToRectCB = false;
+
+ // We ignore the callback position data because the another resize has been
+ // called before the callback have been triggered.
+ if (mPendingSizeRect.height > 0 || mPendingSizeRect.width > 0) {
+ LOG(
+ (" Another resize called during waiting for callback, calling "
+ "Resize(%d, %d)\n",
+ mPendingSizeRect.width, mPendingSizeRect.height));
+ // Set the preferred size to zero to avoid wrong size of popup because the
+ // mPreferredPopupRect is used in nsMenuPopupFrame to set dimensions
+ mPreferredPopupRect = nsRect(0, 0, 0, 0);
+
+ // We need to schedule another resize because the window has been resized
+ // again before callback was called.
+ Resize(mPendingSizeRect.width, mPendingSizeRect.height, true);
+ DispatchResized();
+ mPendingSizeRect.width = mPendingSizeRect.height = 0;
+ return;
+ }
+
+ GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) {
+ NS_WARNING("Popup has no parent!");
+ return;
+ }
+
+ // The position of the menu in GTK is relative to it's parent window while
+ // in mBounds we have position relative to toplevel window. We need to check
+ // and update mBounds in the toplevel coordinates.
+ int x_parent, y_parent;
+ GetWindowOrigin(gtk_widget_get_window(GTK_WIDGET(parentGtkWindow)), &x_parent,
+ &y_parent);
+
+ LayoutDeviceIntRect newBounds(aFinalSize->x, aFinalSize->y, aFinalSize->width,
+ aFinalSize->height);
+
+ newBounds.x = GdkCoordToDevicePixels(newBounds.x);
+ newBounds.y = GdkCoordToDevicePixels(newBounds.y);
+
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t newWidth = NSToIntRound(scale * newBounds.width);
+ int32_t newHeight = NSToIntRound(scale * newBounds.height);
+
+ LOG((" new mBounds x=%d y=%d width=%d height=%d\n", newBounds.x,
+ newBounds.y, newWidth, newHeight));
+
+ bool needsPositionUpdate =
+ (newBounds.x != mBounds.x || newBounds.y != mBounds.y);
+ bool needsSizeUpdate =
+ (newWidth != mBounds.width || newHeight != mBounds.height);
+ // Update view
+
+ if (needsSizeUpdate) {
+ LOG((" needSizeUpdate\n"));
+ int32_t p2a = AppUnitsPerCSSPixel() / gfxPlatformGtk::GetFontScaleFactor();
+ mPreferredPopupRect = nsRect(NSIntPixelsToAppUnits(newBounds.x, p2a),
+ NSIntPixelsToAppUnits(newBounds.y, p2a),
+ NSIntPixelsToAppUnits(newBounds.width, p2a),
+ NSIntPixelsToAppUnits(newBounds.height, p2a));
+ mPreferredPopupRectFlushed = false;
+ Resize(newBounds.width, newBounds.height, true);
+ DispatchResized();
+
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+ if (popupFrame) {
+ RefPtr<PresShell> presShell = popupFrame->PresShell();
+ presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::Resize,
+ NS_FRAME_IS_DIRTY);
+ }
+ }
+
+ if (needsPositionUpdate) {
+ LOG((" needPositionUpdate\n"));
+ // The newBounds are in coordinates relative to the parent window/popup.
+ // The NotifyWindowMoved requires the coordinates relative to the toplevel.
+ // We use the gdk_window_get_origin to get correct coordinates.
+ gint x = 0, y = 0;
+ GetWindowOrigin(gtk_widget_get_window(GTK_WIDGET(mShell)), &x, &y);
+ NotifyWindowMoved(GdkCoordToDevicePixels(x), GdkCoordToDevicePixels(y));
+ }
+}
+
+#ifdef MOZ_WAYLAND
+static GdkGravity PopupAlignmentToGdkGravity(int8_t aAlignment) {
+ switch (aAlignment) {
+ case POPUPALIGNMENT_NONE:
+ return GDK_GRAVITY_NORTH_WEST;
+ break;
+ case POPUPALIGNMENT_TOPLEFT:
+ return GDK_GRAVITY_NORTH_WEST;
+ break;
+ case POPUPALIGNMENT_TOPRIGHT:
+ return GDK_GRAVITY_NORTH_EAST;
+ break;
+ case POPUPALIGNMENT_BOTTOMLEFT:
+ return GDK_GRAVITY_SOUTH_WEST;
+ break;
+ case POPUPALIGNMENT_BOTTOMRIGHT:
+ return GDK_GRAVITY_SOUTH_EAST;
+ break;
+ case POPUPALIGNMENT_LEFTCENTER:
+ return GDK_GRAVITY_WEST;
+ break;
+ case POPUPALIGNMENT_RIGHTCENTER:
+ return GDK_GRAVITY_EAST;
+ break;
+ case POPUPALIGNMENT_TOPCENTER:
+ return GDK_GRAVITY_NORTH;
+ break;
+ case POPUPALIGNMENT_BOTTOMCENTER:
+ return GDK_GRAVITY_SOUTH;
+ break;
+ }
+ return GDK_GRAVITY_STATIC;
+}
+#endif
+
+void nsWindow::NativeMoveResizeWaylandPopup(GdkPoint* aPosition,
+ GdkRectangle* aSize) {
+ // Available as of GTK 3.24+
+ static auto sGdkWindowMoveToRect = (void (*)(
+ GdkWindow*, const GdkRectangle*, GdkGravity, GdkGravity, GdkAnchorHints,
+ gint, gint))dlsym(RTLD_DEFAULT, "gdk_window_move_to_rect");
+ LOG(("nsWindow::NativeMoveResizeWaylandPopup [%p]\n", (void*)this));
+
+ // Compositor may be confused by windows with width/height = 0
+ // and positioning such windows leads to Bug 1555866.
+ if (!AreBoundsSane()) {
+ LOG((" Bounds are not sane (width: %d height: %d)\n", mBounds.width,
+ mBounds.height));
+ return;
+ }
+
+ if (aSize) {
+ gtk_window_resize(GTK_WINDOW(mShell), aSize->width, aSize->height);
+ }
+
+ GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mShell));
+
+ // Use standard gtk_window_move() instead of gdk_window_move_to_rect() when:
+ // - gdk_window_move_to_rect() is not available
+ // - the widget doesn't have a valid GdkWindow
+ if (!sGdkWindowMoveToRect || !gdkWindow) {
+ LOG((" use gtk_window_move(%d, %d)\n", aPosition->x, aPosition->y));
+ gtk_window_move(GTK_WINDOW(mShell), aPosition->x, aPosition->y);
+ return;
+ }
+
+ GtkWidget* parentWindow = ConfigureWaylandPopupWindows();
+ LOG(("nsWindow::NativeMoveResizeWaylandPopup: Set popup parent %p\n",
+ parentWindow));
+
+ // Get anchor rectangle
+ LayoutDeviceIntRect anchorRect(0, 0, 0, 0);
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+
+ int32_t p2a;
+ double devPixelsPerCSSPixel = StaticPrefs::layout_css_devPixelsPerPx();
+ if (devPixelsPerCSSPixel > 0.0) {
+ p2a = AppUnitsPerCSSPixel() / devPixelsPerCSSPixel * GdkScaleFactor();
+ } else {
+ p2a = AppUnitsPerCSSPixel() / gfxPlatformGtk::GetFontScaleFactor();
+ }
+ if (popupFrame) {
+#ifdef MOZ_WAYLAND
+ anchorRect = LayoutDeviceIntRect::FromAppUnitsToOutside(
+ popupFrame->GetAnchorRect(), p2a);
+#endif
+ }
+
+#ifdef MOZ_WAYLAND
+ bool hasAnchorRect = true;
+#endif
+ if (anchorRect.width == 0) {
+ LOG((" No anchor rect given, use aPosition for anchor"));
+ anchorRect.SetRect(aPosition->x, aPosition->y, 1, 1);
+#ifdef MOZ_WAYLAND
+ hasAnchorRect = false;
+#endif
+ }
+ LOG((" anchor x %d y %d width %d height %d (absolute coords)\n",
+ anchorRect.x, anchorRect.y, anchorRect.width, anchorRect.height));
+
+ // Anchor rect is in the toplevel coordinates but we need to transfer it to
+ // the coordinates relative to the popup parent for the
+ // gdk_window_move_to_rect
+ int x_parent = 0, y_parent = 0;
+ GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ if (parentGtkWindow) {
+ GetWindowOrigin(gtk_widget_get_window(GTK_WIDGET(parentGtkWindow)),
+ &x_parent, &y_parent);
+ }
+ LOG((" x_parent %d y_parent %d\n", x_parent, y_parent));
+ anchorRect.MoveBy(-x_parent, -y_parent);
+ GdkRectangle rect = {anchorRect.x, anchorRect.y, anchorRect.width,
+ anchorRect.height};
+
+ // Get gravity and flip type
+ GdkGravity rectAnchor = GDK_GRAVITY_NORTH_WEST;
+ GdkGravity menuAnchor = GDK_GRAVITY_NORTH_WEST;
+ FlipType flipType = FlipType_Default;
+ int8_t position = -1;
+ if (popupFrame) {
+#ifdef MOZ_WAYLAND
+ rectAnchor = PopupAlignmentToGdkGravity(popupFrame->GetPopupAnchor());
+ menuAnchor = PopupAlignmentToGdkGravity(popupFrame->GetPopupAlignment());
+ flipType = popupFrame->GetFlipType();
+ position = popupFrame->GetAlignmentPosition();
+#endif
+ } else {
+ LOG((" NO ANCHOR INFO"));
+ if (GetTextDirection() == GTK_TEXT_DIR_RTL) {
+ rectAnchor = GDK_GRAVITY_NORTH_EAST;
+ menuAnchor = GDK_GRAVITY_NORTH_EAST;
+ }
+ }
+ LOG((" parentRect gravity: %d anchor gravity: %d\n", rectAnchor, menuAnchor));
+
+ GdkAnchorHints hints = GdkAnchorHints(GDK_ANCHOR_RESIZE);
+
+ // slideHorizontal from nsMenuPopupFrame::SetPopupPosition
+ if (position >= POPUPPOSITION_BEFORESTART &&
+ position <= POPUPPOSITION_AFTEREND) {
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE_X);
+ }
+ // slideVertical from nsMenuPopupFrame::SetPopupPosition
+ if (position >= POPUPPOSITION_STARTBEFORE &&
+ position <= POPUPPOSITION_ENDAFTER) {
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE_Y);
+ }
+
+ if (popupFrame && rectAnchor == GDK_GRAVITY_CENTER &&
+ menuAnchor == GDK_GRAVITY_CENTER) {
+ // only slide
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
+ } else {
+ switch (flipType) {
+ case FlipType_Both:
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_FLIP);
+ break;
+ case FlipType_Slide:
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
+ break;
+ case FlipType_Default:
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_FLIP);
+ break;
+ default:
+ break;
+ }
+ }
+ if (!IsMainMenuWindow()) {
+ // we don't want to slide menus to fit the screen rather resize them
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
+ }
+
+ // A workaround for https://gitlab.gnome.org/GNOME/gtk/issues/1986
+ // gdk_window_move_to_rect() does not reposition visible windows.
+ static auto sGtkWidgetIsVisible =
+ (gboolean(*)(GtkWidget*))dlsym(RTLD_DEFAULT, "gtk_widget_is_visible");
+
+ bool isWidgetVisible =
+ (sGtkWidgetIsVisible != nullptr) && sGtkWidgetIsVisible(mShell);
+ if (isWidgetVisible) {
+ PauseRemoteRenderer();
+ gtk_widget_hide(mShell);
+ }
+
+ LOG((" requested rect: x: %d y: %d width: %d height: %d\n", rect.x, rect.y,
+ rect.width, rect.height));
+ if (aSize) {
+ LOG((" aSize: x%d y%d w%d h%d\n", aSize->x, aSize->y, aSize->width,
+ aSize->height));
+ } else {
+ LOG((" No aSize given"));
+ }
+
+ // Inspired by nsMenuPopupFrame::AdjustPositionForAnchorAlign
+ nsPoint cursorOffset(0, 0);
+#ifdef MOZ_WAYLAND
+ // Offset is already computed to the tooltips
+ if (hasAnchorRect && popupFrame && mPopupType != ePopupTypeTooltip) {
+ nsMargin margin(0, 0, 0, 0);
+ popupFrame->StyleMargin()->GetMargin(margin);
+ switch (popupFrame->GetPopupAlignment()) {
+ case POPUPALIGNMENT_TOPRIGHT:
+ cursorOffset.MoveBy(-margin.right, margin.top);
+ break;
+ case POPUPALIGNMENT_BOTTOMLEFT:
+ cursorOffset.MoveBy(margin.left, -margin.bottom);
+ break;
+ case POPUPALIGNMENT_BOTTOMRIGHT:
+ cursorOffset.MoveBy(-margin.right, -margin.bottom);
+ break;
+ case POPUPALIGNMENT_TOPLEFT:
+ default:
+ cursorOffset.MoveBy(margin.left, margin.top);
+ break;
+ }
+ }
+#endif
+
+ if (!g_signal_handler_find(
+ gdkWindow, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
+ FuncToGpointer(NativeMoveResizeWaylandPopupCallback), this)) {
+ g_signal_connect(gdkWindow, "moved-to-rect",
+ G_CALLBACK(NativeMoveResizeWaylandPopupCallback), this);
+ }
+
+ LOG((" popup window cursor offset x: %d y: %d\n", cursorOffset.x / p2a,
+ cursorOffset.y / p2a));
+ mWaitingForMoveToRectCB = true;
+ sGdkWindowMoveToRect(gdkWindow, &rect, rectAnchor, menuAnchor, hints,
+ cursorOffset.x / p2a, cursorOffset.y / p2a);
+
+ if (isWidgetVisible) {
+ // We show the popup with the same configuration so no need to call
+ // ConfigureWaylandPopupWindows() before gtk_widget_show().
+ gtk_widget_show(mShell);
+ }
+}
+
+void nsWindow::NativeMove() {
+ GdkPoint point = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
+
+ LOG(("nsWindow::NativeMove [%p] %d %d\n", (void*)this, point.x, point.y));
+
+ if (IsWaylandPopup()) {
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
+ NativeMoveResizeWaylandPopup(&point, &size);
+ } else if (mIsTopLevel) {
+ gtk_window_move(GTK_WINDOW(mShell), point.x, point.y);
+ } else if (mGdkWindow) {
+ gdk_window_move(mGdkWindow, point.x, point.y);
+ }
+}
+
+void nsWindow::SetZIndex(int32_t aZIndex) {
+ nsIWidget* oldPrev = GetPrevSibling();
+
+ nsBaseWidget::SetZIndex(aZIndex);
+
+ if (GetPrevSibling() == oldPrev) {
+ return;
+ }
+
+ NS_ASSERTION(!mContainer, "Expected Mozilla child widget");
+
+ // We skip the nsWindows that don't have mGdkWindows.
+ // These are probably in the process of being destroyed.
+
+ if (!GetNextSibling()) {
+ // We're to be on top.
+ if (mGdkWindow) gdk_window_raise(mGdkWindow);
+ } else {
+ // All the siblings before us need to be below our widget.
+ for (nsWindow* w = this; w;
+ w = static_cast<nsWindow*>(w->GetPrevSibling())) {
+ if (w->mGdkWindow) gdk_window_lower(w->mGdkWindow);
+ }
+ }
+}
+
+void nsWindow::SetSizeMode(nsSizeMode aMode) {
+ LOG(("nsWindow::SetSizeMode [%p] %d\n", (void*)this, aMode));
+
+ // Save the requested state.
+ nsBaseWidget::SetSizeMode(aMode);
+
+ // return if there's no shell or our current state is the same as
+ // the mode we were just set to.
+ if (!mShell || mSizeState == mSizeMode) {
+ LOG((" already set"));
+ return;
+ }
+
+ switch (aMode) {
+ case nsSizeMode_Maximized:
+ LOG((" set maximized"));
+ gtk_window_maximize(GTK_WINDOW(mShell));
+ break;
+ case nsSizeMode_Minimized:
+ LOG((" set minimized"));
+ gtk_window_iconify(GTK_WINDOW(mShell));
+ break;
+ case nsSizeMode_Fullscreen:
+ LOG((" set fullscreen"));
+ MakeFullScreen(true);
+ break;
+
+ default:
+ LOG((" set normal"));
+ // nsSizeMode_Normal, really.
+ if (mSizeState == nsSizeMode_Minimized)
+ gtk_window_deiconify(GTK_WINDOW(mShell));
+ else if (mSizeState == nsSizeMode_Maximized)
+ gtk_window_unmaximize(GTK_WINDOW(mShell));
+ break;
+ }
+
+ // Request mBounds update from configure event as we may not get
+ // OnSizeAllocate for size state changes (Bug 1489463).
+ mBoundsAreValid = false;
+
+ mSizeState = mSizeMode;
+}
+
+static bool GetWindowManagerName(GdkWindow* gdk_window, nsACString& wmName) {
+ if (!gfxPlatformGtk::GetPlatform()->IsX11Display()) {
+ return false;
+ }
+
+ Display* xdisplay = gdk_x11_get_default_xdisplay();
+ GdkScreen* screen = gdk_window_get_screen(gdk_window);
+ Window root_win = GDK_WINDOW_XID(gdk_screen_get_root_window(screen));
+
+ int actual_format_return;
+ Atom actual_type_return;
+ unsigned long nitems_return;
+ unsigned long bytes_after_return;
+ unsigned char* prop_return = nullptr;
+ auto releaseXProperty = MakeScopeExit([&] {
+ if (prop_return) {
+ XFree(prop_return);
+ }
+ });
+
+ Atom property = XInternAtom(xdisplay, "_NET_SUPPORTING_WM_CHECK", true);
+ Atom req_type = XInternAtom(xdisplay, "WINDOW", true);
+ if (!property || !req_type) {
+ return false;
+ }
+ int result =
+ XGetWindowProperty(xdisplay, root_win, property,
+ 0L, // offset
+ sizeof(Window) / 4, // length
+ false, // delete
+ req_type, &actual_type_return, &actual_format_return,
+ &nitems_return, &bytes_after_return, &prop_return);
+
+ if (result != Success || bytes_after_return != 0 || nitems_return != 1) {
+ return false;
+ }
+
+ Window wmWindow = reinterpret_cast<Window*>(prop_return)[0];
+ if (!wmWindow) {
+ return false;
+ }
+
+ XFree(prop_return);
+ prop_return = nullptr;
+
+ property = XInternAtom(xdisplay, "_NET_WM_NAME", true);
+ req_type = XInternAtom(xdisplay, "UTF8_STRING", true);
+ if (!property || !req_type) {
+ return false;
+ }
+ {
+ // Suppress fatal errors for a missing window.
+ ScopedXErrorHandler handler;
+ result =
+ XGetWindowProperty(xdisplay, wmWindow, property,
+ 0L, // offset
+ INT32_MAX, // length
+ false, // delete
+ req_type, &actual_type_return, &actual_format_return,
+ &nitems_return, &bytes_after_return, &prop_return);
+ }
+
+ if (result != Success || bytes_after_return != 0) {
+ return false;
+ }
+
+ wmName = reinterpret_cast<const char*>(prop_return);
+ return true;
+}
+
+#define kDesktopMutterSchema "org.gnome.mutter"
+#define kDesktopDynamicWorkspacesKey "dynamic-workspaces"
+
+static bool WorkspaceManagementDisabled(GdkWindow* gdk_window) {
+ if (Preferences::GetBool("widget.disable-workspace-management", false)) {
+ return true;
+ }
+ if (Preferences::HasUserValue("widget.workspace-management")) {
+ return Preferences::GetBool("widget.workspace-management");
+ }
+
+ static const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+ if (currentDesktop && strstr(currentDesktop, "GNOME")) {
+ // Gnome uses dynamic workspaces by default so disable workspace management
+ // in that case.
+ bool usesDynamicWorkspaces = true;
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+ if (gsettings) {
+ nsCOMPtr<nsIGSettingsCollection> mutterSettings;
+ gsettings->GetCollectionForSchema(nsLiteralCString(kDesktopMutterSchema),
+ getter_AddRefs(mutterSettings));
+ if (mutterSettings) {
+ if (NS_SUCCEEDED(mutterSettings->GetBoolean(
+ nsLiteralCString(kDesktopDynamicWorkspacesKey),
+ &usesDynamicWorkspaces))) {
+ }
+ }
+ }
+ return usesDynamicWorkspaces;
+ }
+
+ // When XDG_CURRENT_DESKTOP is missing, try to get window manager name.
+ if (!currentDesktop) {
+ nsAutoCString wmName;
+ if (GetWindowManagerName(gdk_window, wmName)) {
+ if (wmName.EqualsLiteral("bspwm")) {
+ return true;
+ }
+ if (wmName.EqualsLiteral("i3")) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void nsWindow::GetWorkspaceID(nsAString& workspaceID) {
+ workspaceID.Truncate();
+
+ if (!mIsX11Display || !mShell) {
+ return;
+ }
+ // Get the gdk window for this widget.
+ GdkWindow* gdk_window = gtk_widget_get_window(mShell);
+ if (!gdk_window) {
+ return;
+ }
+
+ if (WorkspaceManagementDisabled(gdk_window)) {
+ return;
+ }
+
+ GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
+ GdkAtom type_returned;
+ int format_returned;
+ int length_returned;
+ long* wm_desktop;
+
+ if (!gdk_property_get(gdk_window, gdk_atom_intern("_NET_WM_DESKTOP", FALSE),
+ cardinal_atom,
+ 0, // offset
+ INT32_MAX, // length
+ FALSE, // delete
+ &type_returned, &format_returned, &length_returned,
+ (guchar**)&wm_desktop)) {
+ return;
+ }
+
+ workspaceID.AppendInt((int32_t)wm_desktop[0]);
+ g_free(wm_desktop);
+}
+
+void nsWindow::MoveToWorkspace(const nsAString& workspaceIDStr) {
+ nsresult rv = NS_OK;
+ int32_t workspaceID = workspaceIDStr.ToInteger(&rv);
+ if (NS_FAILED(rv) || !workspaceID || !mIsX11Display || !mShell) {
+ return;
+ }
+
+ // Get the gdk window for this widget.
+ GdkWindow* gdk_window = gtk_widget_get_window(mShell);
+ if (!gdk_window) {
+ return;
+ }
+
+ // This code is inspired by some found in the 'gxtuner' project.
+ // https://github.com/brummer10/gxtuner/blob/792d453da0f3a599408008f0f1107823939d730d/deskpager.cpp#L50
+ XEvent xevent;
+ Display* xdisplay = gdk_x11_get_default_xdisplay();
+ GdkScreen* screen = gdk_window_get_screen(gdk_window);
+ Window root_win = GDK_WINDOW_XID(gdk_screen_get_root_window(screen));
+ GdkDisplay* display = gdk_window_get_display(gdk_window);
+ Atom type = gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_DESKTOP");
+
+ xevent.type = ClientMessage;
+ xevent.xclient.type = ClientMessage;
+ xevent.xclient.serial = 0;
+ xevent.xclient.send_event = TRUE;
+ xevent.xclient.display = xdisplay;
+ xevent.xclient.window = GDK_WINDOW_XID(gdk_window);
+ xevent.xclient.message_type = type;
+ xevent.xclient.format = 32;
+ xevent.xclient.data.l[0] = workspaceID;
+ xevent.xclient.data.l[1] = X11CurrentTime;
+ xevent.xclient.data.l[2] = 0;
+ xevent.xclient.data.l[3] = 0;
+ xevent.xclient.data.l[4] = 0;
+
+ XSendEvent(xdisplay, root_win, FALSE,
+ SubstructureNotifyMask | SubstructureRedirectMask, &xevent);
+
+ XFlush(xdisplay);
+}
+
+typedef void (*SetUserTimeFunc)(GdkWindow* aWindow, guint32 aTimestamp);
+
+static void SetUserTimeAndStartupIDForActivatedWindow(GtkWidget* aWindow) {
+ nsGTKToolkit* GTKToolkit = nsGTKToolkit::GetToolkit();
+ if (!GTKToolkit) return;
+
+ nsAutoCString desktopStartupID;
+ GTKToolkit->GetDesktopStartupID(&desktopStartupID);
+ if (desktopStartupID.IsEmpty()) {
+ // We don't have the data we need. Fall back to an
+ // approximation ... using the timestamp of the remote command
+ // being received as a guess for the timestamp of the user event
+ // that triggered it.
+ uint32_t timestamp = GTKToolkit->GetFocusTimestamp();
+ if (timestamp) {
+ gdk_window_focus(gtk_widget_get_window(aWindow), timestamp);
+ GTKToolkit->SetFocusTimestamp(0);
+ }
+ return;
+ }
+
+ gtk_window_set_startup_id(GTK_WINDOW(aWindow), desktopStartupID.get());
+
+ // If we used the startup ID, that already contains the focus timestamp;
+ // we don't want to reuse the timestamp next time we raise the window
+ GTKToolkit->SetFocusTimestamp(0);
+ GTKToolkit->SetDesktopStartupID(""_ns);
+}
+
+/* static */
+guint32 nsWindow::GetLastUserInputTime() {
+ // gdk_x11_display_get_user_time/gtk_get_current_event_time tracks
+ // button and key presses, DESKTOP_STARTUP_ID used to start the app,
+ // drop events from external drags,
+ // WM_DELETE_WINDOW delete events, but not usually mouse motion nor
+ // button and key releases. Therefore use the most recent of
+ // gdk_x11_display_get_user_time and the last time that we have seen.
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ guint32 timestamp = GDK_IS_X11_DISPLAY(gdkDisplay)
+ ? gdk_x11_display_get_user_time(gdkDisplay)
+ : gtk_get_current_event_time();
+
+ if (sLastUserInputTime != GDK_CURRENT_TIME &&
+ TimestampIsNewerThan(sLastUserInputTime, timestamp)) {
+ return sLastUserInputTime;
+ }
+
+ return timestamp;
+}
+
+void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
+ // Make sure that our owning widget has focus. If it doesn't try to
+ // grab it. Note that we don't set our focus flag in this case.
+
+ LOGFOCUS((" SetFocus %d [%p]\n", aRaise == Raise::Yes, (void*)this));
+
+ GtkWidget* owningWidget = GetMozContainerWidget();
+ if (!owningWidget) return;
+
+ // Raise the window if someone passed in true and the prefs are
+ // set properly.
+ GtkWidget* toplevelWidget = gtk_widget_get_toplevel(owningWidget);
+
+ if (gRaiseWindows && aRaise == Raise::Yes && toplevelWidget &&
+ !gtk_widget_has_focus(owningWidget) &&
+ !gtk_widget_has_focus(toplevelWidget)) {
+ GtkWidget* top_window = GetToplevelWidget();
+ if (top_window && (gtk_widget_get_visible(top_window))) {
+ gdk_window_show_unraised(gtk_widget_get_window(top_window));
+ // Unset the urgency hint if possible.
+ SetUrgencyHint(top_window, false);
+ }
+ }
+
+ RefPtr<nsWindow> owningWindow = get_window_for_gtk_widget(owningWidget);
+ if (!owningWindow) return;
+
+ if (aRaise == Raise::Yes) {
+ // means request toplevel activation.
+
+ // This is asynchronous.
+ // If and when the window manager accepts the request, then the focus
+ // widget will get a focus-in-event signal.
+ if (gRaiseWindows && owningWindow->mIsShown && owningWindow->mShell &&
+ !gtk_window_is_active(GTK_WINDOW(owningWindow->mShell))) {
+ if (!mIsX11Display &&
+ Preferences::GetBool("testing.browserTestHarness.running", false)) {
+ // Wayland does not support focus changes so we need to workaround it
+ // by window hide/show sequence but only when it's running in testsuite.
+ owningWindow->NativeShow(false);
+ owningWindow->NativeShow(true);
+ return;
+ }
+
+ uint32_t timestamp = GDK_CURRENT_TIME;
+
+ nsGTKToolkit* GTKToolkit = nsGTKToolkit::GetToolkit();
+ if (GTKToolkit) timestamp = GTKToolkit->GetFocusTimestamp();
+
+ LOGFOCUS((" requesting toplevel activation [%p]\n", (void*)this));
+ NS_ASSERTION(owningWindow->mWindowType != eWindowType_popup || mParent,
+ "Presenting an override-redirect window");
+ gtk_window_present_with_time(GTK_WINDOW(owningWindow->mShell), timestamp);
+
+ if (GTKToolkit) GTKToolkit->SetFocusTimestamp(0);
+ }
+ return;
+ }
+
+ // aRaise == No means that keyboard events should be dispatched from this
+ // widget.
+
+ // Ensure owningWidget is the focused GtkWidget within its toplevel window.
+ //
+ // For eWindowType_popup, this GtkWidget may not actually be the one that
+ // receives the key events as it may be the parent window that is active.
+ if (!gtk_widget_is_focus(owningWidget)) {
+ // This is synchronous. It takes focus from a plugin or from a widget
+ // in an embedder. The focus manager already knows that this window
+ // is active so gBlockActivateEvent avoids another (unnecessary)
+ // activate notification.
+ gBlockActivateEvent = true;
+ gtk_widget_grab_focus(owningWidget);
+ gBlockActivateEvent = false;
+ }
+
+ // If this is the widget that already has focus, return.
+ if (gFocusWindow == this) {
+ LOGFOCUS((" already have focus [%p]\n", (void*)this));
+ return;
+ }
+
+ // Set this window to be the focused child window
+ gFocusWindow = this;
+
+ if (mIMContext) {
+ mIMContext->OnFocusWindow(this);
+ }
+
+ LOGFOCUS((" widget now has focus in SetFocus() [%p]\n", (void*)this));
+}
+
+LayoutDeviceIntRect nsWindow::GetScreenBounds() {
+ LayoutDeviceIntRect rect;
+ if (mIsTopLevel && mContainer) {
+ // use the point including window decorations
+ gint x, y;
+ gdk_window_get_root_origin(gtk_widget_get_window(GTK_WIDGET(mContainer)),
+ &x, &y);
+ rect.MoveTo(GdkPointToDevicePixels({x, y}));
+ } else {
+ rect.MoveTo(WidgetToScreenOffset());
+ }
+ // mBounds.Size() is the window bounds, not the window-manager frame
+ // bounds (bug 581863). gdk_window_get_frame_extents would give the
+ // frame bounds, but mBounds.Size() is returned here for consistency
+ // with Resize.
+ rect.SizeTo(mBounds.Size());
+#if MOZ_LOGGING
+ gint scale = GdkScaleFactor();
+ LOG(("GetScreenBounds [%p] %d,%d -> %d x %d, unscaled %d,%d -> %d x %d\n",
+ this, rect.x, rect.y, rect.width, rect.height, rect.x / scale,
+ rect.y / scale, rect.width / scale, rect.height / scale));
+#endif
+ return rect;
+}
+
+LayoutDeviceIntSize nsWindow::GetClientSize() {
+ return LayoutDeviceIntSize(mBounds.width, mBounds.height);
+}
+
+LayoutDeviceIntRect nsWindow::GetClientBounds() {
+ // GetBounds returns a rect whose top left represents the top left of the
+ // outer bounds, but whose width/height represent the size of the inner
+ // bounds (which is messed up).
+ LayoutDeviceIntRect rect = GetBounds();
+ rect.MoveBy(GetClientOffset());
+ return rect;
+}
+
+void nsWindow::UpdateClientOffsetFromFrameExtents() {
+ AUTO_PROFILER_LABEL("nsWindow::UpdateClientOffsetFromFrameExtents", OTHER);
+
+ if (mCSDSupportLevel == CSD_SUPPORT_CLIENT && mDrawInTitlebar) {
+ return;
+ }
+
+ if (!mIsTopLevel || !mShell ||
+ gtk_window_get_window_type(GTK_WINDOW(mShell)) == GTK_WINDOW_POPUP) {
+ mClientOffset = nsIntPoint(0, 0);
+ return;
+ }
+
+ GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
+
+ GdkAtom type_returned;
+ int format_returned;
+ int length_returned;
+ long* frame_extents;
+
+ if (!gdk_property_get(gtk_widget_get_window(mShell),
+ gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE),
+ cardinal_atom,
+ 0, // offset
+ 4 * 4, // length
+ FALSE, // delete
+ &type_returned, &format_returned, &length_returned,
+ (guchar**)&frame_extents) ||
+ length_returned / sizeof(glong) != 4) {
+ mClientOffset = nsIntPoint(0, 0);
+ } else {
+ // data returned is in the order left, right, top, bottom
+ auto left = int32_t(frame_extents[0]);
+ auto top = int32_t(frame_extents[2]);
+ g_free(frame_extents);
+
+ mClientOffset = nsIntPoint(left, top);
+ }
+
+ // Send a WindowMoved notification. This ensures that BrowserParent
+ // picks up the new client offset and sends it to the child process
+ // if appropriate.
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+
+ LOG(("nsWindow::UpdateClientOffsetFromFrameExtents [%p] %d,%d\n", (void*)this,
+ mClientOffset.x, mClientOffset.y));
+}
+
+LayoutDeviceIntPoint nsWindow::GetClientOffset() {
+ return mIsX11Display ? LayoutDeviceIntPoint::FromUnknownPoint(mClientOffset)
+ : LayoutDeviceIntPoint(0, 0);
+}
+
+gboolean nsWindow::OnPropertyNotifyEvent(GtkWidget* aWidget,
+ GdkEventProperty* aEvent) {
+ if (aEvent->atom == gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE)) {
+ UpdateClientOffsetFromFrameExtents();
+ return FALSE;
+ }
+
+ if (GetCurrentTimeGetter()->PropertyNotifyHandler(aWidget, aEvent)) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GdkCursor* GetCursorForImage(imgIContainer* aCursorImage,
+ uint32_t aHotspotX, uint32_t aHotspotY) {
+ if (!aCursorImage) {
+ return nullptr;
+ }
+ GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(aCursorImage);
+ if (!pixbuf) {
+ return nullptr;
+ }
+
+ int width = gdk_pixbuf_get_width(pixbuf);
+ int height = gdk_pixbuf_get_height(pixbuf);
+
+ auto CleanupPixBuf =
+ mozilla::MakeScopeExit([&]() { g_object_unref(pixbuf); });
+
+ // Reject cursors greater than 128 pixels in some direction, to prevent
+ // spoofing.
+ // XXX ideally we should rescale. Also, we could modify the API to
+ // allow trusted content to set larger cursors.
+ //
+ // TODO(emilio, bug 1445844): Unify the solution for this with other
+ // platforms.
+ if (width > 128 || height > 128) {
+ return nullptr;
+ }
+
+ // Looks like all cursors need an alpha channel (tested on Gtk 2.4.4). This
+ // is of course not documented anywhere...
+ // So add one if there isn't one yet
+ if (!gdk_pixbuf_get_has_alpha(pixbuf)) {
+ GdkPixbuf* alphaBuf = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
+ g_object_unref(pixbuf);
+ pixbuf = alphaBuf;
+ if (!alphaBuf) {
+ return nullptr;
+ }
+ }
+
+ return gdk_cursor_new_from_pixbuf(gdk_display_get_default(), pixbuf,
+ aHotspotX, aHotspotY);
+}
+
+void nsWindow::SetCursor(nsCursor aDefaultCursor, imgIContainer* aCursorImage,
+ uint32_t aHotspotX, uint32_t aHotspotY) {
+ // if we're not the toplevel window pass up the cursor request to
+ // the toplevel window to handle it.
+ if (!mContainer && mGdkWindow) {
+ nsWindow* window = GetContainerWindow();
+ if (!window) return;
+
+ window->SetCursor(aDefaultCursor, aCursorImage, aHotspotX, aHotspotY);
+ return;
+ }
+
+ // Only change cursor if it's actually been changed
+ if (!aCursorImage && aDefaultCursor == mCursor && !mUpdateCursor) {
+ return;
+ }
+
+ mUpdateCursor = false;
+ mCursor = eCursorInvalid;
+
+ // Try to set the cursor image first, and fall back to the numeric cursor.
+ GdkCursor* newCursor = GetCursorForImage(aCursorImage, aHotspotX, aHotspotY);
+ if (!newCursor) {
+ newCursor = get_gtk_cursor(aDefaultCursor);
+ if (newCursor) {
+ mCursor = aDefaultCursor;
+ }
+ }
+
+ auto CleanupCursor = mozilla::MakeScopeExit([&]() {
+ // get_gtk_cursor returns a weak reference, which we shouldn't unref.
+ if (newCursor && mCursor == eCursorInvalid) {
+ g_object_unref(newCursor);
+ }
+ });
+
+ if (!newCursor || !mContainer) {
+ return;
+ }
+
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)),
+ newCursor);
+}
+
+void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
+ if (!mGdkWindow) return;
+
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(aRect);
+ gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
+
+ LOGDRAW(("Invalidate (rect) [%p]: %d %d %d %d\n", (void*)this, rect.x, rect.y,
+ rect.width, rect.height));
+}
+
+void* nsWindow::GetNativeData(uint32_t aDataType) {
+ switch (aDataType) {
+ case NS_NATIVE_WINDOW:
+ case NS_NATIVE_WIDGET: {
+ if (!mGdkWindow) return nullptr;
+
+ return mGdkWindow;
+ }
+
+ case NS_NATIVE_DISPLAY: {
+#ifdef MOZ_X11
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ if (gdkDisplay && GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ return GDK_DISPLAY_XDISPLAY(gdkDisplay);
+ }
+#endif /* MOZ_X11 */
+ // Don't bother to return native display on Wayland as it's for
+ // X11 only NPAPI plugins.
+ return nullptr;
+ }
+ case NS_NATIVE_SHELLWIDGET:
+ return GetToplevelWidget();
+
+ case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID:
+ case NS_NATIVE_SHAREABLE_WINDOW:
+ if (mIsX11Display) {
+ return (void*)GDK_WINDOW_XID(gdk_window_get_toplevel(mGdkWindow));
+ }
+ NS_WARNING(
+ "nsWindow::GetNativeData(): "
+ "NS_NATIVE_SHAREABLE_WINDOW / NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID is "
+ "not "
+ "handled on Wayland!");
+ return nullptr;
+ case NS_RAW_NATIVE_IME_CONTEXT: {
+ void* pseudoIMEContext = GetPseudoIMEContext();
+ if (pseudoIMEContext) {
+ return pseudoIMEContext;
+ }
+ // If IME context isn't available on this widget, we should set |this|
+ // instead of nullptr.
+ if (!mIMContext) {
+ return this;
+ }
+ return mIMContext.get();
+ }
+ case NS_NATIVE_OPENGL_CONTEXT:
+ return nullptr;
+ case NS_NATIVE_EGL_WINDOW: {
+ if (mIsX11Display) {
+ return mGdkWindow ? (void*)GDK_WINDOW_XID(mGdkWindow) : nullptr;
+ }
+#ifdef MOZ_WAYLAND
+ if (mContainer) {
+ return moz_container_wayland_get_egl_window(mContainer,
+ GdkScaleFactor());
+ }
+#endif
+ return nullptr;
+ }
+ default:
+ NS_WARNING("nsWindow::GetNativeData called with bad value");
+ return nullptr;
+ }
+}
+
+nsresult nsWindow::SetTitle(const nsAString& aTitle) {
+ if (!mShell) return NS_OK;
+
+ // convert the string into utf8 and set the title.
+#define UTF8_FOLLOWBYTE(ch) (((ch)&0xC0) == 0x80)
+ NS_ConvertUTF16toUTF8 titleUTF8(aTitle);
+ if (titleUTF8.Length() > NS_WINDOW_TITLE_MAX_LENGTH) {
+ // Truncate overlong titles (bug 167315). Make sure we chop after a
+ // complete sequence by making sure the next char isn't a follow-byte.
+ uint32_t len = NS_WINDOW_TITLE_MAX_LENGTH;
+ while (UTF8_FOLLOWBYTE(titleUTF8[len])) --len;
+ titleUTF8.Truncate(len);
+ }
+ gtk_window_set_title(GTK_WINDOW(mShell), (const char*)titleUTF8.get());
+
+ return NS_OK;
+}
+
+void nsWindow::SetIcon(const nsAString& aIconSpec) {
+ if (!mShell) return;
+
+ nsAutoCString iconName;
+
+ if (aIconSpec.EqualsLiteral("default")) {
+ nsAutoString brandName;
+ WidgetUtils::GetBrandShortName(brandName);
+ if (brandName.IsEmpty()) {
+ brandName.AssignLiteral(u"Mozilla");
+ }
+ AppendUTF16toUTF8(brandName, iconName);
+ ToLowerCase(iconName);
+ } else {
+ AppendUTF16toUTF8(aIconSpec, iconName);
+ }
+
+ nsCOMPtr<nsIFile> iconFile;
+ nsAutoCString path;
+
+ gint* iconSizes = gtk_icon_theme_get_icon_sizes(gtk_icon_theme_get_default(),
+ iconName.get());
+ bool foundIcon = (iconSizes[0] != 0);
+ g_free(iconSizes);
+
+ if (!foundIcon) {
+ // Look for icons with the following suffixes appended to the base name
+ // The last two entries (for the old XPM format) will be ignored unless
+ // no icons are found using other suffixes. XPM icons are deprecated.
+
+ const char16_t extensions[9][8] = {u".png", u"16.png", u"32.png",
+ u"48.png", u"64.png", u"128.png",
+ u"256.png", u".xpm", u"16.xpm"};
+
+ for (uint32_t i = 0; i < ArrayLength(extensions); i++) {
+ // Don't bother looking for XPM versions if we found a PNG.
+ if (i == ArrayLength(extensions) - 2 && foundIcon) break;
+
+ ResolveIconName(aIconSpec, nsDependentString(extensions[i]),
+ getter_AddRefs(iconFile));
+ if (iconFile) {
+ iconFile->GetNativePath(path);
+ GdkPixbuf* icon = gdk_pixbuf_new_from_file(path.get(), nullptr);
+ if (icon) {
+ gtk_icon_theme_add_builtin_icon(iconName.get(),
+ gdk_pixbuf_get_height(icon), icon);
+ g_object_unref(icon);
+ foundIcon = true;
+ }
+ }
+ }
+ }
+
+ // leave the default icon intact if no matching icons were found
+ if (foundIcon) {
+ gtk_window_set_icon_name(GTK_WINDOW(mShell), iconName.get());
+ }
+}
+
+LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
+ nsIntPoint origin(0, 0);
+ GetWindowOrigin(mGdkWindow, &origin.x, &origin.y);
+
+ return GdkPointToDevicePixels({origin.x, origin.y});
+}
+
+void nsWindow::CaptureMouse(bool aCapture) {
+ LOG(("CaptureMouse %p\n", (void*)this));
+
+ if (!mGdkWindow) return;
+
+ if (!mContainer) return;
+
+ if (aCapture) {
+ gtk_grab_add(GTK_WIDGET(mContainer));
+ GrabPointer(GetLastUserInputTime());
+ } else {
+ ReleaseGrabs();
+ gtk_grab_remove(GTK_WIDGET(mContainer));
+ }
+}
+
+void nsWindow::CaptureRollupEvents(nsIRollupListener* aListener,
+ bool aDoCapture) {
+ if (!mGdkWindow) return;
+
+ if (!mContainer) return;
+
+ LOG(("CaptureRollupEvents %p %i\n", this, int(aDoCapture)));
+
+ if (aDoCapture) {
+ gRollupListener = aListener;
+ // Don't add a grab if a drag is in progress, or if the widget is a drag
+ // feedback popup. (panels with type="drag").
+ if (!mIsDragPopup && !nsWindow::DragInProgress()) {
+ gtk_grab_add(GTK_WIDGET(mContainer));
+ GrabPointer(GetLastUserInputTime());
+ }
+ } else {
+ if (!nsWindow::DragInProgress()) {
+ ReleaseGrabs();
+ }
+ // There may not have been a drag in process when aDoCapture was set,
+ // so make sure to remove any added grab. This is a no-op if the grab
+ // was not added to this widget.
+ gtk_grab_remove(GTK_WIDGET(mContainer));
+ gRollupListener = nullptr;
+ }
+}
+
+nsresult nsWindow::GetAttention(int32_t aCycleCount) {
+ LOG(("nsWindow::GetAttention [%p]\n", (void*)this));
+
+ GtkWidget* top_window = GetToplevelWidget();
+ GtkWidget* top_focused_window =
+ gFocusWindow ? gFocusWindow->GetToplevelWidget() : nullptr;
+
+ // Don't get attention if the window is focused anyway.
+ if (top_window && (gtk_widget_get_visible(top_window)) &&
+ top_window != top_focused_window) {
+ SetUrgencyHint(top_window, true);
+ }
+
+ return NS_OK;
+}
+
+bool nsWindow::HasPendingInputEvent() {
+ // This sucks, but gtk/gdk has no way to answer the question we want while
+ // excluding paint events, and there's no X API that will let us peek
+ // without blocking or removing. To prevent event reordering, peek
+ // anything except expose events. Reordering expose and others should be
+ // ok, hopefully.
+ bool haveEvent = false;
+#ifdef MOZ_X11
+ XEvent ev;
+ if (mIsX11Display) {
+ Display* display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+ haveEvent = XCheckMaskEvent(
+ display,
+ KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
+ EnterWindowMask | LeaveWindowMask | PointerMotionMask |
+ PointerMotionHintMask | Button1MotionMask | Button2MotionMask |
+ Button3MotionMask | Button4MotionMask | Button5MotionMask |
+ ButtonMotionMask | KeymapStateMask | VisibilityChangeMask |
+ StructureNotifyMask | ResizeRedirectMask | SubstructureNotifyMask |
+ SubstructureRedirectMask | FocusChangeMask | PropertyChangeMask |
+ ColormapChangeMask | OwnerGrabButtonMask,
+ &ev);
+ if (haveEvent) {
+ XPutBackEvent(display, &ev);
+ }
+ }
+#endif
+ return haveEvent;
+}
+
+#if 0
+# ifdef DEBUG
+// Paint flashing code (disabled for cairo - see below)
+
+# define CAPS_LOCK_IS_ON \
+ (KeymapWrapper::AreModifiersCurrentlyActive(KeymapWrapper::CAPS_LOCK))
+
+# define WANT_PAINT_FLASHING (debug_WantPaintFlashing() && CAPS_LOCK_IS_ON)
+
+# ifdef MOZ_X11
+static void
+gdk_window_flash(GdkWindow * aGdkWindow,
+ unsigned int aTimes,
+ unsigned int aInterval, // Milliseconds
+ GdkRegion * aRegion)
+{
+ gint x;
+ gint y;
+ gint width;
+ gint height;
+ guint i;
+ GdkGC * gc = 0;
+ GdkColor white;
+
+ gdk_window_get_geometry(aGdkWindow,nullptr,nullptr,&width,&height);
+
+ gdk_window_get_origin (aGdkWindow,
+ &x,
+ &y);
+
+ gc = gdk_gc_new(gdk_get_default_root_window());
+
+ white.pixel = WhitePixel(gdk_display,DefaultScreen(gdk_display));
+
+ gdk_gc_set_foreground(gc,&white);
+ gdk_gc_set_function(gc,GDK_XOR);
+ gdk_gc_set_subwindow(gc,GDK_INCLUDE_INFERIORS);
+
+ gdk_region_offset(aRegion, x, y);
+ gdk_gc_set_clip_region(gc, aRegion);
+
+ /*
+ * Need to do this twice so that the XOR effect can replace
+ * the original window contents.
+ */
+ for (i = 0; i < aTimes * 2; i++)
+ {
+ gdk_draw_rectangle(gdk_get_default_root_window(),
+ gc,
+ TRUE,
+ x,
+ y,
+ width,
+ height);
+
+ gdk_flush();
+
+ PR_Sleep(PR_MillisecondsToInterval(aInterval));
+ }
+
+ gdk_gc_destroy(gc);
+
+ gdk_region_offset(aRegion, -x, -y);
+}
+# endif /* MOZ_X11 */
+# endif // DEBUG
+#endif
+
+#ifdef cairo_copy_clip_rectangle_list
+# error "Looks like we're including Mozilla's cairo instead of system cairo"
+#endif
+static bool ExtractExposeRegion(LayoutDeviceIntRegion& aRegion, cairo_t* cr) {
+ cairo_rectangle_list_t* rects = cairo_copy_clip_rectangle_list(cr);
+ if (rects->status != CAIRO_STATUS_SUCCESS) {
+ NS_WARNING("Failed to obtain cairo rectangle list.");
+ return false;
+ }
+
+ for (int i = 0; i < rects->num_rectangles; i++) {
+ const cairo_rectangle_t& r = rects->rectangles[i];
+ aRegion.Or(aRegion,
+ LayoutDeviceIntRect::Truncate(r.x, r.y, r.width, r.height));
+ LOGDRAW(("\t%f %f %f %f\n", r.x, r.y, r.width, r.height));
+ }
+
+ cairo_rectangle_list_destroy(rects);
+ return true;
+}
+
+#ifdef MOZ_WAYLAND
+void nsWindow::MaybeResumeCompositor() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ if (mIsDestroyed || !mNeedsCompositorResume) {
+ return;
+ }
+
+ if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) {
+ MOZ_ASSERT(mCompositorWidgetDelegate);
+ if (mCompositorWidgetDelegate) {
+ mCompositorInitiallyPaused = false;
+ mNeedsCompositorResume = false;
+ remoteRenderer->SendResumeAsync();
+ }
+ remoteRenderer->SendForcePresent();
+ }
+}
+
+void nsWindow::CreateCompositorVsyncDispatcher() {
+ if (!mWaylandVsyncSource) {
+ nsBaseWidget::CreateCompositorVsyncDispatcher();
+ return;
+ }
+
+ if (XRE_IsParentProcess()) {
+ if (!mCompositorVsyncDispatcherLock) {
+ mCompositorVsyncDispatcherLock =
+ MakeUnique<Mutex>("mCompositorVsyncDispatcherLock");
+ }
+ MutexAutoLock lock(*mCompositorVsyncDispatcherLock);
+ if (!mCompositorVsyncDispatcher) {
+ mCompositorVsyncDispatcher =
+ new CompositorVsyncDispatcher(mWaylandVsyncSource);
+ }
+ }
+}
+#endif
+
+gboolean nsWindow::OnExposeEvent(cairo_t* cr) {
+ // Send any pending resize events so that layout can update.
+ // May run event loop.
+ MaybeDispatchResized();
+
+ if (mIsDestroyed) {
+ return FALSE;
+ }
+
+ // Windows that are not visible will be painted after they become visible.
+ if (!mGdkWindow || !mHasMappedToplevel) {
+ return FALSE;
+ }
+#ifdef MOZ_WAYLAND
+ if (!mIsX11Display && !moz_container_wayland_can_draw(mContainer)) {
+ return FALSE;
+ }
+#endif
+
+ nsIWidgetListener* listener = GetListener();
+ if (!listener) return FALSE;
+
+ LOGDRAW(("received expose event [%p] %p 0x%lx (rects follow):\n", this,
+ mGdkWindow, mIsX11Display ? gdk_x11_window_get_xid(mGdkWindow) : 0));
+ LayoutDeviceIntRegion exposeRegion;
+ if (!ExtractExposeRegion(exposeRegion, cr)) {
+ return FALSE;
+ }
+
+ gint scale = GdkScaleFactor();
+ LayoutDeviceIntRegion region = exposeRegion;
+ region.ScaleRoundOut(scale, scale);
+
+ if (GetLayerManager()->AsKnowsCompositor() && mCompositorSession) {
+ // We need to paint to the screen even if nothing changed, since if we
+ // don't have a compositing window manager, our pixels could be stale.
+ GetLayerManager()->SetNeedsComposite(true);
+ GetLayerManager()->SendInvalidRegion(region.ToUnknownRegion());
+ }
+
+ RefPtr<nsWindow> strongThis(this);
+
+ // Dispatch WillPaintWindow notification to allow scripts etc. to run
+ // before we paint
+ {
+ listener->WillPaintWindow(this);
+
+ // If the window has been destroyed during the will paint notification,
+ // there is nothing left to do.
+ if (!mGdkWindow) return TRUE;
+
+ // Re-get the listener since the will paint notification might have
+ // killed it.
+ listener = GetListener();
+ if (!listener) return FALSE;
+ }
+
+ if (GetLayerManager()->AsKnowsCompositor() &&
+ GetLayerManager()->NeedsComposite()) {
+ GetLayerManager()->ScheduleComposite();
+ GetLayerManager()->SetNeedsComposite(false);
+ }
+
+ // Our bounds may have changed after calling WillPaintWindow. Clip
+ // to the new bounds here. The region is relative to this
+ // window.
+ region.And(region, LayoutDeviceIntRect(0, 0, mBounds.width, mBounds.height));
+
+ bool shaped = false;
+ if (eTransparencyTransparent == GetTransparencyMode()) {
+ auto window = static_cast<nsWindow*>(GetTopLevelWidget());
+ if (mTransparencyBitmapForTitlebar) {
+ if (mSizeState == nsSizeMode_Normal) {
+ window->UpdateTitlebarTransparencyBitmap();
+ } else {
+ window->ClearTransparencyBitmap();
+ }
+ } else {
+ if (mHasAlphaVisual) {
+ // Remove possible shape mask from when window manger was not
+ // previously compositing.
+ window->ClearTransparencyBitmap();
+ } else {
+ shaped = true;
+ }
+ }
+ }
+
+ if (!shaped) {
+ GList* children = gdk_window_peek_children(mGdkWindow);
+ while (children) {
+ GdkWindow* gdkWin = GDK_WINDOW(children->data);
+ nsWindow* kid = get_window_for_gdk_window(gdkWin);
+ if (kid && gdk_window_is_visible(gdkWin)) {
+ AutoTArray<LayoutDeviceIntRect, 1> clipRects;
+ kid->GetWindowClipRegion(&clipRects);
+ LayoutDeviceIntRect bounds = kid->GetBounds();
+ for (uint32_t i = 0; i < clipRects.Length(); ++i) {
+ LayoutDeviceIntRect r = clipRects[i] + bounds.TopLeft();
+ region.Sub(region, r);
+ }
+ }
+ children = children->next;
+ }
+ }
+
+ if (region.IsEmpty()) {
+ return TRUE;
+ }
+
+ // If this widget uses OMTC...
+ if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT ||
+ GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_WR) {
+ listener->PaintWindow(this, region);
+
+ // Re-get the listener since the will paint notification might have
+ // killed it.
+ listener = GetListener();
+ if (!listener) return TRUE;
+
+ listener->DidPaintWindow();
+ return TRUE;
+ }
+
+ BufferMode layerBuffering = BufferMode::BUFFERED;
+ RefPtr<DrawTarget> dt = StartRemoteDrawingInRegion(region, &layerBuffering);
+ if (!dt || !dt->IsValid()) {
+ return FALSE;
+ }
+ RefPtr<gfxContext> ctx;
+ IntRect boundsRect = region.GetBounds().ToUnknownRect();
+ IntPoint offset(0, 0);
+ if (dt->GetSize() == boundsRect.Size()) {
+ offset = boundsRect.TopLeft();
+ dt->SetTransform(Matrix::Translation(-offset));
+ }
+
+#ifdef MOZ_X11
+ if (shaped) {
+ // Collapse update area to the bounding box. This is so we only have to
+ // call UpdateTranslucentWindowAlpha once. After we have dropped
+ // support for non-Thebes graphics, UpdateTranslucentWindowAlpha will be
+ // our private interface so we can rework things to avoid this.
+ dt->PushClipRect(Rect(boundsRect));
+
+ // The double buffering is done here to extract the shape mask.
+ // (The shape mask won't be necessary when a visual with an alpha
+ // channel is used on compositing window managers.)
+ layerBuffering = BufferMode::BUFFER_NONE;
+ RefPtr<DrawTarget> destDT =
+ dt->CreateSimilarDrawTarget(boundsRect.Size(), SurfaceFormat::B8G8R8A8);
+ if (!destDT || !destDT->IsValid()) {
+ return FALSE;
+ }
+ destDT->SetTransform(Matrix::Translation(-boundsRect.TopLeft()));
+ ctx = gfxContext::CreatePreservingTransformOrNull(destDT);
+ } else {
+ gfxUtils::ClipToRegion(dt, region.ToUnknownRegion());
+ ctx = gfxContext::CreatePreservingTransformOrNull(dt);
+ }
+ MOZ_ASSERT(ctx); // checked both dt and destDT valid draw target above
+
+# if 0
+ // NOTE: Paint flashing region would be wrong for cairo, since
+ // cairo inflates the update region, etc. So don't paint flash
+ // for cairo.
+# ifdef DEBUG
+ // XXX aEvent->region may refer to a newly-invalid area. FIXME
+ if (0 && WANT_PAINT_FLASHING && gtk_widget_get_window(aEvent))
+ gdk_window_flash(mGdkWindow, 1, 100, aEvent->region);
+# endif
+# endif
+
+#endif // MOZ_X11
+
+ bool painted = false;
+ {
+ if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+ if (GetTransparencyMode() == eTransparencyTransparent &&
+ layerBuffering == BufferMode::BUFFER_NONE && mHasAlphaVisual) {
+ // If our draw target is unbuffered and we use an alpha channel,
+ // clear the image beforehand to ensure we don't get artifacts from a
+ // reused SHM image. See bug 1258086.
+ dt->ClearRect(Rect(boundsRect));
+ }
+ AutoLayerManagerSetup setupLayerManager(this, ctx, layerBuffering);
+ painted = listener->PaintWindow(this, region);
+
+ // Re-get the listener since the will paint notification might have
+ // killed it.
+ listener = GetListener();
+ if (!listener) return TRUE;
+ }
+ }
+
+#ifdef MOZ_X11
+ // PaintWindow can Destroy us (bug 378273), avoid doing any paint
+ // operations below if that happened - it will lead to XError and exit().
+ if (shaped) {
+ if (MOZ_LIKELY(!mIsDestroyed)) {
+ if (painted) {
+ RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
+
+ UpdateAlpha(surf, boundsRect);
+
+ dt->DrawSurface(surf, Rect(boundsRect),
+ Rect(0, 0, boundsRect.width, boundsRect.height),
+ DrawSurfaceOptions(SamplingFilter::POINT),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+ }
+ }
+ }
+
+ ctx = nullptr;
+ dt->PopClip();
+
+#endif // MOZ_X11
+
+ EndRemoteDrawingInRegion(dt, region);
+
+ listener->DidPaintWindow();
+
+ // Synchronously flush any new dirty areas
+ cairo_region_t* dirtyArea = gdk_window_get_update_area(mGdkWindow);
+
+ if (dirtyArea) {
+ gdk_window_invalidate_region(mGdkWindow, dirtyArea, false);
+ cairo_region_destroy(dirtyArea);
+ gdk_window_process_updates(mGdkWindow, false);
+ }
+
+ // check the return value!
+ return TRUE;
+}
+
+void nsWindow::UpdateAlpha(SourceSurface* aSourceSurface,
+ nsIntRect aBoundsRect) {
+ // We need to create our own buffer to force the stride to match the
+ // expected stride.
+ int32_t stride =
+ GetAlignedStride<4>(aBoundsRect.width, BytesPerPixel(SurfaceFormat::A8));
+ if (stride == 0) {
+ return;
+ }
+ int32_t bufferSize = stride * aBoundsRect.height;
+ auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize);
+ {
+ RefPtr<DrawTarget> drawTarget = gfxPlatform::CreateDrawTargetForData(
+ imageBuffer.get(), aBoundsRect.Size(), stride, SurfaceFormat::A8);
+
+ if (drawTarget) {
+ drawTarget->DrawSurface(aSourceSurface,
+ Rect(0, 0, aBoundsRect.width, aBoundsRect.height),
+ Rect(0, 0, aSourceSurface->GetSize().width,
+ aSourceSurface->GetSize().height),
+ DrawSurfaceOptions(SamplingFilter::POINT),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+ }
+ }
+ UpdateTranslucentWindowAlphaInternal(aBoundsRect, imageBuffer.get(), stride);
+}
+
+gboolean nsWindow::OnConfigureEvent(GtkWidget* aWidget,
+ GdkEventConfigure* aEvent) {
+ // These events are only received on toplevel windows.
+ //
+ // GDK ensures that the coordinates are the client window top-left wrt the
+ // root window.
+ //
+ // GDK calculates the cordinates for real ConfigureNotify events on
+ // managed windows (that would normally be relative to the parent
+ // window).
+ //
+ // Synthetic ConfigureNotify events are from the window manager and
+ // already relative to the root window. GDK creates all X windows with
+ // border_width = 0, so synthetic events also indicate the top-left of
+ // the client window.
+ //
+ // Override-redirect windows are children of the root window so parent
+ // coordinates are root coordinates.
+
+ LOG(("configure event [%p] %d %d %d %d\n", (void*)this, aEvent->x, aEvent->y,
+ aEvent->width, aEvent->height));
+
+ if (mPendingConfigures > 0) {
+ mPendingConfigures--;
+ }
+
+ LayoutDeviceIntRect screenBounds = GetScreenBounds();
+
+ if (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog) {
+ // This check avoids unwanted rollup on spurious configure events from
+ // Cygwin/X (bug 672103).
+ if (mBounds.x != screenBounds.x || mBounds.y != screenBounds.y) {
+ CheckForRollup(0, 0, false, true);
+ }
+ }
+
+ NS_ASSERTION(GTK_IS_WINDOW(aWidget),
+ "Configure event on widget that is not a GtkWindow");
+ if (gtk_window_get_window_type(GTK_WINDOW(aWidget)) == GTK_WINDOW_POPUP) {
+ // Override-redirect window
+ //
+ // These windows should not be moved by the window manager, and so any
+ // change in position is a result of our direction. mBounds has
+ // already been set in std::move() or Resize(), and that is more
+ // up-to-date than the position in the ConfigureNotify event if the
+ // event is from an earlier window move.
+ //
+ // Skipping the WindowMoved call saves context menus from an infinite
+ // loop when nsXULPopupManager::PopupMoved moves the window to the new
+ // position and nsMenuPopupFrame::SetPopupPosition adds
+ // offsetForContextMenu on each iteration.
+
+ // Our back buffer might have been invalidated while we drew the last
+ // frame, and its contents might be incorrect. See bug 1280653 comment 7
+ // and comment 10. Specifically we must ensure we recomposite the frame
+ // as soon as possible to avoid the corrupted frame being displayed.
+ GetLayerManager()->FlushRendering();
+ return FALSE;
+ }
+
+ mBounds.MoveTo(screenBounds.TopLeft());
+
+ // XXX mozilla will invalidate the entire window after this move
+ // complete. wtf?
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+
+ // A GTK app would usually update its client area size in response to
+ // a "size-allocate" signal.
+ // However, we need to set mBounds in advance at Resize()
+ // as JS code expects immediate window size change.
+ // If Gecko requests a resize from GTK, but subsequently,
+ // before a corresponding "size-allocate" signal is emitted, the window is
+ // resized to its former size via other means, such as maximizing,
+ // then there is no "size-allocate" signal from which to update
+ // the value of mBounds. Similarly, if Gecko's resize request is refused
+ // by the window manager, then there will be no "size-allocate" signal.
+ // In the refused request case, the window manager is required to dispatch
+ // a ConfigureNotify event. mBounds can then be updated here.
+ // This seems to also be sufficient to update mBounds when Gecko resizes
+ // the window from maximized size and then immediately maximizes again.
+ if (!mBoundsAreValid) {
+ GtkAllocation allocation = {-1, -1, 0, 0};
+ gtk_window_get_size(GTK_WINDOW(mShell), &allocation.width,
+ &allocation.height);
+ OnSizeAllocate(&allocation);
+ }
+
+ return FALSE;
+}
+
+void nsWindow::OnContainerUnrealize() {
+ // The GdkWindows are about to be destroyed (but not deleted), so remove
+ // their references back to their container widget while the GdkWindow
+ // hierarchy is still available.
+
+ if (mGdkWindow) {
+ DestroyChildWindows();
+
+ g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr);
+ mGdkWindow = nullptr;
+ }
+}
+
+void nsWindow::OnSizeAllocate(GtkAllocation* aAllocation) {
+ LOG(("nsWindow::OnSizeAllocate [%p] %d,%d -> %d x %d\n", (void*)this,
+ aAllocation->x, aAllocation->y, aAllocation->width,
+ aAllocation->height));
+
+ // Client offset are updated by _NET_FRAME_EXTENTS on X11 when system titlebar
+ // is enabled. In either cases (Wayland or system titlebar is off on X11)
+ // we don't get _NET_FRAME_EXTENTS X11 property notification so we derive
+ // it from mContainer position.
+ if (mCSDSupportLevel == CSD_SUPPORT_CLIENT) {
+ if (!mIsX11Display || (mIsX11Display && mDrawInTitlebar)) {
+ UpdateClientOffsetFromCSDWindow();
+ }
+ }
+
+ mBoundsAreValid = true;
+
+ LayoutDeviceIntSize size = GdkRectToDevicePixels(*aAllocation).Size();
+ if (mBounds.Size() == size) {
+ LOG((" Already the same size"));
+ // We were already resized at nsWindow::OnConfigureEvent() so skip it.
+ return;
+ }
+
+ // Invalidate the new part of the window now for the pending paint to
+ // minimize background flashes (GDK does not do this for external resizes
+ // of toplevels.)
+ if (mBounds.width < size.width) {
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect(
+ mBounds.width, 0, size.width - mBounds.width, size.height));
+ gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
+ }
+ if (mBounds.height < size.height) {
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect(
+ 0, mBounds.height, size.width, size.height - mBounds.height));
+ gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
+ }
+
+ mBounds.SizeTo(size);
+
+#ifdef MOZ_X11
+ // Notify the GtkCompositorWidget of a ClientSizeChange
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
+ }
+#endif
+
+ // Gecko permits running nested event loops during processing of events,
+ // GtkWindow callers of gtk_widget_size_allocate expect the signal
+ // handlers to return sometime in the near future.
+ mNeedsDispatchResized = true;
+ NS_DispatchToCurrentThread(NewRunnableMethod(
+ "nsWindow::MaybeDispatchResized", this, &nsWindow::MaybeDispatchResized));
+}
+
+void nsWindow::OnDeleteEvent() {
+ if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
+}
+
+void nsWindow::OnEnterNotifyEvent(GdkEventCrossing* aEvent) {
+ // This skips NotifyVirtual and NotifyNonlinearVirtual enter notify events
+ // when the pointer enters a child window. If the destination window is a
+ // Gecko window then we'll catch the corresponding event on that window,
+ // but we won't notice when the pointer directly enters a foreign (plugin)
+ // child window without passing over a visible portion of a Gecko window.
+ if (aEvent->subwindow != nullptr) return;
+
+ // Check before is_parent_ungrab_enter() as the button state may have
+ // changed while a non-Gecko ancestor window had a pointer grab.
+ DispatchMissedButtonReleases(aEvent);
+
+ if (is_parent_ungrab_enter(aEvent)) return;
+
+ WidgetMouseEvent event(true, eMouseEnterIntoWidget, this,
+ WidgetMouseEvent::eReal);
+
+ event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ LOG(("OnEnterNotify: %p\n", (void*)this));
+
+ DispatchInputEvent(&event);
+}
+
+// XXX Is this the right test for embedding cases?
+static bool is_top_level_mouse_exit(GdkWindow* aWindow,
+ GdkEventCrossing* aEvent) {
+ auto x = gint(aEvent->x_root);
+ auto y = gint(aEvent->y_root);
+ GdkDisplay* display = gdk_window_get_display(aWindow);
+ GdkWindow* winAtPt = gdk_display_get_window_at_pointer(display, &x, &y);
+ if (!winAtPt) return true;
+ GdkWindow* topLevelAtPt = gdk_window_get_toplevel(winAtPt);
+ GdkWindow* topLevelWidget = gdk_window_get_toplevel(aWindow);
+ return topLevelAtPt != topLevelWidget;
+}
+
+void nsWindow::OnLeaveNotifyEvent(GdkEventCrossing* aEvent) {
+ // This ignores NotifyVirtual and NotifyNonlinearVirtual leave notify
+ // events when the pointer leaves a child window. If the destination
+ // window is a Gecko window then we'll catch the corresponding event on
+ // that window.
+ //
+ // XXXkt However, we will miss toplevel exits when the pointer directly
+ // leaves a foreign (plugin) child window without passing over a visible
+ // portion of a Gecko window.
+ if (aEvent->subwindow != nullptr) return;
+
+ WidgetMouseEvent event(true, eMouseExitFromWidget, this,
+ WidgetMouseEvent::eReal);
+
+ event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ event.mExitFrom = Some(is_top_level_mouse_exit(mGdkWindow, aEvent)
+ ? WidgetMouseEvent::ePlatformTopLevel
+ : WidgetMouseEvent::ePlatformChild);
+
+ LOG(("OnLeaveNotify: %p\n", (void*)this));
+
+ DispatchInputEvent(&event);
+}
+
+template <typename Event>
+static LayoutDeviceIntPoint GetRefPoint(nsWindow* aWindow, Event* aEvent) {
+ if (aEvent->window == aWindow->GetGdkWindow()) {
+ // we are the window that the event happened on so no need for expensive
+ // WidgetToScreenOffset
+ return aWindow->GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ }
+ // XXX we're never quite sure which GdkWindow the event came from due to our
+ // custom bubbling in scroll_event_cb(), so use ScreenToWidget to translate
+ // the screen root coordinates into coordinates relative to this widget.
+ return aWindow->GdkEventCoordsToDevicePixels(aEvent->x_root, aEvent->y_root) -
+ aWindow->WidgetToScreenOffset();
+}
+
+void nsWindow::OnMotionNotifyEvent(GdkEventMotion* aEvent) {
+ if (mWindowShouldStartDragging) {
+ mWindowShouldStartDragging = false;
+ // find the top-level window
+ GdkWindow* gdk_window = gdk_window_get_toplevel(mGdkWindow);
+ MOZ_ASSERT(gdk_window, "gdk_window_get_toplevel should not return null");
+
+ bool canDrag = true;
+ if (mIsX11Display) {
+ // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=789054
+ // To avoid crashes disable double-click on WM without _NET_WM_MOVERESIZE.
+ // See _should_perform_ewmh_drag() at gdkwindow-x11.c
+ GdkScreen* screen = gdk_window_get_screen(gdk_window);
+ GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
+ if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
+ canDrag = false;
+ }
+ }
+
+ if (canDrag) {
+ gdk_window_begin_move_drag(gdk_window, 1, aEvent->x_root, aEvent->y_root,
+ aEvent->time);
+ return;
+ }
+ }
+
+ // see if we can compress this event
+ // XXXldb Why skip every other motion event when we have multiple,
+ // but not more than that?
+ bool synthEvent = false;
+#ifdef MOZ_X11
+ XEvent xevent;
+
+ if (mIsX11Display) {
+ while (XPending(GDK_WINDOW_XDISPLAY(aEvent->window))) {
+ XEvent peeked;
+ XPeekEvent(GDK_WINDOW_XDISPLAY(aEvent->window), &peeked);
+ if (peeked.xany.window != gdk_x11_window_get_xid(aEvent->window) ||
+ peeked.type != MotionNotify)
+ break;
+
+ synthEvent = true;
+ XNextEvent(GDK_WINDOW_XDISPLAY(aEvent->window), &xevent);
+ }
+ }
+#endif /* MOZ_X11 */
+
+ WidgetMouseEvent event(true, eMouseMove, this, WidgetMouseEvent::eReal);
+
+ gdouble pressure = 0;
+ gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
+ // Sometime gdk generate 0 pressure value between normal values
+ // We have to ignore that and use last valid value
+ if (pressure) mLastMotionPressure = pressure;
+ event.mPressure = mLastMotionPressure;
+
+ guint modifierState;
+ if (synthEvent) {
+#ifdef MOZ_X11
+ event.mRefPoint.x = nscoord(xevent.xmotion.x);
+ event.mRefPoint.y = nscoord(xevent.xmotion.y);
+
+ modifierState = xevent.xmotion.state;
+
+ event.AssignEventTime(GetWidgetEventTime(xevent.xmotion.time));
+#else
+ event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+
+ modifierState = aEvent->state;
+
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+#endif /* MOZ_X11 */
+ } else {
+ event.mRefPoint = GetRefPoint(this, aEvent);
+
+ modifierState = aEvent->state;
+
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+ }
+
+ KeymapWrapper::InitInputEvent(event, modifierState);
+
+ DispatchInputEvent(&event);
+}
+
+// If the automatic pointer grab on ButtonPress has deactivated before
+// ButtonRelease, and the mouse button is released while the pointer is not
+// over any a Gecko window, then the ButtonRelease event will not be received.
+// (A similar situation exists when the pointer is grabbed with owner_events
+// True as the ButtonRelease may be received on a foreign [plugin] window).
+// Use this method to check for released buttons when the pointer returns to a
+// Gecko window.
+void nsWindow::DispatchMissedButtonReleases(GdkEventCrossing* aGdkEvent) {
+ guint changed = aGdkEvent->state ^ gButtonState;
+ // Only consider button releases.
+ // (Ignore button presses that occurred outside Gecko.)
+ guint released = changed & gButtonState;
+ gButtonState = aGdkEvent->state;
+
+ // Loop over each button, excluding mouse wheel buttons 4 and 5 for which
+ // GDK ignores releases.
+ for (guint buttonMask = GDK_BUTTON1_MASK; buttonMask <= GDK_BUTTON3_MASK;
+ buttonMask <<= 1) {
+ if (released & buttonMask) {
+ int16_t buttonType;
+ switch (buttonMask) {
+ case GDK_BUTTON1_MASK:
+ buttonType = MouseButton::ePrimary;
+ break;
+ case GDK_BUTTON2_MASK:
+ buttonType = MouseButton::eMiddle;
+ break;
+ default:
+ NS_ASSERTION(buttonMask == GDK_BUTTON3_MASK,
+ "Unexpected button mask");
+ buttonType = MouseButton::eSecondary;
+ }
+
+ LOG(("Synthesized button %u release on %p\n", guint(buttonType + 1),
+ (void*)this));
+
+ // Dispatch a synthesized button up event to tell Gecko about the
+ // change in state. This event is marked as synthesized so that
+ // it is not dispatched as a DOM event, because we don't know the
+ // position, widget, modifiers, or time/order.
+ WidgetMouseEvent synthEvent(true, eMouseUp, this,
+ WidgetMouseEvent::eSynthesized);
+ synthEvent.mButton = buttonType;
+ DispatchInputEvent(&synthEvent);
+ }
+ }
+}
+
+void nsWindow::InitButtonEvent(WidgetMouseEvent& aEvent,
+ GdkEventButton* aGdkEvent) {
+ aEvent.mRefPoint = GetRefPoint(this, aGdkEvent);
+
+ guint modifierState = aGdkEvent->state;
+ // aEvent's state includes the button state from immediately before this
+ // event. If aEvent is a mousedown or mouseup event, we need to update
+ // the button state.
+ guint buttonMask = 0;
+ switch (aGdkEvent->button) {
+ case 1:
+ buttonMask = GDK_BUTTON1_MASK;
+ break;
+ case 2:
+ buttonMask = GDK_BUTTON2_MASK;
+ break;
+ case 3:
+ buttonMask = GDK_BUTTON3_MASK;
+ break;
+ }
+ if (aGdkEvent->type == GDK_BUTTON_RELEASE) {
+ modifierState &= ~buttonMask;
+ } else {
+ modifierState |= buttonMask;
+ }
+
+ KeymapWrapper::InitInputEvent(aEvent, modifierState);
+
+ aEvent.AssignEventTime(GetWidgetEventTime(aGdkEvent->time));
+
+ switch (aGdkEvent->type) {
+ case GDK_2BUTTON_PRESS:
+ aEvent.mClickCount = 2;
+ break;
+ case GDK_3BUTTON_PRESS:
+ aEvent.mClickCount = 3;
+ break;
+ // default is one click
+ default:
+ aEvent.mClickCount = 1;
+ }
+}
+
+static guint ButtonMaskFromGDKButton(guint button) {
+ return GDK_BUTTON1_MASK << (button - 1);
+}
+
+void nsWindow::DispatchContextMenuEventFromMouseEvent(uint16_t domButton,
+ GdkEventButton* aEvent) {
+ if (domButton == MouseButton::eSecondary && MOZ_LIKELY(!mIsDestroyed)) {
+ WidgetMouseEvent contextMenuEvent(true, eContextMenu, this,
+ WidgetMouseEvent::eReal);
+ InitButtonEvent(contextMenuEvent, aEvent);
+ contextMenuEvent.mPressure = mLastMotionPressure;
+ DispatchInputEvent(&contextMenuEvent);
+ }
+}
+
+void nsWindow::OnButtonPressEvent(GdkEventButton* aEvent) {
+ LOG(("Button %u press on %p\n", aEvent->button, (void*)this));
+
+ // If you double click in GDK, it will actually generate a second
+ // GDK_BUTTON_PRESS before sending the GDK_2BUTTON_PRESS, and this is
+ // different than the DOM spec. GDK puts this in the queue
+ // programatically, so it's safe to assume that if there's a
+ // double click in the queue, it was generated so we can just drop
+ // this click.
+ GdkEvent* peekedEvent = gdk_event_peek();
+ if (peekedEvent) {
+ GdkEventType type = peekedEvent->any.type;
+ gdk_event_free(peekedEvent);
+ if (type == GDK_2BUTTON_PRESS || type == GDK_3BUTTON_PRESS) return;
+ }
+
+ nsWindow* containerWindow = GetContainerWindow();
+ if (!gFocusWindow && containerWindow) {
+ containerWindow->DispatchActivateEvent();
+ }
+
+ // check to see if we should rollup
+ if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) return;
+
+ gdouble pressure = 0;
+ gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
+ mLastMotionPressure = pressure;
+
+ uint16_t domButton;
+ switch (aEvent->button) {
+ case 1:
+ domButton = MouseButton::ePrimary;
+ break;
+ case 2:
+ domButton = MouseButton::eMiddle;
+ break;
+ case 3:
+ domButton = MouseButton::eSecondary;
+ break;
+ // These are mapped to horizontal scroll
+ case 6:
+ case 7:
+ NS_WARNING("We're not supporting legacy horizontal scroll event");
+ return;
+ // Map buttons 8-9 to back/forward
+ case 8:
+ if (!Preferences::GetBool("mousebutton.4th.enabled", true)) {
+ return;
+ }
+ DispatchCommandEvent(nsGkAtoms::Back);
+ return;
+ case 9:
+ if (!Preferences::GetBool("mousebutton.5th.enabled", true)) {
+ return;
+ }
+ DispatchCommandEvent(nsGkAtoms::Forward);
+ return;
+ default:
+ return;
+ }
+
+ gButtonState |= ButtonMaskFromGDKButton(aEvent->button);
+
+ WidgetMouseEvent event(true, eMouseDown, this, WidgetMouseEvent::eReal);
+ event.mButton = domButton;
+ InitButtonEvent(event, aEvent);
+ event.mPressure = mLastMotionPressure;
+
+ nsEventStatus eventStatus = DispatchInputEvent(&event);
+
+ LayoutDeviceIntPoint refPoint =
+ GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ if (mDraggableRegion.Contains(refPoint.x, refPoint.y) &&
+ domButton == MouseButton::ePrimary &&
+ eventStatus != nsEventStatus_eConsumeNoDefault) {
+ mWindowShouldStartDragging = true;
+ }
+
+ // right menu click on linux should also pop up a context menu
+ if (!StaticPrefs::ui_context_menus_after_mouseup()) {
+ DispatchContextMenuEventFromMouseEvent(domButton, aEvent);
+ }
+}
+
+void nsWindow::OnButtonReleaseEvent(GdkEventButton* aEvent) {
+ LOG(("Button %u release on %p\n", aEvent->button, (void*)this));
+
+ if (mWindowShouldStartDragging) {
+ mWindowShouldStartDragging = false;
+ }
+
+ uint16_t domButton;
+ switch (aEvent->button) {
+ case 1:
+ domButton = MouseButton::ePrimary;
+ break;
+ case 2:
+ domButton = MouseButton::eMiddle;
+ break;
+ case 3:
+ domButton = MouseButton::eSecondary;
+ break;
+ default:
+ return;
+ }
+
+ gButtonState &= ~ButtonMaskFromGDKButton(aEvent->button);
+
+ WidgetMouseEvent event(true, eMouseUp, this, WidgetMouseEvent::eReal);
+ event.mButton = domButton;
+ InitButtonEvent(event, aEvent);
+ gdouble pressure = 0;
+ gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
+ event.mPressure = pressure ? pressure : mLastMotionPressure;
+
+ // The mRefPoint is manipulated in DispatchInputEvent, we're saving it
+ // to use it for the doubleclick position check.
+ LayoutDeviceIntPoint pos = event.mRefPoint;
+
+ nsEventStatus eventStatus = DispatchInputEvent(&event);
+
+ bool defaultPrevented = (eventStatus == nsEventStatus_eConsumeNoDefault);
+ // Check if mouse position in titlebar and doubleclick happened to
+ // trigger restore/maximize.
+ if (!defaultPrevented && mDrawInTitlebar &&
+ event.mButton == MouseButton::ePrimary && event.mClickCount == 2 &&
+ mDraggableRegion.Contains(pos.x, pos.y)) {
+ if (mSizeState == nsSizeMode_Maximized) {
+ SetSizeMode(nsSizeMode_Normal);
+ } else {
+ SetSizeMode(nsSizeMode_Maximized);
+ }
+ }
+ mLastMotionPressure = pressure;
+
+ // right menu click on linux should also pop up a context menu
+ if (StaticPrefs::ui_context_menus_after_mouseup()) {
+ DispatchContextMenuEventFromMouseEvent(domButton, aEvent);
+ }
+
+ // Open window manager menu on PIP window to allow user
+ // to place it on top / all workspaces.
+ if (mIsPIPWindow && aEvent->button == 3) {
+ static auto sGdkWindowShowWindowMenu =
+ (gboolean(*)(GdkWindow * window, GdkEvent*))
+ dlsym(RTLD_DEFAULT, "gdk_window_show_window_menu");
+ if (sGdkWindowShowWindowMenu) {
+ sGdkWindowShowWindowMenu(mGdkWindow, (GdkEvent*)aEvent);
+ }
+ }
+}
+
+void nsWindow::OnContainerFocusInEvent(GdkEventFocus* aEvent) {
+ LOGFOCUS(("OnContainerFocusInEvent [%p]\n", (void*)this));
+
+ // Unset the urgency hint, if possible
+ GtkWidget* top_window = GetToplevelWidget();
+ if (top_window && (gtk_widget_get_visible(top_window)))
+ SetUrgencyHint(top_window, false);
+
+ // Return if being called within SetFocus because the focus manager
+ // already knows that the window is active.
+ if (gBlockActivateEvent) {
+ LOGFOCUS(("activated notification is blocked [%p]\n", (void*)this));
+ return;
+ }
+
+ // If keyboard input will be accepted, the focus manager will call
+ // SetFocus to set the correct window.
+ gFocusWindow = nullptr;
+
+ DispatchActivateEvent();
+
+ if (!gFocusWindow) {
+ // We don't really have a window for dispatching key events, but
+ // setting a non-nullptr value here prevents OnButtonPressEvent() from
+ // dispatching an activation notification if the widget is already
+ // active.
+ gFocusWindow = this;
+ }
+
+ LOGFOCUS(("Events sent from focus in event [%p]\n", (void*)this));
+}
+
+void nsWindow::OnContainerFocusOutEvent(GdkEventFocus* aEvent) {
+ LOGFOCUS(("OnContainerFocusOutEvent [%p]\n", (void*)this));
+
+ if (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog) {
+ nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
+ nsCOMPtr<nsIDragSession> dragSession;
+ dragService->GetCurrentSession(getter_AddRefs(dragSession));
+
+ // Rollup popups when a window is focused out unless a drag is occurring.
+ // This check is because drags grab the keyboard and cause a focus out on
+ // versions of GTK before 2.18.
+ bool shouldRollup = !dragSession;
+ if (!shouldRollup) {
+ // we also roll up when a drag is from a different application
+ nsCOMPtr<nsINode> sourceNode;
+ dragSession->GetSourceNode(getter_AddRefs(sourceNode));
+ shouldRollup = (sourceNode == nullptr);
+ }
+
+ if (shouldRollup) {
+ CheckForRollup(0, 0, false, true);
+ }
+ }
+
+ if (gFocusWindow) {
+ RefPtr<nsWindow> kungFuDeathGrip = gFocusWindow;
+ if (gFocusWindow->mIMContext) {
+ gFocusWindow->mIMContext->OnBlurWindow(gFocusWindow);
+ }
+ gFocusWindow = nullptr;
+ }
+
+ DispatchDeactivateEvent();
+
+ if (IsChromeWindowTitlebar()) {
+ // DispatchDeactivateEvent() ultimately results in a call to
+ // BrowsingContext::SetIsActiveBrowserWindow(), which resets
+ // the state. We call UpdateMozWindowActive() to keep it in
+ // sync with GDK_WINDOW_STATE_FOCUSED.
+ UpdateMozWindowActive();
+ }
+
+ LOGFOCUS(("Done with container focus out [%p]\n", (void*)this));
+}
+
+bool nsWindow::DispatchCommandEvent(nsAtom* aCommand) {
+ nsEventStatus status;
+ WidgetCommandEvent appCommandEvent(true, aCommand, this);
+ DispatchEvent(&appCommandEvent, status);
+ return TRUE;
+}
+
+bool nsWindow::DispatchContentCommandEvent(EventMessage aMsg) {
+ nsEventStatus status;
+ WidgetContentCommandEvent event(true, aMsg, this);
+ DispatchEvent(&event, status);
+ return TRUE;
+}
+
+WidgetEventTime nsWindow::GetWidgetEventTime(guint32 aEventTime) {
+ return WidgetEventTime(aEventTime, GetEventTimeStamp(aEventTime));
+}
+
+TimeStamp nsWindow::GetEventTimeStamp(guint32 aEventTime) {
+ if (MOZ_UNLIKELY(!mGdkWindow)) {
+ // nsWindow has been Destroy()ed.
+ return TimeStamp::Now();
+ }
+ if (aEventTime == 0) {
+ // Some X11 and GDK events may be received with a time of 0 to indicate
+ // that they are synthetic events. Some input method editors do this.
+ // In this case too, just return the current timestamp.
+ return TimeStamp::Now();
+ }
+
+ TimeStamp eventTimeStamp;
+
+ if (!mIsX11Display) {
+ // Wayland compositors use monotonic time to set timestamps.
+ int64_t timestampTime = g_get_monotonic_time() / 1000;
+ guint32 refTimeTruncated = guint32(timestampTime);
+
+ timestampTime -= refTimeTruncated - aEventTime;
+ int64_t tick =
+ BaseTimeDurationPlatformUtils::TicksFromMilliseconds(timestampTime);
+ eventTimeStamp = TimeStamp::FromSystemTime(tick);
+ } else {
+ CurrentX11TimeGetter* getCurrentTime = GetCurrentTimeGetter();
+ MOZ_ASSERT(getCurrentTime,
+ "Null current time getter despite having a window");
+ eventTimeStamp =
+ TimeConverter().GetTimeStampFromSystemTime(aEventTime, *getCurrentTime);
+ }
+ return eventTimeStamp;
+}
+
+mozilla::CurrentX11TimeGetter* nsWindow::GetCurrentTimeGetter() {
+ MOZ_ASSERT(mGdkWindow, "Expected mGdkWindow to be set");
+ if (MOZ_UNLIKELY(!mCurrentTimeGetter)) {
+ mCurrentTimeGetter = MakeUnique<CurrentX11TimeGetter>(mGdkWindow);
+ }
+ return mCurrentTimeGetter.get();
+}
+
+gboolean nsWindow::OnKeyPressEvent(GdkEventKey* aEvent) {
+ LOGFOCUS(("OnKeyPressEvent [%p]\n", (void*)this));
+
+ RefPtr<nsWindow> self(this);
+ KeymapWrapper::HandleKeyPressEvent(self, aEvent);
+ return TRUE;
+}
+
+gboolean nsWindow::OnKeyReleaseEvent(GdkEventKey* aEvent) {
+ LOGFOCUS(("OnKeyReleaseEvent [%p]\n", (void*)this));
+
+ RefPtr<nsWindow> self(this);
+ if (NS_WARN_IF(!KeymapWrapper::HandleKeyReleaseEvent(self, aEvent))) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void nsWindow::OnScrollEvent(GdkEventScroll* aEvent) {
+ // check to see if we should rollup
+ if (CheckForRollup(aEvent->x_root, aEvent->y_root, true, false)) return;
+ // check for duplicate legacy scroll event, see GNOME bug 726878
+ if (aEvent->direction != GDK_SCROLL_SMOOTH &&
+ mLastScrollEventTime == aEvent->time) {
+ LOG(("[%d] duplicate legacy scroll event %d\n", aEvent->time,
+ aEvent->direction));
+ return;
+ }
+ WidgetWheelEvent wheelEvent(true, eWheel, this);
+ wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_LINE;
+ switch (aEvent->direction) {
+ case GDK_SCROLL_SMOOTH: {
+ // As of GTK 3.4, all directional scroll events are provided by
+ // the GDK_SCROLL_SMOOTH direction on XInput2 and Wayland devices.
+ mLastScrollEventTime = aEvent->time;
+
+ // Special handling for touchpads to support flings
+ // (also known as kinetic/inertial/momentum scrolling)
+ GdkDevice* device = gdk_event_get_source_device((GdkEvent*)aEvent);
+ GdkInputSource source = gdk_device_get_source(device);
+ if (source == GDK_SOURCE_TOUCHSCREEN || source == GDK_SOURCE_TOUCHPAD) {
+ if (StaticPrefs::apz_gtk_kinetic_scroll_enabled() &&
+ gtk_check_version(3, 20, 0) == nullptr) {
+ static auto sGdkEventIsScrollStopEvent =
+ (gboolean(*)(const GdkEvent*))dlsym(
+ RTLD_DEFAULT, "gdk_event_is_scroll_stop_event");
+
+ LOG(("[%d] pan smooth event dx=%f dy=%f inprogress=%d\n",
+ aEvent->time, aEvent->delta_x, aEvent->delta_y, mPanInProgress));
+ PanGestureInput::PanGestureType eventType =
+ PanGestureInput::PANGESTURE_PAN;
+ if (sGdkEventIsScrollStopEvent((GdkEvent*)aEvent)) {
+ eventType = PanGestureInput::PANGESTURE_END;
+ mPanInProgress = false;
+ } else if (!mPanInProgress) {
+ eventType = PanGestureInput::PANGESTURE_START;
+ mPanInProgress = true;
+ }
+
+ LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
+ PanGestureInput panEvent(
+ eventType, aEvent->time, GetEventTimeStamp(aEvent->time),
+ ScreenPoint(touchPoint.x, touchPoint.y),
+ ScreenPoint(aEvent->delta_x, aEvent->delta_y),
+ KeymapWrapper::ComputeKeyModifiers(aEvent->state));
+ panEvent.mDeltaType = PanGestureInput::PANDELTA_PAGE;
+ panEvent.mSimulateMomentum = true;
+
+ DispatchPanGestureInput(panEvent);
+
+ return;
+ }
+
+ // Older GTK doesn't support stop events, so we can't support fling
+ wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY;
+ }
+
+ // TODO - use a more appropriate scrolling unit than lines.
+ // Multiply event deltas by 3 to emulate legacy behaviour.
+ wheelEvent.mDeltaX = aEvent->delta_x * 3;
+ wheelEvent.mDeltaY = aEvent->delta_y * 3;
+ wheelEvent.mIsNoLineOrPageDelta = true;
+
+ break;
+ }
+ case GDK_SCROLL_UP:
+ wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = -3;
+ break;
+ case GDK_SCROLL_DOWN:
+ wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = 3;
+ break;
+ case GDK_SCROLL_LEFT:
+ wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = -1;
+ break;
+ case GDK_SCROLL_RIGHT:
+ wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = 1;
+ break;
+ }
+
+ wheelEvent.mRefPoint = GetRefPoint(this, aEvent);
+
+ KeymapWrapper::InitInputEvent(wheelEvent, aEvent->state);
+
+ wheelEvent.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ DispatchInputEvent(&wheelEvent);
+}
+
+void nsWindow::OnWindowStateEvent(GtkWidget* aWidget,
+ GdkEventWindowState* aEvent) {
+ LOG(
+ ("nsWindow::OnWindowStateEvent [%p] for %p changed 0x%x new_window_state "
+ "0x%x\n",
+ (void*)this, aWidget, aEvent->changed_mask, aEvent->new_window_state));
+
+ if (IS_MOZ_CONTAINER(aWidget)) {
+ // This event is notifying the container widget of changes to the
+ // toplevel window. Just detect changes affecting whether windows are
+ // viewable.
+ //
+ // (A visibility notify event is sent to each window that becomes
+ // viewable when the toplevel is mapped, but we can't rely on that for
+ // setting mHasMappedToplevel because these toplevel window state
+ // events are asynchronous. The windows in the hierarchy now may not
+ // be the same windows as when the toplevel was mapped, so they may
+ // not get VisibilityNotify events.)
+ bool mapped = !(aEvent->new_window_state &
+ (GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_WITHDRAWN));
+ if (mHasMappedToplevel != mapped) {
+ SetHasMappedToplevel(mapped);
+ }
+ LOG(("\tquick return because IS_MOZ_CONTAINER(aWidget) is true\n"));
+ return;
+ }
+ // else the widget is a shell widget.
+
+ // The block below is a bit evil.
+ //
+ // When a window is resized before it is shown, gtk_window_resize() delays
+ // resizes until the window is shown. If gtk_window_state_event() sees a
+ // GDK_WINDOW_STATE_MAXIMIZED change [1] before the window is shown, then
+ // gtk_window_compute_configure_request_size() ignores the values from the
+ // resize [2]. See bug 1449166 for an example of how this could happen.
+ //
+ // [1] https://gitlab.gnome.org/GNOME/gtk/blob/3.22.30/gtk/gtkwindow.c#L7967
+ // [2] https://gitlab.gnome.org/GNOME/gtk/blob/3.22.30/gtk/gtkwindow.c#L9377
+ //
+ // In order to provide a sensible size for the window when the user exits
+ // maximized state, we hide the GDK_WINDOW_STATE_MAXIMIZED change from
+ // gtk_window_state_event() so as to trick GTK into using the values from
+ // gtk_window_resize() in its configure request.
+ //
+ // We instead notify gtk_window_state_event() of the maximized state change
+ // once the window is shown.
+ //
+ // See https://gitlab.gnome.org/GNOME/gtk/issues/1044
+ //
+ // This may be fixed in Gtk 3.24+ but some DE still have this issue
+ // (Bug 1624199) so let's remove it for Wayland only.
+ if (mIsX11Display) {
+ if (!mIsShown) {
+ aEvent->changed_mask = static_cast<GdkWindowState>(
+ aEvent->changed_mask & ~GDK_WINDOW_STATE_MAXIMIZED);
+ } else if (aEvent->changed_mask & GDK_WINDOW_STATE_WITHDRAWN &&
+ aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
+ aEvent->changed_mask = static_cast<GdkWindowState>(
+ aEvent->changed_mask | GDK_WINDOW_STATE_MAXIMIZED);
+ }
+ }
+
+ // This is a workaround for https://gitlab.gnome.org/GNOME/gtk/issues/1395
+ // Gtk+ controls window active appearance by window-state-event signal.
+ if (IsChromeWindowTitlebar() &&
+ (aEvent->changed_mask & GDK_WINDOW_STATE_FOCUSED)) {
+ // Emulate what Gtk+ does at gtk_window_state_event().
+ // We can't check GTK_STATE_FLAG_BACKDROP directly as it's set by Gtk+
+ // *after* this window-state-event handler.
+ mTitlebarBackdropState =
+ !(aEvent->new_window_state & GDK_WINDOW_STATE_FOCUSED);
+
+ // keep IsActiveBrowserWindow in sync with GDK_WINDOW_STATE_FOCUSED
+ UpdateMozWindowActive();
+
+ ForceTitlebarRedraw();
+ }
+
+ // We don't care about anything but changes in the maximized/icon/fullscreen
+ // states but we need a workaround for bug in Wayland:
+ // https://gitlab.gnome.org/GNOME/gtk/issues/67
+ // Under wayland the gtk_window_iconify implementation does NOT synthetize
+ // window_state_event where the GDK_WINDOW_STATE_ICONIFIED is set.
+ // During restore we won't get aEvent->changed_mask with
+ // the GDK_WINDOW_STATE_ICONIFIED so to detect that change we use the stored
+ // mSizeState and obtaining a focus.
+ bool waylandWasIconified =
+ (!mIsX11Display && aEvent->changed_mask & GDK_WINDOW_STATE_FOCUSED &&
+ aEvent->new_window_state & GDK_WINDOW_STATE_FOCUSED &&
+ mSizeState == nsSizeMode_Minimized);
+ if (!waylandWasIconified &&
+ (aEvent->changed_mask &
+ (GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_MAXIMIZED |
+ GDK_WINDOW_STATE_TILED | GDK_WINDOW_STATE_FULLSCREEN)) == 0) {
+ LOG(("\tearly return because no interesting bits changed\n"));
+ return;
+ }
+
+ if (aEvent->new_window_state & GDK_WINDOW_STATE_ICONIFIED) {
+ LOG(("\tIconified\n"));
+ mSizeState = nsSizeMode_Minimized;
+#ifdef ACCESSIBILITY
+ DispatchMinimizeEventAccessible();
+#endif // ACCESSIBILITY
+ } else if (aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) {
+ LOG(("\tFullscreen\n"));
+ mSizeState = nsSizeMode_Fullscreen;
+ } else if (aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
+ LOG(("\tMaximized\n"));
+ mSizeState = nsSizeMode_Maximized;
+#ifdef ACCESSIBILITY
+ DispatchMaximizeEventAccessible();
+#endif // ACCESSIBILITY
+ } else {
+ LOG(("\tNormal\n"));
+ mSizeState = nsSizeMode_Normal;
+#ifdef ACCESSIBILITY
+ DispatchRestoreEventAccessible();
+#endif // ACCESSIBILITY
+ }
+
+ if (aEvent->new_window_state & GDK_WINDOW_STATE_TILED) {
+ LOG(("\tTiled\n"));
+ mIsTiled = true;
+ } else {
+ LOG(("\tNot tiled\n"));
+ mIsTiled = false;
+ }
+
+ if (mWidgetListener) {
+ mWidgetListener->SizeModeChanged(mSizeState);
+ if (aEvent->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
+ mWidgetListener->FullscreenChanged(aEvent->new_window_state &
+ GDK_WINDOW_STATE_FULLSCREEN);
+ }
+ }
+
+ if (mDrawInTitlebar && mTransparencyBitmapForTitlebar) {
+ if (mSizeState == nsSizeMode_Normal && !mIsTiled) {
+ UpdateTitlebarTransparencyBitmap();
+ } else {
+ ClearTransparencyBitmap();
+ }
+ }
+}
+
+void nsWindow::ThemeChanged() {
+ // Everything could've changed.
+ NotifyThemeChanged(ThemeChangeKind::StyleAndLayout);
+
+ if (!mGdkWindow || MOZ_UNLIKELY(mIsDestroyed)) return;
+
+ // Dispatch theme change notification to all child windows
+ GList* children = gdk_window_peek_children(mGdkWindow);
+ while (children) {
+ GdkWindow* gdkWin = GDK_WINDOW(children->data);
+
+ auto* win = (nsWindow*)g_object_get_data(G_OBJECT(gdkWin), "nsWindow");
+
+ if (win && win != this) { // guard against infinite recursion
+ RefPtr<nsWindow> kungFuDeathGrip = win;
+ win->ThemeChanged();
+ }
+
+ children = children->next;
+ }
+
+ IMContextWrapper::OnThemeChanged();
+}
+
+void nsWindow::OnDPIChanged() {
+ if (mWidgetListener) {
+ if (PresShell* presShell = mWidgetListener->GetPresShell()) {
+ presShell->BackingScaleFactorChanged();
+ // Update menu's font size etc.
+ // This affects style / layout because it affects system font sizes.
+ presShell->ThemeChanged(ThemeChangeKind::StyleAndLayout);
+ }
+ mWidgetListener->UIResolutionChanged();
+ }
+}
+
+void nsWindow::OnCheckResize() { mPendingConfigures++; }
+
+void nsWindow::OnCompositedChanged() {
+ // Update CSD after the change in alpha visibility. This only affects
+ // system metrics, not other theme shenanigans.
+ NotifyThemeChanged(ThemeChangeKind::MediaQueriesOnly);
+ mCompositedScreen = gdk_screen_is_composited(gdk_screen_get_default());
+}
+
+void nsWindow::OnScaleChanged(GtkAllocation* aAllocation) {
+ LOG(("nsWindow::OnScaleChanged [%p] %d,%d -> %d x %d\n", (void*)this,
+ aAllocation->x, aAllocation->y, aAllocation->width,
+ aAllocation->height));
+
+ // Force scale factor recalculation
+ mWindowScaleFactorChanged = true;
+
+ // This eventually propagate new scale to the PuppetWidgets
+ OnDPIChanged();
+
+ // configure_event is already fired before scale-factor signal,
+ // but size-allocate isn't fired by changing scale
+ OnSizeAllocate(aAllocation);
+
+ // Client offset are updated by _NET_FRAME_EXTENTS on X11 when system titlebar
+ // is enabled. In ither cases (Wayland or system titlebar is off on X11)
+ // we don't get _NET_FRAME_EXTENTS X11 property notification so we derive
+ // it from mContainer position.
+ if (mCSDSupportLevel == CSD_SUPPORT_CLIENT) {
+ if (!mIsX11Display || (mIsX11Display && mDrawInTitlebar)) {
+ UpdateClientOffsetFromCSDWindow();
+ }
+ }
+
+#ifdef MOZ_WAYLAND
+ // We need to update scale when scale of egl window is changed.
+ if (mContainer && moz_container_wayland_has_egl_window(mContainer)) {
+ moz_container_wayland_set_scale_factor(mContainer);
+ }
+#endif
+}
+
+void nsWindow::DispatchDragEvent(EventMessage aMsg,
+ const LayoutDeviceIntPoint& aRefPoint,
+ guint aTime) {
+ WidgetDragEvent event(true, aMsg, this);
+
+ InitDragEvent(event);
+
+ event.mRefPoint = aRefPoint;
+ event.AssignEventTime(GetWidgetEventTime(aTime));
+
+ DispatchInputEvent(&event);
+}
+
+void nsWindow::OnDragDataReceivedEvent(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint aTime,
+ gpointer aData) {
+ LOGDRAG(("nsWindow::OnDragDataReceived(%p)\n", (void*)this));
+
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ dragService->TargetDataReceived(aWidget, aDragContext, aX, aY, aSelectionData,
+ aInfo, aTime);
+}
+
+nsWindow* nsWindow::GetTransientForWindowIfPopup() {
+ if (mWindowType != eWindowType_popup) {
+ return nullptr;
+ }
+ GtkWindow* toplevel = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ if (toplevel) {
+ return get_window_for_gtk_widget(GTK_WIDGET(toplevel));
+ }
+ return nullptr;
+}
+
+bool nsWindow::IsHandlingTouchSequence(GdkEventSequence* aSequence) {
+ return mHandleTouchEvent && mTouches.Contains(aSequence);
+}
+
+gboolean nsWindow::OnTouchpadPinchEvent(GdkEventTouchpadPinch* aEvent) {
+ if (StaticPrefs::apz_gtk_touchpad_pinch_enabled()) {
+ PinchGestureInput::PinchGestureType pinchGestureType =
+ PinchGestureInput::PINCHGESTURE_SCALE;
+ ScreenCoord CurrentSpan;
+ ScreenCoord PreviousSpan;
+
+ switch (aEvent->phase) {
+ case GDK_TOUCHPAD_GESTURE_PHASE_BEGIN:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
+ CurrentSpan = aEvent->scale;
+
+ // Assign PreviousSpan --> 0.999 to make mDeltaY field of the
+ // WidgetWheelEvent that this PinchGestureInput event will be converted
+ // to not equal Zero as our discussion because we observed that the
+ // scale of the PHASE_BEGIN event is 1.
+ PreviousSpan = 0.999;
+ mLastPinchEventSpan = aEvent->scale;
+ break;
+
+ case GDK_TOUCHPAD_GESTURE_PHASE_UPDATE:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
+ if (aEvent->scale == mLastPinchEventSpan) {
+ return FALSE;
+ }
+ CurrentSpan = aEvent->scale;
+ PreviousSpan = mLastPinchEventSpan;
+ mLastPinchEventSpan = aEvent->scale;
+ break;
+
+ case GDK_TOUCHPAD_GESTURE_PHASE_END:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
+ CurrentSpan = aEvent->scale;
+ PreviousSpan = mLastPinchEventSpan;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ LayoutDeviceIntPoint touchpadPoint = GetRefPoint(this, aEvent);
+ PinchGestureInput event(
+ pinchGestureType, PinchGestureInput::TRACKPAD, aEvent->time,
+ GetEventTimeStamp(aEvent->time), ExternalPoint(0, 0),
+ ScreenPoint(touchpadPoint.x, touchpadPoint.y), CurrentSpan,
+ PreviousSpan, KeymapWrapper::ComputeKeyModifiers(aEvent->state));
+
+ DispatchPinchGestureInput(event);
+ }
+ return TRUE;
+}
+
+gboolean nsWindow::OnTouchEvent(GdkEventTouch* aEvent) {
+ if (!mHandleTouchEvent) {
+ // If a popup window was spawned (e.g. as the result of a long-press)
+ // and touch events got diverted to that window within a touch sequence,
+ // ensure the touch event gets sent to the original window instead. We
+ // keep the checks here very conservative so that we only redirect
+ // events in this specific scenario.
+ nsWindow* targetWindow = GetTransientForWindowIfPopup();
+ if (targetWindow &&
+ targetWindow->IsHandlingTouchSequence(aEvent->sequence)) {
+ return targetWindow->OnTouchEvent(aEvent);
+ }
+
+ return FALSE;
+ }
+
+ EventMessage msg;
+ switch (aEvent->type) {
+ case GDK_TOUCH_BEGIN:
+ msg = eTouchStart;
+ break;
+ case GDK_TOUCH_UPDATE:
+ msg = eTouchMove;
+ break;
+ case GDK_TOUCH_END:
+ msg = eTouchEnd;
+ break;
+ case GDK_TOUCH_CANCEL:
+ msg = eTouchCancel;
+ break;
+ default:
+ return FALSE;
+ }
+
+ LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
+
+ int32_t id;
+ RefPtr<dom::Touch> touch;
+ if (mTouches.Remove(aEvent->sequence, getter_AddRefs(touch))) {
+ id = touch->mIdentifier;
+ } else {
+ id = ++gLastTouchID & 0x7FFFFFFF;
+ }
+
+ touch =
+ new dom::Touch(id, touchPoint, LayoutDeviceIntPoint(1, 1), 0.0f, 0.0f);
+
+ WidgetTouchEvent event(true, msg, this);
+ KeymapWrapper::InitInputEvent(event, aEvent->state);
+ event.mTime = aEvent->time;
+
+ if (aEvent->type == GDK_TOUCH_BEGIN || aEvent->type == GDK_TOUCH_UPDATE) {
+ mTouches.Put(aEvent->sequence, std::move(touch));
+ // add all touch points to event object
+ for (auto iter = mTouches.Iter(); !iter.Done(); iter.Next()) {
+ event.mTouches.AppendElement(new dom::Touch(*iter.UserData()));
+ }
+ } else if (aEvent->type == GDK_TOUCH_END ||
+ aEvent->type == GDK_TOUCH_CANCEL) {
+ *event.mTouches.AppendElement() = std::move(touch);
+ }
+
+ DispatchInputEvent(&event);
+ return TRUE;
+}
+
+// Return true if toplevel window is transparent.
+// It's transparent when we're running on composited screens
+// and we can draw main window without system titlebar.
+bool nsWindow::IsToplevelWindowTransparent() {
+ static bool transparencyConfigured = false;
+
+ if (!transparencyConfigured) {
+ if (gdk_screen_is_composited(gdk_screen_get_default())) {
+ // Some Gtk+ themes use non-rectangular toplevel windows. To fully
+ // support such themes we need to make toplevel window transparent
+ // with ARGB visual.
+ // It may cause performanance issue so make it configurable
+ // and enable it by default for selected window managers.
+ if (Preferences::HasUserValue("mozilla.widget.use-argb-visuals")) {
+ // argb visual is explicitly required so use it
+ sTransparentMainWindow =
+ Preferences::GetBool("mozilla.widget.use-argb-visuals");
+ } else {
+ // Enable transparent toplevel window if we can draw main window
+ // without system titlebar as Gtk+ themes use titlebar round corners.
+ sTransparentMainWindow = GetSystemCSDSupportLevel() != CSD_SUPPORT_NONE;
+ }
+ }
+ transparencyConfigured = true;
+ }
+
+ return sTransparentMainWindow;
+}
+
+static GdkWindow* CreateGdkWindow(GdkWindow* parent, GtkWidget* widget) {
+ GdkWindowAttr attributes;
+ gint attributes_mask = GDK_WA_VISUAL;
+
+ attributes.event_mask = kEvents;
+
+ attributes.width = 1;
+ attributes.height = 1;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual(widget);
+ attributes.window_type = GDK_WINDOW_CHILD;
+
+ GdkWindow* window = gdk_window_new(parent, &attributes, attributes_mask);
+ gdk_window_set_user_data(window, widget);
+
+ return window;
+}
+
+// Configure GL visual on X11. We add alpha silently
+// if we use WebRender to workaround NVIDIA specific Bug 1663273.
+bool nsWindow::ConfigureX11GLVisual(bool aUseAlpha) {
+ if (!mIsX11Display) {
+ return false;
+ }
+
+ // If using WebRender on X11, we need to select a visual with a depth
+ // buffer, as well as an alpha channel if transparency is requested. This
+ // must be done before the widget is realized.
+ bool useWebRender = gfx::gfxVars::UseWebRender();
+ auto* screen = gtk_widget_get_screen(mShell);
+ int visualId = 0;
+ bool haveVisual;
+
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1663003
+ // We need to use GLX to get visual even on EGL until
+ // EGL can provide compositable visual:
+ // https://gitlab.freedesktop.org/mesa/mesa/-/issues/149
+ if ((true /* !gfx::gfxVars::UseEGL() */)) {
+ auto* display = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(mShell));
+ int screenNumber = GDK_SCREEN_XNUMBER(screen);
+ haveVisual = GLContextGLX::FindVisual(display, screenNumber, useWebRender,
+ aUseAlpha || useWebRender, &visualId);
+ } else {
+ haveVisual = GLContextEGL::FindVisual(useWebRender,
+ aUseAlpha || useWebRender, &visualId);
+ }
+
+ GdkVisual* gdkVisual = nullptr;
+ if (haveVisual) {
+ // If we're using CSD, rendering will go through mContainer, but
+ // it will inherit this visual as it is a child of mShell.
+ gdkVisual = gdk_x11_screen_lookup_visual(screen, visualId);
+ }
+ if (!gdkVisual) {
+ NS_WARNING("We're missing X11 Visual!");
+ if (aUseAlpha || useWebRender) {
+ // We try to use a fallback alpha visual
+ GdkScreen* screen = gtk_widget_get_screen(mShell);
+ gdkVisual = gdk_screen_get_rgba_visual(screen);
+ }
+ }
+ if (gdkVisual) {
+ // TODO: We use alpha visual even on non-compositing screens (Bug 1479135).
+ gtk_widget_set_visual(mShell, gdkVisual);
+ mHasAlphaVisual = aUseAlpha;
+ }
+
+ return true;
+}
+
+nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) {
+#ifdef MOZ_LOGGING
+ if (this->GetFrame() && this->GetFrame()->GetContent()) {
+ nsCString nodeName =
+ NS_ConvertUTF16toUTF8(this->GetFrame()->GetContent()->NodeName());
+ LOG(("nsWindow::Create: creating [%p]: nodename %s\n", this,
+ nodeName.get()));
+ }
+#endif
+ // only set the base parent if we're going to be a dialog or a
+ // toplevel
+ nsIWidget* baseParent =
+ aInitData && (aInitData->mWindowType == eWindowType_dialog ||
+ aInitData->mWindowType == eWindowType_toplevel ||
+ aInitData->mWindowType == eWindowType_invisible)
+ ? nullptr
+ : aParent;
+
+#ifdef ACCESSIBILITY
+ // Send a DBus message to check whether a11y is enabled
+ a11y::PreInit();
+#endif
+
+ // Ensure that the toolkit is created.
+ nsGTKToolkit::GetToolkit();
+
+ // initialize all the common bits of this class
+ BaseCreate(baseParent, aInitData);
+
+ // Do we need to listen for resizes?
+ bool listenForResizes = false;
+ ;
+ if (aNativeParent || (aInitData && aInitData->mListenForResizes))
+ listenForResizes = true;
+
+ // and do our common creation
+ CommonCreate(aParent, listenForResizes);
+
+ // save our bounds
+ mBounds = aRect;
+ LOG((" mBounds: x:%d y:%d w:%d h:%d\n", mBounds.x, mBounds.y, mBounds.width,
+ mBounds.height));
+
+ mPreferredPopupRectFlushed = false;
+
+ ConstrainSize(&mBounds.width, &mBounds.height);
+
+ GtkWidget* eventWidget = nullptr;
+ bool popupNeedsAlphaVisual = (mWindowType == eWindowType_popup &&
+ (aInitData && aInitData->mSupportTranslucency));
+
+ // Figure out our parent window - only used for eWindowType_child
+ GtkWidget* parentMozContainer = nullptr;
+ GtkContainer* parentGtkContainer = nullptr;
+ GdkWindow* parentGdkWindow = nullptr;
+ nsWindow* parentnsWindow = nullptr;
+
+ if (aParent) {
+ parentnsWindow = static_cast<nsWindow*>(aParent);
+ parentGdkWindow = parentnsWindow->mGdkWindow;
+ } else if (aNativeParent && GDK_IS_WINDOW(aNativeParent)) {
+ parentGdkWindow = GDK_WINDOW(aNativeParent);
+ parentnsWindow = get_window_for_gdk_window(parentGdkWindow);
+ if (!parentnsWindow) return NS_ERROR_FAILURE;
+
+ } else if (aNativeParent && GTK_IS_CONTAINER(aNativeParent)) {
+ parentGtkContainer = GTK_CONTAINER(aNativeParent);
+ }
+
+ if (parentGdkWindow) {
+ // get the widget for the window - it should be a moz container
+ parentMozContainer = parentnsWindow->GetMozContainerWidget();
+ if (!parentMozContainer) return NS_ERROR_FAILURE;
+ }
+ // ^^ only used for eWindowType_child
+
+ if (!mIsX11Display) {
+ if (mWindowType == eWindowType_child) {
+ // eWindowType_child is not supported on Wayland. Just switch to toplevel
+ // as a workaround.
+ mWindowType = eWindowType_toplevel;
+ } else if (mWindowType == eWindowType_popup && !aNativeParent && !aParent) {
+ // Workaround for Wayland where the popup windows always need to have
+ // parent window. For example webrtc ui is a popup window without parent.
+ mWindowType = eWindowType_toplevel;
+ }
+ }
+
+ mAlwaysOnTop = aInitData && aInitData->mAlwaysOnTop;
+ mIsPIPWindow = aInitData && aInitData->mPIPWindow;
+
+ // ok, create our windows
+ switch (mWindowType) {
+ case eWindowType_dialog:
+ case eWindowType_popup:
+ case eWindowType_toplevel:
+ case eWindowType_invisible: {
+ mIsTopLevel = true;
+
+ // Popups that are not noautohide are only temporary. The are used
+ // for menus and the like and disappear when another window is used.
+ // For most popups, use the standard GtkWindowType GTK_WINDOW_POPUP,
+ // which will use a Window with the override-redirect attribute
+ // (for temporary windows).
+ // For long-lived windows, their stacking order is managed by the
+ // window manager, as indicated by GTK_WINDOW_TOPLEVEL.
+ // For Wayland we have to always use GTK_WINDOW_POPUP to control
+ // popup window position.
+ GtkWindowType type = GTK_WINDOW_TOPLEVEL;
+ if (mWindowType == eWindowType_popup) {
+ type = (mIsX11Display && aInitData->mNoAutoHide) ? GTK_WINDOW_TOPLEVEL
+ : GTK_WINDOW_POPUP;
+ }
+ mShell = gtk_window_new(type);
+
+ // Ensure gfxPlatform is initialized, since that is what initializes
+ // gfxVars, used below.
+ Unused << gfxPlatform::GetPlatform();
+
+ if (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog) {
+ bool isPopup = mIsPIPWindow || mWindowType == eWindowType_dialog;
+ mCSDSupportLevel = GetSystemCSDSupportLevel(isPopup);
+ }
+
+ // Don't use transparency for PictureInPicture windows.
+ bool toplevelNeedsAlphaVisual = false;
+ if (mWindowType == eWindowType_toplevel && !mIsPIPWindow) {
+ toplevelNeedsAlphaVisual = IsToplevelWindowTransparent();
+ }
+
+ bool isGLVisualSet = false;
+ bool isAccelerated = ComputeShouldAccelerate();
+#ifdef MOZ_X11
+ if (isAccelerated) {
+ isGLVisualSet = ConfigureX11GLVisual(popupNeedsAlphaVisual ||
+ toplevelNeedsAlphaVisual);
+ }
+#endif
+ if (!isGLVisualSet &&
+ (popupNeedsAlphaVisual || toplevelNeedsAlphaVisual)) {
+ // We're running on composited screen so we can use alpha visual
+ // for both toplevel and popups.
+ if (mCompositedScreen) {
+ GdkVisual* visual =
+ gdk_screen_get_rgba_visual(gtk_widget_get_screen(mShell));
+ if (visual) {
+ gtk_widget_set_visual(mShell, visual);
+ mHasAlphaVisual = true;
+ }
+ }
+ }
+
+ // Use X shape mask to draw round corners of Firefox titlebar.
+ // We don't use shape masks any more as we switched to ARGB visual
+ // by default and non-compositing screens use solid-csd decorations
+ // without round corners.
+ // Leave the shape mask code here as it can be used to draw round
+ // corners on EGL (https://gitlab.freedesktop.org/mesa/mesa/-/issues/149)
+ // or when custom titlebar theme is used.
+ mTransparencyBitmapForTitlebar = TitlebarUseShapeMask();
+
+ // We have a toplevel window with transparency.
+ // Calls to UpdateTitlebarTransparencyBitmap() from OnExposeEvent()
+ // occur before SetTransparencyMode() receives eTransparencyTransparent
+ // from layout, so set mIsTransparent here.
+ if (mWindowType == eWindowType_toplevel &&
+ (mHasAlphaVisual || mTransparencyBitmapForTitlebar)) {
+ mIsTransparent = true;
+ }
+
+ // We only move a general managed toplevel window if someone has
+ // actually placed the window somewhere. If no placement has taken
+ // place, we just let the window manager Do The Right Thing.
+ NativeResize();
+
+ if (mWindowType == eWindowType_dialog) {
+ mGtkWindowRoleName = "Dialog";
+
+ SetDefaultIcon();
+ gtk_window_set_type_hint(GTK_WINDOW(mShell),
+ GDK_WINDOW_TYPE_HINT_DIALOG);
+
+ if (parentnsWindow) {
+ gtk_window_set_transient_for(
+ GTK_WINDOW(mShell), GTK_WINDOW(parentnsWindow->GetGtkWidget()));
+ LOG((
+ "nsWindow::Create(): dialog [%p], parent window %p [GdkWindow]\n",
+ this, aNativeParent));
+ }
+
+ } else if (mWindowType == eWindowType_popup) {
+ mGtkWindowRoleName = "Popup";
+
+ if (aInitData->mNoAutoHide) {
+ // ... but the window manager does not decorate this window,
+ // nor provide a separate taskbar icon.
+ if (mBorderStyle == eBorderStyle_default) {
+ gtk_window_set_decorated(GTK_WINDOW(mShell), FALSE);
+ } else {
+ bool decorate = mBorderStyle & eBorderStyle_title;
+ gtk_window_set_decorated(GTK_WINDOW(mShell), decorate);
+ if (decorate) {
+ gtk_window_set_deletable(GTK_WINDOW(mShell),
+ mBorderStyle & eBorderStyle_close);
+ }
+ }
+ gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mShell), TRUE);
+ // Element focus is managed by the parent window so the
+ // WM_HINTS input field is set to False to tell the window
+ // manager not to set input focus to this window ...
+ gtk_window_set_accept_focus(GTK_WINDOW(mShell), FALSE);
+#ifdef MOZ_X11
+ // ... but when the window manager offers focus through
+ // WM_TAKE_FOCUS, focus is requested on the parent window.
+ if (mIsX11Display) {
+ gtk_widget_realize(mShell);
+ gdk_window_add_filter(gtk_widget_get_window(mShell),
+ popup_take_focus_filter, nullptr);
+ }
+#endif
+ }
+
+ GdkWindowTypeHint gtkTypeHint;
+ if (aInitData->mIsDragPopup) {
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_DND;
+ mIsDragPopup = true;
+ } else {
+ switch (aInitData->mPopupHint) {
+ case ePopupTypeMenu:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
+ break;
+ case ePopupTypeTooltip:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
+ break;
+ default:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
+ break;
+ }
+ }
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
+ if (parentnsWindow) {
+ LOG(("nsWindow::Create() [%p]: parent window for popup: %p\n", this,
+ parentnsWindow));
+ gtk_window_set_transient_for(
+ GTK_WINDOW(mShell), GTK_WINDOW(parentnsWindow->GetGtkWidget()));
+ }
+
+ // We need realized mShell at NativeMove().
+ gtk_widget_realize(mShell);
+
+ // With popup windows, we want to control their position, so don't
+ // wait for the window manager to place them (which wouldn't
+ // happen with override-redirect windows anyway).
+ NativeMove();
+ } else { // must be eWindowType_toplevel
+ mGtkWindowRoleName = "Toplevel";
+ SetDefaultIcon();
+
+ if (mIsPIPWindow) {
+ gtk_window_set_type_hint(GTK_WINDOW(mShell),
+ GDK_WINDOW_TYPE_HINT_UTILITY);
+ }
+
+ // each toplevel window gets its own window group
+ GtkWindowGroup* group = gtk_window_group_new();
+ gtk_window_group_add_window(group, GTK_WINDOW(mShell));
+ g_object_unref(group);
+ }
+
+ if (mAlwaysOnTop) {
+ gtk_window_set_keep_above(GTK_WINDOW(mShell), TRUE);
+ }
+
+ // Create a container to hold child windows and child GtkWidgets.
+ GtkWidget* container = moz_container_new();
+ mContainer = MOZ_CONTAINER(container);
+#ifdef MOZ_WAYLAND
+ if (!mIsX11Display && isAccelerated) {
+ mCompositorInitiallyPaused = true;
+ RefPtr<nsWindow> self(this);
+ moz_container_wayland_add_initial_draw_callback(
+ mContainer, [self]() -> void {
+ self->mNeedsCompositorResume = true;
+ self->MaybeResumeCompositor();
+ });
+ }
+#endif
+
+ // "csd" style is set when widget is realized so we need to call
+ // it explicitly now.
+ gtk_widget_realize(mShell);
+
+ /* There are several cases here:
+ *
+ * 1) We're running on Gtk+ without client side decorations.
+ * Content is rendered to mShell window and we listen
+ * to the Gtk+ events on mShell
+ * 2) We're running on Gtk+ and client side decorations
+ * are drawn by Gtk+ to mShell. Content is rendered to mContainer
+ * and we listen to the Gtk+ events on mContainer.
+ * 3) We're running on Wayland. All gecko content is rendered
+ * to mContainer and we listen to the Gtk+ events on mContainer.
+ */
+ GtkStyleContext* style = gtk_widget_get_style_context(mShell);
+ mDrawToContainer = !mIsX11Display ||
+ (mCSDSupportLevel == CSD_SUPPORT_CLIENT) ||
+ gtk_style_context_has_class(style, "csd");
+ eventWidget = (mDrawToContainer) ? container : mShell;
+
+ // Prevent GtkWindow from painting a background to avoid flickering.
+ gtk_widget_set_app_paintable(eventWidget, TRUE);
+
+ gtk_widget_add_events(eventWidget, kEvents);
+ if (mDrawToContainer) {
+ gtk_widget_add_events(mShell, GDK_PROPERTY_CHANGE_MASK);
+ gtk_widget_set_app_paintable(mShell, TRUE);
+ }
+ if (mTransparencyBitmapForTitlebar) {
+ moz_container_force_default_visual(mContainer);
+ }
+
+ // If we draw to mContainer window then configure it now because
+ // gtk_container_add() realizes the child widget.
+ gtk_widget_set_has_window(container, mDrawToContainer);
+
+ gtk_container_add(GTK_CONTAINER(mShell), container);
+
+ // alwaysontop windows are generally used for peripheral indicators,
+ // so we don't focus them by default.
+ if (mAlwaysOnTop) {
+ gtk_window_set_focus_on_map(GTK_WINDOW(mShell), FALSE);
+ }
+
+ gtk_widget_realize(container);
+
+ // make sure this is the focus widget in the container
+ gtk_widget_show(container);
+
+ if (!mAlwaysOnTop) {
+ gtk_widget_grab_focus(container);
+ }
+
+ // the drawing window
+ mGdkWindow = gtk_widget_get_window(eventWidget);
+
+ if (mWindowType == eWindowType_popup) {
+ // gdk does not automatically set the cursor for "temporary"
+ // windows, which are what gtk uses for popups.
+
+ mCursor = eCursor_wait; // force SetCursor to actually set the
+ // cursor, even though our internal state
+ // indicates that we already have the
+ // standard cursor.
+ SetCursor(eCursor_standard, nullptr, 0, 0);
+
+ if (aInitData->mNoAutoHide) {
+ gint wmd = ConvertBorderStyles(mBorderStyle);
+ if (wmd != -1)
+ gdk_window_set_decorations(mGdkWindow, (GdkWMDecoration)wmd);
+ }
+
+ // If the popup ignores mouse events, set an empty input shape.
+ SetWindowMouseTransparent(aInitData->mMouseTransparent);
+ }
+ } break;
+
+ case eWindowType_plugin:
+ case eWindowType_plugin_ipc_chrome:
+ case eWindowType_plugin_ipc_content:
+ MOZ_ASSERT_UNREACHABLE("Unexpected eWindowType_plugin*");
+ return NS_ERROR_FAILURE;
+
+ case eWindowType_child: {
+ if (parentMozContainer) {
+ mGdkWindow = CreateGdkWindow(parentGdkWindow, parentMozContainer);
+ mHasMappedToplevel = parentnsWindow->mHasMappedToplevel;
+ } else if (parentGtkContainer) {
+ // This MozContainer has its own window for drawing and receives
+ // events because there is no mShell widget (corresponding to this
+ // nsWindow).
+ GtkWidget* container = moz_container_new();
+ mContainer = MOZ_CONTAINER(container);
+ eventWidget = container;
+ gtk_widget_add_events(eventWidget, kEvents);
+ gtk_container_add(parentGtkContainer, container);
+ gtk_widget_realize(container);
+
+ mGdkWindow = gtk_widget_get_window(container);
+ } else {
+ NS_WARNING(
+ "Warning: tried to create a new child widget with no parent!");
+ return NS_ERROR_FAILURE;
+ }
+ } break;
+ default:
+ break;
+ }
+
+ // label the drawing window with this object so we can find our way home
+ g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", this);
+ if (mDrawToContainer) {
+ // Also label mShell toplevel window,
+ // property_notify_event_cb callback also needs to find its way home
+ g_object_set_data(G_OBJECT(gtk_widget_get_window(mShell)), "nsWindow",
+ this);
+ }
+
+ if (mContainer) g_object_set_data(G_OBJECT(mContainer), "nsWindow", this);
+
+ if (mShell) g_object_set_data(G_OBJECT(mShell), "nsWindow", this);
+
+ // attach listeners for events
+ if (mShell) {
+ g_signal_connect(mShell, "configure_event", G_CALLBACK(configure_event_cb),
+ nullptr);
+ g_signal_connect(mShell, "delete_event", G_CALLBACK(delete_event_cb),
+ nullptr);
+ g_signal_connect(mShell, "window_state_event",
+ G_CALLBACK(window_state_event_cb), nullptr);
+ g_signal_connect(mShell, "check-resize", G_CALLBACK(check_resize_cb),
+ nullptr);
+ g_signal_connect(mShell, "composited-changed",
+ G_CALLBACK(widget_composited_changed_cb), nullptr);
+ g_signal_connect(mShell, "property-notify-event",
+ G_CALLBACK(property_notify_event_cb), nullptr);
+
+ if (mWindowType == eWindowType_toplevel) {
+ g_signal_connect_after(mShell, "size_allocate",
+ G_CALLBACK(toplevel_window_size_allocate_cb),
+ nullptr);
+ }
+
+ GdkScreen* screen = gtk_widget_get_screen(mShell);
+ if (!g_signal_handler_find(screen, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
+ FuncToGpointer(screen_composited_changed_cb),
+ 0)) {
+ g_signal_connect(screen, "composited-changed",
+ G_CALLBACK(screen_composited_changed_cb), nullptr);
+ }
+
+ GtkSettings* default_settings = gtk_settings_get_default();
+ g_signal_connect_after(default_settings, "notify::gtk-theme-name",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings, "notify::gtk-font-name",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings, "notify::gtk-enable-animations",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings, "notify::gtk-decoration-layout",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings, "notify::gtk-xft-dpi",
+ G_CALLBACK(settings_xft_dpi_changed_cb), this);
+ // For remote LookAndFeel, to refresh the content processes' copies:
+ g_signal_connect_after(default_settings, "notify::gtk-cursor-blink-time",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings, "notify::gtk-cursor-blink",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings,
+ "notify::gtk-entry-select-on-focus",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings,
+ "notify::gtk-primary-button-warps-slider",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings, "notify::gtk-menu-popup-delay",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings, "notify::gtk-dnd-drag-threshold",
+ G_CALLBACK(settings_changed_cb), this);
+ }
+
+ if (mContainer) {
+ // Widget signals
+ g_signal_connect(mContainer, "unrealize",
+ G_CALLBACK(container_unrealize_cb), nullptr);
+ g_signal_connect_after(mContainer, "size_allocate",
+ G_CALLBACK(size_allocate_cb), nullptr);
+ g_signal_connect(mContainer, "hierarchy-changed",
+ G_CALLBACK(hierarchy_changed_cb), nullptr);
+ g_signal_connect(mContainer, "notify::scale-factor",
+ G_CALLBACK(scale_changed_cb), nullptr);
+ // Initialize mHasMappedToplevel.
+ hierarchy_changed_cb(GTK_WIDGET(mContainer), nullptr);
+ // Expose, focus, key, and drag events are sent even to GTK_NO_WINDOW
+ // widgets.
+ g_signal_connect(G_OBJECT(mContainer), "draw", G_CALLBACK(expose_event_cb),
+ nullptr);
+ g_signal_connect(mContainer, "focus_in_event",
+ G_CALLBACK(focus_in_event_cb), nullptr);
+ g_signal_connect(mContainer, "focus_out_event",
+ G_CALLBACK(focus_out_event_cb), nullptr);
+ g_signal_connect(mContainer, "key_press_event",
+ G_CALLBACK(key_press_event_cb), nullptr);
+ g_signal_connect(mContainer, "key_release_event",
+ G_CALLBACK(key_release_event_cb), nullptr);
+
+ gtk_drag_dest_set((GtkWidget*)mContainer, (GtkDestDefaults)0, nullptr, 0,
+ (GdkDragAction)0);
+
+ g_signal_connect(mContainer, "drag_motion",
+ G_CALLBACK(drag_motion_event_cb), nullptr);
+ g_signal_connect(mContainer, "drag_leave", G_CALLBACK(drag_leave_event_cb),
+ nullptr);
+ g_signal_connect(mContainer, "drag_drop", G_CALLBACK(drag_drop_event_cb),
+ nullptr);
+ g_signal_connect(mContainer, "drag_data_received",
+ G_CALLBACK(drag_data_received_event_cb), nullptr);
+
+#ifdef MOZ_X11
+ if (mIsX11Display) {
+ GtkWidget* widgets[] = {GTK_WIDGET(mContainer),
+ !mDrawToContainer ? mShell : nullptr};
+ for (size_t i = 0; i < ArrayLength(widgets) && widgets[i]; ++i) {
+ // Double buffering is controlled by the window's owning
+ // widget. Disable double buffering for painting directly to the
+ // X Window.
+ gtk_widget_set_double_buffered(widgets[i], FALSE);
+ }
+ }
+#endif
+
+ // We create input contexts for all containers, except for
+ // toplevel popup windows
+ if (mWindowType != eWindowType_popup) {
+ mIMContext = new IMContextWrapper(this);
+ }
+ } else if (!mIMContext) {
+ nsWindow* container = GetContainerWindow();
+ if (container) {
+ mIMContext = container->mIMContext;
+ }
+ }
+
+ if (eventWidget) {
+ // These events are sent to the owning widget of the relevant window
+ // and propagate up to the first widget that handles the events, so we
+ // need only connect on mShell, if it exists, to catch events on its
+ // window and windows of mContainer.
+ g_signal_connect(eventWidget, "enter-notify-event",
+ G_CALLBACK(enter_notify_event_cb), nullptr);
+ g_signal_connect(eventWidget, "leave-notify-event",
+ G_CALLBACK(leave_notify_event_cb), nullptr);
+ g_signal_connect(eventWidget, "motion-notify-event",
+ G_CALLBACK(motion_notify_event_cb), nullptr);
+ g_signal_connect(eventWidget, "button-press-event",
+ G_CALLBACK(button_press_event_cb), nullptr);
+ g_signal_connect(eventWidget, "button-release-event",
+ G_CALLBACK(button_release_event_cb), nullptr);
+ g_signal_connect(eventWidget, "scroll-event", G_CALLBACK(scroll_event_cb),
+ nullptr);
+ if (gtk_check_version(3, 18, 0) == nullptr) {
+ g_signal_connect(eventWidget, "event", G_CALLBACK(generic_event_cb),
+ nullptr);
+ }
+ g_signal_connect(eventWidget, "touch-event", G_CALLBACK(touch_event_cb),
+ nullptr);
+ }
+
+ LOG(("nsWindow [%p] %s %s\n", (void*)this,
+ mWindowType == eWindowType_toplevel ? "Toplevel" : "Popup",
+ mIsPIPWindow ? "PIP window" : ""));
+ if (mShell) {
+ LOG(("\tmShell %p mContainer %p mGdkWindow %p 0x%lx\n", mShell, mContainer,
+ mGdkWindow, mIsX11Display ? gdk_x11_window_get_xid(mGdkWindow) : 0));
+ } else if (mContainer) {
+ LOG(("\tmContainer %p mGdkWindow %p\n", mContainer, mGdkWindow));
+ } else if (mGdkWindow) {
+ LOG(("\tmGdkWindow %p parent %p\n", mGdkWindow,
+ gdk_window_get_parent(mGdkWindow)));
+ }
+
+ // resize so that everything is set to the right dimensions
+ if (!mIsTopLevel)
+ Resize(mBounds.x, mBounds.y, mBounds.width, mBounds.height, false);
+
+#ifdef MOZ_X11
+ if (mIsX11Display && mGdkWindow) {
+ mXDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ mXWindow = gdk_x11_window_get_xid(mGdkWindow);
+
+ GdkVisual* gdkVisual = gdk_window_get_visual(mGdkWindow);
+ mXVisual = gdk_x11_visual_get_xvisual(gdkVisual);
+ mXDepth = gdk_visual_get_depth(gdkVisual);
+ bool shaped = popupNeedsAlphaVisual && !mHasAlphaVisual;
+
+ mSurfaceProvider.Initialize(mXDisplay, mXWindow, mXVisual, mXDepth, shaped);
+
+ if (mIsTopLevel) {
+ // Set window manager hint to keep fullscreen windows composited.
+ //
+ // If the window were to get unredirected, there could be visible
+ // tearing because Gecko does not align its framebuffer updates with
+ // vblank.
+ SetCompositorHint(GTK_WIDGET_COMPOSIDED_ENABLED);
+ }
+ }
+# ifdef MOZ_WAYLAND
+ else if (!mIsX11Display) {
+ mSurfaceProvider.Initialize(this);
+ WaylandStartVsync();
+ }
+# endif
+#endif
+
+ // Set default application name when it's empty.
+ if (mGtkWindowAppName.IsEmpty()) {
+ mGtkWindowAppName = gAppData->name;
+ }
+ RefreshWindowClass();
+
+ return NS_OK;
+}
+
+void nsWindow::RefreshWindowClass(void) {
+ GdkWindow* gdkWindow = gtk_widget_get_window(mShell);
+ if (!gdkWindow) {
+ return;
+ }
+
+ if (!mGtkWindowRoleName.IsEmpty()) {
+ gdk_window_set_role(gdkWindow, mGtkWindowRoleName.get());
+ }
+
+#ifdef MOZ_X11
+ if (!mGtkWindowAppName.IsEmpty() && mIsX11Display) {
+ XClassHint* class_hint = XAllocClassHint();
+ if (!class_hint) {
+ return;
+ }
+ const char* res_class = gdk_get_program_class();
+ if (!res_class) return;
+
+ class_hint->res_name = const_cast<char*>(mGtkWindowAppName.get());
+ class_hint->res_class = const_cast<char*>(res_class);
+
+ // Can't use gtk_window_set_wmclass() for this; it prints
+ // a warning & refuses to make the change.
+ GdkDisplay* display = gdk_display_get_default();
+ XSetClassHint(GDK_DISPLAY_XDISPLAY(display),
+ gdk_x11_window_get_xid(gdkWindow), class_hint);
+ XFree(class_hint);
+ }
+#endif /* MOZ_X11 */
+}
+
+void nsWindow::SetWindowClass(const nsAString& xulWinType) {
+ if (!mShell) return;
+
+ char* res_name = ToNewCString(xulWinType, mozilla::fallible);
+ if (!res_name) return;
+
+ const char* role = nullptr;
+
+ // Parse res_name into a name and role. Characters other than
+ // [A-Za-z0-9_-] are converted to '_'. Anything after the first
+ // colon is assigned to role; if there's no colon, assign the
+ // whole thing to both role and res_name.
+ for (char* c = res_name; *c; c++) {
+ if (':' == *c) {
+ *c = 0;
+ role = c + 1;
+ } else if (!isascii(*c) || (!isalnum(*c) && ('_' != *c) && ('-' != *c)))
+ *c = '_';
+ }
+ res_name[0] = toupper(res_name[0]);
+ if (!role) role = res_name;
+
+ mGtkWindowAppName = res_name;
+ mGtkWindowRoleName = role;
+ free(res_name);
+
+ RefreshWindowClass();
+}
+
+void nsWindow::NativeResize() {
+ if (!AreBoundsSane()) {
+ // If someone has set this so that the needs show flag is false
+ // and it needs to be hidden, update the flag and hide the
+ // window. This flag will be cleared the next time someone
+ // hides the window or shows it. It also prevents us from
+ // calling NativeShow(false) excessively on the window which
+ // causes unneeded X traffic.
+ if (!mNeedsShow && mIsShown) {
+ mNeedsShow = true;
+ NativeShow(false);
+ }
+ return;
+ }
+
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
+
+ LOG(("nsWindow::NativeResize [%p] %d %d\n", (void*)this, size.width,
+ size.height));
+
+ if (mIsTopLevel) {
+ MOZ_ASSERT(size.width > 0 && size.height > 0,
+ "Can't resize window smaller than 1x1.");
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ if (mWaitingForMoveToRectCB) {
+ LOG(("Waiting for move to rect, schedulling "));
+ mPendingSizeRect = mBounds;
+ }
+ } else if (mContainer) {
+ GtkWidget* widget = GTK_WIDGET(mContainer);
+ GtkAllocation allocation, prev_allocation;
+ gtk_widget_get_allocation(widget, &prev_allocation);
+ allocation.x = prev_allocation.x;
+ allocation.y = prev_allocation.y;
+ allocation.width = size.width;
+ allocation.height = size.height;
+ gtk_widget_size_allocate(widget, &allocation);
+ } else if (mGdkWindow) {
+ gdk_window_resize(mGdkWindow, size.width, size.height);
+ }
+
+#ifdef MOZ_X11
+ // Notify the GtkCompositorWidget of a ClientSizeChange
+ // This is different than OnSizeAllocate to catch initial sizing
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
+ }
+#endif
+
+ // Does it need to be shown because bounds were previously insane?
+ if (mNeedsShow && mIsShown) {
+ NativeShow(true);
+ }
+}
+
+void nsWindow::NativeMoveResize() {
+ if (!AreBoundsSane()) {
+ // If someone has set this so that the needs show flag is false
+ // and it needs to be hidden, update the flag and hide the
+ // window. This flag will be cleared the next time someone
+ // hides the window or shows it. It also prevents us from
+ // calling NativeShow(false) excessively on the window which
+ // causes unneeded X traffic.
+ if (!mNeedsShow && mIsShown) {
+ mNeedsShow = true;
+ NativeShow(false);
+ }
+ NativeMove();
+
+ return;
+ }
+
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
+ GdkPoint topLeft = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
+
+ LOG(("nsWindow::NativeMoveResize [%p] %d %d %d %d\n", (void*)this, topLeft.x,
+ topLeft.y, size.width, size.height));
+
+ if (IsWaylandPopup()) {
+ NativeMoveResizeWaylandPopup(&topLeft, &size);
+ } else {
+ if (mIsTopLevel) {
+ // x and y give the position of the window manager frame top-left.
+ gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
+ // This sets the client window size.
+ MOZ_ASSERT(size.width > 0 && size.height > 0,
+ "Can't resize window smaller than 1x1.");
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ } else if (mContainer) {
+ GtkAllocation allocation;
+ allocation.x = topLeft.x;
+ allocation.y = topLeft.y;
+ allocation.width = size.width;
+ allocation.height = size.height;
+ gtk_widget_size_allocate(GTK_WIDGET(mContainer), &allocation);
+ } else if (mGdkWindow) {
+ gdk_window_move_resize(mGdkWindow, topLeft.x, topLeft.y, size.width,
+ size.height);
+ }
+ }
+
+#ifdef MOZ_X11
+ // Notify the GtkCompositorWidget of a ClientSizeChange
+ // This is different than OnSizeAllocate to catch initial sizing
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
+ }
+#endif
+
+ // Does it need to be shown because bounds were previously insane?
+ if (mNeedsShow && mIsShown) {
+ NativeShow(true);
+ }
+}
+
+void nsWindow::PauseRemoteRenderer() {
+#ifdef MOZ_WAYLAND
+ if (!mIsDestroyed) {
+ if (mContainer) {
+ // Because wl_egl_window is destroyed on moz_container_unmap(),
+ // the current compositor cannot use it anymore. To avoid crash,
+ // pause the compositor and destroy EGLSurface & resume the compositor
+ // and re-create EGLSurface on next expose event.
+
+ // moz_container_wayland_has_egl_window() could not be used here, since
+ // there is a case that resume compositor is not completed yet.
+
+ CompositorBridgeChild* remoteRenderer = GetRemoteRenderer();
+ bool needsCompositorPause = !mNeedsCompositorResume && !!remoteRenderer &&
+ mCompositorWidgetDelegate;
+ if (needsCompositorPause) {
+ // XXX slow sync IPC
+ remoteRenderer->SendPause();
+ // Re-request initial draw callback
+ RefPtr<nsWindow> self(this);
+ moz_container_wayland_add_initial_draw_callback(
+ mContainer, [self]() -> void {
+ self->mNeedsCompositorResume = true;
+ self->MaybeResumeCompositor();
+ });
+ } else {
+ DestroyLayerManager();
+ }
+ }
+ }
+#endif
+}
+
+void nsWindow::HideWaylandWindow() {
+ if (mWindowType == eWindowType_popup) {
+ LOG(("nsWindow::HideWaylandWindow: popup [%p]\n", this));
+ GList* foundWindow = g_list_find(gVisibleWaylandPopupWindows, this);
+ if (foundWindow) {
+ gVisibleWaylandPopupWindows =
+ g_list_delete_link(gVisibleWaylandPopupWindows, foundWindow);
+ }
+ }
+ PauseRemoteRenderer();
+ gtk_widget_hide(mShell);
+}
+
+void nsWindow::WaylandStartVsync() {
+#ifdef MOZ_WAYLAND
+ // only use for toplevel windows for now - see bug 1619246
+ if (!gUseWaylandVsync || mWindowType != eWindowType_toplevel) {
+ return;
+ }
+
+ if (!mWaylandVsyncSource) {
+ mWaylandVsyncSource = new mozilla::WaylandVsyncSource(mContainer);
+ }
+ WaylandVsyncSource::WaylandDisplay& display =
+ static_cast<WaylandVsyncSource::WaylandDisplay&>(
+ mWaylandVsyncSource->GetGlobalDisplay());
+ display.EnableMonitor();
+#endif
+}
+
+void nsWindow::WaylandStopVsync() {
+#ifdef MOZ_WAYLAND
+ if (mWaylandVsyncSource) {
+ // The widget is going to be hidden, so clear the surface of our
+ // vsync source.
+ WaylandVsyncSource::WaylandDisplay& display =
+ static_cast<WaylandVsyncSource::WaylandDisplay&>(
+ mWaylandVsyncSource->GetGlobalDisplay());
+ display.DisableMonitor();
+ }
+#endif
+}
+
+void nsWindow::NativeShow(bool aAction) {
+ if (aAction) {
+ // unset our flag now that our window has been shown
+ mNeedsShow = false;
+
+ if (mIsTopLevel) {
+ // Set up usertime/startupID metadata for the created window.
+ if (mWindowType != eWindowType_invisible) {
+ SetUserTimeAndStartupIDForActivatedWindow(mShell);
+ }
+ // Update popup window hierarchy run-time on Wayland.
+ if (IsWaylandPopup()) {
+ if (!ConfigureWaylandPopupWindows()) {
+ mNeedsShow = true;
+ return;
+ }
+ }
+
+ LOG((" calling gtk_widget_show(mShell)\n"));
+ gtk_widget_show(mShell);
+ if (!mIsX11Display) {
+ WaylandStartVsync();
+ }
+ } else if (mContainer) {
+ LOG((" calling gtk_widget_show(mContainer)\n"));
+ gtk_widget_show(GTK_WIDGET(mContainer));
+ } else if (mGdkWindow) {
+ LOG((" calling gdk_window_show_unraised\n"));
+ gdk_window_show_unraised(mGdkWindow);
+ }
+ } else {
+ // There's a chance that when the popup will be shown again it might be
+ // resized because parent could be moved meanwhile.
+ mPreferredPopupRect = nsRect(0, 0, 0, 0);
+ mPreferredPopupRectFlushed = false;
+ if (!mIsX11Display) {
+ WaylandStopVsync();
+ if (IsWaylandPopup() && IsMainMenuWindow()) {
+ CleanupWaylandPopups();
+ }
+ HideWaylandWindow();
+ } else if (mIsTopLevel) {
+ // Workaround window freezes on GTK versions before 3.21.2 by
+ // ensuring that configure events get dispatched to windows before
+ // they are unmapped. See bug 1225044.
+ if (gtk_check_version(3, 21, 2) != nullptr && mPendingConfigures > 0) {
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(GTK_WIDGET(mShell), &allocation);
+
+ GdkEventConfigure event;
+ PodZero(&event);
+ event.type = GDK_CONFIGURE;
+ event.window = mGdkWindow;
+ event.send_event = TRUE;
+ event.x = allocation.x;
+ event.y = allocation.y;
+ event.width = allocation.width;
+ event.height = allocation.height;
+
+ auto shellClass = GTK_WIDGET_GET_CLASS(mShell);
+ for (unsigned int i = 0; i < mPendingConfigures; i++) {
+ Unused << shellClass->configure_event(mShell, &event);
+ }
+ mPendingConfigures = 0;
+ }
+ gtk_widget_hide(mShell);
+
+ ClearTransparencyBitmap(); // Release some resources
+ } else if (mContainer) {
+ gtk_widget_hide(GTK_WIDGET(mContainer));
+ } else if (mGdkWindow) {
+ gdk_window_hide(mGdkWindow);
+ }
+ }
+}
+
+void nsWindow::SetHasMappedToplevel(bool aState) {
+ // Even when aState == mHasMappedToplevel (as when this method is called
+ // from Show()), child windows need to have their state checked, so don't
+ // return early.
+ bool oldState = mHasMappedToplevel;
+ mHasMappedToplevel = aState;
+
+ // mHasMappedToplevel is not updated for children of windows that are
+ // hidden; GDK knows not to send expose events for these windows. The
+ // state is recorded on the hidden window itself, but, for child trees of
+ // hidden windows, their state essentially becomes disconnected from their
+ // hidden parent. When the hidden parent gets shown, the child trees are
+ // reconnected, and the state of the window being shown can be easily
+ // propagated.
+ if (!mIsShown || !mGdkWindow) return;
+
+ if (aState && !oldState) {
+ // Check that a grab didn't fail due to the window not being
+ // viewable.
+ EnsureGrabs();
+ }
+
+ for (GList* children = gdk_window_peek_children(mGdkWindow); children;
+ children = children->next) {
+ GdkWindow* gdkWin = GDK_WINDOW(children->data);
+ nsWindow* child = get_window_for_gdk_window(gdkWin);
+
+ if (child && child->mHasMappedToplevel != aState) {
+ child->SetHasMappedToplevel(aState);
+ }
+ }
+}
+
+LayoutDeviceIntSize nsWindow::GetSafeWindowSize(LayoutDeviceIntSize aSize) {
+ // The X protocol uses CARD32 for window sizes, but the server (1.11.3)
+ // reads it as CARD16. Sizes of pixmaps, used for drawing, are (unsigned)
+ // CARD16 in the protocol, but the server's ProcCreatePixmap returns
+ // BadAlloc if dimensions cannot be represented by signed shorts.
+ // Because we are creating Cairo surfaces to represent window buffers,
+ // we also must ensure that the window can fit in a Cairo surface.
+ LayoutDeviceIntSize result = aSize;
+ int32_t maxSize = 32767;
+ if (mLayerManager && mLayerManager->AsKnowsCompositor()) {
+ maxSize = std::min(maxSize,
+ mLayerManager->AsKnowsCompositor()->GetMaxTextureSize());
+ }
+ if (result.width > maxSize) {
+ result.width = maxSize;
+ }
+ if (result.height > maxSize) {
+ result.height = maxSize;
+ }
+ return result;
+}
+
+void nsWindow::EnsureGrabs(void) {
+ if (mRetryPointerGrab) GrabPointer(sRetryGrabTime);
+}
+
+void nsWindow::CleanLayerManagerRecursive(void) {
+ if (mLayerManager) {
+ mLayerManager->Destroy();
+ mLayerManager = nullptr;
+ }
+
+ DestroyCompositor();
+
+ GList* children = gdk_window_peek_children(mGdkWindow);
+ for (GList* list = children; list; list = list->next) {
+ nsWindow* window = get_window_for_gdk_window(GDK_WINDOW(list->data));
+ if (window) {
+ window->CleanLayerManagerRecursive();
+ }
+ }
+}
+
+void nsWindow::SetTransparencyMode(nsTransparencyMode aMode) {
+ if (!mShell) {
+ // Pass the request to the toplevel window
+ GtkWidget* topWidget = GetToplevelWidget();
+ if (!topWidget) return;
+
+ nsWindow* topWindow = get_window_for_gtk_widget(topWidget);
+ if (!topWindow) return;
+
+ topWindow->SetTransparencyMode(aMode);
+ return;
+ }
+
+ bool isTransparent = aMode == eTransparencyTransparent;
+
+ if (mIsTransparent == isTransparent) {
+ return;
+ }
+
+ if (mWindowType != eWindowType_popup) {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1344839 reported
+ // problems cleaning the layer manager for toplevel windows.
+ // Ignore the request so as to workaround that.
+ // mIsTransparent is set in Create() if transparency may be required.
+ if (isTransparent) {
+ NS_WARNING("Transparent mode not supported on non-popup windows.");
+ }
+ return;
+ }
+
+ if (!isTransparent) {
+ ClearTransparencyBitmap();
+ } // else the new default alpha values are "all 1", so we don't
+ // need to change anything yet
+
+ mIsTransparent = isTransparent;
+
+ if (!mHasAlphaVisual) {
+ // The choice of layer manager depends on
+ // GtkCompositorWidgetInitData::Shaped(), which will need to change, so
+ // clean out the old layer manager.
+ CleanLayerManagerRecursive();
+ }
+}
+
+nsTransparencyMode nsWindow::GetTransparencyMode() {
+ if (!mShell) {
+ // Pass the request to the toplevel window
+ GtkWidget* topWidget = GetToplevelWidget();
+ if (!topWidget) {
+ return eTransparencyOpaque;
+ }
+
+ nsWindow* topWindow = get_window_for_gtk_widget(topWidget);
+ if (!topWindow) {
+ return eTransparencyOpaque;
+ }
+
+ return topWindow->GetTransparencyMode();
+ }
+
+ return mIsTransparent ? eTransparencyTransparent : eTransparencyOpaque;
+}
+
+void nsWindow::SetWindowMouseTransparent(bool aIsTransparent) {
+ if (!mGdkWindow) {
+ return;
+ }
+
+ cairo_rectangle_int_t emptyRect = {0, 0, 0, 0};
+ cairo_region_t* region =
+ aIsTransparent ? cairo_region_create_rectangle(&emptyRect) : nullptr;
+
+ gdk_window_input_shape_combine_region(mGdkWindow, region, 0, 0);
+ if (region) {
+ cairo_region_destroy(region);
+ }
+}
+
+// For setting the draggable titlebar region from CSS
+// with -moz-window-dragging: drag.
+void nsWindow::UpdateWindowDraggingRegion(
+ const LayoutDeviceIntRegion& aRegion) {
+ if (mDraggableRegion != aRegion) {
+ mDraggableRegion = aRegion;
+ }
+}
+
+// See subtract_corners_from_region() at gtk/gtkwindow.c
+// We need to subtract corners from toplevel window opaque region
+// to draw transparent corners of default Gtk titlebar.
+// Both implementations (cairo_region_t and wl_region) needs to be synced.
+static void SubtractTitlebarCorners(cairo_region_t* aRegion, int aX, int aY,
+ int aWindowWidth) {
+ cairo_rectangle_int_t rect = {aX, aY, TITLEBAR_SHAPE_MASK_HEIGHT,
+ TITLEBAR_SHAPE_MASK_HEIGHT};
+ cairo_region_subtract_rectangle(aRegion, &rect);
+ rect = {
+ aX + aWindowWidth - TITLEBAR_SHAPE_MASK_HEIGHT,
+ aY,
+ TITLEBAR_SHAPE_MASK_HEIGHT,
+ TITLEBAR_SHAPE_MASK_HEIGHT,
+ };
+ cairo_region_subtract_rectangle(aRegion, &rect);
+}
+
+void nsWindow::UpdateTopLevelOpaqueRegion(void) {
+ if (!mCompositedScreen) {
+ return;
+ }
+
+ GdkWindow* window =
+ (mDrawToContainer) ? gtk_widget_get_window(mShell) : mGdkWindow;
+ if (!window) {
+ return;
+ }
+ MOZ_ASSERT(gdk_window_get_window_type(window) == GDK_WINDOW_TOPLEVEL);
+
+ int x = 0;
+ int y = 0;
+
+ if (mDrawToContainer) {
+ gdk_window_get_position(mGdkWindow, &x, &y);
+ }
+
+ int width = DevicePixelsToGdkCoordRoundDown(mBounds.width);
+ int height = DevicePixelsToGdkCoordRoundDown(mBounds.height);
+
+ cairo_region_t* region = cairo_region_create();
+ cairo_rectangle_int_t rect = {x, y, width, height};
+ cairo_region_union_rectangle(region, &rect);
+
+ bool subtractCorners = DoDrawTilebarCorners();
+ if (subtractCorners) {
+ SubtractTitlebarCorners(region, x, y, width);
+ }
+
+ gdk_window_set_opaque_region(window, region);
+
+ cairo_region_destroy(region);
+
+#ifdef MOZ_WAYLAND
+ // We don't set opaque region to mozContainer by default due to Bug 1615098.
+ if (!mIsX11Display && gUseWaylandUseOpaqueRegion) {
+ moz_container_wayland_update_opaque_region(mContainer, subtractCorners);
+ }
+#endif
+}
+
+bool nsWindow::IsChromeWindowTitlebar() {
+ return mDrawInTitlebar && !mIsPIPWindow &&
+ mWindowType == eWindowType_toplevel;
+}
+
+bool nsWindow::DoDrawTilebarCorners() {
+ return IsChromeWindowTitlebar() && mSizeState == nsSizeMode_Normal &&
+ !mIsTiled;
+}
+
+nsresult nsWindow::ConfigureChildren(
+ const nsTArray<Configuration>& aConfigurations) {
+ // If this is a remotely updated widget we receive clipping, position, and
+ // size information from a source other than our owner. Don't let our parent
+ // update this information.
+ if (mWindowType == eWindowType_plugin_ipc_chrome) {
+ return NS_OK;
+ }
+
+ for (uint32_t i = 0; i < aConfigurations.Length(); ++i) {
+ const Configuration& configuration = aConfigurations[i];
+ auto* w = static_cast<nsWindow*>(configuration.mChild.get());
+ NS_ASSERTION(w->GetParent() == this, "Configured widget is not a child");
+ w->SetWindowClipRegion(configuration.mClipRegion, true);
+ if (w->mBounds.Size() != configuration.mBounds.Size()) {
+ w->Resize(configuration.mBounds.x, configuration.mBounds.y,
+ configuration.mBounds.width, configuration.mBounds.height,
+ true);
+ } else if (w->mBounds.TopLeft() != configuration.mBounds.TopLeft()) {
+ w->Move(configuration.mBounds.x, configuration.mBounds.y);
+ }
+ w->SetWindowClipRegion(configuration.mClipRegion, false);
+ }
+ return NS_OK;
+}
+
+nsresult nsWindow::SetWindowClipRegion(
+ const nsTArray<LayoutDeviceIntRect>& aRects, bool aIntersectWithExisting) {
+ const nsTArray<LayoutDeviceIntRect>* newRects = &aRects;
+
+ AutoTArray<LayoutDeviceIntRect, 1> intersectRects;
+ if (aIntersectWithExisting) {
+ AutoTArray<LayoutDeviceIntRect, 1> existingRects;
+ GetWindowClipRegion(&existingRects);
+
+ LayoutDeviceIntRegion existingRegion = RegionFromArray(existingRects);
+ LayoutDeviceIntRegion newRegion = RegionFromArray(aRects);
+ LayoutDeviceIntRegion intersectRegion;
+ intersectRegion.And(newRegion, existingRegion);
+
+ // If mClipRects is null we haven't set a clip rect yet, so we
+ // need to set the clip even if it is equal.
+ if (mClipRects && intersectRegion.IsEqual(existingRegion)) {
+ return NS_OK;
+ }
+
+ if (!intersectRegion.IsEqual(newRegion)) {
+ ArrayFromRegion(intersectRegion, intersectRects);
+ newRects = &intersectRects;
+ }
+ }
+
+ if (IsWindowClipRegionEqual(*newRects)) return NS_OK;
+
+ StoreWindowClipRegion(*newRects);
+
+ if (!mGdkWindow) return NS_OK;
+
+ cairo_region_t* region = cairo_region_create();
+ for (uint32_t i = 0; i < newRects->Length(); ++i) {
+ const LayoutDeviceIntRect& r = newRects->ElementAt(i);
+ cairo_rectangle_int_t rect = {r.x, r.y, r.width, r.height};
+ cairo_region_union_rectangle(region, &rect);
+ }
+
+ gdk_window_shape_combine_region(mGdkWindow, region, 0, 0);
+ cairo_region_destroy(region);
+
+ return NS_OK;
+}
+
+void nsWindow::ResizeTransparencyBitmap() {
+ if (!mTransparencyBitmap) return;
+
+ if (mBounds.width == mTransparencyBitmapWidth &&
+ mBounds.height == mTransparencyBitmapHeight)
+ return;
+
+ int32_t newRowBytes = GetBitmapStride(mBounds.width);
+ int32_t newSize = newRowBytes * mBounds.height;
+ auto* newBits = new gchar[newSize];
+ // fill new mask with "transparent", first
+ memset(newBits, 0, newSize);
+
+ // Now copy the intersection of the old and new areas into the new mask
+ int32_t copyWidth = std::min(mBounds.width, mTransparencyBitmapWidth);
+ int32_t copyHeight = std::min(mBounds.height, mTransparencyBitmapHeight);
+ int32_t oldRowBytes = GetBitmapStride(mTransparencyBitmapWidth);
+ int32_t copyBytes = GetBitmapStride(copyWidth);
+
+ int32_t i;
+ gchar* fromPtr = mTransparencyBitmap;
+ gchar* toPtr = newBits;
+ for (i = 0; i < copyHeight; i++) {
+ memcpy(toPtr, fromPtr, copyBytes);
+ fromPtr += oldRowBytes;
+ toPtr += newRowBytes;
+ }
+
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = newBits;
+ mTransparencyBitmapWidth = mBounds.width;
+ mTransparencyBitmapHeight = mBounds.height;
+}
+
+static bool ChangedMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
+ int32_t aMaskHeight, const nsIntRect& aRect,
+ uint8_t* aAlphas, int32_t aStride) {
+ int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
+ int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
+ for (y = aRect.y; y < yMax; y++) {
+ gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
+ uint8_t* alphas = aAlphas;
+ for (x = aRect.x; x < xMax; x++) {
+ bool newBit = *alphas > 0x7f;
+ alphas++;
+
+ gchar maskByte = maskBytes[x >> 3];
+ bool maskBit = (maskByte & (1 << (x & 7))) != 0;
+
+ if (maskBit != newBit) {
+ return true;
+ }
+ }
+ aAlphas += aStride;
+ }
+
+ return false;
+}
+
+static void UpdateMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
+ int32_t aMaskHeight, const nsIntRect& aRect,
+ uint8_t* aAlphas, int32_t aStride) {
+ int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
+ int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
+ for (y = aRect.y; y < yMax; y++) {
+ gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
+ uint8_t* alphas = aAlphas;
+ for (x = aRect.x; x < xMax; x++) {
+ bool newBit = *alphas > 0x7f;
+ alphas++;
+
+ gchar mask = 1 << (x & 7);
+ gchar maskByte = maskBytes[x >> 3];
+ // Note: '-newBit' turns 0 into 00...00 and 1 into 11...11
+ maskBytes[x >> 3] = (maskByte & ~mask) | (-newBit & mask);
+ }
+ aAlphas += aStride;
+ }
+}
+
+void nsWindow::ApplyTransparencyBitmap() {
+#ifdef MOZ_X11
+ // We use X11 calls where possible, because GDK handles expose events
+ // for shaped windows in a way that's incompatible with us (Bug 635903).
+ // It doesn't occur when the shapes are set through X.
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
+ Pixmap maskPixmap = XCreateBitmapFromData(
+ xDisplay, xDrawable, mTransparencyBitmap, mTransparencyBitmapWidth,
+ mTransparencyBitmapHeight);
+ XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
+ ShapeSet);
+ XFreePixmap(xDisplay, maskPixmap);
+#else
+ cairo_surface_t* maskBitmap;
+ maskBitmap = cairo_image_surface_create_for_data(
+ (unsigned char*)mTransparencyBitmap, CAIRO_FORMAT_A1,
+ mTransparencyBitmapWidth, mTransparencyBitmapHeight,
+ GetBitmapStride(mTransparencyBitmapWidth));
+ if (!maskBitmap) return;
+
+ cairo_region_t* maskRegion = gdk_cairo_region_create_from_surface(maskBitmap);
+ gtk_widget_shape_combine_region(mShell, maskRegion);
+ cairo_region_destroy(maskRegion);
+ cairo_surface_destroy(maskBitmap);
+#endif // MOZ_X11
+}
+
+void nsWindow::ClearTransparencyBitmap() {
+ if (!mTransparencyBitmap) return;
+
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = nullptr;
+ mTransparencyBitmapWidth = 0;
+ mTransparencyBitmapHeight = 0;
+
+ if (!mShell) return;
+
+#ifdef MOZ_X11
+ if (!mGdkWindow) return;
+
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ Window xWindow = gdk_x11_window_get_xid(mGdkWindow);
+
+ XShapeCombineMask(xDisplay, xWindow, ShapeBounding, 0, 0, X11None, ShapeSet);
+#endif
+}
+
+nsresult nsWindow::UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
+ uint8_t* aAlphas,
+ int32_t aStride) {
+ if (!mShell) {
+ // Pass the request to the toplevel window
+ GtkWidget* topWidget = GetToplevelWidget();
+ if (!topWidget) return NS_ERROR_FAILURE;
+
+ nsWindow* topWindow = get_window_for_gtk_widget(topWidget);
+ if (!topWindow) return NS_ERROR_FAILURE;
+
+ return topWindow->UpdateTranslucentWindowAlphaInternal(aRect, aAlphas,
+ aStride);
+ }
+
+ NS_ASSERTION(mIsTransparent, "Window is not transparent");
+ NS_ASSERTION(!mTransparencyBitmapForTitlebar,
+ "Transparency bitmap is already used for titlebar rendering");
+
+ if (mTransparencyBitmap == nullptr) {
+ int32_t size = GetBitmapStride(mBounds.width) * mBounds.height;
+ mTransparencyBitmap = new gchar[size];
+ memset(mTransparencyBitmap, 255, size);
+ mTransparencyBitmapWidth = mBounds.width;
+ mTransparencyBitmapHeight = mBounds.height;
+ } else {
+ ResizeTransparencyBitmap();
+ }
+
+ nsIntRect rect;
+ rect.IntersectRect(aRect, nsIntRect(0, 0, mBounds.width, mBounds.height));
+
+ if (!ChangedMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height, rect,
+ aAlphas, aStride))
+ // skip the expensive stuff if the mask bits haven't changed; hopefully
+ // this is the common case
+ return NS_OK;
+
+ UpdateMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height, rect,
+ aAlphas, aStride);
+
+ if (!mNeedsShow) {
+ ApplyTransparencyBitmap();
+ }
+ return NS_OK;
+}
+
+bool nsWindow::GetTitlebarRect(mozilla::gfx::Rect& aRect) {
+ if (!mGdkWindow || !mDrawInTitlebar) {
+ return false;
+ }
+
+ aRect = gfx::Rect(0, 0, mBounds.width, TITLEBAR_SHAPE_MASK_HEIGHT);
+ return true;
+}
+
+void nsWindow::UpdateTitlebarTransparencyBitmap() {
+ NS_ASSERTION(mTransparencyBitmapForTitlebar,
+ "Transparency bitmap is already used to draw window shape");
+
+ if (!mGdkWindow || !mDrawInTitlebar ||
+ (mBounds.width == mTransparencyBitmapWidth &&
+ mBounds.height == mTransparencyBitmapHeight)) {
+ return;
+ }
+
+ bool maskCreate =
+ !mTransparencyBitmap || mBounds.width > mTransparencyBitmapWidth;
+
+ bool maskUpdate =
+ !mTransparencyBitmap || mBounds.width != mTransparencyBitmapWidth;
+
+ if (maskCreate) {
+ if (mTransparencyBitmap) {
+ delete[] mTransparencyBitmap;
+ }
+ int32_t size = GetBitmapStride(mBounds.width) * TITLEBAR_SHAPE_MASK_HEIGHT;
+ mTransparencyBitmap = new gchar[size];
+ mTransparencyBitmapWidth = mBounds.width;
+ } else {
+ mTransparencyBitmapWidth = mBounds.width;
+ }
+ mTransparencyBitmapHeight = mBounds.height;
+
+ if (maskUpdate) {
+ cairo_surface_t* surface = cairo_image_surface_create(
+ CAIRO_FORMAT_A8, mTransparencyBitmapWidth, TITLEBAR_SHAPE_MASK_HEIGHT);
+ if (!surface) return;
+
+ cairo_t* cr = cairo_create(surface);
+
+ GtkWidgetState state;
+ memset((void*)&state, 0, sizeof(state));
+ GdkRectangle rect = {0, 0, mTransparencyBitmapWidth,
+ TITLEBAR_SHAPE_MASK_HEIGHT};
+
+ moz_gtk_widget_paint(MOZ_GTK_HEADER_BAR, cr, &rect, &state, 0,
+ GTK_TEXT_DIR_NONE);
+
+ cairo_destroy(cr);
+ cairo_surface_mark_dirty(surface);
+ cairo_surface_flush(surface);
+
+ UpdateMaskBits(
+ mTransparencyBitmap, mTransparencyBitmapWidth,
+ TITLEBAR_SHAPE_MASK_HEIGHT,
+ nsIntRect(0, 0, mTransparencyBitmapWidth, TITLEBAR_SHAPE_MASK_HEIGHT),
+ cairo_image_surface_get_data(surface),
+ cairo_format_stride_for_width(CAIRO_FORMAT_A8,
+ mTransparencyBitmapWidth));
+
+ cairo_surface_destroy(surface);
+ }
+
+ if (!mNeedsShow) {
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
+
+ Pixmap maskPixmap = XCreateBitmapFromData(
+ xDisplay, xDrawable, mTransparencyBitmap, mTransparencyBitmapWidth,
+ TITLEBAR_SHAPE_MASK_HEIGHT);
+
+ XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
+ ShapeSet);
+
+ if (mTransparencyBitmapHeight > TITLEBAR_SHAPE_MASK_HEIGHT) {
+ XRectangle rect = {0, 0, (unsigned short)mTransparencyBitmapWidth,
+ (unsigned short)(mTransparencyBitmapHeight -
+ TITLEBAR_SHAPE_MASK_HEIGHT)};
+ XShapeCombineRectangles(xDisplay, xDrawable, ShapeBounding, 0,
+ TITLEBAR_SHAPE_MASK_HEIGHT, &rect, 1, ShapeUnion,
+ 0);
+ }
+
+ XFreePixmap(xDisplay, maskPixmap);
+ }
+}
+
+void nsWindow::GrabPointer(guint32 aTime) {
+ LOG(("GrabPointer time=0x%08x retry=%d\n", (unsigned int)aTime,
+ mRetryPointerGrab));
+
+ mRetryPointerGrab = false;
+ sRetryGrabTime = aTime;
+
+ // If the window isn't visible, just set the flag to retry the
+ // grab. When this window becomes visible, the grab will be
+ // retried.
+ if (!mHasMappedToplevel) {
+ LOG(("GrabPointer: window not visible\n"));
+ mRetryPointerGrab = true;
+ return;
+ }
+
+ if (!mGdkWindow) return;
+
+ if (!mIsX11Display) {
+ // Don't to the grab on Wayland as it causes a regression
+ // from Bug 1377084.
+ return;
+ }
+
+ gint retval;
+ // Note that we need GDK_TOUCH_MASK below to work around a GDK/X11 bug that
+ // causes touch events that would normally be received by this client on
+ // other windows to be discarded during the grab.
+ retval = gdk_pointer_grab(
+ mGdkWindow, TRUE,
+ (GdkEventMask)(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_TOUCH_MASK),
+ (GdkWindow*)nullptr, nullptr, aTime);
+
+ if (retval == GDK_GRAB_NOT_VIEWABLE) {
+ LOG(("GrabPointer: window not viewable; will retry\n"));
+ mRetryPointerGrab = true;
+ } else if (retval != GDK_GRAB_SUCCESS) {
+ LOG(("GrabPointer: pointer grab failed: %i\n", retval));
+ // A failed grab indicates that another app has grabbed the pointer.
+ // Check for rollup now, because, without the grab, we likely won't
+ // get subsequent button press events. Do this with an event so that
+ // popups don't rollup while potentially adjusting the grab for
+ // this popup.
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("nsWindow::CheckForRollupDuringGrab", this,
+ &nsWindow::CheckForRollupDuringGrab);
+ NS_DispatchToCurrentThread(event.forget());
+ }
+}
+
+void nsWindow::ReleaseGrabs(void) {
+ LOG(("ReleaseGrabs\n"));
+
+ mRetryPointerGrab = false;
+
+ if (!mIsX11Display) {
+ // Don't to the ungrab on Wayland as it causes a regression
+ // from Bug 1377084.
+ return;
+ }
+
+ gdk_pointer_ungrab(GDK_CURRENT_TIME);
+}
+
+GtkWidget* nsWindow::GetToplevelWidget() {
+ if (mShell) {
+ return mShell;
+ }
+
+ GtkWidget* widget = GetMozContainerWidget();
+ if (!widget) return nullptr;
+
+ return gtk_widget_get_toplevel(widget);
+}
+
+GtkWidget* nsWindow::GetMozContainerWidget() {
+ if (!mGdkWindow) return nullptr;
+
+ if (mContainer) return GTK_WIDGET(mContainer);
+
+ GtkWidget* owningWidget = get_gtk_widget_for_gdk_window(mGdkWindow);
+ return owningWidget;
+}
+
+nsWindow* nsWindow::GetContainerWindow() {
+ GtkWidget* owningWidget = GetMozContainerWidget();
+ if (!owningWidget) return nullptr;
+
+ nsWindow* window = get_window_for_gtk_widget(owningWidget);
+ NS_ASSERTION(window, "No nsWindow for container widget");
+ return window;
+}
+
+void nsWindow::SetUrgencyHint(GtkWidget* top_window, bool state) {
+ if (!top_window) return;
+
+ gdk_window_set_urgency_hint(gtk_widget_get_window(top_window), state);
+}
+
+void nsWindow::SetDefaultIcon(void) { SetIcon(u"default"_ns); }
+
+gint nsWindow::ConvertBorderStyles(nsBorderStyle aStyle) {
+ gint w = 0;
+
+ if (aStyle == eBorderStyle_default) return -1;
+
+ // note that we don't handle eBorderStyle_close yet
+ if (aStyle & eBorderStyle_all) w |= GDK_DECOR_ALL;
+ if (aStyle & eBorderStyle_border) w |= GDK_DECOR_BORDER;
+ if (aStyle & eBorderStyle_resizeh) w |= GDK_DECOR_RESIZEH;
+ if (aStyle & eBorderStyle_title) w |= GDK_DECOR_TITLE;
+ if (aStyle & eBorderStyle_menu) w |= GDK_DECOR_MENU;
+ if (aStyle & eBorderStyle_minimize) w |= GDK_DECOR_MINIMIZE;
+ if (aStyle & eBorderStyle_maximize) w |= GDK_DECOR_MAXIMIZE;
+
+ return w;
+}
+
+class FullscreenTransitionWindow final : public nsISupports {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit FullscreenTransitionWindow(GtkWidget* aWidget);
+
+ GtkWidget* mWindow;
+
+ private:
+ ~FullscreenTransitionWindow();
+};
+
+NS_IMPL_ISUPPORTS0(FullscreenTransitionWindow)
+
+FullscreenTransitionWindow::FullscreenTransitionWindow(GtkWidget* aWidget) {
+ mWindow = gtk_window_new(GTK_WINDOW_POPUP);
+ GtkWindow* gtkWin = GTK_WINDOW(mWindow);
+
+ gtk_window_set_type_hint(gtkWin, GDK_WINDOW_TYPE_HINT_SPLASHSCREEN);
+ gtk_window_set_transient_for(gtkWin, GTK_WINDOW(aWidget));
+ gtk_window_set_decorated(gtkWin, false);
+
+ GdkWindow* gdkWin = gtk_widget_get_window(aWidget);
+ GdkScreen* screen = gtk_widget_get_screen(aWidget);
+ gint monitorNum = gdk_screen_get_monitor_at_window(screen, gdkWin);
+ GdkRectangle monitorRect;
+ gdk_screen_get_monitor_geometry(screen, monitorNum, &monitorRect);
+ gtk_window_set_screen(gtkWin, screen);
+ gtk_window_move(gtkWin, monitorRect.x, monitorRect.y);
+ MOZ_ASSERT(monitorRect.width > 0 && monitorRect.height > 0,
+ "Can't resize window smaller than 1x1.");
+ gtk_window_resize(gtkWin, monitorRect.width, monitorRect.height);
+
+ GdkColor bgColor;
+ bgColor.red = bgColor.green = bgColor.blue = 0;
+ gtk_widget_modify_bg(mWindow, GTK_STATE_NORMAL, &bgColor);
+
+ gtk_window_set_opacity(gtkWin, 0.0);
+ gtk_widget_show(mWindow);
+}
+
+FullscreenTransitionWindow::~FullscreenTransitionWindow() {
+ gtk_widget_destroy(mWindow);
+}
+
+class FullscreenTransitionData {
+ public:
+ FullscreenTransitionData(nsIWidget::FullscreenTransitionStage aStage,
+ uint16_t aDuration, nsIRunnable* aCallback,
+ FullscreenTransitionWindow* aWindow)
+ : mStage(aStage),
+ mStartTime(TimeStamp::Now()),
+ mDuration(TimeDuration::FromMilliseconds(aDuration)),
+ mCallback(aCallback),
+ mWindow(aWindow) {}
+
+ static const guint sInterval = 1000 / 30; // 30fps
+ static gboolean TimeoutCallback(gpointer aData);
+
+ private:
+ nsIWidget::FullscreenTransitionStage mStage;
+ TimeStamp mStartTime;
+ TimeDuration mDuration;
+ nsCOMPtr<nsIRunnable> mCallback;
+ RefPtr<FullscreenTransitionWindow> mWindow;
+};
+
+/* static */
+gboolean FullscreenTransitionData::TimeoutCallback(gpointer aData) {
+ bool finishing = false;
+ auto data = static_cast<FullscreenTransitionData*>(aData);
+ gdouble opacity = (TimeStamp::Now() - data->mStartTime) / data->mDuration;
+ if (opacity >= 1.0) {
+ opacity = 1.0;
+ finishing = true;
+ }
+ if (data->mStage == nsIWidget::eAfterFullscreenToggle) {
+ opacity = 1.0 - opacity;
+ }
+ gtk_window_set_opacity(GTK_WINDOW(data->mWindow->mWindow), opacity);
+
+ if (!finishing) {
+ return TRUE;
+ }
+ NS_DispatchToMainThread(data->mCallback.forget());
+ delete data;
+ return FALSE;
+}
+
+/* virtual */
+bool nsWindow::PrepareForFullscreenTransition(nsISupports** aData) {
+ if (!mCompositedScreen) {
+ return false;
+ }
+ *aData = do_AddRef(new FullscreenTransitionWindow(mShell)).take();
+ return true;
+}
+
+/* virtual */
+void nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) {
+ auto data = static_cast<FullscreenTransitionWindow*>(aData);
+ // This will be released at the end of the last timeout callback for it.
+ auto transitionData =
+ new FullscreenTransitionData(aStage, aDuration, aCallback, data);
+ g_timeout_add_full(G_PRIORITY_HIGH, FullscreenTransitionData::sInterval,
+ FullscreenTransitionData::TimeoutCallback, transitionData,
+ nullptr);
+}
+
+already_AddRefed<nsIScreen> nsWindow::GetWidgetScreen() {
+ nsCOMPtr<nsIScreenManager> screenManager;
+ screenManager = do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (!screenManager) {
+ return nullptr;
+ }
+
+ // GetScreenBounds() is slow for the GTK port so we override and use
+ // mBounds directly.
+ LayoutDeviceIntRect bounds = mBounds;
+ if (!mIsTopLevel) {
+ bounds.MoveTo(WidgetToScreenOffset());
+ }
+
+ DesktopIntRect deskBounds = RoundedToInt(bounds / GetDesktopToDeviceScale());
+ nsCOMPtr<nsIScreen> screen;
+ screenManager->ScreenForRect(deskBounds.x, deskBounds.y, deskBounds.width,
+ deskBounds.height, getter_AddRefs(screen));
+ return screen.forget();
+}
+
+RefPtr<VsyncSource> nsWindow::GetVsyncSource() {
+#ifdef MOZ_WAYLAND
+ if (mWaylandVsyncSource) {
+ return mWaylandVsyncSource;
+ }
+#endif
+
+ return nullptr;
+}
+
+static bool IsFullscreenSupported(GtkWidget* aShell) {
+#ifdef MOZ_X11
+ GdkScreen* screen = gtk_widget_get_screen(aShell);
+ GdkAtom atom = gdk_atom_intern("_NET_WM_STATE_FULLSCREEN", FALSE);
+ if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
+ return false;
+ }
+#endif
+ return true;
+}
+
+nsresult nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen) {
+ LOG(("nsWindow::MakeFullScreen [%p] aFullScreen %d\n", (void*)this,
+ aFullScreen));
+
+ if (mIsX11Display && !IsFullscreenSupported(mShell)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ bool wasFullscreen = mSizeState == nsSizeMode_Fullscreen;
+ if (aFullScreen != wasFullscreen && mWidgetListener) {
+ mWidgetListener->FullscreenWillChange(aFullScreen);
+ }
+
+ if (aFullScreen) {
+ if (mSizeMode != nsSizeMode_Fullscreen) mLastSizeMode = mSizeMode;
+
+ mSizeMode = nsSizeMode_Fullscreen;
+
+ if (mIsPIPWindow) {
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_NORMAL);
+ if (gUseAspectRatio) {
+ mAspectRatioSaved = mAspectRatio;
+ mAspectRatio = 0.0f;
+ ApplySizeConstraints();
+ }
+ }
+
+ gtk_window_fullscreen(GTK_WINDOW(mShell));
+ } else {
+ mSizeMode = mLastSizeMode;
+ gtk_window_unfullscreen(GTK_WINDOW(mShell));
+
+ if (mIsPIPWindow) {
+ gtk_window_set_type_hint(GTK_WINDOW(mShell),
+ GDK_WINDOW_TYPE_HINT_UTILITY);
+ if (gUseAspectRatio) {
+ mAspectRatio = mAspectRatioSaved;
+ // ApplySizeConstraints();
+ }
+ }
+ }
+
+ NS_ASSERTION(mLastSizeMode != nsSizeMode_Fullscreen,
+ "mLastSizeMode should never be fullscreen");
+ return NS_OK;
+}
+
+void nsWindow::SetWindowDecoration(nsBorderStyle aStyle) {
+ LOG(("nsWindow::SetWindowDecoration() [%p] Border style %x\n", (void*)this,
+ aStyle));
+
+ if (!mShell) {
+ // Pass the request to the toplevel window
+ GtkWidget* topWidget = GetToplevelWidget();
+ if (!topWidget) return;
+
+ nsWindow* topWindow = get_window_for_gtk_widget(topWidget);
+ if (!topWindow) return;
+
+ topWindow->SetWindowDecoration(aStyle);
+ return;
+ }
+
+ // We can't use mGdkWindow directly here as it can be
+ // derived from mContainer which is not a top-level GdkWindow.
+ GdkWindow* window = gtk_widget_get_window(mShell);
+
+ // Sawfish, metacity, and presumably other window managers get
+ // confused if we change the window decorations while the window
+ // is visible.
+ bool wasVisible = false;
+ if (gdk_window_is_visible(window)) {
+ gdk_window_hide(window);
+ wasVisible = true;
+ }
+
+ gint wmd = ConvertBorderStyles(aStyle);
+ if (wmd != -1) gdk_window_set_decorations(window, (GdkWMDecoration)wmd);
+
+ if (wasVisible) gdk_window_show(window);
+
+ // For some window managers, adding or removing window decorations
+ // requires unmapping and remapping our toplevel window. Go ahead
+ // and flush the queue here so that we don't end up with a BadWindow
+ // error later when this happens (when the persistence timer fires
+ // and GetWindowPos is called)
+#ifdef MOZ_X11
+ if (mIsX11Display) {
+ XSync(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), X11False);
+ } else
+#endif /* MOZ_X11 */
+ {
+ gdk_flush();
+ }
+}
+
+void nsWindow::HideWindowChrome(bool aShouldHide) {
+ SetWindowDecoration(aShouldHide ? eBorderStyle_none : mBorderStyle);
+}
+
+bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
+ bool aAlwaysRollup) {
+ nsIRollupListener* rollupListener = GetActiveRollupListener();
+ nsCOMPtr<nsIWidget> rollupWidget;
+ if (rollupListener) {
+ rollupWidget = rollupListener->GetRollupWidget();
+ }
+ if (!rollupWidget) {
+ nsBaseWidget::gRollupListener = nullptr;
+ return false;
+ }
+
+ bool retVal = false;
+ auto* currentPopup =
+ (GdkWindow*)rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
+ if (aAlwaysRollup || !is_mouse_in_window(currentPopup, aMouseX, aMouseY)) {
+ bool rollup = true;
+ if (aIsWheel) {
+ rollup = rollupListener->ShouldRollupOnMouseWheelEvent();
+ retVal = 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;
+ if (!aAlwaysRollup) {
+ AutoTArray<nsIWidget*, 5> widgetChain;
+ uint32_t sameTypeCount =
+ rollupListener->GetSubmenuWidgetChain(&widgetChain);
+ for (unsigned long i = 0; i < widgetChain.Length(); ++i) {
+ nsIWidget* widget = widgetChain[i];
+ auto* currWindow = (GdkWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
+ if (is_mouse_in_window(currWindow, aMouseX, aMouseY)) {
+ // 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) {
+ rollup = false;
+ } else {
+ popupsToRollup = sameTypeCount;
+ }
+ break;
+ }
+ } // foreach parent menu widget
+ } // if rollup listener knows about menus
+
+ // if we've determined that we should still rollup, do it.
+ bool usePoint = !aIsWheel && !aAlwaysRollup;
+ IntPoint point;
+ if (usePoint) {
+ LayoutDeviceIntPoint p = GdkEventCoordsToDevicePixels(aMouseX, aMouseY);
+ point = p.ToUnknownPoint();
+ }
+ if (rollup &&
+ rollupListener->Rollup(popupsToRollup, true,
+ usePoint ? &point : nullptr, nullptr)) {
+ retVal = true;
+ }
+ }
+ return retVal;
+}
+
+/* static */
+bool nsWindow::DragInProgress(void) {
+ nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
+
+ if (!dragService) return false;
+
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ dragService->GetCurrentSession(getter_AddRefs(currentDragSession));
+
+ return currentDragSession != nullptr;
+}
+
+static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
+ gdouble aMouseY) {
+ gint x = 0;
+ gint y = 0;
+ gint w, h;
+
+ gint offsetX = 0;
+ gint offsetY = 0;
+
+ GdkWindow* window = aWindow;
+
+ while (window) {
+ gint tmpX = 0;
+ gint tmpY = 0;
+
+ gdk_window_get_position(window, &tmpX, &tmpY);
+ GtkWidget* widget = get_gtk_widget_for_gdk_window(window);
+
+ // if this is a window, compute x and y given its origin and our
+ // offset
+ if (GTK_IS_WINDOW(widget)) {
+ x = tmpX + offsetX;
+ y = tmpY + offsetY;
+ break;
+ }
+
+ offsetX += tmpX;
+ offsetY += tmpY;
+ window = gdk_window_get_parent(window);
+ }
+
+ w = gdk_window_get_width(aWindow);
+ h = gdk_window_get_height(aWindow);
+
+ if (aMouseX > x && aMouseX < x + w && aMouseY > y && aMouseY < y + h)
+ return true;
+
+ return false;
+}
+
+static nsWindow* get_window_for_gtk_widget(GtkWidget* widget) {
+ gpointer user_data = g_object_get_data(G_OBJECT(widget), "nsWindow");
+
+ return static_cast<nsWindow*>(user_data);
+}
+
+static nsWindow* get_window_for_gdk_window(GdkWindow* window) {
+ gpointer user_data = g_object_get_data(G_OBJECT(window), "nsWindow");
+
+ return static_cast<nsWindow*>(user_data);
+}
+
+static GtkWidget* get_gtk_widget_for_gdk_window(GdkWindow* window) {
+ gpointer user_data = nullptr;
+ gdk_window_get_user_data(window, &user_data);
+
+ return GTK_WIDGET(user_data);
+}
+
+static GdkCursor* get_gtk_cursor(nsCursor aCursor) {
+ GdkCursor* gdkcursor = nullptr;
+ uint8_t newType = 0xff;
+
+ if ((gdkcursor = gCursorCache[aCursor])) {
+ return gdkcursor;
+ }
+
+ GdkDisplay* defaultDisplay = gdk_display_get_default();
+
+ // The strategy here is to use standard GDK cursors, and, if not available,
+ // load by standard name with gdk_cursor_new_from_name.
+ // Spec is here: http://www.freedesktop.org/wiki/Specifications/cursor-spec/
+ switch (aCursor) {
+ case eCursor_standard:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_PTR);
+ break;
+ case eCursor_wait:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_WATCH);
+ break;
+ case eCursor_select:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_XTERM);
+ break;
+ case eCursor_hyperlink:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_HAND2);
+ break;
+ case eCursor_n_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_SIDE);
+ break;
+ case eCursor_s_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_SIDE);
+ break;
+ case eCursor_w_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_SIDE);
+ break;
+ case eCursor_e_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_RIGHT_SIDE);
+ break;
+ case eCursor_nw_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_LEFT_CORNER);
+ break;
+ case eCursor_se_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_RIGHT_CORNER);
+ break;
+ case eCursor_ne_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_RIGHT_CORNER);
+ break;
+ case eCursor_sw_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_LEFT_CORNER);
+ break;
+ case eCursor_crosshair:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_CROSSHAIR);
+ break;
+ case eCursor_move:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_FLEUR);
+ break;
+ case eCursor_help:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_QUESTION_ARROW);
+ break;
+ case eCursor_copy: // CSS3
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "copy");
+ if (!gdkcursor) newType = MOZ_CURSOR_COPY;
+ break;
+ case eCursor_alias:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "alias");
+ if (!gdkcursor) newType = MOZ_CURSOR_ALIAS;
+ break;
+ case eCursor_context_menu:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "context-menu");
+ if (!gdkcursor) newType = MOZ_CURSOR_CONTEXT_MENU;
+ break;
+ case eCursor_cell:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_PLUS);
+ break;
+ // Those two aren’t standardized. Trying both KDE’s and GNOME’s names
+ case eCursor_grab:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "openhand");
+ if (!gdkcursor) newType = MOZ_CURSOR_HAND_GRAB;
+ break;
+ case eCursor_grabbing:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "closedhand");
+ if (!gdkcursor)
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "grabbing");
+ if (!gdkcursor) newType = MOZ_CURSOR_HAND_GRABBING;
+ break;
+ case eCursor_spinning:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "progress");
+ if (!gdkcursor) newType = MOZ_CURSOR_SPINNING;
+ break;
+ case eCursor_zoom_in:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "zoom-in");
+ if (!gdkcursor) newType = MOZ_CURSOR_ZOOM_IN;
+ break;
+ case eCursor_zoom_out:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "zoom-out");
+ if (!gdkcursor) newType = MOZ_CURSOR_ZOOM_OUT;
+ break;
+ case eCursor_not_allowed:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "not-allowed");
+ if (!gdkcursor) // nonstandard, yet common
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "crossed_circle");
+ if (!gdkcursor) newType = MOZ_CURSOR_NOT_ALLOWED;
+ break;
+ case eCursor_no_drop:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "no-drop");
+ if (!gdkcursor) // this nonstandard sequence makes it work on KDE and
+ // GNOME
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "forbidden");
+ if (!gdkcursor)
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "circle");
+ if (!gdkcursor) newType = MOZ_CURSOR_NOT_ALLOWED;
+ break;
+ case eCursor_vertical_text:
+ newType = MOZ_CURSOR_VERTICAL_TEXT;
+ break;
+ case eCursor_all_scroll:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_FLEUR);
+ break;
+ case eCursor_nesw_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "size_bdiag");
+ if (!gdkcursor) newType = MOZ_CURSOR_NESW_RESIZE;
+ break;
+ case eCursor_nwse_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "size_fdiag");
+ if (!gdkcursor) newType = MOZ_CURSOR_NWSE_RESIZE;
+ break;
+ case eCursor_ns_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_SB_V_DOUBLE_ARROW);
+ break;
+ case eCursor_ew_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_SB_H_DOUBLE_ARROW);
+ break;
+ // Here, two better fitting cursors exist in some cursor themes. Try those
+ // first
+ case eCursor_row_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "split_v");
+ if (!gdkcursor)
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_SB_V_DOUBLE_ARROW);
+ break;
+ case eCursor_col_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "split_h");
+ if (!gdkcursor)
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_SB_H_DOUBLE_ARROW);
+ break;
+ case eCursor_none:
+ newType = MOZ_CURSOR_NONE;
+ break;
+ default:
+ NS_ASSERTION(aCursor, "Invalid cursor type");
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_PTR);
+ break;
+ }
+
+ // If by now we don't have a xcursor, this means we have to make a custom
+ // one. First, we try creating a named cursor based on the hash of our
+ // custom bitmap, as libXcursor has some magic to convert bitmapped cursors
+ // to themed cursors
+ if (newType != 0xFF && GtkCursors[newType].hash) {
+ gdkcursor =
+ gdk_cursor_new_from_name(defaultDisplay, GtkCursors[newType].hash);
+ }
+
+ // If we still don't have a xcursor, we now really create a bitmap cursor
+ if (newType != 0xff && !gdkcursor) {
+ GdkPixbuf* cursor_pixbuf =
+ gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
+ if (!cursor_pixbuf) return nullptr;
+
+ guchar* data = gdk_pixbuf_get_pixels(cursor_pixbuf);
+
+ // Read data from GtkCursors and compose RGBA surface from 1bit bitmap and
+ // mask GtkCursors bits and mask are 32x32 monochrome bitmaps (1 bit for
+ // each pixel) so it's 128 byte array (4 bytes for are one bitmap row and
+ // there are 32 rows here).
+ const unsigned char* bits = GtkCursors[newType].bits;
+ const unsigned char* mask_bits = GtkCursors[newType].mask_bits;
+
+ for (int i = 0; i < 128; i++) {
+ char bit = *bits++;
+ char mask = *mask_bits++;
+ for (int j = 0; j < 8; j++) {
+ unsigned char pix = ~(((bit >> j) & 0x01) * 0xff);
+ *data++ = pix;
+ *data++ = pix;
+ *data++ = pix;
+ *data++ = (((mask >> j) & 0x01) * 0xff);
+ }
+ }
+
+ gdkcursor = gdk_cursor_new_from_pixbuf(
+ gdk_display_get_default(), cursor_pixbuf, GtkCursors[newType].hot_x,
+ GtkCursors[newType].hot_y);
+
+ g_object_unref(cursor_pixbuf);
+ }
+
+ gCursorCache[aCursor] = gdkcursor;
+
+ return gdkcursor;
+}
+
+// gtk callbacks
+
+void draw_window_of_widget(GtkWidget* widget, GdkWindow* aWindow, cairo_t* cr) {
+ if (gtk_cairo_should_draw_window(cr, aWindow)) {
+ RefPtr<nsWindow> window = get_window_for_gdk_window(aWindow);
+ if (!window) {
+ NS_WARNING("Cannot get nsWindow from GtkWidget");
+ } else {
+ cairo_save(cr);
+ gtk_cairo_transform_to_window(cr, widget, aWindow);
+ // TODO - window->OnExposeEvent() can destroy this or other windows,
+ // do we need to handle it somehow?
+ window->OnExposeEvent(cr);
+ cairo_restore(cr);
+ }
+ }
+
+ GList* children = gdk_window_get_children(aWindow);
+ GList* child = children;
+ while (child) {
+ GdkWindow* window = GDK_WINDOW(child->data);
+ gpointer windowWidget;
+ gdk_window_get_user_data(window, &windowWidget);
+ if (windowWidget == widget) {
+ draw_window_of_widget(widget, window, cr);
+ }
+ child = g_list_next(child);
+ }
+ g_list_free(children);
+}
+
+/* static */
+gboolean expose_event_cb(GtkWidget* widget, cairo_t* cr) {
+ draw_window_of_widget(widget, gtk_widget_get_window(widget), cr);
+
+ // A strong reference is already held during "draw" signal emission,
+ // but GTK+ 3.4 wants the object to live a little longer than that
+ // (bug 1225970).
+ g_object_ref(widget);
+ g_idle_add(
+ [](gpointer data) -> gboolean {
+ g_object_unref(data);
+ return G_SOURCE_REMOVE;
+ },
+ widget);
+
+ return FALSE;
+}
+
+static gboolean configure_event_cb(GtkWidget* widget,
+ GdkEventConfigure* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return FALSE;
+ }
+
+ return window->OnConfigureEvent(widget, event);
+}
+
+static void container_unrealize_cb(GtkWidget* widget) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+
+ window->OnContainerUnrealize();
+}
+
+static void size_allocate_cb(GtkWidget* widget, GtkAllocation* allocation) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+
+ window->OnSizeAllocate(allocation);
+}
+
+static void toplevel_window_size_allocate_cb(GtkWidget* widget,
+ GtkAllocation* allocation) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+
+ window->UpdateTopLevelOpaqueRegion();
+}
+
+static gboolean delete_event_cb(GtkWidget* widget, GdkEventAny* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return FALSE;
+ }
+
+ window->OnDeleteEvent();
+
+ return TRUE;
+}
+
+static gboolean enter_notify_event_cb(GtkWidget* widget,
+ GdkEventCrossing* event) {
+ RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
+ if (!window) {
+ return TRUE;
+ }
+
+ window->OnEnterNotifyEvent(event);
+
+ return TRUE;
+}
+
+static gboolean leave_notify_event_cb(GtkWidget* widget,
+ GdkEventCrossing* event) {
+ if (is_parent_grab_leave(event)) {
+ return TRUE;
+ }
+
+ // bug 369599: Suppress LeaveNotify events caused by pointer grabs to
+ // avoid generating spurious mouse exit events.
+ auto x = gint(event->x_root);
+ auto y = gint(event->y_root);
+ GdkDisplay* display = gtk_widget_get_display(widget);
+ GdkWindow* winAtPt = gdk_display_get_window_at_pointer(display, &x, &y);
+ if (winAtPt == event->window) {
+ return TRUE;
+ }
+
+ RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
+ if (!window) return TRUE;
+
+ window->OnLeaveNotifyEvent(event);
+
+ return TRUE;
+}
+
+static nsWindow* GetFirstNSWindowForGDKWindow(GdkWindow* aGdkWindow) {
+ nsWindow* window;
+ while (!(window = get_window_for_gdk_window(aGdkWindow))) {
+ // The event has bubbled to the moz_container widget as passed into each
+ // caller's *widget parameter, but its corresponding nsWindow is an ancestor
+ // of the window that we need. Instead, look at event->window and find the
+ // first ancestor nsWindow of it because event->window may be in a plugin.
+ aGdkWindow = gdk_window_get_parent(aGdkWindow);
+ if (!aGdkWindow) {
+ window = nullptr;
+ break;
+ }
+ }
+ return window;
+}
+
+static gboolean motion_notify_event_cb(GtkWidget* widget,
+ GdkEventMotion* event) {
+ UpdateLastInputEventTime(event);
+
+ nsWindow* window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window) return FALSE;
+
+ window->OnMotionNotifyEvent(event);
+
+ return TRUE;
+}
+
+static gboolean button_press_event_cb(GtkWidget* widget,
+ GdkEventButton* event) {
+ UpdateLastInputEventTime(event);
+
+ nsWindow* window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window) return FALSE;
+
+ window->OnButtonPressEvent(event);
+
+ return TRUE;
+}
+
+static gboolean button_release_event_cb(GtkWidget* widget,
+ GdkEventButton* event) {
+ UpdateLastInputEventTime(event);
+
+ nsWindow* window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window) return FALSE;
+
+ window->OnButtonReleaseEvent(event);
+
+ return TRUE;
+}
+
+static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) return FALSE;
+
+ window->OnContainerFocusInEvent(event);
+
+ return FALSE;
+}
+
+static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) return FALSE;
+
+ window->OnContainerFocusOutEvent(event);
+
+ return FALSE;
+}
+
+#ifdef MOZ_X11
+// For long-lived popup windows that don't really take focus themselves but
+// may have elements that accept keyboard input when the parent window is
+// active, focus is handled specially. These windows include noautohide
+// panels. (This special handling is not necessary for temporary popups where
+// the keyboard is grabbed.)
+//
+// Mousing over or clicking on these windows should not cause them to steal
+// focus from their parent windows, so, the input field of WM_HINTS is set to
+// False to request that the window manager not set the input focus to this
+// window. http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7
+//
+// However, these windows can still receive WM_TAKE_FOCUS messages from the
+// window manager, so they can still detect when the user has indicated that
+// they wish to direct keyboard input at these windows. When the window
+// manager offers focus to these windows (after a mouse over or click, for
+// example), a request to make the parent window active is issued. When the
+// parent window becomes active, keyboard events will be received.
+
+static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
+ GdkEvent* event, gpointer data) {
+ auto* xevent = static_cast<XEvent*>(gdk_xevent);
+ if (xevent->type != ClientMessage) return GDK_FILTER_CONTINUE;
+
+ XClientMessageEvent& xclient = xevent->xclient;
+ if (xclient.message_type != gdk_x11_get_xatom_by_name("WM_PROTOCOLS"))
+ return GDK_FILTER_CONTINUE;
+
+ Atom atom = xclient.data.l[0];
+ if (atom != gdk_x11_get_xatom_by_name("WM_TAKE_FOCUS"))
+ return GDK_FILTER_CONTINUE;
+
+ guint32 timestamp = xclient.data.l[1];
+
+ GtkWidget* widget = get_gtk_widget_for_gdk_window(event->any.window);
+ if (!widget) return GDK_FILTER_CONTINUE;
+
+ GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(widget));
+ if (!parent) return GDK_FILTER_CONTINUE;
+
+ if (gtk_window_is_active(parent))
+ return GDK_FILTER_REMOVE; // leave input focus on the parent
+
+ GdkWindow* parent_window = gtk_widget_get_window(GTK_WIDGET(parent));
+ if (!parent_window) return GDK_FILTER_CONTINUE;
+
+ // In case the parent has not been deconified.
+ gdk_window_show_unraised(parent_window);
+
+ // Request focus on the parent window.
+ // Use gdk_window_focus rather than gtk_window_present to avoid
+ // raising the parent window.
+ gdk_window_focus(parent_window, timestamp);
+ return GDK_FILTER_REMOVE;
+}
+#endif /* MOZ_X11 */
+
+static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event) {
+ LOG(("key_press_event_cb\n"));
+
+ UpdateLastInputEventTime(event);
+
+ // find the window with focus and dispatch this event to that widget
+ nsWindow* window = get_window_for_gtk_widget(widget);
+ if (!window) return FALSE;
+
+ RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
+
+#ifdef MOZ_X11
+ // Keyboard repeat can cause key press events to queue up when there are
+ // slow event handlers (bug 301029). Throttle these events by removing
+ // consecutive pending duplicate KeyPress events to the same window.
+ // We use the event time of the last one.
+ // Note: GDK calls XkbSetDetectableAutorepeat so that KeyRelease events
+ // are generated only when the key is physically released.
+# define NS_GDKEVENT_MATCH_MASK 0x1FFF // GDK_SHIFT_MASK .. GDK_BUTTON5_MASK
+ // Our headers undefine X11 KeyPress - let's redefine it here.
+# ifndef KeyPress
+# define KeyPress 2
+# endif
+ GdkDisplay* gdkDisplay = gtk_widget_get_display(widget);
+ if (GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ Display* dpy = GDK_DISPLAY_XDISPLAY(gdkDisplay);
+ while (XPending(dpy)) {
+ XEvent next_event;
+ XPeekEvent(dpy, &next_event);
+ GdkWindow* nextGdkWindow =
+ gdk_x11_window_lookup_for_display(gdkDisplay, next_event.xany.window);
+ if (nextGdkWindow != event->window || next_event.type != KeyPress ||
+ next_event.xkey.keycode != event->hardware_keycode ||
+ next_event.xkey.state != (event->state & NS_GDKEVENT_MATCH_MASK)) {
+ break;
+ }
+ XNextEvent(dpy, &next_event);
+ event->time = next_event.xkey.time;
+ }
+ }
+#endif
+
+ return focusWindow->OnKeyPressEvent(event);
+}
+
+static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event) {
+ LOG(("key_release_event_cb\n"));
+
+ UpdateLastInputEventTime(event);
+
+ // find the window with focus and dispatch this event to that widget
+ nsWindow* window = get_window_for_gtk_widget(widget);
+ if (!window) return FALSE;
+
+ RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
+
+ return focusWindow->OnKeyReleaseEvent(event);
+}
+
+static gboolean property_notify_event_cb(GtkWidget* aWidget,
+ GdkEventProperty* aEvent) {
+ RefPtr<nsWindow> window = get_window_for_gdk_window(aEvent->window);
+ if (!window) return FALSE;
+
+ return window->OnPropertyNotifyEvent(aWidget, aEvent);
+}
+
+static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event) {
+ nsWindow* window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window) return FALSE;
+
+ window->OnScrollEvent(event);
+
+ return TRUE;
+}
+
+static void hierarchy_changed_cb(GtkWidget* widget,
+ GtkWidget* previous_toplevel) {
+ GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
+ GdkWindowState old_window_state = GDK_WINDOW_STATE_WITHDRAWN;
+ GdkEventWindowState event;
+
+ event.new_window_state = GDK_WINDOW_STATE_WITHDRAWN;
+
+ if (GTK_IS_WINDOW(previous_toplevel)) {
+ g_signal_handlers_disconnect_by_func(
+ previous_toplevel, FuncToGpointer(window_state_event_cb), widget);
+ GdkWindow* win = gtk_widget_get_window(previous_toplevel);
+ if (win) {
+ old_window_state = gdk_window_get_state(win);
+ }
+ }
+
+ if (GTK_IS_WINDOW(toplevel)) {
+ g_signal_connect_swapped(toplevel, "window-state-event",
+ G_CALLBACK(window_state_event_cb), widget);
+ GdkWindow* win = gtk_widget_get_window(toplevel);
+ if (win) {
+ event.new_window_state = gdk_window_get_state(win);
+ }
+ }
+
+ event.changed_mask =
+ static_cast<GdkWindowState>(old_window_state ^ event.new_window_state);
+
+ if (event.changed_mask) {
+ event.type = GDK_WINDOW_STATE;
+ event.window = nullptr;
+ event.send_event = TRUE;
+ window_state_event_cb(widget, &event);
+ }
+}
+
+static gboolean window_state_event_cb(GtkWidget* widget,
+ GdkEventWindowState* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) return FALSE;
+
+ window->OnWindowStateEvent(widget, event);
+
+ return FALSE;
+}
+
+static void settings_changed_cb(GtkSettings* settings, GParamSpec* pspec,
+ nsWindow* data) {
+ if (sIgnoreChangedSettings) {
+ return;
+ }
+ RefPtr<nsWindow> window = data;
+ window->ThemeChanged();
+}
+
+static void settings_xft_dpi_changed_cb(GtkSettings* gtk_settings,
+ GParamSpec* pspec, nsWindow* data) {
+ RefPtr<nsWindow> window = data;
+ window->OnDPIChanged();
+ // Even though the window size in screen pixels has not changed,
+ // nsViewManager stores the dimensions in app units.
+ // DispatchResized() updates those.
+ window->DispatchResized();
+}
+
+static void check_resize_cb(GtkContainer* container, gpointer user_data) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(GTK_WIDGET(container));
+ if (!window) {
+ return;
+ }
+ window->OnCheckResize();
+}
+
+static void screen_composited_changed_cb(GdkScreen* screen,
+ gpointer user_data) {
+ // This callback can run before gfxPlatform::Init() in rare
+ // cases involving the profile manager. When this happens,
+ // we have no reason to reset any compositors as graphics
+ // hasn't been initialized yet.
+ if (GPUProcessManager::Get()) {
+ GPUProcessManager::Get()->ResetCompositors();
+ }
+}
+
+static void widget_composited_changed_cb(GtkWidget* widget,
+ gpointer user_data) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+ window->OnCompositedChanged();
+}
+
+static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
+ gpointer aPointer) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(widget, &allocation);
+ window->OnScaleChanged(&allocation);
+}
+
+static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent) {
+ UpdateLastInputEventTime(aEvent);
+
+ nsWindow* window = GetFirstNSWindowForGDKWindow(aEvent->window);
+ if (!window) {
+ return FALSE;
+ }
+
+ return window->OnTouchEvent(aEvent);
+}
+
+// This function called generic because there is no signal specific to touchpad
+// pinch events.
+static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent) {
+ if (aEvent->type != GDK_TOUCHPAD_PINCH) {
+ return FALSE;
+ }
+ // Using reinterpret_cast because the touchpad_pinch field of GdkEvent is not
+ // available in GTK+ versions lower than v3.18
+ GdkEventTouchpadPinch* event =
+ reinterpret_cast<GdkEventTouchpadPinch*>(aEvent);
+
+ nsWindow* window = GetFirstNSWindowForGDKWindow(event->window);
+
+ if (!window) {
+ return FALSE;
+ }
+ return window->OnTouchpadPinchEvent(event);
+}
+
+//////////////////////////////////////////////////////////////////////
+// These are all of our drag and drop operations
+
+void nsWindow::InitDragEvent(WidgetDragEvent& aEvent) {
+ // set the keyboard modifiers
+ guint modifierState = KeymapWrapper::GetCurrentModifierState();
+ KeymapWrapper::InitInputEvent(aEvent, modifierState);
+}
+
+gboolean WindowDragMotionHandler(GtkWidget* aWidget,
+ GdkDragContext* aDragContext,
+ nsWaylandDragContext* aWaylandDragContext,
+ gint aX, gint aY, guint aTime) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window) return FALSE;
+
+ // figure out which internal widget this drag motion actually happened on
+ nscoord retx = 0;
+ nscoord rety = 0;
+
+ GdkWindow* innerWindow = get_inner_gdk_window(gtk_widget_get_window(aWidget),
+ aX, aY, &retx, &rety);
+ RefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow);
+
+ if (!innerMostWindow) {
+ innerMostWindow = window;
+ }
+
+ LOGDRAG(("nsWindow drag-motion signal for %p\n", (void*)innerMostWindow));
+
+ LayoutDeviceIntPoint point = window->GdkPointToDevicePixels({retx, rety});
+
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ return dragService->ScheduleMotionEvent(innerMostWindow, aDragContext,
+ aWaylandDragContext, point, aTime);
+}
+
+static gboolean drag_motion_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY, guint aTime, gpointer aData) {
+ return WindowDragMotionHandler(aWidget, aDragContext, nullptr, aX, aY, aTime);
+}
+
+void WindowDragLeaveHandler(GtkWidget* aWidget) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window) return;
+
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+
+ nsWindow* mostRecentDragWindow = dragService->GetMostRecentDestWindow();
+ if (!mostRecentDragWindow) {
+ // This can happen when the target will not accept a drop. A GTK drag
+ // source sends the leave message to the destination before the
+ // drag-failed signal on the source widget, but the leave message goes
+ // via the X server, and so doesn't get processed at least until the
+ // event loop runs again.
+ return;
+ }
+
+ GtkWidget* mozContainer = mostRecentDragWindow->GetMozContainerWidget();
+ if (aWidget != mozContainer) {
+ // When the drag moves between widgets, GTK can send leave signal for
+ // the old widget after the motion or drop signal for the new widget.
+ // We'll send the leave event when the motion or drop event is run.
+ return;
+ }
+
+ LOGDRAG(("nsWindow drag-leave signal for %p\n", (void*)mostRecentDragWindow));
+
+ dragService->ScheduleLeaveEvent();
+}
+
+static void drag_leave_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, guint aTime,
+ gpointer aData) {
+ WindowDragLeaveHandler(aWidget);
+}
+
+gboolean WindowDragDropHandler(GtkWidget* aWidget, GdkDragContext* aDragContext,
+ nsWaylandDragContext* aWaylandDragContext,
+ gint aX, gint aY, guint aTime) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window) return FALSE;
+
+ // figure out which internal widget this drag motion actually happened on
+ nscoord retx = 0;
+ nscoord rety = 0;
+
+ GdkWindow* innerWindow = get_inner_gdk_window(gtk_widget_get_window(aWidget),
+ aX, aY, &retx, &rety);
+ RefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow);
+
+ if (!innerMostWindow) {
+ innerMostWindow = window;
+ }
+
+ LOGDRAG(("nsWindow drag-drop signal for %p\n", (void*)innerMostWindow));
+
+ LayoutDeviceIntPoint point = window->GdkPointToDevicePixels({retx, rety});
+
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ return dragService->ScheduleDropEvent(innerMostWindow, aDragContext,
+ aWaylandDragContext, point, aTime);
+}
+
+static gboolean drag_drop_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY, guint aTime, gpointer aData) {
+ return WindowDragDropHandler(aWidget, aDragContext, nullptr, aX, aY, aTime);
+}
+
+static void drag_data_received_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint aTime,
+ gpointer aData) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window) return;
+
+ window->OnDragDataReceivedEvent(aWidget, aDragContext, aX, aY, aSelectionData,
+ aInfo, aTime, aData);
+}
+
+static nsresult initialize_prefs(void) {
+ gRaiseWindows =
+ Preferences::GetBool("mozilla.widget.raise-on-setfocus", true);
+ gUseWaylandVsync =
+ Preferences::GetBool("widget.wayland_vsync.enabled", false);
+ gUseWaylandUseOpaqueRegion =
+ Preferences::GetBool("widget.wayland.use-opaque-region", false);
+
+ if (Preferences::HasUserValue("widget.use-aspect-ratio")) {
+ gUseAspectRatio = Preferences::GetBool("widget.use-aspect-ratio", true);
+ } else {
+ static const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+ gUseAspectRatio =
+ currentDesktop ? (strstr(currentDesktop, "GNOME") != nullptr) : false;
+ }
+
+ return NS_OK;
+}
+
+static GdkWindow* get_inner_gdk_window(GdkWindow* aWindow, gint x, gint y,
+ gint* retx, gint* rety) {
+ gint cx, cy, cw, ch;
+ GList* children = gdk_window_peek_children(aWindow);
+ for (GList* child = g_list_last(children); child;
+ child = g_list_previous(child)) {
+ auto* childWindow = (GdkWindow*)child->data;
+ if (get_window_for_gdk_window(childWindow)) {
+ gdk_window_get_geometry(childWindow, &cx, &cy, &cw, &ch);
+ if ((cx < x) && (x < (cx + cw)) && (cy < y) && (y < (cy + ch)) &&
+ gdk_window_is_visible(childWindow)) {
+ return get_inner_gdk_window(childWindow, x - cx, y - cy, retx, rety);
+ }
+ }
+ }
+ *retx = x;
+ *rety = y;
+ return aWindow;
+}
+
+static int is_parent_ungrab_enter(GdkEventCrossing* aEvent) {
+ return (GDK_CROSSING_UNGRAB == aEvent->mode) &&
+ ((GDK_NOTIFY_ANCESTOR == aEvent->detail) ||
+ (GDK_NOTIFY_VIRTUAL == aEvent->detail));
+}
+
+static int is_parent_grab_leave(GdkEventCrossing* aEvent) {
+ return (GDK_CROSSING_GRAB == aEvent->mode) &&
+ ((GDK_NOTIFY_ANCESTOR == aEvent->detail) ||
+ (GDK_NOTIFY_VIRTUAL == aEvent->detail));
+}
+
+#ifdef ACCESSIBILITY
+void nsWindow::CreateRootAccessible() {
+ if (mIsTopLevel && !mRootAccessible) {
+ LOG(("nsWindow:: Create Toplevel Accessibility\n"));
+ mRootAccessible = GetRootAccessible();
+ }
+}
+
+void nsWindow::DispatchEventToRootAccessible(uint32_t aEventType) {
+ if (!a11y::ShouldA11yBeEnabled()) {
+ return;
+ }
+
+ nsAccessibilityService* accService = GetOrCreateAccService();
+ if (!accService) {
+ return;
+ }
+
+ // Get the root document accessible and fire event to it.
+ a11y::Accessible* acc = GetRootAccessible();
+ if (acc) {
+ accService->FireAccessibleEvent(aEventType, acc);
+ }
+}
+
+void nsWindow::DispatchActivateEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE);
+}
+
+void nsWindow::DispatchDeactivateEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE);
+}
+
+void nsWindow::DispatchMaximizeEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MAXIMIZE);
+}
+
+void nsWindow::DispatchMinimizeEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MINIMIZE);
+}
+
+void nsWindow::DispatchRestoreEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_RESTORE);
+}
+
+#endif /* #ifdef ACCESSIBILITY */
+
+void nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) {
+ if (!mIMContext) {
+ return;
+ }
+ mIMContext->SetInputContext(this, &aContext, &aAction);
+}
+
+InputContext nsWindow::GetInputContext() {
+ InputContext context;
+ if (!mIMContext) {
+ context.mIMEState.mEnabled = IMEEnabled::Disabled;
+ context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
+ } else {
+ context = mIMContext->GetInputContext();
+ }
+ return context;
+}
+
+TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
+ if (NS_WARN_IF(!mIMContext)) {
+ return nullptr;
+ }
+ return mIMContext;
+}
+
+void nsWindow::GetEditCommandsRemapped(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands,
+ uint32_t aGeckoKeyCode,
+ uint32_t aNativeKeyCode) {
+ // If aEvent.mNativeKeyEvent is nullptr, the event was created by chrome
+ // script. In such case, we shouldn't expose the OS settings to it.
+ // So, just ignore such events here.
+ if (!aEvent.mNativeKeyEvent) {
+ return;
+ }
+ WidgetKeyboardEvent modifiedEvent(aEvent);
+ modifiedEvent.mKeyCode = aGeckoKeyCode;
+ static_cast<GdkEventKey*>(modifiedEvent.mNativeKeyEvent)->keyval =
+ aNativeKeyCode;
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ keyBindings->GetEditCommands(modifiedEvent, aCommands);
+}
+
+bool nsWindow::GetEditCommands(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands) {
+ // Validate the arguments.
+ if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
+ return false;
+ }
+
+ if (aEvent.mKeyCode >= NS_VK_LEFT && aEvent.mKeyCode <= NS_VK_DOWN) {
+ // Check if we're targeting content with vertical writing mode,
+ // and if so remap the arrow keys.
+ // XXX This may be expensive.
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ this);
+ nsEventStatus status;
+ DispatchEvent(&querySelectedTextEvent, status);
+
+ if (querySelectedTextEvent.FoundSelection() &&
+ querySelectedTextEvent.mReply->mWritingMode.IsVertical()) {
+ uint32_t geckoCode = 0;
+ uint32_t gdkCode = 0;
+ switch (aEvent.mKeyCode) {
+ case NS_VK_LEFT:
+ if (querySelectedTextEvent.mReply->mWritingMode.IsVerticalLR()) {
+ geckoCode = NS_VK_UP;
+ gdkCode = GDK_Up;
+ } else {
+ geckoCode = NS_VK_DOWN;
+ gdkCode = GDK_Down;
+ }
+ break;
+
+ case NS_VK_RIGHT:
+ if (querySelectedTextEvent.mReply->mWritingMode.IsVerticalLR()) {
+ geckoCode = NS_VK_DOWN;
+ gdkCode = GDK_Down;
+ } else {
+ geckoCode = NS_VK_UP;
+ gdkCode = GDK_Up;
+ }
+ break;
+
+ case NS_VK_UP:
+ geckoCode = NS_VK_LEFT;
+ gdkCode = GDK_Left;
+ break;
+
+ case NS_VK_DOWN:
+ geckoCode = NS_VK_RIGHT;
+ gdkCode = GDK_Right;
+ break;
+ }
+
+ GetEditCommandsRemapped(aType, aEvent, aCommands, geckoCode, gdkCode);
+ return true;
+ }
+ }
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ keyBindings->GetEditCommands(aEvent, aCommands);
+ return true;
+}
+
+already_AddRefed<DrawTarget> nsWindow::StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode) {
+ return mSurfaceProvider.StartRemoteDrawingInRegion(aInvalidRegion,
+ aBufferMode);
+}
+
+void nsWindow::EndRemoteDrawingInRegion(
+ DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
+ mSurfaceProvider.EndRemoteDrawingInRegion(aDrawTarget, aInvalidRegion);
+}
+
+// Code shared begin BeginMoveDrag and BeginResizeDrag
+bool nsWindow::GetDragInfo(WidgetMouseEvent* aMouseEvent, GdkWindow** aWindow,
+ gint* aButton, gint* aRootX, gint* aRootY) {
+ if (aMouseEvent->mButton != MouseButton::ePrimary) {
+ // we can only begin a move drag with the left mouse button
+ return false;
+ }
+ *aButton = 1;
+
+ // get the gdk window for this widget
+ GdkWindow* gdk_window = mGdkWindow;
+ if (!gdk_window) {
+ return false;
+ }
+#ifdef DEBUG
+ // GDK_IS_WINDOW(...) expands to a statement-expression, and
+ // statement-expressions are not allowed in template-argument lists. So we
+ // have to make the MOZ_ASSERT condition indirect.
+ if (!GDK_IS_WINDOW(gdk_window)) {
+ MOZ_ASSERT(false, "must really be window");
+ }
+#endif
+
+ // find the top-level window
+ gdk_window = gdk_window_get_toplevel(gdk_window);
+ MOZ_ASSERT(gdk_window, "gdk_window_get_toplevel should not return null");
+ *aWindow = gdk_window;
+
+ if (!aMouseEvent->mWidget) {
+ return false;
+ }
+
+ if (mIsX11Display) {
+ // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=789054
+ // To avoid crashes disable double-click on WM without _NET_WM_MOVERESIZE.
+ // See _should_perform_ewmh_drag() at gdkwindow-x11.c
+ GdkScreen* screen = gdk_window_get_screen(gdk_window);
+ GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
+ if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
+ static unsigned int lastTimeStamp = 0;
+ if (lastTimeStamp != aMouseEvent->mTime) {
+ lastTimeStamp = aMouseEvent->mTime;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ // FIXME: It would be nice to have the widget position at the time
+ // of the event, but it's relatively unlikely that the widget has
+ // moved since the mousedown. (On the other hand, it's quite likely
+ // that the mouse has moved, which is why we use the mouse position
+ // from the event.)
+ LayoutDeviceIntPoint offset = aMouseEvent->mWidget->WidgetToScreenOffset();
+ *aRootX = aMouseEvent->mRefPoint.x + offset.x;
+ *aRootY = aMouseEvent->mRefPoint.y + offset.y;
+
+ return true;
+}
+
+nsresult nsWindow::BeginResizeDrag(WidgetGUIEvent* aEvent, int32_t aHorizontal,
+ int32_t aVertical) {
+ NS_ENSURE_ARG_POINTER(aEvent);
+
+ if (aEvent->mClass != eMouseEventClass) {
+ // you can only begin a resize drag with a mouse event
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ GdkWindow* gdk_window;
+ gint button, screenX, screenY;
+ if (!GetDragInfo(aEvent->AsMouseEvent(), &gdk_window, &button, &screenX,
+ &screenY)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // work out what GdkWindowEdge we're talking about
+ GdkWindowEdge window_edge;
+ if (aVertical < 0) {
+ if (aHorizontal < 0) {
+ window_edge = GDK_WINDOW_EDGE_NORTH_WEST;
+ } else if (aHorizontal == 0) {
+ window_edge = GDK_WINDOW_EDGE_NORTH;
+ } else {
+ window_edge = GDK_WINDOW_EDGE_NORTH_EAST;
+ }
+ } else if (aVertical == 0) {
+ if (aHorizontal < 0) {
+ window_edge = GDK_WINDOW_EDGE_WEST;
+ } else if (aHorizontal == 0) {
+ return NS_ERROR_INVALID_ARG;
+ } else {
+ window_edge = GDK_WINDOW_EDGE_EAST;
+ }
+ } else {
+ if (aHorizontal < 0) {
+ window_edge = GDK_WINDOW_EDGE_SOUTH_WEST;
+ } else if (aHorizontal == 0) {
+ window_edge = GDK_WINDOW_EDGE_SOUTH;
+ } else {
+ window_edge = GDK_WINDOW_EDGE_SOUTH_EAST;
+ }
+ }
+
+ // tell the window manager to start the resize
+ gdk_window_begin_resize_drag(gdk_window, window_edge, button, screenX,
+ screenY, aEvent->mTime);
+
+ return NS_OK;
+}
+
+nsIWidget::LayerManager* nsWindow::GetLayerManager(
+ PLayerTransactionChild* aShadowManager, LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence) {
+ if (mIsDestroyed) {
+ // Prevent external code from triggering the re-creation of the
+ // LayerManager/Compositor during shutdown. Just return what we currently
+ // have, which is most likely null.
+ return mLayerManager;
+ }
+
+ return nsBaseWidget::GetLayerManager(aShadowManager, aBackendHint,
+ aPersistence);
+}
+
+void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
+ if (delegate) {
+ mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
+ MOZ_ASSERT(mCompositorWidgetDelegate,
+ "nsWindow::SetCompositorWidgetDelegate called with a "
+ "non-PlatformCompositorWidgetDelegate");
+#ifdef MOZ_WAYLAND
+ MaybeResumeCompositor();
+#endif
+ } else {
+ mCompositorWidgetDelegate = nullptr;
+ }
+}
+
+void nsWindow::ClearCachedResources() {
+ if (mLayerManager && mLayerManager->GetBackendType() ==
+ mozilla::layers::LayersBackend::LAYERS_BASIC) {
+ mLayerManager->ClearCachedResources();
+ }
+
+ GList* children = gdk_window_peek_children(mGdkWindow);
+ for (GList* list = children; list; list = list->next) {
+ nsWindow* window = get_window_for_gdk_window(GDK_WINDOW(list->data));
+ if (window) {
+ window->ClearCachedResources();
+ }
+ }
+}
+
+/* nsWindow::UpdateClientOffsetFromCSDWindow() is designed to be called from
+ * nsWindow::OnConfigureEvent() when mContainer window is already positioned.
+ *
+ * It works only for CSD decorated GtkWindow.
+ */
+void nsWindow::UpdateClientOffsetFromCSDWindow() {
+ int x, y;
+ gdk_window_get_position(mGdkWindow, &x, &y);
+
+ x = GdkCoordToDevicePixels(x);
+ y = GdkCoordToDevicePixels(y);
+
+ if (mClientOffset.x != x || mClientOffset.y != y) {
+ mClientOffset = nsIntPoint(x, y);
+
+ LOG(("nsWindow::UpdateClientOffsetFromCSDWindow [%p] %d, %d\n", (void*)this,
+ mClientOffset.x, mClientOffset.y));
+
+ // Send a WindowMoved notification. This ensures that BrowserParent
+ // picks up the new client offset and sends it to the child process
+ // if appropriate.
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+ }
+}
+
+nsresult nsWindow::SetNonClientMargins(LayoutDeviceIntMargin& aMargins) {
+ SetDrawsInTitlebar(aMargins.top == 0);
+ return NS_OK;
+}
+
+void nsWindow::SetDrawsInTitlebar(bool aState) {
+ LOG(("nsWindow::SetDrawsInTitlebar() [%p] State %d mCSDSupportLevel %d\n",
+ (void*)this, aState, (int)mCSDSupportLevel));
+
+ if (!mShell || mCSDSupportLevel == CSD_SUPPORT_NONE ||
+ aState == mDrawInTitlebar) {
+ return;
+ }
+
+ if (mCSDSupportLevel == CSD_SUPPORT_SYSTEM) {
+ SetWindowDecoration(aState ? eBorderStyle_border : mBorderStyle);
+ } else if (mCSDSupportLevel == CSD_SUPPORT_CLIENT) {
+ LOG((" Using CSD mode\n"));
+
+ /* Window manager does not support GDK_DECOR_BORDER,
+ * emulate it by CSD.
+ *
+ * gtk_window_set_titlebar() works on unrealized widgets only,
+ * we need to handle mShell carefully here.
+ * When CSD is enabled mGdkWindow is owned by mContainer which is good
+ * as we can't delete our mGdkWindow. To make mShell unrealized while
+ * mContainer is preserved we temporary reparent mContainer to an
+ * invisible GtkWindow.
+ */
+ NativeShow(false);
+
+ // Using GTK_WINDOW_POPUP rather than
+ // GTK_WINDOW_TOPLEVEL in the hope that POPUP results in less
+ // initialization and window manager interaction.
+ GtkWidget* tmpWindow = gtk_window_new(GTK_WINDOW_POPUP);
+ gtk_widget_realize(tmpWindow);
+
+ gtk_widget_reparent(GTK_WIDGET(mContainer), tmpWindow);
+ gtk_widget_unrealize(GTK_WIDGET(mShell));
+
+ if (aState) {
+ // Add a hidden titlebar widget to trigger CSD, but disable the default
+ // titlebar. GtkFixed is a somewhat random choice for a simple unused
+ // widget. gtk_window_set_titlebar() takes ownership of the titlebar
+ // widget.
+ gtk_window_set_titlebar(GTK_WINDOW(mShell), gtk_fixed_new());
+ } else {
+ gtk_window_set_titlebar(GTK_WINDOW(mShell), nullptr);
+ }
+
+ /* A workaround for https://bugzilla.gnome.org/show_bug.cgi?id=791081
+ * gtk_widget_realize() throws:
+ * "In pixman_region32_init_rect: Invalid rectangle passed"
+ * when mShell has default 1x1 size.
+ */
+ GtkAllocation allocation = {0, 0, 0, 0};
+ gtk_widget_get_preferred_width(GTK_WIDGET(mShell), nullptr,
+ &allocation.width);
+ gtk_widget_get_preferred_height(GTK_WIDGET(mShell), nullptr,
+ &allocation.height);
+ gtk_widget_size_allocate(GTK_WIDGET(mShell), &allocation);
+
+ gtk_widget_realize(GTK_WIDGET(mShell));
+ gtk_widget_reparent(GTK_WIDGET(mContainer), GTK_WIDGET(mShell));
+ mNeedsShow = true;
+ NativeResize();
+
+ // Label mShell toplevel window so property_notify_event_cb callback
+ // can find its way home.
+ g_object_set_data(G_OBJECT(gtk_widget_get_window(mShell)), "nsWindow",
+ this);
+#ifdef MOZ_X11
+ SetCompositorHint(GTK_WIDGET_COMPOSIDED_ENABLED);
+#endif
+ RefreshWindowClass();
+
+ gtk_widget_destroy(tmpWindow);
+ }
+
+ mDrawInTitlebar = aState;
+
+ if (mTransparencyBitmapForTitlebar) {
+ if (mDrawInTitlebar && mSizeState == nsSizeMode_Normal && !mIsTiled) {
+ UpdateTitlebarTransparencyBitmap();
+ } else {
+ ClearTransparencyBitmap();
+ }
+ }
+}
+
+GtkWindow* nsWindow::GetCurrentTopmostWindow() {
+ GtkWindow* parentWindow = GTK_WINDOW(GetGtkWidget());
+ GtkWindow* topmostParentWindow = nullptr;
+ while (parentWindow) {
+ topmostParentWindow = parentWindow;
+ parentWindow = gtk_window_get_transient_for(parentWindow);
+ }
+ return topmostParentWindow;
+}
+
+gint nsWindow::GdkScaleFactor() {
+ // We depend on notify::scale-factor callback which is reliable for toplevel
+ // windows only, so don't use scale cache for popup windows.
+ if (mWindowType == eWindowType_toplevel && !mWindowScaleFactorChanged) {
+ return mWindowScaleFactor;
+ }
+
+ GdkWindow* scaledGdkWindow = mGdkWindow;
+ if (!mIsX11Display) {
+ // For popup windows/dialogs with parent window we need to get scale factor
+ // of the topmost window. Otherwise the scale factor of the popup is
+ // not updated during it's hidden.
+ if (mWindowType == eWindowType_popup || mWindowType == eWindowType_dialog) {
+ // Get toplevel window for scale factor:
+ GtkWindow* topmostParentWindow = GetCurrentTopmostWindow();
+ if (topmostParentWindow) {
+ scaledGdkWindow =
+ gtk_widget_get_window(GTK_WIDGET(topmostParentWindow));
+ } else {
+ NS_WARNING("Popup/Dialog has no parent.");
+ }
+ // Fallback for windows which parent has been unrealized.
+ if (!scaledGdkWindow) {
+ scaledGdkWindow = mGdkWindow;
+ }
+ }
+ }
+
+ // Available as of GTK 3.10+
+ static auto sGdkWindowGetScaleFactorPtr =
+ (gint(*)(GdkWindow*))dlsym(RTLD_DEFAULT, "gdk_window_get_scale_factor");
+ if (sGdkWindowGetScaleFactorPtr && scaledGdkWindow) {
+ mWindowScaleFactor = (*sGdkWindowGetScaleFactorPtr)(scaledGdkWindow);
+ mWindowScaleFactorChanged = false;
+ } else {
+ mWindowScaleFactor = ScreenHelperGTK::GetGTKMonitorScaleFactor();
+ }
+
+ return mWindowScaleFactor;
+}
+
+gint nsWindow::DevicePixelsToGdkCoordRoundUp(int pixels) {
+ gint scale = GdkScaleFactor();
+ return (pixels + scale - 1) / scale;
+}
+
+gint nsWindow::DevicePixelsToGdkCoordRoundDown(int pixels) {
+ gint scale = GdkScaleFactor();
+ return pixels / scale;
+}
+
+GdkPoint nsWindow::DevicePixelsToGdkPointRoundDown(LayoutDeviceIntPoint point) {
+ gint scale = GdkScaleFactor();
+ return {point.x / scale, point.y / scale};
+}
+
+GdkRectangle nsWindow::DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect rect) {
+ gint scale = GdkScaleFactor();
+ int x = rect.x / scale;
+ int y = rect.y / scale;
+ int right = (rect.x + rect.width + scale - 1) / scale;
+ int bottom = (rect.y + rect.height + scale - 1) / scale;
+ return {x, y, right - x, bottom - y};
+}
+
+GdkRectangle nsWindow::DevicePixelsToGdkSizeRoundUp(
+ LayoutDeviceIntSize pixelSize) {
+ gint scale = GdkScaleFactor();
+ gint width = (pixelSize.width + scale - 1) / scale;
+ gint height = (pixelSize.height + scale - 1) / scale;
+ return {0, 0, width, height};
+}
+
+int nsWindow::GdkCoordToDevicePixels(gint coord) {
+ return coord * GdkScaleFactor();
+}
+
+LayoutDeviceIntPoint nsWindow::GdkEventCoordsToDevicePixels(gdouble x,
+ gdouble y) {
+ gint scale = GdkScaleFactor();
+ return LayoutDeviceIntPoint::Round(x * scale, y * scale);
+}
+
+LayoutDeviceIntPoint nsWindow::GdkPointToDevicePixels(GdkPoint point) {
+ gint scale = GdkScaleFactor();
+ return LayoutDeviceIntPoint(point.x * scale, point.y * scale);
+}
+
+LayoutDeviceIntRect nsWindow::GdkRectToDevicePixels(GdkRectangle rect) {
+ gint scale = GdkScaleFactor();
+ return LayoutDeviceIntRect(rect.x * scale, rect.y * scale, rect.width * scale,
+ rect.height * scale);
+}
+
+nsresult nsWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ GdkDisplay* display = gdk_window_get_display(mGdkWindow);
+
+ // When a button-press/release event is requested, create it here and put it
+ // in the event queue. This will not emit a motion event - this needs to be
+ // done explicitly *before* requesting a button-press/release. You will also
+ // need to wait for the motion event to be dispatched before requesting a
+ // button-press/release event to maintain the desired event order.
+ if (aNativeMessage == GDK_BUTTON_PRESS ||
+ aNativeMessage == GDK_BUTTON_RELEASE) {
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+ event.type = (GdkEventType)aNativeMessage;
+ event.button.button = 1;
+ event.button.window = mGdkWindow;
+ event.button.time = GDK_CURRENT_TIME;
+
+ // Get device for event source
+ GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
+ event.button.device = gdk_device_manager_get_client_pointer(device_manager);
+
+ event.button.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
+ event.button.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ event.button.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
+ event.button.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
+
+ gdk_event_put(&event);
+ } else {
+ // We don't support specific events other than button-press/release. In all
+ // other cases we'll synthesize a motion event that will be emitted by
+ // gdk_display_warp_pointer().
+ GdkScreen* screen = gdk_window_get_screen(mGdkWindow);
+ GdkPoint point = DevicePixelsToGdkPointRoundDown(aPoint);
+ gdk_display_warp_pointer(display, screen, point.x, point.y);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsWindow::SynthesizeNativeMouseScrollEvent(
+ mozilla::LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage,
+ double aDeltaX, double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mousescrollevent");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+ event.type = GDK_SCROLL;
+ event.scroll.window = mGdkWindow;
+ event.scroll.time = GDK_CURRENT_TIME;
+ // Get device for event source
+ GdkDisplay* display = gdk_window_get_display(mGdkWindow);
+ GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
+ event.scroll.device = gdk_device_manager_get_client_pointer(device_manager);
+ event.scroll.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
+ event.scroll.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ event.scroll.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
+ event.scroll.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
+
+ // The delta values are backwards on Linux compared to Windows and Cocoa,
+ // hence the negation.
+ event.scroll.direction = GDK_SCROLL_SMOOTH;
+ event.scroll.delta_x = -aDeltaX;
+ event.scroll.delta_y = -aDeltaY;
+
+ gdk_event_put(&event);
+
+ return NS_OK;
+}
+
+nsresult nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+
+ static std::map<uint32_t, GdkEventSequence*> sKnownPointers;
+
+ auto result = sKnownPointers.find(aPointerId);
+ switch (aPointerState) {
+ case TOUCH_CONTACT:
+ if (result == sKnownPointers.end()) {
+ // GdkEventSequence isn't a thing we can instantiate, and never gets
+ // dereferenced in the gtk code. It's an opaque pointer, the only
+ // requirement is that it be distinct from other instances of
+ // GdkEventSequence*.
+ event.touch.sequence = (GdkEventSequence*)((uintptr_t)aPointerId);
+ sKnownPointers[aPointerId] = event.touch.sequence;
+ event.type = GDK_TOUCH_BEGIN;
+ } else {
+ event.touch.sequence = result->second;
+ event.type = GDK_TOUCH_UPDATE;
+ }
+ break;
+ case TOUCH_REMOVE:
+ event.type = GDK_TOUCH_END;
+ if (result == sKnownPointers.end()) {
+ NS_WARNING("Tried to synthesize touch-end for unknown pointer!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ event.touch.sequence = result->second;
+ sKnownPointers.erase(result);
+ break;
+ case TOUCH_CANCEL:
+ event.type = GDK_TOUCH_CANCEL;
+ if (result == sKnownPointers.end()) {
+ NS_WARNING("Tried to synthesize touch-cancel for unknown pointer!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ event.touch.sequence = result->second;
+ sKnownPointers.erase(result);
+ break;
+ case TOUCH_HOVER:
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ event.touch.window = mGdkWindow;
+ event.touch.time = GDK_CURRENT_TIME;
+
+ GdkDisplay* display = gdk_window_get_display(mGdkWindow);
+ GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
+ event.touch.device = gdk_device_manager_get_client_pointer(device_manager);
+
+ event.touch.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
+ event.touch.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ event.touch.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
+ event.touch.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
+
+ gdk_event_put(&event);
+
+ return NS_OK;
+}
+
+nsWindow::CSDSupportLevel nsWindow::GetSystemCSDSupportLevel(bool aIsPopup) {
+ if (sCSDSupportLevel != CSD_SUPPORT_UNKNOWN) {
+ return sCSDSupportLevel;
+ }
+
+ // Allow MOZ_GTK_TITLEBAR_DECORATION to override our heuristics
+ const char* decorationOverride = getenv("MOZ_GTK_TITLEBAR_DECORATION");
+ if (decorationOverride) {
+ if (strcmp(decorationOverride, "none") == 0) {
+ sCSDSupportLevel = CSD_SUPPORT_NONE;
+ } else if (strcmp(decorationOverride, "client") == 0) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ } else if (strcmp(decorationOverride, "system") == 0) {
+ sCSDSupportLevel = CSD_SUPPORT_SYSTEM;
+ }
+ return sCSDSupportLevel;
+ }
+
+ // nsWindow::GetSystemCSDSupportLevel can be called from various threads
+ // so we can't use gfxPlatformGtk here.
+ if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ return sCSDSupportLevel;
+ }
+
+ const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+ if (currentDesktop) {
+ // GNOME Flashback (fallback)
+ if (strstr(currentDesktop, "GNOME-Flashback:GNOME") != nullptr) {
+ sCSDSupportLevel = aIsPopup ? CSD_SUPPORT_CLIENT : CSD_SUPPORT_SYSTEM;
+ // Pop Linux Bug 1629198
+ } else if (strstr(currentDesktop, "pop:GNOME") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ // gnome-shell
+ } else if (strstr(currentDesktop, "GNOME") != nullptr) {
+ sCSDSupportLevel = aIsPopup ? CSD_SUPPORT_CLIENT : CSD_SUPPORT_SYSTEM;
+ } else if (strstr(currentDesktop, "XFCE") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ } else if (strstr(currentDesktop, "X-Cinnamon") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_SYSTEM;
+ // KDE Plasma
+ } else if (strstr(currentDesktop, "KDE") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ } else if (strstr(currentDesktop, "Enlightenment") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ } else if (strstr(currentDesktop, "LXDE") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ } else if (strstr(currentDesktop, "openbox") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ } else if (strstr(currentDesktop, "i3") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_NONE;
+ } else if (strstr(currentDesktop, "MATE") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ // Ubuntu Unity
+ } else if (strstr(currentDesktop, "Unity") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_SYSTEM;
+ // Elementary OS
+ } else if (strstr(currentDesktop, "Pantheon") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_SYSTEM;
+ } else if (strstr(currentDesktop, "LXQt") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_SYSTEM;
+ } else if (strstr(currentDesktop, "Deepin") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ } else {
+// Release or beta builds are not supposed to be broken
+// so disable titlebar rendering on untested/unknown systems.
+#if defined(RELEASE_OR_BETA)
+ sCSDSupportLevel = CSD_SUPPORT_NONE;
+#else
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+#endif
+ }
+ } else {
+ sCSDSupportLevel = CSD_SUPPORT_NONE;
+ }
+
+ // GTK_CSD forces CSD mode - use also CSD because window manager
+ // decorations does not work with CSD.
+ // We check GTK_CSD as well as gtk_window_should_use_csd() does.
+ if (sCSDSupportLevel == CSD_SUPPORT_SYSTEM) {
+ const char* csdOverride = getenv("GTK_CSD");
+ if (csdOverride && g_strcmp0(csdOverride, "1") == 0) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ }
+ }
+
+ return sCSDSupportLevel;
+}
+
+bool nsWindow::TitlebarUseShapeMask() {
+ static int useShapeMask = []() {
+ // Don't use titlebar shape mask on Wayland
+ if (!gfxPlatformGtk::GetPlatform()->IsX11Display()) {
+ return false;
+ }
+
+ // We can'y use shape masks on Mutter/X.org as we can't resize Firefox
+ // window there (Bug 1530252).
+ const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+ if (currentDesktop) {
+ if (strstr(currentDesktop, "GNOME") != nullptr) {
+ const char* sessionType = getenv("XDG_SESSION_TYPE");
+ if (sessionType && strstr(sessionType, "x11") != nullptr) {
+ return false;
+ }
+ }
+ }
+
+ return Preferences::GetBool("widget.titlebar-x11-use-shape-mask", false);
+ }();
+ return useShapeMask;
+}
+
+bool nsWindow::HideTitlebarByDefault() {
+ static int hideTitlebar = []() {
+ // When user defined widget.default-hidden-titlebar don't do any
+ // heuristics and just follow it.
+ if (Preferences::HasUserValue("widget.default-hidden-titlebar")) {
+ return Preferences::GetBool("widget.default-hidden-titlebar", false);
+ }
+
+ // Don't hide titlebar when it's disabled on current desktop.
+ const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+ if (!currentDesktop || GetSystemCSDSupportLevel() == CSD_SUPPORT_NONE) {
+ return false;
+ }
+
+ // We hide system titlebar on Gnome/ElementaryOS without any restriction.
+ return ((strstr(currentDesktop, "GNOME-Flashback:GNOME") != nullptr ||
+ strstr(currentDesktop, "GNOME") != nullptr ||
+ strstr(currentDesktop, "Pantheon") != nullptr));
+ }();
+ return hideTitlebar;
+}
+
+int32_t nsWindow::RoundsWidgetCoordinatesTo() { return GdkScaleFactor(); }
+
+void nsWindow::GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) {
+ // Make sure the window XID is propagated to X server, we can fail otherwise
+ // in GPU process (Bug 1401634).
+ if (mXDisplay && mXWindow != X11None) {
+ XFlush(mXDisplay);
+ }
+
+ bool isShaped =
+ mIsTransparent && !mHasAlphaVisual && !mTransparencyBitmapForTitlebar;
+ *aInitData = mozilla::widget::GtkCompositorWidgetInitData(
+ (mXWindow != X11None) ? mXWindow : (uintptr_t) nullptr,
+ mXDisplay ? nsCString(XDisplayString(mXDisplay)) : nsCString(), isShaped,
+ mIsX11Display, GetClientSize());
+}
+
+#ifdef MOZ_WAYLAND
+bool nsWindow::WaylandSurfaceNeedsClear() {
+ if (mContainer) {
+ return moz_container_wayland_surface_needs_clear(MOZ_CONTAINER(mContainer));
+ }
+ return false;
+}
+#endif
+
+#ifdef MOZ_X11
+/* XApp progress support currently works by setting a property
+ * on a window with this Atom name. A supporting window manager
+ * will notice this and pass it along to whatever handling has
+ * been implemented on that end (e.g. passing it on to a taskbar
+ * widget.) There is no issue if WM support is lacking, this is
+ * simply ignored in that case.
+ *
+ * See https://github.com/linuxmint/xapps/blob/master/libxapp/xapp-gtk-window.c
+ * for further details.
+ */
+
+# define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS"
+
+static void set_window_hint_cardinal(Window xid, const gchar* atom_name,
+ gulong cardinal) {
+ GdkDisplay* display;
+
+ display = gdk_display_get_default();
+
+ if (cardinal > 0) {
+ XChangeProperty(GDK_DISPLAY_XDISPLAY(display), xid,
+ gdk_x11_get_xatom_by_name_for_display(display, atom_name),
+ XA_CARDINAL, 32, PropModeReplace, (guchar*)&cardinal, 1);
+ } else {
+ XDeleteProperty(GDK_DISPLAY_XDISPLAY(display), xid,
+ gdk_x11_get_xatom_by_name_for_display(display, atom_name));
+ }
+}
+#endif // MOZ_X11
+
+void nsWindow::SetProgress(unsigned long progressPercent) {
+#ifdef MOZ_X11
+
+ if (!mIsX11Display) {
+ return;
+ }
+
+ if (!mShell) {
+ return;
+ }
+
+ progressPercent = MIN(progressPercent, 100);
+
+ set_window_hint_cardinal(GDK_WINDOW_XID(gtk_widget_get_window(mShell)),
+ PROGRESS_HINT, progressPercent);
+#endif // MOZ_X11
+}
+
+#ifdef MOZ_X11
+void nsWindow::SetCompositorHint(WindowComposeRequest aState) {
+ if (!mIsX11Display) {
+ return;
+ }
+
+ gulong value = aState;
+ GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
+ gdk_property_change(gtk_widget_get_window(mShell),
+ gdk_atom_intern("_NET_WM_BYPASS_COMPOSITOR", FALSE),
+ cardinal_atom,
+ 32, // format
+ GDK_PROP_MODE_REPLACE, (guchar*)&value, 1);
+}
+#endif
+
+nsresult nsWindow::SetSystemFont(const nsCString& aFontName) {
+ GtkSettings* settings = gtk_settings_get_default();
+ g_object_set(settings, "gtk-font-name", aFontName.get(), nullptr);
+ return NS_OK;
+}
+
+nsresult nsWindow::GetSystemFont(nsCString& aFontName) {
+ GtkSettings* settings = gtk_settings_get_default();
+ gchar* fontName = nullptr;
+ g_object_get(settings, "gtk-font-name", &fontName, nullptr);
+ if (fontName) {
+ aFontName.Assign(fontName);
+ g_free(fontName);
+ }
+ return NS_OK;
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow();
+ return window.forget();
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow();
+ return window.forget();
+}
+
+#ifdef MOZ_WAYLAND
+nsresult nsWindow::GetScreenRect(LayoutDeviceIntRect* aRect) {
+ typedef struct _GdkMonitor GdkMonitor;
+ static auto s_gdk_display_get_monitor_at_window =
+ (GdkMonitor * (*)(GdkDisplay*, GdkWindow*))
+ dlsym(RTLD_DEFAULT, "gdk_display_get_monitor_at_window");
+
+ static auto s_gdk_monitor_get_workarea =
+ (void (*)(GdkMonitor*, GdkRectangle*))dlsym(RTLD_DEFAULT,
+ "gdk_monitor_get_workarea");
+
+ if (!s_gdk_display_get_monitor_at_window || !s_gdk_monitor_get_workarea) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ GtkWindow* topmostParentWindow = GetCurrentTopmostWindow();
+ GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(topmostParentWindow));
+
+ GdkMonitor* monitor =
+ s_gdk_display_get_monitor_at_window(gdk_display_get_default(), gdkWindow);
+ if (monitor) {
+ GdkRectangle workArea;
+ s_gdk_monitor_get_workarea(monitor, &workArea);
+ // The monitor offset won't help us in Wayland, because we can't get the
+ // absolute position of our window.
+ aRect->x = aRect->y = 0;
+ aRect->width = workArea.width;
+ aRect->height = workArea.height;
+ LOG((" workarea for [%p], monitor %p: x%d y%d w%d h%d\n", this, monitor,
+ workArea.x, workArea.y, workArea.width, workArea.height));
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+#endif
+
+bool nsWindow::GetTopLevelWindowActiveState(nsIFrame* aFrame) {
+ // Used by window frame and button box rendering. We can end up in here in
+ // the content process when rendering one of these moz styles freely in a
+ // page. Fail in this case, there is no applicable window focus state.
+ if (!XRE_IsParentProcess()) {
+ return false;
+ }
+ // All headless windows are considered active so they are painted.
+ if (gfxPlatform::IsHeadless()) {
+ return true;
+ }
+ // Get the widget. nsIFrame's GetNearestWidget walks up the view chain
+ // until it finds a real window.
+ nsWindow* window = static_cast<nsWindow*>(aFrame->GetNearestWidget());
+ if (!window) {
+ return false;
+ }
+
+ // Get our toplevel nsWindow.
+ if (!window->mIsTopLevel) {
+ GtkWidget* widget = window->GetMozContainerWidget();
+ if (!widget) {
+ return false;
+ }
+
+ GtkWidget* toplevelWidget = gtk_widget_get_toplevel(widget);
+ window = get_window_for_gtk_widget(toplevelWidget);
+ if (!window) {
+ return false;
+ }
+ }
+
+ return !window->mTitlebarBackdropState;
+}
+
+static nsIFrame* FindTitlebarFrame(nsIFrame* aFrame) {
+ for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
+ StyleAppearance appearance =
+ childFrame->StyleDisplay()->EffectiveAppearance();
+ if (appearance == StyleAppearance::MozWindowTitlebar ||
+ appearance == StyleAppearance::MozWindowTitlebarMaximized) {
+ return childFrame;
+ }
+
+ if (nsIFrame* foundFrame = FindTitlebarFrame(childFrame)) {
+ return foundFrame;
+ }
+ }
+ return nullptr;
+}
+
+nsIFrame* nsWindow::GetFrame(void) {
+ nsView* view = nsView::GetViewFor(this);
+ if (!view) {
+ return nullptr;
+ }
+ return view->GetFrame();
+}
+
+void nsWindow::UpdateMozWindowActive() {
+ // Update activation state for the :-moz-window-inactive pseudoclass.
+ // Normally, this follows focus; we override it here to follow
+ // GDK_WINDOW_STATE_FOCUSED.
+ if (mozilla::dom::Document* document = GetDocument()) {
+ if (nsPIDOMWindowOuter* window = document->GetWindow()) {
+ if (RefPtr<mozilla::dom::BrowsingContext> bc =
+ window->GetBrowsingContext()) {
+ bc->SetIsActiveBrowserWindow(!mTitlebarBackdropState);
+ }
+ }
+ }
+}
+
+void nsWindow::ForceTitlebarRedraw(void) {
+ MOZ_ASSERT(mDrawInTitlebar, "We should not redraw invisible titlebar.");
+
+ if (!mWidgetListener || !mWidgetListener->GetPresShell()) {
+ return;
+ }
+
+ nsIFrame* frame = GetFrame();
+ if (!frame) {
+ return;
+ }
+
+ frame = FindTitlebarFrame(frame);
+ if (frame) {
+ nsIContent* content = frame->GetContent();
+ if (content) {
+ nsLayoutUtils::PostRestyleEvent(content->AsElement(), RestyleHint{0},
+ nsChangeHint_RepaintFrame);
+ }
+ }
+}
+
+GtkTextDirection nsWindow::GetTextDirection() {
+ nsIFrame* frame = GetFrame();
+ if (!frame) {
+ return GTK_TEXT_DIR_LTR;
+ }
+
+ WritingMode wm = frame->GetWritingMode();
+ return wm.IsPhysicalLTR() ? GTK_TEXT_DIR_LTR : GTK_TEXT_DIR_RTL;
+}
+
+void nsWindow::LockAspectRatio(bool aShouldLock) {
+ if (!gUseAspectRatio) {
+ return;
+ }
+
+ if (aShouldLock) {
+ int decWidth = 0, decHeight = 0;
+ AddCSDDecorationSize(&decWidth, &decHeight);
+
+ float width =
+ (float)DevicePixelsToGdkCoordRoundDown(mBounds.width) + decWidth;
+ float height =
+ (float)DevicePixelsToGdkCoordRoundDown(mBounds.height) + decHeight;
+
+ mAspectRatio = width / height;
+ LOG(("nsWindow::LockAspectRatio() [%p] width %f height %f aspect %f\n",
+ (void*)this, width, height, mAspectRatio));
+ } else {
+ mAspectRatio = 0.0;
+ LOG(("nsWindow::LockAspectRatio() [%p] removed aspect ratio\n",
+ (void*)this));
+ }
+
+ ApplySizeConstraints();
+}
+
+#ifdef MOZ_WAYLAND
+void nsWindow::SetEGLNativeWindowSize(
+ const LayoutDeviceIntSize& aEGLWindowSize) {
+ if (mContainer && !mIsX11Display) {
+ moz_container_wayland_egl_window_set_size(mContainer, aEGLWindowSize.width,
+ aEGLWindowSize.height);
+ }
+}
+
+nsWindow* nsWindow::GetFocusedWindow() { return gFocusWindow; }
+#endif
+
+LayoutDeviceIntRect nsWindow::GetMozContainerSize() {
+ LayoutDeviceIntRect size(0, 0, 0, 0);
+ if (mContainer) {
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(GTK_WIDGET(mContainer), &allocation);
+ int scale = GdkScaleFactor();
+ size.width = allocation.width * scale;
+ size.height = allocation.height * scale;
+ }
+ return size;
+}
diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h
new file mode 100644
index 0000000000..5bfdcd9291
--- /dev/null
+++ b/widget/gtk/nsWindow.h
@@ -0,0 +1,719 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __nsWindow_h__
+#define __nsWindow_h__
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#ifdef MOZ_X11
+# include <gdk/gdkx.h>
+# include "X11UndefineNone.h"
+#endif /* MOZ_X11 */
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkwayland.h>
+# include "base/thread.h"
+# include "WaylandVsyncSource.h"
+#endif
+#include "MozContainer.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIDragService.h"
+#include "nsGkAtoms.h"
+#include "nsRefPtrHashtable.h"
+#include "nsBaseWidget.h"
+#include "CompositorWidget.h"
+#include "mozilla/widget/WindowSurface.h"
+#include "mozilla/widget/WindowSurfaceProvider.h"
+#include "mozilla/Maybe.h"
+
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/Accessible.h"
+#endif
+#include "mozilla/EventForwards.h"
+#include "mozilla/TouchEvents.h"
+
+#include "IMContextWrapper.h"
+
+#undef LOG
+#ifdef MOZ_LOGGING
+
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+
+extern mozilla::LazyLogModule gWidgetLog;
+extern mozilla::LazyLogModule gWidgetFocusLog;
+extern mozilla::LazyLogModule gWidgetDragLog;
+extern mozilla::LazyLogModule gWidgetDrawLog;
+
+# define LOG(args) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, args)
+# define LOGFOCUS(args) \
+ MOZ_LOG(gWidgetFocusLog, mozilla::LogLevel::Debug, args)
+# define LOGDRAG(args) MOZ_LOG(gWidgetDragLog, mozilla::LogLevel::Debug, args)
+# define LOGDRAW(args) MOZ_LOG(gWidgetDrawLog, mozilla::LogLevel::Debug, args)
+
+#else
+
+# define LOG(args)
+# define LOGFOCUS(args)
+# define LOGDRAG(args)
+# define LOGDRAW(args)
+
+#endif /* MOZ_LOGGING */
+
+#ifdef MOZ_WAYLAND
+class nsWaylandDragContext;
+
+gboolean WindowDragMotionHandler(GtkWidget* aWidget,
+ GdkDragContext* aDragContext,
+ nsWaylandDragContext* aWaylandDragContext,
+ gint aX, gint aY, guint aTime);
+gboolean WindowDragDropHandler(GtkWidget* aWidget, GdkDragContext* aDragContext,
+ nsWaylandDragContext* aWaylandDragContext,
+ gint aX, gint aY, guint aTime);
+void WindowDragLeaveHandler(GtkWidget* aWidget);
+#endif
+
+class gfxPattern;
+class nsIFrame;
+#if !GTK_CHECK_VERSION(3, 18, 0)
+struct _GdkEventTouchpadPinch;
+typedef struct _GdkEventTouchpadPinch GdkEventTouchpadPinch;
+
+#endif
+
+namespace mozilla {
+class TimeStamp;
+class CurrentX11TimeGetter;
+
+} // namespace mozilla
+
+class nsWindow final : public nsBaseWidget {
+ public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::WidgetEventTime WidgetEventTime;
+ typedef mozilla::WidgetKeyboardEvent WidgetKeyboardEvent;
+ typedef mozilla::widget::PlatformCompositorWidgetDelegate
+ PlatformCompositorWidgetDelegate;
+
+ nsWindow();
+
+ static void ReleaseGlobals();
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsWindow, nsBaseWidget)
+
+ void CommonCreate(nsIWidget* aParent, bool aListenForResizes);
+
+ virtual nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+
+ // called when we are destroyed
+ virtual void OnDestroy(void) override;
+
+ // called to check and see if a widget's dimensions are sane
+ bool AreBoundsSane(void);
+
+ // nsIWidget
+ using nsBaseWidget::Create; // for Create signature not overridden here
+ [[nodiscard]] virtual nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) override;
+ virtual void Destroy() override;
+ virtual nsIWidget* GetParent() override;
+ virtual float GetDPI() override;
+ virtual double GetDefaultScaleInternal() override;
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() override;
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScaleByScreen()
+ override;
+ virtual void SetParent(nsIWidget* aNewParent) override;
+ virtual void SetModal(bool aModal) override;
+ virtual bool IsVisible() const override;
+ virtual void ConstrainPosition(bool aAllowSlop, int32_t* aX,
+ int32_t* aY) override;
+ virtual void SetSizeConstraints(const SizeConstraints& aConstraints) override;
+ virtual void LockAspectRatio(bool aShouldLock) override;
+ virtual void Move(double aX, double aY) override;
+ virtual void Show(bool aState) override;
+ virtual void Resize(double aWidth, double aHeight, bool aRepaint) override;
+ virtual void Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) override;
+ virtual bool IsEnabled() const override;
+
+ void SetZIndex(int32_t aZIndex) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ virtual void GetWorkspaceID(nsAString& workspaceID) override;
+ virtual void MoveToWorkspace(const nsAString& workspaceID) override;
+ virtual void Enable(bool aState) override;
+ virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntSize GetClientSize() override;
+ virtual LayoutDeviceIntPoint GetClientOffset() override;
+ virtual void SetCursor(nsCursor aDefaultCursor, imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY) override;
+ virtual void Invalidate(const LayoutDeviceIntRect& aRect) override;
+ virtual void* GetNativeData(uint32_t aDataType) override;
+ virtual nsresult SetTitle(const nsAString& aTitle) override;
+ virtual void SetIcon(const nsAString& aIconSpec) override;
+ virtual void SetWindowClass(const nsAString& xulWinType) override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ virtual void CaptureMouse(bool aCapture) override;
+ virtual void CaptureRollupEvents(nsIRollupListener* aListener,
+ bool aDoCapture) override;
+ [[nodiscard]] virtual nsresult GetAttention(int32_t aCycleCount) override;
+ virtual nsresult SetWindowClipRegion(
+ const nsTArray<LayoutDeviceIntRect>& aRects,
+ bool aIntersectWithExisting) override;
+ virtual bool HasPendingInputEvent() override;
+
+ virtual bool PrepareForFullscreenTransition(nsISupports** aData) override;
+ virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) override;
+ virtual already_AddRefed<nsIScreen> GetWidgetScreen() override;
+ virtual nsresult MakeFullScreen(bool aFullScreen,
+ nsIScreen* aTargetScreen = nullptr) override;
+ virtual void HideWindowChrome(bool aShouldHide) override;
+
+ /**
+ * GetLastUserInputTime returns a timestamp for the most recent user input
+ * event. This is intended for pointer grab requests (including drags).
+ */
+ static guint32 GetLastUserInputTime();
+
+ // utility method, -1 if no change should be made, otherwise returns a
+ // value that can be passed to gdk_window_set_decorations
+ gint ConvertBorderStyles(nsBorderStyle aStyle);
+
+ GdkRectangle DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect aRect);
+
+ mozilla::widget::IMContextWrapper* GetIMContext() const { return mIMContext; }
+
+ bool DispatchCommandEvent(nsAtom* aCommand);
+ bool DispatchContentCommandEvent(mozilla::EventMessage aMsg);
+
+ // event callbacks
+ gboolean OnExposeEvent(cairo_t* cr);
+ gboolean OnConfigureEvent(GtkWidget* aWidget, GdkEventConfigure* aEvent);
+ void OnContainerUnrealize();
+ void OnSizeAllocate(GtkAllocation* aAllocation);
+ void OnDeleteEvent();
+ void OnEnterNotifyEvent(GdkEventCrossing* aEvent);
+ void OnLeaveNotifyEvent(GdkEventCrossing* aEvent);
+ void OnMotionNotifyEvent(GdkEventMotion* aEvent);
+ void OnButtonPressEvent(GdkEventButton* aEvent);
+ void OnButtonReleaseEvent(GdkEventButton* aEvent);
+ void OnContainerFocusInEvent(GdkEventFocus* aEvent);
+ void OnContainerFocusOutEvent(GdkEventFocus* aEvent);
+ gboolean OnKeyPressEvent(GdkEventKey* aEvent);
+ gboolean OnKeyReleaseEvent(GdkEventKey* aEvent);
+
+ void OnScrollEvent(GdkEventScroll* aEvent);
+
+ void OnWindowStateEvent(GtkWidget* aWidget, GdkEventWindowState* aEvent);
+ void OnDragDataReceivedEvent(GtkWidget* aWidget, GdkDragContext* aDragContext,
+ gint aX, gint aY,
+ GtkSelectionData* aSelectionData, guint aInfo,
+ guint aTime, gpointer aData);
+ gboolean OnPropertyNotifyEvent(GtkWidget* aWidget, GdkEventProperty* aEvent);
+ gboolean OnTouchEvent(GdkEventTouch* aEvent);
+ gboolean OnTouchpadPinchEvent(GdkEventTouchpadPinch* aEvent);
+
+ void UpdateTopLevelOpaqueRegion();
+
+ virtual already_AddRefed<mozilla::gfx::DrawTarget> StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion,
+ mozilla::layers::BufferMode* aBufferMode) override;
+ virtual void EndRemoteDrawingInRegion(
+ mozilla::gfx::DrawTarget* aDrawTarget,
+ const LayoutDeviceIntRegion& aInvalidRegion) override;
+
+ void SetProgress(unsigned long progressPercent);
+
+#ifdef MOZ_WAYLAND
+ void SetEGLNativeWindowSize(const LayoutDeviceIntSize& aEGLWindowSize);
+ static nsWindow* GetFocusedWindow();
+#endif
+
+ RefPtr<mozilla::gfx::VsyncSource> GetVsyncSource() override;
+
+ static void WithSettingsChangesIgnored(const std::function<void()>& aFn);
+
+ private:
+ void UpdateAlpha(mozilla::gfx::SourceSurface* aSourceSurface,
+ nsIntRect aBoundsRect);
+
+ void NativeMove();
+ void NativeResize();
+ void NativeMoveResize();
+
+ void NativeShow(bool aAction);
+ void SetHasMappedToplevel(bool aState);
+ LayoutDeviceIntSize GetSafeWindowSize(LayoutDeviceIntSize aSize);
+
+ void EnsureGrabs(void);
+ void GrabPointer(guint32 aTime);
+ void ReleaseGrabs(void);
+
+ void UpdateClientOffsetFromFrameExtents();
+ void UpdateClientOffsetFromCSDWindow();
+
+ void DispatchContextMenuEventFromMouseEvent(uint16_t domButton,
+ GdkEventButton* aEvent);
+#ifdef MOZ_WAYLAND
+ void MaybeResumeCompositor();
+#endif
+
+ void WaylandStartVsync();
+ void WaylandStopVsync();
+
+ public:
+ void ThemeChanged(void);
+ void OnDPIChanged(void);
+ void OnCheckResize(void);
+ void OnCompositedChanged(void);
+ void OnScaleChanged(GtkAllocation* aAllocation);
+ void DispatchResized();
+
+#ifdef MOZ_X11
+ Window mOldFocusWindow;
+#endif /* MOZ_X11 */
+
+ static guint32 sLastButtonPressTime;
+
+ [[nodiscard]] virtual nsresult BeginResizeDrag(
+ mozilla::WidgetGUIEvent* aEvent, int32_t aHorizontal,
+ int32_t aVertical) override;
+
+ MozContainer* GetMozContainer() { return mContainer; }
+ LayoutDeviceIntRect GetMozContainerSize();
+ // GetMozContainerWidget returns the MozContainer even for undestroyed
+ // descendant windows
+ GtkWidget* GetMozContainerWidget();
+ GdkWindow* GetGdkWindow() { return mGdkWindow; }
+ GtkWidget* GetGtkWidget() { return mShell; }
+ nsIFrame* GetFrame();
+ bool IsDestroyed() { return mIsDestroyed; }
+ bool IsPopup();
+ bool IsWaylandPopup();
+ bool IsPIPWindow() { return mIsPIPWindow; };
+
+ void DispatchDragEvent(mozilla::EventMessage aMsg,
+ const LayoutDeviceIntPoint& aRefPoint, guint aTime);
+ static void UpdateDragStatus(GdkDragContext* aDragContext,
+ nsIDragService* aDragService);
+
+ WidgetEventTime GetWidgetEventTime(guint32 aEventTime);
+ mozilla::TimeStamp GetEventTimeStamp(guint32 aEventTime);
+ mozilla::CurrentX11TimeGetter* GetCurrentTimeGetter();
+
+ virtual void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ virtual InputContext GetInputContext() override;
+ virtual TextEventDispatcherListener* GetNativeTextEventDispatcherListener()
+ override;
+ void GetEditCommandsRemapped(NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ nsTArray<mozilla::CommandInt>& aCommands,
+ uint32_t aGeckoKeyCode, uint32_t aNativeKeyCode);
+ virtual bool GetEditCommands(
+ NativeKeyBindingsType aType, const mozilla::WidgetKeyboardEvent& aEvent,
+ nsTArray<mozilla::CommandInt>& aCommands) override;
+
+ // These methods are for toplevel windows only.
+ void ResizeTransparencyBitmap();
+ void ApplyTransparencyBitmap();
+ void ClearTransparencyBitmap();
+
+ virtual void SetTransparencyMode(nsTransparencyMode aMode) override;
+ virtual nsTransparencyMode GetTransparencyMode() override;
+ virtual void SetWindowMouseTransparent(bool aIsTransparent) override;
+ virtual nsresult ConfigureChildren(
+ const nsTArray<Configuration>& aConfigurations) override;
+ nsresult UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
+ uint8_t* aAlphas,
+ int32_t aStride);
+ void UpdateTitlebarTransparencyBitmap();
+
+ virtual void ReparentNativeWidget(nsIWidget* aNewParent) 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, GDK_MOTION_NOTIFY, 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;
+
+#ifdef MOZ_X11
+ Display* XDisplay() { return mXDisplay; }
+#endif
+#ifdef MOZ_WAYLAND
+ wl_display* GetWaylandDisplay();
+ bool WaylandSurfaceNeedsClear();
+ virtual void CreateCompositorVsyncDispatcher() override;
+#endif
+ virtual void GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) override;
+
+ virtual nsresult SetNonClientMargins(
+ LayoutDeviceIntMargin& aMargins) override;
+ void SetDrawsInTitlebar(bool aState) override;
+ bool GetTitlebarRect(mozilla::gfx::Rect& aRect);
+ virtual void UpdateWindowDraggingRegion(
+ const LayoutDeviceIntRegion& aRegion) override;
+
+ // HiDPI scale conversion
+ gint GdkScaleFactor();
+
+ // To GDK
+ gint DevicePixelsToGdkCoordRoundUp(int pixels);
+ gint DevicePixelsToGdkCoordRoundDown(int pixels);
+ GdkPoint DevicePixelsToGdkPointRoundDown(LayoutDeviceIntPoint point);
+ GdkRectangle DevicePixelsToGdkSizeRoundUp(LayoutDeviceIntSize pixelSize);
+
+ // From GDK
+ int GdkCoordToDevicePixels(gint coord);
+ LayoutDeviceIntPoint GdkPointToDevicePixels(GdkPoint point);
+ LayoutDeviceIntPoint GdkEventCoordsToDevicePixels(gdouble x, gdouble y);
+ LayoutDeviceIntRect GdkRectToDevicePixels(GdkRectangle rect);
+
+ virtual bool WidgetTypeSupportsAcceleration() override;
+
+ nsresult SetSystemFont(const nsCString& aFontName) override;
+ nsresult GetSystemFont(nsCString& aFontName) override;
+
+ typedef enum {
+ CSD_SUPPORT_SYSTEM, // CSD including shadows
+ CSD_SUPPORT_CLIENT, // CSD without shadows
+ CSD_SUPPORT_NONE, // WM does not support CSD at all
+ CSD_SUPPORT_UNKNOWN
+ } CSDSupportLevel;
+ /**
+ * Get the support of Client Side Decoration by checking
+ * the XDG_CURRENT_DESKTOP environment variable.
+ */
+ static CSDSupportLevel GetSystemCSDSupportLevel(bool aIsPopup = false);
+
+ static bool HideTitlebarByDefault();
+ static bool GetTopLevelWindowActiveState(nsIFrame* aFrame);
+ static bool TitlebarUseShapeMask();
+#ifdef MOZ_WAYLAND
+ virtual nsresult GetScreenRect(LayoutDeviceIntRect* aRect) override;
+ virtual nsRect GetPreferredPopupRect() override {
+ return mPreferredPopupRect;
+ };
+ virtual void FlushPreferredPopupRect() override {
+ mPreferredPopupRect = nsRect(0, 0, 0, 0);
+ mPreferredPopupRectFlushed = true;
+ };
+#endif
+ bool IsRemoteContent() { return HasRemoteContent(); }
+ static void HideWaylandOpenedPopups();
+ void NativeMoveResizeWaylandPopupCB(const GdkRectangle* aFinalSize,
+ bool aFlippedX, bool aFlippedY);
+ static bool IsToplevelWindowTransparent();
+
+ protected:
+ virtual ~nsWindow();
+
+ // event handling code
+ void DispatchActivateEvent(void);
+ void DispatchDeactivateEvent(void);
+ void MaybeDispatchResized();
+
+ virtual void RegisterTouchWindow() override;
+ virtual bool CompositorInitiallyPaused() override {
+#ifdef MOZ_WAYLAND
+ return mCompositorInitiallyPaused;
+#else
+ return false;
+#endif
+ }
+ nsCOMPtr<nsIWidget> mParent;
+ // Is this a toplevel window?
+ bool mIsTopLevel;
+ // Has this widget been destroyed yet?
+ bool mIsDestroyed;
+
+ // Should we send resize events on all resizes?
+ bool mListenForResizes;
+ // Does WindowResized need to be called on listeners?
+ bool mNeedsDispatchResized;
+ // This flag tracks if we're hidden or shown.
+ bool mIsShown;
+ bool mNeedsShow;
+ // is this widget enabled?
+ bool mEnabled;
+ // has the native window for this been created yet?
+ bool mCreated;
+ // whether we handle touch event
+ bool mHandleTouchEvent;
+ // true if this is a drag and drop feedback popup
+ bool mIsDragPopup;
+ // Can we access X?
+ bool mIsX11Display;
+#ifdef MOZ_WAYLAND
+ bool mNeedsCompositorResume;
+ bool mCompositorInitiallyPaused;
+#endif
+ bool mWindowScaleFactorChanged;
+ int mWindowScaleFactor;
+ bool mCompositedScreen;
+
+ private:
+ void DestroyChildWindows();
+ GtkWidget* GetToplevelWidget();
+ nsWindow* GetContainerWindow();
+ void SetUrgencyHint(GtkWidget* top_window, bool state);
+ void SetDefaultIcon(void);
+ void SetWindowDecoration(nsBorderStyle aStyle);
+ void InitButtonEvent(mozilla::WidgetMouseEvent& aEvent,
+ GdkEventButton* aGdkEvent);
+ bool CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
+ bool aAlwaysRollup);
+ void CheckForRollupDuringGrab() { CheckForRollup(0, 0, false, true); }
+
+ bool GetDragInfo(mozilla::WidgetMouseEvent* aMouseEvent, GdkWindow** aWindow,
+ gint* aButton, gint* aRootX, gint* aRootY);
+ void ClearCachedResources();
+ nsIWidgetListener* GetListener();
+
+ nsWindow* GetTransientForWindowIfPopup();
+ bool IsHandlingTouchSequence(GdkEventSequence* aSequence);
+
+ void ResizeInt(int aX, int aY, int aWidth, int aHeight, bool aMove,
+ bool aRepaint);
+ void NativeMoveResizeWaylandPopup(GdkPoint* aPosition, GdkRectangle* aSize);
+
+ GtkTextDirection GetTextDirection();
+
+ void AddCSDDecorationSize(int* aWidth, int* aHeight);
+
+#ifdef MOZ_X11
+ typedef enum {GTK_WIDGET_COMPOSIDED_DEFAULT = 0,
+ GTK_WIDGET_COMPOSIDED_DISABLED = 1,
+ GTK_WIDGET_COMPOSIDED_ENABLED = 2} WindowComposeRequest;
+
+ void SetCompositorHint(WindowComposeRequest aState);
+#endif
+ nsCString mGtkWindowAppName;
+ nsCString mGtkWindowRoleName;
+ void RefreshWindowClass();
+
+ GtkWidget* mShell;
+ MozContainer* mContainer;
+ GdkWindow* mGdkWindow;
+ bool mWindowShouldStartDragging = false;
+ PlatformCompositorWidgetDelegate* mCompositorWidgetDelegate;
+
+ uint32_t mHasMappedToplevel : 1, mRetryPointerGrab : 1;
+ nsSizeMode mSizeState;
+ float mAspectRatio;
+ float mAspectRatioSaved;
+ nsIntPoint mClientOffset;
+
+ // This field omits duplicate scroll events caused by GNOME bug 726878.
+ guint32 mLastScrollEventTime;
+ mozilla::ScreenCoord mLastPinchEventSpan;
+ bool mPanInProgress = false;
+
+ // for touch event handling
+ nsRefPtrHashtable<nsPtrHashKey<GdkEventSequence>, mozilla::dom::Touch>
+ mTouches;
+
+#ifdef MOZ_X11
+ Display* mXDisplay;
+ Window mXWindow;
+ Visual* mXVisual;
+ int mXDepth;
+ mozilla::widget::WindowSurfaceProvider mSurfaceProvider;
+
+ bool ConfigureX11GLVisual(bool aUseAlpha);
+#endif
+#ifdef MOZ_WAYLAND
+ RefPtr<mozilla::gfx::VsyncSource> mWaylandVsyncSource;
+#endif
+
+ // Upper bound on pending ConfigureNotify events to be dispatched to the
+ // window. See bug 1225044.
+ unsigned int mPendingConfigures;
+
+ // Window titlebar rendering mode, CSD_SUPPORT_NONE if it's disabled
+ // for this window.
+ CSDSupportLevel mCSDSupportLevel;
+ // Use dedicated GdkWindow for mContainer
+ bool mDrawToContainer;
+ // If true, draw our own window titlebar.
+ bool mDrawInTitlebar;
+ // Draw titlebar with :backdrop css state (inactive/unfocused).
+ bool mTitlebarBackdropState;
+ // Draggable titlebar region maintained by UpdateWindowDraggingRegion
+ LayoutDeviceIntRegion mDraggableRegion;
+ // It's PictureInPicture window.
+ bool mIsPIPWindow;
+ bool mAlwaysOnTop;
+
+#ifdef ACCESSIBILITY
+ RefPtr<mozilla::a11y::Accessible> mRootAccessible;
+
+ /**
+ * Request to create the accessible for this window if it is top level.
+ */
+ void CreateRootAccessible();
+
+ /**
+ * Dispatch accessible event for the top level window accessible.
+ *
+ * @param aEventType [in] the accessible event type to dispatch
+ */
+ void DispatchEventToRootAccessible(uint32_t aEventType);
+
+ /**
+ * Dispatch accessible window activate event for the top level window
+ * accessible.
+ */
+ void DispatchActivateEventAccessible();
+
+ /**
+ * Dispatch accessible window deactivate event for the top level window
+ * accessible.
+ */
+ void DispatchDeactivateEventAccessible();
+
+ /**
+ * Dispatch accessible window maximize event for the top level window
+ * accessible.
+ */
+ void DispatchMaximizeEventAccessible();
+
+ /**
+ * Dispatch accessible window minize event for the top level window
+ * accessible.
+ */
+ void DispatchMinimizeEventAccessible();
+
+ /**
+ * Dispatch accessible window restore event for the top level window
+ * accessible.
+ */
+ void DispatchRestoreEventAccessible();
+#endif
+
+ // The cursor cache
+ static GdkCursor* gsGtkCursorCache[eCursorCount];
+
+ // Transparency
+ bool mIsTransparent;
+ // This bitmap tracks which pixels are transparent. We don't support
+ // full translucency at this time; each pixel is either fully opaque
+ // or fully transparent.
+ gchar* mTransparencyBitmap;
+ int32_t mTransparencyBitmapWidth;
+ int32_t mTransparencyBitmapHeight;
+ // The transparency bitmap is used instead of ARGB visual for toplevel
+ // window to draw titlebar.
+ bool mTransparencyBitmapForTitlebar;
+
+ // True when we're on compositing window manager and this
+ // window is using visual with alpha channel.
+ bool mHasAlphaVisual;
+
+ // all of our DND stuff
+ void InitDragEvent(mozilla::WidgetDragEvent& aEvent);
+
+ float mLastMotionPressure;
+
+ // Remember the last sizemode so that we can restore it when
+ // leaving fullscreen
+ nsSizeMode mLastSizeMode;
+ // We can't detect size state changes correctly so set this flag
+ // to force update mBounds after a size state change from a configure
+ // event.
+ bool mBoundsAreValid;
+
+ static bool DragInProgress(void);
+
+ void DispatchMissedButtonReleases(GdkEventCrossing* aGdkEvent);
+
+ // nsBaseWidget
+ virtual LayerManager* GetLayerManager(
+ PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
+
+ void SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) override;
+
+ void CleanLayerManagerRecursive();
+
+ virtual int32_t RoundsWidgetCoordinatesTo() override;
+
+ void UpdateMozWindowActive();
+
+ void ForceTitlebarRedraw();
+ bool DoDrawTilebarCorners();
+ bool IsChromeWindowTitlebar();
+
+ void SetPopupWindowDecoration(bool aShowOnTaskbar);
+
+ void ApplySizeConstraints(void);
+
+ bool IsMainMenuWindow();
+ GtkWidget* ConfigureWaylandPopupWindows();
+ void PauseRemoteRenderer();
+ void HideWaylandWindow();
+ void HideWaylandTooltips();
+ void HideWaylandPopupAndAllChildren();
+ void CleanupWaylandPopups();
+ GtkWindow* GetCurrentTopmostWindow();
+ GtkWindow* GetCurrentWindow();
+ GtkWindow* GetTopmostWindow();
+ bool IsWidgetOverflowWindow();
+ nsRect mPreferredPopupRect;
+ bool mPreferredPopupRectFlushed;
+ bool mWaitingForMoveToRectCB;
+ LayoutDeviceIntRect mPendingSizeRect;
+
+ /**
+ * |mIMContext| takes all IME related stuff.
+ *
+ * This is owned by the top-level nsWindow or the topmost child
+ * nsWindow embedded in a non-Gecko widget.
+ *
+ * The instance is created when the top level widget is created. And when
+ * the widget is destroyed, it's released. All child windows refer its
+ * ancestor widget's instance. So, one set of IM contexts is created for
+ * all windows in a hierarchy. If the children are released after the top
+ * level window is released, the children still have a valid pointer,
+ * however, IME doesn't work at that time.
+ */
+ RefPtr<mozilla::widget::IMContextWrapper> mIMContext;
+
+ mozilla::UniquePtr<mozilla::CurrentX11TimeGetter> mCurrentTimeGetter;
+ static CSDSupportLevel sCSDSupportLevel;
+
+ static bool sTransparentMainWindow;
+};
+
+#endif /* __nsWindow_h__ */
diff --git a/widget/gtk/wayland/gbm.h b/widget/gtk/wayland/gbm.h
new file mode 100644
index 0000000000..bd94fa8967
--- /dev/null
+++ b/widget/gtk/wayland/gbm.h
@@ -0,0 +1,480 @@
+/*
+ * Copyright © 2011 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Benjamin Franzke <benjaminfranzke@googlemail.com>
+ */
+
+#ifndef _GBM_H_
+#define _GBM_H_
+
+#define __GBM__ 1
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file gbm.h
+ * \brief Generic Buffer Manager
+ */
+
+struct gbm_device;
+struct gbm_bo;
+struct gbm_surface;
+
+/**
+ * \mainpage The Generic Buffer Manager
+ *
+ * This module provides an abstraction that the caller can use to request a
+ * buffer from the underlying memory management system for the platform.
+ *
+ * This allows the creation of portable code whilst still allowing access to
+ * the underlying memory manager.
+ */
+
+/**
+ * Abstraction representing the handle to a buffer allocated by the
+ * manager
+ */
+union gbm_bo_handle {
+ void* ptr;
+ int32_t s32;
+ uint32_t u32;
+ int64_t s64;
+ uint64_t u64;
+};
+
+/** Format of the allocated buffer */
+enum gbm_bo_format {
+ /** RGB with 8 bits per channel in a 32 bit value */
+ GBM_BO_FORMAT_XRGB8888,
+ /** ARGB with 8 bits per channel in a 32 bit value */
+ GBM_BO_FORMAT_ARGB8888
+};
+
+/**
+ * The FourCC format codes are taken from the drm_fourcc.h definition, and
+ * re-namespaced. New GBM formats must not be added, unless they are
+ * identical ports from drm_fourcc.
+ */
+#define __gbm_fourcc_code(a, b, c, d) \
+ ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | \
+ ((uint32_t)(d) << 24))
+
+#define GBM_FORMAT_BIG_ENDIAN \
+ (1 << 31) /* format is big endian instead of little endian */
+
+/* color index */
+#define GBM_FORMAT_C8 __gbm_fourcc_code('C', '8', ' ', ' ') /* [7:0] C */
+
+/* 8 bpp Red */
+#define GBM_FORMAT_R8 __gbm_fourcc_code('R', '8', ' ', ' ') /* [7:0] R */
+
+/* 16 bpp RG */
+#define GBM_FORMAT_GR88 \
+ __gbm_fourcc_code('G', 'R', '8', '8') /* [15:0] G:R 8:8 little endian */
+
+/* 8 bpp RGB */
+#define GBM_FORMAT_RGB332 \
+ __gbm_fourcc_code('R', 'G', 'B', '8') /* [7:0] R:G:B 3:3:2 */
+#define GBM_FORMAT_BGR233 \
+ __gbm_fourcc_code('B', 'G', 'R', '8') /* [7:0] B:G:R 2:3:3 */
+
+/* 16 bpp RGB */
+#define GBM_FORMAT_XRGB4444 \
+ __gbm_fourcc_code('X', 'R', '1', \
+ '2') /* [15:0] x:R:G:B 4:4:4:4 little endian */
+#define GBM_FORMAT_XBGR4444 \
+ __gbm_fourcc_code('X', 'B', '1', \
+ '2') /* [15:0] x:B:G:R 4:4:4:4 little endian */
+#define GBM_FORMAT_RGBX4444 \
+ __gbm_fourcc_code('R', 'X', '1', \
+ '2') /* [15:0] R:G:B:x 4:4:4:4 little endian */
+#define GBM_FORMAT_BGRX4444 \
+ __gbm_fourcc_code('B', 'X', '1', \
+ '2') /* [15:0] B:G:R:x 4:4:4:4 little endian */
+
+#define GBM_FORMAT_ARGB4444 \
+ __gbm_fourcc_code('A', 'R', '1', \
+ '2') /* [15:0] A:R:G:B 4:4:4:4 little endian */
+#define GBM_FORMAT_ABGR4444 \
+ __gbm_fourcc_code('A', 'B', '1', \
+ '2') /* [15:0] A:B:G:R 4:4:4:4 little endian */
+#define GBM_FORMAT_RGBA4444 \
+ __gbm_fourcc_code('R', 'A', '1', \
+ '2') /* [15:0] R:G:B:A 4:4:4:4 little endian */
+#define GBM_FORMAT_BGRA4444 \
+ __gbm_fourcc_code('B', 'A', '1', \
+ '2') /* [15:0] B:G:R:A 4:4:4:4 little endian */
+
+#define GBM_FORMAT_XRGB1555 \
+ __gbm_fourcc_code('X', 'R', '1', \
+ '5') /* [15:0] x:R:G:B 1:5:5:5 little endian */
+#define GBM_FORMAT_XBGR1555 \
+ __gbm_fourcc_code('X', 'B', '1', \
+ '5') /* [15:0] x:B:G:R 1:5:5:5 little endian */
+#define GBM_FORMAT_RGBX5551 \
+ __gbm_fourcc_code('R', 'X', '1', \
+ '5') /* [15:0] R:G:B:x 5:5:5:1 little endian */
+#define GBM_FORMAT_BGRX5551 \
+ __gbm_fourcc_code('B', 'X', '1', \
+ '5') /* [15:0] B:G:R:x 5:5:5:1 little endian */
+
+#define GBM_FORMAT_ARGB1555 \
+ __gbm_fourcc_code('A', 'R', '1', \
+ '5') /* [15:0] A:R:G:B 1:5:5:5 little endian */
+#define GBM_FORMAT_ABGR1555 \
+ __gbm_fourcc_code('A', 'B', '1', \
+ '5') /* [15:0] A:B:G:R 1:5:5:5 little endian */
+#define GBM_FORMAT_RGBA5551 \
+ __gbm_fourcc_code('R', 'A', '1', \
+ '5') /* [15:0] R:G:B:A 5:5:5:1 little endian */
+#define GBM_FORMAT_BGRA5551 \
+ __gbm_fourcc_code('B', 'A', '1', \
+ '5') /* [15:0] B:G:R:A 5:5:5:1 little endian */
+
+#define GBM_FORMAT_RGB565 \
+ __gbm_fourcc_code('R', 'G', '1', '6') /* [15:0] R:G:B 5:6:5 little endian */
+#define GBM_FORMAT_BGR565 \
+ __gbm_fourcc_code('B', 'G', '1', '6') /* [15:0] B:G:R 5:6:5 little endian */
+
+/* 24 bpp RGB */
+#define GBM_FORMAT_RGB888 \
+ __gbm_fourcc_code('R', 'G', '2', '4') /* [23:0] R:G:B little endian */
+#define GBM_FORMAT_BGR888 \
+ __gbm_fourcc_code('B', 'G', '2', '4') /* [23:0] B:G:R little endian */
+
+/* 32 bpp RGB */
+#define GBM_FORMAT_XRGB8888 \
+ __gbm_fourcc_code('X', 'R', '2', \
+ '4') /* [31:0] x:R:G:B 8:8:8:8 little endian */
+#define GBM_FORMAT_XBGR8888 \
+ __gbm_fourcc_code('X', 'B', '2', \
+ '4') /* [31:0] x:B:G:R 8:8:8:8 little endian */
+#define GBM_FORMAT_RGBX8888 \
+ __gbm_fourcc_code('R', 'X', '2', \
+ '4') /* [31:0] R:G:B:x 8:8:8:8 little endian */
+#define GBM_FORMAT_BGRX8888 \
+ __gbm_fourcc_code('B', 'X', '2', \
+ '4') /* [31:0] B:G:R:x 8:8:8:8 little endian */
+
+#define GBM_FORMAT_ARGB8888 \
+ __gbm_fourcc_code('A', 'R', '2', \
+ '4') /* [31:0] A:R:G:B 8:8:8:8 little endian */
+#define GBM_FORMAT_ABGR8888 \
+ __gbm_fourcc_code('A', 'B', '2', \
+ '4') /* [31:0] A:B:G:R 8:8:8:8 little endian */
+#define GBM_FORMAT_RGBA8888 \
+ __gbm_fourcc_code('R', 'A', '2', \
+ '4') /* [31:0] R:G:B:A 8:8:8:8 little endian */
+#define GBM_FORMAT_BGRA8888 \
+ __gbm_fourcc_code('B', 'A', '2', \
+ '4') /* [31:0] B:G:R:A 8:8:8:8 little endian */
+
+#define GBM_FORMAT_XRGB2101010 \
+ __gbm_fourcc_code('X', 'R', '3', \
+ '0') /* [31:0] x:R:G:B 2:10:10:10 little endian */
+#define GBM_FORMAT_XBGR2101010 \
+ __gbm_fourcc_code('X', 'B', '3', \
+ '0') /* [31:0] x:B:G:R 2:10:10:10 little endian */
+#define GBM_FORMAT_RGBX1010102 \
+ __gbm_fourcc_code('R', 'X', '3', \
+ '0') /* [31:0] R:G:B:x 10:10:10:2 little endian */
+#define GBM_FORMAT_BGRX1010102 \
+ __gbm_fourcc_code('B', 'X', '3', \
+ '0') /* [31:0] B:G:R:x 10:10:10:2 little endian */
+
+#define GBM_FORMAT_ARGB2101010 \
+ __gbm_fourcc_code('A', 'R', '3', \
+ '0') /* [31:0] A:R:G:B 2:10:10:10 little endian */
+#define GBM_FORMAT_ABGR2101010 \
+ __gbm_fourcc_code('A', 'B', '3', \
+ '0') /* [31:0] A:B:G:R 2:10:10:10 little endian */
+#define GBM_FORMAT_RGBA1010102 \
+ __gbm_fourcc_code('R', 'A', '3', \
+ '0') /* [31:0] R:G:B:A 10:10:10:2 little endian */
+#define GBM_FORMAT_BGRA1010102 \
+ __gbm_fourcc_code('B', 'A', '3', \
+ '0') /* [31:0] B:G:R:A 10:10:10:2 little endian */
+
+/* packed YCbCr */
+#define GBM_FORMAT_YUYV \
+ __gbm_fourcc_code('Y', 'U', 'Y', \
+ 'V') /* [31:0] Cr0:Y1:Cb0:Y0 8:8:8:8 little endian */
+#define GBM_FORMAT_YVYU \
+ __gbm_fourcc_code('Y', 'V', 'Y', \
+ 'U') /* [31:0] Cb0:Y1:Cr0:Y0 8:8:8:8 little endian */
+#define GBM_FORMAT_UYVY \
+ __gbm_fourcc_code('U', 'Y', 'V', \
+ 'Y') /* [31:0] Y1:Cr0:Y0:Cb0 8:8:8:8 little endian */
+#define GBM_FORMAT_VYUY \
+ __gbm_fourcc_code('V', 'Y', 'U', \
+ 'Y') /* [31:0] Y1:Cb0:Y0:Cr0 8:8:8:8 little endian */
+
+#define GBM_FORMAT_AYUV \
+ __gbm_fourcc_code('A', 'Y', 'U', \
+ 'V') /* [31:0] A:Y:Cb:Cr 8:8:8:8 little endian */
+
+/*
+ * 2 plane YCbCr
+ * index 0 = Y plane, [7:0] Y
+ * index 1 = Cr:Cb plane, [15:0] Cr:Cb little endian
+ * or
+ * index 1 = Cb:Cr plane, [15:0] Cb:Cr little endian
+ */
+#define GBM_FORMAT_NV12 \
+ __gbm_fourcc_code('N', 'V', '1', '2') /* 2x2 subsampled Cr:Cb plane */
+#define GBM_FORMAT_NV21 \
+ __gbm_fourcc_code('N', 'V', '2', '1') /* 2x2 subsampled Cb:Cr plane */
+#define GBM_FORMAT_NV16 \
+ __gbm_fourcc_code('N', 'V', '1', '6') /* 2x1 subsampled Cr:Cb plane */
+#define GBM_FORMAT_NV61 \
+ __gbm_fourcc_code('N', 'V', '6', '1') /* 2x1 subsampled Cb:Cr plane */
+
+/*
+ * 3 plane YCbCr
+ * index 0: Y plane, [7:0] Y
+ * index 1: Cb plane, [7:0] Cb
+ * index 2: Cr plane, [7:0] Cr
+ * or
+ * index 1: Cr plane, [7:0] Cr
+ * index 2: Cb plane, [7:0] Cb
+ */
+#define GBM_FORMAT_YUV410 \
+ __gbm_fourcc_code('Y', 'U', 'V', \
+ '9') /* 4x4 subsampled Cb (1) and Cr (2) planes */
+#define GBM_FORMAT_YVU410 \
+ __gbm_fourcc_code('Y', 'V', 'U', \
+ '9') /* 4x4 subsampled Cr (1) and Cb (2) planes */
+#define GBM_FORMAT_YUV411 \
+ __gbm_fourcc_code('Y', 'U', '1', \
+ '1') /* 4x1 subsampled Cb (1) and Cr (2) planes */
+#define GBM_FORMAT_YVU411 \
+ __gbm_fourcc_code('Y', 'V', '1', \
+ '1') /* 4x1 subsampled Cr (1) and Cb (2) planes */
+#define GBM_FORMAT_YUV420 \
+ __gbm_fourcc_code('Y', 'U', '1', \
+ '2') /* 2x2 subsampled Cb (1) and Cr (2) planes */
+#define GBM_FORMAT_YVU420 \
+ __gbm_fourcc_code('Y', 'V', '1', \
+ '2') /* 2x2 subsampled Cr (1) and Cb (2) planes */
+#define GBM_FORMAT_YUV422 \
+ __gbm_fourcc_code('Y', 'U', '1', \
+ '6') /* 2x1 subsampled Cb (1) and Cr (2) planes */
+#define GBM_FORMAT_YVU422 \
+ __gbm_fourcc_code('Y', 'V', '1', \
+ '6') /* 2x1 subsampled Cr (1) and Cb (2) planes */
+#define GBM_FORMAT_YUV444 \
+ __gbm_fourcc_code('Y', 'U', '2', \
+ '4') /* non-subsampled Cb (1) and Cr (2) planes */
+#define GBM_FORMAT_YVU444 \
+ __gbm_fourcc_code('Y', 'V', '2', \
+ '4') /* non-subsampled Cr (1) and Cb (2) planes */
+
+struct gbm_format_name_desc {
+ char name[5];
+};
+
+/**
+ * Flags to indicate the intended use for the buffer - these are passed into
+ * gbm_bo_create(). The caller must set the union of all the flags that are
+ * appropriate
+ *
+ * \sa Use gbm_device_is_format_supported() to check if the combination of
+ * format and use flags are supported
+ */
+enum gbm_bo_flags {
+ /**
+ * Buffer is going to be presented to the screen using an API such as KMS
+ */
+ GBM_BO_USE_SCANOUT = (1 << 0),
+ /**
+ * Buffer is going to be used as cursor
+ */
+ GBM_BO_USE_CURSOR = (1 << 1),
+ /**
+ * Deprecated
+ */
+ GBM_BO_USE_CURSOR_64X64 = GBM_BO_USE_CURSOR,
+ /**
+ * Buffer is to be used for rendering - for example it is going to be used
+ * as the storage for a color buffer
+ */
+ GBM_BO_USE_RENDERING = (1 << 2),
+ /**
+ * Buffer can be used for gbm_bo_write. This is guaranteed to work
+ * with GBM_BO_USE_CURSOR, but may not work for other combinations.
+ */
+ GBM_BO_USE_WRITE = (1 << 3),
+ /**
+ * Buffer is linear, i.e. not tiled.
+ */
+ GBM_BO_USE_LINEAR = (1 << 4),
+};
+
+int gbm_device_get_fd(struct gbm_device* gbm);
+
+const char* gbm_device_get_backend_name(struct gbm_device* gbm);
+
+int gbm_device_is_format_supported(struct gbm_device* gbm, uint32_t format,
+ uint32_t usage);
+
+int gbm_device_get_format_modifier_plane_count(struct gbm_device* gbm,
+ uint32_t format,
+ uint64_t modifier);
+
+void gbm_device_destroy(struct gbm_device* gbm);
+
+struct gbm_device* gbm_create_device(int fd);
+
+struct gbm_bo* gbm_bo_create(struct gbm_device* gbm, uint32_t width,
+ uint32_t height, uint32_t format, uint32_t flags);
+
+struct gbm_bo* gbm_bo_create_with_modifiers(struct gbm_device* gbm,
+ uint32_t width, uint32_t height,
+ uint32_t format,
+ const uint64_t* modifiers,
+ const unsigned int count);
+#define GBM_BO_IMPORT_WL_BUFFER 0x5501
+#define GBM_BO_IMPORT_EGL_IMAGE 0x5502
+#define GBM_BO_IMPORT_FD 0x5503
+#define GBM_BO_IMPORT_FD_MODIFIER 0x5504
+
+struct gbm_import_fd_data {
+ int fd;
+ uint32_t width;
+ uint32_t height;
+ uint32_t stride;
+ uint32_t format;
+};
+
+struct gbm_import_fd_modifier_data {
+ uint32_t width;
+ uint32_t height;
+ uint32_t format;
+ uint32_t num_fds;
+ int fds[4];
+ int strides[4];
+ int offsets[4];
+ uint64_t modifier;
+};
+
+struct gbm_bo* gbm_bo_import(struct gbm_device* gbm, uint32_t type,
+ void* buffer, uint32_t usage);
+
+/**
+ * Flags to indicate the type of mapping for the buffer - these are
+ * passed into gbm_bo_map(). The caller must set the union of all the
+ * flags that are appropriate.
+ *
+ * These flags are independent of the GBM_BO_USE_* creation flags. However,
+ * mapping the buffer may require copying to/from a staging buffer.
+ *
+ * See also: pipe_transfer_usage
+ */
+enum gbm_bo_transfer_flags {
+ /**
+ * Buffer contents read back (or accessed directly) at transfer
+ * create time.
+ */
+ GBM_BO_TRANSFER_READ = (1 << 0),
+ /**
+ * Buffer contents will be written back at unmap time
+ * (or modified as a result of being accessed directly).
+ */
+ GBM_BO_TRANSFER_WRITE = (1 << 1),
+ /**
+ * Read/modify/write
+ */
+ GBM_BO_TRANSFER_READ_WRITE = (GBM_BO_TRANSFER_READ | GBM_BO_TRANSFER_WRITE),
+};
+
+void* gbm_bo_map(struct gbm_bo* bo, uint32_t x, uint32_t y, uint32_t width,
+ uint32_t height, uint32_t flags, uint32_t* stride,
+ void** map_data);
+
+void gbm_bo_unmap(struct gbm_bo* bo, void* map_data);
+
+uint32_t gbm_bo_get_width(struct gbm_bo* bo);
+
+uint32_t gbm_bo_get_height(struct gbm_bo* bo);
+
+uint32_t gbm_bo_get_stride(struct gbm_bo* bo);
+
+uint32_t gbm_bo_get_stride_for_plane(struct gbm_bo* bo, int plane);
+
+uint32_t gbm_bo_get_format(struct gbm_bo* bo);
+
+uint32_t gbm_bo_get_bpp(struct gbm_bo* bo);
+
+uint32_t gbm_bo_get_offset(struct gbm_bo* bo, int plane);
+
+struct gbm_device* gbm_bo_get_device(struct gbm_bo* bo);
+
+union gbm_bo_handle gbm_bo_get_handle(struct gbm_bo* bo);
+
+int gbm_bo_get_fd(struct gbm_bo* bo);
+
+uint64_t gbm_bo_get_modifier(struct gbm_bo* bo);
+
+int gbm_bo_get_plane_count(struct gbm_bo* bo);
+
+union gbm_bo_handle gbm_bo_get_handle_for_plane(struct gbm_bo* bo, int plane);
+
+int gbm_bo_write(struct gbm_bo* bo, const void* buf, size_t count);
+
+void gbm_bo_set_user_data(struct gbm_bo* bo, void* data,
+ void (*destroy_user_data)(struct gbm_bo*, void*));
+
+void* gbm_bo_get_user_data(struct gbm_bo* bo);
+
+void gbm_bo_destroy(struct gbm_bo* bo);
+
+struct gbm_surface* gbm_surface_create(struct gbm_device* gbm, uint32_t width,
+ uint32_t height, uint32_t format,
+ uint32_t flags);
+
+struct gbm_surface* gbm_surface_create_with_modifiers(
+ struct gbm_device* gbm, uint32_t width, uint32_t height, uint32_t format,
+ const uint64_t* modifiers, const unsigned int count);
+
+struct gbm_bo* gbm_surface_lock_front_buffer(struct gbm_surface* surface);
+
+void gbm_surface_release_buffer(struct gbm_surface* surface, struct gbm_bo* bo);
+
+int gbm_surface_has_free_buffers(struct gbm_surface* surface);
+
+void gbm_surface_destroy(struct gbm_surface* surface);
+
+char* gbm_format_get_name(uint32_t gbm_format,
+ struct gbm_format_name_desc* desc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/gtk-primary-selection-client-protocol.h b/widget/gtk/wayland/gtk-primary-selection-client-protocol.h
new file mode 100644
index 0000000000..770c35e03f
--- /dev/null
+++ b/widget/gtk/wayland/gtk-primary-selection-client-protocol.h
@@ -0,0 +1,580 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+#ifndef GTK_PRIMARY_SELECTION_CLIENT_PROTOCOL_H
+#define GTK_PRIMARY_SELECTION_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_gtk_primary_selection The gtk_primary_selection protocol
+ * Primary selection protocol
+ *
+ * @section page_desc_gtk_primary_selection Description
+ *
+ * This protocol provides the ability to have a primary selection device to
+ * match that of the X server. This primary selection is a shortcut to the
+ * common clipboard selection, where text just needs to be selected in order
+ * to allow copying it elsewhere. The de facto way to perform this action
+ * is the middle mouse button, although it is not limited to this one.
+ *
+ * Clients wishing to honor primary selection should create a primary
+ * selection source and set it as the selection through
+ * wp_primary_selection_device.set_selection whenever the text selection
+ * changes. In order to minimize calls in pointer-driven text selection,
+ * it should happen only once after the operation finished. Similarly,
+ * a NULL source should be set when text is unselected.
+ *
+ * wp_primary_selection_offer objects are first announced through the
+ * wp_primary_selection_device.data_offer event. Immediately after this event,
+ * the primary data offer will emit wp_primary_selection_offer.offer events
+ * to let know of the mime types being offered.
+ *
+ * When the primary selection changes, the client with the keyboard focus
+ * will receive wp_primary_selection_device.selection events. Only the client
+ * with the keyboard focus will receive such events with a non-NULL
+ * wp_primary_selection_offer. Across keyboard focus changes, previously
+ * focused clients will receive wp_primary_selection_device.events with a
+ * NULL wp_primary_selection_offer.
+ *
+ * In order to request the primary selection data, the client must pass
+ * a recent serial pertaining to the press event that is triggering the
+ * operation, if the compositor deems the serial valid and recent, the
+ * wp_primary_selection_source.send event will happen in the other end
+ * to let the transfer begin. The client owning the primary selection
+ * should write the requested data, and close the file descriptor
+ * immediately.
+ *
+ * If the primary selection owner client disappeared during the transfer,
+ * the client reading the data will receive a
+ * wp_primary_selection_device.selection event with a NULL
+ * wp_primary_selection_offer, the client should take this as a hint
+ * to finish the reads related to the no longer existing offer.
+ *
+ * The primary selection owner should be checking for errors during
+ * writes, merely cancelling the ongoing transfer if any happened.
+ *
+ * @section page_ifaces_gtk_primary_selection Interfaces
+ * - @subpage page_iface_gtk_primary_selection_device_manager - X primary selection emulation
+ * - @subpage page_iface_gtk_primary_selection_device -
+ * - @subpage page_iface_gtk_primary_selection_offer - offer to transfer primary selection contents
+ * - @subpage page_iface_gtk_primary_selection_source - offer to replace the contents of the primary selection
+ * @section page_copyright_gtk_primary_selection Copyright
+ * <pre>
+ *
+ * Copyright © 2015, 2016 Red Hat
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct gtk_primary_selection_device;
+struct gtk_primary_selection_device_manager;
+struct gtk_primary_selection_offer;
+struct gtk_primary_selection_source;
+struct wl_seat;
+
+/**
+ * @page page_iface_gtk_primary_selection_device_manager gtk_primary_selection_device_manager
+ * @section page_iface_gtk_primary_selection_device_manager_desc Description
+ *
+ * The primary selection device manager is a singleton global object that
+ * provides access to the primary selection. It allows to create
+ * wp_primary_selection_source objects, as well as retrieving the per-seat
+ * wp_primary_selection_device objects.
+ * @section page_iface_gtk_primary_selection_device_manager_api API
+ * See @ref iface_gtk_primary_selection_device_manager.
+ */
+/**
+ * @defgroup iface_gtk_primary_selection_device_manager The gtk_primary_selection_device_manager interface
+ *
+ * The primary selection device manager is a singleton global object that
+ * provides access to the primary selection. It allows to create
+ * wp_primary_selection_source objects, as well as retrieving the per-seat
+ * wp_primary_selection_device objects.
+ */
+extern const struct wl_interface gtk_primary_selection_device_manager_interface;
+/**
+ * @page page_iface_gtk_primary_selection_device gtk_primary_selection_device
+ * @section page_iface_gtk_primary_selection_device_api API
+ * See @ref iface_gtk_primary_selection_device.
+ */
+/**
+ * @defgroup iface_gtk_primary_selection_device The gtk_primary_selection_device interface
+ */
+extern const struct wl_interface gtk_primary_selection_device_interface;
+/**
+ * @page page_iface_gtk_primary_selection_offer gtk_primary_selection_offer
+ * @section page_iface_gtk_primary_selection_offer_desc Description
+ *
+ * A wp_primary_selection_offer represents an offer to transfer the contents
+ * of the primary selection clipboard to the client. Similar to
+ * wl_data_offer, the offer also describes the mime types that the source
+ * will transferthat the
+ * data can be converted to and provides the mechanisms for transferring the
+ * data directly to the client.
+ * @section page_iface_gtk_primary_selection_offer_api API
+ * See @ref iface_gtk_primary_selection_offer.
+ */
+/**
+ * @defgroup iface_gtk_primary_selection_offer The gtk_primary_selection_offer interface
+ *
+ * A wp_primary_selection_offer represents an offer to transfer the contents
+ * of the primary selection clipboard to the client. Similar to
+ * wl_data_offer, the offer also describes the mime types that the source
+ * will transferthat the
+ * data can be converted to and provides the mechanisms for transferring the
+ * data directly to the client.
+ */
+extern const struct wl_interface gtk_primary_selection_offer_interface;
+/**
+ * @page page_iface_gtk_primary_selection_source gtk_primary_selection_source
+ * @section page_iface_gtk_primary_selection_source_desc Description
+ *
+ * The source side of a wp_primary_selection_offer, it provides a way to
+ * describe the offered data and respond to requests to transfer the
+ * requested contents of the primary selection clipboard.
+ * @section page_iface_gtk_primary_selection_source_api API
+ * See @ref iface_gtk_primary_selection_source.
+ */
+/**
+ * @defgroup iface_gtk_primary_selection_source The gtk_primary_selection_source interface
+ *
+ * The source side of a wp_primary_selection_offer, it provides a way to
+ * describe the offered data and respond to requests to transfer the
+ * requested contents of the primary selection clipboard.
+ */
+extern const struct wl_interface gtk_primary_selection_source_interface;
+
+#define GTK_PRIMARY_SELECTION_DEVICE_MANAGER_CREATE_SOURCE 0
+#define GTK_PRIMARY_SELECTION_DEVICE_MANAGER_GET_DEVICE 1
+#define GTK_PRIMARY_SELECTION_DEVICE_MANAGER_DESTROY 2
+
+
+/**
+ * @ingroup iface_gtk_primary_selection_device_manager
+ */
+#define GTK_PRIMARY_SELECTION_DEVICE_MANAGER_CREATE_SOURCE_SINCE_VERSION 1
+/**
+ * @ingroup iface_gtk_primary_selection_device_manager
+ */
+#define GTK_PRIMARY_SELECTION_DEVICE_MANAGER_GET_DEVICE_SINCE_VERSION 1
+/**
+ * @ingroup iface_gtk_primary_selection_device_manager
+ */
+#define GTK_PRIMARY_SELECTION_DEVICE_MANAGER_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_gtk_primary_selection_device_manager */
+static inline void
+gtk_primary_selection_device_manager_set_user_data(struct gtk_primary_selection_device_manager *gtk_primary_selection_device_manager, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) gtk_primary_selection_device_manager, user_data);
+}
+
+/** @ingroup iface_gtk_primary_selection_device_manager */
+static inline void *
+gtk_primary_selection_device_manager_get_user_data(struct gtk_primary_selection_device_manager *gtk_primary_selection_device_manager)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) gtk_primary_selection_device_manager);
+}
+
+static inline uint32_t
+gtk_primary_selection_device_manager_get_version(struct gtk_primary_selection_device_manager *gtk_primary_selection_device_manager)
+{
+ return wl_proxy_get_version((struct wl_proxy *) gtk_primary_selection_device_manager);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_device_manager
+ *
+ * Create a new primary selection source.
+ */
+static inline struct gtk_primary_selection_source *
+gtk_primary_selection_device_manager_create_source(struct gtk_primary_selection_device_manager *gtk_primary_selection_device_manager)
+{
+ struct wl_proxy *id;
+
+ id = wl_proxy_marshal_constructor((struct wl_proxy *) gtk_primary_selection_device_manager,
+ GTK_PRIMARY_SELECTION_DEVICE_MANAGER_CREATE_SOURCE, &gtk_primary_selection_source_interface, NULL);
+
+ return (struct gtk_primary_selection_source *) id;
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_device_manager
+ *
+ * Create a new data device for a given seat.
+ */
+static inline struct gtk_primary_selection_device *
+gtk_primary_selection_device_manager_get_device(struct gtk_primary_selection_device_manager *gtk_primary_selection_device_manager, struct wl_seat *seat)
+{
+ struct wl_proxy *id;
+
+ id = wl_proxy_marshal_constructor((struct wl_proxy *) gtk_primary_selection_device_manager,
+ GTK_PRIMARY_SELECTION_DEVICE_MANAGER_GET_DEVICE, &gtk_primary_selection_device_interface, NULL, seat);
+
+ return (struct gtk_primary_selection_device *) id;
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_device_manager
+ *
+ * Destroy the primary selection device manager.
+ */
+static inline void
+gtk_primary_selection_device_manager_destroy(struct gtk_primary_selection_device_manager *gtk_primary_selection_device_manager)
+{
+ wl_proxy_marshal((struct wl_proxy *) gtk_primary_selection_device_manager,
+ GTK_PRIMARY_SELECTION_DEVICE_MANAGER_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) gtk_primary_selection_device_manager);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ * @struct gtk_primary_selection_device_listener
+ */
+struct gtk_primary_selection_device_listener {
+ /**
+ * introduce a new wp_primary_selection_offer
+ *
+ * Introduces a new wp_primary_selection_offer object that may be
+ * used to receive the current primary selection. Immediately
+ * following this event, the new wp_primary_selection_offer object
+ * will send wp_primary_selection_offer.offer events to describe
+ * the offered mime types.
+ */
+ void (*data_offer)(void *data,
+ struct gtk_primary_selection_device *gtk_primary_selection_device,
+ struct gtk_primary_selection_offer *offer);
+ /**
+ * advertise a new primary selection
+ *
+ * The wp_primary_selection_device.selection event is sent to
+ * notify the client of a new primary selection. This event is sent
+ * after the wp_primary_selection.data_offer event introducing this
+ * object, and after the offer has announced its mimetypes through
+ * wp_primary_selection_offer.offer.
+ *
+ * The data_offer is valid until a new offer or NULL is received or
+ * until the client loses keyboard focus. The client must destroy
+ * the previous selection data_offer, if any, upon receiving this
+ * event.
+ */
+ void (*selection)(void *data,
+ struct gtk_primary_selection_device *gtk_primary_selection_device,
+ struct gtk_primary_selection_offer *id);
+};
+
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ */
+static inline int
+gtk_primary_selection_device_add_listener(struct gtk_primary_selection_device *gtk_primary_selection_device,
+ const struct gtk_primary_selection_device_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) gtk_primary_selection_device,
+ (void (**)(void)) listener, data);
+}
+
+#define GTK_PRIMARY_SELECTION_DEVICE_SET_SELECTION 0
+#define GTK_PRIMARY_SELECTION_DEVICE_DESTROY 1
+
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ */
+#define GTK_PRIMARY_SELECTION_DEVICE_DATA_OFFER_SINCE_VERSION 1
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ */
+#define GTK_PRIMARY_SELECTION_DEVICE_SELECTION_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ */
+#define GTK_PRIMARY_SELECTION_DEVICE_SET_SELECTION_SINCE_VERSION 1
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ */
+#define GTK_PRIMARY_SELECTION_DEVICE_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_gtk_primary_selection_device */
+static inline void
+gtk_primary_selection_device_set_user_data(struct gtk_primary_selection_device *gtk_primary_selection_device, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) gtk_primary_selection_device, user_data);
+}
+
+/** @ingroup iface_gtk_primary_selection_device */
+static inline void *
+gtk_primary_selection_device_get_user_data(struct gtk_primary_selection_device *gtk_primary_selection_device)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) gtk_primary_selection_device);
+}
+
+static inline uint32_t
+gtk_primary_selection_device_get_version(struct gtk_primary_selection_device *gtk_primary_selection_device)
+{
+ return wl_proxy_get_version((struct wl_proxy *) gtk_primary_selection_device);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ *
+ * Replaces the current selection. The previous owner of the primary selection
+ * will receive a wp_primary_selection_source.cancelled event.
+ *
+ * To unset the selection, set the source to NULL.
+ */
+static inline void
+gtk_primary_selection_device_set_selection(struct gtk_primary_selection_device *gtk_primary_selection_device, struct gtk_primary_selection_source *source, uint32_t serial)
+{
+ wl_proxy_marshal((struct wl_proxy *) gtk_primary_selection_device,
+ GTK_PRIMARY_SELECTION_DEVICE_SET_SELECTION, source, serial);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ *
+ * Destroy the primary selection device.
+ */
+static inline void
+gtk_primary_selection_device_destroy(struct gtk_primary_selection_device *gtk_primary_selection_device)
+{
+ wl_proxy_marshal((struct wl_proxy *) gtk_primary_selection_device,
+ GTK_PRIMARY_SELECTION_DEVICE_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) gtk_primary_selection_device);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_offer
+ * @struct gtk_primary_selection_offer_listener
+ */
+struct gtk_primary_selection_offer_listener {
+ /**
+ * advertise offered mime type
+ *
+ * Sent immediately after creating announcing the
+ * wp_primary_selection_offer through
+ * wp_primary_selection_device.data_offer. One event is sent per
+ * offered mime type.
+ */
+ void (*offer)(void *data,
+ struct gtk_primary_selection_offer *gtk_primary_selection_offer,
+ const char *mime_type);
+};
+
+/**
+ * @ingroup iface_gtk_primary_selection_offer
+ */
+static inline int
+gtk_primary_selection_offer_add_listener(struct gtk_primary_selection_offer *gtk_primary_selection_offer,
+ const struct gtk_primary_selection_offer_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) gtk_primary_selection_offer,
+ (void (**)(void)) listener, data);
+}
+
+#define GTK_PRIMARY_SELECTION_OFFER_RECEIVE 0
+#define GTK_PRIMARY_SELECTION_OFFER_DESTROY 1
+
+/**
+ * @ingroup iface_gtk_primary_selection_offer
+ */
+#define GTK_PRIMARY_SELECTION_OFFER_OFFER_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_gtk_primary_selection_offer
+ */
+#define GTK_PRIMARY_SELECTION_OFFER_RECEIVE_SINCE_VERSION 1
+/**
+ * @ingroup iface_gtk_primary_selection_offer
+ */
+#define GTK_PRIMARY_SELECTION_OFFER_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_gtk_primary_selection_offer */
+static inline void
+gtk_primary_selection_offer_set_user_data(struct gtk_primary_selection_offer *gtk_primary_selection_offer, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) gtk_primary_selection_offer, user_data);
+}
+
+/** @ingroup iface_gtk_primary_selection_offer */
+static inline void *
+gtk_primary_selection_offer_get_user_data(struct gtk_primary_selection_offer *gtk_primary_selection_offer)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) gtk_primary_selection_offer);
+}
+
+static inline uint32_t
+gtk_primary_selection_offer_get_version(struct gtk_primary_selection_offer *gtk_primary_selection_offer)
+{
+ return wl_proxy_get_version((struct wl_proxy *) gtk_primary_selection_offer);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_offer
+ *
+ * To transfer the contents of the primary selection clipboard, the client
+ * issues this request and indicates the mime type that it wants to
+ * receive. The transfer happens through the passed file descriptor
+ * (typically created with the pipe system call). The source client writes
+ * the data in the mime type representation requested and then closes the
+ * file descriptor.
+ *
+ * The receiving client reads from the read end of the pipe until EOF and
+ * closes its end, at which point the transfer is complete.
+ */
+static inline void
+gtk_primary_selection_offer_receive(struct gtk_primary_selection_offer *gtk_primary_selection_offer, const char *mime_type, int32_t fd)
+{
+ wl_proxy_marshal((struct wl_proxy *) gtk_primary_selection_offer,
+ GTK_PRIMARY_SELECTION_OFFER_RECEIVE, mime_type, fd);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_offer
+ *
+ * Destroy the primary selection offer.
+ */
+static inline void
+gtk_primary_selection_offer_destroy(struct gtk_primary_selection_offer *gtk_primary_selection_offer)
+{
+ wl_proxy_marshal((struct wl_proxy *) gtk_primary_selection_offer,
+ GTK_PRIMARY_SELECTION_OFFER_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) gtk_primary_selection_offer);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ * @struct gtk_primary_selection_source_listener
+ */
+struct gtk_primary_selection_source_listener {
+ /**
+ * send the primary selection contents
+ *
+ * Request for the current primary selection contents from the
+ * client. Send the specified mime type over the passed file
+ * descriptor, then close it.
+ */
+ void (*send)(void *data,
+ struct gtk_primary_selection_source *gtk_primary_selection_source,
+ const char *mime_type,
+ int32_t fd);
+ /**
+ * request for primary selection contents was canceled
+ *
+ * This primary selection source is no longer valid. The client
+ * should clean up and destroy this primary selection source.
+ */
+ void (*cancelled)(void *data,
+ struct gtk_primary_selection_source *gtk_primary_selection_source);
+};
+
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ */
+static inline int
+gtk_primary_selection_source_add_listener(struct gtk_primary_selection_source *gtk_primary_selection_source,
+ const struct gtk_primary_selection_source_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) gtk_primary_selection_source,
+ (void (**)(void)) listener, data);
+}
+
+#define GTK_PRIMARY_SELECTION_SOURCE_OFFER 0
+#define GTK_PRIMARY_SELECTION_SOURCE_DESTROY 1
+
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ */
+#define GTK_PRIMARY_SELECTION_SOURCE_SEND_SINCE_VERSION 1
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ */
+#define GTK_PRIMARY_SELECTION_SOURCE_CANCELLED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ */
+#define GTK_PRIMARY_SELECTION_SOURCE_OFFER_SINCE_VERSION 1
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ */
+#define GTK_PRIMARY_SELECTION_SOURCE_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_gtk_primary_selection_source */
+static inline void
+gtk_primary_selection_source_set_user_data(struct gtk_primary_selection_source *gtk_primary_selection_source, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) gtk_primary_selection_source, user_data);
+}
+
+/** @ingroup iface_gtk_primary_selection_source */
+static inline void *
+gtk_primary_selection_source_get_user_data(struct gtk_primary_selection_source *gtk_primary_selection_source)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) gtk_primary_selection_source);
+}
+
+static inline uint32_t
+gtk_primary_selection_source_get_version(struct gtk_primary_selection_source *gtk_primary_selection_source)
+{
+ return wl_proxy_get_version((struct wl_proxy *) gtk_primary_selection_source);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ *
+ * This request adds a mime type to the set of mime types advertised to
+ * targets. Can be called several times to offer multiple types.
+ */
+static inline void
+gtk_primary_selection_source_offer(struct gtk_primary_selection_source *gtk_primary_selection_source, const char *mime_type)
+{
+ wl_proxy_marshal((struct wl_proxy *) gtk_primary_selection_source,
+ GTK_PRIMARY_SELECTION_SOURCE_OFFER, mime_type);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ *
+ * Destroy the primary selection source.
+ */
+static inline void
+gtk_primary_selection_source_destroy(struct gtk_primary_selection_source *gtk_primary_selection_source)
+{
+ wl_proxy_marshal((struct wl_proxy *) gtk_primary_selection_source,
+ GTK_PRIMARY_SELECTION_SOURCE_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) gtk_primary_selection_source);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/gtk-primary-selection-protocol.c b/widget/gtk/wayland/gtk-primary-selection-protocol.c
new file mode 100644
index 0000000000..8a1166d2a2
--- /dev/null
+++ b/widget/gtk/wayland/gtk-primary-selection-protocol.c
@@ -0,0 +1,115 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+/*
+ * Copyright © 2015, 2016 Red Hat
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <gdk/gdkwayland.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
+#endif
+
+#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+#define WL_PRIVATE __attribute__ ((visibility("hidden")))
+#else
+#define WL_PRIVATE
+#endif
+
+extern const struct wl_interface gtk_primary_selection_device_interface;
+extern const struct wl_interface gtk_primary_selection_offer_interface;
+extern const struct wl_interface gtk_primary_selection_source_interface;
+extern const struct wl_interface wl_seat_interface;
+
+static const struct wl_interface *gtk_primary_selection_types[] = {
+ NULL,
+ NULL,
+ &gtk_primary_selection_source_interface,
+ &gtk_primary_selection_device_interface,
+ &wl_seat_interface,
+ &gtk_primary_selection_source_interface,
+ NULL,
+ &gtk_primary_selection_offer_interface,
+ &gtk_primary_selection_offer_interface,
+};
+
+static const struct wl_message gtk_primary_selection_device_manager_requests[] = {
+ { "create_source", "n", gtk_primary_selection_types + 2 },
+ { "get_device", "no", gtk_primary_selection_types + 3 },
+ { "destroy", "", gtk_primary_selection_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface gtk_primary_selection_device_manager_interface = {
+ "gtk_primary_selection_device_manager", 1,
+ 3, gtk_primary_selection_device_manager_requests,
+ 0, NULL,
+};
+
+static const struct wl_message gtk_primary_selection_device_requests[] = {
+ { "set_selection", "?ou", gtk_primary_selection_types + 5 },
+ { "destroy", "", gtk_primary_selection_types + 0 },
+};
+
+static const struct wl_message gtk_primary_selection_device_events[] = {
+ { "data_offer", "n", gtk_primary_selection_types + 7 },
+ { "selection", "?o", gtk_primary_selection_types + 8 },
+};
+
+WL_PRIVATE const struct wl_interface gtk_primary_selection_device_interface = {
+ "gtk_primary_selection_device", 1,
+ 2, gtk_primary_selection_device_requests,
+ 2, gtk_primary_selection_device_events,
+};
+
+static const struct wl_message gtk_primary_selection_offer_requests[] = {
+ { "receive", "sh", gtk_primary_selection_types + 0 },
+ { "destroy", "", gtk_primary_selection_types + 0 },
+};
+
+static const struct wl_message gtk_primary_selection_offer_events[] = {
+ { "offer", "s", gtk_primary_selection_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface gtk_primary_selection_offer_interface = {
+ "gtk_primary_selection_offer", 1,
+ 2, gtk_primary_selection_offer_requests,
+ 1, gtk_primary_selection_offer_events,
+};
+
+static const struct wl_message gtk_primary_selection_source_requests[] = {
+ { "offer", "s", gtk_primary_selection_types + 0 },
+ { "destroy", "", gtk_primary_selection_types + 0 },
+};
+
+static const struct wl_message gtk_primary_selection_source_events[] = {
+ { "send", "sh", gtk_primary_selection_types + 0 },
+ { "cancelled", "", gtk_primary_selection_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface gtk_primary_selection_source_interface = {
+ "gtk_primary_selection_source", 1,
+ 2, gtk_primary_selection_source_requests,
+ 2, gtk_primary_selection_source_events,
+};
diff --git a/widget/gtk/wayland/idle-inhibit-unstable-v1-client-protocol.h b/widget/gtk/wayland/idle-inhibit-unstable-v1-client-protocol.h
new file mode 100644
index 0000000000..3ae2303b3c
--- /dev/null
+++ b/widget/gtk/wayland/idle-inhibit-unstable-v1-client-protocol.h
@@ -0,0 +1,228 @@
+/* Generated by wayland-scanner 1.16.0 */
+
+#ifndef IDLE_INHIBIT_UNSTABLE_V1_CLIENT_PROTOCOL_H
+#define IDLE_INHIBIT_UNSTABLE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_idle_inhibit_unstable_v1 The idle_inhibit_unstable_v1 protocol
+ * @section page_ifaces_idle_inhibit_unstable_v1 Interfaces
+ * - @subpage page_iface_zwp_idle_inhibit_manager_v1 - control behavior when
+ * display idles
+ * - @subpage page_iface_zwp_idle_inhibitor_v1 - context object for inhibiting
+ * idle behavior
+ * @section page_copyright_idle_inhibit_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2015 Samsung Electronics Co., Ltd
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_surface;
+struct zwp_idle_inhibit_manager_v1;
+struct zwp_idle_inhibitor_v1;
+
+/**
+ * @page page_iface_zwp_idle_inhibit_manager_v1 zwp_idle_inhibit_manager_v1
+ * @section page_iface_zwp_idle_inhibit_manager_v1_desc Description
+ *
+ * This interface permits inhibiting the idle behavior such as screen
+ * blanking, locking, and screensaving. The client binds the idle manager
+ * globally, then creates idle-inhibitor objects for each surface.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible changes
+ * may be added together with the corresponding interface version bump.
+ * Backward incompatible changes are done by bumping the version number in
+ * the protocol and interface names and resetting the interface version.
+ * Once the protocol is to be declared stable, the 'z' prefix and the
+ * version number in the protocol and interface names are removed and the
+ * interface version number is reset.
+ * @section page_iface_zwp_idle_inhibit_manager_v1_api API
+ * See @ref iface_zwp_idle_inhibit_manager_v1.
+ */
+/**
+ * @defgroup iface_zwp_idle_inhibit_manager_v1 The zwp_idle_inhibit_manager_v1
+ * interface
+ *
+ * This interface permits inhibiting the idle behavior such as screen
+ * blanking, locking, and screensaving. The client binds the idle manager
+ * globally, then creates idle-inhibitor objects for each surface.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible changes
+ * may be added together with the corresponding interface version bump.
+ * Backward incompatible changes are done by bumping the version number in
+ * the protocol and interface names and resetting the interface version.
+ * Once the protocol is to be declared stable, the 'z' prefix and the
+ * version number in the protocol and interface names are removed and the
+ * interface version number is reset.
+ */
+extern const struct wl_interface zwp_idle_inhibit_manager_v1_interface;
+/**
+ * @page page_iface_zwp_idle_inhibitor_v1 zwp_idle_inhibitor_v1
+ * @section page_iface_zwp_idle_inhibitor_v1_desc Description
+ *
+ * An idle inhibitor prevents the output that the associated surface is
+ * visible on from being set to a state where it is not visually usable due
+ * to lack of user interaction (e.g. blanked, dimmed, locked, set to power
+ * save, etc.) Any screensaver processes are also blocked from displaying.
+ *
+ * If the surface is destroyed, unmapped, becomes occluded, loses
+ * visibility, or otherwise becomes not visually relevant for the user, the
+ * idle inhibitor will not be honored by the compositor; if the surface
+ * subsequently regains visibility the inhibitor takes effect once again.
+ * Likewise, the inhibitor isn't honored if the system was already idled at
+ * the time the inhibitor was established, although if the system later
+ * de-idles and re-idles the inhibitor will take effect.
+ * @section page_iface_zwp_idle_inhibitor_v1_api API
+ * See @ref iface_zwp_idle_inhibitor_v1.
+ */
+/**
+ * @defgroup iface_zwp_idle_inhibitor_v1 The zwp_idle_inhibitor_v1 interface
+ *
+ * An idle inhibitor prevents the output that the associated surface is
+ * visible on from being set to a state where it is not visually usable due
+ * to lack of user interaction (e.g. blanked, dimmed, locked, set to power
+ * save, etc.) Any screensaver processes are also blocked from displaying.
+ *
+ * If the surface is destroyed, unmapped, becomes occluded, loses
+ * visibility, or otherwise becomes not visually relevant for the user, the
+ * idle inhibitor will not be honored by the compositor; if the surface
+ * subsequently regains visibility the inhibitor takes effect once again.
+ * Likewise, the inhibitor isn't honored if the system was already idled at
+ * the time the inhibitor was established, although if the system later
+ * de-idles and re-idles the inhibitor will take effect.
+ */
+extern const struct wl_interface zwp_idle_inhibitor_v1_interface;
+
+#define ZWP_IDLE_INHIBIT_MANAGER_V1_DESTROY 0
+#define ZWP_IDLE_INHIBIT_MANAGER_V1_CREATE_INHIBITOR 1
+
+/**
+ * @ingroup iface_zwp_idle_inhibit_manager_v1
+ */
+#define ZWP_IDLE_INHIBIT_MANAGER_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_idle_inhibit_manager_v1
+ */
+#define ZWP_IDLE_INHIBIT_MANAGER_V1_CREATE_INHIBITOR_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_idle_inhibit_manager_v1 */
+static inline void zwp_idle_inhibit_manager_v1_set_user_data(
+ struct zwp_idle_inhibit_manager_v1* zwp_idle_inhibit_manager_v1,
+ void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zwp_idle_inhibit_manager_v1,
+ user_data);
+}
+
+/** @ingroup iface_zwp_idle_inhibit_manager_v1 */
+static inline void* zwp_idle_inhibit_manager_v1_get_user_data(
+ struct zwp_idle_inhibit_manager_v1* zwp_idle_inhibit_manager_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zwp_idle_inhibit_manager_v1);
+}
+
+static inline uint32_t zwp_idle_inhibit_manager_v1_get_version(
+ struct zwp_idle_inhibit_manager_v1* zwp_idle_inhibit_manager_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zwp_idle_inhibit_manager_v1);
+}
+
+/**
+ * @ingroup iface_zwp_idle_inhibit_manager_v1
+ *
+ * Destroy the inhibit manager.
+ */
+static inline void zwp_idle_inhibit_manager_v1_destroy(
+ struct zwp_idle_inhibit_manager_v1* zwp_idle_inhibit_manager_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_idle_inhibit_manager_v1,
+ ZWP_IDLE_INHIBIT_MANAGER_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zwp_idle_inhibit_manager_v1);
+}
+
+/**
+ * @ingroup iface_zwp_idle_inhibit_manager_v1
+ *
+ * Create a new inhibitor object associated with the given surface.
+ */
+static inline struct zwp_idle_inhibitor_v1*
+zwp_idle_inhibit_manager_v1_create_inhibitor(
+ struct zwp_idle_inhibit_manager_v1* zwp_idle_inhibit_manager_v1,
+ struct wl_surface* surface) {
+ struct wl_proxy* id;
+
+ id = wl_proxy_marshal_constructor(
+ (struct wl_proxy*)zwp_idle_inhibit_manager_v1,
+ ZWP_IDLE_INHIBIT_MANAGER_V1_CREATE_INHIBITOR,
+ &zwp_idle_inhibitor_v1_interface, NULL, surface);
+
+ return (struct zwp_idle_inhibitor_v1*)id;
+}
+
+#define ZWP_IDLE_INHIBITOR_V1_DESTROY 0
+
+/**
+ * @ingroup iface_zwp_idle_inhibitor_v1
+ */
+#define ZWP_IDLE_INHIBITOR_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_idle_inhibitor_v1 */
+static inline void zwp_idle_inhibitor_v1_set_user_data(
+ struct zwp_idle_inhibitor_v1* zwp_idle_inhibitor_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zwp_idle_inhibitor_v1, user_data);
+}
+
+/** @ingroup iface_zwp_idle_inhibitor_v1 */
+static inline void* zwp_idle_inhibitor_v1_get_user_data(
+ struct zwp_idle_inhibitor_v1* zwp_idle_inhibitor_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zwp_idle_inhibitor_v1);
+}
+
+static inline uint32_t zwp_idle_inhibitor_v1_get_version(
+ struct zwp_idle_inhibitor_v1* zwp_idle_inhibitor_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zwp_idle_inhibitor_v1);
+}
+
+/**
+ * @ingroup iface_zwp_idle_inhibitor_v1
+ *
+ * Remove the inhibitor effect from the associated wl_surface.
+ */
+static inline void zwp_idle_inhibitor_v1_destroy(
+ struct zwp_idle_inhibitor_v1* zwp_idle_inhibitor_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_idle_inhibitor_v1,
+ ZWP_IDLE_INHIBITOR_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zwp_idle_inhibitor_v1);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/idle-inhibit-unstable-v1-protocol.c b/widget/gtk/wayland/idle-inhibit-unstable-v1-protocol.c
new file mode 100644
index 0000000000..579095e003
--- /dev/null
+++ b/widget/gtk/wayland/idle-inhibit-unstable-v1-protocol.c
@@ -0,0 +1,60 @@
+/* Generated by wayland-scanner 1.16.0 */
+
+/*
+ * Copyright © 2015 Samsung Electronics Co., Ltd
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
+#endif
+
+#pragma GCC visibility push(default)
+extern const struct wl_interface wl_surface_interface;
+extern const struct wl_interface zwp_idle_inhibitor_v1_interface;
+#pragma GCC visibility pop
+
+static const struct wl_interface* types[] = {
+ &zwp_idle_inhibitor_v1_interface,
+ &wl_surface_interface,
+};
+
+static const struct wl_message zwp_idle_inhibit_manager_v1_requests[] = {
+ {"destroy", "", types + 0},
+ {"create_inhibitor", "no", types + 0},
+};
+
+const struct wl_interface zwp_idle_inhibit_manager_v1_interface = {
+ "zwp_idle_inhibit_manager_v1", 1, 2,
+ zwp_idle_inhibit_manager_v1_requests, 0, NULL,
+};
+
+static const struct wl_message zwp_idle_inhibitor_v1_requests[] = {
+ {"destroy", "", types + 0},
+};
+
+const struct wl_interface zwp_idle_inhibitor_v1_interface = {
+ "zwp_idle_inhibitor_v1", 1, 1, zwp_idle_inhibitor_v1_requests, 0, NULL,
+};
diff --git a/widget/gtk/wayland/linux-dmabuf-unstable-v1-client-protocol.h b/widget/gtk/wayland/linux-dmabuf-unstable-v1-client-protocol.h
new file mode 100644
index 0000000000..cff0426a9c
--- /dev/null
+++ b/widget/gtk/wayland/linux-dmabuf-unstable-v1-client-protocol.h
@@ -0,0 +1,650 @@
+/* Generated by wayland-scanner 1.17.0 */
+
+#ifndef LINUX_DMABUF_UNSTABLE_V1_CLIENT_PROTOCOL_H
+#define LINUX_DMABUF_UNSTABLE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_linux_dmabuf_unstable_v1 The linux_dmabuf_unstable_v1 protocol
+ * @section page_ifaces_linux_dmabuf_unstable_v1 Interfaces
+ * - @subpage page_iface_zwp_linux_dmabuf_v1 - factory for creating dmabuf-based
+ * wl_buffers
+ * - @subpage page_iface_zwp_linux_buffer_params_v1 - parameters for creating a
+ * dmabuf-based wl_buffer
+ * @section page_copyright_linux_dmabuf_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2014, 2015 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_buffer;
+struct zwp_linux_buffer_params_v1;
+struct zwp_linux_dmabuf_v1;
+
+/**
+ * @page page_iface_zwp_linux_dmabuf_v1 zwp_linux_dmabuf_v1
+ * @section page_iface_zwp_linux_dmabuf_v1_desc Description
+ *
+ * Following the interfaces from:
+ * https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt
+ * and the Linux DRM sub-system's AddFb2 ioctl.
+ *
+ * This interface offers ways to create generic dmabuf-based
+ * wl_buffers. Immediately after a client binds to this interface,
+ * the set of supported formats and format modifiers is sent with
+ * 'format' and 'modifier' events.
+ *
+ * The following are required from clients:
+ *
+ * - Clients must ensure that either all data in the dma-buf is
+ * coherent for all subsequent read access or that coherency is
+ * correctly handled by the underlying kernel-side dma-buf
+ * implementation.
+ *
+ * - Don't make any more attachments after sending the buffer to the
+ * compositor. Making more attachments later increases the risk of
+ * the compositor not being able to use (re-import) an existing
+ * dmabuf-based wl_buffer.
+ *
+ * The underlying graphics stack must ensure the following:
+ *
+ * - The dmabuf file descriptors relayed to the server will stay valid
+ * for the whole lifetime of the wl_buffer. This means the server may
+ * at any time use those fds to import the dmabuf into any kernel
+ * sub-system that might accept it.
+ *
+ * To create a wl_buffer from one or more dmabufs, a client creates a
+ * zwp_linux_dmabuf_params_v1 object with a zwp_linux_dmabuf_v1.create_params
+ * request. All planes required by the intended format are added with
+ * the 'add' request. Finally, a 'create' or 'create_immed' request is
+ * issued, which has the following outcome depending on the import success.
+ *
+ * The 'create' request,
+ * - on success, triggers a 'created' event which provides the final
+ * wl_buffer to the client.
+ * - on failure, triggers a 'failed' event to convey that the server
+ * cannot use the dmabufs received from the client.
+ *
+ * For the 'create_immed' request,
+ * - on success, the server immediately imports the added dmabufs to
+ * create a wl_buffer. No event is sent from the server in this case.
+ * - on failure, the server can choose to either:
+ * - terminate the client by raising a fatal error.
+ * - mark the wl_buffer as failed, and send a 'failed' event to the
+ * client. If the client uses a failed wl_buffer as an argument to any
+ * request, the behaviour is compositor implementation-defined.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible changes
+ * may be added together with the corresponding interface version bump.
+ * Backward incompatible changes are done by bumping the version number in
+ * the protocol and interface names and resetting the interface version.
+ * Once the protocol is to be declared stable, the 'z' prefix and the
+ * version number in the protocol and interface names are removed and the
+ * interface version number is reset.
+ * @section page_iface_zwp_linux_dmabuf_v1_api API
+ * See @ref iface_zwp_linux_dmabuf_v1.
+ */
+/**
+ * @defgroup iface_zwp_linux_dmabuf_v1 The zwp_linux_dmabuf_v1 interface
+ *
+ * Following the interfaces from:
+ * https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt
+ * and the Linux DRM sub-system's AddFb2 ioctl.
+ *
+ * This interface offers ways to create generic dmabuf-based
+ * wl_buffers. Immediately after a client binds to this interface,
+ * the set of supported formats and format modifiers is sent with
+ * 'format' and 'modifier' events.
+ *
+ * The following are required from clients:
+ *
+ * - Clients must ensure that either all data in the dma-buf is
+ * coherent for all subsequent read access or that coherency is
+ * correctly handled by the underlying kernel-side dma-buf
+ * implementation.
+ *
+ * - Don't make any more attachments after sending the buffer to the
+ * compositor. Making more attachments later increases the risk of
+ * the compositor not being able to use (re-import) an existing
+ * dmabuf-based wl_buffer.
+ *
+ * The underlying graphics stack must ensure the following:
+ *
+ * - The dmabuf file descriptors relayed to the server will stay valid
+ * for the whole lifetime of the wl_buffer. This means the server may
+ * at any time use those fds to import the dmabuf into any kernel
+ * sub-system that might accept it.
+ *
+ * To create a wl_buffer from one or more dmabufs, a client creates a
+ * zwp_linux_dmabuf_params_v1 object with a zwp_linux_dmabuf_v1.create_params
+ * request. All planes required by the intended format are added with
+ * the 'add' request. Finally, a 'create' or 'create_immed' request is
+ * issued, which has the following outcome depending on the import success.
+ *
+ * The 'create' request,
+ * - on success, triggers a 'created' event which provides the final
+ * wl_buffer to the client.
+ * - on failure, triggers a 'failed' event to convey that the server
+ * cannot use the dmabufs received from the client.
+ *
+ * For the 'create_immed' request,
+ * - on success, the server immediately imports the added dmabufs to
+ * create a wl_buffer. No event is sent from the server in this case.
+ * - on failure, the server can choose to either:
+ * - terminate the client by raising a fatal error.
+ * - mark the wl_buffer as failed, and send a 'failed' event to the
+ * client. If the client uses a failed wl_buffer as an argument to any
+ * request, the behaviour is compositor implementation-defined.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible changes
+ * may be added together with the corresponding interface version bump.
+ * Backward incompatible changes are done by bumping the version number in
+ * the protocol and interface names and resetting the interface version.
+ * Once the protocol is to be declared stable, the 'z' prefix and the
+ * version number in the protocol and interface names are removed and the
+ * interface version number is reset.
+ */
+extern const struct wl_interface zwp_linux_dmabuf_v1_interface;
+/**
+ * @page page_iface_zwp_linux_buffer_params_v1 zwp_linux_buffer_params_v1
+ * @section page_iface_zwp_linux_buffer_params_v1_desc Description
+ *
+ * This temporary object is a collection of dmabufs and other
+ * parameters that together form a single logical buffer. The temporary
+ * object may eventually create one wl_buffer unless cancelled by
+ * destroying it before requesting 'create'.
+ *
+ * Single-planar formats only require one dmabuf, however
+ * multi-planar formats may require more than one dmabuf. For all
+ * formats, an 'add' request must be called once per plane (even if the
+ * underlying dmabuf fd is identical).
+ *
+ * You must use consecutive plane indices ('plane_idx' argument for 'add')
+ * from zero to the number of planes used by the drm_fourcc format code.
+ * All planes required by the format must be given exactly once, but can
+ * be given in any order. Each plane index can be set only once.
+ * @section page_iface_zwp_linux_buffer_params_v1_api API
+ * See @ref iface_zwp_linux_buffer_params_v1.
+ */
+/**
+ * @defgroup iface_zwp_linux_buffer_params_v1 The zwp_linux_buffer_params_v1
+ * interface
+ *
+ * This temporary object is a collection of dmabufs and other
+ * parameters that together form a single logical buffer. The temporary
+ * object may eventually create one wl_buffer unless cancelled by
+ * destroying it before requesting 'create'.
+ *
+ * Single-planar formats only require one dmabuf, however
+ * multi-planar formats may require more than one dmabuf. For all
+ * formats, an 'add' request must be called once per plane (even if the
+ * underlying dmabuf fd is identical).
+ *
+ * You must use consecutive plane indices ('plane_idx' argument for 'add')
+ * from zero to the number of planes used by the drm_fourcc format code.
+ * All planes required by the format must be given exactly once, but can
+ * be given in any order. Each plane index can be set only once.
+ */
+extern const struct wl_interface zwp_linux_buffer_params_v1_interface;
+
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ * @struct zwp_linux_dmabuf_v1_listener
+ */
+struct zwp_linux_dmabuf_v1_listener {
+ /**
+ * supported buffer format
+ *
+ * This event advertises one buffer format that the server
+ * supports. All the supported formats are advertised once when the
+ * client binds to this interface. A roundtrip after binding
+ * guarantees that the client has received all supported formats.
+ *
+ * For the definition of the format codes, see the
+ * zwp_linux_buffer_params_v1::create request.
+ *
+ * Warning: the 'format' event is likely to be deprecated and
+ * replaced with the 'modifier' event introduced in
+ * zwp_linux_dmabuf_v1 version 3, described below. Please refrain
+ * from using the information received from this event.
+ * @param format DRM_FORMAT code
+ */
+ void (*format)(void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1,
+ uint32_t format);
+ /**
+ * supported buffer format modifier
+ *
+ * This event advertises the formats that the server supports,
+ * along with the modifiers supported for each format. All the
+ * supported modifiers for all the supported formats are advertised
+ * once when the client binds to this interface. A roundtrip after
+ * binding guarantees that the client has received all supported
+ * format-modifier pairs.
+ *
+ * For the definition of the format and modifier codes, see the
+ * zwp_linux_buffer_params_v1::create request.
+ * @param format DRM_FORMAT code
+ * @param modifier_hi high 32 bits of layout modifier
+ * @param modifier_lo low 32 bits of layout modifier
+ * @since 3
+ */
+ void (*modifier)(void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1,
+ uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo);
+};
+
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ */
+static inline int zwp_linux_dmabuf_v1_add_listener(
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1,
+ const struct zwp_linux_dmabuf_v1_listener* listener, void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*)zwp_linux_dmabuf_v1,
+ (void (**)(void))listener, data);
+}
+
+#define ZWP_LINUX_DMABUF_V1_DESTROY 0
+#define ZWP_LINUX_DMABUF_V1_CREATE_PARAMS 1
+
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ */
+#define ZWP_LINUX_DMABUF_V1_FORMAT_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ */
+#define ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION 3
+
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ */
+#define ZWP_LINUX_DMABUF_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ */
+#define ZWP_LINUX_DMABUF_V1_CREATE_PARAMS_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_linux_dmabuf_v1 */
+static inline void zwp_linux_dmabuf_v1_set_user_data(
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zwp_linux_dmabuf_v1, user_data);
+}
+
+/** @ingroup iface_zwp_linux_dmabuf_v1 */
+static inline void* zwp_linux_dmabuf_v1_get_user_data(
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zwp_linux_dmabuf_v1);
+}
+
+static inline uint32_t zwp_linux_dmabuf_v1_get_version(
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zwp_linux_dmabuf_v1);
+}
+
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ *
+ * Objects created through this interface, especially wl_buffers, will
+ * remain valid.
+ */
+static inline void zwp_linux_dmabuf_v1_destroy(
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_linux_dmabuf_v1,
+ ZWP_LINUX_DMABUF_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zwp_linux_dmabuf_v1);
+}
+
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ *
+ * This temporary object is used to collect multiple dmabuf handles into
+ * a single batch to create a wl_buffer. It can only be used once and
+ * should be destroyed after a 'created' or 'failed' event has been
+ * received.
+ */
+static inline struct zwp_linux_buffer_params_v1*
+zwp_linux_dmabuf_v1_create_params(
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1) {
+ struct wl_proxy* params_id;
+
+ params_id = wl_proxy_marshal_constructor(
+ (struct wl_proxy*)zwp_linux_dmabuf_v1, ZWP_LINUX_DMABUF_V1_CREATE_PARAMS,
+ &zwp_linux_buffer_params_v1_interface, NULL);
+
+ return (struct zwp_linux_buffer_params_v1*)params_id;
+}
+
+#ifndef ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM
+# define ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM
+enum zwp_linux_buffer_params_v1_error {
+ /**
+ * the dmabuf_batch object has already been used to create a wl_buffer
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED = 0,
+ /**
+ * plane index out of bounds
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX = 1,
+ /**
+ * the plane index was already set
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET = 2,
+ /**
+ * missing or too many planes to create a buffer
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE = 3,
+ /**
+ * format not supported
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT = 4,
+ /**
+ * invalid width or height
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS = 5,
+ /**
+ * offset + stride * height goes out of dmabuf bounds
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS = 6,
+ /**
+ * invalid wl_buffer resulted from importing dmabufs via the
+ * create_immed request on given buffer_params
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER = 7,
+};
+#endif /* ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM */
+
+#ifndef ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM
+# define ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM
+enum zwp_linux_buffer_params_v1_flags {
+ /**
+ * contents are y-inverted
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT = 1,
+ /**
+ * content is interlaced
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_INTERLACED = 2,
+ /**
+ * bottom field first
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_BOTTOM_FIRST = 4,
+};
+#endif /* ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM */
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ * @struct zwp_linux_buffer_params_v1_listener
+ */
+struct zwp_linux_buffer_params_v1_listener {
+ /**
+ * buffer creation succeeded
+ *
+ * This event indicates that the attempted buffer creation was
+ * successful. It provides the new wl_buffer referencing the
+ * dmabuf(s).
+ *
+ * Upon receiving this event, the client should destroy the
+ * zlinux_dmabuf_params object.
+ * @param buffer the newly created wl_buffer
+ */
+ void (*created)(void* data,
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1,
+ struct wl_buffer* buffer);
+ /**
+ * buffer creation failed
+ *
+ * This event indicates that the attempted buffer creation has
+ * failed. It usually means that one of the dmabuf constraints has
+ * not been fulfilled.
+ *
+ * Upon receiving this event, the client should destroy the
+ * zlinux_buffer_params object.
+ */
+ void (*failed)(void* data,
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1);
+};
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ */
+static inline int zwp_linux_buffer_params_v1_add_listener(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1,
+ const struct zwp_linux_buffer_params_v1_listener* listener, void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*)zwp_linux_buffer_params_v1,
+ (void (**)(void))listener, data);
+}
+
+#define ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY 0
+#define ZWP_LINUX_BUFFER_PARAMS_V1_ADD 1
+#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATE 2
+#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED 3
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ */
+#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATED_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ */
+#define ZWP_LINUX_BUFFER_PARAMS_V1_FAILED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ */
+#define ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ */
+#define ZWP_LINUX_BUFFER_PARAMS_V1_ADD_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ */
+#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ */
+#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED_SINCE_VERSION 2
+
+/** @ingroup iface_zwp_linux_buffer_params_v1 */
+static inline void zwp_linux_buffer_params_v1_set_user_data(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1,
+ void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zwp_linux_buffer_params_v1,
+ user_data);
+}
+
+/** @ingroup iface_zwp_linux_buffer_params_v1 */
+static inline void* zwp_linux_buffer_params_v1_get_user_data(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zwp_linux_buffer_params_v1);
+}
+
+static inline uint32_t zwp_linux_buffer_params_v1_get_version(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zwp_linux_buffer_params_v1);
+}
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ *
+ * Cleans up the temporary data sent to the server for dmabuf-based
+ * wl_buffer creation.
+ */
+static inline void zwp_linux_buffer_params_v1_destroy(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_linux_buffer_params_v1,
+ ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zwp_linux_buffer_params_v1);
+}
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ *
+ * This request adds one dmabuf to the set in this
+ * zwp_linux_buffer_params_v1.
+ *
+ * The 64-bit unsigned value combined from modifier_hi and modifier_lo
+ * is the dmabuf layout modifier. DRM AddFB2 ioctl calls this the
+ * fb modifier, which is defined in drm_mode.h of Linux UAPI.
+ * This is an opaque token. Drivers use this token to express tiling,
+ * compression, etc. driver-specific modifications to the base format
+ * defined by the DRM fourcc code.
+ *
+ * This request raises the PLANE_IDX error if plane_idx is too large.
+ * The error PLANE_SET is raised if attempting to set a plane that
+ * was already set.
+ */
+static inline void zwp_linux_buffer_params_v1_add(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1, int32_t fd,
+ uint32_t plane_idx, uint32_t offset, uint32_t stride, uint32_t modifier_hi,
+ uint32_t modifier_lo) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_linux_buffer_params_v1,
+ ZWP_LINUX_BUFFER_PARAMS_V1_ADD, fd, plane_idx, offset,
+ stride, modifier_hi, modifier_lo);
+}
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ *
+ * This asks for creation of a wl_buffer from the added dmabuf
+ * buffers. The wl_buffer is not created immediately but returned via
+ * the 'created' event if the dmabuf sharing succeeds. The sharing
+ * may fail at runtime for reasons a client cannot predict, in
+ * which case the 'failed' event is triggered.
+ *
+ * The 'format' argument is a DRM_FORMAT code, as defined by the
+ * libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the
+ * authoritative source on how the format codes should work.
+ *
+ * The 'flags' is a bitfield of the flags defined in enum "flags".
+ * 'y_invert' means the that the image needs to be y-flipped.
+ *
+ * Flag 'interlaced' means that the frame in the buffer is not
+ * progressive as usual, but interlaced. An interlaced buffer as
+ * supported here must always contain both top and bottom fields.
+ * The top field always begins on the first pixel row. The temporal
+ * ordering between the two fields is top field first, unless
+ * 'bottom_first' is specified. It is undefined whether 'bottom_first'
+ * is ignored if 'interlaced' is not set.
+ *
+ * This protocol does not convey any information about field rate,
+ * duration, or timing, other than the relative ordering between the
+ * two fields in one buffer. A compositor may have to estimate the
+ * intended field rate from the incoming buffer rate. It is undefined
+ * whether the time of receiving wl_surface.commit with a new buffer
+ * attached, applying the wl_surface state, wl_surface.frame callback
+ * trigger, presentation, or any other point in the compositor cycle
+ * is used to measure the frame or field times. There is no support
+ * for detecting missed or late frames/fields/buffers either, and
+ * there is no support whatsoever for cooperating with interlaced
+ * compositor output.
+ *
+ * The composited image quality resulting from the use of interlaced
+ * buffers is explicitly undefined. A compositor may use elaborate
+ * hardware features or software to deinterlace and create progressive
+ * output frames from a sequence of interlaced input buffers, or it
+ * may produce substandard image quality. However, compositors that
+ * cannot guarantee reasonable image quality in all cases are recommended
+ * to just reject all interlaced buffers.
+ *
+ * Any argument errors, including non-positive width or height,
+ * mismatch between the number of planes and the format, bad
+ * format, bad offset or stride, may be indicated by fatal protocol
+ * errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS,
+ * OUT_OF_BOUNDS.
+ *
+ * Dmabuf import errors in the server that are not obvious client
+ * bugs are returned via the 'failed' event as non-fatal. This
+ * allows attempting dmabuf sharing and falling back in the client
+ * if it fails.
+ *
+ * This request can be sent only once in the object's lifetime, after
+ * which the only legal request is destroy. This object should be
+ * destroyed after issuing a 'create' request. Attempting to use this
+ * object after issuing 'create' raises ALREADY_USED protocol error.
+ *
+ * It is not mandatory to issue 'create'. If a client wants to
+ * cancel the buffer creation, it can just destroy this object.
+ */
+static inline void zwp_linux_buffer_params_v1_create(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1,
+ int32_t width, int32_t height, uint32_t format, uint32_t flags) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_linux_buffer_params_v1,
+ ZWP_LINUX_BUFFER_PARAMS_V1_CREATE, width, height, format,
+ flags);
+}
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ *
+ * This asks for immediate creation of a wl_buffer by importing the
+ * added dmabufs.
+ *
+ * In case of import success, no event is sent from the server, and the
+ * wl_buffer is ready to be used by the client.
+ *
+ * Upon import failure, either of the following may happen, as seen fit
+ * by the implementation:
+ * - the client is terminated with one of the following fatal protocol
+ * errors:
+ * - INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS,
+ * in case of argument errors such as mismatch between the number
+ * of planes and the format, bad format, non-positive width or
+ * height, or bad offset or stride.
+ * - INVALID_WL_BUFFER, in case the cause for failure is unknown or
+ * plaform specific.
+ * - the server creates an invalid wl_buffer, marks it as failed and
+ * sends a 'failed' event to the client. The result of using this
+ * invalid wl_buffer as an argument in any request by the client is
+ * defined by the compositor implementation.
+ *
+ * This takes the same arguments as a 'create' request, and obeys the
+ * same restrictions.
+ */
+static inline struct wl_buffer* zwp_linux_buffer_params_v1_create_immed(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1,
+ int32_t width, int32_t height, uint32_t format, uint32_t flags) {
+ struct wl_proxy* buffer_id;
+
+ buffer_id = wl_proxy_marshal_constructor(
+ (struct wl_proxy*)zwp_linux_buffer_params_v1,
+ ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED, &wl_buffer_interface, NULL,
+ width, height, format, flags);
+
+ return (struct wl_buffer*)buffer_id;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/linux-dmabuf-unstable-v1-protocol.c b/widget/gtk/wayland/linux-dmabuf-unstable-v1-protocol.c
new file mode 100644
index 0000000000..51c1e8e575
--- /dev/null
+++ b/widget/gtk/wayland/linux-dmabuf-unstable-v1-protocol.c
@@ -0,0 +1,81 @@
+/* Generated by wayland-scanner 1.17.0 */
+
+/*
+ * Copyright © 2014, 2015 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#pragma GCC visibility push(default)
+extern const struct wl_interface wl_buffer_interface;
+extern const struct wl_interface zwp_linux_buffer_params_v1_interface;
+#pragma GCC visibility pop
+
+static const struct wl_interface* types[] = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &zwp_linux_buffer_params_v1_interface,
+ &wl_buffer_interface,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &wl_buffer_interface,
+};
+
+static const struct wl_message zwp_linux_dmabuf_v1_requests[] = {
+ {"destroy", "", types + 0},
+ {"create_params", "n", types + 6},
+};
+
+static const struct wl_message zwp_linux_dmabuf_v1_events[] = {
+ {"format", "u", types + 0},
+ {"modifier", "3uuu", types + 0},
+};
+
+const struct wl_interface zwp_linux_dmabuf_v1_interface = {
+ "zwp_linux_dmabuf_v1", 3, 2,
+ zwp_linux_dmabuf_v1_requests, 2, zwp_linux_dmabuf_v1_events,
+};
+
+static const struct wl_message zwp_linux_buffer_params_v1_requests[] = {
+ {"destroy", "", types + 0},
+ {"add", "huuuuu", types + 0},
+ {"create", "iiuu", types + 0},
+ {"create_immed", "2niiuu", types + 7},
+};
+
+static const struct wl_message zwp_linux_buffer_params_v1_events[] = {
+ {"created", "n", types + 12},
+ {"failed", "", types + 0},
+};
+
+const struct wl_interface zwp_linux_buffer_params_v1_interface = {
+ "zwp_linux_buffer_params_v1", 3, 4,
+ zwp_linux_buffer_params_v1_requests, 2, zwp_linux_buffer_params_v1_events,
+};
diff --git a/widget/gtk/wayland/moz.build b/widget/gtk/wayland/moz.build
new file mode 100644
index 0000000000..a6d4adbdc3
--- /dev/null
+++ b/widget/gtk/wayland/moz.build
@@ -0,0 +1,35 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget: Gtk")
+
+SOURCES += [
+ "gtk-primary-selection-protocol.c",
+ "idle-inhibit-unstable-v1-protocol.c",
+ "linux-dmabuf-unstable-v1-protocol.c",
+ "primary-selection-unstable-v1-protocol.c",
+ "xdg-output-unstable-v1-protocol.c",
+]
+
+EXPORTS.mozilla.widget += [
+ "gbm.h",
+ "gtk-primary-selection-client-protocol.h",
+ "idle-inhibit-unstable-v1-client-protocol.h",
+ "linux-dmabuf-unstable-v1-client-protocol.h",
+ "primary-selection-unstable-v1-client-protocol.h",
+ "va_drmcommon.h",
+ "xdg-output-unstable-v1-client-protocol.h",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+CFLAGS += CONFIG["TK_CFLAGS"]
+CXXFLAGS += CONFIG["TK_CFLAGS"]
+
+CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/widget/gtk/wayland/primary-selection-unstable-v1-client-protocol.h b/widget/gtk/wayland/primary-selection-unstable-v1-client-protocol.h
new file mode 100644
index 0000000000..998266c3db
--- /dev/null
+++ b/widget/gtk/wayland/primary-selection-unstable-v1-client-protocol.h
@@ -0,0 +1,578 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+#ifndef WP_PRIMARY_SELECTION_UNSTABLE_V1_CLIENT_PROTOCOL_H
+#define WP_PRIMARY_SELECTION_UNSTABLE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_wp_primary_selection_unstable_v1 The wp_primary_selection_unstable_v1 protocol
+ * Primary selection protocol
+ *
+ * @section page_desc_wp_primary_selection_unstable_v1 Description
+ *
+ * This protocol provides the ability to have a primary selection device to
+ * match that of the X server. This primary selection is a shortcut to the
+ * common clipboard selection, where text just needs to be selected in order
+ * to allow copying it elsewhere. The de facto way to perform this action
+ * is the middle mouse button, although it is not limited to this one.
+ *
+ * Clients wishing to honor primary selection should create a primary
+ * selection source and set it as the selection through
+ * wp_primary_selection_device.set_selection whenever the text selection
+ * changes. In order to minimize calls in pointer-driven text selection,
+ * it should happen only once after the operation finished. Similarly,
+ * a NULL source should be set when text is unselected.
+ *
+ * wp_primary_selection_offer objects are first announced through the
+ * wp_primary_selection_device.data_offer event. Immediately after this event,
+ * the primary data offer will emit wp_primary_selection_offer.offer events
+ * to let know of the mime types being offered.
+ *
+ * When the primary selection changes, the client with the keyboard focus
+ * will receive wp_primary_selection_device.selection events. Only the client
+ * with the keyboard focus will receive such events with a non-NULL
+ * wp_primary_selection_offer. Across keyboard focus changes, previously
+ * focused clients will receive wp_primary_selection_device.events with a
+ * NULL wp_primary_selection_offer.
+ *
+ * In order to request the primary selection data, the client must pass
+ * a recent serial pertaining to the press event that is triggering the
+ * operation, if the compositor deems the serial valid and recent, the
+ * wp_primary_selection_source.send event will happen in the other end
+ * to let the transfer begin. The client owning the primary selection
+ * should write the requested data, and close the file descriptor
+ * immediately.
+ *
+ * If the primary selection owner client disappeared during the transfer,
+ * the client reading the data will receive a
+ * wp_primary_selection_device.selection event with a NULL
+ * wp_primary_selection_offer, the client should take this as a hint
+ * to finish the reads related to the no longer existing offer.
+ *
+ * The primary selection owner should be checking for errors during
+ * writes, merely cancelling the ongoing transfer if any happened.
+ *
+ * @section page_ifaces_wp_primary_selection_unstable_v1 Interfaces
+ * - @subpage page_iface_zwp_primary_selection_device_manager_v1 - X primary selection emulation
+ * - @subpage page_iface_zwp_primary_selection_device_v1 -
+ * - @subpage page_iface_zwp_primary_selection_offer_v1 - offer to transfer primary selection contents
+ * - @subpage page_iface_zwp_primary_selection_source_v1 - offer to replace the contents of the primary selection
+ * @section page_copyright_wp_primary_selection_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2015, 2016 Red Hat
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_seat;
+struct zwp_primary_selection_device_manager_v1;
+struct zwp_primary_selection_device_v1;
+struct zwp_primary_selection_offer_v1;
+struct zwp_primary_selection_source_v1;
+
+/**
+ * @page page_iface_zwp_primary_selection_device_manager_v1 zwp_primary_selection_device_manager_v1
+ * @section page_iface_zwp_primary_selection_device_manager_v1_desc Description
+ *
+ * The primary selection device manager is a singleton global object that
+ * provides access to the primary selection. It allows to create
+ * wp_primary_selection_source objects, as well as retrieving the per-seat
+ * wp_primary_selection_device objects.
+ * @section page_iface_zwp_primary_selection_device_manager_v1_api API
+ * See @ref iface_zwp_primary_selection_device_manager_v1.
+ */
+/**
+ * @defgroup iface_zwp_primary_selection_device_manager_v1 The zwp_primary_selection_device_manager_v1 interface
+ *
+ * The primary selection device manager is a singleton global object that
+ * provides access to the primary selection. It allows to create
+ * wp_primary_selection_source objects, as well as retrieving the per-seat
+ * wp_primary_selection_device objects.
+ */
+extern const struct wl_interface zwp_primary_selection_device_manager_v1_interface;
+/**
+ * @page page_iface_zwp_primary_selection_device_v1 zwp_primary_selection_device_v1
+ * @section page_iface_zwp_primary_selection_device_v1_api API
+ * See @ref iface_zwp_primary_selection_device_v1.
+ */
+/**
+ * @defgroup iface_zwp_primary_selection_device_v1 The zwp_primary_selection_device_v1 interface
+ */
+extern const struct wl_interface zwp_primary_selection_device_v1_interface;
+/**
+ * @page page_iface_zwp_primary_selection_offer_v1 zwp_primary_selection_offer_v1
+ * @section page_iface_zwp_primary_selection_offer_v1_desc Description
+ *
+ * A wp_primary_selection_offer represents an offer to transfer the contents
+ * of the primary selection clipboard to the client. Similar to
+ * wl_data_offer, the offer also describes the mime types that the data can
+ * be converted to and provides the mechanisms for transferring the data
+ * directly to the client.
+ * @section page_iface_zwp_primary_selection_offer_v1_api API
+ * See @ref iface_zwp_primary_selection_offer_v1.
+ */
+/**
+ * @defgroup iface_zwp_primary_selection_offer_v1 The zwp_primary_selection_offer_v1 interface
+ *
+ * A wp_primary_selection_offer represents an offer to transfer the contents
+ * of the primary selection clipboard to the client. Similar to
+ * wl_data_offer, the offer also describes the mime types that the data can
+ * be converted to and provides the mechanisms for transferring the data
+ * directly to the client.
+ */
+extern const struct wl_interface zwp_primary_selection_offer_v1_interface;
+/**
+ * @page page_iface_zwp_primary_selection_source_v1 zwp_primary_selection_source_v1
+ * @section page_iface_zwp_primary_selection_source_v1_desc Description
+ *
+ * The source side of a wp_primary_selection_offer, it provides a way to
+ * describe the offered data and respond to requests to transfer the
+ * requested contents of the primary selection clipboard.
+ * @section page_iface_zwp_primary_selection_source_v1_api API
+ * See @ref iface_zwp_primary_selection_source_v1.
+ */
+/**
+ * @defgroup iface_zwp_primary_selection_source_v1 The zwp_primary_selection_source_v1 interface
+ *
+ * The source side of a wp_primary_selection_offer, it provides a way to
+ * describe the offered data and respond to requests to transfer the
+ * requested contents of the primary selection clipboard.
+ */
+extern const struct wl_interface zwp_primary_selection_source_v1_interface;
+
+#define ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_CREATE_SOURCE 0
+#define ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_GET_DEVICE 1
+#define ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_DESTROY 2
+
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_manager_v1
+ */
+#define ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_CREATE_SOURCE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_primary_selection_device_manager_v1
+ */
+#define ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_GET_DEVICE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_primary_selection_device_manager_v1
+ */
+#define ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_primary_selection_device_manager_v1 */
+static inline void
+zwp_primary_selection_device_manager_v1_set_user_data(struct zwp_primary_selection_device_manager_v1 *zwp_primary_selection_device_manager_v1, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) zwp_primary_selection_device_manager_v1, user_data);
+}
+
+/** @ingroup iface_zwp_primary_selection_device_manager_v1 */
+static inline void *
+zwp_primary_selection_device_manager_v1_get_user_data(struct zwp_primary_selection_device_manager_v1 *zwp_primary_selection_device_manager_v1)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) zwp_primary_selection_device_manager_v1);
+}
+
+static inline uint32_t
+zwp_primary_selection_device_manager_v1_get_version(struct zwp_primary_selection_device_manager_v1 *zwp_primary_selection_device_manager_v1)
+{
+ return wl_proxy_get_version((struct wl_proxy *) zwp_primary_selection_device_manager_v1);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_manager_v1
+ *
+ * Create a new primary selection source.
+ */
+static inline struct zwp_primary_selection_source_v1 *
+zwp_primary_selection_device_manager_v1_create_source(struct zwp_primary_selection_device_manager_v1 *zwp_primary_selection_device_manager_v1)
+{
+ struct wl_proxy *id;
+
+ id = wl_proxy_marshal_constructor((struct wl_proxy *) zwp_primary_selection_device_manager_v1,
+ ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_CREATE_SOURCE, &zwp_primary_selection_source_v1_interface, NULL);
+
+ return (struct zwp_primary_selection_source_v1 *) id;
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_manager_v1
+ *
+ * Create a new data device for a given seat.
+ */
+static inline struct zwp_primary_selection_device_v1 *
+zwp_primary_selection_device_manager_v1_get_device(struct zwp_primary_selection_device_manager_v1 *zwp_primary_selection_device_manager_v1, struct wl_seat *seat)
+{
+ struct wl_proxy *id;
+
+ id = wl_proxy_marshal_constructor((struct wl_proxy *) zwp_primary_selection_device_manager_v1,
+ ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_GET_DEVICE, &zwp_primary_selection_device_v1_interface, NULL, seat);
+
+ return (struct zwp_primary_selection_device_v1 *) id;
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_manager_v1
+ *
+ * Destroy the primary selection device manager.
+ */
+static inline void
+zwp_primary_selection_device_manager_v1_destroy(struct zwp_primary_selection_device_manager_v1 *zwp_primary_selection_device_manager_v1)
+{
+ wl_proxy_marshal((struct wl_proxy *) zwp_primary_selection_device_manager_v1,
+ ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) zwp_primary_selection_device_manager_v1);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ * @struct zwp_primary_selection_device_v1_listener
+ */
+struct zwp_primary_selection_device_v1_listener {
+ /**
+ * introduce a new wp_primary_selection_offer
+ *
+ * Introduces a new wp_primary_selection_offer object that may be
+ * used to receive the current primary selection. Immediately
+ * following this event, the new wp_primary_selection_offer object
+ * will send wp_primary_selection_offer.offer events to describe
+ * the offered mime types.
+ */
+ void (*data_offer)(void *data,
+ struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1,
+ struct zwp_primary_selection_offer_v1 *offer);
+ /**
+ * advertise a new primary selection
+ *
+ * The wp_primary_selection_device.selection event is sent to
+ * notify the client of a new primary selection. This event is sent
+ * after the wp_primary_selection.data_offer event introducing this
+ * object, and after the offer has announced its mimetypes through
+ * wp_primary_selection_offer.offer.
+ *
+ * The data_offer is valid until a new offer or NULL is received or
+ * until the client loses keyboard focus. The client must destroy
+ * the previous selection data_offer, if any, upon receiving this
+ * event.
+ */
+ void (*selection)(void *data,
+ struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1,
+ struct zwp_primary_selection_offer_v1 *id);
+};
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ */
+static inline int
+zwp_primary_selection_device_v1_add_listener(struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1,
+ const struct zwp_primary_selection_device_v1_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) zwp_primary_selection_device_v1,
+ (void (**)(void)) listener, data);
+}
+
+#define ZWP_PRIMARY_SELECTION_DEVICE_V1_SET_SELECTION 0
+#define ZWP_PRIMARY_SELECTION_DEVICE_V1_DESTROY 1
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ */
+#define ZWP_PRIMARY_SELECTION_DEVICE_V1_DATA_OFFER_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ */
+#define ZWP_PRIMARY_SELECTION_DEVICE_V1_SELECTION_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ */
+#define ZWP_PRIMARY_SELECTION_DEVICE_V1_SET_SELECTION_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ */
+#define ZWP_PRIMARY_SELECTION_DEVICE_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_primary_selection_device_v1 */
+static inline void
+zwp_primary_selection_device_v1_set_user_data(struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) zwp_primary_selection_device_v1, user_data);
+}
+
+/** @ingroup iface_zwp_primary_selection_device_v1 */
+static inline void *
+zwp_primary_selection_device_v1_get_user_data(struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) zwp_primary_selection_device_v1);
+}
+
+static inline uint32_t
+zwp_primary_selection_device_v1_get_version(struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1)
+{
+ return wl_proxy_get_version((struct wl_proxy *) zwp_primary_selection_device_v1);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ *
+ * Replaces the current selection. The previous owner of the primary
+ * selection will receive a wp_primary_selection_source.cancelled event.
+ *
+ * To unset the selection, set the source to NULL.
+ */
+static inline void
+zwp_primary_selection_device_v1_set_selection(struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1, struct zwp_primary_selection_source_v1 *source, uint32_t serial)
+{
+ wl_proxy_marshal((struct wl_proxy *) zwp_primary_selection_device_v1,
+ ZWP_PRIMARY_SELECTION_DEVICE_V1_SET_SELECTION, source, serial);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ *
+ * Destroy the primary selection device.
+ */
+static inline void
+zwp_primary_selection_device_v1_destroy(struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1)
+{
+ wl_proxy_marshal((struct wl_proxy *) zwp_primary_selection_device_v1,
+ ZWP_PRIMARY_SELECTION_DEVICE_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) zwp_primary_selection_device_v1);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_offer_v1
+ * @struct zwp_primary_selection_offer_v1_listener
+ */
+struct zwp_primary_selection_offer_v1_listener {
+ /**
+ * advertise offered mime type
+ *
+ * Sent immediately after creating announcing the
+ * wp_primary_selection_offer through
+ * wp_primary_selection_device.data_offer. One event is sent per
+ * offered mime type.
+ */
+ void (*offer)(void *data,
+ struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1,
+ const char *mime_type);
+};
+
+/**
+ * @ingroup iface_zwp_primary_selection_offer_v1
+ */
+static inline int
+zwp_primary_selection_offer_v1_add_listener(struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1,
+ const struct zwp_primary_selection_offer_v1_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) zwp_primary_selection_offer_v1,
+ (void (**)(void)) listener, data);
+}
+
+#define ZWP_PRIMARY_SELECTION_OFFER_V1_RECEIVE 0
+#define ZWP_PRIMARY_SELECTION_OFFER_V1_DESTROY 1
+
+/**
+ * @ingroup iface_zwp_primary_selection_offer_v1
+ */
+#define ZWP_PRIMARY_SELECTION_OFFER_V1_OFFER_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwp_primary_selection_offer_v1
+ */
+#define ZWP_PRIMARY_SELECTION_OFFER_V1_RECEIVE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_primary_selection_offer_v1
+ */
+#define ZWP_PRIMARY_SELECTION_OFFER_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_primary_selection_offer_v1 */
+static inline void
+zwp_primary_selection_offer_v1_set_user_data(struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) zwp_primary_selection_offer_v1, user_data);
+}
+
+/** @ingroup iface_zwp_primary_selection_offer_v1 */
+static inline void *
+zwp_primary_selection_offer_v1_get_user_data(struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) zwp_primary_selection_offer_v1);
+}
+
+static inline uint32_t
+zwp_primary_selection_offer_v1_get_version(struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1)
+{
+ return wl_proxy_get_version((struct wl_proxy *) zwp_primary_selection_offer_v1);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_offer_v1
+ *
+ * To transfer the contents of the primary selection clipboard, the client
+ * issues this request and indicates the mime type that it wants to
+ * receive. The transfer happens through the passed file descriptor
+ * (typically created with the pipe system call). The source client writes
+ * the data in the mime type representation requested and then closes the
+ * file descriptor.
+ *
+ * The receiving client reads from the read end of the pipe until EOF and
+ * closes its end, at which point the transfer is complete.
+ */
+static inline void
+zwp_primary_selection_offer_v1_receive(struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1, const char *mime_type, int32_t fd)
+{
+ wl_proxy_marshal((struct wl_proxy *) zwp_primary_selection_offer_v1,
+ ZWP_PRIMARY_SELECTION_OFFER_V1_RECEIVE, mime_type, fd);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_offer_v1
+ *
+ * Destroy the primary selection offer.
+ */
+static inline void
+zwp_primary_selection_offer_v1_destroy(struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1)
+{
+ wl_proxy_marshal((struct wl_proxy *) zwp_primary_selection_offer_v1,
+ ZWP_PRIMARY_SELECTION_OFFER_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) zwp_primary_selection_offer_v1);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ * @struct zwp_primary_selection_source_v1_listener
+ */
+struct zwp_primary_selection_source_v1_listener {
+ /**
+ * send the primary selection contents
+ *
+ * Request for the current primary selection contents from the
+ * client. Send the specified mime type over the passed file
+ * descriptor, then close it.
+ */
+ void (*send)(void *data,
+ struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1,
+ const char *mime_type,
+ int32_t fd);
+ /**
+ * request for primary selection contents was canceled
+ *
+ * This primary selection source is no longer valid. The client
+ * should clean up and destroy this primary selection source.
+ */
+ void (*cancelled)(void *data,
+ struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1);
+};
+
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ */
+static inline int
+zwp_primary_selection_source_v1_add_listener(struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1,
+ const struct zwp_primary_selection_source_v1_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) zwp_primary_selection_source_v1,
+ (void (**)(void)) listener, data);
+}
+
+#define ZWP_PRIMARY_SELECTION_SOURCE_V1_OFFER 0
+#define ZWP_PRIMARY_SELECTION_SOURCE_V1_DESTROY 1
+
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ */
+#define ZWP_PRIMARY_SELECTION_SOURCE_V1_SEND_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ */
+#define ZWP_PRIMARY_SELECTION_SOURCE_V1_CANCELLED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ */
+#define ZWP_PRIMARY_SELECTION_SOURCE_V1_OFFER_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ */
+#define ZWP_PRIMARY_SELECTION_SOURCE_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_primary_selection_source_v1 */
+static inline void
+zwp_primary_selection_source_v1_set_user_data(struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) zwp_primary_selection_source_v1, user_data);
+}
+
+/** @ingroup iface_zwp_primary_selection_source_v1 */
+static inline void *
+zwp_primary_selection_source_v1_get_user_data(struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) zwp_primary_selection_source_v1);
+}
+
+static inline uint32_t
+zwp_primary_selection_source_v1_get_version(struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1)
+{
+ return wl_proxy_get_version((struct wl_proxy *) zwp_primary_selection_source_v1);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ *
+ * This request adds a mime type to the set of mime types advertised to
+ * targets. Can be called several times to offer multiple types.
+ */
+static inline void
+zwp_primary_selection_source_v1_offer(struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1, const char *mime_type)
+{
+ wl_proxy_marshal((struct wl_proxy *) zwp_primary_selection_source_v1,
+ ZWP_PRIMARY_SELECTION_SOURCE_V1_OFFER, mime_type);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ *
+ * Destroy the primary selection source.
+ */
+static inline void
+zwp_primary_selection_source_v1_destroy(struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1)
+{
+ wl_proxy_marshal((struct wl_proxy *) zwp_primary_selection_source_v1,
+ ZWP_PRIMARY_SELECTION_SOURCE_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) zwp_primary_selection_source_v1);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/primary-selection-unstable-v1-protocol.c b/widget/gtk/wayland/primary-selection-unstable-v1-protocol.c
new file mode 100644
index 0000000000..9f43d4d65e
--- /dev/null
+++ b/widget/gtk/wayland/primary-selection-unstable-v1-protocol.c
@@ -0,0 +1,115 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+/*
+ * Copyright © 2015, 2016 Red Hat
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <gdk/gdkwayland.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
+#endif
+
+#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+#define WL_PRIVATE __attribute__ ((visibility("hidden")))
+#else
+#define WL_PRIVATE
+#endif
+
+extern const struct wl_interface wl_seat_interface;
+extern const struct wl_interface zwp_primary_selection_device_v1_interface;
+extern const struct wl_interface zwp_primary_selection_offer_v1_interface;
+extern const struct wl_interface zwp_primary_selection_source_v1_interface;
+
+static const struct wl_interface *wp_primary_selection_unstable_v1_types[] = {
+ NULL,
+ NULL,
+ &zwp_primary_selection_source_v1_interface,
+ &zwp_primary_selection_device_v1_interface,
+ &wl_seat_interface,
+ &zwp_primary_selection_source_v1_interface,
+ NULL,
+ &zwp_primary_selection_offer_v1_interface,
+ &zwp_primary_selection_offer_v1_interface,
+};
+
+static const struct wl_message zwp_primary_selection_device_manager_v1_requests[] = {
+ { "create_source", "n", wp_primary_selection_unstable_v1_types + 2 },
+ { "get_device", "no", wp_primary_selection_unstable_v1_types + 3 },
+ { "destroy", "", wp_primary_selection_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zwp_primary_selection_device_manager_v1_interface = {
+ "zwp_primary_selection_device_manager_v1", 1,
+ 3, zwp_primary_selection_device_manager_v1_requests,
+ 0, NULL,
+};
+
+static const struct wl_message zwp_primary_selection_device_v1_requests[] = {
+ { "set_selection", "?ou", wp_primary_selection_unstable_v1_types + 5 },
+ { "destroy", "", wp_primary_selection_unstable_v1_types + 0 },
+};
+
+static const struct wl_message zwp_primary_selection_device_v1_events[] = {
+ { "data_offer", "n", wp_primary_selection_unstable_v1_types + 7 },
+ { "selection", "?o", wp_primary_selection_unstable_v1_types + 8 },
+};
+
+WL_PRIVATE const struct wl_interface zwp_primary_selection_device_v1_interface = {
+ "zwp_primary_selection_device_v1", 1,
+ 2, zwp_primary_selection_device_v1_requests,
+ 2, zwp_primary_selection_device_v1_events,
+};
+
+static const struct wl_message zwp_primary_selection_offer_v1_requests[] = {
+ { "receive", "sh", wp_primary_selection_unstable_v1_types + 0 },
+ { "destroy", "", wp_primary_selection_unstable_v1_types + 0 },
+};
+
+static const struct wl_message zwp_primary_selection_offer_v1_events[] = {
+ { "offer", "s", wp_primary_selection_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zwp_primary_selection_offer_v1_interface = {
+ "zwp_primary_selection_offer_v1", 1,
+ 2, zwp_primary_selection_offer_v1_requests,
+ 1, zwp_primary_selection_offer_v1_events,
+};
+
+static const struct wl_message zwp_primary_selection_source_v1_requests[] = {
+ { "offer", "s", wp_primary_selection_unstable_v1_types + 0 },
+ { "destroy", "", wp_primary_selection_unstable_v1_types + 0 },
+};
+
+static const struct wl_message zwp_primary_selection_source_v1_events[] = {
+ { "send", "sh", wp_primary_selection_unstable_v1_types + 0 },
+ { "cancelled", "", wp_primary_selection_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zwp_primary_selection_source_v1_interface = {
+ "zwp_primary_selection_source_v1", 1,
+ 2, zwp_primary_selection_source_v1_requests,
+ 2, zwp_primary_selection_source_v1_events,
+};
diff --git a/widget/gtk/wayland/va_drmcommon.h b/widget/gtk/wayland/va_drmcommon.h
new file mode 100644
index 0000000000..e16f244a46
--- /dev/null
+++ b/widget/gtk/wayland/va_drmcommon.h
@@ -0,0 +1,156 @@
+/*
+ * va_drmcommon.h - Common utilities for DRM-based drivers
+ *
+ * Copyright (c) 2012 Intel Corporation. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
+ * IN NO EVENT SHALL INTEL AND/OR ITS SUPPLIERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VA_DRM_COMMON_H
+#define VA_DRM_COMMON_H
+
+#include <stdint.h>
+
+/** \brief DRM authentication type. */
+enum {
+ /** \brief Disconnected. */
+ VA_DRM_AUTH_NONE = 0,
+ /**
+ * \brief Connected. Authenticated with DRI1 protocol.
+ *
+ * @deprecated
+ * This is a deprecated authentication type. All DRI-based drivers have
+ * been migrated to use the DRI2 protocol. Newly written drivers shall
+ * use DRI2 protocol only, or a custom authentication means. e.g. opt
+ * for authenticating on the VA driver side, instead of libva side.
+ */
+ VA_DRM_AUTH_DRI1 = 1,
+ /**
+ * \brief Connected. Authenticated with DRI2 protocol.
+ *
+ * This is only useful to VA/X11 drivers. The libva-x11 library provides
+ * a helper function VA_DRI2Authenticate() for authenticating the
+ * connection. However, DRI2 conformant drivers don't need to call that
+ * function since authentication happens on the libva side, implicitly.
+ */
+ VA_DRM_AUTH_DRI2 = 2,
+ /**
+ * \brief Connected. Authenticated with some alternate raw protocol.
+ *
+ * This authentication mode is mainly used in non-VA/X11 drivers.
+ * Authentication happens through some alternative method, at the
+ * discretion of the VA driver implementation.
+ */
+ VA_DRM_AUTH_CUSTOM = 3
+};
+
+/** \brief Base DRM state. */
+struct drm_state {
+ /** \brief DRM connection descriptor. */
+ int fd;
+ /** \brief DRM authentication type. */
+ int auth_type;
+ /** \brief Reserved bytes for future use, must be zero */
+ int va_reserved[8];
+};
+
+/** \brief Kernel DRM buffer memory type. */
+#define VA_SURFACE_ATTRIB_MEM_TYPE_KERNEL_DRM 0x10000000
+/** \brief DRM PRIME memory type (old version)
+ *
+ * This supports only single objects with restricted memory layout.
+ * Used with VASurfaceAttribExternalBuffers.
+ */
+#define VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME 0x20000000
+/** \brief DRM PRIME memory type
+ *
+ * Used with VADRMPRIMESurfaceDescriptor.
+ */
+#define VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 0x40000000
+
+/**
+ * \brief External buffer descriptor for a DRM PRIME surface.
+ *
+ * For export, call vaExportSurfaceHandle() with mem_type set to
+ * VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 and pass a pointer to an
+ * instance of this structure to fill.
+ * If VA_EXPORT_SURFACE_SEPARATE_LAYERS is specified on export, each
+ * layer will contain exactly one plane. For example, an NV12
+ * surface will be exported as two layers, one of DRM_FORMAT_R8 and
+ * one of DRM_FORMAT_GR88.
+ * If VA_EXPORT_SURFACE_COMPOSED_LAYERS is specified on export,
+ * there will be exactly one layer.
+ *
+ * For import, call vaCreateSurfaces() with the MemoryType attribute
+ * set to VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 and the
+ * ExternalBufferDescriptor attribute set to point to an array of
+ * num_surfaces instances of this structure.
+ * The number of planes which need to be provided for a given layer
+ * is dependent on both the format and the format modifier used for
+ * the objects containing it. For example, the format DRM_FORMAT_RGBA
+ * normally requires one plane, but with the format modifier
+ * I915_FORMAT_MOD_Y_TILED_CCS it requires two planes - the first
+ * being the main data plane and the second containing the color
+ * control surface.
+ * Note that a given driver may only support a subset of possible
+ * representations of a particular format. For example, it may only
+ * support NV12 surfaces when they are contained within a single DRM
+ * object, and therefore fail to create such surfaces if the two
+ * planes are in different DRM objects.
+ */
+typedef struct _VADRMPRIMESurfaceDescriptor {
+ /** Pixel format fourcc of the whole surface (VA_FOURCC_*). */
+ uint32_t fourcc;
+ /** Width of the surface in pixels. */
+ uint32_t width;
+ /** Height of the surface in pixels. */
+ uint32_t height;
+ /** Number of distinct DRM objects making up the surface. */
+ uint32_t num_objects;
+ /** Description of each object. */
+ struct {
+ /** DRM PRIME file descriptor for this object. */
+ int fd;
+ /** Total size of this object (may include regions which are
+ * not part of the surface). */
+ uint32_t size;
+ /** Format modifier applied to this object. */
+ uint64_t drm_format_modifier;
+ } objects[4];
+ /** Number of layers making up the surface. */
+ uint32_t num_layers;
+ /** Description of each layer in the surface. */
+ struct {
+ /** DRM format fourcc of this layer (DRM_FOURCC_*). */
+ uint32_t drm_format;
+ /** Number of planes in this layer. */
+ uint32_t num_planes;
+ /** Index in the objects array of the object containing each
+ * plane. */
+ uint32_t object_index[4];
+ /** Offset within the object of each plane. */
+ uint32_t offset[4];
+ /** Pitch of each plane. */
+ uint32_t pitch[4];
+ } layers[4];
+} VADRMPRIMESurfaceDescriptor;
+
+#endif /* VA_DRM_COMMON_H */
diff --git a/widget/gtk/wayland/xdg-output-unstable-v1-client-protocol.h b/widget/gtk/wayland/xdg-output-unstable-v1-client-protocol.h
new file mode 100644
index 0000000000..432057ccef
--- /dev/null
+++ b/widget/gtk/wayland/xdg-output-unstable-v1-client-protocol.h
@@ -0,0 +1,392 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+#ifndef XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H
+#define XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_xdg_output_unstable_v1 The xdg_output_unstable_v1 protocol
+ * Protocol to describe output regions
+ *
+ * @section page_desc_xdg_output_unstable_v1 Description
+ *
+ * This protocol aims at describing outputs in a way which is more in line
+ * with the concept of an output on desktop oriented systems.
+ *
+ * Some information are more specific to the concept of an output for
+ * a desktop oriented system and may not make sense in other applications,
+ * such as IVI systems for example.
+ *
+ * Typically, the global compositor space on a desktop system is made of
+ * a contiguous or overlapping set of rectangular regions.
+ *
+ * Some of the information provided in this protocol might be identical
+ * to their counterparts already available from wl_output, in which case
+ * the information provided by this protocol should be preferred to their
+ * equivalent in wl_output. The goal is to move the desktop specific
+ * concepts (such as output location within the global compositor space,
+ * the connector name and types, etc.) out of the core wl_output protocol.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible
+ * changes may be added together with the corresponding interface
+ * version bump.
+ * Backward incompatible changes are done by bumping the version
+ * number in the protocol and interface names and resetting the
+ * interface version. Once the protocol is to be declared stable,
+ * the 'z' prefix and the version number in the protocol and
+ * interface names are removed and the interface version number is
+ * reset.
+ *
+ * @section page_ifaces_xdg_output_unstable_v1 Interfaces
+ * - @subpage page_iface_zxdg_output_manager_v1 - manage xdg_output objects
+ * - @subpage page_iface_zxdg_output_v1 - compositor logical output region
+ * @section page_copyright_xdg_output_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2017 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_output;
+struct zxdg_output_manager_v1;
+struct zxdg_output_v1;
+
+/**
+ * @page page_iface_zxdg_output_manager_v1 zxdg_output_manager_v1
+ * @section page_iface_zxdg_output_manager_v1_desc Description
+ *
+ * A global factory interface for xdg_output objects.
+ * @section page_iface_zxdg_output_manager_v1_api API
+ * See @ref iface_zxdg_output_manager_v1.
+ */
+/**
+ * @defgroup iface_zxdg_output_manager_v1 The zxdg_output_manager_v1 interface
+ *
+ * A global factory interface for xdg_output objects.
+ */
+extern const struct wl_interface zxdg_output_manager_v1_interface;
+/**
+ * @page page_iface_zxdg_output_v1 zxdg_output_v1
+ * @section page_iface_zxdg_output_v1_desc Description
+ *
+ * An xdg_output describes part of the compositor geometry.
+ *
+ * This typically corresponds to a monitor that displays part of the
+ * compositor space.
+ *
+ * For objects version 3 onwards, after all xdg_output properties have been
+ * sent (when the object is created and when properties are updated), a
+ * wl_output.done event is sent. This allows changes to the output
+ * properties to be seen as atomic, even if they happen via multiple events.
+ * @section page_iface_zxdg_output_v1_api API
+ * See @ref iface_zxdg_output_v1.
+ */
+/**
+ * @defgroup iface_zxdg_output_v1 The zxdg_output_v1 interface
+ *
+ * An xdg_output describes part of the compositor geometry.
+ *
+ * This typically corresponds to a monitor that displays part of the
+ * compositor space.
+ *
+ * For objects version 3 onwards, after all xdg_output properties have been
+ * sent (when the object is created and when properties are updated), a
+ * wl_output.done event is sent. This allows changes to the output
+ * properties to be seen as atomic, even if they happen via multiple events.
+ */
+extern const struct wl_interface zxdg_output_v1_interface;
+
+#define ZXDG_OUTPUT_MANAGER_V1_DESTROY 0
+#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT 1
+
+/**
+ * @ingroup iface_zxdg_output_manager_v1
+ */
+#define ZXDG_OUTPUT_MANAGER_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_output_manager_v1
+ */
+#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT_SINCE_VERSION 1
+
+/** @ingroup iface_zxdg_output_manager_v1 */
+static inline void zxdg_output_manager_v1_set_user_data(
+ struct zxdg_output_manager_v1* zxdg_output_manager_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zxdg_output_manager_v1, user_data);
+}
+
+/** @ingroup iface_zxdg_output_manager_v1 */
+static inline void* zxdg_output_manager_v1_get_user_data(
+ struct zxdg_output_manager_v1* zxdg_output_manager_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zxdg_output_manager_v1);
+}
+
+static inline uint32_t zxdg_output_manager_v1_get_version(
+ struct zxdg_output_manager_v1* zxdg_output_manager_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zxdg_output_manager_v1);
+}
+
+/**
+ * @ingroup iface_zxdg_output_manager_v1
+ *
+ * Using this request a client can tell the server that it is not
+ * going to use the xdg_output_manager object anymore.
+ *
+ * Any objects already created through this instance are not affected.
+ */
+static inline void zxdg_output_manager_v1_destroy(
+ struct zxdg_output_manager_v1* zxdg_output_manager_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zxdg_output_manager_v1,
+ ZXDG_OUTPUT_MANAGER_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zxdg_output_manager_v1);
+}
+
+/**
+ * @ingroup iface_zxdg_output_manager_v1
+ *
+ * This creates a new xdg_output object for the given wl_output.
+ */
+static inline struct zxdg_output_v1* zxdg_output_manager_v1_get_xdg_output(
+ struct zxdg_output_manager_v1* zxdg_output_manager_v1,
+ struct wl_output* output) {
+ struct wl_proxy* id;
+
+ id = wl_proxy_marshal_constructor((struct wl_proxy*)zxdg_output_manager_v1,
+ ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT,
+ &zxdg_output_v1_interface, NULL, output);
+
+ return (struct zxdg_output_v1*)id;
+}
+
+/**
+ * @ingroup iface_zxdg_output_v1
+ * @struct zxdg_output_v1_listener
+ */
+struct zxdg_output_v1_listener {
+ /**
+ * position of the output within the global compositor space
+ *
+ * The position event describes the location of the wl_output
+ * within the global compositor space.
+ *
+ * The logical_position event is sent after creating an xdg_output
+ * (see xdg_output_manager.get_xdg_output) and whenever the
+ * location of the output changes within the global compositor
+ * space.
+ * @param x x position within the global compositor space
+ * @param y y position within the global compositor space
+ */
+ void (*logical_position)(void* data, struct zxdg_output_v1* zxdg_output_v1,
+ int32_t x, int32_t y);
+ /**
+ * size of the output in the global compositor space
+ *
+ * The logical_size event describes the size of the output in the
+ * global compositor space.
+ *
+ * For example, a surface without any buffer scale, transformation
+ * nor rotation set, with the size matching the logical_size will
+ * have the same size as the corresponding output when displayed.
+ *
+ * Most regular Wayland clients should not pay attention to the
+ * logical size and would rather rely on xdg_shell interfaces.
+ *
+ * Some clients such as Xwayland, however, need this to configure
+ * their surfaces in the global compositor space as the compositor
+ * may apply a different scale from what is advertised by the
+ * output scaling property (to achieve fractional scaling, for
+ * example).
+ *
+ * For example, for a wl_output mode 3840×2160 and a scale factor
+ * 2:
+ *
+ * - A compositor not scaling the surface buffers will advertise a
+ * logical size of 3840×2160,
+ *
+ * - A compositor automatically scaling the surface buffers will
+ * advertise a logical size of 1920×1080,
+ *
+ * - A compositor using a fractional scale of 1.5 will advertise a
+ * logical size to 2560×1620.
+ *
+ * For example, for a wl_output mode 1920×1080 and a 90 degree
+ * rotation, the compositor will advertise a logical size of
+ * 1080x1920.
+ *
+ * The logical_size event is sent after creating an xdg_output (see
+ * xdg_output_manager.get_xdg_output) and whenever the logical size
+ * of the output changes, either as a result of a change in the
+ * applied scale or because of a change in the corresponding output
+ * mode(see wl_output.mode) or transform (see wl_output.transform).
+ * @param width width in global compositor space
+ * @param height height in global compositor space
+ */
+ void (*logical_size)(void* data, struct zxdg_output_v1* zxdg_output_v1,
+ int32_t width, int32_t height);
+ /**
+ * all information about the output have been sent
+ *
+ * This event is sent after all other properties of an xdg_output
+ * have been sent.
+ *
+ * This allows changes to the xdg_output properties to be seen as
+ * atomic, even if they happen via multiple events.
+ *
+ * For objects version 3 onwards, this event is deprecated.
+ * Compositors are not required to send it anymore and must send
+ * wl_output.done instead.
+ */
+ void (*done)(void* data, struct zxdg_output_v1* zxdg_output_v1);
+ /**
+ * name of this output
+ *
+ * Many compositors will assign names to their outputs, show them
+ * to the user, allow them to be configured by name, etc. The
+ * client may wish to know this name as well to offer the user
+ * similar behaviors.
+ *
+ * The naming convention is compositor defined, but limited to
+ * alphanumeric characters and dashes (-). Each name is unique
+ * among all wl_output globals, but if a wl_output global is
+ * destroyed the same name may be reused later. The names will also
+ * remain consistent across sessions with the same hardware and
+ * software configuration.
+ *
+ * Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc.
+ * However, do not assume that the name is a reflection of an
+ * underlying DRM connector, X11 connection, etc.
+ *
+ * The name event is sent after creating an xdg_output (see
+ * xdg_output_manager.get_xdg_output). This event is only sent once
+ * per xdg_output, and the name does not change over the lifetime
+ * of the wl_output global.
+ * @param name output name
+ * @since 2
+ */
+ void (*name)(void* data, struct zxdg_output_v1* zxdg_output_v1,
+ const char* name);
+ /**
+ * human-readable description of this output
+ *
+ * Many compositors can produce human-readable descriptions of
+ * their outputs. The client may wish to know this description as
+ * well, to communicate the user for various purposes.
+ *
+ * The description is a UTF-8 string with no convention defined for
+ * its contents. Examples might include 'Foocorp 11" Display' or
+ * 'Virtual X11 output via :1'.
+ *
+ * The description event is sent after creating an xdg_output (see
+ * xdg_output_manager.get_xdg_output) and whenever the description
+ * changes. The description is optional, and may not be sent at
+ * all.
+ *
+ * For objects of version 2 and lower, this event is only sent once
+ * per xdg_output, and the description does not change over the
+ * lifetime of the wl_output global.
+ * @param description output description
+ * @since 2
+ */
+ void (*description)(void* data, struct zxdg_output_v1* zxdg_output_v1,
+ const char* description);
+};
+
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+static inline int zxdg_output_v1_add_listener(
+ struct zxdg_output_v1* zxdg_output_v1,
+ const struct zxdg_output_v1_listener* listener, void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*)zxdg_output_v1,
+ (void (**)(void))listener, data);
+}
+
+#define ZXDG_OUTPUT_V1_DESTROY 0
+
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_LOGICAL_POSITION_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_LOGICAL_SIZE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_DONE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_NAME_SINCE_VERSION 2
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION 2
+
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zxdg_output_v1 */
+static inline void zxdg_output_v1_set_user_data(
+ struct zxdg_output_v1* zxdg_output_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zxdg_output_v1, user_data);
+}
+
+/** @ingroup iface_zxdg_output_v1 */
+static inline void* zxdg_output_v1_get_user_data(
+ struct zxdg_output_v1* zxdg_output_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zxdg_output_v1);
+}
+
+static inline uint32_t zxdg_output_v1_get_version(
+ struct zxdg_output_v1* zxdg_output_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zxdg_output_v1);
+}
+
+/**
+ * @ingroup iface_zxdg_output_v1
+ *
+ * Using this request a client can tell the server that it is not
+ * going to use the xdg_output object anymore.
+ */
+static inline void zxdg_output_v1_destroy(
+ struct zxdg_output_v1* zxdg_output_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zxdg_output_v1, ZXDG_OUTPUT_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zxdg_output_v1);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/xdg-output-unstable-v1-protocol.c b/widget/gtk/wayland/xdg-output-unstable-v1-protocol.c
new file mode 100644
index 0000000000..f80133f357
--- /dev/null
+++ b/widget/gtk/wayland/xdg-output-unstable-v1-protocol.c
@@ -0,0 +1,74 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+/*
+ * Copyright © 2017 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <gdk/gdkwayland.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
+#endif
+
+#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+# define WL_PRIVATE __attribute__((visibility("hidden")))
+#else
+# define WL_PRIVATE
+#endif
+
+extern const struct wl_interface wl_output_interface;
+extern const struct wl_interface zxdg_output_v1_interface;
+
+static const struct wl_interface* xdg_output_unstable_v1_types[] = {
+ NULL,
+ NULL,
+ &zxdg_output_v1_interface,
+ &wl_output_interface,
+};
+
+static const struct wl_message zxdg_output_manager_v1_requests[] = {
+ {"destroy", "", xdg_output_unstable_v1_types + 0},
+ {"get_xdg_output", "no", xdg_output_unstable_v1_types + 2},
+};
+
+WL_PRIVATE const struct wl_interface zxdg_output_manager_v1_interface = {
+ "zxdg_output_manager_v1", 3, 2, zxdg_output_manager_v1_requests, 0, NULL,
+};
+
+static const struct wl_message zxdg_output_v1_requests[] = {
+ {"destroy", "", xdg_output_unstable_v1_types + 0},
+};
+
+static const struct wl_message zxdg_output_v1_events[] = {
+ {"logical_position", "ii", xdg_output_unstable_v1_types + 0},
+ {"logical_size", "ii", xdg_output_unstable_v1_types + 0},
+ {"done", "", xdg_output_unstable_v1_types + 0},
+ {"name", "2s", xdg_output_unstable_v1_types + 0},
+ {"description", "2s", xdg_output_unstable_v1_types + 0},
+};
+
+WL_PRIVATE const struct wl_interface zxdg_output_v1_interface = {
+ "zxdg_output_v1", 3, 1, zxdg_output_v1_requests, 5, zxdg_output_v1_events,
+};
diff --git a/widget/headless/HeadlessClipboard.cpp b/widget/headless/HeadlessClipboard.cpp
new file mode 100644
index 0000000000..0d8c7fe572
--- /dev/null
+++ b/widget/headless/HeadlessClipboard.cpp
@@ -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 "HeadlessClipboard.h"
+
+#include "nsISupportsPrimitives.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS(HeadlessClipboard, nsIClipboard)
+
+HeadlessClipboard::HeadlessClipboard()
+ : mClipboard(MakeUnique<HeadlessClipboardData>()) {}
+
+NS_IMETHODIMP
+HeadlessClipboard::SetData(nsITransferable* aTransferable,
+ nsIClipboardOwner* anOwner,
+ int32_t aWhichClipboard) {
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // Clear out the clipboard in order to set the new data.
+ EmptyClipboard(aWhichClipboard);
+
+ // Only support plain text for now.
+ nsCOMPtr<nsISupports> clip;
+ nsresult rv =
+ aTransferable->GetTransferData(kUnicodeMime, getter_AddRefs(clip));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsISupportsString> wideString = do_QueryInterface(clip);
+ if (!wideString) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsAutoString utf16string;
+ wideString->GetData(utf16string);
+ mClipboard->SetText(utf16string);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HeadlessClipboard::GetData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) {
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISupportsString> dataWrapper =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ rv = dataWrapper->SetData(mClipboard->GetText());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ nsCOMPtr<nsISupports> genericDataWrapper = do_QueryInterface(dataWrapper);
+ rv = aTransferable->SetTransferData(kUnicodeMime, genericDataWrapper);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HeadlessClipboard::EmptyClipboard(int32_t aWhichClipboard) {
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ mClipboard->Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HeadlessClipboard::HasDataMatchingFlavors(
+ const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
+ bool* aHasType) {
+ *aHasType = false;
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ // Retrieve the union of all aHasType in aFlavorList
+ for (auto& flavor : aFlavorList) {
+ if (flavor.EqualsLiteral(kUnicodeMime) && mClipboard->HasText()) {
+ *aHasType = true;
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HeadlessClipboard::SupportsSelectionClipboard(bool* aIsSupported) {
+ *aIsSupported = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HeadlessClipboard::SupportsFindClipboard(bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = false;
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/headless/HeadlessClipboard.h b/widget/headless/HeadlessClipboard.h
new file mode 100644
index 0000000000..da7819e195
--- /dev/null
+++ b/widget/headless/HeadlessClipboard.h
@@ -0,0 +1,33 @@
+/* -*- 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_HeadlessClipboard_h
+#define mozilla_widget_HeadlessClipboard_h
+
+#include "nsIClipboard.h"
+#include "mozilla/UniquePtr.h"
+#include "HeadlessClipboardData.h"
+
+namespace mozilla {
+namespace widget {
+
+class HeadlessClipboard final : public nsIClipboard {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICLIPBOARD
+
+ HeadlessClipboard();
+
+ protected:
+ ~HeadlessClipboard() = default;
+
+ private:
+ UniquePtr<HeadlessClipboardData> mClipboard;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/headless/HeadlessClipboardData.cpp b/widget/headless/HeadlessClipboardData.cpp
new file mode 100644
index 0000000000..3722605854
--- /dev/null
+++ b/widget/headless/HeadlessClipboardData.cpp
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "HeadlessClipboardData.h"
+
+namespace mozilla {
+namespace widget {
+
+void HeadlessClipboardData::SetText(const nsAString& aText) { mPlain = aText; }
+
+bool HeadlessClipboardData::HasText() const { return !mPlain.IsEmpty(); }
+
+const nsAString& HeadlessClipboardData::GetText() const { return mPlain; }
+
+void HeadlessClipboardData::Clear() { mPlain.Truncate(0); }
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/headless/HeadlessClipboardData.h b/widget/headless/HeadlessClipboardData.h
new file mode 100644
index 0000000000..14cb84820a
--- /dev/null
+++ b/widget/headless/HeadlessClipboardData.h
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_HeadlessClipboardData_h
+#define mozilla_widget_HeadlessClipboardData_h
+
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace widget {
+
+class HeadlessClipboardData final {
+ public:
+ explicit HeadlessClipboardData() = default;
+ ~HeadlessClipboardData() = default;
+
+ // For text/plain
+ void SetText(const nsAString& aText);
+ bool HasText() const;
+ const nsAString& GetText() const;
+
+ // For other APIs
+ void Clear();
+
+ private:
+ nsAutoString mPlain;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_HeadlessClipboardData_h
diff --git a/widget/headless/HeadlessCompositorWidget.cpp b/widget/headless/HeadlessCompositorWidget.cpp
new file mode 100644
index 0000000000..b31a969b7a
--- /dev/null
+++ b/widget/headless/HeadlessCompositorWidget.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "HeadlessCompositorWidget.h"
+#include "VsyncDispatcher.h"
+
+namespace mozilla {
+namespace widget {
+
+HeadlessCompositorWidget::HeadlessCompositorWidget(
+ const HeadlessCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions, HeadlessWidget* aWindow)
+ : CompositorWidget(aOptions), mWidget(aWindow) {
+ mClientSize = aInitData.InitialClientSize();
+}
+
+void HeadlessCompositorWidget::ObserveVsync(VsyncObserver* aObserver) {
+ if (RefPtr<CompositorVsyncDispatcher> cvd =
+ mWidget->GetCompositorVsyncDispatcher()) {
+ cvd->SetCompositorVsyncObserver(aObserver);
+ }
+}
+
+nsIWidget* HeadlessCompositorWidget::RealWidget() { return mWidget; }
+
+void HeadlessCompositorWidget::NotifyClientSizeChanged(
+ const LayoutDeviceIntSize& aClientSize) {
+ mClientSize = aClientSize;
+}
+
+LayoutDeviceIntSize HeadlessCompositorWidget::GetClientSize() {
+ return mClientSize;
+}
+
+uintptr_t HeadlessCompositorWidget::GetWidgetKey() {
+ return reinterpret_cast<uintptr_t>(mWidget);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/headless/HeadlessCompositorWidget.h b/widget/headless/HeadlessCompositorWidget.h
new file mode 100644
index 0000000000..7f91de9e67
--- /dev/null
+++ b/widget/headless/HeadlessCompositorWidget.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 widget_headless_HeadlessCompositorWidget_h
+#define widget_headless_HeadlessCompositorWidget_h
+
+#include "mozilla/widget/CompositorWidget.h"
+
+#include "HeadlessWidget.h"
+
+namespace mozilla {
+namespace widget {
+
+class HeadlessCompositorWidgetInitData;
+
+class HeadlessCompositorWidget final : public CompositorWidget,
+ public CompositorWidgetDelegate {
+ public:
+ HeadlessCompositorWidget(const HeadlessCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions,
+ HeadlessWidget* aWindow);
+
+ void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize);
+
+ // CompositorWidget Overrides
+
+ uintptr_t GetWidgetKey() override;
+
+ LayoutDeviceIntSize GetClientSize() override;
+
+ nsIWidget* RealWidget() override;
+ CompositorWidgetDelegate* AsDelegate() override { return this; }
+
+ void ObserveVsync(VsyncObserver* aObserver) override;
+
+ // CompositorWidgetDelegate Overrides
+
+ HeadlessCompositorWidget* AsHeadlessCompositorWidget() override {
+ return this;
+ }
+
+ private:
+ HeadlessWidget* mWidget;
+
+ LayoutDeviceIntSize mClientSize;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_headless_HeadlessCompositor_h
diff --git a/widget/headless/HeadlessKeyBindings.cpp b/widget/headless/HeadlessKeyBindings.cpp
new file mode 100644
index 0000000000..1704612426
--- /dev/null
+++ b/widget/headless/HeadlessKeyBindings.cpp
@@ -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/. */
+
+#include "HeadlessKeyBindings.h"
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla {
+namespace widget {
+
+HeadlessKeyBindings& HeadlessKeyBindings::GetInstance() {
+ static UniquePtr<HeadlessKeyBindings> sInstance;
+ if (!sInstance) {
+ sInstance.reset(new HeadlessKeyBindings());
+ ClearOnShutdown(&sInstance);
+ }
+ return *sInstance;
+}
+
+nsresult HeadlessKeyBindings::AttachNativeKeyEvent(
+ WidgetKeyboardEvent& aEvent) {
+ // Stub for non-mac platforms.
+ return NS_OK;
+}
+
+void HeadlessKeyBindings::GetEditCommands(
+ nsIWidget::NativeKeyBindingsType aType, const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands) {
+ // Stub for non-mac platforms.
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/headless/HeadlessKeyBindings.h b/widget/headless/HeadlessKeyBindings.h
new file mode 100644
index 0000000000..c658360cf8
--- /dev/null
+++ b/widget/headless/HeadlessKeyBindings.h
@@ -0,0 +1,35 @@
+/* -*- 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_HeadlessKeyBindings_h
+#define mozilla_widget_HeadlessKeyBindings_h
+
+#include "mozilla/TextEvents.h"
+#include "nsIWidget.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * Helper to emulate native key bindings. Currently only MacOS is supported.
+ */
+
+class HeadlessKeyBindings final {
+ public:
+ HeadlessKeyBindings() = default;
+
+ static HeadlessKeyBindings& GetInstance();
+
+ void GetEditCommands(nsIWidget::NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands);
+ [[nodiscard]] nsresult AttachNativeKeyEvent(WidgetKeyboardEvent& aEvent);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_HeadlessKeyBindings_h
diff --git a/widget/headless/HeadlessKeyBindingsCocoa.mm b/widget/headless/HeadlessKeyBindingsCocoa.mm
new file mode 100644
index 0000000000..356eec0e56
--- /dev/null
+++ b/widget/headless/HeadlessKeyBindingsCocoa.mm
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HeadlessKeyBindings.h"
+#import <Cocoa/Cocoa.h>
+#include "nsCocoaUtils.h"
+#include "NativeKeyBindings.h"
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla {
+namespace widget {
+
+HeadlessKeyBindings& HeadlessKeyBindings::GetInstance() {
+ static UniquePtr<HeadlessKeyBindings> sInstance;
+ if (!sInstance) {
+ sInstance.reset(new HeadlessKeyBindings());
+ ClearOnShutdown(&sInstance);
+ }
+ return *sInstance;
+}
+
+nsresult HeadlessKeyBindings::AttachNativeKeyEvent(WidgetKeyboardEvent& aEvent) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ aEvent.mNativeKeyEvent = nsCocoaUtils::MakeNewCococaEventFromWidgetEvent(aEvent, 0, nil);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void HeadlessKeyBindings::GetEditCommands(nsIWidget::NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands) {
+ // Convert the widget keyboard into a cocoa event so it can be translated
+ // into commands in the NativeKeyBindings.
+ WidgetKeyboardEvent modifiedEvent(aEvent);
+ modifiedEvent.mNativeKeyEvent = nsCocoaUtils::MakeNewCococaEventFromWidgetEvent(aEvent, 0, nil);
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ keyBindings->GetEditCommands(modifiedEvent, aCommands);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/headless/HeadlessLookAndFeel.h b/widget/headless/HeadlessLookAndFeel.h
new file mode 100644
index 0000000000..8b6cf033fd
--- /dev/null
+++ b/widget/headless/HeadlessLookAndFeel.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_HeadlessLookAndFeel_h
+#define mozilla_widget_HeadlessLookAndFeel_h
+
+#include "nsXPLookAndFeel.h"
+#include "nsLookAndFeel.h"
+
+namespace mozilla {
+namespace widget {
+
+#if defined(MOZ_WIDGET_GTK)
+
+// Our nsLookAndFeel for Gtk relies on APIs that aren't available in headless
+// mode, so for processes that are unable to connect to a display server, we use
+// an implementation with hardcoded values.
+//
+// HeadlessLookAndFeel is used:
+//
+// * in the parent process, when full headless mode (MOZ_HEADLESS=1) is
+// enabled
+// * in content processes, when full headless mode or headless content
+// mode (security.sandbox.content.headless) is enabled, unless
+// widget.remote-look-and-feel is also enabled, in which case
+// RemoteLookAndFeel is used instead.
+//
+// The result of this is that when headless content mode is enabled, content
+// processes use values derived from the parent's nsLookAndFeel (i.e., values
+// derived from Gtk APIs) while still refraining from making any display server
+// connections.
+
+class HeadlessLookAndFeel : public nsXPLookAndFeel {
+ public:
+ explicit HeadlessLookAndFeel(const LookAndFeelCache* aCache);
+ virtual ~HeadlessLookAndFeel();
+
+ void NativeInit() final{};
+ virtual nsresult NativeGetInt(IntID aID, int32_t& aResult) override;
+ virtual nsresult NativeGetFloat(FloatID aID, float& aResult) override;
+ virtual nsresult NativeGetColor(ColorID aID, nscolor& aResult) override;
+ virtual bool NativeGetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) override;
+
+ virtual void RefreshImpl() override;
+ virtual char16_t GetPasswordCharacterImpl() override;
+ virtual bool GetEchoPasswordImpl() override;
+};
+
+#else
+
+// When possible, we simply reuse the platform's existing nsLookAndFeel
+// implementation in headless mode.
+
+typedef nsLookAndFeel HeadlessLookAndFeel;
+
+#endif
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/headless/HeadlessLookAndFeelGTK.cpp b/widget/headless/HeadlessLookAndFeelGTK.cpp
new file mode 100644
index 0000000000..3d4a135ee1
--- /dev/null
+++ b/widget/headless/HeadlessLookAndFeelGTK.cpp
@@ -0,0 +1,359 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "HeadlessLookAndFeel.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "nsIContent.h"
+
+using mozilla::LookAndFeel;
+
+namespace mozilla {
+namespace widget {
+
+static const char16_t UNICODE_BULLET = 0x2022;
+
+HeadlessLookAndFeel::HeadlessLookAndFeel(const LookAndFeelCache* aCache) {}
+
+HeadlessLookAndFeel::~HeadlessLookAndFeel() = default;
+
+nsresult HeadlessLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor) {
+ // For headless mode, we use GetStandinForNativeColor for everything we can,
+ // and hardcoded values for everything else.
+
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ // Override the solid black that GetStandinForNativeColor provides for
+ // FieldText, to match our behavior under the real GTK.
+ case ColorID::Fieldtext:
+ aColor = NS_RGB(0x21, 0x21, 0x21);
+ break;
+
+ // The rest are not provided by GetStandinForNativeColor.
+ case ColorID::IMESelectedRawTextBackground:
+ case ColorID::IMESelectedConvertedTextBackground:
+ case ColorID::IMERawInputBackground:
+ case ColorID::IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ break;
+ case ColorID::IMESelectedRawTextForeground:
+ case ColorID::IMESelectedConvertedTextForeground:
+ case ColorID::IMERawInputForeground:
+ case ColorID::IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case ColorID::IMERawInputUnderline:
+ case ColorID::IMEConvertedTextUnderline:
+ aColor = NS_40PERCENT_FOREGROUND_COLOR;
+ break;
+ case ColorID::IMESelectedRawTextUnderline:
+ case ColorID::IMESelectedConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case ColorID::MozEventreerow:
+ aColor = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::MozGtkInfoBarText:
+ aColor = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::MozMacButtonactivetext:
+ case ColorID::MozMacDefaultbuttontext:
+ aColor = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::SpellCheckerUnderline:
+ aColor = NS_RGB(0xff, 0x00, 0x00);
+ break;
+ case ColorID::TextBackground:
+ aColor = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::TextForeground:
+ aColor = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::TextHighlightBackground:
+ aColor = NS_RGB(0xef, 0x0f, 0xff);
+ break;
+ case ColorID::TextHighlightForeground:
+ aColor = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::TextSelectBackground:
+ aColor = NS_RGB(0xaa, 0xaa, 0xaa);
+ break;
+ case ColorID::TextSelectBackgroundAttention:
+ aColor = NS_TRANSPARENT;
+ break;
+ case ColorID::TextSelectBackgroundDisabled:
+ aColor = NS_RGB(0xaa, 0xaa, 0xaa);
+ break;
+ case ColorID::TextSelectForeground:
+ GetColor(ColorID::TextSelectBackground, aColor);
+ if (aColor == 0x000000)
+ aColor = NS_RGB(0xff, 0xff, 0xff);
+ else
+ aColor = NS_DONT_CHANGE_COLOR;
+ break;
+ case ColorID::Widget3DHighlight:
+ aColor = NS_RGB(0xa0, 0xa0, 0xa0);
+ break;
+ case ColorID::Widget3DShadow:
+ aColor = NS_RGB(0x40, 0x40, 0x40);
+ break;
+ case ColorID::WidgetBackground:
+ aColor = NS_RGB(0xdd, 0xdd, 0xdd);
+ break;
+ case ColorID::WidgetForeground:
+ aColor = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::WidgetSelectBackground:
+ aColor = NS_RGB(0x80, 0x80, 0x80);
+ break;
+ case ColorID::WidgetSelectForeground:
+ aColor = NS_RGB(0x00, 0x00, 0x80);
+ break;
+ case ColorID::WindowBackground:
+ aColor = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::WindowForeground:
+ aColor = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ default:
+ aColor = GetStandinForNativeColor(aID);
+ break;
+ }
+
+ return res;
+}
+
+nsresult HeadlessLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
+ nsresult res = NS_OK;
+ // These values should be sane defaults for headless mode under GTK.
+ switch (aID) {
+ case IntID::CaretBlinkTime:
+ aResult = 567;
+ break;
+ case IntID::CaretWidth:
+ aResult = 1;
+ break;
+ case IntID::ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case IntID::SelectTextfieldsOnKeyFocus:
+ aResult = 1;
+ break;
+ case IntID::SubmenuDelay:
+ aResult = 200;
+ break;
+ case IntID::MenusCanOverlapOSBar:
+ aResult = 0;
+ break;
+ case IntID::UseOverlayScrollbars:
+ aResult = 0;
+ break;
+ case IntID::AllowOverlayScrollbarsOverlap:
+ aResult = 0;
+ break;
+ case IntID::ShowHideScrollbars:
+ aResult = 0;
+ break;
+ case IntID::SkipNavigatingDisabledMenuItem:
+ aResult = 1;
+ break;
+ case IntID::DragThresholdX:
+ case IntID::DragThresholdY:
+ aResult = 4;
+ break;
+ case IntID::UseAccessibilityTheme:
+ aResult = 0;
+ break;
+ case IntID::ScrollArrowStyle:
+ aResult = eScrollArrow_None;
+ break;
+ case IntID::ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+ case IntID::ScrollButtonLeftMouseButtonAction:
+ aResult = 0;
+ return NS_OK;
+ case IntID::ScrollButtonMiddleMouseButtonAction:
+ aResult = 3;
+ return NS_OK;
+ case IntID::ScrollButtonRightMouseButtonAction:
+ aResult = 3;
+ return NS_OK;
+ case IntID::TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case IntID::TreeCloseDelay:
+ aResult = 1000;
+ break;
+ case IntID::TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case IntID::TreeScrollDelay:
+ aResult = 100;
+ break;
+ case IntID::TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case IntID::TabFocusModel:
+ aResult = nsIContent::eTabFocus_textControlsMask;
+ break;
+ case IntID::ChosenMenuItemsShouldBlink:
+ aResult = 1;
+ break;
+ case IntID::WindowsAccentColorInTitlebar:
+ case IntID::WindowsDefaultTheme:
+ case IntID::DWMCompositor:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case IntID::WindowsClassic:
+ case IntID::WindowsGlass:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ break;
+ case IntID::TouchEnabled:
+ case IntID::MacGraphiteTheme:
+ case IntID::MacBigSurTheme:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case IntID::AlertNotificationOrigin:
+ aResult = NS_ALERT_TOP;
+ break;
+ case IntID::ScrollToClick:
+ aResult = 0;
+ break;
+ case IntID::IMERawInputUnderlineStyle:
+ case IntID::IMESelectedRawTextUnderlineStyle:
+ case IntID::IMEConvertedTextUnderlineStyle:
+ case IntID::IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ break;
+ case IntID::SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_DOTTED;
+ break;
+ case IntID::MenuBarDrag:
+ aResult = 0;
+ break;
+ case IntID::WindowsThemeIdentifier:
+ case IntID::OperatingSystemVersionIdentifier:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case IntID::ScrollbarButtonAutoRepeatBehavior:
+ aResult = 0;
+ break;
+ case IntID::TooltipDelay:
+ aResult = 500;
+ break;
+ case IntID::SwipeAnimationEnabled:
+ aResult = 0;
+ break;
+ case IntID::ScrollbarDisplayOnMouseMove:
+ aResult = 0;
+ break;
+ case IntID::ScrollbarFadeBeginDelay:
+ aResult = 0;
+ break;
+ case IntID::ScrollbarFadeDuration:
+ aResult = 0;
+ break;
+ case IntID::ContextMenuOffsetVertical:
+ aResult = -6;
+ break;
+ case IntID::ContextMenuOffsetHorizontal:
+ aResult = 1;
+ break;
+ case IntID::GTKCSDAvailable:
+ case IntID::GTKCSDHideTitlebarByDefault:
+ case IntID::GTKCSDTransparentBackground:
+ aResult = 0;
+ break;
+ case IntID::GTKCSDMinimizeButton:
+ aResult = 0;
+ break;
+ case IntID::GTKCSDMaximizeButton:
+ aResult = 0;
+ break;
+ case IntID::GTKCSDCloseButton:
+ aResult = 1;
+ break;
+ case IntID::GTKCSDReversedPlacement:
+ aResult = 0;
+ break;
+ case IntID::SystemUsesDarkTheme:
+ aResult = 0;
+ break;
+ case IntID::PrefersReducedMotion:
+ aResult = 0;
+ break;
+ case IntID::PrimaryPointerCapabilities:
+ aResult = 0;
+ break;
+ case IntID::AllPointerCapabilities:
+ aResult = 0;
+ break;
+ default:
+ NS_WARNING(
+ "HeadlessLookAndFeel::NativeGetInt called with an unrecognized aID");
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ break;
+ }
+ return res;
+}
+
+nsresult HeadlessLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
+ nsresult res = NS_OK;
+
+ // Hardcoded values for GTK.
+ switch (aID) {
+ case FloatID::IMEUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case FloatID::SpellCheckerUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case FloatID::CaretAspectRatio:
+ // Intentionally failing to quietly indicate lack of support.
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ break;
+ default:
+ NS_WARNING(
+ "HeadlessLookAndFeel::NativeGetFloat called with an unrecognized "
+ "aID");
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ break;
+ }
+
+ return res;
+}
+
+bool HeadlessLookAndFeel::NativeGetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) {
+ // Default to san-serif for everything.
+ aFontStyle.style = FontSlantStyle::Normal();
+ aFontStyle.weight = FontWeight::Normal();
+ aFontStyle.stretch = FontStretch::Normal();
+ aFontStyle.size = 14;
+ aFontStyle.systemFont = true;
+
+ aFontName.AssignLiteral("sans-serif");
+ return true;
+}
+
+char16_t HeadlessLookAndFeel::GetPasswordCharacterImpl() {
+ return UNICODE_BULLET;
+}
+
+void HeadlessLookAndFeel::RefreshImpl() { nsXPLookAndFeel::RefreshImpl(); }
+
+bool HeadlessLookAndFeel::GetEchoPasswordImpl() { return false; }
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/headless/HeadlessScreenHelper.cpp b/widget/headless/HeadlessScreenHelper.cpp
new file mode 100644
index 0000000000..3800c9c73a
--- /dev/null
+++ b/widget/headless/HeadlessScreenHelper.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "HeadlessScreenHelper.h"
+
+#include "prenv.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/RefPtr.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace widget {
+
+/* static */
+LayoutDeviceIntRect HeadlessScreenHelper::GetScreenRect() {
+ char* ev = PR_GetEnv("MOZ_HEADLESS_WIDTH");
+ int width = 1366;
+ if (ev) {
+ width = atoi(ev);
+ }
+ ev = PR_GetEnv("MOZ_HEADLESS_HEIGHT");
+ int height = 768;
+ if (ev) {
+ height = atoi(ev);
+ }
+ return LayoutDeviceIntRect(0, 0, width, height);
+}
+
+HeadlessScreenHelper::HeadlessScreenHelper() {
+ AutoTArray<RefPtr<Screen>, 1> screenList;
+ LayoutDeviceIntRect rect = GetScreenRect();
+ RefPtr<Screen> ret =
+ new Screen(rect, rect, 24, 24, DesktopToLayoutDeviceScale(),
+ CSSToLayoutDeviceScale(), 96.0f);
+ screenList.AppendElement(ret.forget());
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ screenManager.Refresh(std::move(screenList));
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/headless/HeadlessScreenHelper.h b/widget/headless/HeadlessScreenHelper.h
new file mode 100644
index 0000000000..f86677af52
--- /dev/null
+++ b/widget/headless/HeadlessScreenHelper.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_HeadlessScreenHelper_h
+#define mozilla_widget_HeadlessScreenHelper_h
+
+#include "mozilla/widget/ScreenManager.h"
+
+namespace mozilla {
+namespace widget {
+
+class HeadlessScreenHelper final : public ScreenManager::Helper {
+ public:
+ HeadlessScreenHelper();
+ ~HeadlessScreenHelper() override = default;
+
+ private:
+ static LayoutDeviceIntRect GetScreenRect();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_HeadlessScreenHelper_h
diff --git a/widget/headless/HeadlessSound.cpp b/widget/headless/HeadlessSound.cpp
new file mode 100644
index 0000000000..278d31b355
--- /dev/null
+++ b/widget/headless/HeadlessSound.cpp
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "HeadlessSound.h"
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS(HeadlessSound, nsISound, nsIStreamLoaderObserver)
+
+HeadlessSound::HeadlessSound() = default;
+
+HeadlessSound::~HeadlessSound() = default;
+
+NS_IMETHODIMP
+HeadlessSound::Init() { return NS_OK; }
+
+NS_IMETHODIMP HeadlessSound::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* context,
+ nsresult aStatus,
+ uint32_t dataLen,
+ const uint8_t* data) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP HeadlessSound::Beep() { return NS_OK; }
+
+NS_IMETHODIMP HeadlessSound::Play(nsIURL* aURL) { return NS_OK; }
+
+NS_IMETHODIMP HeadlessSound::PlayEventSound(uint32_t aEventId) { return NS_OK; }
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/headless/HeadlessSound.h b/widget/headless/HeadlessSound.h
new file mode 100644
index 0000000000..a481acc61e
--- /dev/null
+++ b/widget/headless/HeadlessSound.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_HeadlessSound_h
+#define mozilla_widget_HeadlessSound_h
+
+#include "nsISound.h"
+#include "nsIStreamLoader.h"
+
+namespace mozilla {
+namespace widget {
+
+class HeadlessSound : public nsISound, public nsIStreamLoaderObserver {
+ public:
+ HeadlessSound();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISOUND
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+ private:
+ virtual ~HeadlessSound();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_HeadlessSound_h
diff --git a/widget/headless/HeadlessThemeGTK.cpp b/widget/headless/HeadlessThemeGTK.cpp
new file mode 100644
index 0000000000..64135202e4
--- /dev/null
+++ b/widget/headless/HeadlessThemeGTK.cpp
@@ -0,0 +1,417 @@
+/* -*- 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 "HeadlessThemeGTK.h"
+
+#include "mozilla/StaticPrefs_layout.h"
+#include "nsStyleConsts.h"
+#include "nsIFrame.h"
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS_INHERITED(HeadlessThemeGTK, nsNativeTheme, nsITheme)
+
+NS_IMETHODIMP
+HeadlessThemeGTK::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) {
+ return NS_OK;
+}
+
+LayoutDeviceIntMargin HeadlessThemeGTK::GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
+ LayoutDeviceIntMargin result;
+ // The following values are generated from the Ubuntu GTK theme.
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ case StyleAppearance::Toolbarbutton:
+ result.top = 6;
+ result.right = 7;
+ result.bottom = 6;
+ result.left = 7;
+ break;
+ case StyleAppearance::FocusOutline:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ result.top = 5;
+ result.right = 7;
+ result.bottom = 5;
+ result.left = 7;
+ break;
+ case StyleAppearance::Statusbarpanel:
+ case StyleAppearance::Resizerpanel:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Treeheadersortarrow:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ case StyleAppearance::SpinnerTextfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Menupopup:
+ case StyleAppearance::MozGtkInfoBar:
+ result.top = 1;
+ result.right = 1;
+ result.bottom = 1;
+ result.left = 1;
+ break;
+ case StyleAppearance::Treeheadercell:
+ result.top = 5;
+ result.right = 7;
+ result.bottom = 6;
+ result.left = 6;
+ break;
+ case StyleAppearance::Tab:
+ result.top = 4;
+ result.right = 7;
+ result.bottom = 2;
+ result.left = 7;
+ break;
+ case StyleAppearance::Tooltip:
+ result.top = 6;
+ result.right = 6;
+ result.bottom = 6;
+ result.left = 6;
+ break;
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Menulist:
+ result.top = 6;
+ result.right = 22;
+ result.bottom = 6;
+ result.left = 7;
+ break;
+ case StyleAppearance::MozMenulistArrowButton:
+ result.top = 1;
+ result.right = 1;
+ result.bottom = 1;
+ result.left = 0;
+ break;
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem:
+ if (IsRegularMenuItem(aFrame)) {
+ break;
+ }
+ result.top = 3;
+ result.right = 5;
+ result.bottom = 3;
+ result.left = 5;
+ break;
+ default:
+ break;
+ }
+ return result;
+}
+
+bool HeadlessThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) {
+ // The following values are generated from the Ubuntu GTK theme.
+ switch (aAppearance) {
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::Dualbutton:
+ case StyleAppearance::ToolbarbuttonDropdown:
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowDown:
+ case StyleAppearance::ButtonArrowNext:
+ case StyleAppearance::ButtonArrowPrevious:
+ case StyleAppearance::TabScrollArrowBack:
+ case StyleAppearance::TabScrollArrowForward:
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::RangeThumb:
+ case StyleAppearance::ButtonFocus:
+ aResult->top = 0;
+ aResult->right = 0;
+ aResult->bottom = 0;
+ aResult->left = 0;
+ return true;
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem:
+ if (!IsRegularMenuItem(aFrame)) {
+ return false;
+ }
+ aResult->top = 3;
+ aResult->right = 5;
+ aResult->bottom = 3;
+ aResult->left = 5;
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+NS_IMETHODIMP
+HeadlessThemeGTK::GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) {
+ aResult->width = aResult->height = 0;
+ *aIsOverridable = true;
+
+ // The following values are generated from the Ubuntu GTK theme.
+ switch (aAppearance) {
+ case StyleAppearance::Splitter:
+ if (IsHorizontal(aFrame)) {
+ aResult->width = 6;
+ aResult->height = 0;
+ } else {
+ aResult->width = 0;
+ aResult->height = 6;
+ }
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::Button:
+ case StyleAppearance::Toolbarbutton:
+ aResult->width = 14;
+ aResult->height = 12;
+ break;
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ aResult->width = 18;
+ aResult->height = 18;
+ break;
+ case StyleAppearance::ToolbarbuttonDropdown:
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowDown:
+ case StyleAppearance::ButtonArrowNext:
+ case StyleAppearance::ButtonArrowPrevious:
+ case StyleAppearance::Resizer:
+ aResult->width = 15;
+ aResult->height = 15;
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::Separator:
+ aResult->width = 12;
+ aResult->height = 0;
+ break;
+ case StyleAppearance::Treetwisty:
+ case StyleAppearance::Treetwistyopen:
+ aResult->width = 8;
+ aResult->height = 8;
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::Treeheadercell:
+ aResult->width = 13;
+ aResult->height = 11;
+ break;
+ case StyleAppearance::Treeheadersortarrow:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ aResult->width = 14;
+ aResult->height = 13;
+ break;
+ case StyleAppearance::TabScrollArrowBack:
+ case StyleAppearance::TabScrollArrowForward:
+ aResult->width = 16;
+ aResult->height = 16;
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::Spinner:
+ aResult->width = 14;
+ aResult->height = 26;
+ break;
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ aResult->width = 0;
+ aResult->height = 12;
+ break;
+ case StyleAppearance::ScrollbarHorizontal:
+ aResult->width = 31;
+ aResult->height = 10;
+ break;
+ case StyleAppearance::ScrollbarVertical:
+ aResult->width = 10;
+ aResult->height = 31;
+ break;
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ aResult->width = 10;
+ aResult->height = 13;
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ aResult->width = 13;
+ aResult->height = 10;
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ aResult->width = 31;
+ aResult->height = 10;
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::ScrollbarthumbVertical:
+ aResult->width = 10;
+ aResult->height = 31;
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Menulist:
+ aResult->width = 44;
+ aResult->height = 27;
+ break;
+ case StyleAppearance::MozMenulistArrowButton:
+ aResult->width = 29;
+ aResult->height = 28;
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::RangeThumb:
+ aResult->width = 14;
+ aResult->height = 18;
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::Menuseparator:
+ aResult->width = 0;
+ aResult->height = 8;
+ *aIsOverridable = false;
+ break;
+ default:
+ break;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HeadlessThemeGTK::WidgetStateChanged(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HeadlessThemeGTK::ThemeChanged() { return NS_OK; }
+
+static bool IsFrameContentNodeInNamespace(nsIFrame* aFrame,
+ uint32_t aNamespace) {
+ nsIContent* content = aFrame ? aFrame->GetContent() : nullptr;
+ if (!content) return false;
+ return content->IsInNamespace(aNamespace);
+}
+
+NS_IMETHODIMP_(bool)
+HeadlessThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::FocusOutline:
+ case StyleAppearance::Toolbox:
+ case StyleAppearance::Toolbar:
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::Dualbutton:
+ case StyleAppearance::ToolbarbuttonDropdown:
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowDown:
+ case StyleAppearance::ButtonArrowNext:
+ case StyleAppearance::ButtonArrowPrevious:
+ case StyleAppearance::Separator:
+ case StyleAppearance::Toolbargripper:
+ case StyleAppearance::Splitter:
+ case StyleAppearance::Statusbar:
+ case StyleAppearance::Statusbarpanel:
+ case StyleAppearance::Resizerpanel:
+ case StyleAppearance::Resizer:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Treetwisty:
+ case StyleAppearance::Treeheadercell:
+ case StyleAppearance::Treeheadersortarrow:
+ case StyleAppearance::Treetwistyopen:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Tab:
+ case StyleAppearance::Tabpanels:
+ case StyleAppearance::TabScrollArrowBack:
+ case StyleAppearance::TabScrollArrowForward:
+ case StyleAppearance::Tooltip:
+ case StyleAppearance::Spinner:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ case StyleAppearance::SpinnerTextfield:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::MenulistText:
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ case StyleAppearance::CheckboxContainer:
+ case StyleAppearance::RadioContainer:
+ case StyleAppearance::CheckboxLabel:
+ case StyleAppearance::RadioLabel:
+ case StyleAppearance::ButtonFocus:
+ case StyleAppearance::Window:
+ case StyleAppearance::Dialog:
+ case StyleAppearance::Menubar:
+ case StyleAppearance::Menupopup:
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem:
+ case StyleAppearance::Menuseparator:
+ case StyleAppearance::Menuarrow:
+ case StyleAppearance::MozGtkInfoBar:
+ return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
+ case StyleAppearance::MozMenulistArrowButton:
+ return (!aFrame ||
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) &&
+ !IsWidgetStyled(aPresContext, aFrame, aAppearance);
+ default:
+ break;
+ }
+ return false;
+}
+
+NS_IMETHODIMP_(bool)
+HeadlessThemeGTK::WidgetIsContainer(StyleAppearance aAppearance) {
+ if (aAppearance == StyleAppearance::MozMenulistArrowButton ||
+ aAppearance == StyleAppearance::Radio ||
+ aAppearance == StyleAppearance::RangeThumb ||
+ aAppearance == StyleAppearance::Checkbox ||
+ aAppearance == StyleAppearance::TabScrollArrowBack ||
+ aAppearance == StyleAppearance::TabScrollArrowForward ||
+ aAppearance == StyleAppearance::ButtonArrowUp ||
+ aAppearance == StyleAppearance::ButtonArrowDown ||
+ aAppearance == StyleAppearance::ButtonArrowNext ||
+ aAppearance == StyleAppearance::ButtonArrowPrevious) {
+ return false;
+ }
+ return true;
+}
+
+bool HeadlessThemeGTK::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) {
+ if (aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::Button ||
+ aAppearance == StyleAppearance::Treeheadercell) {
+ return true;
+ }
+ return false;
+}
+
+bool HeadlessThemeGTK::ThemeNeedsComboboxDropmarker() { return false; }
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/headless/HeadlessThemeGTK.h b/widget/headless/HeadlessThemeGTK.h
new file mode 100644
index 0000000000..11ca0a1f92
--- /dev/null
+++ b/widget/headless/HeadlessThemeGTK.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 mozilla_widget_HeadlessThemeGTK_h
+#define mozilla_widget_HeadlessThemeGTK_h
+
+#include "nsITheme.h"
+#include "nsNativeTheme.h"
+
+namespace mozilla {
+namespace widget {
+
+class HeadlessThemeGTK final : private nsNativeTheme, public nsITheme {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ HeadlessThemeGTK() = default;
+ NS_IMETHOD DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) override;
+
+ [[nodiscard]] LayoutDeviceIntMargin GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ bool GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) override;
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) override;
+
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) override;
+
+ NS_IMETHOD ThemeChanged() override;
+
+ NS_IMETHOD_(bool)
+ ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ NS_IMETHOD_(bool) WidgetIsContainer(StyleAppearance aAppearance) override;
+
+ NS_IMETHOD_(bool)
+ ThemeDrawsFocusForWidget(StyleAppearance aAppearance) override;
+
+ virtual bool ThemeNeedsComboboxDropmarker() override;
+
+ protected:
+ virtual ~HeadlessThemeGTK() = default;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_HeadlessThemeGTK_h
diff --git a/widget/headless/HeadlessWidget.cpp b/widget/headless/HeadlessWidget.cpp
new file mode 100644
index 0000000000..f4f3acda7e
--- /dev/null
+++ b/widget/headless/HeadlessWidget.cpp
@@ -0,0 +1,503 @@
+/* -*- 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 "HeadlessWidget.h"
+#include "HeadlessCompositorWidget.h"
+#include "Layers.h"
+#include "BasicLayers.h"
+#include "BasicEvents.h"
+#include "MouseEvents.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/widget/HeadlessWidgetTypes.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsIScreen.h"
+#include "HeadlessKeyBindings.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+using mozilla::LogLevel;
+
+#ifdef MOZ_LOGGING
+
+# include "mozilla/Logging.h"
+static mozilla::LazyLogModule sWidgetLog("Widget");
+static mozilla::LazyLogModule sWidgetFocusLog("WidgetFocus");
+# define LOG(args) MOZ_LOG(sWidgetLog, mozilla::LogLevel::Debug, args)
+# define LOGFOCUS(args) \
+ MOZ_LOG(sWidgetFocusLog, mozilla::LogLevel::Debug, args)
+
+#else
+
+# define LOG(args)
+# define LOGFOCUS(args)
+
+#endif /* MOZ_LOGGING */
+
+/*static*/
+already_AddRefed<nsIWidget> nsIWidget::CreateHeadlessWidget() {
+ nsCOMPtr<nsIWidget> widget = new mozilla::widget::HeadlessWidget();
+ return widget.forget();
+}
+
+namespace mozilla {
+namespace widget {
+
+StaticAutoPtr<nsTArray<HeadlessWidget*>> HeadlessWidget::sActiveWindows;
+
+already_AddRefed<HeadlessWidget> HeadlessWidget::GetActiveWindow() {
+ if (!sActiveWindows) {
+ return nullptr;
+ }
+ auto length = sActiveWindows->Length();
+ if (length == 0) {
+ return nullptr;
+ }
+ RefPtr<HeadlessWidget> widget = sActiveWindows->ElementAt(length - 1);
+ return widget.forget();
+}
+
+HeadlessWidget::HeadlessWidget()
+ : mEnabled(true),
+ mVisible(false),
+ mDestroyed(false),
+ mTopLevel(nullptr),
+ mCompositorWidget(nullptr),
+ mLastSizeMode(nsSizeMode_Normal),
+ mEffectiveSizeMode(nsSizeMode_Normal),
+ mRestoreBounds(0, 0, 0, 0) {
+ if (!sActiveWindows) {
+ sActiveWindows = new nsTArray<HeadlessWidget*>();
+ ClearOnShutdown(&sActiveWindows);
+ }
+}
+
+HeadlessWidget::~HeadlessWidget() {
+ LOG(("HeadlessWidget::~HeadlessWidget() [%p]\n", (void*)this));
+
+ Destroy();
+}
+
+void HeadlessWidget::Destroy() {
+ if (mDestroyed) {
+ return;
+ }
+ LOG(("HeadlessWidget::Destroy [%p]\n", (void*)this));
+ mDestroyed = true;
+
+ if (sActiveWindows) {
+ int32_t index = sActiveWindows->IndexOf(this);
+ if (index != -1) {
+ RefPtr<HeadlessWidget> activeWindow = GetActiveWindow();
+ sActiveWindows->RemoveElementAt(index);
+ // If this is the currently active widget and there's a previously active
+ // widget, activate the previous widget.
+ RefPtr<HeadlessWidget> previousActiveWindow = GetActiveWindow();
+ if (this == activeWindow && previousActiveWindow &&
+ previousActiveWindow->mWidgetListener) {
+ previousActiveWindow->mWidgetListener->WindowActivated();
+ }
+ }
+ }
+
+ nsBaseWidget::OnDestroy();
+
+ nsBaseWidget::Destroy();
+}
+
+nsresult HeadlessWidget::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) {
+ MOZ_ASSERT(!aNativeParent, "No native parents for headless widgets.");
+
+ BaseCreate(nullptr, aInitData);
+
+ mBounds = aRect;
+ mRestoreBounds = aRect;
+
+ if (aParent) {
+ mTopLevel = aParent->GetTopLevelWidget();
+ } else {
+ mTopLevel = this;
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIWidget> HeadlessWidget::CreateChild(
+ const LayoutDeviceIntRect& aRect, nsWidgetInitData* aInitData,
+ bool aForceUseIWidgetParent) {
+ nsCOMPtr<nsIWidget> widget = nsIWidget::CreateHeadlessWidget();
+ if (!widget) {
+ return nullptr;
+ }
+ if (NS_FAILED(widget->Create(this, nullptr, aRect, aInitData))) {
+ return nullptr;
+ }
+ return widget.forget();
+}
+
+void HeadlessWidget::GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) {
+ *aInitData =
+ mozilla::widget::HeadlessCompositorWidgetInitData(GetClientSize());
+}
+
+nsIWidget* HeadlessWidget::GetTopLevelWidget() { return mTopLevel; }
+
+void HeadlessWidget::RaiseWindow() {
+ MOZ_ASSERT(mTopLevel == this || mWindowType == eWindowType_dialog ||
+ mWindowType == eWindowType_sheet,
+ "Raising a non-toplevel window.");
+
+ // Do nothing if this is the currently active window.
+ RefPtr<HeadlessWidget> activeWindow = GetActiveWindow();
+ if (activeWindow == this) {
+ return;
+ }
+
+ // Raise the window to the top of the stack.
+ nsWindowZ placement = nsWindowZTop;
+ nsCOMPtr<nsIWidget> actualBelow;
+ if (mWidgetListener)
+ mWidgetListener->ZLevelChanged(true, &placement, nullptr,
+ getter_AddRefs(actualBelow));
+
+ // Deactivate the last active window.
+ if (activeWindow && activeWindow->mWidgetListener) {
+ activeWindow->mWidgetListener->WindowDeactivated();
+ }
+
+ // Remove this window if it's already tracked.
+ int32_t index = sActiveWindows->IndexOf(this);
+ if (index != -1) {
+ sActiveWindows->RemoveElementAt(index);
+ }
+
+ // Activate this window.
+ sActiveWindows->AppendElement(this);
+ if (mWidgetListener) mWidgetListener->WindowActivated();
+}
+
+void HeadlessWidget::Show(bool aState) {
+ mVisible = aState;
+
+ LOG(("HeadlessWidget::Show [%p] state %d\n", (void*)this, aState));
+
+ // Top-level window and dialogs are activated/raised when shown.
+ if (aState && (mTopLevel == this || mWindowType == eWindowType_dialog ||
+ mWindowType == eWindowType_sheet)) {
+ RaiseWindow();
+ }
+
+ ApplySizeModeSideEffects();
+}
+
+bool HeadlessWidget::IsVisible() const { return mVisible; }
+
+void HeadlessWidget::SetFocus(Raise aRaise,
+ mozilla::dom::CallerType aCallerType) {
+ LOGFOCUS((" SetFocus %d [%p]\n", aRaise == Raise::Yes, (void*)this));
+
+ // This means we request activation of our toplevel window.
+ if (aRaise == Raise::Yes) {
+ HeadlessWidget* topLevel = (HeadlessWidget*)GetTopLevelWidget();
+
+ // The toplevel only becomes active if it's currently visible; otherwise, it
+ // will be activated anyway when it's shown.
+ if (topLevel->IsVisible()) topLevel->RaiseWindow();
+ }
+}
+
+void HeadlessWidget::Enable(bool aState) { mEnabled = aState; }
+
+bool HeadlessWidget::IsEnabled() const { return mEnabled; }
+
+void HeadlessWidget::Move(double aX, double aY) {
+ LOG(("HeadlessWidget::Move [%p] %f %f\n", (void*)this, aX, aY));
+
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t x = NSToIntRound(aX * scale);
+ int32_t y = NSToIntRound(aY * scale);
+
+ if (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog) {
+ SetSizeMode(nsSizeMode_Normal);
+ }
+
+ // Since a popup window's x/y coordinates are in relation to
+ // the parent, the parent might have moved so we always move a
+ // popup window.
+ if (mBounds.IsEqualXY(x, y) && mWindowType != eWindowType_popup) {
+ return;
+ }
+
+ mBounds.MoveTo(x, y);
+ NotifyRollupGeometryChange();
+}
+
+LayoutDeviceIntPoint HeadlessWidget::WidgetToScreenOffset() {
+ return mTopLevel->GetBounds().TopLeft();
+}
+
+LayerManager* HeadlessWidget::GetLayerManager(
+ PLayerTransactionChild* aShadowManager, LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence) {
+ return nsBaseWidget::GetLayerManager(aShadowManager, aBackendHint,
+ aPersistence);
+}
+
+void HeadlessWidget::SetCompositorWidgetDelegate(
+ CompositorWidgetDelegate* delegate) {
+ if (delegate) {
+ mCompositorWidget = delegate->AsHeadlessCompositorWidget();
+ MOZ_ASSERT(mCompositorWidget,
+ "HeadlessWidget::SetCompositorWidgetDelegate called with a "
+ "non-HeadlessCompositorWidget");
+ } else {
+ mCompositorWidget = nullptr;
+ }
+}
+
+void HeadlessWidget::Resize(double aWidth, double aHeight, bool aRepaint) {
+ int32_t width = NSToIntRound(aWidth);
+ int32_t height = NSToIntRound(aHeight);
+ ConstrainSize(&width, &height);
+ mBounds.SizeTo(LayoutDeviceIntSize(width, height));
+
+ if (mCompositorWidget) {
+ mCompositorWidget->NotifyClientSizeChanged(
+ LayoutDeviceIntSize(mBounds.Width(), mBounds.Height()));
+ }
+ if (mWidgetListener) {
+ mWidgetListener->WindowResized(this, mBounds.Width(), mBounds.Height());
+ }
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->WindowResized(this, mBounds.Width(),
+ mBounds.Height());
+ }
+}
+
+void HeadlessWidget::Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) {
+ if (!mBounds.IsEqualXY(aX, aY)) {
+ NotifyWindowMoved(aX, aY);
+ }
+ return Resize(aWidth, aHeight, aRepaint);
+}
+
+void HeadlessWidget::SetSizeMode(nsSizeMode aMode) {
+ LOG(("HeadlessWidget::SetSizeMode [%p] %d\n", (void*)this, aMode));
+
+ if (aMode == mSizeMode) {
+ return;
+ }
+
+ nsBaseWidget::SetSizeMode(aMode);
+
+ // Normally in real widget backends a window event would be triggered that
+ // would cause the window manager to handle resizing the window. In headless
+ // the window must manually be resized.
+ ApplySizeModeSideEffects();
+}
+
+void HeadlessWidget::ApplySizeModeSideEffects() {
+ if (!mVisible || mEffectiveSizeMode == mSizeMode) {
+ return;
+ }
+
+ if (mEffectiveSizeMode == nsSizeMode_Normal) {
+ // Store the last normal size bounds so it can be restored when entering
+ // normal mode again.
+ mRestoreBounds = mBounds;
+ }
+
+ switch (mSizeMode) {
+ case nsSizeMode_Normal: {
+ Resize(mRestoreBounds.X(), mRestoreBounds.Y(), mRestoreBounds.Width(),
+ mRestoreBounds.Height(), false);
+ break;
+ }
+ case nsSizeMode_Minimized:
+ break;
+ case nsSizeMode_Maximized: {
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ if (screen) {
+ int32_t left, top, width, height;
+ if (NS_SUCCEEDED(
+ screen->GetRectDisplayPix(&left, &top, &width, &height))) {
+ Resize(0, 0, width, height, true);
+ }
+ }
+ break;
+ }
+ case nsSizeMode_Fullscreen:
+ // This will take care of resizing the window.
+ nsBaseWidget::InfallibleMakeFullScreen(true);
+ break;
+ default:
+ break;
+ }
+
+ mEffectiveSizeMode = mSizeMode;
+ if (mWidgetListener) {
+ mWidgetListener->SizeModeChanged(mSizeMode);
+ }
+}
+
+nsresult HeadlessWidget::MakeFullScreen(bool aFullScreen,
+ nsIScreen* aTargetScreen) {
+ // Directly update the size mode here so a later call SetSizeMode does
+ // nothing.
+ if (aFullScreen) {
+ if (mSizeMode != nsSizeMode_Fullscreen) {
+ mLastSizeMode = mSizeMode;
+ }
+ mSizeMode = nsSizeMode_Fullscreen;
+ } else {
+ mSizeMode = mLastSizeMode;
+ }
+
+ // Notify the listener first so size mode change events are triggered before
+ // resize events.
+ if (mWidgetListener) {
+ mWidgetListener->SizeModeChanged(mSizeMode);
+ mWidgetListener->FullscreenChanged(aFullScreen);
+ }
+
+ // Real widget backends don't seem to follow a common approach for
+ // when and how many resize events are triggered during fullscreen
+ // transitions. InfallibleMakeFullScreen will trigger a resize, but it
+ // will be ignored if still transitioning to fullscreen, so it must be
+ // triggered on the next tick.
+ RefPtr<HeadlessWidget> self(this);
+ nsCOMPtr<nsIScreen> targetScreen(aTargetScreen);
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "HeadlessWidget::MakeFullScreen",
+ [self, targetScreen, aFullScreen]() -> void {
+ self->InfallibleMakeFullScreen(aFullScreen, targetScreen);
+ }));
+
+ return NS_OK;
+}
+
+nsresult HeadlessWidget::AttachNativeKeyEvent(WidgetKeyboardEvent& aEvent) {
+ HeadlessKeyBindings& bindings = HeadlessKeyBindings::GetInstance();
+ return bindings.AttachNativeKeyEvent(aEvent);
+}
+
+bool HeadlessWidget::GetEditCommands(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands) {
+ // Validate the arguments.
+ if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
+ return false;
+ }
+
+ HeadlessKeyBindings& bindings = HeadlessKeyBindings::GetInstance();
+ bindings.GetEditCommands(aType, aEvent, aCommands);
+ return true;
+}
+
+nsresult HeadlessWidget::DispatchEvent(WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) {
+#ifdef DEBUG
+ debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "HeadlessWidget", 0);
+#endif
+
+ aStatus = nsEventStatus_eIgnore;
+
+ if (mAttachedWidgetListener) {
+ aStatus = mAttachedWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
+ } else if (mWidgetListener) {
+ aStatus = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
+ }
+
+ return NS_OK;
+}
+
+nsresult HeadlessWidget::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+ EventMessage msg;
+ switch (aNativeMessage) {
+ case MOZ_HEADLESS_MOUSE_MOVE:
+ msg = eMouseMove;
+ break;
+ case MOZ_HEADLESS_MOUSE_DOWN:
+ msg = eMouseDown;
+ break;
+ case MOZ_HEADLESS_MOUSE_UP:
+ msg = eMouseUp;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsupported synthesized mouse event");
+ return NS_ERROR_UNEXPECTED;
+ }
+ WidgetMouseEvent event(true, msg, this, WidgetMouseEvent::eReal);
+ event.mRefPoint = aPoint - WidgetToScreenOffset();
+ if (msg == eMouseDown || msg == eMouseUp) {
+ event.mButton = MouseButton::ePrimary;
+ }
+ if (msg == eMouseDown) {
+ event.mClickCount = 1;
+ }
+ event.AssignEventTime(WidgetEventTime());
+ DispatchInputEvent(&event);
+ return NS_OK;
+}
+
+nsresult HeadlessWidget::SynthesizeNativeMouseScrollEvent(
+ mozilla::LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage,
+ double aDeltaX, double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mousescrollevent");
+ printf(">>> DEBUG_ME: Synth: aDeltaY=%f\n", aDeltaY);
+ // The various platforms seem to handle scrolling deltas differently,
+ // but the following seems to emulate it well enough.
+ WidgetWheelEvent event(true, eWheel, this);
+ event.mDeltaMode = MOZ_HEADLESS_SCROLL_DELTA_MODE;
+ event.mIsNoLineOrPageDelta = true;
+ event.mDeltaX = -aDeltaX * MOZ_HEADLESS_SCROLL_MULTIPLIER;
+ event.mDeltaY = -aDeltaY * MOZ_HEADLESS_SCROLL_MULTIPLIER;
+ event.mDeltaZ = -aDeltaZ * MOZ_HEADLESS_SCROLL_MULTIPLIER;
+ event.mRefPoint = aPoint - WidgetToScreenOffset();
+ event.AssignEventTime(WidgetEventTime());
+ DispatchInputEvent(&event);
+ return NS_OK;
+}
+
+nsresult HeadlessWidget::SynthesizeNativeTouchPoint(
+ uint32_t aPointerId, TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint, double aPointerPressure,
+ uint32_t aPointerOrientation, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aPointerState == TOUCH_HOVER) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mSynthesizedTouchInput) {
+ mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
+ }
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
+ mSynthesizedTouchInput.get(), PR_IntervalNow(), TimeStamp::Now(),
+ aPointerId, aPointerState, pointInWindow, aPointerPressure,
+ aPointerOrientation);
+ DispatchTouchInput(inputToDispatch);
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/headless/HeadlessWidget.h b/widget/headless/HeadlessWidget.h
new file mode 100644
index 0000000000..25cb1623ba
--- /dev/null
+++ b/widget/headless/HeadlessWidget.h
@@ -0,0 +1,187 @@
+/* -*- 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 HEADLESSWIDGET_H
+#define HEADLESSWIDGET_H
+
+#include "mozilla/widget/InProcessCompositorWidget.h"
+#include "nsBaseWidget.h"
+#include "CompositorWidget.h"
+#include "mozilla/dom/WheelEventBinding.h"
+
+// The various synthesized event values are hardcoded to avoid pulling
+// in the platform specific widget code.
+#if defined(MOZ_WIDGET_GTK)
+# define MOZ_HEADLESS_MOUSE_MOVE 3 // GDK_MOTION_NOTIFY
+# define MOZ_HEADLESS_MOUSE_DOWN 4 // GDK_BUTTON_PRESS
+# define MOZ_HEADLESS_MOUSE_UP 7 // GDK_BUTTON_RELEASE
+# define MOZ_HEADLESS_SCROLL_MULTIPLIER 3
+# define MOZ_HEADLESS_SCROLL_DELTA_MODE \
+ mozilla::dom::WheelEvent_Binding::DOM_DELTA_LINE
+#elif defined(XP_WIN)
+# define MOZ_HEADLESS_MOUSE_MOVE 1 // MOUSEEVENTF_MOVE
+# define MOZ_HEADLESS_MOUSE_DOWN 2 // MOUSEEVENTF_LEFTDOWN
+# define MOZ_HEADLESS_MOUSE_UP 4 // MOUSEEVENTF_LEFTUP
+# define MOZ_HEADLESS_SCROLL_MULTIPLIER \
+ .025 // default scroll lines (3) / WHEEL_DELTA (120)
+# define MOZ_HEADLESS_SCROLL_DELTA_MODE \
+ mozilla::dom::WheelEvent_Binding::DOM_DELTA_LINE
+#elif defined(XP_MACOSX)
+# define MOZ_HEADLESS_MOUSE_MOVE 5 // NSEventTypeMouseMoved
+# define MOZ_HEADLESS_MOUSE_DOWN 1 // NSEventTypeLeftMouseDown
+# define MOZ_HEADLESS_MOUSE_UP 2 // NSEventTypeLeftMouseUp
+# define MOZ_HEADLESS_SCROLL_MULTIPLIER 1
+# define MOZ_HEADLESS_SCROLL_DELTA_MODE \
+ mozilla::dom::WheelEvent_Binding::DOM_DELTA_PIXEL
+#elif defined(ANDROID)
+# define MOZ_HEADLESS_MOUSE_MOVE 7 // ACTION_HOVER_MOVE
+# define MOZ_HEADLESS_MOUSE_DOWN 5 // ACTION_POINTER_DOWN
+# define MOZ_HEADLESS_MOUSE_UP 6 // ACTION_POINTER_UP
+# define MOZ_HEADLESS_SCROLL_MULTIPLIER 1
+# define MOZ_HEADLESS_SCROLL_DELTA_MODE \
+ mozilla::dom::WheelEvent_Binding::DOM_DELTA_LINE
+#else
+# define MOZ_HEADLESS_MOUSE_MOVE -1
+# define MOZ_HEADLESS_MOUSE_DOWN -1
+# define MOZ_HEADLESS_MOUSE_UP -1
+# define MOZ_HEADLESS_SCROLL_MULTIPLIER -1
+# define MOZ_HEADLESS_SCROLL_DELTA_MODE -1
+#endif
+
+namespace mozilla {
+namespace widget {
+
+class HeadlessWidget : public nsBaseWidget {
+ public:
+ HeadlessWidget();
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(HeadlessWidget, nsBaseWidget)
+
+ void* GetNativeData(uint32_t aDataType) override {
+ // Headless widgets have no native data.
+ return nullptr;
+ }
+
+ virtual nsresult Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr) override;
+ using nsBaseWidget::Create; // for Create signature not overridden here
+ virtual already_AddRefed<nsIWidget> CreateChild(
+ const LayoutDeviceIntRect& aRect, nsWidgetInitData* aInitData = nullptr,
+ bool aForceUseIWidgetParent = false) override;
+
+ virtual nsIWidget* GetTopLevelWidget() override;
+
+ virtual void GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) override;
+
+ virtual void Destroy() override;
+ virtual void Show(bool aState) override;
+ virtual bool IsVisible() const override;
+ virtual void Move(double aX, double aY) override;
+ virtual void Resize(double aWidth, double aHeight, bool aRepaint) override;
+ virtual void Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ virtual nsresult MakeFullScreen(bool aFullScreen,
+ nsIScreen* aTargetScreen = nullptr) override;
+ virtual void Enable(bool aState) override;
+ virtual bool IsEnabled() const override;
+ virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override;
+ virtual nsresult ConfigureChildren(
+ const nsTArray<Configuration>& aConfigurations) override {
+ MOZ_ASSERT_UNREACHABLE(
+ "Headless widgets do not support configuring children.");
+ return NS_ERROR_FAILURE;
+ }
+ virtual void Invalidate(const LayoutDeviceIntRect& aRect) override {
+ // TODO: see if we need to do anything here.
+ }
+ virtual nsresult SetTitle(const nsAString& title) override {
+ // Headless widgets have no title, so just ignore it.
+ return NS_OK;
+ }
+ virtual nsresult SetNonClientMargins(
+ LayoutDeviceIntMargin& margins) override {
+ // Headless widgets have no chrome margins, so just ignore the call.
+ return NS_OK;
+ }
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ virtual void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override {
+ mInputContext = aContext;
+ }
+ virtual InputContext GetInputContext() override { return mInputContext; }
+
+ virtual LayerManager* GetLayerManager(
+ PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
+
+ void SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) override;
+
+ [[nodiscard]] virtual nsresult AttachNativeKeyEvent(
+ WidgetKeyboardEvent& aEvent) override;
+ virtual bool GetEditCommands(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands) override;
+
+ virtual nsresult DispatchEvent(WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) 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, MOZ_HEADLESS_MOUSE_MOVE, 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;
+
+ private:
+ ~HeadlessWidget();
+ bool mEnabled;
+ bool mVisible;
+ bool mDestroyed;
+ nsIWidget* mTopLevel;
+ HeadlessCompositorWidget* mCompositorWidget;
+ // The size mode before entering fullscreen mode.
+ nsSizeMode mLastSizeMode;
+ // The last size mode set while the window was visible.
+ nsSizeMode mEffectiveSizeMode;
+ InputContext mInputContext;
+ mozilla::UniquePtr<mozilla::MultiTouchInput> mSynthesizedTouchInput;
+ // In headless there is no window manager to track window bounds
+ // across size mode changes, so we must track it to emulate.
+ LayoutDeviceIntRect mRestoreBounds;
+ void ApplySizeModeSideEffects();
+ // Similarly, we must track the active window ourselves in order
+ // to dispatch (de)activation events properly.
+ void RaiseWindow();
+ // The top level widgets are tracked for window ordering. They are
+ // stored in order of activation where the last element is always the
+ // currently active widget.
+ static StaticAutoPtr<nsTArray<HeadlessWidget*>> sActiveWindows;
+ // Get the most recently activated widget or null if there are none.
+ static already_AddRefed<HeadlessWidget> GetActiveWindow();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/headless/HeadlessWidgetTypes.ipdlh b/widget/headless/HeadlessWidgetTypes.ipdlh
new file mode 100644
index 0000000000..3dba0a2ddd
--- /dev/null
+++ b/widget/headless/HeadlessWidgetTypes.ipdlh
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+using mozilla::LayoutDeviceIntSize from "Units.h";
+
+namespace mozilla {
+namespace widget {
+
+struct HeadlessCompositorWidgetInitData
+{
+ LayoutDeviceIntSize InitialClientSize;
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/headless/moz.build b/widget/headless/moz.build
new file mode 100644
index 0000000000..ea9a69c11b
--- /dev/null
+++ b/widget/headless/moz.build
@@ -0,0 +1,49 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Firefox", "Headless")
+
+DIRS += ["tests"]
+
+LOCAL_INCLUDES += [
+ "/widget",
+ "/widget/headless",
+]
+
+widget_dir = CONFIG["MOZ_WIDGET_TOOLKIT"]
+
+LOCAL_INCLUDES += [
+ "/widget/%s" % widget_dir,
+]
+
+UNIFIED_SOURCES += [
+ "HeadlessClipboard.cpp",
+ "HeadlessClipboardData.cpp",
+ "HeadlessCompositorWidget.cpp",
+ "HeadlessScreenHelper.cpp",
+ "HeadlessSound.cpp",
+ "HeadlessWidget.cpp",
+]
+
+if widget_dir == "gtk":
+ UNIFIED_SOURCES += [
+ "HeadlessLookAndFeelGTK.cpp",
+ "HeadlessThemeGTK.cpp",
+ ]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ UNIFIED_SOURCES += [
+ "HeadlessKeyBindingsCocoa.mm",
+ ]
+else:
+ UNIFIED_SOURCES += [
+ "HeadlessKeyBindings.cpp",
+ ]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/widget/headless/tests/.eslintrc.js b/widget/headless/tests/.eslintrc.js
new file mode 100644
index 0000000000..69e89d0054
--- /dev/null
+++ b/widget/headless/tests/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/xpcshell-test"],
+};
diff --git a/widget/headless/tests/headless.html b/widget/headless/tests/headless.html
new file mode 100644
index 0000000000..bbde895077
--- /dev/null
+++ b/widget/headless/tests/headless.html
@@ -0,0 +1,6 @@
+<html>
+<head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"></head>
+<body style="background-color: rgb(0, 255, 0); color: rgb(0, 0, 255)">
+Hi
+</body>
+</html>
diff --git a/widget/headless/tests/headless_button.html b/widget/headless/tests/headless_button.html
new file mode 100644
index 0000000000..5641066bfe
--- /dev/null
+++ b/widget/headless/tests/headless_button.html
@@ -0,0 +1,6 @@
+<html>
+<head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"></head>
+<body>
+<button id="btn">button</button>
+</body>
+</html>
diff --git a/widget/headless/tests/moz.build b/widget/headless/tests/moz.build
new file mode 100644
index 0000000000..e48fa734e0
--- /dev/null
+++ b/widget/headless/tests/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ["xpcshell.ini"]
diff --git a/widget/headless/tests/test_headless.js b/widget/headless/tests/test_headless.js
new file mode 100644
index 0000000000..79486a9d8c
--- /dev/null
+++ b/widget/headless/tests/test_headless.js
@@ -0,0 +1,215 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+const gProfDir = do_get_profile();
+const server = new HttpServer();
+server.registerDirectory("/", do_get_cwd());
+server.start(-1);
+const ROOT = `http://localhost:${server.identity.primaryPort}`;
+const BASE = `${ROOT}/`;
+const HEADLESS_URL = `${BASE}/headless.html`;
+const HEADLESS_BUTTON_URL = `${BASE}/headless_button.html`;
+registerCleanupFunction(() => {
+ server.stop(() => {});
+});
+
+// Refrences to the progress listeners to keep them from being gc'ed
+// before they are called.
+const progressListeners = new Map();
+
+function loadContentWindow(windowlessBrowser, uri) {
+ return new Promise((resolve, reject) => {
+ let loadURIOptions = {
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ };
+ windowlessBrowser.loadURI(uri, loadURIOptions);
+ let docShell = windowlessBrowser.docShell;
+ let webProgress = docShell
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ let progressListener = {
+ onLocationChange(progress, request, location, flags) {
+ // Ignore inner-frame events
+ if (progress != webProgress) {
+ return;
+ }
+ // Ignore events that don't change the document
+ if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
+ return;
+ }
+ let contentWindow = docShell.domWindow;
+ webProgress.removeProgressListener(progressListener);
+ progressListeners.delete(progressListener);
+ contentWindow.addEventListener(
+ "load",
+ event => {
+ resolve(contentWindow);
+ },
+ { once: true }
+ );
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+ progressListeners.set(progressListener, progressListener);
+ webProgress.addProgressListener(
+ progressListener,
+ Ci.nsIWebProgress.NOTIFY_LOCATION
+ );
+ });
+}
+
+add_task(async function test_snapshot() {
+ let windowlessBrowser = Services.appShell.createWindowlessBrowser(false);
+ let contentWindow = await loadContentWindow(windowlessBrowser, HEADLESS_URL);
+ const contentWidth = 400;
+ const contentHeight = 300;
+ // Verify dimensions.
+ contentWindow.resizeTo(contentWidth, contentHeight);
+ equal(contentWindow.innerWidth, contentWidth);
+ equal(contentWindow.innerHeight, contentHeight);
+
+ // Snapshot the test page.
+ let canvas = contentWindow.document.createElementNS(
+ "http://www.w3.org/1999/xhtml",
+ "html:canvas"
+ );
+ let context = canvas.getContext("2d");
+ let width = contentWindow.innerWidth;
+ let height = contentWindow.innerHeight;
+ canvas.width = width;
+ canvas.height = height;
+ context.drawWindow(contentWindow, 0, 0, width, height, "rgb(255, 255, 255)");
+ let imageData = context.getImageData(0, 0, width, height).data;
+ ok(
+ imageData[0] === 0 &&
+ imageData[1] === 255 &&
+ imageData[2] === 0 &&
+ imageData[3] === 255,
+ "Page is green."
+ );
+
+ // Search for a blue pixel (a quick and dirty check to see if the blue text is
+ // on the page)
+ let found = false;
+ for (let i = 0; i < imageData.length; i += 4) {
+ if (imageData[i + 2] === 255) {
+ found = true;
+ break;
+ }
+ }
+ ok(found, "Found blue text on page.");
+
+ windowlessBrowser.close();
+});
+
+add_task(async function test_snapshot_widget_layers() {
+ let windowlessBrowser = Services.appShell.createWindowlessBrowser(false);
+ // nsIWindowlessBrowser inherits from nsIWebNavigation.
+ let contentWindow = await loadContentWindow(windowlessBrowser, HEADLESS_URL);
+ const contentWidth = 1;
+ const contentHeight = 2;
+ // Verify dimensions.
+ contentWindow.resizeTo(contentWidth, contentHeight);
+ equal(contentWindow.innerWidth, contentWidth);
+ equal(contentWindow.innerHeight, contentHeight);
+
+ // Snapshot the test page.
+ let canvas = contentWindow.document.createElementNS(
+ "http://www.w3.org/1999/xhtml",
+ "html:canvas"
+ );
+ let context = canvas.getContext("2d");
+ let width = contentWindow.innerWidth;
+ let height = contentWindow.innerHeight;
+ canvas.width = width;
+ canvas.height = height;
+ context.drawWindow(
+ contentWindow,
+ 0,
+ 0,
+ width,
+ height,
+ "rgb(255, 255, 255)",
+ context.DRAWWINDOW_DRAW_CARET |
+ context.DRAWWINDOW_DRAW_VIEW |
+ context.DRAWWINDOW_USE_WIDGET_LAYERS
+ );
+ ok(true, "Snapshot with widget layers didn't crash.");
+
+ windowlessBrowser.close();
+});
+
+// Ensure keydown events are triggered on the windowless browser.
+add_task(async function test_keydown() {
+ let windowlessBrowser = Services.appShell.createWindowlessBrowser(false);
+ // nsIWindowlessBrowser inherits from nsIWebNavigation.
+ let contentWindow = await loadContentWindow(windowlessBrowser, HEADLESS_URL);
+
+ let keydown = new Promise(resolve => {
+ contentWindow.addEventListener(
+ "keydown",
+ () => {
+ resolve();
+ },
+ { once: true }
+ );
+ });
+
+ let tip = Cc["@mozilla.org/text-input-processor;1"].createInstance(
+ Ci.nsITextInputProcessor
+ );
+ let begun = tip.beginInputTransactionForTests(contentWindow);
+ ok(
+ begun,
+ "nsITextInputProcessor.beginInputTransactionForTests() should succeed"
+ );
+ tip.keydown(
+ new contentWindow.KeyboardEvent("", {
+ key: "a",
+ code: "KeyA",
+ keyCode: contentWindow.KeyboardEvent.DOM_VK_A,
+ })
+ );
+
+ await keydown;
+ ok(true, "Send keydown didn't crash");
+
+ windowlessBrowser.close();
+});
+
+// Test dragging the mouse on a button to ensure the creation of the drag
+// service doesn't crash in headless.
+add_task(async function test_mouse_drag() {
+ let windowlessBrowser = Services.appShell.createWindowlessBrowser(false);
+ // nsIWindowlessBrowser inherits from nsIWebNavigation.
+ let contentWindow = await loadContentWindow(
+ windowlessBrowser,
+ HEADLESS_BUTTON_URL
+ );
+ contentWindow.resizeTo(400, 400);
+
+ let target = contentWindow.document.getElementById("btn");
+ let rect = target.getBoundingClientRect();
+ let left = rect.left;
+ let top = rect.top;
+
+ let utils = contentWindow.windowUtils;
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mousemove", left, top, 0, 1, 0, false, 0, 0);
+ // Wait for a turn of the event loop since the synthetic mouse event
+ // that creates the drag service is processed during the refresh driver.
+ await new Promise(r => {
+ executeSoon(r);
+ });
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+
+ ok(true, "Send mouse event didn't crash");
+
+ windowlessBrowser.close();
+});
diff --git a/widget/headless/tests/test_headless_clipboard.js b/widget/headless/tests/test_headless_clipboard.js
new file mode 100644
index 0000000000..6c0ffcd5a1
--- /dev/null
+++ b/widget/headless/tests/test_headless_clipboard.js
@@ -0,0 +1,46 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+function getString(clipboard) {
+ var str = "";
+
+ // Create transferable that will transfer the text.
+ var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
+ Ci.nsITransferable
+ );
+ trans.init(null);
+ trans.addDataFlavor("text/unicode");
+
+ clipboard.getData(trans, Ci.nsIClipboard.kGlobalClipboard);
+
+ try {
+ var data = {};
+ trans.getTransferData("text/unicode", data);
+
+ if (data) {
+ data = data.value.QueryInterface(Ci.nsISupportsString);
+ str = data.data;
+ }
+ } catch (ex) {
+ // If the clipboard is empty getTransferData will throw.
+ }
+
+ return str;
+}
+
+add_task(async function test_clipboard() {
+ let clipboard = Services.clipboard;
+
+ // Test copy.
+ const data = "random number: " + Math.random();
+ let helper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
+ Ci.nsIClipboardHelper
+ );
+ helper.copyString(data);
+ equal(getString(clipboard), data, "Data was successfully copied.");
+
+ clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
+ equal(getString(clipboard), "", "Data was successfully cleared.");
+});
diff --git a/widget/headless/tests/xpcshell.ini b/widget/headless/tests/xpcshell.ini
new file mode 100644
index 0000000000..307a12cb0d
--- /dev/null
+++ b/widget/headless/tests/xpcshell.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+skip-if = toolkit != "gtk" && toolkit != "windows"
+headless = true
+
+[test_headless_clipboard.js]
+[test_headless.js]
+skip-if = appname == "thunderbird"
+support-files =
+ headless.html
+ headless_button.html
diff --git a/widget/moz.build b/widget/moz.build
new file mode 100644
index 0000000000..0645f2d46d
--- /dev/null
+++ b/widget/moz.build
@@ -0,0 +1,363 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget")
+
+with Files("crashtests/*1128214*"):
+ BUG_COMPONENT = ("Core", "Layout")
+
+with Files("crashtests/*303901*"):
+ BUG_COMPONENT = ("Core", "Graphics")
+
+with Files("crashtests/*380359*"):
+ BUG_COMPONENT = ("Core", "Widget")
+
+with Files("reftests/**"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("reftests/*fallback*"):
+ BUG_COMPONENT = ("Core", "Layout: Form Controls")
+
+with Files("*CompositorWidget*"):
+ BUG_COMPONENT = ("Core", "Graphics")
+
+with Files("*Gfx*"):
+ BUG_COMPONENT = ("Core", "Graphics")
+
+with Files("*WindowSurface*"):
+ BUG_COMPONENT = ("Core", "Graphics")
+
+with Files("*FontRange*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+toolkit = CONFIG["MOZ_WIDGET_TOOLKIT"]
+
+if toolkit in ("cocoa", "android", "uikit"):
+ DIRS += [toolkit]
+
+if toolkit == "windows":
+ DIRS += ["windows"]
+
+ XPIDL_SOURCES += [
+ "nsIJumpListBuilder.idl",
+ "nsIJumpListItem.idl",
+ "nsIPrintSettingsWin.idl",
+ "nsITaskbarOverlayIconController.idl",
+ "nsITaskbarPreview.idl",
+ "nsITaskbarPreviewButton.idl",
+ "nsITaskbarPreviewController.idl",
+ "nsITaskbarProgress.idl",
+ "nsITaskbarTabPreview.idl",
+ "nsITaskbarWindowPreview.idl",
+ "nsIWindowsUIUtils.idl",
+ "nsIWinTaskbar.idl",
+ ]
+elif toolkit == "cocoa":
+ XPIDL_SOURCES += [
+ "nsIMacDockSupport.idl",
+ "nsIMacFinderProgress.idl",
+ "nsIMacSharingService.idl",
+ "nsIMacWebAppUtils.idl",
+ "nsIStandaloneNativeMenu.idl",
+ "nsITaskbarProgress.idl",
+ "nsITouchBarHelper.idl",
+ "nsITouchBarInput.idl",
+ "nsITouchBarUpdater.idl",
+ ]
+ EXPORTS += [
+ "nsINativeMenuService.h",
+ ]
+
+TEST_DIRS += ["tests", "tests/gtest"]
+
+DIRS += ["headless"]
+
+# Don't build the DSO under the 'build' directory as windows does.
+#
+# The DSOs get built in the toolkit dir itself. Do this so that
+# multiple implementations of widget can be built on the same
+# source tree.
+#
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ DIRS += ["gtk"]
+
+ XPIDL_SOURCES += [
+ "nsIGtkTaskbarProgress.idl",
+ "nsITaskbarProgress.idl",
+ ]
+
+XPIDL_SOURCES += [
+ "nsIAppShell.idl",
+ "nsIBaseWindow.idl",
+ "nsIBidiKeyboard.idl",
+ "nsIClipboard.idl",
+ "nsIClipboardHelper.idl",
+ "nsIClipboardOwner.idl",
+ "nsIColorPicker.idl",
+ "nsIDisplayInfo.idl",
+ "nsIDragService.idl",
+ "nsIDragSession.idl",
+ "nsIFilePicker.idl",
+ "nsIFormatConverter.idl",
+ "nsIGfxInfo.idl",
+ "nsIGfxInfoDebug.idl",
+ "nsIPaper.idl",
+ "nsIPaperMargin.idl",
+ "nsIPrinter.idl",
+ "nsIPrinterList.idl",
+ "nsIPrintSession.idl",
+ "nsIPrintSettings.idl",
+ "nsIPrintSettingsService.idl",
+ "nsIScreen.idl",
+ "nsIScreenManager.idl",
+ "nsISharePicker.idl",
+ "nsISound.idl",
+ "nsISystemStatusBar.idl",
+ "nsITransferable.idl",
+ "nsIUserIdleService.idl",
+ "nsIUserIdleServiceInternal.idl",
+]
+
+XPIDL_MODULE = "widget"
+
+EXPORTS += [
+ "GfxDriverInfo.h",
+ "GfxInfoBase.h",
+ "GfxInfoCollector.h",
+ "InputData.h",
+ "nsBaseAppShell.h",
+ "nsBaseDragService.h",
+ "nsBaseFilePicker.h",
+ "nsBaseScreen.h",
+ "nsBaseWidget.h",
+ "nsIDeviceContextSpec.h",
+ "nsIKeyEventInPluginCallback.h",
+ "nsIPluginWidget.h",
+ "nsIPrintDialogService.h",
+ "nsIRollupListener.h",
+ "nsIWidget.h",
+ "nsIWidgetListener.h",
+ "nsPaper.h",
+ "nsPrinterListBase.h",
+ "nsUserIdleService.h",
+ "nsWidgetInitData.h",
+ "nsWidgetsCID.h",
+ "PuppetWidget.h",
+]
+
+EXPORTS.mozilla += [
+ "BasicEvents.h",
+ "CommandList.h",
+ "ContentCache.h",
+ "ContentEvents.h",
+ "EventClassList.h",
+ "EventForwards.h",
+ "EventMessageList.h",
+ "FontRange.h",
+ "LookAndFeel.h",
+ "MiscEvents.h",
+ "MouseEvents.h",
+ "TextEventDispatcher.h",
+ "TextEventDispatcherListener.h",
+ "TextEvents.h",
+ "TextRange.h",
+ "TouchEvents.h",
+ "VsyncDispatcher.h",
+ "WidgetUtils.h",
+]
+
+EXPORTS.mozilla.widget += [
+ "CompositorWidget.h",
+ "IconLoader.h",
+ "IMEData.h",
+ "InProcessCompositorWidget.h",
+ "MediaKeysEventSourceFactory.h",
+ "nsAutoRollup.h",
+ "nsXPLookAndFeel.h",
+ "PuppetBidiKeyboard.h",
+ "RemoteLookAndFeel.h",
+ "Screen.h",
+ "ScreenManager.h",
+ "ThemeChangeKind.h",
+ "WidgetMessageUtils.h",
+ "WindowSurface.h",
+]
+
+UNIFIED_SOURCES += [
+ "CompositorWidget.cpp",
+ "ContentCache.cpp",
+ "GfxDriverInfo.cpp",
+ "GfxInfoBase.cpp",
+ "GfxInfoCollector.cpp",
+ "IconLoader.cpp",
+ "IMEData.cpp",
+ "InProcessCompositorWidget.cpp",
+ "InputData.cpp",
+ "nsAutoRollup.cpp",
+ "nsBaseAppShell.cpp",
+ "nsBaseScreen.cpp",
+ "nsClipboardHelper.cpp",
+ "nsClipboardProxy.cpp",
+ "nsColorPickerProxy.cpp",
+ "nsContentProcessWidgetFactory.cpp",
+ "nsDragServiceProxy.cpp",
+ "nsFilePickerProxy.cpp",
+ "nsHTMLFormatConverter.cpp",
+ "nsIWidgetListener.cpp",
+ "nsNativeBasicTheme.cpp",
+ "nsPrimitiveHelpers.cpp",
+ "nsPrintSettingsImpl.cpp",
+ "nsSoundProxy.cpp",
+ "nsTransferable.cpp",
+ "nsUserIdleService.cpp",
+ "nsXPLookAndFeel.cpp",
+ "PuppetBidiKeyboard.cpp",
+ "PuppetWidget.cpp",
+ "RemoteLookAndFeel.cpp",
+ "Screen.cpp",
+ "ScrollbarDrawingMac.cpp",
+ "SharedWidgetUtils.cpp",
+ "TextEventDispatcher.cpp",
+ "TouchResampler.cpp",
+ "VsyncDispatcher.cpp",
+ "WidgetEventImpl.cpp",
+ "WidgetUtils.cpp",
+]
+
+if CONFIG["OS_ARCH"] == "Linux":
+ EXPORTS.mozilla.widget += ["LSBUtils.h"]
+ SOURCES += ["LSBUtils.cpp"]
+
+if CONFIG["MOZ_XUL"] and CONFIG["NS_PRINTING"]:
+ EXPORTS += [
+ "nsDeviceContextSpecProxy.h",
+ "nsPrintSettingsService.h",
+ ]
+ UNIFIED_SOURCES += [
+ "nsDeviceContextSpecProxy.cpp",
+ "nsPaper.cpp",
+ "nsPaperMargin.cpp",
+ "nsPrinterBase.cpp",
+ "nsPrinterListBase.cpp",
+ "nsPrintSession.cpp",
+ "nsPrintSettingsService.cpp",
+ ]
+
+ if toolkit in ("cocoa", "gtk"):
+ UNIFIED_SOURCES += [
+ "nsCUPSShim.cpp",
+ "nsPrinterCUPS.cpp",
+ "nsPrinterListCUPS.cpp",
+ ]
+
+# nsBaseWidget.cpp needs to be built separately because of name clashes in the OS X headers
+# nsBaseDragService.cpp moved out of UNIFIED to fix xgill crash (bug 1259850) after moving widget/ContentHelper -> apz/util/TouchActionHelper
+SOURCES += [
+ "nsBaseDragService.cpp",
+ "nsBaseWidget.cpp",
+ "ScreenManager.cpp",
+]
+
+if CONFIG["MOZ_INSTRUMENT_EVENT_LOOP"]:
+ EXPORTS.mozilla += [
+ "WidgetTraceEvent.h",
+ ]
+
+EXPORTS.ipc = ["nsGUIEventIPC.h"]
+
+if CONFIG["MOZ_X11"]:
+ DIRS += ["x11"]
+ SOURCES += [
+ "GfxInfoX11.cpp",
+ "nsShmImage.cpp",
+ "WindowSurfaceX11SHM.cpp",
+ ]
+
+if toolkit == "windows":
+ EXPORTS += [
+ "PluginWidgetProxy.h",
+ ]
+ SOURCES += [
+ "PluginWidgetProxy.cpp",
+ ]
+
+if toolkit in ("cocoa", "windows"):
+ UNIFIED_SOURCES += [
+ "nsBaseClipboard.cpp",
+ ]
+
+if toolkit in {"gtk", "cocoa", "windows", "android", "uikit"}:
+ UNIFIED_SOURCES += [
+ "nsBaseFilePicker.cpp",
+ ]
+
+if toolkit in ("gtk", "windows", "cocoa", "android"):
+ UNIFIED_SOURCES += [
+ "nsNativeTheme.cpp",
+ ]
+if toolkit == "gtk":
+ XPIDL_SOURCES += [
+ "nsIApplicationChooser.idl",
+ ]
+
+DEFINES["MOZ_CROSS_PROCESS_IME"] = True
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/dom/ipc",
+ "/gfx/2d",
+ "/layout/base",
+ "/layout/forms",
+ "/layout/generic",
+ "/layout/painting",
+ "/layout/xul",
+ "/layout/xul/tree/",
+ "/view",
+ "/widget",
+ "/widget/headless",
+]
+
+# We use the CUPS headers on Cocoa and GTK, but on GTK we don't depend on there being system headers.
+if toolkit == "gtk":
+ LOCAL_INCLUDES += ["/third_party/cups/include"]
+
+if toolkit == "windows":
+ IPDL_SOURCES = [
+ "headless/HeadlessWidgetTypes.ipdlh",
+ "windows/PCompositorWidget.ipdl",
+ "windows/PlatformWidgetTypes.ipdlh",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk" and CONFIG["MOZ_X11"]:
+ IPDL_SOURCES = [
+ "gtk/PCompositorWidget.ipdl",
+ "gtk/PlatformWidgetTypes.ipdlh",
+ "headless/HeadlessWidgetTypes.ipdlh",
+ ]
+else:
+ IPDL_SOURCES = [
+ "generic/PCompositorWidget.ipdl",
+ "generic/PlatformWidgetTypes.ipdlh",
+ "headless/HeadlessWidgetTypes.ipdlh",
+ ]
+
+PREPROCESSED_IPDL_SOURCES += [
+ "LookAndFeelTypes.ipdlh",
+]
+
+LOCAL_INCLUDES += [
+ "/widget/%s" % toolkit,
+]
+FINAL_LIBRARY = "xul"
+
+if CONFIG["MOZ_ENABLE_D3D10_LAYER"]:
+ DEFINES["MOZ_ENABLE_D3D10_LAYER"] = True
+
+CXXFLAGS += CONFIG["TK_CFLAGS"]
+if CONFIG["MOZ_WAYLAND"]:
+ CXXFLAGS += CONFIG["MOZ_WAYLAND_CFLAGS"]
diff --git a/widget/nsAppShellSingleton.h b/widget/nsAppShellSingleton.h
new file mode 100644
index 0000000000..a7d440bd53
--- /dev/null
+++ b/widget/nsAppShellSingleton.h
@@ -0,0 +1,62 @@
+/* -*- 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 nsAppShellSingleton_h__
+#define nsAppShellSingleton_h__
+
+/**
+ * This file is designed to be included into the file that provides the
+ * nsIModule implementation for a particular widget toolkit.
+ *
+ * The following functions are defined:
+ * nsAppShellInit
+ * nsAppShellShutdown
+ * nsAppShellConstructor
+ *
+ * The nsAppShellInit function is designed to be used as a module constructor.
+ * If you already have a module constructor, then call nsAppShellInit from your
+ * module constructor.
+ *
+ * The nsAppShellShutdown function is designed to be used as a module
+ * destructor. If you already have a module destructor, then call
+ * nsAppShellShutdown from your module destructor.
+ *
+ * The nsAppShellConstructor function is designed to be used as a factory
+ * method for the nsAppShell class.
+ */
+
+#include "nsXULAppAPI.h"
+
+static nsIAppShell* sAppShell;
+
+static nsresult nsAppShellInit() {
+ NS_ASSERTION(!sAppShell, "already initialized");
+
+ sAppShell = new nsAppShell();
+ if (!sAppShell) return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(sAppShell);
+
+ nsresult rv = static_cast<nsAppShell*>(sAppShell)->Init();
+ // If we somehow failed to initialize the appshell, it's extremely likely
+ // that we are sufficiently hosed that continuing on is just going to lead
+ // to bad things later. By crashing early here, the crash report will
+ // potentially contain a little more insight into what's going wrong than
+ // if we waited for a crash further down the line. See also bug 1545381.
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_OK;
+}
+
+static void nsAppShellShutdown() { NS_RELEASE(sAppShell); }
+
+nsresult nsAppShellConstructor(nsISupports* outer, const nsIID& iid,
+ void** result) {
+ NS_ENSURE_TRUE(!outer, NS_ERROR_NO_AGGREGATION);
+ NS_ENSURE_TRUE(sAppShell, NS_ERROR_NOT_INITIALIZED);
+
+ return sAppShell->QueryInterface(iid, result);
+}
+
+#endif // nsAppShellSingleton_h__
diff --git a/widget/nsAutoRollup.cpp b/widget/nsAutoRollup.cpp
new file mode 100644
index 0000000000..9a043576df
--- /dev/null
+++ b/widget/nsAutoRollup.cpp
@@ -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/. */
+
+#include "mozilla/widget/nsAutoRollup.h"
+
+namespace mozilla {
+namespace widget {
+
+/*static*/
+uint32_t nsAutoRollup::sCount = 0;
+/*static*/
+StaticRefPtr<nsIContent> nsAutoRollup::sLastRollup;
+
+nsAutoRollup::nsAutoRollup() {
+ // remember if sLastRollup was null, and only clear it upon destruction
+ // if so. This prevents recursive usage of nsAutoRollup from clearing
+ // sLastRollup when it shouldn't.
+ mWasClear = !sLastRollup;
+ sCount++;
+}
+
+nsAutoRollup::nsAutoRollup(nsIContent* aRollup) {
+ MOZ_ASSERT(!sLastRollup);
+ mWasClear = true;
+ sCount++;
+ SetLastRollup(aRollup);
+}
+
+nsAutoRollup::~nsAutoRollup() {
+ if (sLastRollup && mWasClear) {
+ sLastRollup = nullptr;
+ }
+ sCount--;
+}
+
+/*static*/
+void nsAutoRollup::SetLastRollup(nsIContent* aLastRollup) {
+ // There must be at least one nsAutoRollup on the stack.
+ MOZ_ASSERT(sCount);
+
+ sLastRollup = aLastRollup;
+}
+
+/*static*/
+nsIContent* nsAutoRollup::GetLastRollup() { return sLastRollup.get(); }
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/nsAutoRollup.h b/widget/nsAutoRollup.h
new file mode 100644
index 0000000000..f6f3685887
--- /dev/null
+++ b/widget/nsAutoRollup.h
@@ -0,0 +1,54 @@
+/* -*- 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 nsAutoRollup_h__
+#define nsAutoRollup_h__
+
+#include "mozilla/Attributes.h" // for MOZ_RAII
+#include "mozilla/StaticPtr.h" // for StaticRefPtr
+#include "nsIContent.h"
+
+namespace mozilla {
+namespace widget {
+
+// A situation can occur when a mouse event occurs over a menu label while the
+// menu popup is already open. The expected behaviour is to close the popup.
+// This happens by calling nsIRollupListener::Rollup before the mouse event is
+// processed. However, in cases where the mouse event is not consumed, this
+// event will then get targeted at the menu label causing the menu to open
+// again. To prevent this, we store in sLastRollup a reference to the popup
+// that was closed during the Rollup call, and prevent this popup from
+// reopening while processing the mouse event.
+// sLastRollup can only be set while an nsAutoRollup is in scope;
+// when it goes out of scope sLastRollup is cleared automatically.
+// As sLastRollup is static, it can be retrieved by calling
+// nsAutoRollup::GetLastRollup.
+class MOZ_RAII nsAutoRollup {
+ public:
+ nsAutoRollup();
+ ~nsAutoRollup();
+
+ // Convenience constructor that creates a nsAutoRollup and also sets
+ // the last rollup.
+ explicit nsAutoRollup(nsIContent* aRollup);
+
+ static void SetLastRollup(nsIContent* aLastRollup);
+ // Return the popup that was last rolled up, or null if there isn't one.
+ static nsIContent* GetLastRollup();
+
+ private:
+ // Whether sLastRollup was clear when this nsAutoRollup
+ // was created.
+ bool mWasClear;
+
+ // The number of nsAutoRollup instances active.
+ static uint32_t sCount;
+ // The last rolled up popup.
+ static StaticRefPtr<nsIContent> sLastRollup;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // nsAutoRollup_h__
diff --git a/widget/nsBaseAppShell.cpp b/widget/nsBaseAppShell.cpp
new file mode 100644
index 0000000000..648096b8c2
--- /dev/null
+++ b/widget/nsBaseAppShell.cpp
@@ -0,0 +1,305 @@
+/* -*- 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 "base/message_loop.h"
+
+#include "nsBaseAppShell.h"
+#include "nsExceptionHandler.h"
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+#include "nsXULAppAPI.h"
+
+// When processing the next thread event, the appshell may process native
+// events (if not in performance mode), which can result in suppressing the
+// next thread event for at most this many ticks:
+#define THREAD_EVENT_STARVATION_LIMIT PR_MillisecondsToInterval(10)
+
+NS_IMPL_ISUPPORTS(nsBaseAppShell, nsIAppShell, nsIThreadObserver, nsIObserver)
+
+nsBaseAppShell::nsBaseAppShell()
+ : mSuspendNativeCount(0),
+ mEventloopNestingLevel(0),
+ mBlockedWait(nullptr),
+ mFavorPerf(0),
+ mNativeEventPending(false),
+ mStarvationDelay(0),
+ mSwitchTime(0),
+ mLastNativeEventTime(0),
+ mEventloopNestingState(eEventloopNone),
+ mRunning(false),
+ mExiting(false),
+ mBlockNativeEvent(false),
+ mProcessedGeckoEvents(false) {}
+
+nsBaseAppShell::~nsBaseAppShell() = default;
+
+nsresult nsBaseAppShell::Init() {
+ // Configure ourselves as an observer for the current thread:
+
+ if (XRE_UseNativeEventProcessing()) {
+ nsCOMPtr<nsIThreadInternal> threadInt =
+ do_QueryInterface(NS_GetCurrentThread());
+ NS_ENSURE_STATE(threadInt);
+
+ threadInt->SetObserver(this);
+ }
+
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ return NS_OK;
+}
+
+// Called by nsAppShell's native event callback
+void nsBaseAppShell::NativeEventCallback() {
+ if (!mNativeEventPending.exchange(false)) return;
+
+ // If DoProcessNextNativeEvent is on the stack, then we assume that we can
+ // just unwind and let nsThread::ProcessNextEvent process the next event.
+ // However, if we are called from a nested native event loop (maybe via some
+ // plug-in or library function), then go ahead and process Gecko events now.
+ if (mEventloopNestingState == eEventloopXPCOM) {
+ mEventloopNestingState = eEventloopOther;
+ // XXX there is a tiny risk we will never get a new NativeEventCallback,
+ // XXX see discussion in bug 389931.
+ return;
+ }
+
+ // nsBaseAppShell::Run is not being used to pump events, so this may be
+ // our only opportunity to process pending gecko events.
+
+ nsIThread* thread = NS_GetCurrentThread();
+ bool prevBlockNativeEvent = mBlockNativeEvent;
+ if (mEventloopNestingState == eEventloopOther) {
+ if (!NS_HasPendingEvents(thread)) return;
+ // We're in a nested native event loop and have some gecko events to
+ // process. While doing that we block processing native events from the
+ // appshell - instead, we want to get back to the nested native event
+ // loop ASAP (bug 420148).
+ mBlockNativeEvent = true;
+ }
+
+ IncrementEventloopNestingLevel();
+ EventloopNestingState prevVal = mEventloopNestingState;
+ NS_ProcessPendingEvents(thread, THREAD_EVENT_STARVATION_LIMIT);
+ mProcessedGeckoEvents = true;
+ mEventloopNestingState = prevVal;
+ mBlockNativeEvent = prevBlockNativeEvent;
+
+ // Continue processing pending events later (we don't want to starve the
+ // embedders event loop).
+ if (NS_HasPendingEvents(thread)) DoProcessMoreGeckoEvents();
+
+ DecrementEventloopNestingLevel();
+}
+
+// Note, this is currently overidden on windows, see comments in nsAppShell for
+// details.
+void nsBaseAppShell::DoProcessMoreGeckoEvents() { OnDispatchedEvent(); }
+
+// Main thread via OnProcessNextEvent below
+bool nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait) {
+ // The next native event to be processed may trigger our NativeEventCallback,
+ // in which case we do not want it to process any thread events since we'll
+ // do that when this function returns.
+ //
+ // If the next native event is not our NativeEventCallback, then we may end
+ // up recursing into this function.
+ //
+ // However, if the next native event is not our NativeEventCallback, but it
+ // results in another native event loop, then our NativeEventCallback could
+ // fire and it will see mEventloopNestingState as eEventloopOther.
+ //
+ EventloopNestingState prevVal = mEventloopNestingState;
+ mEventloopNestingState = eEventloopXPCOM;
+
+ IncrementEventloopNestingLevel();
+ bool result = ProcessNextNativeEvent(mayWait);
+ DecrementEventloopNestingLevel();
+
+ mEventloopNestingState = prevVal;
+ return result;
+}
+
+//-------------------------------------------------------------------------
+// nsIAppShell methods:
+
+NS_IMETHODIMP
+nsBaseAppShell::Run(void) {
+ NS_ENSURE_STATE(!mRunning); // should not call Run twice
+ mRunning = true;
+
+ nsIThread* thread = NS_GetCurrentThread();
+
+ MessageLoop::current()->Run();
+
+ NS_ProcessPendingEvents(thread);
+
+ mRunning = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseAppShell::Exit(void) {
+ if (mRunning && !mExiting) {
+ MessageLoop::current()->Quit();
+ }
+ mExiting = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseAppShell::FavorPerformanceHint(bool favorPerfOverStarvation,
+ uint32_t starvationDelay) {
+ mStarvationDelay = PR_MillisecondsToInterval(starvationDelay);
+ if (favorPerfOverStarvation) {
+ ++mFavorPerf;
+ } else {
+ --mFavorPerf;
+ mSwitchTime = PR_IntervalNow();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseAppShell::SuspendNative() {
+ ++mSuspendNativeCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseAppShell::ResumeNative() {
+ --mSuspendNativeCount;
+ NS_ASSERTION(mSuspendNativeCount >= 0,
+ "Unbalanced call to nsBaseAppShell::ResumeNative!");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseAppShell::GetEventloopNestingLevel(uint32_t* aNestingLevelResult) {
+ NS_ENSURE_ARG_POINTER(aNestingLevelResult);
+
+ *aNestingLevelResult = mEventloopNestingLevel;
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+// nsIThreadObserver methods:
+
+// Called from any thread
+NS_IMETHODIMP
+nsBaseAppShell::OnDispatchedEvent() {
+ if (mBlockNativeEvent) return NS_OK;
+
+ if (mNativeEventPending.exchange(true)) return NS_OK;
+
+ // Returns on the main thread in NativeEventCallback above
+ ScheduleNativeEventCallback();
+ return NS_OK;
+}
+
+// Called from the main thread
+NS_IMETHODIMP
+nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal* thr, bool mayWait) {
+ if (mBlockNativeEvent) {
+ if (!mayWait) return NS_OK;
+ // Hmm, we're in a nested native event loop and would like to get
+ // back to it ASAP, but it seems a gecko event has caused us to
+ // spin up a nested XPCOM event loop (eg. modal window), so we
+ // really must start processing native events here again.
+ mBlockNativeEvent = false;
+ if (NS_HasPendingEvents(thr))
+ OnDispatchedEvent(); // in case we blocked it earlier
+ }
+
+ PRIntervalTime start = PR_IntervalNow();
+ PRIntervalTime limit = THREAD_EVENT_STARVATION_LIMIT;
+
+ // Unblock outer nested wait loop (below).
+ if (mBlockedWait) *mBlockedWait = false;
+
+ bool* oldBlockedWait = mBlockedWait;
+ mBlockedWait = &mayWait;
+
+ // When mayWait is true, we need to make sure that there is an event in the
+ // thread's event queue before we return. Otherwise, the thread will block
+ // on its event queue waiting for an event.
+ bool needEvent = mayWait;
+ // Reset prior to invoking DoProcessNextNativeEvent which might cause
+ // NativeEventCallback to process gecko events.
+ mProcessedGeckoEvents = false;
+
+ if (mFavorPerf <= 0 && start > mSwitchTime + mStarvationDelay) {
+ // Favor pending native events
+ PRIntervalTime now = start;
+ bool keepGoing;
+ do {
+ mLastNativeEventTime = now;
+ keepGoing = DoProcessNextNativeEvent(false);
+ } while (keepGoing && ((now = PR_IntervalNow()) - start) < limit);
+ } else {
+ // Avoid starving native events completely when in performance mode
+ if (start - mLastNativeEventTime > limit) {
+ mLastNativeEventTime = start;
+ DoProcessNextNativeEvent(false);
+ }
+ }
+
+ while (!NS_HasPendingEvents(thr) && !mProcessedGeckoEvents) {
+ // If we have been asked to exit from Run, then we should not wait for
+ // events to process. Note that an inner nested event loop causes
+ // 'mayWait' to become false too, through 'mBlockedWait'.
+ if (mExiting) mayWait = false;
+
+ mLastNativeEventTime = PR_IntervalNow();
+ if (!DoProcessNextNativeEvent(mayWait) || !mayWait) break;
+ }
+
+ mBlockedWait = oldBlockedWait;
+
+ // Make sure that the thread event queue does not block on its monitor, as
+ // it normally would do if it did not have any pending events. To avoid
+ // that, we simply insert a dummy event into its queue during shutdown.
+ if (needEvent && !mExiting && !NS_HasPendingEvents(thr)) {
+ DispatchDummyEvent(thr);
+ }
+
+ return NS_OK;
+}
+
+bool nsBaseAppShell::DispatchDummyEvent(nsIThread* aTarget) {
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ if (!mDummyEvent) mDummyEvent = new mozilla::Runnable("DummyEvent");
+
+ return NS_SUCCEEDED(aTarget->Dispatch(mDummyEvent, NS_DISPATCH_NORMAL));
+}
+
+void nsBaseAppShell::IncrementEventloopNestingLevel() {
+ ++mEventloopNestingLevel;
+ CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel);
+}
+
+void nsBaseAppShell::DecrementEventloopNestingLevel() {
+ --mEventloopNestingLevel;
+ CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel);
+}
+
+// Called from the main thread
+NS_IMETHODIMP
+nsBaseAppShell::AfterProcessNextEvent(nsIThreadInternal* thr,
+ bool eventWasProcessed) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseAppShell::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ NS_ASSERTION(!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID), "oops");
+ Exit();
+ return NS_OK;
+}
diff --git a/widget/nsBaseAppShell.h b/widget/nsBaseAppShell.h
new file mode 100644
index 0000000000..f87fc3414d
--- /dev/null
+++ b/widget/nsBaseAppShell.h
@@ -0,0 +1,136 @@
+/* -*- 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 nsBaseAppShell_h__
+#define nsBaseAppShell_h__
+
+#include "mozilla/Atomics.h"
+#include "nsIAppShell.h"
+#include "nsIThreadInternal.h"
+#include "nsIObserver.h"
+#include "nsIRunnable.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "prinrval.h"
+
+/**
+ * A singleton that manages the UI thread's event queue. Subclass this class
+ * to enable platform-specific event queue support.
+ */
+class nsBaseAppShell : public nsIAppShell,
+ public nsIThreadObserver,
+ public nsIObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIAPPSHELL
+
+ NS_DECL_NSITHREADOBSERVER
+ NS_DECL_NSIOBSERVER
+
+ nsBaseAppShell();
+
+ protected:
+ virtual ~nsBaseAppShell();
+
+ /**
+ * This method is called by subclasses when the app shell singleton is
+ * instantiated.
+ */
+ nsresult Init();
+
+ /**
+ * Called by subclasses from a native event. See ScheduleNativeEventCallback.
+ */
+ void NativeEventCallback();
+
+ /**
+ * Make a decision as to whether or not NativeEventCallback will
+ * trigger gecko event processing when there are pending gecko
+ * events.
+ */
+ virtual void DoProcessMoreGeckoEvents();
+
+ /**
+ * Implemented by subclasses. Invoke NativeEventCallback from a native
+ * event. This method may be called on any thread.
+ */
+ virtual void ScheduleNativeEventCallback() = 0;
+
+ /**
+ * Implemented by subclasses. Process the next native event. Only wait for
+ * the next native event if mayWait is true. This method is only called on
+ * the main application thread.
+ *
+ * @param mayWait
+ * If "true", then this method may wait if necessary for the next available
+ * native event. DispatchNativeEvent may be called to unblock a call to
+ * ProcessNextNativeEvent that is waiting.
+ * @return
+ * This method returns "true" if a native event was processed.
+ */
+ virtual bool ProcessNextNativeEvent(bool mayWait) = 0;
+
+ int32_t mSuspendNativeCount;
+ uint32_t mEventloopNestingLevel;
+
+ private:
+ bool DoProcessNextNativeEvent(bool mayWait);
+
+ bool DispatchDummyEvent(nsIThread* target);
+
+ void IncrementEventloopNestingLevel();
+ void DecrementEventloopNestingLevel();
+
+ nsCOMPtr<nsIRunnable> mDummyEvent;
+ /**
+ * mBlockedWait points back to a slot that controls the wait loop in
+ * an outer OnProcessNextEvent invocation. Nested calls always set
+ * it to false to unblock an outer loop, since all events may
+ * have been consumed by the inner event loop(s).
+ */
+ bool* mBlockedWait;
+ int32_t mFavorPerf;
+ mozilla::Atomic<bool> mNativeEventPending;
+ PRIntervalTime mStarvationDelay;
+ PRIntervalTime mSwitchTime;
+ PRIntervalTime mLastNativeEventTime;
+ enum EventloopNestingState {
+ eEventloopNone, // top level thread execution
+ eEventloopXPCOM, // innermost native event loop is ProcessNextNativeEvent
+ eEventloopOther // innermost native event loop is a native library/plugin
+ // etc
+ };
+ EventloopNestingState mEventloopNestingState;
+ bool mRunning;
+ bool mExiting;
+ /**
+ * mBlockNativeEvent blocks the appshell from processing native events.
+ * It is set to true while a nested native event loop (eEventloopOther)
+ * is processing gecko events in NativeEventCallback(), thus queuing up
+ * native events until we return to that loop (bug 420148).
+ * We force mBlockNativeEvent to false in case handling one of the gecko
+ * events spins up a nested XPCOM event loop (eg. modal window) which would
+ * otherwise lead to a "deadlock" where native events aren't processed at all.
+ */
+ bool mBlockNativeEvent;
+ /**
+ * Tracks whether we have processed any gecko events in NativeEventCallback so
+ * that we can avoid erroneously entering a blocking loop waiting for gecko
+ * events to show up during OnProcessNextEvent. This is required because on
+ * OS X ProcessGeckoEvents may be invoked inside the context of
+ * ProcessNextNativeEvent and may result in NativeEventCallback being invoked
+ * and in turn invoking NS_ProcessPendingEvents. Because
+ * ProcessNextNativeEvent may be invoked prior to the NS_HasPendingEvents
+ * waiting loop, this is the only way to make the loop aware that events may
+ * have been processed.
+ *
+ * This variable is set to false in OnProcessNextEvent prior to the first
+ * call to DoProcessNextNativeEvent. It is set to true by
+ * NativeEventCallback after calling NS_ProcessPendingEvents.
+ */
+ bool mProcessedGeckoEvents;
+};
+
+#endif // nsBaseAppShell_h__
diff --git a/widget/nsBaseClipboard.cpp b/widget/nsBaseClipboard.cpp
new file mode 100644
index 0000000000..91da56d0ad
--- /dev/null
+++ b/widget/nsBaseClipboard.cpp
@@ -0,0 +1,117 @@
+/* -*- 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 "nsBaseClipboard.h"
+
+#include "nsIClipboardOwner.h"
+#include "nsCOMPtr.h"
+#include "nsXPCOM.h"
+
+nsBaseClipboard::nsBaseClipboard()
+ : mEmptyingForSetData(false), mIgnoreEmptyNotification(false) {}
+
+nsBaseClipboard::~nsBaseClipboard() {
+ EmptyClipboard(kSelectionClipboard);
+ EmptyClipboard(kGlobalClipboard);
+ EmptyClipboard(kFindClipboard);
+}
+
+NS_IMPL_ISUPPORTS(nsBaseClipboard, nsIClipboard)
+
+/**
+ * Sets the transferable object
+ *
+ */
+NS_IMETHODIMP nsBaseClipboard::SetData(nsITransferable* aTransferable,
+ nsIClipboardOwner* anOwner,
+ int32_t aWhichClipboard) {
+ NS_ASSERTION(aTransferable, "clipboard given a null transferable");
+
+ 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;
+
+ mEmptyingForSetData = true;
+ EmptyClipboard(aWhichClipboard);
+ mEmptyingForSetData = false;
+
+ mClipboardOwner = anOwner;
+ mTransferable = aTransferable;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ if (mTransferable) {
+ rv = SetNativeClipboardData(aWhichClipboard);
+ }
+
+ return rv;
+}
+
+/**
+ * Gets the transferable object
+ *
+ */
+NS_IMETHODIMP nsBaseClipboard::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 nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard) {
+ 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
+nsBaseClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
+ int32_t aWhichClipboard,
+ bool* outResult) {
+ *outResult = true; // say we always do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseClipboard::SupportsSelectionClipboard(bool* _retval) {
+ *_retval = false; // we don't support the selection clipboard by default.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseClipboard::SupportsFindClipboard(bool* _retval) {
+ *_retval = false; // we don't support the find clipboard by default.
+ return NS_OK;
+}
diff --git a/widget/nsBaseClipboard.h b/widget/nsBaseClipboard.h
new file mode 100644
index 0000000000..49dd748c0f
--- /dev/null
+++ b/widget/nsBaseClipboard.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 nsBaseClipboard_h__
+#define nsBaseClipboard_h__
+
+#include "nsIClipboard.h"
+#include "nsITransferable.h"
+#include "nsCOMPtr.h"
+
+class nsITransferable;
+class nsIClipboardOwner;
+class nsIWidget;
+
+/**
+ * Native Win32 BaseClipboard wrapper
+ */
+
+class nsBaseClipboard : public nsIClipboard {
+ public:
+ nsBaseClipboard();
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsIClipboard
+ NS_DECL_NSICLIPBOARD
+
+ protected:
+ virtual ~nsBaseClipboard();
+
+ NS_IMETHOD SetNativeClipboardData(int32_t aWhichClipboard) = 0;
+ NS_IMETHOD GetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) = 0;
+
+ bool mEmptyingForSetData;
+ bool mIgnoreEmptyNotification;
+ nsCOMPtr<nsIClipboardOwner> mClipboardOwner;
+ nsCOMPtr<nsITransferable> mTransferable;
+};
+
+#endif // nsBaseClipboard_h__
diff --git a/widget/nsBaseDragService.cpp b/widget/nsBaseDragService.cpp
new file mode 100644
index 0000000000..cfe09dc2f2
--- /dev/null
+++ b/widget/nsBaseDragService.cpp
@@ -0,0 +1,951 @@
+/* -*- 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 "nsBaseDragService.h"
+#include "nsITransferable.h"
+
+#include "nsArrayUtils.h"
+#include "nsITransferable.h"
+#include "nsSize.h"
+#include "nsXPCOM.h"
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIFrame.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsIContent.h"
+#include "nsViewManager.h"
+#include "nsINode.h"
+#include "nsPresContext.h"
+#include "nsIImageLoadingContent.h"
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "ImageRegion.h"
+#include "nsQueryObject.h"
+#include "nsRegion.h"
+#include "nsXULPopupManager.h"
+#include "nsMenuPopupFrame.h"
+#ifdef MOZ_XUL
+# include "nsTreeBodyFrame.h"
+#endif
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGImageContext.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ViewportUtils.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/DataTransferItemList.h"
+#include "mozilla/dom/DataTransfer.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/DragEvent.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/gfx/2D.h"
+#include "nsFrameLoader.h"
+#include "BrowserParent.h"
+#include "GeckoProfiler.h"
+#include "nsIMutableArray.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+#define DRAGIMAGES_PREF "nglayout.enable_drag_images"
+
+nsBaseDragService::nsBaseDragService()
+ : mCanDrop(false),
+ mOnlyChromeDrop(false),
+ mDoingDrag(false),
+ mSessionIsSynthesizedForTests(false),
+ mEndingSession(false),
+ mHasImage(false),
+ mUserCancelled(false),
+ mDragEventDispatchedToChildProcess(false),
+ mDragAction(DRAGDROP_ACTION_NONE),
+ mDragActionFromChildProcess(DRAGDROP_ACTION_UNINITIALIZED),
+ mEffectAllowedForTests(DRAGDROP_ACTION_UNINITIALIZED),
+ mContentPolicyType(nsIContentPolicy::TYPE_OTHER),
+ mSuppressLevel(0),
+ mInputSource(MouseEvent_Binding::MOZ_SOURCE_MOUSE) {}
+
+nsBaseDragService::~nsBaseDragService() = default;
+
+NS_IMPL_ISUPPORTS(nsBaseDragService, nsIDragService, nsIDragSession)
+
+//---------------------------------------------------------
+NS_IMETHODIMP
+nsBaseDragService::SetCanDrop(bool aCanDrop) {
+ mCanDrop = aCanDrop;
+ return NS_OK;
+}
+
+//---------------------------------------------------------
+NS_IMETHODIMP
+nsBaseDragService::GetCanDrop(bool* aCanDrop) {
+ *aCanDrop = mCanDrop;
+ return NS_OK;
+}
+//---------------------------------------------------------
+NS_IMETHODIMP
+nsBaseDragService::SetOnlyChromeDrop(bool aOnlyChrome) {
+ mOnlyChromeDrop = aOnlyChrome;
+ return NS_OK;
+}
+
+//---------------------------------------------------------
+NS_IMETHODIMP
+nsBaseDragService::GetOnlyChromeDrop(bool* aOnlyChrome) {
+ *aOnlyChrome = mOnlyChromeDrop;
+ return NS_OK;
+}
+
+//---------------------------------------------------------
+NS_IMETHODIMP
+nsBaseDragService::SetDragAction(uint32_t anAction) {
+ mDragAction = anAction;
+ return NS_OK;
+}
+
+//---------------------------------------------------------
+NS_IMETHODIMP
+nsBaseDragService::GetDragAction(uint32_t* anAction) {
+ *anAction = mDragAction;
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsBaseDragService::GetNumDropItems(uint32_t* aNumItems) {
+ *aNumItems = 0;
+ return NS_ERROR_FAILURE;
+}
+
+//
+// GetSourceDocument
+//
+// Returns the DOM document where the drag was initiated. This will be
+// nullptr if the drag began outside of our application.
+//
+NS_IMETHODIMP
+nsBaseDragService::GetSourceDocument(Document** aSourceDocument) {
+ *aSourceDocument = mSourceDocument.get();
+ NS_IF_ADDREF(*aSourceDocument);
+
+ return NS_OK;
+}
+
+//
+// GetSourceNode
+//
+// Returns the DOM node where the drag was initiated. This will be
+// nullptr if the drag began outside of our application.
+//
+NS_IMETHODIMP
+nsBaseDragService::GetSourceNode(nsINode** aSourceNode) {
+ *aSourceNode = do_AddRef(mSourceNode).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::GetTriggeringPrincipal(nsIPrincipal** aPrincipal) {
+ NS_IF_ADDREF(*aPrincipal = mTriggeringPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::SetTriggeringPrincipal(nsIPrincipal* aPrincipal) {
+ mTriggeringPrincipal = aPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::GetCsp(nsIContentSecurityPolicy** aCsp) {
+ NS_IF_ADDREF(*aCsp = mCsp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::SetCsp(nsIContentSecurityPolicy* aCsp) {
+ mCsp = aCsp;
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsBaseDragService::GetData(nsITransferable* aTransferable,
+ uint32_t aItemIndex) {
+ return NS_ERROR_FAILURE;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsBaseDragService::IsDataFlavorSupported(const char* aDataFlavor,
+ bool* _retval) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::GetDataTransferXPCOM(DataTransfer** aDataTransfer) {
+ *aDataTransfer = mDataTransfer;
+ NS_IF_ADDREF(*aDataTransfer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::SetDataTransferXPCOM(DataTransfer* aDataTransfer) {
+ NS_ENSURE_STATE(aDataTransfer);
+ mDataTransfer = aDataTransfer;
+ return NS_OK;
+}
+
+DataTransfer* nsBaseDragService::GetDataTransfer() { return mDataTransfer; }
+
+void nsBaseDragService::SetDataTransfer(DataTransfer* aDataTransfer) {
+ mDataTransfer = aDataTransfer;
+}
+
+bool nsBaseDragService::IsSynthesizedForTests() {
+ return mSessionIsSynthesizedForTests;
+}
+
+uint32_t nsBaseDragService::GetEffectAllowedForTests() {
+ MOZ_ASSERT(mSessionIsSynthesizedForTests);
+ return mEffectAllowedForTests;
+}
+
+NS_IMETHODIMP nsBaseDragService::SetDragEndPointForTests(int32_t aScreenX,
+ int32_t aScreenY) {
+ MOZ_ASSERT(mDoingDrag);
+ MOZ_ASSERT(mSourceDocument);
+ MOZ_ASSERT(mSessionIsSynthesizedForTests);
+ if (!mDoingDrag || !mSourceDocument || !mSessionIsSynthesizedForTests) {
+ return NS_ERROR_FAILURE;
+ }
+ nsPresContext* presContext = mSourceDocument->GetPresContext();
+ if (NS_WARN_IF(!presContext)) {
+ return NS_ERROR_FAILURE;
+ }
+ SetDragEndPoint(
+ LayoutDeviceIntPoint(presContext->CSSPixelsToDevPixels(aScreenX),
+ presContext->CSSPixelsToDevPixels(aScreenY)));
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsBaseDragService::InvokeDragSession(
+ nsINode* aDOMNode, nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp,
+ nsICookieJarSettings* aCookieJarSettings, nsIArray* aTransferableArray,
+ uint32_t aActionType,
+ nsContentPolicyType aContentPolicyType = nsIContentPolicy::TYPE_OTHER) {
+ AUTO_PROFILER_LABEL("nsBaseDragService::InvokeDragSession", OTHER);
+
+ NS_ENSURE_TRUE(aDOMNode, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
+
+ // stash the document of the dom node
+ mSourceDocument = aDOMNode->OwnerDoc();
+ mTriggeringPrincipal = aPrincipal;
+ mCsp = aCsp;
+ mSourceNode = aDOMNode;
+ mContentPolicyType = aContentPolicyType;
+ mEndDragPoint = LayoutDeviceIntPoint(0, 0);
+
+ // When the mouse goes down, the selection code starts a mouse
+ // capture. However, this gets in the way of determining drag
+ // feedback for things like trees because the event coordinates
+ // are in the wrong coord system, so turn off mouse capture.
+ PresShell::ClearMouseCapture(nullptr);
+
+ if (mSessionIsSynthesizedForTests) {
+ mDoingDrag = true;
+ mDragAction = aActionType;
+ mEffectAllowedForTests = aActionType;
+ return NS_OK;
+ }
+
+ // If you're hitting this, a test is causing the browser to attempt to enter
+ // the drag-drop native nested event loop, which will put the browser in a
+ // state that won't run tests properly until there's manual intervention
+ // to exit the drag-drop loop (either by moving the mouse or hitting escape),
+ // which can't be done from script since we're in the nested loop.
+ //
+ // The best way to avoid this is to catch the dragstart event on the item
+ // being dragged, and then to call preventDefault() and stopPropagating() on
+ // it.
+ if (XRE_IsParentProcess()) {
+ MOZ_ASSERT(
+ !xpc::IsInAutomation(),
+ "About to start drag-drop native loop on which will prevent later "
+ "tests from running properly.");
+ }
+
+ uint32_t length = 0;
+ mozilla::Unused << aTransferableArray->GetLength(&length);
+ if (!length) {
+ nsCOMPtr<nsIMutableArray> mutableArray =
+ do_QueryInterface(aTransferableArray);
+ if (mutableArray) {
+ // In order to be able trigger dnd, we need to have some transferable
+ // object.
+ nsCOMPtr<nsITransferable> trans =
+ do_CreateInstance("@mozilla.org/widget/transferable;1");
+ trans->Init(nullptr);
+ trans->SetRequestingPrincipal(mSourceNode->NodePrincipal());
+ trans->SetContentPolicyType(mContentPolicyType);
+ trans->SetCookieJarSettings(aCookieJarSettings);
+ mutableArray->AppendElement(trans);
+ }
+ } else {
+ for (uint32_t i = 0; i < length; ++i) {
+ nsCOMPtr<nsITransferable> trans =
+ do_QueryElementAt(aTransferableArray, i);
+ if (trans) {
+ // Set the requestingPrincipal on the transferable.
+ trans->SetRequestingPrincipal(mSourceNode->NodePrincipal());
+ trans->SetContentPolicyType(mContentPolicyType);
+ trans->SetCookieJarSettings(aCookieJarSettings);
+ }
+ }
+ }
+
+ nsresult rv = InvokeDragSessionImpl(aTransferableArray, mRegion, aActionType);
+
+ if (NS_FAILED(rv)) {
+ // Set mDoingDrag so that EndDragSession cleans up and sends the dragend
+ // event after the aborted drag.
+ mDoingDrag = true;
+ EndDragSession(true, 0);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::InvokeDragSessionWithImage(
+ nsINode* aDOMNode, nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp,
+ nsICookieJarSettings* aCookieJarSettings, nsIArray* aTransferableArray,
+ uint32_t aActionType, nsINode* aImage, int32_t aImageX, int32_t aImageY,
+ DragEvent* aDragEvent, DataTransfer* aDataTransfer) {
+ NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(aDataTransfer, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
+
+ mSessionIsSynthesizedForTests =
+ aDragEvent->WidgetEventPtr()->mFlags.mIsSynthesizedForTests;
+ mDataTransfer = aDataTransfer;
+ mSelection = nullptr;
+ mHasImage = true;
+ mDragPopup = nullptr;
+ mImage = aImage;
+ mImageOffset = CSSIntPoint(aImageX, aImageY);
+ mDragStartData = nullptr;
+
+ mScreenPosition.x = aDragEvent->ScreenX(CallerType::System);
+ mScreenPosition.y = aDragEvent->ScreenY(CallerType::System);
+ mInputSource = aDragEvent->MozInputSource();
+
+ // If dragging within a XUL tree and no custom drag image was
+ // set, the region argument to InvokeDragSessionWithImage needs
+ // to be set to the area encompassing the selected rows of the
+ // tree to ensure that the drag feedback gets clipped to those
+ // rows. For other content, region should be null.
+ mRegion = Nothing();
+#ifdef MOZ_XUL
+ if (aDOMNode && aDOMNode->IsContent() && !aImage) {
+ if (aDOMNode->NodeInfo()->Equals(nsGkAtoms::treechildren,
+ kNameSpaceID_XUL)) {
+ nsTreeBodyFrame* treeBody =
+ do_QueryFrame(aDOMNode->AsContent()->GetPrimaryFrame());
+ if (treeBody) {
+ mRegion = treeBody->GetSelectionRegion();
+ }
+ }
+ }
+#endif
+
+ nsresult rv = InvokeDragSession(
+ aDOMNode, aPrincipal, aCsp, aCookieJarSettings, aTransferableArray,
+ aActionType, nsIContentPolicy::TYPE_INTERNAL_IMAGE);
+ mRegion = Nothing();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::InvokeDragSessionWithRemoteImage(
+ nsINode* aDOMNode, nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp,
+ nsICookieJarSettings* aCookieJarSettings, nsIArray* aTransferableArray,
+ uint32_t aActionType, RemoteDragStartData* aDragStartData,
+ DragEvent* aDragEvent, DataTransfer* aDataTransfer) {
+ NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(aDataTransfer, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
+
+ mSessionIsSynthesizedForTests =
+ aDragEvent->WidgetEventPtr()->mFlags.mIsSynthesizedForTests;
+ mDataTransfer = aDataTransfer;
+ mSelection = nullptr;
+ mHasImage = true;
+ mDragPopup = nullptr;
+ mImage = nullptr;
+ mDragStartData = aDragStartData;
+ mImageOffset = CSSIntPoint(0, 0);
+
+ mScreenPosition.x = aDragEvent->ScreenX(CallerType::System);
+ mScreenPosition.y = aDragEvent->ScreenY(CallerType::System);
+ mInputSource = aDragEvent->MozInputSource();
+
+ nsresult rv = InvokeDragSession(
+ aDOMNode, aPrincipal, aCsp, aCookieJarSettings, aTransferableArray,
+ aActionType, nsIContentPolicy::TYPE_INTERNAL_IMAGE);
+ mRegion = Nothing();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::InvokeDragSessionWithSelection(
+ Selection* aSelection, nsIPrincipal* aPrincipal,
+ nsIContentSecurityPolicy* aCsp, nsICookieJarSettings* aCookieJarSettings,
+ nsIArray* aTransferableArray, uint32_t aActionType, DragEvent* aDragEvent,
+ DataTransfer* aDataTransfer) {
+ NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
+
+ mSessionIsSynthesizedForTests =
+ aDragEvent->WidgetEventPtr()->mFlags.mIsSynthesizedForTests;
+ mDataTransfer = aDataTransfer;
+ mSelection = aSelection;
+ mHasImage = true;
+ mDragPopup = nullptr;
+ mImage = nullptr;
+ mImageOffset = CSSIntPoint();
+ mDragStartData = nullptr;
+ mRegion = Nothing();
+
+ mScreenPosition.x = aDragEvent->ScreenX(CallerType::System);
+ mScreenPosition.y = aDragEvent->ScreenY(CallerType::System);
+ mInputSource = aDragEvent->MozInputSource();
+
+ // just get the focused node from the selection
+ // XXXndeakin this should actually be the deepest node that contains both
+ // endpoints of the selection
+ nsCOMPtr<nsINode> node = aSelection->GetFocusNode();
+
+ return InvokeDragSession(node, aPrincipal, aCsp, aCookieJarSettings,
+ aTransferableArray, aActionType,
+ nsIContentPolicy::TYPE_OTHER);
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsBaseDragService::GetCurrentSession(nsIDragSession** aSession) {
+ if (!aSession) return NS_ERROR_INVALID_ARG;
+
+ // "this" also implements a drag session, so say we are one but only
+ // if there is currently a drag going on.
+ if (!mSuppressLevel && mDoingDrag) {
+ *aSession = this;
+ NS_ADDREF(*aSession); // addRef because we're a "getter"
+ } else
+ *aSession = nullptr;
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsBaseDragService::StartDragSession() {
+ if (mDoingDrag) {
+ return NS_ERROR_FAILURE;
+ }
+ mDoingDrag = true;
+ // By default dispatch drop also to content.
+ mOnlyChromeDrop = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseDragService::StartDragSessionForTests(
+ uint32_t aAllowedEffect) {
+ if (NS_WARN_IF(NS_FAILED(StartDragSession()))) {
+ return NS_ERROR_FAILURE;
+ }
+ mDragAction = aAllowedEffect;
+ mEffectAllowedForTests = aAllowedEffect;
+ mSessionIsSynthesizedForTests = true;
+ return NS_OK;
+}
+
+void nsBaseDragService::OpenDragPopup() {
+ if (mDragPopup) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ pm->ShowPopupAtScreen(mDragPopup, mScreenPosition.x - mImageOffset.x,
+ mScreenPosition.y - mImageOffset.y, false, nullptr);
+ }
+ }
+}
+
+int32_t nsBaseDragService::TakeChildProcessDragAction() {
+ // If the last event was dispatched to the child process, use the drag action
+ // assigned from it instead and return it. DRAGDROP_ACTION_UNINITIALIZED is
+ // returned otherwise.
+ int32_t retval = DRAGDROP_ACTION_UNINITIALIZED;
+ if (TakeDragEventDispatchedToChildProcess() &&
+ mDragActionFromChildProcess != DRAGDROP_ACTION_UNINITIALIZED) {
+ retval = mDragActionFromChildProcess;
+ }
+
+ return retval;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsBaseDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
+ if (!mDoingDrag || mEndingSession) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mEndingSession = true;
+
+ if (aDoneDrag && !mSuppressLevel) {
+ FireDragEventAtSource(eDragEnd, aKeyModifiers);
+ }
+
+ if (mDragPopup) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ pm->HidePopup(mDragPopup, false, true, false, false);
+ }
+ }
+
+ for (uint32_t i = 0; i < mChildProcesses.Length(); ++i) {
+ mozilla::Unused << mChildProcesses[i]->SendEndDragSession(
+ aDoneDrag, mUserCancelled, mEndDragPoint, aKeyModifiers);
+ // Continue sending input events with input priority when stopping the dnd
+ // session.
+ mChildProcesses[i]->SetInputPriorityEventEnabled(true);
+ }
+ mChildProcesses.Clear();
+
+ // mDataTransfer and the items it owns are going to die anyway, but we
+ // explicitly deref the contained data here so that we don't have to wait for
+ // CC to reclaim the memory.
+ if (XRE_IsParentProcess()) {
+ DiscardInternalTransferData();
+ }
+
+ mDoingDrag = false;
+ mSessionIsSynthesizedForTests = false;
+ mEffectAllowedForTests = nsIDragService::DRAGDROP_ACTION_UNINITIALIZED;
+ mEndingSession = false;
+ mCanDrop = false;
+
+ // release the source we've been holding on to.
+ mSourceDocument = nullptr;
+ mSourceNode = nullptr;
+ mTriggeringPrincipal = nullptr;
+ mCsp = nullptr;
+ mSelection = nullptr;
+ mDataTransfer = nullptr;
+ mHasImage = false;
+ mUserCancelled = false;
+ mDragPopup = nullptr;
+ mDragStartData = nullptr;
+ mImage = nullptr;
+ mImageOffset = CSSIntPoint();
+ mScreenPosition = CSSIntPoint();
+ mEndDragPoint = LayoutDeviceIntPoint(0, 0);
+ mInputSource = MouseEvent_Binding::MOZ_SOURCE_MOUSE;
+ mRegion = Nothing();
+
+ return NS_OK;
+}
+
+void nsBaseDragService::DiscardInternalTransferData() {
+ if (mDataTransfer && mSourceNode) {
+ MOZ_ASSERT(mDataTransfer);
+
+ DataTransferItemList* items = mDataTransfer->Items();
+ for (size_t i = 0; i < items->Length(); i++) {
+ bool found;
+ DataTransferItem* item = items->IndexedGetter(i, found);
+
+ // Non-OTHER items may still be needed by JS. Skip them.
+ if (!found || item->Kind() != DataTransferItem::KIND_OTHER) {
+ continue;
+ }
+
+ nsCOMPtr<nsIVariant> variant = item->DataNoSecurityCheck();
+ nsCOMPtr<nsIWritableVariant> writable = do_QueryInterface(variant);
+
+ if (writable) {
+ writable->SetAsEmpty();
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsBaseDragService::FireDragEventAtSource(EventMessage aEventMessage,
+ uint32_t aKeyModifiers) {
+ if (mSourceNode && mSourceDocument && !mSuppressLevel) {
+ RefPtr<PresShell> presShell = mSourceDocument->GetPresShell();
+ if (presShell) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetDragEvent event(true, aEventMessage, nullptr);
+ event.mFlags.mIsSynthesizedForTests = mSessionIsSynthesizedForTests;
+ event.mInputSource = mInputSource;
+ if (aEventMessage == eDragEnd) {
+ event.mRefPoint = mEndDragPoint;
+ event.mUserCancelled = mUserCancelled;
+ }
+ event.mModifiers = aKeyModifiers;
+ // Send the drag event to APZ, which needs to know about them to be
+ // able to accurately detect the end of a drag gesture.
+ if (nsPresContext* presContext = presShell->GetPresContext()) {
+ if (nsCOMPtr<nsIWidget> widget = presContext->GetRootWidget()) {
+ widget->DispatchEventToAPZOnly(&event);
+ }
+ }
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(mSourceNode);
+ return presShell->HandleDOMEventWithTarget(content, &event, &status);
+ }
+ }
+
+ return NS_OK;
+}
+
+/* This is used by Windows and Mac to update the position of a popup being
+ * used as a drag image during the drag. This isn't used on GTK as it manages
+ * the drag popup itself.
+ */
+NS_IMETHODIMP
+nsBaseDragService::DragMoved(int32_t aX, int32_t aY) {
+ if (mDragPopup) {
+ nsIFrame* frame = mDragPopup->GetPrimaryFrame();
+ if (frame && frame->IsMenuPopupFrame()) {
+ CSSIntPoint cssPos =
+ RoundedToInt(LayoutDeviceIntPoint(aX, aY) /
+ frame->PresContext()->CSSToDevPixelScale()) -
+ mImageOffset;
+ (static_cast<nsMenuPopupFrame*>(frame))->MoveTo(cssPos, true);
+ }
+ }
+
+ return NS_OK;
+}
+
+static PresShell* GetPresShellForContent(nsINode* aDOMNode) {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aDOMNode);
+ if (!content) return nullptr;
+
+ RefPtr<Document> document = content->GetComposedDoc();
+ if (document) {
+ document->FlushPendingNotifications(FlushType::Display);
+ return document->GetPresShell();
+ }
+
+ return nullptr;
+}
+
+nsresult nsBaseDragService::DrawDrag(nsINode* aDOMNode,
+ const Maybe<CSSIntRegion>& aRegion,
+ CSSIntPoint aScreenPosition,
+ LayoutDeviceIntRect* aScreenDragRect,
+ RefPtr<SourceSurface>* aSurface,
+ nsPresContext** aPresContext) {
+ *aSurface = nullptr;
+ *aPresContext = nullptr;
+
+ // use a default size, in case of an error.
+ aScreenDragRect->SetRect(aScreenPosition.x - mImageOffset.x,
+ aScreenPosition.y - mImageOffset.y, 1, 1);
+
+ // if a drag image was specified, use that, otherwise, use the source node
+ nsCOMPtr<nsINode> dragNode = mImage ? mImage.get() : aDOMNode;
+
+ // get the presshell for the node being dragged. If the drag image is not in
+ // a document or has no frame, get the presshell from the source drag node
+ PresShell* presShell = GetPresShellForContent(dragNode);
+ if (!presShell && mImage) {
+ presShell = GetPresShellForContent(aDOMNode);
+ }
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aPresContext = presShell->GetPresContext();
+
+ if (mDragStartData) {
+ if (mImage) {
+ // Just clear the surface if chrome has overridden it with an image.
+ *aSurface = nullptr;
+ } else {
+ *aSurface = mDragStartData->TakeVisualization(aScreenDragRect);
+ }
+
+ mDragStartData = nullptr;
+ return NS_OK;
+ }
+
+ // convert mouse position to dev pixels of the prescontext
+ CSSIntPoint screenPosition(aScreenPosition);
+ screenPosition.x -= mImageOffset.x;
+ screenPosition.y -= mImageOffset.y;
+ LayoutDeviceIntPoint screenPoint =
+ ConvertToUnscaledDevPixels(*aPresContext, screenPosition);
+ aScreenDragRect->MoveTo(screenPoint.x, screenPoint.y);
+
+ // check if drag images are disabled
+ bool enableDragImages = Preferences::GetBool(DRAGIMAGES_PREF, true);
+
+ // didn't want an image, so just set the screen rectangle to the frame size
+ if (!enableDragImages || !mHasImage) {
+ // This holds a quantity in RelativeTo{presShell->GetRootFrame(),
+ // ViewportType::Layout} space.
+ nsRect presLayoutRect;
+ if (aRegion) {
+ // if a region was specified, set the screen rectangle to the area that
+ // the region occupies
+ presLayoutRect = ToAppUnits(aRegion->GetBounds(), AppUnitsPerCSSPixel());
+ } else {
+ // otherwise, there was no region so just set the rectangle to
+ // the size of the primary frame of the content.
+ nsCOMPtr<nsIContent> content = do_QueryInterface(dragNode);
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (frame) {
+ presLayoutRect = frame->GetRect();
+ }
+ }
+
+ LayoutDeviceRect screenVisualRect = ViewportUtils::ToScreenRelativeVisual(
+ LayoutDeviceRect::FromAppUnits(presLayoutRect,
+ (*aPresContext)->AppUnitsPerDevPixel()),
+ *aPresContext);
+ aScreenDragRect->SizeTo(screenVisualRect.Width(),
+ screenVisualRect.Height());
+ return NS_OK;
+ }
+
+ // draw the image for selections
+ if (mSelection) {
+ LayoutDeviceIntPoint pnt(aScreenDragRect->TopLeft());
+ *aSurface = presShell->RenderSelection(
+ mSelection, pnt, aScreenDragRect,
+ mImage ? RenderImageFlags::None : RenderImageFlags::AutoScale);
+ return NS_OK;
+ }
+
+ // if a custom image was specified, check if it is an image node and draw
+ // using the source rather than the displayed image. But if mImage isn't
+ // an image or canvas, fall through to RenderNode below.
+ if (mImage) {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(dragNode);
+ HTMLCanvasElement* canvas = HTMLCanvasElement::FromNodeOrNull(content);
+ if (canvas) {
+ return DrawDragForImage(*aPresContext, nullptr, canvas, aScreenDragRect,
+ aSurface);
+ }
+
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(dragNode);
+ // for image nodes, create the drag image from the actual image data
+ if (imageLoader) {
+ return DrawDragForImage(*aPresContext, imageLoader, nullptr,
+ aScreenDragRect, aSurface);
+ }
+
+ // If the image is a popup, use that as the image. This allows custom drag
+ // images that can change during the drag, but means that any platform
+ // default image handling won't occur.
+ // XXXndeakin this should be chrome-only
+
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (frame && frame->IsMenuPopupFrame()) {
+ mDragPopup = content;
+ }
+ }
+
+ if (!mDragPopup) {
+ // otherwise, just draw the node
+ RenderImageFlags renderFlags =
+ mImage ? RenderImageFlags::None : RenderImageFlags::AutoScale;
+ if (renderFlags != RenderImageFlags::None) {
+ // check if the dragged node itself is an img element
+ if (dragNode->NodeName().LowerCaseEqualsLiteral("img")) {
+ renderFlags = renderFlags | RenderImageFlags::IsImage;
+ } else {
+ nsINodeList* childList = dragNode->ChildNodes();
+ uint32_t length = childList->Length();
+ // check every childnode for being an img element
+ // XXXbz why don't we need to check descendants recursively?
+ for (uint32_t count = 0; count < length; ++count) {
+ if (childList->Item(count)->NodeName().LowerCaseEqualsLiteral(
+ "img")) {
+ // if the dragnode contains an image, set RenderImageFlags::IsImage
+ // flag
+ renderFlags = renderFlags | RenderImageFlags::IsImage;
+ break;
+ }
+ }
+ }
+ }
+ LayoutDeviceIntPoint pnt(aScreenDragRect->TopLeft());
+ *aSurface = presShell->RenderNode(dragNode, aRegion, pnt, aScreenDragRect,
+ renderFlags);
+ }
+
+ // If an image was specified, reset the position from the offset that was
+ // supplied.
+ if (mImage) {
+ aScreenDragRect->MoveTo(screenPoint.x, screenPoint.y);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsBaseDragService::DrawDragForImage(
+ nsPresContext* aPresContext, nsIImageLoadingContent* aImageLoader,
+ HTMLCanvasElement* aCanvas, LayoutDeviceIntRect* aScreenDragRect,
+ RefPtr<SourceSurface>* aSurface) {
+ nsCOMPtr<imgIContainer> imgContainer;
+ if (aImageLoader) {
+ nsCOMPtr<imgIRequest> imgRequest;
+ nsresult rv = aImageLoader->GetRequest(
+ nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(imgRequest));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!imgRequest) return NS_ERROR_NOT_AVAILABLE;
+
+ rv = imgRequest->GetImage(getter_AddRefs(imgContainer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!imgContainer) return NS_ERROR_NOT_AVAILABLE;
+
+ // use the size of the image as the size of the drag image
+ int32_t imageWidth, imageHeight;
+ rv = imgContainer->GetWidth(&imageWidth);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = imgContainer->GetHeight(&imageHeight);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aScreenDragRect->SizeTo(aPresContext->CSSPixelsToDevPixels(imageWidth),
+ aPresContext->CSSPixelsToDevPixels(imageHeight));
+ } else {
+ // XXX The canvas size should be converted to dev pixels.
+ NS_ASSERTION(aCanvas, "both image and canvas are null");
+ nsIntSize sz = aCanvas->GetSize();
+ aScreenDragRect->SizeTo(sz.width, sz.height);
+ }
+
+ nsIntSize destSize;
+ destSize.width = aScreenDragRect->Width();
+ destSize.height = aScreenDragRect->Height();
+ if (destSize.width == 0 || destSize.height == 0) return NS_ERROR_FAILURE;
+
+ nsresult result = NS_OK;
+ if (aImageLoader) {
+ RefPtr<DrawTarget> dt =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ destSize, SurfaceFormat::B8G8R8A8);
+ if (!dt || !dt->IsValid()) return NS_ERROR_FAILURE;
+
+ RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt);
+ if (!ctx) return NS_ERROR_FAILURE;
+
+ ImgDrawResult res =
+ imgContainer->Draw(ctx, destSize, ImageRegion::Create(destSize),
+ imgIContainer::FRAME_CURRENT, SamplingFilter::GOOD,
+ /* no SVGImageContext */ Nothing(),
+ imgIContainer::FLAG_SYNC_DECODE, 1.0);
+ if (res == ImgDrawResult::BAD_IMAGE || res == ImgDrawResult::BAD_ARGS ||
+ res == ImgDrawResult::NOT_SUPPORTED) {
+ return NS_ERROR_FAILURE;
+ }
+ *aSurface = dt->Snapshot();
+ } else {
+ *aSurface = aCanvas->GetSurfaceSnapshot();
+ }
+
+ return result;
+}
+
+LayoutDeviceIntPoint nsBaseDragService::ConvertToUnscaledDevPixels(
+ nsPresContext* aPresContext, CSSIntPoint aScreenPosition) {
+ int32_t adj =
+ aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
+ return LayoutDeviceIntPoint(
+ nsPresContext::CSSPixelsToAppUnits(aScreenPosition.x) / adj,
+ nsPresContext::CSSPixelsToAppUnits(aScreenPosition.y) / adj);
+}
+
+NS_IMETHODIMP
+nsBaseDragService::Suppress() {
+ EndDragSession(false, 0);
+ ++mSuppressLevel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::Unsuppress() {
+ --mSuppressLevel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::UserCancelled() {
+ mUserCancelled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::UpdateDragEffect() {
+ mDragActionFromChildProcess = mDragAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::UpdateDragImage(nsINode* aImage, int32_t aImageX,
+ int32_t aImageY) {
+ // Don't change the image if this is a drag from another source or if there
+ // is a drag popup.
+ if (!mSourceNode || mDragPopup) return NS_OK;
+
+ mImage = aImage;
+ mImageOffset = CSSIntPoint(aImageX, aImageY);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::DragEventDispatchedToChildProcess() {
+ mDragEventDispatchedToChildProcess = true;
+ return NS_OK;
+}
+
+bool nsBaseDragService::MaybeAddChildProcess(
+ mozilla::dom::ContentParent* aChild) {
+ if (!mChildProcesses.Contains(aChild)) {
+ mChildProcesses.AppendElement(aChild);
+ return true;
+ }
+ return false;
+}
+
+bool nsBaseDragService::RemoveAllChildProcesses() {
+ for (uint32_t c = 0; c < mChildProcesses.Length(); c++) {
+ mozilla::Unused << mChildProcesses[c]->SendEndDragSession(
+ true, false, LayoutDeviceIntPoint(), 0);
+ }
+ mChildProcesses.Clear();
+ return true;
+}
diff --git a/widget/nsBaseDragService.h b/widget/nsBaseDragService.h
new file mode 100644
index 0000000000..d91b71dfe7
--- /dev/null
+++ b/widget/nsBaseDragService.h
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBaseDragService_h__
+#define nsBaseDragService_h__
+
+#include "nsIDragService.h"
+#include "nsIDragSession.h"
+#include "nsCOMPtr.h"
+#include "nsRect.h"
+#include "nsPoint.h"
+#include "nsString.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/RemoteDragStartData.h"
+#include "nsTArray.h"
+#include "nsRegion.h"
+#include "Units.h"
+
+// translucency level for drag images
+#define DRAG_TRANSLUCENCY 0.65
+
+class nsIContent;
+
+class nsINode;
+class nsPresContext;
+class nsIImageLoadingContent;
+
+namespace mozilla {
+namespace gfx {
+class SourceSurface;
+} // namespace gfx
+
+namespace dom {
+class DataTransfer;
+class Selection;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * XP DragService wrapper base class
+ */
+
+class nsBaseDragService : public nsIDragService, public nsIDragSession {
+ public:
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+
+ nsBaseDragService();
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsIDragSession and nsIDragService
+ NS_DECL_NSIDRAGSERVICE
+ NS_DECL_NSIDRAGSESSION
+
+ void SetDragEndPoint(nsIntPoint aEndDragPoint) {
+ mEndDragPoint =
+ mozilla::LayoutDeviceIntPoint::FromUnknownPoint(aEndDragPoint);
+ }
+ void SetDragEndPoint(mozilla::LayoutDeviceIntPoint aEndDragPoint) {
+ mEndDragPoint = aEndDragPoint;
+ }
+
+ uint16_t GetInputSource() { return mInputSource; }
+
+ int32_t TakeChildProcessDragAction();
+
+ protected:
+ virtual ~nsBaseDragService();
+
+ /**
+ * Called from nsBaseDragService to initiate a platform drag from a source
+ * in this process. This is expected to ensure that StartDragSession() and
+ * EndDragSession() get called if the platform drag is successfully invoked.
+ */
+ MOZ_CAN_RUN_SCRIPT virtual nsresult InvokeDragSessionImpl(
+ nsIArray* aTransferableArray,
+ const mozilla::Maybe<mozilla::CSSIntRegion>& aRegion,
+ uint32_t aActionType) = 0;
+
+ /**
+ * Draw the drag image, if any, to a surface and return it. The drag image
+ * is constructed from mImage if specified, or aDOMNode if mImage is null.
+ *
+ * aRegion may be used to draw only a subset of the element. This region
+ * should be supplied using x and y coordinates measured in css pixels
+ * that are relative to the upper-left corner of the window.
+ *
+ * aScreenPosition should be the screen coordinates of the mouse click
+ * for the drag. These are in CSS pixels.
+ *
+ * On return, aScreenDragRect will contain the screen coordinates of the
+ * area being dragged. This is used by the platform-specific part of the
+ * drag service to determine the drag feedback. This rect will be in the
+ * device pixels of the presContext.
+ *
+ * If there is no drag image, the returned surface will be null, but
+ * aScreenDragRect will still be set to the drag area.
+ *
+ * aPresContext will be set to the nsPresContext used determined from
+ * whichever of mImage or aDOMNode is used.
+ */
+ nsresult DrawDrag(nsINode* aDOMNode,
+ const mozilla::Maybe<mozilla::CSSIntRegion>& aRegion,
+ mozilla::CSSIntPoint aScreenPosition,
+ mozilla::LayoutDeviceIntRect* aScreenDragRect,
+ RefPtr<SourceSurface>* aSurface,
+ nsPresContext** aPresContext);
+
+ /**
+ * Draw a drag image for an image node specified by aImageLoader or aCanvas.
+ * This is called by DrawDrag.
+ */
+ nsresult DrawDragForImage(nsPresContext* aPresContext,
+ nsIImageLoadingContent* aImageLoader,
+ mozilla::dom::HTMLCanvasElement* aCanvas,
+ mozilla::LayoutDeviceIntRect* aScreenDragRect,
+ RefPtr<SourceSurface>* aSurface);
+
+ /**
+ * Convert aScreenPosition from CSS pixels into unscaled device pixels.
+ */
+ mozilla::LayoutDeviceIntPoint ConvertToUnscaledDevPixels(
+ nsPresContext* aPresContext, mozilla::CSSIntPoint aScreenPosition);
+
+ /**
+ * If the drag image is a popup, open the popup when the drag begins.
+ */
+ void OpenDragPopup();
+
+ /**
+ * Free resources contained in DataTransferItems that aren't needed by JS.
+ */
+ void DiscardInternalTransferData();
+
+ // Returns true if a drag event was dispatched to a child process after
+ // the previous TakeDragEventDispatchedToChildProcess() call.
+ bool TakeDragEventDispatchedToChildProcess() {
+ bool retval = mDragEventDispatchedToChildProcess;
+ mDragEventDispatchedToChildProcess = false;
+ return retval;
+ }
+
+ bool mCanDrop;
+ bool mOnlyChromeDrop;
+ bool mDoingDrag;
+ bool mSessionIsSynthesizedForTests;
+
+ // true if in EndDragSession
+ bool mEndingSession;
+ // true if mImage should be used to set a drag image
+ bool mHasImage;
+ // true if the user cancelled the drag operation
+ bool mUserCancelled;
+
+ bool mDragEventDispatchedToChildProcess;
+
+ uint32_t mDragAction;
+ uint32_t mDragActionFromChildProcess;
+
+ // mEffectAllowedForTests stores allowed effects at invoking the drag
+ // for tests.
+ uint32_t mEffectAllowedForTests;
+
+ nsCOMPtr<nsINode> mSourceNode;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ nsCOMPtr<nsIContentSecurityPolicy> mCsp;
+
+ // the document at the drag source. will be null if it came from outside the
+ // app.
+ RefPtr<mozilla::dom::Document> mSourceDocument;
+
+ // the contentpolicy type passed to the channel when initiating the drag
+ // session
+ nsContentPolicyType mContentPolicyType;
+
+ RefPtr<mozilla::dom::DataTransfer> mDataTransfer;
+
+ // used to determine the image to appear on the cursor while dragging
+ nsCOMPtr<nsINode> mImage;
+ // offset of cursor within the image
+ mozilla::CSSIntPoint mImageOffset;
+
+ // set if a selection is being dragged
+ RefPtr<mozilla::dom::Selection> mSelection;
+
+ // remote drag data
+ RefPtr<mozilla::dom::RemoteDragStartData> mDragStartData;
+
+ // set if the image in mImage is a popup. If this case, the popup will be
+ // opened and moved instead of using a drag image.
+ nsCOMPtr<nsIContent> mDragPopup;
+
+ // the screen position where drag gesture occurred, used for positioning the
+ // drag image.
+ mozilla::CSSIntPoint mScreenPosition;
+
+ // the screen position where the drag ended
+ mozilla::LayoutDeviceIntPoint mEndDragPoint;
+
+ uint32_t mSuppressLevel;
+
+ // The input source of the drag event. Possible values are from MouseEvent.
+ uint16_t mInputSource;
+
+ nsTArray<RefPtr<mozilla::dom::ContentParent>> mChildProcesses;
+
+ // Sub-region for tree-selections.
+ mozilla::Maybe<mozilla::CSSIntRegion> mRegion;
+};
+
+#endif // nsBaseDragService_h__
diff --git a/widget/nsBaseFilePicker.cpp b/widget/nsBaseFilePicker.cpp
new file mode 100644
index 0000000000..8cc2369c95
--- /dev/null
+++ b/widget/nsBaseFilePicker.cpp
@@ -0,0 +1,408 @@
+/* -*- 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 "nsPIDOMWindow.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWidget.h"
+
+#include "nsIStringBundle.h"
+#include "nsString.h"
+#include "nsCOMArray.h"
+#include "nsIFile.h"
+#include "nsEnumeratorUtils.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/Services.h"
+#include "WidgetUtils.h"
+#include "nsSimpleEnumerator.h"
+#include "nsThreadUtils.h"
+
+#include "nsBaseFilePicker.h"
+
+using namespace mozilla::widget;
+using namespace mozilla::dom;
+
+#define FILEPICKER_TITLES "chrome://global/locale/filepicker.properties"
+#define FILEPICKER_FILTERS "chrome://global/content/filepicker.properties"
+
+namespace {
+
+nsresult LocalFileToDirectoryOrBlob(nsPIDOMWindowInner* aWindow,
+ bool aIsDirectory, nsIFile* aFile,
+ nsISupports** aResult) {
+ MOZ_ASSERT(aWindow);
+
+ if (aIsDirectory) {
+#ifdef DEBUG
+ bool isDir;
+ aFile->IsDirectory(&isDir);
+ MOZ_ASSERT(isDir);
+#endif
+
+ RefPtr<Directory> directory = Directory::Create(aWindow->AsGlobal(), aFile);
+ MOZ_ASSERT(directory);
+
+ directory.forget(aResult);
+ return NS_OK;
+ }
+
+ RefPtr<File> file = File::CreateFromFile(aWindow->AsGlobal(), aFile);
+ if (NS_WARN_IF(!file)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ file.forget(aResult);
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+/**
+ * A runnable to dispatch from the main thread to the main thread to display
+ * the file picker while letting the showAsync method return right away.
+ */
+class nsBaseFilePicker::AsyncShowFilePicker : public mozilla::Runnable {
+ public:
+ AsyncShowFilePicker(nsBaseFilePicker* aFilePicker,
+ nsIFilePickerShownCallback* aCallback)
+ : mozilla::Runnable("AsyncShowFilePicker"),
+ mFilePicker(aFilePicker),
+ mCallback(aCallback) {}
+
+ NS_IMETHOD Run() override {
+ NS_ASSERTION(NS_IsMainThread(),
+ "AsyncShowFilePicker should be on the main thread!");
+
+ // It's possible that some widget implementations require GUI operations
+ // to be on the main thread, so that's why we're not dispatching to another
+ // thread and calling back to the main after it's done.
+ int16_t result = nsIFilePicker::returnCancel;
+ nsresult rv = mFilePicker->Show(&result);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("FilePicker's Show() implementation failed!");
+ }
+
+ if (mCallback) {
+ mCallback->Done(result);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsBaseFilePicker> mFilePicker;
+ RefPtr<nsIFilePickerShownCallback> mCallback;
+};
+
+class nsBaseFilePickerEnumerator : public nsSimpleEnumerator {
+ public:
+ nsBaseFilePickerEnumerator(nsPIDOMWindowOuter* aParent,
+ nsISimpleEnumerator* iterator, int16_t aMode)
+ : mIterator(iterator),
+ mParent(aParent->GetCurrentInnerWindow()),
+ mMode(aMode) {}
+
+ const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); }
+
+ NS_IMETHOD
+ GetNext(nsISupports** aResult) override {
+ nsCOMPtr<nsISupports> tmp;
+ nsresult rv = mIterator->GetNext(getter_AddRefs(tmp));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!tmp) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> localFile = do_QueryInterface(tmp);
+ if (!localFile) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mParent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return LocalFileToDirectoryOrBlob(
+ mParent, mMode == nsIFilePicker::modeGetFolder, localFile, aResult);
+ }
+
+ NS_IMETHOD
+ HasMoreElements(bool* aResult) override {
+ return mIterator->HasMoreElements(aResult);
+ }
+
+ private:
+ nsCOMPtr<nsISimpleEnumerator> mIterator;
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ int16_t mMode;
+};
+
+nsBaseFilePicker::nsBaseFilePicker()
+ : mAddToRecentDocs(true), mMode(nsIFilePicker::modeOpen) {}
+
+nsBaseFilePicker::~nsBaseFilePicker() = default;
+
+NS_IMETHODIMP nsBaseFilePicker::Init(mozIDOMWindowProxy* aParent,
+ const nsAString& aTitle, int16_t aMode) {
+ MOZ_ASSERT(aParent,
+ "Null parent passed to filepicker, no file "
+ "picker for you!");
+
+ mParent = nsPIDOMWindowOuter::From(aParent);
+
+ nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(mParent);
+ NS_ENSURE_TRUE(widget, NS_ERROR_FAILURE);
+
+ mMode = aMode;
+ InitNative(widget, aTitle);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::Open(nsIFilePickerShownCallback* aCallback) {
+ nsCOMPtr<nsIRunnable> filePickerEvent =
+ new AsyncShowFilePicker(this, aCallback);
+ return NS_DispatchToMainThread(filePickerEvent);
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::AppendFilters(int32_t aFilterMask) {
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::services::GetStringBundleService();
+ if (!stringService) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIStringBundle> titleBundle, filterBundle;
+
+ nsresult rv = stringService->CreateBundle(FILEPICKER_TITLES,
+ getter_AddRefs(titleBundle));
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ rv = stringService->CreateBundle(FILEPICKER_FILTERS,
+ getter_AddRefs(filterBundle));
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ nsAutoString title;
+ nsAutoString filter;
+
+ if (aFilterMask & filterAll) {
+ titleBundle->GetStringFromName("allTitle", title);
+ filterBundle->GetStringFromName("allFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterHTML) {
+ titleBundle->GetStringFromName("htmlTitle", title);
+ filterBundle->GetStringFromName("htmlFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterText) {
+ titleBundle->GetStringFromName("textTitle", title);
+ filterBundle->GetStringFromName("textFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterImages) {
+ titleBundle->GetStringFromName("imageTitle", title);
+ filterBundle->GetStringFromName("imageFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterAudio) {
+ titleBundle->GetStringFromName("audioTitle", title);
+ filterBundle->GetStringFromName("audioFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterVideo) {
+ titleBundle->GetStringFromName("videoTitle", title);
+ filterBundle->GetStringFromName("videoFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterXML) {
+ titleBundle->GetStringFromName("xmlTitle", title);
+ filterBundle->GetStringFromName("xmlFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterXUL) {
+ titleBundle->GetStringFromName("xulTitle", title);
+ filterBundle->GetStringFromName("xulFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterApps) {
+ titleBundle->GetStringFromName("appsTitle", title);
+ // Pass the magic string "..apps" to the platform filepicker, which it
+ // should recognize and do the correct platform behavior for.
+ AppendFilter(title, u"..apps"_ns);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseFilePicker::AppendRawFilter(const nsAString& aFilter) {
+ mRawFilters.AppendElement(aFilter);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseFilePicker::GetCapture(int16_t* aCapture) {
+ *aCapture = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseFilePicker::SetCapture(int16_t aCapture) { return NS_OK; }
+
+// Set the filter index
+NS_IMETHODIMP nsBaseFilePicker::GetFilterIndex(int32_t* aFilterIndex) {
+ *aFilterIndex = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseFilePicker::SetFilterIndex(int32_t aFilterIndex) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseFilePicker::GetFiles(nsISimpleEnumerator** aFiles) {
+ NS_ENSURE_ARG_POINTER(aFiles);
+ nsCOMArray<nsIFile> files;
+ nsresult rv;
+
+ // if we get into the base class, the platform
+ // doesn't implement GetFiles() yet.
+ // so we fake it.
+ nsCOMPtr<nsIFile> file;
+ rv = GetFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ files.AppendObject(file);
+
+ return NS_NewArrayEnumerator(aFiles, files, NS_GET_IID(nsIFile));
+}
+
+// Set the display directory
+NS_IMETHODIMP nsBaseFilePicker::SetDisplayDirectory(nsIFile* aDirectory) {
+ // if displaySpecialDirectory has been previously called, let's abort this
+ // operation.
+ if (!mDisplaySpecialDirectory.IsEmpty()) {
+ return NS_OK;
+ }
+
+ if (!aDirectory) {
+ mDisplayDirectory = nullptr;
+ return NS_OK;
+ }
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv = aDirectory->Clone(getter_AddRefs(directory));
+ if (NS_FAILED(rv)) return rv;
+ mDisplayDirectory = directory;
+ return NS_OK;
+}
+
+// Get the display directory
+NS_IMETHODIMP nsBaseFilePicker::GetDisplayDirectory(nsIFile** aDirectory) {
+ *aDirectory = nullptr;
+
+ // if displaySpecialDirectory has been previously called, let's abort this
+ // operation.
+ if (!mDisplaySpecialDirectory.IsEmpty()) {
+ return NS_OK;
+ }
+
+ if (!mDisplayDirectory) return NS_OK;
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv = mDisplayDirectory->Clone(getter_AddRefs(directory));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ directory.forget(aDirectory);
+ return NS_OK;
+}
+
+// Set the display special directory
+NS_IMETHODIMP nsBaseFilePicker::SetDisplaySpecialDirectory(
+ const nsAString& aDirectory) {
+ // if displayDirectory has been previously called, let's abort this operation.
+ if (mDisplayDirectory && mDisplaySpecialDirectory.IsEmpty()) {
+ return NS_OK;
+ }
+
+ mDisplaySpecialDirectory = aDirectory;
+ if (mDisplaySpecialDirectory.IsEmpty()) {
+ mDisplayDirectory = nullptr;
+ return NS_OK;
+ }
+
+ return NS_GetSpecialDirectory(
+ NS_ConvertUTF16toUTF8(mDisplaySpecialDirectory).get(),
+ getter_AddRefs(mDisplayDirectory));
+}
+
+// Get the display special directory
+NS_IMETHODIMP nsBaseFilePicker::GetDisplaySpecialDirectory(
+ nsAString& aDirectory) {
+ aDirectory = mDisplaySpecialDirectory;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::GetAddToRecentDocs(bool* aFlag) {
+ *aFlag = mAddToRecentDocs;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::SetAddToRecentDocs(bool aFlag) {
+ mAddToRecentDocs = aFlag;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::GetMode(int16_t* aMode) {
+ *aMode = mMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::SetOkButtonLabel(const nsAString& aLabel) {
+ mOkButtonLabel = aLabel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::GetOkButtonLabel(nsAString& aLabel) {
+ aLabel = mOkButtonLabel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::GetDomFileOrDirectory(nsISupports** aValue) {
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = GetFile(getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!localFile) {
+ *aValue = nullptr;
+ return NS_OK;
+ }
+
+ auto* innerParent = mParent ? mParent->GetCurrentInnerWindow() : nullptr;
+
+ if (!innerParent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return LocalFileToDirectoryOrBlob(
+ innerParent, mMode == nsIFilePicker::modeGetFolder, localFile, aValue);
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::GetDomFileOrDirectoryEnumerator(
+ nsISimpleEnumerator** aValue) {
+ nsCOMPtr<nsISimpleEnumerator> iter;
+ nsresult rv = GetFiles(getter_AddRefs(iter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsBaseFilePickerEnumerator> retIter =
+ new nsBaseFilePickerEnumerator(mParent, iter, mMode);
+
+ retIter.forget(aValue);
+ return NS_OK;
+}
diff --git a/widget/nsBaseFilePicker.h b/widget/nsBaseFilePicker.h
new file mode 100644
index 0000000000..27e37b0f30
--- /dev/null
+++ b/widget/nsBaseFilePicker.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 nsBaseFilePicker_h__
+#define nsBaseFilePicker_h__
+
+#include "nsISupports.h"
+#include "nsIFilePicker.h"
+#include "nsISimpleEnumerator.h"
+#include "nsArrayEnumerator.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsPIDOMWindowOuter;
+class nsIWidget;
+
+class nsBaseFilePicker : public nsIFilePicker {
+ class AsyncShowFilePicker;
+
+ public:
+ nsBaseFilePicker();
+ virtual ~nsBaseFilePicker();
+
+ NS_IMETHOD Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ int16_t aMode) override;
+
+ NS_IMETHOD Open(nsIFilePickerShownCallback* aCallback) override;
+ NS_IMETHOD AppendFilters(int32_t filterMask) override;
+ NS_IMETHOD AppendRawFilter(const nsAString& aFilter) override;
+ NS_IMETHOD GetCapture(int16_t* aCapture) override;
+ NS_IMETHOD SetCapture(int16_t aCapture) override;
+ NS_IMETHOD GetFilterIndex(int32_t* aFilterIndex) override;
+ NS_IMETHOD SetFilterIndex(int32_t aFilterIndex) override;
+ NS_IMETHOD GetFiles(nsISimpleEnumerator** aFiles) override;
+ NS_IMETHOD GetDisplayDirectory(nsIFile** aDisplayDirectory) override;
+ NS_IMETHOD SetDisplayDirectory(nsIFile* aDisplayDirectory) override;
+ NS_IMETHOD GetDisplaySpecialDirectory(nsAString& aDisplayDirectory) override;
+ NS_IMETHOD SetDisplaySpecialDirectory(
+ const nsAString& aDisplayDirectory) override;
+ NS_IMETHOD GetAddToRecentDocs(bool* aFlag) override;
+ NS_IMETHOD SetAddToRecentDocs(bool aFlag) override;
+ NS_IMETHOD GetMode(int16_t* aMode) override;
+ NS_IMETHOD SetOkButtonLabel(const nsAString& aLabel) override;
+ NS_IMETHOD GetOkButtonLabel(nsAString& aLabel) override;
+
+ NS_IMETHOD GetDomFileOrDirectory(nsISupports** aValue) override;
+ NS_IMETHOD GetDomFileOrDirectoryEnumerator(
+ nsISimpleEnumerator** aValue) override;
+
+ protected:
+ virtual void InitNative(nsIWidget* aParent, const nsAString& aTitle) = 0;
+ virtual nsresult Show(int16_t* _retval) = 0;
+
+ bool mAddToRecentDocs;
+ nsCOMPtr<nsIFile> mDisplayDirectory;
+ nsString mDisplaySpecialDirectory;
+
+ nsCOMPtr<nsPIDOMWindowOuter> mParent;
+ int16_t mMode;
+ nsString mOkButtonLabel;
+ nsTArray<nsString> mRawFilters;
+};
+
+#endif // nsBaseFilePicker_h__
diff --git a/widget/nsBaseScreen.cpp b/widget/nsBaseScreen.cpp
new file mode 100644
index 0000000000..b0730011d0
--- /dev/null
+++ b/widget/nsBaseScreen.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define MOZ_FATAL_ASSERTIONS_FOR_THREAD_SAFETY
+
+#include "nsBaseScreen.h"
+
+NS_IMPL_ISUPPORTS(nsBaseScreen, nsIScreen)
+
+nsBaseScreen::nsBaseScreen() = default;
+
+nsBaseScreen::~nsBaseScreen() = default;
+
+NS_IMETHODIMP
+nsBaseScreen::GetRectDisplayPix(int32_t* outLeft, int32_t* outTop,
+ int32_t* outWidth, int32_t* outHeight) {
+ return GetRect(outLeft, outTop, outWidth, outHeight);
+}
+
+NS_IMETHODIMP
+nsBaseScreen::GetAvailRectDisplayPix(int32_t* outLeft, int32_t* outTop,
+ int32_t* outWidth, int32_t* outHeight) {
+ return GetAvailRect(outLeft, outTop, outWidth, outHeight);
+}
+
+NS_IMETHODIMP
+nsBaseScreen::GetContentsScaleFactor(double* aContentsScaleFactor) {
+ *aContentsScaleFactor = 1.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseScreen::GetDefaultCSSScaleFactor(double* aScaleFactor) {
+ *aScaleFactor = 1.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseScreen::GetDpi(float* aDPI) {
+ *aDPI = 96;
+ return NS_OK;
+}
diff --git a/widget/nsBaseScreen.h b/widget/nsBaseScreen.h
new file mode 100644
index 0000000000..3158894754
--- /dev/null
+++ b/widget/nsBaseScreen.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBaseScreen_h
+#define nsBaseScreen_h
+
+#include "mozilla/Attributes.h"
+#include "nsIScreen.h"
+
+class nsBaseScreen : public nsIScreen {
+ public:
+ nsBaseScreen();
+
+ NS_DECL_ISUPPORTS
+
+ // nsIScreen interface
+
+ // These simply forward to the device-pixel versions;
+ // implementations where desktop pixels may not correspond
+ // to per-screen device pixels must override.
+ NS_IMETHOD GetRectDisplayPix(int32_t* outLeft, int32_t* outTop,
+ int32_t* outWidth, int32_t* outHeight) override;
+ NS_IMETHOD GetAvailRectDisplayPix(int32_t* outLeft, int32_t* outTop,
+ int32_t* outWidth,
+ int32_t* outHeight) override;
+
+ NS_IMETHOD GetContentsScaleFactor(double* aContentsScaleFactor) override;
+
+ NS_IMETHOD GetDefaultCSSScaleFactor(double* aScaleFactor) override;
+
+ NS_IMETHOD GetDpi(float* aDPI) override;
+
+ protected:
+ virtual ~nsBaseScreen();
+};
+
+#endif // nsBaseScreen_h
diff --git a/widget/nsBaseWidget.cpp b/widget/nsBaseWidget.cpp
new file mode 100644
index 0000000000..cf394fce09
--- /dev/null
+++ b/widget/nsBaseWidget.cpp
@@ -0,0 +1,3290 @@
+
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsBaseWidget.h"
+
+#include <utility>
+
+#include "BasicLayers.h"
+#include "ClientLayerManager.h"
+#include "FrameLayerBuilder.h"
+#include "GLConsts.h"
+#include "InputData.h"
+#include "LiveResizeListener.h"
+#include "TouchEvents.h"
+#include "WritingModes.h"
+#include "X11UndefineNone.h"
+#include "base/thread.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/GlobalKeyListener.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/VsyncDispatcher.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/APZEventState.h"
+#include "mozilla/layers/APZInputBridge.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/ChromeProcessController.h"
+#include "mozilla/layers/Compositor.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorOptions.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/layers/PLayerTransactionChild.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "npapi.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsDeviceContext.h"
+#include "nsGfxCIID.h"
+#include "nsIAppWindow.h"
+#include "nsIBaseWindow.h"
+#include "nsIContent.h"
+#include "nsIKeyEventInPluginCallback.h"
+#include "nsIScreenManager.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIWidgetListener.h"
+#include "nsRefPtrHashtable.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWidgetsCID.h"
+#include "nsXULPopupManager.h"
+#include "prdtoa.h"
+#include "prenv.h"
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif
+#include "gfxConfig.h"
+#include "gfxUtils.h" // for ToDeviceColor
+#include "mozilla/layers/CompositorSession.h"
+#include "VRManagerChild.h"
+#include "gfxConfig.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+
+#ifdef DEBUG
+# include "nsIObserver.h"
+
+static void debug_RegisterPrefCallbacks();
+
+#endif
+
+#ifdef NOISY_WIDGET_LEAKS
+static int32_t gNumWidgets;
+#endif
+
+#ifdef XP_MACOSX
+# include "nsCocoaFeatures.h"
+#endif
+
+#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+static nsRefPtrHashtable<nsVoidPtrHashKey, nsIWidget>* sPluginWidgetList;
+#endif
+
+nsIRollupListener* nsBaseWidget::gRollupListener = nullptr;
+
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+using namespace mozilla::ipc;
+using namespace mozilla::widget;
+using namespace mozilla;
+
+// Async pump timer during injected long touch taps
+#define TOUCH_INJECT_PUMP_TIMER_MSEC 50
+#define TOUCH_INJECT_LONG_TAP_DEFAULT_MSEC 1500
+int32_t nsIWidget::sPointerIdCounter = 0;
+
+// Some statics from nsIWidget.h
+/*static*/
+uint64_t AutoObserverNotifier::sObserverId = 0;
+/*static*/ nsDataHashtable<nsUint64HashKey, nsCOMPtr<nsIObserver>>
+ AutoObserverNotifier::sSavedObservers;
+
+// The maximum amount of time to let the EnableDragDrop runnable wait in the
+// idle queue before timing out and moving it to the regular queue. Value is in
+// milliseconds.
+const uint32_t kAsyncDragDropTimeout = 1000;
+
+namespace mozilla::widget {
+
+void IMENotification::SelectionChangeDataBase::SetWritingMode(
+ const WritingMode& aWritingMode) {
+ mWritingMode = aWritingMode.mWritingMode.bits;
+}
+
+WritingMode IMENotification::SelectionChangeDataBase::GetWritingMode() const {
+ return WritingMode(mWritingMode);
+}
+
+} // namespace mozilla::widget
+
+NS_IMPL_ISUPPORTS(nsBaseWidget, nsIWidget, nsISupportsWeakReference)
+
+//-------------------------------------------------------------------------
+//
+// nsBaseWidget constructor
+//
+//-------------------------------------------------------------------------
+
+nsBaseWidget::nsBaseWidget()
+ : mWidgetListener(nullptr),
+ mAttachedWidgetListener(nullptr),
+ mPreviouslyAttachedWidgetListener(nullptr),
+ mLayerManager(nullptr),
+ mCompositorVsyncDispatcher(nullptr),
+ mCursor(eCursor_standard),
+ mBorderStyle(eBorderStyle_none),
+ mBounds(0, 0, 0, 0),
+ mOriginalBounds(nullptr),
+ mClipRectCount(0),
+ mSizeMode(nsSizeMode_Normal),
+ mIsTiled(false),
+ mPopupLevel(ePopupLevelTop),
+ mPopupType(ePopupTypeAny),
+ mHasRemoteContent(false),
+ mFissionWindow(false),
+ mUpdateCursor(true),
+ mUseAttachedEvents(false),
+ mIMEHasFocus(false),
+ mIMEHasQuit(false),
+ mIsFullyOccluded(false) {
+#ifdef NOISY_WIDGET_LEAKS
+ gNumWidgets++;
+ printf("WIDGETS+ = %d\n", gNumWidgets);
+#endif
+
+#ifdef DEBUG
+ debug_RegisterPrefCallbacks();
+#endif
+
+#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+ if (!sPluginWidgetList) {
+ sPluginWidgetList = new nsRefPtrHashtable<nsVoidPtrHashKey, nsIWidget>();
+ }
+#endif
+ mShutdownObserver = new WidgetShutdownObserver(this);
+}
+
+NS_IMPL_ISUPPORTS(WidgetShutdownObserver, nsIObserver)
+
+WidgetShutdownObserver::WidgetShutdownObserver(nsBaseWidget* aWidget)
+ : mWidget(aWidget), mRegistered(false) {
+ Register();
+}
+
+WidgetShutdownObserver::~WidgetShutdownObserver() {
+ // No need to call Unregister(), we can't be destroyed until nsBaseWidget
+ // gets torn down. The observer service and nsBaseWidget have a ref on us
+ // so nsBaseWidget has to call Unregister and then clear its ref.
+}
+
+NS_IMETHODIMP
+WidgetShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!mWidget) {
+ return NS_OK;
+ }
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ RefPtr<nsBaseWidget> widget(mWidget);
+ widget->Shutdown();
+ } else if (!strcmp(aTopic, "quit-application")) {
+ RefPtr<nsBaseWidget> widget(mWidget);
+ widget->QuitIME();
+ }
+ return NS_OK;
+}
+
+void WidgetShutdownObserver::Register() {
+ if (!mRegistered) {
+ mRegistered = true;
+ nsContentUtils::RegisterShutdownObserver(this);
+
+#ifndef MOZ_WIDGET_ANDROID
+ // The primary purpose of observing quit-application is
+ // to avoid leaking a widget on Windows when nothing else
+ // breaks the circular reference between the widget and
+ // TSFTextStore. However, our Android IME code crashes if
+ // doing this on Android, so let's not do this on Android.
+ // Doing this on Gtk and Mac just in case.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, "quit-application", false);
+ }
+#endif
+ }
+}
+
+void WidgetShutdownObserver::Unregister() {
+ if (mRegistered) {
+ mWidget = nullptr;
+
+#ifndef MOZ_WIDGET_ANDROID
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, "quit-application");
+ }
+#endif
+
+ nsContentUtils::UnregisterShutdownObserver(this);
+ mRegistered = false;
+ }
+}
+
+void nsBaseWidget::Shutdown() {
+ NotifyLiveResizeStopped();
+ RevokeTransactionIdAllocator();
+ DestroyCompositor();
+ FreeShutdownObserver();
+#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+ if (sPluginWidgetList) {
+ delete sPluginWidgetList;
+ sPluginWidgetList = nullptr;
+ }
+#endif
+}
+
+void nsBaseWidget::QuitIME() {
+ IMEStateManager::WidgetOnQuit(this);
+ this->mIMEHasQuit = true;
+}
+
+void nsBaseWidget::DestroyCompositor() {
+ // We release this before releasing the compositor, since it may hold the
+ // last reference to our ClientLayerManager. ClientLayerManager's dtor can
+ // trigger a paint, creating a new compositor, and we don't want to re-use
+ // the old vsync dispatcher.
+ if (mCompositorVsyncDispatcher) {
+ MOZ_ASSERT(mCompositorVsyncDispatcherLock.get());
+
+ MutexAutoLock lock(*mCompositorVsyncDispatcherLock.get());
+ mCompositorVsyncDispatcher->Shutdown();
+ mCompositorVsyncDispatcher = nullptr;
+ }
+
+ // The compositor shutdown sequence looks like this:
+ // 1. CompositorSession calls CompositorBridgeChild::Destroy.
+ // 2. CompositorBridgeChild synchronously sends WillClose.
+ // 3. CompositorBridgeParent releases some resources (such as the layer
+ // manager, compositor, and widget).
+ // 4. CompositorBridgeChild::Destroy returns.
+ // 5. Asynchronously, CompositorBridgeParent::ActorDestroy will fire on the
+ // compositor thread when the I/O thread closes the IPC channel.
+ // 6. Step 5 will schedule DeferredDestroy on the compositor thread, which
+ // releases the reference CompositorBridgeParent holds to itself.
+ //
+ // When CompositorSession::Shutdown returns, we assume the compositor is gone
+ // or will be gone very soon.
+ if (mCompositorSession) {
+ ReleaseContentController();
+ mAPZC = nullptr;
+ SetCompositorWidgetDelegate(nullptr);
+ mCompositorBridgeChild = nullptr;
+
+ // XXX CompositorBridgeChild and CompositorBridgeParent might be re-created
+ // in ClientLayerManager destructor. See bug 1133426.
+ RefPtr<CompositorSession> session = std::move(mCompositorSession);
+ session->Shutdown();
+ }
+}
+
+// This prevents the layer manager from starting a new transaction during
+// shutdown.
+void nsBaseWidget::RevokeTransactionIdAllocator() {
+ if (!mLayerManager) {
+ return;
+ }
+ mLayerManager->SetTransactionIdAllocator(nullptr);
+}
+
+void nsBaseWidget::ReleaseContentController() {
+ if (mRootContentController) {
+ mRootContentController->Destroy();
+ mRootContentController = nullptr;
+ }
+}
+
+void nsBaseWidget::DestroyLayerManager() {
+ if (mLayerManager) {
+ mLayerManager->Destroy();
+ mLayerManager = nullptr;
+ }
+ DestroyCompositor();
+}
+
+void nsBaseWidget::OnRenderingDeviceReset() { DestroyLayerManager(); }
+
+void nsBaseWidget::FreeShutdownObserver() {
+ if (mShutdownObserver) {
+ mShutdownObserver->Unregister();
+ }
+ mShutdownObserver = nullptr;
+}
+
+//-------------------------------------------------------------------------
+//
+// nsBaseWidget destructor
+//
+//-------------------------------------------------------------------------
+
+nsBaseWidget::~nsBaseWidget() {
+ IMEStateManager::WidgetDestroyed(this);
+
+ if (mLayerManager) {
+ if (BasicLayerManager* mgr = mLayerManager->AsBasicLayerManager()) {
+ mgr->ClearRetainerWidget();
+ }
+ }
+
+ FreeShutdownObserver();
+ RevokeTransactionIdAllocator();
+ DestroyLayerManager();
+
+#ifdef NOISY_WIDGET_LEAKS
+ gNumWidgets--;
+ printf("WIDGETS- = %d\n", gNumWidgets);
+#endif
+
+ delete mOriginalBounds;
+}
+
+//-------------------------------------------------------------------------
+//
+// Basic create.
+//
+//-------------------------------------------------------------------------
+void nsBaseWidget::BaseCreate(nsIWidget* aParent, nsWidgetInitData* aInitData) {
+ // keep a reference to the device context
+ if (nullptr != aInitData) {
+ mWindowType = aInitData->mWindowType;
+ mBorderStyle = aInitData->mBorderStyle;
+ mPopupLevel = aInitData->mPopupLevel;
+ mPopupType = aInitData->mPopupHint;
+ mHasRemoteContent = aInitData->mHasRemoteContent;
+ mFissionWindow = aInitData->mFissionWindow;
+ }
+
+ if (aParent) {
+ aParent->AddChild(this);
+ }
+}
+
+//-------------------------------------------------------------------------
+//
+// Accessor functions to get/set the client data
+//
+//-------------------------------------------------------------------------
+
+nsIWidgetListener* nsBaseWidget::GetWidgetListener() { return mWidgetListener; }
+
+void nsBaseWidget::SetWidgetListener(nsIWidgetListener* aWidgetListener) {
+ mWidgetListener = aWidgetListener;
+}
+
+already_AddRefed<nsIWidget> nsBaseWidget::CreateChild(
+ const LayoutDeviceIntRect& aRect, nsWidgetInitData* aInitData,
+ bool aForceUseIWidgetParent) {
+ nsIWidget* parent = this;
+ nsNativeWidget nativeParent = nullptr;
+
+ if (!aForceUseIWidgetParent) {
+ // Use only either parent or nativeParent, not both, to match
+ // existing code. Eventually Create() should be divested of its
+ // nativeWidget parameter.
+ nativeParent = parent ? parent->GetNativeData(NS_NATIVE_WIDGET) : nullptr;
+ parent = nativeParent ? nullptr : parent;
+ MOZ_ASSERT(!parent || !nativeParent, "messed up logic");
+ }
+
+ nsCOMPtr<nsIWidget> widget;
+ if (aInitData && aInitData->mWindowType == eWindowType_popup) {
+ widget = AllocateChildPopupWidget();
+ } else {
+ widget = nsIWidget::CreateChildWindow();
+ }
+
+ if (widget &&
+ NS_SUCCEEDED(widget->Create(parent, nativeParent, aRect, aInitData))) {
+ return widget.forget();
+ }
+
+ return nullptr;
+}
+
+// Attach a view to our widget which we'll send events to.
+void nsBaseWidget::AttachViewToTopLevel(bool aUseAttachedEvents) {
+ NS_ASSERTION((mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog ||
+ mWindowType == eWindowType_invisible ||
+ mWindowType == eWindowType_child),
+ "Can't attach to window of that type");
+
+ mUseAttachedEvents = aUseAttachedEvents;
+}
+
+nsIWidgetListener* nsBaseWidget::GetAttachedWidgetListener() {
+ return mAttachedWidgetListener;
+}
+
+nsIWidgetListener* nsBaseWidget::GetPreviouslyAttachedWidgetListener() {
+ return mPreviouslyAttachedWidgetListener;
+}
+
+void nsBaseWidget::SetPreviouslyAttachedWidgetListener(
+ nsIWidgetListener* aListener) {
+ mPreviouslyAttachedWidgetListener = aListener;
+}
+
+void nsBaseWidget::SetAttachedWidgetListener(nsIWidgetListener* aListener) {
+ mAttachedWidgetListener = aListener;
+}
+
+//-------------------------------------------------------------------------
+//
+// Close this nsBaseWidget
+//
+//-------------------------------------------------------------------------
+void nsBaseWidget::Destroy() {
+ // Just in case our parent is the only ref to us
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+ // disconnect from the parent
+ nsIWidget* parent = GetParent();
+ if (parent) {
+ parent->RemoveChild(this);
+ }
+
+#if defined(XP_WIN)
+ // Allow our scroll capture container to be cleaned up, if we have one.
+ mScrollCaptureContainer = nullptr;
+#endif
+}
+
+//-------------------------------------------------------------------------
+//
+// Get this nsBaseWidget parent
+//
+//-------------------------------------------------------------------------
+nsIWidget* nsBaseWidget::GetParent(void) { return nullptr; }
+
+//-------------------------------------------------------------------------
+//
+// Get this nsBaseWidget top level widget
+//
+//-------------------------------------------------------------------------
+nsIWidget* nsBaseWidget::GetTopLevelWidget() {
+ nsIWidget *topLevelWidget = nullptr, *widget = this;
+ while (widget) {
+ topLevelWidget = widget;
+ widget = widget->GetParent();
+ }
+ return topLevelWidget;
+}
+
+//-------------------------------------------------------------------------
+//
+// Get this nsBaseWidget's top (non-sheet) parent (if it's a sheet)
+//
+//-------------------------------------------------------------------------
+nsIWidget* nsBaseWidget::GetSheetWindowParent(void) { return nullptr; }
+
+float nsBaseWidget::GetDPI() { return 96.0f; }
+
+CSSToLayoutDeviceScale nsIWidget::GetDefaultScale() {
+ double devPixelsPerCSSPixel = StaticPrefs::layout_css_devPixelsPerPx();
+
+ if (devPixelsPerCSSPixel <= 0.0) {
+ devPixelsPerCSSPixel = GetDefaultScaleInternal();
+ }
+
+ return CSSToLayoutDeviceScale(devPixelsPerCSSPixel);
+}
+
+//-------------------------------------------------------------------------
+//
+// Add a child to the list of children
+//
+//-------------------------------------------------------------------------
+void nsBaseWidget::AddChild(nsIWidget* aChild) {
+ MOZ_ASSERT(!aChild->GetNextSibling() && !aChild->GetPrevSibling(),
+ "aChild not properly removed from its old child list");
+
+ if (!mFirstChild) {
+ mFirstChild = mLastChild = aChild;
+ } else {
+ // append to the list
+ MOZ_ASSERT(mLastChild);
+ MOZ_ASSERT(!mLastChild->GetNextSibling());
+ mLastChild->SetNextSibling(aChild);
+ aChild->SetPrevSibling(mLastChild);
+ mLastChild = aChild;
+ }
+}
+
+//-------------------------------------------------------------------------
+//
+// Remove a child from the list of children
+//
+//-------------------------------------------------------------------------
+void nsBaseWidget::RemoveChild(nsIWidget* aChild) {
+#ifdef DEBUG
+# ifdef XP_MACOSX
+ // nsCocoaWindow doesn't implement GetParent, so in that case parent will be
+ // null and we'll just have to do without this assertion.
+ nsIWidget* parent = aChild->GetParent();
+ NS_ASSERTION(!parent || parent == this, "Not one of our kids!");
+# else
+ MOZ_RELEASE_ASSERT(aChild->GetParent() == this, "Not one of our kids!");
+# endif
+#endif
+
+ if (mLastChild == aChild) {
+ mLastChild = mLastChild->GetPrevSibling();
+ }
+ if (mFirstChild == aChild) {
+ mFirstChild = mFirstChild->GetNextSibling();
+ }
+
+ // Now remove from the list. Make sure that we pass ownership of the tail
+ // of the list correctly before we have aChild let go of it.
+ nsIWidget* prev = aChild->GetPrevSibling();
+ nsIWidget* next = aChild->GetNextSibling();
+ if (prev) {
+ prev->SetNextSibling(next);
+ }
+ if (next) {
+ next->SetPrevSibling(prev);
+ }
+
+ aChild->SetNextSibling(nullptr);
+ aChild->SetPrevSibling(nullptr);
+}
+
+//-------------------------------------------------------------------------
+//
+// Sets widget's position within its parent's child list.
+//
+//-------------------------------------------------------------------------
+void nsBaseWidget::SetZIndex(int32_t aZIndex) {
+ // Hold a ref to ourselves just in case, since we're going to remove
+ // from our parent.
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ mZIndex = aZIndex;
+
+ // reorder this child in its parent's list.
+ auto* parent = static_cast<nsBaseWidget*>(GetParent());
+ if (parent) {
+ parent->RemoveChild(this);
+ // Scope sib outside the for loop so we can check it afterward
+ nsIWidget* sib = parent->GetFirstChild();
+ for (; sib; sib = sib->GetNextSibling()) {
+ int32_t childZIndex = GetZIndex();
+ if (aZIndex < childZIndex) {
+ // Insert ourselves before sib
+ nsIWidget* prev = sib->GetPrevSibling();
+ mNextSibling = sib;
+ mPrevSibling = prev;
+ sib->SetPrevSibling(this);
+ if (prev) {
+ prev->SetNextSibling(this);
+ } else {
+ NS_ASSERTION(sib == parent->mFirstChild, "Broken child list");
+ // We've taken ownership of sib, so it's safe to have parent let
+ // go of it
+ parent->mFirstChild = this;
+ }
+ PlaceBehind(eZPlacementBelow, sib, false);
+ break;
+ }
+ }
+ // were we added to the list?
+ if (!sib) {
+ parent->AddChild(this);
+ }
+ }
+}
+
+//-------------------------------------------------------------------------
+//
+// Maximize, minimize or restore the window. The BaseWidget implementation
+// merely stores the state.
+//
+//-------------------------------------------------------------------------
+void nsBaseWidget::SetSizeMode(nsSizeMode aMode) {
+ MOZ_ASSERT(aMode == nsSizeMode_Normal || aMode == nsSizeMode_Minimized ||
+ aMode == nsSizeMode_Maximized || aMode == nsSizeMode_Fullscreen);
+ mSizeMode = aMode;
+}
+
+void nsBaseWidget::GetWorkspaceID(nsAString& workspaceID) {
+ workspaceID.Truncate();
+}
+
+void nsBaseWidget::MoveToWorkspace(const nsAString& workspaceID) {
+ // Noop.
+}
+
+//-------------------------------------------------------------------------
+//
+// Get this component cursor
+//
+//-------------------------------------------------------------------------
+
+void nsBaseWidget::SetCursor(nsCursor aCursor, imgIContainer*, uint32_t,
+ uint32_t) {
+ // We don't support the cursor image.
+ mCursor = aCursor;
+}
+
+//-------------------------------------------------------------------------
+//
+// Window transparency methods
+//
+//-------------------------------------------------------------------------
+
+void nsBaseWidget::SetTransparencyMode(nsTransparencyMode aMode) {}
+
+nsTransparencyMode nsBaseWidget::GetTransparencyMode() {
+ return eTransparencyOpaque;
+}
+
+bool nsBaseWidget::IsWindowClipRegionEqual(
+ const nsTArray<LayoutDeviceIntRect>& aRects) {
+ return mClipRects && mClipRectCount == aRects.Length() &&
+ memcmp(mClipRects.get(), aRects.Elements(),
+ sizeof(LayoutDeviceIntRect) * mClipRectCount) == 0;
+}
+
+void nsBaseWidget::StoreWindowClipRegion(
+ const nsTArray<LayoutDeviceIntRect>& aRects) {
+ mClipRectCount = aRects.Length();
+ mClipRects = MakeUnique<LayoutDeviceIntRect[]>(mClipRectCount);
+ if (mClipRects) {
+ memcpy(mClipRects.get(), aRects.Elements(),
+ sizeof(LayoutDeviceIntRect) * mClipRectCount);
+ }
+}
+
+void nsBaseWidget::GetWindowClipRegion(nsTArray<LayoutDeviceIntRect>* aRects) {
+ if (mClipRects) {
+ aRects->AppendElements(mClipRects.get(), mClipRectCount);
+ } else {
+ aRects->AppendElement(
+ LayoutDeviceIntRect(0, 0, mBounds.Width(), mBounds.Height()));
+ }
+}
+
+const LayoutDeviceIntRegion nsBaseWidget::RegionFromArray(
+ const nsTArray<LayoutDeviceIntRect>& aRects) {
+ LayoutDeviceIntRegion region;
+ for (uint32_t i = 0; i < aRects.Length(); ++i) {
+ region.Or(region, aRects[i]);
+ }
+ return region;
+}
+
+void nsBaseWidget::ArrayFromRegion(const LayoutDeviceIntRegion& aRegion,
+ nsTArray<LayoutDeviceIntRect>& aRects) {
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ aRects.AppendElement(iter.Get());
+ }
+}
+
+nsresult nsBaseWidget::SetWindowClipRegion(
+ const nsTArray<LayoutDeviceIntRect>& aRects, bool aIntersectWithExisting) {
+ if (!aIntersectWithExisting) {
+ StoreWindowClipRegion(aRects);
+ } else {
+ // get current rects
+ nsTArray<LayoutDeviceIntRect> currentRects;
+ GetWindowClipRegion(&currentRects);
+ // create region from them
+ LayoutDeviceIntRegion currentRegion = RegionFromArray(currentRects);
+ // create region from new rects
+ LayoutDeviceIntRegion newRegion = RegionFromArray(aRects);
+ // intersect regions
+ LayoutDeviceIntRegion intersection;
+ intersection.And(currentRegion, newRegion);
+ // create int rect array from intersection
+ nsTArray<LayoutDeviceIntRect> rects;
+ ArrayFromRegion(intersection, rects);
+ // store
+ StoreWindowClipRegion(rects);
+ }
+ return NS_OK;
+}
+
+/* virtual */
+void nsBaseWidget::PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Should never call PerformFullscreenTransition on nsBaseWidget");
+}
+
+//-------------------------------------------------------------------------
+//
+// Put the window into full-screen mode
+//
+//-------------------------------------------------------------------------
+void nsBaseWidget::InfallibleMakeFullScreen(bool aFullScreen,
+ nsIScreen* aScreen) {
+ HideWindowChrome(aFullScreen);
+
+ if (aFullScreen) {
+ if (!mOriginalBounds) {
+ mOriginalBounds = new LayoutDeviceIntRect();
+ }
+ *mOriginalBounds = GetScreenBounds();
+
+ // Move to top-left corner of screen and size to the screen dimensions
+ nsCOMPtr<nsIScreen> screen = aScreen;
+ if (!screen) {
+ screen = GetWidgetScreen();
+ }
+ if (screen) {
+ int32_t left, top, width, height;
+ if (NS_SUCCEEDED(
+ screen->GetRectDisplayPix(&left, &top, &width, &height))) {
+ Resize(left, top, width, height, true);
+ }
+ }
+ } else if (mOriginalBounds) {
+ if (BoundsUseDesktopPixels()) {
+ DesktopRect deskRect = *mOriginalBounds / GetDesktopToDeviceScale();
+ Resize(deskRect.X(), deskRect.Y(), deskRect.Width(), deskRect.Height(),
+ true);
+ } else {
+ Resize(mOriginalBounds->X(), mOriginalBounds->Y(),
+ mOriginalBounds->Width(), mOriginalBounds->Height(), true);
+ }
+ }
+}
+
+nsresult nsBaseWidget::MakeFullScreen(bool aFullScreen, nsIScreen* aScreen) {
+ InfallibleMakeFullScreen(aFullScreen, aScreen);
+ return NS_OK;
+}
+
+nsBaseWidget::AutoLayerManagerSetup::AutoLayerManagerSetup(
+ nsBaseWidget* aWidget, gfxContext* aTarget, BufferMode aDoubleBuffering,
+ ScreenRotation aRotation)
+ : mWidget(aWidget) {
+ LayerManager* lm = mWidget->GetLayerManager();
+ NS_ASSERTION(
+ !lm || lm->GetBackendType() == LayersBackend::LAYERS_BASIC,
+ "AutoLayerManagerSetup instantiated for non-basic layer backend!");
+ if (lm) {
+ mLayerManager = lm->AsBasicLayerManager();
+ if (mLayerManager) {
+ mLayerManager->SetDefaultTarget(aTarget);
+ mLayerManager->SetDefaultTargetConfiguration(aDoubleBuffering, aRotation);
+ }
+ }
+}
+
+nsBaseWidget::AutoLayerManagerSetup::~AutoLayerManagerSetup() {
+ if (mLayerManager) {
+ mLayerManager->SetDefaultTarget(nullptr);
+ mLayerManager->SetDefaultTargetConfiguration(
+ mozilla::layers::BufferMode::BUFFER_NONE, ROTATION_0);
+ }
+}
+
+bool nsBaseWidget::IsSmallPopup() const {
+ return mWindowType == eWindowType_popup && mPopupType != ePopupTypePanel;
+}
+
+bool nsBaseWidget::ComputeShouldAccelerate() {
+ return gfx::gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING) &&
+ WidgetTypeSupportsAcceleration();
+}
+
+bool nsBaseWidget::UseAPZ() {
+ return (gfxPlatform::AsyncPanZoomEnabled() &&
+ (WindowType() == eWindowType_toplevel ||
+ WindowType() == eWindowType_child ||
+ (WindowType() == eWindowType_popup && HasRemoteContent() &&
+ StaticPrefs::apz_popups_enabled())));
+}
+
+void nsBaseWidget::CreateCompositor() {
+ LayoutDeviceIntRect rect = GetBounds();
+ CreateCompositor(rect.Width(), rect.Height());
+}
+
+already_AddRefed<GeckoContentController>
+nsBaseWidget::CreateRootContentController() {
+ RefPtr<GeckoContentController> controller =
+ new ChromeProcessController(this, mAPZEventState, mAPZC);
+ return controller.forget();
+}
+
+void nsBaseWidget::ConfigureAPZCTreeManager() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mAPZC);
+
+ ConfigureAPZControllerThread();
+
+ float dpi = GetDPI();
+ // On Android the main thread is not the controller thread
+ APZThreadUtils::RunOnControllerThread(
+ NewRunnableMethod<float>("layers::IAPZCTreeManager::SetDPI", mAPZC,
+ &IAPZCTreeManager::SetDPI, dpi));
+
+ if (StaticPrefs::apz_keyboard_enabled_AtStartup()) {
+ KeyboardMap map = RootWindowGlobalKeyListener::CollectKeyboardShortcuts();
+ // On Android the main thread is not the controller thread
+ APZThreadUtils::RunOnControllerThread(NewRunnableMethod<KeyboardMap>(
+ "layers::IAPZCTreeManager::SetKeyboardMap", mAPZC,
+ &IAPZCTreeManager::SetKeyboardMap, map));
+ }
+
+ RefPtr<IAPZCTreeManager> treeManager = mAPZC; // for capture by the lambdas
+
+ ContentReceivedInputBlockCallback callback(
+ [treeManager](uint64_t aInputBlockId, bool aPreventDefault) {
+ MOZ_ASSERT(NS_IsMainThread());
+ APZThreadUtils::RunOnControllerThread(NewRunnableMethod<uint64_t, bool>(
+ "layers::IAPZCTreeManager::ContentReceivedInputBlock", treeManager,
+ &IAPZCTreeManager::ContentReceivedInputBlock, aInputBlockId,
+ aPreventDefault));
+ });
+ mAPZEventState = new APZEventState(this, std::move(callback));
+
+ mSetAllowedTouchBehaviorCallback =
+ [treeManager](uint64_t aInputBlockId,
+ const nsTArray<TouchBehaviorFlags>& aFlags) {
+ MOZ_ASSERT(NS_IsMainThread());
+ APZThreadUtils::RunOnControllerThread(
+ NewRunnableMethod<
+ uint64_t, StoreCopyPassByLRef<nsTArray<TouchBehaviorFlags>>>(
+ "layers::IAPZCTreeManager::SetAllowedTouchBehavior",
+ treeManager, &IAPZCTreeManager::SetAllowedTouchBehavior,
+ aInputBlockId, aFlags.Clone()));
+ };
+
+ mRootContentController = CreateRootContentController();
+ if (mRootContentController) {
+ mCompositorSession->SetContentController(mRootContentController);
+ }
+
+ // When APZ is enabled, we can actually enable raw touch events because we
+ // have code that can deal with them properly. If APZ is not enabled, this
+ // function doesn't get called.
+ if (StaticPrefs::dom_w3c_touch_events_enabled() ||
+ StaticPrefs::dom_w3c_pointer_events_enabled()) {
+ RegisterTouchWindow();
+ }
+}
+
+void nsBaseWidget::ConfigureAPZControllerThread() {
+ // By default the controller thread is the main thread.
+ APZThreadUtils::SetControllerThread(NS_GetCurrentThread());
+}
+
+void nsBaseWidget::SetConfirmedTargetAPZC(
+ uint64_t aInputBlockId,
+ const nsTArray<ScrollableLayerGuid>& aTargets) const {
+ APZThreadUtils::RunOnControllerThread(
+ NewRunnableMethod<uint64_t,
+ StoreCopyPassByRRef<nsTArray<ScrollableLayerGuid>>>(
+ "layers::IAPZCTreeManager::SetTargetAPZC", mAPZC,
+ &IAPZCTreeManager::SetTargetAPZC, aInputBlockId, aTargets.Clone()));
+}
+
+void nsBaseWidget::UpdateZoomConstraints(
+ const uint32_t& aPresShellId, const ScrollableLayerGuid::ViewID& aViewId,
+ const Maybe<ZoomConstraints>& aConstraints) {
+ if (!mCompositorSession || !mAPZC) {
+ if (mInitialZoomConstraints) {
+ MOZ_ASSERT(mInitialZoomConstraints->mPresShellID == aPresShellId);
+ MOZ_ASSERT(mInitialZoomConstraints->mViewID == aViewId);
+ if (!aConstraints) {
+ mInitialZoomConstraints.reset();
+ }
+ }
+
+ if (aConstraints) {
+ // We have some constraints, but the compositor and APZC aren't created
+ // yet. Save these so we can use them later.
+ mInitialZoomConstraints = Some(
+ InitialZoomConstraints(aPresShellId, aViewId, aConstraints.ref()));
+ }
+ return;
+ }
+ LayersId layersId = mCompositorSession->RootLayerTreeId();
+ mAPZC->UpdateZoomConstraints(
+ ScrollableLayerGuid(layersId, aPresShellId, aViewId), aConstraints);
+}
+
+bool nsBaseWidget::AsyncPanZoomEnabled() const { return !!mAPZC; }
+
+nsEventStatus nsBaseWidget::ProcessUntransformedAPZEvent(
+ WidgetInputEvent* aEvent, const APZEventResult& aApzResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ ScrollableLayerGuid targetGuid = aApzResult.mTargetGuid;
+ uint64_t inputBlockId = aApzResult.mInputBlockId;
+ InputAPZContext context(aApzResult.mTargetGuid, inputBlockId,
+ aApzResult.mStatus);
+
+ // Make a copy of the original event for the APZCCallbackHelper helpers that
+ // we call later, because the event passed to DispatchEvent can get mutated in
+ // ways that we don't want (i.e. touch points can get stripped out).
+ nsEventStatus status;
+ UniquePtr<WidgetEvent> original(aEvent->Duplicate());
+ DispatchEvent(aEvent, status);
+
+ if (mAPZC && !InputAPZContext::WasRoutedToChildProcess() && inputBlockId) {
+ // EventStateManager did not route the event into the child process.
+ // It's safe to communicate to APZ that the event has been processed.
+ // Note that here aGuid.mLayersId might be different from
+ // mCompositorSession->RootLayerTreeId() because the event might have gotten
+ // hit-tested by APZ to be targeted at a child process, but a parent process
+ // event listener called preventDefault on it. In that case aGuid.mLayersId
+ // would still be the layers id for the child process, but the event would
+ // not have actually gotten routed to the child process. The main-thread
+ // hit-test result therefore needs to use the parent process layers id.
+ LayersId rootLayersId = mCompositorSession->RootLayerTreeId();
+
+ UniquePtr<DisplayportSetListener> postLayerization;
+ if (WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent()) {
+ nsTArray<TouchBehaviorFlags> allowedTouchBehaviors;
+ if (touchEvent->mMessage == eTouchStart) {
+ if (StaticPrefs::layout_css_touch_action_enabled()) {
+ allowedTouchBehaviors =
+ APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification(
+ this, GetDocument(), *(original->AsTouchEvent()),
+ inputBlockId, mSetAllowedTouchBehaviorCallback);
+ }
+ postLayerization = APZCCallbackHelper::SendSetTargetAPZCNotification(
+ this, GetDocument(), *(original->AsTouchEvent()), rootLayersId,
+ inputBlockId);
+ }
+ mAPZEventState->ProcessTouchEvent(*touchEvent, targetGuid, inputBlockId,
+ aApzResult.mStatus, status,
+ std::move(allowedTouchBehaviors));
+ } else if (WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent()) {
+ MOZ_ASSERT(wheelEvent->mFlags.mHandledByAPZ);
+ postLayerization = APZCCallbackHelper::SendSetTargetAPZCNotification(
+ this, GetDocument(), *(original->AsWheelEvent()), rootLayersId,
+ inputBlockId);
+ if (wheelEvent->mCanTriggerSwipe) {
+ ReportSwipeStarted(inputBlockId, wheelEvent->TriggersSwipe());
+ }
+ mAPZEventState->ProcessWheelEvent(*wheelEvent, inputBlockId);
+ } else if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) {
+ MOZ_ASSERT(mouseEvent->mFlags.mHandledByAPZ);
+ postLayerization = APZCCallbackHelper::SendSetTargetAPZCNotification(
+ this, GetDocument(), *(original->AsMouseEvent()), rootLayersId,
+ inputBlockId);
+ mAPZEventState->ProcessMouseEvent(*mouseEvent, inputBlockId);
+ }
+ if (postLayerization && postLayerization->Register()) {
+ Unused << postLayerization.release();
+ }
+ }
+
+ return status;
+}
+
+template <class InputType, class EventType>
+class DispatchEventOnMainThread : public Runnable {
+ public:
+ DispatchEventOnMainThread(const InputType& aInput, nsBaseWidget* aWidget,
+ const APZEventResult& aAPZResult)
+ : mozilla::Runnable("DispatchEventOnMainThread"),
+ mInput(aInput),
+ mWidget(aWidget),
+ mAPZResult(aAPZResult) {}
+
+ NS_IMETHOD Run() override {
+ EventType event = mInput.ToWidgetEvent(mWidget);
+ mWidget->ProcessUntransformedAPZEvent(&event, mAPZResult);
+ return NS_OK;
+ }
+
+ private:
+ InputType mInput;
+ nsBaseWidget* mWidget;
+ APZEventResult mAPZResult;
+};
+
+template <class InputType, class EventType>
+class DispatchInputOnControllerThread : public Runnable {
+ public:
+ DispatchInputOnControllerThread(const EventType& aEvent,
+ IAPZCTreeManager* aAPZC,
+ nsBaseWidget* aWidget)
+ : mozilla::Runnable("DispatchInputOnControllerThread"),
+ mMainMessageLoop(MessageLoop::current()),
+ mInput(aEvent),
+ mAPZC(aAPZC),
+ mWidget(aWidget) {}
+
+ NS_IMETHOD Run() override {
+ APZEventResult result = mAPZC->InputBridge()->ReceiveInputEvent(mInput);
+ if (result.mStatus == nsEventStatus_eConsumeNoDefault) {
+ return NS_OK;
+ }
+ RefPtr<Runnable> r = new DispatchEventOnMainThread<InputType, EventType>(
+ mInput, mWidget, result);
+ mMainMessageLoop->PostTask(r.forget());
+ return NS_OK;
+ }
+
+ private:
+ MessageLoop* mMainMessageLoop;
+ InputType mInput;
+ RefPtr<IAPZCTreeManager> mAPZC;
+ nsBaseWidget* mWidget;
+};
+
+void nsBaseWidget::DispatchTouchInput(MultiTouchInput& aInput) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mAPZC) {
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+
+ APZEventResult result = mAPZC->InputBridge()->ReceiveInputEvent(aInput);
+ if (result.mStatus == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+
+ WidgetTouchEvent event = aInput.ToWidgetTouchEvent(this);
+ ProcessUntransformedAPZEvent(&event, result);
+ } else {
+ WidgetTouchEvent event = aInput.ToWidgetTouchEvent(this);
+
+ nsEventStatus status;
+ DispatchEvent(&event, status);
+ }
+}
+
+void nsBaseWidget::DispatchPanGestureInput(PanGestureInput& aInput) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mAPZC) {
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+
+ APZEventResult result = mAPZC->InputBridge()->ReceiveInputEvent(aInput);
+ if (result.mStatus == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+
+ WidgetWheelEvent event = aInput.ToWidgetEvent(this);
+ ProcessUntransformedAPZEvent(&event, result);
+ } else {
+ WidgetWheelEvent event = aInput.ToWidgetEvent(this);
+ nsEventStatus status;
+ DispatchEvent(&event, status);
+ }
+}
+
+void nsBaseWidget::DispatchPinchGestureInput(PinchGestureInput& aInput) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mAPZC) {
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+ APZEventResult result = mAPZC->InputBridge()->ReceiveInputEvent(aInput);
+
+ if (result.mStatus == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+ WidgetWheelEvent event = aInput.ToWidgetEvent(this);
+ ProcessUntransformedAPZEvent(&event, result);
+ } else {
+ WidgetWheelEvent event = aInput.ToWidgetEvent(this);
+ nsEventStatus status;
+ DispatchEvent(&event, status);
+ }
+}
+
+nsEventStatus nsBaseWidget::DispatchInputEvent(WidgetInputEvent* aEvent) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mAPZC) {
+ if (APZThreadUtils::IsControllerThread()) {
+ APZEventResult result = mAPZC->InputBridge()->ReceiveInputEvent(*aEvent);
+ if (result.mStatus == nsEventStatus_eConsumeNoDefault) {
+ return result.mStatus;
+ }
+ return ProcessUntransformedAPZEvent(aEvent, result);
+ }
+ if (WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent()) {
+ RefPtr<Runnable> r =
+ new DispatchInputOnControllerThread<ScrollWheelInput,
+ WidgetWheelEvent>(*wheelEvent,
+ mAPZC, this);
+ APZThreadUtils::RunOnControllerThread(std::move(r));
+ return nsEventStatus_eConsumeDoDefault;
+ }
+ if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) {
+ RefPtr<Runnable> r =
+ new DispatchInputOnControllerThread<MouseInput, WidgetMouseEvent>(
+ *mouseEvent, mAPZC, this);
+ APZThreadUtils::RunOnControllerThread(std::move(r));
+ return nsEventStatus_eConsumeDoDefault;
+ }
+ // Allow dispatching keyboard events on Gecko thread.
+ MOZ_ASSERT(aEvent->AsKeyboardEvent());
+ }
+
+ nsEventStatus status;
+ DispatchEvent(aEvent, status);
+ return status;
+}
+
+void nsBaseWidget::DispatchEventToAPZOnly(mozilla::WidgetInputEvent* aEvent) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mAPZC) {
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+ mAPZC->InputBridge()->ReceiveInputEvent(*aEvent);
+ }
+}
+
+Document* nsBaseWidget::GetDocument() const {
+ if (mWidgetListener) {
+ if (PresShell* presShell = mWidgetListener->GetPresShell()) {
+ return presShell->GetDocument();
+ }
+ }
+ return nullptr;
+}
+
+void nsBaseWidget::CreateCompositorVsyncDispatcher() {
+ // Parent directly listens to the vsync source whereas
+ // child process communicate via IPC
+ // Should be called AFTER gfxPlatform is initialized
+ if (XRE_IsParentProcess()) {
+ if (!mCompositorVsyncDispatcherLock) {
+ mCompositorVsyncDispatcherLock =
+ MakeUnique<Mutex>("mCompositorVsyncDispatcherLock");
+ }
+ MutexAutoLock lock(*mCompositorVsyncDispatcherLock.get());
+ if (!mCompositorVsyncDispatcher) {
+ mCompositorVsyncDispatcher = new CompositorVsyncDispatcher();
+ }
+ }
+}
+
+already_AddRefed<CompositorVsyncDispatcher>
+nsBaseWidget::GetCompositorVsyncDispatcher() {
+ MOZ_ASSERT(mCompositorVsyncDispatcherLock.get());
+
+ MutexAutoLock lock(*mCompositorVsyncDispatcherLock.get());
+ RefPtr<CompositorVsyncDispatcher> dispatcher = mCompositorVsyncDispatcher;
+ return dispatcher.forget();
+}
+
+already_AddRefed<LayerManager> nsBaseWidget::CreateCompositorSession(
+ int aWidth, int aHeight, CompositorOptions* aOptionsOut) {
+ MOZ_ASSERT(aOptionsOut);
+
+ do {
+ CreateCompositorVsyncDispatcher();
+
+ gfx::GPUProcessManager* gpu = gfx::GPUProcessManager::Get();
+ // Make sure GPU process is ready for use.
+ // If it failed to connect to GPU process, GPU process usage is disabled in
+ // EnsureGPUReady(). It could update gfxVars and gfxConfigs.
+ gpu->EnsureGPUReady();
+
+ // If widget type does not supports acceleration, we use ClientLayerManager
+ // even when gfxVars::UseWebRender() is true. WebRender could coexist only
+ // with BasicCompositor.
+ bool enableWR =
+ gfx::gfxVars::UseWebRender() && WidgetTypeSupportsAcceleration();
+ bool enableAPZ = UseAPZ();
+ CompositorOptions options(enableAPZ, enableWR);
+
+ // Bug 1588484 - Advanced Layers is currently disabled for fission windows,
+ // since it doesn't properly support nested RefLayers.
+ bool enableAL =
+ gfx::gfxConfig::IsEnabled(gfx::Feature::ADVANCED_LAYERS) &&
+ (!mFissionWindow || StaticPrefs::layers_advanced_fission_enabled());
+ options.SetUseAdvancedLayers(enableAL);
+
+#ifdef MOZ_WIDGET_ANDROID
+ if (!GetNativeData(NS_JAVA_SURFACE)) {
+ options.SetInitiallyPaused(true);
+ }
+#else
+ options.SetInitiallyPaused(CompositorInitiallyPaused());
+#endif
+
+ RefPtr<LayerManager> lm;
+ if (options.UseWebRender()) {
+ lm = new WebRenderLayerManager(this);
+ } else {
+ lm = new ClientLayerManager(this);
+ }
+
+ bool retry = false;
+ mCompositorSession = gpu->CreateTopLevelCompositor(
+ this, lm, GetDefaultScale(), options, UseExternalCompositingSurface(),
+ gfx::IntSize(aWidth, aHeight), &retry);
+
+ if (lm->AsWebRenderLayerManager() && mCompositorSession) {
+ TextureFactoryIdentifier textureFactoryIdentifier;
+ nsCString error;
+ lm->AsWebRenderLayerManager()->Initialize(
+ mCompositorSession->GetCompositorBridgeChild(),
+ wr::AsPipelineId(mCompositorSession->RootLayerTreeId()),
+ &textureFactoryIdentifier, error);
+ if (textureFactoryIdentifier.mParentBackend != LayersBackend::LAYERS_WR) {
+ retry = true;
+ DestroyCompositor();
+ // gfxVars::UseDoubleBufferingWithCompositor() is also disabled.
+ gfx::GPUProcessManager::Get()->DisableWebRender(
+ wr::WebRenderError::INITIALIZE, error);
+ }
+ } else if (lm->AsClientLayerManager() && mCompositorSession) {
+ bool shouldAccelerate = ComputeShouldAccelerate();
+ TextureFactoryIdentifier textureFactoryIdentifier;
+ lm->AsClientLayerManager()->Initialize(
+ mCompositorSession->GetCompositorBridgeChild(), shouldAccelerate,
+ &textureFactoryIdentifier);
+ if (textureFactoryIdentifier.mParentBackend ==
+ LayersBackend::LAYERS_NONE) {
+ DestroyCompositor();
+ lm = nullptr;
+ }
+ }
+
+ // We need to retry in a loop because the act of failing to create the
+ // compositor can change our state (e.g. disable WebRender).
+ if (mCompositorSession || !retry) {
+ *aOptionsOut = options;
+ return lm.forget();
+ }
+ } while (true);
+}
+
+void nsBaseWidget::CreateCompositor(int aWidth, int aHeight) {
+ // This makes sure that gfxPlatforms gets initialized if it hasn't by now.
+ gfxPlatform::GetPlatform();
+
+ MOZ_ASSERT(gfxPlatform::UsesOffMainThreadCompositing(),
+ "This function assumes OMTC");
+
+ MOZ_ASSERT(!mCompositorSession && !mCompositorBridgeChild,
+ "Should have properly cleaned up the previous PCompositor pair "
+ "beforehand");
+
+ if (mCompositorBridgeChild) {
+ mCompositorBridgeChild->Destroy();
+ }
+
+ // Recreating this is tricky, as we may still have an old and we need
+ // to make sure it's properly destroyed by calling DestroyCompositor!
+
+ // If we've already received a shutdown notification, don't try
+ // create a new compositor.
+ if (!mShutdownObserver) {
+ return;
+ }
+
+ CompositorOptions options;
+ RefPtr<LayerManager> lm = CreateCompositorSession(aWidth, aHeight, &options);
+ if (!lm) {
+ return;
+ }
+
+ MOZ_ASSERT(mCompositorSession);
+ mCompositorBridgeChild = mCompositorSession->GetCompositorBridgeChild();
+ SetCompositorWidgetDelegate(
+ mCompositorSession->GetCompositorWidgetDelegate());
+
+ if (options.UseAPZ()) {
+ mAPZC = mCompositorSession->GetAPZCTreeManager();
+ ConfigureAPZCTreeManager();
+ } else {
+ mAPZC = nullptr;
+ }
+
+ if (mInitialZoomConstraints) {
+ UpdateZoomConstraints(mInitialZoomConstraints->mPresShellID,
+ mInitialZoomConstraints->mViewID,
+ Some(mInitialZoomConstraints->mConstraints));
+ mInitialZoomConstraints.reset();
+ }
+
+ if (lm->AsWebRenderLayerManager()) {
+ TextureFactoryIdentifier textureFactoryIdentifier =
+ lm->GetTextureFactoryIdentifier();
+ MOZ_ASSERT(textureFactoryIdentifier.mParentBackend ==
+ LayersBackend::LAYERS_WR);
+ ImageBridgeChild::IdentifyCompositorTextureHost(textureFactoryIdentifier);
+ gfx::VRManagerChild::IdentifyTextureHost(textureFactoryIdentifier);
+ } else if (lm->AsClientLayerManager()) {
+ TextureFactoryIdentifier textureFactoryIdentifier =
+ lm->GetTextureFactoryIdentifier();
+ // Some popup or transparent widgets may use a different backend than the
+ // compositors used with ImageBridge and VR (and more generally web
+ // content).
+ if (WidgetTypeSupportsAcceleration()) {
+ ImageBridgeChild::IdentifyCompositorTextureHost(textureFactoryIdentifier);
+ gfx::VRManagerChild::IdentifyTextureHost(textureFactoryIdentifier);
+ }
+ }
+
+ WindowUsesOMTC();
+
+ mLayerManager = std::move(lm);
+
+ // Only track compositors for top-level windows, since other window types
+ // 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());
+ }
+}
+
+void nsBaseWidget::NotifyCompositorSessionLost(CompositorSession* aSession) {
+ MOZ_ASSERT(aSession == mCompositorSession);
+ DestroyLayerManager();
+}
+
+bool nsBaseWidget::ShouldUseOffMainThreadCompositing() {
+ return gfxPlatform::UsesOffMainThreadCompositing();
+}
+
+LayerManager* nsBaseWidget::GetLayerManager(
+ PLayerTransactionChild* aShadowManager, LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence) {
+ if (!mLayerManager) {
+ if (!mShutdownObserver) {
+ // We are shutting down, do not try to re-create a LayerManager
+ return nullptr;
+ }
+ // Try to use an async compositor first, if possible
+ if (ShouldUseOffMainThreadCompositing()) {
+ // e10s uses the parameter to pass in the shadow manager from the
+ // BrowserChild so we don't expect to see it there since this doesn't
+ // support e10s.
+ NS_ASSERTION(aShadowManager == nullptr,
+ "Async Compositor not supported with e10s");
+ CreateCompositor();
+ }
+
+ if (!mLayerManager) {
+ mLayerManager = CreateBasicLayerManager();
+ }
+ }
+ return mLayerManager;
+}
+
+LayerManager* nsBaseWidget::CreateBasicLayerManager() {
+ return new BasicLayerManager(this);
+}
+
+CompositorBridgeChild* nsBaseWidget::GetRemoteRenderer() {
+ return mCompositorBridgeChild;
+}
+
+void nsBaseWidget::ClearCachedWebrenderResources() {
+ if (!mLayerManager || !mLayerManager->AsWebRenderLayerManager()) {
+ return;
+ }
+ mLayerManager->ClearCachedResources();
+}
+
+already_AddRefed<gfx::DrawTarget> nsBaseWidget::StartRemoteDrawing() {
+ return nullptr;
+}
+
+uint32_t nsBaseWidget::GetGLFrameBufferFormat() { return LOCAL_GL_RGBA; }
+
+//-------------------------------------------------------------------------
+//
+// Destroy the window
+//
+//-------------------------------------------------------------------------
+void nsBaseWidget::OnDestroy() {
+ if (mTextEventDispatcher) {
+ mTextEventDispatcher->OnDestroyWidget();
+ // Don't release it until this widget actually released because after this
+ // is called, TextEventDispatcher() may create it again.
+ }
+
+ // If this widget is being destroyed, let the APZ code know to drop references
+ // to this widget. Callers of this function all should be holding a deathgrip
+ // on this widget already.
+ ReleaseContentController();
+}
+
+void nsBaseWidget::MoveClient(const DesktopPoint& aOffset) {
+ LayoutDeviceIntPoint clientOffset(GetClientOffset());
+
+ // GetClientOffset returns device pixels; scale back to desktop pixels
+ // if that's what this widget uses for the Move/Resize APIs
+ if (BoundsUseDesktopPixels()) {
+ DesktopPoint desktopOffset = clientOffset / GetDesktopToDeviceScale();
+ Move(aOffset.x - desktopOffset.x, aOffset.y - desktopOffset.y);
+ } else {
+ LayoutDevicePoint layoutOffset = aOffset * GetDesktopToDeviceScale();
+ Move(layoutOffset.x - clientOffset.x, layoutOffset.y - clientOffset.y);
+ }
+}
+
+void nsBaseWidget::ResizeClient(const DesktopSize& aSize, bool aRepaint) {
+ NS_ASSERTION((aSize.width >= 0), "Negative width passed to ResizeClient");
+ NS_ASSERTION((aSize.height >= 0), "Negative height passed to ResizeClient");
+
+ LayoutDeviceIntRect clientBounds = GetClientBounds();
+
+ // GetClientBounds and mBounds are device pixels; scale back to desktop pixels
+ // if that's what this widget uses for the Move/Resize APIs
+ if (BoundsUseDesktopPixels()) {
+ DesktopSize desktopDelta =
+ (LayoutDeviceIntSize(mBounds.Width(), mBounds.Height()) -
+ clientBounds.Size()) /
+ GetDesktopToDeviceScale();
+ Resize(aSize.width + desktopDelta.width, aSize.height + desktopDelta.height,
+ aRepaint);
+ } else {
+ LayoutDeviceSize layoutSize = aSize * GetDesktopToDeviceScale();
+ Resize(mBounds.Width() + (layoutSize.width - clientBounds.Width()),
+ mBounds.Height() + (layoutSize.height - clientBounds.Height()),
+ aRepaint);
+ }
+}
+
+void nsBaseWidget::ResizeClient(const DesktopRect& aRect, bool aRepaint) {
+ NS_ASSERTION((aRect.Width() >= 0), "Negative width passed to ResizeClient");
+ NS_ASSERTION((aRect.Height() >= 0), "Negative height passed to ResizeClient");
+
+ LayoutDeviceIntRect clientBounds = GetClientBounds();
+ LayoutDeviceIntPoint clientOffset = GetClientOffset();
+ DesktopToLayoutDeviceScale scale = GetDesktopToDeviceScale();
+
+ if (BoundsUseDesktopPixels()) {
+ DesktopPoint desktopOffset = clientOffset / scale;
+ DesktopSize desktopDelta =
+ (LayoutDeviceIntSize(mBounds.Width(), mBounds.Height()) -
+ clientBounds.Size()) /
+ scale;
+ Resize(aRect.X() - desktopOffset.x, aRect.Y() - desktopOffset.y,
+ aRect.Width() + desktopDelta.width,
+ aRect.Height() + desktopDelta.height, aRepaint);
+ } else {
+ LayoutDeviceRect layoutRect = aRect * scale;
+ Resize(layoutRect.X() - clientOffset.x, layoutRect.Y() - clientOffset.y,
+ layoutRect.Width() + mBounds.Width() - clientBounds.Width(),
+ layoutRect.Height() + mBounds.Height() - clientBounds.Height(),
+ aRepaint);
+ }
+}
+
+//-------------------------------------------------------------------------
+//
+// Bounds
+//
+//-------------------------------------------------------------------------
+
+/**
+ * If the implementation of nsWindow supports borders this method MUST be
+ * overridden
+ *
+ **/
+LayoutDeviceIntRect nsBaseWidget::GetClientBounds() { return GetBounds(); }
+
+/**
+ * If the implementation of nsWindow supports borders this method MUST be
+ * overridden
+ *
+ **/
+LayoutDeviceIntRect nsBaseWidget::GetBounds() { return mBounds; }
+
+/**
+ * If the implementation of nsWindow uses a local coordinate system within the
+ *window, this method must be overridden
+ *
+ **/
+LayoutDeviceIntRect nsBaseWidget::GetScreenBounds() { return GetBounds(); }
+
+nsresult nsBaseWidget::GetRestoredBounds(LayoutDeviceIntRect& aRect) {
+ if (SizeMode() != nsSizeMode_Normal) {
+ return NS_ERROR_FAILURE;
+ }
+ aRect = GetScreenBounds();
+ return NS_OK;
+}
+
+LayoutDeviceIntPoint nsBaseWidget::GetClientOffset() {
+ return LayoutDeviceIntPoint(0, 0);
+}
+
+nsresult nsBaseWidget::SetNonClientMargins(LayoutDeviceIntMargin& margins) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t nsBaseWidget::GetMaxTouchPoints() const { return 0; }
+
+bool nsBaseWidget::HasPendingInputEvent() { return false; }
+
+bool nsBaseWidget::ShowsResizeIndicator(LayoutDeviceIntRect* aResizerRect) {
+ return false;
+}
+
+/**
+ * Modifies aFile to point at an icon file with the given name and suffix. The
+ * suffix may correspond to a file extension with leading '.' if appropriate.
+ * Returns true if the icon file exists and can be read.
+ */
+static bool ResolveIconNameHelper(nsIFile* aFile, const nsAString& aIconName,
+ const nsAString& aIconSuffix) {
+ aFile->Append(u"icons"_ns);
+ aFile->Append(u"default"_ns);
+ aFile->Append(aIconName + aIconSuffix);
+
+ bool readable;
+ return NS_SUCCEEDED(aFile->IsReadable(&readable)) && readable;
+}
+
+/**
+ * Resolve the given icon name into a local file object. This method is
+ * intended to be called by subclasses of nsBaseWidget. aIconSuffix is a
+ * platform specific icon file suffix (e.g., ".ico" under Win32).
+ *
+ * If no file is found matching the given parameters, then null is returned.
+ */
+void nsBaseWidget::ResolveIconName(const nsAString& aIconName,
+ const nsAString& aIconSuffix,
+ nsIFile** aResult) {
+ *aResult = nullptr;
+
+ nsCOMPtr<nsIProperties> dirSvc =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
+ if (!dirSvc) return;
+
+ // first check auxilary chrome directories
+
+ nsCOMPtr<nsISimpleEnumerator> dirs;
+ dirSvc->Get(NS_APP_CHROME_DIR_LIST, NS_GET_IID(nsISimpleEnumerator),
+ getter_AddRefs(dirs));
+ if (dirs) {
+ bool hasMore;
+ while (NS_SUCCEEDED(dirs->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> element;
+ dirs->GetNext(getter_AddRefs(element));
+ if (!element) continue;
+ nsCOMPtr<nsIFile> file = do_QueryInterface(element);
+ if (!file) continue;
+ if (ResolveIconNameHelper(file, aIconName, aIconSuffix)) {
+ NS_ADDREF(*aResult = file);
+ return;
+ }
+ }
+ }
+
+ // then check the main app chrome directory
+
+ nsCOMPtr<nsIFile> file;
+ dirSvc->Get(NS_APP_CHROME_DIR, NS_GET_IID(nsIFile), getter_AddRefs(file));
+ if (file && ResolveIconNameHelper(file, aIconName, aIconSuffix))
+ NS_ADDREF(*aResult = file);
+}
+
+void nsBaseWidget::SetSizeConstraints(const SizeConstraints& aConstraints) {
+ mSizeConstraints = aConstraints;
+
+ // Popups are constrained during layout, and we don't want to synchronously
+ // paint from reflow, so bail out... This is not great, but it's no worse than
+ // what we used to do.
+ //
+ // The right fix here is probably making constraint changes go through the
+ // view manager and such.
+ if (mWindowType == eWindowType_popup) {
+ return;
+ }
+
+ // If the current size doesn't meet the new constraints, trigger a
+ // resize to apply it. Note that, we don't want to invoke Resize if
+ // the new constraints don't affect the current size, because Resize
+ // implementation on some platforms may touch other geometry even if
+ // the size don't need to change.
+ LayoutDeviceIntSize curSize = mBounds.Size();
+ LayoutDeviceIntSize clampedSize =
+ Max(aConstraints.mMinSize, Min(aConstraints.mMaxSize, curSize));
+ if (clampedSize != curSize) {
+ gfx::Size size;
+ if (BoundsUseDesktopPixels()) {
+ DesktopSize desktopSize = clampedSize / GetDesktopToDeviceScale();
+ size = desktopSize.ToUnknownSize();
+ } else {
+ size = gfx::Size(clampedSize.ToUnknownSize());
+ }
+ Resize(size.width, size.height, true);
+ }
+}
+
+const widget::SizeConstraints nsBaseWidget::GetSizeConstraints() {
+ return mSizeConstraints;
+}
+
+// static
+nsIRollupListener* nsBaseWidget::GetActiveRollupListener() {
+ // If set, then this is likely an <html:select> dropdown.
+ if (gRollupListener) return gRollupListener;
+
+ return nsXULPopupManager::GetInstance();
+}
+
+void nsBaseWidget::NotifyWindowDestroyed() {
+ if (!mWidgetListener) return;
+
+ nsCOMPtr<nsIAppWindow> window = mWidgetListener->GetAppWindow();
+ nsCOMPtr<nsIBaseWindow> appWindow(do_QueryInterface(window));
+ if (appWindow) {
+ appWindow->Destroy();
+ }
+}
+
+void nsBaseWidget::NotifyWindowMoved(int32_t aX, int32_t aY) {
+ if (mWidgetListener) {
+ mWidgetListener->WindowMoved(this, aX, aY);
+ }
+
+ if (mIMEHasFocus && IMENotificationRequestsRef().WantPositionChanged()) {
+ NotifyIME(IMENotification(IMEMessage::NOTIFY_IME_OF_POSITION_CHANGE));
+ }
+}
+
+void nsBaseWidget::NotifySizeMoveDone() {
+ if (!mWidgetListener) {
+ return;
+ }
+ if (PresShell* presShell = mWidgetListener->GetPresShell()) {
+ presShell->WindowSizeMoveDone();
+ }
+}
+
+void nsBaseWidget::NotifyThemeChanged(ThemeChangeKind aKind) {
+ if (!mWidgetListener) {
+ return;
+ }
+ if (PresShell* presShell = mWidgetListener->GetPresShell()) {
+ presShell->ThemeChanged(aKind);
+ }
+}
+
+void nsBaseWidget::NotifyUIStateChanged(UIStateChangeType aShowFocusRings) {
+ if (Document* doc = GetDocument()) {
+ if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
+ win->SetKeyboardIndicators(aShowFocusRings);
+ }
+ }
+}
+
+nsresult nsBaseWidget::NotifyIME(const IMENotification& aIMENotification) {
+ if (mIMEHasQuit) {
+ return NS_OK;
+ }
+ switch (aIMENotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ case REQUEST_TO_CANCEL_COMPOSITION:
+ // We should send request to IME only when there is a TextEventDispatcher
+ // instance (this means that this widget has dispatched at least one
+ // composition event or keyboard event) and the it has composition.
+ // Otherwise, there is nothing to do.
+ // Note that if current input transaction is for native input events,
+ // TextEventDispatcher::NotifyIME() will call
+ // TextEventDispatcherListener::NotifyIME().
+ if (mTextEventDispatcher && mTextEventDispatcher->IsComposing()) {
+ return mTextEventDispatcher->NotifyIME(aIMENotification);
+ }
+ return NS_OK;
+ default: {
+ if (aIMENotification.mMessage == NOTIFY_IME_OF_FOCUS) {
+ mIMEHasFocus = true;
+ }
+ EnsureTextEventDispatcher();
+ // TextEventDispatcher::NotifyIME() will always call
+ // TextEventDispatcherListener::NotifyIME(). I.e., even if current
+ // input transaction is for synthesized events for automated tests,
+ // notifications will be sent to native IME.
+ nsresult rv = mTextEventDispatcher->NotifyIME(aIMENotification);
+ if (aIMENotification.mMessage == NOTIFY_IME_OF_BLUR) {
+ mIMEHasFocus = false;
+ }
+ return rv;
+ }
+ }
+}
+
+void nsBaseWidget::EnsureTextEventDispatcher() {
+ if (mTextEventDispatcher) {
+ return;
+ }
+ mTextEventDispatcher = new TextEventDispatcher(this);
+}
+
+nsIWidget::NativeIMEContext nsBaseWidget::GetNativeIMEContext() {
+ if (mTextEventDispatcher && mTextEventDispatcher->GetPseudoIMEContext()) {
+ // If we already have a TextEventDispatcher and it's working with
+ // a TextInputProcessor, we need to return pseudo IME context since
+ // TextCompositionArray::IndexOf(nsIWidget*) should return a composition
+ // on the pseudo IME context in such case.
+ NativeIMEContext pseudoIMEContext;
+ pseudoIMEContext.InitWithRawNativeIMEContext(
+ mTextEventDispatcher->GetPseudoIMEContext());
+ return pseudoIMEContext;
+ }
+ return NativeIMEContext(this);
+}
+
+nsIWidget::TextEventDispatcher* nsBaseWidget::GetTextEventDispatcher() {
+ EnsureTextEventDispatcher();
+ return mTextEventDispatcher;
+}
+
+void* nsBaseWidget::GetPseudoIMEContext() {
+ TextEventDispatcher* dispatcher = GetTextEventDispatcher();
+ if (!dispatcher) {
+ return nullptr;
+ }
+ return dispatcher->GetPseudoIMEContext();
+}
+
+TextEventDispatcherListener*
+nsBaseWidget::GetNativeTextEventDispatcherListener() {
+ // TODO: If all platforms supported use of TextEventDispatcher for handling
+ // native IME and keyboard events, this method should be removed since
+ // in such case, this is overridden by all the subclasses.
+ return nullptr;
+}
+
+void nsBaseWidget::ZoomToRect(const uint32_t& aPresShellId,
+ const ScrollableLayerGuid::ViewID& aViewId,
+ const CSSRect& aRect, const uint32_t& aFlags) {
+ if (!mCompositorSession || !mAPZC) {
+ return;
+ }
+ LayersId layerId = mCompositorSession->RootLayerTreeId();
+ APZThreadUtils::RunOnControllerThread(
+ NewRunnableMethod<ScrollableLayerGuid, CSSRect, uint32_t>(
+ "layers::IAPZCTreeManager::ZoomToRect", mAPZC,
+ &IAPZCTreeManager::ZoomToRect,
+ ScrollableLayerGuid(layerId, aPresShellId, aViewId), aRect, aFlags));
+}
+
+#ifdef ACCESSIBILITY
+
+a11y::Accessible* nsBaseWidget::GetRootAccessible() {
+ NS_ENSURE_TRUE(mWidgetListener, nullptr);
+
+ PresShell* presShell = mWidgetListener->GetPresShell();
+ NS_ENSURE_TRUE(presShell, nullptr);
+
+ // If container is null then the presshell is not active. This often happens
+ // when a preshell is being held onto for fastback.
+ nsPresContext* presContext = presShell->GetPresContext();
+ NS_ENSURE_TRUE(presContext->GetContainerWeak(), nullptr);
+
+ // Accessible creation might be not safe so use IsSafeToRunScript to
+ // make sure it's not created at unsafe times.
+ nsAccessibilityService* accService = GetOrCreateAccService();
+ if (accService) {
+ return accService->GetRootDocumentAccessible(
+ presShell, nsContentUtils::IsSafeToRunScript());
+ }
+
+ return nullptr;
+}
+
+#endif // ACCESSIBILITY
+
+void nsBaseWidget::StartAsyncScrollbarDrag(
+ const AsyncDragMetrics& aDragMetrics) {
+ if (!AsyncPanZoomEnabled()) {
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess() && mCompositorSession);
+
+ LayersId layersId = mCompositorSession->RootLayerTreeId();
+ ScrollableLayerGuid guid(layersId, aDragMetrics.mPresShellId,
+ aDragMetrics.mViewId);
+
+ APZThreadUtils::RunOnControllerThread(
+ NewRunnableMethod<ScrollableLayerGuid, AsyncDragMetrics>(
+ "layers::IAPZCTreeManager::StartScrollbarDrag", mAPZC,
+ &IAPZCTreeManager::StartScrollbarDrag, guid, aDragMetrics));
+}
+
+bool nsBaseWidget::StartAsyncAutoscroll(const ScreenPoint& aAnchorLocation,
+ const ScrollableLayerGuid& aGuid) {
+ MOZ_ASSERT(XRE_IsParentProcess() && AsyncPanZoomEnabled());
+
+ return mAPZC->StartAutoscroll(aGuid, aAnchorLocation);
+}
+
+void nsBaseWidget::StopAsyncAutoscroll(const ScrollableLayerGuid& aGuid) {
+ MOZ_ASSERT(XRE_IsParentProcess() && AsyncPanZoomEnabled());
+
+ mAPZC->StopAutoscroll(aGuid);
+}
+
+already_AddRefed<nsIScreen> nsBaseWidget::GetWidgetScreen() {
+ nsCOMPtr<nsIScreenManager> screenManager;
+ screenManager = do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (!screenManager) {
+ return nullptr;
+ }
+
+ LayoutDeviceIntRect bounds = GetScreenBounds();
+ DesktopIntRect deskBounds = RoundedToInt(bounds / GetDesktopToDeviceScale());
+ nsCOMPtr<nsIScreen> screen;
+ screenManager->ScreenForRect(deskBounds.X(), deskBounds.Y(),
+ deskBounds.Width(), deskBounds.Height(),
+ getter_AddRefs(screen));
+ return screen.forget();
+}
+
+mozilla::DesktopToLayoutDeviceScale
+nsBaseWidget::GetDesktopToDeviceScaleByScreen() {
+ return (nsView::GetViewFor(this)->GetViewManager()->GetDeviceContext())
+ ->GetDesktopToDeviceScale();
+}
+
+nsresult nsIWidget::SynthesizeNativeTouchTap(LayoutDeviceIntPoint aPoint,
+ bool aLongTap,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "touchtap");
+
+ if (sPointerIdCounter > TOUCH_INJECT_MAX_POINTS) {
+ sPointerIdCounter = 0;
+ }
+ int pointerId = sPointerIdCounter;
+ sPointerIdCounter++;
+ nsresult rv = SynthesizeNativeTouchPoint(pointerId, TOUCH_CONTACT, aPoint,
+ 1.0, 90, nullptr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!aLongTap) {
+ return SynthesizeNativeTouchPoint(pointerId, TOUCH_REMOVE, aPoint, 0, 0,
+ nullptr);
+ }
+
+ // initiate a long tap
+ int elapse = Preferences::GetInt("ui.click_hold_context_menus.delay",
+ TOUCH_INJECT_LONG_TAP_DEFAULT_MSEC);
+ if (!mLongTapTimer) {
+ mLongTapTimer = NS_NewTimer();
+ if (!mLongTapTimer) {
+ SynthesizeNativeTouchPoint(pointerId, TOUCH_CANCEL, aPoint, 0, 0,
+ nullptr);
+ return NS_ERROR_UNEXPECTED;
+ }
+ // Windows requires recuring events, so we set this to a smaller window
+ // than the pref value.
+ int timeout = elapse;
+ if (timeout > TOUCH_INJECT_PUMP_TIMER_MSEC) {
+ timeout = TOUCH_INJECT_PUMP_TIMER_MSEC;
+ }
+ mLongTapTimer->InitWithNamedFuncCallback(
+ OnLongTapTimerCallback, this, timeout, nsITimer::TYPE_REPEATING_SLACK,
+ "nsIWidget::SynthesizeNativeTouchTap");
+ }
+
+ // If we already have a long tap pending, cancel it. We only allow one long
+ // tap to be active at a time.
+ if (mLongTapTouchPoint) {
+ SynthesizeNativeTouchPoint(mLongTapTouchPoint->mPointerId, TOUCH_CANCEL,
+ mLongTapTouchPoint->mPosition, 0, 0, nullptr);
+ }
+
+ mLongTapTouchPoint = MakeUnique<LongTapInfo>(
+ pointerId, aPoint, TimeDuration::FromMilliseconds(elapse), aObserver);
+ notifier.SkipNotification(); // we'll do it in the long-tap callback
+ return NS_OK;
+}
+
+// static
+void nsIWidget::OnLongTapTimerCallback(nsITimer* aTimer, void* aClosure) {
+ auto* self = static_cast<nsIWidget*>(aClosure);
+
+ if ((self->mLongTapTouchPoint->mStamp + self->mLongTapTouchPoint->mDuration) >
+ TimeStamp::Now()) {
+#ifdef XP_WIN
+ // Windows needs us to keep pumping feedback to the digitizer, so update
+ // the pointer id with the same position.
+ self->SynthesizeNativeTouchPoint(
+ self->mLongTapTouchPoint->mPointerId, TOUCH_CONTACT,
+ self->mLongTapTouchPoint->mPosition, 1.0, 90, nullptr);
+#endif
+ return;
+ }
+
+ AutoObserverNotifier notifier(self->mLongTapTouchPoint->mObserver,
+ "touchtap");
+
+ // finished, remove the touch point
+ self->mLongTapTimer->Cancel();
+ self->mLongTapTimer = nullptr;
+ self->SynthesizeNativeTouchPoint(
+ self->mLongTapTouchPoint->mPointerId, TOUCH_REMOVE,
+ self->mLongTapTouchPoint->mPosition, 0, 0, nullptr);
+ self->mLongTapTouchPoint = nullptr;
+}
+
+nsresult nsIWidget::ClearNativeTouchSequence(nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "cleartouch");
+
+ if (!mLongTapTimer) {
+ return NS_OK;
+ }
+ mLongTapTimer->Cancel();
+ mLongTapTimer = nullptr;
+ SynthesizeNativeTouchPoint(mLongTapTouchPoint->mPointerId, TOUCH_CANCEL,
+ mLongTapTouchPoint->mPosition, 0, 0, nullptr);
+ mLongTapTouchPoint = nullptr;
+ return NS_OK;
+}
+
+MultiTouchInput nsBaseWidget::UpdateSynthesizedTouchState(
+ MultiTouchInput* aState, uint32_t aTime, mozilla::TimeStamp aTimeStamp,
+ uint32_t aPointerId, TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint, double aPointerPressure,
+ uint32_t aPointerOrientation) {
+ ScreenIntPoint pointerScreenPoint = ViewAs<ScreenPixel>(
+ aPoint, PixelCastJustification::LayoutDeviceIsScreenForBounds);
+
+ // We can't dispatch *aState directly because (a) dispatching
+ // it might inadvertently modify it and (b) in the case of touchend or
+ // touchcancel events aState will hold the touches that are
+ // still down whereas the input dispatched needs to hold the removed
+ // touch(es). We use |inputToDispatch| for this purpose.
+ MultiTouchInput inputToDispatch;
+ inputToDispatch.mInputType = MULTITOUCH_INPUT;
+ inputToDispatch.mTime = aTime;
+ inputToDispatch.mTimeStamp = aTimeStamp;
+
+ int32_t index = aState->IndexOfTouch((int32_t)aPointerId);
+ if (aPointerState == TOUCH_CONTACT) {
+ if (index >= 0) {
+ // found an existing touch point, update it
+ SingleTouchData& point = aState->mTouches[index];
+ point.mScreenPoint = pointerScreenPoint;
+ point.mRotationAngle = (float)aPointerOrientation;
+ point.mForce = (float)aPointerPressure;
+ inputToDispatch.mType = MultiTouchInput::MULTITOUCH_MOVE;
+ } else {
+ // new touch point, add it
+ aState->mTouches.AppendElement(SingleTouchData(
+ (int32_t)aPointerId, pointerScreenPoint, ScreenSize(0, 0),
+ (float)aPointerOrientation, (float)aPointerPressure));
+ inputToDispatch.mType = MultiTouchInput::MULTITOUCH_START;
+ }
+ inputToDispatch.mTouches = aState->mTouches;
+ } else {
+ MOZ_ASSERT(aPointerState == TOUCH_REMOVE || aPointerState == TOUCH_CANCEL);
+ // a touch point is being lifted, so remove it from the stored list
+ if (index >= 0) {
+ aState->mTouches.RemoveElementAt(index);
+ }
+ inputToDispatch.mType =
+ (aPointerState == TOUCH_REMOVE ? MultiTouchInput::MULTITOUCH_END
+ : MultiTouchInput::MULTITOUCH_CANCEL);
+ inputToDispatch.mTouches.AppendElement(SingleTouchData(
+ (int32_t)aPointerId, pointerScreenPoint, ScreenSize(0, 0),
+ (float)aPointerOrientation, (float)aPointerPressure));
+ }
+
+ return inputToDispatch;
+}
+
+void nsBaseWidget::NotifyLiveResizeStarted() {
+ // If we have mLiveResizeListeners already non-empty, we should notify those
+ // listeners that the resize stopped before starting anew. In theory this
+ // should never happen because we shouldn't get nested live resize actions.
+ NotifyLiveResizeStopped();
+ MOZ_ASSERT(mLiveResizeListeners.IsEmpty());
+
+ // If we can get the active remote tab for the current widget, suppress
+ // the displayport on it during the live resize.
+ if (!mWidgetListener) {
+ return;
+ }
+ nsCOMPtr<nsIAppWindow> appWindow = mWidgetListener->GetAppWindow();
+ if (!appWindow) {
+ return;
+ }
+ mLiveResizeListeners = appWindow->GetLiveResizeListeners();
+ for (uint32_t i = 0; i < mLiveResizeListeners.Length(); i++) {
+ mLiveResizeListeners[i]->LiveResizeStarted();
+ }
+}
+
+void nsBaseWidget::NotifyLiveResizeStopped() {
+ if (!mLiveResizeListeners.IsEmpty()) {
+ for (uint32_t i = 0; i < mLiveResizeListeners.Length(); i++) {
+ mLiveResizeListeners[i]->LiveResizeStopped();
+ }
+ mLiveResizeListeners.Clear();
+ }
+}
+
+void nsBaseWidget::RegisterPluginWindowForRemoteUpdates() {
+#if !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK)
+ MOZ_ASSERT_UNREACHABLE(
+ "nsBaseWidget::RegisterPluginWindowForRemoteUpdates "
+ "not implemented!");
+ return;
+#else
+ MOZ_ASSERT(NS_IsMainThread());
+ void* id = GetNativeData(NS_NATIVE_PLUGIN_ID);
+ if (!id) {
+ NS_WARNING("This is not a valid native widget!");
+ return;
+ }
+ MOZ_ASSERT(sPluginWidgetList);
+ sPluginWidgetList->Put(id, RefPtr{this});
+#endif
+}
+
+void nsBaseWidget::UnregisterPluginWindowForRemoteUpdates() {
+#if !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK)
+ MOZ_ASSERT_UNREACHABLE(
+ "nsBaseWidget::UnregisterPluginWindowForRemoteUpdates "
+ "not implemented!");
+ return;
+#else
+ MOZ_ASSERT(NS_IsMainThread());
+ void* id = GetNativeData(NS_NATIVE_PLUGIN_ID);
+ if (!id) {
+ NS_WARNING("This is not a valid native widget!");
+ return;
+ }
+ MOZ_ASSERT(sPluginWidgetList);
+ sPluginWidgetList->Remove(id);
+#endif
+}
+
+nsresult nsBaseWidget::AsyncEnableDragDrop(bool aEnable) {
+ RefPtr<nsBaseWidget> kungFuDeathGrip = this;
+ return NS_DispatchToCurrentThreadQueue(
+ NS_NewRunnableFunction(
+ "AsyncEnableDragDropFn",
+ [this, aEnable, kungFuDeathGrip]() { EnableDragDrop(aEnable); }),
+ kAsyncDragDropTimeout, EventQueuePriority::Idle);
+}
+
+// static
+nsIWidget* nsIWidget::LookupRegisteredPluginWindow(uintptr_t aWindowID) {
+#if !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK)
+ MOZ_ASSERT_UNREACHABLE(
+ "nsBaseWidget::LookupRegisteredPluginWindow "
+ "not implemented!");
+ return nullptr;
+#else
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(sPluginWidgetList);
+ return sPluginWidgetList->GetWeak((void*)aWindowID);
+#endif
+}
+
+// static
+void nsIWidget::UpdateRegisteredPluginWindowVisibility(
+ uintptr_t aOwnerWidget, nsTArray<uintptr_t>& aPluginIds) {
+#if !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK)
+ MOZ_ASSERT_UNREACHABLE(
+ "nsBaseWidget::UpdateRegisteredPluginWindowVisibility"
+ " not implemented!");
+ return;
+#else
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(sPluginWidgetList);
+
+ // Our visible list is associated with a compositor which is associated with
+ // a specific top level window. We use the parent widget during iteration
+ // to skip the plugin widgets owned by other top level windows.
+ for (auto iter = sPluginWidgetList->Iter(); !iter.Done(); iter.Next()) {
+ const void* windowId = iter.Key();
+ nsIWidget* widget = iter.UserData();
+
+ MOZ_ASSERT(windowId);
+ MOZ_ASSERT(widget);
+
+ if (!widget->Destroyed()) {
+ if ((uintptr_t)widget->GetParent() == aOwnerWidget) {
+ widget->Show(aPluginIds.Contains((uintptr_t)windowId));
+ }
+ }
+ }
+#endif
+}
+
+#if defined(XP_WIN)
+// static
+void nsIWidget::CaptureRegisteredPlugins(uintptr_t aOwnerWidget) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(sPluginWidgetList);
+
+ // Our visible list is associated with a compositor which is associated with
+ // a specific top level window. We use the parent widget during iteration
+ // to skip the plugin widgets owned by other top level windows.
+ for (auto iter = sPluginWidgetList->Iter(); !iter.Done(); iter.Next()) {
+ DebugOnly<const void*> windowId = iter.Key();
+ nsIWidget* widget = iter.UserData();
+
+ MOZ_ASSERT(windowId);
+ MOZ_ASSERT(widget);
+
+ if (!widget->Destroyed() && widget->IsVisible()) {
+ if ((uintptr_t)widget->GetParent() == aOwnerWidget) {
+ widget->UpdateScrollCapture();
+ }
+ }
+ }
+}
+
+uint64_t nsBaseWidget::CreateScrollCaptureContainer() {
+ mScrollCaptureContainer =
+ LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS);
+ if (!mScrollCaptureContainer) {
+ NS_WARNING("Failed to create ImageContainer for widget image capture.");
+ return ImageContainer::sInvalidAsyncContainerId;
+ }
+
+ return mScrollCaptureContainer->GetAsyncContainerHandle().Value();
+}
+
+void nsBaseWidget::UpdateScrollCapture() {
+ // Don't capture if no container or no size.
+ if (!mScrollCaptureContainer || mBounds.IsEmpty()) {
+ return;
+ }
+
+ // If the derived class cannot take a snapshot, for example due to clipping,
+ // then it is responsible for creating a fallback. If null is returned, this
+ // means that we want to keep the existing snapshot.
+ RefPtr<gfx::SourceSurface> snapshot = CreateScrollSnapshot();
+ if (!snapshot) {
+ return;
+ }
+
+ ImageContainer::NonOwningImage holder(new SourceSurfaceImage(snapshot));
+
+ AutoTArray<ImageContainer::NonOwningImage, 1> imageList;
+ imageList.AppendElement(holder);
+
+ mScrollCaptureContainer->SetCurrentImages(imageList);
+}
+
+void nsBaseWidget::DefaultFillScrollCapture(DrawTarget* aSnapshotDrawTarget) {
+ gfx::IntSize dtSize = aSnapshotDrawTarget->GetSize();
+ aSnapshotDrawTarget->FillRect(
+ gfx::Rect(0, 0, dtSize.width, dtSize.height),
+ gfx::ColorPattern(gfx::ToDeviceColor(kScrollCaptureFillColor)),
+ gfx::DrawOptions(1.f, gfx::CompositionOp::OP_SOURCE));
+ aSnapshotDrawTarget->Flush();
+}
+#endif
+
+const IMENotificationRequests& nsIWidget::IMENotificationRequestsRef() {
+ TextEventDispatcher* dispatcher = GetTextEventDispatcher();
+ return dispatcher->IMENotificationRequestsRef();
+}
+
+nsresult nsIWidget::OnWindowedPluginKeyEvent(
+ const NativeEventData& aKeyEventData,
+ nsIKeyEventInPluginCallback* aCallback) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void nsIWidget::PostHandleKeyEvent(mozilla::WidgetKeyboardEvent* aEvent) {}
+
+bool nsIWidget::GetEditCommands(nsIWidget::NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands) {
+ MOZ_ASSERT(aEvent.IsTrusted());
+ MOZ_ASSERT(aCommands.IsEmpty());
+ return true;
+}
+
+already_AddRefed<nsIBidiKeyboard> nsIWidget::CreateBidiKeyboard() {
+ if (XRE_IsContentProcess()) {
+ return CreateBidiKeyboardContentProcess();
+ }
+ return CreateBidiKeyboardInner();
+}
+
+#ifdef ANDROID
+already_AddRefed<nsIBidiKeyboard> nsIWidget::CreateBidiKeyboardInner() {
+ // no bidi keyboard implementation
+ return nullptr;
+}
+#endif
+
+namespace mozilla::widget {
+
+const char* ToChar(InputContext::Origin aOrigin) {
+ switch (aOrigin) {
+ case InputContext::ORIGIN_MAIN:
+ return "ORIGIN_MAIN";
+ case InputContext::ORIGIN_CONTENT:
+ return "ORIGIN_CONTENT";
+ default:
+ return "Unexpected value";
+ }
+}
+
+const char* ToChar(IMEMessage aIMEMessage) {
+ switch (aIMEMessage) {
+ case NOTIFY_IME_OF_NOTHING:
+ return "NOTIFY_IME_OF_NOTHING";
+ case NOTIFY_IME_OF_FOCUS:
+ return "NOTIFY_IME_OF_FOCUS";
+ case NOTIFY_IME_OF_BLUR:
+ return "NOTIFY_IME_OF_BLUR";
+ case NOTIFY_IME_OF_SELECTION_CHANGE:
+ return "NOTIFY_IME_OF_SELECTION_CHANGE";
+ case NOTIFY_IME_OF_TEXT_CHANGE:
+ return "NOTIFY_IME_OF_TEXT_CHANGE";
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
+ return "NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED";
+ case NOTIFY_IME_OF_POSITION_CHANGE:
+ return "NOTIFY_IME_OF_POSITION_CHANGE";
+ case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
+ return "NOTIFY_IME_OF_MOUSE_BUTTON_EVENT";
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ return "REQUEST_TO_COMMIT_COMPOSITION";
+ case REQUEST_TO_CANCEL_COMPOSITION:
+ return "REQUEST_TO_CANCEL_COMPOSITION";
+ default:
+ return "Unexpected value";
+ }
+}
+
+void NativeIMEContext::Init(nsIWidget* aWidget) {
+ if (!aWidget) {
+ mRawNativeIMEContext = reinterpret_cast<uintptr_t>(nullptr);
+ mOriginProcessID = static_cast<uint64_t>(-1);
+ return;
+ }
+ if (!XRE_IsContentProcess()) {
+ mRawNativeIMEContext = reinterpret_cast<uintptr_t>(
+ aWidget->GetNativeData(NS_RAW_NATIVE_IME_CONTEXT));
+ mOriginProcessID = 0;
+ return;
+ }
+ // If this is created in a child process, aWidget is an instance of
+ // PuppetWidget which doesn't support NS_RAW_NATIVE_IME_CONTEXT.
+ // Instead of that PuppetWidget::GetNativeIMEContext() returns cached
+ // native IME context of the parent process.
+ *this = aWidget->GetNativeIMEContext();
+}
+
+void NativeIMEContext::InitWithRawNativeIMEContext(void* aRawNativeIMEContext) {
+ if (NS_WARN_IF(!aRawNativeIMEContext)) {
+ mRawNativeIMEContext = reinterpret_cast<uintptr_t>(nullptr);
+ mOriginProcessID = static_cast<uint64_t>(-1);
+ return;
+ }
+ mRawNativeIMEContext = reinterpret_cast<uintptr_t>(aRawNativeIMEContext);
+ mOriginProcessID =
+ XRE_IsContentProcess() ? ContentChild::GetSingleton()->GetID() : 0;
+}
+
+void IMENotification::TextChangeDataBase::MergeWith(
+ const IMENotification::TextChangeDataBase& aOther) {
+ MOZ_ASSERT(aOther.IsValid(), "Merging data must store valid data");
+ MOZ_ASSERT(aOther.mStartOffset <= aOther.mRemovedEndOffset,
+ "end of removed text must be same or larger than start");
+ MOZ_ASSERT(aOther.mStartOffset <= aOther.mAddedEndOffset,
+ "end of added text must be same or larger than start");
+
+ if (!IsValid()) {
+ *this = aOther;
+ return;
+ }
+
+ // |mStartOffset| and |mRemovedEndOffset| represent all replaced or removed
+ // text ranges. I.e., mStartOffset should be the smallest offset of all
+ // modified text ranges in old text. |mRemovedEndOffset| should be the
+ // largest end offset in old text of all modified text ranges.
+ // |mAddedEndOffset| represents the end offset of all inserted text ranges.
+ // I.e., only this is an offset in new text.
+ // In other words, between mStartOffset and |mRemovedEndOffset| of the
+ // premodified text was already removed. And some text whose length is
+ // |mAddedEndOffset - mStartOffset| is inserted to |mStartOffset|. I.e.,
+ // this allows IME to mark dirty the modified text range with |mStartOffset|
+ // and |mRemovedEndOffset| if IME stores all text of the focused editor and
+ // to compute new text length with |mAddedEndOffset| and |mRemovedEndOffset|.
+ // Additionally, IME can retrieve only the text between |mStartOffset| and
+ // |mAddedEndOffset| for updating stored text.
+
+ // For comparing new and old |mStartOffset|/|mRemovedEndOffset| values, they
+ // should be adjusted to be in same text. The |newData.mStartOffset| and
+ // |newData.mRemovedEndOffset| should be computed as in old text because
+ // |mStartOffset| and |mRemovedEndOffset| represent the modified text range
+ // in the old text but even if some text before the values of the newData
+ // has already been modified, the values don't include the changes.
+
+ // For comparing new and old |mAddedEndOffset| values, they should be
+ // adjusted to be in same text. The |oldData.mAddedEndOffset| should be
+ // computed as in the new text because |mAddedEndOffset| indicates the end
+ // offset of inserted text in the new text but |oldData.mAddedEndOffset|
+ // doesn't include any changes of the text before |newData.mAddedEndOffset|.
+
+ const TextChangeDataBase& newData = aOther;
+ const TextChangeDataBase oldData = *this;
+
+ // mCausedOnlyByComposition should be true only when all changes are caused
+ // by composition.
+ mCausedOnlyByComposition =
+ newData.mCausedOnlyByComposition && oldData.mCausedOnlyByComposition;
+
+ // mIncludingChangesWithoutComposition should be true if at least one of
+ // merged changes occurred without composition.
+ mIncludingChangesWithoutComposition =
+ newData.mIncludingChangesWithoutComposition ||
+ oldData.mIncludingChangesWithoutComposition;
+
+ // mIncludingChangesDuringComposition should be true when at least one of
+ // the merged non-composition changes occurred during the latest composition.
+ if (!newData.mCausedOnlyByComposition &&
+ !newData.mIncludingChangesDuringComposition) {
+ MOZ_ASSERT(newData.mIncludingChangesWithoutComposition);
+ MOZ_ASSERT(mIncludingChangesWithoutComposition);
+ // If new change is neither caused by composition nor occurred during
+ // composition, set mIncludingChangesDuringComposition to false because
+ // IME doesn't want outdated text changes as text change during current
+ // composition.
+ mIncludingChangesDuringComposition = false;
+ } else {
+ // Otherwise, set mIncludingChangesDuringComposition to true if either
+ // oldData or newData includes changes during composition.
+ mIncludingChangesDuringComposition =
+ newData.mIncludingChangesDuringComposition ||
+ oldData.mIncludingChangesDuringComposition;
+ }
+
+ if (newData.mStartOffset >= oldData.mAddedEndOffset) {
+ // Case 1:
+ // If new start is after old end offset of added text, it means that text
+ // after the modified range is modified. Like:
+ // added range of old change: +----------+
+ // removed range of new change: +----------+
+ // So, the old start offset is always the smaller offset.
+ mStartOffset = oldData.mStartOffset;
+ // The new end offset of removed text is moved by the old change and we
+ // need to cancel the move of the old change for comparing the offsets in
+ // same text because it doesn't make sensce to compare offsets in different
+ // text.
+ uint32_t newRemovedEndOffsetInOldText =
+ newData.mRemovedEndOffset - oldData.Difference();
+ mRemovedEndOffset =
+ std::max(newRemovedEndOffsetInOldText, oldData.mRemovedEndOffset);
+ // The new end offset of added text is always the larger offset.
+ mAddedEndOffset = newData.mAddedEndOffset;
+ return;
+ }
+
+ if (newData.mStartOffset >= oldData.mStartOffset) {
+ // If new start is in the modified range, it means that new data changes
+ // a part or all of the range.
+ mStartOffset = oldData.mStartOffset;
+ if (newData.mRemovedEndOffset >= oldData.mAddedEndOffset) {
+ // Case 2:
+ // If new end of removed text is greater than old end of added text, it
+ // means that all or a part of modified range modified again and text
+ // after the modified range is also modified. Like:
+ // added range of old change: +----------+
+ // removed range of new change: +----------+
+ // So, the new removed end offset is moved by the old change and we need
+ // to cancel the move of the old change for comparing the offsets in the
+ // same text because it doesn't make sense to compare the offsets in
+ // different text.
+ uint32_t newRemovedEndOffsetInOldText =
+ newData.mRemovedEndOffset - oldData.Difference();
+ mRemovedEndOffset =
+ std::max(newRemovedEndOffsetInOldText, oldData.mRemovedEndOffset);
+ // The old end of added text is replaced by new change. So, it should be
+ // same as the new start. On the other hand, the new added end offset is
+ // always same or larger. Therefore, the merged end offset of added
+ // text should be the new end offset of added text.
+ mAddedEndOffset = newData.mAddedEndOffset;
+ return;
+ }
+
+ // Case 3:
+ // If new end of removed text is less than old end of added text, it means
+ // that only a part of the modified range is modified again. Like:
+ // added range of old change: +------------+
+ // removed range of new change: +-----+
+ // So, the new end offset of removed text should be same as the old end
+ // offset of removed text. Therefore, the merged end offset of removed
+ // text should be the old text change's |mRemovedEndOffset|.
+ mRemovedEndOffset = oldData.mRemovedEndOffset;
+ // The old end of added text is moved by new change. So, we need to cancel
+ // the move of the new change for comparing the offsets in same text.
+ uint32_t oldAddedEndOffsetInNewText =
+ oldData.mAddedEndOffset + newData.Difference();
+ mAddedEndOffset =
+ std::max(newData.mAddedEndOffset, oldAddedEndOffsetInNewText);
+ return;
+ }
+
+ if (newData.mRemovedEndOffset >= oldData.mStartOffset) {
+ // If new end of removed text is greater than old start (and new start is
+ // less than old start), it means that a part of modified range is modified
+ // again and some new text before the modified range is also modified.
+ MOZ_ASSERT(newData.mStartOffset < oldData.mStartOffset,
+ "new start offset should be less than old one here");
+ mStartOffset = newData.mStartOffset;
+ if (newData.mRemovedEndOffset >= oldData.mAddedEndOffset) {
+ // Case 4:
+ // If new end of removed text is greater than old end of added text, it
+ // means that all modified text and text after the modified range is
+ // modified. Like:
+ // added range of old change: +----------+
+ // removed range of new change: +------------------+
+ // So, the new end of removed text is moved by the old change. Therefore,
+ // we need to cancel the move of the old change for comparing the offsets
+ // in same text because it doesn't make sense to compare the offsets in
+ // different text.
+ uint32_t newRemovedEndOffsetInOldText =
+ newData.mRemovedEndOffset - oldData.Difference();
+ mRemovedEndOffset =
+ std::max(newRemovedEndOffsetInOldText, oldData.mRemovedEndOffset);
+ // The old end of added text is replaced by new change. So, the old end
+ // offset of added text is same as new text change's start offset. Then,
+ // new change's end offset of added text is always same or larger than
+ // it. Therefore, merged end offset of added text is always the new end
+ // offset of added text.
+ mAddedEndOffset = newData.mAddedEndOffset;
+ return;
+ }
+
+ // Case 5:
+ // If new end of removed text is less than old end of added text, it
+ // means that only a part of the modified range is modified again. Like:
+ // added range of old change: +----------+
+ // removed range of new change: +----------+
+ // So, the new end of removed text should be same as old end of removed
+ // text for preventing end of removed text to be modified. Therefore,
+ // merged end offset of removed text is always the old end offset of removed
+ // text.
+ mRemovedEndOffset = oldData.mRemovedEndOffset;
+ // The old end of added text is moved by this change. So, we need to
+ // cancel the move of the new change for comparing the offsets in same text
+ // because it doesn't make sense to compare the offsets in different text.
+ uint32_t oldAddedEndOffsetInNewText =
+ oldData.mAddedEndOffset + newData.Difference();
+ mAddedEndOffset =
+ std::max(newData.mAddedEndOffset, oldAddedEndOffsetInNewText);
+ return;
+ }
+
+ // Case 6:
+ // Otherwise, i.e., both new end of added text and new start are less than
+ // old start, text before the modified range is modified. Like:
+ // added range of old change: +----------+
+ // removed range of new change: +----------+
+ MOZ_ASSERT(newData.mStartOffset < oldData.mStartOffset,
+ "new start offset should be less than old one here");
+ mStartOffset = newData.mStartOffset;
+ MOZ_ASSERT(newData.mRemovedEndOffset < oldData.mRemovedEndOffset,
+ "new removed end offset should be less than old one here");
+ mRemovedEndOffset = oldData.mRemovedEndOffset;
+ // The end of added text should be adjusted with the new difference.
+ uint32_t oldAddedEndOffsetInNewText =
+ oldData.mAddedEndOffset + newData.Difference();
+ mAddedEndOffset =
+ std::max(newData.mAddedEndOffset, oldAddedEndOffsetInNewText);
+}
+
+#ifdef DEBUG
+
+// Let's test the code of merging multiple text change data in debug build
+// and crash if one of them fails because this feature is very complex but
+// cannot be tested with mochitest.
+void IMENotification::TextChangeDataBase::Test() {
+ static bool gTestTextChangeEvent = true;
+ if (!gTestTextChangeEvent) {
+ return;
+ }
+ gTestTextChangeEvent = false;
+
+ /****************************************************************************
+ * Case 1
+ ****************************************************************************/
+
+ // Appending text
+ MergeWith(TextChangeData(10, 10, 20, false, false));
+ MergeWith(TextChangeData(20, 20, 35, false, false));
+ MOZ_ASSERT(mStartOffset == 10,
+ "Test 1-1-1: mStartOffset should be the first offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 10, // 20 - (20 - 10)
+ "Test 1-1-2: mRemovedEndOffset should be the first end of removed text");
+ MOZ_ASSERT(
+ mAddedEndOffset == 35,
+ "Test 1-1-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ // Removing text (longer line -> shorter line)
+ MergeWith(TextChangeData(10, 20, 10, false, false));
+ MergeWith(TextChangeData(10, 30, 10, false, false));
+ MOZ_ASSERT(mStartOffset == 10,
+ "Test 1-2-1: mStartOffset should be the first offset");
+ MOZ_ASSERT(mRemovedEndOffset == 40, // 30 + (10 - 20)
+ "Test 1-2-2: mRemovedEndOffset should be the the last end of "
+ "removed text "
+ "with already removed length");
+ MOZ_ASSERT(
+ mAddedEndOffset == 10,
+ "Test 1-2-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ // Removing text (shorter line -> longer line)
+ MergeWith(TextChangeData(10, 20, 10, false, false));
+ MergeWith(TextChangeData(10, 15, 10, false, false));
+ MOZ_ASSERT(mStartOffset == 10,
+ "Test 1-3-1: mStartOffset should be the first offset");
+ MOZ_ASSERT(mRemovedEndOffset == 25, // 15 + (10 - 20)
+ "Test 1-3-2: mRemovedEndOffset should be the the last end of "
+ "removed text "
+ "with already removed length");
+ MOZ_ASSERT(
+ mAddedEndOffset == 10,
+ "Test 1-3-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ // Appending text at different point (not sure if actually occurs)
+ MergeWith(TextChangeData(10, 10, 20, false, false));
+ MergeWith(TextChangeData(55, 55, 60, false, false));
+ MOZ_ASSERT(mStartOffset == 10,
+ "Test 1-4-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 45, // 55 - (10 - 20)
+ "Test 1-4-2: mRemovedEndOffset should be the the largest end of removed "
+ "text without already added length");
+ MOZ_ASSERT(
+ mAddedEndOffset == 60,
+ "Test 1-4-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ // Removing text at different point (not sure if actually occurs)
+ MergeWith(TextChangeData(10, 20, 10, false, false));
+ MergeWith(TextChangeData(55, 68, 55, false, false));
+ MOZ_ASSERT(mStartOffset == 10,
+ "Test 1-5-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 78, // 68 - (10 - 20)
+ "Test 1-5-2: mRemovedEndOffset should be the the largest end of removed "
+ "text with already removed length");
+ MOZ_ASSERT(
+ mAddedEndOffset == 55,
+ "Test 1-5-3: mAddedEndOffset should be the largest end of added text");
+ Clear();
+
+ // Replacing text and append text (becomes longer)
+ MergeWith(TextChangeData(30, 35, 32, false, false));
+ MergeWith(TextChangeData(32, 32, 40, false, false));
+ MOZ_ASSERT(mStartOffset == 30,
+ "Test 1-6-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 35, // 32 - (32 - 35)
+ "Test 1-6-2: mRemovedEndOffset should be the the first end of removed "
+ "text");
+ MOZ_ASSERT(
+ mAddedEndOffset == 40,
+ "Test 1-6-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ // Replacing text and append text (becomes shorter)
+ MergeWith(TextChangeData(30, 35, 32, false, false));
+ MergeWith(TextChangeData(32, 32, 33, false, false));
+ MOZ_ASSERT(mStartOffset == 30,
+ "Test 1-7-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 35, // 32 - (32 - 35)
+ "Test 1-7-2: mRemovedEndOffset should be the the first end of removed "
+ "text");
+ MOZ_ASSERT(
+ mAddedEndOffset == 33,
+ "Test 1-7-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ // Removing text and replacing text after first range (not sure if actually
+ // occurs)
+ MergeWith(TextChangeData(30, 35, 30, false, false));
+ MergeWith(TextChangeData(32, 34, 48, false, false));
+ MOZ_ASSERT(mStartOffset == 30,
+ "Test 1-8-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 39, // 34 - (30 - 35)
+ "Test 1-8-2: mRemovedEndOffset should be the the first end of "
+ "removed text "
+ "without already removed text");
+ MOZ_ASSERT(
+ mAddedEndOffset == 48,
+ "Test 1-8-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ // Removing text and replacing text after first range (not sure if actually
+ // occurs)
+ MergeWith(TextChangeData(30, 35, 30, false, false));
+ MergeWith(TextChangeData(32, 38, 36, false, false));
+ MOZ_ASSERT(mStartOffset == 30,
+ "Test 1-9-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 43, // 38 - (30 - 35)
+ "Test 1-9-2: mRemovedEndOffset should be the the first end of "
+ "removed text "
+ "without already removed text");
+ MOZ_ASSERT(
+ mAddedEndOffset == 36,
+ "Test 1-9-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ /****************************************************************************
+ * Case 2
+ ****************************************************************************/
+
+ // Replacing text in around end of added text (becomes shorter) (not sure
+ // if actually occurs)
+ MergeWith(TextChangeData(50, 50, 55, false, false));
+ MergeWith(TextChangeData(53, 60, 54, false, false));
+ MOZ_ASSERT(mStartOffset == 50,
+ "Test 2-1-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 55, // 60 - (55 - 50)
+ "Test 2-1-2: mRemovedEndOffset should be the the last end of "
+ "removed text "
+ "without already added text length");
+ MOZ_ASSERT(
+ mAddedEndOffset == 54,
+ "Test 2-1-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ // Replacing text around end of added text (becomes longer) (not sure
+ // if actually occurs)
+ MergeWith(TextChangeData(50, 50, 55, false, false));
+ MergeWith(TextChangeData(54, 62, 68, false, false));
+ MOZ_ASSERT(mStartOffset == 50,
+ "Test 2-2-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 57, // 62 - (55 - 50)
+ "Test 2-2-2: mRemovedEndOffset should be the the last end of "
+ "removed text "
+ "without already added text length");
+ MOZ_ASSERT(
+ mAddedEndOffset == 68,
+ "Test 2-2-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ // Replacing text around end of replaced text (became shorter) (not sure if
+ // actually occurs)
+ MergeWith(TextChangeData(36, 48, 45, false, false));
+ MergeWith(TextChangeData(43, 50, 49, false, false));
+ MOZ_ASSERT(mStartOffset == 36,
+ "Test 2-3-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 53, // 50 - (45 - 48)
+ "Test 2-3-2: mRemovedEndOffset should be the the last end of "
+ "removed text "
+ "without already removed text length");
+ MOZ_ASSERT(
+ mAddedEndOffset == 49,
+ "Test 2-3-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ // Replacing text around end of replaced text (became longer) (not sure if
+ // actually occurs)
+ MergeWith(TextChangeData(36, 52, 53, false, false));
+ MergeWith(TextChangeData(43, 68, 61, false, false));
+ MOZ_ASSERT(mStartOffset == 36,
+ "Test 2-4-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 67, // 68 - (53 - 52)
+ "Test 2-4-2: mRemovedEndOffset should be the the last end of "
+ "removed text "
+ "without already added text length");
+ MOZ_ASSERT(
+ mAddedEndOffset == 61,
+ "Test 2-4-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ /****************************************************************************
+ * Case 3
+ ****************************************************************************/
+
+ // Appending text in already added text (not sure if actually occurs)
+ MergeWith(TextChangeData(10, 10, 20, false, false));
+ MergeWith(TextChangeData(15, 15, 30, false, false));
+ MOZ_ASSERT(mStartOffset == 10,
+ "Test 3-1-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 10,
+ "Test 3-1-2: mRemovedEndOffset should be the the first end of "
+ "removed text");
+ MOZ_ASSERT(
+ mAddedEndOffset == 35, // 20 + (30 - 15)
+ "Test 3-1-3: mAddedEndOffset should be the first end of added text with "
+ "added text length by the new change");
+ Clear();
+
+ // Replacing text in added text (not sure if actually occurs)
+ MergeWith(TextChangeData(50, 50, 55, false, false));
+ MergeWith(TextChangeData(52, 53, 56, false, false));
+ MOZ_ASSERT(mStartOffset == 50,
+ "Test 3-2-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 50,
+ "Test 3-2-2: mRemovedEndOffset should be the the first end of "
+ "removed text");
+ MOZ_ASSERT(
+ mAddedEndOffset == 58, // 55 + (56 - 53)
+ "Test 3-2-3: mAddedEndOffset should be the first end of added text with "
+ "added text length by the new change");
+ Clear();
+
+ // Replacing text in replaced text (became shorter) (not sure if actually
+ // occurs)
+ MergeWith(TextChangeData(36, 48, 45, false, false));
+ MergeWith(TextChangeData(37, 38, 50, false, false));
+ MOZ_ASSERT(mStartOffset == 36,
+ "Test 3-3-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 48,
+ "Test 3-3-2: mRemovedEndOffset should be the the first end of "
+ "removed text");
+ MOZ_ASSERT(
+ mAddedEndOffset == 57, // 45 + (50 - 38)
+ "Test 3-3-3: mAddedEndOffset should be the first end of added text with "
+ "added text length by the new change");
+ Clear();
+
+ // Replacing text in replaced text (became longer) (not sure if actually
+ // occurs)
+ MergeWith(TextChangeData(32, 48, 53, false, false));
+ MergeWith(TextChangeData(43, 50, 52, false, false));
+ MOZ_ASSERT(mStartOffset == 32,
+ "Test 3-4-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 48,
+ "Test 3-4-2: mRemovedEndOffset should be the the last end of "
+ "removed text "
+ "without already added text length");
+ MOZ_ASSERT(
+ mAddedEndOffset == 55, // 53 + (52 - 50)
+ "Test 3-4-3: mAddedEndOffset should be the first end of added text with "
+ "added text length by the new change");
+ Clear();
+
+ // Replacing text in replaced text (became shorter) (not sure if actually
+ // occurs)
+ MergeWith(TextChangeData(36, 48, 50, false, false));
+ MergeWith(TextChangeData(37, 49, 47, false, false));
+ MOZ_ASSERT(mStartOffset == 36,
+ "Test 3-5-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 48,
+ "Test 3-5-2: mRemovedEndOffset should be the the first end of removed "
+ "text");
+ MOZ_ASSERT(mAddedEndOffset == 48, // 50 + (47 - 49)
+ "Test 3-5-3: mAddedEndOffset should be the first end of added "
+ "text without "
+ "removed text length by the new change");
+ Clear();
+
+ // Replacing text in replaced text (became longer) (not sure if actually
+ // occurs)
+ MergeWith(TextChangeData(32, 48, 53, false, false));
+ MergeWith(TextChangeData(43, 50, 47, false, false));
+ MOZ_ASSERT(mStartOffset == 32,
+ "Test 3-6-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 48,
+ "Test 3-6-2: mRemovedEndOffset should be the the last end of "
+ "removed text "
+ "without already added text length");
+ MOZ_ASSERT(mAddedEndOffset == 50, // 53 + (47 - 50)
+ "Test 3-6-3: mAddedEndOffset should be the first end of added "
+ "text without "
+ "removed text length by the new change");
+ Clear();
+
+ /****************************************************************************
+ * Case 4
+ ****************************************************************************/
+
+ // Replacing text all of already append text (not sure if actually occurs)
+ MergeWith(TextChangeData(50, 50, 55, false, false));
+ MergeWith(TextChangeData(44, 66, 68, false, false));
+ MOZ_ASSERT(mStartOffset == 44,
+ "Test 4-1-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 61, // 66 - (55 - 50)
+ "Test 4-1-2: mRemovedEndOffset should be the the last end of "
+ "removed text "
+ "without already added text length");
+ MOZ_ASSERT(
+ mAddedEndOffset == 68,
+ "Test 4-1-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ // Replacing text around a point in which text was removed (not sure if
+ // actually occurs)
+ MergeWith(TextChangeData(50, 62, 50, false, false));
+ MergeWith(TextChangeData(44, 66, 68, false, false));
+ MOZ_ASSERT(mStartOffset == 44,
+ "Test 4-2-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 78, // 66 - (50 - 62)
+ "Test 4-2-2: mRemovedEndOffset should be the the last end of "
+ "removed text "
+ "without already removed text length");
+ MOZ_ASSERT(
+ mAddedEndOffset == 68,
+ "Test 4-2-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ // Replacing text all replaced text (became shorter) (not sure if actually
+ // occurs)
+ MergeWith(TextChangeData(50, 62, 60, false, false));
+ MergeWith(TextChangeData(49, 128, 130, false, false));
+ MOZ_ASSERT(mStartOffset == 49,
+ "Test 4-3-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 130, // 128 - (60 - 62)
+ "Test 4-3-2: mRemovedEndOffset should be the the last end of "
+ "removed text "
+ "without already removed text length");
+ MOZ_ASSERT(
+ mAddedEndOffset == 130,
+ "Test 4-3-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ // Replacing text all replaced text (became longer) (not sure if actually
+ // occurs)
+ MergeWith(TextChangeData(50, 61, 73, false, false));
+ MergeWith(TextChangeData(44, 100, 50, false, false));
+ MOZ_ASSERT(mStartOffset == 44,
+ "Test 4-4-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(mRemovedEndOffset == 88, // 100 - (73 - 61)
+ "Test 4-4-2: mRemovedEndOffset should be the the last end of "
+ "removed text "
+ "with already added text length");
+ MOZ_ASSERT(
+ mAddedEndOffset == 50,
+ "Test 4-4-3: mAddedEndOffset should be the last end of added text");
+ Clear();
+
+ /****************************************************************************
+ * Case 5
+ ****************************************************************************/
+
+ // Replacing text around start of added text (not sure if actually occurs)
+ MergeWith(TextChangeData(50, 50, 55, false, false));
+ MergeWith(TextChangeData(48, 52, 49, false, false));
+ MOZ_ASSERT(mStartOffset == 48,
+ "Test 5-1-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 50,
+ "Test 5-1-2: mRemovedEndOffset should be the the first end of removed "
+ "text");
+ MOZ_ASSERT(
+ mAddedEndOffset == 52, // 55 + (52 - 49)
+ "Test 5-1-3: mAddedEndOffset should be the first end of added text with "
+ "added text length by the new change");
+ Clear();
+
+ // Replacing text around start of replaced text (became shorter) (not sure if
+ // actually occurs)
+ MergeWith(TextChangeData(50, 60, 58, false, false));
+ MergeWith(TextChangeData(43, 50, 48, false, false));
+ MOZ_ASSERT(mStartOffset == 43,
+ "Test 5-2-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 60,
+ "Test 5-2-2: mRemovedEndOffset should be the the first end of removed "
+ "text");
+ MOZ_ASSERT(mAddedEndOffset == 56, // 58 + (48 - 50)
+ "Test 5-2-3: mAddedEndOffset should be the first end of added "
+ "text without "
+ "removed text length by the new change");
+ Clear();
+
+ // Replacing text around start of replaced text (became longer) (not sure if
+ // actually occurs)
+ MergeWith(TextChangeData(50, 60, 68, false, false));
+ MergeWith(TextChangeData(43, 55, 53, false, false));
+ MOZ_ASSERT(mStartOffset == 43,
+ "Test 5-3-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 60,
+ "Test 5-3-2: mRemovedEndOffset should be the the first end of removed "
+ "text");
+ MOZ_ASSERT(mAddedEndOffset == 66, // 68 + (53 - 55)
+ "Test 5-3-3: mAddedEndOffset should be the first end of added "
+ "text without "
+ "removed text length by the new change");
+ Clear();
+
+ // Replacing text around start of replaced text (became shorter) (not sure if
+ // actually occurs)
+ MergeWith(TextChangeData(50, 60, 58, false, false));
+ MergeWith(TextChangeData(43, 50, 128, false, false));
+ MOZ_ASSERT(mStartOffset == 43,
+ "Test 5-4-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 60,
+ "Test 5-4-2: mRemovedEndOffset should be the the first end of removed "
+ "text");
+ MOZ_ASSERT(
+ mAddedEndOffset == 136, // 58 + (128 - 50)
+ "Test 5-4-3: mAddedEndOffset should be the first end of added text with "
+ "added text length by the new change");
+ Clear();
+
+ // Replacing text around start of replaced text (became longer) (not sure if
+ // actually occurs)
+ MergeWith(TextChangeData(50, 60, 68, false, false));
+ MergeWith(TextChangeData(43, 55, 65, false, false));
+ MOZ_ASSERT(mStartOffset == 43,
+ "Test 5-5-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 60,
+ "Test 5-5-2: mRemovedEndOffset should be the the first end of removed "
+ "text");
+ MOZ_ASSERT(
+ mAddedEndOffset == 78, // 68 + (65 - 55)
+ "Test 5-5-3: mAddedEndOffset should be the first end of added text with "
+ "added text length by the new change");
+ Clear();
+
+ /****************************************************************************
+ * Case 6
+ ****************************************************************************/
+
+ // Appending text before already added text (not sure if actually occurs)
+ MergeWith(TextChangeData(30, 30, 45, false, false));
+ MergeWith(TextChangeData(10, 10, 20, false, false));
+ MOZ_ASSERT(mStartOffset == 10,
+ "Test 6-1-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 30,
+ "Test 6-1-2: mRemovedEndOffset should be the the largest end of removed "
+ "text");
+ MOZ_ASSERT(
+ mAddedEndOffset == 55, // 45 + (20 - 10)
+ "Test 6-1-3: mAddedEndOffset should be the first end of added text with "
+ "added text length by the new change");
+ Clear();
+
+ // Removing text before already removed text (not sure if actually occurs)
+ MergeWith(TextChangeData(30, 35, 30, false, false));
+ MergeWith(TextChangeData(10, 25, 10, false, false));
+ MOZ_ASSERT(mStartOffset == 10,
+ "Test 6-2-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 35,
+ "Test 6-2-2: mRemovedEndOffset should be the the largest end of removed "
+ "text");
+ MOZ_ASSERT(
+ mAddedEndOffset == 15, // 30 - (25 - 10)
+ "Test 6-2-3: mAddedEndOffset should be the first end of added text with "
+ "removed text length by the new change");
+ Clear();
+
+ // Replacing text before already replaced text (not sure if actually occurs)
+ MergeWith(TextChangeData(50, 65, 70, false, false));
+ MergeWith(TextChangeData(13, 24, 15, false, false));
+ MOZ_ASSERT(mStartOffset == 13,
+ "Test 6-3-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 65,
+ "Test 6-3-2: mRemovedEndOffset should be the the largest end of removed "
+ "text");
+ MOZ_ASSERT(mAddedEndOffset == 61, // 70 + (15 - 24)
+ "Test 6-3-3: mAddedEndOffset should be the first end of added "
+ "text without "
+ "removed text length by the new change");
+ Clear();
+
+ // Replacing text before already replaced text (not sure if actually occurs)
+ MergeWith(TextChangeData(50, 65, 70, false, false));
+ MergeWith(TextChangeData(13, 24, 36, false, false));
+ MOZ_ASSERT(mStartOffset == 13,
+ "Test 6-4-1: mStartOffset should be the smallest offset");
+ MOZ_ASSERT(
+ mRemovedEndOffset == 65,
+ "Test 6-4-2: mRemovedEndOffset should be the the largest end of removed "
+ "text");
+ MOZ_ASSERT(mAddedEndOffset == 82, // 70 + (36 - 24)
+ "Test 6-4-3: mAddedEndOffset should be the first end of added "
+ "text without "
+ "removed text length by the new change");
+ Clear();
+}
+
+#endif // #ifdef DEBUG
+
+} // namespace mozilla::widget
+
+#ifdef DEBUG
+//////////////////////////////////////////////////////////////
+//
+// Convert a GUI event message code to a string.
+// Makes it a lot easier to debug events.
+//
+// See gtk/nsWidget.cpp and windows/nsWindow.cpp
+// for a DebugPrintEvent() function that uses
+// this.
+//
+//////////////////////////////////////////////////////////////
+/* static */
+nsAutoString nsBaseWidget::debug_GuiEventToString(WidgetGUIEvent* aGuiEvent) {
+ NS_ASSERTION(nullptr != aGuiEvent, "cmon, null gui event.");
+
+ nsAutoString eventName(u"UNKNOWN"_ns);
+
+# define _ASSIGN_eventName(_value, _name) \
+ case _value: \
+ eventName.AssignLiteral(_name); \
+ break
+
+ switch (aGuiEvent->mMessage) {
+ _ASSIGN_eventName(eBlur, "eBlur");
+ _ASSIGN_eventName(eDrop, "eDrop");
+ _ASSIGN_eventName(eDragEnter, "eDragEnter");
+ _ASSIGN_eventName(eDragExit, "eDragExit");
+ _ASSIGN_eventName(eDragOver, "eDragOver");
+ _ASSIGN_eventName(eEditorInput, "eEditorInput");
+ _ASSIGN_eventName(eFocus, "eFocus");
+ _ASSIGN_eventName(eFocusIn, "eFocusIn");
+ _ASSIGN_eventName(eFocusOut, "eFocusOut");
+ _ASSIGN_eventName(eFormSelect, "eFormSelect");
+ _ASSIGN_eventName(eFormChange, "eFormChange");
+ _ASSIGN_eventName(eFormReset, "eFormReset");
+ _ASSIGN_eventName(eFormSubmit, "eFormSubmit");
+ _ASSIGN_eventName(eImageAbort, "eImageAbort");
+ _ASSIGN_eventName(eLoadError, "eLoadError");
+ _ASSIGN_eventName(eKeyDown, "eKeyDown");
+ _ASSIGN_eventName(eKeyPress, "eKeyPress");
+ _ASSIGN_eventName(eKeyUp, "eKeyUp");
+ _ASSIGN_eventName(eMouseEnterIntoWidget, "eMouseEnterIntoWidget");
+ _ASSIGN_eventName(eMouseExitFromWidget, "eMouseExitFromWidget");
+ _ASSIGN_eventName(eMouseDown, "eMouseDown");
+ _ASSIGN_eventName(eMouseUp, "eMouseUp");
+ _ASSIGN_eventName(eMouseClick, "eMouseClick");
+ _ASSIGN_eventName(eMouseAuxClick, "eMouseAuxClick");
+ _ASSIGN_eventName(eMouseDoubleClick, "eMouseDoubleClick");
+ _ASSIGN_eventName(eMouseMove, "eMouseMove");
+ _ASSIGN_eventName(eLoad, "eLoad");
+ _ASSIGN_eventName(ePopState, "ePopState");
+ _ASSIGN_eventName(eBeforeScriptExecute, "eBeforeScriptExecute");
+ _ASSIGN_eventName(eAfterScriptExecute, "eAfterScriptExecute");
+ _ASSIGN_eventName(eUnload, "eUnload");
+ _ASSIGN_eventName(eHashChange, "eHashChange");
+ _ASSIGN_eventName(eReadyStateChange, "eReadyStateChange");
+ _ASSIGN_eventName(eXULBroadcast, "eXULBroadcast");
+ _ASSIGN_eventName(eXULCommandUpdate, "eXULCommandUpdate");
+
+# undef _ASSIGN_eventName
+
+ default: {
+ eventName.AssignLiteral("UNKNOWN: ");
+ eventName.AppendInt(aGuiEvent->mMessage);
+ } break;
+ }
+
+ return nsAutoString(eventName);
+}
+//////////////////////////////////////////////////////////////
+//
+// Code to deal with paint and event debug prefs.
+//
+//////////////////////////////////////////////////////////////
+struct PrefPair {
+ const char* name;
+ bool value;
+};
+
+static PrefPair debug_PrefValues[] = {
+ {"nglayout.debug.crossing_event_dumping", false},
+ {"nglayout.debug.event_dumping", false},
+ {"nglayout.debug.invalidate_dumping", false},
+ {"nglayout.debug.motion_event_dumping", false},
+ {"nglayout.debug.paint_dumping", false},
+ {"nglayout.debug.paint_flashing", false},
+ {"nglayout.debug.paint_flashing_chrome", false}};
+
+//////////////////////////////////////////////////////////////
+bool nsBaseWidget::debug_GetCachedBoolPref(const char* aPrefName) {
+ NS_ASSERTION(nullptr != aPrefName, "cmon, pref name is null.");
+
+ for (uint32_t i = 0; i < ArrayLength(debug_PrefValues); i++) {
+ if (strcmp(debug_PrefValues[i].name, aPrefName) == 0) {
+ return debug_PrefValues[i].value;
+ }
+ }
+
+ return false;
+}
+//////////////////////////////////////////////////////////////
+static void debug_SetCachedBoolPref(const char* aPrefName, bool aValue) {
+ NS_ASSERTION(nullptr != aPrefName, "cmon, pref name is null.");
+
+ for (uint32_t i = 0; i < ArrayLength(debug_PrefValues); i++) {
+ if (strcmp(debug_PrefValues[i].name, aPrefName) == 0) {
+ debug_PrefValues[i].value = aValue;
+
+ return;
+ }
+ }
+
+ NS_ASSERTION(false, "cmon, this code is not reached dude.");
+}
+
+//////////////////////////////////////////////////////////////
+class Debug_PrefObserver final : public nsIObserver {
+ ~Debug_PrefObserver() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+};
+
+NS_IMPL_ISUPPORTS(Debug_PrefObserver, nsIObserver)
+
+NS_IMETHODIMP
+Debug_PrefObserver::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ NS_ConvertUTF16toUTF8 prefName(data);
+
+ bool value = Preferences::GetBool(prefName.get(), false);
+ debug_SetCachedBoolPref(prefName.get(), value);
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////
+/* static */ void debug_RegisterPrefCallbacks() {
+ static bool once = true;
+
+ if (!once) {
+ return;
+ }
+
+ once = false;
+
+ nsCOMPtr<nsIObserver> obs(new Debug_PrefObserver());
+ for (uint32_t i = 0; i < ArrayLength(debug_PrefValues); i++) {
+ // Initialize the pref values
+ debug_PrefValues[i].value =
+ Preferences::GetBool(debug_PrefValues[i].name, false);
+
+ if (obs) {
+ // Register callbacks for when these change
+ nsCString name;
+ name.AssignLiteral(debug_PrefValues[i].name,
+ strlen(debug_PrefValues[i].name));
+ Preferences::AddStrongObserver(obs, name);
+ }
+ }
+}
+//////////////////////////////////////////////////////////////
+static int32_t _GetPrintCount() {
+ static int32_t sCount = 0;
+
+ return ++sCount;
+}
+//////////////////////////////////////////////////////////////
+/* static */
+bool nsBaseWidget::debug_WantPaintFlashing() {
+ return debug_GetCachedBoolPref("nglayout.debug.paint_flashing");
+}
+//////////////////////////////////////////////////////////////
+/* static */
+void nsBaseWidget::debug_DumpEvent(FILE* aFileOut, nsIWidget* aWidget,
+ WidgetGUIEvent* aGuiEvent,
+ const char* aWidgetName, int32_t aWindowID) {
+ if (aGuiEvent->mMessage == eMouseMove) {
+ if (!debug_GetCachedBoolPref("nglayout.debug.motion_event_dumping")) return;
+ }
+
+ if (aGuiEvent->mMessage == eMouseEnterIntoWidget ||
+ aGuiEvent->mMessage == eMouseExitFromWidget) {
+ if (!debug_GetCachedBoolPref("nglayout.debug.crossing_event_dumping"))
+ return;
+ }
+
+ if (!debug_GetCachedBoolPref("nglayout.debug.event_dumping")) return;
+
+ NS_LossyConvertUTF16toASCII tempString(
+ debug_GuiEventToString(aGuiEvent).get());
+
+ fprintf(aFileOut, "%4d %-26s widget=%-8p name=%-12s id=0x%-6x refpt=%d,%d\n",
+ _GetPrintCount(), tempString.get(), (void*)aWidget, aWidgetName,
+ aWindowID, aGuiEvent->mRefPoint.x, aGuiEvent->mRefPoint.y);
+}
+//////////////////////////////////////////////////////////////
+/* static */
+void nsBaseWidget::debug_DumpPaintEvent(FILE* aFileOut, nsIWidget* aWidget,
+ const nsIntRegion& aRegion,
+ const char* aWidgetName,
+ int32_t aWindowID) {
+ NS_ASSERTION(nullptr != aFileOut, "cmon, null output FILE");
+ NS_ASSERTION(nullptr != aWidget, "cmon, the widget is null");
+
+ if (!debug_GetCachedBoolPref("nglayout.debug.paint_dumping")) return;
+
+ nsIntRect rect = aRegion.GetBounds();
+ fprintf(aFileOut,
+ "%4d PAINT widget=%p name=%-12s id=0x%-6x bounds-rect=%3d,%-3d "
+ "%3d,%-3d",
+ _GetPrintCount(), (void*)aWidget, aWidgetName, aWindowID, rect.X(),
+ rect.Y(), rect.Width(), rect.Height());
+
+ fprintf(aFileOut, "\n");
+}
+//////////////////////////////////////////////////////////////
+/* static */
+void nsBaseWidget::debug_DumpInvalidate(FILE* aFileOut, nsIWidget* aWidget,
+ const LayoutDeviceIntRect* aRect,
+ const char* aWidgetName,
+ int32_t aWindowID) {
+ if (!debug_GetCachedBoolPref("nglayout.debug.invalidate_dumping")) return;
+
+ NS_ASSERTION(nullptr != aFileOut, "cmon, null output FILE");
+ NS_ASSERTION(nullptr != aWidget, "cmon, the widget is null");
+
+ fprintf(aFileOut, "%4d Invalidate widget=%p name=%-12s id=0x%-6x",
+ _GetPrintCount(), (void*)aWidget, aWidgetName, aWindowID);
+
+ if (aRect) {
+ fprintf(aFileOut, " rect=%3d,%-3d %3d,%-3d", aRect->X(), aRect->Y(),
+ aRect->Width(), aRect->Height());
+ } else {
+ fprintf(aFileOut, " rect=%-15s", "none");
+ }
+
+ fprintf(aFileOut, "\n");
+}
+//////////////////////////////////////////////////////////////
+
+#endif // DEBUG
diff --git a/widget/nsBaseWidget.h b/widget/nsBaseWidget.h
new file mode 100644
index 0000000000..1e212638bb
--- /dev/null
+++ b/widget/nsBaseWidget.h
@@ -0,0 +1,744 @@
+/* -*- 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 nsBaseWidget_h__
+#define nsBaseWidget_h__
+
+#include "InputData.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WidgetUtils.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/CompositorOptions.h"
+#include "mozilla/layers/NativeLayer.h"
+#include "mozilla/widget/ThemeChangeKind.h"
+#include "nsRect.h"
+#include "nsIWidget.h"
+#include "nsWidgetsCID.h"
+#include "nsIFile.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIRollupListener.h"
+#include "nsIObserver.h"
+#include "nsIWidgetListener.h"
+#include "nsPIDOMWindow.h"
+#include "nsWeakReference.h"
+
+#include <algorithm>
+
+#if defined(XP_WIN)
+// Scroll capture constants
+const uint32_t kScrollCaptureFillColor = 0xFFa0a0a0; // gray
+const mozilla::gfx::SurfaceFormat kScrollCaptureFormat =
+ mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32;
+#endif
+
+class nsIContent;
+class gfxContext;
+
+namespace mozilla {
+class CompositorVsyncDispatcher;
+class LiveResizeListener;
+
+#ifdef ACCESSIBILITY
+namespace a11y {
+class Accessible;
+}
+#endif
+
+namespace gfx {
+class DrawTarget;
+class SourceSurface;
+} // namespace gfx
+
+namespace layers {
+class BasicLayerManager;
+class CompositorBridgeChild;
+class CompositorBridgeParent;
+class IAPZCTreeManager;
+class GeckoContentController;
+class APZEventState;
+struct APZEventResult;
+class CompositorSession;
+class ImageContainer;
+struct ScrollableLayerGuid;
+class RemoteCompositorSession;
+} // namespace layers
+
+namespace widget {
+class CompositorWidgetDelegate;
+class InProcessCompositorWidget;
+class WidgetRenderingContext;
+} // namespace widget
+
+class CompositorVsyncDispatcher;
+} // namespace mozilla
+
+namespace base {
+class Thread;
+} // namespace base
+
+// Windows specific constant indicating the maximum number of touch points the
+// inject api will allow. This also sets the maximum numerical value for touch
+// ids we can use when injecting touch points on Windows.
+#define TOUCH_INJECT_MAX_POINTS 256
+
+class nsBaseWidget;
+
+// Helper class used in shutting down gfx related code.
+class WidgetShutdownObserver final : public nsIObserver {
+ ~WidgetShutdownObserver();
+
+ public:
+ explicit WidgetShutdownObserver(nsBaseWidget* aWidget);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ void Register();
+ void Unregister();
+
+ nsBaseWidget* mWidget;
+ bool mRegistered;
+};
+
+/**
+ * Common widget implementation used as base class for native
+ * or crossplatform implementations of Widgets.
+ * All cross-platform behavior that all widgets need to implement
+ * should be placed in this class.
+ * (Note: widget implementations are not required to use this
+ * class, but it gives them a head start.)
+ */
+
+class nsBaseWidget : public nsIWidget, public nsSupportsWeakReference {
+ template <class EventType, class InputType>
+ friend class DispatchEventOnMainThread;
+ friend class mozilla::widget::InProcessCompositorWidget;
+ friend class mozilla::layers::RemoteCompositorSession;
+
+ protected:
+ typedef base::Thread Thread;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+ typedef mozilla::layers::BasicLayerManager BasicLayerManager;
+ typedef mozilla::layers::BufferMode BufferMode;
+ typedef mozilla::layers::CompositorBridgeChild CompositorBridgeChild;
+ typedef mozilla::layers::CompositorBridgeParent CompositorBridgeParent;
+ typedef mozilla::layers::IAPZCTreeManager IAPZCTreeManager;
+ typedef mozilla::layers::GeckoContentController GeckoContentController;
+ typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid;
+ typedef mozilla::layers::APZEventState APZEventState;
+ typedef mozilla::layers::SetAllowedTouchBehaviorCallback
+ SetAllowedTouchBehaviorCallback;
+ typedef mozilla::CSSIntRect CSSIntRect;
+ typedef mozilla::CSSRect CSSRect;
+ typedef mozilla::ScreenRotation ScreenRotation;
+ typedef mozilla::widget::CompositorWidgetDelegate CompositorWidgetDelegate;
+ typedef mozilla::layers::CompositorSession CompositorSession;
+ typedef mozilla::layers::ImageContainer ImageContainer;
+
+ virtual ~nsBaseWidget();
+
+ public:
+ nsBaseWidget();
+
+ NS_DECL_ISUPPORTS
+
+ // nsIWidget interface
+ virtual void CaptureMouse(bool aCapture) override {}
+ virtual void CaptureRollupEvents(nsIRollupListener* aListener,
+ bool aDoCapture) override {}
+ virtual nsIWidgetListener* GetWidgetListener() override;
+ virtual void SetWidgetListener(nsIWidgetListener* alistener) override;
+ virtual void Destroy() override;
+ virtual void SetParent(nsIWidget* aNewParent) override{};
+ virtual nsIWidget* GetParent(void) override;
+ virtual nsIWidget* GetTopLevelWidget() override;
+ virtual nsIWidget* GetSheetWindowParent(void) override;
+ virtual float GetDPI() override;
+ virtual void AddChild(nsIWidget* aChild) override;
+ virtual void RemoveChild(nsIWidget* aChild) override;
+
+ void SetZIndex(int32_t aZIndex) override;
+ virtual void PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
+ nsIWidget* aWidget, bool aActivate) override {}
+
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ virtual nsSizeMode SizeMode() override { return mSizeMode; }
+ virtual void GetWorkspaceID(nsAString& workspaceID) override;
+ virtual void MoveToWorkspace(const nsAString& workspaceID) override;
+ virtual bool IsTiled() const override { return mIsTiled; }
+
+ virtual bool IsFullyOccluded() const override { return mIsFullyOccluded; }
+
+ virtual void SetCursor(nsCursor aDefaultCursor, imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY) override;
+ virtual void ClearCachedCursor() override { mUpdateCursor = true; }
+ virtual void SetTransparencyMode(nsTransparencyMode aMode) override;
+ virtual nsTransparencyMode GetTransparencyMode() override;
+ virtual void GetWindowClipRegion(
+ nsTArray<LayoutDeviceIntRect>* aRects) override;
+ virtual void SetWindowShadowStyle(
+ mozilla::StyleWindowShadow aStyle) override {}
+ virtual void SetShowsToolbarButton(bool aShow) override {}
+ virtual void SetSupportsNativeFullscreen(
+ bool aSupportsNativeFullscreen) override {}
+ virtual void SetWindowAnimationType(WindowAnimationType aType) override {}
+ virtual void HideWindowChrome(bool aShouldHide) override {}
+ virtual bool PrepareForFullscreenTransition(nsISupports** aData) override {
+ return false;
+ }
+ virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) override;
+ virtual void CleanupFullscreenTransition() override{};
+ virtual already_AddRefed<nsIScreen> GetWidgetScreen() override;
+ virtual nsresult MakeFullScreen(bool aFullScreen,
+ nsIScreen* aScreen = nullptr) override;
+ void InfallibleMakeFullScreen(bool aFullScreen, nsIScreen* aScreen = nullptr);
+
+ virtual LayerManager* GetLayerManager(
+ PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
+
+ // A remote compositor session tied to this window has been lost and IPC
+ // messages will no longer work. The widget must clean up any lingering
+ // resources and possibly schedule another paint.
+ //
+ // A reference to the session object is held until this function has
+ // returned.
+ void NotifyCompositorSessionLost(
+ mozilla::layers::CompositorSession* aSession);
+
+ already_AddRefed<mozilla::CompositorVsyncDispatcher>
+ GetCompositorVsyncDispatcher();
+ virtual void CreateCompositorVsyncDispatcher();
+ virtual void CreateCompositor();
+ virtual void CreateCompositor(int aWidth, int aHeight);
+ virtual void SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
+ }
+ virtual void PrepareWindowEffects() override {}
+ virtual void UpdateThemeGeometries(
+ const nsTArray<ThemeGeometry>& aThemeGeometries) override {}
+ virtual void SetModal(bool aModal) override {}
+ virtual uint32_t GetMaxTouchPoints() const override;
+ virtual void SetWindowClass(const nsAString& xulWinType) override {}
+ virtual nsresult SetWindowClipRegion(
+ const nsTArray<LayoutDeviceIntRect>& aRects,
+ bool aIntersectWithExisting) override;
+ // Return whether this widget interprets parameters to Move and Resize APIs
+ // as "desktop pixels" rather than "device pixels", and therefore
+ // applies its GetDefaultScale() value to them before using them as mBounds
+ // etc (which are always stored in device pixels).
+ // Note that APIs that -get- the widget's position/size/bounds, rather than
+ // -setting- them (i.e. moving or resizing the widget) will always return
+ // values in the widget's device pixels.
+ bool BoundsUseDesktopPixels() const {
+ return mWindowType <= eWindowType_popup;
+ }
+ // Default implementation, to be overridden by platforms where desktop coords
+ // are virtualized and may not correspond to device pixels on the screen.
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() override {
+ return mozilla::DesktopToLayoutDeviceScale(1.0);
+ }
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScaleByScreen()
+ override;
+
+ virtual void ConstrainPosition(bool aAllowSlop, int32_t* aX,
+ int32_t* aY) override {}
+ virtual void MoveClient(const DesktopPoint& aOffset) override;
+ virtual void ResizeClient(const DesktopSize& aSize, bool aRepaint) override;
+ virtual void ResizeClient(const DesktopRect& aRect, bool aRepaint) override;
+ virtual LayoutDeviceIntRect GetBounds() override;
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ [[nodiscard]] virtual nsresult GetRestoredBounds(
+ LayoutDeviceIntRect& aRect) override;
+ virtual nsresult SetNonClientMargins(
+ LayoutDeviceIntMargin& aMargins) override;
+ virtual LayoutDeviceIntPoint GetClientOffset() override;
+ virtual void EnableDragDrop(bool aEnable) override{};
+ virtual nsresult AsyncEnableDragDrop(bool aEnable) override;
+ [[nodiscard]] virtual nsresult GetAttention(int32_t aCycleCount) override {
+ return NS_OK;
+ }
+ virtual bool HasPendingInputEvent() override;
+ virtual void SetIcon(const nsAString& aIconSpec) override {}
+ virtual void SetDrawsInTitlebar(bool aState) override {}
+ virtual bool ShowsResizeIndicator(LayoutDeviceIntRect* aResizerRect) override;
+ virtual void FreeNativeData(void* data, uint32_t aDataType) override {}
+ [[nodiscard]] virtual nsresult BeginResizeDrag(
+ mozilla::WidgetGUIEvent* aEvent, int32_t aHorizontal,
+ int32_t aVertical) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ virtual nsresult ActivateNativeMenuItemAt(
+ const nsAString& indexString) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ virtual nsresult ForceUpdateNativeMenuAt(
+ const nsAString& indexString) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult NotifyIME(const IMENotification& aIMENotification) final;
+ [[nodiscard]] virtual nsresult AttachNativeKeyEvent(
+ mozilla::WidgetKeyboardEvent& aEvent) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ bool ComputeShouldAccelerate();
+ virtual bool WidgetTypeSupportsAcceleration() { return true; }
+ [[nodiscard]] virtual nsresult OnDefaultButtonLoaded(
+ const LayoutDeviceIntRect& aButtonRect) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ virtual already_AddRefed<nsIWidget> CreateChild(
+ const LayoutDeviceIntRect& aRect, nsWidgetInitData* aInitData = nullptr,
+ bool aForceUseIWidgetParent = false) override;
+ virtual void AttachViewToTopLevel(bool aUseAttachedEvents) override;
+ virtual nsIWidgetListener* GetAttachedWidgetListener() override;
+ virtual void SetAttachedWidgetListener(nsIWidgetListener* aListener) override;
+ virtual nsIWidgetListener* GetPreviouslyAttachedWidgetListener() override;
+ virtual void SetPreviouslyAttachedWidgetListener(
+ nsIWidgetListener* aListener) override;
+ virtual NativeIMEContext GetNativeIMEContext() override;
+ TextEventDispatcher* GetTextEventDispatcher() final;
+ virtual TextEventDispatcherListener* GetNativeTextEventDispatcherListener()
+ override;
+ virtual void ZoomToRect(const uint32_t& aPresShellId,
+ const ScrollableLayerGuid::ViewID& aViewId,
+ const CSSRect& aRect,
+ const uint32_t& aFlags) override;
+ // Dispatch an event that must be first be routed through APZ.
+ nsEventStatus DispatchInputEvent(mozilla::WidgetInputEvent* aEvent) override;
+ void DispatchEventToAPZOnly(mozilla::WidgetInputEvent* aEvent) override;
+
+ void SetConfirmedTargetAPZC(
+ uint64_t aInputBlockId,
+ const nsTArray<ScrollableLayerGuid>& aTargets) const override;
+
+ void UpdateZoomConstraints(
+ const uint32_t& aPresShellId, const ScrollableLayerGuid::ViewID& aViewId,
+ const mozilla::Maybe<ZoomConstraints>& aConstraints) override;
+
+ bool AsyncPanZoomEnabled() const override;
+
+ void NotifyWindowDestroyed();
+ void NotifySizeMoveDone();
+ void NotifyWindowMoved(int32_t aX, int32_t aY);
+
+ // Register plugin windows for remote updates from the compositor
+ virtual void RegisterPluginWindowForRemoteUpdates() override;
+ virtual void UnregisterPluginWindowForRemoteUpdates() override;
+
+ virtual void SetNativeData(uint32_t aDataType, uintptr_t aVal) override{};
+
+ // Should be called by derived implementations to notify on system color and
+ // theme changes.
+ void NotifyThemeChanged(mozilla::widget::ThemeChangeKind);
+ void NotifyUIStateChanged(UIStateChangeType aShowFocusRings);
+
+#ifdef ACCESSIBILITY
+ // Get the accessible for the window.
+ mozilla::a11y::Accessible* GetRootAccessible();
+#endif
+
+ // Return true if this is a simple widget (that is typically not worth
+ // accelerating)
+ bool IsSmallPopup() const;
+
+ nsPopupLevel PopupLevel() { return mPopupLevel; }
+
+ virtual LayoutDeviceIntSize ClientToWindowSize(
+ const LayoutDeviceIntSize& aClientSize) override {
+ return aClientSize;
+ }
+
+ // return true if this is a popup widget with a native titlebar
+ bool IsPopupWithTitleBar() const {
+ return (mWindowType == eWindowType_popup &&
+ mBorderStyle != eBorderStyle_default &&
+ mBorderStyle & eBorderStyle_title);
+ }
+
+ virtual void ReparentNativeWidget(nsIWidget* aNewParent) override {}
+
+ virtual const SizeConstraints GetSizeConstraints() override;
+ virtual void SetSizeConstraints(const SizeConstraints& aConstraints) override;
+
+ virtual void StartAsyncScrollbarDrag(
+ const AsyncDragMetrics& aDragMetrics) override;
+
+ virtual bool StartAsyncAutoscroll(const ScreenPoint& aAnchorLocation,
+ const ScrollableLayerGuid& aGuid) override;
+
+ virtual void StopAsyncAutoscroll(const ScrollableLayerGuid& aGuid) override;
+
+ /**
+ * Use this when GetLayerManager() returns a BasicLayerManager
+ * (nsBaseWidget::GetLayerManager() does). This sets up the widget's
+ * layer manager to temporarily render into aTarget.
+ *
+ * |aNaturalWidgetBounds| is the un-rotated bounds of |aWidget|.
+ * |aRotation| is the "virtual rotation" to apply when rendering to
+ * the target. When |aRotation| is ROTATION_0,
+ * |aNaturalWidgetBounds| is not used.
+ */
+ class AutoLayerManagerSetup {
+ public:
+ AutoLayerManagerSetup(nsBaseWidget* aWidget, gfxContext* aTarget,
+ BufferMode aDoubleBuffering,
+ ScreenRotation aRotation = mozilla::ROTATION_0);
+ ~AutoLayerManagerSetup();
+
+ private:
+ nsBaseWidget* mWidget;
+ RefPtr<BasicLayerManager> mLayerManager;
+ };
+ friend class AutoLayerManagerSetup;
+
+ virtual bool ShouldUseOffMainThreadCompositing();
+
+ static nsIRollupListener* GetActiveRollupListener();
+
+ void Shutdown();
+
+ void QuitIME();
+
+#if defined(XP_WIN)
+ uint64_t CreateScrollCaptureContainer() override;
+#endif
+
+ // These functions should be called at the start and end of a "live" widget
+ // resize (i.e. when the window contents are repainting during the resize,
+ // such as when the user drags a window border). It will suppress the
+ // displayport during the live resize to avoid unneccessary overpainting.
+ void NotifyLiveResizeStarted();
+ void NotifyLiveResizeStopped();
+
+#if defined(MOZ_WIDGET_ANDROID)
+ void RecvToolbarAnimatorMessageFromCompositor(int32_t) override{};
+ void UpdateRootFrameMetrics(const ScreenPoint& aScrollOffset,
+ const CSSToScreenScale& aZoom) override{};
+ void RecvScreenPixels(mozilla::ipc::Shmem&& aMem, const ScreenIntSize& aSize,
+ bool aNeedsYFlip) override{};
+#endif
+
+ protected:
+ // These are methods for CompositorWidgetWrapper, and should only be
+ // accessed from that class. Derived widgets can choose which methods to
+ // implement, or none if supporting out-of-process compositing.
+ virtual bool PreRender(mozilla::widget::WidgetRenderingContext* aContext) {
+ return true;
+ }
+ virtual void PostRender(mozilla::widget::WidgetRenderingContext* aContext) {}
+ virtual RefPtr<mozilla::layers::NativeLayerRoot> GetNativeLayerRoot() {
+ return nullptr;
+ }
+ virtual already_AddRefed<DrawTarget> StartRemoteDrawing();
+ virtual already_AddRefed<DrawTarget> StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode) {
+ return StartRemoteDrawing();
+ }
+ virtual void EndRemoteDrawing() {}
+ virtual void EndRemoteDrawingInRegion(
+ DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
+ EndRemoteDrawing();
+ }
+ virtual void CleanupRemoteDrawing() {}
+ virtual void CleanupWindowEffects() {}
+ virtual bool InitCompositor(mozilla::layers::Compositor* aCompositor) {
+ return true;
+ }
+ virtual uint32_t GetGLFrameBufferFormat();
+ virtual bool CompositorInitiallyPaused() { return false; }
+
+ protected:
+ void ResolveIconName(const nsAString& aIconName, const nsAString& aIconSuffix,
+ nsIFile** aResult);
+ virtual void OnDestroy();
+ void BaseCreate(nsIWidget* aParent, nsWidgetInitData* aInitData);
+
+ virtual void ConfigureAPZCTreeManager();
+ virtual void ConfigureAPZControllerThread();
+ virtual already_AddRefed<GeckoContentController>
+ CreateRootContentController();
+
+ // Dispatch an event that has already been routed through APZ.
+ nsEventStatus ProcessUntransformedAPZEvent(
+ mozilla::WidgetInputEvent* aEvent,
+ const mozilla::layers::APZEventResult& aApzResult);
+
+ const LayoutDeviceIntRegion RegionFromArray(
+ const nsTArray<LayoutDeviceIntRect>& aRects);
+ void ArrayFromRegion(const LayoutDeviceIntRegion& aRegion,
+ nsTArray<LayoutDeviceIntRect>& aRects);
+
+ virtual nsresult SynthesizeNativeKeyEvent(
+ int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode,
+ uint32_t aModifierFlags, const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters, nsIObserver* aObserver) override {
+ mozilla::widget::AutoObserverNotifier notifier(aObserver, "keyevent");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) override {
+ mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ virtual nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver) override {
+ mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ virtual nsresult SynthesizeNativeMouseScrollEvent(
+ LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
+ double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags, nsIObserver* aObserver) override {
+ mozilla::widget::AutoObserverNotifier notifier(aObserver,
+ "mousescrollevent");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) override {
+ mozilla::widget::AutoObserverNotifier notifier(aObserver, "touchpoint");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ /**
+ * GetPseudoIMEContext() returns pseudo IME context when TextEventDispatcher
+ * has non-native input transaction. Otherwise, returns nullptr.
+ */
+ void* GetPseudoIMEContext();
+
+ protected:
+ // Utility to check if an array of clip rects is equal to our
+ // internally stored clip rect array mClipRects.
+ bool IsWindowClipRegionEqual(const nsTArray<LayoutDeviceIntRect>& aRects);
+
+ // Stores the clip rectangles in aRects into mClipRects.
+ void StoreWindowClipRegion(const nsTArray<LayoutDeviceIntRect>& aRects);
+
+ virtual already_AddRefed<nsIWidget> AllocateChildPopupWidget() {
+ return nsIWidget::CreateChildWindow();
+ }
+
+ LayerManager* CreateBasicLayerManager();
+
+ nsPopupType PopupType() const { return mPopupType; }
+
+ bool HasRemoteContent() const { return mHasRemoteContent; }
+
+ void NotifyRollupGeometryChange() {
+ // XULPopupManager isn't interested in this notification, so only
+ // send it if gRollupListener is set.
+ if (gRollupListener) {
+ gRollupListener->NotifyGeometryChange();
+ }
+ }
+
+ /**
+ * Apply the current size constraints to the given size.
+ *
+ * @param aWidth width to constrain
+ * @param aHeight height to constrain
+ */
+ void ConstrainSize(int32_t* aWidth, int32_t* aHeight) {
+ SizeConstraints c = GetSizeConstraints();
+ *aWidth = std::max(c.mMinSize.width, std::min(c.mMaxSize.width, *aWidth));
+ *aHeight =
+ std::max(c.mMinSize.height, std::min(c.mMaxSize.height, *aHeight));
+ }
+
+ virtual CompositorBridgeChild* GetRemoteRenderer() override;
+
+ virtual void ClearCachedWebrenderResources() override;
+
+ /**
+ * Notify the widget that this window is being used with OMTC.
+ */
+ virtual void WindowUsesOMTC() {}
+ virtual void RegisterTouchWindow() {}
+
+ mozilla::dom::Document* GetDocument() const;
+
+ void EnsureTextEventDispatcher();
+
+ // Notify the compositor that a device reset has occurred.
+ void OnRenderingDeviceReset();
+
+ bool UseAPZ();
+
+ bool AllowWebRenderForThisWindow();
+
+ /**
+ * For widgets that support synthesizing native touch events, this function
+ * can be used to manage the current state of synthetic pointers. Each widget
+ * must maintain its own MultiTouchInput instance and pass it in as the state,
+ * along with the desired parameters for the changes. This function returns
+ * a new MultiTouchInput object that is ready to be dispatched.
+ */
+ mozilla::MultiTouchInput UpdateSynthesizedTouchState(
+ mozilla::MultiTouchInput* aState, uint32_t aTime,
+ mozilla::TimeStamp aTimeStamp, uint32_t aPointerId,
+ TouchPointerState aPointerState, LayoutDeviceIntPoint aPoint,
+ double aPointerPressure, uint32_t aPointerOrientation);
+
+ /**
+ * Dispatch the given MultiTouchInput through APZ to Gecko (if APZ is enabled)
+ * or directly to gecko (if APZ is not enabled). This function must only
+ * be called from the main thread, and if APZ is enabled, that must also be
+ * the APZ controller thread.
+ */
+ void DispatchTouchInput(mozilla::MultiTouchInput& aInput);
+
+ /**
+ * Dispatch the given PanGestureInput through APZ to Gecko (if APZ is enabled)
+ * or directly to gecko (if APZ is not enabled). This function must only
+ * be called from the main thread, and if APZ is enabled, that must also be
+ * the APZ controller thread.
+ */
+ void DispatchPanGestureInput(mozilla::PanGestureInput& aInput);
+ void DispatchPinchGestureInput(mozilla::PinchGestureInput& aInput);
+
+#if defined(XP_WIN)
+ void UpdateScrollCapture() override;
+
+ /**
+ * To be overridden by derived classes to return a snapshot that can be used
+ * during scrolling. Returning null means we won't update the container.
+ * @return an already AddRefed SourceSurface containing the snapshot
+ */
+ virtual already_AddRefed<SourceSurface> CreateScrollSnapshot() {
+ return nullptr;
+ };
+
+ /**
+ * Used by derived classes to create a fallback scroll image.
+ * @param aSnapshotDrawTarget DrawTarget to fill with fallback image.
+ */
+ void DefaultFillScrollCapture(DrawTarget* aSnapshotDrawTarget);
+
+ RefPtr<ImageContainer> mScrollCaptureContainer;
+#endif
+
+ protected:
+ // Returns whether compositing should use an external surface size.
+ virtual bool UseExternalCompositingSurface() const { return false; }
+
+ /**
+ * Starts the OMTC compositor destruction sequence.
+ *
+ * When this function returns, the compositor should not be
+ * able to access the opengl context anymore.
+ * It is safe to call it several times if platform implementations
+ * require the compositor to be destroyed before ~nsBaseWidget is
+ * reached (This is the case with gtk2 for instance).
+ */
+ virtual void DestroyCompositor();
+ void DestroyLayerManager();
+ void ReleaseContentController();
+ void RevokeTransactionIdAllocator();
+
+ void FreeShutdownObserver();
+
+ nsIWidgetListener* mWidgetListener;
+ nsIWidgetListener* mAttachedWidgetListener;
+ nsIWidgetListener* mPreviouslyAttachedWidgetListener;
+ RefPtr<LayerManager> mLayerManager;
+ RefPtr<CompositorSession> mCompositorSession;
+ RefPtr<CompositorBridgeChild> mCompositorBridgeChild;
+
+ mozilla::UniquePtr<mozilla::Mutex> mCompositorVsyncDispatcherLock;
+ RefPtr<mozilla::CompositorVsyncDispatcher> mCompositorVsyncDispatcher;
+
+ RefPtr<IAPZCTreeManager> mAPZC;
+ RefPtr<GeckoContentController> mRootContentController;
+ RefPtr<APZEventState> mAPZEventState;
+ SetAllowedTouchBehaviorCallback mSetAllowedTouchBehaviorCallback;
+ RefPtr<WidgetShutdownObserver> mShutdownObserver;
+ RefPtr<TextEventDispatcher> mTextEventDispatcher;
+ nsCursor mCursor;
+ nsBorderStyle mBorderStyle;
+ LayoutDeviceIntRect mBounds;
+ LayoutDeviceIntRect* mOriginalBounds;
+ // When this pointer is null, the widget is not clipped
+ mozilla::UniquePtr<LayoutDeviceIntRect[]> mClipRects;
+ uint32_t mClipRectCount;
+ nsSizeMode mSizeMode;
+ bool mIsTiled;
+ nsPopupLevel mPopupLevel;
+ nsPopupType mPopupType;
+ SizeConstraints mSizeConstraints;
+ bool mHasRemoteContent;
+ bool mFissionWindow;
+
+ bool mUpdateCursor;
+ bool mUseAttachedEvents;
+ bool mIMEHasFocus;
+ bool mIMEHasQuit;
+ bool mIsFullyOccluded;
+ static nsIRollupListener* gRollupListener;
+
+ struct InitialZoomConstraints {
+ InitialZoomConstraints(const uint32_t& aPresShellID,
+ const ScrollableLayerGuid::ViewID& aViewID,
+ const ZoomConstraints& aConstraints)
+ : mPresShellID(aPresShellID),
+ mViewID(aViewID),
+ mConstraints(aConstraints) {}
+
+ uint32_t mPresShellID;
+ ScrollableLayerGuid::ViewID mViewID;
+ ZoomConstraints mConstraints;
+ };
+
+ mozilla::Maybe<InitialZoomConstraints> mInitialZoomConstraints;
+
+ // This points to the resize listeners who have been notified that a live
+ // resize is in progress. This should always be empty when a live-resize is
+ // not in progress.
+ nsTArray<RefPtr<mozilla::LiveResizeListener>> mLiveResizeListeners;
+
+#ifdef DEBUG
+ protected:
+ static nsAutoString debug_GuiEventToString(
+ mozilla::WidgetGUIEvent* aGuiEvent);
+ static bool debug_WantPaintFlashing();
+
+ static void debug_DumpInvalidate(FILE* aFileOut, nsIWidget* aWidget,
+ const LayoutDeviceIntRect* aRect,
+ const char* aWidgetName, int32_t aWindowID);
+
+ static void debug_DumpEvent(FILE* aFileOut, nsIWidget* aWidget,
+ mozilla::WidgetGUIEvent* aGuiEvent,
+ const char* aWidgetName, int32_t aWindowID);
+
+ static void debug_DumpPaintEvent(FILE* aFileOut, nsIWidget* aWidget,
+ const nsIntRegion& aPaintEvent,
+ const char* aWidgetName, int32_t aWindowID);
+
+ static bool debug_GetCachedBoolPref(const char* aPrefName);
+#endif
+
+ private:
+ already_AddRefed<LayerManager> CreateCompositorSession(
+ int aWidth, int aHeight, mozilla::layers::CompositorOptions* aOptionsOut);
+};
+
+#endif // nsBaseWidget_h__
diff --git a/widget/nsCUPSShim.cpp b/widget/nsCUPSShim.cpp
new file mode 100644
index 0000000000..1da189c725
--- /dev/null
+++ b/widget/nsCUPSShim.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ex: set tabstop=8 softtabstop=2 shiftwidth=2 expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsDebug.h"
+#include "nsString.h"
+#include "nsCUPSShim.h"
+#include "mozilla/ArrayUtils.h"
+#include "prlink.h"
+
+#ifdef CUPS_SHIM_RUNTIME_LINK
+
+// TODO: This is currently pointless as we always use the compile-time linked
+// version of CUPS, but in the future this may become a configure option.
+// We also cannot use NSPR's library suffix support, since that cannot handle
+// version number suffixes.
+# ifdef XP_MACOSX
+static const char gCUPSLibraryName[] = "libcups.2.dylib";
+# else
+static const char gCUPSLibraryName[] = "libcups.so.2";
+# endif
+
+template <typename FuncT>
+static bool LoadCupsFunc(PRLibrary*& lib, FuncT*& dest,
+ const char* const name) {
+ dest = (FuncT*)PR_FindSymbol(lib, name);
+ if (MOZ_UNLIKELY(!dest)) {
+# ifdef DEBUG
+ nsAutoCString msg(name);
+ msg.AppendLiteral(" not found in CUPS library");
+ NS_WARNING(msg.get());
+# endif
+# ifndef MOZ_TSAN
+ // With TSan, we cannot unload libcups once we have loaded it because
+ // TSan does not support unloading libraries that are matched from its
+ // suppression list. Hence we just keep the library loaded in TSan builds.
+ PR_UnloadLibrary(lib);
+# endif
+ lib = nullptr;
+ return false;
+ }
+ return true;
+}
+
+nsCUPSShim::nsCUPSShim() {
+ mCupsLib = PR_LoadLibrary(gCUPSLibraryName);
+ if (!mCupsLib) {
+ return;
+ }
+
+ // This is a macro so that it could also load from libcups if we are
+ // configured to use it as a compile-time dependency.
+# define CUPS_SHIM_LOAD(NAME) \
+ if (!LoadCupsFunc(mCupsLib, NAME, #NAME)) return;
+ CUPS_SHIM_ALL_FUNCS(CUPS_SHIM_LOAD)
+# undef CUPS_SHIM_LOAD
+
+ // Set mInitOkay only if all cups functions are loaded successfully.
+ mInitOkay = true;
+}
+
+#endif
diff --git a/widget/nsCUPSShim.h b/widget/nsCUPSShim.h
new file mode 100644
index 0000000000..658c1e7a17
--- /dev/null
+++ b/widget/nsCUPSShim.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ex: set tabstop=8 softtabstop=2 shiftwidth=2 expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsCUPSShim_h___
+#define nsCUPSShim_h___
+
+#include <cups/cups.h>
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+
+// TODO: This should be a configure option, ideally.
+#ifndef XP_MACOSX
+# define CUPS_SHIM_RUNTIME_LINK
+#endif
+
+struct PRLibrary;
+
+class nsCUPSShim {
+ public:
+#ifdef CUPS_SHIM_RUNTIME_LINK
+ nsCUPSShim();
+ bool InitOkay() { return mInitOkay; }
+#else
+ nsCUPSShim() = default;
+ bool InitOkay() { return true; }
+#endif
+
+ /**
+ * Function pointers for supported functions. These are only
+ * valid after successful initialization.
+ */
+#define CUPS_SHIM_ALL_FUNCS(X) \
+ X(cupsAddOption) \
+ X(cupsCheckDestSupported) \
+ X(cupsConnectDest) \
+ X(cupsCopyDest) \
+ X(cupsCopyDestInfo) \
+ X(cupsDoRequest) \
+ X(cupsEnumDests) \
+ X(cupsFindDestDefault) \
+ X(cupsFreeDestInfo) \
+ X(cupsFreeDests) \
+ X(cupsGetDestMediaDefault) \
+ X(cupsGetDestMediaCount) \
+ X(cupsGetDestMediaByIndex) \
+ X(cupsGetDestMediaByName) \
+ X(cupsGetDest) \
+ X(cupsGetDests) \
+ X(cupsGetNamedDest) \
+ X(cupsGetOption) \
+ X(cupsLocalizeDestMedia) \
+ X(cupsPrintFile) \
+ X(cupsTempFd) \
+ X(httpClose) \
+ X(ippAddString) \
+ X(ippAddStrings) \
+ X(ippDelete) \
+ X(ippFindAttribute) \
+ X(ippGetCount) \
+ X(ippGetString) \
+ X(ippNewRequest)
+
+#ifdef CUPS_SHIM_RUNTIME_LINK
+ // Define a single field which holds a function pointer.
+# define CUPS_SHIM_FUNC_DECL(X) decltype(::X)* X = nullptr;
+#else
+ // Define a static constexpr function pointer. GCC can sometimes optimize
+ // away the pointer fetch for this.
+# define CUPS_SHIM_FUNC_DECL(X) static constexpr decltype(::X)* const X = ::X;
+#endif
+
+ CUPS_SHIM_ALL_FUNCS(CUPS_SHIM_FUNC_DECL)
+#undef CUPS_SHIM_FUNC_DECL
+
+#ifdef CUPS_SHIM_RUNTIME_LINK
+ private:
+ bool mInitOkay = false;
+ PRLibrary* mCupsLib = nullptr;
+#endif
+};
+
+#endif /* nsCUPSShim_h___ */
diff --git a/widget/nsClipboardHelper.cpp b/widget/nsClipboardHelper.cpp
new file mode 100644
index 0000000000..3f42800a28
--- /dev/null
+++ b/widget/nsClipboardHelper.cpp
@@ -0,0 +1,124 @@
+/* -*- 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 "nsClipboardHelper.h"
+
+// basics
+#include "nsCOMPtr.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsServiceManagerUtils.h"
+
+// helpers
+#include "nsIClipboard.h"
+#include "mozilla/dom/Document.h"
+#include "nsITransferable.h"
+#include "nsReadableUtils.h"
+
+NS_IMPL_ISUPPORTS(nsClipboardHelper, nsIClipboardHelper)
+
+/*****************************************************************************
+ * nsClipboardHelper ctor / dtor
+ *****************************************************************************/
+
+nsClipboardHelper::nsClipboardHelper() = default;
+
+nsClipboardHelper::~nsClipboardHelper() {
+ // no members, nothing to destroy
+}
+
+/*****************************************************************************
+ * nsIClipboardHelper methods
+ *****************************************************************************/
+
+NS_IMETHODIMP
+nsClipboardHelper::CopyStringToClipboard(const nsAString& aString,
+ int32_t aClipboardID) {
+ nsresult rv;
+
+ // get the clipboard
+ nsCOMPtr<nsIClipboard> clipboard(
+ do_GetService("@mozilla.org/widget/clipboard;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(clipboard, NS_ERROR_FAILURE);
+
+ bool clipboardSupported;
+ // don't go any further if they're asking for the selection
+ // clipboard on a platform which doesn't support it (i.e., unix)
+ if (nsIClipboard::kSelectionClipboard == aClipboardID) {
+ rv = clipboard->SupportsSelectionClipboard(&clipboardSupported);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!clipboardSupported) return NS_ERROR_FAILURE;
+ }
+
+ // don't go any further if they're asking for the find clipboard on a platform
+ // which doesn't support it (i.e., non-osx)
+ if (nsIClipboard::kFindClipboard == aClipboardID) {
+ rv = clipboard->SupportsFindClipboard(&clipboardSupported);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!clipboardSupported) return NS_ERROR_FAILURE;
+ }
+
+ // create a transferable for putting data on the clipboard
+ nsCOMPtr<nsITransferable> trans(
+ do_CreateInstance("@mozilla.org/widget/transferable;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(trans, NS_ERROR_FAILURE);
+
+ trans->Init(nullptr);
+
+ // Add the text data flavor to the transferable
+ rv = trans->AddDataFlavor(kUnicodeMime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get wStrings to hold clip data
+ nsCOMPtr<nsISupportsString> data(
+ do_CreateInstance("@mozilla.org/supports-string;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(data, NS_ERROR_FAILURE);
+
+ // populate the string
+ rv = data->SetData(aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // qi the data object an |nsISupports| so that when the transferable holds
+ // onto it, it will addref the correct interface.
+ nsCOMPtr<nsISupports> genericData(do_QueryInterface(data, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(genericData, NS_ERROR_FAILURE);
+
+ // set the transfer data
+ rv = trans->SetTransferData(kUnicodeMime, genericData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // put the transferable on the clipboard
+ rv = clipboard->SetData(trans, nullptr, aClipboardID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboardHelper::CopyString(const nsAString& aString) {
+ nsresult rv;
+
+ // copy to the global clipboard. it's bad if this fails in any way.
+ rv = CopyStringToClipboard(aString, nsIClipboard::kGlobalClipboard);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // unix also needs us to copy to the selection clipboard. this will
+ // fail in CopyStringToClipboard if we're not on a platform that
+ // supports the selection clipboard. (this could have been #ifdef
+ // XP_UNIX, but using the SupportsSelectionClipboard call is the
+ // more correct thing to do.
+ //
+ // if this fails in any way other than "not being unix", we'll get
+ // the assertion we need in CopyStringToClipboard, and we needn't
+ // assert again here.
+ CopyStringToClipboard(aString, nsIClipboard::kSelectionClipboard);
+
+ return NS_OK;
+}
diff --git a/widget/nsClipboardHelper.h b/widget/nsClipboardHelper.h
new file mode 100644
index 0000000000..b9954e7bd4
--- /dev/null
+++ b/widget/nsClipboardHelper.h
@@ -0,0 +1,30 @@
+/* -*- 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 nsClipboardHelper_h__
+#define nsClipboardHelper_h__
+
+// interfaces
+#include "nsIClipboardHelper.h"
+
+// basics
+#include "nsString.h"
+
+/**
+ * impl class for nsIClipboardHelper, a helper for common uses of nsIClipboard.
+ */
+
+class nsClipboardHelper : public nsIClipboardHelper {
+ virtual ~nsClipboardHelper();
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICLIPBOARDHELPER
+
+ nsClipboardHelper();
+};
+
+#endif // nsClipboardHelper_h__
diff --git a/widget/nsClipboardProxy.cpp b/widget/nsClipboardProxy.cpp
new file mode 100644
index 0000000000..d2d90e47f4
--- /dev/null
+++ b/widget/nsClipboardProxy.cpp
@@ -0,0 +1,140 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/ContentChild.h"
+#include "mozilla/Unused.h"
+#include "nsArrayUtils.h"
+#include "nsClipboardProxy.h"
+#include "nsISupportsPrimitives.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsXULAppAPI.h"
+#include "nsContentUtils.h"
+#include "nsStringStream.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsClipboardProxy, nsIClipboard, nsIClipboardProxy)
+
+nsClipboardProxy::nsClipboardProxy() : mClipboardCaps(false, false) {}
+
+NS_IMETHODIMP
+nsClipboardProxy::SetData(nsITransferable* aTransferable,
+ nsIClipboardOwner* anOwner, int32_t aWhichClipboard) {
+ ContentChild* child = ContentChild::GetSingleton();
+
+ IPCDataTransfer ipcDataTransfer;
+ nsContentUtils::TransferableToIPCTransferable(aTransferable, &ipcDataTransfer,
+ false, child, nullptr);
+
+ bool isPrivateData = aTransferable->GetIsPrivateData();
+ nsCOMPtr<nsIPrincipal> requestingPrincipal =
+ aTransferable->GetRequestingPrincipal();
+ nsContentPolicyType contentPolicyType = aTransferable->GetContentPolicyType();
+ child->SendSetClipboard(ipcDataTransfer, isPrivateData,
+ IPC::Principal(requestingPrincipal),
+ contentPolicyType, aWhichClipboard);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboardProxy::GetData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) {
+ nsTArray<nsCString> types;
+ aTransferable->FlavorsTransferableCanImport(types);
+
+ nsresult rv;
+ IPCDataTransfer dataTransfer;
+ ContentChild::GetSingleton()->SendGetClipboard(types, aWhichClipboard,
+ &dataTransfer);
+
+ auto& items = dataTransfer.items();
+ for (uint32_t j = 0; j < items.Length(); ++j) {
+ const IPCDataTransferItem& item = items[j];
+
+ if (item.data().type() == IPCDataTransferData::TnsString) {
+ nsCOMPtr<nsISupportsString> dataWrapper =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const nsString& data = item.data().get_nsString();
+ rv = dataWrapper->SetData(data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aTransferable->SetTransferData(item.flavor().get(), dataWrapper);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (item.data().type() == IPCDataTransferData::TShmem) {
+ // If this is an image, convert it into an nsIInputStream.
+ const nsCString& flavor = item.flavor();
+ mozilla::ipc::Shmem data = item.data().get_Shmem();
+ if (flavor.EqualsLiteral(kJPEGImageMime) ||
+ flavor.EqualsLiteral(kJPGImageMime) ||
+ flavor.EqualsLiteral(kPNGImageMime) ||
+ flavor.EqualsLiteral(kGIFImageMime)) {
+ nsCOMPtr<nsIInputStream> stream;
+
+ NS_NewCStringInputStream(
+ getter_AddRefs(stream),
+ nsDependentCSubstring(data.get<char>(), data.Size<char>()));
+
+ rv = aTransferable->SetTransferData(flavor.get(), stream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (flavor.EqualsLiteral(kNativeHTMLMime) ||
+ flavor.EqualsLiteral(kRTFMime) ||
+ flavor.EqualsLiteral(kCustomTypesMime)) {
+ nsCOMPtr<nsISupportsCString> dataWrapper =
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dataWrapper->SetData(
+ nsDependentCSubstring(data.get<char>(), data.Size<char>()));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aTransferable->SetTransferData(item.flavor().get(), dataWrapper);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mozilla::Unused << ContentChild::GetSingleton()->DeallocShmem(data);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboardProxy::EmptyClipboard(int32_t aWhichClipboard) {
+ ContentChild::GetSingleton()->SendEmptyClipboard(aWhichClipboard);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboardProxy::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
+ int32_t aWhichClipboard,
+ bool* aHasType) {
+ *aHasType = false;
+
+ ContentChild::GetSingleton()->SendClipboardHasType(aFlavorList,
+ aWhichClipboard, aHasType);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboardProxy::SupportsSelectionClipboard(bool* aIsSupported) {
+ *aIsSupported = mClipboardCaps.supportsSelectionClipboard();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboardProxy::SupportsFindClipboard(bool* aIsSupported) {
+ *aIsSupported = mClipboardCaps.supportsFindClipboard();
+ return NS_OK;
+}
+
+void nsClipboardProxy::SetCapabilities(
+ const ClipboardCapabilities& aClipboardCaps) {
+ mClipboardCaps = aClipboardCaps;
+}
diff --git a/widget/nsClipboardProxy.h b/widget/nsClipboardProxy.h
new file mode 100644
index 0000000000..c25ad8ab70
--- /dev/null
+++ b/widget/nsClipboardProxy.h
@@ -0,0 +1,48 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 NS_CLIPBOARD_PROXY_H
+#define NS_CLIPBOARD_PROXY_H
+
+#include "nsIClipboard.h"
+#include "mozilla/dom/PContent.h"
+
+#define NS_CLIPBOARDPROXY_IID \
+ { \
+ 0xa64c82da, 0x7326, 0x4681, { \
+ 0xa0, 0x95, 0x81, 0x2c, 0xc9, 0x86, 0xe6, 0xde \
+ } \
+ }
+
+// Hack for ContentChild to be able to know that we're an nsClipboardProxy.
+class nsIClipboardProxy : public nsIClipboard {
+ protected:
+ typedef mozilla::dom::ClipboardCapabilities ClipboardCapabilities;
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_CLIPBOARDPROXY_IID)
+
+ virtual void SetCapabilities(const ClipboardCapabilities& aClipboardCaps) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIClipboardProxy, NS_CLIPBOARDPROXY_IID)
+
+class nsClipboardProxy final : public nsIClipboardProxy {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICLIPBOARD
+
+ nsClipboardProxy();
+
+ virtual void SetCapabilities(
+ const ClipboardCapabilities& aClipboardCaps) override;
+
+ private:
+ ~nsClipboardProxy() = default;
+
+ ClipboardCapabilities mClipboardCaps;
+};
+
+#endif
diff --git a/widget/nsColorPickerProxy.cpp b/widget/nsColorPickerProxy.cpp
new file mode 100644
index 0000000000..5a9e5c13a4
--- /dev/null
+++ b/widget/nsColorPickerProxy.cpp
@@ -0,0 +1,53 @@
+/* -*- 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 "nsColorPickerProxy.h"
+
+#include "mozilla/dom/BrowserChild.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsColorPickerProxy, nsIColorPicker)
+
+NS_IMETHODIMP
+nsColorPickerProxy::Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ const nsAString& aInitialColor) {
+ BrowserChild* browserChild = BrowserChild::GetFrom(aParent);
+ if (!browserChild) {
+ return NS_ERROR_FAILURE;
+ }
+
+ browserChild->SendPColorPickerConstructor(this, nsString(aTitle),
+ nsString(aInitialColor));
+ NS_ADDREF_THIS();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsColorPickerProxy::Open(
+ nsIColorPickerShownCallback* aColorPickerShownCallback) {
+ NS_ENSURE_STATE(!mCallback);
+ mCallback = aColorPickerShownCallback;
+
+ SendOpen();
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult nsColorPickerProxy::RecvUpdate(const nsString& aColor) {
+ if (mCallback) {
+ mCallback->Update(aColor);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult nsColorPickerProxy::Recv__delete__(
+ const nsString& aColor) {
+ if (mCallback) {
+ mCallback->Done(aColor);
+ mCallback = nullptr;
+ }
+ return IPC_OK();
+}
diff --git a/widget/nsColorPickerProxy.h b/widget/nsColorPickerProxy.h
new file mode 100644
index 0000000000..439b8cdfc1
--- /dev/null
+++ b/widget/nsColorPickerProxy.h
@@ -0,0 +1,33 @@
+/* -*- 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 nsColorPickerProxy_h
+#define nsColorPickerProxy_h
+
+#include "nsIColorPicker.h"
+
+#include "mozilla/dom/PColorPickerChild.h"
+
+class nsColorPickerProxy final : public nsIColorPicker,
+ public mozilla::dom::PColorPickerChild {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOLORPICKER
+
+ nsColorPickerProxy() = default;
+
+ virtual mozilla::ipc::IPCResult RecvUpdate(const nsString& aColor) override;
+ virtual mozilla::ipc::IPCResult Recv__delete__(
+ const nsString& aColor) override;
+
+ private:
+ ~nsColorPickerProxy() = default;
+
+ nsCOMPtr<nsIColorPickerShownCallback> mCallback;
+ nsString mTitle;
+ nsString mInitialColor;
+};
+
+#endif // nsColorPickerProxy_h
diff --git a/widget/nsContentProcessWidgetFactory.cpp b/widget/nsContentProcessWidgetFactory.cpp
new file mode 100644
index 0000000000..70d91f1f1d
--- /dev/null
+++ b/widget/nsContentProcessWidgetFactory.cpp
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "mozilla/ModuleUtils.h"
+#include "nsWidgetsCID.h"
+#include "nsClipboardProxy.h"
+#include "nsColorPickerProxy.h"
+#include "nsDragServiceProxy.h"
+#include "nsFilePickerProxy.h"
+#include "nsSoundProxy.h"
+#include "mozilla/widget/ScreenManager.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardProxy)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsColorPickerProxy)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDragServiceProxy)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFilePickerProxy)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSoundProxy)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(ScreenManager,
+ ScreenManager::GetAddRefedSingleton)
+
+NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_COLORPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_FILEPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_SOUND_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ {&kNS_CLIPBOARD_CID, false, nullptr, nsClipboardProxyConstructor,
+ Module::CONTENT_PROCESS_ONLY},
+ {&kNS_COLORPICKER_CID, false, nullptr, nsColorPickerProxyConstructor,
+ Module::CONTENT_PROCESS_ONLY},
+ {&kNS_DRAGSERVICE_CID, false, nullptr, nsDragServiceProxyConstructor,
+ Module::CONTENT_PROCESS_ONLY},
+ {&kNS_FILEPICKER_CID, false, nullptr, nsFilePickerProxyConstructor,
+ Module::CONTENT_PROCESS_ONLY},
+ {&kNS_SOUND_CID, false, nullptr, nsSoundProxyConstructor,
+ Module::CONTENT_PROCESS_ONLY},
+ {&kNS_SCREENMANAGER_CID, false, nullptr, ScreenManagerConstructor,
+ Module::CONTENT_PROCESS_ONLY},
+ {nullptr}};
+
+static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
+ {"@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID,
+ Module::CONTENT_PROCESS_ONLY},
+ {"@mozilla.org/colorpicker;1", &kNS_COLORPICKER_CID,
+ Module::CONTENT_PROCESS_ONLY},
+ {"@mozilla.org/filepicker;1", &kNS_FILEPICKER_CID,
+ Module::CONTENT_PROCESS_ONLY},
+ {"@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID,
+ Module::CONTENT_PROCESS_ONLY},
+ {"@mozilla.org/sound;1", &kNS_SOUND_CID, Module::CONTENT_PROCESS_ONLY},
+ {"@mozilla.org/widget/dragservice;1", &kNS_DRAGSERVICE_CID,
+ Module::CONTENT_PROCESS_ONLY},
+ {nullptr}};
+
+extern const mozilla::Module kContentProcessWidgetModule = {
+ mozilla::Module::kVersion, kWidgetCIDs, kWidgetContracts};
diff --git a/widget/nsDeviceContextSpecProxy.cpp b/widget/nsDeviceContextSpecProxy.cpp
new file mode 100644
index 0000000000..739aee36a8
--- /dev/null
+++ b/widget/nsDeviceContextSpecProxy.cpp
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsDeviceContextSpecProxy.h"
+
+#include "gfxASurface.h"
+#include "gfxPlatform.h"
+#include "mozilla/gfx/DrawEventRecorder.h"
+#include "mozilla/gfx/PrintTargetThebes.h"
+#include "mozilla/layout/RemotePrintJobChild.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Unused.h"
+#include "nsComponentManagerUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIPrintSession.h"
+#include "nsIPrintSettings.h"
+#include "private/pprio.h"
+
+using mozilla::Unused;
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+NS_IMPL_ISUPPORTS(nsDeviceContextSpecProxy, nsIDeviceContextSpec)
+
+NS_IMETHODIMP
+nsDeviceContextSpecProxy::Init(nsIWidget* aWidget,
+ nsIPrintSettings* aPrintSettings,
+ bool aIsPrintPreview) {
+ nsresult rv;
+ mRealDeviceContextSpec =
+ do_CreateInstance("@mozilla.org/gfx/devicecontextspec;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mRealDeviceContextSpec->Init(nullptr, aPrintSettings, aIsPrintPreview);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mRealDeviceContextSpec = nullptr;
+ return rv;
+ }
+
+ mPrintSettings = aPrintSettings;
+
+ if (aIsPrintPreview) {
+ return NS_OK;
+ }
+
+ // nsIPrintSettings only has a weak reference to nsIPrintSession, so we hold
+ // it to make sure it's available for the lifetime of the print.
+ rv = mPrintSettings->GetPrintSession(getter_AddRefs(mPrintSession));
+ if (NS_FAILED(rv) || !mPrintSession) {
+ NS_WARNING("We can't print via the parent without an nsIPrintSession.");
+ return NS_ERROR_FAILURE;
+ }
+
+ mRemotePrintJob = mPrintSession->GetRemotePrintJob();
+ if (!mRemotePrintJob) {
+ NS_WARNING("We can't print via the parent without a RemotePrintJobChild.");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<PrintTarget> nsDeviceContextSpecProxy::MakePrintTarget() {
+ MOZ_ASSERT(mRealDeviceContextSpec);
+
+ double width, height;
+ mPrintSettings->GetEffectiveSheetSize(&width, &height);
+ if (width <= 0 || height <= 0) {
+ return nullptr;
+ }
+
+ // convert twips to points
+ width /= TWIPS_PER_POINT_FLOAT;
+ height /= TWIPS_PER_POINT_FLOAT;
+
+ RefPtr<gfxASurface> surface =
+ gfxPlatform::GetPlatform()->CreateOffscreenSurface(
+ mozilla::gfx::IntSize::Ceil(width, height),
+ mozilla::gfx::SurfaceFormat::A8R8G8B8_UINT32);
+ if (!surface) {
+ return nullptr;
+ }
+
+ // The type of PrintTarget that we return here shouldn't really matter since
+ // our implementation of GetDrawEventRecorder returns an object, which means
+ // the DrawTarget returned by the PrintTarget will be a
+ // DrawTargetWrapAndRecord. The recording will be serialized and sent over to
+ // the parent process where PrintTranslator::TranslateRecording will call
+ // MakePrintTarget (indirectly via PrintTranslator::CreateDrawTarget) on
+ // whatever type of nsIDeviceContextSpecProxy is created for the platform that
+ // we are running on. It is that DrawTarget that the recording will be
+ // replayed on to print.
+ // XXX(jwatt): The above isn't quite true. We do want to use a
+ // PrintTargetRecording here, but we can't until bug 1280324 is figured out
+ // and fixed otherwise we will cause bug 1280181 to happen again.
+ RefPtr<PrintTarget> target = PrintTargetThebes::CreateOrNull(surface);
+
+ return target.forget();
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecProxy::GetDrawEventRecorder(
+ mozilla::gfx::DrawEventRecorder** aDrawEventRecorder) {
+ MOZ_ASSERT(aDrawEventRecorder);
+ RefPtr<mozilla::gfx::DrawEventRecorder> result = mRecorder;
+ result.forget(aDrawEventRecorder);
+ return NS_OK;
+}
+
+float nsDeviceContextSpecProxy::GetDPI() {
+ MOZ_ASSERT(mRealDeviceContextSpec);
+
+ return mRealDeviceContextSpec->GetDPI();
+}
+
+float nsDeviceContextSpecProxy::GetPrintingScale() {
+ MOZ_ASSERT(mRealDeviceContextSpec);
+
+ return mRealDeviceContextSpec->GetPrintingScale();
+}
+
+gfxPoint nsDeviceContextSpecProxy::GetPrintingTranslate() {
+ MOZ_ASSERT(mRealDeviceContextSpec);
+
+ return mRealDeviceContextSpec->GetPrintingTranslate();
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecProxy::BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) {
+ mRecorder = new mozilla::layout::DrawEventRecorderPRFileDesc();
+ nsresult rv = mRemotePrintJob->InitializePrint(
+ nsString(aTitle), nsString(aPrintToFileName), aStartPage, aEndPage);
+ if (NS_FAILED(rv)) {
+ // The parent process will send a 'delete' message to tell this process to
+ // delete our RemotePrintJobChild. As soon as we return to the event loop
+ // and evaluate that message we will crash if we try to access
+ // mRemotePrintJob. We must not try to use it again.
+ mRemotePrintJob = nullptr;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecProxy::EndDocument() {
+ if (mRemotePrintJob) {
+ Unused << mRemotePrintJob->SendFinalizePrint();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecProxy::AbortDocument() {
+ if (mRemotePrintJob) {
+ Unused << mRemotePrintJob->SendAbortPrint(NS_OK);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecProxy::BeginPage() {
+ mRecorder->OpenFD(mRemotePrintJob->GetNextPageFD());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecProxy::EndPage() {
+ // Send the page recording to the parent.
+ mRecorder->Close();
+ mRemotePrintJob->ProcessPage(std::move(mRecorder->TakeDependentSurfaces()));
+
+ return NS_OK;
+}
diff --git a/widget/nsDeviceContextSpecProxy.h b/widget/nsDeviceContextSpecProxy.h
new file mode 100644
index 0000000000..d82b182c97
--- /dev/null
+++ b/widget/nsDeviceContextSpecProxy.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsDeviceContextSpecProxy_h
+#define nsDeviceContextSpecProxy_h
+
+#include "nsIDeviceContextSpec.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "mozilla/layout/printing/DrawEventRecorder.h"
+
+class nsIFile;
+class nsIPrintSession;
+class nsIUUIDGenerator;
+
+namespace mozilla {
+namespace layout {
+class RemotePrintJobChild;
+}
+} // namespace mozilla
+
+class nsDeviceContextSpecProxy final : public nsIDeviceContextSpec {
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init(nsIWidget* aWidget, nsIPrintSettings* aPrintSettings,
+ bool aIsPrintPreview) final;
+
+ already_AddRefed<PrintTarget> MakePrintTarget() final;
+
+ NS_IMETHOD GetDrawEventRecorder(
+ mozilla::gfx::DrawEventRecorder** aDrawEventRecorder) final;
+
+ float GetDPI() final;
+
+ float GetPrintingScale() final;
+
+ gfxPoint GetPrintingTranslate() final;
+
+ NS_IMETHOD BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) final;
+
+ NS_IMETHOD EndDocument() final;
+
+ NS_IMETHOD AbortDocument() final;
+
+ NS_IMETHOD BeginPage() final;
+
+ NS_IMETHOD EndPage() final;
+
+ private:
+ ~nsDeviceContextSpecProxy() = default;
+
+ nsCOMPtr<nsIPrintSettings> mPrintSettings;
+ nsCOMPtr<nsIPrintSession> mPrintSession;
+ nsCOMPtr<nsIDeviceContextSpec> mRealDeviceContextSpec;
+ RefPtr<mozilla::layout::RemotePrintJobChild> mRemotePrintJob;
+ RefPtr<mozilla::layout::DrawEventRecorderPRFileDesc> mRecorder;
+};
+
+#endif // nsDeviceContextSpecProxy_h
diff --git a/widget/nsDragServiceProxy.cpp b/widget/nsDragServiceProxy.cpp
new file mode 100644
index 0000000000..428b114ce5
--- /dev/null
+++ b/widget/nsDragServiceProxy.cpp
@@ -0,0 +1,95 @@
+/* -*- 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 "nsDragServiceProxy.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+
+using mozilla::CSSIntRegion;
+using mozilla::LayoutDeviceIntRect;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+using mozilla::dom::BrowserChild;
+using mozilla::gfx::DataSourceSurface;
+using mozilla::gfx::SourceSurface;
+using mozilla::gfx::SurfaceFormat;
+using mozilla::ipc::Shmem;
+
+nsDragServiceProxy::nsDragServiceProxy() = default;
+
+nsDragServiceProxy::~nsDragServiceProxy() = default;
+
+nsresult nsDragServiceProxy::InvokeDragSessionImpl(
+ nsIArray* aArrayTransferables, const Maybe<CSSIntRegion>& aRegion,
+ uint32_t aActionType) {
+ NS_ENSURE_STATE(mSourceDocument->GetDocShell());
+ BrowserChild* child = BrowserChild::GetFrom(mSourceDocument->GetDocShell());
+ NS_ENSURE_STATE(child);
+ nsTArray<mozilla::dom::IPCDataTransfer> dataTransfers;
+ nsContentUtils::TransferablesToIPCTransferables(
+ aArrayTransferables, dataTransfers, false, child->Manager(), nullptr);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ if (mSourceNode) {
+ principal = mSourceNode->NodePrincipal();
+ }
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ if (mSourceDocument) {
+ csp = mSourceDocument->GetCsp();
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ cookieJarSettings = mSourceDocument->CookieJarSettings();
+ net::CookieJarSettingsArgs csArgs;
+ net::CookieJarSettings::Cast(cookieJarSettings)->Serialize(csArgs);
+
+ LayoutDeviceIntRect dragRect;
+ if (mHasImage || mSelection) {
+ nsPresContext* pc;
+ RefPtr<SourceSurface> surface;
+ DrawDrag(mSourceNode, aRegion, mScreenPosition, &dragRect, &surface, &pc);
+
+ if (surface) {
+ RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
+ if (dataSurface) {
+ size_t length;
+ int32_t stride;
+ Maybe<Shmem> maybeShm = nsContentUtils::GetSurfaceData(
+ dataSurface, &length, &stride, child);
+ if (maybeShm.isNothing()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto surfaceData = maybeShm.value();
+
+ // Save the surface data to shared memory.
+ if (!surfaceData.IsReadable() || !surfaceData.get<char>()) {
+ NS_WARNING("Failed to create shared memory for drag session.");
+ return NS_ERROR_FAILURE;
+ }
+
+ mozilla::Unused << child->SendInvokeDragSession(
+ dataTransfers, aActionType, Some(std::move(surfaceData)), stride,
+ dataSurface->GetFormat(), dragRect, principal, csp, csArgs);
+ StartDragSession();
+ return NS_OK;
+ }
+ }
+ }
+
+ mozilla::Unused << child->SendInvokeDragSession(
+ dataTransfers, aActionType, Nothing(), 0, static_cast<SurfaceFormat>(0),
+ dragRect, principal, csp, csArgs);
+ StartDragSession();
+ return NS_OK;
+}
diff --git a/widget/nsDragServiceProxy.h b/widget/nsDragServiceProxy.h
new file mode 100644
index 0000000000..15aa6fa23e
--- /dev/null
+++ b/widget/nsDragServiceProxy.h
@@ -0,0 +1,27 @@
+/* -*- 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 NSDRAGSERVICEPROXY_H
+#define NSDRAGSERVICEPROXY_H
+
+#include "nsBaseDragService.h"
+
+class nsDragServiceProxy : public nsBaseDragService {
+ public:
+ nsDragServiceProxy();
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsDragServiceProxy, nsBaseDragService)
+
+ // nsBaseDragService
+ virtual nsresult InvokeDragSessionImpl(
+ nsIArray* anArrayTransferables,
+ const mozilla::Maybe<mozilla::CSSIntRegion>& aRegion,
+ uint32_t aActionType) override;
+
+ private:
+ virtual ~nsDragServiceProxy();
+};
+
+#endif // NSDRAGSERVICEPROXY_H
diff --git a/widget/nsFilePickerProxy.cpp b/widget/nsFilePickerProxy.cpp
new file mode 100644
index 0000000000..f140e60ef7
--- /dev/null
+++ b/widget/nsFilePickerProxy.cpp
@@ -0,0 +1,276 @@
+/* -*- 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 "nsFilePickerProxy.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIFile.h"
+#include "nsSimpleEnumerator.h"
+#include "mozilla/dom/BlobImpl.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/IPCBlobUtils.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsFilePickerProxy, nsIFilePicker)
+
+nsFilePickerProxy::nsFilePickerProxy()
+ : mSelectedType(0), mCapture(captureNone), mIPCActive(false) {}
+
+nsFilePickerProxy::~nsFilePickerProxy() = default;
+
+NS_IMETHODIMP
+nsFilePickerProxy::Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ int16_t aMode) {
+ BrowserChild* browserChild = BrowserChild::GetFrom(aParent);
+ if (!browserChild) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mParent = nsPIDOMWindowOuter::From(aParent);
+
+ mMode = aMode;
+
+ NS_ADDREF_THIS();
+ browserChild->SendPFilePickerConstructor(this, nsString(aTitle), aMode);
+
+ mIPCActive = true;
+ return NS_OK;
+}
+
+void nsFilePickerProxy::InitNative(nsIWidget* aParent,
+ const nsAString& aTitle) {}
+
+NS_IMETHODIMP
+nsFilePickerProxy::AppendFilter(const nsAString& aTitle,
+ const nsAString& aFilter) {
+ mFilterNames.AppendElement(aTitle);
+ mFilters.AppendElement(aFilter);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::GetCapture(int16_t* aCapture) {
+ *aCapture = mCapture;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::SetCapture(int16_t aCapture) {
+ mCapture = aCapture;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::GetDefaultString(nsAString& aDefaultString) {
+ aDefaultString = mDefault;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::SetDefaultString(const nsAString& aDefaultString) {
+ mDefault = aDefaultString;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::GetDefaultExtension(nsAString& aDefaultExtension) {
+ aDefaultExtension = mDefaultExtension;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::SetDefaultExtension(const nsAString& aDefaultExtension) {
+ mDefaultExtension = aDefaultExtension;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::GetFilterIndex(int32_t* aFilterIndex) {
+ *aFilterIndex = mSelectedType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::SetFilterIndex(int32_t aFilterIndex) {
+ mSelectedType = aFilterIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::GetFile(nsIFile** aFile) {
+ MOZ_ASSERT(false, "GetFile is unimplemented; use GetDomFileOrDirectory");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::GetFileURL(nsIURI** aFileURL) {
+ MOZ_ASSERT(false, "GetFileURL is unimplemented; use GetDomFileOrDirectory");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::GetFiles(nsISimpleEnumerator** aFiles) {
+ MOZ_ASSERT(false,
+ "GetFiles is unimplemented; use GetDomFileOrDirectoryEnumerator");
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsFilePickerProxy::Show(int16_t* aReturn) {
+ MOZ_ASSERT(false, "Show is unimplemented; use Open");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::Open(nsIFilePickerShownCallback* aCallback) {
+ mCallback = aCallback;
+
+ nsString displayDirectory;
+ if (mDisplayDirectory) {
+ mDisplayDirectory->GetPath(displayDirectory);
+ }
+
+ if (!mIPCActive) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SendOpen(mSelectedType, mAddToRecentDocs, mDefault, mDefaultExtension,
+ mFilters, mFilterNames, mRawFilters, displayDirectory,
+ mDisplaySpecialDirectory, mOkButtonLabel, mCapture);
+
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult nsFilePickerProxy::Recv__delete__(
+ const MaybeInputData& aData, const int16_t& aResult) {
+ nsPIDOMWindowInner* inner =
+ mParent ? mParent->GetCurrentInnerWindow() : nullptr;
+
+ if (NS_WARN_IF(!inner)) {
+ return IPC_OK();
+ }
+
+ if (aData.type() == MaybeInputData::TInputBlobs) {
+ const nsTArray<IPCBlob>& blobs = aData.get_InputBlobs().blobs();
+ for (uint32_t i = 0; i < blobs.Length(); ++i) {
+ RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(blobs[i]);
+ NS_ENSURE_TRUE(blobImpl, IPC_OK());
+
+ if (!blobImpl->IsFile()) {
+ return IPC_OK();
+ }
+
+ RefPtr<File> file = File::Create(inner->AsGlobal(), blobImpl);
+ if (NS_WARN_IF(!file)) {
+ return IPC_OK();
+ }
+
+ OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement();
+ element->SetAsFile() = file;
+ }
+ } else if (aData.type() == MaybeInputData::TInputDirectory) {
+ nsCOMPtr<nsIFile> file;
+ const nsAString& path(aData.get_InputDirectory().directoryPath());
+ nsresult rv = NS_NewLocalFile(path, true, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return IPC_OK();
+ }
+
+ RefPtr<Directory> directory = Directory::Create(inner->AsGlobal(), file);
+ MOZ_ASSERT(directory);
+
+ OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement();
+ element->SetAsDirectory() = directory;
+ }
+
+ if (mCallback) {
+ mCallback->Done(aResult);
+ mCallback = nullptr;
+ }
+
+ return IPC_OK();
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::GetDomFileOrDirectory(nsISupports** aValue) {
+ *aValue = nullptr;
+ if (mFilesOrDirectories.IsEmpty()) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mFilesOrDirectories.Length() == 1);
+
+ if (mFilesOrDirectories[0].IsFile()) {
+ nsCOMPtr<nsISupports> blob = ToSupports(mFilesOrDirectories[0].GetAsFile());
+ blob.forget(aValue);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mFilesOrDirectories[0].IsDirectory());
+ RefPtr<Directory> directory = mFilesOrDirectories[0].GetAsDirectory();
+ directory.forget(aValue);
+ return NS_OK;
+}
+
+namespace {
+
+class SimpleEnumerator final : public nsSimpleEnumerator {
+ public:
+ explicit SimpleEnumerator(
+ const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
+ : mFilesOrDirectories(aFilesOrDirectories.Clone()), mIndex(0) {}
+
+ NS_IMETHOD
+ HasMoreElements(bool* aRetvalue) override {
+ MOZ_ASSERT(aRetvalue);
+ *aRetvalue = mIndex < mFilesOrDirectories.Length();
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetNext(nsISupports** aValue) override {
+ NS_ENSURE_TRUE(mIndex < mFilesOrDirectories.Length(), NS_ERROR_FAILURE);
+
+ uint32_t index = mIndex++;
+
+ if (mFilesOrDirectories[index].IsFile()) {
+ nsCOMPtr<nsISupports> blob =
+ ToSupports(mFilesOrDirectories[index].GetAsFile());
+ blob.forget(aValue);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mFilesOrDirectories[index].IsDirectory());
+ RefPtr<Directory> directory = mFilesOrDirectories[index].GetAsDirectory();
+ directory.forget(aValue);
+ return NS_OK;
+ }
+
+ private:
+ nsTArray<mozilla::dom::OwningFileOrDirectory> mFilesOrDirectories;
+ uint32_t mIndex;
+};
+
+} // namespace
+
+NS_IMETHODIMP
+nsFilePickerProxy::GetDomFileOrDirectoryEnumerator(
+ nsISimpleEnumerator** aDomfiles) {
+ RefPtr<SimpleEnumerator> enumerator =
+ new SimpleEnumerator(mFilesOrDirectories);
+ enumerator.forget(aDomfiles);
+ return NS_OK;
+}
+
+void nsFilePickerProxy::ActorDestroy(ActorDestroyReason aWhy) {
+ mIPCActive = false;
+
+ if (mCallback) {
+ mCallback->Done(nsIFilePicker::returnCancel);
+ mCallback = nullptr;
+ }
+}
diff --git a/widget/nsFilePickerProxy.h b/widget/nsFilePickerProxy.h
new file mode 100644
index 0000000000..6e3e3eb5ee
--- /dev/null
+++ b/widget/nsFilePickerProxy.h
@@ -0,0 +1,84 @@
+/* -*- 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 NSFILEPICKERPROXY_H
+#define NSFILEPICKERPROXY_H
+
+#include "nsBaseFilePicker.h"
+#include "nsString.h"
+#include "nsIURI.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+
+#include "mozilla/dom/PFilePickerChild.h"
+#include "mozilla/dom/UnionTypes.h"
+
+class nsIWidget;
+class nsIFile;
+class nsPIDOMWindowInner;
+
+/**
+ This class creates a proxy file picker to be used in content processes.
+ The file picker just collects the initialization data and when Show() is
+ called, remotes everything to the chrome process which in turn can show a
+ platform specific file picker.
+*/
+class nsFilePickerProxy : public nsBaseFilePicker,
+ public mozilla::dom::PFilePickerChild {
+ public:
+ nsFilePickerProxy();
+
+ NS_DECL_ISUPPORTS
+
+ // nsIFilePicker (less what's in nsBaseFilePicker)
+ NS_IMETHOD Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ int16_t aMode) override;
+ NS_IMETHOD AppendFilter(const nsAString& aTitle,
+ const nsAString& aFilter) override;
+ NS_IMETHOD GetCapture(int16_t* aCapture) override;
+ NS_IMETHOD SetCapture(int16_t aCapture) override;
+ NS_IMETHOD GetDefaultString(nsAString& aDefaultString) override;
+ NS_IMETHOD SetDefaultString(const nsAString& aDefaultString) override;
+ NS_IMETHOD GetDefaultExtension(nsAString& aDefaultExtension) override;
+ NS_IMETHOD SetDefaultExtension(const nsAString& aDefaultExtension) override;
+ NS_IMETHOD GetFilterIndex(int32_t* aFilterIndex) override;
+ NS_IMETHOD SetFilterIndex(int32_t aFilterIndex) override;
+ NS_IMETHOD GetFile(nsIFile** aFile) override;
+ NS_IMETHOD GetFileURL(nsIURI** aFileURL) override;
+ NS_IMETHOD GetFiles(nsISimpleEnumerator** aFiles) override;
+
+ NS_IMETHOD GetDomFileOrDirectory(nsISupports** aValue) override;
+ NS_IMETHOD GetDomFileOrDirectoryEnumerator(
+ nsISimpleEnumerator** aValue) override;
+
+ NS_IMETHOD Open(nsIFilePickerShownCallback* aCallback) override;
+
+ // PFilePickerChild
+ virtual mozilla::ipc::IPCResult Recv__delete__(
+ const MaybeInputData& aData, const int16_t& aResult) override;
+
+ private:
+ ~nsFilePickerProxy();
+ void InitNative(nsIWidget*, const nsAString&) override;
+ nsresult Show(int16_t* aReturn) override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ nsTArray<mozilla::dom::OwningFileOrDirectory> mFilesOrDirectories;
+ nsCOMPtr<nsIFilePickerShownCallback> mCallback;
+
+ int16_t mSelectedType;
+ nsString mFile;
+ nsString mDefault;
+ nsString mDefaultExtension;
+ int16_t mCapture;
+
+ bool mIPCActive;
+
+ nsTArray<nsString> mFilters;
+ nsTArray<nsString> mFilterNames;
+};
+
+#endif // NSFILEPICKERPROXY_H
diff --git a/widget/nsGUIEventIPC.h b/widget/nsGUIEventIPC.h
new file mode 100644
index 0000000000..f3efea85bb
--- /dev/null
+++ b/widget/nsGUIEventIPC.h
@@ -0,0 +1,1455 @@
+/* -*- 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 nsGUIEventIPC_h__
+#define nsGUIEventIPC_h__
+
+#include "ipc/EnumSerializer.h"
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/ContentCache.h"
+#include "mozilla/GfxMessageUtils.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/layers/LayersMessageUtils.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/WheelHandlingHelper.h" // for WheelDeltaAdjustmentStrategy
+#include "mozilla/dom/Selection.h"
+#include "InputData.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::EventMessage>
+ : public ContiguousEnumSerializer<
+ mozilla::EventMessage, mozilla::EventMessage(0),
+ mozilla::EventMessage::eEventMessage_MaxValue> {};
+
+template <>
+struct ParamTraits<mozilla::BaseEventFlags> {
+ typedef mozilla::BaseEventFlags paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ aMsg->WriteBytes(&aParam, sizeof(aParam));
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return aMsg->ReadBytesInto(aIter, aResult, sizeof(*aResult));
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::WidgetEvent> {
+ typedef mozilla::WidgetEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ // Mark the event as posted to another process.
+ const_cast<mozilla::WidgetEvent&>(aParam).MarkAsPostedToRemoteProcess();
+
+ WriteParam(aMsg, static_cast<mozilla::EventClassIDType>(aParam.mClass));
+ WriteParam(aMsg, aParam.mMessage);
+ WriteParam(aMsg, aParam.mRefPoint);
+ WriteParam(aMsg, aParam.mFocusSequenceNumber);
+ WriteParam(aMsg, aParam.mTime);
+ WriteParam(aMsg, aParam.mTimeStamp);
+ WriteParam(aMsg, aParam.mFlags);
+ WriteParam(aMsg, aParam.mLayersId);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ mozilla::EventClassIDType eventClassID = 0;
+ bool ret = ReadParam(aMsg, aIter, &eventClassID) &&
+ ReadParam(aMsg, aIter, &aResult->mMessage) &&
+ ReadParam(aMsg, aIter, &aResult->mRefPoint) &&
+ ReadParam(aMsg, aIter, &aResult->mFocusSequenceNumber) &&
+ ReadParam(aMsg, aIter, &aResult->mTime) &&
+ ReadParam(aMsg, aIter, &aResult->mTimeStamp) &&
+ ReadParam(aMsg, aIter, &aResult->mFlags) &&
+ ReadParam(aMsg, aIter, &aResult->mLayersId);
+ aResult->mClass = static_cast<mozilla::EventClassID>(eventClassID);
+ if (ret) {
+ // Reset cross process dispatching state here because the event has not
+ // been dispatched to different process from current process.
+ aResult->ResetCrossProcessDispatchingState();
+ // Mark the event comes from another process.
+ aResult->MarkAsComingFromAnotherProcess();
+ }
+ return ret;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::NativeEventData> {
+ typedef mozilla::NativeEventData paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mBuffer);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mBuffer);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::WidgetGUIEvent> {
+ typedef mozilla::WidgetGUIEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::WidgetEvent&>(aParam));
+ WriteParam(aMsg, aParam.mPluginEvent);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetEvent*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mPluginEvent);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::WidgetInputEvent> {
+ typedef mozilla::WidgetInputEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::WidgetGUIEvent&>(aParam));
+ WriteParam(aMsg, aParam.mModifiers);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetGUIEvent*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mModifiers);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::WidgetMouseEventBase> {
+ typedef mozilla::WidgetMouseEventBase paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::WidgetInputEvent&>(aParam));
+ WriteParam(aMsg, aParam.mButton);
+ WriteParam(aMsg, aParam.mButtons);
+ WriteParam(aMsg, aParam.mPressure);
+ WriteParam(aMsg, aParam.mInputSource);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetInputEvent*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mButton) &&
+ ReadParam(aMsg, aIter, &aResult->mButtons) &&
+ ReadParam(aMsg, aIter, &aResult->mPressure) &&
+ ReadParam(aMsg, aIter, &aResult->mInputSource);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::WidgetWheelEvent> {
+ typedef mozilla::WidgetWheelEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::WidgetMouseEventBase&>(aParam));
+ WriteParam(aMsg, aParam.mDeltaX);
+ WriteParam(aMsg, aParam.mDeltaY);
+ WriteParam(aMsg, aParam.mDeltaZ);
+ WriteParam(aMsg, aParam.mDeltaMode);
+ WriteParam(aMsg, aParam.mCustomizedByUserPrefs);
+ WriteParam(aMsg, aParam.mMayHaveMomentum);
+ WriteParam(aMsg, aParam.mIsMomentum);
+ WriteParam(aMsg, aParam.mIsNoLineOrPageDelta);
+ WriteParam(aMsg, aParam.mLineOrPageDeltaX);
+ WriteParam(aMsg, aParam.mLineOrPageDeltaY);
+ WriteParam(aMsg, static_cast<uint8_t>(aParam.mScrollType));
+ WriteParam(aMsg, aParam.mOverflowDeltaX);
+ WriteParam(aMsg, aParam.mOverflowDeltaY);
+ WriteParam(aMsg, aParam.mViewPortIsOverscrolled);
+ WriteParam(aMsg, aParam.mCanTriggerSwipe);
+ WriteParam(aMsg, aParam.mAllowToOverrideSystemScrollSpeed);
+ WriteParam(aMsg, aParam.mDeltaValuesHorizontalizedForDefaultHandler);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ uint8_t scrollType = 0;
+ bool rv =
+ ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetMouseEventBase*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mDeltaX) &&
+ ReadParam(aMsg, aIter, &aResult->mDeltaY) &&
+ ReadParam(aMsg, aIter, &aResult->mDeltaZ) &&
+ ReadParam(aMsg, aIter, &aResult->mDeltaMode) &&
+ ReadParam(aMsg, aIter, &aResult->mCustomizedByUserPrefs) &&
+ ReadParam(aMsg, aIter, &aResult->mMayHaveMomentum) &&
+ ReadParam(aMsg, aIter, &aResult->mIsMomentum) &&
+ ReadParam(aMsg, aIter, &aResult->mIsNoLineOrPageDelta) &&
+ ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaX) &&
+ ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaY) &&
+ ReadParam(aMsg, aIter, &scrollType) &&
+ ReadParam(aMsg, aIter, &aResult->mOverflowDeltaX) &&
+ ReadParam(aMsg, aIter, &aResult->mOverflowDeltaY) &&
+ ReadParam(aMsg, aIter, &aResult->mViewPortIsOverscrolled) &&
+ ReadParam(aMsg, aIter, &aResult->mCanTriggerSwipe) &&
+ ReadParam(aMsg, aIter, &aResult->mAllowToOverrideSystemScrollSpeed) &&
+ ReadParam(aMsg, aIter,
+ &aResult->mDeltaValuesHorizontalizedForDefaultHandler);
+ aResult->mScrollType =
+ static_cast<mozilla::WidgetWheelEvent::ScrollType>(scrollType);
+ return rv;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::WidgetPointerHelper> {
+ typedef mozilla::WidgetPointerHelper paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.pointerId);
+ WriteParam(aMsg, aParam.tiltX);
+ WriteParam(aMsg, aParam.tiltY);
+ WriteParam(aMsg, aParam.twist);
+ WriteParam(aMsg, aParam.tangentialPressure);
+ // We don't serialize convertToPointer since it's temporarily variable and
+ // should be reset to default.
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ bool rv;
+ rv = ReadParam(aMsg, aIter, &aResult->pointerId) &&
+ ReadParam(aMsg, aIter, &aResult->tiltX) &&
+ ReadParam(aMsg, aIter, &aResult->tiltY) &&
+ ReadParam(aMsg, aIter, &aResult->twist) &&
+ ReadParam(aMsg, aIter, &aResult->tangentialPressure);
+ return rv;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::WidgetMouseEvent> {
+ typedef mozilla::WidgetMouseEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::WidgetMouseEventBase&>(aParam));
+ WriteParam(aMsg, static_cast<const mozilla::WidgetPointerHelper&>(aParam));
+ WriteParam(aMsg, aParam.mIgnoreRootScrollFrame);
+ WriteParam(aMsg, static_cast<paramType::ReasonType>(aParam.mReason));
+ WriteParam(aMsg, static_cast<paramType::ContextMenuTriggerType>(
+ aParam.mContextMenuTrigger));
+ WriteParam(aMsg, aParam.mExitFrom.isSome());
+ if (aParam.mExitFrom.isSome()) {
+ WriteParam(
+ aMsg, static_cast<paramType::ExitFromType>(aParam.mExitFrom.value()));
+ }
+ WriteParam(aMsg, aParam.mClickCount);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ bool rv;
+ paramType::ReasonType reason = 0;
+ paramType::ContextMenuTriggerType contextMenuTrigger = 0;
+ bool hasExitFrom = false;
+ rv = ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetMouseEventBase*>(aResult)) &&
+ ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetPointerHelper*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mIgnoreRootScrollFrame) &&
+ ReadParam(aMsg, aIter, &reason) &&
+ ReadParam(aMsg, aIter, &contextMenuTrigger);
+ aResult->mReason = static_cast<paramType::Reason>(reason);
+ aResult->mContextMenuTrigger =
+ static_cast<paramType::ContextMenuTrigger>(contextMenuTrigger);
+ rv = rv && ReadParam(aMsg, aIter, &hasExitFrom);
+ if (hasExitFrom) {
+ paramType::ExitFromType exitFrom = 0;
+ rv = rv && ReadParam(aMsg, aIter, &exitFrom);
+ aResult->mExitFrom = Some(static_cast<paramType::ExitFrom>(exitFrom));
+ }
+ rv = rv && ReadParam(aMsg, aIter, &aResult->mClickCount);
+ return rv;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::WidgetDragEvent> {
+ typedef mozilla::WidgetDragEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::WidgetMouseEvent&>(aParam));
+ WriteParam(aMsg, aParam.mUserCancelled);
+ WriteParam(aMsg, aParam.mDefaultPreventedOnContent);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ bool rv = ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetMouseEvent*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mUserCancelled) &&
+ ReadParam(aMsg, aIter, &aResult->mDefaultPreventedOnContent);
+ return rv;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::WidgetPointerEvent> {
+ typedef mozilla::WidgetPointerEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::WidgetMouseEvent&>(aParam));
+ WriteParam(aMsg, aParam.mWidth);
+ WriteParam(aMsg, aParam.mHeight);
+ WriteParam(aMsg, aParam.mIsPrimary);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ bool rv = ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetMouseEvent*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mWidth) &&
+ ReadParam(aMsg, aIter, &aResult->mHeight) &&
+ ReadParam(aMsg, aIter, &aResult->mIsPrimary);
+ return rv;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::WidgetTouchEvent> {
+ typedef mozilla::WidgetTouchEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::WidgetInputEvent&>(aParam));
+ // Sigh, Touch bites us again! We want to be able to do
+ // WriteParam(aMsg, aParam.mTouches);
+ const paramType::TouchArray& touches = aParam.mTouches;
+ WriteParam(aMsg, touches.Length());
+ for (uint32_t i = 0; i < touches.Length(); ++i) {
+ mozilla::dom::Touch* touch = touches[i];
+ WriteParam(aMsg, touch->mIdentifier);
+ WriteParam(aMsg, touch->mRefPoint);
+ WriteParam(aMsg, touch->mRadius);
+ WriteParam(aMsg, touch->mRotationAngle);
+ WriteParam(aMsg, touch->mForce);
+ }
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ paramType::TouchArray::size_type numTouches;
+ if (!ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetInputEvent*>(aResult)) ||
+ !ReadParam(aMsg, aIter, &numTouches)) {
+ return false;
+ }
+ for (uint32_t i = 0; i < numTouches; ++i) {
+ int32_t identifier;
+ mozilla::LayoutDeviceIntPoint refPoint;
+ mozilla::LayoutDeviceIntPoint radius;
+ float rotationAngle;
+ float force;
+ if (!ReadParam(aMsg, aIter, &identifier) ||
+ !ReadParam(aMsg, aIter, &refPoint) ||
+ !ReadParam(aMsg, aIter, &radius) ||
+ !ReadParam(aMsg, aIter, &rotationAngle) ||
+ !ReadParam(aMsg, aIter, &force)) {
+ return false;
+ }
+ aResult->mTouches.AppendElement(new mozilla::dom::Touch(
+ identifier, refPoint, radius, rotationAngle, force));
+ }
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::AlternativeCharCode> {
+ typedef mozilla::AlternativeCharCode paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mUnshiftedCharCode);
+ WriteParam(aMsg, aParam.mShiftedCharCode);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mUnshiftedCharCode) &&
+ ReadParam(aMsg, aIter, &aResult->mShiftedCharCode);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::ShortcutKeyCandidate> {
+ typedef mozilla::ShortcutKeyCandidate paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mCharCode);
+ WriteParam(aMsg, aParam.mIgnoreShift);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mCharCode) &&
+ ReadParam(aMsg, aIter, &aResult->mIgnoreShift);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::WidgetKeyboardEvent> {
+ typedef mozilla::WidgetKeyboardEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::WidgetInputEvent&>(aParam));
+ WriteParam(aMsg,
+ static_cast<mozilla::KeyNameIndexType>(aParam.mKeyNameIndex));
+ WriteParam(aMsg,
+ static_cast<mozilla::CodeNameIndexType>(aParam.mCodeNameIndex));
+ WriteParam(aMsg, aParam.mKeyValue);
+ WriteParam(aMsg, aParam.mCodeValue);
+ WriteParam(aMsg, aParam.mKeyCode);
+ WriteParam(aMsg, aParam.mCharCode);
+ WriteParam(aMsg, aParam.mPseudoCharCode);
+ WriteParam(aMsg, aParam.mAlternativeCharCodes);
+ WriteParam(aMsg, aParam.mIsRepeat);
+ WriteParam(aMsg, aParam.mLocation);
+ WriteParam(aMsg, aParam.mUniqueId);
+ WriteParam(aMsg, aParam.mIsSynthesizedByTIP);
+ WriteParam(aMsg, aParam.mMaybeSkippableInRemoteProcess);
+
+ // An OS-specific native event might be attached in |mNativeKeyEvent|, but
+ // that cannot be copied across process boundaries.
+
+ WriteParam(aMsg, aParam.mEditCommandsForSingleLineEditor);
+ WriteParam(aMsg, aParam.mEditCommandsForMultiLineEditor);
+ WriteParam(aMsg, aParam.mEditCommandsForRichTextEditor);
+ WriteParam(aMsg, aParam.mEditCommandsForSingleLineEditorInitialized);
+ WriteParam(aMsg, aParam.mEditCommandsForMultiLineEditorInitialized);
+ WriteParam(aMsg, aParam.mEditCommandsForRichTextEditorInitialized);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ mozilla::KeyNameIndexType keyNameIndex = 0;
+ mozilla::CodeNameIndexType codeNameIndex = 0;
+ if (ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetInputEvent*>(aResult)) &&
+ ReadParam(aMsg, aIter, &keyNameIndex) &&
+ ReadParam(aMsg, aIter, &codeNameIndex) &&
+ ReadParam(aMsg, aIter, &aResult->mKeyValue) &&
+ ReadParam(aMsg, aIter, &aResult->mCodeValue) &&
+ ReadParam(aMsg, aIter, &aResult->mKeyCode) &&
+ ReadParam(aMsg, aIter, &aResult->mCharCode) &&
+ ReadParam(aMsg, aIter, &aResult->mPseudoCharCode) &&
+ ReadParam(aMsg, aIter, &aResult->mAlternativeCharCodes) &&
+ ReadParam(aMsg, aIter, &aResult->mIsRepeat) &&
+ ReadParam(aMsg, aIter, &aResult->mLocation) &&
+ ReadParam(aMsg, aIter, &aResult->mUniqueId) &&
+ ReadParam(aMsg, aIter, &aResult->mIsSynthesizedByTIP) &&
+ ReadParam(aMsg, aIter, &aResult->mMaybeSkippableInRemoteProcess) &&
+ ReadParam(aMsg, aIter, &aResult->mEditCommandsForSingleLineEditor) &&
+ ReadParam(aMsg, aIter, &aResult->mEditCommandsForMultiLineEditor) &&
+ ReadParam(aMsg, aIter, &aResult->mEditCommandsForRichTextEditor) &&
+ ReadParam(aMsg, aIter,
+ &aResult->mEditCommandsForSingleLineEditorInitialized) &&
+ ReadParam(aMsg, aIter,
+ &aResult->mEditCommandsForMultiLineEditorInitialized) &&
+ ReadParam(aMsg, aIter,
+ &aResult->mEditCommandsForRichTextEditorInitialized)) {
+ aResult->mKeyNameIndex = static_cast<mozilla::KeyNameIndex>(keyNameIndex);
+ aResult->mCodeNameIndex =
+ static_cast<mozilla::CodeNameIndex>(codeNameIndex);
+ aResult->mNativeKeyEvent = nullptr;
+ return true;
+ }
+ return false;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::TextRangeStyle> {
+ typedef mozilla::TextRangeStyle paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mDefinedStyles);
+ WriteParam(aMsg, static_cast<mozilla::TextRangeStyle::LineStyleType>(
+ aParam.mLineStyle));
+ WriteParam(aMsg, aParam.mIsBoldLine);
+ WriteParam(aMsg, aParam.mForegroundColor);
+ WriteParam(aMsg, aParam.mBackgroundColor);
+ WriteParam(aMsg, aParam.mUnderlineColor);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ mozilla::TextRangeStyle::LineStyleType lineStyle;
+ if (!ReadParam(aMsg, aIter, &aResult->mDefinedStyles) ||
+ !ReadParam(aMsg, aIter, &lineStyle) ||
+ !ReadParam(aMsg, aIter, &aResult->mIsBoldLine) ||
+ !ReadParam(aMsg, aIter, &aResult->mForegroundColor) ||
+ !ReadParam(aMsg, aIter, &aResult->mBackgroundColor) ||
+ !ReadParam(aMsg, aIter, &aResult->mUnderlineColor)) {
+ return false;
+ }
+ aResult->mLineStyle = mozilla::TextRangeStyle::ToLineStyle(lineStyle);
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::TextRange> {
+ typedef mozilla::TextRange paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mStartOffset);
+ WriteParam(aMsg, aParam.mEndOffset);
+ WriteParam(aMsg, mozilla::ToRawTextRangeType(aParam.mRangeType));
+ WriteParam(aMsg, aParam.mRangeStyle);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ mozilla::RawTextRangeType rawTextRangeType;
+ if (ReadParam(aMsg, aIter, &aResult->mStartOffset) &&
+ ReadParam(aMsg, aIter, &aResult->mEndOffset) &&
+ ReadParam(aMsg, aIter, &rawTextRangeType) &&
+ ReadParam(aMsg, aIter, &aResult->mRangeStyle)) {
+ aResult->mRangeType = mozilla::ToTextRangeType(rawTextRangeType);
+ return true;
+ }
+ return false;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::TextRangeArray> {
+ typedef mozilla::TextRangeArray paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.Length());
+ for (uint32_t index = 0; index < aParam.Length(); index++) {
+ WriteParam(aMsg, aParam[index]);
+ }
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ paramType::size_type length;
+ if (!ReadParam(aMsg, aIter, &length)) {
+ return false;
+ }
+ for (uint32_t index = 0; index < length; index++) {
+ mozilla::TextRange textRange;
+ if (!ReadParam(aMsg, aIter, &textRange)) {
+ aResult->Clear();
+ return false;
+ }
+ aResult->AppendElement(textRange);
+ }
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::WidgetCompositionEvent> {
+ typedef mozilla::WidgetCompositionEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::WidgetGUIEvent&>(aParam));
+ WriteParam(aMsg, aParam.mData);
+ WriteParam(aMsg, aParam.mNativeIMEContext);
+ bool hasRanges = !!aParam.mRanges;
+ WriteParam(aMsg, hasRanges);
+ if (hasRanges) {
+ WriteParam(aMsg, *aParam.mRanges.get());
+ }
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ bool hasRanges;
+ if (!ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetGUIEvent*>(aResult)) ||
+ !ReadParam(aMsg, aIter, &aResult->mData) ||
+ !ReadParam(aMsg, aIter, &aResult->mNativeIMEContext) ||
+ !ReadParam(aMsg, aIter, &hasRanges)) {
+ return false;
+ }
+
+ if (!hasRanges) {
+ aResult->mRanges = nullptr;
+ } else {
+ aResult->mRanges = new mozilla::TextRangeArray();
+ if (!ReadParam(aMsg, aIter, aResult->mRanges.get())) {
+ return false;
+ }
+ }
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::FontRange> {
+ typedef mozilla::FontRange paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mStartOffset);
+ WriteParam(aMsg, aParam.mFontName);
+ WriteParam(aMsg, aParam.mFontSize);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mStartOffset) &&
+ ReadParam(aMsg, aIter, &aResult->mFontName) &&
+ ReadParam(aMsg, aIter, &aResult->mFontSize);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::WidgetSelectionEvent> {
+ typedef mozilla::WidgetSelectionEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::WidgetGUIEvent&>(aParam));
+ WriteParam(aMsg, aParam.mOffset);
+ WriteParam(aMsg, aParam.mLength);
+ WriteParam(aMsg, aParam.mReversed);
+ WriteParam(aMsg, aParam.mExpandToClusterBoundary);
+ WriteParam(aMsg, aParam.mSucceeded);
+ WriteParam(aMsg, aParam.mUseNativeLineBreak);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetGUIEvent*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mOffset) &&
+ ReadParam(aMsg, aIter, &aResult->mLength) &&
+ ReadParam(aMsg, aIter, &aResult->mReversed) &&
+ ReadParam(aMsg, aIter, &aResult->mExpandToClusterBoundary) &&
+ ReadParam(aMsg, aIter, &aResult->mSucceeded) &&
+ ReadParam(aMsg, aIter, &aResult->mUseNativeLineBreak);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::widget::IMENotificationRequests> {
+ typedef mozilla::widget::IMENotificationRequests paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mWantUpdates);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mWantUpdates);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::widget::NativeIMEContext> {
+ typedef mozilla::widget::NativeIMEContext paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mRawNativeIMEContext);
+ WriteParam(aMsg, aParam.mOriginProcessID);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mRawNativeIMEContext) &&
+ ReadParam(aMsg, aIter, &aResult->mOriginProcessID);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::widget::IMENotification::Point> {
+ typedef mozilla::widget::IMENotification::Point paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mX);
+ WriteParam(aMsg, aParam.mY);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mX) &&
+ ReadParam(aMsg, aIter, &aResult->mY);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::widget::IMENotification::Rect> {
+ typedef mozilla::widget::IMENotification::Rect paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mX);
+ WriteParam(aMsg, aParam.mY);
+ WriteParam(aMsg, aParam.mWidth);
+ WriteParam(aMsg, aParam.mHeight);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mX) &&
+ ReadParam(aMsg, aIter, &aResult->mY) &&
+ ReadParam(aMsg, aIter, &aResult->mWidth) &&
+ ReadParam(aMsg, aIter, &aResult->mHeight);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::widget::IMENotification::SelectionChangeDataBase> {
+ typedef mozilla::widget::IMENotification::SelectionChangeDataBase paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ MOZ_RELEASE_ASSERT(aParam.mString);
+ WriteParam(aMsg, aParam.mOffset);
+ WriteParam(aMsg, *aParam.mString);
+ WriteParam(aMsg, aParam.mWritingMode);
+ WriteParam(aMsg, aParam.mReversed);
+ WriteParam(aMsg, aParam.mCausedByComposition);
+ WriteParam(aMsg, aParam.mCausedBySelectionEvent);
+ WriteParam(aMsg, aParam.mOccurredDuringComposition);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ aResult->mString = new nsString();
+ return ReadParam(aMsg, aIter, &aResult->mOffset) &&
+ ReadParam(aMsg, aIter, aResult->mString) &&
+ ReadParam(aMsg, aIter, &aResult->mWritingMode) &&
+ ReadParam(aMsg, aIter, &aResult->mReversed) &&
+ ReadParam(aMsg, aIter, &aResult->mCausedByComposition) &&
+ ReadParam(aMsg, aIter, &aResult->mCausedBySelectionEvent) &&
+ ReadParam(aMsg, aIter, &aResult->mOccurredDuringComposition);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::widget::IMENotification::TextChangeDataBase> {
+ typedef mozilla::widget::IMENotification::TextChangeDataBase paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mStartOffset);
+ WriteParam(aMsg, aParam.mRemovedEndOffset);
+ WriteParam(aMsg, aParam.mAddedEndOffset);
+ WriteParam(aMsg, aParam.mCausedOnlyByComposition);
+ WriteParam(aMsg, aParam.mIncludingChangesDuringComposition);
+ WriteParam(aMsg, aParam.mIncludingChangesWithoutComposition);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mStartOffset) &&
+ ReadParam(aMsg, aIter, &aResult->mRemovedEndOffset) &&
+ ReadParam(aMsg, aIter, &aResult->mAddedEndOffset) &&
+ ReadParam(aMsg, aIter, &aResult->mCausedOnlyByComposition) &&
+ ReadParam(aMsg, aIter,
+ &aResult->mIncludingChangesDuringComposition) &&
+ ReadParam(aMsg, aIter,
+ &aResult->mIncludingChangesWithoutComposition);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::widget::IMENotification::MouseButtonEventData> {
+ typedef mozilla::widget::IMENotification::MouseButtonEventData paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mEventMessage);
+ WriteParam(aMsg, aParam.mOffset);
+ WriteParam(aMsg, aParam.mCursorPos);
+ WriteParam(aMsg, aParam.mCharRect);
+ WriteParam(aMsg, aParam.mButton);
+ WriteParam(aMsg, aParam.mButtons);
+ WriteParam(aMsg, aParam.mModifiers);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mEventMessage) &&
+ ReadParam(aMsg, aIter, &aResult->mOffset) &&
+ ReadParam(aMsg, aIter, &aResult->mCursorPos) &&
+ ReadParam(aMsg, aIter, &aResult->mCharRect) &&
+ ReadParam(aMsg, aIter, &aResult->mButton) &&
+ ReadParam(aMsg, aIter, &aResult->mButtons) &&
+ ReadParam(aMsg, aIter, &aResult->mModifiers);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::widget::IMENotification> {
+ typedef mozilla::widget::IMENotification paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg,
+ static_cast<mozilla::widget::IMEMessageType>(aParam.mMessage));
+ switch (aParam.mMessage) {
+ case mozilla::widget::NOTIFY_IME_OF_SELECTION_CHANGE:
+ WriteParam(aMsg, aParam.mSelectionChangeData);
+ return;
+ case mozilla::widget::NOTIFY_IME_OF_TEXT_CHANGE:
+ WriteParam(aMsg, aParam.mTextChangeData);
+ return;
+ case mozilla::widget::NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
+ WriteParam(aMsg, aParam.mMouseButtonEventData);
+ return;
+ default:
+ return;
+ }
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ mozilla::widget::IMEMessageType IMEMessage = 0;
+ if (!ReadParam(aMsg, aIter, &IMEMessage)) {
+ return false;
+ }
+ aResult->mMessage = static_cast<mozilla::widget::IMEMessage>(IMEMessage);
+ switch (aResult->mMessage) {
+ case mozilla::widget::NOTIFY_IME_OF_SELECTION_CHANGE:
+ return ReadParam(aMsg, aIter, &aResult->mSelectionChangeData);
+ case mozilla::widget::NOTIFY_IME_OF_TEXT_CHANGE:
+ return ReadParam(aMsg, aIter, &aResult->mTextChangeData);
+ case mozilla::widget::NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
+ return ReadParam(aMsg, aIter, &aResult->mMouseButtonEventData);
+ default:
+ return true;
+ }
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::widget::IMEEnabled>
+ : ContiguousEnumSerializer<mozilla::widget::IMEEnabled,
+ mozilla::widget::IMEEnabled::Disabled,
+ mozilla::widget::IMEEnabled::Unknown> {};
+
+template <>
+struct ParamTraits<mozilla::widget::IMEState::Open>
+ : ContiguousEnumSerializerInclusive<
+ mozilla::widget::IMEState::Open,
+ mozilla::widget::IMEState::Open::OPEN_STATE_NOT_SUPPORTED,
+ mozilla::widget::IMEState::Open::CLOSED> {};
+
+template <>
+struct ParamTraits<mozilla::widget::IMEState> {
+ typedef mozilla::widget::IMEState paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mEnabled);
+ WriteParam(aMsg, aParam.mOpen);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mEnabled) &&
+ ReadParam(aMsg, aIter, &aResult->mOpen);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::widget::InputContext::Origin>
+ : ContiguousEnumSerializerInclusive<
+ mozilla::widget::InputContext::Origin,
+ mozilla::widget::InputContext::Origin::ORIGIN_MAIN,
+ mozilla::widget::InputContext::Origin::ORIGIN_CONTENT> {};
+
+template <>
+struct ParamTraits<mozilla::widget::InputContext> {
+ typedef mozilla::widget::InputContext paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mIMEState);
+ WriteParam(aMsg, aParam.mHTMLInputType);
+ WriteParam(aMsg, aParam.mHTMLInputInputmode);
+ WriteParam(aMsg, aParam.mActionHint);
+ WriteParam(aMsg, aParam.mAutocapitalize);
+ WriteParam(aMsg, aParam.mOrigin);
+ WriteParam(aMsg, aParam.mMayBeIMEUnaware);
+ WriteParam(aMsg, aParam.mHasHandledUserInput);
+ WriteParam(aMsg, aParam.mInPrivateBrowsing);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mIMEState) &&
+ ReadParam(aMsg, aIter, &aResult->mHTMLInputType) &&
+ ReadParam(aMsg, aIter, &aResult->mHTMLInputInputmode) &&
+ ReadParam(aMsg, aIter, &aResult->mActionHint) &&
+ ReadParam(aMsg, aIter, &aResult->mAutocapitalize) &&
+ ReadParam(aMsg, aIter, &aResult->mOrigin) &&
+ ReadParam(aMsg, aIter, &aResult->mMayBeIMEUnaware) &&
+ ReadParam(aMsg, aIter, &aResult->mHasHandledUserInput) &&
+ ReadParam(aMsg, aIter, &aResult->mInPrivateBrowsing);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::widget::InputContextAction::Cause>
+ : ContiguousEnumSerializerInclusive<
+ mozilla::widget::InputContextAction::Cause,
+ mozilla::widget::InputContextAction::Cause::CAUSE_UNKNOWN,
+ mozilla::widget::InputContextAction::Cause::
+ CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT> {};
+
+template <>
+struct ParamTraits<mozilla::widget::InputContextAction::FocusChange>
+ : ContiguousEnumSerializerInclusive<
+ mozilla::widget::InputContextAction::FocusChange,
+ mozilla::widget::InputContextAction::FocusChange::FOCUS_NOT_CHANGED,
+ mozilla::widget::InputContextAction::FocusChange::WIDGET_CREATED> {};
+
+template <>
+struct ParamTraits<mozilla::widget::InputContextAction> {
+ typedef mozilla::widget::InputContextAction paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mCause);
+ WriteParam(aMsg, aParam.mFocusChange);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mCause) &&
+ ReadParam(aMsg, aIter, &aResult->mFocusChange);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::WritingMode> {
+ typedef mozilla::WritingMode paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mWritingMode.bits);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mWritingMode.bits);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::ContentCache::Selection> {
+ typedef mozilla::ContentCache::Selection paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mAnchor);
+ WriteParam(aMsg, aParam.mFocus);
+ WriteParam(aMsg, aParam.mWritingMode);
+ WriteParam(aMsg, aParam.mAnchorCharRects[0]);
+ WriteParam(aMsg, aParam.mAnchorCharRects[1]);
+ WriteParam(aMsg, aParam.mFocusCharRects[0]);
+ WriteParam(aMsg, aParam.mFocusCharRects[1]);
+ WriteParam(aMsg, aParam.mRect);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mAnchor) &&
+ ReadParam(aMsg, aIter, &aResult->mFocus) &&
+ ReadParam(aMsg, aIter, &aResult->mWritingMode) &&
+ ReadParam(aMsg, aIter, &aResult->mAnchorCharRects[0]) &&
+ ReadParam(aMsg, aIter, &aResult->mAnchorCharRects[1]) &&
+ ReadParam(aMsg, aIter, &aResult->mFocusCharRects[0]) &&
+ ReadParam(aMsg, aIter, &aResult->mFocusCharRects[1]) &&
+ ReadParam(aMsg, aIter, &aResult->mRect);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::ContentCache::Caret> {
+ typedef mozilla::ContentCache::Caret paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mOffset);
+ WriteParam(aMsg, aParam.mRect);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mOffset) &&
+ ReadParam(aMsg, aIter, &aResult->mRect);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::ContentCache::TextRectArray> {
+ typedef mozilla::ContentCache::TextRectArray paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mStart);
+ WriteParam(aMsg, aParam.mRects);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mStart) &&
+ ReadParam(aMsg, aIter, &aResult->mRects);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::ContentCache> {
+ typedef mozilla::ContentCache paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mCompositionStart);
+ WriteParam(aMsg, aParam.mText);
+ WriteParam(aMsg, aParam.mSelection);
+ WriteParam(aMsg, aParam.mFirstCharRect);
+ WriteParam(aMsg, aParam.mCaret);
+ WriteParam(aMsg, aParam.mTextRectArray);
+ WriteParam(aMsg, aParam.mLastCommitStringTextRectArray);
+ WriteParam(aMsg, aParam.mEditorRect);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mCompositionStart) &&
+ ReadParam(aMsg, aIter, &aResult->mText) &&
+ ReadParam(aMsg, aIter, &aResult->mSelection) &&
+ ReadParam(aMsg, aIter, &aResult->mFirstCharRect) &&
+ ReadParam(aMsg, aIter, &aResult->mCaret) &&
+ ReadParam(aMsg, aIter, &aResult->mTextRectArray) &&
+ ReadParam(aMsg, aIter, &aResult->mLastCommitStringTextRectArray) &&
+ ReadParam(aMsg, aIter, &aResult->mEditorRect);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::widget::CandidateWindowPosition> {
+ typedef mozilla::widget::CandidateWindowPosition paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mPoint);
+ WriteParam(aMsg, aParam.mRect);
+ WriteParam(aMsg, aParam.mExcludeRect);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mPoint) &&
+ ReadParam(aMsg, aIter, &aResult->mRect) &&
+ ReadParam(aMsg, aIter, &aResult->mExcludeRect);
+ }
+};
+
+// InputData.h
+
+template <>
+struct ParamTraits<mozilla::InputType>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::InputType, mozilla::InputType::MULTITOUCH_INPUT,
+ mozilla::kHighestInputType> {};
+
+template <>
+struct ParamTraits<mozilla::InputData> {
+ typedef mozilla::InputData paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mInputType);
+ WriteParam(aMsg, aParam.mTime);
+ WriteParam(aMsg, aParam.mTimeStamp);
+ WriteParam(aMsg, aParam.modifiers);
+ WriteParam(aMsg, aParam.mFocusSequenceNumber);
+ WriteParam(aMsg, aParam.mLayersId);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mInputType) &&
+ ReadParam(aMsg, aIter, &aResult->mTime) &&
+ ReadParam(aMsg, aIter, &aResult->mTimeStamp) &&
+ ReadParam(aMsg, aIter, &aResult->modifiers) &&
+ ReadParam(aMsg, aIter, &aResult->mFocusSequenceNumber) &&
+ ReadParam(aMsg, aIter, &aResult->mLayersId);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::SingleTouchData::HistoricalTouchData> {
+ typedef mozilla::SingleTouchData::HistoricalTouchData paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mTimeStamp);
+ WriteParam(aMsg, aParam.mScreenPoint);
+ WriteParam(aMsg, aParam.mLocalScreenPoint);
+ WriteParam(aMsg, aParam.mRadius);
+ WriteParam(aMsg, aParam.mRotationAngle);
+ WriteParam(aMsg, aParam.mForce);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return (ReadParam(aMsg, aIter, &aResult->mTimeStamp) &&
+ ReadParam(aMsg, aIter, &aResult->mScreenPoint) &&
+ ReadParam(aMsg, aIter, &aResult->mLocalScreenPoint) &&
+ ReadParam(aMsg, aIter, &aResult->mRadius) &&
+ ReadParam(aMsg, aIter, &aResult->mRotationAngle) &&
+ ReadParam(aMsg, aIter, &aResult->mForce));
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::SingleTouchData> {
+ typedef mozilla::SingleTouchData paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mHistoricalData);
+ WriteParam(aMsg, aParam.mIdentifier);
+ WriteParam(aMsg, aParam.mScreenPoint);
+ WriteParam(aMsg, aParam.mLocalScreenPoint);
+ WriteParam(aMsg, aParam.mRadius);
+ WriteParam(aMsg, aParam.mRotationAngle);
+ WriteParam(aMsg, aParam.mForce);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return (ReadParam(aMsg, aIter, &aResult->mHistoricalData) &&
+ ReadParam(aMsg, aIter, &aResult->mIdentifier) &&
+ ReadParam(aMsg, aIter, &aResult->mScreenPoint) &&
+ ReadParam(aMsg, aIter, &aResult->mLocalScreenPoint) &&
+ ReadParam(aMsg, aIter, &aResult->mRadius) &&
+ ReadParam(aMsg, aIter, &aResult->mRotationAngle) &&
+ ReadParam(aMsg, aIter, &aResult->mForce));
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::MultiTouchInput::MultiTouchType>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::MultiTouchInput::MultiTouchType,
+ mozilla::MultiTouchInput::MultiTouchType::MULTITOUCH_START,
+ mozilla::MultiTouchInput::sHighestMultiTouchType> {};
+
+template <>
+struct ParamTraits<mozilla::MultiTouchInput> {
+ typedef mozilla::MultiTouchInput paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::InputData&>(aParam));
+ WriteParam(aMsg, aParam.mType);
+ WriteParam(aMsg, aParam.mTouches);
+ WriteParam(aMsg, aParam.mHandledByAPZ);
+ WriteParam(aMsg, aParam.mScreenOffset);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, static_cast<mozilla::InputData*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mType) &&
+ ReadParam(aMsg, aIter, &aResult->mTouches) &&
+ ReadParam(aMsg, aIter, &aResult->mHandledByAPZ) &&
+ ReadParam(aMsg, aIter, &aResult->mScreenOffset);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::MouseInput::MouseType>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::MouseInput::MouseType,
+ mozilla::MouseInput::MouseType::MOUSE_NONE,
+ mozilla::MouseInput::sHighestMouseType> {};
+
+template <>
+struct ParamTraits<mozilla::MouseInput::ButtonType>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::MouseInput::ButtonType,
+ mozilla::MouseInput::ButtonType::PRIMARY_BUTTON,
+ mozilla::MouseInput::sHighestButtonType> {};
+
+template <>
+struct ParamTraits<mozilla::MouseInput> {
+ typedef mozilla::MouseInput paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::InputData&>(aParam));
+ WriteParam(aMsg, aParam.mButtonType);
+ WriteParam(aMsg, aParam.mType);
+ WriteParam(aMsg, aParam.mInputSource);
+ WriteParam(aMsg, aParam.mButtons);
+ WriteParam(aMsg, aParam.mOrigin);
+ WriteParam(aMsg, aParam.mLocalOrigin);
+ WriteParam(aMsg, aParam.mHandledByAPZ);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, static_cast<mozilla::InputData*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mButtonType) &&
+ ReadParam(aMsg, aIter, &aResult->mType) &&
+ ReadParam(aMsg, aIter, &aResult->mInputSource) &&
+ ReadParam(aMsg, aIter, &aResult->mButtons) &&
+ ReadParam(aMsg, aIter, &aResult->mOrigin) &&
+ ReadParam(aMsg, aIter, &aResult->mLocalOrigin) &&
+ ReadParam(aMsg, aIter, &aResult->mHandledByAPZ);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::PanGestureInput::PanGestureType>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::PanGestureInput::PanGestureType,
+ mozilla::PanGestureInput::PanGestureType::PANGESTURE_MAYSTART,
+ mozilla::PanGestureInput::sHighestPanGestureType> {};
+
+template <>
+struct ParamTraits<mozilla::PanGestureInput::PanDeltaType>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::PanGestureInput::PanDeltaType,
+ mozilla::PanGestureInput::PanDeltaType::PANDELTA_PAGE,
+ mozilla::PanGestureInput::sHighestPanDeltaType> {};
+
+template <>
+struct ParamTraits<mozilla::PanGestureInput>
+ : BitfieldHelper<mozilla::PanGestureInput> {
+ typedef mozilla::PanGestureInput paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::InputData&>(aParam));
+ WriteParam(aMsg, aParam.mType);
+ WriteParam(aMsg, aParam.mPanStartPoint);
+ WriteParam(aMsg, aParam.mPanDisplacement);
+ WriteParam(aMsg, aParam.mLocalPanStartPoint);
+ WriteParam(aMsg, aParam.mLocalPanDisplacement);
+ WriteParam(aMsg, aParam.mLineOrPageDeltaX);
+ WriteParam(aMsg, aParam.mLineOrPageDeltaY);
+ WriteParam(aMsg, aParam.mUserDeltaMultiplierX);
+ WriteParam(aMsg, aParam.mUserDeltaMultiplierY);
+ WriteParam(aMsg, aParam.mDeltaType);
+ WriteParam(aMsg, aParam.mHandledByAPZ);
+ WriteParam(aMsg, aParam.mFollowedByMomentum);
+ WriteParam(
+ aMsg,
+ aParam
+ .mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection);
+ WriteParam(aMsg, aParam.mOverscrollBehaviorAllowsSwipe);
+ WriteParam(aMsg, aParam.mSimulateMomentum);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, static_cast<mozilla::InputData*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mType) &&
+ ReadParam(aMsg, aIter, &aResult->mPanStartPoint) &&
+ ReadParam(aMsg, aIter, &aResult->mPanDisplacement) &&
+ ReadParam(aMsg, aIter, &aResult->mLocalPanStartPoint) &&
+ ReadParam(aMsg, aIter, &aResult->mLocalPanDisplacement) &&
+ ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaX) &&
+ ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaY) &&
+ ReadParam(aMsg, aIter, &aResult->mUserDeltaMultiplierX) &&
+ ReadParam(aMsg, aIter, &aResult->mUserDeltaMultiplierY) &&
+ ReadParam(aMsg, aIter, &aResult->mDeltaType) &&
+ ReadBoolForBitfield(aMsg, aIter, aResult,
+ &paramType::SetHandledByAPZ) &&
+ ReadBoolForBitfield(aMsg, aIter, aResult,
+ &paramType::SetFollowedByMomentum) &&
+ ReadBoolForBitfield(
+ aMsg, aIter, aResult,
+ &paramType::
+ SetRequiresContentResponseIfCannotScrollHorizontallyInStartDirection) &&
+ ReadBoolForBitfield(aMsg, aIter, aResult,
+ &paramType::SetOverscrollBehaviorAllowsSwipe) &&
+ ReadBoolForBitfield(aMsg, aIter, aResult,
+ &paramType::SetSimulateMomentum);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::PinchGestureInput::PinchGestureType>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::PinchGestureInput::PinchGestureType,
+ mozilla::PinchGestureInput::PinchGestureType::PINCHGESTURE_START,
+ mozilla::PinchGestureInput::sHighestPinchGestureType> {};
+
+template <>
+struct ParamTraits<mozilla::PinchGestureInput::PinchGestureSource>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::PinchGestureInput::PinchGestureSource,
+ // Set the min to TOUCH, to ensure UNKNOWN is never sent over IPC
+ mozilla::PinchGestureInput::PinchGestureSource::TOUCH,
+ mozilla::PinchGestureInput::sHighestPinchGestureSource> {};
+
+template <>
+struct ParamTraits<mozilla::PinchGestureInput> {
+ typedef mozilla::PinchGestureInput paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::InputData&>(aParam));
+ WriteParam(aMsg, aParam.mType);
+ WriteParam(aMsg, aParam.mSource);
+ WriteParam(aMsg, aParam.mScreenOffset);
+ WriteParam(aMsg, aParam.mFocusPoint);
+ WriteParam(aMsg, aParam.mLocalFocusPoint);
+ WriteParam(aMsg, aParam.mCurrentSpan);
+ WriteParam(aMsg, aParam.mPreviousSpan);
+ WriteParam(aMsg, aParam.mLineOrPageDeltaY);
+ WriteParam(aMsg, aParam.mHandledByAPZ);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, static_cast<mozilla::InputData*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mType) &&
+ ReadParam(aMsg, aIter, &aResult->mSource) &&
+ ReadParam(aMsg, aIter, &aResult->mScreenOffset) &&
+ ReadParam(aMsg, aIter, &aResult->mFocusPoint) &&
+ ReadParam(aMsg, aIter, &aResult->mLocalFocusPoint) &&
+ ReadParam(aMsg, aIter, &aResult->mCurrentSpan) &&
+ ReadParam(aMsg, aIter, &aResult->mPreviousSpan) &&
+ ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaY) &&
+ ReadParam(aMsg, aIter, &aResult->mHandledByAPZ);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::TapGestureInput::TapGestureType>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::TapGestureInput::TapGestureType,
+ mozilla::TapGestureInput::TapGestureType::TAPGESTURE_LONG,
+ mozilla::TapGestureInput::sHighestTapGestureType> {};
+
+template <>
+struct ParamTraits<mozilla::TapGestureInput> {
+ typedef mozilla::TapGestureInput paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::InputData&>(aParam));
+ WriteParam(aMsg, aParam.mType);
+ WriteParam(aMsg, aParam.mPoint);
+ WriteParam(aMsg, aParam.mLocalPoint);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, static_cast<mozilla::InputData*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mType) &&
+ ReadParam(aMsg, aIter, &aResult->mPoint) &&
+ ReadParam(aMsg, aIter, &aResult->mLocalPoint);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::ScrollWheelInput::ScrollDeltaType>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::ScrollWheelInput::ScrollDeltaType,
+ mozilla::ScrollWheelInput::ScrollDeltaType::SCROLLDELTA_LINE,
+ mozilla::ScrollWheelInput::sHighestScrollDeltaType> {};
+
+template <>
+struct ParamTraits<mozilla::ScrollWheelInput::ScrollMode>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::ScrollWheelInput::ScrollMode,
+ mozilla::ScrollWheelInput::ScrollMode::SCROLLMODE_INSTANT,
+ mozilla::ScrollWheelInput::sHighestScrollMode> {};
+
+template <>
+struct ParamTraits<mozilla::WheelDeltaAdjustmentStrategy>
+ : public ContiguousEnumSerializer<
+ mozilla::WheelDeltaAdjustmentStrategy,
+ mozilla::WheelDeltaAdjustmentStrategy(0),
+ mozilla::WheelDeltaAdjustmentStrategy::eSentinel> {};
+
+template <>
+struct ParamTraits<mozilla::layers::APZWheelAction>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::layers::APZWheelAction,
+ mozilla::layers::APZWheelAction::Scroll,
+ mozilla::layers::kHighestAPZWheelAction> {};
+
+template <>
+struct ParamTraits<mozilla::ScrollWheelInput> {
+ typedef mozilla::ScrollWheelInput paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::InputData&>(aParam));
+ WriteParam(aMsg, aParam.mDeltaType);
+ WriteParam(aMsg, aParam.mScrollMode);
+ WriteParam(aMsg, aParam.mOrigin);
+ WriteParam(aMsg, aParam.mHandledByAPZ);
+ WriteParam(aMsg, aParam.mDeltaX);
+ WriteParam(aMsg, aParam.mDeltaY);
+ WriteParam(aMsg, aParam.mLocalOrigin);
+ WriteParam(aMsg, aParam.mLineOrPageDeltaX);
+ WriteParam(aMsg, aParam.mLineOrPageDeltaY);
+ WriteParam(aMsg, aParam.mScrollSeriesNumber);
+ WriteParam(aMsg, aParam.mUserDeltaMultiplierX);
+ WriteParam(aMsg, aParam.mUserDeltaMultiplierY);
+ WriteParam(aMsg, aParam.mMayHaveMomentum);
+ WriteParam(aMsg, aParam.mIsMomentum);
+ WriteParam(aMsg, aParam.mAllowToOverrideSystemScrollSpeed);
+ WriteParam(aMsg, aParam.mWheelDeltaAdjustmentStrategy);
+ WriteParam(aMsg, aParam.mAPZAction);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, static_cast<mozilla::InputData*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mDeltaType) &&
+ ReadParam(aMsg, aIter, &aResult->mScrollMode) &&
+ ReadParam(aMsg, aIter, &aResult->mOrigin) &&
+ ReadParam(aMsg, aIter, &aResult->mHandledByAPZ) &&
+ ReadParam(aMsg, aIter, &aResult->mDeltaX) &&
+ ReadParam(aMsg, aIter, &aResult->mDeltaY) &&
+ ReadParam(aMsg, aIter, &aResult->mLocalOrigin) &&
+ ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaX) &&
+ ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaY) &&
+ ReadParam(aMsg, aIter, &aResult->mScrollSeriesNumber) &&
+ ReadParam(aMsg, aIter, &aResult->mUserDeltaMultiplierX) &&
+ ReadParam(aMsg, aIter, &aResult->mUserDeltaMultiplierY) &&
+ ReadParam(aMsg, aIter, &aResult->mMayHaveMomentum) &&
+ ReadParam(aMsg, aIter, &aResult->mIsMomentum) &&
+ ReadParam(aMsg, aIter,
+ &aResult->mAllowToOverrideSystemScrollSpeed) &&
+ ReadParam(aMsg, aIter, &aResult->mWheelDeltaAdjustmentStrategy) &&
+ ReadParam(aMsg, aIter, &aResult->mAPZAction);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::KeyboardInput::KeyboardEventType>
+ : public ContiguousEnumSerializer<
+ mozilla::KeyboardInput::KeyboardEventType,
+ mozilla::KeyboardInput::KeyboardEventType::KEY_DOWN,
+ mozilla::KeyboardInput::KeyboardEventType::KEY_SENTINEL> {};
+
+template <>
+struct ParamTraits<mozilla::KeyboardInput> {
+ typedef mozilla::KeyboardInput paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const mozilla::InputData&>(aParam));
+ WriteParam(aMsg, aParam.mType);
+ WriteParam(aMsg, aParam.mKeyCode);
+ WriteParam(aMsg, aParam.mCharCode);
+ WriteParam(aMsg, aParam.mShortcutCandidates);
+ WriteParam(aMsg, aParam.mHandledByAPZ);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, static_cast<mozilla::InputData*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mType) &&
+ ReadParam(aMsg, aIter, &aResult->mKeyCode) &&
+ ReadParam(aMsg, aIter, &aResult->mCharCode) &&
+ ReadParam(aMsg, aIter, &aResult->mShortcutCandidates) &&
+ ReadParam(aMsg, aIter, &aResult->mHandledByAPZ);
+ }
+};
+
+} // namespace IPC
+
+#endif // nsGUIEventIPC_h__
diff --git a/widget/nsHTMLFormatConverter.cpp b/widget/nsHTMLFormatConverter.cpp
new file mode 100644
index 0000000000..59ec15c7c1
--- /dev/null
+++ b/widget/nsHTMLFormatConverter.cpp
@@ -0,0 +1,183 @@
+/* -*- 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 "nsHTMLFormatConverter.h"
+
+#include "nsArray.h"
+#include "nsCRT.h"
+#include "nsCOMPtr.h"
+#include "nsITransferable.h"
+#include "nsLiteralString.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+
+// HTML convertor stuff
+#include "nsPrimitiveHelpers.h"
+#include "nsIDocumentEncoder.h"
+#include "nsContentUtils.h"
+
+nsHTMLFormatConverter::nsHTMLFormatConverter() = default;
+
+nsHTMLFormatConverter::~nsHTMLFormatConverter() = default;
+
+NS_IMPL_ISUPPORTS(nsHTMLFormatConverter, nsIFormatConverter)
+
+//
+// GetInputDataFlavors
+//
+// Creates a new list and returns the list of all the flavors this converter
+// knows how to import. In this case, it's just HTML.
+//
+NS_IMETHODIMP
+nsHTMLFormatConverter::GetInputDataFlavors(nsTArray<nsCString>& aFlavors) {
+ aFlavors.AppendElement(nsLiteralCString(kHTMLMime));
+ return NS_OK;
+}
+
+//
+// GetOutputDataFlavors
+//
+// Creates a new list and returns the list of all the flavors this converter
+// knows how to export (convert). In this case, it's all sorts of things that
+// HTML can be converted to.
+//
+NS_IMETHODIMP
+nsHTMLFormatConverter::GetOutputDataFlavors(nsTArray<nsCString>& aFlavors) {
+ aFlavors.AppendElement(nsLiteralCString(kHTMLMime));
+ aFlavors.AppendElement(nsLiteralCString(kUnicodeMime));
+ return NS_OK;
+}
+
+//
+// CanConvert
+//
+// Determines if we support the given conversion. Currently, this method only
+// converts from HTML to others.
+//
+NS_IMETHODIMP
+nsHTMLFormatConverter::CanConvert(const char* aFromDataFlavor,
+ const char* aToDataFlavor, bool* _retval) {
+ if (!_retval) return NS_ERROR_INVALID_ARG;
+
+ *_retval = false;
+ if (!nsCRT::strcmp(aFromDataFlavor, kHTMLMime)) {
+ if (!nsCRT::strcmp(aToDataFlavor, kHTMLMime))
+ *_retval = true;
+ else if (!nsCRT::strcmp(aToDataFlavor, kUnicodeMime))
+ *_retval = true;
+#if NOT_NOW
+ // pinkerton
+ // no one uses this flavor right now, so it's just slowing things down. If
+ // anyone cares I can put it back in.
+ else if (toFlavor.Equals(kAOLMailMime))
+ *_retval = true;
+#endif
+ }
+ return NS_OK;
+
+} // CanConvert
+
+//
+// Convert
+//
+// Convert data from one flavor to another. The data is wrapped in primitive
+// objects so that it is accessible from JS. Currently, this only accepts HTML
+// input, so anything else is invalid.
+//
+// XXX This method copies the data WAAAAY too many time for my liking. Grrrrrr.
+// Mostly it's because
+// XXX we _must_ put things into nsStrings so that the parser will accept it.
+// Lame lame lame lame. We
+// XXX also can't just get raw unicode out of the nsString, so we have to
+// allocate heap to get
+// XXX unicode out of the string. Lame lame lame.
+//
+NS_IMETHODIMP
+nsHTMLFormatConverter::Convert(const char* aFromDataFlavor,
+ nsISupports* aFromData,
+ const char* aToDataFlavor,
+ nsISupports** aToData) {
+ if (!aToData) return NS_ERROR_INVALID_ARG;
+
+ nsresult rv = NS_OK;
+ *aToData = nullptr;
+
+ if (!nsCRT::strcmp(aFromDataFlavor, kHTMLMime)) {
+ nsAutoCString toFlavor(aToDataFlavor);
+
+ // HTML on clipboard is going to always be double byte so it will be in a
+ // primitive class of nsISupportsString. Also, since the data is in two byte
+ // chunks the length represents the length in 1-byte chars, so we need to
+ // divide by two.
+ nsCOMPtr<nsISupportsString> dataWrapper0(do_QueryInterface(aFromData));
+ if (!dataWrapper0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoString dataStr;
+ dataWrapper0->GetData(dataStr); // COPY #1
+ // note: conversion to text/plain is done inside the clipboard. we do not
+ // need to worry about it here.
+ if (toFlavor.Equals(kHTMLMime) || toFlavor.Equals(kUnicodeMime)) {
+ nsresult res;
+ if (toFlavor.Equals(kHTMLMime)) {
+ int32_t dataLen = dataStr.Length() * 2;
+ nsPrimitiveHelpers::CreatePrimitiveForData(toFlavor, dataStr.get(),
+ dataLen, aToData);
+ } else {
+ nsAutoString outStr;
+ res = ConvertFromHTMLToUnicode(dataStr, outStr);
+ if (NS_SUCCEEDED(res)) {
+ int32_t dataLen = outStr.Length() * 2;
+ nsPrimitiveHelpers::CreatePrimitiveForData(toFlavor, outStr.get(),
+ dataLen, aToData);
+ }
+ }
+ } // else if HTML or Unicode
+ else if (toFlavor.Equals(kAOLMailMime)) {
+ nsAutoString outStr;
+ if (NS_SUCCEEDED(ConvertFromHTMLToAOLMail(dataStr, outStr))) {
+ int32_t dataLen = outStr.Length() * 2;
+ nsPrimitiveHelpers::CreatePrimitiveForData(toFlavor, outStr.get(),
+ dataLen, aToData);
+ }
+ } // else if AOL mail
+ else {
+ rv = NS_ERROR_FAILURE;
+ }
+ } // if we got html mime
+ else
+ rv = NS_ERROR_FAILURE;
+
+ return rv;
+
+} // Convert
+
+//
+// ConvertFromHTMLToUnicode
+//
+// Takes HTML and converts it to plain text but in unicode.
+//
+NS_IMETHODIMP
+nsHTMLFormatConverter::ConvertFromHTMLToUnicode(const nsAutoString& aFromStr,
+ nsAutoString& aToStr) {
+ return nsContentUtils::ConvertToPlainText(
+ aFromStr, aToStr,
+ nsIDocumentEncoder::OutputSelectionOnly |
+ nsIDocumentEncoder::OutputAbsoluteLinks |
+ nsIDocumentEncoder::OutputNoScriptContent |
+ nsIDocumentEncoder::OutputNoFramesContent,
+ 0);
+} // ConvertFromHTMLToUnicode
+
+NS_IMETHODIMP
+nsHTMLFormatConverter::ConvertFromHTMLToAOLMail(const nsAutoString& aFromStr,
+ nsAutoString& aToStr) {
+ aToStr.AssignLiteral("<HTML>");
+ aToStr.Append(aFromStr);
+ aToStr.AppendLiteral("</HTML>");
+
+ return NS_OK;
+}
diff --git a/widget/nsHTMLFormatConverter.h b/widget/nsHTMLFormatConverter.h
new file mode 100644
index 0000000000..f01373233b
--- /dev/null
+++ b/widget/nsHTMLFormatConverter.h
@@ -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/. */
+
+#ifndef nsHTMLFormatConverter_h__
+#define nsHTMLFormatConverter_h__
+
+#include "nsCOMPtr.h"
+#include "nsIFormatConverter.h"
+#include "nsString.h"
+
+class nsIMutableArray;
+
+class nsHTMLFormatConverter : public nsIFormatConverter {
+ public:
+ nsHTMLFormatConverter();
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFORMATCONVERTER
+
+ protected:
+ virtual ~nsHTMLFormatConverter();
+
+ NS_IMETHOD ConvertFromHTMLToUnicode(const nsAutoString& aFromStr,
+ nsAutoString& aToStr);
+ NS_IMETHOD ConvertFromHTMLToAOLMail(const nsAutoString& aFromStr,
+ nsAutoString& aToStr);
+};
+
+#endif // nsHTMLFormatConverter_h__
diff --git a/widget/nsIAppShell.idl b/widget/nsIAppShell.idl
new file mode 100644
index 0000000000..c21c4d107b
--- /dev/null
+++ b/widget/nsIAppShell.idl
@@ -0,0 +1,76 @@
+/* -*- 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 nsIRunnable;
+%{ C++
+template <class T> struct already_AddRefed;
+%}
+
+/**
+ * Interface for the native event system layer. This interface is designed
+ * to be used on the main application thread only.
+ */
+[uuid(7cd5c71d-223b-4afe-931d-5eedb1f2b01f)]
+interface nsIAppShell : nsISupports
+{
+ /**
+ * Enter an event loop. Don't leave until exit() is called.
+ */
+ void run();
+
+ /**
+ * Exit the handle event loop
+ */
+ void exit();
+
+ /**
+ * Give hint to native event queue notification mechanism. If the native
+ * platform needs to tradeoff performance vs. native event starvation this
+ * hint tells the native dispatch code which to favor. The default is to
+ * prevent native event starvation.
+ *
+ * Calls to this function may be nested. When the number of calls that pass
+ * PR_TRUE is subtracted from the number of calls that pass PR_FALSE is
+ * greater than 0, performance is given precedence over preventing event
+ * starvation.
+ *
+ * The starvationDelay arg is only used when favorPerfOverStarvation is
+ * PR_FALSE. It is the amount of time in milliseconds to wait before the
+ * PR_FALSE actually takes effect.
+ */
+ void favorPerformanceHint(in boolean favorPerfOverStarvation,
+ in unsigned long starvationDelay);
+
+ /**
+ * Suspends the use of additional platform-specific methods (besides the
+ * nsIAppShell->run() event loop) to run Gecko events on the main
+ * application thread. Under some circumstances these "additional methods"
+ * can cause Gecko event handlers to be re-entered, sometimes leading to
+ * hangs and crashes. Calls to suspendNative() and resumeNative() may be
+ * nested. On some platforms (those that don't use any "additional
+ * methods") this will be a no-op. Does not (in itself) stop Gecko events
+ * from being processed on the main application thread. But if the
+ * nsIAppShell->run() event loop is blocked when this call is made, Gecko
+ * events will stop being processed until resumeNative() is called (even
+ * if a plugin or library is temporarily processing events on a nested
+ * event loop).
+ */
+ void suspendNative();
+
+ /**
+ * Resumes the use of additional platform-specific methods to run Gecko
+ * events on the main application thread. Calls to suspendNative() and
+ * resumeNative() may be nested. On some platforms this will be a no-op.
+ */
+ void resumeNative();
+
+ /**
+ * The current event loop nesting level.
+ */
+ readonly attribute unsigned long eventloopNestingLevel;
+};
diff --git a/widget/nsIApplicationChooser.idl b/widget/nsIApplicationChooser.idl
new file mode 100644
index 0000000000..05d15f7ac2
--- /dev/null
+++ b/widget/nsIApplicationChooser.idl
@@ -0,0 +1,40 @@
+/* -*- 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"
+#include "nsIMIMEInfo.idl"
+
+interface mozIDOMWindowProxy;
+
+[scriptable, function, uuid(8144404d-e6c7-4861-bcca-47de912ee811)]
+interface nsIApplicationChooserFinishedCallback : nsISupports
+{
+ void done(in nsIHandlerApp handlerApp);
+};
+
+[scriptable, uuid(f7a149da-612a-46ba-8a2f-54786fc28791)]
+interface nsIApplicationChooser : nsISupports
+{
+ /**
+ * Initialize the application chooser picker widget. The application chooser
+ * is not valid until this method is called.
+ *
+ * @param parent nsIDOMWindow parent. This dialog will be dependent
+ * on this parent. parent must be non-null.
+ * @param title The title for the file widget
+ *
+ */
+ void init(in mozIDOMWindowProxy parent, in ACString title);
+
+ /**
+ * Open application chooser dialog.
+ *
+ * @param contentType content type of file to open
+ * @param applicationChooserFinishedCallback callback fuction to run when dialog is closed
+ */
+ void open(in ACString contentType, in nsIApplicationChooserFinishedCallback applicationChooserFinishedCallback);
+};
+
diff --git a/widget/nsIBaseWindow.idl b/widget/nsIBaseWindow.idl
new file mode 100644
index 0000000000..b130a238c2
--- /dev/null
+++ b/widget/nsIBaseWindow.idl
@@ -0,0 +1,218 @@
+/* -*- 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"
+#include "nsrootidl.idl"
+/*#include "nsIWidget.idl" Boy this would be nice.*/
+
+[ptr] native nsIWidget(nsIWidget);
+%{ C++
+class nsIWidget;
+%}
+
+typedef voidPtr nativeWindow;
+
+/**
+ * The nsIBaseWindow describes a generic window and basic operations that
+ * can be performed on it. This is not to be a complete windowing interface
+ * but rather a common set that nearly all windowed objects support.
+ */
+
+[scriptable, uuid(ca635529-a977-4552-9b8a-66187e54d882)]
+interface nsIBaseWindow : nsISupports
+{
+ /*
+ Allows a client to initialize an object implementing this interface with
+ the usually required window setup information.
+ It is possible to pass null for both parentNativeWindow and parentWidget,
+ but only docshells support this.
+
+ @param parentNativeWindow - This allows a system to pass in the parenting
+ window as a native reference rather than relying on the calling
+ application to have created the parent window as an nsIWidget. This
+ value will be ignored (should be nullptr) if an nsIWidget is passed in to
+ the parentWidget parameter.
+
+ @param parentWidget - This allows a system to pass in the parenting widget.
+ This allows some objects to optimize themselves and rely on the view
+ system for event flow rather than creating numerous native windows. If
+ one of these is not available, nullptr should be passed.
+
+ @param x - This is the x co-ordinate relative to the parent to place the
+ window.
+
+ @param y - This is the y co-ordinate relative to the parent to place the
+ window.
+
+ @param cx - This is the width for the window to be.
+
+ @param cy - This is the height for the window to be.
+
+ @return NS_OK - Window Init succeeded without a problem.
+ NS_ERROR_UNEXPECTED - Call was unexpected at this time. Perhaps
+ initWindow() had already been called.
+ NS_ERROR_INVALID_ARG - controls that require either a parentNativeWindow
+ or a parentWidget may return invalid arg when they do not
+ receive what they are needing.
+ */
+ [noscript]void initWindow(in nativeWindow parentNativeWindow,
+ in nsIWidget parentWidget, in long x, in long y, in long cx, in long cy);
+
+ /*
+ Tell the window that it should destroy itself. This call should not be
+ necessary as it will happen implictly when final release occurs on the
+ object. If for some reaons you want the window destroyed prior to release
+ due to cycle or ordering issues, then this call provides that ability.
+
+ @return NS_OK - Everything destroyed properly.
+ NS_ERROR_UNEXPECTED - This call was unexpected at this time.
+ Perhaps create() has not been called yet.
+ */
+ void destroy();
+
+ /*
+ Sets the current x and y coordinates of the control. This is relative to
+ the parent window.
+ */
+ void setPosition(in long x, in long y);
+
+ /*
+ Ditto, with arguments in global desktop pixels rather than (potentially
+ ambiguous) device pixels
+ */
+ void setPositionDesktopPix(in long x, in long y);
+
+ /*
+ Gets the current x and y coordinates of the control. This is relatie to the
+ parent window.
+ */
+ void getPosition(out long x, out long y);
+
+ /*
+ Sets the width and height of the control.
+ */
+ void setSize(in long cx, in long cy, in boolean fRepaint);
+
+ /*
+ Gets the width and height of the control.
+ */
+ void getSize(out long cx, out long cy);
+
+ /**
+ * The 'flags' argument to setPositionAndSize is a set of these bits.
+ */
+ const unsigned long eRepaint = 1;
+ const unsigned long eDelayResize = 2;
+
+ /*
+ Convenience function combining the SetPosition and SetSize into one call.
+ Also is more efficient than calling both.
+ */
+ void setPositionAndSize(in long x, in long y, in long cx, in long cy,
+ in unsigned long flags);
+
+ /*
+ Convenience function combining the GetPosition and GetSize into one call.
+ Also is more efficient than calling both.
+ */
+ void getPositionAndSize(out long x, out long y, out long cx, out long cy);
+
+ /**
+ * Tell the window to repaint itself
+ * @param aForce - if true, repaint immediately
+ * if false, the window may defer repainting as it sees fit.
+ */
+ void repaint(in boolean force);
+
+ /*
+ This is the parenting widget for the control. This may be null if the
+ native window was handed in for the parent during initialization.
+ If this is returned, it should refer to the same object as
+ parentNativeWindow.
+
+ Setting this after Create() has been called may not be supported by some
+ implementations.
+
+ On controls that don't support widgets, setting this will return a
+ NS_ERROR_NOT_IMPLEMENTED error.
+ */
+ [noscript] attribute nsIWidget parentWidget;
+
+ /*
+ This is the native window parent of the control.
+
+ Setting this after Create() has been called may not be supported by some
+ implementations.
+
+ On controls that don't support setting nativeWindow parents, setting this
+ will return a NS_ERROR_NOT_IMPLEMENTED error.
+ */
+ attribute nativeWindow parentNativeWindow;
+
+ /*
+ This is the handle (HWND, GdkWindow*, ...) to the native window of the
+ control, exposed as an AString.
+
+ @return AString in hex format with "0x" prepended, or empty string if
+ mainWidget undefined
+
+ @throws NS_ERROR_NOT_IMPLEMENTED for non-XULWindows
+ */
+ readonly attribute AString nativeHandle;
+
+ /*
+ Attribute controls the visibility of the object behind this interface.
+ Setting this attribute to false will hide the control. Setting it to
+ true will show it.
+ */
+ attribute boolean visibility;
+
+ /*
+ a disabled window should accept no user interaction; it's a dead window,
+ like the parent of a modal window.
+ */
+ attribute boolean enabled;
+
+ /*
+ Allows you to find out what the widget is of a given object. Depending
+ on the object, this may return the parent widget in which this object
+ lives if it has not had to create its own widget.
+ */
+ [noscript] readonly attribute nsIWidget mainWidget;
+
+ /*
+ The number of device pixels per CSS pixel used on this window's current
+ screen at the default zoom level.
+ This is the value returned by GetDefaultScale() of the underlying widget.
+ Note that this may change if the window is moved between screens with
+ differing resolutions.
+ */
+ readonly attribute double unscaledDevicePixelsPerCSSPixel;
+
+ /*
+ The number of device pixels per display pixel on this window's current
+ screen. (The meaning of "display pixel" varies across OS environments;
+ it is the pixel units used by the desktop environment to manage screen
+ real estate and window positioning, which may correspond to (per-screen)
+ device pixels, or may be a virtual coordinate space that covers a multi-
+ monitor, mixed-dpi desktop space.)
+ This is the value returned by DevicePixelsPerDesktopPixel() of the underlying
+ widget.
+ Note that this may change if the window is moved between screens with
+ differing resolutions.
+ */
+ readonly attribute double devicePixelsPerDesktopPixel;
+
+ /**
+ * Give the window focus.
+ */
+ void setFocus();
+
+ /*
+ Title of the window.
+ */
+ attribute AString title;
+};
diff --git a/widget/nsIBidiKeyboard.idl b/widget/nsIBidiKeyboard.idl
new file mode 100644
index 0000000000..fc68d1ae81
--- /dev/null
+++ b/widget/nsIBidiKeyboard.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"
+
+[builtinclass, scriptable, uuid(288dae24-76e2-43a3-befe-9d9fabe8014e)]
+interface nsIBidiKeyboard : nsISupports
+{
+ /**
+ * Inspects the installed keyboards and resets the bidi keyboard state
+ */
+ void reset();
+
+ /**
+ * Determines if the current keyboard language is right-to-left
+ * @throws NS_ERROR_FAILURE if no right-to-left keyboards are installed
+ */
+ boolean isLangRTL();
+
+ /**
+ * Determines whether the system has at least one keyboard of each direction
+ * installed.
+ *
+ * @throws NS_ERROR_NOT_IMPLEMENTED if the widget layer does not provide this
+ * information.
+ */
+ readonly attribute boolean haveBidiKeyboards;
+};
+
diff --git a/widget/nsIClipboard.idl b/widget/nsIClipboard.idl
new file mode 100644
index 0000000000..dd4f7cfbac
--- /dev/null
+++ b/widget/nsIClipboard.idl
@@ -0,0 +1,90 @@
+/* -*- 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/. */
+
+
+#include "nsISupports.idl"
+#include "nsITransferable.idl"
+#include "nsIClipboardOwner.idl"
+
+interface nsIArray;
+
+[scriptable, uuid(ceaa0047-647f-4b8e-ad1c-aff9fa62aa51)]
+interface nsIClipboard : nsISupports
+{
+ const long kSelectionClipboard = 0;
+ const long kGlobalClipboard = 1;
+ const long kFindClipboard = 2;
+ // Used to cache current selection on (nsClipboard) for macOS service menu.
+ const long kSelectionCache = 3;
+
+ /**
+ * Given a transferable, set the data on the native clipboard
+ *
+ * @param aTransferable The transferable
+ * @param anOwner The owner of the transferable
+ * @param aWhichClipboard Specifies the clipboard to which this operation applies.
+ * @result NS_Ok if no errors
+ */
+
+ void setData ( in nsITransferable aTransferable, in nsIClipboardOwner anOwner,
+ in long aWhichClipboard ) ;
+
+ /**
+ * Given a transferable, get the clipboard data.
+ *
+ * @param aTransferable The transferable
+ * @param aWhichClipboard Specifies the clipboard to which this operation applies.
+ * @result NS_Ok if no errors
+ */
+
+ void getData ( in nsITransferable aTransferable, in long aWhichClipboard ) ;
+
+ /**
+ * This empties the clipboard and notifies the clipboard owner.
+ * This empties the "logical" clipboard. It does not clear the native clipboard.
+ *
+ * @param aWhichClipboard Specifies the clipboard to which this operation applies.
+ * @result NS_OK if successful.
+ */
+
+ void emptyClipboard ( in long aWhichClipboard ) ;
+
+ /**
+ * This provides a way to give correct UI feedback about, for instance, a paste
+ * should be allowed. It does _NOT_ actually retreive the data and should be a very
+ * inexpensive call. All it does is check if there is data on the clipboard matching
+ * any of the flavors in the given list.
+ *
+ * @param aFlavorList An array of ASCII strings.
+ * @param aWhichClipboard Specifies the clipboard to which this operation applies.
+ * @outResult - if data is present matching one of
+ * @result NS_OK if successful.
+ */
+ boolean hasDataMatchingFlavors ( in Array<ACString> aFlavorList,
+ in long aWhichClipboard ) ;
+
+ /**
+ * Allows clients to determine if the implementation supports the concept of a
+ * separate clipboard for selection.
+ *
+ * @outResult - true if
+ * @result NS_OK if successful.
+ */
+ boolean supportsSelectionClipboard ( ) ;
+
+ /**
+ * Allows clients to determine if the implementation supports the concept of a
+ * separate clipboard for find search strings.
+ *
+ * @result NS_OK if successful.
+ */
+ boolean supportsFindClipboard ( ) ;
+};
+
+
+%{ C++
+
+%}
diff --git a/widget/nsIClipboardHelper.idl b/widget/nsIClipboardHelper.idl
new file mode 100644
index 0000000000..fffe97f3ea
--- /dev/null
+++ b/widget/nsIClipboardHelper.idl
@@ -0,0 +1,36 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+#include "nsIClipboard.idl"
+
+%{ C++
+#include "nsString.h" // needed for AString -> nsAString, unfortunately
+%}
+
+/**
+ * helper service for common uses of nsIClipboard.
+ */
+
+[scriptable, uuid(438307fd-0c68-4d79-922a-f6cc9550cd02)]
+interface nsIClipboardHelper : nsISupports
+{
+ /**
+ * copy string to given clipboard
+ *
+ * @param aString, the string to copy to the clipboard
+ * @param aClipboardID, the ID of the clipboard to copy to
+ * (eg. kSelectionClipboard -- see nsIClipboard.idl)
+ */
+ void copyStringToClipboard(in AString aString, in long aClipboardID);
+
+ /**
+ * copy string to (default) clipboard
+ *
+ * @param aString, the string to copy to the clipboard
+ */
+ void copyString(in AString aString);
+};
diff --git a/widget/nsIClipboardOwner.idl b/widget/nsIClipboardOwner.idl
new file mode 100644
index 0000000000..df9dfd1049
--- /dev/null
+++ b/widget/nsIClipboardOwner.idl
@@ -0,0 +1,29 @@
+/* -*- 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/. */
+
+
+#include "nsISupports.idl"
+#include "nsITransferable.idl"
+
+
+[scriptable, uuid(5A31C7A1-E122-11d2-9A57-000064657374)]
+interface nsIClipboardOwner : nsISupports
+{
+ /**
+ * Notifies the owner of the clipboard transferable that the
+ * transferable is being removed from the clipboard
+ *
+ * @param aTransferable The transferable
+ * @result NS_Ok if no errors
+ */
+
+ void LosingOwnership ( in nsITransferable aTransferable ) ;
+};
+
+
+%{ C++
+
+%}
diff --git a/widget/nsIColorPicker.idl b/widget/nsIColorPicker.idl
new file mode 100644
index 0000000000..24b128e1b1
--- /dev/null
+++ b/widget/nsIColorPicker.idl
@@ -0,0 +1,72 @@
+/* -*- 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 mozIDOMWindowProxy;
+
+/**
+ * nsIColorPicker is representing colors as strings because the internal
+ * representation will depend on the underlying backend.
+ * The format of the colors taken in input and returned will always follow the
+ * format of the <input type='color'> value as described in the HTML
+ * specifications.
+ */
+
+[scriptable, uuid(d2ce78d1-40b5-49d1-b66d-5801fcb9a385)]
+interface nsIColorPickerShownCallback : nsISupports
+{
+ /**
+ * Callback called when the color picker requests a color update.
+ * This callback can not be called after done() was called.
+ * When this callback is used, the consumer can assume that the color value has
+ * changed.
+ *
+ * @param color The new selected color value following the format specifed on
+ * top of this file.
+ */
+ void update(in AString color);
+
+ /**
+ * Callback called when the color picker is dismissed.
+ * When this callback is used, the color might have changed or could stay the
+ * same.
+ * If the color has not changed, the color parameter will be the empty string.
+ *
+ * @param color The new selected color value following the format specifed on
+ * top of this file or the empty string.
+ */
+ void done(in AString color);
+};
+
+[scriptable, uuid(de229d37-a8a6-46f1-969a-0c1de33d0ad7)]
+interface nsIColorPicker : nsISupports
+{
+ /**
+ * Initialize the color picker widget. The color picker will not be shown until
+ * open() is called.
+ * If the backend doesn't support setting a title to the native color picker
+ * widget, the title parameter might be ignored.
+ * If the initialColor parameter does not follow the format specified on top of
+ * this file, the behavior will be unspecified. The initialColor could be the
+ * one used by the underlying backend or an arbitrary one. The backend could
+ * also assert.
+ *
+ * @param parent nsIDOMWindow parent. This dialog will be dependent
+ * on this parent. parent must be non-null.
+ * @param title The title for the color picker widget.
+ * @param initialColor The color to show when the widget is opened. The
+ * parameter has to follow the format specified on top
+ * of this file.
+ */
+ void init(in mozIDOMWindowProxy parent, in AString title, in AString initialColor);
+
+ /**
+ * Opens the color dialog asynchrounously.
+ * The results are provided via the callback object.
+ */
+ void open(in nsIColorPickerShownCallback aColorPickerShownCallback);
+};
diff --git a/widget/nsIDeviceContextSpec.h b/widget/nsIDeviceContextSpec.h
new file mode 100644
index 0000000000..cbcaf1535e
--- /dev/null
+++ b/widget/nsIDeviceContextSpec.h
@@ -0,0 +1,93 @@
+/* -*- 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 nsIDeviceContextSpec_h___
+#define nsIDeviceContextSpec_h___
+
+#include "gfxPoint.h"
+#include "nsISupports.h"
+#include "mozilla/StaticPrefs_print.h"
+
+class nsIWidget;
+class nsIPrintSettings;
+
+namespace mozilla {
+namespace gfx {
+class DrawEventRecorder;
+class PrintTarget;
+} // namespace gfx
+} // namespace mozilla
+
+#define NS_IDEVICE_CONTEXT_SPEC_IID \
+ { \
+ 0xf407cfba, 0xbe28, 0x46c9, { \
+ 0x8a, 0xba, 0x04, 0x2d, 0xae, 0xbb, 0x4f, 0x23 \
+ } \
+ }
+
+class nsIDeviceContextSpec : public nsISupports {
+ public:
+ typedef mozilla::gfx::PrintTarget PrintTarget;
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDEVICE_CONTEXT_SPEC_IID)
+
+ /**
+ * Initialize the device context spec.
+ * @param aWidget A widget a dialog can be hosted in
+ * @param aPrintSettings Print settings for the print operation
+ * @param aIsPrintPreview True if creating Spec for PrintPreview
+ * @return NS_OK or a suitable error code.
+ */
+ NS_IMETHOD Init(nsIWidget* aWidget, nsIPrintSettings* aPrintSettings,
+ bool aIsPrintPreview) = 0;
+
+ virtual already_AddRefed<PrintTarget> MakePrintTarget() = 0;
+
+ /**
+ * If required override to return a recorder to record the print.
+ *
+ * @param aDrawEventRecorder out param for the recorder to use
+ * @return NS_OK or a suitable error code
+ */
+ NS_IMETHOD GetDrawEventRecorder(
+ mozilla::gfx::DrawEventRecorder** aDrawEventRecorder) {
+ MOZ_ASSERT(aDrawEventRecorder);
+ *aDrawEventRecorder = nullptr;
+ return NS_OK;
+ }
+
+ /**
+ * Override to return something other than the default.
+ *
+ * @return DPI for printing.
+ */
+ virtual float GetDPI() { return mozilla::StaticPrefs::print_default_dpi(); }
+
+ /**
+ * Override to return something other than the default.
+ *
+ * @return the printing scale to be applied to the context for printing.
+ */
+ virtual float GetPrintingScale() { return 72.0f / GetDPI(); }
+
+ /**
+ * Override to return something other than the default.
+ *
+ * @return the point to translate the context to for printing.
+ */
+ virtual gfxPoint GetPrintingTranslate() { return gfxPoint(0, 0); }
+
+ NS_IMETHOD BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) = 0;
+
+ NS_IMETHOD EndDocument() = 0;
+ NS_IMETHOD AbortDocument() { return EndDocument(); }
+ NS_IMETHOD BeginPage() = 0;
+ NS_IMETHOD EndPage() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIDeviceContextSpec, NS_IDEVICE_CONTEXT_SPEC_IID)
+#endif
diff --git a/widget/nsIDisplayInfo.idl b/widget/nsIDisplayInfo.idl
new file mode 100644
index 0000000000..69d5064e4e
--- /dev/null
+++ b/widget/nsIDisplayInfo.idl
@@ -0,0 +1,14 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(615bc23d-6346-4b15-9c10-add002f140b6)]
+interface nsIDisplayInfo : nsISupports
+{
+ readonly attribute long id;
+ readonly attribute boolean connected;
+};
diff --git a/widget/nsIDragService.idl b/widget/nsIDragService.idl
new file mode 100644
index 0000000000..4dd3efd051
--- /dev/null
+++ b/widget/nsIDragService.idl
@@ -0,0 +1,200 @@
+/* -*- 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/. */
+
+#include "nsIArray.idl"
+#include "nsISupports.idl"
+#include "nsIDragSession.idl"
+#include "nsIContentPolicy.idl"
+
+webidl DragEvent;
+webidl Node;
+webidl Selection;
+
+interface nsICookieJarSettings;
+
+%{C++
+#include "mozilla/EventForwards.h"
+
+namespace mozilla {
+namespace dom {
+class ContentParent;
+class DataTransfer;
+class RemoteDragStartData;
+} // namespace dom
+} // namespace mozilla
+%}
+
+[ptr] native ContentParentPtr(mozilla::dom::ContentParent);
+[ptr] native DataTransferPtr(mozilla::dom::DataTransfer);
+[ptr] native RemoteDragStartDataPtr(mozilla::dom::RemoteDragStartData);
+native EventMessage(mozilla::EventMessage);
+
+[scriptable, uuid(ebd6b3a2-af16-43af-a698-3091a087dd62), builtinclass]
+interface nsIDragService : nsISupports
+{
+ const long DRAGDROP_ACTION_NONE = 0;
+ const long DRAGDROP_ACTION_COPY = 1;
+ const long DRAGDROP_ACTION_MOVE = 2;
+ const long DRAGDROP_ACTION_LINK = 4;
+ const long DRAGDROP_ACTION_UNINITIALIZED = 64;
+
+ /**
+ * Starts a modal drag session with an array of transaferables.
+ *
+ * Note: This method is deprecated for non-native code.
+ *
+ * @param aPrincipal - the triggering principal of the drag, or null if
+ * it's from browser chrome or OS
+ * @param aCsp - The csp of the triggering Document
+ * @param aTransferables - an array of transferables to be dragged
+ * @param aActionType - specified which of copy/move/link are allowed
+ * @param aContentPolicyType - the contentPolicyType that will be
+ * passed to the loadInfo when creating a new channel
+ * (defaults to TYPE_OTHER)
+ */
+ [can_run_script]
+ void invokeDragSession (in Node aDOMNode,
+ in nsIPrincipal aPrincipal,
+ in nsIContentSecurityPolicy aCsp,
+ in nsICookieJarSettings aCookieJarSettings,
+ in nsIArray aTransferables,
+ in unsigned long aActionType,
+ [optional] in nsContentPolicyType aContentPolicyType);
+
+ /**
+ * Starts a modal drag session using an image. The first four arguments are
+ * the same as invokeDragSession.
+ *
+ * Note: This method is deprecated for non-native code.
+ *
+ * A custom image may be specified using the aImage argument. If this is
+ * supplied, the aImageX and aImageY arguments specify the offset within
+ * the image where the cursor would be positioned. That is, when the image
+ * is drawn, it is offset up and left the amount so that the cursor appears
+ * at that location within the image.
+ *
+ * If aImage is null, aImageX and aImageY are not used and the image is instead
+ * determined from the source node aDOMNode, and the offset calculated so that
+ * the initial location for the image appears in the same screen position as
+ * where the element is located. The node must be within a document.
+ *
+ * Currently, supported images are all DOM nodes. If this is an HTML <image> or
+ * <canvas>, the drag image is taken from the image data. If the element is in
+ * a document, it will be rendered at its displayed size, othewise, it will be
+ * rendered at its real size. For other types of elements, the element is
+ * rendered into an offscreen buffer in the same manner as it is currently
+ * displayed. The document selection is hidden while drawing.
+ *
+ * The aDragEvent must be supplied as the current screen coordinates of the
+ * event are needed to calculate the image location.
+ */
+ [noscript, can_run_script]
+ void invokeDragSessionWithImage(in Node aDOMNode,
+ in nsIPrincipal aPrincipal,
+ in nsIContentSecurityPolicy aCsp,
+ in nsICookieJarSettings aCookieJarSettings,
+ in nsIArray aTransferableArray,
+ in unsigned long aActionType,
+ in Node aImage,
+ in long aImageX,
+ in long aImageY,
+ in DragEvent aDragEvent,
+ in DataTransferPtr aDataTransfer);
+
+ /** Start a drag session with the data in aDragStartData from a child process.
+ * Other arguments are the same as invokeDragSessionWithImage.
+ */
+ [noscript, can_run_script]
+ void invokeDragSessionWithRemoteImage(in Node aDOMNode,
+ in nsIPrincipal aPrincipal,
+ in nsIContentSecurityPolicy aCsp,
+ in nsICookieJarSettings aCookieJarSettings,
+ in nsIArray aTransferableArray,
+ in unsigned long aActionType,
+ in RemoteDragStartDataPtr aDragStartData,
+ in DragEvent aDragEvent,
+ in DataTransferPtr aDataTransfer);
+
+ /**
+ * Start a modal drag session using the selection as the drag image.
+ * The aDragEvent must be supplied as the current screen coordinates of the
+ * event are needed to calculate the image location.
+ *
+ * Note: This method is deprecated for non-native code.
+ */
+ [can_run_script]
+ void invokeDragSessionWithSelection(in Selection aSelection,
+ in nsIPrincipal aPrincipal,
+ in nsIContentSecurityPolicy aCsp,
+ in nsICookieJarSettings aCookieJarSettings,
+ in nsIArray aTransferableArray,
+ in unsigned long aActionType,
+ in DragEvent aDragEvent,
+ in DataTransferPtr aDataTransfer);
+
+ /**
+ * Returns the current Drag Session
+ */
+ nsIDragSession getCurrentSession();
+
+ /**
+ * Tells the Drag Service to start a drag session. This is called when
+ * an external drag occurs
+ */
+ void startDragSession() ;
+
+ /**
+ * Similar to startDragSession(), automated tests may want to start
+ * session for emulating an external drag. At that time, this should
+ * be used instead of startDragSession().
+ *
+ * @param aAllowedEffect Set default drag action which means allowed effects
+ * in the session and every DnD events are initialized
+ * with one of specified value. So, the value can be
+ * DRAGDROP_ACTION_NONE, or one or more values of
+ * DRAGDROP_ACTION_(MOVE|COPY|LINK).
+ */
+ void startDragSessionForTests(in unsigned long aAllowedEffect);
+
+ /**
+ * Tells the Drag Service to end a drag session. This is called when
+ * an external drag occurs
+ *
+ * If aDoneDrag is true, the drag has finished, otherwise the drag has
+ * just left the window.
+ */
+ [can_run_script]
+ void endDragSession(in boolean aDoneDrag,
+ [optional] in unsigned long aKeyModifiers);
+
+ /**
+ * Fire a drag event at the source of the drag
+ */
+ [noscript, can_run_script]
+ void fireDragEventAtSource(in EventMessage aEventMessage,
+ in unsigned long aKeyModifiers);
+
+ /**
+ * Increase/decrease dragging suppress level by one.
+ * If level is greater than one, dragging is disabled.
+ */
+ [can_run_script]
+ void suppress();
+ void unsuppress();
+
+ /**
+ * aX and aY are in LayoutDevice pixels.
+ */
+ [noscript] void dragMoved(in long aX, in long aY);
+
+ [notxpcom, nostdcall] boolean maybeAddChildProcess(in ContentParentPtr aChild);
+ [notxpcom, nostdcall] boolean removeAllChildProcesses();
+};
+
+
+%{ C++
+
+%}
diff --git a/widget/nsIDragSession.idl b/widget/nsIDragSession.idl
new file mode 100644
index 0000000000..a60fd8433b
--- /dev/null
+++ b/widget/nsIDragSession.idl
@@ -0,0 +1,125 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+#include "nsITransferable.idl"
+
+
+
+%{ C++
+#include "nsSize.h"
+%}
+interface nsIContentSecurityPolicy;
+
+native nsSize(nsSize);
+
+webidl DataTransfer;
+webidl Document;
+webidl Node;
+
+[scriptable, builtinclass, uuid(25bce737-73f0-43c7-bc20-c71044a73c5a)]
+interface nsIDragSession : nsISupports
+{
+ /**
+ * Set the current state of the drag, whether it can be dropped or not.
+ * usually the target "frame" sets this so the native system can render the correct feedback
+ */
+ attribute boolean canDrop;
+
+ /**
+ * Indicates if the drop event should be dispatched only to chrome.
+ */
+ attribute boolean onlyChromeDrop;
+
+ /**
+ * Sets the action (copy, move, link, et.c) for the current drag
+ */
+ attribute unsigned long dragAction;
+
+ /**
+ * Get the number of items that were dropped
+ */
+ readonly attribute unsigned long numDropItems;
+
+ /**
+ * The document where the drag was started, which will be null if the
+ * drag originated outside the application. Useful for determining if a drop
+ * originated in the same document.
+ */
+ [infallible]
+ readonly attribute Document sourceDocument;
+
+ /**
+ * The dom node that was originally dragged to start the session, which will be null if the
+ * drag originated outside the application.
+ */
+ readonly attribute Node sourceNode;
+
+ /**
+ * the triggering principal. This may be different than sourceNode's
+ * principal when sourceNode is xul:browser and the drag is
+ * triggered in a browsing context inside it.
+ */
+ attribute nsIPrincipal triggeringPrincipal;
+
+ /**
+ * the triggering csp. This may be different than sourceNode's
+ * csp when sourceNode is xul:browser and the drag is
+ * triggered in a browsing context inside it.
+ */
+ attribute nsIContentSecurityPolicy csp;
+
+ /**
+ * The data transfer object for the current drag.
+ */
+ [binaryname(DataTransferXPCOM)]
+ attribute DataTransfer dataTransfer;
+ [notxpcom, nostdcall] DataTransfer getDataTransfer();
+ [notxpcom, nostdcall] void setDataTransfer(in DataTransfer aDataTransfer);
+
+ /**
+ * Get data from a Drag&Drop. Can be called while the drag is in process
+ * or after the drop has completed.
+ *
+ * @param aTransferable the transferable for the data to be put into
+ * @param aItemIndex which of multiple drag items, zero-based
+ */
+ void getData ( in nsITransferable aTransferable, in unsigned long aItemIndex ) ;
+
+ /**
+ * Check to set if any of the native data on the clipboard matches this data flavor
+ */
+ boolean isDataFlavorSupported ( in string aDataFlavor ) ;
+
+ void userCancelled();
+
+ void dragEventDispatchedToChildProcess();
+
+ // Called when nsIDragSession implementation should update the UI for the
+ // drag-and-drop based on the data got from the child process in response to
+ // NS_DRAGDROP_OVER sent from parent process to child process.
+ void updateDragEffect();
+
+ // Change the drag image, using similar arguments as
+ // nsIDragService::InvokeDragSessionWithImage.
+ void updateDragImage(in Node aImage, in long aImageX, in long aImageY);
+
+ /**
+ * Returns effects allowed at starting the session for tests.
+ */
+ [notxpcom, nostdcall] unsigned long getEffectAllowedForTests();
+
+ /**
+ * Returns true if current session was started with synthesized drag start.
+ */
+ [notxpcom, nostdcall] bool isSynthesizedForTests();
+
+ /**
+ * Sets drag end point of synthesized session when the test does not dispatch
+ * "drop" event.
+ */
+ void setDragEndPointForTests(in long aScreenX, in long aScreenY);
+};
diff --git a/widget/nsIFilePicker.idl b/widget/nsIFilePicker.idl
new file mode 100644
index 0000000000..75034b6908
--- /dev/null
+++ b/widget/nsIFilePicker.idl
@@ -0,0 +1,215 @@
+/* -*- 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 nsIFile;
+interface nsIURI;
+interface mozIDOMWindowProxy;
+interface nsISimpleEnumerator;
+
+[scriptable, function, uuid(0d79adad-b244-49A5-9997-2a8cad93fc44)]
+interface nsIFilePickerShownCallback : nsISupports
+{
+ /**
+ * Callback which is called when a filepicker is shown and a result
+ * is returned.
+ *
+ * @param aResult One of returnOK, returnCancel, or returnReplace
+ */
+ void done(in short aResult);
+};
+
+[scriptable, uuid(9285b984-02d3-46b4-9514-7da8c471a747)]
+interface nsIFilePicker : nsISupports
+{
+ const short modeOpen = 0; // Load a file or directory
+ const short modeSave = 1; // Save a file or directory
+ const short modeGetFolder = 2; // Select a folder/directory
+ const short modeOpenMultiple= 3; // Load multiple files
+
+ const short returnOK = 0; // User hit Ok, process selection
+ const short returnCancel = 1; // User hit cancel, ignore selection
+ const short returnReplace = 2; // User acknowledged file already exists so ok to replace, process selection
+
+ const long filterAll = 0x001; // *.*
+ const long filterHTML = 0x002; // *.html; *.htm
+ const long filterText = 0x004; // *.txt
+ const long filterImages = 0x008; // *.jpe; *.jpg; *.jpeg; *.gif;
+ // *.png; *.bmp; *.ico; *.svg;
+ // *.svgz; *.tif; *.tiff; *.ai;
+ // *.drw; *.pct; *.psp; *.xcf;
+ // *.psd; *.raw; *.webp
+ const long filterXML = 0x010; // *.xml
+ const long filterXUL = 0x020; // *.xul
+ const long filterApps = 0x040; // Applications (per-platform implementation)
+ const long filterAllowURLs = 0x080; // Allow URLs
+ const long filterAudio = 0x100; // *.aac; *.aif; *.flac; *.iff;
+ // *.m4a; *.m4b; *.mid; *.midi;
+ // *.mp3; *.mpa; *.mpc; *.oga;
+ // *.ogg; *.ra; *.ram; *.snd;
+ // *.wav; *.wma
+ const long filterVideo = 0x200; // *.avi; *.divx; *.flv; *.m4v;
+ // *.mkv; *.mov; *.mp4; *.mpeg;
+ // *.mpg; *.ogm; *.ogv; *.ogx;
+ // *.rm; *.rmvb; *.smil; *.webm;
+ // *.wmv; *.xvid
+
+ const short captureNone = 0; // No capture target specified.
+ const short captureDefault = 1; // Missing/invalid value default.
+ const short captureUser = 2; // "user" capture target specified.
+ const short captureEnv = 3; // "environment" capture target specified.
+
+ /**
+ * Initialize the file picker widget. The file picker is not valid until this
+ * method is called.
+ *
+ * @param parent mozIDOMWindow parent. This dialog will be dependent
+ * on this parent. parent must be non-null.
+ * @param title The title for the file widget
+ * @param mode load, save, or get folder
+ *
+ */
+ void init(in mozIDOMWindowProxy parent, in AString title, in short mode);
+
+ /**
+ * Append to the filter list with things from the predefined list
+ *
+ * @param filters mask of filters i.e. (filterAll | filterHTML)
+ *
+ */
+ void appendFilters(in long filterMask);
+
+ /**
+ * Add a filter
+ *
+ * @param title name of the filter
+ * @param filter extensions to filter -- semicolon and space separated
+ *
+ */
+ void appendFilter(in AString title,
+ in AString filter);
+
+ /**
+ * Add a raw filter (eg, add a MIME type without transforming it to a list of
+ * extensions).
+ *
+ * @param filter a filter taken directly from the accept attribute
+ * without processing
+ *
+ */
+ void appendRawFilter(in AString filter);
+
+ /**
+ * The filename that should be suggested to the user as a default. This should
+ * include the extension.
+ *
+ * @throws NS_ERROR_FAILURE on attempts to get
+ */
+ attribute AString defaultString;
+
+ /**
+ * The extension that should be associated with files of the type we
+ * want to work with. On some platforms, this extension will be
+ * automatically appended to filenames the user enters, if needed.
+ */
+ attribute AString defaultExtension;
+
+ /**
+ * The filter which is currently selected in the File Picker dialog
+ *
+ * @return Returns the index (0 based) of the selected filter in the filter list.
+ */
+ attribute long filterIndex;
+
+ /**
+ * Set the directory that the file open/save dialog initially displays
+ * Note that, if displaySpecialDirectory has been already set, this value will
+ * be ignored.
+ *
+ * @param displayDirectory the name of the directory
+ *
+ */
+ attribute nsIFile displayDirectory;
+
+ /**
+ * Set the directory that the file open/save dialog initially displays using
+ * one of the special name as such as 'Desk', 'TmpD', and so on.
+ * Note that, if displayDirectory has been already set, this value will be
+ * ignored.
+ *
+ * @param displaySpecialDirectory the name of the special directory
+ *
+ */
+ attribute AString displaySpecialDirectory;
+
+
+ /**
+ * Get the nsIFile for the file or directory.
+ *
+ * @return Returns the file currently selected
+ */
+ readonly attribute nsIFile file;
+
+ /**
+ * Get the nsIURI for the file or directory.
+ *
+ * @return Returns the file currently selected
+ */
+ readonly attribute nsIURI fileURL;
+
+ /**
+ * Get the enumerator for the selected files
+ * only works in the modeOpenMultiple mode
+ *
+ * @return Returns the files currently selected
+ */
+ readonly attribute nsISimpleEnumerator files;
+
+ /**
+ * Get the DOM File or the DOM Directory
+ *
+ * @return Returns the file or directory currently selected DOM object.
+ */
+ readonly attribute nsISupports domFileOrDirectory;
+
+ /**
+ * Get the enumerator for the selected files or directories
+ * only works in the modeOpenMultiple mode
+ *
+ * @return Returns the files/directories currently selected as DOM object.
+ */
+ readonly attribute nsISimpleEnumerator domFileOrDirectoryEnumerator;
+
+ /**
+ * Controls whether the chosen file(s) should be added to the system's recent
+ * documents list. This attribute will be ignored if the system has no "Recent
+ * Docs" concept, or if the application is in private browsing mode (in which
+ * case the file will not be added). Defaults to true.
+ */
+ attribute boolean addToRecentDocs;
+
+ /**
+ * Opens the file dialog asynchrounously.
+ * The passed in object's done method will be called upon completion.
+ */
+ void open(in nsIFilePickerShownCallback aFilePickerShownCallback);
+
+ /**
+ * The picker's mode, as set by the 'mode' argument passed to init()
+ * (one of the modeOpen et. al. constants specified above).
+ */
+ readonly attribute short mode;
+
+ /**
+ * If set to non-empty string, the nsIFilePicker implementation
+ * may use okButtonLabel as the label for the button the user uses to accept
+ * file selection.
+ */
+ attribute AString okButtonLabel;
+
+ attribute short capture;
+};
diff --git a/widget/nsIFormatConverter.idl b/widget/nsIFormatConverter.idl
new file mode 100644
index 0000000000..0d72f49d2a
--- /dev/null
+++ b/widget/nsIFormatConverter.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(948A0023-E3A7-11d2-96CF-0060B0FB9956)]
+interface nsIFormatConverter : nsISupports
+{
+ /**
+ * Get the list of the "input" data flavors (mime types as nsISupportsCString),
+ * in otherwords, the flavors that this converter can convert "from" (the
+ * incoming data to the converter).
+ */
+ Array<ACString> getInputDataFlavors ( ) ;
+
+ /**
+ * Get the list of the "output" data flavors (mime types as nsISupportsCString),
+ * in otherwords, the flavors that this converter can convert "to" (the
+ * outgoing data to the converter).
+ *
+ * @param aDataFlavorList fills list with supported flavors
+ */
+ Array<ACString> getOutputDataFlavors ( ) ;
+
+ /**
+ * Determines whether a conversion from one flavor to another is supported
+ *
+ * @param aFromFormatConverter flavor to convert from
+ * @param aFromFormatConverter flavor to convert to
+ */
+ boolean canConvert ( in string aFromDataFlavor, in string aToDataFlavor ) ;
+
+ /**
+ * Converts from one flavor to another.
+ *
+ * @param aFromFormatConverter flavor to convert from
+ * @param aFromFormatConverter flavor to convert to (destination own the memory)
+ * @returns returns NS_OK if it was converted
+ */
+ void convert ( in string aFromDataFlavor, in nsISupports aFromData,
+ in string aToDataFlavor, out nsISupports aToData ) ;
+
+};
+
+
+%{ C++
+
+%}
diff --git a/widget/nsIGfxInfo.idl b/widget/nsIGfxInfo.idl
new file mode 100644
index 0000000000..04cfcd98ae
--- /dev/null
+++ b/widget/nsIGfxInfo.idl
@@ -0,0 +1,314 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+
+/* NOTE: this interface is completely undesigned, not stable and likely to change */
+[scriptable, builtinclass, uuid(1accd618-4c80-4703-9d29-ecf257d397c8)]
+interface nsIGfxInfo : nsISupports
+{
+ /*
+ * These are win32-specific
+ */
+ readonly attribute boolean D2DEnabled;
+ readonly attribute boolean DWriteEnabled;
+ readonly attribute boolean EmbeddedInFirefoxReality;
+ readonly attribute boolean usingGPUProcess;
+ readonly attribute boolean hasBattery;
+ readonly attribute AString DWriteVersion;
+ readonly attribute AString cleartypeParameters;
+
+ /*
+ * These are non-Android linux-specific
+ */
+ readonly attribute AString windowProtocol;
+ readonly attribute AString desktopEnvironment;
+
+ /*
+ * These are valid across all platforms.
+ */
+ readonly attribute AString ContentBackend;
+ readonly attribute boolean WebRenderEnabled;
+ readonly attribute boolean isHeadless;
+ readonly attribute boolean UsesTiling;
+ readonly attribute boolean ContentUsesTiling;
+ readonly attribute boolean OffMainThreadPaintEnabled;
+ readonly attribute long OffMainThreadPaintWorkerCount;
+ readonly attribute unsigned long TargetFrameRate;
+
+ // XXX: Switch to a list of devices, rather than explicitly numbering them.
+
+ /**
+ * The name of the display adapter.
+ */
+ readonly attribute AString adapterDescription;
+ readonly attribute AString adapterDescription2;
+
+ readonly attribute AString adapterDriver;
+ readonly attribute AString adapterDriver2;
+
+ /* These types are inspired by DXGI_ADAPTER_DESC */
+ readonly attribute AString adapterVendorID;
+ readonly attribute AString adapterVendorID2;
+
+ readonly attribute AString adapterDeviceID;
+ readonly attribute AString adapterDeviceID2;
+
+ readonly attribute AString adapterSubsysID;
+ readonly attribute AString adapterSubsysID2;
+
+ /**
+ * The amount of RAM in MB in the display adapter.
+ */
+ readonly attribute unsigned long adapterRAM;
+ readonly attribute unsigned long adapterRAM2;
+
+ readonly attribute AString adapterDriverVendor;
+ readonly attribute AString adapterDriverVendor2;
+
+ readonly attribute AString adapterDriverVersion;
+ readonly attribute AString adapterDriverVersion2;
+
+ readonly attribute AString adapterDriverDate;
+ readonly attribute AString adapterDriverDate2;
+
+ readonly attribute boolean isGPU2Active;
+
+ readonly attribute ACString drmRenderDevice;
+
+ /**
+ * Information about display devices
+ */
+ readonly attribute Array<AString> displayInfo;
+ readonly attribute Array<unsigned long> displayWidth;
+ readonly attribute Array<unsigned long> displayHeight;
+
+ /**
+ * Returns an array of objects describing each monitor. Guaranteed properties
+ * are "screenWidth" and "screenHeight". This is only implemented on Desktop.
+ *
+ * Windows additionally supplies "refreshRate" and "pseudoDisplay".
+ *
+ * OS X additionally supplies "scale".
+ */
+ [implicit_jscontext]
+ jsval getMonitors();
+
+ Array<ACString> getFailures(out Array<long> indices);
+
+ [noscript, notxpcom] void logFailure(in ACString failure);
+ /*
+ * A set of constants for features that we can ask this GfxInfo object
+ * about via GetFeatureStatus
+ */
+ /* Don't assign any value <= 0 */
+ /* Values must be contiguous */
+ /* Whether Direct2D is supported for content rendering. */
+ const long FEATURE_DIRECT2D = 1;
+ /* Whether Direct3D 9 is supported for layers. */
+ const long FEATURE_DIRECT3D_9_LAYERS = 2;
+ /* Whether Direct3D 10.0 is supported for layers. */
+ const long FEATURE_DIRECT3D_10_LAYERS = 3;
+ /* Whether Direct3D 10.1 is supported for layers. */
+ const long FEATURE_DIRECT3D_10_1_LAYERS = 4;
+ /* Whether OpenGL is supported for layers */
+ const long FEATURE_OPENGL_LAYERS = 5;
+ /* Whether WebGL is supported via OpenGL. */
+ const long FEATURE_WEBGL_OPENGL = 6;
+ /* Whether WebGL is supported via ANGLE (D3D9 -- does not check for the presence of ANGLE libs). */
+ const long FEATURE_WEBGL_ANGLE = 7;
+ /* (Unused) Whether WebGL antialiasing is supported. */
+ const long UNUSED_FEATURE_WEBGL_MSAA = 8;
+ /* Whether Stagefright is supported, starting in 17. */
+ const long FEATURE_STAGEFRIGHT = 9;
+ /* Whether Webrtc Hardware H.264 acceleration is supported, starting in 71. */
+ const long FEATURE_WEBRTC_HW_ACCELERATION_H264 = 10;
+ /* Whether Direct3D 11 is supported for layers, starting in 32. */
+ const long FEATURE_DIRECT3D_11_LAYERS = 11;
+ /* Whether hardware accelerated video decoding is supported, starting in 36. */
+ const long FEATURE_HARDWARE_VIDEO_DECODING = 12;
+ /* Whether Direct3D 11 is supported for ANGLE, starting in 38. */
+ const long FEATURE_DIRECT3D_11_ANGLE = 13;
+ /* Whether Webrtc Hardware acceleration is supported, starting in 42. */
+ const long FEATURE_WEBRTC_HW_ACCELERATION_ENCODE = 14;
+ /* Whether Webrtc Hardware acceleration is supported, starting in 42. */
+ const long FEATURE_WEBRTC_HW_ACCELERATION_DECODE = 15;
+ /* Whether Canvas acceleration is supported, starting in 45 */
+ const long FEATURE_CANVAS2D_ACCELERATION = 16;
+ /* Whether hardware VP8 decoding is supported, starting in 48; not for downloadable blocking. */
+ const long FEATURE_VP8_HW_DECODE = 17;
+ /* Whether hardware VP9 decoding is supported, starting in 48; not for downloadable blocking. */
+ const long FEATURE_VP9_HW_DECODE = 18;
+ /* Whether NV_dx_interop2 is supported, starting in 50; downloadable blocking in 58. */
+ const long FEATURE_DX_INTEROP2 = 19;
+ /* Whether the GPU process is supported, starting in 52; downloadable blocking in 58. */
+ const long FEATURE_GPU_PROCESS = 20;
+ /* Whether the WebGL2 is supported, starting in 54 */
+ const long FEATURE_WEBGL2 = 21;
+ /* Whether Advanced Layers is supported, starting in 56 */
+ const long FEATURE_ADVANCED_LAYERS = 22;
+ /* Whether D3D11 keyed mutex is supported, starting in 56 */
+ const long FEATURE_D3D11_KEYED_MUTEX = 23;
+ /* Whether WebRender is supported, starting in 62 */
+ const long FEATURE_WEBRENDER = 24;
+ /* Whether WebRender is supported, starting in 62 */
+ const long FEATURE_DX_NV12 = 25;
+ const long FEATURE_DX_P010 = 26;
+ const long FEATURE_DX_P016 = 27;
+ /* Whether OpenGL swizzle configuration of texture units is supported, starting in 70 */
+ const long FEATURE_GL_SWIZZLE = 28;
+ /* Whether WebRender native compositor is supported, starting in 73 */
+ const long FEATURE_WEBRENDER_COMPOSITOR = 29;
+ /* Whether WebRender can use scissored clears for cached surfaces, staring in 79 */
+ const long FEATURE_WEBRENDER_SCISSORED_CACHE_CLEARS = 30;
+ /* Support webgl.out-of-process: true (starting in 83) */
+ const long FEATURE_ALLOW_WEBGL_OUT_OF_PROCESS = 31;
+ /* Is OpenGL threadsafe (starting in 83) */
+ const long FEATURE_THREADSAFE_GL = 32;
+ /* Support running WebRender using the software backend, starting in 84. */
+ const long FEATURE_WEBRENDER_SOFTWARE = 33;
+ /* the maximum feature value. */
+ const long FEATURE_MAX_VALUE = FEATURE_WEBRENDER_SOFTWARE;
+
+ /*
+ * A set of return values from GetFeatureStatus
+ */
+
+ /* The driver is safe to the best of our knowledge */
+ const long FEATURE_STATUS_OK = 1;
+ /* We don't know the status of the feature yet. The analysis probably hasn't finished yet. */
+ const long FEATURE_STATUS_UNKNOWN = 2;
+ /* This feature is blocked on this driver version. Updating driver will typically unblock it. */
+ const long FEATURE_BLOCKED_DRIVER_VERSION = 3;
+ /* This feature is blocked on this device, regardless of driver version.
+ * Typically means we hit too many driver crashes without a good reason to hope for them to
+ * get fixed soon. */
+ const long FEATURE_BLOCKED_DEVICE = 4;
+ /* This feature is available and can be used, but is not suggested (e.g. shouldn't be used by default */
+ const long FEATURE_DISCOURAGED = 5;
+ /* This feature is blocked on this OS version. */
+ const long FEATURE_BLOCKED_OS_VERSION = 6;
+ /* This feature is blocked because of mismatched driver versions. */
+ const long FEATURE_BLOCKED_MISMATCHED_VERSION = 7;
+ /* This feature is blocked due to not being on the allowlist. */
+ const long FEATURE_DENIED = 8;
+ /* This feature is safe to be on this device due to the allowlist. */
+ const long FEATURE_ALLOW_ALWAYS = 9;
+ /* This feature is safe to be on this device due to the allowlist, depending on qualified/experiment status. */
+ const long FEATURE_ALLOW_QUALIFIED = 10;
+
+ /**
+ * Ask about a feature, and return the status of that feature.
+ * If the feature is not ok then aFailureId will give a unique failure Id
+ * otherwise it will be empty.
+ */
+ long getFeatureStatus(in long aFeature, [optional] out ACString aFailureId);
+
+ /*
+ * Ask about a feature, return the minimum driver version required for it if its status is
+ * FEATURE_BLOCKED_DRIVER_VERSION, otherwise return an empty string.
+ */
+ AString getFeatureSuggestedDriverVersion(in long aFeature);
+
+ // only useful on X11
+ [noscript, notxpcom] void GetData();
+
+ /**
+ * Maximum refresh rate among detected monitors. -1 if unknown. aMixed is set
+ * to true if we know there are multiple displays and they have different
+ * refresh rates, else false.
+ */
+ [noscript, notxpcom] long GetMaxRefreshRate(out boolean aMixed);
+
+ [implicit_jscontext]
+ jsval getInfo();
+
+ // Return an object describing all features that have been configured:
+ //
+ // "features": [
+ // // For each feature:
+ // {
+ // "name": <string>,
+ // "description": <string>,
+ // "status": <string>,
+ // "log": [
+ // // One or more log entries, the first denotes the default value.
+ // {
+ // "type": <string>, // "base", "user", "env", or "runtime"
+ // "status": <string>,
+ // "message": <string> // Set unless type is "base" and status is "available".
+ // }
+ // ]
+ // }
+ // ]
+ // "fallbacks": [
+ // // For each workaround:
+ // {
+ // "name:" <string>,
+ // "description": <string>,
+ // "message": <string>
+ // ]
+ // }
+ //
+ // When a message is prefixed with a '#', it is a special status code. Status
+ // codes are unique identifiers that can be searched in the codebase to find
+ // which line of code caused the message. Some codes are standardized to
+ // improve about:support messaging:
+ //
+ // "[CONTEXT_]FEATURE_FAILURE_BUG_<number>"
+ // CONTEXT is optional and can currently only be "BLOCKLIST".
+ // <number> refers to a bug number in Bugzilla.
+ //
+ [implicit_jscontext]
+ jsval getFeatureLog();
+
+ // Returns an object containing information about graphics features. It is
+ // intended to be directly included into the Telemetry environment.
+ //
+ // "layers":
+ // {
+ // "compositor": "d3d9", "d3d11", "opengl", "basic", or "none"
+ // // ("none" indicates no compositors have been created)
+ // // Feature is one of "d3d9", "d3d11", "opengl", "basic", or "d2d".
+ // "<feature>": {
+ // // Each backend can have one of the following statuses:
+ // // "unused" - This feature has not been requested.
+ // // "unavailable" - OS version or restriction prevents use.
+ // // "blocked" - An internal condition (such as safe mode) prevents use.
+ // // "blocklisted" - Blocked due to a blocklist restriction.
+ // // "denied" - Blocked due to allowlist restrictions.
+ // // "disabled" - User explicitly disabled this default feature.
+ // // "failed" - Feature failed to initialize.
+ // // "available" - User has this feature available by default.
+ // "status": "<status>",
+ // "version": "<version>",
+ // "warp": true|false, // D3D11 only.
+ // "textureSharing": true|false, // D3D11 only.
+ // }
+ // }
+ [implicit_jscontext]
+ jsval getFeatures();
+
+ // Returns an array listing any active crash guards.
+ //
+ // [
+ // {
+ // // Type is one of "d3d11layers", or "glcontext".
+ // "type": "<identifier>",
+ //
+ // // Preference that must be deleted/reset to retrigger the guard.
+ // "prefName": "<preference>",
+ // }
+ // ]
+ [implicit_jscontext]
+ jsval getActiveCrashGuards();
+
+ // Forces the GPU process to start or shutdown. This is intended only for
+ // xpcshell-tests.
+ boolean controlGPUProcessForXPCShell(in boolean aEnable);
+};
+
+
diff --git a/widget/nsIGfxInfoDebug.idl b/widget/nsIGfxInfoDebug.idl
new file mode 100644
index 0000000000..afdbcef1e3
--- /dev/null
+++ b/widget/nsIGfxInfoDebug.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+/* NOTE: this interface is only implemented in debug builds */
+
+[scriptable, uuid(ca7b0bc7-c67c-4b79-8270-ed7ba002af08)]
+interface nsIGfxInfoDebug : nsISupports
+{
+ void spoofVendorID(in AString aVendorID);
+ void spoofDeviceID(in AString aDeviceID);
+
+ void spoofDriverVersion(in AString aDriverVersion);
+
+ void spoofOSVersion(in unsigned long aVersion);
+
+ /* Manually invoke any test processes required to query for driver
+ information. This is used by XPC shell tests which do not run these queries
+ by default. */
+ void fireTestProcess();
+};
diff --git a/widget/nsIGtkTaskbarProgress.idl b/widget/nsIGtkTaskbarProgress.idl
new file mode 100644
index 0000000000..ebefe5e3a1
--- /dev/null
+++ b/widget/nsIGtkTaskbarProgress.idl
@@ -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/. */
+
+#include "nsITaskbarProgress.idl"
+
+interface mozIDOMWindowProxy;
+
+/**
+ * Allow the TaskbarProgress instance to set a new target window.
+ */
+
+[scriptable, uuid(39f6fc5a-2386-4bc6-941c-d7479253bc3f)]
+interface nsIGtkTaskbarProgress : nsITaskbarProgress
+{
+ /**
+ * Sets the window that is considered primary for purposes of
+ * setting the XApp progress property.
+ */
+
+ void setPrimaryWindow(in mozIDOMWindowProxy aWindow);
+};
diff --git a/widget/nsIJumpListBuilder.idl b/widget/nsIJumpListBuilder.idl
new file mode 100644
index 0000000000..d56a7f9570
--- /dev/null
+++ b/widget/nsIJumpListBuilder.idl
@@ -0,0 +1,159 @@
+/* -*- 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 nsIArray;
+
+[scriptable, function, uuid(5131a62a-e99f-4631-9138-751f8aad1ae4)]
+interface nsIJumpListCommittedCallback : nsISupports
+{
+ void done(in boolean result);
+};
+
+[scriptable, uuid(1FE6A9CD-2B18-4dd5-A176-C2B32FA4F683)]
+interface nsIJumpListBuilder : nsISupports
+{
+ /**
+ * JumpLists
+ *
+ * Jump lists are built and then applied. Modifying an applied jump list is not
+ * permitted. Callers should begin the creation of a new jump list using
+ * initListBuild, add sub lists using addListToBuild, then commit the jump list
+ * using commitListBuild. Lists are built in real-time during the sequence of
+ * build calls, make sure to check for errors on each individual step.
+ *
+ * The default number of allowed items in a jump list is ten. Users can change
+ * the number through system preferences. User may also pin items to jump lists,
+ * which take up additional slots. Applications do not have control over the
+ * number of items allowed in jump lists; excess items added are dropped by the
+ * system. Item insertion priority is defined as first to last added.
+ *
+ * Users may remove items from jump lists after they are commited. The system
+ * tracks removed items between commits. A list of these items is returned by
+ * a call to initListBuild. nsIJumpListBuilder does not filter entries added that
+ * have been removed since the last commit. To prevent repeatedly adding entries
+ * users have removed, applications are encoraged to track removed items
+ * internally.
+ *
+ * Each list is made up of an array of nsIJumpListItem representing items
+ * such as shortcuts, links, and separators. See nsIJumpListItem for information
+ * on adding additional jump list types.
+ */
+
+ /**
+ * List Types
+ */
+
+ /**
+ * Task List
+ *
+ * Tasks are common actions performed by users within the application. A task
+ * can be represented by an application shortcut and associated command line
+ * parameters or a URI. Task lists should generally be static lists that do not
+ * change often, if at all - similar to an application menu.
+ *
+ * Tasks are given the highest priority of all lists when space is limited.
+ */
+ const short JUMPLIST_CATEGORY_TASKS = 0;
+
+ /**
+ * Recent or Frequent list
+ *
+ * Recent and frequent lists are based on Window's recent document lists. The
+ * lists are generated automatically by Windows. Applications that use recent
+ * or frequent lists should keep document use tracking up to date by calling
+ * the SHAddToRecentDocs shell api.
+ */
+ const short JUMPLIST_CATEGORY_RECENT = 1;
+ const short JUMPLIST_CATEGORY_FREQUENT = 2;
+
+ /**
+ * Custom Lists
+ *
+ * Custom lists can be made up of tasks, links, and separators. The title of
+ * of the list is passed through the optional string parameter of addBuildList.
+ */
+ const short JUMPLIST_CATEGORY_CUSTOMLIST = 3;
+
+ /**
+ * Indicates whether jump list taskbar features are supported by the current
+ * host.
+ */
+ readonly attribute short available;
+
+ /**
+ * JumpList management
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE on all calls if taskbar functionality
+ * is not supported by the operating system.
+ */
+
+ /**
+ * Indicates if a commit has already occurred in this session.
+ */
+ readonly attribute boolean isListCommitted;
+
+ /**
+ * The maximum number of jump list items the current desktop can support.
+ */
+ readonly attribute short maxListItems;
+
+ /**
+ * Initializes a jump list build and returns a promise with the list of
+ * items the user removed since the last time a jump list was committed.
+ * Removed items can become state after initListBuild is called, lists
+ * should be built in single-shot fasion.
+ *
+ * @returns a promise with the list of items that were removed by the user
+ * since the last commit.
+ */
+ [implicit_jscontext]
+ Promise initListBuild();
+
+ /**
+ * Adds a list and if required, a set of items for the list.
+ *
+ * @param aCatType
+ * The type of list to add.
+ * @param items
+ * An array of nsIJumpListItem items to add to the list.
+ * @param catName
+ * For custom lists, the title of the list.
+ *
+ * @returns true if the operation completed successfully.
+ *
+ * @throw NS_ERROR_INVALID_ARG if incorrect parameters are passed for
+ * a particular category or item type.
+ * @throw NS_ERROR_ILLEGAL_VALUE if an item is added that was removed
+ * since the last commit.
+ * @throw NS_ERROR_UNEXPECTED on internal errors.
+ */
+ boolean addListToBuild(in short aCatType, [optional] in nsIArray items, [optional] in AString catName);
+
+ /**
+ * Aborts and clears the current jump list build.
+ */
+ void abortListBuild();
+
+ /**
+ * Commits the current jump list build to the Taskbar.
+ *
+ * @param callback
+ * Receives one argument, which is true if the operation completed
+ * successfully, otherwise it is false.
+ */
+ void commitListBuild([optional] in nsIJumpListCommittedCallback callback);
+
+ /**
+ * Deletes any currently applied taskbar jump list for this application.
+ * Common uses would be the enabling of a privacy mode and uninstallation.
+ *
+ * @returns true if the operation completed successfully.
+ *
+ * @throw NS_ERROR_UNEXPECTED on internal errors.
+ */
+ boolean deleteActiveList();
+};
diff --git a/widget/nsIJumpListItem.idl b/widget/nsIJumpListItem.idl
new file mode 100644
index 0000000000..5097e1d7c2
--- /dev/null
+++ b/widget/nsIJumpListItem.idl
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsILocalHandlerApp;
+interface nsIMutableArray;
+
+/**
+ * Implements Win7 Taskbar jump list item interfaces.
+ *
+ * Note to consumers: it's reasonable to expect we'll need support for other types
+ * of jump list items (an audio file, an email message, etc.). To add types,
+ * create the specific interface here, add an implementation class to WinJumpListItem,
+ * and add support to addListBuild & removed items processing.
+ *
+ */
+
+[scriptable, uuid(ACB8FB3C-E1B0-4044-8A50-E52C3E7C1057)]
+interface nsIJumpListItem : nsISupports
+{
+ const short JUMPLIST_ITEM_EMPTY = 0; // Empty list item
+ const short JUMPLIST_ITEM_SEPARATOR = 1; // Separator
+ const short JUMPLIST_ITEM_LINK = 2; // Web link item
+ const short JUMPLIST_ITEM_SHORTCUT = 3; // Application shortcut
+
+ /**
+ * Retrieves the jump list item type.
+ */
+ readonly attribute short type;
+
+ /**
+ * Compare this item to another.
+ *
+ * Compares the type and other properties specific to this item's
+ * type.
+ *
+ * separator: type
+ * link: type, uri, title
+ * shortcut: type, handler app
+ */
+ boolean equals(in nsIJumpListItem item);
+};
+
+/**
+ * A menu separator.
+ */
+
+[scriptable, uuid(69A2D5C5-14DC-47da-925D-869E0BD64D27)]
+interface nsIJumpListSeparator : nsIJumpListItem
+{
+ /* nothing needed here */
+};
+
+/**
+ * A URI link jump list item.
+ *
+ * Note the application must be the registered protocol
+ * handler for the protocol of the link.
+ */
+
+[scriptable, uuid(76EA47B1-C797-49b3-9F18-5E740A688524)]
+interface nsIJumpListLink : nsIJumpListItem
+{
+ /**
+ * Set or get the uri for this link item.
+ */
+ attribute nsIURI uri;
+
+ /**
+ * Set or get the title for a link item.
+ */
+ attribute AString uriTitle;
+
+ /**
+ * Get a 'privacy safe' unique string hash of the uri's
+ * spec. Useful in tracking removed items using visible
+ * data stores such as prefs. Generates an MD5 hash of
+ * the URI spec using nsICryptoHash.
+ */
+ readonly attribute ACString uriHash;
+
+ /**
+ * Compare this item's hash to another uri.
+ *
+ * Generates a spec hash of the incoming uri and compares
+ * it to this item's uri spec hash.
+ */
+ boolean compareHash(in nsIURI uri);
+};
+
+/**
+ * A generic application shortcut with command line support.
+ */
+
+[scriptable, uuid(CBE3A37C-BCE1-4fec-80A5-5FFBC7F33EEA)]
+interface nsIJumpListShortcut : nsIJumpListItem
+{
+ /**
+ * Set or get the handler app for this shortcut item.
+ *
+ * The handler app may also be used along with iconIndex to generate an icon
+ * for the jump list item.
+ *
+ * @throw NS_ERROR_FILE_NOT_FOUND if the handler app can
+ * not be found on the system.
+ *
+ * @see faviconPageUri
+ */
+ attribute nsILocalHandlerApp app;
+
+ /**
+ * Set or get the icon displayed with the jump list item.
+ *
+ * Indicates the resource index of the icon contained within the handler
+ * executable which may be used as the jump list icon.
+ *
+ * @see faviconPageUri
+ */
+ attribute long iconIndex;
+
+ /**
+ * Set or get the URI of a page whose favicon may be used as the icon.
+ *
+ * When a jump list build occurs, the favicon to be used for the item is
+ * obtained using the following steps:
+ * - First, attempt to use the asynchronously retrieved and scaled favicon
+ * associated with the faviconPageUri.
+ * - If faviconPageUri is null, or if retrieving the favicon fails, fall
+ * back to using the handler executable and iconIndex.
+ */
+ attribute nsIURI faviconPageUri;
+};
+
diff --git a/widget/nsIKeyEventInPluginCallback.h b/widget/nsIKeyEventInPluginCallback.h
new file mode 100644
index 0000000000..f594739254
--- /dev/null
+++ b/widget/nsIKeyEventInPluginCallback.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 40; 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 nsIKeyEventInPluginCallback_h_
+#define nsIKeyEventInPluginCallback_h_
+
+#include "mozilla/EventForwards.h"
+
+#include "nsISupports.h"
+
+#define NS_IKEYEVENTINPLUGINCALLBACK_IID \
+ { \
+ 0x543c5a8a, 0xc50e, 0x4cf9, { \
+ 0xa6, 0xba, 0x29, 0xa1, 0xc5, 0xa5, 0x47, 0x07 \
+ } \
+ }
+
+class nsIKeyEventInPluginCallback : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IKEYEVENTINPLUGINCALLBACK_IID)
+
+ /**
+ * HandledWindowedPluginKeyEvent() is a callback method of
+ * nsIWidget::OnWindowedPluginKeyEvent(). When it returns
+ * NS_SUCCESS_EVENT_HANDLED_ASYNCHRONOUSLY, it should call this method
+ * when the key event is handled.
+ *
+ * @param aKeyEventData The key event which was posted to the parent
+ * process from a plugin process.
+ * @param aIsConsumed true if aKeyEventData is consumed in the
+ * parent process. Otherwise, false.
+ */
+ virtual void HandledWindowedPluginKeyEvent(
+ const mozilla::NativeEventData& aKeyEventData, bool aIsConsumed) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIKeyEventInPluginCallback,
+ NS_IKEYEVENTINPLUGINCALLBACK_IID)
+
+#endif // #ifndef nsIKeyEventInPluginCallback_h_
diff --git a/widget/nsIMacDockSupport.idl b/widget/nsIMacDockSupport.idl
new file mode 100644
index 0000000000..375d4ca6b4
--- /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/nsIMacFinderProgress.idl b/widget/nsIMacFinderProgress.idl
new file mode 100644
index 0000000000..3c568bf1b5
--- /dev/null
+++ b/widget/nsIMacFinderProgress.idl
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+
+[scriptable, function, uuid(6BAE6D1C-7FFD-4354-8D7B-64697E98A801)]
+interface nsIMacFinderProgressCanceledCallback : nsISupports
+{
+ void canceled();
+};
+
+
+[scriptable, uuid(25A0B01F-54D4-4AEF-B2BF-C5764CDC68A8)]
+interface nsIMacFinderProgress : nsISupports
+{
+ /**
+ * Initialize and display a new Finder progressbar on the given file
+ *
+ * @param path The path of the file
+ *
+ * @param canceledCallback Callback which is called when cancelation is requested
+ */
+ void init(in AString path, in nsIMacFinderProgressCanceledCallback canceledCallback);
+
+ /**
+ * Update the current and total progess. If currentProgress and totalProgress are both 0,
+ * the progress is indetermined
+ *
+ * @param currentProgress The current progress
+ *
+ * @param totalProgress The total progress
+ */
+ void updateProgress(in unsigned long long currentProgress, in unsigned long long totalProgress);
+
+ /**
+ * End displaying the progressbar on the file
+ */
+ void end();
+};
diff --git a/widget/nsIMacSharingService.idl b/widget/nsIMacSharingService.idl
new file mode 100644
index 0000000000..cec0dae2d7
--- /dev/null
+++ b/widget/nsIMacSharingService.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Allow applications to interface with the Mac OS X Sharing APIs.
+ */
+
+[scriptable, uuid(de59fe1a-46c8-490f-b04d-34545acb06c9)]
+interface nsIMacSharingService : nsISupports
+{
+ /**
+ * Get list of sharing providers
+ */
+ [implicit_jscontext] jsval getSharingProviders(in AString pageUrl);
+
+ /**
+ * Launch service with shareTitle with given url
+ */
+ void shareUrl(in AString serviceName,
+ in AString pageUrl,
+ in AString pageTitle);
+
+ /**
+ * Open the MacOS preferences window to the sharing panel
+ */
+ void openSharingPreferences();
+};
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/nsINativeMenuService.h b/widget/nsINativeMenuService.h
new file mode 100644
index 0000000000..e92d7a74a3
--- /dev/null
+++ b/widget/nsINativeMenuService.h
@@ -0,0 +1,39 @@
+/* -*- 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 nsINativeMenuService_h_
+#define nsINativeMenuService_h_
+
+#include "nsISupports.h"
+
+class nsIWidget;
+class nsIContent;
+namespace mozilla {
+namespace dom {
+class Element;
+}
+} // namespace mozilla
+
+// {90DF88F9-F084-4EF3-829A-49496E636DED}
+#define NS_INATIVEMENUSERVICE_IID \
+ { \
+ 0x90DF88F9, 0xF084, 0x4EF3, { \
+ 0x82, 0x9A, 0x49, 0x49, 0x6E, 0x63, 0x6D, 0xED \
+ } \
+ }
+
+class nsINativeMenuService : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_INATIVEMENUSERVICE_IID)
+ // Given a top-level window widget and a menu bar DOM node, sets up native
+ // menus. Once created, native menus are controlled via the DOM, including
+ // destruction.
+ NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent,
+ mozilla::dom::Element* aMenuBarNode) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsINativeMenuService, NS_INATIVEMENUSERVICE_IID)
+
+#endif // nsINativeMenuService_h_
diff --git a/widget/nsIPaper.idl b/widget/nsIPaper.idl
new file mode 100644
index 0000000000..0ec957d221
--- /dev/null
+++ b/widget/nsIPaper.idl
@@ -0,0 +1,42 @@
+/* -*- 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 "nsISupports.idl"
+
+[scriptable, uuid(a4dd9675-6311-45a9-a547-44e0127304a6)]
+interface nsIPaper : nsISupports
+{
+ /**
+ * The internal name of the paper (a fixed, non-localized ID).
+ * (For CUPS, this is the PWG-standardized name as used internally by CUPS;
+ * on Windows, it is the integer paper ID as a string.)
+ */
+ readonly attribute AString id;
+
+ /**
+ * The human-readable (potentially localized) name of the paper.
+ */
+ readonly attribute AString name;
+
+ /**
+ * The width of the paper assuming portrait orientation, in points.
+ * That is, the length of the shorter edges of the paper.
+ */
+ readonly attribute double width;
+
+ /**
+ * The height of the paper assuming portrait orientation, in points.
+ * That is, the length of the longer edges of the paper.
+ */
+ readonly attribute double height;
+
+ /**
+ * The Promise resolves with an nsIPaperMargin object. The margin widths contained
+ * in that object's top/bottom/left/right properties are relative to the paper in
+ * portrait orientation. That is, top and bottom are the margins for the short edges,
+ * and left and right are the margins for the long edges.
+ */
+ [implicit_jscontext] readonly attribute Promise unwriteableMargin;
+};
diff --git a/widget/nsIPaperMargin.idl b/widget/nsIPaperMargin.idl
new file mode 100644
index 0000000000..80242f63b3
--- /dev/null
+++ b/widget/nsIPaperMargin.idl
@@ -0,0 +1,16 @@
+/* -*- 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 "nsISupports.idl"
+
+// A margin for a sheet of paper.
+[builtinclass, scriptable, uuid(0858d1a7-b646-4b15-a1e8-7eb5ab572d0a)]
+interface nsIPaperMargin : nsISupports
+{
+ [infallible] readonly attribute double top;
+ [infallible] readonly attribute double right;
+ [infallible] readonly attribute double bottom;
+ [infallible] readonly attribute double left;
+};
diff --git a/widget/nsIPluginWidget.h b/widget/nsIPluginWidget.h
new file mode 100644
index 0000000000..9dcd04679b
--- /dev/null
+++ b/widget/nsIPluginWidget.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/. */
+
+#include "nsISupports.h"
+#include "nsPoint.h"
+
+#define NS_IPLUGINWIDGET_IID \
+ { \
+ 0xEB9207E0, 0xD8F1, 0x44B9, { \
+ 0xB7, 0x52, 0xAF, 0x8E, 0x9F, 0x8E, 0xBD, 0xF7 \
+ } \
+ }
+
+class nsIPluginInstanceOwner;
+
+/**
+ * This is used by Mac only.
+ */
+class NS_NO_VTABLE nsIPluginWidget : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IPLUGINWIDGET_IID)
+
+ NS_IMETHOD GetPluginClipRect(nsIntRect& outClipRect, nsIntPoint& outOrigin,
+ bool& outWidgetVisible) = 0;
+
+ NS_IMETHOD StartDrawPlugin(void) = 0;
+
+ NS_IMETHOD EndDrawPlugin(void) = 0;
+
+ NS_IMETHOD SetPluginInstanceOwner(
+ nsIPluginInstanceOwner* pluginInstanceOwner) = 0;
+
+ NS_IMETHOD SetPluginEventModel(int inEventModel) = 0;
+
+ NS_IMETHOD GetPluginEventModel(int* outEventModel) = 0;
+
+ NS_IMETHOD SetPluginDrawingModel(int inDrawingModel) = 0;
+
+ NS_IMETHOD StartComplexTextInputForCurrentEvent() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIPluginWidget, NS_IPLUGINWIDGET_IID)
diff --git a/widget/nsIPrintDialogService.h b/widget/nsIPrintDialogService.h
new file mode 100644
index 0000000000..106897a5c2
--- /dev/null
+++ b/widget/nsIPrintDialogService.h
@@ -0,0 +1,70 @@
+/* -*- 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 nsIPrintDialogService_h__
+#define nsIPrintDialogService_h__
+
+#include "nsISupports.h"
+
+class nsPIDOMWindowOuter;
+class nsIPrintSettings;
+
+/*
+ * Interface to a print dialog accessed through the widget library.
+ */
+
+#define NS_IPRINTDIALOGSERVICE_IID \
+ { \
+ 0x3715eb1a, 0xb314, 0x447c, { \
+ 0x95, 0x33, 0xd0, 0x6a, 0x6d, 0xa6, 0xa6, 0xf0 \
+ } \
+ }
+
+/**
+ *
+ */
+class nsIPrintDialogService : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IPRINTDIALOGSERVICE_IID)
+
+ /**
+ * Initialize the service.
+ * @return NS_OK or a suitable error.
+ */
+ NS_IMETHOD Init() = 0;
+
+ /**
+ * Show the print dialog.
+ * @param aParent A DOM window the dialog will be parented to.
+ * @param aSettings On entry, this contains initial settings for the
+ * print dialog. On return, if the print operation should
+ * proceed then this contains settings for the print
+ * operation.
+ * @return NS_OK if the print operation should proceed
+ * @return NS_ERROR_ABORT if the user indicated not to proceed
+ * @return a suitable error for failures to show the print dialog.
+ */
+ NS_IMETHOD Show(nsPIDOMWindowOuter* aParent, nsIPrintSettings* aSettings) = 0;
+
+ /**
+ * Show the page setup dialog. Note that there is no way to tell whether the
+ * user clicked OK or Cancel on the dialog.
+ * @param aParent A DOM window the dialog will be parented to.
+ * @param aSettings On entry, this contains initial settings for the
+ * page setup dialog. On return, this contains new default
+ * page setup options.
+ * @return NS_OK if everything is OK.
+ * @return a suitable error for failures to show the page setup dialog.
+ */
+ NS_IMETHOD ShowPageSetup(nsPIDOMWindowOuter* aParent,
+ nsIPrintSettings* aSettings) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIPrintDialogService, NS_IPRINTDIALOGSERVICE_IID)
+
+#define NS_PRINTDIALOGSERVICE_CONTRACTID \
+ ("@mozilla.org/widget/printdialog-service;1")
+
+#endif // nsIPrintDialogService_h__
diff --git a/widget/nsIPrintSession.idl b/widget/nsIPrintSession.idl
new file mode 100644
index 0000000000..778fdbf1ce
--- /dev/null
+++ b/widget/nsIPrintSession.idl
@@ -0,0 +1,39 @@
+/* -*- 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 "nsISupports.idl"
+
+/**
+ * nsIPrintSession
+ *
+ * Stores data pertaining only to a single print job. This
+ * differs from nsIPrintSettings, which stores data which may
+ * be valid across a number of jobs.
+ *
+ * The creation of a component which implements this interface
+ * will begin the session. Likewise, destruction of that object
+ * will end the session.
+ *
+ * @status
+ */
+
+%{ C++
+namespace mozilla {
+namespace layout {
+class RemotePrintJobChild;
+}
+}
+%}
+
+[ptr] native RemotePrintJobChildPtr(mozilla::layout::RemotePrintJobChild);
+
+[uuid(424ae4bb-10ca-4f35-b84e-eab893322df4)]
+interface nsIPrintSession : nsISupports
+{
+ /**
+ * The remote print job is used for printing via the parent process.
+ */
+ [notxpcom, nostdcall] attribute RemotePrintJobChildPtr remotePrintJob;
+};
diff --git a/widget/nsIPrintSettings.idl b/widget/nsIPrintSettings.idl
new file mode 100644
index 0000000000..c1ed9a06ce
--- /dev/null
+++ b/widget/nsIPrintSettings.idl
@@ -0,0 +1,317 @@
+/* -*- 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 "nsISupports.idl"
+
+
+%{ C++
+#include "nsMargin.h"
+#include "nsTArray.h"
+%}
+
+/**
+ * Native types
+ */
+native nsNativeIntMargin(nsIntMargin);
+[ref] native nsNativeIntMarginRef(nsIntMargin);
+
+interface nsIPrintSession;
+
+/**
+ * Simplified graphics interface for JS rendering.
+ */
+[scriptable, builtinclass, uuid(ecc5cbad-57fc-4731-b0bd-09e865bd62ad)]
+interface nsIPrintSettings : nsISupports
+{
+ /**
+ * PrintSettings to be Saved Navigation Constants
+ */
+ /* Flag 0x00000001 is unused */
+ const unsigned long kInitSaveHeaderLeft = 0x00000002;
+ const unsigned long kInitSaveHeaderCenter = 0x00000004;
+ const unsigned long kInitSaveHeaderRight = 0x00000008;
+ const unsigned long kInitSaveFooterLeft = 0x00000010;
+ const unsigned long kInitSaveFooterCenter = 0x00000020;
+ const unsigned long kInitSaveFooterRight = 0x00000040;
+ const unsigned long kInitSaveBGColors = 0x00000080;
+ const unsigned long kInitSaveBGImages = 0x00000100;
+ const unsigned long kInitSavePaperSize = 0x00000200;
+ const unsigned long kInitSaveResolution = 0x00000400;
+ const unsigned long kInitSaveDuplex = 0x00000800;
+ /* Flag 0x00001000 is unused */
+ /* Flag 0x00002000 is unused */
+ const unsigned long kInitSaveUnwriteableMargins = 0x00004000;
+ const unsigned long kInitSaveEdges = 0x00008000;
+
+ const unsigned long kInitSaveReversed = 0x00010000;
+ const unsigned long kInitSaveInColor = 0x00020000;
+ const unsigned long kInitSaveOrientation = 0x00040000;
+
+ const unsigned long kInitSavePrinterName = 0x00100000;
+ const unsigned long kInitSavePrintToFile = 0x00200000;
+ const unsigned long kInitSaveToFileName = 0x00400000;
+ const unsigned long kInitSavePageDelay = 0x00800000;
+ const unsigned long kInitSaveMargins = 0x01000000;
+ const unsigned long kInitSaveNativeData = 0x02000000;
+
+ const unsigned long kInitSaveShrinkToFit = 0x08000000;
+ const unsigned long kInitSaveScaling = 0x10000000;
+
+ const unsigned long kInitSaveAll = 0xFFFFFFFF;
+
+ /* Justification Enums */
+ const long kJustLeft = 0;
+ const long kJustCenter = 1;
+ const long kJustRight = 2;
+
+ /**
+ * Page Size Unit Constants
+ */
+ const short kPaperSizeInches = 0;
+ const short kPaperSizeMillimeters = 1;
+
+ /**
+ * Orientation Constants
+ */
+ const short kPortraitOrientation = 0;
+ const short kLandscapeOrientation = 1;
+
+ /**
+ * Output file format
+ */
+ const short kOutputFormatNative = 0;
+ const short kOutputFormatPS = 1;
+ const short kOutputFormatPDF = 2;
+
+ /**
+ * Simplex/Duplex printing options.
+ *
+ * NOTE: These names correspond to the GTK duplex keywords. But as noted
+ * alongside the options, Windows's enum constants use names whose semantics
+ * are *reversed* with respect to ours.
+ */
+ const short kSimplex = 0;
+ const short kDuplexHorizontal = 1; /* Maps to DMDUP_VERTICAL on Windows */
+ const short kDuplexVertical = 2; /* Maps to DMDUP_HORIZONTAL on Windows */
+
+ /**
+ * Get the page size in twips, considering the
+ * orientation (portrait or landscape).
+ */
+ void GetEffectivePageSize(out double aWidth, out double aHeight);
+
+ /**
+ * Get the printed sheet size in twips, considering both the user-specified
+ * orientation (portrait or landscape) *as well as* the fact that we might be
+ * inverting the orientation to account for 2 or 6 pages-per-sheet.
+ *
+ * This API will usually behave the same (& return the same thing) as
+ * GetEffectivePageSize, *except for* when we are printing with 2 or 6
+ * pages-per-sheet, in which case the return values (aWidth & aHeight) will
+ * be swapped with respect to what GetEffectivePageSize would return.
+ *
+ * Callers should use this method rather than GetEffectivePageSize when they
+ * really do want the size of the sheet of paper to be printed, rather than
+ * the possibly-"virtualized"-via-pages-per-sheet page size.
+ */
+ [noscript, notxpcom, nostdcall] void GetEffectiveSheetSize(out double aWidth,
+ out double aHeight);
+
+ /**
+ * Get the orientation of a printed sheet. This is usually the same as the
+ * 'orientation' attribute (which is the orientation of individual pages),
+ * except when we're printing with 2 or 6 pages-per-sheet, in which case
+ * it'll be the opposite value.
+ *
+ * Note that this value is not independently settable. Its value is fully
+ * determined by the 'orientation' and 'numPagesPerSheet' attributes.
+ */
+ [noscript, notxpcom, nostdcall] long GetSheetOrientation();
+
+ /**
+ * Convenience getter, which returns true IFF the sheet orientation and the
+ * page orientation are orthogonal. (In other words, returns true IFF we
+ * are printing with 2 or 6 pages-per-sheet.)
+ */
+ [noscript, notxpcom, nostdcall] bool HasOrthogonalSheetsAndPages();
+
+ /**
+ * Makes a new copy
+ */
+ nsIPrintSettings clone();
+
+ /**
+ * Assigns the internal values from the "in" arg to the current object
+ */
+ void assign(in nsIPrintSettings aPS);
+
+ /**
+ * Data Members
+ */
+ [noscript] attribute nsIPrintSession printSession; /* We hold a weak reference */
+
+ /**
+ * The edge measurements define the positioning of the headers
+ * and footers on the page. They're treated as an offset from the edges of
+ * the page, but are forced to be at least the "unwriteable margin"
+ * (described below).
+ */
+ attribute double edgeTop; /* these are in inches */
+ attribute double edgeLeft;
+ attribute double edgeBottom;
+ attribute double edgeRight;
+
+ /**
+ * The margins define the positioning of the content on the page.
+ * and footers on the page. They're treated as an offset from the edges of
+ * the page, but are forced to be at least the "unwriteable margin"
+ * (described below).
+ */
+ attribute double marginTop; /* these are in inches */
+ attribute double marginLeft;
+ attribute double marginBottom;
+ attribute double marginRight;
+ /**
+ * The unwriteable margin defines the printable region of the paper.
+ */
+ attribute double unwriteableMarginTop; /* these are in inches */
+ attribute double unwriteableMarginLeft;
+ attribute double unwriteableMarginBottom;
+ attribute double unwriteableMarginRight;
+
+ attribute double scaling; /* values 0.0 - 1.0 */
+ [infallible] attribute boolean printBGColors; /* Print Background Colors */
+ [infallible] attribute boolean printBGImages; /* Print Background Images */
+
+ /** Whether @page rule margins should be honored or not. */
+ [infallible] attribute boolean honorPageRuleMargins;
+
+ /** Whether to draw guidelines showing the margin settings */
+ [infallible] attribute boolean showMarginGuides;
+
+ /** Whether whether the "print selection" radio button should be enabled
+ * in the UI (i.e. whether there is an active selection) */
+ [infallible] attribute boolean isPrintSelectionRBEnabled;
+
+ /** Whether to only print the selected nodes */
+ [infallible] attribute boolean printSelectionOnly;
+
+ attribute AString title;
+ attribute AString docURL;
+
+ attribute AString headerStrLeft;
+ attribute AString headerStrCenter;
+ attribute AString headerStrRight;
+
+ attribute AString footerStrLeft;
+ attribute AString footerStrCenter;
+ attribute AString footerStrRight;
+
+ attribute boolean isCancelled; /* indicates whether the print job has been cancelled */
+ readonly attribute boolean saveOnCancel; /* indicates whether the print settings should be saved after a cancel */
+ attribute boolean printSilent; /* print without putting up the dialog */
+ attribute boolean shrinkToFit; /* shrinks content to fit on page */
+ attribute boolean showPrintProgress; /* indicates whether the progress dialog should be shown */
+
+ /* Additional XP Related */
+ attribute AString paperId; /* identifier of paper (not display name) */
+ attribute double paperWidth; /* width of the paper in inches or mm */
+ attribute double paperHeight; /* height of the paper in inches or mm */
+ attribute short paperSizeUnit; /* paper is in inches or mm */
+
+ attribute boolean printReversed;
+ [infallible] attribute boolean printInColor; /* a false means grayscale */
+ attribute long orientation; /* see orientation consts */
+ attribute long numCopies;
+
+ /**
+ * For numPagesPerSheet, we support these values: 1, 2, 4, 6, 9, 16.
+ *
+ * Unsupported values will be treated internally as 1 page per sheet, and
+ * will trigger assertion failures in debug builds.
+ */
+ attribute long numPagesPerSheet;
+
+ attribute AString printerName; /* name of destination printer */
+
+ attribute boolean printToFile;
+ attribute AString toFileName;
+ attribute short outputFormat;
+
+ attribute long printPageDelay; /* in milliseconds */
+
+ attribute long resolution; /* print resolution (dpi) */
+
+ attribute long duplex; /* duplex mode */
+
+ /* initialize helpers */
+ /**
+ * This attribute tracks whether the PS has been initialized
+ * from a printer specified by the "printerName" attr.
+ * If a different name is set into the "printerName"
+ * attribute than the one it was initialized with the PS
+ * will then get intialized from that printer.
+ */
+ attribute boolean isInitializedFromPrinter;
+
+ /**
+ * This attribute tracks whether the PS has been initialized
+ * from prefs. If a different name is set into the "printerName"
+ * attribute than the one it was initialized with the PS
+ * will then get intialized from prefs again.
+ */
+ attribute boolean isInitializedFromPrefs;
+
+ /* C++ Helper Functions */
+ [noscript] void SetMarginInTwips(in nsNativeIntMarginRef aMargin);
+ [noscript] void SetEdgeInTwips(in nsNativeIntMarginRef aEdge);
+ [noscript, notxpcom, nostdcall] nsNativeIntMargin GetMarginInTwips();
+ [noscript, notxpcom, nostdcall] nsNativeIntMargin GetEdgeInTwips();
+
+ /**
+ * We call this function so that anything that requires a run of the event loop
+ * can do so safely. The print dialog runs the event loop but in silent printing
+ * that doesn't happen.
+ *
+ * Either this or ShowPrintDialog (but not both) MUST be called by the print engine
+ * before printing, otherwise printing can fail on some platforms.
+ */
+ [noscript] void SetupSilentPrinting();
+
+ /**
+ * Sets/Gets the "unwriteable margin" for the page format. This defines
+ * the boundary from which we'll measure the EdgeInTwips and MarginInTwips
+ * attributes, to place the headers and content, respectively.
+ *
+ * Note: Implementations of SetUnwriteableMarginInTwips should handle
+ * negative margin values by falling back on the system default for
+ * that margin.
+ */
+ [noscript] void SetUnwriteableMarginInTwips(in nsNativeIntMarginRef aEdge);
+ [noscript, notxpcom, nostdcall] nsNativeIntMargin GetUnwriteableMarginInTwips();
+
+ /**
+ * Get more accurate print ranges from the superior interval
+ * (startPageRange, endPageRange). The aPages array is populated with a
+ * list of pairs (start, end), where the endpoints are included. The print
+ * ranges (start, end), must not overlap and must be in the
+ * (startPageRange, endPageRange) scope.
+ *
+ * If there are no print ranges the aPages array is empty.
+ */
+ attribute Array<long> pageRanges;
+
+%{C++
+ static bool IsPageSkipped(int32_t aPageNum, const nsTArray<int32_t>& aRanges);
+%}
+};
+
+%{ C++
+namespace mozilla {
+struct PrintSettingsInitializer;
+}
+
+already_AddRefed<nsIPrintSettings> CreatePlatformPrintSettings(const mozilla::PrintSettingsInitializer&);
+%}
diff --git a/widget/nsIPrintSettingsService.idl b/widget/nsIPrintSettingsService.idl
new file mode 100644
index 0000000000..c36fdd2e81
--- /dev/null
+++ b/widget/nsIPrintSettingsService.idl
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Interface to the Service for gwetting the Global PrintSettings object
+ or a unique PrintSettings object
+*/
+
+#include "nsISupports.idl"
+
+interface nsIPrintSettings;
+interface nsIWebBrowserPrint;
+
+%{ C++
+namespace mozilla {
+namespace embedding {
+ class PrintData;
+}
+}
+%}
+
+/**
+ * Native types
+ */
+[ref] native PrintDataRef(const mozilla::embedding::PrintData);
+[ptr] native PrintDataPtr(mozilla::embedding::PrintData);
+
+[scriptable, uuid(841387C8-72E6-484b-9296-BF6EEA80D58A)]
+interface nsIPrintSettingsService : nsISupports
+{
+ /**
+ * Returns the default print settings as used for printing.
+ */
+ [noscript] readonly attribute nsIPrintSettings defaultPrintSettingsForPrinting;
+
+ /**
+ * Returns a new, unique PrintSettings object each time.
+ *
+ * Initializes the newPrintSettings from the unprefixed printer
+ * (Note: this may not happen if there is an OS specific implementation.)
+ *
+ * XXX This should really be a function called `createPrintSettings()`.
+ */
+ readonly attribute nsIPrintSettings newPrintSettings;
+
+ /**
+ * The name of the last printer used. Note that this may not be set, or may
+ * no longer be a valid printer. The caller is responsible for checking and
+ * falling back to some other printer (such as the system default printer).
+ *
+ * XXX: make it [infallible] when AString supports that (bug 1491187).
+ */
+ readonly attribute AString lastUsedPrinterName;
+
+ /**
+ * Initializes certain settings from the native printer into the PrintSettings
+ * if aPrinterName is null then it uses the default printer name if it can
+ * These settings include, but are not limited to:
+ * Page Orientation
+ * Page Size
+ * Number of Copies
+ */
+ void initPrintSettingsFromPrinter(in AString aPrinterName,
+ in nsIPrintSettings aPrintSettings);
+
+ /**
+ * Reads PrintSettings values from Prefs,
+ * the values to be read are indicated by the "flags" arg.
+ *
+ * aPrintSettings should be initialized with the name of a printer. First
+ * it reads in the PrintSettings from the last print job. Then it uses the
+ * PrinterName in the PrinterSettings to read any settings that were saved
+ * just for that printer.
+ *
+ * aPS - PrintSettings to have its settings read
+ * aUsePrinterNamePrefix - indicates whether to use the printer name as a prefix
+ * aFlags - indicates which prefs to read, see nsIPrintSettings.idl for the
+ * const values.
+ *
+ * Items not read:
+ * startPageRange, endPageRange, scaling, printRange, title
+ * docURL, isCancelled,
+ * printSilent, shrinkToFit, numCopies,
+ * printerName
+ *
+ */
+ void initPrintSettingsFromPrefs(in nsIPrintSettings aPrintSettings, in boolean aUsePrinterNamePrefix, in unsigned long aFlags);
+
+ /**
+ * Writes PrintSettings values to Prefs,
+ * the values to be written are indicated by the "flags" arg.
+ *
+ * If there is no PrinterName in the PrinterSettings
+ * the values are saved as the "generic" values not associated with any printer.
+ * If a PrinterName is there, then it saves the items qualified for that Printer
+ *
+ * aPS - PrintSettings to have its settings saved
+ * aUsePrinterNamePrefix - indicates whether to use the printer name as a prefix
+ * aFlags - indicates which prefs to save, see nsIPrintSettings.idl for the const values.
+ *
+ * Items not written:
+ * startPageRange, endPageRange, scaling, printRange, title
+ * docURL, isCancelled,
+ * printSilent, shrinkToFit, numCopies
+ *
+ */
+ void savePrintSettingsToPrefs(in nsIPrintSettings aPrintSettings, in boolean aUsePrinterNamePrefix, in unsigned long aFlags);
+
+ /**
+ * Given some nsIPrintSettings,
+ * populates a PrintData representing them which can be sent over IPC. Values
+ * are only ever read from aSettings and aWBP.
+ *
+ * @param aSettings
+ * An nsIPrintSettings for a print job.
+ * @param data
+ * Pointer to a pre-existing PrintData to populate.
+ *
+ * @return nsresult
+ */
+ [noscript]
+ void SerializeToPrintData(in nsIPrintSettings aPrintSettings,
+ in PrintDataPtr data);
+
+ /**
+ * This function is the opposite of SerializeToPrintData, in that it takes
+ * a PrintData, and populates a pre-existing nsIPrintSettings with the data
+ * from PrintData.
+ *
+ * @param PrintData
+ * Printing information sent through IPC.
+ * @param settings
+ * A pre-existing nsIPrintSettings to populate with the PrintData.
+ *
+ * @return nsresult
+ */
+ [noscript]
+ void DeserializeToPrintSettings(in PrintDataRef data,
+ in nsIPrintSettings aPrintSettings);
+
+};
+
+%{C++
+// {841387C8-72E6-484b-9296-BF6EEA80D58A}
+#define NS_PRINTSETTINGSSERVICE_IID \
+ {0x841387c8, 0x72e6, 0x484b, { 0x92, 0x96, 0xbf, 0x6e, 0xea, 0x80, 0xd5, 0x8a}}
+%}
diff --git a/widget/nsIPrintSettingsWin.idl b/widget/nsIPrintSettingsWin.idl
new file mode 100644
index 0000000000..fae68a9fdd
--- /dev/null
+++ b/widget/nsIPrintSettingsWin.idl
@@ -0,0 +1,56 @@
+/* -*- 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 "nsISupports.idl"
+
+%{ C++
+#include "windows.h"
+%}
+
+/**
+ * Native types
+ */
+ [ptr] native nsDevMode(DEVMODEW);
+ native nsHdc(HDC);
+
+/**
+ * Simplified PrintSettings for Windows interface
+ */
+[uuid(c63eed41-6ac5-459e-8a64-033eb9ad770a)]
+interface nsIPrintSettingsWin : nsISupports
+{
+ /**
+ * Data Members
+ *
+ * Each of these data members make a copy
+ * of the contents. If you get the value,
+ * you own the memory.
+ *
+ * The following three pieces of data are needed
+ * to create a DC for printing. These are typcially
+ * gotten via the PrintDLG call ro can be obtained
+ * via the "m_pd" data member of the CPrintDialog
+ * in MFC.
+ */
+ [noscript] attribute AString deviceName;
+ [noscript] attribute AString driverName;
+
+ [noscript] attribute nsDevMode devMode;
+
+ /**
+ * Copy relevant print settings from native Windows device.
+ *
+ * @param hdc HDC to copy from
+ * @param devMode DEVMODE to copy from
+ */
+ [notxpcom] void copyFromNative(in nsHdc hdc, in nsDevMode devMode);
+
+ /**
+ * Copy relevant print settings to native windows structures.
+ *
+ * @param devMode DEVMODE to be populated.
+ */
+ [notxpcom] void copyToNative(in nsDevMode devMode);
+};
diff --git a/widget/nsIPrinter.idl b/widget/nsIPrinter.idl
new file mode 100644
index 0000000000..0e7710bb10
--- /dev/null
+++ b/widget/nsIPrinter.idl
@@ -0,0 +1,74 @@
+/* -*- 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 "nsIPaper.idl"
+#include "nsIPrintSettings.idl"
+#include "nsISupports.idl"
+
+[scriptable, uuid(855ae9dd-62a4-64aa-9c60-b1078ff028f1)]
+interface nsIPrinterInfo : nsISupports
+{
+ /**
+ * An array of nsIPaper instances that represents the available paper sizes.
+ */
+ readonly attribute Array<nsIPaper> paperList;
+ /**
+ * nsIPrintSettings object containing the default settings for a printer.
+ */
+ readonly attribute nsIPrintSettings defaultSettings;
+};
+
+[scriptable, uuid(d2dde9bb-df86-469c-bfcc-fd95a44b1db8)]
+interface nsIPrinter : nsISupports
+{
+ /**
+ * The name of the printer.
+ */
+ readonly attribute AString name;
+
+ /**
+ * The system name of the printer.
+ *
+ * This may be faster for lookup in nsIPrinterList functions, but will only
+ * work for functions that will accept the system name.
+ */
+ readonly attribute AString systemName;
+
+ /**
+ * Returns a Promise that resolves to a nsIPrinterInfo.
+ * This will contain the default printer settings, and the list of paper
+ * sizes supported by the printer.
+ */
+ [implicit_jscontext]
+ readonly attribute Promise printerInfo;
+
+ /**
+ * Returns a Promise that resolves to true or false to indicate whether this
+ * printer supports duplex printing.
+ */
+ [implicit_jscontext]
+ readonly attribute Promise supportsDuplex;
+
+ /**
+ * Returns a Promise that resolves to true or false to indicate whether this
+ * printer supports color printing.
+ */
+ [implicit_jscontext]
+ readonly attribute Promise supportsColor;
+
+ /**
+ * Returns a Promise that resolves to true or false to indicate whether this
+ * printer supports monochrome printing.
+ */
+ [implicit_jscontext]
+ readonly attribute Promise supportsMonochrome;
+
+ /**
+ * Returns a Promise that resolves to true or false to indicate whether this
+ * printer supports collation.
+ */
+ [implicit_jscontext]
+ readonly attribute Promise supportsCollation;
+};
diff --git a/widget/nsIPrinterList.idl b/widget/nsIPrinterList.idl
new file mode 100644
index 0000000000..d49cedc16e
--- /dev/null
+++ b/widget/nsIPrinterList.idl
@@ -0,0 +1,61 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIPrinter.idl"
+#include "nsIPrintSettings.idl"
+
+[scriptable, uuid(5e738fff-404c-4c94-9189-e8f2cce93e94)]
+interface nsIPrinterList : nsISupports
+{
+ /**
+ * Initializes certain settings from the native printer into the PrintSettings
+ * These settings include, but are not limited to:
+ * Page Orientation
+ * Page Size
+ * Number of Copies
+ */
+ void initPrintSettingsFromPrinter(in AString aPrinterName,
+ in nsIPrintSettings aPrintSettings);
+
+ /**
+ * The system default printer name. This is not necessarily gecko's
+ * default printer; see nsIPrintSettingsService.lastUsedPrinterName
+ * for that.
+ */
+ readonly attribute AString systemDefaultPrinterName;
+
+ /**
+ * Returns a promise that resolves to the printer of a given name, or is
+ * rejected if there is no such printer.
+ */
+ [implicit_jscontext] Promise getPrinterByName(in AString aPrinterName);
+
+ /**
+ * Returns a promise that resolves to the printer of a given system name, or
+ * is rejected if there is no such printer.
+ * This may be more efficient than using getNamedPrinter, but requires the
+ * caller to know the system name of the printer they want to find.
+ */
+ [implicit_jscontext] Promise getPrinterBySystemName(in AString aPrinterName);
+
+ /**
+ * Returns a promise that resolves to the printer of the given name, or
+ * the default system printer, or is rejected if there are no printers
+ * available.
+ */
+ [implicit_jscontext] Promise getNamedOrDefaultPrinter(in AString aPrinterName);
+
+ /**
+ * Returns a promise that resolves to an array of printers.
+ */
+ [implicit_jscontext] readonly attribute Promise printers;
+
+ /**
+ * Returns a Promise that resolves to an array of nsIPaper instances
+ * for common paper sizes suitable to be supported for Save to PDF.
+ */
+ [implicit_jscontext] readonly attribute Promise fallbackPaperList;
+};
diff --git a/widget/nsIRollupListener.h b/widget/nsIRollupListener.h
new file mode 100644
index 0000000000..c3010a31d2
--- /dev/null
+++ b/widget/nsIRollupListener.h
@@ -0,0 +1,72 @@
+/* -*- 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 __nsIRollupListener_h__
+#define __nsIRollupListener_h__
+
+#include "nsTArray.h"
+#include "nsPoint.h"
+
+class nsIContent;
+class nsIWidget;
+
+class nsIRollupListener {
+ public:
+ /**
+ * Notifies the object to rollup, optionally returning the node that
+ * was just rolled up.
+ *
+ * If aFlush is true, then views should be flushed after the rollup.
+ *
+ * aPoint is the mouse pointer position where the event that triggered the
+ * rollup occurred, which may be nullptr.
+ *
+ * aCount is the number of popups in a chain to close. If this is
+ * UINT32_MAX, then all popups are closed.
+ * If aLastRolledUp is non-null, it will be set to the last rolled up popup,
+ * if this is supported. aLastRolledUp is not addrefed.
+ *
+ * Returns true if the event that the caller is processing should be consumed.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual bool Rollup(uint32_t aCount, bool aFlush, const nsIntPoint* aPoint,
+ nsIContent** aLastRolledUp) = 0;
+
+ /**
+ * Asks the RollupListener if it should rollup on mouse wheel events
+ */
+ virtual bool ShouldRollupOnMouseWheelEvent() = 0;
+
+ /**
+ * Asks the RollupListener if it should consume mouse wheel events
+ */
+ virtual bool ShouldConsumeOnMouseWheelEvent() = 0;
+
+ /**
+ * Asks the RollupListener if it should rollup on mouse activate, eg. X-Mouse
+ */
+ virtual bool ShouldRollupOnMouseActivate() = 0;
+
+ /*
+ * Retrieve the widgets for open menus and store them in the array
+ * aWidgetChain. The number of menus of the same type should be returned,
+ * for example, if a context menu is open, return only the number of menus
+ * that are part of the context menu chain. This allows closing up only
+ * those menus in different situations. The returned value should be exactly
+ * the same number of widgets added to aWidgetChain.
+ */
+ virtual uint32_t GetSubmenuWidgetChain(
+ nsTArray<nsIWidget*>* aWidgetChain) = 0;
+
+ /**
+ * Notify the RollupListener that the widget did a Move or Resize.
+ */
+ virtual void NotifyGeometryChange() = 0;
+
+ virtual nsIWidget* GetRollupWidget() = 0;
+};
+
+#endif /* __nsIRollupListener_h__ */
diff --git a/widget/nsIScreen.idl b/widget/nsIScreen.idl
new file mode 100644
index 0000000000..db9e3f8942
--- /dev/null
+++ b/widget/nsIScreen.idl
@@ -0,0 +1,66 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+
+%{C++
+/**
+ * The display type of nsIScreen belongs to.
+ */
+enum class DisplayType: int32_t {
+ DISPLAY_PRIMARY, // primary screen
+ DISPLAY_EXTERNAL, // wired displays, such as HDMI, DisplayPort, etc.
+ DISPLAY_VIRTUAL // wireless displays, such as Chromecast, WiFi-Display, etc.
+};
+%}
+
+[scriptable, uuid(826e80c8-d70f-42e2-8aa9-82c05f2a370a)]
+interface nsIScreen : nsISupports
+{
+ /**
+ * These report screen dimensions in (screen-specific) device pixels
+ */
+ void GetRect(out long left, out long top, out long width, out long height);
+ void GetAvailRect(out long left, out long top, out long width, out long height);
+
+ /**
+ * And these report in desktop pixels
+ */
+ void GetRectDisplayPix(out long left, out long top, out long width, out long height);
+ void GetAvailRectDisplayPix(out long left, out long top, out long width, out long height);
+
+ readonly attribute long pixelDepth;
+ readonly attribute long colorDepth;
+
+ /**
+ * The number of device pixels per desktop pixel for this screen (for
+ * hidpi configurations where there may be multiple device pixels per
+ * desktop px and/or per CSS px).
+ *
+ * This seems poorly named (something like devicePixelsPerDesktopPixel
+ * would be more accurate/explicit), but given that it is exposed to
+ * front-end code and may also be used by add-ons, it's probably not
+ * worth the disruption of changing it.
+ *
+ * Returns 1.0 if HiDPI mode is disabled or unsupported, or if the
+ * host OS uses device pixels as its desktop pixel units (e.g. Windows 7 or
+ * GTK/X11). Per-monitor DPI is available in Windows 8.1+, GTK/Wayland or
+ * macOS.
+ */
+ readonly attribute double contentsScaleFactor;
+
+ /**
+ * The default number of device pixels per unscaled CSS pixel for this
+ * screen. This is probably what contentsScaleFactor originally meant
+ * to be, prior to confusion between CSS pixels and desktop pixel units.
+ */
+ readonly attribute double defaultCSSScaleFactor;
+
+ /**
+ * The DPI of the screen.
+ */
+ readonly attribute float dpi;
+};
diff --git a/widget/nsIScreenManager.idl b/widget/nsIScreenManager.idl
new file mode 100644
index 0000000000..2864b960fb
--- /dev/null
+++ b/widget/nsIScreenManager.idl
@@ -0,0 +1,27 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+#include "nsIScreen.idl"
+
+[scriptable, uuid(e8a96e60-6b61-4a14-bacc-53891604b502)]
+interface nsIScreenManager : nsISupports
+{
+ //
+ // Returns the screen that contains the rectangle. If the rect overlaps
+ // multiple screens, it picks the screen with the greatest area of intersection.
+ //
+ // The coordinates are in pixels (not twips) and in screen coordinates.
+ //
+ nsIScreen screenForRect ( in long left, in long top, in long width, in long height ) ;
+
+ // The screen with the menubar/taskbar. This shouldn't be needed very
+ // often.
+ readonly attribute nsIScreen primaryScreen;
+
+ // The total number of pixels across all monitors.
+ readonly attribute int64_t totalScreenPixels;
+};
diff --git a/widget/nsISharePicker.idl b/widget/nsISharePicker.idl
new file mode 100644
index 0000000000..34c7f18d0e
--- /dev/null
+++ b/widget/nsISharePicker.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"
+
+interface nsIURI;
+
+interface mozIDOMWindowProxy;
+
+[scriptable, uuid(1201d357-8417-4926-a694-e6408fbedcf8)]
+interface nsISharePicker : nsISupports
+{
+ /**
+ * Initialize the share picker widget.
+ * @param nsIDOMWindow openerWindow.
+ */
+ void init(in mozIDOMWindowProxy openerWindow);
+
+ /**
+ * Returns the parent window this was initialized with.
+ */
+ readonly attribute mozIDOMWindowProxy openerWindow;
+
+ /**
+ * XPCOM Analog of navigator.share() as per:
+ * https://w3c.github.io/web-share/#share-method
+ */
+ Promise share(in AUTF8String title, in AUTF8String text, in nsIURI url);
+};
diff --git a/widget/nsISound.idl b/widget/nsISound.idl
new file mode 100644
index 0000000000..b2adcc0fc1
--- /dev/null
+++ b/widget/nsISound.idl
@@ -0,0 +1,40 @@
+/* -*- 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 nsIURL;
+
+[scriptable, uuid(C3C28D92-A17F-43DF-976D-4EEAE6F995FC)]
+interface nsISound : nsISupports
+{
+ void play(in nsIURL aURL);
+ void beep();
+
+ /**
+ * Not strictly necessary, but avoids delay before first sound.
+ * The various methods on nsISound call Init() if they need to.
+ */
+ void init();
+
+ /**
+ * In some situations, playEventSound will be called. Then, each
+ * implementations will play a system sound for the event if it's necessary.
+ *
+ * NOTE: Don't change these values because they are used in
+ * nsPIPromptService.idl. So, if they are changed, that makes big impact for
+ * the embedders.
+ */
+ const unsigned long EVENT_NEW_MAIL_RECEIVED = 0;
+ const unsigned long EVENT_ALERT_DIALOG_OPEN = 1;
+ const unsigned long EVENT_CONFIRM_DIALOG_OPEN = 2;
+ const unsigned long EVENT_PROMPT_DIALOG_OPEN = 3;
+ const unsigned long EVENT_SELECT_DIALOG_OPEN = 4;
+ const unsigned long EVENT_MENU_EXECUTE = 5;
+ const unsigned long EVENT_MENU_POPUP = 6;
+ const unsigned long EVENT_EDITOR_MAX_LEN = 7;
+ void playEventSound(in unsigned long aEventId);
+};
diff --git a/widget/nsIStandaloneNativeMenu.idl b/widget/nsIStandaloneNativeMenu.idl
new file mode 100644
index 0000000000..a40b3f8840
--- /dev/null
+++ b/widget/nsIStandaloneNativeMenu.idl
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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"
+
+webidl Element;
+
+/**
+ * Platform-independent interface to platform native menu objects.
+ */
+
+[scriptable, uuid(7F7201EB-510C-4CEF-BDF0-04A15A7A4A8C)]
+interface nsIStandaloneNativeMenu : nsISupports
+{
+ /**
+ * Initialize the native menu using given XUL DOM element.
+ *
+ * @param aDOMElement A XUL DOM element of tag type |menu| or |menupopup|.
+ */
+ void init(in Element aElement);
+
+ /**
+ * This method must be called before the menu is opened and displayed to the
+ * user. It allows the platform code to update the menu and also determine
+ * whether the menu should even be shown.
+ *
+ * @return true if the menu can be shown, false if it should not be shown
+ */
+ boolean menuWillOpen();
+
+ /**
+ * The native object representing the XUL menu that was passed to Init(). On
+ * Mac OS X, this will be a NSMenu pointer, which will be retained and
+ * autoreleased when the attribute is retrieved.
+ */
+ [noscript] readonly attribute voidPtr nativeMenu;
+
+ /**
+ * Activate the native menu item specified by |anIndexString|. This method
+ * is intended to be used by the test suite.
+ *
+ * @param anIndexString string containing a list of indices separated by
+ * pipe ('|') characters
+ */
+ void activateNativeMenuItemAt(in AString anIndexString);
+
+ /**
+ * Force an update of the native menu item specified by |anIndexString|. This
+ * method is intended to be used by the test suite.
+ *
+ * @param anIndexString string containing a list of indices separated by
+ * pipe ('|') characters
+ */
+ void forceUpdateNativeMenuAt(in AString anIndexString);
+};
diff --git a/widget/nsISystemStatusBar.idl b/widget/nsISystemStatusBar.idl
new file mode 100644
index 0000000000..51aa457a08
--- /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"
+
+webidl Element;
+
+/**
+ * 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 Element aMenuElement);
+
+ /**
+ * 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 Element aMenuElement);
+};
diff --git a/widget/nsITaskbarOverlayIconController.idl b/widget/nsITaskbarOverlayIconController.idl
new file mode 100644
index 0000000000..f11cf16ed0
--- /dev/null
+++ b/widget/nsITaskbarOverlayIconController.idl
@@ -0,0 +1,39 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+
+interface imgIContainer;
+
+/**
+ * Starting in Windows 7, applications can display an overlay on the icon in
+ * the taskbar. This class wraps around the native functionality to do this.
+ */
+[scriptable, uuid(b1858889-a698-428a-a14b-b5d60cff6de2)]
+interface nsITaskbarOverlayIconController : nsISupports
+{
+ /**
+ * Sets the overlay icon and its corresponding alt text.
+ *
+ * @param statusIcon The handle to the overlay icon. The icon will be scaled
+ * to the small icon size (16x16 at 96 dpi). Can be null, in
+ * which case if the taskbar button represents a single window
+ * the icon is removed.
+ * @param statusDescription The alt text version of the information
+ * conveyed by the overlay, for accessibility
+ * purposes.
+ *
+ * @note The behavior for window groups is managed by Windows.
+ * - If an overlay icon is set for any window in a window group and another
+ * overlay icon is already applied to the corresponding taskbar button, that
+ * existing overlay is replaced.
+ * - If null is passed in to replace the overlay currently being displayed,
+ * and if a previous overlay set for a different window in the group is
+ * still available, then that previous overlay is displayed.
+ */
+ void setOverlayIcon(in imgIContainer statusIcon,
+ in AString statusDescription);
+};
diff --git a/widget/nsITaskbarPreview.idl b/widget/nsITaskbarPreview.idl
new file mode 100644
index 0000000000..623dd11b94
--- /dev/null
+++ b/widget/nsITaskbarPreview.idl
@@ -0,0 +1,71 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+#include "nsIBaseWindow.idl"
+
+interface nsITaskbarPreviewController;
+
+/**
+ * nsITaskbarPreview
+ *
+ * Common interface for both window and tab taskbar previews. This interface
+ * cannot be instantiated directly.
+ *
+ */
+[scriptable, uuid(CBFDF766-D002-403B-A3D9-B091C9AD465B)]
+interface nsITaskbarPreview : nsISupports
+{
+ /**
+ * The controller for this preview. A controller is required to provide
+ * the behavior and appearance of the taskbar previews. It is responsible for
+ * determining the size and contents of the preview, which buttons are
+ * displayed and how the application responds to user actions on the preview.
+ *
+ * Neither preview makes full use of the controller. See the documentation
+ * for nsITaskbarWindowPreview and nsITaskbarTabPreview for details on which
+ * controller methods are used.
+ *
+ * The controller is not allowed to be null.
+ *
+ * @see nsITaskbarPreviewController
+ */
+ attribute nsITaskbarPreviewController controller;
+
+ /**
+ * The tooltip displayed above the preview when the user hovers over it
+ *
+ * Default: an empty string
+ */
+ attribute AString tooltip;
+
+ /**
+ * Whether or not the preview is visible.
+ *
+ * Changing this option is expensive for tab previews since toggling this
+ * option will destroy/create the proxy window and its registration with the
+ * taskbar. If any step of that fails, an exception will be thrown.
+ *
+ * For window previews, this operation is very cheap.
+ *
+ * Default: false
+ */
+ attribute boolean visible;
+
+ /**
+ * Gets/sets whether or not the preview is marked active (selected) in the
+ * taskbar.
+ */
+ attribute boolean active;
+
+ /**
+ * Invalidates the taskbar's cached image of this preview, forcing a redraw
+ * if necessary
+ */
+ void invalidate();
+};
+
diff --git a/widget/nsITaskbarPreviewButton.idl b/widget/nsITaskbarPreviewButton.idl
new file mode 100644
index 0000000000..ef8358cf01
--- /dev/null
+++ b/widget/nsITaskbarPreviewButton.idl
@@ -0,0 +1,63 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+
+interface imgIContainer;
+
+/**
+ * nsITaskbarPreviewButton
+ *
+ * Provides access to a window preview's toolbar button's properties.
+ */
+[scriptable, uuid(CED8842D-FE37-4767-9A8E-FDFA56510C75)]
+interface nsITaskbarPreviewButton : nsISupports
+{
+ /**
+ * The button's tooltip.
+ *
+ * Default: an empty string
+ */
+ attribute AString tooltip;
+
+ /**
+ * True if the array of previews should be dismissed when this button is clicked.
+ *
+ * Default: false
+ */
+ attribute boolean dismissOnClick;
+
+ /**
+ * True if the taskbar should draw a border around this button's image.
+ *
+ * Default: true
+ */
+ attribute boolean hasBorder;
+
+ /**
+ * True if the button is disabled. This is not the same as visible.
+ *
+ * Default: false
+ */
+ attribute boolean disabled;
+
+ /**
+ * The icon used for the button.
+ *
+ * Default: null
+ */
+ attribute imgIContainer image;
+
+ /**
+ * True if the button is shown. Buttons that are invisible do not
+ * participate in the layout of buttons underneath the preview.
+ *
+ * Default: false
+ */
+ attribute boolean visible;
+};
+
diff --git a/widget/nsITaskbarPreviewController.idl b/widget/nsITaskbarPreviewController.idl
new file mode 100644
index 0000000000..4920687dfb
--- /dev/null
+++ b/widget/nsITaskbarPreviewController.idl
@@ -0,0 +1,103 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIDocShell;
+interface nsITaskbarPreview;
+interface nsITaskbarPreviewButton;
+
+/**
+ * nsITaskbarPreviewCallback
+ *
+ * Provides an interface for async image result callbacks. See
+ * nsITaskbarPreviewController request apis below.
+ */
+[scriptable, function, uuid(f3744696-320d-4804-9c27-6a84c29acaa6)]
+interface nsITaskbarPreviewCallback : nsISupports
+{
+ void done(in nsISupports aCanvas, in boolean aDrawBorder);
+};
+
+/**
+ * nsITaskbarPreviewController
+ *
+ * nsITaskbarPreviewController provides the behavior for the taskbar previews.
+ * Its methods and properties are used by nsITaskbarPreview. Clients are
+ * intended to provide their own implementation of this interface. Depending on
+ * the interface the controller is attached to, only certain methods/attributes
+ * are required to be implemented.
+ */
+[scriptable, uuid(8b427646-e446-4941-ae0b-c1122a173a68)]
+interface nsITaskbarPreviewController : nsISupports
+{
+ /**
+ * The width of the preview image. This value is allowed to change at any
+ * time. See requestPreview for more information.
+ */
+ readonly attribute unsigned long width;
+
+ /**
+ * The height of the preview image. This value is allowed to change at any
+ * time. See requestPreview for more information.
+ */
+ readonly attribute unsigned long height;
+
+ /**
+ * The aspect ratio of the thumbnail - this does not need to match the ratio
+ * of the preview. This value is allowed to change at any time. See
+ * requestThumbnail for more information.
+ */
+ readonly attribute float thumbnailAspectRatio;
+
+ /**
+ * Invoked by nsITaskbarPreview when it needs to render the preview.
+ *
+ * @param aCallback Async callback the controller should invoke once
+ * the thumbnail is rendered. aCallback receives as its only parameter
+ * a canvas containing the preview image.
+ */
+ void requestPreview(in nsITaskbarPreviewCallback aCallback);
+
+ /**
+ * Note: it is guaranteed that width/height == thumbnailAspectRatio
+ * (modulo rounding errors)
+ *
+ * Also note that the context is not attached to a canvas element.
+ *
+ * @param aCallback Async callback the controller should invoke once
+ * the thumbnail is rendered. aCallback receives as its only parameter
+ * a canvas containing the thumbnail image. Canvas dimensions should
+ * match the requested width or height otherwise setting the thumbnail
+ * will fail.
+ * @param width The width of the requested thumbnail
+ * @param height The height of the requested thumbnail
+ */
+ void requestThumbnail(in nsITaskbarPreviewCallback aCallback,
+ in unsigned long width, in unsigned long height);
+
+ /**
+ * Invoked when the user presses the close button on the tab preview.
+ */
+ void onClose();
+
+ /**
+ * Invoked when the user clicks on the tab preview.
+ *
+ * @return true if the top level window corresponding to the preview should
+ * be activated, false if activation is not accepted.
+ */
+ boolean onActivate();
+
+ /**
+ * Invoked when one of the buttons on the window preview's toolbar is pressed.
+ *
+ * @param button The button that was pressed. This can be compared with the
+ * buttons returned by nsITaskbarWindowPreview.getButton.
+ */
+ void onClick(in nsITaskbarPreviewButton button);
+};
diff --git a/widget/nsITaskbarProgress.idl b/widget/nsITaskbarProgress.idl
new file mode 100644
index 0000000000..d81179d311
--- /dev/null
+++ b/widget/nsITaskbarProgress.idl
@@ -0,0 +1,58 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+#include "nsIBaseWindow.idl"
+
+typedef long nsTaskbarProgressState;
+
+/**
+ * Starting in Windows 7, applications can display a progress notification in
+ * the taskbar. This class wraps around the native functionality to do this.
+ */
+[scriptable, uuid(23ac257d-ef3c-4033-b424-be7fef91a86c)]
+interface nsITaskbarProgress : nsISupports
+{
+ /**
+ * Stop displaying progress on the taskbar button. This should be used when
+ * the operation is complete or cancelled.
+ */
+ const nsTaskbarProgressState STATE_NO_PROGRESS = 0;
+
+ /**
+ * Display a cycling, indeterminate progress bar.
+ */
+ const nsTaskbarProgressState STATE_INDETERMINATE = 1;
+
+ /**
+ * Display a determinate, normal progress bar.
+ */
+ const nsTaskbarProgressState STATE_NORMAL = 2;
+
+ /**
+ * Display a determinate, error progress bar.
+ */
+ const nsTaskbarProgressState STATE_ERROR = 3;
+
+ /**
+ * Display a determinate progress bar indicating that the operation has
+ * paused.
+ */
+ const nsTaskbarProgressState STATE_PAUSED = 4;
+
+ /**
+ * Sets the taskbar progress state and value for this window. The currentValue
+ * and maxValue parameters are optional and should be supplied when |state|
+ * is one of STATE_NORMAL, STATE_ERROR or STATE_PAUSED.
+ *
+ * @throws NS_ERROR_INVALID_ARG if state is STATE_NO_PROGRESS or
+ * STATE_INDETERMINATE, and either currentValue or maxValue is not 0.
+ * @throws NS_ERROR_ILLEGAL_VALUE if currentValue is greater than maxValue.
+ */
+ void setProgressState(in nsTaskbarProgressState state,
+ [optional] in unsigned long long currentValue,
+ [optional] in unsigned long long maxValue);
+};
diff --git a/widget/nsITaskbarTabPreview.idl b/widget/nsITaskbarTabPreview.idl
new file mode 100644
index 0000000000..b56239ed85
--- /dev/null
+++ b/widget/nsITaskbarTabPreview.idl
@@ -0,0 +1,63 @@
+/* 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/. */
+
+#include "nsITaskbarPreview.idl"
+interface imgIContainer;
+
+/*
+ * nsITaskbarTabPreview
+ *
+ * This interface controls tab preview-specific behavior. Creating an
+ * nsITaskbarTabPreview for a window will hide that window's
+ * nsITaskbarWindowPreview in the taskbar - the native API performs this
+ * unconditionally. When there are no more tab previews for a window, the
+ * nsITaskbarWindowPreview will automatically become visible again.
+ *
+ * An application may have as many tab previews per window as memory allows.
+ *
+ */
+[scriptable, builtinclass, uuid(11E4C8BD-5C2D-4E1A-A9A1-79DD5B0FE544)]
+interface nsITaskbarTabPreview : nsITaskbarPreview
+{
+ /**
+ * The title displayed above the thumbnail
+ *
+ * Default: an empty string
+ */
+ attribute AString title;
+
+ /**
+ * The icon displayed next to the title in the preview
+ *
+ * Default: null
+ */
+ attribute imgIContainer icon;
+
+ /**
+ * Rearranges the preview relative to another tab preview from the same window
+ * @param aNext The preview to the right of this one. A value of null
+ * indicates that the preview is the rightmost one.
+ */
+ void move(in nsITaskbarTabPreview aNext);
+
+ /**
+ * Used internally to grab the handle to the proxy window.
+ */
+ [notxpcom]
+ nativeWindow GetHWND();
+
+ /**
+ * Used internally to ensure that the taskbar knows about this preview. If a
+ * preview is not registered, then the API call to set its sibling (via move)
+ * will silently fail.
+ *
+ * This method is only invoked when it is safe to make taskbar API calls.
+ */
+ [notxpcom]
+ void EnsureRegistration();
+};
+
diff --git a/widget/nsITaskbarWindowPreview.idl b/widget/nsITaskbarWindowPreview.idl
new file mode 100644
index 0000000000..cc36fed2b0
--- /dev/null
+++ b/widget/nsITaskbarWindowPreview.idl
@@ -0,0 +1,70 @@
+/* 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/. */
+
+#include "nsITaskbarPreview.idl"
+interface nsITaskbarPreviewButton;
+
+/*
+ * nsITaskbarWindowPreview
+ *
+ * This interface represents the preview for a window in the taskbar. By
+ * default, Windows implements much of the behavior for applications by
+ * default. The primary purpose of this interface is to allow Gecko
+ * applications to take control over parts of the preview. Some parts are not
+ * controlled through this interface: the title and icon of the preview match
+ * the title and icon of the window always.
+ *
+ * By default, Windows takes care of drawing the thumbnail and preview for the
+ * application however if enableCustomDrawing is set to true, then the
+ * controller will start to receive requestPreview and requestThumbnail calls
+ * as well as reads on the thumbnailAspectRatio, width and height properties.
+ *
+ * By default, nsITaskbarWindowPreviews are visible. When made invisible, the
+ * window disappears from the list of windows in the taskbar for the
+ * application.
+ *
+ * If the window has any visible nsITaskbarTabPreviews, then the
+ * nsITaskbarWindowPreview for the corresponding window is automatically
+ * hidden. This is not reflected in the visible property. Note that other parts
+ * of the system (such as alt-tab) may still request thumbnails and/or previews
+ * through the nsITaskbarWindowPreview's controller.
+ *
+ * nsITaskbarWindowPreview will never invoke the controller's onClose or
+ * onActivate methods since handling them may conflict with other internal
+ * Gecko state and there is existing infrastructure in place to allow clients
+ * to handle those events
+ *
+ * Window previews may have a toolbar with up to 7 buttons. See
+ * nsITaskbarPreviewButton for more information about button properties.
+ */
+[scriptable, uuid(EC67CC57-342D-4064-B4C6-74A375E07B10)]
+interface nsITaskbarWindowPreview : nsITaskbarPreview
+{
+ /**
+ * Max 7 buttons per preview per the Windows Taskbar API
+ */
+ const long NUM_TOOLBAR_BUTTONS = 7;
+
+ /**
+ * Gets the nth button for the preview image. By default, all of the buttons
+ * are invisible.
+ *
+ * @see nsITaskbarPreviewButton
+ *
+ * @param index The index into the button array. Must be >= 0 and <
+ * MAX_TOOLBAR_BUTTONS.
+ */
+ nsITaskbarPreviewButton getButton(in unsigned long index);
+
+ /**
+ * Enables/disables custom drawing of thumbnails and previews
+ *
+ * Default value: false
+ */
+ attribute boolean enableCustomDrawing;
+};
+
diff --git a/widget/nsITouchBarHelper.idl b/widget/nsITouchBarHelper.idl
new file mode 100644
index 0000000000..13f3d6a02e
--- /dev/null
+++ b/widget/nsITouchBarHelper.idl
@@ -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/. */
+
+#include "nsIArray.idl"
+#include "nsISupports.idl"
+#include "nsITouchBarInput.idl"
+
+webidl Document;
+
+/**
+ * Back-to-frontend communication for the Touch Bar
+ */
+[scriptable, uuid(ea109912-3acc-48de-b679-c23b6a122da5)]
+interface nsITouchBarHelper : nsISupports
+{
+ /**
+ * Returns the active browser's URL.
+ */
+ readonly attribute AString activeUrl;
+
+ /**
+ * Return the active browser's page title.
+ */
+ readonly attribute AString activeTitle;
+
+ /**
+ * Return true if the Urlbar has focus.
+ */
+ readonly attribute boolean isUrlbarFocused;
+
+ /**
+ * Returns all available Touch Bar Inputs in an nsIArray
+ * of nsITouchBarInput objects.
+ */
+ attribute nsIArray allItems;
+
+ /**
+ * The context in which this nsITouchBarHelper exists. Required to create
+ * an imgLoader to load our SVG icons.
+ */
+ readonly attribute Document document;
+
+ /**
+ * Returns the requested TouchBarInput.
+ * Exposed for testing.
+ */
+ nsITouchBarInput getTouchBarInput(in string aInputName);
+
+ /**
+ * Inserts a search restriction string in the Urlbar.
+ * Exposed for testing.
+ */
+ void insertRestrictionInUrlbar(in string aToken);
+};
diff --git a/widget/nsITouchBarInput.idl b/widget/nsITouchBarInput.idl
new file mode 100644
index 0000000000..0c9c3b0b1a
--- /dev/null
+++ b/widget/nsITouchBarInput.idl
@@ -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/. */
+
+#include "nsIArray.idl"
+#include "nsISupports.idl"
+#include "nsIURI.idl"
+
+[scriptable, function, uuid(001ab07c-1b3a-4dbf-a657-fada0065ff55)]
+interface nsITouchBarInputCallback : nsISupports
+{
+ void onCommand();
+};
+
+/**
+ * Implements an input to be registered on the Mac Touch Bar.
+ */
+
+[scriptable, uuid(77441d17-f29c-49d7-982f-f20a5ab5a900)]
+interface nsITouchBarInput : nsISupports
+{
+ readonly attribute AString key;
+
+ /**
+ * The lookup key for the button's localized text title.
+ */
+ attribute AString title;
+
+ /**
+ * The URI of an icon file.
+ */
+ attribute nsIURI image;
+
+ /**
+ * The type of the input.
+ * Takes one of:
+ * `button`:
+ * A standard button.
+ * If an image is available, only the image is displayed.
+ * `mainButton`:
+ * An extra-wide button. Displays both the image and title.
+ * `scrubber`:
+ * A Scrubber element. Not yet implemented, except in the case of Apple's
+ * pre-built Share scrubber.
+ * `popover`:
+ * An element that displays a new instance of nsTouchBar when tapped.
+ * The elements in the new Touch Bar should be defined in the
+ * input's `children` property.
+ * `label`:
+ * A text label.
+ * `scrollView`:
+ * Contains several buttons, defined in the input's `children` property.
+ * The user can scroll through the buttons.
+ */
+ attribute AString type;
+
+ /**
+ * A callback function to be invoked when an element is touched.
+ */
+ attribute nsITouchBarInputCallback callback;
+
+ /**
+ * A hexadecimal uint32_t specifying the input's
+ * background color. If omitted, the default background color is used.
+ */
+ attribute uint32_t color;
+
+ /**
+ * If `true`, the Touch Bar input is greyed out and inoperable.
+ */
+ attribute boolean disabled;
+
+ /**
+ * An array containing an input's children.
+ * Available for type = ("scrollView" || "popover").
+ */
+ attribute nsIArray children;
+};
diff --git a/widget/nsITouchBarUpdater.idl b/widget/nsITouchBarUpdater.idl
new file mode 100644
index 0000000000..f804191a08
--- /dev/null
+++ b/widget/nsITouchBarUpdater.idl
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIArray.idl"
+#include "nsIBaseWindow.idl"
+#include "nsISupports.idl"
+#include "nsITouchBarInput.idl"
+
+/**
+ * Front-to-backend communication to keep Touch Bar updated
+ */
+[scriptable, uuid(38f396e2-93c9-4a77-aaf7-2d50b9962186)]
+interface nsITouchBarUpdater : nsISupports
+{
+ /**
+ * Updates an array of nsITouchBarInputs in the specified window.
+ */
+ void updateTouchBarInputs(in nsIBaseWindow aWindow, in Array<nsITouchBarInput> aInputs);
+
+ /**
+ * Enter the native Touch Bar customization window.
+ */
+ void enterCustomizeMode();
+
+ /**
+ * Checks if the user is using a Touch Bar-compatible Mac.
+ */
+ boolean isTouchBarInitialized();
+
+ /**
+ * Sets whether the Touch Bar is initialized.
+ * NOTE: This method is for internal unit tests only! Normally, the system
+ * sets this value after a Touch Bar is initialized on compatible Macs.
+ */
+ void setTouchBarInitialized(in boolean aIsInitialized);
+
+ /**
+ * If aShowing is true, aPopover is shown. Otherwise, it is hidden.
+ */
+ void showPopover(in nsIBaseWindow aWindow, in nsITouchBarInput aPopover, in boolean aShowing);
+};
diff --git a/widget/nsITransferable.idl b/widget/nsITransferable.idl
new file mode 100644
index 0000000000..78227a70a8
--- /dev/null
+++ b/widget/nsITransferable.idl
@@ -0,0 +1,204 @@
+/* -*- 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/. */
+
+#include "nsIArray.idl"
+#include "nsISupports.idl"
+#include "nsIFormatConverter.idl"
+#include "nsIContentPolicy.idl"
+
+interface nsICookieJarSettings;
+interface nsIPrincipal;
+
+%{ C++
+
+// these probably shouldn't live here, but in some central repository shared
+// by the entire app.
+#define kTextMime "text/plain"
+#define kRTFMime "text/rtf"
+#define kUnicodeMime "text/unicode"
+#define kMozTextInternal "text/x-moz-text-internal" // text data which isn't suppoed to be parsed by other apps.
+#define kHTMLMime "text/html"
+#define kAOLMailMime "AOLMAIL"
+#define kPNGImageMime "image/png"
+#define kJPEGImageMime "image/jpeg"
+#define kJPGImageMime "image/jpg"
+#define kGIFImageMime "image/gif"
+#define kFileMime "application/x-moz-file"
+
+#define kURLMime "text/x-moz-url" // data contains url\ntitle
+#define kURLDataMime "text/x-moz-url-data" // data contains url only
+#define kURLDescriptionMime "text/x-moz-url-desc" // data contains description
+#define kURLPrivateMime "text/x-moz-url-priv" // same as kURLDataMime but for private uses
+#define kNativeImageMime "application/x-moz-nativeimage"
+#define kNativeHTMLMime "application/x-moz-nativehtml"
+
+// These are used to indicate the context for a fragment of HTML source, such
+// that some parent structure and style can be preserved. kHTMLContext
+// contains the serialized ancestor elements, whereas kHTMLInfo are numbers
+// identifying where in the context the fragment was from.
+#define kHTMLContext "text/_moz_htmlcontext"
+#define kHTMLInfo "text/_moz_htmlinfo"
+
+// Holds the MIME type from the image request. This is used to ensure the
+// local application handler for the request's MIME type accepts images with
+// the given filename extension (from kFilePromiseDestFilename). When the
+// image is dragged out, we replace the extension with a compatible extension.
+#define kImageRequestMime "text/_moz_requestmime"
+
+// the source URL for a file promise
+#define kFilePromiseURLMime "application/x-moz-file-promise-url"
+// the destination filename for a file promise
+#define kFilePromiseDestFilename "application/x-moz-file-promise-dest-filename"
+// a dataless flavor used to interact with the OS during file drags
+#define kFilePromiseMime "application/x-moz-file-promise"
+// a synthetic flavor, put into the transferable once we know the destination directory of a file drag
+#define kFilePromiseDirectoryMime "application/x-moz-file-promise-dir"
+
+#define kCustomTypesMime "application/x-moz-custom-clipdata"
+
+%}
+
+
+/**
+ * nsIFlavorDataProvider allows a flavor to 'promise' data later,
+ * supplying the data lazily.
+ *
+ * To use it, call setTransferData, passing the flavor string,
+ * a nsIFlavorDataProvider QI'd to nsISupports, and a data size of 0.
+ *
+ * When someone calls getTransferData later, if the data size is
+ * stored as 0, the nsISupports will be QI'd to nsIFlavorDataProvider,
+ * and its getFlavorData called.
+ *
+ */
+interface nsITransferable;
+interface nsILoadContext;
+
+[scriptable, uuid(7E225E5F-711C-11D7-9FAE-000393636592)]
+interface nsIFlavorDataProvider : nsISupports
+{
+
+ /**
+ * Retrieve the data from this data provider.
+ *
+ * @param aTransferable (in parameter) the transferable we're being called for.
+ * @param aFlavor (in parameter) the flavor of data to retrieve
+ * @param aData the data. Some variant of class in nsISupportsPrimitives.idl
+ */
+ void getFlavorData(in nsITransferable aTransferable, in string aFlavor, out nsISupports aData);
+};
+
+
+[scriptable, builtinclass, uuid(97e0c418-1c1e-4106-bad1-9fcb11dff2fe)]
+interface nsITransferable : nsISupports
+{
+ /**
+ * Initializes a transferable object. This should be called on all
+ * transferable objects. Failure to do so will result in fatal assertions in
+ * debug builds.
+ *
+ * The load context is used to track whether the transferable is storing privacy-
+ * sensitive information.
+ *
+ * To get the appropriate load context in Javascript callers, one needs to get
+ * to the document that the transferable corresponds to, and then get the load
+ * context from the document like this:
+ *
+ * var loadContext = doc.defaultView.docShell
+ * .QueryInterface(Ci.nsILoadContext);
+ *
+ * In C++ callers, if you have the corresponding document, you can just call
+ * Document::GetLoadContext to get to the load context object.
+ *
+ * @param aContext the load context associated with the transferable object.
+ * This can be set to null if a load context is not available.
+ */
+ void init(in nsILoadContext aContext);
+
+ /**
+ * Computes a list of flavors that the transferable can export, either
+ * through intrinsic knowledge or output data converters.
+ */
+ Array<ACString> flavorsTransferableCanExport();
+
+ /**
+ * Given a flavor retrieve the data.
+ *
+ * @param aFlavor (in parameter) the flavor of data to retrieve
+ * @param aData the data. Some variant of class in nsISupportsPrimitives.idl
+ */
+ [must_use] void getTransferData(in string aFlavor, out nsISupports aData);
+
+ /**
+ * Returns the first flavor which has data.
+ *
+ * @param aFlavor (out parameter) the flavor of data that was retrieved
+ * @param aData the data. Some variant of class in nsISupportsPrimitives.idl
+ */
+ void getAnyTransferData(out ACString aFlavor, out nsISupports aData);
+
+ ///////////////////////////////
+ // Setter part of interface
+ ///////////////////////////////
+
+ /**
+ * Computes a list of flavors that the transferable can
+ * accept into it, either through intrinsic knowledge or input data converters.
+ *
+ */
+ Array<ACString> flavorsTransferableCanImport();
+
+ /**
+ * Sets the data in the transferable with the specified flavor. The transferable
+ * will maintain its own copy the data, so it is not necessary to do that beforehand.
+ *
+ * @param aFlavor the flavor of data that is being set
+ * @param aData the data, either some variant of class in nsISupportsPrimitives.idl,
+ * an nsIFile, or an nsIFlavorDataProvider (see above)
+ * @param aDataLen the length of the data, or 0 if passing a nsIFlavorDataProvider
+ */
+ void setTransferData(in string aFlavor, in nsISupports aData);
+
+ /**
+ * Add the data flavor, indicating that this transferable
+ * can receive this type of flavor
+ *
+ * @param aDataFlavor a new data flavor to handle
+ */
+ void addDataFlavor ( in string aDataFlavor ) ;
+
+ /**
+ * Removes the data flavor matching the given one (string compare) and the data
+ * that goes along with it.
+ *
+ * @param aDataFlavor a data flavor to remove
+ */
+ void removeDataFlavor ( in string aDataFlavor ) ;
+
+ attribute nsIFormatConverter converter;
+
+ /**
+ * Use of the SetIsPrivateData() method generated by isPrivateData attribute should
+ * be avoided as much as possible because the value set may not reflect the status
+ * of the context in which the transferable was created.
+ */
+ [notxpcom, nostdcall] attribute boolean isPrivateData;
+
+ /**
+ * The principal of the source dom node this transferable was
+ * created from and the contentPolicyType for the transferable.
+ * Note, currently only used on Windows for network principal and
+ * contentPolicyType information in clipboard and drag operations.
+ */
+ [notxpcom, nostdcall] attribute nsIPrincipal requestingPrincipal;
+ [notxpcom, nostdcall] attribute nsContentPolicyType contentPolicyType;
+
+ /**
+ * The cookieJarSettings of the source dom node this transferable was created
+ * from.
+ */
+ [notxpcom, nostdcall] attribute nsICookieJarSettings cookieJarSettings;
+};
diff --git a/widget/nsIUserIdleService.idl b/widget/nsIUserIdleService.idl
new file mode 100644
index 0000000000..3507232c36
--- /dev/null
+++ b/widget/nsIUserIdleService.idl
@@ -0,0 +1,86 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+interface nsIObserver;
+
+/**
+ * This interface lets you monitor how long the user has been 'idle',
+ * i.e. not used their mouse or keyboard. You can get the idle time directly,
+ * but in most cases you will want to register an observer for a predefined
+ * interval. The observer will get an 'idle' notification when the user is idle
+ * for that interval (or longer), and receive an 'active' notification when the
+ * user starts using their computer again.
+ */
+
+[scriptable, uuid(cc52f19a-63ae-4a1c-9cc3-e79eace0b471)]
+interface nsIUserIdleService : nsISupports
+{
+ /**
+ * The amount of time in milliseconds that has passed
+ * since the last user activity.
+ *
+ * If we do not have a valid idle time to report, 0 is returned
+ * (this can happen if the user never interacted with the browser
+ * at all, and if we are also unable to poll for idle time manually).
+ */
+ readonly attribute unsigned long idleTime;
+
+ /**
+ * Add an observer to be notified when the user idles for some period of
+ * time, and when they get back from that.
+ *
+ * @param observer the observer to be notified
+ * @param time the amount of time in seconds the user should be idle before
+ * the observer should be notified.
+ *
+ * @note
+ * The subject of the notification the observer will get is always the
+ * nsIUserIdleService itself.
+ * When the user goes idle, the observer topic is "idle" and when he gets
+ * back, the observer topic is "active".
+ * The data param for the notification contains the current user idle time.
+ *
+ * @note
+ * You can add the same observer twice.
+ * @note
+ * Most implementations need to poll the OS for idle info themselves,
+ * meaning your notifications could arrive with a delay up to the length
+ * of the polling interval in that implementation.
+ * Current implementations use a delay of 5 seconds.
+ */
+ void addIdleObserver(in nsIObserver observer, in unsigned long time);
+
+ /**
+ * Remove an observer registered with addIdleObserver.
+ * @param observer the observer that needs to be removed.
+ * @param time the amount of time they were listening for.
+ * @note
+ * Removing an observer will remove it once, for the idle time you specify.
+ * If you have added an observer multiple times, you will need to remove it
+ * just as many times.
+ */
+ void removeIdleObserver(in nsIObserver observer, in unsigned long time);
+
+ /**
+ * If true, the idle service is temporarily disabled, and all idle events
+ * will be ignored.
+ *
+ * This should only be used in automation.
+ */
+ attribute boolean disabled;
+};
+
+%{C++
+ /**
+ * Observer topic notification for idle window: OBSERVER_TOPIC_IDLE.
+ * Observer topic notification for active window: OBSERVER_TOPIC_ACTIVE.
+ */
+
+ #define OBSERVER_TOPIC_IDLE "idle"
+ #define OBSERVER_TOPIC_ACTIVE "active"
+ #define OBSERVER_TOPIC_IDLE_DAILY "idle-daily"
+%}
diff --git a/widget/nsIUserIdleServiceInternal.idl b/widget/nsIUserIdleServiceInternal.idl
new file mode 100644
index 0000000000..840db486ed
--- /dev/null
+++ b/widget/nsIUserIdleServiceInternal.idl
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIUserIdleService.idl"
+
+[scriptable, uuid(7b89a2e7-ed12-42e0-b86d-4984239abd7b)]
+interface nsIUserIdleServiceInternal : nsIUserIdleService
+{
+ /**
+ * "Resets the idle time to the value specified."
+ *
+ * @param idleDelta the time (in milliseconds) since the last user inter
+ * action
+ **/
+ void resetIdleTimeOut(in unsigned long idleDeltaInMS);
+};
+
diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h
new file mode 100644
index 0000000000..54358da41c
--- /dev/null
+++ b/widget/nsIWidget.h
@@ -0,0 +1,2157 @@
+/* -*- Mode: C++; tab-width: 40; 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 nsIWidget_h__
+#define nsIWidget_h__
+
+#include <cmath>
+#include <cstdint>
+#include "ErrorList.h"
+#include "Units.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/layers/ScrollableLayerGuid.h"
+#include "mozilla/layers/ZoomConstraints.h"
+#include "mozilla/widget/IMEData.h"
+#include "nsCOMPtr.h"
+#include "nsColor.h"
+#include "nsDataHashtable.h"
+#include "nsDebug.h"
+#include "nsID.h"
+#include "nsIObserver.h"
+#include "nsISupports.h"
+#include "nsITheme.h"
+#include "nsITimer.h"
+#include "nsIWidgetListener.h"
+#include "nsRect.h"
+#include "nsSize.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsWidgetInitData.h"
+#include "nsXULAppAPI.h"
+
+#ifdef MOZ_IS_GCC
+# include "VsyncSource.h"
+#endif
+
+// forward declarations
+class nsIBidiKeyboard;
+class nsIRollupListener;
+class imgIContainer;
+class nsIContent;
+class ViewWrapper;
+class nsIScreen;
+class nsIRunnable;
+class nsIKeyEventInPluginCallback;
+class nsUint64HashKey;
+
+namespace mozilla {
+class NativeEventData;
+class WidgetGUIEvent;
+class WidgetInputEvent;
+class WidgetKeyboardEvent;
+struct FontRange;
+
+enum class StyleWindowShadow : uint8_t;
+
+#if defined(MOZ_WIDGET_ANDROID)
+namespace ipc {
+class Shmem;
+}
+#endif // defined(MOZ_WIDGET_ANDROID)
+namespace dom {
+class BrowserChild;
+} // namespace dom
+namespace plugins {
+class PluginWidgetChild;
+} // namespace plugins
+namespace layers {
+class AsyncDragMetrics;
+class Compositor;
+class CompositorBridgeChild;
+struct FrameMetrics;
+class LayerManager;
+class LayerManagerComposite;
+class PLayerTransactionChild;
+class WebRenderBridgeChild;
+} // namespace layers
+namespace gfx {
+class VsyncSource;
+} // namespace gfx
+namespace widget {
+class TextEventDispatcher;
+class TextEventDispatcherListener;
+class CompositorWidget;
+class CompositorWidgetInitData;
+} // namespace widget
+namespace wr {
+class DisplayListBuilder;
+class IpcResourceUpdateQueue;
+enum class RenderRoot : uint8_t;
+} // namespace wr
+} // namespace mozilla
+
+/**
+ * Callback function that processes events.
+ *
+ * The argument is actually a subtype (subclass) of WidgetEvent which carries
+ * platform specific information about the event. Platform specific code
+ * knows how to deal with it.
+ *
+ * The return value determines whether or not the default action should take
+ * place.
+ */
+typedef nsEventStatus (*EVENT_CALLBACK)(mozilla::WidgetGUIEvent* aEvent);
+
+// Hide the native window system's real window type so as to avoid
+// including native window system types and APIs. This is necessary
+// to ensure cross-platform code.
+typedef void* nsNativeWidget;
+
+/**
+ * Flags for the GetNativeData and SetNativeData functions
+ */
+#define NS_NATIVE_WINDOW 0
+#define NS_NATIVE_GRAPHIC 1
+#define NS_NATIVE_TMP_WINDOW 2
+#define NS_NATIVE_WIDGET 3
+#define NS_NATIVE_DISPLAY 4
+#define NS_NATIVE_REGION 5
+#define NS_NATIVE_OFFSETX 6
+#define NS_NATIVE_OFFSETY 7
+#define NS_NATIVE_PLUGIN_PORT 8
+#define NS_NATIVE_SCREEN 9
+// The toplevel GtkWidget containing this nsIWidget:
+#define NS_NATIVE_SHELLWIDGET 10
+// Has to match to NPNVnetscapeWindow, and shareable across processes
+// HWND on Windows and XID on X11
+#define NS_NATIVE_SHAREABLE_WINDOW 11
+#define NS_NATIVE_OPENGL_CONTEXT 12
+// See RegisterPluginWindowForRemoteUpdates
+#define NS_NATIVE_PLUGIN_ID 13
+// This is available only with GetNativeData() in parent process. Anybody
+// shouldn't access this pointer as a valid pointer since the result may be
+// special value like NS_ONLY_ONE_NATIVE_IME_CONTEXT. So, the result is just
+// an identifier of distinguishing a text composition is caused by which native
+// 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
+#define NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID 15
+#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
+# define NS_NATIVE_TSF_DISPLAY_ATTR_MGR 102
+# define NS_NATIVE_ICOREWINDOW 103 // winrt specific
+# define NS_NATIVE_CHILD_WINDOW 104
+# define NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW 105
+#endif
+#if defined(MOZ_WIDGET_GTK)
+// set/get nsPluginNativeWindowGtk, e10s specific
+# define NS_NATIVE_PLUGIN_OBJECT_PTR 104
+# define NS_NATIVE_EGL_WINDOW 106
+#endif
+#ifdef MOZ_WIDGET_ANDROID
+# define NS_JAVA_SURFACE 100
+# define NS_PRESENTATION_WINDOW 101
+# define NS_PRESENTATION_SURFACE 102
+#endif
+
+#define MOZ_WIDGET_MAX_SIZE 16384
+
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
+#define NS_IWIDGET_IID \
+ { \
+ 0x06396bf6, 0x2dd8, 0x45e5, { \
+ 0xac, 0x45, 0x75, 0x26, 0x53, 0xb1, 0xc9, 0x80 \
+ } \
+ }
+
+/**
+ * Transparency modes
+ */
+
+enum nsTransparencyMode {
+ eTransparencyOpaque = 0, // Fully opaque
+ eTransparencyTransparent, // Parts of the window may be transparent
+ eTransparencyGlass, // Transparent parts of the window have Vista AeroGlass
+ // effect applied
+ eTransparencyBorderlessGlass // As above, but without a border around the
+ // opaque areas when there would otherwise be
+ // one with eTransparencyGlass
+ // If you add to the end here, you must update the serialization code in
+ // WidgetMessageUtils.h
+};
+
+/**
+ * Cursor types.
+ */
+
+enum nsCursor { ///(normal cursor, usually rendered as an arrow)
+ eCursor_standard,
+ ///(system is busy, usually rendered as a hourglass or watch)
+ eCursor_wait,
+ ///(Selecting something, usually rendered as an IBeam)
+ eCursor_select,
+ ///(can hyper-link, usually rendered as a human hand)
+ eCursor_hyperlink,
+ ///(north/south/west/east edge sizing)
+ eCursor_n_resize,
+ eCursor_s_resize,
+ eCursor_w_resize,
+ eCursor_e_resize,
+ ///(corner sizing)
+ eCursor_nw_resize,
+ eCursor_se_resize,
+ eCursor_ne_resize,
+ eCursor_sw_resize,
+ eCursor_crosshair,
+ eCursor_move,
+ eCursor_help,
+ eCursor_copy, // CSS3
+ eCursor_alias,
+ eCursor_context_menu,
+ eCursor_cell,
+ eCursor_grab,
+ eCursor_grabbing,
+ eCursor_spinning,
+ eCursor_zoom_in,
+ eCursor_zoom_out,
+ eCursor_not_allowed,
+ eCursor_col_resize,
+ eCursor_row_resize,
+ eCursor_no_drop,
+ eCursor_vertical_text,
+ eCursor_all_scroll,
+ eCursor_nesw_resize,
+ eCursor_nwse_resize,
+ eCursor_ns_resize,
+ eCursor_ew_resize,
+ eCursor_none,
+ // This one is used for array sizing, and so better be the last
+ // one in this list...
+ eCursorCount,
+
+ // ...except for this one.
+ eCursorInvalid = eCursorCount + 1
+};
+
+enum nsTopLevelWidgetZPlacement { // for PlaceBehind()
+ eZPlacementBottom = 0, // bottom of the window stack
+ eZPlacementBelow, // just below another widget
+ eZPlacementTop // top of the window stack
+};
+
+/**
+ * Before the OS goes to sleep, this topic is notified.
+ */
+#define NS_WIDGET_SLEEP_OBSERVER_TOPIC "sleep_notification"
+
+/**
+ * After the OS wakes up, this topic is notified.
+ */
+#define NS_WIDGET_WAKE_OBSERVER_TOPIC "wake_notification"
+
+/**
+ * Before the OS suspends the current process, this topic is notified. Some
+ * OS will kill processes that are suspended instead of resuming them.
+ * For that reason this topic may be useful to safely close down resources.
+ */
+#define NS_WIDGET_SUSPEND_PROCESS_OBSERVER_TOPIC "suspend_process_notification"
+
+/**
+ * After the current process resumes from being suspended, this topic is
+ * notified.
+ */
+#define NS_WIDGET_RESUME_PROCESS_OBSERVER_TOPIC "resume_process_notification"
+
+/**
+ * When an app(-shell) is activated by the OS, this topic is notified.
+ * Currently, this only happens on Mac OSX.
+ */
+#define NS_WIDGET_MAC_APP_ACTIVATE_OBSERVER_TOPIC "mac_app_activate"
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * Size constraints for setting the minimum and maximum size of a widget.
+ * Values are in device pixels.
+ */
+struct SizeConstraints {
+ SizeConstraints() : mMaxSize(MOZ_WIDGET_MAX_SIZE, MOZ_WIDGET_MAX_SIZE) {}
+
+ SizeConstraints(mozilla::LayoutDeviceIntSize aMinSize,
+ mozilla::LayoutDeviceIntSize aMaxSize)
+ : mMinSize(aMinSize), mMaxSize(aMaxSize) {
+ if (mMaxSize.width > MOZ_WIDGET_MAX_SIZE) {
+ mMaxSize.width = MOZ_WIDGET_MAX_SIZE;
+ }
+ if (mMaxSize.height > MOZ_WIDGET_MAX_SIZE) {
+ mMaxSize.height = MOZ_WIDGET_MAX_SIZE;
+ }
+ }
+
+ mozilla::LayoutDeviceIntSize mMinSize;
+ mozilla::LayoutDeviceIntSize mMaxSize;
+};
+
+struct AutoObserverNotifier {
+ AutoObserverNotifier(nsIObserver* aObserver, const char* aTopic)
+ : mObserver(aObserver), mTopic(aTopic) {}
+
+ void SkipNotification() { mObserver = nullptr; }
+
+ uint64_t SaveObserver() {
+ if (!mObserver) {
+ return 0;
+ }
+ uint64_t observerId = ++sObserverId;
+ sSavedObservers.Put(observerId, mObserver);
+ SkipNotification();
+ return observerId;
+ }
+
+ ~AutoObserverNotifier() {
+ if (mObserver) {
+ mObserver->Observe(nullptr, mTopic, nullptr);
+ }
+ }
+
+ static void NotifySavedObserver(const uint64_t& aObserverId,
+ const char* aTopic) {
+ nsCOMPtr<nsIObserver> observer = sSavedObservers.Get(aObserverId);
+ if (!observer) {
+ MOZ_ASSERT(aObserverId == 0,
+ "We should always find a saved observer for nonzero IDs");
+ return;
+ }
+
+ sSavedObservers.Remove(aObserverId);
+ observer->Observe(nullptr, aTopic, nullptr);
+ }
+
+ private:
+ nsCOMPtr<nsIObserver> mObserver;
+ const char* mTopic;
+
+ private:
+ static uint64_t sObserverId;
+ static nsDataHashtable<nsUint64HashKey, nsCOMPtr<nsIObserver>>
+ sSavedObservers;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+/**
+ * The base class for all the widgets. It provides the interface for
+ * all basic and necessary functionality.
+ */
+class nsIWidget : public nsISupports {
+ protected:
+ typedef mozilla::dom::BrowserChild BrowserChild;
+
+ public:
+ typedef mozilla::layers::CompositorBridgeChild CompositorBridgeChild;
+ typedef mozilla::layers::AsyncDragMetrics AsyncDragMetrics;
+ typedef mozilla::layers::FrameMetrics FrameMetrics;
+ typedef mozilla::layers::LayerManager LayerManager;
+ typedef mozilla::layers::LayerManagerComposite LayerManagerComposite;
+ typedef mozilla::layers::LayersBackend LayersBackend;
+ typedef mozilla::layers::PLayerTransactionChild PLayerTransactionChild;
+ typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid;
+ typedef mozilla::layers::ZoomConstraints ZoomConstraints;
+ typedef mozilla::widget::IMEEnabled IMEEnabled;
+ typedef mozilla::widget::IMEMessage IMEMessage;
+ typedef mozilla::widget::IMENotification IMENotification;
+ typedef mozilla::widget::IMENotificationRequests IMENotificationRequests;
+ typedef mozilla::widget::IMEState IMEState;
+ typedef mozilla::widget::InputContext InputContext;
+ typedef mozilla::widget::InputContextAction InputContextAction;
+ typedef mozilla::widget::NativeIMEContext NativeIMEContext;
+ typedef mozilla::widget::SizeConstraints SizeConstraints;
+ typedef mozilla::widget::TextEventDispatcher TextEventDispatcher;
+ typedef mozilla::widget::TextEventDispatcherListener
+ TextEventDispatcherListener;
+ typedef mozilla::LayoutDeviceIntMargin LayoutDeviceIntMargin;
+ typedef mozilla::LayoutDeviceIntPoint LayoutDeviceIntPoint;
+ typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
+ typedef mozilla::LayoutDeviceIntRegion LayoutDeviceIntRegion;
+ typedef mozilla::LayoutDeviceIntSize LayoutDeviceIntSize;
+ typedef mozilla::ScreenIntPoint ScreenIntPoint;
+ typedef mozilla::ScreenIntMargin ScreenIntMargin;
+ typedef mozilla::ScreenIntSize ScreenIntSize;
+ typedef mozilla::ScreenPoint ScreenPoint;
+ typedef mozilla::CSSToScreenScale CSSToScreenScale;
+ typedef mozilla::DesktopIntRect DesktopIntRect;
+ typedef mozilla::DesktopPoint DesktopPoint;
+ typedef mozilla::DesktopRect DesktopRect;
+ typedef mozilla::DesktopSize DesktopSize;
+ typedef mozilla::CSSPoint CSSPoint;
+ typedef mozilla::CSSRect CSSRect;
+
+ // Used in UpdateThemeGeometries.
+ struct ThemeGeometry {
+ // The ThemeGeometryType value for the themed widget, see
+ // nsITheme::ThemeGeometryTypeForWidget.
+ nsITheme::ThemeGeometryType mType;
+ // The device-pixel rect within the window for the themed widget
+ LayoutDeviceIntRect mRect;
+
+ ThemeGeometry(nsITheme::ThemeGeometryType aType,
+ const LayoutDeviceIntRect& aRect)
+ : mType(aType), mRect(aRect) {}
+ };
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IWIDGET_IID)
+
+ nsIWidget()
+ : mLastChild(nullptr),
+ mPrevSibling(nullptr),
+ mOnDestroyCalled(false),
+ mWindowType(eWindowType_child),
+ mZIndex(0)
+
+ {
+ ClearNativeTouchSequence(nullptr);
+ }
+
+ /**
+ * Create and initialize a widget.
+ *
+ * All the arguments can be null in which case a top level window
+ * with size 0 is created. The event callback function has to be
+ * provided only if the caller wants to deal with the events this
+ * widget receives. The event callback is basically a preprocess
+ * hook called synchronously. The return value determines whether
+ * the event goes to the default window procedure or it is hidden
+ * to the os. The assumption is that if the event handler returns
+ * false the widget does not see the event. The widget should not
+ * automatically clear the window to the background color. The
+ * calling code must handle paint messages and clear the background
+ * itself.
+ *
+ * In practice at least one of aParent and aNativeParent will be null. If
+ * both are null the widget isn't parented (e.g. context menus or
+ * independent top level windows).
+ *
+ * The dimensions given in aRect are specified in the parent's
+ * device coordinate system.
+ * This must not be called for parentless widgets such as top-level
+ * windows, which use the desktop pixel coordinate system; a separate
+ * method is provided for these.
+ *
+ * @param aParent parent nsIWidget
+ * @param aNativeParent native parent widget
+ * @param aRect the widget dimension
+ * @param aInitData data that is used for widget initialization
+ *
+ */
+ [[nodiscard]] virtual nsresult Create(
+ nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr) = 0;
+
+ /*
+ * As above, but with aRect specified in DesktopPixel units (for top-level
+ * widgets).
+ * Default implementation just converts aRect to device pixels and calls
+ * through to device-pixel Create, but platforms may override this if the
+ * mapping is not straightforward or the native platform needs to use the
+ * desktop pixel values directly.
+ */
+ [[nodiscard]] virtual nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const DesktopIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr) {
+ LayoutDeviceIntRect devPixRect =
+ RoundedToInt(aRect * GetDesktopToDeviceScale());
+ return Create(aParent, aNativeParent, devPixRect, aInitData);
+ }
+
+ /**
+ * Allocate, initialize, and return a widget that is a child of
+ * |this|. The returned widget (if nonnull) has gone through the
+ * equivalent of CreateInstance(widgetCID) + Create(...).
+ *
+ * |CreateChild()| lets widget backends decide whether to parent
+ * the new child widget to this, nonnatively parent it, or both.
+ * This interface exists to support the PuppetWidget backend,
+ * which is entirely non-native. All other params are the same as
+ * for |Create()|.
+ *
+ * |aForceUseIWidgetParent| forces |CreateChild()| to only use the
+ * |nsIWidget*| this, not its native widget (if it exists), when
+ * calling |Create()|. This is a timid hack around poorly
+ * understood code, and shouldn't be used in new code.
+ */
+ virtual already_AddRefed<nsIWidget> CreateChild(
+ const LayoutDeviceIntRect& aRect, nsWidgetInitData* aInitData = nullptr,
+ bool aForceUseIWidgetParent = false) = 0;
+
+ /**
+ * Attach to a top level widget.
+ *
+ * In cases where a top level chrome widget is being used as a content
+ * container, attach a secondary listener and update the device
+ * context. The primary widget listener will continue to be called for
+ * notifications relating to the top-level window, whereas other
+ * notifications such as painting and events will instead be called via
+ * the attached listener. SetAttachedWidgetListener should be used to
+ * assign the attached listener.
+ *
+ * aUseAttachedEvents if true, events are sent to the attached listener
+ * instead of the normal listener.
+ */
+ virtual void AttachViewToTopLevel(bool aUseAttachedEvents) = 0;
+
+ /**
+ * Accessor functions to get and set the attached listener. Used by
+ * nsView in connection with AttachViewToTopLevel above.
+ */
+ virtual void SetAttachedWidgetListener(nsIWidgetListener* aListener) = 0;
+ virtual nsIWidgetListener* GetAttachedWidgetListener() = 0;
+ virtual void SetPreviouslyAttachedWidgetListener(
+ nsIWidgetListener* aListener) = 0;
+ virtual nsIWidgetListener* GetPreviouslyAttachedWidgetListener() = 0;
+
+ /**
+ * Accessor functions to get and set the listener which handles various
+ * actions for the widget.
+ */
+ //@{
+ virtual nsIWidgetListener* GetWidgetListener() = 0;
+ virtual void SetWidgetListener(nsIWidgetListener* alistener) = 0;
+ //@}
+
+ /**
+ * Close and destroy the internal native window.
+ * This method does not delete the widget.
+ */
+
+ virtual void Destroy() = 0;
+
+ /**
+ * Destroyed() returns true if Destroy() has been called already.
+ * Otherwise, false.
+ */
+ bool Destroyed() const { return mOnDestroyCalled; }
+
+ /**
+ * Reparent a widget
+ *
+ * Change the widget's parent. Null parents are allowed.
+ *
+ * @param aNewParent new parent
+ */
+ virtual void SetParent(nsIWidget* aNewParent) = 0;
+
+ /**
+ * Return the parent Widget of this Widget or nullptr if this is a
+ * top level window
+ *
+ * @return the parent widget or nullptr if it does not have a parent
+ *
+ */
+ virtual nsIWidget* GetParent(void) = 0;
+
+ /**
+ * Return the top level Widget of this Widget
+ *
+ * @return the top level widget
+ */
+ virtual nsIWidget* GetTopLevelWidget() = 0;
+
+ /**
+ * Return the top (non-sheet) parent of this Widget if it's a sheet,
+ * or nullptr if this isn't a sheet (or some other error occurred).
+ * Sheets are only supported on some platforms (currently only OS X).
+ *
+ * @return the top (non-sheet) parent widget or nullptr
+ *
+ */
+ virtual nsIWidget* GetSheetWindowParent(void) = 0;
+
+ /**
+ * Return the physical DPI of the screen containing the window ...
+ * the number of device pixels per inch.
+ */
+ virtual float GetDPI() = 0;
+
+ /**
+ * Return the scaling factor between device pixels and the platform-
+ * dependent "desktop pixels" used to manage window positions on a
+ * potentially multi-screen, mixed-resolution desktop.
+ */
+ virtual mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() = 0;
+
+ /**
+ * Return the scaling factor between device pixels and the platform-
+ * dependent "desktop pixels" by looking up the screen by the position
+ * of the widget.
+ */
+ virtual mozilla::DesktopToLayoutDeviceScale
+ GetDesktopToDeviceScaleByScreen() = 0;
+
+ /**
+ * Return the default scale factor for the window. This is the
+ * default number of device pixels per CSS pixel to use. This should
+ * depend on OS/platform settings such as the Mac's "UI scale factor"
+ * or Windows' "font DPI". This will take into account Gecko preferences
+ * overriding the system setting.
+ */
+ mozilla::CSSToLayoutDeviceScale GetDefaultScale();
+
+ /**
+ * Return the first child of this widget. Will return null if
+ * there are no children.
+ */
+ nsIWidget* GetFirstChild() const { return mFirstChild; }
+
+ /**
+ * Return the last child of this widget. Will return null if
+ * there are no children.
+ */
+ nsIWidget* GetLastChild() const { return mLastChild; }
+
+ /**
+ * Return the next sibling of this widget
+ */
+ nsIWidget* GetNextSibling() const { return mNextSibling; }
+
+ /**
+ * Set the next sibling of this widget
+ */
+ void SetNextSibling(nsIWidget* aSibling) { mNextSibling = aSibling; }
+
+ /**
+ * Return the previous sibling of this widget
+ */
+ nsIWidget* GetPrevSibling() const { return mPrevSibling; }
+
+ /**
+ * Set the previous sibling of this widget
+ */
+ void SetPrevSibling(nsIWidget* aSibling) { mPrevSibling = aSibling; }
+
+ /**
+ * Show or hide this widget
+ *
+ * @param aState true to show the Widget, false to hide it
+ *
+ */
+ virtual void Show(bool aState) = 0;
+
+ /**
+ * Whether or not a widget must be recreated after being hidden to show
+ * again properly.
+ */
+ virtual bool NeedsRecreateToReshow() { return false; }
+
+ /**
+ * Make the window modal.
+ */
+ virtual void SetModal(bool aModal) = 0;
+
+ /**
+ * Make the non-modal window opened by modal window fake-modal, that will
+ * call SetFakeModal(false) on destroy on Cocoa.
+ */
+ virtual void SetFakeModal(bool aModal) { SetModal(aModal); }
+
+ /**
+ * Are we app modal. Currently only implemented on Cocoa.
+ */
+ virtual bool IsRunningAppModal() { return false; }
+
+ /**
+ * The maximum number of simultaneous touch contacts supported by the device.
+ * In the case of devices with multiple digitizers (e.g. multiple touch
+ * screens), the value will be the maximum of the set of maximum supported
+ * contacts by each individual digitizer.
+ */
+ virtual uint32_t GetMaxTouchPoints() const = 0;
+
+ /**
+ * Returns whether the window is visible
+ *
+ */
+ virtual bool IsVisible() const = 0;
+
+ /**
+ * Perform platform-dependent sanity check on a potential window position.
+ * This is guaranteed to work only for top-level windows.
+ *
+ * @param aAllowSlop: if true, allow the window to slop offscreen;
+ * the window should be partially visible. if false,
+ * force the entire window onscreen (or at least
+ * the upper-left corner, if it's too large).
+ * @param aX in: an x position expressed in screen coordinates.
+ * out: the x position constrained to fit on the screen(s).
+ * @param aY in: an y position expressed in screen coordinates.
+ * out: the y position constrained to fit on the screen(s).
+ *
+ **/
+ virtual void ConstrainPosition(bool aAllowSlop, int32_t* aX, int32_t* aY) = 0;
+
+ /**
+ * NOTE:
+ *
+ * For a top-level window widget, the "parent's coordinate system" is the
+ * "global" display pixel coordinate space, *not* device pixels (which
+ * may be inconsistent between multiple screens, at least in the Mac OS
+ * case with mixed hi-dpi and lo-dpi displays). This applies to all the
+ * following Move and Resize widget APIs.
+ *
+ * The display-/device-pixel distinction becomes important for (at least)
+ * Mac OS X with Hi-DPI (retina) displays, and Windows when the UI scale
+ * factor is set to other than 100%.
+ *
+ * The Move and Resize methods take floating-point parameters, rather than
+ * integer ones. This is important when manipulating top-level widgets,
+ * where the coordinate system may not be an integral multiple of the
+ * device-pixel space.
+ **/
+
+ /**
+ * Move this widget.
+ *
+ * Coordinates refer to the top-left of the widget. For toplevel windows
+ * with decorations, this is the top-left of the titlebar and frame .
+ *
+ * @param aX the new x position expressed in the parent's coordinate system
+ * @param aY the new y position expressed in the parent's coordinate system
+ *
+ **/
+ virtual void Move(double aX, double aY) = 0;
+
+ /**
+ * Reposition this widget so that the client area has the given offset.
+ *
+ * @param aOffset the new offset of the client area expressed as an
+ * offset from the origin of the client area of the parent
+ * widget (for root widgets and popup widgets it is in
+ * screen coordinates)
+ **/
+ virtual void MoveClient(const DesktopPoint& aOffset) = 0;
+
+ /**
+ * Resize this widget. Any size constraints set for the window by a
+ * previous call to SetSizeConstraints will be applied.
+ *
+ * @param aWidth the new width expressed in the parent's coordinate system
+ * @param aHeight the new height expressed in the parent's coordinate
+ * system
+ * @param aRepaint whether the widget should be repainted
+ */
+ virtual void Resize(double aWidth, double aHeight, bool aRepaint) = 0;
+
+ /**
+ * Lock the aspect ratio of a Window
+ *
+ * @param aShouldLock bool
+ *
+ */
+ virtual void LockAspectRatio(bool aShouldLock){};
+
+ /**
+ * Move or resize this widget. Any size constraints set for the window by
+ * a previous call to SetSizeConstraints will be applied.
+ *
+ * @param aX the new x position expressed in the parent's coordinate
+ * system
+ * @param aY the new y position expressed in the parent's coordinate
+ * system
+ * @param aWidth the new width expressed in the parent's coordinate system
+ * @param aHeight the new height expressed in the parent's coordinate
+ * system
+ * @param aRepaint whether the widget should be repainted if the size
+ * changes
+ *
+ */
+ virtual void Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) = 0;
+
+ virtual mozilla::Maybe<bool> IsResizingNativeWidget() {
+ return mozilla::Nothing();
+ }
+
+ /**
+ * Resize the widget so that the inner client area has the given size.
+ *
+ * @param aSize the new size of the client area.
+ * @param aRepaint whether the widget should be repainted
+ */
+ virtual void ResizeClient(const DesktopSize& aSize, bool aRepaint) = 0;
+
+ /**
+ * Resize and reposition the widget so tht inner client area has the given
+ * offset and size.
+ *
+ * @param aRect the new offset and size of the client area. The offset is
+ * expressed as an offset from the origin of the client area
+ * of the parent widget (for root widgets and popup widgets it
+ * is in screen coordinates).
+ * @param aRepaint whether the widget should be repainted
+ */
+ virtual void ResizeClient(const DesktopRect& aRect, bool aRepaint) = 0;
+
+ /**
+ * Sets the widget's z-index.
+ */
+ virtual void SetZIndex(int32_t aZIndex) = 0;
+
+ /**
+ * Gets the widget's z-index.
+ */
+ int32_t GetZIndex() { return mZIndex; }
+
+ /**
+ * Position this widget just behind the given widget. (Used to
+ * control z-order for top-level widgets. Get/SetZIndex by contrast
+ * control z-order for child widgets of other widgets.)
+ * @param aPlacement top, bottom, or below a widget
+ * (if top or bottom, param aWidget is ignored)
+ * @param aWidget widget to place this widget behind
+ * (only if aPlacement is eZPlacementBelow).
+ * null is equivalent to aPlacement of eZPlacementTop
+ * @param aActivate true to activate the widget after placing it
+ */
+ virtual void PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
+ nsIWidget* aWidget, bool aActivate) = 0;
+
+ /**
+ * Minimize, maximize or normalize the window size.
+ * Takes a value from nsSizeMode (see nsIWidgetListener.h)
+ */
+ virtual void SetSizeMode(nsSizeMode aMode) = 0;
+
+ virtual void GetWorkspaceID(nsAString& workspaceID) = 0;
+
+ virtual void MoveToWorkspace(const nsAString& workspaceID) = 0;
+
+ /**
+ * Suppress animations that are applied to a window by OS.
+ */
+ virtual void SuppressAnimation(bool aSuppress) {}
+
+ /**
+ * Return size mode (minimized, maximized, normalized).
+ * Returns a value from nsSizeMode (see nsIWidgetListener.h)
+ */
+ virtual nsSizeMode SizeMode() = 0;
+
+ /**
+ * Ask whether the window is tiled.
+ */
+ virtual bool IsTiled() const = 0;
+
+ /**
+ * Ask wether the widget is fully occluded
+ */
+ virtual bool IsFullyOccluded() const = 0;
+
+ /**
+ * Enable or disable this Widget
+ *
+ * @param aState true to enable the Widget, false to disable it.
+ */
+ virtual void Enable(bool aState) = 0;
+
+ /**
+ * Ask whether the widget is enabled
+ */
+ virtual bool IsEnabled() const = 0;
+
+ /*
+ * Whether we should request activation of this widget's toplevel window.
+ */
+ enum class Raise {
+ No,
+ Yes,
+ };
+
+ /**
+ * Request activation of this window or give focus to this widget.
+ */
+ virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) = 0;
+
+ /**
+ * Get this widget's outside dimensions relative to its parent widget. For
+ * popup widgets the returned rect is in screen coordinates and not
+ * relative to its parent widget.
+ *
+ * @return the x, y, width and height of this widget.
+ */
+ virtual LayoutDeviceIntRect GetBounds() = 0;
+
+ /**
+ * Get this widget's outside dimensions in device coordinates. This
+ * includes any title bar on the window.
+ *
+ * @return the x, y, width and height of this widget.
+ */
+ virtual LayoutDeviceIntRect GetScreenBounds() = 0;
+
+ /**
+ * Similar to GetScreenBounds except that this function will always
+ * get the size when the widget is in the nsSizeMode_Normal size mode
+ * even if the current size mode is not nsSizeMode_Normal.
+ * This method will fail if the size mode is not nsSizeMode_Normal and
+ * the platform doesn't have the ability.
+ * This method will always succeed if the current size mode is
+ * nsSizeMode_Normal.
+ *
+ * @param aRect On return it holds the x, y, width and height of
+ * this widget.
+ */
+ [[nodiscard]] virtual nsresult GetRestoredBounds(
+ LayoutDeviceIntRect& aRect) = 0;
+
+ /**
+ * Get this widget's client area bounds, if the window has a 3D border
+ * appearance this returns the area inside the border. The position is the
+ * position of the client area relative to the client area of the parent
+ * widget (for root widgets and popup widgets it is in screen coordinates).
+ *
+ * @return the x, y, width and height of the client area of this widget.
+ */
+ virtual LayoutDeviceIntRect GetClientBounds() = 0;
+
+ /**
+ * Sets the non-client area dimensions of the window. Pass -1 to restore
+ * the system default frame size for that border. Pass zero to remove
+ * a border, or pass a specific value adjust a border. Units are in
+ * pixels. (DPI dependent)
+ *
+ * Platform notes:
+ * Windows: shrinking top non-client height will remove application
+ * icon and window title text. Glass desktops will refuse to set
+ * dimensions between zero and size < system default.
+ */
+ virtual nsresult SetNonClientMargins(LayoutDeviceIntMargin& aMargins) = 0;
+
+ /**
+ * Get the client offset from the window origin.
+ *
+ * @return the x and y of the offset.
+ */
+ virtual LayoutDeviceIntPoint GetClientOffset() = 0;
+
+ /**
+ * Equivalent to GetClientBounds but only returns the size.
+ */
+ virtual LayoutDeviceIntSize GetClientSize() {
+ // Depending on the backend, overloading this method may be useful if
+ // requesting the client offset is expensive.
+ return GetClientBounds().Size();
+ }
+
+ /**
+ * Set the background color for this widget
+ *
+ * @param aColor the new background color
+ *
+ */
+
+ virtual void SetBackgroundColor(const nscolor& aColor) {}
+
+ /**
+ * If a cursor type is currently cached locally for this widget, clear the
+ * cached cursor to force an update on the next SetCursor call.
+ */
+
+ virtual void ClearCachedCursor() = 0;
+
+ /**
+ * Sets the cursor cursor for this widget.
+ *
+ * @param aDefaultCursor the default cursor to be set
+ * @param aCursorImage a custom cursor, maybe null.
+ * @param aX the X coordinate of the hotspot for aCursorImage (from left).
+ * @param aY the Y coordinate of the hotspot for aCursorImage (from top).
+ */
+ virtual void SetCursor(nsCursor aDefaultCursor, imgIContainer* aCursorImage,
+ uint32_t aHotspotX, uint32_t aHotspotY) = 0;
+
+ /**
+ * Get the window type of this widget.
+ */
+ nsWindowType WindowType() { return mWindowType; }
+
+ /**
+ * Determines if this widget is one of the three types of plugin widgets.
+ */
+ bool IsPlugin() {
+ return mWindowType == eWindowType_plugin ||
+ mWindowType == eWindowType_plugin_ipc_chrome ||
+ mWindowType == eWindowType_plugin_ipc_content;
+ }
+
+ /**
+ * Set the transparency mode of the top-level window containing this widget.
+ * So, e.g., if you call this on the widget for an IFRAME, the top level
+ * browser window containing the IFRAME actually gets set. Be careful.
+ *
+ * This can fail if the platform doesn't support
+ * transparency/glass. By default widgets are not
+ * transparent. This will also fail if the toplevel window is not
+ * a Mozilla window, e.g., if the widget is in an embedded
+ * context.
+ *
+ * After transparency/glass has been enabled, the initial alpha channel
+ * value for all pixels is 1, i.e., opaque.
+ * If the window is resized then the alpha channel values for
+ * all pixels are reset to 1.
+ * Pixel RGB color values are already premultiplied with alpha channel values.
+ */
+ virtual void SetTransparencyMode(nsTransparencyMode aMode) = 0;
+
+ /**
+ * Get the transparency mode of the top-level window that contains this
+ * widget.
+ */
+ virtual nsTransparencyMode GetTransparencyMode() = 0;
+
+ /**
+ * This represents a command to set the bounds and clip region of
+ * a child widget.
+ */
+ struct Configuration {
+ nsCOMPtr<nsIWidget> mChild;
+ uintptr_t mWindowID; // e10s specific, the unique plugin port id
+ bool mVisible; // e10s specific, widget visibility
+ LayoutDeviceIntRect mBounds;
+ CopyableTArray<LayoutDeviceIntRect> mClipRegion;
+ };
+
+ /**
+ * Sets the clip region of each mChild (which must actually be a child
+ * of this widget) to the union of the pixel rects given in
+ * mClipRegion, all relative to the top-left of the child
+ * widget. Clip regions are not implemented on all platforms and only
+ * need to actually work for children that are plugins.
+ *
+ * Also sets the bounds of each child to mBounds.
+ *
+ * This will invalidate areas of the children that have changed, but
+ * does not need to invalidate any part of this widget.
+ *
+ * Children should be moved in the order given; the array is
+ * sorted so to minimize unnecessary invalidation if children are
+ * moved in that order.
+ */
+ virtual nsresult ConfigureChildren(
+ const nsTArray<Configuration>& aConfigurations) = 0;
+ virtual nsresult SetWindowClipRegion(
+ const nsTArray<LayoutDeviceIntRect>& aRects,
+ bool aIntersectWithExisting) = 0;
+
+ /**
+ * Appends to aRects the rectangles constituting this widget's clip
+ * region. If this widget is not clipped, appends a single rectangle
+ * (0, 0, bounds.width, bounds.height).
+ */
+ virtual void GetWindowClipRegion(nsTArray<LayoutDeviceIntRect>* aRects) = 0;
+
+ /**
+ * Register or unregister native plugin widgets which receive Configuration
+ * data from the content process via the compositor.
+ *
+ * Lookups are used by the main thread via the compositor to lookup widgets
+ * based on a unique window id. On Windows and Linux this is the
+ * NS_NATIVE_PLUGIN_PORT (hwnd/XID). This tracking maintains a reference to
+ * widgets held. Consumers are responsible for removing widgets from this
+ * list.
+ */
+ virtual void RegisterPluginWindowForRemoteUpdates() = 0;
+ virtual void UnregisterPluginWindowForRemoteUpdates() = 0;
+ static nsIWidget* LookupRegisteredPluginWindow(uintptr_t aWindowID);
+
+ /**
+ * Iterates across the list of registered plugin widgets and updates thier
+ * visibility based on which plugins are included in the 'visible' list.
+ *
+ * The compositor knows little about tabs, but it does know which plugin
+ * widgets are currently included in the visible layer tree. It calls this
+ * helper to hide widgets it knows nothing about.
+ */
+ static void UpdateRegisteredPluginWindowVisibility(
+ uintptr_t aOwnerWidget, nsTArray<uintptr_t>& aPluginIds);
+
+#if defined(XP_WIN)
+ /**
+ * Iterates over the list of registered plugins and for any that are owned
+ * by aOwnerWidget and visible it takes a snapshot.
+ *
+ * @param aOwnerWidget only captures visible widgets owned by this
+ */
+ static void CaptureRegisteredPlugins(uintptr_t aOwnerWidget);
+
+ /**
+ * Take a scroll capture for this widget if possible.
+ */
+ virtual void UpdateScrollCapture() = 0;
+
+ /**
+ * Creates an async ImageContainer to hold scroll capture images that can be
+ * used if the plugin is hidden during scroll.
+ * @return the async container ID of the created ImageContainer.
+ */
+ virtual uint64_t CreateScrollCaptureContainer() = 0;
+#endif
+
+ /**
+ * Set the shadow style of the window.
+ *
+ * Ignored on child widgets and on non-Mac platforms.
+ */
+ virtual void SetWindowShadowStyle(mozilla::StyleWindowShadow aStyle) = 0;
+
+ /**
+ * Set the opacity of the window.
+ * Values need to be between 0.0f (invisible) and 1.0f (fully opaque).
+ *
+ * Ignored on child widgets and on non-Mac platforms.
+ */
+ virtual void SetWindowOpacity(float aOpacity) {}
+
+ /**
+ * Set the transform of the window. Values are in device pixels,
+ * the origin is the top left corner of the window.
+ *
+ * Ignored on child widgets and on non-Mac platforms.
+ */
+ virtual void SetWindowTransform(const mozilla::gfx::Matrix& aTransform) {}
+
+ /**
+ * Set whether the window should ignore mouse events or not.
+ *
+ * This is only used on popup windows.
+ */
+ virtual void SetWindowMouseTransparent(bool aIsTransparent) {}
+
+ /*
+ * On Mac OS X, this method shows or hides the pill button in the titlebar
+ * that's used to collapse the toolbar.
+ *
+ * Ignored on child widgets and on non-Mac platforms.
+ */
+ virtual void SetShowsToolbarButton(bool aShow) = 0;
+
+ /*
+ * On macOS, this method determines whether we tell cocoa that the window
+ * supports native full screen. If we do so, and another window is in
+ * native full screen, this window will also appear in native full screen.
+ *
+ * We generally only want to do this for primary application windows.
+ *
+ * Ignored on child widgets and on non-Mac platforms.
+ */
+ virtual void SetSupportsNativeFullscreen(bool aSupportsNativeFullscreen) = 0;
+
+ enum WindowAnimationType {
+ eGenericWindowAnimation,
+ eDocumentWindowAnimation
+ };
+
+ /**
+ * Sets the kind of top-level window animation this widget should have. On
+ * Mac OS X, this causes a particular kind of animation to be shown when the
+ * window is first made visible.
+ *
+ * Ignored on child widgets and on non-Mac platforms.
+ */
+ virtual void SetWindowAnimationType(WindowAnimationType aType) = 0;
+
+ /**
+ * Specifies whether the window title should be drawn even if the window
+ * contents extend into the titlebar. Ignored on windows that don't draw
+ * in the titlebar. Only implemented on OS X.
+ */
+ virtual void SetDrawsTitle(bool aDrawTitle) {}
+
+ /**
+ * Indicates whether the widget should attempt to make titlebar controls
+ * easier to see on dark titlebar backgrounds.
+ */
+ virtual void SetUseBrightTitlebarForeground(bool aBrightForeground) {}
+
+ /**
+ * Hide window chrome (borders, buttons) for this widget.
+ *
+ */
+ virtual void HideWindowChrome(bool aShouldHide) = 0;
+
+ enum FullscreenTransitionStage {
+ eBeforeFullscreenToggle,
+ eAfterFullscreenToggle
+ };
+
+ /**
+ * Prepares for fullscreen transition and returns whether the widget
+ * supports fullscreen transition. If this method returns false,
+ * PerformFullscreenTransition() must never be called. Otherwise,
+ * caller should call that method twice with "before" and "after"
+ * stages respectively in order. In the latter case, this method may
+ * return some data via aData pointer. Caller must pass that data to
+ * PerformFullscreenTransition() if any, and caller is responsible
+ * for releasing that data.
+ */
+ virtual bool PrepareForFullscreenTransition(nsISupports** aData) = 0;
+
+ /**
+ * Performs fullscreen transition. This method returns immediately,
+ * and will post aCallback to the main thread when the transition
+ * finishes.
+ */
+ virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) = 0;
+
+ /**
+ * Perform any actions needed after the fullscreen transition has ended.
+ */
+ virtual void CleanupFullscreenTransition() = 0;
+
+ /**
+ * Return the screen the widget is in, or null if we don't know.
+ */
+ virtual already_AddRefed<nsIScreen> GetWidgetScreen() = 0;
+
+ /**
+ * Put the toplevel window into or out of fullscreen mode.
+ * If aTargetScreen is given, attempt to go fullscreen on that screen,
+ * if possible. (If not, it behaves as if aTargetScreen is null.)
+ * If !aFullScreen, aTargetScreen is ignored.
+ * aTargetScreen support is currently only implemented on Windows.
+ *
+ * @return NS_OK if the widget is setup properly for fullscreen and
+ * FullscreenChanged callback has been or will be called. If other
+ * value is returned, the caller should continue the change itself.
+ */
+ virtual nsresult MakeFullScreen(bool aFullScreen,
+ nsIScreen* aTargetScreen = nullptr) = 0;
+
+ /**
+ * Same as MakeFullScreen, except that, on systems which natively
+ * support fullscreen transition, calling this method explicitly
+ * requests that behavior.
+ * It is currently only supported on OS X 10.7+.
+ */
+ virtual nsresult MakeFullScreenWithNativeTransition(
+ bool aFullScreen, nsIScreen* aTargetScreen = nullptr) {
+ return MakeFullScreen(aFullScreen, aTargetScreen);
+ }
+
+ /**
+ * Invalidate a specified rect for a widget so that it will be repainted
+ * later.
+ */
+ virtual void Invalidate(const LayoutDeviceIntRect& aRect) = 0;
+
+ enum LayerManagerPersistence {
+ LAYER_MANAGER_CURRENT = 0,
+ LAYER_MANAGER_PERSISTENT
+ };
+
+ /**
+ * Return the widget's LayerManager. The layer tree for that
+ * LayerManager is what gets rendered to the widget.
+ */
+ inline LayerManager* GetLayerManager() {
+ return GetLayerManager(nullptr, mozilla::layers::LayersBackend::LAYERS_NONE,
+ LAYER_MANAGER_CURRENT);
+ }
+
+ inline LayerManager* GetLayerManager(LayerManagerPersistence aPersistence) {
+ return GetLayerManager(nullptr, mozilla::layers::LayersBackend::LAYERS_NONE,
+ aPersistence);
+ }
+
+ /**
+ * Like GetLayerManager(), but prefers creating a layer manager of
+ * type |aBackendHint| instead of what would normally be created.
+ * LayersBackend::LAYERS_NONE means "no hint".
+ */
+ virtual LayerManager* GetLayerManager(
+ PLayerTransactionChild* aShadowManager, LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) = 0;
+
+ /**
+ * Called before each layer manager transaction to allow any preparation
+ * for DrawWindowUnderlay/Overlay that needs to be on the main thread.
+ *
+ * Always called on the main thread.
+ */
+ virtual void PrepareWindowEffects() = 0;
+
+ /**
+ * Called on the main thread at the end of WebRender display list building.
+ */
+ virtual void AddWindowOverlayWebRenderCommands(
+ mozilla::layers::WebRenderBridgeChild* aWrBridge,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources) {}
+
+ /**
+ * Called when Gecko knows which themed widgets exist in this window.
+ * The passed array contains an entry for every themed widget of the right
+ * type (currently only StyleAppearance::Toolbar) within the window, except
+ * for themed widgets which are transformed or have effects applied to them
+ * (e.g. CSS opacity or filters).
+ * This could sometimes be called during display list construction
+ * outside of painting.
+ * If called during painting, it will be called before we actually
+ * paint anything.
+ */
+ virtual void UpdateThemeGeometries(
+ const nsTArray<ThemeGeometry>& aThemeGeometries) = 0;
+
+ /**
+ * Informs the widget about the region of the window that is opaque.
+ *
+ * @param aOpaqueRegion the region of the window that is opaque.
+ */
+ virtual void UpdateOpaqueRegion(const LayoutDeviceIntRegion& aOpaqueRegion) {}
+
+ /**
+ * Informs the widget about the region of the window that is draggable.
+ */
+ virtual void UpdateWindowDraggingRegion(
+ const LayoutDeviceIntRegion& aRegion) {}
+
+ /**
+ * Tells the widget whether the given input block results in a swipe.
+ * Should be called in response to a WidgetWheelEvent that has
+ * mFlags.mCanTriggerSwipe set on it.
+ */
+ virtual void ReportSwipeStarted(uint64_t aInputBlockId, bool aStartSwipe) {}
+
+ /**
+ * Internal methods
+ */
+
+ //@{
+ virtual void AddChild(nsIWidget* aChild) = 0;
+ virtual void RemoveChild(nsIWidget* aChild) = 0;
+ virtual void* GetNativeData(uint32_t aDataType) = 0;
+ virtual void SetNativeData(uint32_t aDataType, uintptr_t aVal) = 0;
+ virtual void FreeNativeData(void* data, uint32_t aDataType) = 0; //~~~
+
+ //@}
+
+ /**
+ * Set the widget's title.
+ * Must be called after Create.
+ *
+ * @param aTitle string displayed as the title of the widget
+ */
+ virtual nsresult SetTitle(const nsAString& aTitle) = 0;
+
+ /**
+ * Set the widget's icon.
+ * Must be called after Create.
+ *
+ * @param aIconSpec string specifying the icon to use; convention is to
+ * pass a resource: URL from which a platform-dependent
+ * resource file name will be constructed
+ */
+ virtual void SetIcon(const nsAString& aIconSpec) = 0;
+
+ /**
+ * Return this widget's origin in screen coordinates.
+ *
+ * @return screen coordinates stored in the x,y members
+ */
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() = 0;
+
+ /**
+ * The same as WidgetToScreenOffset(), except in the case of
+ * PuppetWidget where this method omits the chrome offset.
+ */
+ virtual LayoutDeviceIntPoint TopLevelWidgetToScreenOffset() {
+ return WidgetToScreenOffset();
+ }
+
+ /**
+ * For a PuppetWidget, returns the transform from the coordinate
+ * space of the PuppetWidget to the coordinate space of the
+ * top-level native widget.
+ *
+ * Identity transform in other cases.
+ */
+ virtual mozilla::LayoutDeviceToLayoutDeviceMatrix4x4
+ WidgetToTopLevelWidgetTransform() {
+ return mozilla::LayoutDeviceToLayoutDeviceMatrix4x4();
+ }
+
+ /**
+ * Given the specified client size, return the corresponding window size,
+ * which includes the area for the borders and titlebar. This method
+ * should work even when the window is not yet visible.
+ */
+ virtual LayoutDeviceIntSize ClientToWindowSize(
+ const LayoutDeviceIntSize& aClientSize) = 0;
+
+ /**
+ * Dispatches an event to the widget
+ */
+ virtual nsresult DispatchEvent(mozilla::WidgetGUIEvent* event,
+ nsEventStatus& aStatus) = 0;
+
+ /**
+ * Dispatches an event to APZ only.
+ * No-op in the child process.
+ */
+ virtual void DispatchEventToAPZOnly(mozilla::WidgetInputEvent* aEvent) = 0;
+
+ /**
+ * Dispatches an event that must be handled by APZ first, when APZ is
+ * enabled. If invoked in the child process, it is forwarded to the
+ * parent process synchronously.
+ */
+ virtual nsEventStatus DispatchInputEvent(
+ mozilla::WidgetInputEvent* aEvent) = 0;
+
+ /**
+ * Confirm an APZ-aware event target. This should be used when APZ will
+ * not need a layers update to process the event.
+ */
+ virtual void SetConfirmedTargetAPZC(
+ uint64_t aInputBlockId,
+ const nsTArray<ScrollableLayerGuid>& aTargets) const = 0;
+
+ /**
+ * Returns true if APZ is in use, false otherwise.
+ */
+ virtual bool AsyncPanZoomEnabled() const = 0;
+
+ /**
+ * Enables the dropping of files to a widget.
+ */
+ virtual void EnableDragDrop(bool aEnable) = 0;
+ virtual nsresult AsyncEnableDragDrop(bool aEnable) = 0;
+
+ /**
+ * Enables/Disables system mouse capture.
+ * @param aCapture true enables mouse capture, false disables mouse capture
+ *
+ */
+ virtual void CaptureMouse(bool aCapture) = 0;
+
+ /**
+ * Classify the window for the window manager. Mostly for X11.
+ */
+ virtual void SetWindowClass(const nsAString& xulWinType) = 0;
+
+ /**
+ * Enables/Disables system capture of any and all events that would cause a
+ * popup to be rolled up. aListener should be set to a non-null value for
+ * any popups that are not managed by the popup manager.
+ * @param aDoCapture true enables capture, false disables capture
+ *
+ */
+ virtual void CaptureRollupEvents(nsIRollupListener* aListener,
+ bool aDoCapture) = 0;
+
+ /**
+ * Bring this window to the user's attention. This is intended to be a more
+ * gentle notification than popping the window to the top or putting up an
+ * alert. See, for example, Win32 FlashWindow or the NotificationManager on
+ * the Mac. The notification should be suppressed if the window is already
+ * in the foreground and should be dismissed when the user brings this window
+ * to the foreground.
+ * @param aCycleCount Maximum number of times to animate the window per system
+ * conventions. If set to -1, cycles indefinitely until
+ * window is brought into the foreground.
+ */
+ [[nodiscard]] virtual nsresult GetAttention(int32_t aCycleCount) = 0;
+
+ /**
+ * Ask whether there user input events pending. All input events are
+ * included, including those not targeted at this nsIwidget instance.
+ */
+ virtual bool HasPendingInputEvent() = 0;
+
+ /**
+ * If set to true, the window will draw its contents into the titlebar
+ * instead of below it.
+ *
+ * Ignored on any platform that does not support it. Ignored by widgets that
+ * do not represent windows.
+ * May result in a resize event, so should only be called from places where
+ * reflow and painting is allowed.
+ *
+ * @param aState Whether drawing into the titlebar should be activated.
+ */
+ virtual void SetDrawsInTitlebar(bool aState) = 0;
+
+ /*
+ * Determine whether the widget shows a resize widget. If it does,
+ * aResizerRect returns the resizer's rect.
+ *
+ * Returns false on any platform that does not support it.
+ *
+ * @param aResizerRect The resizer's rect in device pixels.
+ * @return Whether a resize widget is shown.
+ */
+ virtual bool ShowsResizeIndicator(LayoutDeviceIntRect* aResizerRect) = 0;
+
+ /**
+ * Begin a window resizing drag, based on the event passed in.
+ */
+ [[nodiscard]] virtual nsresult BeginResizeDrag(
+ mozilla::WidgetGUIEvent* aEvent, int32_t aHorizontal,
+ int32_t aVertical) = 0;
+
+ enum Modifiers {
+ CAPS_LOCK = 0x00000001, // when CapsLock is active
+ NUM_LOCK = 0x00000002, // when NumLock is active
+ SHIFT_L = 0x00000100,
+ SHIFT_R = 0x00000200,
+ CTRL_L = 0x00000400,
+ CTRL_R = 0x00000800,
+ ALT_L = 0x00001000, // includes Option
+ ALT_R = 0x00002000,
+ COMMAND_L = 0x00004000,
+ COMMAND_R = 0x00008000,
+ HELP = 0x00010000,
+ ALTGRAPH = 0x00020000, // AltGr key on Windows. This emulates
+ // AltRight key behavior of keyboard
+ // layouts which maps AltGr to AltRight
+ // key.
+ FUNCTION = 0x00100000,
+ NUMERIC_KEY_PAD = 0x01000000 // when the key is coming from the keypad
+ };
+ /**
+ * Utility method intended for testing. Dispatches native key events
+ * to this widget to simulate the press and release of a key.
+ * @param aNativeKeyboardLayout a *platform-specific* constant.
+ * On Mac, this is the resource ID for a 'uchr' or 'kchr' resource.
+ * On Windows, it is converted to a hex string and passed to
+ * LoadKeyboardLayout, see
+ * http://msdn.microsoft.com/en-us/library/ms646305(VS.85).aspx
+ * @param aNativeKeyCode a *platform-specific* keycode.
+ * On Windows, this is the virtual key code.
+ * @param aModifiers some combination of the above 'Modifiers' flags;
+ * not all flags will apply to all platforms. Mac ignores the _R
+ * modifiers. Windows ignores COMMAND, NUMERIC_KEY_PAD, HELP and
+ * FUNCTION.
+ * @param aCharacters characters that the OS would decide to generate
+ * from the event. On Windows, this is the charCode passed by
+ * WM_CHAR.
+ * @param aUnmodifiedCharacters characters that the OS would decide
+ * to generate from the event if modifier keys (other than shift)
+ * were assumed inactive. Needed on Mac, ignored on Windows.
+ * @param aObserver the observer that will get notified once the events
+ * have been dispatched.
+ * @return NS_ERROR_NOT_AVAILABLE to indicate that the keyboard
+ * layout is not supported and the event was not fired
+ */
+ virtual nsresult SynthesizeNativeKeyEvent(
+ int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode,
+ uint32_t aModifierFlags, const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters, nsIObserver* aObserver) = 0;
+
+ /**
+ * Utility method intended for testing. Dispatches native mouse events
+ * may even move the mouse cursor. On Mac the events are guaranteed to
+ * be sent to the window containing this widget, but on Windows they'll go
+ * to whatever's topmost on the screen at that position, so for
+ * cross-platform testing ensure that your window is at the top of the
+ * z-order.
+ * @param aPoint screen location of the mouse, in device
+ * pixels, with origin at the top left
+ * @param aNativeMessage *platform-specific* event type (e.g. on Mac,
+ * NSEventTypeMouseMoved; on Windows, MOUSEEVENTF_MOVE, MOUSEEVENTF_LEFTDOWN
+ * etc)
+ * @param aModifierFlags *platform-specific* modifier flags (ignored
+ * on Windows)
+ * @param aObserver the observer that will get notified once the events
+ * have been dispatched.
+ */
+ virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) = 0;
+
+ /**
+ * A shortcut to SynthesizeNativeMouseEvent, abstracting away the native
+ * message. aPoint is location in device pixels to which the mouse pointer
+ * moves to.
+ * @param aObserver the observer that will get notified once the events
+ * have been dispatched.
+ */
+ virtual nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver) = 0;
+
+ /**
+ * Utility method intended for testing. Dispatching native mouse scroll
+ * events may move the mouse cursor.
+ *
+ * @param aPoint Mouse cursor position in screen coordinates.
+ * In device pixels, the origin at the top left of
+ * the primary display.
+ * @param aNativeMessage Platform native message.
+ * @param aDeltaX The delta value for X direction. If the native
+ * message doesn't indicate X direction scrolling,
+ * this may be ignored.
+ * @param aDeltaY The delta value for Y direction. If the native
+ * message doesn't indicate Y direction scrolling,
+ * this may be ignored.
+ * @param aDeltaZ The delta value for Z direction. If the native
+ * message doesn't indicate Z direction scrolling,
+ * this may be ignored.
+ * @param aModifierFlags Must be values of Modifiers, or zero.
+ * @param aAdditionalFlags See nsIDOMWidnowUtils' consts and their
+ * document.
+ * @param aObserver The observer that will get notified once the
+ * events have been dispatched.
+ */
+ virtual nsresult SynthesizeNativeMouseScrollEvent(
+ LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
+ double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags, nsIObserver* aObserver) = 0;
+
+ /*
+ * TouchPointerState states for SynthesizeNativeTouchPoint. Match
+ * touch states in nsIDOMWindowUtils.idl.
+ */
+ enum TouchPointerState {
+ // The pointer is in a hover state above the digitizer
+ TOUCH_HOVER = (1 << 0),
+ // The pointer is in contact with the digitizer
+ TOUCH_CONTACT = (1 << 1),
+ // The pointer has been removed from the digitizer detection area
+ TOUCH_REMOVE = (1 << 2),
+ // The pointer has been canceled. Will cancel any pending os level
+ // gestures that would triggered as a result of completion of the
+ // input sequence. This may not cancel moz platform related events
+ // that might get tirggered by input already delivered.
+ TOUCH_CANCEL = (1 << 3),
+
+ // ALL_BITS used for validity checking during IPC serialization
+ ALL_BITS = (1 << 4) - 1
+ };
+
+ /*
+ * Create a new or update an existing touch pointer on the digitizer.
+ * To trigger os level gestures, individual touch points should
+ * transition through a complete set of touch states which should be
+ * sent as individual messages.
+ *
+ * @param aPointerId The touch point id to create or update.
+ * @param aPointerState one or more of the touch states listed above
+ * @param aPoint coords of this event
+ * @param aPressure 0.0 -> 1.0 float val indicating pressure
+ * @param aOrientation 0 -> 359 degree value indicating the
+ * orientation of the pointer. Use 90 for normal taps.
+ * @param aObserver The observer that will get notified once the events
+ * have been dispatched.
+ */
+ virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) = 0;
+
+ /*
+ * Helper for simulating a simple tap event with one touch point. When
+ * aLongTap is true, simulates a native long tap with a duration equal to
+ * ui.click_hold_context_menus.delay. This pref is compatible with the
+ * apzc long tap duration. Defaults to 1.5 seconds.
+ * @param aObserver The observer that will get notified once the events
+ * have been dispatched.
+ */
+ virtual nsresult SynthesizeNativeTouchTap(LayoutDeviceIntPoint aPoint,
+ bool aLongTap,
+ nsIObserver* aObserver);
+
+ /*
+ * Cancels all active simulated touch input points and pending long taps.
+ * Native widgets should track existing points such that they can clear the
+ * digitizer state when this call is made.
+ * @param aObserver The observer that will get notified once the touch
+ * sequence has been cleared.
+ */
+ virtual nsresult ClearNativeTouchSequence(nsIObserver* aObserver);
+
+ virtual void StartAsyncScrollbarDrag(
+ const AsyncDragMetrics& aDragMetrics) = 0;
+
+ /**
+ * Notify APZ to start autoscrolling.
+ * @param aAnchorLocation the location of the autoscroll anchor
+ * @param aGuid identifies the scroll frame to be autoscrolled
+ * @return true if APZ has been successfully notified
+ */
+ virtual bool StartAsyncAutoscroll(const ScreenPoint& aAnchorLocation,
+ const ScrollableLayerGuid& aGuid) = 0;
+
+ /**
+ * Notify APZ to stop autoscrolling.
+ * @param aGuid identifies the scroll frame which is being autoscrolled.
+ */
+ virtual void StopAsyncAutoscroll(const ScrollableLayerGuid& aGuid) = 0;
+
+ // If this widget supports out-of-process compositing, it can override
+ // this method to provide additional information to the compositor.
+ virtual void GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) {}
+
+ /**
+ * Setter/Getter of the system font setting for testing.
+ */
+ virtual nsresult SetSystemFont(const nsCString& aFontName) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ virtual nsresult GetSystemFont(nsCString& aFontName) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // Get rectangle of the screen where the window is placed.
+ // It's used to detect popup overflow under Wayland because
+ // Screenmanager does not work under it.
+#ifdef MOZ_WAYLAND
+ virtual nsresult GetScreenRect(LayoutDeviceIntRect* aRect) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ virtual nsRect GetPreferredPopupRect() {
+ NS_WARNING("GetPreferredPopupRect implemented only for wayland");
+ return nsRect(0, 0, 0, 0);
+ }
+ virtual void FlushPreferredPopupRect() {
+ NS_WARNING("FlushPreferredPopupRect implemented only for wayland");
+ return;
+ }
+
+#endif
+
+ /*
+ * Get safe area insets except to cutout.
+ * See https://drafts.csswg.org/css-env-1/#safe-area-insets.
+ */
+ virtual mozilla::ScreenIntMargin GetSafeAreaInsets() const {
+ return mozilla::ScreenIntMargin();
+ }
+
+ private:
+ class LongTapInfo {
+ public:
+ LongTapInfo(int32_t aPointerId, LayoutDeviceIntPoint& aPoint,
+ mozilla::TimeDuration aDuration, nsIObserver* aObserver)
+ : mPointerId(aPointerId),
+ mPosition(aPoint),
+ mDuration(aDuration),
+ mObserver(aObserver),
+ mStamp(mozilla::TimeStamp::Now()) {}
+
+ int32_t mPointerId;
+ LayoutDeviceIntPoint mPosition;
+ mozilla::TimeDuration mDuration;
+ nsCOMPtr<nsIObserver> mObserver;
+ mozilla::TimeStamp mStamp;
+ };
+
+ static void OnLongTapTimerCallback(nsITimer* aTimer, void* aClosure);
+
+ static already_AddRefed<nsIBidiKeyboard> CreateBidiKeyboardContentProcess();
+ static already_AddRefed<nsIBidiKeyboard> CreateBidiKeyboardInner();
+
+ mozilla::UniquePtr<LongTapInfo> mLongTapTouchPoint;
+ nsCOMPtr<nsITimer> mLongTapTimer;
+ static int32_t sPointerIdCounter;
+
+ public:
+ /**
+ * If key events have not been handled by content or XBL handlers, they can
+ * be offered to the system (for custom application shortcuts set in system
+ * preferences, for example).
+ */
+ virtual void PostHandleKeyEvent(mozilla::WidgetKeyboardEvent* aEvent);
+
+ /**
+ * Activates a native menu item at the position specified by the index
+ * string. The index string is a string of positive integers separated
+ * by the "|" (pipe) character. The last integer in the string represents
+ * the item index in a submenu located using the integers preceding it.
+ *
+ * Example: 1|0|4
+ * In this string, the first integer represents the top-level submenu
+ * in the native menu bar. Since the integer is 1, it is the second submeu
+ * in the native menu bar. Within that, the first item (index 0) is a
+ * submenu, and we want to activate the 5th item within that submenu.
+ */
+ virtual nsresult ActivateNativeMenuItemAt(const nsAString& indexString) = 0;
+
+ /**
+ * This is used for native menu system testing.
+ *
+ * Updates a native menu at the position specified by the index string.
+ * The index string is a string of positive integers separated by the "|"
+ * (pipe) character.
+ *
+ * Example: 1|0|4
+ * In this string, the first integer represents the top-level submenu
+ * in the native menu bar. Since the integer is 1, it is the second submeu
+ * in the native menu bar. Within that, the first item (index 0) is a
+ * submenu, and we want to update submenu at index 4 within that submenu.
+ *
+ * If this is called with an empty string it forces a full reload of the
+ * menu system.
+ */
+ virtual nsresult ForceUpdateNativeMenuAt(const nsAString& indexString) = 0;
+
+ /**
+ * This is used for testing macOS service menu code.
+ *
+ * @param aResult - the current text selection. Is empty if no selection.
+ * @return nsresult - whether or not aResult was assigned the selected text.
+ */
+ [[nodiscard]] virtual nsresult GetSelectionAsPlaintext(nsAString& aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ /**
+ * Notify IME of the specified notification.
+ *
+ * @return If the notification is mouse button event and it's consumed by
+ * IME, this returns NS_SUCCESS_EVENT_CONSUMED.
+ */
+ virtual nsresult NotifyIME(const IMENotification& aIMENotification) = 0;
+
+ /**
+ * MaybeDispatchInitialFocusEvent will dispatch a focus event after creation
+ * of the widget, in the event that we were not able to observe and respond to
+ * the initial focus event. This is necessary for the early skeleton UI
+ * window, which is displayed and receives its initial focus event before we
+ * can actually respond to it.
+ */
+ virtual void MaybeDispatchInitialFocusEvent() {}
+
+ /*
+ * Notifies the input context changes.
+ */
+ virtual void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) = 0;
+
+ /*
+ * Get current input context.
+ */
+ virtual InputContext GetInputContext() = 0;
+
+ /**
+ * Get native IME context. This is different from GetNativeData() with
+ * NS_RAW_NATIVE_IME_CONTEXT, the result is unique even if in a remote
+ * process.
+ */
+ virtual NativeIMEContext GetNativeIMEContext() = 0;
+
+ /*
+ * Given a WidgetKeyboardEvent, this method synthesizes a corresponding
+ * native (OS-level) event for it. This method allows tests to simulate
+ * keystrokes that trigger native key bindings (which require a native
+ * event).
+ */
+ [[nodiscard]] virtual nsresult AttachNativeKeyEvent(
+ mozilla::WidgetKeyboardEvent& aEvent) = 0;
+
+ /**
+ * Retrieve edit commands when the key combination of aEvent is used
+ * in platform native applications.
+ */
+ enum NativeKeyBindingsType : uint8_t {
+ NativeKeyBindingsForSingleLineEditor,
+ NativeKeyBindingsForMultiLineEditor,
+ NativeKeyBindingsForRichTextEditor
+ };
+ virtual bool GetEditCommands(NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ nsTArray<mozilla::CommandInt>& aCommands);
+
+ /*
+ * Retrieves a reference to notification requests of IME. Note that the
+ * reference is valid while the nsIWidget instance is alive. So, if you
+ * need to store the reference for a long time, you need to grab the widget
+ * instance too.
+ */
+ const IMENotificationRequests& IMENotificationRequestsRef();
+
+ /*
+ * Call this method when a dialog is opened which has a default button.
+ * The button's rectangle should be supplied in aButtonRect.
+ */
+ [[nodiscard]] virtual nsresult OnDefaultButtonLoaded(
+ const LayoutDeviceIntRect& aButtonRect) = 0;
+
+ /**
+ * Return true if this process shouldn't use platform widgets, and
+ * so should use PuppetWidgets instead. If this returns true, the
+ * result of creating and using a platform widget is undefined,
+ * and likely to end in crashes or other buggy behavior.
+ */
+ static bool UsePuppetWidgets() { return XRE_IsContentProcess(); }
+
+ static already_AddRefed<nsIWidget> CreateTopLevelWindow();
+
+ static already_AddRefed<nsIWidget> CreateChildWindow();
+
+ /**
+ * Allocate and return a "puppet widget" that doesn't directly
+ * correlate to a platform widget; platform events and data must
+ * be fed to it. Currently used in content processes. NULL is
+ * returned if puppet widgets aren't supported in this build
+ * config, on this platform, or for this process type.
+ *
+ * This function is called "Create" to match CreateInstance().
+ * The returned widget must still be nsIWidget::Create()d.
+ */
+ static already_AddRefed<nsIWidget> CreatePuppetWidget(
+ BrowserChild* aBrowserChild);
+
+ static already_AddRefed<nsIWidget> CreateHeadlessWidget();
+
+ /**
+ * Allocate and return a "plugin proxy widget", a subclass of PuppetWidget
+ * used in wrapping a PPluginWidget connection for remote widgets. Note
+ * this call creates the base object, it does not create the widget. Use
+ * nsIWidget's Create to do this.
+ */
+ static already_AddRefed<nsIWidget> CreatePluginProxyWidget(
+ BrowserChild* aBrowserChild, mozilla::plugins::PluginWidgetChild* aActor);
+
+ /**
+ * Reparent this widget's native widget.
+ * @param aNewParent the native widget of aNewParent is the new native
+ * parent widget
+ */
+ virtual void ReparentNativeWidget(nsIWidget* aNewParent) = 0;
+
+ /**
+ * Return true if widget has it's own GL context
+ */
+ virtual bool HasGLContext() { return false; }
+
+ /**
+ * Returns true to indicate that this widget paints an opaque background
+ * that we want to be visible under the page, so layout should not force
+ * a default background.
+ */
+ virtual bool WidgetPaintsBackground() { return false; }
+
+ virtual bool NeedsPaint() { return IsVisible() && !GetBounds().IsEmpty(); }
+
+ /**
+ * Get the natural bounds of this widget. This method is only
+ * meaningful for widgets for which Gecko implements screen
+ * rotation natively. When this is the case, GetBounds() returns
+ * the widget bounds taking rotation into account, and
+ * GetNaturalBounds() returns the bounds *not* taking rotation
+ * into account.
+ *
+ * No code outside of the composition pipeline should know or care
+ * about this. If you're not an agent of the compositor, you
+ * probably shouldn't call this method.
+ */
+ virtual LayoutDeviceIntRect GetNaturalBounds() { return GetBounds(); }
+
+ /**
+ * Set size constraints on the window size such that it is never less than
+ * the specified minimum size and never larger than the specified maximum
+ * size. The size constraints are sizes of the outer rectangle including
+ * the window frame and title bar. Use 0 for an unconstrained minimum size
+ * and NS_MAXSIZE for an unconstrained maximum size. Note that this method
+ * does not necessarily change the size of a window to conform to this size,
+ * thus Resize should be called afterwards.
+ *
+ * @param aConstraints: the size constraints in device pixels
+ */
+ virtual void SetSizeConstraints(const SizeConstraints& aConstraints) = 0;
+
+ /**
+ * Return the size constraints currently observed by the widget.
+ *
+ * @return the constraints in device pixels
+ */
+ virtual const SizeConstraints GetSizeConstraints() = 0;
+
+ /**
+ * If this is owned by a BrowserChild, return that. Otherwise return
+ * null.
+ */
+ virtual BrowserChild* GetOwningBrowserChild() { return nullptr; }
+
+ /**
+ * If this isn't directly compositing to its window surface,
+ * return the compositor which is doing that on our behalf.
+ */
+ virtual CompositorBridgeChild* GetRemoteRenderer() { return nullptr; }
+
+ /**
+ * Clear WebRender resources
+ */
+ virtual void ClearCachedWebrenderResources() {}
+
+ /**
+ * If this widget has its own vsync source, return it, otherwise return
+ * nullptr. An example of such local source would be Wayland frame callbacks.
+ */
+ virtual RefPtr<mozilla::gfx::VsyncSource> GetVsyncSource() { return nullptr; }
+
+ /**
+ * Returns true if the widget requires synchronous repaints on resize,
+ * false otherwise.
+ */
+ virtual bool SynchronouslyRepaintOnResize() { return true; }
+
+ /**
+ * Some platforms (only cocoa right now) round widget coordinates to the
+ * nearest even pixels (see bug 892994), this function allows us to
+ * determine how widget coordinates will be rounded.
+ */
+ virtual int32_t RoundsWidgetCoordinatesTo() { return 1; }
+
+ virtual void UpdateZoomConstraints(
+ const uint32_t& aPresShellId, const ScrollableLayerGuid::ViewID& aViewId,
+ const mozilla::Maybe<ZoomConstraints>& aConstraints){};
+
+ /**
+ * GetTextEventDispatcher() returns TextEventDispatcher belonging to the
+ * widget. Note that this never returns nullptr.
+ */
+ virtual TextEventDispatcher* GetTextEventDispatcher() = 0;
+
+ /**
+ * GetNativeTextEventDispatcherListener() returns a
+ * TextEventDispatcherListener instance which is used when the widget
+ * instance handles native IME and/or keyboard events.
+ */
+ virtual TextEventDispatcherListener*
+ GetNativeTextEventDispatcherListener() = 0;
+
+ virtual void ZoomToRect(const uint32_t& aPresShellId,
+ const ScrollableLayerGuid::ViewID& aViewId,
+ const CSSRect& aRect, const uint32_t& aFlags) = 0;
+
+ /**
+ * OnWindowedPluginKeyEvent() is called when native key event is
+ * received in the focused plugin process directly in PluginInstanceChild.
+ *
+ * @param aKeyEventData The native key event data. The actual type
+ * copied into NativeEventData depends on the
+ * caller. Please check PluginInstanceChild.
+ * @param aCallback Callback interface. When this returns
+ * NS_SUCCESS_EVENT_HANDLED_ASYNCHRONOUSLY,
+ * the event handler has to call this callback.
+ * Otherwise, the caller should do that instead.
+ * @return NS_ERROR_* if this fails to handle the event.
+ * NS_SUCCESS_EVENT_CONSUMED if the key event is
+ * consumed.
+ * NS_OK if the key event isn't consumed.
+ * NS_SUCCESS_EVENT_HANDLED_ASYNCHRONOUSLY if the
+ * key event will be handled asynchronously.
+ */
+ virtual nsresult OnWindowedPluginKeyEvent(
+ const mozilla::NativeEventData& aKeyEventData,
+ nsIKeyEventInPluginCallback* aCallback);
+
+ /**
+ * LookUpDictionary shows the dictionary for the word around current point.
+ *
+ * @param aText the word to look up dictiorary.
+ * @param aFontRangeArray text decoration of aText
+ * @param aIsVertical true if the word is vertical layout
+ * @param aPoint top-left point of aText
+ */
+ virtual void LookUpDictionary(
+ const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRangeArray,
+ const bool aIsVertical, const LayoutDeviceIntPoint& aPoint) {}
+
+ virtual void RequestFxrOutput() {
+ MOZ_ASSERT(false, "This function should only execute in Windows");
+ }
+
+#if defined(MOZ_WIDGET_ANDROID)
+ /**
+ * RecvToolbarAnimatorMessageFromCompositor receive message from compositor
+ * thread.
+ *
+ * @param aMessage message being sent to Android UI thread.
+ */
+ virtual void RecvToolbarAnimatorMessageFromCompositor(int32_t aMessage) = 0;
+
+ /**
+ * UpdateRootFrameMetrics steady state frame metrics send from compositor
+ * thread
+ *
+ * @param aScrollOffset page scroll offset value in screen pixels.
+ * @param aZoom current page zoom.
+ */
+ virtual void UpdateRootFrameMetrics(const ScreenPoint& aScrollOffset,
+ const CSSToScreenScale& aZoom) = 0;
+
+ /**
+ * RecvScreenPixels Buffer containing the pixel from the frame buffer. Used
+ * for android robocop tests.
+ *
+ * @param aMem shared memory containing the frame buffer pixels.
+ * @param aSize size of the buffer in screen pixels.
+ */
+ virtual void RecvScreenPixels(mozilla::ipc::Shmem&& aMem,
+ const ScreenIntSize& aSize,
+ bool aNeedsYFlip) = 0;
+
+ virtual void UpdateDynamicToolbarMaxHeight(mozilla::ScreenIntCoord aHeight) {}
+ virtual mozilla::ScreenIntCoord GetDynamicToolbarMaxHeight() const {
+ return 0;
+ }
+#endif
+
+ static already_AddRefed<nsIBidiKeyboard> CreateBidiKeyboard();
+
+ /**
+ * Like GetDefaultScale, but taking into account only the system settings
+ * and ignoring Gecko preferences.
+ */
+ virtual double GetDefaultScaleInternal() { return 1.0; }
+
+ protected:
+ // keep the list of children. We also keep track of our siblings.
+ // The ownership model is as follows: parent holds a strong ref to
+ // the first element of the list, and each element holds a strong
+ // ref to the next element in the list. The prevsibling and
+ // lastchild pointers are weak, which is fine as long as they are
+ // maintained properly.
+ nsCOMPtr<nsIWidget> mFirstChild;
+ nsIWidget* MOZ_NON_OWNING_REF mLastChild;
+ nsCOMPtr<nsIWidget> mNextSibling;
+ nsIWidget* MOZ_NON_OWNING_REF mPrevSibling;
+ // When Destroy() is called, the sub class should set this true.
+ bool mOnDestroyCalled;
+ nsWindowType mWindowType;
+ int32_t mZIndex;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIWidget, NS_IWIDGET_IID)
+
+#endif // nsIWidget_h__
diff --git a/widget/nsIWidgetListener.cpp b/widget/nsIWidgetListener.cpp
new file mode 100644
index 0000000000..911627bcce
--- /dev/null
+++ b/widget/nsIWidgetListener.cpp
@@ -0,0 +1,90 @@
+/* -*- 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 "nsIWidgetListener.h"
+
+#include "nsRegion.h"
+#include "nsView.h"
+#include "nsIWidget.h"
+#include "nsIAppWindow.h"
+
+#include "mozilla/BasicEvents.h"
+#include "mozilla/PresShell.h"
+
+using namespace mozilla;
+
+nsIAppWindow* nsIWidgetListener::GetAppWindow() { return nullptr; }
+
+nsView* nsIWidgetListener::GetView() { return nullptr; }
+
+PresShell* nsIWidgetListener::GetPresShell() { return nullptr; }
+
+bool nsIWidgetListener::WindowMoved(nsIWidget* aWidget, int32_t aX,
+ int32_t aY) {
+ return false;
+}
+
+bool nsIWidgetListener::WindowResized(nsIWidget* aWidget, int32_t aWidth,
+ int32_t aHeight) {
+ return false;
+}
+
+void nsIWidgetListener::SizeModeChanged(nsSizeMode aSizeMode) {}
+
+void nsIWidgetListener::SafeAreaInsetsChanged(const mozilla::ScreenIntMargin&) {
+}
+
+void nsIWidgetListener::UIResolutionChanged() {}
+
+#if defined(MOZ_WIDGET_ANDROID)
+void nsIWidgetListener::DynamicToolbarMaxHeightChanged(ScreenIntCoord aHeight) {
+}
+void nsIWidgetListener::DynamicToolbarOffsetChanged(ScreenIntCoord aOffset) {}
+#endif
+
+void nsIWidgetListener::FullscreenWillChange(bool aInFullscreen) {}
+
+void nsIWidgetListener::FullscreenChanged(bool aInFullscreen) {}
+
+bool nsIWidgetListener::ZLevelChanged(bool aImmediate, nsWindowZ* aPlacement,
+ nsIWidget* aRequestBelow,
+ nsIWidget** aActualBelow) {
+ return false;
+}
+
+void nsIWidgetListener::OcclusionStateChanged(bool aIsFullyOccluded) {}
+
+void nsIWidgetListener::WindowActivated() {}
+
+void nsIWidgetListener::WindowDeactivated() {}
+
+void nsIWidgetListener::OSToolbarButtonPressed() {}
+
+bool nsIWidgetListener::RequestWindowClose(nsIWidget* aWidget) { return false; }
+
+void nsIWidgetListener::WillPaintWindow(nsIWidget* aWidget) {}
+
+bool nsIWidgetListener::PaintWindow(nsIWidget* aWidget,
+ LayoutDeviceIntRegion aRegion) {
+ return false;
+}
+
+void nsIWidgetListener::DidPaintWindow() {}
+
+void nsIWidgetListener::DidCompositeWindow(
+ mozilla::layers::TransactionId aTransactionId,
+ const TimeStamp& aCompositeStart, const TimeStamp& aCompositeEnd) {}
+
+void nsIWidgetListener::RequestRepaint() {}
+
+bool nsIWidgetListener::ShouldNotBeVisible() {
+ // Returns false to assume that nothing should happen in most cases.
+ return false;
+}
+
+nsEventStatus nsIWidgetListener::HandleEvent(WidgetGUIEvent* aEvent,
+ bool aUseAttachedEvents) {
+ return nsEventStatus_eIgnore;
+}
diff --git a/widget/nsIWidgetListener.h b/widget/nsIWidgetListener.h
new file mode 100644
index 0000000000..9a1d47e539
--- /dev/null
+++ b/widget/nsIWidgetListener.h
@@ -0,0 +1,196 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIWidgetListener_h__
+#define nsIWidgetListener_h__
+
+#include <stdint.h>
+
+#include "mozilla/EventForwards.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/TimeStamp.h"
+
+#include "nsRegionFwd.h"
+#include "Units.h"
+
+class nsView;
+class nsIWidget;
+class nsIAppWindow;
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+/**
+ * sizemode is an adjunct to widget size
+ */
+enum nsSizeMode {
+ nsSizeMode_Normal = 0,
+ nsSizeMode_Minimized,
+ nsSizeMode_Maximized,
+ nsSizeMode_Fullscreen,
+ nsSizeMode_Invalid
+};
+
+/**
+ * different types of (top-level) window z-level positioning
+ */
+enum nsWindowZ {
+ nsWindowZTop = 0, // on top
+ nsWindowZBottom, // on bottom
+ nsWindowZRelative // just below some specified widget
+};
+
+class nsIWidgetListener {
+ public:
+ /**
+ * If this listener is for an nsIAppWindow, return it. If this is null, then
+ * this is likely a listener for a view, which can be determined using
+ * GetView. If both methods return null, this will be an nsWebBrowser.
+ */
+ virtual nsIAppWindow* GetAppWindow();
+
+ /**
+ * If this listener is for an nsView, return it.
+ */
+ virtual nsView* GetView();
+
+ /**
+ * Return the presshell for this widget listener.
+ */
+ virtual mozilla::PresShell* GetPresShell();
+
+ /**
+ * Called when a window is moved to location (x, y). Returns true if the
+ * notification was handled. Coordinates are outer window screen coordinates.
+ */
+ virtual bool WindowMoved(nsIWidget* aWidget, int32_t aX, int32_t aY);
+
+ /**
+ * Called when a window is resized to (width, height). Returns true if the
+ * notification was handled. Coordinates are outer window screen coordinates.
+ */
+ virtual bool WindowResized(nsIWidget* aWidget, int32_t aWidth,
+ int32_t aHeight);
+
+ /**
+ * Called when the size mode (minimized, maximized, fullscreen) is changed.
+ */
+ virtual void SizeModeChanged(nsSizeMode aSizeMode);
+
+ /**
+ * Called when the DPI (device resolution scaling factor) is changed,
+ * such that UI elements may need to be rescaled.
+ */
+ virtual void UIResolutionChanged();
+
+#if defined(MOZ_WIDGET_ANDROID)
+ virtual void DynamicToolbarMaxHeightChanged(mozilla::ScreenIntCoord aHeight);
+ virtual void DynamicToolbarOffsetChanged(mozilla::ScreenIntCoord aOffset);
+#endif
+
+ /**
+ * Called when the z-order of the window is changed. Returns true if the
+ * notification was handled. aPlacement indicates the new z order. If
+ * placement is nsWindowZRelative, then aRequestBelow should be the
+ * window to place below. On return, aActualBelow will be set to the
+ * window actually behind. This generally only applies to Windows.
+ */
+ virtual bool ZLevelChanged(bool aImmediate, nsWindowZ* aPlacement,
+ nsIWidget* aRequestBelow,
+ nsIWidget** aActualBelow);
+
+ /**
+ * Called when the window will enter or leave the fullscreen state.
+ */
+ virtual void FullscreenWillChange(bool aInFullscreen);
+
+ /**
+ * Called when the window entered or left the fullscreen state.
+ */
+ virtual void FullscreenChanged(bool aInFullscreen);
+
+ /**
+ * Called when the occlusion state is changed.
+ */
+ virtual void OcclusionStateChanged(bool aIsFullyOccluded);
+
+ /**
+ * Called when the window is activated and focused.
+ */
+ virtual void WindowActivated();
+
+ /**
+ * Called when the window is deactivated and no longer focused.
+ */
+ virtual void WindowDeactivated();
+
+ /**
+ * Called when the show/hide toolbar button on the Mac titlebar is pressed.
+ */
+ virtual void OSToolbarButtonPressed();
+
+ /**
+ * Called when a request is made to close the window. Returns true if the
+ * notification was handled. Returns true if the notification was handled.
+ */
+ virtual bool RequestWindowClose(nsIWidget* aWidget);
+
+ /*
+ * Indicate that a paint is about to occur on this window. This is called
+ * at a time when it's OK to change the geometry of this widget or of
+ * other widgets. Must be called before every call to PaintWindow.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual void WillPaintWindow(nsIWidget* aWidget);
+
+ /**
+ * Paint the specified region of the window. Returns true if the
+ * notification was handled.
+ * This is called at a time when it is not OK to change the geometry of
+ * this widget or of other widgets.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual bool PaintWindow(nsIWidget* aWidget,
+ mozilla::LayoutDeviceIntRegion aRegion);
+
+ /**
+ * Indicates that a paint occurred.
+ * This is called at a time when it is OK to change the geometry of
+ * this widget or of other widgets.
+ * Must be called after every call to PaintWindow.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual void DidPaintWindow();
+
+ virtual void DidCompositeWindow(mozilla::layers::TransactionId aTransactionId,
+ const mozilla::TimeStamp& aCompositeStart,
+ const mozilla::TimeStamp& aCompositeEnd);
+
+ /**
+ * Request that layout schedules a repaint on the next refresh driver tick.
+ */
+ virtual void RequestRepaint();
+
+ /**
+ * Returns true if this is a popup that should not be visible. If this
+ * is a popup that is visible, not a popup or this state is unknown,
+ * returns false.
+ */
+ virtual bool ShouldNotBeVisible();
+
+ /**
+ * Handle an event.
+ */
+ virtual nsEventStatus HandleEvent(mozilla::WidgetGUIEvent* aEvent,
+ bool aUseAttachedEvents);
+
+ /**
+ * Called when safe area insets are changed.
+ */
+ virtual void SafeAreaInsetsChanged(
+ const mozilla::ScreenIntMargin& aSafeAreaInsets);
+};
+
+#endif
diff --git a/widget/nsIWinTaskbar.idl b/widget/nsIWinTaskbar.idl
new file mode 100644
index 0000000000..bcc2dd1282
--- /dev/null
+++ b/widget/nsIWinTaskbar.idl
@@ -0,0 +1,178 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+#include "nsIBaseWindow.idl"
+
+interface nsIDocShell;
+interface nsITaskbarTabPreview;
+interface nsITaskbarWindowPreview;
+interface nsITaskbarPreviewController;
+interface nsITaskbarProgress;
+interface nsITaskbarOverlayIconController;
+interface nsIJumpListBuilder;
+interface mozIDOMWindow;
+
+/*
+ * nsIWinTaskbar
+ *
+ * This interface represents a service which exposes the APIs provided by the
+ * Windows taskbar to applications.
+ *
+ * Starting in Windows 7, applications gain some control over their appearance
+ * in the taskbar. By default, there is one taskbar preview per top level
+ * window (excluding popups). This preview is represented by an
+ * nsITaskbarWindowPreview object.
+ *
+ * An application can register its own "tab" previews. Such previews will hide
+ * the corresponding nsITaskbarWindowPreview automatically (though this is not
+ * reflected in the visible attribute of the nsITaskbarWindowPreview). These
+ * tab previews do not have to correspond to tabs in the application - they can
+ * vary in size, shape and location. They do not even need to be actual GUI
+ * elements on the window. Unlike window previews, tab previews require most of
+ * the functionality of the nsITaskbarPreviewController to be implemented.
+ *
+ * Applications can also show progress on their taskbar icon. This does not
+ * interact with the taskbar previews except if the nsITaskbarWindowPreview is
+ * made invisible in which case the progress is naturally not shown on that
+ * window.
+ *
+ * When taskbar icons are combined as is the default in Windows 7, the progress
+ * for those windows is also combined as defined here:
+ * http://msdn.microsoft.com/en-us/library/dd391697%28VS.85%29.aspx
+ *
+ * Applications may also define custom taskbar jump lists on application shortcuts.
+ * See nsIJumpListBuilder for more information.
+ */
+
+[scriptable, uuid(11751471-9246-4c72-a80f-0c7df765d640)]
+interface nsIWinTaskbar : nsISupports
+{
+ /**
+ * Returns true if the operating system supports Win7+ taskbar features.
+ * This property acts as a replacement for in-place os version checking.
+ */
+ readonly attribute boolean available;
+
+ /**
+ * Returns the default application user model identity the application
+ * registers with the system. This id is used by the taskbar in grouping
+ * windows and in associating pinned shortcuts with running instances and
+ * jump lists.
+ */
+ readonly attribute AString defaultGroupId;
+
+ /**
+ * Taskbar window and tab preview management
+ */
+
+ /**
+ * Creates a taskbar preview. The docshell should be a toplevel docshell and
+ * is used to find the toplevel window. See the documentation for
+ * nsITaskbarTabPreview for more information.
+ */
+ nsITaskbarTabPreview createTaskbarTabPreview(in nsIDocShell shell,
+ in nsITaskbarPreviewController controller);
+
+ /**
+ * Gets the taskbar preview for a window. The docshell is used to find the
+ * toplevel window. See the documentation for nsITaskbarTabPreview for more
+ * information.
+ *
+ * Note: to implement custom drawing or buttons, a controller is required.
+ */
+ nsITaskbarWindowPreview getTaskbarWindowPreview(in nsIDocShell shell);
+
+ /**
+ * Taskbar icon progress indicator
+ */
+
+ /**
+ * Gets the taskbar progress for a window. The docshell is used to find the
+ * toplevel window. See the documentation for nsITaskbarProgress for more
+ * information.
+ */
+ nsITaskbarProgress getTaskbarProgress(in nsIDocShell shell);
+
+ /**
+ * Taskbar icon overlay
+ */
+
+ /**
+ * Gets the taskbar icon overlay controller for a window. The docshell is used
+ * to find the toplevel window. See the documentation in
+ * nsITaskbarOverlayIconController for more details.
+ */
+ nsITaskbarOverlayIconController getOverlayIconController(in nsIDocShell shell);
+
+ /**
+ * Taskbar and start menu jump list management
+ */
+
+ /**
+ * Retrieve a taskbar jump list builder
+ *
+ * Fails if a jump list build operation has already been initiated, developers
+ * should make use of a single instance of nsIJumpListBuilder for building lists
+ * within an application.
+ *
+ * @throw NS_ERROR_ALREADY_INITIALIZED if an nsIJumpListBuilder instance is
+ * currently building a list.
+ */
+ nsIJumpListBuilder createJumpListBuilder();
+
+ /**
+ * Application window taskbar group settings
+ */
+
+ /**
+ * Set the grouping id for a window.
+ *
+ * The runtime sets a default, global grouping id for all windows on startup.
+ * setGroupIdForWindow allows individual windows to be grouped independently
+ * on the taskbar. Ids should be unique to the app and window to insure
+ * conflicts with other pinned applications do no arise.
+ *
+ * The default group id is based on application.ini vendor, application, and
+ * version values, with a format of 'vendor.app.version'. The default can be
+ * retrieved via defaultGroupId.
+ *
+ * Note, when a window changes taskbar window stacks, it is placed at the
+ * bottom of the new stack.
+ *
+ * @throw NS_ERROR_INVALID_ARG if the window is not a valid top level window
+ * associated with a widget.
+ * @throw NS_ERROR_FAILURE if the property on the window could not be set.
+ * @throw NS_ERROR_UNEXPECTED for general failures.
+ */
+ void setGroupIdForWindow(in mozIDOMWindow aParent, in AString aIdentifier);
+
+ /**
+ * Notify the taskbar that a window is about to enter full screen mode.
+ *
+ * A Windows autohide taskbar will not behave correctly in all cases if
+ * it is not notified when full screen operations start and end.
+ *
+ * @throw NS_ERROR_INVALID_ARG if the window is not a valid top level window
+ * @throw NS_ERROR_UNEXPECTED for general failures.
+ * @throw NS_ERROR_NOT_AVAILABLE if the taskbar cannot be obtained.
+ */
+ void prepareFullScreen(in mozIDOMWindow aWindow, in boolean aFullScreen);
+
+ /**
+ * Notify the taskbar that a window identified by its HWND is about to enter
+ * full screen mode.
+ *
+ * A Windows autohide taskbar will not behave correctly in all cases if
+ * it is not notified when full screen operations start and end.
+ *
+ * @throw NS_ERROR_INVALID_ARG if the window is not a valid top level window
+ * @throw NS_ERROR_UNEXPECTED for general failures.
+ * @throw NS_ERROR_NOT_AVAILABLE if the taskbar cannot be obtained.
+ */
+ [noscript] void prepareFullScreenHWND(in voidPtr aWindow, in boolean aFullScreen);
+};
diff --git a/widget/nsIWindowsUIUtils.idl b/widget/nsIWindowsUIUtils.idl
new file mode 100644
index 0000000000..e8861f8db6
--- /dev/null
+++ b/widget/nsIWindowsUIUtils.idl
@@ -0,0 +1,35 @@
+/* -*- 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 mozIDOMWindowProxy;
+interface imgIContainer;
+
+[scriptable, uuid(aa8a0ecf-96a1-418c-b80e-f24ae18bbedc)]
+interface nsIWindowsUIUtils : nsISupports
+{
+ readonly attribute long systemSmallIconSize;
+ readonly attribute long systemLargeIconSize;
+
+ void setWindowIcon(in mozIDOMWindowProxy aWindow, in imgIContainer aSmallIcon, in imgIContainer aLargeIcon);
+
+ /**
+ * Whether the OS is currently in tablet mode. Always false on
+ * non-Windows and on versions of Windows before win10
+ */
+ readonly attribute boolean inTabletMode;
+
+ /**
+ * Update the tablet mode state
+ */
+ void updateTabletModeState();
+
+ /**
+ * Share URL
+ */
+ void shareUrl(in AString shareTitle, in AString urlToShare);
+};
diff --git a/widget/nsNativeBasicTheme.cpp b/widget/nsNativeBasicTheme.cpp
new file mode 100644
index 0000000000..60083ae5f7
--- /dev/null
+++ b/widget/nsNativeBasicTheme.cpp
@@ -0,0 +1,1599 @@
+/* -*- Mode: C++; tab-width: 40; 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 "nsNativeBasicTheme.h"
+
+#include "gfxBlur.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/gfx/Filters.h"
+#include "nsCSSColorUtils.h"
+#include "nsCSSRendering.h"
+#include "nsLayoutUtils.h"
+#include "PathHelpers.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+using namespace mozilla::gfx;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeBasicTheme, nsNativeTheme, nsITheme)
+
+namespace {
+
+// This pushes and pops a clip rect to the draw target.
+//
+// This is done to reduce fuzz in places where we may have antialiasing,
+// because skia is not clip-invariant: given different clips, it does not
+// guarantee the same result, even if the painted content doesn't intersect
+// the clips.
+//
+// This is a bit sad, overall, but...
+struct MOZ_RAII AutoClipRect {
+ AutoClipRect(DrawTarget& aDt, const LayoutDeviceRect& aRect) : mDt(aDt) {
+ mDt.PushClipRect(aRect.ToUnknownRect());
+ }
+
+ ~AutoClipRect() { mDt.PopClip(); }
+
+ private:
+ DrawTarget& mDt;
+};
+
+} // namespace
+
+static bool IsScrollbarWidthThin(nsIFrame* aFrame) {
+ ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
+ auto scrollbarWidth = style->StyleUIReset()->mScrollbarWidth;
+ return scrollbarWidth == StyleScrollbarWidth::Thin;
+}
+
+/* static */
+auto nsNativeBasicTheme::GetDPIRatio(nsIFrame* aFrame) -> DPIRatio {
+ return DPIRatio(float(AppUnitsPerCSSPixel()) /
+ aFrame->PresContext()
+ ->DeviceContext()
+ ->AppUnitsPerDevPixelAtUnitFullZoom());
+}
+
+/* static */
+bool nsNativeBasicTheme::IsDateTimeResetButton(nsIFrame* aFrame) {
+ if (!aFrame) {
+ return false;
+ }
+
+ nsIFrame* parent = aFrame->GetParent();
+ if (parent && (parent = parent->GetParent()) &&
+ (parent = parent->GetParent())) {
+ nsDateTimeControlFrame* dateTimeFrame = do_QueryFrame(parent);
+ if (dateTimeFrame) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */
+bool nsNativeBasicTheme::IsDateTimeTextField(nsIFrame* aFrame) {
+ nsDateTimeControlFrame* dateTimeFrame = do_QueryFrame(aFrame);
+ return dateTimeFrame;
+}
+
+/* static */
+bool nsNativeBasicTheme::IsColorPickerButton(nsIFrame* aFrame) {
+ nsColorControlFrame* colorPickerButton = do_QueryFrame(aFrame);
+ return colorPickerButton;
+}
+
+/* static */
+LayoutDeviceRect nsNativeBasicTheme::FixAspectRatio(
+ const LayoutDeviceRect& aRect) {
+ // Checkbox and radio need to preserve aspect-ratio for compat.
+ LayoutDeviceRect rect(aRect);
+ if (rect.width == rect.height) {
+ return rect;
+ }
+
+ if (rect.width > rect.height) {
+ auto diff = rect.width - rect.height;
+ rect.width = rect.height;
+ rect.x += diff / 2;
+ } else {
+ auto diff = rect.height - rect.width;
+ rect.height = rect.width;
+ rect.y += diff / 2;
+ }
+
+ return rect;
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeCheckboxColors(
+ const EventStates& aState, StyleAppearance aAppearance) {
+ MOZ_ASSERT(aAppearance == StyleAppearance::Checkbox ||
+ aAppearance == StyleAppearance::Radio);
+
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ bool isPressed = !isDisabled && aState.HasAllStates(NS_EVENT_STATE_HOVER |
+ NS_EVENT_STATE_ACTIVE);
+ bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
+ bool isChecked = aState.HasState(NS_EVENT_STATE_CHECKED);
+ bool isIndeterminate = aAppearance == StyleAppearance::Checkbox &&
+ aState.HasState(NS_EVENT_STATE_INDETERMINATE);
+
+ sRGBColor backgroundColor = sColorWhite;
+ sRGBColor borderColor = sColorGrey40;
+ if (isDisabled) {
+ if (isChecked || isIndeterminate) {
+ backgroundColor = borderColor = sColorGrey40Alpha50;
+ } else {
+ backgroundColor = sColorWhiteAlpha50;
+ borderColor = sColorGrey40Alpha50;
+ }
+ } else {
+ if (isChecked || isIndeterminate) {
+ if (isPressed) {
+ backgroundColor = borderColor = sColorAccentDarker;
+ } else if (isHovered) {
+ backgroundColor = borderColor = sColorAccentDark;
+ } else {
+ backgroundColor = borderColor = sColorAccent;
+ }
+ } else if (isPressed) {
+ backgroundColor = sColorGrey20;
+ borderColor = sColorGrey60;
+ } else if (isHovered) {
+ backgroundColor = sColorWhite;
+ borderColor = sColorGrey50;
+ } else {
+ backgroundColor = sColorWhite;
+ borderColor = sColorGrey40;
+ }
+ }
+
+ return std::make_pair(backgroundColor, borderColor);
+}
+
+sRGBColor nsNativeBasicTheme::ComputeCheckmarkColor(const EventStates& aState) {
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ return isDisabled ? sColorWhiteAlpha50 : sColorWhite;
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRadioCheckmarkColors(
+ const EventStates& aState) {
+ auto [unusedColor, checkColor] =
+ ComputeCheckboxColors(aState, StyleAppearance::Radio);
+ Unused << unusedColor;
+
+ return std::make_pair(sColorWhite, checkColor);
+}
+
+sRGBColor nsNativeBasicTheme::ComputeBorderColor(const EventStates& aState) {
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ bool isActive =
+ aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
+ bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
+ bool isFocused = aState.HasState(NS_EVENT_STATE_FOCUSRING);
+ if (isDisabled) {
+ return sColorGrey40Alpha50;
+ }
+ if (isFocused) {
+ return sColorAccent;
+ }
+ if (isActive) {
+ return sColorGrey60;
+ }
+ if (isHovered) {
+ return sColorGrey50;
+ }
+ return sColorGrey40;
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeButtonColors(
+ const EventStates& aState, nsIFrame* aFrame) {
+ bool isActive =
+ aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
+
+ const sRGBColor& backgroundColor = [&] {
+ if (isDisabled) {
+ return sColorGrey10Alpha50;
+ }
+ if (IsDateTimeResetButton(aFrame)) {
+ return sColorWhite;
+ }
+ if (isActive) {
+ return sColorGrey30;
+ }
+ if (isHovered) {
+ return sColorGrey20;
+ }
+ return sColorGrey10;
+ }();
+
+ const sRGBColor borderColor = ComputeBorderColor(aState);
+ return std::make_pair(backgroundColor, borderColor);
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeTextfieldColors(
+ const EventStates& aState) {
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ const sRGBColor& backgroundColor =
+ isDisabled ? sColorWhiteAlpha50 : sColorWhite;
+ const sRGBColor borderColor = ComputeBorderColor(aState);
+
+ return std::make_pair(backgroundColor, borderColor);
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeProgressColors(
+ const EventStates& aState) {
+ bool isActive =
+ aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
+
+ if (isDisabled) {
+ return std::make_pair(sColorGrey40Alpha50, sColorGrey40Alpha50);
+ }
+ if (isActive || isHovered) {
+ return std::make_pair(sColorAccentDark, sColorAccentDarker);
+ }
+ return std::make_pair(sColorAccent, sColorAccentDark);
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeTrackColors(
+ const EventStates& aState) {
+ bool isActive =
+ aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
+
+ if (isDisabled) {
+ return std::make_pair(sColorGrey10Alpha50, sColorGrey40Alpha50);
+ }
+ if (isActive || isHovered) {
+ return std::make_pair(sColorGrey20, sColorGrey50);
+ }
+ return std::make_pair(sColorGrey10, sColorGrey40);
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeThumbColors(
+ const EventStates& aState) {
+ bool isActive =
+ aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
+
+ const sRGBColor& backgroundColor = [&] {
+ if (isDisabled) {
+ return sColorGrey50Alpha50;
+ }
+ if (isActive) {
+ return sColorAccent;
+ }
+ if (isHovered) {
+ return sColorGrey60;
+ }
+ return sColorGrey50;
+ }();
+
+ const sRGBColor borderColor = sColorWhite;
+
+ return std::make_pair(backgroundColor, borderColor);
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeProgressColors() {
+ return std::make_pair(sColorAccent, sColorAccentDark);
+}
+
+std::pair<sRGBColor, sRGBColor>
+nsNativeBasicTheme::ComputeProgressTrackColors() {
+ return std::make_pair(sColorGrey10, sColorGrey40);
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeMeterchunkColors(
+ const EventStates& aMeterState) {
+ sRGBColor borderColor = sColorMeterGreen20;
+ sRGBColor chunkColor = sColorMeterGreen10;
+
+ if (aMeterState.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
+ borderColor = sColorMeterYellow20;
+ chunkColor = sColorMeterYellow10;
+ } else if (aMeterState.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
+ borderColor = sColorMeterRed20;
+ chunkColor = sColorMeterRed10;
+ }
+
+ return std::make_pair(chunkColor, borderColor);
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeMeterTrackColors() {
+ return std::make_pair(sColorGrey10, sColorGrey40);
+}
+
+sRGBColor nsNativeBasicTheme::ComputeMenulistArrowButtonColor(
+ const EventStates& aState) {
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ return isDisabled ? sColorGrey60Alpha50 : sColorGrey60;
+}
+
+std::array<sRGBColor, 3> nsNativeBasicTheme::ComputeFocusRectColors() {
+ return {sColorAccent, sColorWhiteAlpha80, sColorAccentLight};
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeScrollbarColors(
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aDocumentState, bool aIsRoot) {
+ const nsStyleUI* ui = aStyle.StyleUI();
+ nscolor color;
+ if (ui->mScrollbarColor.IsColors()) {
+ color = ui->mScrollbarColor.AsColors().track.CalcColor(aStyle);
+ } else if (aDocumentState.HasAllStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
+ color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarInactive,
+ sScrollbarColor.ToABGR());
+ } else {
+ color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbar,
+ sScrollbarColor.ToABGR());
+ }
+ if (aIsRoot) {
+ // Root scrollbars must be opaque.
+ nscolor bg = LookAndFeel::GetColor(LookAndFeel::ColorID::WindowBackground,
+ NS_RGB(0xff, 0xff, 0xff));
+ color = NS_ComposeColors(bg, color);
+ }
+ return std::make_pair(gfx::sRGBColor::FromABGR(color), sScrollbarBorderColor);
+}
+
+sRGBColor nsNativeBasicTheme::ComputeScrollbarThumbColor(
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aElementState, const EventStates& aDocumentState) {
+ const nsStyleUI* ui = aStyle.StyleUI();
+ nscolor color;
+ if (ui->mScrollbarColor.IsColors()) {
+ color = ui->mScrollbarColor.AsColors().thumb.CalcColor(aStyle);
+ } else if (aDocumentState.HasAllStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
+ color = LookAndFeel::GetColor(
+ LookAndFeel::ColorID::ThemedScrollbarThumbInactive,
+ sScrollbarThumbColor.ToABGR());
+ } else if (aElementState.HasAllStates(NS_EVENT_STATE_ACTIVE)) {
+ color =
+ LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumbActive,
+ sScrollbarThumbColorActive.ToABGR());
+ } else if (aElementState.HasAllStates(NS_EVENT_STATE_HOVER)) {
+ color =
+ LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumbHover,
+ sScrollbarThumbColorHover.ToABGR());
+ } else {
+ color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumb,
+ sScrollbarThumbColor.ToABGR());
+ }
+ return gfx::sRGBColor::FromABGR(color);
+}
+
+std::array<sRGBColor, 3> nsNativeBasicTheme::ComputeScrollbarButtonColors(
+ nsIFrame* aFrame, StyleAppearance aAppearance, const ComputedStyle& aStyle,
+ const EventStates& aElementState, const EventStates& aDocumentState) {
+ bool isActive = aElementState.HasState(NS_EVENT_STATE_ACTIVE);
+ bool isHovered = aElementState.HasState(NS_EVENT_STATE_HOVER);
+
+ bool hasCustomColor = aStyle.StyleUI()->mScrollbarColor.IsColors();
+ sRGBColor buttonColor;
+ if (hasCustomColor) {
+ // When scrollbar-color is in use, use the thumb color for the button.
+ buttonColor = ComputeScrollbarThumbColor(aFrame, aStyle, aElementState,
+ aDocumentState);
+ } else if (isActive) {
+ buttonColor = sScrollbarButtonActiveColor;
+ } else if (!hasCustomColor && isHovered) {
+ buttonColor = sScrollbarButtonHoverColor;
+ } else {
+ buttonColor = sScrollbarColor;
+ }
+
+ sRGBColor arrowColor;
+ if (hasCustomColor) {
+ // When scrollbar-color is in use, derive the arrow color from the button
+ // color.
+ nscolor bg = buttonColor.ToABGR();
+ bool darken = NS_GetLuminosity(bg) >= NS_MAX_LUMINOSITY / 2;
+ if (isActive) {
+ float c = darken ? 0.0f : 1.0f;
+ arrowColor = sRGBColor(c, c, c);
+ } else {
+ uint8_t c = darken ? 0 : 255;
+ arrowColor =
+ sRGBColor::FromABGR(NS_ComposeColors(bg, NS_RGBA(c, c, c, 160)));
+ }
+ } else if (isActive) {
+ arrowColor = sScrollbarArrowColorActive;
+ } else if (isHovered) {
+ arrowColor = sScrollbarArrowColorHover;
+ } else {
+ arrowColor = sScrollbarArrowColor;
+ }
+
+ return {buttonColor, arrowColor, sScrollbarBorderColor};
+}
+
+static already_AddRefed<Path> GetFocusStrokePath(
+ DrawTarget* aDrawTarget, LayoutDeviceRect& aFocusRect,
+ LayoutDeviceCoord aOffset, const LayoutDeviceCoord aRadius,
+ LayoutDeviceCoord aFocusWidth) {
+ RectCornerRadii radii(aRadius, aRadius, aRadius, aRadius);
+ aFocusRect.Inflate(aOffset);
+
+ LayoutDeviceRect focusRect(aFocusRect);
+ // Deflate the rect by half the border width, so that the middle of the stroke
+ // fills exactly the area we want to fill and not more.
+ focusRect.Deflate(aFocusWidth * 0.5f);
+
+ return MakePathForRoundedRect(*aDrawTarget, focusRect.ToUnknownRect(), radii);
+}
+
+void nsNativeBasicTheme::PaintRoundedFocusRect(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ DPIRatio aDpiRatio,
+ CSSCoord aRadius,
+ CSSCoord aOffset) {
+ // NOTE(emilio): If the widths or offsets here change, make sure to tweak the
+ // GetWidgetOverflow path for FocusOutline.
+ auto [innerColor, middleColor, outerColor] = ComputeFocusRectColors();
+
+ LayoutDeviceRect focusRect(aRect);
+
+ // The focus rect is painted outside of the border area (aRect), see:
+ //
+ // data:text/html,<div style="border: 1px solid; outline: 2px solid
+ // red">Foobar</div>
+ //
+ // But some controls might provide a negative offset to cover the border, if
+ // necessary.
+ LayoutDeviceCoord offset = aOffset * aDpiRatio;
+ LayoutDeviceCoord strokeWidth = CSSCoord(2.0f) * aDpiRatio;
+ focusRect.Inflate(strokeWidth);
+
+ LayoutDeviceCoord strokeRadius = aRadius * aDpiRatio;
+ RefPtr<Path> roundedRect = GetFocusStrokePath(aDrawTarget, focusRect, offset,
+ strokeRadius, strokeWidth);
+ aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(innerColor)),
+ StrokeOptions(strokeWidth));
+
+ offset = CSSCoord(1.0f) * aDpiRatio;
+ strokeRadius += offset;
+ strokeWidth = CSSCoord(1.0f) * aDpiRatio;
+ roundedRect = GetFocusStrokePath(aDrawTarget, focusRect, offset, strokeRadius,
+ strokeWidth);
+ aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(middleColor)),
+ StrokeOptions(strokeWidth));
+
+ offset = CSSCoord(2.0f) * aDpiRatio;
+ strokeRadius += offset;
+ strokeWidth = CSSCoord(2.0f) * aDpiRatio;
+ roundedRect = GetFocusStrokePath(aDrawTarget, focusRect, offset, strokeRadius,
+ strokeWidth);
+ aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(outerColor)),
+ StrokeOptions(strokeWidth));
+}
+
+void nsNativeBasicTheme::PaintRoundedRect(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const sRGBColor& aBackgroundColor,
+ const sRGBColor& aBorderColor,
+ CSSCoord aBorderWidth,
+ RectCornerRadii aDpiAdjustedRadii,
+ DPIRatio aDpiRatio) {
+ const LayoutDeviceCoord borderWidth(aBorderWidth * aDpiRatio);
+
+ LayoutDeviceRect rect(aRect);
+ // Deflate the rect by half the border width, so that the middle of the stroke
+ // fills exactly the area we want to fill and not more.
+ rect.Deflate(borderWidth * 0.5f);
+
+ RefPtr<Path> roundedRect = MakePathForRoundedRect(
+ *aDrawTarget, rect.ToUnknownRect(), aDpiAdjustedRadii);
+
+ aDrawTarget->Fill(roundedRect, ColorPattern(ToDeviceColor(aBackgroundColor)));
+ aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(aBorderColor)),
+ StrokeOptions(borderWidth));
+}
+
+void nsNativeBasicTheme::PaintRoundedRectWithRadius(
+ DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ const sRGBColor& aBackgroundColor, const sRGBColor& aBorderColor,
+ CSSCoord aBorderWidth, CSSCoord aRadius, DPIRatio aDpiRatio) {
+ const LayoutDeviceCoord radius(aRadius * aDpiRatio);
+ RectCornerRadii radii(radius, radius, radius, radius);
+ PaintRoundedRect(aDrawTarget, aRect, aBackgroundColor, aBorderColor,
+ aBorderWidth, radii, aDpiRatio);
+}
+
+void nsNativeBasicTheme::PaintCheckboxControl(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState,
+ DPIRatio aDpiRatio) {
+ const CSSCoord borderWidth = 2.0f;
+ const CSSCoord radius = 2.0f;
+ auto [backgroundColor, borderColor] =
+ ComputeCheckboxColors(aState, StyleAppearance::Checkbox);
+ PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
+ borderWidth, radius, aDpiRatio);
+
+ if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
+ PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, 5.0f, 1.0f);
+ }
+}
+
+// Returns the right scale to cover aRect in the smaller dimension.
+static float ScaleToWidgetRect(const LayoutDeviceRect& aRect) {
+ return std::min(aRect.width, aRect.height) / kMinimumWidgetSize;
+}
+
+void nsNativeBasicTheme::PaintCheckMark(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState) {
+ // Points come from the coordinates on a 14X14 unit box centered at 0,0
+ const float checkPolygonX[] = {-4.5f, -1.5f, -0.5f, 5.0f, 4.75f,
+ 3.5f, -0.5f, -1.5f, -3.5f};
+ const float checkPolygonY[] = {0.5f, 4.0f, 4.0f, -2.5f, -4.0f,
+ -4.0f, 1.0f, 1.25f, -1.0f};
+ const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(float);
+ const float scale = ScaleToWidgetRect(aRect);
+ auto center = aRect.Center().ToUnknownPoint();
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ Point p = center + Point(checkPolygonX[0] * scale, checkPolygonY[0] * scale);
+ builder->MoveTo(p);
+ for (int32_t i = 1; i < checkNumPoints; i++) {
+ p = center + Point(checkPolygonX[i] * scale, checkPolygonY[i] * scale);
+ builder->LineTo(p);
+ }
+ RefPtr<Path> path = builder->Finish();
+
+ sRGBColor fillColor = ComputeCheckmarkColor(aState);
+ aDrawTarget->Fill(path, ColorPattern(ToDeviceColor(fillColor)));
+}
+
+void nsNativeBasicTheme::PaintIndeterminateMark(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState) {
+ const CSSCoord borderWidth = 2.0f;
+ const float scale = ScaleToWidgetRect(aRect);
+
+ Rect rect = aRect.ToUnknownRect();
+ rect.y += (rect.height / 2) - (borderWidth * scale / 2);
+ rect.height = borderWidth * scale;
+ rect.x += (borderWidth * scale) + (borderWidth * scale / 8);
+ rect.width -= ((borderWidth * scale) + (borderWidth * scale / 8)) * 2;
+
+ sRGBColor fillColor = ComputeCheckmarkColor(aState);
+ aDrawTarget->FillRect(rect, ColorPattern(ToDeviceColor(fillColor)));
+}
+
+void nsNativeBasicTheme::PaintStrokedEllipse(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const sRGBColor& aBackgroundColor,
+ const sRGBColor& aBorderColor,
+ const CSSCoord aBorderWidth,
+ DPIRatio aDpiRatio) {
+ const LayoutDeviceCoord borderWidth(aBorderWidth * aDpiRatio);
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+
+ // Deflate for the same reason as PaintRoundedRectWithRadius. Note that the
+ // size is the diameter, so we just shrink by the border width once.
+ auto size = aRect.Size() - LayoutDeviceSize(borderWidth, borderWidth);
+ AppendEllipseToPath(builder, aRect.Center().ToUnknownPoint(),
+ size.ToUnknownSize());
+ RefPtr<Path> ellipse = builder->Finish();
+
+ aDrawTarget->Fill(ellipse, ColorPattern(ToDeviceColor(aBackgroundColor)));
+ aDrawTarget->Stroke(ellipse, ColorPattern(ToDeviceColor(aBorderColor)),
+ StrokeOptions(borderWidth));
+}
+
+void nsNativeBasicTheme::PaintEllipseShadow(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ float aShadowAlpha,
+ const CSSPoint& aShadowOffset,
+ CSSCoord aShadowBlurStdDev,
+ DPIRatio aDpiRatio) {
+ Float stdDev = aShadowBlurStdDev * aDpiRatio;
+ Point offset = (aShadowOffset * aDpiRatio).ToUnknownPoint();
+
+ RefPtr<FilterNode> blurFilter =
+ aDrawTarget->CreateFilter(FilterType::GAUSSIAN_BLUR);
+ if (!blurFilter) {
+ return;
+ }
+
+ blurFilter->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION, stdDev);
+
+ IntSize inflation =
+ gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev, stdDev));
+ Rect inflatedRect = aRect.ToUnknownRect();
+ inflatedRect.Inflate(inflation.width, inflation.height);
+ Rect sourceRectInFilterSpace =
+ inflatedRect - aRect.TopLeft().ToUnknownPoint();
+ Point destinationPointOfSourceRect = inflatedRect.TopLeft() + offset;
+
+ IntSize dtSize = RoundedToInt(aRect.Size().ToUnknownSize());
+ RefPtr<DrawTarget> ellipseDT = aDrawTarget->CreateSimilarDrawTargetForFilter(
+ dtSize, SurfaceFormat::A8, blurFilter, blurFilter,
+ sourceRectInFilterSpace, destinationPointOfSourceRect);
+ if (!ellipseDT) {
+ return;
+ }
+
+ RefPtr<Path> ellipse = MakePathForEllipse(
+ *ellipseDT, (aRect - aRect.TopLeft()).Center().ToUnknownPoint(),
+ aRect.Size().ToUnknownSize());
+ ellipseDT->Fill(ellipse,
+ ColorPattern(DeviceColor(0.0f, 0.0f, 0.0f, aShadowAlpha)));
+ RefPtr<SourceSurface> ellipseSurface = ellipseDT->Snapshot();
+
+ blurFilter->SetInput(IN_GAUSSIAN_BLUR_IN, ellipseSurface);
+ aDrawTarget->DrawFilter(blurFilter, sourceRectInFilterSpace,
+ destinationPointOfSourceRect);
+}
+
+void nsNativeBasicTheme::PaintRadioControl(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState,
+ DPIRatio aDpiRatio) {
+ const CSSCoord borderWidth = 2.0f;
+ auto [backgroundColor, borderColor] =
+ ComputeCheckboxColors(aState, StyleAppearance::Radio);
+
+ PaintStrokedEllipse(aDrawTarget, aRect, backgroundColor, borderColor,
+ borderWidth, aDpiRatio);
+
+ if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
+ PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, 5.0f, 1.0f);
+ }
+}
+
+void nsNativeBasicTheme::PaintRadioCheckmark(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState,
+ DPIRatio aDpiRatio) {
+ const CSSCoord borderWidth = 2.0f;
+ const float scale = ScaleToWidgetRect(aRect);
+ auto [backgroundColor, checkColor] = ComputeRadioCheckmarkColors(aState);
+
+ LayoutDeviceRect rect(aRect);
+ rect.Deflate(borderWidth * scale);
+
+ PaintStrokedEllipse(aDrawTarget, rect, checkColor, backgroundColor,
+ borderWidth, aDpiRatio);
+}
+
+void nsNativeBasicTheme::PaintTextField(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState,
+ DPIRatio aDpiRatio) {
+ auto [backgroundColor, borderColor] = ComputeTextfieldColors(aState);
+
+ const CSSCoord radius = 2.0f;
+
+ PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
+ kTextFieldBorderWidth, radius, aDpiRatio);
+
+ if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
+ PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius,
+ -kTextFieldBorderWidth);
+ }
+}
+
+void nsNativeBasicTheme::PaintListbox(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState,
+ DPIRatio aDpiRatio) {
+ const CSSCoord radius = 2.0f;
+ auto [backgroundColor, borderColor] = ComputeTextfieldColors(aState);
+
+ PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
+ kMenulistBorderWidth, radius, aDpiRatio);
+
+ if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
+ PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius,
+ -kMenulistBorderWidth);
+ }
+}
+
+void nsNativeBasicTheme::PaintMenulist(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState,
+ DPIRatio aDpiRatio) {
+ const CSSCoord radius = 4.0f;
+ auto [backgroundColor, borderColor] = ComputeButtonColors(aState);
+
+ PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
+ kMenulistBorderWidth, radius, aDpiRatio);
+
+ if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
+ PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius,
+ -kMenulistBorderWidth);
+ }
+}
+
+void nsNativeBasicTheme::PaintArrow(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const float aArrowPolygonX[],
+ const float aArrowPolygonY[],
+ const int32_t aArrowNumPoints,
+ const sRGBColor aFillColor) {
+ const float scale = ScaleToWidgetRect(aRect);
+
+ auto center = aRect.Center().ToUnknownPoint();
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ Point p =
+ center + Point(aArrowPolygonX[0] * scale, aArrowPolygonY[0] * scale);
+ builder->MoveTo(p);
+ for (int32_t i = 1; i < aArrowNumPoints; i++) {
+ p = center + Point(aArrowPolygonX[i] * scale, aArrowPolygonY[i] * scale);
+ builder->LineTo(p);
+ }
+ RefPtr<Path> path = builder->Finish();
+
+ aDrawTarget->Fill(path, ColorPattern(ToDeviceColor(aFillColor)));
+}
+
+void nsNativeBasicTheme::PaintMenulistArrowButton(nsIFrame* aFrame,
+ DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState) {
+ const float arrowPolygonX[] = {-3.5f, -0.5f, 0.5f, 3.5f, 3.5f,
+ 3.0f, 0.5f, -0.5f, -3.0f, -3.5f};
+ const float arrowPolygonY[] = {-0.5f, 2.5f, 2.5f, -0.5f, -2.0f,
+ -2.0f, 1.0f, 1.0f, -2.0f, -2.0f};
+ const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
+ sRGBColor arrowColor = ComputeMenulistArrowButtonColor(aState);
+ PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
+ arrowColor);
+}
+
+void nsNativeBasicTheme::PaintSpinnerButton(nsIFrame* aFrame,
+ DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState,
+ StyleAppearance aAppearance,
+ DPIRatio aDpiRatio) {
+ auto [backgroundColor, borderColor] = ComputeButtonColors(aState);
+
+ RefPtr<Path> pathRect = MakePathForRect(*aDrawTarget, aRect.ToUnknownRect());
+
+ aDrawTarget->Fill(pathRect, ColorPattern(ToDeviceColor(backgroundColor)));
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ Point p;
+ if (IsFrameRTL(aFrame)) {
+ p = Point(aRect.x + aRect.width - 0.5f, aRect.y);
+ } else {
+ p = Point(aRect.x - 0.5f, aRect.y);
+ }
+ builder->MoveTo(p);
+ p = Point(p.x, p.y + aRect.height);
+ builder->LineTo(p);
+ RefPtr<Path> path = builder->Finish();
+
+ aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)),
+ StrokeOptions(kSpinnerBorderWidth * aDpiRatio));
+ const float arrowPolygonX[] = {-5.25f, -0.75f, 0.75f, 5.25f, 5.25f,
+ 4.5f, 0.75f, -0.75f, -4.5f, -5.25f};
+ const float arrowPolygonY[] = {-1.875f, 2.625f, 2.625f, -1.875f, -4.125f,
+ -4.125f, 0.375f, 0.375f, -4.125f, -4.125f};
+ const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
+ const float scaleX = ScaleToWidgetRect(aRect);
+ const float scaleY =
+ aAppearance == StyleAppearance::SpinnerDownbutton ? scaleX : -scaleX;
+
+ builder = aDrawTarget->CreatePathBuilder();
+ auto center = aRect.Center().ToUnknownPoint();
+ p = center + Point(arrowPolygonX[0] * scaleX, arrowPolygonY[0] * scaleY);
+ builder->MoveTo(p);
+ for (int32_t i = 1; i < arrowNumPoints; i++) {
+ p = center + Point(arrowPolygonX[i] * scaleX, arrowPolygonY[i] * scaleY);
+ builder->LineTo(p);
+ }
+ path = builder->Finish();
+ aDrawTarget->Fill(path, ColorPattern(ToDeviceColor(borderColor)));
+}
+
+void nsNativeBasicTheme::PaintRange(nsIFrame* aFrame, DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState,
+ DPIRatio aDpiRatio, bool aHorizontal) {
+ nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
+ if (!rangeFrame) {
+ return;
+ }
+
+ double progress = rangeFrame->GetValueAsFractionOfRange();
+ auto rect = aRect;
+ LayoutDeviceRect thumbRect(0, 0, kMinimumRangeThumbSize * aDpiRatio,
+ kMinimumRangeThumbSize * aDpiRatio);
+ Rect overflowRect = aRect.ToUnknownRect();
+ overflowRect.Inflate(CSSCoord(6.0f) * aDpiRatio); // See GetWidgetOverflow
+ Rect progressClipRect(overflowRect);
+ Rect trackClipRect(overflowRect);
+ const LayoutDeviceCoord verticalSize = kRangeHeight * aDpiRatio;
+ if (aHorizontal) {
+ rect.height = verticalSize;
+ rect.y = aRect.y + (aRect.height - rect.height) / 2;
+ thumbRect.y = aRect.y + (aRect.height - thumbRect.height) / 2;
+
+ if (IsFrameRTL(aFrame)) {
+ thumbRect.x =
+ aRect.x + (aRect.width - thumbRect.width) * (1.0 - progress);
+ float midPoint = thumbRect.Center().X();
+ trackClipRect.SetBoxX(overflowRect.X(), midPoint);
+ progressClipRect.SetBoxX(midPoint, overflowRect.XMost());
+ } else {
+ thumbRect.x = aRect.x + (aRect.width - thumbRect.width) * progress;
+ float midPoint = thumbRect.Center().X();
+ progressClipRect.SetBoxX(overflowRect.X(), midPoint);
+ trackClipRect.SetBoxX(midPoint, overflowRect.XMost());
+ }
+ } else {
+ rect.width = verticalSize;
+ rect.x = aRect.x + (aRect.width - rect.width) / 2;
+ thumbRect.x = aRect.x + (aRect.width - thumbRect.width) / 2;
+
+ thumbRect.y = aRect.y + (aRect.height - thumbRect.height) * progress;
+ float midPoint = thumbRect.Center().Y();
+ trackClipRect.SetBoxY(overflowRect.Y(), midPoint);
+ progressClipRect.SetBoxY(midPoint, overflowRect.YMost());
+ }
+
+ const CSSCoord borderWidth = 1.0f;
+ const CSSCoord radius = 2.0f;
+
+ auto [progressColor, progressBorderColor] =
+ ComputeRangeProgressColors(aState);
+ auto [trackColor, trackBorderColor] = ComputeRangeTrackColors(aState);
+
+ // Make a path that clips out the range thumb.
+ RefPtr<PathBuilder> builder =
+ aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
+ AppendRectToPath(builder, overflowRect);
+ AppendEllipseToPath(builder, thumbRect.Center().ToUnknownPoint(),
+ thumbRect.Size().ToUnknownSize());
+ RefPtr<Path> path = builder->Finish();
+
+ // Draw the progress and track pieces with the thumb clipped out, so that
+ // they're not visible behind the thumb even if the thumb is partially
+ // transparent (which is the case in the disabled state).
+ aDrawTarget->PushClip(path);
+ {
+ aDrawTarget->PushClipRect(progressClipRect);
+ PaintRoundedRectWithRadius(aDrawTarget, rect, progressColor,
+ progressBorderColor, borderWidth, radius,
+ aDpiRatio);
+ aDrawTarget->PopClip();
+
+ aDrawTarget->PushClipRect(trackClipRect);
+ PaintRoundedRectWithRadius(aDrawTarget, rect, trackColor, trackBorderColor,
+ borderWidth, radius, aDpiRatio);
+ aDrawTarget->PopClip();
+
+ if (!aState.HasState(NS_EVENT_STATE_DISABLED)) {
+ // Thumb shadow
+ PaintEllipseShadow(aDrawTarget, thumbRect, 0.3f, CSSPoint(0.0f, 2.0f),
+ 2.0f, aDpiRatio);
+ }
+ }
+ aDrawTarget->PopClip();
+
+ // Draw the thumb on top.
+ const CSSCoord thumbBorderWidth = 2.0f;
+ auto [thumbColor, thumbBorderColor] = ComputeRangeThumbColors(aState);
+
+ PaintStrokedEllipse(aDrawTarget, thumbRect, thumbColor, thumbBorderColor,
+ thumbBorderWidth, aDpiRatio);
+
+ if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
+ PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius, 1.0f);
+ }
+}
+
+void nsNativeBasicTheme::PaintProgressBar(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState,
+ DPIRatio aDpiRatio) {
+ const CSSCoord borderWidth = 1.0f;
+ const CSSCoord radius = 2.0f;
+
+ LayoutDeviceRect rect(aRect);
+ const LayoutDeviceCoord height = kProgressbarHeight * aDpiRatio;
+ rect.y += (rect.height - height) / 2;
+ rect.height = height;
+
+ auto [trackColor, trackBorderColor] = ComputeProgressTrackColors();
+
+ PaintRoundedRectWithRadius(aDrawTarget, rect, trackColor, trackBorderColor,
+ borderWidth, radius, aDpiRatio);
+}
+
+void nsNativeBasicTheme::PaintProgresschunk(nsIFrame* aFrame,
+ DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState,
+ DPIRatio aDpiRatio) {
+ // TODO: vertical?
+ // TODO: Address artifacts when position is between 0 and radius + border.
+ // TODO: Handle indeterminate case.
+ nsProgressFrame* progressFrame = do_QueryFrame(aFrame->GetParent());
+ if (!progressFrame) {
+ return;
+ }
+
+ const CSSCoord borderWidth = 1.0f;
+ const LayoutDeviceCoord radius = CSSCoord(2.0f) * aDpiRatio;
+ LayoutDeviceCoord progressEndRadius = 0.0f;
+
+ LayoutDeviceRect rect(aRect);
+ const LayoutDeviceCoord height = kProgressbarHeight * aDpiRatio;
+ rect.y += (rect.height - height) / 2;
+ rect.height = height;
+
+ double position = GetProgressValue(aFrame) / GetProgressMaxValue(aFrame);
+ if (rect.width - (rect.width * position) <
+ (borderWidth * aDpiRatio + radius)) {
+ // Round corners when the progress chunk approaches the maximum value to
+ // avoid artifacts.
+ progressEndRadius = radius;
+ }
+ RectCornerRadii radii;
+ if (IsFrameRTL(aFrame)) {
+ radii =
+ RectCornerRadii(progressEndRadius, radius, radius, progressEndRadius);
+ } else {
+ radii =
+ RectCornerRadii(radius, progressEndRadius, progressEndRadius, radius);
+ }
+
+ auto [progressColor, progressBorderColor] = ComputeProgressColors();
+
+ PaintRoundedRect(aDrawTarget, rect, progressColor, progressBorderColor,
+ borderWidth, radii, aDpiRatio);
+}
+
+void nsNativeBasicTheme::PaintMeter(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState,
+ DPIRatio aDpiRatio) {
+ const CSSCoord borderWidth = 1.0f;
+ const CSSCoord radius = 5.0f;
+
+ LayoutDeviceRect rect(aRect);
+ const LayoutDeviceCoord height = kMeterHeight * aDpiRatio;
+ rect.y += (rect.height - height) / 2;
+ rect.height = height;
+
+ auto [backgroundColor, borderColor] = ComputeMeterTrackColors();
+
+ PaintRoundedRectWithRadius(aDrawTarget, rect, backgroundColor, borderColor,
+ borderWidth, radius, aDpiRatio);
+}
+
+void nsNativeBasicTheme::PaintMeterchunk(nsIFrame* aFrame,
+ DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ DPIRatio aDpiRatio) {
+ // TODO: Address artifacts when position is between 0 and (radius + border).
+ nsMeterFrame* meterFrame = do_QueryFrame(aFrame->GetParent());
+ if (!meterFrame) {
+ return;
+ }
+
+ const CSSCoord borderWidth = 1.0f;
+ const LayoutDeviceCoord radius = CSSCoord(5.0f) * aDpiRatio;
+ LayoutDeviceCoord progressEndRadius = 0.0f;
+
+ LayoutDeviceRect rect(aRect);
+ const LayoutDeviceCoord height = kMeterHeight * aDpiRatio;
+ rect.y += (rect.height - height) / 2;
+ rect.height = height;
+
+ auto* meter =
+ static_cast<mozilla::dom::HTMLMeterElement*>(meterFrame->GetContent());
+ double value = meter->Value();
+ double max = meter->Max();
+ double position = value / max;
+ if (rect.width - (rect.width * position) <
+ (borderWidth * aDpiRatio + radius)) {
+ // Round corners when the progress chunk approaches the maximum value to
+ // avoid artifacts.
+ progressEndRadius = radius;
+ }
+ RectCornerRadii radii;
+ if (IsFrameRTL(aFrame)) {
+ radii =
+ RectCornerRadii(progressEndRadius, radius, radius, progressEndRadius);
+ } else {
+ radii =
+ RectCornerRadii(radius, progressEndRadius, progressEndRadius, radius);
+ }
+
+ auto [chunkColor, borderColor] = ComputeMeterchunkColors(meter->State());
+
+ PaintRoundedRect(aDrawTarget, rect, chunkColor, borderColor, borderWidth,
+ radii, aDpiRatio);
+}
+
+void nsNativeBasicTheme::PaintButton(nsIFrame* aFrame, DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState,
+ DPIRatio aDpiRatio) {
+ const CSSCoord radius = 4.0f;
+ auto [backgroundColor, borderColor] = ComputeButtonColors(aState, aFrame);
+
+ PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
+ kButtonBorderWidth, radius, aDpiRatio);
+
+ if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
+ PaintRoundedFocusRect(aDrawTarget, aRect, aDpiRatio, radius,
+ -kButtonBorderWidth);
+ }
+}
+
+void nsNativeBasicTheme::PaintScrollbarThumb(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ bool aHorizontal, nsIFrame* aFrame,
+ const ComputedStyle& aStyle,
+ const EventStates& aElementState,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio) {
+ sRGBColor thumbColor =
+ ComputeScrollbarThumbColor(aFrame, aStyle, aElementState, aDocumentState);
+
+ aDrawTarget->FillRect(aRect.ToUnknownRect(),
+ ColorPattern(ToDeviceColor(thumbColor)));
+}
+
+void nsNativeBasicTheme::PaintScrollbarTrack(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ bool aHorizontal, nsIFrame* aFrame,
+ const ComputedStyle& aStyle,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio, bool aIsRoot) {
+ // Draw nothing by default. Subclasses can override this.
+}
+
+void nsNativeBasicTheme::PaintScrollbar(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ bool aHorizontal, nsIFrame* aFrame,
+ const ComputedStyle& aStyle,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio, bool aIsRoot) {
+ auto [scrollbarColor, borderColor] =
+ ComputeScrollbarColors(aFrame, aStyle, aDocumentState, aIsRoot);
+ aDrawTarget->FillRect(aRect.ToUnknownRect(),
+ ColorPattern(ToDeviceColor(scrollbarColor)));
+ // FIXME(heycam): We should probably derive the border color when custom
+ // scrollbar colors are in use too. But for now, just skip painting it,
+ // to avoid ugliness.
+ if (aStyle.StyleUI()->mScrollbarColor.IsAuto()) {
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ LayoutDeviceRect strokeRect(aRect);
+ strokeRect.Deflate(CSSCoord(0.5f) * aDpiRatio);
+ builder->MoveTo(strokeRect.TopLeft().ToUnknownPoint());
+ builder->LineTo(
+ (aHorizontal ? strokeRect.TopRight() : strokeRect.BottomLeft())
+ .ToUnknownPoint());
+ RefPtr<Path> path = builder->Finish();
+ aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)),
+ StrokeOptions(CSSCoord(1.0f) * aDpiRatio));
+ }
+}
+
+void nsNativeBasicTheme::PaintScrollCorner(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ nsIFrame* aFrame,
+ const ComputedStyle& aStyle,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio, bool aIsRoot) {
+ auto [scrollbarColor, borderColor] =
+ ComputeScrollbarColors(aFrame, aStyle, aDocumentState, aIsRoot);
+ Unused << borderColor;
+ aDrawTarget->FillRect(aRect.ToUnknownRect(),
+ ColorPattern(ToDeviceColor(scrollbarColor)));
+}
+
+void nsNativeBasicTheme::PaintScrollbarButton(
+ DrawTarget* aDrawTarget, StyleAppearance aAppearance,
+ const LayoutDeviceRect& aRect, nsIFrame* aFrame,
+ const ComputedStyle& aStyle, const EventStates& aElementState,
+ const EventStates& aDocumentState, DPIRatio aDpiRatio) {
+ bool hasCustomColor = aStyle.StyleUI()->mScrollbarColor.IsColors();
+ auto [buttonColor, arrowColor, borderColor] = ComputeScrollbarButtonColors(
+ aFrame, aAppearance, aStyle, aElementState, aDocumentState);
+ aDrawTarget->FillRect(aRect.ToUnknownRect(),
+ ColorPattern(ToDeviceColor(buttonColor)));
+
+ // Start with Up arrow.
+ float arrowPolygonX[] = {-3.0f, 0.0f, 3.0f, 3.0f, 0.0f, -3.0f};
+ float arrowPolygonY[] = {0.4f, -3.1f, 0.4f, 2.7f, -0.8f, 2.7f};
+
+ const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarbuttonUp:
+ break;
+ case StyleAppearance::ScrollbarbuttonDown:
+ for (int32_t i = 0; i < arrowNumPoints; i++) {
+ arrowPolygonY[i] *= -1;
+ }
+ break;
+ case StyleAppearance::ScrollbarbuttonLeft:
+ for (int32_t i = 0; i < arrowNumPoints; i++) {
+ int32_t temp = arrowPolygonX[i];
+ arrowPolygonX[i] = arrowPolygonY[i];
+ arrowPolygonY[i] = temp;
+ }
+ break;
+ case StyleAppearance::ScrollbarbuttonRight:
+ for (int32_t i = 0; i < arrowNumPoints; i++) {
+ int32_t temp = arrowPolygonX[i];
+ arrowPolygonX[i] = arrowPolygonY[i] * -1;
+ arrowPolygonY[i] = temp;
+ }
+ break;
+ default:
+ return;
+ }
+ PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
+ arrowColor);
+
+ // FIXME(heycam): We should probably derive the border color when custom
+ // scrollbar colors are in use too. But for now, just skip painting it,
+ // to avoid ugliness.
+ if (!hasCustomColor) {
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ builder->MoveTo(Point(aRect.x, aRect.y));
+ if (aAppearance == StyleAppearance::ScrollbarbuttonUp ||
+ aAppearance == StyleAppearance::ScrollbarbuttonDown) {
+ builder->LineTo(Point(aRect.x, aRect.y + aRect.height));
+ } else {
+ builder->LineTo(Point(aRect.x + aRect.width, aRect.y));
+ }
+
+ RefPtr<Path> path = builder->Finish();
+ aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(borderColor)),
+ StrokeOptions(CSSCoord(1.0f) * aDpiRatio));
+ }
+}
+
+// Checks whether the frame is for a root <scrollbar> or <scrollcorner>, which
+// influences some platforms' scrollbar rendering.
+bool nsNativeBasicTheme::IsRootScrollbar(nsIFrame* aFrame) {
+ return CheckBooleanAttr(aFrame, nsGkAtoms::root_) &&
+ aFrame->PresContext()->IsRootContentDocument() &&
+ aFrame->GetContent() &&
+ aFrame->GetContent()->IsInNamespace(kNameSpaceID_XUL);
+}
+
+NS_IMETHODIMP
+nsNativeBasicTheme::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& /* aDirtyRect */) {
+ DrawTarget* dt = aContext->GetDrawTarget();
+ // FIXME(emilio): Why does this use AppUnitsPerDevPixel() but GetDPIRatio()
+ // uses AppUnitsPerDevPixelAtUnitFullZoom()?
+ const nscoord twipsPerPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+ EventStates docState = aFrame->GetContent()->OwnerDoc()->GetDocumentState();
+ auto devPxRect = LayoutDeviceRect::FromUnknownRect(
+ NSRectToSnappedRect(aRect, twipsPerPixel, *dt));
+
+ if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
+ bool isHTML = IsHTMLContent(aFrame);
+ nsIFrame* parentFrame = aFrame->GetParent();
+ bool isMenulist = !isHTML && parentFrame->IsMenuFrame();
+ // HTML select and XUL menulist dropdown buttons get state from the
+ // parent.
+ if (isHTML || isMenulist) {
+ aFrame = parentFrame;
+ eventState = GetContentState(parentFrame, aAppearance);
+ }
+ }
+
+ // Hack to avoid skia fuzziness: Add a dummy clip if the widget doesn't
+ // overflow devPxRect.
+ Maybe<AutoClipRect> maybeClipRect;
+ if (aAppearance != StyleAppearance::FocusOutline &&
+ aAppearance != StyleAppearance::Range &&
+ !eventState.HasState(NS_EVENT_STATE_FOCUSRING)) {
+ maybeClipRect.emplace(*dt, devPxRect);
+ }
+
+ DPIRatio dpiRatio = GetDPIRatio(aFrame);
+
+ switch (aAppearance) {
+ case StyleAppearance::Radio: {
+ auto rect = FixAspectRatio(devPxRect);
+ PaintRadioControl(dt, rect, eventState, dpiRatio);
+ if (IsSelected(aFrame)) {
+ PaintRadioCheckmark(dt, rect, eventState, dpiRatio);
+ }
+ break;
+ }
+ case StyleAppearance::Checkbox: {
+ auto rect = FixAspectRatio(devPxRect);
+ PaintCheckboxControl(dt, rect, eventState, dpiRatio);
+ if (GetIndeterminate(aFrame)) {
+ PaintIndeterminateMark(dt, rect, eventState);
+ } else if (IsChecked(aFrame)) {
+ PaintCheckMark(dt, rect, eventState);
+ }
+ break;
+ }
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::NumberInput:
+ PaintTextField(dt, devPxRect, eventState, dpiRatio);
+ break;
+ case StyleAppearance::Listbox:
+ PaintListbox(dt, devPxRect, eventState, dpiRatio);
+ break;
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Menulist:
+ PaintMenulist(dt, devPxRect, eventState, dpiRatio);
+ break;
+ case StyleAppearance::MozMenulistArrowButton:
+ PaintMenulistArrowButton(aFrame, dt, devPxRect, eventState);
+ break;
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ PaintSpinnerButton(aFrame, dt, devPxRect, eventState, aAppearance,
+ dpiRatio);
+ break;
+ case StyleAppearance::Range:
+ PaintRange(aFrame, dt, devPxRect, eventState, dpiRatio,
+ IsRangeHorizontal(aFrame));
+ break;
+ case StyleAppearance::RangeThumb:
+ // Painted as part of StyleAppearance::Range.
+ break;
+ case StyleAppearance::ProgressBar:
+ PaintProgressBar(dt, devPxRect, eventState, dpiRatio);
+ break;
+ case StyleAppearance::Progresschunk:
+ PaintProgresschunk(aFrame, dt, devPxRect, eventState, dpiRatio);
+ break;
+ case StyleAppearance::Meter:
+ PaintMeter(dt, devPxRect, eventState, dpiRatio);
+ break;
+ case StyleAppearance::Meterchunk:
+ PaintMeterchunk(aFrame, dt, devPxRect, dpiRatio);
+ break;
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarthumbVertical: {
+ bool isHorizontal =
+ aAppearance == StyleAppearance::ScrollbarthumbHorizontal;
+ PaintScrollbarThumb(dt, devPxRect, isHorizontal, aFrame,
+ *nsLayoutUtils::StyleForScrollbar(aFrame), eventState,
+ docState, dpiRatio);
+ break;
+ }
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical: {
+ bool isHorizontal =
+ aAppearance == StyleAppearance::ScrollbartrackHorizontal;
+ PaintScrollbarTrack(dt, devPxRect, isHorizontal, aFrame,
+ *nsLayoutUtils::StyleForScrollbar(aFrame), docState,
+ dpiRatio, IsRootScrollbar(aFrame));
+ break;
+ }
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical: {
+ bool isHorizontal = aAppearance == StyleAppearance::ScrollbarHorizontal;
+ PaintScrollbar(dt, devPxRect, isHorizontal, aFrame,
+ *nsLayoutUtils::StyleForScrollbar(aFrame), docState,
+ dpiRatio, IsRootScrollbar(aFrame));
+ break;
+ }
+ case StyleAppearance::Scrollcorner:
+ PaintScrollCorner(dt, devPxRect, aFrame,
+ *nsLayoutUtils::StyleForScrollbar(aFrame), docState,
+ dpiRatio, IsRootScrollbar(aFrame));
+ break;
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ // For scrollbar-width:thin, we don't display the buttons.
+ if (!IsScrollbarWidthThin(aFrame)) {
+ PaintScrollbarButton(dt, aAppearance, devPxRect, aFrame,
+ *nsLayoutUtils::StyleForScrollbar(aFrame),
+ eventState, docState, dpiRatio);
+ }
+ break;
+ case StyleAppearance::Button:
+ PaintButton(aFrame, dt, devPxRect, eventState, dpiRatio);
+ break;
+ case StyleAppearance::FocusOutline:
+ // TODO(emilio): Consider supporting outline-radius / outline-offset?
+ PaintRoundedFocusRect(dt, devPxRect, dpiRatio, 0.0f, 0.0f);
+ break;
+ default:
+ // Various appearance values are used for XUL elements. Normally these
+ // will not be available in content documents (and thus in the content
+ // processes where the native basic theme can be used), but tests are run
+ // with the remote XUL pref enabled and so we can get in here. So we
+ // just return an error rather than assert.
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return NS_OK;
+}
+
+/*bool
+nsNativeBasicTheme::CreateWebRenderCommandsForWidget(mozilla::wr::DisplayListBuilder&
+aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const
+mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager*
+aManager, nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
+}*/
+
+LayoutDeviceIntMargin nsNativeBasicTheme::GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
+ DPIRatio dpiRatio = GetDPIRatio(aFrame);
+ switch (aAppearance) {
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::NumberInput: {
+ // FIXME: Do we want this margin not to be int-based? The native windows
+ // theme rounds (see ScaleForDPI)...
+ LayoutDeviceIntCoord w = (kTextFieldBorderWidth * dpiRatio).Rounded();
+ return LayoutDeviceIntMargin(w, w, w, w);
+ }
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton: {
+ LayoutDeviceIntCoord w = (kMenulistBorderWidth * dpiRatio).Rounded();
+ return LayoutDeviceIntMargin(w, w, w, w);
+ }
+ case StyleAppearance::Button: {
+ LayoutDeviceIntCoord w = (kButtonBorderWidth * dpiRatio).Rounded();
+ return LayoutDeviceIntMargin(w, w, w, w);
+ }
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio: {
+ LayoutDeviceIntCoord w = (kCheckboxRadioBorderWidth * dpiRatio).Rounded();
+ return LayoutDeviceIntMargin(w, w, w, w);
+ }
+ default:
+ return LayoutDeviceIntMargin();
+ }
+}
+
+bool nsNativeBasicTheme::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) {
+ switch (aAppearance) {
+ // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
+ // and have a meaningful baseline, so they can't have
+ // author-specified padding.
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ aResult->SizeTo(0, 0, 0, 0);
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool nsNativeBasicTheme::GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) {
+ nsIntMargin overflow;
+ switch (aAppearance) {
+ case StyleAppearance::FocusOutline:
+ // 2px * each of the segments + 1 px for the separation between them.
+ overflow.SizeTo(5, 5, 5, 5);
+ break;
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Range:
+ overflow.SizeTo(6, 6, 6, 6);
+ break;
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::Button:
+ // 2px for each segment, plus 1px separation, but we paint 1px inside the
+ // border area so 4px overflow.
+ overflow.SizeTo(4, 4, 4, 4);
+ break;
+ default:
+ return false;
+ }
+
+ // TODO: This should convert from device pixels to app units, not from CSS
+ // pixels. And it should take the dpi ratio into account.
+ // Using CSS pixels can cause the overflow to be too small if the page is
+ // zoomed out.
+ aOverflowRect->Inflate(nsMargin(CSSPixel::ToAppUnits(overflow.top),
+ CSSPixel::ToAppUnits(overflow.right),
+ CSSPixel::ToAppUnits(overflow.bottom),
+ CSSPixel::ToAppUnits(overflow.left)));
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsNativeBasicTheme::GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) {
+ DPIRatio dpiRatio = GetDPIRatio(aFrame);
+
+ aResult->width = aResult->height = (kMinimumWidgetSize * dpiRatio).Rounded();
+
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ if (IsColorPickerButton(aFrame)) {
+ aResult->height = (kMinimumColorPickerHeight * dpiRatio).Rounded();
+ }
+ break;
+ case StyleAppearance::RangeThumb:
+ aResult->SizeTo((kMinimumRangeThumbSize * dpiRatio).Rounded(),
+ (kMinimumRangeThumbSize * dpiRatio).Rounded());
+ break;
+ case StyleAppearance::MozMenulistArrowButton:
+ aResult->width = (kMinimumDropdownArrowButtonWidth * dpiRatio).Rounded();
+ break;
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ aResult->width = (kMinimumSpinnerButtonWidth * dpiRatio).Rounded();
+ aResult->height = (kMinimumSpinnerButtonHeight * dpiRatio).Rounded();
+ break;
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ // For scrollbar-width:thin, we don't display the buttons.
+ if (IsScrollbarWidthThin(aFrame)) {
+ aResult->SizeTo(0, 0);
+ break;
+ }
+ [[fallthrough]];
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::Scrollcorner: {
+ if (IsScrollbarWidthThin(aFrame)) {
+ aResult->SizeTo((kMinimumThinScrollbarSize * dpiRatio).Rounded(),
+ (kMinimumThinScrollbarSize * dpiRatio).Rounded());
+ } else {
+ aResult->SizeTo((kMinimumScrollbarSize * dpiRatio).Rounded(),
+ (kMinimumScrollbarSize * dpiRatio).Rounded());
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ *aIsOverridable = true;
+ return NS_OK;
+}
+
+nsITheme::Transparency nsNativeBasicTheme::GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ return eUnknownTransparency;
+}
+
+NS_IMETHODIMP
+nsNativeBasicTheme::WidgetStateChanged(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) {
+ 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
+nsNativeBasicTheme::ThemeChanged() { return NS_OK; }
+
+bool nsNativeBasicTheme::WidgetAppearanceDependsOnWindowFocus(
+ StyleAppearance aAppearance) {
+ return IsWidgetScrollbarPart(aAppearance);
+}
+
+nsITheme::ThemeGeometryType nsNativeBasicTheme::ThemeGeometryTypeForWidget(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ return eThemeGeometryTypeUnknown;
+}
+
+bool nsNativeBasicTheme::ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::FocusOutline:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Meter:
+ case StyleAppearance::Meterchunk:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::Scrollcorner:
+ case StyleAppearance::Button:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Menuitemtext:
+ case StyleAppearance::MenulistText:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
+ default:
+ return false;
+ }
+}
+
+bool nsNativeBasicTheme::WidgetIsContainer(StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ return false;
+ default:
+ return true;
+ }
+}
+
+bool nsNativeBasicTheme::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) {
+ return true;
+}
+
+bool nsNativeBasicTheme::ThemeNeedsComboboxDropmarker() { return true; }
diff --git a/widget/nsNativeBasicTheme.h b/widget/nsNativeBasicTheme.h
new file mode 100644
index 0000000000..94b3cdc62e
--- /dev/null
+++ b/widget/nsNativeBasicTheme.h
@@ -0,0 +1,334 @@
+/* -*- 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 nsNativeBasicTheme_h
+#define nsNativeBasicTheme_h
+
+#include "Units.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/HTMLMeterElement.h"
+#include "mozilla/dom/HTMLProgressElement.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/gfx/Types.h"
+#include "nsColorControlFrame.h"
+#include "nsDateTimeControlFrame.h"
+#include "nsDeviceContext.h"
+#include "nsITheme.h"
+#include "nsMeterFrame.h"
+#include "nsNativeTheme.h"
+#include "nsProgressFrame.h"
+#include "nsRangeFrame.h"
+
+namespace mozilla {
+namespace widget {
+
+static const gfx::sRGBColor sColorWhite(gfx::sRGBColor::OpaqueWhite());
+static const gfx::sRGBColor sColorWhiteAlpha50(gfx::sRGBColor::White(0.5f));
+static const gfx::sRGBColor sColorWhiteAlpha80(gfx::sRGBColor::White(0.8f));
+static const gfx::sRGBColor sColorBlack(gfx::sRGBColor::OpaqueBlack());
+
+static const gfx::sRGBColor sColorGrey10(
+ gfx::sRGBColor::UnusualFromARGB(0xffe9e9ed));
+static const gfx::sRGBColor sColorGrey10Alpha50(
+ gfx::sRGBColor::UnusualFromARGB(0x7fe9e9ed));
+static const gfx::sRGBColor sColorGrey20(
+ gfx::sRGBColor::UnusualFromARGB(0xffd0d0d7));
+static const gfx::sRGBColor sColorGrey30(
+ gfx::sRGBColor::UnusualFromARGB(0xffb1b1b9));
+static const gfx::sRGBColor sColorGrey40(
+ gfx::sRGBColor::UnusualFromARGB(0xff8f8f9d));
+static const gfx::sRGBColor sColorGrey40Alpha50(
+ gfx::sRGBColor::UnusualFromARGB(0x7f8f8f9d));
+static const gfx::sRGBColor sColorGrey50(
+ gfx::sRGBColor::UnusualFromARGB(0xff676774));
+static const gfx::sRGBColor sColorGrey50Alpha50(
+ gfx::sRGBColor::UnusualFromARGB(0x7f676774));
+static const gfx::sRGBColor sColorGrey60(
+ gfx::sRGBColor::UnusualFromARGB(0xff484851));
+static const gfx::sRGBColor sColorGrey60Alpha50(
+ gfx::sRGBColor::UnusualFromARGB(0x7f484851));
+
+static const gfx::sRGBColor sColorAccentLight(
+ gfx::sRGBColor::UnusualFromARGB(0x4d008deb));
+static const gfx::sRGBColor sColorAccent(
+ gfx::sRGBColor::UnusualFromARGB(0xff0060df));
+static const gfx::sRGBColor sColorAccentDark(
+ gfx::sRGBColor::UnusualFromARGB(0xff0250bb));
+static const gfx::sRGBColor sColorAccentDarker(
+ gfx::sRGBColor::UnusualFromARGB(0xff054096));
+
+static const gfx::sRGBColor sColorMeterGreen10(
+ gfx::sRGBColor::UnusualFromARGB(0xff00ab60));
+static const gfx::sRGBColor sColorMeterGreen20(
+ gfx::sRGBColor::UnusualFromARGB(0xff056139));
+static const gfx::sRGBColor sColorMeterYellow10(
+ gfx::sRGBColor::UnusualFromARGB(0xffffbd4f));
+static const gfx::sRGBColor sColorMeterYellow20(
+ gfx::sRGBColor::UnusualFromARGB(0xffd2811e));
+static const gfx::sRGBColor sColorMeterRed10(
+ gfx::sRGBColor::UnusualFromARGB(0xffe22850));
+static const gfx::sRGBColor sColorMeterRed20(
+ gfx::sRGBColor::UnusualFromARGB(0xff810220));
+
+static const gfx::sRGBColor sScrollbarColor(
+ gfx::sRGBColor::UnusualFromARGB(0xfff0f0f0));
+static const gfx::sRGBColor sScrollbarBorderColor(gfx::sRGBColor(1.0f, 1.0f,
+ 1.0f));
+static const gfx::sRGBColor sScrollbarThumbColor(
+ gfx::sRGBColor::UnusualFromARGB(0xffcdcdcd));
+static const gfx::sRGBColor sScrollbarThumbColorActive(gfx::sRGBColor(0.375f,
+ 0.375f,
+ 0.375f));
+static const gfx::sRGBColor sScrollbarThumbColorHover(gfx::sRGBColor(0.65f,
+ 0.65f,
+ 0.65f));
+static const gfx::sRGBColor sScrollbarArrowColor(gfx::sRGBColor(0.375f, 0.375f,
+ 0.375f));
+static const gfx::sRGBColor sScrollbarArrowColorActive(gfx::sRGBColor(1.0f,
+ 1.0f,
+ 1.0f));
+static const gfx::sRGBColor sScrollbarArrowColorHover(gfx::sRGBColor(0.0f, 0.0f,
+ 0.0f));
+static const gfx::sRGBColor sScrollbarButtonColor(sScrollbarColor);
+static const gfx::sRGBColor sScrollbarButtonActiveColor(gfx::sRGBColor(0.375f,
+ 0.375f,
+ 0.375f));
+static const gfx::sRGBColor sScrollbarButtonHoverColor(gfx::sRGBColor(0.86f,
+ 0.86f,
+ 0.86f));
+
+static const CSSCoord kMinimumWidgetSize = 14.0f;
+static const CSSCoord kMinimumScrollbarSize = 17.0f;
+static const CSSCoord kMinimumThinScrollbarSize = 6.0f;
+static const CSSCoord kMinimumColorPickerHeight = 32.0f;
+static const CSSCoord kMinimumRangeThumbSize = 20.0f;
+static const CSSCoord kMinimumDropdownArrowButtonWidth = 18.0f;
+static const CSSCoord kMinimumSpinnerButtonWidth = 18.0f;
+static const CSSCoord kMinimumSpinnerButtonHeight = 9.0f;
+static const CSSCoord kButtonBorderWidth = 1.0f;
+static const CSSCoord kMenulistBorderWidth = 1.0f;
+static const CSSCoord kTextFieldBorderWidth = 1.0f;
+static const CSSCoord kSpinnerBorderWidth = 1.0f;
+static const CSSCoord kRangeHeight = 6.0f;
+static const CSSCoord kProgressbarHeight = 6.0f;
+static const CSSCoord kMeterHeight = 12.0f;
+
+// nsCheckboxRadioFrame takes the bottom of the content box as the baseline.
+// This border-width makes its baseline 2px under the bottom, which is nice.
+static const CSSCoord kCheckboxRadioBorderWidth = 2.0f;
+
+} // namespace widget
+} // namespace mozilla
+
+class nsNativeBasicTheme : protected nsNativeTheme, public nsITheme {
+ protected:
+ using sRGBColor = mozilla::gfx::sRGBColor;
+ using CSSCoord = mozilla::CSSCoord;
+ using CSSPoint = mozilla::CSSPoint;
+ using CSSIntCoord = mozilla::CSSIntCoord;
+ using ComputedStyle = mozilla::ComputedStyle;
+ using EventStates = mozilla::EventStates;
+ using DrawTarget = mozilla::gfx::DrawTarget;
+ using Path = mozilla::gfx::Path;
+ using Rect = mozilla::gfx::Rect;
+ using Point = mozilla::gfx::Point;
+ using RectCornerRadii = mozilla::gfx::RectCornerRadii;
+ using LayoutDeviceCoord = mozilla::LayoutDeviceCoord;
+ using LayoutDeviceRect = mozilla::LayoutDeviceRect;
+
+ public:
+ using DPIRatio = mozilla::CSSToLayoutDeviceScale;
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // The nsITheme interface.
+ NS_IMETHOD DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) override;
+ /*bool CreateWebRenderCommandsForWidget(mozilla::wr::DisplayListBuilder&
+ aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const
+ mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager*
+ aManager, nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect&
+ aRect) override;*/
+ [[nodiscard]] LayoutDeviceIntMargin GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+ bool GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) override;
+ bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) override;
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) override;
+ Transparency GetWidgetTransparency(nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) override;
+ NS_IMETHOD ThemeChanged() override;
+ bool WidgetAppearanceDependsOnWindowFocus(
+ StyleAppearance aAppearance) override;
+ /*bool NeedToClearBackgroundBehindWidget(
+ nsIFrame* aFrame, StyleAppearance aAppearance) override;*/
+ ThemeGeometryType ThemeGeometryTypeForWidget(
+ nsIFrame* aFrame, StyleAppearance aAppearance) override;
+ bool ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+ bool WidgetIsContainer(StyleAppearance aAppearance) override;
+ bool ThemeDrawsFocusForWidget(StyleAppearance aAppearance) override;
+ bool ThemeNeedsComboboxDropmarker() override;
+
+ protected:
+ nsNativeBasicTheme() = default;
+ virtual ~nsNativeBasicTheme() = default;
+
+ static DPIRatio GetDPIRatio(nsIFrame* aFrame);
+ static bool IsDateTimeResetButton(nsIFrame* aFrame);
+ static bool IsDateTimeTextField(nsIFrame* aFrame);
+ static bool IsColorPickerButton(nsIFrame* aFrame);
+ static bool IsRootScrollbar(nsIFrame* aFrame);
+ static LayoutDeviceRect FixAspectRatio(const LayoutDeviceRect& aRect);
+
+ virtual std::pair<sRGBColor, sRGBColor> ComputeCheckboxColors(
+ const EventStates& aState, StyleAppearance aAppearance);
+ virtual sRGBColor ComputeCheckmarkColor(const EventStates& aState);
+ virtual std::pair<sRGBColor, sRGBColor> ComputeRadioCheckmarkColors(
+ const EventStates& aState);
+ virtual sRGBColor ComputeBorderColor(const EventStates& aState);
+
+ virtual std::pair<sRGBColor, sRGBColor> ComputeButtonColors(
+ const EventStates& aState, nsIFrame* aFrame = nullptr);
+ virtual std::pair<sRGBColor, sRGBColor> ComputeTextfieldColors(
+ const EventStates& aState);
+ virtual std::pair<sRGBColor, sRGBColor> ComputeRangeProgressColors(
+ const EventStates& aState);
+ virtual std::pair<sRGBColor, sRGBColor> ComputeRangeTrackColors(
+ const EventStates& aState);
+ virtual std::pair<sRGBColor, sRGBColor> ComputeRangeThumbColors(
+ const EventStates& aState);
+ virtual std::pair<sRGBColor, sRGBColor> ComputeProgressColors();
+ virtual std::pair<sRGBColor, sRGBColor> ComputeProgressTrackColors();
+ virtual std::pair<sRGBColor, sRGBColor> ComputeMeterchunkColors(
+ const EventStates& aMeterState);
+ virtual std::pair<sRGBColor, sRGBColor> ComputeMeterTrackColors();
+ virtual sRGBColor ComputeMenulistArrowButtonColor(const EventStates& aState);
+ virtual std::array<sRGBColor, 3> ComputeFocusRectColors();
+ virtual std::pair<sRGBColor, sRGBColor> ComputeScrollbarColors(
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aDocumentState, bool aIsRoot);
+ virtual sRGBColor ComputeScrollbarThumbColor(
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aElementState, const EventStates& aDocumentState);
+ virtual std::array<sRGBColor, 3> ComputeScrollbarButtonColors(
+ nsIFrame* aFrame, StyleAppearance aAppearance,
+ const ComputedStyle& aStyle, const EventStates& aElementState,
+ const EventStates& aDocumentState);
+
+ void PaintRoundedFocusRect(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect, DPIRatio aDpiRatio,
+ CSSCoord aRadius, CSSCoord aOffset);
+ void PaintRoundedRect(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ const sRGBColor& aBackgroundColor,
+ const sRGBColor& aBorderColor, CSSCoord aBorderWidth,
+ RectCornerRadii aDpiAdjustedRadii, DPIRatio aDpiRatio);
+ void PaintRoundedRectWithRadius(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const sRGBColor& aBackgroundColor,
+ const sRGBColor& aBorderColor,
+ CSSCoord aBorderWidth, CSSCoord aRadius,
+ DPIRatio aDpiRatio);
+ void PaintCheckboxControl(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState, DPIRatio aDpiRatio);
+ void PaintCheckMark(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ const EventStates& aState);
+ void PaintIndeterminateMark(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState);
+ void PaintStrokedEllipse(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const sRGBColor& aBackgroundColor,
+ const sRGBColor& aBorderColor,
+ const CSSCoord aBorderWidth, DPIRatio aDpiRatio);
+ void PaintEllipseShadow(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect, float aShadowAlpha,
+ const CSSPoint& aShadowOffset,
+ CSSCoord aShadowBlurStdDev, DPIRatio aDpiRatio);
+ void PaintRadioControl(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ const EventStates& aState, DPIRatio aDpiRatio);
+ void PaintRadioCheckmark(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState, DPIRatio aDpiRatio);
+ void PaintTextField(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ const EventStates& aState, DPIRatio aDpiRatio);
+ void PaintListbox(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ const EventStates& aState, DPIRatio aDpiRatio);
+ void PaintMenulist(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ const EventStates& aState, DPIRatio aDpiRatio);
+ void PaintArrow(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ const float aArrowPolygonX[], const float aArrowPolygonY[],
+ const int32_t aArrowNumPoints, const sRGBColor aFillColor);
+ void PaintMenulistArrowButton(nsIFrame* aFrame, DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState);
+ void PaintSpinnerButton(nsIFrame* aFrame, DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState,
+ StyleAppearance aAppearance, DPIRatio aDpiRatio);
+ void PaintRange(nsIFrame* aFrame, DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect, const EventStates& aState,
+ DPIRatio aDpiRatio, bool aHorizontal);
+ void PaintProgressBar(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ const EventStates& aState, DPIRatio aDpiRatio);
+ void PaintProgresschunk(nsIFrame* aFrame, DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ const EventStates& aState, DPIRatio aDpiRatio);
+ void PaintMeter(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ const EventStates& aState, DPIRatio aDpiRatio);
+ void PaintMeterchunk(nsIFrame* aFrame, DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect, DPIRatio aDpiRatio);
+ void PaintButton(nsIFrame* aFrame, DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect, const EventStates& aState,
+ DPIRatio aDpiRatio);
+
+ virtual void PaintScrollbarThumb(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ bool aHorizontal, nsIFrame* aFrame,
+ const ComputedStyle& aStyle,
+ const EventStates& aElementState,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio);
+ virtual void PaintScrollbar(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect, bool aHorizontal,
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio, bool aIsRoot);
+ virtual void PaintScrollbarTrack(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ bool aHorizontal, nsIFrame* aFrame,
+ const ComputedStyle& aStyle,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio, bool aIsRoot);
+ virtual void PaintScrollCorner(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio, bool aIsRoot);
+ virtual void PaintScrollbarButton(
+ DrawTarget* aDrawTarget, StyleAppearance aAppearance,
+ const LayoutDeviceRect& aRect, nsIFrame* aFrame,
+ const ComputedStyle& aStyle, const EventStates& aElementState,
+ const EventStates& aDocumentState, DPIRatio aDpiRatio);
+};
+
+#endif
diff --git a/widget/nsNativeTheme.cpp b/widget/nsNativeTheme.cpp
new file mode 100644
index 0000000000..b758dd5d26
--- /dev/null
+++ b/widget/nsNativeTheme.cpp
@@ -0,0 +1,677 @@
+/* -*- 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 "nsNativeTheme.h"
+#include "nsIWidget.h"
+#include "mozilla/dom/Document.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsNumberControlFrame.h"
+#include "nsPresContext.h"
+#include "nsString.h"
+#include "nsNameSpaceManager.h"
+#include "nsStyleConsts.h"
+#include "nsPIDOMWindow.h"
+#include "nsProgressFrame.h"
+#include "nsMeterFrame.h"
+#include "nsMenuFrame.h"
+#include "nsRangeFrame.h"
+#include "nsCSSRendering.h"
+#include "ImageContainer.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/HTMLProgressElement.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsNativeTheme::nsNativeTheme() : mAnimatedContentTimeout(UINT32_MAX) {}
+
+NS_IMPL_ISUPPORTS(nsNativeTheme, nsITimerCallback, nsINamed)
+
+/* static */ EventStates nsNativeTheme::GetContentState(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ if (!aFrame) return EventStates();
+
+ bool isXULCheckboxRadio = (aAppearance == StyleAppearance::Checkbox ||
+ aAppearance == StyleAppearance::Radio) &&
+ aFrame->GetContent()->IsXULElement();
+ if (isXULCheckboxRadio) aFrame = aFrame->GetParent();
+
+ if (!aFrame->GetContent()) return EventStates();
+
+ nsIContent* frameContent = aFrame->GetContent();
+ EventStates flags;
+ if (frameContent->IsElement()) {
+ flags = frameContent->AsElement()->State();
+
+ nsNumberControlFrame* numberControlFrame =
+ nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
+ if (numberControlFrame &&
+ numberControlFrame->GetContent()->AsElement()->State().HasState(
+ NS_EVENT_STATE_DISABLED)) {
+ flags |= NS_EVENT_STATE_DISABLED;
+ }
+ }
+
+ if (isXULCheckboxRadio && aAppearance == StyleAppearance::Radio &&
+ IsFocused(aFrame)) {
+ flags |= NS_EVENT_STATE_FOCUS;
+ nsPIDOMWindowOuter* window = aFrame->GetContent()->OwnerDoc()->GetWindow();
+ if (window && window->ShouldShowFocusRing()) {
+ flags |= NS_EVENT_STATE_FOCUSRING;
+ }
+ }
+
+ return flags;
+}
+
+/* static */
+bool nsNativeTheme::CheckBooleanAttr(nsIFrame* aFrame, nsAtom* aAtom) {
+ if (!aFrame) return false;
+
+ nsIContent* content = aFrame->GetContent();
+ if (!content || !content->IsElement()) return false;
+
+ if (content->IsHTMLElement())
+ return content->AsElement()->HasAttr(kNameSpaceID_None, aAtom);
+
+ // For XML/XUL elements, an attribute must be equal to the literal
+ // string "true" to be counted as true. An empty string should _not_
+ // be counted as true.
+ return content->AsElement()->AttrValueIs(kNameSpaceID_None, aAtom, u"true"_ns,
+ eCaseMatters);
+}
+
+/* static */
+int32_t nsNativeTheme::CheckIntAttr(nsIFrame* aFrame, nsAtom* aAtom,
+ int32_t defaultValue) {
+ if (!aFrame) return defaultValue;
+
+ nsIContent* content = aFrame->GetContent();
+ if (!content || !content->IsElement()) return defaultValue;
+
+ nsAutoString attr;
+ content->AsElement()->GetAttr(kNameSpaceID_None, aAtom, attr);
+ nsresult err;
+ int32_t value = attr.ToInteger(&err);
+ if (attr.IsEmpty() || NS_FAILED(err)) return defaultValue;
+
+ return value;
+}
+
+/* static */
+double nsNativeTheme::GetProgressValue(nsIFrame* aFrame) {
+ if (!aFrame || !aFrame->GetContent()->IsHTMLElement(nsGkAtoms::progress)) {
+ return 0;
+ }
+
+ return static_cast<HTMLProgressElement*>(aFrame->GetContent())->Value();
+}
+
+/* static */
+double nsNativeTheme::GetProgressMaxValue(nsIFrame* aFrame) {
+ if (!aFrame || !aFrame->GetContent()->IsHTMLElement(nsGkAtoms::progress)) {
+ return 100;
+ }
+
+ return static_cast<HTMLProgressElement*>(aFrame->GetContent())->Max();
+}
+
+bool nsNativeTheme::GetCheckedOrSelected(nsIFrame* aFrame,
+ bool aCheckSelected) {
+ if (!aFrame) return false;
+
+ nsIContent* content = aFrame->GetContent();
+
+ if (content->IsXULElement()) {
+ // For a XUL checkbox or radio button, the state of the parent determines
+ // the checked state
+ aFrame = aFrame->GetParent();
+ } else {
+ // Check for an HTML input element
+ HTMLInputElement* inputElt = HTMLInputElement::FromNode(content);
+ if (inputElt) {
+ return inputElt->Checked();
+ }
+ }
+
+ return CheckBooleanAttr(
+ aFrame, aCheckSelected ? nsGkAtoms::selected : nsGkAtoms::checked);
+}
+
+bool nsNativeTheme::IsButtonTypeMenu(nsIFrame* aFrame) {
+ if (!aFrame) return false;
+
+ nsIContent* content = aFrame->GetContent();
+ return content->IsXULElement(nsGkAtoms::button) &&
+ content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ u"menu"_ns, eCaseMatters);
+}
+
+bool nsNativeTheme::IsPressedButton(nsIFrame* aFrame) {
+ EventStates eventState =
+ GetContentState(aFrame, StyleAppearance::Toolbarbutton);
+ if (IsDisabled(aFrame, eventState)) return false;
+
+ return IsOpenButton(aFrame) ||
+ eventState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER);
+}
+
+bool nsNativeTheme::GetIndeterminate(nsIFrame* aFrame) {
+ if (!aFrame) return false;
+
+ nsIContent* content = aFrame->GetContent();
+
+ if (content->IsXULElement()) {
+ // For a XUL checkbox or radio button, the state of the parent determines
+ // the state
+ return CheckBooleanAttr(aFrame->GetParent(), nsGkAtoms::indeterminate);
+ }
+
+ // Check for an HTML input element
+ HTMLInputElement* inputElt = HTMLInputElement::FromNode(content);
+ if (inputElt) {
+ return inputElt->Indeterminate();
+ }
+
+ return false;
+}
+
+bool nsNativeTheme::IsWidgetStyled(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ // Check for specific widgets to see if HTML has overridden the style.
+ if (!aFrame) return false;
+
+ // Resizers have some special handling, dependent on whether in a scrollable
+ // container or not. If so, use the scrollable container's to determine
+ // whether the style is overriden instead of the resizer. This allows a
+ // non-native transparent resizer to be used instead. Otherwise, we just
+ // fall through and return false.
+ if (aAppearance == StyleAppearance::Resizer) {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (parentFrame && parentFrame->IsScrollFrame()) {
+ // if the parent is a scrollframe, the resizer should be native themed
+ // only if the scrollable area doesn't override the widget style.
+ parentFrame = parentFrame->GetParent();
+ if (parentFrame) {
+ return IsWidgetStyled(
+ aPresContext, parentFrame,
+ parentFrame->StyleDisplay()->EffectiveAppearance());
+ }
+ }
+ }
+
+ /**
+ * Progress bar appearance should be the same for the bar and the container
+ * frame. nsProgressFrame owns the logic and will tell us what we should do.
+ */
+ if (aAppearance == StyleAppearance::Progresschunk ||
+ aAppearance == StyleAppearance::ProgressBar) {
+ nsProgressFrame* progressFrame = do_QueryFrame(
+ aAppearance == StyleAppearance::Progresschunk ? aFrame->GetParent()
+ : aFrame);
+ if (progressFrame) {
+ return !progressFrame->ShouldUseNativeStyle();
+ }
+ }
+
+ /**
+ * Meter bar appearance should be the same for the bar and the container
+ * frame. nsMeterFrame owns the logic and will tell us what we should do.
+ */
+ if (aAppearance == StyleAppearance::Meterchunk ||
+ aAppearance == StyleAppearance::Meter) {
+ nsMeterFrame* meterFrame = do_QueryFrame(
+ aAppearance == StyleAppearance::Meterchunk ? aFrame->GetParent()
+ : aFrame);
+ if (meterFrame) {
+ return !meterFrame->ShouldUseNativeStyle();
+ }
+ }
+
+ /**
+ * An nsRangeFrame and its children are treated atomically when it
+ * comes to native theming (either all parts, or no parts, are themed).
+ * nsRangeFrame owns the logic and will tell us what we should do.
+ */
+ if (aAppearance == StyleAppearance::Range ||
+ aAppearance == StyleAppearance::RangeThumb) {
+ nsRangeFrame* rangeFrame = do_QueryFrame(
+ aAppearance == StyleAppearance::RangeThumb ? aFrame->GetParent()
+ : aFrame);
+ if (rangeFrame) {
+ return !rangeFrame->ShouldUseNativeStyle();
+ }
+ }
+
+ if (aAppearance == StyleAppearance::SpinnerUpbutton ||
+ aAppearance == StyleAppearance::SpinnerDownbutton) {
+ nsNumberControlFrame* numberControlFrame =
+ nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
+ if (numberControlFrame) {
+ return !numberControlFrame->ShouldUseNativeStyleForSpinner();
+ }
+ }
+
+ return (aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::Button ||
+ aAppearance == StyleAppearance::Textfield ||
+ aAppearance == StyleAppearance::Textarea ||
+ aAppearance == StyleAppearance::Listbox ||
+ aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton) &&
+ aFrame->GetContent()->IsHTMLElement() &&
+ aPresContext->HasAuthorSpecifiedRules(
+ aFrame, NS_AUTHOR_SPECIFIED_BORDER_OR_BACKGROUND);
+}
+
+bool nsNativeTheme::IsDisabled(nsIFrame* aFrame, EventStates aEventStates) {
+ if (!aFrame) {
+ return false;
+ }
+
+ nsIContent* content = aFrame->GetContent();
+ if (!content || !content->IsElement()) {
+ return false;
+ }
+
+ if (content->IsHTMLElement()) {
+ return aEventStates.HasState(NS_EVENT_STATE_DISABLED);
+ }
+
+ // For XML/XUL elements, an attribute must be equal to the literal
+ // string "true" to be counted as true. An empty string should _not_
+ // be counted as true.
+ return content->AsElement()->AttrValueIs(
+ kNameSpaceID_None, nsGkAtoms::disabled, u"true"_ns, eCaseMatters);
+}
+
+/* static */
+bool nsNativeTheme::IsFrameRTL(nsIFrame* aFrame) {
+ if (!aFrame) {
+ return false;
+ }
+ return aFrame->GetWritingMode().IsPhysicalRTL();
+}
+
+/* static */
+bool nsNativeTheme::IsHTMLContent(nsIFrame* aFrame) {
+ if (!aFrame) {
+ return false;
+ }
+ nsIContent* content = aFrame->GetContent();
+ return content && content->IsHTMLElement();
+}
+
+// scrollbar button:
+int32_t nsNativeTheme::GetScrollbarButtonType(nsIFrame* aFrame) {
+ if (!aFrame) return 0;
+
+ static Element::AttrValuesArray strings[] = {
+ nsGkAtoms::scrollbarDownBottom, nsGkAtoms::scrollbarDownTop,
+ nsGkAtoms::scrollbarUpBottom, nsGkAtoms::scrollbarUpTop, nullptr};
+
+ nsIContent* content = aFrame->GetContent();
+ if (!content || !content->IsElement()) {
+ return 0;
+ }
+
+ switch (content->AsElement()->FindAttrValueIn(
+ kNameSpaceID_None, nsGkAtoms::sbattr, strings, eCaseMatters)) {
+ case 0:
+ return eScrollbarButton_Down | eScrollbarButton_Bottom;
+ case 1:
+ return eScrollbarButton_Down;
+ case 2:
+ return eScrollbarButton_Bottom;
+ case 3:
+ return eScrollbarButton_UpTop;
+ }
+
+ return 0;
+}
+
+// treeheadercell:
+nsNativeTheme::TreeSortDirection nsNativeTheme::GetTreeSortDirection(
+ nsIFrame* aFrame) {
+ if (!aFrame || !aFrame->GetContent()) return eTreeSortDirection_Natural;
+
+ static Element::AttrValuesArray strings[] = {nsGkAtoms::descending,
+ nsGkAtoms::ascending, nullptr};
+
+ nsIContent* content = aFrame->GetContent();
+ if (content->IsElement()) {
+ switch (content->AsElement()->FindAttrValueIn(
+ kNameSpaceID_None, nsGkAtoms::sortDirection, strings, eCaseMatters)) {
+ case 0:
+ return eTreeSortDirection_Descending;
+ case 1:
+ return eTreeSortDirection_Ascending;
+ }
+ }
+
+ return eTreeSortDirection_Natural;
+}
+
+bool nsNativeTheme::IsLastTreeHeaderCell(nsIFrame* aFrame) {
+ if (!aFrame) return false;
+
+ // A tree column picker is always the last header cell.
+ if (aFrame->GetContent()->IsXULElement(nsGkAtoms::treecolpicker)) return true;
+
+ // Find the parent tree.
+ nsIContent* parent = aFrame->GetContent()->GetParent();
+ while (parent && !parent->IsXULElement(nsGkAtoms::tree)) {
+ parent = parent->GetParent();
+ }
+
+ // If the column picker is visible, this can't be the last column.
+ if (parent && !parent->AsElement()->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::hidecolumnpicker,
+ u"true"_ns, eCaseMatters))
+ return false;
+
+ while ((aFrame = aFrame->GetNextSibling())) {
+ if (aFrame->GetRect().Width() > 0) return false;
+ }
+ return true;
+}
+
+// tab:
+bool nsNativeTheme::IsBottomTab(nsIFrame* aFrame) {
+ if (!aFrame) return false;
+
+ nsAutoString classStr;
+ if (aFrame->GetContent()->IsElement()) {
+ aFrame->GetContent()->AsElement()->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::_class, classStr);
+ }
+ // FIXME: This looks bogus, shouldn't this be looking at GetClasses()?
+ return !classStr.IsEmpty() && classStr.Find("tab-bottom") != kNotFound;
+}
+
+bool nsNativeTheme::IsFirstTab(nsIFrame* aFrame) {
+ if (!aFrame) return false;
+
+ for (nsIFrame* first : aFrame->GetParent()->PrincipalChildList()) {
+ if (first->GetRect().Width() > 0 &&
+ first->GetContent()->IsXULElement(nsGkAtoms::tab))
+ return (first == aFrame);
+ }
+ return false;
+}
+
+bool nsNativeTheme::IsHorizontal(nsIFrame* aFrame) {
+ if (!aFrame) return false;
+
+ if (!aFrame->GetContent()->IsElement()) return true;
+
+ return !aFrame->GetContent()->AsElement()->AttrValueIs(
+ kNameSpaceID_None, nsGkAtoms::orient, nsGkAtoms::vertical, eCaseMatters);
+}
+
+bool nsNativeTheme::IsNextToSelectedTab(nsIFrame* aFrame, int32_t aOffset) {
+ if (!aFrame) return false;
+
+ if (aOffset == 0) return IsSelectedTab(aFrame);
+
+ int32_t thisTabIndex = -1, selectedTabIndex = -1;
+
+ nsIFrame* currentTab = aFrame->GetParent()->PrincipalChildList().FirstChild();
+ for (int32_t i = 0; currentTab; currentTab = currentTab->GetNextSibling()) {
+ if (currentTab->GetRect().Width() == 0) continue;
+ if (aFrame == currentTab) thisTabIndex = i;
+ if (IsSelectedTab(currentTab)) selectedTabIndex = i;
+ ++i;
+ }
+
+ if (thisTabIndex == -1 || selectedTabIndex == -1) return false;
+
+ return (thisTabIndex - selectedTabIndex == aOffset);
+}
+
+bool nsNativeTheme::IsIndeterminateProgress(nsIFrame* aFrame,
+ EventStates aEventStates) {
+ if (!aFrame || !aFrame->GetContent() ||
+ !aFrame->GetContent()->IsHTMLElement(nsGkAtoms::progress)) {
+ return false;
+ }
+
+ return aEventStates.HasState(NS_EVENT_STATE_INDETERMINATE);
+}
+
+bool nsNativeTheme::IsVerticalProgress(nsIFrame* aFrame) {
+ if (!aFrame) {
+ return false;
+ }
+ return IsVerticalMeter(aFrame);
+}
+
+bool nsNativeTheme::IsVerticalMeter(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame, "You have to pass a non-null aFrame");
+ switch (aFrame->StyleDisplay()->mOrient) {
+ case StyleOrient::Horizontal:
+ return false;
+ case StyleOrient::Vertical:
+ return true;
+ case StyleOrient::Inline:
+ return aFrame->GetWritingMode().IsVertical();
+ case StyleOrient::Block:
+ return !aFrame->GetWritingMode().IsVertical();
+ }
+ MOZ_ASSERT_UNREACHABLE("unexpected -moz-orient value");
+ return false;
+}
+
+// menupopup:
+bool nsNativeTheme::IsSubmenu(nsIFrame* aFrame, bool* aLeftOfParent) {
+ if (!aFrame) return false;
+
+ nsIContent* parentContent = aFrame->GetContent()->GetParent();
+ if (!parentContent || !parentContent->IsXULElement(nsGkAtoms::menu))
+ return false;
+
+ nsIFrame* parent = aFrame;
+ while ((parent = parent->GetParent())) {
+ if (parent->GetContent() == parentContent) {
+ if (aLeftOfParent) {
+ LayoutDeviceIntRect selfBounds, parentBounds;
+ selfBounds = aFrame->GetNearestWidget()->GetScreenBounds();
+ parentBounds = parent->GetNearestWidget()->GetScreenBounds();
+ *aLeftOfParent = selfBounds.X() < parentBounds.X();
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool nsNativeTheme::IsRegularMenuItem(nsIFrame* aFrame) {
+ nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
+ return !(menuFrame &&
+ (menuFrame->IsOnMenuBar() || menuFrame->IsParentMenuList()));
+}
+
+bool nsNativeTheme::QueueAnimatedContentForRefresh(nsIContent* aContent,
+ uint32_t aMinimumFrameRate) {
+ NS_ASSERTION(aContent, "Null pointer!");
+ NS_ASSERTION(aMinimumFrameRate, "aMinimumFrameRate must be non-zero!");
+ NS_ASSERTION(aMinimumFrameRate <= 1000,
+ "aMinimumFrameRate must be less than 1000!");
+
+ uint32_t timeout = 1000 / aMinimumFrameRate;
+ timeout = std::min(mAnimatedContentTimeout, timeout);
+
+ if (!mAnimatedContentTimer) {
+ mAnimatedContentTimer = NS_NewTimer();
+ NS_ENSURE_TRUE(mAnimatedContentTimer, false);
+ }
+
+ if (mAnimatedContentList.IsEmpty() || timeout != mAnimatedContentTimeout) {
+ nsresult rv;
+ if (!mAnimatedContentList.IsEmpty()) {
+ rv = mAnimatedContentTimer->Cancel();
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ if (XRE_IsContentProcess() && NS_IsMainThread()) {
+ mAnimatedContentTimer->SetTarget(
+ aContent->OwnerDoc()->EventTargetFor(TaskCategory::Other));
+ }
+ rv = mAnimatedContentTimer->InitWithCallback(this, timeout,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ mAnimatedContentTimeout = timeout;
+ }
+
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ mAnimatedContentList.AppendElement(aContent);
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsNativeTheme::Notify(nsITimer* aTimer) {
+ NS_ASSERTION(aTimer == mAnimatedContentTimer, "Wrong timer!");
+
+ // XXX Assumes that calling nsIFrame::Invalidate won't reenter
+ // QueueAnimatedContentForRefresh.
+
+ uint32_t count = mAnimatedContentList.Length();
+ for (uint32_t index = 0; index < count; index++) {
+ nsIFrame* frame = mAnimatedContentList[index]->GetPrimaryFrame();
+ if (frame) {
+ frame->InvalidateFrame();
+ }
+ }
+
+ mAnimatedContentList.Clear();
+ mAnimatedContentTimeout = UINT32_MAX;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeTheme::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsNativeTheme");
+ return NS_OK;
+}
+
+nsIFrame* nsNativeTheme::GetAdjacentSiblingFrameWithSameAppearance(
+ nsIFrame* aFrame, bool aNextSibling) {
+ if (!aFrame) return nullptr;
+
+ // Find the next visible sibling.
+ nsIFrame* sibling = aFrame;
+ do {
+ sibling =
+ aNextSibling ? sibling->GetNextSibling() : sibling->GetPrevSibling();
+ } while (sibling && sibling->GetRect().Width() == 0);
+
+ // Check same appearance and adjacency.
+ if (!sibling ||
+ sibling->StyleDisplay()->EffectiveAppearance() !=
+ aFrame->StyleDisplay()->EffectiveAppearance() ||
+ (sibling->GetRect().XMost() != aFrame->GetRect().X() &&
+ aFrame->GetRect().XMost() != sibling->GetRect().X()))
+ return nullptr;
+ return sibling;
+}
+
+bool nsNativeTheme::IsRangeHorizontal(nsIFrame* aFrame) {
+ nsIFrame* rangeFrame = aFrame;
+ if (!rangeFrame->IsRangeFrame()) {
+ // If the thumb's frame is passed in, get its range parent:
+ rangeFrame = aFrame->GetParent();
+ }
+ if (rangeFrame->IsRangeFrame()) {
+ return static_cast<nsRangeFrame*>(rangeFrame)->IsHorizontal();
+ }
+ // Not actually a range frame - just use the ratio of the frame's size to
+ // decide:
+ return aFrame->GetSize().width >= aFrame->GetSize().height;
+}
+
+static nsIFrame* GetBodyFrame(nsIFrame* aCanvasFrame) {
+ nsIContent* content = aCanvasFrame->GetContent();
+ if (!content) {
+ return nullptr;
+ }
+ nsIContent* body = content->OwnerDoc()->GetBodyElement();
+ if (!body) {
+ return nullptr;
+ }
+ return body->GetPrimaryFrame();
+}
+
+/* static */
+bool nsNativeTheme::IsDarkBackground(nsIFrame* aFrame) {
+ nsIScrollableFrame* scrollFrame = nullptr;
+ while (!scrollFrame && aFrame) {
+ scrollFrame = aFrame->GetScrollTargetFrame();
+ aFrame = aFrame->GetParent();
+ }
+ if (!scrollFrame) return false;
+
+ nsIFrame* frame = scrollFrame->GetScrolledFrame();
+ if (nsCSSRendering::IsCanvasFrame(frame)) {
+ // For canvas frames, prefer to look at the body first, because the body
+ // background color is most likely what will be visible as the background
+ // color of the page, even if the html element has a different background
+ // color which prevents that of the body frame to propagate to the viewport.
+ nsIFrame* bodyFrame = GetBodyFrame(frame);
+ if (bodyFrame) {
+ frame = bodyFrame;
+ }
+ }
+ ComputedStyle* bgSC = nullptr;
+ if (!nsCSSRendering::FindBackground(frame, &bgSC) ||
+ bgSC->StyleBackground()->IsTransparent(bgSC)) {
+ nsIFrame* backgroundFrame =
+ nsCSSRendering::FindNonTransparentBackgroundFrame(frame, true);
+ nsCSSRendering::FindBackground(backgroundFrame, &bgSC);
+ }
+ if (bgSC) {
+ nscolor bgColor = bgSC->StyleBackground()->BackgroundColor(bgSC);
+ // Consider the background color dark if the sum of the r, g and b values is
+ // less than 384 in a semi-transparent document. This heuristic matches
+ // what WebKit does, and we can improve it later if needed.
+ return NS_GET_A(bgColor) > 127 &&
+ NS_GET_R(bgColor) + NS_GET_G(bgColor) + NS_GET_B(bgColor) < 384;
+ }
+ return false;
+}
+
+/*static*/
+bool nsNativeTheme::IsWidgetScrollbarPart(StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::Scrollcorner:
+ return true;
+ default:
+ return false;
+ }
+}
diff --git a/widget/nsNativeTheme.h b/widget/nsNativeTheme.h
new file mode 100644
index 0000000000..b72b30a3e0
--- /dev/null
+++ b/widget/nsNativeTheme.h
@@ -0,0 +1,199 @@
+/* -*- 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/. */
+
+// This defines a common base class for nsITheme implementations, to reduce
+// code duplication.
+
+#ifndef _NSNATIVETHEME_H_
+#define _NSNATIVETHEME_H_
+
+#include "nsAlgorithm.h"
+#include "nsAtom.h"
+#include "nsColor.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsMargin.h"
+#include "nsGkAtoms.h"
+#include "nsTArray.h"
+#include "nsINamed.h"
+#include "nsITimer.h"
+#include "nsIContent.h"
+
+class nsIFrame;
+class nsPresContext;
+
+namespace mozilla {
+class ComputedStyle;
+enum class StyleAppearance : uint8_t;
+class EventStates;
+} // namespace mozilla
+
+class nsNativeTheme : public nsITimerCallback, public nsINamed {
+ protected:
+ virtual ~nsNativeTheme() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ nsNativeTheme();
+
+ public:
+ enum ScrollbarButtonType {
+ eScrollbarButton_UpTop = 0,
+ eScrollbarButton_Down = 1 << 0,
+ eScrollbarButton_Bottom = 1 << 1
+ };
+
+ enum TreeSortDirection {
+ eTreeSortDirection_Descending,
+ eTreeSortDirection_Natural,
+ eTreeSortDirection_Ascending
+ };
+ // Returns the content state (hover, focus, etc), see EventStateManager.h
+ static mozilla::EventStates GetContentState(
+ nsIFrame* aFrame, mozilla::StyleAppearance aAppearance);
+
+ // Returns whether the widget is already styled by content
+ // Normally called from ThemeSupportsWidget to turn off native theming
+ // for elements that are already styled.
+ bool IsWidgetStyled(nsPresContext* aPresContext, nsIFrame* aFrame,
+ mozilla::StyleAppearance aAppearance);
+
+ // Accessors to widget-specific state information
+
+ bool IsDisabled(nsIFrame* aFrame, mozilla::EventStates aEventStates);
+
+ // RTL chrome direction
+ static bool IsFrameRTL(nsIFrame* aFrame);
+
+ static bool IsHTMLContent(nsIFrame* aFrame);
+
+ // button:
+ bool IsDefaultButton(nsIFrame* aFrame) {
+ return CheckBooleanAttr(aFrame, nsGkAtoms::_default);
+ }
+
+ bool IsButtonTypeMenu(nsIFrame* aFrame);
+
+ // checkbox:
+ bool IsChecked(nsIFrame* aFrame) {
+ return GetCheckedOrSelected(aFrame, false);
+ }
+
+ // radiobutton:
+ bool IsSelected(nsIFrame* aFrame) {
+ return GetCheckedOrSelected(aFrame, true);
+ }
+
+ static bool IsFocused(nsIFrame* aFrame) {
+ return CheckBooleanAttr(aFrame, nsGkAtoms::focused);
+ }
+
+ // scrollbar button:
+ int32_t GetScrollbarButtonType(nsIFrame* aFrame);
+
+ // tab:
+ bool IsSelectedTab(nsIFrame* aFrame) {
+ return CheckBooleanAttr(aFrame, nsGkAtoms::visuallyselected);
+ }
+
+ bool IsNextToSelectedTab(nsIFrame* aFrame, int32_t aOffset);
+
+ bool IsBeforeSelectedTab(nsIFrame* aFrame) {
+ return IsNextToSelectedTab(aFrame, -1);
+ }
+
+ bool IsAfterSelectedTab(nsIFrame* aFrame) {
+ return IsNextToSelectedTab(aFrame, 1);
+ }
+
+ bool IsLeftToSelectedTab(nsIFrame* aFrame) {
+ return IsFrameRTL(aFrame) ? IsAfterSelectedTab(aFrame)
+ : IsBeforeSelectedTab(aFrame);
+ }
+
+ bool IsRightToSelectedTab(nsIFrame* aFrame) {
+ return IsFrameRTL(aFrame) ? IsBeforeSelectedTab(aFrame)
+ : IsAfterSelectedTab(aFrame);
+ }
+
+ // button / toolbarbutton:
+ bool IsCheckedButton(nsIFrame* aFrame) {
+ return CheckBooleanAttr(aFrame, nsGkAtoms::checked);
+ }
+
+ bool IsSelectedButton(nsIFrame* aFrame) {
+ return CheckBooleanAttr(aFrame, nsGkAtoms::checked) ||
+ CheckBooleanAttr(aFrame, nsGkAtoms::selected);
+ }
+
+ bool IsOpenButton(nsIFrame* aFrame) {
+ return CheckBooleanAttr(aFrame, nsGkAtoms::open);
+ }
+
+ bool IsPressedButton(nsIFrame* aFrame);
+
+ // treeheadercell:
+ TreeSortDirection GetTreeSortDirection(nsIFrame* aFrame);
+ bool IsLastTreeHeaderCell(nsIFrame* aFrame);
+
+ // tab:
+ bool IsBottomTab(nsIFrame* aFrame);
+ bool IsFirstTab(nsIFrame* aFrame);
+
+ bool IsHorizontal(nsIFrame* aFrame);
+
+ // progressbar:
+ bool IsIndeterminateProgress(nsIFrame* aFrame,
+ mozilla::EventStates aEventStates);
+ bool IsVerticalProgress(nsIFrame* aFrame);
+
+ // meter:
+ bool IsVerticalMeter(nsIFrame* aFrame);
+
+ // textfield:
+ bool IsReadOnly(nsIFrame* aFrame) {
+ return CheckBooleanAttr(aFrame, nsGkAtoms::readonly);
+ }
+
+ // menupopup:
+ bool IsSubmenu(nsIFrame* aFrame, bool* aLeftOfParent);
+
+ // True if it's not a menubar item or menulist item
+ bool IsRegularMenuItem(nsIFrame* aFrame);
+
+ static bool CheckBooleanAttr(nsIFrame* aFrame, nsAtom* aAtom);
+ static int32_t CheckIntAttr(nsIFrame* aFrame, nsAtom* aAtom,
+ int32_t defaultValue);
+
+ // Helpers for progressbar.
+ static double GetProgressValue(nsIFrame* aFrame);
+ static double GetProgressMaxValue(nsIFrame* aFrame);
+
+ bool GetCheckedOrSelected(nsIFrame* aFrame, bool aCheckSelected);
+ bool GetIndeterminate(nsIFrame* aFrame);
+
+ bool QueueAnimatedContentForRefresh(nsIContent* aContent,
+ uint32_t aMinimumFrameRate);
+
+ nsIFrame* GetAdjacentSiblingFrameWithSameAppearance(nsIFrame* aFrame,
+ bool aNextSibling);
+
+ bool IsRangeHorizontal(nsIFrame* aFrame);
+
+ // scrollbar
+ static bool IsDarkBackground(nsIFrame* aFrame);
+ // custom scrollbar
+ typedef nscolor (*AutoColorGetter)(mozilla::ComputedStyle*);
+ static bool IsWidgetScrollbarPart(mozilla::StyleAppearance aAppearance);
+
+ private:
+ uint32_t mAnimatedContentTimeout;
+ nsCOMPtr<nsITimer> mAnimatedContentTimer;
+ AutoTArray<nsCOMPtr<nsIContent>, 20> mAnimatedContentList;
+};
+
+#endif // _NSNATIVETHEME_H_
diff --git a/widget/nsPaper.cpp b/widget/nsPaper.cpp
new file mode 100644
index 0000000000..a940d3c1cb
--- /dev/null
+++ b/widget/nsPaper.cpp
@@ -0,0 +1,87 @@
+/* -*- 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 "nsPaper.h"
+#include "nsPaperMargin.h"
+#include "nsPrinterBase.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Promise.h"
+
+using mozilla::ErrorResult;
+using mozilla::PaperInfo;
+
+NS_IMPL_CYCLE_COLLECTION(nsPaper, mMarginPromise, mPrinter)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPaper)
+ NS_INTERFACE_MAP_ENTRY(nsIPaper)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPaper)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPaper)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPaper)
+
+nsPaper::nsPaper(const mozilla::PaperInfo& aInfo)
+ : mPrinter(nullptr), mInfo(aInfo) {}
+
+nsPaper::nsPaper(nsPrinterBase& aPrinter, const mozilla::PaperInfo& aInfo)
+ : mPrinter(&aPrinter), mInfo(aInfo) {}
+
+nsPaper::~nsPaper() = default;
+
+NS_IMETHODIMP
+nsPaper::GetId(nsAString& aId) {
+ aId = mInfo.mId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPaper::GetName(nsAString& aName) {
+ aName = mInfo.mName.IsEmpty() ? mInfo.mId : mInfo.mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPaper::GetWidth(double* aWidth) {
+ NS_ENSURE_ARG_POINTER(aWidth);
+ *aWidth = mInfo.mSize.Width();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPaper::GetHeight(double* aHeight) {
+ NS_ENSURE_ARG_POINTER(aHeight);
+ *aHeight = mInfo.mSize.Height();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPaper::GetUnwriteableMargin(JSContext* aCx, Promise** aPromise) {
+ if (RefPtr<Promise> existing = mMarginPromise) {
+ existing.forget(aPromise);
+ return NS_OK;
+ }
+ ErrorResult rv;
+ RefPtr<Promise> promise = Promise::Create(xpc::CurrentNativeGlobal(aCx), rv);
+ if (MOZ_UNLIKELY(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ mMarginPromise = promise;
+
+ if (mInfo.mUnwriteableMargin) {
+ auto margin = mozilla::MakeRefPtr<nsPaperMargin>(*mInfo.mUnwriteableMargin);
+ mMarginPromise->MaybeResolve(margin);
+ } else {
+ if (mPrinter) {
+ mPrinter->QueryMarginsForPaper(*mMarginPromise, mInfo.mId);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("common paper sizes should know their margins");
+ mMarginPromise->MaybeRejectWithNotSupportedError("Margins unavailable");
+ }
+ }
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
diff --git a/widget/nsPaper.h b/widget/nsPaper.h
new file mode 100644
index 0000000000..b0117b7306
--- /dev/null
+++ b/widget/nsPaper.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPaper_h__
+#define nsPaper_h__
+
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/Maybe.h"
+#include "nsIPaper.h"
+#include "nsISupportsImpl.h"
+#include "js/TypeDecls.h"
+#include "nsString.h"
+
+struct JSContext;
+
+namespace mozilla {
+
+// Simple struct that can be used off the main thread to hold all the info from
+// an nsPaper instance.
+struct PaperInfo {
+ using MarginDouble = mozilla::gfx::MarginDouble;
+ using SizeDouble = mozilla::gfx::SizeDouble;
+
+ PaperInfo() = default;
+ PaperInfo(const nsAString& aId, const nsAString& aName,
+ const SizeDouble& aSize,
+ const Maybe<MarginDouble>& aUnwriteableMargin)
+ : mId(aId),
+ mName(aName),
+ mSize(aSize),
+ mUnwriteableMargin(aUnwriteableMargin) {}
+
+ nsString mId;
+ nsString mName;
+
+ SizeDouble mSize;
+
+ // The margins may not be known by some back-ends.
+ Maybe<MarginDouble> mUnwriteableMargin{Nothing()};
+};
+
+/**
+ * Plain struct used for commonly used, hard-coded paper sizes.
+ *
+ * Used to construct PaperInfo at runtime by localizing the name.
+ */
+struct CommonPaperSize final {
+ // The standardized PWG name, which should be used as the PaperInfo id
+ nsLiteralString mPWGName;
+ // The name key to localize the name of this paper size using strings in
+ // printUI.ftl
+ nsLiteralCString mLocalizableNameKey;
+ // Size is in points, same as PaperInfo
+ gfx::SizeDouble mSize;
+};
+
+} // namespace mozilla
+
+class nsPrinterBase;
+
+class nsPaper final : public nsIPaper {
+ using Promise = mozilla::dom::Promise;
+ using CommonPaperSize = mozilla::CommonPaperSize;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsPaper)
+ NS_DECL_NSIPAPER
+
+ nsPaper() = delete;
+ explicit nsPaper(const mozilla::PaperInfo&);
+ nsPaper(nsPrinterBase&, const mozilla::PaperInfo&);
+
+ // This list is used for both our fallback paper sizes (used when a printer
+ // does not provide a list of available paper sizes), and for localizing
+ // common paper sizes to avoid querying the printer for extra information in
+ // the common case, as well as to provide more uniform paper names in the
+ // frontend.
+ // If we need to separate these two uses, we will need to either split this
+ // into two lists, or add a flag to indicate what the size is used for.
+#define mm *72.0 / 25.4
+#define in *72.0
+ static constexpr CommonPaperSize kCommonPaperSizes[] = {
+ CommonPaperSize{u"iso_a5"_ns, "a5"_ns, {148 mm, 210 mm}},
+ CommonPaperSize{u"iso_a4"_ns, "a4"_ns, {210 mm, 297 mm}},
+ CommonPaperSize{u"iso_a3"_ns, "a3"_ns, {297 mm, 420 mm}},
+ CommonPaperSize{u"iso_b5"_ns, "b5"_ns, {176 mm, 250 mm}},
+ CommonPaperSize{u"iso_b4"_ns, "b4"_ns, {250 mm, 353 mm}},
+ CommonPaperSize{u"jis_b5"_ns, "jis-b5"_ns, {182 mm, 257 mm}},
+ CommonPaperSize{u"jis_b4"_ns, "jis-b4"_ns, {257 mm, 364 mm}},
+ CommonPaperSize{u"na_letter"_ns, "letter"_ns, {8.5 in, 11 in}},
+ CommonPaperSize{u"na_legal"_ns, "legal"_ns, {8.5 in, 14 in}},
+ CommonPaperSize{u"na_ledger"_ns, "tabloid"_ns, {11 in, 17 in}}};
+#undef mm
+#undef in
+ static constexpr size_t kNumCommonPaperSizes =
+ mozilla::ArrayLength(kCommonPaperSizes);
+
+ private:
+ ~nsPaper();
+
+ // null if not associated with a printer (for "Save-to-PDF" paper sizes)
+ RefPtr<nsPrinterBase> mPrinter;
+
+ RefPtr<Promise> mMarginPromise;
+ const mozilla::PaperInfo mInfo;
+};
+
+namespace mozilla {
+
+// Used to allow fixed-sized arrays of PWG paper info to be ref-counted.
+class CommonPaperInfoArray
+ : public Array<PaperInfo, nsPaper::kNumCommonPaperSizes> {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CommonPaperInfoArray);
+ CommonPaperInfoArray() = default;
+
+ private:
+ ~CommonPaperInfoArray() = default;
+};
+
+} // namespace mozilla
+
+#endif /* nsPaper_h__ */
diff --git a/widget/nsPaperMargin.cpp b/widget/nsPaperMargin.cpp
new file mode 100644
index 0000000000..1e6171cd16
--- /dev/null
+++ b/widget/nsPaperMargin.cpp
@@ -0,0 +1,32 @@
+/* -*- 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 "nsPaperMargin.h"
+
+NS_IMPL_ISUPPORTS(nsPaperMargin, nsIPaperMargin)
+
+NS_IMETHODIMP
+nsPaperMargin::GetTop(double* aTop) {
+ *aTop = mMargin.top;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPaperMargin::GetRight(double* aRight) {
+ *aRight = mMargin.right;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPaperMargin::GetBottom(double* aBottom) {
+ *aBottom = mMargin.bottom;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPaperMargin::GetLeft(double* aLeft) {
+ *aLeft = mMargin.left;
+ return NS_OK;
+}
diff --git a/widget/nsPaperMargin.h b/widget/nsPaperMargin.h
new file mode 100644
index 0000000000..594b266c00
--- /dev/null
+++ b/widget/nsPaperMargin.h
@@ -0,0 +1,29 @@
+/* -*- 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 nsPaperMargin_h_
+#define nsPaperMargin_h_
+
+#include "nsISupportsImpl.h"
+#include "nsIPaperMargin.h"
+#include "mozilla/gfx/Rect.h"
+
+class nsPaperMargin final : public nsIPaperMargin {
+ using MarginDouble = mozilla::gfx::MarginDouble;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPAPERMARGIN
+
+ nsPaperMargin() = delete;
+
+ explicit nsPaperMargin(const MarginDouble& aMargin) : mMargin(aMargin) {}
+
+ private:
+ ~nsPaperMargin() = default;
+ const MarginDouble mMargin;
+};
+
+#endif
diff --git a/widget/nsPrimitiveHelpers.cpp b/widget/nsPrimitiveHelpers.cpp
new file mode 100644
index 0000000000..4bcbc4a408
--- /dev/null
+++ b/widget/nsPrimitiveHelpers.cpp
@@ -0,0 +1,197 @@
+/* -*- 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/. */
+
+//
+// Part of the reason these routines are all in once place is so that as new
+// data flavors are added that are known to be one-byte or two-byte strings, or
+// even raw binary data, then we just have to go to one place to change how the
+// data moves into/out of the primitives and native line endings.
+//
+// If you add new flavors that have special consideration (binary data or
+// one-byte char* strings), please update all the helper classes in this file.
+//
+// For now, this is the assumption that we are making:
+// - text/plain is always a char*
+// - anything else is a char16_t*
+//
+
+#include "nsPrimitiveHelpers.h"
+
+#include "mozilla/UniquePtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITransferable.h"
+#include "nsLinebreakConverter.h"
+#include "nsReadableUtils.h"
+
+//
+// CreatePrimitiveForData
+//
+// Given some data and the flavor it corresponds to, creates the appropriate
+// nsISupports* wrapper for passing across IDL boundaries. Right now, everything
+// creates a two-byte |nsISupportsString|, except for "text/plain" and native
+// platform HTML (CF_HTML on win32)
+//
+void nsPrimitiveHelpers ::CreatePrimitiveForData(const nsACString& aFlavor,
+ const void* aDataBuff,
+ uint32_t aDataLen,
+ nsISupports** aPrimitive) {
+ if (!aPrimitive) return;
+
+ if (aFlavor.EqualsLiteral(kTextMime) ||
+ aFlavor.EqualsLiteral(kNativeHTMLMime) ||
+ aFlavor.EqualsLiteral(kRTFMime) ||
+ aFlavor.EqualsLiteral(kCustomTypesMime)) {
+ nsCOMPtr<nsISupportsCString> primitive =
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
+ if (primitive) {
+ const char* start = reinterpret_cast<const char*>(aDataBuff);
+ primitive->SetData(Substring(start, start + aDataLen));
+ NS_ADDREF(*aPrimitive = primitive);
+ }
+ } else {
+ nsCOMPtr<nsISupportsString> primitive =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
+ if (primitive) {
+ if (aDataLen % 2) {
+ auto buffer = mozilla::MakeUnique<char[]>(aDataLen + 1);
+ if (!MOZ_LIKELY(buffer)) return;
+
+ memcpy(buffer.get(), aDataBuff, aDataLen);
+ buffer[aDataLen] = 0;
+ const char16_t* start = reinterpret_cast<const char16_t*>(buffer.get());
+ // recall that length takes length as characters, not bytes
+ primitive->SetData(Substring(start, start + (aDataLen + 1) / 2));
+ } else {
+ const char16_t* start = reinterpret_cast<const char16_t*>(aDataBuff);
+ // recall that length takes length as characters, not bytes
+ primitive->SetData(Substring(start, start + (aDataLen / 2)));
+ }
+ NS_ADDREF(*aPrimitive = primitive);
+ }
+ }
+
+} // CreatePrimitiveForData
+
+//
+// CreatePrimitiveForCFHTML
+//
+// Platform specific CreatePrimitive, windows CF_HTML.
+//
+void nsPrimitiveHelpers ::CreatePrimitiveForCFHTML(const void* aDataBuff,
+ uint32_t* aDataLen,
+ nsISupports** aPrimitive) {
+ if (!aPrimitive) return;
+
+ nsCOMPtr<nsISupportsString> primitive =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
+ if (!primitive) return;
+
+ // We need to duplicate the input buffer, since the removal of linebreaks
+ // might reallocte it.
+ void* utf8 = moz_xmalloc(*aDataLen);
+ memcpy(utf8, aDataBuff, *aDataLen);
+ int32_t signedLen = static_cast<int32_t>(*aDataLen);
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(
+ nsDependentCString(kTextMime), &utf8, &signedLen);
+ *aDataLen = signedLen;
+
+ nsAutoString str(
+ NS_ConvertUTF8toUTF16(reinterpret_cast<const char*>(utf8), *aDataLen));
+ free(utf8);
+ *aDataLen = str.Length() * sizeof(char16_t);
+ primitive->SetData(str);
+ NS_ADDREF(*aPrimitive = primitive);
+}
+
+//
+// CreateDataFromPrimitive
+//
+// Given a nsISupports* primitive and the flavor it represents, creates a new
+// data buffer with the data in it. This data will be null terminated, but the
+// length parameter does not reflect that.
+//
+void nsPrimitiveHelpers::CreateDataFromPrimitive(const nsACString& aFlavor,
+ nsISupports* aPrimitive,
+ void** aDataBuff,
+ uint32_t* aDataLen) {
+ if (!aDataBuff) return;
+
+ *aDataBuff = nullptr;
+ *aDataLen = 0;
+
+ if (aFlavor.EqualsLiteral(kTextMime) ||
+ aFlavor.EqualsLiteral(kCustomTypesMime)) {
+ nsCOMPtr<nsISupportsCString> plainText(do_QueryInterface(aPrimitive));
+ if (plainText) {
+ nsAutoCString data;
+ plainText->GetData(data);
+ *aDataBuff = ToNewCString(data);
+ *aDataLen = data.Length() * sizeof(char);
+ }
+ } else {
+ nsCOMPtr<nsISupportsString> doubleByteText(do_QueryInterface(aPrimitive));
+ if (doubleByteText) {
+ nsAutoString data;
+ doubleByteText->GetData(data);
+ *aDataBuff = ToNewUnicode(data);
+ *aDataLen = data.Length() * sizeof(char16_t);
+ }
+ }
+}
+
+//
+// ConvertPlatformToDOMLinebreaks
+//
+// Given some data, convert from the platform linebreaks into the LF expected by
+// the DOM. This will attempt to convert the data in place, but the buffer may
+// still need to be reallocated regardless (disposing the old buffer is taken
+// care of internally, see the note below).
+//
+// NOTE: this assumes that it can use 'free' to dispose of the old buffer.
+//
+nsresult nsLinebreakHelpers ::ConvertPlatformToDOMLinebreaks(
+ const nsACString& inFlavor, void** ioData, int32_t* ioLengthInBytes) {
+ NS_ASSERTION(ioData && *ioData && ioLengthInBytes, "Bad Params");
+ if (!(ioData && *ioData && ioLengthInBytes)) return NS_ERROR_INVALID_ARG;
+
+ nsresult retVal = NS_OK;
+
+ if (inFlavor.EqualsLiteral(kTextMime) || inFlavor.EqualsLiteral(kRTFMime)) {
+ char* buffAsChars = reinterpret_cast<char*>(*ioData);
+ char* oldBuffer = buffAsChars;
+ retVal = nsLinebreakConverter::ConvertLineBreaksInSitu(
+ &buffAsChars, nsLinebreakConverter::eLinebreakAny,
+ nsLinebreakConverter::eLinebreakContent, *ioLengthInBytes,
+ ioLengthInBytes);
+ if (NS_SUCCEEDED(retVal)) {
+ if (buffAsChars != oldBuffer) // check if buffer was reallocated
+ free(oldBuffer);
+ *ioData = buffAsChars;
+ }
+ } else if (inFlavor.EqualsLiteral("image/jpeg")) {
+ // I'd assume we don't want to do anything for binary data....
+ } else {
+ char16_t* buffAsUnichar = reinterpret_cast<char16_t*>(*ioData);
+ char16_t* oldBuffer = buffAsUnichar;
+ int32_t newLengthInChars;
+ retVal = nsLinebreakConverter::ConvertUnicharLineBreaksInSitu(
+ &buffAsUnichar, nsLinebreakConverter::eLinebreakAny,
+ nsLinebreakConverter::eLinebreakContent,
+ *ioLengthInBytes / sizeof(char16_t), &newLengthInChars);
+ if (NS_SUCCEEDED(retVal)) {
+ if (buffAsUnichar != oldBuffer) // check if buffer was reallocated
+ free(oldBuffer);
+ *ioData = buffAsUnichar;
+ *ioLengthInBytes = newLengthInChars * sizeof(char16_t);
+ }
+ }
+
+ return retVal;
+
+} // ConvertPlatformToDOMLinebreaks
diff --git a/widget/nsPrimitiveHelpers.h b/widget/nsPrimitiveHelpers.h
new file mode 100644
index 0000000000..33e2163b08
--- /dev/null
+++ b/widget/nsPrimitiveHelpers.h
@@ -0,0 +1,54 @@
+/* -*- 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 nsPrimitiveHelpers_h___
+#define nsPrimitiveHelpers_h___
+
+#include "nsError.h"
+#include "nscore.h"
+#include "nsString.h"
+
+class nsISupports;
+
+class nsPrimitiveHelpers {
+ public:
+ // Given some data and the flavor it corresponds to, creates the appropriate
+ // nsISupports* wrapper for passing across IDL boundaries. The length
+ // parameter should not include the null if the data is null terminated.
+ static void CreatePrimitiveForData(const nsACString& aFlavor,
+ const void* aDataBuff, uint32_t aDataLen,
+ nsISupports** aPrimitive);
+
+ // A specific case of CreatePrimitive for windows CF_HTML handling in
+ // DataTransfer
+ static void CreatePrimitiveForCFHTML(const void* aDataBuff,
+ uint32_t* aDataLen,
+ nsISupports** aPrimitive);
+
+ // Given a nsISupports* primitive and the flavor it represents, creates a new
+ // data buffer with the data in it. This data will be null terminated, but the
+ // length parameter does not reflect that.
+ static void CreateDataFromPrimitive(const nsACString& aFlavor,
+ nsISupports* aPrimitive, void** aDataBuff,
+ uint32_t* aDataLen);
+
+}; // class nsPrimitiveHelpers
+
+class nsLinebreakHelpers {
+ public:
+ // Given some data, convert from the platform linebreaks into the LF expected
+ // by the DOM. This will attempt to convert the data in place, but the buffer
+ // may still need to be reallocated regardless (disposing the old buffer is
+ // taken care of internally, see the note below).
+ //
+ // NOTE: this assumes that it can use 'free' to dispose of the old buffer.
+ static nsresult ConvertPlatformToDOMLinebreaks(const nsACString& inFlavor,
+ void** ioData,
+ int32_t* ioLengthInBytes);
+
+}; // class nsLinebreakHelpers
+
+#endif // nsPrimitiveHelpers_h___
diff --git a/widget/nsPrintSession.cpp b/widget/nsPrintSession.cpp
new file mode 100644
index 0000000000..522230e819
--- /dev/null
+++ b/widget/nsPrintSession.cpp
@@ -0,0 +1,33 @@
+/* -*- 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 "nsPrintSession.h"
+
+#include "mozilla/layout/RemotePrintJobChild.h"
+
+typedef mozilla::layout::RemotePrintJobChild RemotePrintJobChild;
+
+//*****************************************************************************
+//*** nsPrintSession
+//*****************************************************************************
+
+NS_IMPL_ISUPPORTS(nsPrintSession, nsIPrintSession, nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+nsPrintSession::nsPrintSession() = default;
+
+//-----------------------------------------------------------------------------
+nsPrintSession::~nsPrintSession() = default;
+
+//-----------------------------------------------------------------------------
+nsresult nsPrintSession::Init() { return NS_OK; }
+
+RemotePrintJobChild* nsPrintSession::GetRemotePrintJob() {
+ return mRemotePrintJob;
+}
+
+void nsPrintSession::SetRemotePrintJob(RemotePrintJobChild* aRemotePrintJob) {
+ mRemotePrintJob = aRemotePrintJob;
+}
diff --git a/widget/nsPrintSession.h b/widget/nsPrintSession.h
new file mode 100644
index 0000000000..35fe11973a
--- /dev/null
+++ b/widget/nsPrintSession.h
@@ -0,0 +1,39 @@
+/* -*- 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 nsPrintSession_h__
+#define nsPrintSession_h__
+
+#include "nsIPrintSession.h"
+
+#include "mozilla/RefPtr.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace layout {
+class RemotePrintJobChild;
+}
+} // namespace mozilla
+
+//*****************************************************************************
+//*** nsPrintSession
+//*****************************************************************************
+
+class nsPrintSession : public nsIPrintSession, public nsSupportsWeakReference {
+ virtual ~nsPrintSession();
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTSESSION
+
+ nsPrintSession();
+
+ virtual nsresult Init();
+
+ private:
+ RefPtr<mozilla::layout::RemotePrintJobChild> mRemotePrintJob;
+};
+
+#endif // nsPrintSession_h__
diff --git a/widget/nsPrintSettingsImpl.cpp b/widget/nsPrintSettingsImpl.cpp
new file mode 100644
index 0000000000..02add9448e
--- /dev/null
+++ b/widget/nsPrintSettingsImpl.cpp
@@ -0,0 +1,854 @@
+/* -*- 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 "nsPrintSettingsImpl.h"
+
+#include "prenv.h"
+#include "nsCoord.h"
+#include "nsPaper.h"
+#include "nsReadableUtils.h"
+#include "nsIPrintSession.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/RefPtr.h"
+
+#define DEFAULT_MARGIN_WIDTH 0.5
+
+NS_IMPL_ISUPPORTS(nsPrintSettings, nsIPrintSettings)
+
+nsPrintSettings::nsPrintSettings()
+ : mScaling(1.0),
+ mPrintBGColors(false),
+ mPrintBGImages(false),
+ mIsCancelled(false),
+ mSaveOnCancel(true),
+ mPrintSilent(false),
+ mShrinkToFit(true),
+ mShowPrintProgress(true),
+ mShowMarginGuides(false),
+ mHonorPageRuleMargins(true),
+ mIsPrintSelectionRBEnabled(false),
+ mPrintSelectionOnly(false),
+ mPrintPageDelay(50),
+ mPaperWidth(8.5),
+ mPaperHeight(11.0),
+ mPaperSizeUnit(kPaperSizeInches),
+ mPrintReversed(false),
+ mPrintInColor(true),
+ mOrientation(kPortraitOrientation),
+ mResolution(0),
+ mDuplex(0),
+ mNumCopies(1),
+ mNumPagesPerSheet(1),
+ mPrintToFile(false),
+ mOutputFormat(kOutputFormatNative),
+ mIsInitedFromPrinter(false),
+ mIsInitedFromPrefs(false) {
+ /* member initializers and constructor code */
+ int32_t marginWidth = NS_INCHES_TO_INT_TWIPS(DEFAULT_MARGIN_WIDTH);
+ mMargin.SizeTo(marginWidth, marginWidth, marginWidth, marginWidth);
+ mEdge.SizeTo(0, 0, 0, 0);
+ mUnwriteableMargin.SizeTo(0, 0, 0, 0);
+
+ mHeaderStrs[0].AssignLiteral("&T");
+ mHeaderStrs[2].AssignLiteral("&U");
+
+ mFooterStrs[0].AssignLiteral(
+ "&PT"); // Use &P (Page Num Only) or &PT (Page Num of Page Total)
+ mFooterStrs[2].AssignLiteral("&D");
+}
+
+void nsPrintSettings::InitWithInitializer(
+ const PrintSettingsInitializer& aSettings) {
+ const double kInchesPerPoint = 1.0 / 72.0;
+
+ SetPrinterName(aSettings.mPrinter);
+ SetPrintInColor(aSettings.mPrintInColor);
+ SetResolution(aSettings.mResolution);
+ // The paper ID used by nsPrintSettings is the non-localizable identifier
+ // exposed as "id" by the paper, not the potentially localized human-friendly
+ // "name", which could change, e.g. if the user changes their system locale.
+ SetPaperId(aSettings.mPaperInfo.mId);
+ SetPaperWidth(aSettings.mPaperInfo.mSize.Width() * kInchesPerPoint);
+ SetPaperHeight(aSettings.mPaperInfo.mSize.Height() * kInchesPerPoint);
+ SetPaperSizeUnit(nsIPrintSettings::kPaperSizeInches);
+
+ if (aSettings.mPaperInfo.mUnwriteableMargin) {
+ const auto& margin = aSettings.mPaperInfo.mUnwriteableMargin.value();
+ // Margins are stored internally in TWIPS, but the setters expect inches.
+ SetUnwriteableMarginTop(margin.top * kInchesPerPoint);
+ SetUnwriteableMarginRight(margin.right * kInchesPerPoint);
+ SetUnwriteableMarginBottom(margin.bottom * kInchesPerPoint);
+ SetUnwriteableMarginLeft(margin.left * kInchesPerPoint);
+ }
+
+ // Set this last because other setters may overwrite its value.
+ SetIsInitializedFromPrinter(true);
+}
+
+nsPrintSettings::nsPrintSettings(const nsPrintSettings& aPS) { *this = aPS; }
+
+nsPrintSettings::~nsPrintSettings() = default;
+
+NS_IMETHODIMP nsPrintSettings::GetPrintSession(
+ nsIPrintSession** aPrintSession) {
+ NS_ENSURE_ARG_POINTER(aPrintSession);
+ *aPrintSession = nullptr;
+
+ nsCOMPtr<nsIPrintSession> session = do_QueryReferent(mSession);
+ if (!session) return NS_ERROR_NOT_INITIALIZED;
+ *aPrintSession = session;
+ NS_ADDREF(*aPrintSession);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::SetPrintSession(nsIPrintSession* aPrintSession) {
+ // Clearing it by passing nullptr is not allowed. That's why we
+ // use a weak ref so that it doesn't have to be cleared.
+ NS_ENSURE_ARG(aPrintSession);
+
+ mSession = do_GetWeakReference(aPrintSession);
+ if (!mSession) {
+ // This may happen if the implementation of this object does
+ // not support weak references - programmer error.
+ NS_ERROR("Could not get a weak reference from aPrintSession");
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPrintReversed(bool* aPrintReversed) {
+ *aPrintReversed = mPrintReversed;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPrintReversed(bool aPrintReversed) {
+ mPrintReversed = aPrintReversed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPrintInColor(bool* aPrintInColor) {
+ *aPrintInColor = mPrintInColor;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPrintInColor(bool aPrintInColor) {
+ mPrintInColor = aPrintInColor;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetOrientation(int32_t* aOrientation) {
+ *aOrientation = mOrientation;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetOrientation(int32_t aOrientation) {
+ mOrientation = aOrientation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetResolution(int32_t* aResolution) {
+ NS_ENSURE_ARG_POINTER(aResolution);
+ *aResolution = mResolution;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetResolution(const int32_t aResolution) {
+ mResolution = aResolution;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetDuplex(int32_t* aDuplex) {
+ NS_ENSURE_ARG_POINTER(aDuplex);
+ *aDuplex = mDuplex;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetDuplex(const int32_t aDuplex) {
+ mDuplex = aDuplex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPrinterName(nsAString& aPrinter) {
+ aPrinter = mPrinter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::SetPrinterName(const nsAString& aPrinter) {
+ if (!mPrinter.Equals(aPrinter)) {
+ mIsInitedFromPrinter = false;
+ mIsInitedFromPrefs = false;
+ }
+
+ mPrinter.Assign(aPrinter);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetNumCopies(int32_t* aNumCopies) {
+ NS_ENSURE_ARG_POINTER(aNumCopies);
+ *aNumCopies = mNumCopies;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetNumCopies(int32_t aNumCopies) {
+ mNumCopies = aNumCopies;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetNumPagesPerSheet(int32_t* aNumPagesPerSheet) {
+ NS_ENSURE_ARG_POINTER(aNumPagesPerSheet);
+ *aNumPagesPerSheet = mNumPagesPerSheet;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetNumPagesPerSheet(int32_t aNumPagesPerSheet) {
+ mNumPagesPerSheet = aNumPagesPerSheet;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPrintToFile(bool* aPrintToFile) {
+ NS_ENSURE_ARG_POINTER(aPrintToFile);
+ *aPrintToFile = mPrintToFile;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPrintToFile(bool aPrintToFile) {
+ mPrintToFile = aPrintToFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetToFileName(nsAString& aToFileName) {
+ aToFileName = mToFileName;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetToFileName(const nsAString& aToFileName) {
+ mToFileName = aToFileName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetOutputFormat(int16_t* aOutputFormat) {
+ NS_ENSURE_ARG_POINTER(aOutputFormat);
+ *aOutputFormat = mOutputFormat;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetOutputFormat(int16_t aOutputFormat) {
+ mOutputFormat = aOutputFormat;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPrintPageDelay(int32_t* aPrintPageDelay) {
+ *aPrintPageDelay = mPrintPageDelay;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPrintPageDelay(int32_t aPrintPageDelay) {
+ mPrintPageDelay = aPrintPageDelay;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetIsInitializedFromPrinter(
+ bool* aIsInitializedFromPrinter) {
+ NS_ENSURE_ARG_POINTER(aIsInitializedFromPrinter);
+ *aIsInitializedFromPrinter = mIsInitedFromPrinter;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetIsInitializedFromPrinter(
+ bool aIsInitializedFromPrinter) {
+ mIsInitedFromPrinter = aIsInitializedFromPrinter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetIsInitializedFromPrefs(
+ bool* aInitializedFromPrefs) {
+ NS_ENSURE_ARG_POINTER(aInitializedFromPrefs);
+ *aInitializedFromPrefs = mIsInitedFromPrefs;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetIsInitializedFromPrefs(
+ bool aInitializedFromPrefs) {
+ mIsInitedFromPrefs = aInitializedFromPrefs;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetMarginTop(double* aMarginTop) {
+ NS_ENSURE_ARG_POINTER(aMarginTop);
+ *aMarginTop = NS_TWIPS_TO_INCHES(mMargin.top);
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetMarginTop(double aMarginTop) {
+ mMargin.top = NS_INCHES_TO_INT_TWIPS(float(aMarginTop));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetMarginLeft(double* aMarginLeft) {
+ NS_ENSURE_ARG_POINTER(aMarginLeft);
+ *aMarginLeft = NS_TWIPS_TO_INCHES(mMargin.left);
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetMarginLeft(double aMarginLeft) {
+ mMargin.left = NS_INCHES_TO_INT_TWIPS(float(aMarginLeft));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetMarginBottom(double* aMarginBottom) {
+ NS_ENSURE_ARG_POINTER(aMarginBottom);
+ *aMarginBottom = NS_TWIPS_TO_INCHES(mMargin.bottom);
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetMarginBottom(double aMarginBottom) {
+ mMargin.bottom = NS_INCHES_TO_INT_TWIPS(float(aMarginBottom));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetMarginRight(double* aMarginRight) {
+ NS_ENSURE_ARG_POINTER(aMarginRight);
+ *aMarginRight = NS_TWIPS_TO_INCHES(mMargin.right);
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetMarginRight(double aMarginRight) {
+ mMargin.right = NS_INCHES_TO_INT_TWIPS(float(aMarginRight));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetEdgeTop(double* aEdgeTop) {
+ NS_ENSURE_ARG_POINTER(aEdgeTop);
+ *aEdgeTop = NS_TWIPS_TO_INCHES(mEdge.top);
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetEdgeTop(double aEdgeTop) {
+ mEdge.top = NS_INCHES_TO_INT_TWIPS(float(aEdgeTop));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetEdgeLeft(double* aEdgeLeft) {
+ NS_ENSURE_ARG_POINTER(aEdgeLeft);
+ *aEdgeLeft = NS_TWIPS_TO_INCHES(mEdge.left);
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetEdgeLeft(double aEdgeLeft) {
+ mEdge.left = NS_INCHES_TO_INT_TWIPS(float(aEdgeLeft));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetEdgeBottom(double* aEdgeBottom) {
+ NS_ENSURE_ARG_POINTER(aEdgeBottom);
+ *aEdgeBottom = NS_TWIPS_TO_INCHES(mEdge.bottom);
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetEdgeBottom(double aEdgeBottom) {
+ mEdge.bottom = NS_INCHES_TO_INT_TWIPS(float(aEdgeBottom));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetEdgeRight(double* aEdgeRight) {
+ NS_ENSURE_ARG_POINTER(aEdgeRight);
+ *aEdgeRight = NS_TWIPS_TO_INCHES(mEdge.right);
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetEdgeRight(double aEdgeRight) {
+ mEdge.right = NS_INCHES_TO_INT_TWIPS(float(aEdgeRight));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetUnwriteableMarginTop(
+ double* aUnwriteableMarginTop) {
+ NS_ENSURE_ARG_POINTER(aUnwriteableMarginTop);
+ *aUnwriteableMarginTop = NS_TWIPS_TO_INCHES(mUnwriteableMargin.top);
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetUnwriteableMarginTop(
+ double aUnwriteableMarginTop) {
+ if (aUnwriteableMarginTop >= 0.0) {
+ mUnwriteableMargin.top = NS_INCHES_TO_INT_TWIPS(aUnwriteableMarginTop);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetUnwriteableMarginLeft(
+ double* aUnwriteableMarginLeft) {
+ NS_ENSURE_ARG_POINTER(aUnwriteableMarginLeft);
+ *aUnwriteableMarginLeft = NS_TWIPS_TO_INCHES(mUnwriteableMargin.left);
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetUnwriteableMarginLeft(
+ double aUnwriteableMarginLeft) {
+ if (aUnwriteableMarginLeft >= 0.0) {
+ mUnwriteableMargin.left = NS_INCHES_TO_INT_TWIPS(aUnwriteableMarginLeft);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetUnwriteableMarginBottom(
+ double* aUnwriteableMarginBottom) {
+ NS_ENSURE_ARG_POINTER(aUnwriteableMarginBottom);
+ *aUnwriteableMarginBottom = NS_TWIPS_TO_INCHES(mUnwriteableMargin.bottom);
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetUnwriteableMarginBottom(
+ double aUnwriteableMarginBottom) {
+ if (aUnwriteableMarginBottom >= 0.0) {
+ mUnwriteableMargin.bottom =
+ NS_INCHES_TO_INT_TWIPS(aUnwriteableMarginBottom);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetUnwriteableMarginRight(
+ double* aUnwriteableMarginRight) {
+ NS_ENSURE_ARG_POINTER(aUnwriteableMarginRight);
+ *aUnwriteableMarginRight = NS_TWIPS_TO_INCHES(mUnwriteableMargin.right);
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetUnwriteableMarginRight(
+ double aUnwriteableMarginRight) {
+ if (aUnwriteableMarginRight >= 0.0) {
+ mUnwriteableMargin.right = NS_INCHES_TO_INT_TWIPS(aUnwriteableMarginRight);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetScaling(double* aScaling) {
+ NS_ENSURE_ARG_POINTER(aScaling);
+ *aScaling = mScaling;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::SetScaling(double aScaling) {
+ mScaling = aScaling;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPrintBGColors(bool* aPrintBGColors) {
+ NS_ENSURE_ARG_POINTER(aPrintBGColors);
+ *aPrintBGColors = mPrintBGColors;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPrintBGColors(bool aPrintBGColors) {
+ mPrintBGColors = aPrintBGColors;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPrintBGImages(bool* aPrintBGImages) {
+ NS_ENSURE_ARG_POINTER(aPrintBGImages);
+ *aPrintBGImages = mPrintBGImages;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPrintBGImages(bool aPrintBGImages) {
+ mPrintBGImages = aPrintBGImages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetTitle(nsAString& aTitle) {
+ aTitle = mTitle;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetTitle(const nsAString& aTitle) {
+ mTitle = aTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetDocURL(nsAString& aDocURL) {
+ aDocURL = mURL;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetDocURL(const nsAString& aDocURL) {
+ mURL = aDocURL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetHeaderStrLeft(nsAString& aTitle) {
+ aTitle = mHeaderStrs[0];
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetHeaderStrLeft(const nsAString& aTitle) {
+ mHeaderStrs[0] = aTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetHeaderStrCenter(nsAString& aTitle) {
+ aTitle = mHeaderStrs[1];
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetHeaderStrCenter(const nsAString& aTitle) {
+ mHeaderStrs[1] = aTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetHeaderStrRight(nsAString& aTitle) {
+ aTitle = mHeaderStrs[2];
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetHeaderStrRight(const nsAString& aTitle) {
+ mHeaderStrs[2] = aTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetFooterStrLeft(nsAString& aTitle) {
+ aTitle = mFooterStrs[0];
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetFooterStrLeft(const nsAString& aTitle) {
+ mFooterStrs[0] = aTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetFooterStrCenter(nsAString& aTitle) {
+ aTitle = mFooterStrs[1];
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetFooterStrCenter(const nsAString& aTitle) {
+ mFooterStrs[1] = aTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetFooterStrRight(nsAString& aTitle) {
+ aTitle = mFooterStrs[2];
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetFooterStrRight(const nsAString& aTitle) {
+ mFooterStrs[2] = aTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPrintSilent(bool* aPrintSilent) {
+ NS_ENSURE_ARG_POINTER(aPrintSilent);
+ *aPrintSilent = mPrintSilent;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPrintSilent(bool aPrintSilent) {
+ mPrintSilent = aPrintSilent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetShrinkToFit(bool* aShrinkToFit) {
+ NS_ENSURE_ARG_POINTER(aShrinkToFit);
+ *aShrinkToFit = mShrinkToFit;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetShrinkToFit(bool aShrinkToFit) {
+ mShrinkToFit = aShrinkToFit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetShowPrintProgress(bool* aShowPrintProgress) {
+ NS_ENSURE_ARG_POINTER(aShowPrintProgress);
+ *aShowPrintProgress = mShowPrintProgress;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetShowPrintProgress(bool aShowPrintProgress) {
+ mShowPrintProgress = aShowPrintProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetShowMarginGuides(bool* aShowMarginGuides) {
+ *aShowMarginGuides = mShowMarginGuides;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::SetShowMarginGuides(bool aShowMarginGuides) {
+ mShowMarginGuides = aShowMarginGuides;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetHonorPageRuleMargins(bool* aResult) {
+ *aResult = mHonorPageRuleMargins;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::SetHonorPageRuleMargins(bool aHonor) {
+ mHonorPageRuleMargins = aHonor;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetIsPrintSelectionRBEnabled(
+ bool* aIsPrintSelectionRBEnabled) {
+ *aIsPrintSelectionRBEnabled = mIsPrintSelectionRBEnabled;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetIsPrintSelectionRBEnabled(
+ bool aIsPrintSelectionRBEnabled) {
+ mIsPrintSelectionRBEnabled = aIsPrintSelectionRBEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPrintSelectionOnly(bool* aResult) {
+ *aResult = mPrintSelectionOnly;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::SetPrintSelectionOnly(bool aSelectionOnly) {
+ mPrintSelectionOnly = aSelectionOnly;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPaperId(nsAString& aPaperId) {
+ aPaperId = mPaperId;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPaperId(const nsAString& aPaperId) {
+ mPaperId = aPaperId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetIsCancelled(bool* aIsCancelled) {
+ NS_ENSURE_ARG_POINTER(aIsCancelled);
+ *aIsCancelled = mIsCancelled;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetIsCancelled(bool aIsCancelled) {
+ mIsCancelled = aIsCancelled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetSaveOnCancel(bool* aSaveOnCancel) {
+ *aSaveOnCancel = mSaveOnCancel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPaperWidth(double* aPaperWidth) {
+ NS_ENSURE_ARG_POINTER(aPaperWidth);
+ *aPaperWidth = mPaperWidth;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPaperWidth(double aPaperWidth) {
+ mPaperWidth = aPaperWidth;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPaperHeight(double* aPaperHeight) {
+ NS_ENSURE_ARG_POINTER(aPaperHeight);
+ *aPaperHeight = mPaperHeight;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPaperHeight(double aPaperHeight) {
+ mPaperHeight = aPaperHeight;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPaperSizeUnit(int16_t* aPaperSizeUnit) {
+ NS_ENSURE_ARG_POINTER(aPaperSizeUnit);
+ *aPaperSizeUnit = mPaperSizeUnit;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPaperSizeUnit(int16_t aPaperSizeUnit) {
+ mPaperSizeUnit = aPaperSizeUnit;
+ return NS_OK;
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsService.h
+ * @update 6/21/00 dwc
+ * @update 1/12/01 rods
+ */
+NS_IMETHODIMP
+nsPrintSettings::SetMarginInTwips(nsIntMargin& aMargin) {
+ mMargin = aMargin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettings::SetEdgeInTwips(nsIntMargin& aEdge) {
+ mEdge = aEdge;
+ return NS_OK;
+}
+
+// NOTE: Any subclass implementation of this function should make sure
+// to check for negative margin values in aUnwriteableMargin (which
+// would indicate that we should use the system default unwriteable margin.)
+NS_IMETHODIMP
+nsPrintSettings::SetUnwriteableMarginInTwips(nsIntMargin& aUnwriteableMargin) {
+ if (aUnwriteableMargin.top >= 0) {
+ mUnwriteableMargin.top = aUnwriteableMargin.top;
+ }
+ if (aUnwriteableMargin.left >= 0) {
+ mUnwriteableMargin.left = aUnwriteableMargin.left;
+ }
+ if (aUnwriteableMargin.bottom >= 0) {
+ mUnwriteableMargin.bottom = aUnwriteableMargin.bottom;
+ }
+ if (aUnwriteableMargin.right >= 0) {
+ mUnwriteableMargin.right = aUnwriteableMargin.right;
+ }
+ return NS_OK;
+}
+
+nsIntMargin nsPrintSettings::GetMarginInTwips() { return mMargin; }
+
+nsIntMargin nsPrintSettings::GetEdgeInTwips() { return mEdge; }
+
+nsIntMargin nsPrintSettings::GetUnwriteableMarginInTwips() {
+ return mUnwriteableMargin;
+}
+
+/** ---------------------------------------------------
+ * Stub - platform-specific implementations can use this function.
+ */
+NS_IMETHODIMP
+nsPrintSettings::SetupSilentPrinting() { return NS_OK; }
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsService.h
+ */
+NS_IMETHODIMP
+nsPrintSettings::GetEffectivePageSize(double* aWidth, double* aHeight) {
+ if (mPaperSizeUnit == kPaperSizeInches) {
+ *aWidth = NS_INCHES_TO_TWIPS(float(mPaperWidth));
+ *aHeight = NS_INCHES_TO_TWIPS(float(mPaperHeight));
+ } else {
+ MOZ_ASSERT(mPaperSizeUnit == kPaperSizeMillimeters,
+ "unexpected paper size unit");
+ *aWidth = NS_MILLIMETERS_TO_TWIPS(float(mPaperWidth));
+ *aHeight = NS_MILLIMETERS_TO_TWIPS(float(mPaperHeight));
+ }
+ if (kLandscapeOrientation == mOrientation) {
+ double temp = *aWidth;
+ *aWidth = *aHeight;
+ *aHeight = temp;
+ }
+ return NS_OK;
+}
+
+bool nsPrintSettings::HasOrthogonalSheetsAndPages() {
+ return mNumPagesPerSheet == 2 || mNumPagesPerSheet == 6;
+}
+
+void nsPrintSettings::GetEffectiveSheetSize(double* aWidth, double* aHeight) {
+ mozilla::DebugOnly<nsresult> rv = GetEffectivePageSize(aWidth, aHeight);
+
+ // Our GetEffectivePageSize impls only return NS_OK, so this should hold:
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Uh oh, GetEffectivePageSize failed");
+
+ if (HasOrthogonalSheetsAndPages()) {
+ std::swap(*aWidth, *aHeight);
+ }
+}
+
+int32_t nsPrintSettings::GetSheetOrientation() {
+ if (HasOrthogonalSheetsAndPages()) {
+ // Sheet orientation is rotated with respect to the page orientation.
+ return kLandscapeOrientation == mOrientation ? kPortraitOrientation
+ : kLandscapeOrientation;
+ }
+
+ // Sheet orientation is the same as the page orientation.
+ return mOrientation;
+}
+
+NS_IMETHODIMP
+nsPrintSettings::SetPageRanges(const nsTArray<int32_t>& aPages) {
+ // Needs to be a set of (start, end) pairs.
+ if (aPages.Length() % 2 != 0) {
+ return NS_ERROR_FAILURE;
+ }
+ mPageRanges = aPages.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettings::GetPageRanges(nsTArray<int32_t>& aPages) {
+ aPages = mPageRanges.Clone();
+ return NS_OK;
+}
+
+bool nsIPrintSettings::IsPageSkipped(int32_t aPageNum,
+ const nsTArray<int32_t>& aRanges) {
+ MOZ_RELEASE_ASSERT(aRanges.Length() % 2 == 0);
+ if (aRanges.IsEmpty()) {
+ return false;
+ }
+ for (size_t i = 0; i < aRanges.Length(); i += 2) {
+ if (aRanges[i] <= aPageNum && aPageNum <= aRanges[i + 1]) {
+ // The page is included in this piece of the custom range,
+ // so it's not skipped.
+ return false;
+ }
+ }
+ return true;
+}
+
+nsresult nsPrintSettings::_Clone(nsIPrintSettings** _retval) {
+ RefPtr<nsPrintSettings> printSettings = new nsPrintSettings(*this);
+ printSettings.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettings::Clone(nsIPrintSettings** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ return _Clone(_retval);
+}
+
+nsresult nsPrintSettings::_Assign(nsIPrintSettings* aPS) {
+ nsPrintSettings* ps = static_cast<nsPrintSettings*>(aPS);
+ *this = *ps;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettings::Assign(nsIPrintSettings* aPS) {
+ NS_ENSURE_ARG(aPS);
+ return _Assign(aPS);
+}
+
+//-------------------------------------------
+nsPrintSettings& nsPrintSettings::operator=(const nsPrintSettings& rhs) {
+ if (this == &rhs) {
+ return *this;
+ }
+
+ mPageRanges = rhs.mPageRanges.Clone();
+ mMargin = rhs.mMargin;
+ mEdge = rhs.mEdge;
+ mUnwriteableMargin = rhs.mUnwriteableMargin;
+ mScaling = rhs.mScaling;
+ mPrintBGColors = rhs.mPrintBGColors;
+ mPrintBGImages = rhs.mPrintBGImages;
+ mTitle = rhs.mTitle;
+ mURL = rhs.mURL;
+ mIsCancelled = rhs.mIsCancelled;
+ mSaveOnCancel = rhs.mSaveOnCancel;
+ mPrintSilent = rhs.mPrintSilent;
+ mShrinkToFit = rhs.mShrinkToFit;
+ mShowPrintProgress = rhs.mShowPrintProgress;
+ mShowMarginGuides = rhs.mShowMarginGuides;
+ mHonorPageRuleMargins = rhs.mHonorPageRuleMargins;
+ mIsPrintSelectionRBEnabled = rhs.mIsPrintSelectionRBEnabled;
+ mPrintSelectionOnly = rhs.mPrintSelectionOnly;
+ mPaperId = rhs.mPaperId;
+ mPaperWidth = rhs.mPaperWidth;
+ mPaperHeight = rhs.mPaperHeight;
+ mPaperSizeUnit = rhs.mPaperSizeUnit;
+ mPrintReversed = rhs.mPrintReversed;
+ mPrintInColor = rhs.mPrintInColor;
+ mOrientation = rhs.mOrientation;
+ mResolution = rhs.mResolution;
+ mDuplex = rhs.mDuplex;
+ mNumCopies = rhs.mNumCopies;
+ mNumPagesPerSheet = rhs.mNumPagesPerSheet;
+ mPrinter = rhs.mPrinter;
+ mPrintToFile = rhs.mPrintToFile;
+ mToFileName = rhs.mToFileName;
+ mOutputFormat = rhs.mOutputFormat;
+ mIsInitedFromPrinter = rhs.mIsInitedFromPrinter;
+ mIsInitedFromPrefs = rhs.mIsInitedFromPrefs;
+ mPrintPageDelay = rhs.mPrintPageDelay;
+
+ for (int32_t i = 0; i < NUM_HEAD_FOOT; i++) {
+ mHeaderStrs[i] = rhs.mHeaderStrs[i];
+ mFooterStrs[i] = rhs.mFooterStrs[i];
+ }
+
+ return *this;
+}
+
+void nsPrintSettings::SetDefaultFileName() {
+ nsAutoString filename;
+ nsresult rv = GetToFileName(filename);
+ if (NS_FAILED(rv) || filename.IsEmpty()) {
+ const char* path = PR_GetEnv("PWD");
+ if (!path) {
+ path = PR_GetEnv("HOME");
+ }
+
+ if (path) {
+ CopyUTF8toUTF16(mozilla::MakeStringSpan(path), filename);
+ filename.AppendLiteral("/mozilla.pdf");
+ } else {
+ filename.AssignLiteral("mozilla.pdf");
+ }
+
+ SetToFileName(filename);
+ }
+}
diff --git a/widget/nsPrintSettingsImpl.h b/widget/nsPrintSettingsImpl.h
new file mode 100644
index 0000000000..e391bab5bc
--- /dev/null
+++ b/widget/nsPrintSettingsImpl.h
@@ -0,0 +1,123 @@
+/* -*- Mode: IDL; 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 nsPrintSettingsImpl_h__
+#define nsPrintSettingsImpl_h__
+
+#include "nsIPrintSettings.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsMargin.h"
+#include "nsPaper.h"
+#include "nsString.h"
+
+#define NUM_HEAD_FOOT 3
+
+//*****************************************************************************
+//*** nsPrintSettings
+//*****************************************************************************
+
+namespace mozilla {
+
+/**
+ * A struct that can be used off the main thread to collect printer-specific
+ * info that can be used to initialized a default nsIPrintSettings object.
+ */
+struct PrintSettingsInitializer {
+ nsString mPrinter;
+ PaperInfo mPaperInfo;
+ // If we fail to obtain printer capabilities, being given the option to print
+ // in color to your monochrome printer is a lot less annoying than not being
+ // given the option to print in color to your color printer.
+ bool mPrintInColor = true;
+ int mResolution = 0;
+#ifdef XP_WIN
+ CopyableTArray<uint8_t> mDevmodeWStorage;
+#endif
+};
+
+} // namespace mozilla
+
+class nsPrintSettings : public nsIPrintSettings {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTSETTINGS
+ using PrintSettingsInitializer = mozilla::PrintSettingsInitializer;
+
+ nsPrintSettings();
+ nsPrintSettings(const nsPrintSettings& aPS);
+
+ /**
+ * Initialize relevant members from the PrintSettingsInitializer.
+ * This is specifically not a constructor so that we can ensure that the
+ * relevant setters are dynamically dispatched to derived classes.
+ */
+ virtual void InitWithInitializer(const PrintSettingsInitializer& aSettings);
+
+ nsPrintSettings& operator=(const nsPrintSettings& rhs);
+
+ // Sets a default file name for the print settings.
+ void SetDefaultFileName();
+
+ protected:
+ virtual ~nsPrintSettings();
+
+ // May be implemented by the platform-specific derived class
+ virtual nsresult _Clone(nsIPrintSettings** _retval);
+ virtual nsresult _Assign(nsIPrintSettings* aPS);
+
+ typedef enum { eHeader, eFooter } nsHeaderFooterEnum;
+
+ // Members
+ nsWeakPtr mSession; // Should never be touched by Clone or Assign
+
+ // mMargin, mEdge, and mUnwriteableMargin are stored in twips
+ nsIntMargin mMargin;
+ nsIntMargin mEdge;
+ nsIntMargin mUnwriteableMargin;
+
+ nsTArray<int32_t> mPageRanges;
+
+ double mScaling;
+ bool mPrintBGColors; // print background colors
+ bool mPrintBGImages; // print background images
+
+ bool mIsCancelled;
+ bool mSaveOnCancel;
+ bool mPrintSilent;
+ bool mShrinkToFit;
+ bool mShowPrintProgress;
+ bool mShowMarginGuides;
+ bool mHonorPageRuleMargins;
+ bool mIsPrintSelectionRBEnabled;
+ bool mPrintSelectionOnly;
+ int32_t mPrintPageDelay;
+
+ nsString mTitle;
+ nsString mURL;
+ nsString mHeaderStrs[NUM_HEAD_FOOT];
+ nsString mFooterStrs[NUM_HEAD_FOOT];
+
+ nsString mPaperId;
+ double mPaperWidth;
+ double mPaperHeight;
+ int16_t mPaperSizeUnit;
+
+ bool mPrintReversed;
+ bool mPrintInColor; // a false means grayscale
+ int32_t mOrientation; // see orientation consts
+ int32_t mResolution;
+ int32_t mDuplex;
+ int32_t mNumCopies;
+ int32_t mNumPagesPerSheet;
+ nsString mPrinter;
+ bool mPrintToFile;
+ nsString mToFileName;
+ int16_t mOutputFormat;
+ bool mIsInitedFromPrinter;
+ bool mIsInitedFromPrefs;
+};
+
+#endif /* nsPrintSettings_h__ */
diff --git a/widget/nsPrintSettingsService.cpp b/widget/nsPrintSettingsService.cpp
new file mode 100644
index 0000000000..497577a5e0
--- /dev/null
+++ b/widget/nsPrintSettingsService.cpp
@@ -0,0 +1,1021 @@
+/* -*- 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 "nsPrintSettingsService.h"
+
+#include "mozilla/embedding/PPrinting.h"
+#include "mozilla/layout/RemotePrintJobChild.h"
+#include "mozilla/RefPtr.h"
+#include "nsCoord.h"
+#include "nsIPrinterList.h"
+#include "nsPrintingProxy.h"
+#include "nsReadableUtils.h"
+#include "nsPrintSettingsImpl.h"
+#include "nsIPrintSession.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSize.h"
+
+#include "nsArray.h"
+#include "nsXPCOM.h"
+#include "nsXULAppAPI.h"
+
+#include "nsIStringEnumerator.h"
+#include "stdlib.h"
+#include "mozilla/Preferences.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla;
+using namespace mozilla::embedding;
+
+typedef mozilla::layout::RemotePrintJobChild RemotePrintJobChild;
+
+NS_IMPL_ISUPPORTS(nsPrintSettingsService, nsIPrintSettingsService)
+
+// Pref Constants
+static const char kMarginTop[] = "print_margin_top";
+static const char kMarginLeft[] = "print_margin_left";
+static const char kMarginBottom[] = "print_margin_bottom";
+static const char kMarginRight[] = "print_margin_right";
+static const char kEdgeTop[] = "print_edge_top";
+static const char kEdgeLeft[] = "print_edge_left";
+static const char kEdgeBottom[] = "print_edge_bottom";
+static const char kEdgeRight[] = "print_edge_right";
+static const char kUnwriteableMarginTop[] = "print_unwriteable_margin_top";
+static const char kUnwriteableMarginLeft[] = "print_unwriteable_margin_left";
+static const char kUnwriteableMarginBottom[] =
+ "print_unwriteable_margin_bottom";
+static const char kUnwriteableMarginRight[] = "print_unwriteable_margin_right";
+
+// Prefs for Print Options
+static const char kPrintHeaderStrLeft[] = "print_headerleft";
+static const char kPrintHeaderStrCenter[] = "print_headercenter";
+static const char kPrintHeaderStrRight[] = "print_headerright";
+static const char kPrintFooterStrLeft[] = "print_footerleft";
+static const char kPrintFooterStrCenter[] = "print_footercenter";
+static const char kPrintFooterStrRight[] = "print_footerright";
+
+// Additional Prefs
+static const char kPrintReversed[] = "print_reversed";
+static const char kPrintInColor[] = "print_in_color";
+static const char kPrintPaperId[] = "print_paper_id";
+static const char kPrintPaperSizeUnit[] = "print_paper_size_unit";
+static const char kPrintPaperWidth[] = "print_paper_width";
+static const char kPrintPaperHeight[] = "print_paper_height";
+static const char kPrintOrientation[] = "print_orientation";
+static const char kPrinterName[] = "print_printer";
+static const char kPrintToFile[] = "print_to_file";
+static const char kPrintToFileName[] = "print_to_filename";
+static const char kPrintPageDelay[] = "print_page_delay";
+static const char kPrintBGColors[] = "print_bgcolor";
+static const char kPrintBGImages[] = "print_bgimages";
+static const char kPrintShrinkToFit[] = "print_shrink_to_fit";
+static const char kPrintScaling[] = "print_scaling";
+static const char kPrintResolution[] = "print_resolution";
+static const char kPrintDuplex[] = "print_duplex";
+
+static const char kJustLeft[] = "left";
+static const char kJustCenter[] = "center";
+static const char kJustRight[] = "right";
+
+#define NS_PRINTER_LIST_CONTRACTID "@mozilla.org/gfx/printerlist;1"
+
+nsresult nsPrintSettingsService::Init() { return NS_OK; }
+
+NS_IMETHODIMP
+nsPrintSettingsService::SerializeToPrintData(nsIPrintSettings* aSettings,
+ PrintData* data) {
+ aSettings->GetPageRanges(data->pageRanges());
+
+ aSettings->GetEdgeTop(&data->edgeTop());
+ aSettings->GetEdgeLeft(&data->edgeLeft());
+ aSettings->GetEdgeBottom(&data->edgeBottom());
+ aSettings->GetEdgeRight(&data->edgeRight());
+
+ aSettings->GetMarginTop(&data->marginTop());
+ aSettings->GetMarginLeft(&data->marginLeft());
+ aSettings->GetMarginBottom(&data->marginBottom());
+ aSettings->GetMarginRight(&data->marginRight());
+ aSettings->GetUnwriteableMarginTop(&data->unwriteableMarginTop());
+ aSettings->GetUnwriteableMarginLeft(&data->unwriteableMarginLeft());
+ aSettings->GetUnwriteableMarginBottom(&data->unwriteableMarginBottom());
+ aSettings->GetUnwriteableMarginRight(&data->unwriteableMarginRight());
+
+ aSettings->GetScaling(&data->scaling());
+
+ data->printBGColors() = aSettings->GetPrintBGColors();
+ data->printBGImages() = aSettings->GetPrintBGImages();
+
+ data->honorPageRuleMargins() = aSettings->GetHonorPageRuleMargins();
+ data->showMarginGuides() = aSettings->GetShowMarginGuides();
+ data->isPrintSelectionRBEnabled() = aSettings->GetIsPrintSelectionRBEnabled();
+ data->printSelectionOnly() = aSettings->GetPrintSelectionOnly();
+
+ aSettings->GetTitle(data->title());
+ aSettings->GetDocURL(data->docURL());
+
+ aSettings->GetHeaderStrLeft(data->headerStrLeft());
+ aSettings->GetHeaderStrCenter(data->headerStrCenter());
+ aSettings->GetHeaderStrRight(data->headerStrRight());
+
+ aSettings->GetFooterStrLeft(data->footerStrLeft());
+ aSettings->GetFooterStrCenter(data->footerStrCenter());
+ aSettings->GetFooterStrRight(data->footerStrRight());
+
+ aSettings->GetIsCancelled(&data->isCancelled());
+ aSettings->GetPrintSilent(&data->printSilent());
+ aSettings->GetShrinkToFit(&data->shrinkToFit());
+ aSettings->GetShowPrintProgress(&data->showPrintProgress());
+
+ aSettings->GetPaperId(data->paperId());
+ aSettings->GetPaperWidth(&data->paperWidth());
+ aSettings->GetPaperHeight(&data->paperHeight());
+ aSettings->GetPaperSizeUnit(&data->paperSizeUnit());
+
+ aSettings->GetPrintReversed(&data->printReversed());
+ aSettings->GetPrintInColor(&data->printInColor());
+ aSettings->GetOrientation(&data->orientation());
+
+ aSettings->GetNumCopies(&data->numCopies());
+ aSettings->GetNumPagesPerSheet(&data->numPagesPerSheet());
+
+ aSettings->GetPrinterName(data->printerName());
+
+ aSettings->GetPrintToFile(&data->printToFile());
+
+ aSettings->GetToFileName(data->toFileName());
+
+ aSettings->GetOutputFormat(&data->outputFormat());
+ aSettings->GetPrintPageDelay(&data->printPageDelay());
+ aSettings->GetResolution(&data->resolution());
+ aSettings->GetDuplex(&data->duplex());
+ aSettings->GetIsInitializedFromPrinter(&data->isInitializedFromPrinter());
+ aSettings->GetIsInitializedFromPrefs(&data->isInitializedFromPrefs());
+
+ // Initialize the platform-specific values that don't
+ // default-initialize, so that we don't send uninitialized data over
+ // IPC (which leads to valgrind warnings, and, for bools, fatal
+ // assertions).
+ // data->driverName() default-initializes
+ // data->deviceName() default-initializes
+ // data->GTKPrintSettings() default-initializes
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsService::DeserializeToPrintSettings(const PrintData& data,
+ nsIPrintSettings* settings) {
+ nsCOMPtr<nsIPrintSession> session;
+ nsresult rv = settings->GetPrintSession(getter_AddRefs(session));
+ if (NS_SUCCEEDED(rv) && session) {
+ session->SetRemotePrintJob(
+ static_cast<RemotePrintJobChild*>(data.remotePrintJobChild()));
+ }
+
+ settings->SetPageRanges(data.pageRanges());
+
+ settings->SetEdgeTop(data.edgeTop());
+ settings->SetEdgeLeft(data.edgeLeft());
+ settings->SetEdgeBottom(data.edgeBottom());
+ settings->SetEdgeRight(data.edgeRight());
+
+ settings->SetMarginTop(data.marginTop());
+ settings->SetMarginLeft(data.marginLeft());
+ settings->SetMarginBottom(data.marginBottom());
+ settings->SetMarginRight(data.marginRight());
+ settings->SetUnwriteableMarginTop(data.unwriteableMarginTop());
+ settings->SetUnwriteableMarginLeft(data.unwriteableMarginLeft());
+ settings->SetUnwriteableMarginBottom(data.unwriteableMarginBottom());
+ settings->SetUnwriteableMarginRight(data.unwriteableMarginRight());
+
+ settings->SetScaling(data.scaling());
+
+ settings->SetPrintBGColors(data.printBGColors());
+ settings->SetPrintBGImages(data.printBGImages());
+ settings->SetHonorPageRuleMargins(data.honorPageRuleMargins());
+ settings->SetShowMarginGuides(data.showMarginGuides());
+ settings->SetIsPrintSelectionRBEnabled(data.isPrintSelectionRBEnabled());
+ settings->SetPrintSelectionOnly(data.printSelectionOnly());
+
+ settings->SetTitle(data.title());
+ settings->SetDocURL(data.docURL());
+
+ // Header strings...
+ settings->SetHeaderStrLeft(data.headerStrLeft());
+ settings->SetHeaderStrCenter(data.headerStrCenter());
+ settings->SetHeaderStrRight(data.headerStrRight());
+
+ // Footer strings...
+ settings->SetFooterStrLeft(data.footerStrLeft());
+ settings->SetFooterStrCenter(data.footerStrCenter());
+ settings->SetFooterStrRight(data.footerStrRight());
+
+ settings->SetIsCancelled(data.isCancelled());
+ settings->SetPrintSilent(data.printSilent());
+ settings->SetShrinkToFit(data.shrinkToFit());
+ settings->SetShowPrintProgress(data.showPrintProgress());
+
+ settings->SetPaperId(data.paperId());
+
+ settings->SetPaperWidth(data.paperWidth());
+ settings->SetPaperHeight(data.paperHeight());
+ settings->SetPaperSizeUnit(data.paperSizeUnit());
+
+ settings->SetPrintReversed(data.printReversed());
+ settings->SetPrintInColor(data.printInColor());
+ settings->SetOrientation(data.orientation());
+
+ settings->SetNumCopies(data.numCopies());
+ settings->SetNumPagesPerSheet(data.numPagesPerSheet());
+
+ settings->SetPrinterName(data.printerName());
+
+ settings->SetPrintToFile(data.printToFile());
+
+ settings->SetToFileName(data.toFileName());
+
+ settings->SetOutputFormat(data.outputFormat());
+ settings->SetPrintPageDelay(data.printPageDelay());
+ settings->SetResolution(data.resolution());
+ settings->SetDuplex(data.duplex());
+ settings->SetIsInitializedFromPrinter(data.isInitializedFromPrinter());
+ settings->SetIsInitializedFromPrefs(data.isInitializedFromPrefs());
+
+ return NS_OK;
+}
+
+/** ---------------------------------------------------
+ * Helper function - Creates the "prefix" for the pref
+ * It is either "print."
+ * or "print.printer_<print name>."
+ */
+const char* nsPrintSettingsService::GetPrefName(const char* aPrefName,
+ const nsAString& aPrinterName) {
+ if (!aPrefName || !*aPrefName) {
+ NS_ERROR("Must have a valid pref name!");
+ return aPrefName;
+ }
+
+ mPrefName.AssignLiteral("print.");
+
+ if (aPrinterName.Length()) {
+ mPrefName.AppendLiteral("printer_");
+ AppendUTF16toUTF8(aPrinterName, mPrefName);
+ mPrefName.Append('.');
+ }
+ mPrefName += aPrefName;
+
+ return mPrefName.get();
+}
+
+/**
+ * This will either read in the generic prefs (not specific to a printer)
+ * or read the prefs in using the printer name to qualify.
+ * It is either "print.attr_name" or "print.printer_HPLasr5.attr_name"
+ */
+nsresult nsPrintSettingsService::ReadPrefs(nsIPrintSettings* aPS,
+ const nsAString& aPrinterName,
+ uint32_t aFlags) {
+ NS_ENSURE_ARG_POINTER(aPS);
+
+ bool b;
+ nsAutoString str;
+ int32_t iVal;
+ double dbl;
+
+#define GETBOOLPREF(_prefname, _retval) \
+ NS_SUCCEEDED( \
+ Preferences::GetBool(GetPrefName(_prefname, aPrinterName), _retval))
+
+#define GETSTRPREF(_prefname, _retval) \
+ NS_SUCCEEDED( \
+ Preferences::GetString(GetPrefName(_prefname, aPrinterName), _retval))
+
+#define GETINTPREF(_prefname, _retval) \
+ NS_SUCCEEDED( \
+ Preferences::GetInt(GetPrefName(_prefname, aPrinterName), _retval))
+
+#define GETDBLPREF(_prefname, _retval) \
+ NS_SUCCEEDED(ReadPrefDouble(GetPrefName(_prefname, aPrinterName), _retval))
+
+ bool gotPaperSizeFromPrefs = false;
+ int16_t paperSizeUnit;
+ double paperWidth, paperHeight;
+
+ // Paper size prefs are read as a group
+ if (aFlags & nsIPrintSettings::kInitSavePaperSize) {
+ gotPaperSizeFromPrefs = GETINTPREF(kPrintPaperSizeUnit, &iVal) &&
+ GETDBLPREF(kPrintPaperWidth, paperWidth) &&
+ GETDBLPREF(kPrintPaperHeight, paperHeight) &&
+ GETSTRPREF(kPrintPaperId, str);
+ paperSizeUnit = (int16_t)iVal;
+
+ if (gotPaperSizeFromPrefs &&
+ paperSizeUnit != nsIPrintSettings::kPaperSizeInches &&
+ paperSizeUnit != nsIPrintSettings::kPaperSizeMillimeters) {
+ gotPaperSizeFromPrefs = false;
+ }
+
+ if (gotPaperSizeFromPrefs) {
+ // Bug 315687: Sanity check paper size to avoid paper size values in
+ // mm when the size unit flag is inches. The value 100 is arbitrary
+ // and can be changed.
+ gotPaperSizeFromPrefs =
+ (paperSizeUnit != nsIPrintSettings::kPaperSizeInches) ||
+ (paperWidth < 100.0) || (paperHeight < 100.0);
+ }
+
+ if (gotPaperSizeFromPrefs) {
+ aPS->SetPaperSizeUnit(paperSizeUnit);
+ aPS->SetPaperWidth(paperWidth);
+ aPS->SetPaperHeight(paperHeight);
+ aPS->SetPaperId(str);
+ }
+ }
+
+ nsIntSize pageSizeInTwips; // to sanity check margins
+ if (!gotPaperSizeFromPrefs) {
+ aPS->GetPaperSizeUnit(&paperSizeUnit);
+ aPS->GetPaperWidth(&paperWidth);
+ aPS->GetPaperHeight(&paperHeight);
+ }
+ if (paperSizeUnit == nsIPrintSettings::kPaperSizeMillimeters) {
+ pageSizeInTwips = nsIntSize((int)NS_MILLIMETERS_TO_TWIPS(paperWidth),
+ (int)NS_MILLIMETERS_TO_TWIPS(paperHeight));
+ } else {
+ pageSizeInTwips = nsIntSize((int)NS_INCHES_TO_TWIPS(paperWidth),
+ (int)NS_INCHES_TO_TWIPS(paperHeight));
+ }
+
+ auto MarginIsOK = [&pageSizeInTwips](const nsIntMargin& aMargin) {
+ return aMargin.top >= 0 && aMargin.right >= 0 && aMargin.bottom >= 0 &&
+ aMargin.left >= 0 && aMargin.LeftRight() < pageSizeInTwips.width &&
+ aMargin.TopBottom() < pageSizeInTwips.height;
+ };
+
+ if (aFlags & nsIPrintSettings::kInitSaveUnwriteableMargins) {
+ nsIntMargin margin;
+ ReadInchesIntToTwipsPref(GetPrefName(kUnwriteableMarginTop, aPrinterName),
+ margin.top, kUnwriteableMarginTop);
+ ReadInchesIntToTwipsPref(GetPrefName(kUnwriteableMarginLeft, aPrinterName),
+ margin.left, kUnwriteableMarginLeft);
+ ReadInchesIntToTwipsPref(
+ GetPrefName(kUnwriteableMarginBottom, aPrinterName), margin.bottom,
+ kUnwriteableMarginBottom);
+ ReadInchesIntToTwipsPref(GetPrefName(kUnwriteableMarginRight, aPrinterName),
+ margin.right, kUnwriteableMarginRight);
+ // SetUnwriteableMarginInTwips does its own validation and drops negative
+ // values individually. We still want to block overly large values though,
+ // so we do that part of MarginIsOK manually.
+ if (margin.LeftRight() < pageSizeInTwips.width &&
+ margin.TopBottom() < pageSizeInTwips.height) {
+ aPS->SetUnwriteableMarginInTwips(margin);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveMargins) {
+ int32_t halfInch = NS_INCHES_TO_INT_TWIPS(0.5);
+ nsIntMargin margin(halfInch, halfInch, halfInch, halfInch);
+ ReadInchesToTwipsPref(GetPrefName(kMarginTop, aPrinterName), margin.top,
+ kMarginTop);
+ ReadInchesToTwipsPref(GetPrefName(kMarginLeft, aPrinterName), margin.left,
+ kMarginLeft);
+ ReadInchesToTwipsPref(GetPrefName(kMarginBottom, aPrinterName),
+ margin.bottom, kMarginBottom);
+ ReadInchesToTwipsPref(GetPrefName(kMarginRight, aPrinterName), margin.right,
+ kMarginRight);
+ if (MarginIsOK(margin)) {
+ aPS->SetMarginInTwips(margin);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveEdges) {
+ nsIntMargin margin(0, 0, 0, 0);
+ ReadInchesIntToTwipsPref(GetPrefName(kEdgeTop, aPrinterName), margin.top,
+ kEdgeTop);
+ ReadInchesIntToTwipsPref(GetPrefName(kEdgeLeft, aPrinterName), margin.left,
+ kEdgeLeft);
+ ReadInchesIntToTwipsPref(GetPrefName(kEdgeBottom, aPrinterName),
+ margin.bottom, kEdgeBottom);
+ ReadInchesIntToTwipsPref(GetPrefName(kEdgeRight, aPrinterName),
+ margin.right, kEdgeRight);
+ if (MarginIsOK(margin)) {
+ aPS->SetEdgeInTwips(margin);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveHeaderLeft) {
+ if (GETSTRPREF(kPrintHeaderStrLeft, str)) {
+ aPS->SetHeaderStrLeft(str);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveHeaderCenter) {
+ if (GETSTRPREF(kPrintHeaderStrCenter, str)) {
+ aPS->SetHeaderStrCenter(str);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveHeaderRight) {
+ if (GETSTRPREF(kPrintHeaderStrRight, str)) {
+ aPS->SetHeaderStrRight(str);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveFooterLeft) {
+ if (GETSTRPREF(kPrintFooterStrLeft, str)) {
+ aPS->SetFooterStrLeft(str);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveFooterCenter) {
+ if (GETSTRPREF(kPrintFooterStrCenter, str)) {
+ aPS->SetFooterStrCenter(str);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveFooterRight) {
+ if (GETSTRPREF(kPrintFooterStrRight, str)) {
+ aPS->SetFooterStrRight(str);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveBGColors) {
+ if (GETBOOLPREF(kPrintBGColors, &b)) {
+ aPS->SetPrintBGColors(b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveBGImages) {
+ if (GETBOOLPREF(kPrintBGImages, &b)) {
+ aPS->SetPrintBGImages(b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveReversed) {
+ if (GETBOOLPREF(kPrintReversed, &b)) {
+ aPS->SetPrintReversed(b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveInColor) {
+ if (GETBOOLPREF(kPrintInColor, &b)) {
+ aPS->SetPrintInColor(b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveOrientation) {
+ if (GETINTPREF(kPrintOrientation, &iVal) &&
+ (iVal == nsIPrintSettings::kPortraitOrientation ||
+ iVal == nsIPrintSettings::kLandscapeOrientation)) {
+ aPS->SetOrientation(iVal);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSavePrintToFile) {
+ if (GETBOOLPREF(kPrintToFile, &b)) {
+ aPS->SetPrintToFile(b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveToFileName) {
+ if (GETSTRPREF(kPrintToFileName, str)) {
+ if (StringEndsWith(str, u".ps"_ns)) {
+ // We only support PDF since bug 1425188 landed. Users may still have
+ // prefs with .ps filenames if they last saved a file as Postscript
+ // though, so we fix that up here. (The pref values will be
+ // overwritten the next time they save to file as a PDF.)
+ str.Truncate(str.Length() - 2);
+ str.AppendLiteral("pdf");
+ }
+ aPS->SetToFileName(str);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSavePageDelay) {
+ // milliseconds
+ if (GETINTPREF(kPrintPageDelay, &iVal) && iVal >= 0 && iVal <= 1000) {
+ aPS->SetPrintPageDelay(iVal);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveShrinkToFit) {
+ if (GETBOOLPREF(kPrintShrinkToFit, &b)) {
+ aPS->SetShrinkToFit(b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveScaling) {
+ // The limits imposed here are fairly arbitrary and mainly intended to
+ // purge bad values which tend to be negative and/or very large. If we
+ // get complaints from users that settings outside these values "aren't
+ // saved" then we can consider increasing them.
+ if (GETDBLPREF(kPrintScaling, dbl) && dbl >= 0.05 && dbl <= 20) {
+ aPS->SetScaling(dbl);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveResolution) {
+ // DPI. Again, an arbitrary range mainly to purge bad values that have made
+ // their way into user prefs.
+ if (GETINTPREF(kPrintResolution, &iVal) && iVal >= 50 && iVal <= 12000) {
+ aPS->SetResolution(iVal);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveDuplex) {
+ if (GETINTPREF(kPrintDuplex, &iVal)) {
+ aPS->SetDuplex(iVal);
+ }
+ }
+
+ // Not Reading In:
+ // Number of Copies
+
+ return NS_OK;
+}
+
+nsresult nsPrintSettingsService::WritePrefs(nsIPrintSettings* aPS,
+ const nsAString& aPrinterName,
+ uint32_t aFlags) {
+ NS_ENSURE_ARG_POINTER(aPS);
+
+ if (aFlags & nsIPrintSettings::kInitSaveMargins) {
+ nsIntMargin margin = aPS->GetMarginInTwips();
+ WriteInchesFromTwipsPref(GetPrefName(kMarginTop, aPrinterName), margin.top);
+ WriteInchesFromTwipsPref(GetPrefName(kMarginLeft, aPrinterName),
+ margin.left);
+ WriteInchesFromTwipsPref(GetPrefName(kMarginBottom, aPrinterName),
+ margin.bottom);
+ WriteInchesFromTwipsPref(GetPrefName(kMarginRight, aPrinterName),
+ margin.right);
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveEdges) {
+ nsIntMargin edge = aPS->GetEdgeInTwips();
+ WriteInchesIntFromTwipsPref(GetPrefName(kEdgeTop, aPrinterName), edge.top);
+ WriteInchesIntFromTwipsPref(GetPrefName(kEdgeLeft, aPrinterName),
+ edge.left);
+ WriteInchesIntFromTwipsPref(GetPrefName(kEdgeBottom, aPrinterName),
+ edge.bottom);
+ WriteInchesIntFromTwipsPref(GetPrefName(kEdgeRight, aPrinterName),
+ edge.right);
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveUnwriteableMargins) {
+ nsIntMargin unwriteableMargin = aPS->GetUnwriteableMarginInTwips();
+ WriteInchesIntFromTwipsPref(
+ GetPrefName(kUnwriteableMarginTop, aPrinterName),
+ unwriteableMargin.top);
+ WriteInchesIntFromTwipsPref(
+ GetPrefName(kUnwriteableMarginLeft, aPrinterName),
+ unwriteableMargin.left);
+ WriteInchesIntFromTwipsPref(
+ GetPrefName(kUnwriteableMarginBottom, aPrinterName),
+ unwriteableMargin.bottom);
+ WriteInchesIntFromTwipsPref(
+ GetPrefName(kUnwriteableMarginRight, aPrinterName),
+ unwriteableMargin.right);
+ }
+
+ // Paper size prefs are saved as a group
+ if (aFlags & nsIPrintSettings::kInitSavePaperSize) {
+ int16_t sizeUnit;
+ double width, height;
+ nsString name;
+
+ if (NS_SUCCEEDED(aPS->GetPaperSizeUnit(&sizeUnit)) &&
+ NS_SUCCEEDED(aPS->GetPaperWidth(&width)) &&
+ NS_SUCCEEDED(aPS->GetPaperHeight(&height)) &&
+ NS_SUCCEEDED(aPS->GetPaperId(name))) {
+ Preferences::SetInt(GetPrefName(kPrintPaperSizeUnit, aPrinterName),
+ int32_t(sizeUnit));
+ WritePrefDouble(GetPrefName(kPrintPaperWidth, aPrinterName), width);
+ WritePrefDouble(GetPrefName(kPrintPaperHeight, aPrinterName), height);
+ Preferences::SetString(GetPrefName(kPrintPaperId, aPrinterName), name);
+ }
+ }
+
+ bool b;
+ nsString uStr;
+ int32_t iVal;
+ double dbl;
+
+ if (aFlags & nsIPrintSettings::kInitSaveHeaderLeft) {
+ if (NS_SUCCEEDED(aPS->GetHeaderStrLeft(uStr))) {
+ Preferences::SetString(GetPrefName(kPrintHeaderStrLeft, aPrinterName),
+ uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveHeaderCenter) {
+ if (NS_SUCCEEDED(aPS->GetHeaderStrCenter(uStr))) {
+ Preferences::SetString(GetPrefName(kPrintHeaderStrCenter, aPrinterName),
+ uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveHeaderRight) {
+ if (NS_SUCCEEDED(aPS->GetHeaderStrRight(uStr))) {
+ Preferences::SetString(GetPrefName(kPrintHeaderStrRight, aPrinterName),
+ uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveFooterLeft) {
+ if (NS_SUCCEEDED(aPS->GetFooterStrLeft(uStr))) {
+ Preferences::SetString(GetPrefName(kPrintFooterStrLeft, aPrinterName),
+ uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveFooterCenter) {
+ if (NS_SUCCEEDED(aPS->GetFooterStrCenter(uStr))) {
+ Preferences::SetString(GetPrefName(kPrintFooterStrCenter, aPrinterName),
+ uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveFooterRight) {
+ if (NS_SUCCEEDED(aPS->GetFooterStrRight(uStr))) {
+ Preferences::SetString(GetPrefName(kPrintFooterStrRight, aPrinterName),
+ uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveBGColors) {
+ b = aPS->GetPrintBGColors();
+ Preferences::SetBool(GetPrefName(kPrintBGColors, aPrinterName), b);
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveBGImages) {
+ b = aPS->GetPrintBGImages();
+ Preferences::SetBool(GetPrefName(kPrintBGImages, aPrinterName), b);
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveReversed) {
+ if (NS_SUCCEEDED(aPS->GetPrintReversed(&b))) {
+ Preferences::SetBool(GetPrefName(kPrintReversed, aPrinterName), b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveInColor) {
+ if (NS_SUCCEEDED(aPS->GetPrintInColor(&b))) {
+ Preferences::SetBool(GetPrefName(kPrintInColor, aPrinterName), b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveOrientation) {
+ if (NS_SUCCEEDED(aPS->GetOrientation(&iVal))) {
+ Preferences::SetInt(GetPrefName(kPrintOrientation, aPrinterName), iVal);
+ }
+ }
+
+ // Only the general version of this pref is saved
+ if ((aFlags & nsIPrintSettings::kInitSavePrinterName) &&
+ aPrinterName.IsEmpty()) {
+ if (NS_SUCCEEDED(aPS->GetPrinterName(uStr))) {
+ Preferences::SetString(kPrinterName, uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSavePrintToFile) {
+ if (NS_SUCCEEDED(aPS->GetPrintToFile(&b))) {
+ Preferences::SetBool(GetPrefName(kPrintToFile, aPrinterName), b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveToFileName) {
+ if (NS_SUCCEEDED(aPS->GetToFileName(uStr))) {
+ Preferences::SetString(GetPrefName(kPrintToFileName, aPrinterName), uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSavePageDelay) {
+ if (NS_SUCCEEDED(aPS->GetPrintPageDelay(&iVal))) {
+ Preferences::SetInt(GetPrefName(kPrintPageDelay, aPrinterName), iVal);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveShrinkToFit) {
+ if (NS_SUCCEEDED(aPS->GetShrinkToFit(&b))) {
+ Preferences::SetBool(GetPrefName(kPrintShrinkToFit, aPrinterName), b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveScaling) {
+ if (NS_SUCCEEDED(aPS->GetScaling(&dbl))) {
+ WritePrefDouble(GetPrefName(kPrintScaling, aPrinterName), dbl);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveResolution) {
+ if (NS_SUCCEEDED(aPS->GetResolution(&iVal))) {
+ Preferences::SetInt(GetPrefName(kPrintResolution, aPrinterName), iVal);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveDuplex) {
+ if (NS_SUCCEEDED(aPS->GetDuplex(&iVal))) {
+ Preferences::SetInt(GetPrefName(kPrintDuplex, aPrinterName), iVal);
+ }
+ }
+
+ // Not Writing Out:
+ // Number of Copies
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsService::GetDefaultPrintSettingsForPrinting(
+ nsIPrintSettings** aPrintSettings) {
+ nsresult rv = GetNewPrintSettings(aPrintSettings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIPrintSettings* settings = *aPrintSettings;
+
+ nsAutoString printerName;
+ settings->GetPrinterName(printerName);
+ if (printerName.IsEmpty()) {
+ GetLastUsedPrinterName(printerName);
+ settings->SetPrinterName(printerName);
+ }
+ InitPrintSettingsFromPrinter(printerName, settings);
+ InitPrintSettingsFromPrefs(settings, true, nsIPrintSettings::kInitSaveAll);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsService::GetNewPrintSettings(
+ nsIPrintSettings** aNewPrintSettings) {
+ return _CreatePrintSettings(aNewPrintSettings);
+}
+
+NS_IMETHODIMP
+nsPrintSettingsService::GetLastUsedPrinterName(
+ nsAString& aLastUsedPrinterName) {
+ aLastUsedPrinterName.Truncate();
+ Preferences::GetString(kPrinterName, aLastUsedPrinterName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsService::InitPrintSettingsFromPrinter(
+ const nsAString& aPrinterName, nsIPrintSettings* aPrintSettings) {
+ // Don't get print settings from the printer in the child when printing via
+ // parent, these will be retrieved in the parent later in the print process.
+ if (XRE_IsContentProcess() &&
+ Preferences::GetBool("print.print_via_parent")) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_ARG_POINTER(aPrintSettings);
+
+#ifdef DEBUG
+ nsString printerName;
+ aPrintSettings->GetPrinterName(printerName);
+ if (!printerName.Equals(aPrinterName)) {
+ NS_WARNING("Printer names should match!");
+ }
+#endif
+
+ bool isInitialized;
+ aPrintSettings->GetIsInitializedFromPrinter(&isInitialized);
+ if (isInitialized) return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsIPrinterList> printerList =
+ do_GetService(NS_PRINTER_LIST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = printerList->InitPrintSettingsFromPrinter(aPrinterName, aPrintSettings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aPrintSettings->SetIsInitializedFromPrinter(true);
+ return rv;
+}
+
+/** ---------------------------------------------------
+ * Helper function - Returns either the name or sets the length to zero
+ */
+static nsresult GetAdjustedPrinterName(nsIPrintSettings* aPS, bool aUsePNP,
+ nsAString& aPrinterName) {
+ NS_ENSURE_ARG_POINTER(aPS);
+
+ aPrinterName.Truncate();
+ if (!aUsePNP) return NS_OK;
+
+ // Get the Printer Name from the PrintSettings
+ // to use as a prefix for Pref Names
+ nsresult rv = aPS->GetPrinterName(aPrinterName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Convert any whitespaces, carriage returns or newlines to _
+ // The below algorithm is supposedly faster than using iterators
+ constexpr auto replSubstr = u"_"_ns;
+ const char* replaceStr = " \n\r";
+
+ int32_t x;
+ for (x = 0; x < (int32_t)strlen(replaceStr); x++) {
+ char16_t uChar = replaceStr[x];
+
+ int32_t i = 0;
+ while ((i = aPrinterName.FindChar(uChar, i)) != kNotFound) {
+ aPrinterName.Replace(i, 1, replSubstr);
+ i++;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsService::InitPrintSettingsFromPrefs(nsIPrintSettings* aPS,
+ bool aUsePNP,
+ uint32_t aFlags) {
+ NS_ENSURE_ARG_POINTER(aPS);
+
+ bool isInitialized;
+ aPS->GetIsInitializedFromPrefs(&isInitialized);
+
+ if (isInitialized) return NS_OK;
+
+ auto globalPrintSettings = aFlags;
+#ifndef MOZ_WIDGET_ANDROID
+ globalPrintSettings &= nsIPrintSettings::kInitSaveShrinkToFit |
+ nsIPrintSettings::kInitSaveHeaderLeft |
+ nsIPrintSettings::kInitSaveHeaderCenter |
+ nsIPrintSettings::kInitSaveHeaderRight |
+ nsIPrintSettings::kInitSaveFooterLeft |
+ nsIPrintSettings::kInitSaveFooterCenter |
+ nsIPrintSettings::kInitSaveFooterRight |
+ nsIPrintSettings::kInitSaveEdges |
+ nsIPrintSettings::kInitSaveReversed |
+ nsIPrintSettings::kInitSaveInColor;
+#endif
+
+ nsAutoString prtName;
+ // read any non printer specific prefs
+ // with empty printer name
+ nsresult rv = ReadPrefs(aPS, prtName, globalPrintSettings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the Printer Name from the PrintSettings to use as a prefix for Pref
+ // Names
+ rv = GetAdjustedPrinterName(aPS, aUsePNP, prtName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (prtName.IsEmpty()) {
+ NS_WARNING("Caller should supply a printer name.");
+ return NS_OK;
+ }
+
+ // Now read any printer specific prefs
+ rv = ReadPrefs(aPS, prtName, aFlags);
+ if (NS_SUCCEEDED(rv)) aPS->SetIsInitializedFromPrefs(true);
+
+ return NS_OK;
+}
+
+/**
+ * Save all of the printer settings; if we can find a printer name, save
+ * printer-specific preferences. Otherwise, save generic ones.
+ */
+nsresult nsPrintSettingsService::SavePrintSettingsToPrefs(
+ nsIPrintSettings* aPS, bool aUsePrinterNamePrefix, uint32_t aFlags) {
+ NS_ENSURE_ARG_POINTER(aPS);
+
+ if (GeckoProcessType_Content == XRE_GetProcessType()) {
+ // If we're in the content process, we can't directly write to the
+ // Preferences service - we have to proxy the save up to the
+ // parent process.
+ RefPtr<nsPrintingProxy> proxy = nsPrintingProxy::GetInstance();
+ return proxy->SavePrintSettings(aPS, aUsePrinterNamePrefix, aFlags);
+ }
+
+ nsAutoString prtName;
+
+ // Get the printer name from the PrinterSettings for an optional prefix.
+ nsresult rv = GetAdjustedPrinterName(aPS, aUsePrinterNamePrefix, prtName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Write the prefs, with or without a printer name prefix.
+ return WritePrefs(aPS, prtName, aFlags);
+}
+
+//-----------------------------------------------------
+//-- Protected Methods --------------------------------
+//-----------------------------------------------------
+nsresult nsPrintSettingsService::ReadPrefDouble(const char* aPrefId,
+ double& aVal) {
+ NS_ENSURE_ARG_POINTER(aPrefId);
+
+ nsAutoCString str;
+ nsresult rv = Preferences::GetCString(aPrefId, str);
+ if (NS_FAILED(rv) || str.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ double value = str.ToDouble(&rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ aVal = value;
+ return NS_OK;
+}
+
+nsresult nsPrintSettingsService::WritePrefDouble(const char* aPrefId,
+ double aVal) {
+ NS_ENSURE_ARG_POINTER(aPrefId);
+
+ nsAutoCString str;
+ // We cast to a float so we only get up to 6 digits precision in the prefs.
+ str.AppendFloat((float)aVal);
+ return Preferences::SetCString(aPrefId, str);
+}
+
+void nsPrintSettingsService::ReadInchesToTwipsPref(const char* aPrefId,
+ int32_t& aTwips,
+ const char* aMarginPref) {
+ nsAutoString str;
+ nsresult rv = Preferences::GetString(aPrefId, str);
+ if (NS_FAILED(rv) || str.IsEmpty()) {
+ rv = Preferences::GetString(aMarginPref, str);
+ }
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty()) {
+ nsresult errCode;
+ float inches = str.ToFloat(&errCode);
+ if (NS_SUCCEEDED(errCode)) {
+ aTwips = NS_INCHES_TO_INT_TWIPS(inches);
+ } else {
+ aTwips = 0;
+ }
+ }
+}
+
+void nsPrintSettingsService::WriteInchesFromTwipsPref(const char* aPrefId,
+ int32_t aTwips) {
+ double inches = NS_TWIPS_TO_INCHES(aTwips);
+ nsAutoCString inchesStr;
+ inchesStr.AppendFloat(inches);
+
+ Preferences::SetCString(aPrefId, inchesStr);
+}
+
+void nsPrintSettingsService::ReadInchesIntToTwipsPref(const char* aPrefId,
+ int32_t& aTwips,
+ const char* aMarginPref) {
+ int32_t value;
+ nsresult rv = Preferences::GetInt(aPrefId, &value);
+ if (NS_FAILED(rv)) {
+ rv = Preferences::GetInt(aMarginPref, &value);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ aTwips = NS_INCHES_TO_INT_TWIPS(float(value) / 100.0f);
+ } else {
+ aTwips = 0;
+ }
+}
+
+void nsPrintSettingsService::WriteInchesIntFromTwipsPref(const char* aPrefId,
+ int32_t aTwips) {
+ Preferences::SetInt(aPrefId,
+ int32_t(NS_TWIPS_TO_INCHES(aTwips) * 100.0f + 0.5f));
+}
+
+void nsPrintSettingsService::ReadJustification(const char* aPrefId,
+ int16_t& aJust,
+ int16_t aInitValue) {
+ aJust = aInitValue;
+ nsAutoString justStr;
+ if (NS_SUCCEEDED(Preferences::GetString(aPrefId, justStr))) {
+ if (justStr.EqualsASCII(kJustRight)) {
+ aJust = nsIPrintSettings::kJustRight;
+ } else if (justStr.EqualsASCII(kJustCenter)) {
+ aJust = nsIPrintSettings::kJustCenter;
+ } else {
+ aJust = nsIPrintSettings::kJustLeft;
+ }
+ }
+}
+
+//---------------------------------------------------
+void nsPrintSettingsService::WriteJustification(const char* aPrefId,
+ int16_t aJust) {
+ switch (aJust) {
+ case nsIPrintSettings::kJustLeft:
+ Preferences::SetCString(aPrefId, kJustLeft);
+ break;
+
+ case nsIPrintSettings::kJustCenter:
+ Preferences::SetCString(aPrefId, kJustCenter);
+ break;
+
+ case nsIPrintSettings::kJustRight:
+ Preferences::SetCString(aPrefId, kJustRight);
+ break;
+ } // switch
+}
diff --git a/widget/nsPrintSettingsService.h b/widget/nsPrintSettingsService.h
new file mode 100644
index 0000000000..75ac930c6b
--- /dev/null
+++ b/widget/nsPrintSettingsService.h
@@ -0,0 +1,90 @@
+/* -*- Mode: IDL; 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 nsPrintSettingsService_h
+#define nsPrintSettingsService_h
+
+#include "mozilla/embedding/PPrinting.h"
+#include "nsCOMPtr.h"
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsService.h"
+#include "nsString.h"
+#include "nsFont.h"
+
+/**
+ * Class nsPrintSettingsService. Base class for the platform specific widget
+ * subclasses to inherit from.
+ */
+class nsPrintSettingsService : public nsIPrintSettingsService {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTSETTINGSSERVICE
+
+ nsPrintSettingsService() = default;
+
+ /**
+ * method Init
+ * Initializes member variables. Every consumer that does manual
+ * creation (instead of do_CreateInstance) needs to call this method
+ * immediately after instantiation.
+ */
+ virtual nsresult Init();
+
+ private:
+ // Copying is not supported.
+ nsPrintSettingsService(const nsPrintSettingsService& x) = delete;
+ nsPrintSettingsService& operator=(const nsPrintSettingsService& x) = delete;
+
+ protected:
+ virtual ~nsPrintSettingsService() = default;
+
+ void ReadBitFieldPref(const char* aPrefId, int32_t anOption);
+ void WriteBitFieldPref(const char* aPrefId, int32_t anOption);
+ void ReadJustification(const char* aPrefId, int16_t& aJust,
+ int16_t aInitValue);
+ void WriteJustification(const char* aPrefId, int16_t aJust);
+ void ReadInchesToTwipsPref(const char* aPrefId, int32_t& aTwips,
+ const char* aMarginPref);
+ void WriteInchesFromTwipsPref(const char* aPrefId, int32_t aTwips);
+ void ReadInchesIntToTwipsPref(const char* aPrefId, int32_t& aTwips,
+ const char* aMarginPref);
+ void WriteInchesIntFromTwipsPref(const char* aPrefId, int32_t aTwips);
+
+ nsresult ReadPrefDouble(const char* aPrefId, double& aVal);
+ nsresult WritePrefDouble(const char* aPrefId, double aVal);
+
+ /**
+ * method ReadPrefs
+ * @param aPS a pointer to the printer settings
+ * @param aPrinterName the name of the printer for which to read prefs
+ * @param aFlags flag specifying which prefs to read
+ */
+ virtual nsresult ReadPrefs(nsIPrintSettings* aPS,
+ const nsAString& aPrinterName, uint32_t aFlags);
+ /**
+ * method WritePrefs
+ * @param aPS a pointer to the printer settings
+ * @param aPrinterName the name of the printer for which to write prefs
+ * @param aFlags flag specifying which prefs to read
+ */
+ virtual nsresult WritePrefs(nsIPrintSettings* aPS,
+ const nsAString& aPrinterName, uint32_t aFlags);
+
+ const char* GetPrefName(const char* aPrefName, const nsAString& aPrinterName);
+
+ /**
+ * method _CreatePrintSettings
+ * May be implemented by the platform-specific derived class
+ *
+ * @return printer settings instance
+ */
+ virtual nsresult _CreatePrintSettings(nsIPrintSettings** _retval) = 0;
+
+ // Members
+ nsCString mPrefName;
+};
+
+#endif // nsPrintSettingsService_h
diff --git a/widget/nsPrinterBase.cpp b/widget/nsPrinterBase.cpp
new file mode 100644
index 0000000000..d86f30c4a0
--- /dev/null
+++ b/widget/nsPrinterBase.cpp
@@ -0,0 +1,212 @@
+/* -*- 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 "nsPrinterBase.h"
+#include "nsPaperMargin.h"
+#include <utility>
+#include "nsPaper.h"
+#include "nsIPrintSettings.h"
+#include "PrintBackgroundTask.h"
+#include "mozilla/dom/Promise.h"
+
+using namespace mozilla;
+using mozilla::dom::Promise;
+using mozilla::gfx::MarginDouble;
+
+// The maximum error when considering a paper size equal, in points.
+// There is some variance in the actual sizes returned by printer drivers and
+// print servers for paper sizes. This is a best-guess based on initial
+// telemetry which should catch most near-miss dimensions. This should let us
+// get consistent paper size names even when the size isn't quite exactly the
+// correct size.
+static constexpr double kPaperSizePointsEpsilon = 4.0;
+
+// Basic implementation of nsIPrinterInfo
+class nsPrinterInfo : public nsIPrinterInfo {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsPrinterInfo)
+ NS_DECL_NSIPRINTERINFO
+ nsPrinterInfo() = delete;
+ nsPrinterInfo(nsPrinterBase& aPrinter,
+ const nsPrinterBase::PrinterInfo& aPrinterInfo)
+ : mDefaultSettings(
+ CreatePlatformPrintSettings(aPrinterInfo.mDefaultSettings)) {
+ mPaperList.SetCapacity(aPrinterInfo.mPaperList.Length());
+ for (const PaperInfo& info : aPrinterInfo.mPaperList) {
+ mPaperList.AppendElement(MakeRefPtr<nsPaper>(aPrinter, info));
+ }
+ }
+
+ private:
+ virtual ~nsPrinterInfo() = default;
+
+ nsTArray<RefPtr<nsIPaper>> mPaperList;
+ RefPtr<nsIPrintSettings> mDefaultSettings;
+};
+
+NS_IMETHODIMP
+nsPrinterInfo::GetPaperList(nsTArray<RefPtr<nsIPaper>>& aPaperList) {
+ aPaperList = mPaperList.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrinterInfo::GetDefaultSettings(nsIPrintSettings** aDefaultSettings) {
+ NS_ENSURE_ARG_POINTER(aDefaultSettings);
+ MOZ_ASSERT(mDefaultSettings);
+ RefPtr<nsIPrintSettings> settings = mDefaultSettings;
+ settings.forget(aDefaultSettings);
+ return NS_OK;
+}
+
+NS_IMPL_CYCLE_COLLECTION(nsPrinterInfo, mPaperList, mDefaultSettings)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPrinterInfo)
+ NS_INTERFACE_MAP_ENTRY(nsIPrinterInfo)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrinterInfo)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPrinterInfo)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPrinterInfo)
+
+template <typename Index, Index Size, typename Value>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ EnumeratedArray<Index, Size, Value>& aArray, const char* aName,
+ uint32_t aFlags = 0) {
+ aFlags |= CycleCollectionEdgeNameArrayFlag;
+ for (Value& element : aArray) {
+ ImplCycleCollectionTraverse(aCallback, element, aName, aFlags);
+ }
+}
+
+template <typename Index, Index Size, typename Value>
+inline void ImplCycleCollectionUnlink(
+ EnumeratedArray<Index, Size, Value>& aArray) {
+ for (Value& element : aArray) {
+ ImplCycleCollectionUnlink(element);
+ }
+}
+
+namespace mozilla {
+
+template <>
+void ResolveOrReject(Promise& aPromise, nsPrinterBase&,
+ const MarginDouble& aResult) {
+ auto margin = MakeRefPtr<nsPaperMargin>(aResult);
+ aPromise.MaybeResolve(margin);
+}
+
+template <>
+void ResolveOrReject(Promise& aPromise, nsPrinterBase& aPrinter,
+ const nsTArray<PaperInfo>& aResult) {
+ nsTArray<RefPtr<nsPaper>> result;
+ result.SetCapacity(aResult.Length());
+ for (const PaperInfo& info : aResult) {
+ result.AppendElement(MakeRefPtr<nsPaper>(aPrinter, info));
+ }
+ aPromise.MaybeResolve(result);
+}
+
+template <>
+void ResolveOrReject(Promise& aPromise, nsPrinterBase& aPrinter,
+ const PrintSettingsInitializer& aResult) {
+ aPromise.MaybeResolve(
+ RefPtr<nsIPrintSettings>(CreatePlatformPrintSettings(aResult)));
+}
+
+template <>
+void ResolveOrReject(Promise& aPromise, nsPrinterBase& aPrinter,
+ const nsPrinterBase::PrinterInfo& aResult) {
+ aPromise.MaybeResolve(MakeRefPtr<nsPrinterInfo>(aPrinter, aResult));
+}
+
+} // namespace mozilla
+
+template <typename T, typename... Args>
+nsresult nsPrinterBase::AsyncPromiseAttributeGetter(
+ JSContext* aCx, Promise** aResultPromise, AsyncAttribute aAttribute,
+ BackgroundTask<T, Args...> aBackgroundTask, Args... aArgs) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ static constexpr EnumeratedArray<AsyncAttribute, AsyncAttribute::Last,
+ nsLiteralCString>
+ attributeKeys{"SupportsDuplex"_ns, "SupportsColor"_ns,
+ "SupportsMonochrome"_ns, "SupportsCollation"_ns,
+ "PrinterInfo"_ns};
+ return mozilla::AsyncPromiseAttributeGetter(
+ *this, mAsyncAttributePromises[aAttribute], aCx, aResultPromise,
+ attributeKeys[aAttribute], aBackgroundTask, std::forward<Args>(aArgs)...);
+}
+
+NS_IMETHODIMP nsPrinterBase::GetSupportsDuplex(JSContext* aCx,
+ Promise** aResultPromise) {
+ return AsyncPromiseAttributeGetter(aCx, aResultPromise,
+ AsyncAttribute::SupportsDuplex,
+ &nsPrinterBase::SupportsDuplex);
+}
+
+NS_IMETHODIMP nsPrinterBase::GetSupportsColor(JSContext* aCx,
+ Promise** aResultPromise) {
+ return AsyncPromiseAttributeGetter(aCx, aResultPromise,
+ AsyncAttribute::SupportsColor,
+ &nsPrinterBase::SupportsColor);
+}
+
+NS_IMETHODIMP nsPrinterBase::GetSupportsMonochrome(JSContext* aCx,
+ Promise** aResultPromise) {
+ return AsyncPromiseAttributeGetter(aCx, aResultPromise,
+ AsyncAttribute::SupportsMonochrome,
+ &nsPrinterBase::SupportsMonochrome);
+}
+
+NS_IMETHODIMP nsPrinterBase::GetSupportsCollation(JSContext* aCx,
+ Promise** aResultPromise) {
+ return AsyncPromiseAttributeGetter(aCx, aResultPromise,
+ AsyncAttribute::SupportsCollation,
+ &nsPrinterBase::SupportsCollation);
+}
+
+NS_IMETHODIMP nsPrinterBase::GetPrinterInfo(JSContext* aCx,
+ Promise** aResultPromise) {
+ return AsyncPromiseAttributeGetter(aCx, aResultPromise,
+ AsyncAttribute::PrinterInfo,
+ &nsPrinterBase::CreatePrinterInfo);
+}
+
+void nsPrinterBase::QueryMarginsForPaper(Promise& aPromise,
+ const nsString& aPaperId) {
+ return SpawnPrintBackgroundTask(*this, aPromise, "MarginsForPaper"_ns,
+ &nsPrinterBase::GetMarginsForPaper, aPaperId);
+}
+
+NS_IMPL_CYCLE_COLLECTION(nsPrinterBase, mAsyncAttributePromises)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPrinterBase)
+ NS_INTERFACE_MAP_ENTRY(nsIPrinter)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrinter)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPrinterBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPrinterBase)
+
+nsPrinterBase::nsPrinterBase(const CommonPaperInfoArray* aPaperInfoArray)
+ : mCommonPaperInfo(aPaperInfoArray) {
+ MOZ_DIAGNOSTIC_ASSERT(aPaperInfoArray, "Localized paper info was null");
+}
+nsPrinterBase::~nsPrinterBase() = default;
+
+const PaperInfo* nsPrinterBase::FindCommonPaperSize(
+ const gfx::SizeDouble& aSize) const {
+ for (const PaperInfo& paper : *mCommonPaperInfo) {
+ if (std::abs(paper.mSize.width - aSize.width) <= kPaperSizePointsEpsilon &&
+ std::abs(paper.mSize.height - aSize.height) <=
+ kPaperSizePointsEpsilon) {
+ return &paper;
+ }
+ }
+ return nullptr;
+}
diff --git a/widget/nsPrinterBase.h b/widget/nsPrinterBase.h
new file mode 100644
index 0000000000..da16e00277
--- /dev/null
+++ b/widget/nsPrinterBase.h
@@ -0,0 +1,112 @@
+/* -*- 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 nsPrinterBase_h__
+#define nsPrinterBase_h__
+
+#include "mozilla/gfx/Rect.h"
+#include "nsIPrinter.h"
+#include "nsTArray.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupportsImpl.h"
+#include "nsPrintSettingsImpl.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/Result.h"
+
+namespace mozilla {
+
+struct PaperInfo;
+
+namespace dom {
+class Promise;
+}
+
+} // namespace mozilla
+
+class nsPrinterBase : public nsIPrinter {
+ public:
+ using Promise = mozilla::dom::Promise;
+ using MarginDouble = mozilla::gfx::MarginDouble;
+ using PrintSettingsInitializer = mozilla::PrintSettingsInitializer;
+
+ NS_IMETHOD GetSupportsDuplex(JSContext*, Promise**) final;
+ NS_IMETHOD GetSupportsColor(JSContext*, Promise**) final;
+ NS_IMETHOD GetSupportsMonochrome(JSContext*, Promise**) final;
+ NS_IMETHOD GetSupportsCollation(JSContext*, Promise**) final;
+ NS_IMETHOD GetPrinterInfo(JSContext*, Promise**) final;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsPrinterBase)
+
+ // We require the paper info array
+ nsPrinterBase() = delete;
+
+ // No copy or move, we're an identity.
+ nsPrinterBase(const nsPrinterBase&) = delete;
+ nsPrinterBase(nsPrinterBase&&) = delete;
+
+ void QueryMarginsForPaper(Promise&, const nsString& aPaperId);
+
+ /**
+ * Caches the argument by copying it into mPrintSettingsInitializer.
+ * If mPrintSettingsInitializer is already populated this is a no-op.
+ */
+ void CachePrintSettingsInitializer(
+ const PrintSettingsInitializer& aInitializer);
+
+ // Data to pass through to create the nsPrinterInfo
+ struct PrinterInfo {
+ nsTArray<mozilla::PaperInfo> mPaperList;
+ PrintSettingsInitializer mDefaultSettings;
+ };
+
+ private:
+ enum class AsyncAttribute {
+ // If you change this list you must update attributeKeys in
+ // nsPrinterBase::AsyncPromiseAttributeGetter.
+ SupportsDuplex = 0,
+ SupportsColor,
+ SupportsMonochrome,
+ SupportsCollation,
+ PrinterInfo,
+ // Just a guard.
+ Last,
+ };
+
+ template <typename Result, typename... Args>
+ using BackgroundTask = Result (nsPrinterBase::*)(Args...) const;
+
+ // Resolves an async attribute via a background task.
+ template <typename T, typename... Args>
+ nsresult AsyncPromiseAttributeGetter(JSContext*, Promise**, AsyncAttribute,
+ BackgroundTask<T, Args...>,
+ Args... aArgs);
+
+ protected:
+ nsPrinterBase(const mozilla::CommonPaperInfoArray* aPaperInfoArray);
+ virtual ~nsPrinterBase();
+
+ // Implementation-specific methods. These must not make assumptions about
+ // which thread they run on.
+ virtual bool SupportsDuplex() const = 0;
+ virtual bool SupportsColor() const = 0;
+ virtual bool SupportsMonochrome() const = 0;
+ virtual bool SupportsCollation() const = 0;
+ virtual MarginDouble GetMarginsForPaper(nsString aPaperId) const = 0;
+ virtual PrinterInfo CreatePrinterInfo() const = 0;
+ // Searches our built-in list of commonly used PWG paper sizes for a matching,
+ // localized PaperInfo. Returns null if there is no matching size.
+ const mozilla::PaperInfo* FindCommonPaperSize(
+ const mozilla::gfx::SizeDouble& aSize) const;
+
+ private:
+ mozilla::EnumeratedArray<AsyncAttribute, AsyncAttribute::Last,
+ RefPtr<Promise>>
+ mAsyncAttributePromises;
+ // List of built-in, commonly used paper sizes.
+ const RefPtr<const mozilla::CommonPaperInfoArray> mCommonPaperInfo;
+};
+
+#endif
diff --git a/widget/nsPrinterCUPS.cpp b/widget/nsPrinterCUPS.cpp
new file mode 100644
index 0000000000..7adbaf0d98
--- /dev/null
+++ b/widget/nsPrinterCUPS.cpp
@@ -0,0 +1,375 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsPrinterCUPS.h"
+
+#include "mozilla/GkRustUtils.h"
+#include "mozilla/StaticPrefs_print.h"
+#include "nsTHashtable.h"
+#include "nsPaper.h"
+#include "nsPrinterBase.h"
+#include "nsPrintSettingsImpl.h"
+#include "plstr.h"
+
+using namespace mozilla;
+
+// Requested attributes for IPP requests, just the CUPS version now.
+static constexpr Array<const char* const, 1> requestedAttributes{
+ "cups-version"};
+
+static constexpr double kPointsPerHundredthMillimeter = 72.0 / 2540.0;
+
+static PaperInfo MakePaperInfo(const nsAString& aName,
+ const cups_size_t& aMedia) {
+ // XXX Do we actually have the guarantee that this is utf-8?
+ NS_ConvertUTF8toUTF16 paperId(aMedia.media); // internal paper name/ID
+ return PaperInfo(
+ paperId, aName,
+ {aMedia.width * kPointsPerHundredthMillimeter,
+ aMedia.length * kPointsPerHundredthMillimeter},
+ Some(gfx::MarginDouble{aMedia.top * kPointsPerHundredthMillimeter,
+ aMedia.right * kPointsPerHundredthMillimeter,
+ aMedia.bottom * kPointsPerHundredthMillimeter,
+ aMedia.left * kPointsPerHundredthMillimeter}));
+}
+
+// Fetches the CUPS version for the print server controlling the printer. This
+// will only modify the output arguments if the fetch succeeds.
+static void FetchCUPSVersionForPrinter(const nsCUPSShim& aShim,
+ const cups_dest_t* const aDest,
+ uint64_t& aOutMajor, uint64_t& aOutMinor,
+ uint64_t& aOutPatch) {
+ // Make an IPP request to the server for the printer.
+ const char* const uri = aShim.cupsGetOption(
+ "printer-uri-supported", aDest->num_options, aDest->options);
+ if (!uri) {
+ return;
+ }
+
+ ipp_t* const ippRequest = aShim.ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
+
+ // Set the URI we want to use.
+ aShim.ippAddString(ippRequest, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
+ nullptr, uri);
+
+ // Set the attributes to request.
+ aShim.ippAddStrings(ippRequest, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes", requestedAttributes.Length,
+ nullptr, &(requestedAttributes[0]));
+
+ // Use the default HTTP connection to query the CUPS server itself to get
+ // the CUPS version.
+ // Note that cupsDoRequest will delete the request whether it succeeds or
+ // fails, so we should not use ippDelete on it.
+ if (ipp_t* const ippResponse =
+ aShim.cupsDoRequest(CUPS_HTTP_DEFAULT, ippRequest, "/")) {
+ ipp_attribute_t* const versionAttrib =
+ aShim.ippFindAttribute(ippResponse, "cups-version", IPP_TAG_TEXT);
+ if (versionAttrib && aShim.ippGetCount(versionAttrib) == 1) {
+ const char* versionString = aShim.ippGetString(versionAttrib, 0, nullptr);
+ MOZ_ASSERT(versionString);
+ // On error, GkRustUtils::ParseSemVer will not modify its arguments.
+ GkRustUtils::ParseSemVer(
+ nsDependentCSubstring{MakeStringSpan(versionString)}, aOutMajor,
+ aOutMinor, aOutPatch);
+ }
+ aShim.ippDelete(ippResponse);
+ }
+}
+
+nsPrinterCUPS::~nsPrinterCUPS() {
+ auto printerInfoLock = mPrinterInfoMutex.Lock();
+ if (printerInfoLock->mPrinterInfo) {
+ mShim.cupsFreeDestInfo(printerInfoLock->mPrinterInfo);
+ }
+ if (mPrinter) {
+ mShim.cupsFreeDests(1, mPrinter);
+ mPrinter = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsPrinterCUPS::GetName(nsAString& aName) {
+ GetPrinterName(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrinterCUPS::GetSystemName(nsAString& aName) {
+ CopyUTF8toUTF16(MakeStringSpan(mPrinter->name), aName);
+ return NS_OK;
+}
+
+void nsPrinterCUPS::GetPrinterName(nsAString& aName) const {
+ if (mDisplayName.IsEmpty()) {
+ aName.Truncate();
+ CopyUTF8toUTF16(MakeStringSpan(mPrinter->name), aName);
+ } else {
+ aName = mDisplayName;
+ }
+}
+
+const char* nsPrinterCUPS::LocalizeMediaName(http_t& aConnection,
+ cups_size_t& aMedia) const {
+ // The returned string is owned by mPrinterInfo.
+ // https://www.cups.org/doc/cupspm.html#cupsLocalizeDestMedia
+ auto printerInfoLock = TryEnsurePrinterInfo();
+ cups_dinfo_t* const printerInfo = printerInfoLock->mPrinterInfo;
+ return mShim.cupsLocalizeDestMedia(&aConnection, mPrinter, printerInfo,
+ CUPS_MEDIA_FLAGS_DEFAULT, &aMedia);
+}
+
+bool nsPrinterCUPS::SupportsDuplex() const {
+ return Supports(CUPS_SIDES, CUPS_SIDES_TWO_SIDED_PORTRAIT);
+}
+
+bool nsPrinterCUPS::SupportsMonochrome() const {
+ if (!SupportsColor()) {
+ return true;
+ }
+ return StaticPrefs::print_cups_monochrome_enabled();
+}
+
+bool nsPrinterCUPS::SupportsColor() const {
+ // CUPS 2.1 (particularly as used in Ubuntu 16) is known to have inaccurate
+ // results for CUPS_PRINT_COLOR_MODE.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1660658#c15
+ if (!IsCUPSVersionAtLeast(2, 2, 0)) {
+ return true; // See comment for PrintSettingsInitializer.mPrintInColor
+ }
+ return Supports(CUPS_PRINT_COLOR_MODE, CUPS_PRINT_COLOR_MODE_AUTO) ||
+ Supports(CUPS_PRINT_COLOR_MODE, CUPS_PRINT_COLOR_MODE_COLOR) ||
+ !Supports(CUPS_PRINT_COLOR_MODE, CUPS_PRINT_COLOR_MODE_MONOCHROME);
+}
+
+bool nsPrinterCUPS::SupportsCollation() const {
+ // We can't depend on cupsGetIntegerOption existing.
+ const char* const value = mShim.cupsGetOption(
+ "printer-type", mPrinter->num_options, mPrinter->options);
+ if (!value) {
+ return false;
+ }
+ // If the value is non-numeric, then atoi will return 0, which will still
+ // cause this function to return false.
+ const int type = atoi(value);
+ return type & CUPS_PRINTER_COLLATE;
+}
+
+nsPrinterBase::PrinterInfo nsPrinterCUPS::CreatePrinterInfo() const {
+ Connection connection{mShim};
+ return PrinterInfo{PaperList(connection), DefaultSettings(connection)};
+}
+
+bool nsPrinterCUPS::Supports(const char* aOption, const char* aValue) const {
+ auto printerInfoLock = TryEnsurePrinterInfo();
+ cups_dinfo_t* const printerInfo = printerInfoLock->mPrinterInfo;
+ return mShim.cupsCheckDestSupported(CUPS_HTTP_DEFAULT, mPrinter, printerInfo,
+ aOption, aValue);
+}
+
+bool nsPrinterCUPS::IsCUPSVersionAtLeast(uint64_t aCUPSMajor,
+ uint64_t aCUPSMinor,
+ uint64_t aCUPSPatch) const {
+ auto printerInfoLock = TryEnsurePrinterInfo();
+ // Compare major version.
+ if (printerInfoLock->mCUPSMajor > aCUPSMajor) {
+ return true;
+ }
+ if (printerInfoLock->mCUPSMajor < aCUPSMajor) {
+ return false;
+ }
+
+ // Compare minor version.
+ if (printerInfoLock->mCUPSMinor > aCUPSMinor) {
+ return true;
+ }
+ if (printerInfoLock->mCUPSMinor < aCUPSMinor) {
+ return false;
+ }
+
+ // Compare patch.
+ return aCUPSPatch <= printerInfoLock->mCUPSPatch;
+}
+
+http_t* nsPrinterCUPS::Connection::GetConnection(cups_dest_t* aDest) {
+ if (mWasInited) {
+ return mConnection;
+ }
+ mWasInited = true;
+
+ // blocking call
+ http_t* const connection = mShim.cupsConnectDest(aDest, CUPS_DEST_FLAGS_NONE,
+ /* timeout(ms) */ 5000,
+ /* cancel */ nullptr,
+ /* resource */ nullptr,
+ /* resourcesize */ 0,
+ /* callback */ nullptr,
+ /* user_data */ nullptr);
+ if (connection) {
+ mConnection = connection;
+ }
+ return mConnection;
+}
+
+nsPrinterCUPS::Connection::~Connection() {
+ if (mWasInited && mConnection) {
+ mShim.httpClose(mConnection);
+ }
+}
+
+PrintSettingsInitializer nsPrinterCUPS::DefaultSettings(
+ Connection& aConnection) const {
+ nsString printerName;
+ GetPrinterName(printerName);
+ auto printerInfoLock = TryEnsurePrinterInfo();
+ cups_dinfo_t* const printerInfo = printerInfoLock->mPrinterInfo;
+
+ cups_size_t media;
+
+// cupsGetDestMediaDefault appears to return more accurate defaults on macOS,
+// and the IPP attribute appears to return more accurate defaults on Linux.
+#ifdef XP_MACOSX
+ bool hasDefaultMedia =
+ mShim.cupsGetDestMediaDefault(CUPS_HTTP_DEFAULT, mPrinter, printerInfo,
+ CUPS_MEDIA_FLAGS_DEFAULT, &media);
+#else
+ ipp_attribute_t* defaultMediaIPP = mShim.cupsFindDestDefault(
+ CUPS_HTTP_DEFAULT, mPrinter, printerInfo, "media");
+
+ const char* defaultMediaName =
+ mShim.ippGetString(defaultMediaIPP, 0, nullptr);
+
+ bool hasDefaultMedia = mShim.cupsGetDestMediaByName(
+ CUPS_HTTP_DEFAULT, mPrinter, printerInfo, defaultMediaName,
+ CUPS_MEDIA_FLAGS_DEFAULT, &media);
+#endif
+
+ if (!hasDefaultMedia) {
+ return PrintSettingsInitializer{
+ std::move(printerName),
+ PaperInfo(),
+ SupportsColor(),
+ };
+ }
+
+ // Check if this is a localized fallback paper size, in which case we can
+ // avoid using the CUPS localization methods.
+ const gfx::SizeDouble sizeDouble{
+ media.width * kPointsPerHundredthMillimeter,
+ media.length * kPointsPerHundredthMillimeter};
+ if (const PaperInfo* const paperInfo = FindCommonPaperSize(sizeDouble)) {
+ return PrintSettingsInitializer{
+ std::move(printerName),
+ MakePaperInfo(paperInfo->mName, media),
+ SupportsColor(),
+ };
+ }
+
+ http_t* const connection = aConnection.GetConnection(mPrinter);
+ // XXX Do we actually have the guarantee that this is utf-8?
+ NS_ConvertUTF8toUTF16 localizedName{
+ connection ? LocalizeMediaName(*connection, media) : ""};
+
+ return PrintSettingsInitializer{
+ std::move(printerName),
+ MakePaperInfo(localizedName, media),
+ SupportsColor(),
+ };
+}
+
+nsTArray<mozilla::PaperInfo> nsPrinterCUPS::PaperList(
+ Connection& aConnection) const {
+ http_t* const connection = aConnection.GetConnection(mPrinter);
+ auto printerInfoLock = TryEnsurePrinterInfo(connection);
+ cups_dinfo_t* const printerInfo = printerInfoLock->mPrinterInfo;
+
+ if (!printerInfo) {
+ return {};
+ }
+
+ const int paperCount = mShim.cupsGetDestMediaCount(
+ connection, mPrinter, printerInfo, CUPS_MEDIA_FLAGS_DEFAULT);
+ nsTArray<PaperInfo> paperList;
+ nsTHashtable<nsCharPtrHashKey> paperSet(std::max(paperCount, 0));
+
+ paperList.SetCapacity(paperCount);
+ for (int i = 0; i < paperCount; ++i) {
+ cups_size_t media;
+ const int getInfoSucceeded = mShim.cupsGetDestMediaByIndex(
+ connection, mPrinter, printerInfo, i, CUPS_MEDIA_FLAGS_DEFAULT, &media);
+
+ if (!getInfoSucceeded || !paperSet.EnsureInserted(media.media)) {
+ continue;
+ }
+ // Check if this is a PWG paper size, in which case we can avoid using the
+ // CUPS localization methods.
+ const gfx::SizeDouble sizeDouble{
+ media.width * kPointsPerHundredthMillimeter,
+ media.length * kPointsPerHundredthMillimeter};
+ if (const PaperInfo* const paperInfo = FindCommonPaperSize(sizeDouble)) {
+ paperList.AppendElement(MakePaperInfo(paperInfo->mName, media));
+ } else {
+ const char* const mediaName =
+ connection ? LocalizeMediaName(*connection, media) : media.media;
+ paperList.AppendElement(
+ MakePaperInfo(NS_ConvertUTF8toUTF16(mediaName), media));
+ }
+ }
+
+ return paperList;
+}
+
+nsPrinterCUPS::PrinterInfoLock nsPrinterCUPS::TryEnsurePrinterInfo(
+ http_t* const aConnection) const {
+ PrinterInfoLock lock = mPrinterInfoMutex.Lock();
+ if (lock->mPrinterInfo ||
+ (aConnection == CUPS_HTTP_DEFAULT ? lock->mTriedInitWithDefault
+ : lock->mTriedInitWithConnection)) {
+ return lock;
+ }
+
+ if (aConnection == CUPS_HTTP_DEFAULT) {
+ lock->mTriedInitWithDefault = true;
+ } else {
+ lock->mTriedInitWithConnection = true;
+ }
+
+ // All CUPS calls that take the printer info do null-checks internally, so we
+ // can fetch this info and only worry about the result of the later CUPS
+ // functions.
+ lock->mPrinterInfo = mShim.cupsCopyDestInfo(aConnection, mPrinter);
+
+ // Even if we failed to fetch printer info, it is still possible we can talk
+ // to the print server and get its CUPS version.
+ FetchCUPSVersionForPrinter(mShim, mPrinter, lock->mCUPSMajor,
+ lock->mCUPSMinor, lock->mCUPSPatch);
+ return lock;
+}
+
+void nsPrinterCUPS::ForEachExtraMonochromeSetting(
+ FunctionRef<void(const nsACString&, const nsACString&)> aCallback) {
+ nsAutoCString pref;
+ Preferences::GetCString("print.cups.monochrome.extra_settings", pref);
+ if (pref.IsEmpty()) {
+ return;
+ }
+
+ for (const auto& pair : pref.Split(',')) {
+ auto splitter = pair.Split(':');
+ auto end = splitter.end();
+
+ auto key = splitter.begin();
+ if (key == end) {
+ continue;
+ }
+
+ auto value = ++splitter.begin();
+ if (value == end) {
+ continue;
+ }
+
+ aCallback(*key, *value);
+ }
+}
diff --git a/widget/nsPrinterCUPS.h b/widget/nsPrinterCUPS.h
new file mode 100644
index 0000000000..67ffcecbbd
--- /dev/null
+++ b/widget/nsPrinterCUPS.h
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsPrinterCUPS_h___
+#define nsPrinterCUPS_h___
+
+#include "nsPrinterBase.h"
+#include "nsPrintSettingsImpl.h"
+#include "nsCUPSShim.h"
+#include "nsString.h"
+
+#include "mozilla/DataMutex.h"
+#include "mozilla/FunctionRef.h"
+#include "mozilla/RecursiveMutex.h"
+
+/**
+ * @brief Interface to help implementing nsIPrinter using a CUPS printer.
+ */
+class nsPrinterCUPS final : public nsPrinterBase {
+ public:
+ NS_IMETHOD GetName(nsAString& aName) override;
+ NS_IMETHOD GetSystemName(nsAString& aName) override;
+ bool SupportsDuplex() const final;
+ bool SupportsColor() const final;
+ bool SupportsMonochrome() const final;
+ bool SupportsCollation() const final;
+ PrinterInfo CreatePrinterInfo() const final;
+ MarginDouble GetMarginsForPaper(nsString aPaperId) const final {
+ MOZ_ASSERT_UNREACHABLE(
+ "The CUPS API requires us to always get the margin when fetching the "
+ "paper list so there should be no need to query it separately");
+ return {};
+ }
+
+ nsPrinterCUPS() = delete;
+
+ nsPrinterCUPS(const mozilla::CommonPaperInfoArray* aArray,
+ const nsCUPSShim& aShim, nsString aDisplayName,
+ cups_dest_t* aPrinter)
+ : nsPrinterBase(aArray),
+ mShim(aShim),
+ mDisplayName(std::move(aDisplayName)),
+ mPrinter(aPrinter),
+ mPrinterInfoMutex("nsPrinterCUPS::mPrinterInfoMutex") {}
+
+ static void ForEachExtraMonochromeSetting(
+ mozilla::FunctionRef<void(const nsACString&, const nsACString&)>);
+
+ private:
+ struct CUPSPrinterInfo {
+ cups_dinfo_t* mPrinterInfo = nullptr;
+ uint64_t mCUPSMajor = 0;
+ uint64_t mCUPSMinor = 0;
+ uint64_t mCUPSPatch = 0;
+
+ // Whether we have attempted to fetch mPrinterInfo with CUPS_HTTP_DEFAULT.
+ bool mTriedInitWithDefault = false;
+ // Whether we have attempted to fetch mPrinterInfo with a connection.
+ bool mTriedInitWithConnection = false;
+ CUPSPrinterInfo() = default;
+ CUPSPrinterInfo(const CUPSPrinterInfo&) = delete;
+ CUPSPrinterInfo(CUPSPrinterInfo&&) = delete;
+ };
+
+ using PrinterInfoMutex =
+ mozilla::DataMutexBase<CUPSPrinterInfo, mozilla::RecursiveMutex>;
+
+ using PrinterInfoLock = PrinterInfoMutex::AutoLock;
+
+ ~nsPrinterCUPS();
+
+ /**
+ * Retrieves the localized name for a given media (paper).
+ * Returns nullptr if the name cannot be localized.
+ */
+ const char* LocalizeMediaName(http_t& aConnection, cups_size_t& aMedia) const;
+
+ void GetPrinterName(nsAString& aName) const;
+
+ // Little util for getting support flags using the direct CUPS names.
+ bool Supports(const char* aOption, const char* aValue) const;
+
+ // Returns support value if CUPS meets the minimum version, otherwise returns
+ // |aDefault|
+ bool IsCUPSVersionAtLeast(uint64_t aCUPSMajor, uint64_t aCUPSMinor,
+ uint64_t aCUPSPatch) const;
+
+ class Connection {
+ public:
+ http_t* GetConnection(cups_dest_t* aDest);
+
+ inline explicit Connection(const nsCUPSShim& aShim) : mShim(aShim) {}
+ Connection() = delete;
+ ~Connection();
+
+ protected:
+ const nsCUPSShim& mShim;
+ http_t* mConnection = CUPS_HTTP_DEFAULT;
+ bool mWasInited = false;
+ };
+
+ PrintSettingsInitializer DefaultSettings(Connection& aConnection) const;
+ nsTArray<mozilla::PaperInfo> PaperList(Connection& aConnection) const;
+ /**
+ * Attempts to populate the CUPSPrinterInfo object.
+ * This usually works with the CUPS default connection,
+ * but has been known to require an established connection
+ * on older versions of Ubuntu (18 and below).
+ */
+ PrinterInfoLock TryEnsurePrinterInfo(
+ http_t* const aConnection = CUPS_HTTP_DEFAULT) const;
+
+ const nsCUPSShim& mShim;
+ nsString mDisplayName;
+ cups_dest_t* mPrinter;
+ mutable PrinterInfoMutex mPrinterInfoMutex;
+};
+
+// There's no standard setting in Core Printing for monochrome. Or rather,
+// there is (PMSetColorMode) but it does nothing. Similarly, the relevant gtk
+// setting only works on Windows, yay.
+//
+// So on CUPS the right setting to use depends on the print driver. So we set /
+// look for a variety of driver-specific keys that are known to work across
+// printers.
+//
+// We set all the known settings, because the alternative to that is parsing ppd
+// files from the printer and find the relevant known choices that can apply,
+// and that is a lot more complex, similarly sketchy (requires the same amount
+// of driver-specific knowledge), and requires using deprecated CUPS APIs.
+#define CUPS_EACH_MONOCHROME_PRINTER_SETTING(macro_) \
+ macro_("ColorModel", "Gray") /* Generic */ \
+ macro_("BRMonoColor", "Mono") /* Brother */ \
+ macro_("BRPrintQuality", "Black") /* Brother */ \
+ macro_("CNIJGrayScale", "1") /* Canon */ \
+ macro_("INK", "MONO") /* Epson */ \
+ macro_("HPColorMode", "GrayscalePrint") /* HP */ \
+ macro_("ColorMode", "Mono") /* Samsung */ \
+ macro_("PrintoutMode", "Normal.Gray") /* Foomatic */ \
+ macro_("ProcessColorModel", "Mono") /* Samsung */ \
+ macro_("ARCMode", "CMBW") /* Sharp */ \
+ macro_("XRXColor", "BW") /* Xerox */
+
+#endif /* nsPrinterCUPS_h___ */
diff --git a/widget/nsPrinterListBase.cpp b/widget/nsPrinterListBase.cpp
new file mode 100644
index 0000000000..cf5e22b7f6
--- /dev/null
+++ b/widget/nsPrinterListBase.cpp
@@ -0,0 +1,160 @@
+/* -*- 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 "nsPrinterListBase.h"
+#include "PrintBackgroundTask.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/intl/Localization.h"
+#include "xpcpublic.h"
+
+using mozilla::ErrorResult;
+using mozilla::intl::Localization;
+using PrinterInfo = nsPrinterListBase::PrinterInfo;
+
+nsPrinterListBase::nsPrinterListBase() = default;
+nsPrinterListBase::~nsPrinterListBase() = default;
+
+NS_IMPL_CYCLE_COLLECTION(nsPrinterListBase, mPrintersPromise)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPrinterListBase)
+ NS_INTERFACE_MAP_ENTRY(nsIPrinterList)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrinterList)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPrinterListBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPrinterListBase)
+
+namespace mozilla {
+
+template <>
+void ResolveOrReject(dom::Promise& aPromise, nsPrinterListBase& aList,
+ const nsTArray<PrinterInfo>& aInfo) {
+ nsTArray<RefPtr<nsIPrinter>> printers;
+ printers.SetCapacity(aInfo.Length());
+ for (auto& info : aInfo) {
+ printers.AppendElement(aList.CreatePrinter(info));
+ }
+ aPromise.MaybeResolve(printers);
+}
+
+template <>
+void ResolveOrReject(dom::Promise& aPromise, nsPrinterListBase& aList,
+ const Maybe<PrinterInfo>& aInfo) {
+ if (aInfo) {
+ aPromise.MaybeResolve(aList.CreatePrinter(aInfo.value()));
+ } else {
+ aPromise.MaybeRejectWithNotFoundError("Printer not found");
+ }
+}
+
+} // namespace mozilla
+
+NS_IMETHODIMP nsPrinterListBase::GetPrinters(JSContext* aCx,
+ Promise** aResult) {
+ EnsureCommonPaperInfo(aCx);
+ return mozilla::AsyncPromiseAttributeGetter(*this, mPrintersPromise, aCx,
+ aResult, "Printers"_ns,
+ &nsPrinterListBase::Printers);
+}
+
+NS_IMETHODIMP nsPrinterListBase::GetPrinterByName(const nsAString& aPrinterName,
+ JSContext* aCx,
+ Promise** aResult) {
+ EnsureCommonPaperInfo(aCx);
+ return PrintBackgroundTaskPromise(*this, aCx, aResult, "PrinterByName"_ns,
+ &nsPrinterListBase::PrinterByName,
+ nsString{aPrinterName});
+}
+
+NS_IMETHODIMP nsPrinterListBase::GetPrinterBySystemName(
+ const nsAString& aPrinterName, JSContext* aCx, Promise** aResult) {
+ EnsureCommonPaperInfo(aCx);
+ return PrintBackgroundTaskPromise(
+ *this, aCx, aResult, "PrinterBySystemName"_ns,
+ &nsPrinterListBase::PrinterBySystemName, nsString{aPrinterName});
+}
+
+NS_IMETHODIMP nsPrinterListBase::GetNamedOrDefaultPrinter(
+ const nsAString& aPrinterName, JSContext* aCx, Promise** aResult) {
+ EnsureCommonPaperInfo(aCx);
+ return PrintBackgroundTaskPromise(
+ *this, aCx, aResult, "NamedOrDefaultPrinter"_ns,
+ &nsPrinterListBase::NamedOrDefaultPrinter, nsString{aPrinterName});
+}
+
+Maybe<PrinterInfo> nsPrinterListBase::NamedOrDefaultPrinter(
+ nsString aName) const {
+ if (Maybe<PrinterInfo> value = PrinterByName(std::move(aName))) {
+ return value;
+ }
+
+ // Since the name had to be passed by-value, we can re-use it to fetch the
+ // default printer name, potentially avoiding an extra string allocation.
+ if (NS_SUCCEEDED(SystemDefaultPrinterName(aName))) {
+ return PrinterByName(std::move(aName));
+ }
+
+ return Nothing();
+}
+
+NS_IMETHODIMP nsPrinterListBase::GetFallbackPaperList(JSContext* aCx,
+ Promise** aResult) {
+ ErrorResult rv;
+ nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx);
+ RefPtr<Promise> promise = Promise::Create(global, rv);
+ if (MOZ_UNLIKELY(rv.Failed())) {
+ *aResult = nullptr;
+ return rv.StealNSResult();
+ }
+
+ EnsureCommonPaperInfo(aCx);
+ nsTArray<RefPtr<nsPaper>> papers;
+ papers.SetCapacity(nsPaper::kNumCommonPaperSizes);
+ for (const auto& info : *mCommonPaperInfo) {
+ papers.AppendElement(MakeRefPtr<nsPaper>(info));
+ }
+
+ promise->MaybeResolve(papers);
+ promise.forget(aResult);
+ return NS_OK;
+}
+
+void nsPrinterListBase::EnsureCommonPaperInfo(JSContext* aCx) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ if (mCommonPaperInfo) {
+ return;
+ }
+ RefPtr<CommonPaperInfoArray> localizedPaperInfo =
+ MakeRefPtr<CommonPaperInfoArray>();
+ CommonPaperInfoArray& paperArray = *localizedPaperInfo;
+ // Apply localization to the names while constructing the PaperInfo, if
+ // available (otherwise leave them as the internal keys, which are at least
+ // somewhat recognizable).
+ IgnoredErrorResult rv;
+ nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx);
+ RefPtr<Localization> l10n = Localization::Create(global, true, {});
+ l10n->AddResourceId(u"toolkit/printing/printUI.ftl"_ns);
+
+ for (auto i : IntegerRange(nsPaper::kNumCommonPaperSizes)) {
+ const CommonPaperSize& size = nsPaper::kCommonPaperSizes[i];
+ PaperInfo& info = paperArray[i];
+
+ nsAutoCString key{"printui-paper-"};
+ key.Append(size.mLocalizableNameKey);
+ nsAutoCString name;
+ l10n->FormatValueSync(aCx, key, {}, name, rv);
+
+ // Fill out the info with our PWG size and the localized name.
+ info.mId = size.mPWGName;
+ CopyUTF8toUTF16(
+ (rv.Failed() || name.IsEmpty())
+ ? static_cast<const nsCString&>(size.mLocalizableNameKey)
+ : name,
+ info.mName);
+ info.mSize = size.mSize;
+ info.mUnwriteableMargin = Some(MarginDouble{});
+ }
+ mCommonPaperInfo = std::move(localizedPaperInfo);
+}
diff --git a/widget/nsPrinterListBase.h b/widget/nsPrinterListBase.h
new file mode 100644
index 0000000000..9f922d74b5
--- /dev/null
+++ b/widget/nsPrinterListBase.h
@@ -0,0 +1,92 @@
+/* -*- 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 nsPrinterListBase_h__
+#define nsPrinterListBase_h__
+
+#include "nsIPrinterList.h"
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupportsImpl.h"
+#include "nsPaper.h"
+#include "nsString.h"
+
+class nsPrinterListBase : public nsIPrinterList {
+ public:
+ using Promise = mozilla::dom::Promise;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsPrinterListBase)
+ NS_IMETHOD GetSystemDefaultPrinterName(nsAString& aName) final {
+ return SystemDefaultPrinterName(aName);
+ }
+ NS_IMETHOD GetPrinters(JSContext*, Promise**) final;
+ NS_IMETHOD GetPrinterByName(const nsAString& aPrinterName, JSContext* aCx,
+ Promise** aResult) final;
+ NS_IMETHOD GetPrinterBySystemName(const nsAString& aPrinterName,
+ JSContext* aCx, Promise** aResult) final;
+ NS_IMETHOD GetNamedOrDefaultPrinter(const nsAString& aPrinterName,
+ JSContext* aCx, Promise** aResult) final;
+ NS_IMETHOD GetFallbackPaperList(JSContext*, Promise**) final;
+
+ struct PrinterInfo {
+ // Both windows and CUPS: The name of the printer.
+ nsString mName;
+ // CUPS only: Handle to owned cups_dest_t.
+ void* mCupsHandle = nullptr;
+ };
+
+ // Called off the main thread, collect information to create an appropriate
+ // list of printers.
+ virtual nsTArray<PrinterInfo> Printers() const = 0;
+
+ // Create an nsIPrinter object given the information we obtained from the
+ // background thread.
+ virtual RefPtr<nsIPrinter> CreatePrinter(PrinterInfo) const = 0;
+
+ mozilla::Maybe<PrinterInfo> NamedOrDefaultPrinter(nsString aName) const;
+
+ // No copy or move, we're an identity.
+ nsPrinterListBase(const nsPrinterListBase&) = delete;
+ nsPrinterListBase(nsPrinterListBase&&) = delete;
+
+ protected:
+ nsPrinterListBase();
+ virtual ~nsPrinterListBase();
+
+ // This could be implemented in terms of Printers() and then searching the
+ // returned printer info for a printer of the given name, but we expect
+ // backends to have more efficient methods of implementing this.
+ virtual mozilla::Maybe<PrinterInfo> PrinterByName(nsString aName) const = 0;
+
+ // Same as NamedPrinter, but uses the system name.
+ // Depending on whether or not there is a more efficient way to address the
+ // printer for a given backend, this may or may not be equivalent to
+ // NamedPrinter.
+ virtual mozilla::Maybe<PrinterInfo> PrinterBySystemName(
+ nsString aName) const = 0;
+
+ // This is implemented separately from the IDL interface version so that it
+ // can be made const, which allows it to be used while resolving promises.
+ virtual nsresult SystemDefaultPrinterName(nsAString&) const = 0;
+
+ // Return "paper" sizes to be supported by the Save to PDF destination;
+ // for actual printer drivers the list is retrieved from nsIPrinter.
+ nsTArray<RefPtr<nsPaper>> FallbackPaperList() const;
+
+ // Constructs mCommonPaperInfo by localizing the sizes in
+ // nsPaper::kCommonPaperSizes and creating corresponding PaperInfo.
+ void EnsureCommonPaperInfo(JSContext* aCx);
+
+ RefPtr<Promise> mPrintersPromise;
+ // PaperInfo for our fallback sizes and common size localization.
+ // This field contains the same data for every instance of this class.
+ // It's unfortunate that this needs to be a member rather than static data
+ // like nsPaper::kCommonPaperSizes, but that's because PaperInfo contains
+ // localized data, and we need a JSContext to do the localization.
+ RefPtr<const mozilla::CommonPaperInfoArray> mCommonPaperInfo;
+};
+
+#endif
diff --git a/widget/nsPrinterListCUPS.cpp b/widget/nsPrinterListCUPS.cpp
new file mode 100644
index 0000000000..960fb32c41
--- /dev/null
+++ b/widget/nsPrinterListCUPS.cpp
@@ -0,0 +1,201 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsPrinterListCUPS.h"
+
+#include "mozilla/IntegerRange.h"
+#include "mozilla/Maybe.h"
+#include "nsCUPSShim.h"
+#include "nsPrinterCUPS.h"
+#include "nsString.h"
+#include "prenv.h"
+
+// Use a local static to initialize the CUPS shim lazily, when it's needed.
+// This is used in order to avoid a global constructor.
+static nsCUPSShim& CupsShim() {
+ static nsCUPSShim sCupsShim;
+ return sCupsShim;
+}
+
+using PrinterInfo = nsPrinterListBase::PrinterInfo;
+
+/**
+ * Retrieves a human-readable name for the printer from CUPS.
+ * https://www.cups.org/doc/cupspm.html#basic-destination-information
+ */
+static void GetDisplayNameForPrinter(const cups_dest_t& aDest,
+ nsAString& aName) {
+// macOS clients expect prettified printer names
+// while GTK clients expect non-prettified names.
+// If you change this, please change NamedPrinter accordingly.
+#ifdef XP_MACOSX
+ const char* displayName = CupsShim().cupsGetOption(
+ "printer-info", aDest.num_options, aDest.options);
+ if (displayName) {
+ CopyUTF8toUTF16(MakeStringSpan(displayName), aName);
+ }
+#endif
+}
+
+NS_IMETHODIMP
+nsPrinterListCUPS::InitPrintSettingsFromPrinter(
+ const nsAString& aPrinterName, nsIPrintSettings* aPrintSettings) {
+ MOZ_ASSERT(aPrintSettings);
+
+ // Set a default file name.
+ nsAutoString filename;
+ nsresult rv = aPrintSettings->GetToFileName(filename);
+ if (NS_FAILED(rv) || filename.IsEmpty()) {
+ const char* path = PR_GetEnv("PWD");
+ if (!path) {
+ path = PR_GetEnv("HOME");
+ }
+
+ if (path) {
+ CopyUTF8toUTF16(mozilla::MakeStringSpan(path), filename);
+ filename.AppendLiteral("/mozilla.pdf");
+ } else {
+ filename.AssignLiteral("mozilla.pdf");
+ }
+
+ aPrintSettings->SetToFileName(filename);
+ }
+
+ aPrintSettings->SetIsInitializedFromPrinter(true);
+ return NS_OK;
+}
+
+static int CupsDestCallback(void* user_data, unsigned aFlags,
+ cups_dest_t* aDest) {
+ MOZ_ASSERT(user_data);
+ nsTArray<PrinterInfo>* printerInfoList =
+ reinterpret_cast<nsTArray<PrinterInfo>*>(user_data);
+
+ cups_dest_t* ownedDest = nullptr;
+ mozilla::DebugOnly<const int> numCopied =
+ CupsShim().cupsCopyDest(aDest, 0, &ownedDest);
+ MOZ_ASSERT(numCopied == 1);
+
+ nsString name;
+ GetDisplayNameForPrinter(*aDest, name);
+
+ printerInfoList->AppendElement(PrinterInfo{std::move(name), ownedDest});
+
+ return aFlags == CUPS_DEST_FLAGS_MORE ? 1 : 0;
+}
+
+nsTArray<PrinterInfo> nsPrinterListCUPS::Printers() const {
+ if (!CupsShim().InitOkay()) {
+ return {};
+ }
+
+ nsTArray<PrinterInfo> printerInfoList;
+ if (!CupsShim().cupsEnumDests(
+ CUPS_DEST_FLAGS_NONE,
+ 0 /* timeout, 0 timeout shouldn't be a problem since we are masking
+ CUPS_PRINTER_DISCOVERED */
+ ,
+ nullptr /* cancel* */, CUPS_PRINTER_LOCAL,
+ CUPS_PRINTER_FAX | CUPS_PRINTER_SCANNER | CUPS_PRINTER_DISCOVERED,
+ &CupsDestCallback, &printerInfoList)) {
+ return {};
+ }
+
+ return printerInfoList;
+}
+
+RefPtr<nsIPrinter> nsPrinterListCUPS::CreatePrinter(PrinterInfo aInfo) const {
+ return mozilla::MakeRefPtr<nsPrinterCUPS>(
+ mCommonPaperInfo, CupsShim(), std::move(aInfo.mName),
+ static_cast<cups_dest_t*>(aInfo.mCupsHandle));
+}
+
+mozilla::Maybe<PrinterInfo> nsPrinterListCUPS::PrinterByName(
+ nsString aPrinterName) const {
+ mozilla::Maybe<PrinterInfo> rv;
+ if (!CupsShim().InitOkay()) {
+ return rv;
+ }
+
+ // Will contain the printer, if found. This must be fully owned, and not a
+ // member of another array of printers.
+ cups_dest_t* printer = nullptr;
+
+#ifdef XP_MACOSX
+ // On OS X the printer name given to this function is the readable/display
+ // name and not the CUPS name, so we iterate over all the printers for now.
+ // See bug 1659807 for one approach to improve perf here.
+ {
+ nsAutoCString printerName;
+ CopyUTF16toUTF8(aPrinterName, printerName);
+ cups_dest_t* printers = nullptr;
+ const auto numPrinters = CupsShim().cupsGetDests(&printers);
+ for (auto i : mozilla::IntegerRange(0, numPrinters)) {
+ const char* const displayName = CupsShim().cupsGetOption(
+ "printer-info", printers[i].num_options, printers[i].options);
+ if (printerName == displayName) {
+ // The second arg to CupsShim().cupsCopyDest is called num_dests, but
+ // it actually copies num_dests + 1 elements.
+ CupsShim().cupsCopyDest(printers + i, 0, &printer);
+ break;
+ }
+ }
+ CupsShim().cupsFreeDests(numPrinters, printers);
+ }
+#else
+ // On GTK, we only ever show the CUPS name of printers, so we can use
+ // cupsGetNamedDest directly.
+ {
+ const auto printerName = NS_ConvertUTF16toUTF8(aPrinterName);
+ printer = CupsShim().cupsGetNamedDest(CUPS_HTTP_DEFAULT, printerName.get(),
+ nullptr);
+ }
+#endif
+
+ if (printer) {
+ // Since the printer name had to be passed by-value, we can move the
+ // name from that.
+ rv.emplace(PrinterInfo{std::move(aPrinterName), printer});
+ }
+ return rv;
+}
+
+mozilla::Maybe<PrinterInfo> nsPrinterListCUPS::PrinterBySystemName(
+ nsString aPrinterName) const {
+ mozilla::Maybe<PrinterInfo> rv;
+ if (!CupsShim().InitOkay()) {
+ return rv;
+ }
+
+ const auto printerName = NS_ConvertUTF16toUTF8(aPrinterName);
+ if (cups_dest_t* const printer = CupsShim().cupsGetNamedDest(
+ CUPS_HTTP_DEFAULT, printerName.get(), nullptr)) {
+ rv.emplace(PrinterInfo{std::move(aPrinterName), printer});
+ }
+ return rv;
+}
+
+nsresult nsPrinterListCUPS::SystemDefaultPrinterName(nsAString& aName) const {
+ aName.Truncate();
+
+ if (!CupsShim().InitOkay()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Passing in nullptr for the name will return the default, if any.
+ cups_dest_t* dest =
+ CupsShim().cupsGetNamedDest(CUPS_HTTP_DEFAULT, /* name */ nullptr,
+ /* instance */ nullptr);
+ if (!dest) {
+ return NS_OK;
+ }
+
+ GetDisplayNameForPrinter(*dest, aName);
+ if (aName.IsEmpty()) {
+ CopyUTF8toUTF16(mozilla::MakeStringSpan(dest->name), aName);
+ }
+
+ CupsShim().cupsFreeDests(1, dest);
+ return NS_OK;
+}
diff --git a/widget/nsPrinterListCUPS.h b/widget/nsPrinterListCUPS.h
new file mode 100644
index 0000000000..77fff571c6
--- /dev/null
+++ b/widget/nsPrinterListCUPS.h
@@ -0,0 +1,32 @@
+/* -*- 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 nsPrinterListCUPS_h__
+#define nsPrinterListCUPS_h__
+
+#include "nsPrinterListBase.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+template <typename T>
+class Maybe;
+} // namespace mozilla
+
+class nsPrinterListCUPS final : public nsPrinterListBase {
+ NS_IMETHOD InitPrintSettingsFromPrinter(const nsAString&,
+ nsIPrintSettings*) final;
+
+ nsTArray<PrinterInfo> Printers() const final;
+ RefPtr<nsIPrinter> CreatePrinter(PrinterInfo) const final;
+ mozilla::Maybe<PrinterInfo> PrinterByName(nsString aPrinterName) const final;
+ mozilla::Maybe<PrinterInfo> PrinterBySystemName(
+ nsString aPrinterName) const final;
+ nsresult SystemDefaultPrinterName(nsAString&) const final;
+
+ private:
+ ~nsPrinterListCUPS() override = default;
+};
+
+#endif
diff --git a/widget/nsShmImage.cpp b/widget/nsShmImage.cpp
new file mode 100644
index 0000000000..7208b7075f
--- /dev/null
+++ b/widget/nsShmImage.cpp
@@ -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 "nsShmImage.h"
+
+#ifdef MOZ_HAVE_SHMIMAGE
+# include "mozilla/X11Util.h"
+# include "mozilla/gfx/gfxVars.h"
+# include "mozilla/ipc/SharedMemory.h"
+# include "gfxPlatform.h"
+# include "nsPrintfCString.h"
+# include "nsTArray.h"
+
+# include <dlfcn.h>
+# include <errno.h>
+# include <string.h>
+# include <sys/ipc.h>
+# include <sys/shm.h>
+
+extern "C" {
+# include <X11/ImUtil.h>
+}
+
+using namespace mozilla::ipc;
+using namespace mozilla::gfx;
+
+nsShmImage::nsShmImage(Display* aDisplay, Drawable aWindow, Visual* aVisual,
+ unsigned int aDepth)
+ : mDisplay(aDisplay),
+ mConnection(XGetXCBConnection(aDisplay)),
+ mWindow(aWindow),
+ mVisual(aVisual),
+ mDepth(aDepth),
+ mFormat(mozilla::gfx::SurfaceFormat::UNKNOWN),
+ mSize(0, 0),
+ mStride(0),
+ mPixmap(XCB_NONE),
+ mGC(XCB_NONE),
+ mRequestPending(false),
+ mShmSeg(XCB_NONE),
+ mShmId(-1),
+ mShmAddr(nullptr) {
+ mozilla::PodZero(&mSyncRequest);
+}
+
+nsShmImage::~nsShmImage() { DestroyImage(); }
+
+// If XShm isn't available to our client, we'll try XShm once, fail,
+// set this to false and then never try again.
+static bool gShmAvailable = true;
+bool nsShmImage::UseShm() { return gShmAvailable; }
+
+bool nsShmImage::CreateShmSegment() {
+ size_t size = SharedMemory::PageAlignedSize(mStride * mSize.height);
+
+# if defined(__OpenBSD__) && defined(MOZ_SANDBOX)
+ static mozilla::LazyLogModule sPledgeLog("SandboxPledge");
+ MOZ_LOG(sPledgeLog, mozilla::LogLevel::Debug,
+ ("%s called when pledged, returning false\n", __func__));
+ return false;
+# endif
+ mShmId = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600);
+ if (mShmId == -1) {
+ return false;
+ }
+ mShmAddr = (uint8_t*)shmat(mShmId, nullptr, 0);
+ mShmSeg = xcb_generate_id(mConnection);
+
+ // Mark the handle removed so that it will destroy the segment when unmapped.
+ shmctl(mShmId, IPC_RMID, nullptr);
+
+ if (mShmAddr == (void*)-1) {
+ // Since mapping failed, the segment is already destroyed.
+ mShmId = -1;
+
+ nsPrintfCString warning("shmat(): %s (%d)\n", strerror(errno), errno);
+ NS_WARNING(warning.get());
+ return false;
+ }
+
+# ifdef DEBUG
+ struct shmid_ds info;
+ if (shmctl(mShmId, IPC_STAT, &info) < 0) {
+ return false;
+ }
+
+ MOZ_ASSERT(size <= info.shm_segsz, "Segment doesn't have enough space!");
+# endif
+
+ return true;
+}
+
+void nsShmImage::DestroyShmSegment() {
+ if (mShmId != -1) {
+ shmdt(mShmAddr);
+ mShmId = -1;
+ }
+}
+
+static bool gShmInitialized = false;
+static bool gUseShmPixmaps = false;
+
+bool nsShmImage::InitExtension() {
+ if (gShmInitialized) {
+ return gShmAvailable;
+ }
+
+ gShmInitialized = true;
+
+ // Bugs 1397918, 1293474 - race condition in libxcb fixed upstream as of
+ // version 1.11. Since we can't query libxcb's version directly, the only
+ // other option is to check for symbols that were added after 1.11.
+ // xcb_discard_reply64 was added in 1.11.1, so check for existence of
+ // that to verify we are using a version of libxcb with the bug fixed.
+ // Otherwise, we can't risk using libxcb due to aforementioned crashes.
+ if (!dlsym(RTLD_DEFAULT, "xcb_discard_reply64")) {
+ gShmAvailable = false;
+ return false;
+ }
+
+ const xcb_query_extension_reply_t* extReply;
+ extReply = xcb_get_extension_data(mConnection, &xcb_shm_id);
+ if (!extReply || !extReply->present) {
+ gShmAvailable = false;
+ return false;
+ }
+
+ xcb_shm_query_version_reply_t* shmReply = xcb_shm_query_version_reply(
+ mConnection, xcb_shm_query_version(mConnection), nullptr);
+
+ if (!shmReply) {
+ gShmAvailable = false;
+ return false;
+ }
+
+ gUseShmPixmaps = shmReply->shared_pixmaps &&
+ shmReply->pixmap_format == XCB_IMAGE_FORMAT_Z_PIXMAP;
+
+ free(shmReply);
+
+ return true;
+}
+
+bool nsShmImage::CreateImage(const IntSize& aSize) {
+ MOZ_ASSERT(mConnection && mVisual);
+
+ if (!InitExtension()) {
+ return false;
+ }
+
+ mSize = aSize;
+
+ BackendType backend = gfxVars::ContentBackend();
+
+ mFormat = SurfaceFormat::UNKNOWN;
+ switch (mDepth) {
+ case 32:
+ if (mVisual->red_mask == 0xff0000 && mVisual->green_mask == 0xff00 &&
+ mVisual->blue_mask == 0xff) {
+ mFormat = SurfaceFormat::B8G8R8A8;
+ }
+ break;
+ case 24:
+ // Only support the BGRX layout, and report it as BGRA to the compositor.
+ // The alpha channel will be discarded when we put the image.
+ // Cairo/pixman lacks some fast paths for compositing BGRX onto BGRA, so
+ // just report it as BGRX directly in that case.
+ if (mVisual->red_mask == 0xff0000 && mVisual->green_mask == 0xff00 &&
+ mVisual->blue_mask == 0xff) {
+ mFormat = backend == BackendType::CAIRO ? SurfaceFormat::B8G8R8X8
+ : SurfaceFormat::B8G8R8A8;
+ }
+ break;
+ case 16:
+ if (mVisual->red_mask == 0xf800 && mVisual->green_mask == 0x07e0 &&
+ mVisual->blue_mask == 0x1f) {
+ mFormat = SurfaceFormat::R5G6B5_UINT16;
+ }
+ break;
+ }
+
+ if (mFormat == SurfaceFormat::UNKNOWN) {
+ NS_WARNING("Unsupported XShm Image format!");
+ gShmAvailable = false;
+ return false;
+ }
+
+ // Round up stride to the display's scanline pad (in bits) as XShm expects.
+ int scanlinePad = _XGetScanlinePad(mDisplay, mDepth);
+ int bitsPerPixel = _XGetBitsPerPixel(mDisplay, mDepth);
+ int bitsPerLine =
+ ((bitsPerPixel * aSize.width + scanlinePad - 1) / scanlinePad) *
+ scanlinePad;
+ mStride = bitsPerLine / 8;
+
+ if (!CreateShmSegment()) {
+ DestroyImage();
+ return false;
+ }
+
+ xcb_generic_error_t* error;
+ xcb_void_cookie_t cookie;
+
+ cookie = xcb_shm_attach_checked(mConnection, mShmSeg, mShmId, 0);
+
+ if ((error = xcb_request_check(mConnection, cookie))) {
+ NS_WARNING("Failed to attach MIT-SHM segment.");
+ DestroyImage();
+ gShmAvailable = false;
+ free(error);
+ return false;
+ }
+
+ if (gUseShmPixmaps) {
+ mPixmap = xcb_generate_id(mConnection);
+ cookie = xcb_shm_create_pixmap_checked(mConnection, mPixmap, mWindow,
+ aSize.width, aSize.height, mDepth,
+ mShmSeg, 0);
+
+ if ((error = xcb_request_check(mConnection, cookie))) {
+ // Disable shared pixmaps permanently if creation failed.
+ mPixmap = XCB_NONE;
+ gUseShmPixmaps = false;
+ free(error);
+ }
+ }
+
+ return true;
+}
+
+void nsShmImage::DestroyImage() {
+ if (mGC) {
+ xcb_free_gc(mConnection, mGC);
+ mGC = XCB_NONE;
+ }
+ if (mPixmap != XCB_NONE) {
+ xcb_free_pixmap(mConnection, mPixmap);
+ mPixmap = XCB_NONE;
+ }
+ if (mShmSeg != XCB_NONE) {
+ xcb_shm_detach_checked(mConnection, mShmSeg);
+ mShmSeg = XCB_NONE;
+ }
+ DestroyShmSegment();
+ // Avoid leaking any pending reply. No real need to wait but CentOS 6 build
+ // machines don't have xcb_discard_reply().
+ WaitIfPendingReply();
+}
+
+// Wait for any in-flight shm-affected requests to complete.
+// Typically X clients would wait for a XShmCompletionEvent to be received,
+// but this works as it's sent immediately after the request is sent.
+void nsShmImage::WaitIfPendingReply() {
+ if (mRequestPending) {
+ xcb_get_input_focus_reply_t* reply =
+ xcb_get_input_focus_reply(mConnection, mSyncRequest, nullptr);
+ free(reply);
+ mRequestPending = false;
+ }
+}
+
+already_AddRefed<DrawTarget> nsShmImage::CreateDrawTarget(
+ const mozilla::LayoutDeviceIntRegion& aRegion) {
+ WaitIfPendingReply();
+
+ // Due to bug 1205045, we must avoid making GTK calls off the main thread to
+ // query window size. Instead we just track the largest offset within the
+ // image we are drawing to and grow the image to accomodate it. Since usually
+ // the entire window is invalidated on the first paint to it, this should grow
+ // the image to the necessary size quickly without many intermediate
+ // reallocations.
+ IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+ IntSize size(bounds.XMost(), bounds.YMost());
+ if (size.width > mSize.width || size.height > mSize.height) {
+ DestroyImage();
+ if (!CreateImage(size)) {
+ return nullptr;
+ }
+ }
+
+ return gfxPlatform::CreateDrawTargetForData(
+ reinterpret_cast<unsigned char*>(mShmAddr) + bounds.y * mStride +
+ bounds.x * BytesPerPixel(mFormat),
+ bounds.Size(), mStride, mFormat);
+}
+
+void nsShmImage::Put(const mozilla::LayoutDeviceIntRegion& aRegion) {
+ AutoTArray<xcb_rectangle_t, 32> xrects;
+ xrects.SetCapacity(aRegion.GetNumRects());
+
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const mozilla::LayoutDeviceIntRect& r = iter.Get();
+ xcb_rectangle_t xrect = {(short)r.x, (short)r.y, (unsigned short)r.width,
+ (unsigned short)r.height};
+ xrects.AppendElement(xrect);
+ }
+
+ if (!mGC) {
+ mGC = xcb_generate_id(mConnection);
+ xcb_create_gc(mConnection, mGC, mWindow, 0, nullptr);
+ }
+
+ xcb_set_clip_rectangles(mConnection, XCB_CLIP_ORDERING_YX_BANDED, mGC, 0, 0,
+ xrects.Length(), xrects.Elements());
+
+ if (mPixmap != XCB_NONE) {
+ xcb_copy_area(mConnection, mPixmap, mWindow, mGC, 0, 0, 0, 0, mSize.width,
+ mSize.height);
+ } else {
+ xcb_shm_put_image(mConnection, mWindow, mGC, mSize.width, mSize.height, 0,
+ 0, mSize.width, mSize.height, 0, 0, mDepth,
+ XCB_IMAGE_FORMAT_Z_PIXMAP, 0, mShmSeg, 0);
+ }
+
+ // Send a request that returns a response so that we don't have to start a
+ // sync in nsShmImage::CreateDrawTarget.
+ mSyncRequest = xcb_get_input_focus(mConnection);
+ mRequestPending = true;
+
+ xcb_flush(mConnection);
+}
+
+#endif // MOZ_HAVE_SHMIMAGE
diff --git a/widget/nsShmImage.h b/widget/nsShmImage.h
new file mode 100644
index 0000000000..2d92c39e0d
--- /dev/null
+++ b/widget/nsShmImage.h
@@ -0,0 +1,75 @@
+/* -*- 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_widget_nsShmImage_h__
+#define __mozilla_widget_nsShmImage_h__
+
+#if defined(MOZ_X11)
+# define MOZ_HAVE_SHMIMAGE
+#endif
+
+#ifdef MOZ_HAVE_SHMIMAGE
+
+# include "mozilla/gfx/2D.h"
+# include "nsIWidget.h"
+# include "Units.h"
+
+# include <X11/Xlib-xcb.h>
+# include <xcb/shm.h>
+
+class nsShmImage {
+ // bug 1168843, compositor thread may create shared memory instances that are
+ // destroyed by main thread on shutdown, so this must use thread-safe RC to
+ // avoid hitting assertion
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsShmImage)
+
+ public:
+ static bool UseShm();
+
+ already_AddRefed<mozilla::gfx::DrawTarget> CreateDrawTarget(
+ const mozilla::LayoutDeviceIntRegion& aRegion);
+
+ void Put(const mozilla::LayoutDeviceIntRegion& aRegion);
+
+ nsShmImage(Display* aDisplay, Drawable aWindow, Visual* aVisual,
+ unsigned int aDepth);
+
+ private:
+ ~nsShmImage();
+
+ bool InitExtension();
+
+ bool CreateShmSegment();
+ void DestroyShmSegment();
+
+ bool CreateImage(const mozilla::gfx::IntSize& aSize);
+ void DestroyImage();
+
+ void WaitIfPendingReply();
+
+ Display* mDisplay;
+ xcb_connection_t* mConnection;
+ Window mWindow;
+ Visual* mVisual;
+ unsigned int mDepth;
+
+ mozilla::gfx::SurfaceFormat mFormat;
+ mozilla::gfx::IntSize mSize;
+ int mStride;
+
+ xcb_pixmap_t mPixmap;
+ xcb_gcontext_t mGC;
+ xcb_get_input_focus_cookie_t mSyncRequest;
+ bool mRequestPending;
+
+ xcb_shm_seg_t mShmSeg;
+ int mShmId;
+ uint8_t* mShmAddr;
+};
+
+#endif // MOZ_HAVE_SHMIMAGE
+
+#endif
diff --git a/widget/nsSoundProxy.cpp b/widget/nsSoundProxy.cpp
new file mode 100644
index 0000000000..e991fa240e
--- /dev/null
+++ b/widget/nsSoundProxy.cpp
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/ContentChild.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "nsIURL.h"
+#include "nsIURI.h"
+#include "nsSoundProxy.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsSoundProxy, nsISound)
+
+NS_IMETHODIMP
+nsSoundProxy::Play(nsIURL* aURL) {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content);
+ // Only allow playing a chrome:// URL from the content process.
+ if (!aURL || !aURL->SchemeIs("chrome")) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ContentChild::GetSingleton()->SendPlaySound(aURL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSoundProxy::Beep() {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content);
+
+ ContentChild::GetSingleton()->SendBeep();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSoundProxy::Init() {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content);
+ MOZ_DIAGNOSTIC_ASSERT(false, "Only called by XUL in the parent process.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSoundProxy::PlayEventSound(uint32_t aEventId) {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content);
+
+ ContentChild::GetSingleton()->SendPlayEventSound(aEventId);
+ return NS_OK;
+}
diff --git a/widget/nsSoundProxy.h b/widget/nsSoundProxy.h
new file mode 100644
index 0000000000..d6ffc38ba1
--- /dev/null
+++ b/widget/nsSoundProxy.h
@@ -0,0 +1,22 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; 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 NS_SOUND_PROXY_H
+#define NS_SOUND_PROXY_H
+
+#include "nsISound.h"
+
+class nsSoundProxy final : public nsISound {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISOUND
+
+ nsSoundProxy() = default;
+
+ private:
+ ~nsSoundProxy() = default;
+};
+
+#endif
diff --git a/widget/nsTransferable.cpp b/widget/nsTransferable.cpp
new file mode 100644
index 0000000000..c82549a4d1
--- /dev/null
+++ b/widget/nsTransferable.cpp
@@ -0,0 +1,531 @@
+/* -*- 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/. */
+
+/*
+Notes to self:
+
+- at some point, strings will be accessible from JS, so we won't have to wrap
+ flavors in an nsISupportsCString. Until then, we're kinda stuck with
+ this crappy API of nsIArrays.
+
+*/
+
+#include "nsTransferable.h"
+#include "nsAnonymousTemporaryFile.h"
+#include "nsArray.h"
+#include "nsArrayUtils.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsTArray.h"
+#include "nsIFormatConverter.h"
+#include "nsIContentPolicy.h"
+#include "nsCOMPtr.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsMemory.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryService.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsILoadContext.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/UniquePtr.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsTransferable, nsITransferable)
+
+DataStruct::DataStruct(DataStruct&& aRHS)
+ : mData(aRHS.mData.forget()),
+ mCacheFD(aRHS.mCacheFD),
+ mFlavor(aRHS.mFlavor) {
+ aRHS.mCacheFD = nullptr;
+}
+
+//-------------------------------------------------------------------------
+DataStruct::~DataStruct() {
+ if (mCacheFD) {
+ PR_Close(mCacheFD);
+ }
+}
+
+//-------------------------------------------------------------------------
+
+void DataStruct::SetData(nsISupports* aData, bool aIsPrivateData) {
+ // Now, check to see if we consider the data to be "too large"
+ // as well as ensuring that private browsing mode is disabled.
+ // File IO is not allowed in content processes.
+ if (!aIsPrivateData && XRE_IsParentProcess()) {
+ void* data = nullptr;
+ uint32_t dataLen = 0;
+ nsPrimitiveHelpers::CreateDataFromPrimitive(mFlavor, aData, &data,
+ &dataLen);
+
+ if (dataLen > kLargeDatasetSize) {
+ // Too large, cache it to disk instead of memory.
+ if (NS_SUCCEEDED(WriteCache(data, dataLen))) {
+ free(data);
+ // Clear previously set small data.
+ mData = nullptr;
+ return;
+ }
+
+ NS_WARNING("Oh no, couldn't write data to the cache file");
+ }
+
+ free(data);
+ }
+
+ if (mCacheFD) {
+ // Clear previously set big data.
+ PR_Close(mCacheFD);
+ mCacheFD = nullptr;
+ }
+
+ mData = aData;
+}
+
+//-------------------------------------------------------------------------
+void DataStruct::GetData(nsISupports** aData) {
+ // check here to see if the data is cached on disk
+ if (mCacheFD) {
+ // if so, read it in and pass it back
+ // ReadCache creates memory and copies the data into it.
+ if (NS_SUCCEEDED(ReadCache(aData))) {
+ return;
+ }
+
+ // oh shit, something went horribly wrong here.
+ NS_WARNING("Oh no, couldn't read data in from the cache file");
+ *aData = nullptr;
+ PR_Close(mCacheFD);
+ mCacheFD = nullptr;
+ return;
+ }
+
+ nsCOMPtr<nsISupports> data = mData;
+ data.forget(aData);
+}
+
+//-------------------------------------------------------------------------
+nsresult DataStruct::WriteCache(void* aData, uint32_t aDataLen) {
+ MOZ_ASSERT(aData && aDataLen);
+ MOZ_ASSERT(aDataLen <= uint32_t(std::numeric_limits<int32_t>::max()),
+ "too large size for PR_Write");
+
+ nsresult rv;
+ if (!mCacheFD) {
+ rv = NS_OpenAnonymousTemporaryFile(&mCacheFD);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else if (PR_Seek64(mCacheFD, 0, PR_SEEK_SET) == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Write out the contents of the clipboard to the file.
+ int32_t written = PR_Write(mCacheFD, aData, aDataLen);
+ if (written == int32_t(aDataLen)) {
+ return NS_OK;
+ }
+
+ PR_Close(mCacheFD);
+ mCacheFD = nullptr;
+ return NS_ERROR_FAILURE;
+}
+
+//-------------------------------------------------------------------------
+nsresult DataStruct::ReadCache(nsISupports** aData) {
+ if (!mCacheFD) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRFileInfo fileInfo;
+ if (PR_GetOpenFileInfo(mCacheFD, &fileInfo) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ if (PR_Seek64(mCacheFD, 0, PR_SEEK_SET) == -1) {
+ return NS_ERROR_FAILURE;
+ }
+ uint32_t fileSize = fileInfo.size;
+
+ auto data = MakeUnique<char[]>(fileSize);
+ if (!data) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t actual = PR_Read(mCacheFD, data.get(), fileSize);
+ if (actual != fileSize) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsPrimitiveHelpers::CreatePrimitiveForData(mFlavor, data.get(), fileSize,
+ aData);
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+//
+// Transferable constructor
+//
+//-------------------------------------------------------------------------
+nsTransferable::nsTransferable()
+ : mPrivateData(false),
+ mContentPolicyType(nsIContentPolicy::TYPE_OTHER)
+#ifdef DEBUG
+ ,
+ mInitialized(false)
+#endif
+{
+}
+
+//-------------------------------------------------------------------------
+//
+// Transferable destructor
+//
+//-------------------------------------------------------------------------
+nsTransferable::~nsTransferable() = default;
+
+NS_IMETHODIMP
+nsTransferable::Init(nsILoadContext* aContext) {
+ MOZ_ASSERT(!mInitialized);
+
+ if (aContext) {
+ mPrivateData = aContext->UsePrivateBrowsing();
+ }
+#ifdef DEBUG
+ mInitialized = true;
+#endif
+ return NS_OK;
+}
+
+//
+// GetTransferDataFlavors
+//
+// Returns a copy of the internal list of flavors. This does NOT take into
+// account any converter that may be registered.
+//
+void nsTransferable::GetTransferDataFlavors(nsTArray<nsCString>& aFlavors) {
+ MOZ_ASSERT(mInitialized);
+
+ for (size_t i = 0; i < mDataArray.Length(); ++i) {
+ DataStruct& data = mDataArray.ElementAt(i);
+ aFlavors.AppendElement(data.GetFlavor());
+ }
+}
+
+Maybe<size_t> nsTransferable::FindDataFlavor(const char* aFlavor) {
+ nsDependentCString flavor(aFlavor);
+
+ for (size_t i = 0; i < mDataArray.Length(); ++i) {
+ if (mDataArray[i].GetFlavor().Equals(flavor)) {
+ return Some(i);
+ }
+ }
+
+ return Nothing();
+}
+
+//
+// GetTransferData
+//
+// Returns the data of the requested flavor, obtained from either having the
+// data on hand or using a converter to get it. The data is wrapped in a
+// nsISupports primitive so that it is accessible from JS.
+//
+NS_IMETHODIMP
+nsTransferable::GetTransferData(const char* aFlavor, nsISupports** aData) {
+ MOZ_ASSERT(mInitialized);
+
+ *aData = nullptr;
+
+ nsresult rv = NS_OK;
+
+ // First look and see if the data is present in one of the intrinsic flavors.
+ if (Maybe<size_t> index = FindDataFlavor(aFlavor)) {
+ nsCOMPtr<nsISupports> dataBytes;
+ mDataArray[index.value()].GetData(getter_AddRefs(dataBytes));
+
+ // Do we have a (lazy) data provider?
+ if (nsCOMPtr<nsIFlavorDataProvider> dataProvider =
+ do_QueryInterface(dataBytes)) {
+ rv =
+ dataProvider->GetFlavorData(this, aFlavor, getter_AddRefs(dataBytes));
+ if (NS_FAILED(rv)) {
+ dataBytes = nullptr;
+ // The provider failed, fall into the converter code below.
+ }
+ }
+
+ if (dataBytes) {
+ dataBytes.forget(aData);
+ return NS_OK;
+ }
+
+ // Empty data
+ }
+
+ // If not, try using a format converter to get the requested flavor.
+ if (mFormatConv) {
+ for (size_t i = 0; i < mDataArray.Length(); ++i) {
+ DataStruct& data = mDataArray.ElementAt(i);
+ bool canConvert = false;
+ mFormatConv->CanConvert(data.GetFlavor().get(), aFlavor, &canConvert);
+ if (canConvert) {
+ nsCOMPtr<nsISupports> dataBytes;
+ data.GetData(getter_AddRefs(dataBytes));
+
+ // Do we have a (lazy) data provider?
+ if (nsCOMPtr<nsIFlavorDataProvider> dataProvider =
+ do_QueryInterface(dataBytes)) {
+ rv = dataProvider->GetFlavorData(this, aFlavor,
+ getter_AddRefs(dataBytes));
+ if (NS_FAILED(rv)) {
+ // Give up.
+ return rv;
+ }
+ }
+
+ return mFormatConv->Convert(data.GetFlavor().get(), dataBytes, aFlavor,
+ aData);
+ }
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+//
+// GetAnyTransferData
+//
+// Returns the data of the first flavor found. Caller is responsible for
+// deleting the flavor string.
+//
+NS_IMETHODIMP
+nsTransferable::GetAnyTransferData(nsACString& aFlavor, nsISupports** aData) {
+ MOZ_ASSERT(mInitialized);
+
+ for (size_t i = 0; i < mDataArray.Length(); ++i) {
+ DataStruct& data = mDataArray.ElementAt(i);
+ if (data.IsDataAvailable()) {
+ aFlavor.Assign(data.GetFlavor());
+ data.GetData(aData);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+//
+// SetTransferData
+//
+//
+//
+NS_IMETHODIMP
+nsTransferable::SetTransferData(const char* aFlavor, nsISupports* aData) {
+ MOZ_ASSERT(mInitialized);
+
+ // first check our intrinsic flavors to see if one has been registered.
+ if (Maybe<size_t> index = FindDataFlavor(aFlavor)) {
+ DataStruct& data = mDataArray.ElementAt(index.value());
+ data.SetData(aData, mPrivateData);
+ return NS_OK;
+ }
+
+ // if not, try using a format converter to find a flavor to put the data in
+ if (mFormatConv) {
+ for (size_t i = 0; i < mDataArray.Length(); ++i) {
+ DataStruct& data = mDataArray.ElementAt(i);
+ bool canConvert = false;
+ mFormatConv->CanConvert(aFlavor, data.GetFlavor().get(), &canConvert);
+
+ if (canConvert) {
+ nsCOMPtr<nsISupports> ConvertedData;
+ mFormatConv->Convert(aFlavor, aData, data.GetFlavor().get(),
+ getter_AddRefs(ConvertedData));
+ data.SetData(ConvertedData, mPrivateData);
+ return NS_OK;
+ }
+ }
+ }
+
+ // Can't set data neither directly nor through converter. Just add this flavor
+ // and try again
+ if (NS_SUCCEEDED(AddDataFlavor(aFlavor))) {
+ return SetTransferData(aFlavor, aData);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+//
+// AddDataFlavor
+//
+// Adds a data flavor to our list with no data. Error if it already exists.
+//
+NS_IMETHODIMP
+nsTransferable::AddDataFlavor(const char* aDataFlavor) {
+ MOZ_ASSERT(mInitialized);
+
+ if (FindDataFlavor(aDataFlavor).isSome()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Create a new "slot" for the data
+ mDataArray.AppendElement(DataStruct(aDataFlavor));
+ return NS_OK;
+}
+
+//
+// RemoveDataFlavor
+//
+// Removes a data flavor (and causes the data to be destroyed). Error if
+// the requested flavor is not present.
+//
+NS_IMETHODIMP
+nsTransferable::RemoveDataFlavor(const char* aDataFlavor) {
+ MOZ_ASSERT(mInitialized);
+
+ if (Maybe<size_t> index = FindDataFlavor(aDataFlavor)) {
+ mDataArray.RemoveElementAt(index.value());
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsTransferable::SetConverter(nsIFormatConverter* aConverter) {
+ MOZ_ASSERT(mInitialized);
+
+ mFormatConv = aConverter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTransferable::GetConverter(nsIFormatConverter** aConverter) {
+ MOZ_ASSERT(mInitialized);
+
+ nsCOMPtr<nsIFormatConverter> converter = mFormatConv;
+ converter.forget(aConverter);
+ return NS_OK;
+}
+
+//
+// FlavorsTransferableCanImport
+//
+// Computes a list of flavors that the transferable can accept into it, either
+// through intrinsic knowledge or input data converters.
+//
+NS_IMETHODIMP
+nsTransferable::FlavorsTransferableCanImport(nsTArray<nsCString>& aFlavors) {
+ MOZ_ASSERT(mInitialized);
+
+ // Get the flavor list, and on to the end of it, append the list of flavors we
+ // can also get to through a converter. This is so that we can just walk the
+ // list in one go, looking for the desired flavor.
+ GetTransferDataFlavors(aFlavors);
+
+ if (mFormatConv) {
+ nsTArray<nsCString> convertedList;
+ mFormatConv->GetInputDataFlavors(convertedList);
+
+ for (uint32_t i = 0; i < convertedList.Length(); ++i) {
+ nsCString& flavorStr = convertedList[i];
+
+ // Don't append if already in intrinsic list
+ if (!aFlavors.Contains(flavorStr)) {
+ aFlavors.AppendElement(flavorStr);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//
+// FlavorsTransferableCanExport
+//
+// Computes a list of flavors that the transferable can export, either through
+// intrinsic knowledge or output data converters.
+//
+NS_IMETHODIMP
+nsTransferable::FlavorsTransferableCanExport(nsTArray<nsCString>& aFlavors) {
+ MOZ_ASSERT(mInitialized);
+
+ // Get the flavor list, and on to the end of it, append the list of flavors we
+ // can also get to through a converter. This is so that we can just walk the
+ // list in one go, looking for the desired flavor.
+ GetTransferDataFlavors(aFlavors);
+
+ if (mFormatConv) {
+ nsTArray<nsCString> convertedList;
+ mFormatConv->GetOutputDataFlavors(convertedList);
+
+ for (uint32_t i = 0; i < convertedList.Length(); ++i) {
+ nsCString& flavorStr = convertedList[i];
+
+ // Don't append if already in intrinsic list
+ if (!aFlavors.Contains(flavorStr)) {
+ aFlavors.AppendElement(flavorStr);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+bool nsTransferable::GetIsPrivateData() {
+ MOZ_ASSERT(mInitialized);
+
+ return mPrivateData;
+}
+
+void nsTransferable::SetIsPrivateData(bool aIsPrivateData) {
+ MOZ_ASSERT(mInitialized);
+
+ mPrivateData = aIsPrivateData;
+}
+
+nsIPrincipal* nsTransferable::GetRequestingPrincipal() {
+ MOZ_ASSERT(mInitialized);
+
+ return mRequestingPrincipal;
+}
+
+void nsTransferable::SetRequestingPrincipal(
+ nsIPrincipal* aRequestingPrincipal) {
+ MOZ_ASSERT(mInitialized);
+
+ mRequestingPrincipal = aRequestingPrincipal;
+}
+
+nsContentPolicyType nsTransferable::GetContentPolicyType() {
+ MOZ_ASSERT(mInitialized);
+
+ return mContentPolicyType;
+}
+
+void nsTransferable::SetContentPolicyType(
+ nsContentPolicyType aContentPolicyType) {
+ MOZ_ASSERT(mInitialized);
+
+ mContentPolicyType = aContentPolicyType;
+}
+
+nsICookieJarSettings* nsTransferable::GetCookieJarSettings() {
+ MOZ_ASSERT(mInitialized);
+
+ return mCookieJarSettings;
+}
+
+void nsTransferable::SetCookieJarSettings(
+ nsICookieJarSettings* aCookieJarSettings) {
+ MOZ_ASSERT(mInitialized);
+
+ mCookieJarSettings = aCookieJarSettings;
+}
diff --git a/widget/nsTransferable.h b/widget/nsTransferable.h
new file mode 100644
index 0000000000..0529bf713b
--- /dev/null
+++ b/widget/nsTransferable.h
@@ -0,0 +1,89 @@
+/* -*- 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 nsTransferable_h__
+#define nsTransferable_h__
+
+#include "nsICookieJarSettings.h"
+#include "nsIFormatConverter.h"
+#include "nsITransferable.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIPrincipal.h"
+#include "prio.h"
+#include "mozilla/Maybe.h"
+
+class nsIMutableArray;
+
+//
+// DataStruct
+//
+// Holds a flavor (a mime type) that describes the data and the associated data.
+//
+struct DataStruct {
+ explicit DataStruct(const char* aFlavor)
+ : mCacheFD(nullptr), mFlavor(aFlavor) {}
+ DataStruct(DataStruct&& aRHS);
+ ~DataStruct();
+
+ const nsCString& GetFlavor() const { return mFlavor; }
+ void SetData(nsISupports* aData, bool aIsPrivateData);
+ void GetData(nsISupports** aData);
+ bool IsDataAvailable() const { return mData || mCacheFD; }
+
+ protected:
+ enum {
+ // The size of data over which we write the data to disk rather than
+ // keep it around in memory.
+ kLargeDatasetSize = 1000000 // 1 million bytes
+ };
+
+ nsresult WriteCache(void* aData, uint32_t aDataLen);
+ nsresult ReadCache(nsISupports** aData);
+
+ // mData OR mCacheFD should be used, not both.
+ nsCOMPtr<nsISupports> mData; // OWNER - some varient of primitive wrapper
+ PRFileDesc* mCacheFD;
+ const nsCString mFlavor;
+
+ private:
+ DataStruct(const DataStruct&) = delete;
+ DataStruct& operator=(const DataStruct&) = delete;
+};
+
+/**
+ * XP Transferable wrapper
+ */
+
+class nsTransferable : public nsITransferable {
+ public:
+ nsTransferable();
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITRANSFERABLE
+
+ protected:
+ virtual ~nsTransferable();
+
+ // Get flavors w/out converter
+ void GetTransferDataFlavors(nsTArray<nsCString>& aFlavors);
+
+ // Find index for data with the matching flavor in mDataArray.
+ mozilla::Maybe<size_t> FindDataFlavor(const char* aFlavor);
+
+ nsTArray<DataStruct> mDataArray;
+ nsCOMPtr<nsIFormatConverter> mFormatConv;
+ bool mPrivateData;
+ nsCOMPtr<nsIPrincipal> mRequestingPrincipal;
+ nsContentPolicyType mContentPolicyType;
+ nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
+#if DEBUG
+ bool mInitialized;
+#endif
+};
+
+#endif // nsTransferable_h__
diff --git a/widget/nsUserIdleService.cpp b/widget/nsUserIdleService.cpp
new file mode 100644
index 0000000000..3dbb5450ed
--- /dev/null
+++ b/widget/nsUserIdleService.cpp
@@ -0,0 +1,883 @@
+/* -*- 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/. */
+
+#include "nsUserIdleService.h"
+#include "nsString.h"
+#include "nsIObserverService.h"
+#include "nsDebug.h"
+#include "nsCOMArray.h"
+#include "nsXULAppAPI.h"
+#include "prinrval.h"
+#include "mozilla/Logging.h"
+#include "prtime.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include <algorithm>
+
+#ifdef MOZ_WIDGET_ANDROID
+# include <android/log.h>
+#endif
+
+using namespace mozilla;
+
+// interval in milliseconds between internal idle time requests.
+#define MIN_IDLE_POLL_INTERVAL_MSEC (5 * PR_MSEC_PER_SEC) /* 5 sec */
+
+// After the twenty four hour period expires for an idle daily, this is the
+// amount of idle time we wait for before actually firing the idle-daily
+// event.
+#define DAILY_SIGNIFICANT_IDLE_SERVICE_SEC (3 * 60)
+
+// In cases where it's been longer than twenty four hours since the last
+// idle-daily, this is the shortend amount of idle time we wait for before
+// firing the idle-daily event.
+#define DAILY_SHORTENED_IDLE_SERVICE_SEC 60
+
+// Pref for last time (seconds since epoch) daily notification was sent.
+#define PREF_LAST_DAILY "idle.lastDailyNotification"
+
+// Number of seconds in a day.
+#define SECONDS_PER_DAY 86400
+
+static LazyLogModule sLog("idleService");
+
+#define LOG_TAG "GeckoIdleService"
+#define LOG_LEVEL ANDROID_LOG_DEBUG
+
+// Use this to find previously added observers in our array:
+class IdleListenerComparator {
+ public:
+ bool Equals(IdleListener a, IdleListener b) const {
+ return (a.observer == b.observer) && (a.reqIdleTime == b.reqIdleTime);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsUserIdleServiceDaily
+
+NS_IMPL_ISUPPORTS(nsUserIdleServiceDaily, nsIObserver, nsISupportsWeakReference)
+
+NS_IMETHODIMP
+nsUserIdleServiceDaily::Observe(nsISupports*, const char* aTopic,
+ const char16_t*) {
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsUserIdleServiceDaily: Observe '%s' (%d)", aTopic,
+ mShutdownInProgress));
+
+ if (strcmp(aTopic, "profile-after-change") == 0) {
+ // We are back. Start sending notifications again.
+ mShutdownInProgress = false;
+ return NS_OK;
+ }
+
+ if (strcmp(aTopic, "xpcom-will-shutdown") == 0 ||
+ strcmp(aTopic, "profile-change-teardown") == 0) {
+ mShutdownInProgress = true;
+ }
+
+ if (mShutdownInProgress || strcmp(aTopic, OBSERVER_TOPIC_ACTIVE) == 0) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0);
+
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsUserIdleServiceDaily: Notifying idle-daily observers"));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG, "Notifying idle-daily observers");
+#endif
+
+ // Send the idle-daily observer event
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(observerService);
+ (void)observerService->NotifyObservers(nullptr, OBSERVER_TOPIC_IDLE_DAILY,
+ nullptr);
+
+ // Notify the category observers.
+ nsCOMArray<nsIObserver> entries;
+ mCategoryObservers.GetEntries(entries);
+ for (int32_t i = 0; i < entries.Count(); ++i) {
+ (void)entries[i]->Observe(nullptr, OBSERVER_TOPIC_IDLE_DAILY, nullptr);
+ }
+
+ // Stop observing idle for today.
+ (void)mIdleService->RemoveIdleObserver(this, mIdleDailyTriggerWait);
+
+ // Set the last idle-daily time pref.
+ int32_t nowSec = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
+ Preferences::SetInt(PREF_LAST_DAILY, nowSec);
+
+ // Force that to be stored so we don't retrigger twice a day under
+ // any circumstances.
+ nsIPrefService* prefs = Preferences::GetService();
+ if (prefs) {
+ prefs->SavePrefFile(nullptr);
+ }
+
+ MOZ_LOG(
+ sLog, LogLevel::Debug,
+ ("nsUserIdleServiceDaily: Storing last idle time as %d sec.", nowSec));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG, "Storing last idle time as %d",
+ nowSec);
+#endif
+
+ // Note the moment we expect to get the next timer callback
+ mExpectedTriggerTime =
+ PR_Now() + ((PRTime)SECONDS_PER_DAY * (PRTime)PR_USEC_PER_SEC);
+
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsUserIdleServiceDaily: Restarting daily timer"));
+
+ // Start timer for the next check in one day.
+ (void)mTimer->InitWithNamedFuncCallback(
+ DailyCallback, this, SECONDS_PER_DAY * PR_MSEC_PER_SEC,
+ nsITimer::TYPE_ONE_SHOT, "nsUserIdleServiceDaily::Observe");
+
+ return NS_OK;
+}
+
+nsUserIdleServiceDaily::nsUserIdleServiceDaily(nsIUserIdleService* aIdleService)
+ : mIdleService(aIdleService),
+ mTimer(NS_NewTimer()),
+ mCategoryObservers(OBSERVER_TOPIC_IDLE_DAILY),
+ mShutdownInProgress(false),
+ mExpectedTriggerTime(0),
+ mIdleDailyTriggerWait(DAILY_SIGNIFICANT_IDLE_SERVICE_SEC) {}
+
+void nsUserIdleServiceDaily::Init() {
+ // First check the time of the last idle-daily event notification. If it
+ // has been 24 hours or higher, or if we have never sent an idle-daily,
+ // get ready to send an idle-daily event. Otherwise set a timer targeted
+ // at 24 hours past the last idle-daily we sent.
+
+ int32_t lastDaily = Preferences::GetInt(PREF_LAST_DAILY, 0);
+ // Setting the pref to -1 allows to disable idle-daily, and it's particularly
+ // useful in tests. Normally there should be no need for the user to set
+ // this value.
+ if (lastDaily == -1) {
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsUserIdleServiceDaily: Init: disabled idle-daily"));
+ return;
+ }
+
+ int32_t nowSec = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
+ if (lastDaily < 0 || lastDaily > nowSec) {
+ // The time is bogus, use default.
+ lastDaily = 0;
+ }
+ int32_t secondsSinceLastDaily = nowSec - lastDaily;
+
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsUserIdleServiceDaily: Init: seconds since last daily: %d",
+ secondsSinceLastDaily));
+
+ // If it has been twenty four hours or more or if we have never sent an
+ // idle-daily event get ready to send it during the next idle period.
+ if (secondsSinceLastDaily > SECONDS_PER_DAY) {
+ // Check for a "long wait", e.g. 48-hours or more.
+ bool hasBeenLongWait =
+ (lastDaily && (secondsSinceLastDaily > (SECONDS_PER_DAY * 2)));
+
+ MOZ_LOG(
+ sLog, LogLevel::Debug,
+ ("nsUserIdleServiceDaily: has been long wait? %d", hasBeenLongWait));
+
+ // StageIdleDaily sets up a wait for the user to become idle and then
+ // sends the idle-daily event.
+ StageIdleDaily(hasBeenLongWait);
+ } else {
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsUserIdleServiceDaily: Setting timer a day from now"));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG, "Setting timer a day from now");
+#endif
+
+ // According to our last idle-daily pref, the last idle-daily was fired
+ // less then 24 hours ago. Set a wait for the amount of time remaining.
+ int32_t milliSecLeftUntilDaily =
+ (SECONDS_PER_DAY - secondsSinceLastDaily) * PR_MSEC_PER_SEC;
+
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsUserIdleServiceDaily: Seconds till next timeout: %d",
+ (SECONDS_PER_DAY - secondsSinceLastDaily)));
+
+ // Mark the time at which we expect this to fire. On systems with faulty
+ // timers, we need to be able to cross check that the timer fired at the
+ // expected time.
+ mExpectedTriggerTime =
+ PR_Now() + (milliSecLeftUntilDaily * PR_USEC_PER_MSEC);
+
+ (void)mTimer->InitWithNamedFuncCallback(
+ DailyCallback, this, milliSecLeftUntilDaily, nsITimer::TYPE_ONE_SHOT,
+ "nsUserIdleServiceDaily::Init");
+ }
+
+ // Register for when we should terminate/pause
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ MOZ_LOG(
+ sLog, LogLevel::Debug,
+ ("nsUserIdleServiceDaily: Registering for system event observers."));
+ obs->AddObserver(this, "xpcom-will-shutdown", true);
+ obs->AddObserver(this, "profile-change-teardown", true);
+ obs->AddObserver(this, "profile-after-change", true);
+ }
+}
+
+nsUserIdleServiceDaily::~nsUserIdleServiceDaily() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+void nsUserIdleServiceDaily::StageIdleDaily(bool aHasBeenLongWait) {
+ NS_ASSERTION(mIdleService, "No idle service available?");
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsUserIdleServiceDaily: Registering Idle observer callback "
+ "(short wait requested? %d)",
+ aHasBeenLongWait));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG, "Registering Idle observer callback");
+#endif
+ mIdleDailyTriggerWait =
+ (aHasBeenLongWait ? DAILY_SHORTENED_IDLE_SERVICE_SEC
+ : DAILY_SIGNIFICANT_IDLE_SERVICE_SEC);
+ (void)mIdleService->AddIdleObserver(this, mIdleDailyTriggerWait);
+}
+
+// static
+void nsUserIdleServiceDaily::DailyCallback(nsITimer* aTimer, void* aClosure) {
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsUserIdleServiceDaily: DailyCallback running"));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG, "DailyCallback running");
+#endif
+
+ nsUserIdleServiceDaily* self = static_cast<nsUserIdleServiceDaily*>(aClosure);
+
+ // Check to be sure the timer didn't fire early. This currently only
+ // happens on android.
+ PRTime now = PR_Now();
+ if (self->mExpectedTriggerTime && now < self->mExpectedTriggerTime) {
+ // Timer returned early, reschedule to the appropriate time.
+ PRTime delayTime = self->mExpectedTriggerTime - now;
+
+ // Add 10 ms to ensure we don't undershoot, and never get a "0" timer.
+ delayTime += 10 * PR_USEC_PER_MSEC;
+
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsUserIdleServiceDaily: DailyCallback resetting timer to %" PRId64
+ " msec",
+ delayTime / PR_USEC_PER_MSEC));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "DailyCallback resetting timer to %" PRId64 " msec",
+ delayTime / PR_USEC_PER_MSEC);
+#endif
+
+ (void)self->mTimer->InitWithNamedFuncCallback(
+ DailyCallback, self, delayTime / PR_USEC_PER_MSEC,
+ nsITimer::TYPE_ONE_SHOT, "nsUserIdleServiceDaily::DailyCallback");
+ return;
+ }
+
+ // Register for a short term wait for idle event. When this fires we fire
+ // our idle-daily event.
+ self->StageIdleDaily(false);
+}
+
+/**
+ * The idle services goal is to notify subscribers when a certain time has
+ * passed since the last user interaction with the system.
+ *
+ * On some platforms this is defined as the last time user events reached this
+ * application, on other platforms it is a system wide thing - the preferred
+ * implementation is to use the system idle time, rather than the application
+ * idle time, as the things depending on the idle service are likely to use
+ * significant resources (network, disk, memory, cpu, etc.).
+ *
+ * When the idle service needs to use the system wide idle timer, it typically
+ * needs to poll the idle time value by the means of a timer. It needs to
+ * poll fast when it is in active idle mode (when it has a listener in the idle
+ * mode) as it needs to detect if the user is active in other applications.
+ *
+ * When the service is waiting for the first listener to become idle, or when
+ * it is only monitoring application idle time, it only needs to have the timer
+ * expire at the time the next listener goes idle.
+ *
+ * The core state of the service is determined by:
+ *
+ * - A list of listeners.
+ *
+ * - A boolean that tells if any listeners are in idle mode.
+ *
+ * - A delta value that indicates when, measured from the last non-idle time,
+ * the next listener should switch to idle mode.
+ *
+ * - An absolute time of the last time idle mode was detected (this is used to
+ * judge if we have been out of idle mode since the last invocation of the
+ * service.
+ *
+ * There are four entry points into the system:
+ *
+ * - A new listener is registered.
+ *
+ * - An existing listener is deregistered.
+ *
+ * - User interaction is detected.
+ *
+ * - The timer expires.
+ *
+ * When a new listener is added its idle timeout, is compared with the next idle
+ * timeout, and if lower, that time is stored as the new timeout, and the timer
+ * is reconfigured to ensure a timeout around the time the new listener should
+ * timeout.
+ *
+ * If the next idle time is above the idle time requested by the new listener
+ * it won't be informed until the timer expires, this is to avoid recursive
+ * behavior and to simplify the code. In this case the timer will be set to
+ * about 10 ms.
+ *
+ * When an existing listener is deregistered, it is just removed from the list
+ * of active listeners, we don't stop the timer, we just let it expire.
+ *
+ * When user interaction is detected, either because it was directly detected or
+ * because we polled the system timer and found it to be unexpected low, then we
+ * check the flag that tells us if any listeners are in idle mode, if there are
+ * they are removed from idle mode and told so, and we reset our state
+ * caculating the next timeout and restart the timer if needed.
+ *
+ * ---- Build in logic
+ *
+ * In order to avoid restarting the timer endlessly, the timer function has
+ * logic that will only restart the timer, if the requested timeout is before
+ * the current timeout.
+ *
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsUserIdleService
+
+namespace {
+nsUserIdleService* gIdleService;
+} // namespace
+
+already_AddRefed<nsUserIdleService> nsUserIdleService::GetInstance() {
+ RefPtr<nsUserIdleService> instance(gIdleService);
+ return instance.forget();
+}
+
+nsUserIdleService::nsUserIdleService()
+ : mCurrentlySetToTimeoutAt(TimeStamp()),
+ mIdleObserverCount(0),
+ mDeltaToNextIdleSwitchInS(UINT32_MAX),
+ mLastUserInteraction(TimeStamp::Now()) {
+ MOZ_ASSERT(!gIdleService);
+ gIdleService = this;
+ if (XRE_IsParentProcess()) {
+ mDailyIdle = new nsUserIdleServiceDaily(this);
+ mDailyIdle->Init();
+ }
+}
+
+nsUserIdleService::~nsUserIdleService() {
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+
+ MOZ_ASSERT(gIdleService == this);
+ gIdleService = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsUserIdleService, nsIUserIdleService,
+ nsIUserIdleServiceInternal)
+
+NS_IMETHODIMP
+nsUserIdleService::AddIdleObserver(nsIObserver* aObserver,
+ uint32_t aIdleTimeInS) {
+ NS_ENSURE_ARG_POINTER(aObserver);
+ // We don't accept idle time at 0, and we can't handle idle time that are too
+ // high either - no more than ~136 years.
+ NS_ENSURE_ARG_RANGE(aIdleTimeInS, 1, (UINT32_MAX / 10) - 1);
+
+ if (XRE_IsContentProcess()) {
+ dom::ContentChild* cpc = dom::ContentChild::GetSingleton();
+ cpc->AddIdleObserver(aObserver, aIdleTimeInS);
+ return NS_OK;
+ }
+
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("idleService: Register idle observer %p for %d seconds", aObserver,
+ aIdleTimeInS));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "Register idle observer %p for %d seconds", aObserver,
+ aIdleTimeInS);
+#endif
+
+ // Put the time + observer in a struct we can keep:
+ IdleListener listener(aObserver, aIdleTimeInS);
+
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ mArrayListeners.AppendElement(listener);
+
+ // Create our timer callback if it's not there already.
+ if (!mTimer) {
+ mTimer = NS_NewTimer();
+ NS_ENSURE_TRUE(mTimer, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ // Check if the newly added observer has a smaller wait time than what we
+ // are waiting for now.
+ if (mDeltaToNextIdleSwitchInS > aIdleTimeInS) {
+ // If it is, then this is the next to move to idle (at this point we
+ // don't care if it should have switched already).
+ MOZ_LOG(
+ sLog, LogLevel::Debug,
+ ("idleService: Register: adjusting next switch from %d to %d seconds",
+ mDeltaToNextIdleSwitchInS, aIdleTimeInS));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "Register: adjusting next switch from %d to %d seconds",
+ mDeltaToNextIdleSwitchInS, aIdleTimeInS);
+#endif
+
+ mDeltaToNextIdleSwitchInS = aIdleTimeInS;
+ }
+
+ // Ensure timer is running.
+ ReconfigureTimer();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUserIdleService::RemoveIdleObserver(nsIObserver* aObserver,
+ uint32_t aTimeInS) {
+ NS_ENSURE_ARG_POINTER(aObserver);
+ NS_ENSURE_ARG(aTimeInS);
+
+ if (XRE_IsContentProcess()) {
+ dom::ContentChild* cpc = dom::ContentChild::GetSingleton();
+ cpc->RemoveIdleObserver(aObserver, aTimeInS);
+ return NS_OK;
+ }
+
+ IdleListener listener(aObserver, aTimeInS);
+
+ // Find the entry and remove it, if it was the last entry, we just let the
+ // existing timer run to completion (there might be a new registration in a
+ // little while.
+ IdleListenerComparator c;
+ nsTArray<IdleListener>::index_type listenerIndex =
+ mArrayListeners.IndexOf(listener, 0, c);
+ if (listenerIndex != mArrayListeners.NoIndex) {
+ if (mArrayListeners.ElementAt(listenerIndex).isIdle) mIdleObserverCount--;
+ mArrayListeners.RemoveElementAt(listenerIndex);
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("idleService: Remove observer %p (%d seconds), %d remain idle",
+ aObserver, aTimeInS, mIdleObserverCount));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "Remove observer %p (%d seconds), %d remain idle",
+ aObserver, aTimeInS, mIdleObserverCount);
+#endif
+ return NS_OK;
+ }
+
+ // If we get here, we haven't removed anything:
+ MOZ_LOG(sLog, LogLevel::Warning,
+ ("idleService: Failed to remove idle observer %p (%d seconds)",
+ aObserver, aTimeInS));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "Failed to remove idle observer %p (%d seconds)",
+ aObserver, aTimeInS);
+#endif
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsUserIdleService::ResetIdleTimeOut(uint32_t idleDeltaInMS) {
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("idleService: Reset idle timeout (last interaction %u msec)",
+ idleDeltaInMS));
+
+ // Store the time
+ mLastUserInteraction =
+ TimeStamp::Now() - TimeDuration::FromMilliseconds(idleDeltaInMS);
+
+ // If no one is idle, then we are done, any existing timers can keep running.
+ if (mIdleObserverCount == 0) {
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("idleService: Reset idle timeout: no idle observers"));
+ return NS_OK;
+ }
+
+ // Mark all idle services as non-idle, and calculate the next idle timeout.
+ nsCOMArray<nsIObserver> notifyList;
+ mDeltaToNextIdleSwitchInS = UINT32_MAX;
+
+ // Loop through all listeners, and find any that have detected idle.
+ for (uint32_t i = 0; i < mArrayListeners.Length(); i++) {
+ IdleListener& curListener = mArrayListeners.ElementAt(i);
+
+ // If the listener was idle, then he shouldn't be any longer.
+ if (curListener.isIdle) {
+ notifyList.AppendObject(curListener.observer);
+ curListener.isIdle = false;
+ }
+
+ // Check if the listener is the next one to timeout.
+ mDeltaToNextIdleSwitchInS =
+ std::min(mDeltaToNextIdleSwitchInS, curListener.reqIdleTime);
+ }
+
+ // When we are done, then we wont have anyone idle.
+ mIdleObserverCount = 0;
+
+ // Restart the idle timer, and do so before anyone can delay us.
+ ReconfigureTimer();
+
+ int32_t numberOfPendingNotifications = notifyList.Count();
+
+ // Bail if nothing to do.
+ if (!numberOfPendingNotifications) {
+ return NS_OK;
+ }
+
+ // Now send "active" events to all, if any should have timed out already,
+ // then they will be reawaken by the timer that is already running.
+
+ // We need a text string to send with any state change events.
+ nsAutoString timeStr;
+
+ timeStr.AppendInt((int32_t)(idleDeltaInMS / PR_MSEC_PER_SEC));
+
+ // Send the "non-idle" events.
+ while (numberOfPendingNotifications--) {
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("idleService: Reset idle timeout: tell observer %p user is back",
+ notifyList[numberOfPendingNotifications]));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "Reset idle timeout: tell observer %p user is back",
+ notifyList[numberOfPendingNotifications]);
+#endif
+ notifyList[numberOfPendingNotifications]->Observe(
+ this, OBSERVER_TOPIC_ACTIVE, timeStr.get());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUserIdleService::GetIdleTime(uint32_t* idleTime) {
+ // Check sanity of in parameter.
+ if (!idleTime) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // Polled idle time in ms.
+ uint32_t polledIdleTimeMS;
+
+ bool polledIdleTimeIsValid = PollIdleTime(&polledIdleTimeMS);
+
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("idleService: Get idle time: polled %u msec, valid = %d",
+ polledIdleTimeMS, polledIdleTimeIsValid));
+
+ // timeSinceReset is in milliseconds.
+ TimeDuration timeSinceReset = TimeStamp::Now() - mLastUserInteraction;
+ uint32_t timeSinceResetInMS = timeSinceReset.ToMilliseconds();
+
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("idleService: Get idle time: time since reset %u msec",
+ timeSinceResetInMS));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "Get idle time: time since reset %u msec",
+ timeSinceResetInMS);
+#endif
+
+ // If we did't get pulled data, return the time since last idle reset.
+ if (!polledIdleTimeIsValid) {
+ // We need to convert to ms before returning the time.
+ *idleTime = timeSinceResetInMS;
+ return NS_OK;
+ }
+
+ // Otherwise return the shortest time detected (in ms).
+ *idleTime = std::min(timeSinceResetInMS, polledIdleTimeMS);
+
+ return NS_OK;
+}
+
+bool nsUserIdleService::PollIdleTime(uint32_t* /*aIdleTime*/) {
+ // Default behavior is not to have the ability to poll an idle time.
+ return false;
+}
+
+bool nsUserIdleService::UsePollMode() {
+ uint32_t dummy;
+ return PollIdleTime(&dummy);
+}
+
+nsresult nsUserIdleService::GetDisabled(bool* aResult) {
+ *aResult = mDisabled;
+ return NS_OK;
+}
+
+nsresult nsUserIdleService::SetDisabled(bool aDisabled) {
+ mDisabled = aDisabled;
+ return NS_OK;
+}
+
+void nsUserIdleService::StaticIdleTimerCallback(nsITimer* aTimer,
+ void* aClosure) {
+ static_cast<nsUserIdleService*>(aClosure)->IdleTimerCallback();
+}
+
+void nsUserIdleService::IdleTimerCallback(void) {
+ // Remember that we no longer have a timer running.
+ mCurrentlySetToTimeoutAt = TimeStamp();
+
+ // Find the last detected idle time.
+ uint32_t lastIdleTimeInMS = static_cast<uint32_t>(
+ (TimeStamp::Now() - mLastUserInteraction).ToMilliseconds());
+ // Get the current idle time.
+ uint32_t currentIdleTimeInMS;
+
+ if (NS_FAILED(GetIdleTime(&currentIdleTimeInMS))) {
+ MOZ_LOG(sLog, LogLevel::Info,
+ ("idleService: Idle timer callback: failed to get idle time"));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "Idle timer callback: failed to get idle time");
+#endif
+ return;
+ }
+
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("idleService: Idle timer callback: current idle time %u msec",
+ currentIdleTimeInMS));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "Idle timer callback: current idle time %u msec",
+ currentIdleTimeInMS);
+#endif
+
+ // Check if we have had some user interaction we didn't handle previously
+ // we do the calculation in ms to lessen the chance for rounding errors to
+ // trigger wrong results.
+ if (lastIdleTimeInMS > currentIdleTimeInMS) {
+ // We had user activity, so handle that part first (to ensure the listeners
+ // don't risk getting an non-idle after they get a new idle indication.
+ ResetIdleTimeOut(currentIdleTimeInMS);
+
+ // NOTE: We can't bail here, as we might have something already timed out.
+ }
+
+ // Find the idle time in S.
+ uint32_t currentIdleTimeInS = currentIdleTimeInMS / PR_MSEC_PER_SEC;
+
+ // Restart timer and bail if no-one are expected to be in idle
+ if (mDeltaToNextIdleSwitchInS > currentIdleTimeInS) {
+ // If we didn't expect anyone to be idle, then just re-start the timer.
+ ReconfigureTimer();
+ return;
+ }
+
+ if (mDisabled) {
+ MOZ_LOG(sLog, LogLevel::Info,
+ ("idleService: Skipping idle callback while disabled"));
+
+ ReconfigureTimer();
+ return;
+ }
+
+ // Tell expired listeners they are expired,and find the next timeout
+ Telemetry::AutoTimer<Telemetry::IDLE_NOTIFY_IDLE_MS> timer;
+
+ // We need to initialise the time to the next idle switch.
+ mDeltaToNextIdleSwitchInS = UINT32_MAX;
+
+ // Create list of observers that should be notified.
+ nsCOMArray<nsIObserver> notifyList;
+
+ for (uint32_t i = 0; i < mArrayListeners.Length(); i++) {
+ IdleListener& curListener = mArrayListeners.ElementAt(i);
+
+ // We are only interested in items, that are not in the idle state.
+ if (!curListener.isIdle) {
+ // If they have an idle time smaller than the actual idle time.
+ if (curListener.reqIdleTime <= currentIdleTimeInS) {
+ // Then add the listener to the list of listeners that should be
+ // notified.
+ notifyList.AppendObject(curListener.observer);
+ // This listener is now idle.
+ curListener.isIdle = true;
+ // Remember we have someone idle.
+ mIdleObserverCount++;
+ } else {
+ // Listeners that are not timed out yet are candidates for timing out.
+ mDeltaToNextIdleSwitchInS =
+ std::min(mDeltaToNextIdleSwitchInS, curListener.reqIdleTime);
+ }
+ }
+ }
+
+ // Restart the timer before any notifications that could slow us down are
+ // done.
+ ReconfigureTimer();
+
+ int32_t numberOfPendingNotifications = notifyList.Count();
+
+ // Bail if nothing to do.
+ if (!numberOfPendingNotifications) {
+ MOZ_LOG(
+ sLog, LogLevel::Debug,
+ ("idleService: **** Idle timer callback: no observers to message."));
+ return;
+ }
+
+ // We need a text string to send with any state change events.
+ nsAutoString timeStr;
+ timeStr.AppendInt(currentIdleTimeInS);
+
+ // Notify all listeners that just timed out.
+ while (numberOfPendingNotifications--) {
+ MOZ_LOG(
+ sLog, LogLevel::Debug,
+ ("idleService: **** Idle timer callback: tell observer %p user is idle",
+ notifyList[numberOfPendingNotifications]));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "Idle timer callback: tell observer %p user is idle",
+ notifyList[numberOfPendingNotifications]);
+#endif
+ notifyList[numberOfPendingNotifications]->Observe(this, OBSERVER_TOPIC_IDLE,
+ timeStr.get());
+ }
+}
+
+void nsUserIdleService::SetTimerExpiryIfBefore(TimeStamp aNextTimeout) {
+ TimeDuration nextTimeoutDuration = aNextTimeout - TimeStamp::Now();
+
+ MOZ_LOG(
+ sLog, LogLevel::Debug,
+ ("idleService: SetTimerExpiryIfBefore: next timeout %0.f msec from now",
+ nextTimeoutDuration.ToMilliseconds()));
+
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "SetTimerExpiryIfBefore: next timeout %0.f msec from now",
+ nextTimeoutDuration.ToMilliseconds());
+#endif
+
+ // Bail if we don't have a timer service.
+ if (!mTimer) {
+ return;
+ }
+
+ // If the new timeout is before the old one or we don't have a timer running,
+ // then restart the timer.
+ if (mCurrentlySetToTimeoutAt.IsNull() ||
+ mCurrentlySetToTimeoutAt > aNextTimeout) {
+ mCurrentlySetToTimeoutAt = aNextTimeout;
+
+ // Stop the current timer (it's ok to try'n stop it, even it isn't running).
+ mTimer->Cancel();
+
+ // Check that the timeout is actually in the future, otherwise make it so.
+ TimeStamp currentTime = TimeStamp::Now();
+ if (currentTime > mCurrentlySetToTimeoutAt) {
+ mCurrentlySetToTimeoutAt = currentTime;
+ }
+
+ // Add 10 ms to ensure we don't undershoot, and never get a "0" timer.
+ mCurrentlySetToTimeoutAt += TimeDuration::FromMilliseconds(10);
+
+ TimeDuration deltaTime = mCurrentlySetToTimeoutAt - currentTime;
+ MOZ_LOG(
+ sLog, LogLevel::Debug,
+ ("idleService: IdleService reset timer expiry to %0.f msec from now",
+ deltaTime.ToMilliseconds()));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "reset timer expiry to %0.f msec from now",
+ deltaTime.ToMilliseconds());
+#endif
+
+ // Start the timer
+ mTimer->InitWithNamedFuncCallback(
+ StaticIdleTimerCallback, this, deltaTime.ToMilliseconds(),
+ nsITimer::TYPE_ONE_SHOT, "nsUserIdleService::SetTimerExpiryIfBefore");
+ }
+}
+
+void nsUserIdleService::ReconfigureTimer(void) {
+ // Check if either someone is idle, or someone will become idle.
+ if ((mIdleObserverCount == 0) && UINT32_MAX == mDeltaToNextIdleSwitchInS) {
+ // If not, just let any existing timers run to completion
+ // And bail out.
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("idleService: ReconfigureTimer: no idle or waiting observers"));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "ReconfigureTimer: no idle or waiting observers");
+#endif
+ return;
+ }
+
+ // Find the next timeout value, assuming we are not polling.
+
+ // We need to store the current time, so we don't get artifacts from the time
+ // ticking while we are processing.
+ TimeStamp curTime = TimeStamp::Now();
+
+ TimeStamp nextTimeoutAt =
+ mLastUserInteraction +
+ TimeDuration::FromSeconds(mDeltaToNextIdleSwitchInS);
+
+ TimeDuration nextTimeoutDuration = nextTimeoutAt - curTime;
+
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("idleService: next timeout %0.f msec from now",
+ nextTimeoutDuration.ToMilliseconds()));
+
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG, "next timeout %0.f msec from now",
+ nextTimeoutDuration.ToMilliseconds());
+#endif
+
+ // Check if we should correct the timeout time because we should poll before.
+ if ((mIdleObserverCount > 0) && UsePollMode()) {
+ TimeStamp pollTimeout =
+ curTime + TimeDuration::FromMilliseconds(MIN_IDLE_POLL_INTERVAL_MSEC);
+
+ if (nextTimeoutAt > pollTimeout) {
+ MOZ_LOG(
+ sLog, LogLevel::Debug,
+ ("idleService: idle observers, reducing timeout to %lu msec from now",
+ MIN_IDLE_POLL_INTERVAL_MSEC));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(
+ LOG_LEVEL, LOG_TAG,
+ "idle observers, reducing timeout to %lu msec from now",
+ MIN_IDLE_POLL_INTERVAL_MSEC);
+#endif
+ nextTimeoutAt = pollTimeout;
+ }
+ }
+
+ SetTimerExpiryIfBefore(nextTimeoutAt);
+}
diff --git a/widget/nsUserIdleService.h b/widget/nsUserIdleService.h
new file mode 100644
index 0000000000..bbe06a2b8c
--- /dev/null
+++ b/widget/nsUserIdleService.h
@@ -0,0 +1,227 @@
+/* -*- 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 nsUserIdleService_h__
+#define nsUserIdleService_h__
+
+#include "nsIUserIdleServiceInternal.h"
+#include "nsCOMPtr.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "nsIObserver.h"
+#include "nsIUserIdleService.h"
+#include "nsCategoryCache.h"
+#include "nsWeakReference.h"
+#include "mozilla/TimeStamp.h"
+
+/**
+ * Class we can use to store an observer with its associated idle time
+ * requirement and whether or not the observer thinks it's "idle".
+ */
+class IdleListener {
+ public:
+ nsCOMPtr<nsIObserver> observer;
+ uint32_t reqIdleTime;
+ bool isIdle;
+
+ IdleListener(nsIObserver* obs, uint32_t reqIT, bool aIsIdle = false)
+ : observer(obs), reqIdleTime(reqIT), isIdle(aIsIdle) {}
+ ~IdleListener() = default;
+};
+
+// This one will be declared later.
+class nsUserIdleService;
+
+/**
+ * Class to handle the daily idle timer.
+ */
+class nsUserIdleServiceDaily : public nsIObserver,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ explicit nsUserIdleServiceDaily(nsIUserIdleService* aIdleService);
+
+ /**
+ * Initializes the daily idle observer.
+ * Keep this separated from the constructor, since it could cause pointer
+ * corruption due to AddRef/Release of "this".
+ */
+ void Init();
+
+ private:
+ virtual ~nsUserIdleServiceDaily();
+
+ /**
+ * StageIdleDaily is the interim call made when an idle-daily event is due.
+ * However we don't want to fire idle-daily until the user is idle for this
+ * session, so this sets up a short wait for an idle event which triggers
+ * the actual idle-daily event.
+ *
+ * @param aHasBeenLongWait Pass true indicating nsUserIdleServiceDaily is
+ * having trouble getting the idle-daily event fired. If true StageIdleDaily
+ * will use a shorter idle wait time before firing idle-daily.
+ */
+ void StageIdleDaily(bool aHasBeenLongWait);
+
+ /**
+ * @note This is a normal pointer, part to avoid creating a cycle with the
+ * idle service, part to avoid potential pointer corruption due to this class
+ * being instantiated in the constructor of the service itself.
+ */
+ nsIUserIdleService* mIdleService;
+
+ /**
+ * Place to hold the timer used by this class to determine when a day has
+ * passed, after that it will wait for idle time to be detected.
+ */
+ nsCOMPtr<nsITimer> mTimer;
+
+ /**
+ * Function that is called back once a day.
+ */
+ static void DailyCallback(nsITimer* aTimer, void* aClosure);
+
+ /**
+ * Cache of observers for the "idle-daily" category.
+ */
+ nsCategoryCache<nsIObserver> mCategoryObservers;
+
+ /**
+ * Boolean set to true when daily idle notifications should be disabled.
+ */
+ bool mShutdownInProgress;
+
+ /**
+ * Next time we expect an idle-daily timer to fire, in case timers aren't
+ * very reliable on the platform. Value is in PR_Now microsecond units.
+ */
+ PRTime mExpectedTriggerTime;
+
+ /**
+ * Tracks which idle daily observer callback we ask for. There are two: a
+ * regular long idle wait and a shorter wait if we've been waiting to fire
+ * idle daily for an extended period. Set by StageIdleDaily.
+ */
+ int32_t mIdleDailyTriggerWait;
+};
+
+class nsUserIdleService : public nsIUserIdleServiceInternal {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUSERIDLESERVICE NS_DECL_NSIUSERIDLESERVICEINTERNAL
+
+ protected : static already_AddRefed<nsUserIdleService>
+ GetInstance();
+
+ nsUserIdleService();
+ virtual ~nsUserIdleService();
+
+ /**
+ * If there is a platform specific function to poll the system idel time
+ * then that must be returned in this function, and the function MUST return
+ * true, otherwise then the function should be left unimplemented or made
+ * to return false (this can also be used for systems where it depends on
+ * the configuration of the system if the idle time can be determined)
+ *
+ * @param aIdleTime
+ * The idle time in ms.
+ *
+ * @return true if the idle time could be polled, false otherwise.
+ *
+ * @note The time returned by this function can be different than the one
+ * returned by GetIdleTime, as that is corrected by any calls to
+ * ResetIdleTimeOut(), unless you overwrite that function too...
+ */
+ virtual bool PollIdleTime(uint32_t* aIdleTime);
+
+ /**
+ * Function that determines if we are in poll mode or not.
+ *
+ * @return true if polling is supported, false otherwise.
+ */
+ virtual bool UsePollMode();
+
+ private:
+ /**
+ * Ensure that the timer is expiring at least at the given time
+ *
+ * The function might not restart the timer if there is one running currently
+ *
+ * @param aNextTimeout
+ * The last absolute time the timer should expire
+ */
+ void SetTimerExpiryIfBefore(mozilla::TimeStamp aNextTimeout);
+
+ /**
+ * Stores the next timeout time, 0 means timer not running
+ */
+ mozilla::TimeStamp mCurrentlySetToTimeoutAt;
+
+ /**
+ * mTimer holds the internal timer used by this class to detect when to poll
+ * for idle time, when to check if idle timers should expire etc.
+ */
+ nsCOMPtr<nsITimer> mTimer;
+
+ /**
+ * Array of listeners that wants to be notified about idle time.
+ */
+ nsTArray<IdleListener> mArrayListeners;
+
+ /**
+ * Object keeping track of the daily idle thingy.
+ */
+ RefPtr<nsUserIdleServiceDaily> mDailyIdle;
+
+ /**
+ * Number of observers currently in idle mode.
+ */
+ uint32_t mIdleObserverCount;
+
+ /**
+ * Delta time from last non idle time to when the next observer should switch
+ * to idle mode
+ *
+ * Time in seconds
+ *
+ * If this value is 0 it means there are no active observers
+ */
+ uint32_t mDeltaToNextIdleSwitchInS;
+
+ /**
+ * If true, the idle service is temporarily disabled, and all idle events
+ * will be ignored.
+ */
+ bool mDisabled = false;
+
+ /**
+ * Absolute value for when the last user interaction took place.
+ */
+ mozilla::TimeStamp mLastUserInteraction;
+
+ /**
+ * Function that ensures the timer is running with at least the minimum time
+ * needed. It will kill the timer if there are no active observers.
+ */
+ void ReconfigureTimer(void);
+
+ /**
+ * Callback function that is called when the internal timer expires.
+ *
+ * This in turn calls the IdleTimerCallback that does the real processing
+ */
+ static void StaticIdleTimerCallback(nsITimer* aTimer, void* aClosure);
+
+ /**
+ * Function that handles when a timer has expired
+ */
+ void IdleTimerCallback(void);
+};
+
+#endif // nsUserIdleService_h__
diff --git a/widget/nsWidgetInitData.h b/widget/nsWidgetInitData.h
new file mode 100644
index 0000000000..acda84ef7e
--- /dev/null
+++ b/widget/nsWidgetInitData.h
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 40; 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 nsWidgetInitData_h__
+#define nsWidgetInitData_h__
+
+/**
+ * Window types
+ *
+ * Don't alter previously encoded enum values - 3rd party apps may look at
+ * these.
+ */
+enum nsWindowType {
+ eWindowType_toplevel, // default top level window
+ eWindowType_dialog, // top level window but usually handled differently
+ // by the OS
+ eWindowType_sheet, // MacOSX sheet (special dialog class)
+ eWindowType_popup, // used for combo boxes, etc
+ eWindowType_child, // child windows (contained inside a window on the
+ // desktop (has no border))
+ eWindowType_invisible, // windows that are invisible or offscreen
+ eWindowType_plugin, // plugin window
+ eWindowType_plugin_ipc_chrome, // chrome side native widget for plugins
+ // (e10s)
+ eWindowType_plugin_ipc_content, // content side puppet widget for plugins
+ // (e10s)
+};
+
+/**
+ * Popup types
+ *
+ * For eWindowType_popup
+ */
+enum nsPopupType {
+ ePopupTypePanel,
+ ePopupTypeMenu,
+ ePopupTypeTooltip,
+ ePopupTypeAny = 0xF000 // used only to pass to
+ // nsXULPopupManager::GetTopPopup
+};
+
+/**
+ * Popup levels specify the window ordering behaviour.
+ */
+enum nsPopupLevel {
+ // the popup appears just above its parent and maintains its position
+ // relative to the parent
+ ePopupLevelParent,
+ // the popup is a floating popup used for tool palettes. A parent window
+ // must be specified, but a platform implementation need not use this.
+ // On Windows, floating is generally equivalent to parent. On Mac, floating
+ // puts the popup at toplevel, but it will hide when the application is
+ // deactivated
+ ePopupLevelFloating,
+ // the popup appears on top of other windows, including those of other
+ // applications
+ ePopupLevelTop
+};
+
+/**
+ * Border styles
+ */
+enum nsBorderStyle {
+ eBorderStyle_none = 0, // no border, titlebar, etc.. opposite of
+ // all
+ eBorderStyle_all = 1 << 0, // all window decorations
+ eBorderStyle_border = 1 << 1, // enables the border on the window. these
+ // are only for decoration and are not
+ // resize handles
+ eBorderStyle_resizeh = 1 << 2, // enables the resize handles for the
+ // window. if this is set, border is
+ // implied to also be set
+ eBorderStyle_title = 1 << 3, // enables the titlebar for the window
+ eBorderStyle_menu = 1 << 4, // enables the window menu button on the
+ // title bar. this being on should force
+ // the title bar to display
+ eBorderStyle_minimize = 1 << 5, // enables the minimize button so the user
+ // can minimize the window. turned off for
+ // tranient windows since they can not be
+ // minimized separate from their parent
+ eBorderStyle_maximize = 1 << 6, // enables the maxmize button so the user
+ // can maximize the window
+ eBorderStyle_close = 1 << 7, // show the close button
+ eBorderStyle_default = -1 // whatever the OS wants... i.e. don't do
+ // anything
+};
+
+/**
+ * Basic struct for widget initialization data.
+ * @see Create member function of nsIWidget
+ */
+
+struct nsWidgetInitData {
+ nsWidgetInitData()
+ : mWindowType(eWindowType_child),
+ mBorderStyle(eBorderStyle_default),
+ mPopupHint(ePopupTypePanel),
+ mPopupLevel(ePopupLevelTop),
+ mScreenId(0),
+ clipChildren(false),
+ clipSiblings(false),
+ mDropShadow(false),
+ mListenForResizes(false),
+ mRTL(false),
+ mNoAutoHide(false),
+ mIsDragPopup(false),
+ mIsAnimationSuppressed(false),
+ mSupportTranslucency(false),
+ mMouseTransparent(false),
+ mHasRemoteContent(false),
+ mAlwaysOnTop(false),
+ mPIPWindow(false),
+ mFissionWindow(false),
+ mResizable(false) {}
+
+ nsWindowType mWindowType;
+ nsBorderStyle mBorderStyle;
+ nsPopupType mPopupHint;
+ nsPopupLevel mPopupLevel;
+ // B2G multi-screen support. Screen ID is for differentiating screens of
+ // windows, and due to the hardware limitation, it is platform-specific for
+ // now, which align with the value of display type defined in HWC.
+ uint32_t mScreenId;
+ // when painting exclude area occupied by child windows and sibling windows
+ bool clipChildren, clipSiblings, mDropShadow;
+ bool mListenForResizes;
+ bool mRTL;
+ bool mNoAutoHide; // true for noautohide panels
+ bool mIsDragPopup; // true for drag feedback panels
+ // true if window creation animation is suppressed, e.g. for session restore
+ bool mIsAnimationSuppressed;
+ // true if the window should support an alpha channel, if available.
+ bool mSupportTranslucency;
+ // true if the window should be transparent to mouse events. Currently this is
+ // only valid for eWindowType_popup widgets
+ bool mMouseTransparent;
+ bool mHasRemoteContent;
+ bool mAlwaysOnTop;
+ // Is PictureInPicture window
+ bool mPIPWindow;
+ // True if fission is enabled for this window
+ bool mFissionWindow;
+ // True if the window is user-resizable.
+ bool mResizable;
+};
+
+#endif // nsWidgetInitData_h__
diff --git a/widget/nsWidgetsCID.h b/widget/nsWidgetsCID.h
new file mode 100644
index 0000000000..61547225a9
--- /dev/null
+++ b/widget/nsWidgetsCID.h
@@ -0,0 +1,339 @@
+/* -*- 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/. */
+
+/* bd57cee8-1dd1-11b2-9fe7-95cf4709aea3 */
+#define NS_FILEPICKER_CID \
+ { \
+ 0xbd57cee8, 0x1dd1, 0x11b2, { \
+ 0x9f, 0xe7, 0x95, 0xcf, 0x47, 0x09, 0xae, 0xa3 \
+ } \
+ }
+
+/* e221df9b-3d66-4045-9a66-5720949f8d10 */
+#define NS_APPLICATIONCHOOSER_CID \
+ { \
+ 0xe221df9b, 0x3d66, 0x4045, { \
+ 0x9a, 0x66, 0x57, 0x20, 0x94, 0x9f, 0x8d, 0x10 \
+ } \
+ }
+
+/* 0f872c8c-3ee6-46bd-92a2-69652c6b474e */
+#define NS_COLORPICKER_CID \
+ { \
+ 0x0f872c8c, 0x3ee6, 0x46bd, { \
+ 0x92, 0xa2, 0x69, 0x65, 0x2c, 0x6b, 0x47, 0x4e \
+ } \
+ }
+
+/* 2d96b3df-c051-11d1-a827-0040959a28c9 */
+#define NS_APPSHELL_CID \
+ { \
+ 0x2d96b3df, 0xc051, 0x11d1, { \
+ 0xa8, 0x27, 0x00, 0x40, 0x95, 0x9a, 0x28, 0xc9 \
+ } \
+ }
+
+/* 2d96b3e0-c051-11d1-a827-0040959a28c9 */
+#define NS_TOOLKIT_CID \
+ { \
+ 0x2d96b3e0, 0xc051, 0x11d1, { \
+ 0xa8, 0x27, 0x00, 0x40, 0x95, 0x9a, 0x28, 0xc9 \
+ } \
+ }
+
+/* 9A0CB62B-D638-4FAF-9588-AE96F5E29093 */
+#define NS_TASKBARPREVIEWCALLBACK_CID \
+ { \
+ 0x9a0cb62b, 0xd638, 0x4faf, { \
+ 0x95, 0x88, 0xae, 0x96, 0xf5, 0xe2, 0x90, 0x93 \
+ } \
+ }
+
+/*1201d357-8417-4926-a694-e6408fbedcf8*/
+#define NS_SHAREPICKER_CID \
+ { \
+ 0x1201d357, 0x8417, 0x4926, { \
+ 0xa6, 0x94, 0xe6, 0x40, 0x8f, 0xbe, 0xdc, 0xf8 \
+ } \
+ }
+
+/* XXX the following CID's are not in order. This needs
+ to be fixed. */
+
+//-----------------------------------------------------------
+// Menus
+//-----------------------------------------------------------
+
+// {0B3FE5AA-BC72-4303-85AE-76365DF1251D}
+#define NS_NATIVEMENUSERVICE_CID \
+ { \
+ 0x0B3FE5AA, 0xBC72, 0x4303, { \
+ 0x85, 0xAE, 0x76, 0x36, 0x5D, 0xF1, 0x25, 0x1D \
+ } \
+ }
+
+// {F6CD4F21-53AF-11d2-8DC4-00609703C14E}
+#define NS_POPUPMENU_CID \
+ { \
+ 0xf6cd4f21, 0x53af, 0x11d2, { \
+ 0x8d, 0xc4, 0x0, 0x60, 0x97, 0x3, 0xc1, 0x4e \
+ } \
+ }
+
+// {1F39AE50-B6A0-4B37-90F4-60AF614193D8}
+#define NS_STANDALONENATIVEMENU_CID \
+ { \
+ 0x1F39AE50, 0xB6A0, 0x4B37, { \
+ 0x90, 0xF4, 0x60, 0xAF, 0x61, 0x41, 0x93, 0xD8 \
+ } \
+ }
+
+// {2451BAED-8DC3-46D9-9E30-96E1BAA03666}
+#define NS_MACDOCKSUPPORT_CID \
+ { \
+ 0x2451BAED, 0x8DC3, 0x46D9, { \
+ 0x9E, 0x30, 0x96, 0xE1, 0xBA, 0xA0, 0x36, 0x66 \
+ } \
+ }
+
+// {74EA4101-A5BB-49BC-9984-66DA8B225A37}
+#define NS_MACFINDERPROGRESS_CID \
+ { \
+ 0x74EA4101, 0xA5BB, 0x49BC, { \
+ 0x99, 0x84, 0x66, 0xDA, 0x8B, 0x22, 0x5A, 0x37 \
+ } \
+ }
+
+// {de59fe1a-46c8-490f-b04d-34545acb06c9}
+#define NS_MACSHARINGSERVICE_CID \
+ { \
+ 0xde59fe1a, 0x46c8, 0x490f, { \
+ 0xb0, 0x4d, 0x34, 0x54, 0x5a, 0xcb, 0x06, 0xc9 \
+ } \
+ }
+
+// {b6e1a890-b2b8-4883-a65f-9476f6185313}
+#define NS_SYSTEMSTATUSBAR_CID \
+ { \
+ 0xb6e1a890, 0xb2b8, 0x4883, { \
+ 0xa6, 0x5f, 0x94, 0x76, 0xf6, 0x18, 0x53, 0x13 \
+ } \
+ }
+
+// {ea109912-3acc-48de-b679-c23b6a122da5}
+#define NS_TOUCHBARHELPER_CID \
+ { \
+ 0xea109912, 0x3acc, 0x48de, { \
+ 0xb6, 0x79, 0xc2, 0x3b, 0x6a, 0x12, 0x2d, 0xa5 \
+ } \
+ }
+
+// {38f396e2-93c9-4a77-aaf7-2d50b9962186}
+#define NS_TOUCHBARUPDATER_CID \
+ { \
+ 0x38f396e2, 0x93c9, 0x4a77, { \
+ 0xaa, 0xf7, 0x2d, 0x50, 0xb9, 0x96, 0x21, 0x86 \
+ } \
+ }
+
+// {77441d17-f29c-49d7-982f-f20a5ab5a900}
+#define NS_TOUCHBARINPUT_CID \
+ { \
+ 0x77441d17, 0xf29c, 0x49d7, { \
+ 0x98, 0x2f, 0xf2, 0x0a, 0x5a, 0xb5, 0xa9, 0x00 \
+ } \
+ }
+
+//-----------------------------------------------------------
+// Drag & Drop & Clipboard
+//-----------------------------------------------------------
+// {8B5314BB-DB01-11d2-96CE-0060B0FB9956}
+#define NS_DRAGSERVICE_CID \
+ { \
+ 0x8b5314bb, 0xdb01, 0x11d2, { \
+ 0x96, 0xce, 0x0, 0x60, 0xb0, 0xfb, 0x99, 0x56 \
+ } \
+ }
+
+// {8B5314BC-DB01-11d2-96CE-0060B0FB9956}
+#define NS_TRANSFERABLE_CID \
+ { \
+ 0x8b5314bc, 0xdb01, 0x11d2, { \
+ 0x96, 0xce, 0x0, 0x60, 0xb0, 0xfb, 0x99, 0x56 \
+ } \
+ }
+
+// {8B5314BA-DB01-11d2-96CE-0060B0FB9956}
+#define NS_CLIPBOARD_CID \
+ { \
+ 0x8b5314ba, 0xdb01, 0x11d2, { \
+ 0x96, 0xce, 0x0, 0x60, 0xb0, 0xfb, 0x99, 0x56 \
+ } \
+ }
+
+// {77221D5A-1DD2-11B2-8C69-C710F15D2ED5}
+#define NS_CLIPBOARDHELPER_CID \
+ { \
+ 0x77221d5a, 0x1dd2, 0x11b2, { \
+ 0x8c, 0x69, 0xc7, 0x10, 0xf1, 0x5d, 0x2e, 0xd5 \
+ } \
+ }
+
+// {8B5314BD-DB01-11d2-96CE-0060B0FB9956}
+#define NS_DATAFLAVOR_CID \
+ { \
+ 0x8b5314bd, 0xdb01, 0x11d2, { \
+ 0x96, 0xce, 0x0, 0x60, 0xb0, 0xfb, 0x99, 0x56 \
+ } \
+ }
+
+// {948A0023-E3A7-11d2-96CF-0060B0FB9956}
+#define NS_HTMLFORMATCONVERTER_CID \
+ { \
+ 0x948a0023, 0xe3a7, 0x11d2, { \
+ 0x96, 0xcf, 0x0, 0x60, 0xb0, 0xfb, 0x99, 0x56 \
+ } \
+ }
+
+//-----------------------------------------------------------
+// Other
+//-----------------------------------------------------------
+// {B148EED2-236D-11d3-B35C-00A0CC3C1CDE}
+#define NS_SOUND_CID \
+ { \
+ 0xb148eed2, 0x236d, 0x11d3, { \
+ 0xb3, 0x5c, 0x0, 0xa0, 0xcc, 0x3c, 0x1c, 0xde \
+ } \
+ }
+
+#define NS_SCREENMANAGER_CID \
+ { \
+ 0xc401eb80, 0xf9ea, 0x11d3, { \
+ 0xbb, 0x6f, 0xe7, 0x32, 0xb7, 0x3e, 0xbe, 0x7c \
+ } \
+ }
+
+// {6987230e-0089-4e78-bc5f-1493ee7519fa}
+#define NS_IDLE_SERVICE_CID \
+ { \
+ 0x6987230e, 0x0098, 0x4e78, { \
+ 0xbc, 0x5f, 0x14, 0x93, 0xee, 0x75, 0x19, 0xfa \
+ } \
+ }
+
+#define NS_WIN_TASKBAR_CID \
+ { \
+ 0xb8e5bc54, 0xa22f, 0x4eb2, { \
+ 0xb0, 0x61, 0x24, 0xcb, 0x6d, 0x19, 0xc1, 0x5f \
+ } \
+ }
+
+// {73A5946F-608D-454f-9D33-0B8F8C7294B6}
+#define NS_WIN_JUMPLISTBUILDER_CID \
+ { \
+ 0x73a5946f, 0x608d, 0x454f, { \
+ 0x9d, 0x33, 0xb, 0x8f, 0x8c, 0x72, 0x94, 0xb6 \
+ } \
+ }
+
+// {2B9A1F2C-27CE-45b6-8D4E-755D0E34F8DB}
+#define NS_WIN_JUMPLISTITEM_CID \
+ { \
+ 0x2b9a1f2c, 0x27ce, 0x45b6, { \
+ 0x8d, 0x4e, 0x75, 0x5d, 0x0e, 0x34, 0xf8, 0xdb \
+ } \
+ }
+
+// {21F1F13B-F75A-42ad-867A-D91AD694447E}
+#define NS_WIN_JUMPLISTSEPARATOR_CID \
+ { \
+ 0x21f1f13b, 0xf75a, 0x42ad, { \
+ 0x86, 0x7a, 0xd9, 0x1a, 0xd6, 0x94, 0x44, 0x7e \
+ } \
+ }
+
+// {F72C5DC4-5A12-47be-BE28-AB105F33B08F}
+#define NS_WIN_JUMPLISTLINK_CID \
+ { \
+ 0xf72c5dc4, 0x5a12, 0x47be, { \
+ 0xbe, 0x28, 0xab, 0x10, 0x5f, 0x33, 0xb0, 0x8f \
+ } \
+ }
+
+// {B16656B2-5187-498f-ABF4-56346126BFDB}
+#define NS_WIN_JUMPLISTSHORTCUT_CID \
+ { \
+ 0xb16656b2, 0x5187, 0x498f, { \
+ 0xab, 0xf4, 0x56, 0x34, 0x61, 0x26, 0xbf, 0xdb \
+ } \
+ }
+
+// {e9096367-ddd9-45e4-b762-49c0c18b7119}
+#define NS_MACWEBAPPUTILS_CID \
+ { \
+ 0xe9096367, 0xddd9, 0x45e4, { \
+ 0xb7, 0x62, 0x49, 0xc0, 0xc1, 0x8b, 0x71, 0x19 \
+ } \
+ }
+
+// {a9339876-0027-430f-b953-84c9c11c2da3}
+#define NS_GTK_TASKBARPROGRESS_CID \
+ { \
+ 0xa9339876, 0x0027, 0x430f, { \
+ 0xb9, 0x53, 0x84, 0xc9, 0xc1, 0x1c, 0x2d, 0xa3 \
+ } \
+ }
+
+//-----------------------------------------------------------
+// Printing
+//-----------------------------------------------------------
+#define NS_DEVICE_CONTEXT_SPEC_CID \
+ { \
+ 0xd3f69889, 0xe13a, 0x4321, { \
+ 0x98, 0x0c, 0xa3, 0x93, 0x32, 0xe2, 0x1f, 0x34 \
+ } \
+ }
+
+#define NS_PRINTSETTINGSSERVICE_CID \
+ { \
+ 0x841387c8, 0x72e6, 0x484b, { \
+ 0x92, 0x96, 0xbf, 0x6e, 0xea, 0x80, 0xd5, 0x8a \
+ } \
+ }
+
+#define NS_PRINTER_LIST_CID \
+ { \
+ 0xa6cf9129, 0x15b3, 0x11d2, { \
+ 0x93, 0x2e, 0x00, 0x80, 0x5f, 0x8a, 0xdd, 0x32 \
+ } \
+ }
+
+#define NS_PRINTSESSION_CID \
+ { \
+ 0x2f977d53, 0x5485, 0x11d4, { \
+ 0x87, 0xe2, 0x00, 0x10, 0xa4, 0xe7, 0x5e, 0xf2 \
+ } \
+ }
+
+#define NS_PRINTDIALOGSERVICE_CID \
+ { \
+ 0x06beec76, 0xa183, 0x4d9f, { \
+ 0x85, 0xdd, 0x08, 0x5f, 0x26, 0xda, 0x56, 0x5a \
+ } \
+ }
+
+#define NS_GFXINFO_CID \
+ { \
+ 0xd755a760, 0x9f27, 0x11df, { \
+ 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66, 0x42, 0x42 \
+ } \
+ }
+
+#define NS_WINDOWS_UIUTILS_CID \
+ { \
+ 0xe04a55e8, 0xfee3, 0x4ea2, { \
+ 0xa9, 0x8b, 0x41, 0xd2, 0x62, 0x1a, 0xdc, 0x3c \
+ } \
+ }
diff --git a/widget/nsXPLookAndFeel.cpp b/widget/nsXPLookAndFeel.cpp
new file mode 100644
index 0000000000..e99d394626
--- /dev/null
+++ b/widget/nsXPLookAndFeel.cpp
@@ -0,0 +1,1127 @@
+/* -*- 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 "mozilla/ArrayUtils.h"
+
+#include "nscore.h"
+
+#include "nsXPLookAndFeel.h"
+#include "nsLookAndFeel.h"
+#include "HeadlessLookAndFeel.h"
+#include "RemoteLookAndFeel.h"
+#include "nsContentUtils.h"
+#include "nsCRT.h"
+#include "nsFont.h"
+#include "nsIXULRuntime.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/StaticPrefs_editor.h"
+#include "mozilla/StaticPrefs_findbar.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/widget/WidgetMessageUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryScalarEnums.h"
+
+#include "gfxPlatform.h"
+
+#include "qcms.h"
+
+#ifdef DEBUG
+# include "nsSize.h"
+#endif
+
+using namespace mozilla;
+
+// To make one of these prefs toggleable from a reftest add a user
+// pref in testing/profiles/reftest/user.js. For example, to make
+// ui.useAccessibilityTheme toggleable, add:
+//
+// user_pref("ui.useAccessibilityTheme", 0);
+//
+nsLookAndFeelIntPref nsXPLookAndFeel::sIntPrefs[] = {
+ {"ui.caretBlinkTime", IntID::CaretBlinkTime, false, 0},
+ {"ui.caretWidth", IntID::CaretWidth, false, 0},
+ {"ui.caretVisibleWithSelection", IntID::ShowCaretDuringSelection, false, 0},
+ {"ui.submenuDelay", IntID::SubmenuDelay, false, 0},
+ {"ui.dragThresholdX", IntID::DragThresholdX, false, 0},
+ {"ui.dragThresholdY", IntID::DragThresholdY, false, 0},
+ {"ui.useAccessibilityTheme", IntID::UseAccessibilityTheme, false, 0},
+ {"ui.menusCanOverlapOSBar", IntID::MenusCanOverlapOSBar, false, 0},
+ {"ui.useOverlayScrollbars", IntID::UseOverlayScrollbars, false, 0},
+ {"ui.scrollbarDisplayOnMouseMove", IntID::ScrollbarDisplayOnMouseMove,
+ false, 0},
+ {"ui.scrollbarFadeBeginDelay", IntID::ScrollbarFadeBeginDelay, false, 0},
+ {"ui.scrollbarFadeDuration", IntID::ScrollbarFadeDuration, false, 0},
+ {"ui.showHideScrollbars", IntID::ShowHideScrollbars, false, 0},
+ {"ui.skipNavigatingDisabledMenuItem", IntID::SkipNavigatingDisabledMenuItem,
+ false, 0},
+ {"ui.treeOpenDelay", IntID::TreeOpenDelay, false, 0},
+ {"ui.treeCloseDelay", IntID::TreeCloseDelay, false, 0},
+ {"ui.treeLazyScrollDelay", IntID::TreeLazyScrollDelay, false, 0},
+ {"ui.treeScrollDelay", IntID::TreeScrollDelay, false, 0},
+ {"ui.treeScrollLinesMax", IntID::TreeScrollLinesMax, false, 0},
+ {"accessibility.tabfocus", IntID::TabFocusModel, false, 0},
+ {"ui.alertNotificationOrigin", IntID::AlertNotificationOrigin, false, 0},
+ {"ui.scrollToClick", IntID::ScrollToClick, false, 0},
+ {"ui.IMERawInputUnderlineStyle", IntID::IMERawInputUnderlineStyle, false,
+ 0},
+ {"ui.IMESelectedRawTextUnderlineStyle",
+ IntID::IMESelectedRawTextUnderlineStyle, false, 0},
+ {"ui.IMEConvertedTextUnderlineStyle", IntID::IMEConvertedTextUnderlineStyle,
+ false, 0},
+ {"ui.IMESelectedConvertedTextUnderlineStyle",
+ IntID::IMESelectedConvertedTextUnderline, false, 0},
+ {"ui.SpellCheckerUnderlineStyle", IntID::SpellCheckerUnderlineStyle, false,
+ 0},
+ {"ui.scrollbarButtonAutoRepeatBehavior",
+ IntID::ScrollbarButtonAutoRepeatBehavior, false, 0},
+ {"ui.tooltipDelay", IntID::TooltipDelay, false, 0},
+ {"ui.contextMenuOffsetVertical", IntID::ContextMenuOffsetVertical, false,
+ 0},
+ {"ui.contextMenuOffsetHorizontal", IntID::ContextMenuOffsetHorizontal,
+ false, 0},
+ {"ui.GtkCSDAvailable", IntID::GTKCSDAvailable, false, 0},
+ {"ui.GtkCSDHideTitlebarByDefault", IntID::GTKCSDHideTitlebarByDefault,
+ false, 0},
+ {"ui.GtkCSDTransparentBackground", IntID::GTKCSDTransparentBackground,
+ false, 0},
+ {"ui.GtkCSDMinimizeButton", IntID::GTKCSDMinimizeButton, false, 0},
+ {"ui.GtkCSDMaximizeButton", IntID::GTKCSDMaximizeButton, false, 0},
+ {"ui.GtkCSDCloseButton", IntID::GTKCSDCloseButton, false, 0},
+ {"ui.systemUsesDarkTheme", IntID::SystemUsesDarkTheme, false, 0},
+ {"ui.prefersReducedMotion", IntID::PrefersReducedMotion, false, 0},
+ {"ui.primaryPointerCapabilities", IntID::PrimaryPointerCapabilities, false,
+ 6 /* fine and hover-capable pointer, i.e. mouse-type */},
+ {"ui.allPointerCapabilities", IntID::AllPointerCapabilities, false,
+ 6 /* fine and hover-capable pointer, i.e. mouse-type */},
+ {"ui.scrollArrowStyle", IntID::ScrollArrowStyle, false, 0},
+};
+
+nsLookAndFeelFloatPref nsXPLookAndFeel::sFloatPrefs[] = {
+ {"ui.IMEUnderlineRelativeSize", FloatID::IMEUnderlineRelativeSize, false,
+ 0},
+ {"ui.SpellCheckerUnderlineRelativeSize",
+ FloatID::SpellCheckerUnderlineRelativeSize, false, 0},
+ {"ui.caretAspectRatio", FloatID::CaretAspectRatio, false, 0},
+};
+
+// This array MUST be kept in the same order as the color list in
+// specified/color.rs
+/* XXX If you add any strings longer than
+ * "ui.-moz-mac-active-source-list-selection"
+ * to the following array then you MUST update the
+ * sizes of the sColorPrefs array in nsXPLookAndFeel.h
+ */
+const char nsXPLookAndFeel::sColorPrefs[][41] = {
+ "ui.windowBackground",
+ "ui.windowForeground",
+ "ui.widgetBackground",
+ "ui.widgetForeground",
+ "ui.widgetSelectBackground",
+ "ui.widgetSelectForeground",
+ "ui.widget3DHighlight",
+ "ui.widget3DShadow",
+ "ui.textBackground",
+ "ui.textForeground",
+ "ui.textSelectBackground",
+ "ui.textSelectForeground",
+ "ui.textSelectForegroundCustom",
+ "ui.textSelectBackgroundDisabled",
+ "ui.textSelectBackgroundAttention",
+ "ui.textHighlightBackground",
+ "ui.textHighlightForeground",
+ "ui.IMERawInputBackground",
+ "ui.IMERawInputForeground",
+ "ui.IMERawInputUnderline",
+ "ui.IMESelectedRawTextBackground",
+ "ui.IMESelectedRawTextForeground",
+ "ui.IMESelectedRawTextUnderline",
+ "ui.IMEConvertedTextBackground",
+ "ui.IMEConvertedTextForeground",
+ "ui.IMEConvertedTextUnderline",
+ "ui.IMESelectedConvertedTextBackground",
+ "ui.IMESelectedConvertedTextForeground",
+ "ui.IMESelectedConvertedTextUnderline",
+ "ui.SpellCheckerUnderline",
+ "ui.themedScrollbar",
+ "ui.themedScrollbarInactive",
+ "ui.themedScrollbarThumb",
+ "ui.themedScrollbarThumbHover",
+ "ui.themedScrollbarThumbActive",
+ "ui.themedScrollbarThumbInactive",
+ "ui.activeborder",
+ "ui.activecaption",
+ "ui.appworkspace",
+ "ui.background",
+ "ui.buttonface",
+ "ui.buttonhighlight",
+ "ui.buttonshadow",
+ "ui.buttontext",
+ "ui.captiontext",
+ "ui.-moz-field",
+ "ui.-moz-fieldtext",
+ "ui.graytext",
+ "ui.highlight",
+ "ui.highlighttext",
+ "ui.inactiveborder",
+ "ui.inactivecaption",
+ "ui.inactivecaptiontext",
+ "ui.infobackground",
+ "ui.infotext",
+ "ui.menu",
+ "ui.menutext",
+ "ui.scrollbar",
+ "ui.threeddarkshadow",
+ "ui.threedface",
+ "ui.threedhighlight",
+ "ui.threedlightshadow",
+ "ui.threedshadow",
+ "ui.window",
+ "ui.windowframe",
+ "ui.windowtext",
+ "ui.-moz-buttondefault",
+ "ui.-moz-default-color",
+ "ui.-moz-default-background-color",
+ "ui.-moz-dialog",
+ "ui.-moz-dialogtext",
+ "ui.-moz-dragtargetzone",
+ "ui.-moz-cellhighlight",
+ "ui.-moz_cellhighlighttext",
+ "ui.-moz-html-cellhighlight",
+ "ui.-moz-html-cellhighlighttext",
+ "ui.-moz-buttonhoverface",
+ "ui.-moz_buttonhovertext",
+ "ui.-moz_menuhover",
+ "ui.-moz_menuhovertext",
+ "ui.-moz_menubartext",
+ "ui.-moz_menubarhovertext",
+ "ui.-moz_eventreerow",
+ "ui.-moz_oddtreerow",
+ "ui.-moz-gtk-buttonactivetext",
+ "ui.-moz-mac-buttonactivetext",
+ "ui.-moz_mac_chrome_active",
+ "ui.-moz_mac_chrome_inactive",
+ "ui.-moz-mac-defaultbuttontext",
+ "ui.-moz-mac-focusring",
+ "ui.-moz-mac-menuselect",
+ "ui.-moz-mac-menushadow",
+ "ui.-moz-mac-menutextdisable",
+ "ui.-moz-mac-menutextselect",
+ "ui.-moz_mac_disabledtoolbartext",
+ "ui.-moz-mac-secondaryhighlight",
+ "ui.-moz-mac-vibrancy-light",
+ "ui.-moz-mac-vibrancy-dark",
+ "ui.-moz-mac-vibrant-titlebar-light",
+ "ui.-moz-mac-vibrant-titlebar-dark",
+ "ui.-moz-mac-menupopup",
+ "ui.-moz-mac-menuitem",
+ "ui.-moz-mac-active-menuitem",
+ "ui.-moz-mac-source-list",
+ "ui.-moz-mac-source-list-selection",
+ "ui.-moz-mac-active-source-list-selection",
+ "ui.-moz-mac-tooltip",
+ "ui.-moz-win-accentcolor",
+ "ui.-moz-win-accentcolortext",
+ "ui.-moz-win-mediatext",
+ "ui.-moz-win-communicationstext",
+ "ui.-moz-nativehyperlinktext",
+ "ui.-moz-hyperlinktext",
+ "ui.-moz-activehyperlinktext",
+ "ui.-moz-visitedhyperlinktext",
+ "ui.-moz-comboboxtext",
+ "ui.-moz-combobox",
+ "ui.-moz-gtk-info-bar-text",
+ "ui.-moz-colheadertext",
+ "ui.-moz-colheaderhovertext"};
+
+int32_t nsXPLookAndFeel::sCachedColors[size_t(LookAndFeel::ColorID::End)] = {0};
+int32_t nsXPLookAndFeel::sCachedColorBits[COLOR_CACHE_SIZE] = {0};
+
+bool nsXPLookAndFeel::sInitialized = false;
+
+nsXPLookAndFeel* nsXPLookAndFeel::sInstance = nullptr;
+bool nsXPLookAndFeel::sShutdown = false;
+
+// static
+nsXPLookAndFeel* nsXPLookAndFeel::GetInstance() {
+ if (sInstance) {
+ return sInstance;
+ }
+
+ NS_ENSURE_TRUE(!sShutdown, nullptr);
+
+ // If we're in a content process, then the parent process will have supplied
+ // us with an initial FullLookAndFeel object (for when the RemoteLookAndFeel
+ // is to be used) or an initial LookAndFeelCache object (for regular
+ // LookAndFeel implementations). We grab this data from the ContentChild,
+ // where it's been temporarily stashed, and initialize our new LookAndFeel
+ // object with it.
+
+ LookAndFeelCache* lnfCache = nullptr;
+ FullLookAndFeel* fullLnf = nullptr;
+ widget::LookAndFeelData* lnfData = nullptr;
+
+ if (auto* cc = mozilla::dom::ContentChild::GetSingleton()) {
+ lnfData = &cc->BorrowLookAndFeelData();
+ switch (lnfData->type()) {
+ case widget::LookAndFeelData::TLookAndFeelCache:
+ lnfCache = &lnfData->get_LookAndFeelCache();
+ break;
+ case widget::LookAndFeelData::TFullLookAndFeel:
+ fullLnf = &lnfData->get_FullLookAndFeel();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected LookAndFeelData type");
+ }
+ }
+
+ if (fullLnf) {
+ sInstance = new widget::RemoteLookAndFeel(std::move(*fullLnf));
+ } else if (gfxPlatform::IsHeadless()) {
+ sInstance = new widget::HeadlessLookAndFeel(lnfCache);
+ } else {
+ sInstance = new nsLookAndFeel(lnfCache);
+ }
+
+ // This is only ever used once during initialization, and can be cleared now.
+ if (lnfData) {
+ *lnfData = widget::LookAndFeelData{};
+ }
+
+ return sInstance;
+}
+
+// static
+void nsXPLookAndFeel::Shutdown() {
+ if (sShutdown) {
+ return;
+ }
+ sShutdown = true;
+ delete sInstance;
+ sInstance = nullptr;
+}
+
+// static
+void nsXPLookAndFeel::IntPrefChanged(nsLookAndFeelIntPref* data) {
+ if (!data) {
+ return;
+ }
+
+ int32_t intpref;
+ nsresult rv = Preferences::GetInt(data->name, &intpref);
+ if (NS_FAILED(rv)) {
+ data->isSet = false;
+
+#ifdef DEBUG_akkana
+ printf("====== Cleared int pref %s\n", data->name);
+#endif
+ } else {
+ data->intVar = intpref;
+ data->isSet = true;
+
+#ifdef DEBUG_akkana
+ printf("====== Changed int pref %s to %d\n", data->name, data->intVar);
+#endif
+ }
+
+ // Int prefs can't change our system colors or fonts.
+ NotifyChangedAllWindows(widget::ThemeChangeKind::MediaQueriesOnly);
+}
+
+// static
+void nsXPLookAndFeel::FloatPrefChanged(nsLookAndFeelFloatPref* data) {
+ if (!data) {
+ return;
+ }
+
+ int32_t intpref;
+ nsresult rv = Preferences::GetInt(data->name, &intpref);
+ if (NS_FAILED(rv)) {
+ data->isSet = false;
+
+#ifdef DEBUG_akkana
+ printf("====== Cleared float pref %s\n", data->name);
+#endif
+ } else {
+ data->floatVar = (float)intpref / 100.0f;
+ data->isSet = true;
+
+#ifdef DEBUG_akkana
+ printf("====== Changed float pref %s to %f\n", data->name);
+#endif
+ }
+
+ // Float prefs can't change our system colors or fonts.
+ NotifyChangedAllWindows(widget::ThemeChangeKind::MediaQueriesOnly);
+}
+
+// static
+void nsXPLookAndFeel::ColorPrefChanged(unsigned int index,
+ const char* prefName) {
+ nsAutoString colorStr;
+ nsresult rv = Preferences::GetString(prefName, colorStr);
+ if (NS_SUCCEEDED(rv) && !colorStr.IsEmpty()) {
+ nscolor thecolor;
+ if (colorStr[0] == char16_t('#')) {
+ if (NS_HexToRGBA(nsDependentString(colorStr, 1), nsHexColorType::NoAlpha,
+ &thecolor)) {
+ int32_t id = NS_PTR_TO_INT32(index);
+ CACHE_COLOR(id, thecolor);
+ }
+ } else if (NS_ColorNameToRGB(colorStr, &thecolor)) {
+ int32_t id = NS_PTR_TO_INT32(index);
+ CACHE_COLOR(id, thecolor);
+#ifdef DEBUG_akkana
+ printf("====== Changed color pref %s to 0x%lx\n", prefName, thecolor);
+#endif
+ }
+ } else {
+ // Reset to the default color, by clearing the cache
+ // to force lookup when the color is next used
+ int32_t id = NS_PTR_TO_INT32(index);
+ CLEAR_COLOR_CACHE(id);
+
+#ifdef DEBUG_akkana
+ printf("====== Cleared color pref %s\n", prefName);
+#endif
+ }
+
+ // Color prefs affect style, because they by definition change system colors.
+ NotifyChangedAllWindows(widget::ThemeChangeKind::Style);
+}
+
+void nsXPLookAndFeel::InitFromPref(nsLookAndFeelIntPref* aPref) {
+ int32_t intpref;
+ nsresult rv = Preferences::GetInt(aPref->name, &intpref);
+ if (NS_SUCCEEDED(rv)) {
+ aPref->isSet = true;
+ aPref->intVar = intpref;
+ }
+}
+
+void nsXPLookAndFeel::InitFromPref(nsLookAndFeelFloatPref* aPref) {
+ int32_t intpref;
+ nsresult rv = Preferences::GetInt(aPref->name, &intpref);
+ if (NS_SUCCEEDED(rv)) {
+ aPref->isSet = true;
+ aPref->floatVar = (float)intpref / 100.0f;
+ }
+}
+
+void nsXPLookAndFeel::InitColorFromPref(int32_t i) {
+ static_assert(ArrayLength(sColorPrefs) == size_t(ColorID::End),
+ "Should have a pref for each color value");
+
+ nsAutoString colorStr;
+ nsresult rv = Preferences::GetString(sColorPrefs[i], colorStr);
+ if (NS_FAILED(rv) || colorStr.IsEmpty()) {
+ return;
+ }
+ nscolor thecolor;
+ if (colorStr[0] == char16_t('#')) {
+ nsAutoString hexString;
+ colorStr.Right(hexString, colorStr.Length() - 1);
+ if (NS_HexToRGBA(hexString, nsHexColorType::NoAlpha, &thecolor)) {
+ CACHE_COLOR(i, thecolor);
+ }
+ } else if (NS_ColorNameToRGB(colorStr, &thecolor)) {
+ CACHE_COLOR(i, thecolor);
+ }
+}
+
+// static
+void nsXPLookAndFeel::OnPrefChanged(const char* aPref, void* aClosure) {
+ // looping in the same order as in ::Init
+
+ nsDependentCString prefName(aPref);
+ unsigned int i;
+ for (i = 0; i < ArrayLength(sIntPrefs); ++i) {
+ if (prefName.Equals(sIntPrefs[i].name)) {
+ IntPrefChanged(&sIntPrefs[i]);
+ return;
+ }
+ }
+
+ for (i = 0; i < ArrayLength(sFloatPrefs); ++i) {
+ if (prefName.Equals(sFloatPrefs[i].name)) {
+ FloatPrefChanged(&sFloatPrefs[i]);
+ return;
+ }
+ }
+
+ for (i = 0; i < ArrayLength(sColorPrefs); ++i) {
+ if (prefName.Equals(sColorPrefs[i])) {
+ ColorPrefChanged(i, sColorPrefs[i]);
+ return;
+ }
+ }
+}
+
+//
+// Read values from the user's preferences.
+// This is done once at startup, but since the user's preferences
+// haven't actually been read yet at that time, we also have to
+// set a callback to inform us of changes to each pref.
+//
+void nsXPLookAndFeel::Init() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ // Say we're already initialized, and take the chance that it might fail;
+ // protects against some other process writing to our static variables.
+ sInitialized = true;
+
+ // XXX If we could reorganize the pref names, we should separate the branch
+ // for each types. Then, we could reduce the unnecessary loop from
+ // nsXPLookAndFeel::OnPrefChanged().
+ Preferences::RegisterPrefixCallback(OnPrefChanged, "ui.");
+ // We really do just want the accessibility.tabfocus pref, not other prefs
+ // that start with that string.
+ Preferences::RegisterCallback(OnPrefChanged, "accessibility.tabfocus");
+
+ unsigned int i;
+ for (i = 0; i < ArrayLength(sIntPrefs); ++i) {
+ InitFromPref(&sIntPrefs[i]);
+ }
+
+ for (i = 0; i < ArrayLength(sFloatPrefs); ++i) {
+ InitFromPref(&sFloatPrefs[i]);
+ }
+
+ for (i = 0; i < ArrayLength(sColorPrefs); ++i) {
+ InitColorFromPref(i);
+ }
+}
+
+nsXPLookAndFeel::~nsXPLookAndFeel() {
+ NS_ASSERTION(sInstance == this,
+ "This destroying instance isn't the singleton instance");
+ sInstance = nullptr;
+}
+
+bool nsXPLookAndFeel::IsSpecialColor(ColorID aID, nscolor& aColor) {
+ switch (aID) {
+ case ColorID::TextSelectForeground:
+ return (aColor == NS_DONT_CHANGE_COLOR);
+ case ColorID::IMESelectedRawTextBackground:
+ case ColorID::IMESelectedConvertedTextBackground:
+ case ColorID::IMERawInputBackground:
+ case ColorID::IMEConvertedTextBackground:
+ case ColorID::IMESelectedRawTextForeground:
+ case ColorID::IMESelectedConvertedTextForeground:
+ case ColorID::IMERawInputForeground:
+ case ColorID::IMEConvertedTextForeground:
+ case ColorID::IMERawInputUnderline:
+ case ColorID::IMEConvertedTextUnderline:
+ case ColorID::IMESelectedRawTextUnderline:
+ case ColorID::IMESelectedConvertedTextUnderline:
+ case ColorID::SpellCheckerUnderline:
+ return NS_IS_SELECTION_SPECIAL_COLOR(aColor);
+ default:
+ /*
+ * In GetColor(), every color that is not a special color is color
+ * corrected. Use false to make other colors color corrected.
+ */
+ return false;
+ }
+ return false;
+}
+
+bool nsXPLookAndFeel::ColorIsNotCSSAccessible(ColorID aID) {
+ bool result = false;
+
+ switch (aID) {
+ case ColorID::WindowBackground:
+ case ColorID::WindowForeground:
+ case ColorID::WidgetBackground:
+ case ColorID::WidgetForeground:
+ case ColorID::WidgetSelectBackground:
+ case ColorID::WidgetSelectForeground:
+ case ColorID::Widget3DHighlight:
+ case ColorID::Widget3DShadow:
+ case ColorID::TextBackground:
+ case ColorID::TextForeground:
+ case ColorID::TextSelectBackground:
+ case ColorID::TextSelectForeground:
+ case ColorID::TextSelectBackgroundDisabled:
+ case ColorID::TextSelectBackgroundAttention:
+ case ColorID::TextHighlightBackground:
+ case ColorID::TextHighlightForeground:
+ case ColorID::IMERawInputBackground:
+ case ColorID::IMERawInputForeground:
+ case ColorID::IMERawInputUnderline:
+ case ColorID::IMESelectedRawTextBackground:
+ case ColorID::IMESelectedRawTextForeground:
+ case ColorID::IMESelectedRawTextUnderline:
+ case ColorID::IMEConvertedTextBackground:
+ case ColorID::IMEConvertedTextForeground:
+ case ColorID::IMEConvertedTextUnderline:
+ case ColorID::IMESelectedConvertedTextBackground:
+ case ColorID::IMESelectedConvertedTextForeground:
+ case ColorID::IMESelectedConvertedTextUnderline:
+ case ColorID::SpellCheckerUnderline:
+ result = true;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+nscolor nsXPLookAndFeel::GetStandinForNativeColor(ColorID aID) {
+ nscolor result = NS_RGB(0xFF, 0xFF, 0xFF);
+
+ // The stand-in colors are taken from the Windows 7 Aero theme
+ // except Mac-specific colors which are taken from Mac OS 10.7.
+ switch (aID) {
+ // CSS 2 colors:
+ case ColorID::Activeborder:
+ result = NS_RGB(0xB4, 0xB4, 0xB4);
+ break;
+ case ColorID::Activecaption:
+ result = NS_RGB(0x99, 0xB4, 0xD1);
+ break;
+ case ColorID::Appworkspace:
+ result = NS_RGB(0xAB, 0xAB, 0xAB);
+ break;
+ case ColorID::Background:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::Buttonface:
+ result = NS_RGB(0xF0, 0xF0, 0xF0);
+ break;
+ case ColorID::Buttonhighlight:
+ result = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::Buttonshadow:
+ result = NS_RGB(0xA0, 0xA0, 0xA0);
+ break;
+ case ColorID::Buttontext:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::Captiontext:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::Graytext:
+ result = NS_RGB(0x6D, 0x6D, 0x6D);
+ break;
+ case ColorID::Highlight:
+ result = NS_RGB(0x33, 0x99, 0xFF);
+ break;
+ case ColorID::Highlighttext:
+ result = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::Inactiveborder:
+ result = NS_RGB(0xF4, 0xF7, 0xFC);
+ break;
+ case ColorID::Inactivecaption:
+ result = NS_RGB(0xBF, 0xCD, 0xDB);
+ break;
+ case ColorID::Inactivecaptiontext:
+ result = NS_RGB(0x43, 0x4E, 0x54);
+ break;
+ case ColorID::Infobackground:
+ result = NS_RGB(0xFF, 0xFF, 0xE1);
+ break;
+ case ColorID::Infotext:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::Menu:
+ result = NS_RGB(0xF0, 0xF0, 0xF0);
+ break;
+ case ColorID::Menutext:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::Scrollbar:
+ result = NS_RGB(0xC8, 0xC8, 0xC8);
+ break;
+ case ColorID::Threeddarkshadow:
+ result = NS_RGB(0x69, 0x69, 0x69);
+ break;
+ case ColorID::Threedface:
+ result = NS_RGB(0xF0, 0xF0, 0xF0);
+ break;
+ case ColorID::Threedhighlight:
+ result = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::Threedlightshadow:
+ result = NS_RGB(0xE3, 0xE3, 0xE3);
+ break;
+ case ColorID::Threedshadow:
+ result = NS_RGB(0xA0, 0xA0, 0xA0);
+ break;
+ case ColorID::Window:
+ result = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::Windowframe:
+ result = NS_RGB(0x64, 0x64, 0x64);
+ break;
+ case ColorID::Windowtext:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::MozButtondefault:
+ result = NS_RGB(0x69, 0x69, 0x69);
+ break;
+ case ColorID::Field:
+ result = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::Fieldtext:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::MozDialog:
+ result = NS_RGB(0xF0, 0xF0, 0xF0);
+ break;
+ case ColorID::MozDialogtext:
+ case ColorID::MozColheadertext:
+ case ColorID::MozColheaderhovertext:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::MozDragtargetzone:
+ result = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::MozCellhighlight:
+ result = NS_RGB(0xF0, 0xF0, 0xF0);
+ break;
+ case ColorID::MozCellhighlighttext:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::MozHtmlCellhighlight:
+ result = NS_RGB(0x33, 0x99, 0xFF);
+ break;
+ case ColorID::MozHtmlCellhighlighttext:
+ result = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::MozButtonhoverface:
+ result = NS_RGB(0xF0, 0xF0, 0xF0);
+ break;
+ case ColorID::MozButtonhovertext:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::MozMenuhover:
+ result = NS_RGB(0x33, 0x99, 0xFF);
+ break;
+ case ColorID::MozMenuhovertext:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::MozMenubartext:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::MozMenubarhovertext:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::MozOddtreerow:
+ case ColorID::MozGtkButtonactivetext:
+ result = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::MozMacChromeActive:
+ result = NS_RGB(0xB2, 0xB2, 0xB2);
+ break;
+ case ColorID::MozMacChromeInactive:
+ result = NS_RGB(0xE1, 0xE1, 0xE1);
+ break;
+ case ColorID::MozMacFocusring:
+ result = NS_RGB(0x60, 0x9D, 0xD7);
+ break;
+ case ColorID::MozMacMenuselect:
+ result = NS_RGB(0x38, 0x75, 0xD7);
+ break;
+ case ColorID::MozMacMenushadow:
+ result = NS_RGB(0xA3, 0xA3, 0xA3);
+ break;
+ case ColorID::MozMacMenutextdisable:
+ result = NS_RGB(0x88, 0x88, 0x88);
+ break;
+ case ColorID::MozMacMenutextselect:
+ result = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::MozMacDisabledtoolbartext:
+ result = NS_RGB(0x3F, 0x3F, 0x3F);
+ break;
+ case ColorID::MozMacSecondaryhighlight:
+ result = NS_RGB(0xD4, 0xD4, 0xD4);
+ break;
+ case ColorID::MozMacVibrancyLight:
+ case ColorID::MozMacVibrantTitlebarLight:
+ result = NS_RGB(0xf7, 0xf7, 0xf7);
+ break;
+ case ColorID::MozMacVibrancyDark:
+ case ColorID::MozMacVibrantTitlebarDark:
+ result = NS_RGB(0x28, 0x28, 0x28);
+ break;
+ case ColorID::MozMacMenupopup:
+ result = NS_RGB(0xe6, 0xe6, 0xe6);
+ break;
+ case ColorID::MozMacMenuitem:
+ result = NS_RGB(0xe6, 0xe6, 0xe6);
+ break;
+ case ColorID::MozMacActiveMenuitem:
+ result = NS_RGB(0x0a, 0x64, 0xdc);
+ break;
+ case ColorID::MozMacSourceList:
+ result = NS_RGB(0xf7, 0xf7, 0xf7);
+ break;
+ case ColorID::MozMacSourceListSelection:
+ result = NS_RGB(0xc8, 0xc8, 0xc8);
+ break;
+ case ColorID::MozMacActiveSourceListSelection:
+ result = NS_RGB(0x0a, 0x64, 0xdc);
+ break;
+ case ColorID::MozMacTooltip:
+ result = NS_RGB(0xf7, 0xf7, 0xf7);
+ break;
+ case ColorID::MozWinAccentcolor:
+ // Seems to be the default color (hardcoded because of bug 1065998)
+ result = NS_RGB(0x9E, 0x9E, 0x9E);
+ break;
+ case ColorID::MozWinAccentcolortext:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::MozWinMediatext:
+ result = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::MozWinCommunicationstext:
+ result = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::MozNativehyperlinktext:
+ result = NS_RGB(0x00, 0x66, 0xCC);
+ break;
+ case ColorID::MozComboboxtext:
+ result = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::MozCombobox:
+ result = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+//
+// All these routines will return NS_OK if they have a value,
+// in which case the nsLookAndFeel should use that value;
+// otherwise we'll return NS_ERROR_NOT_AVAILABLE, in which case, the
+// platform-specific nsLookAndFeel should use its own values instead.
+//
+nsresult nsXPLookAndFeel::GetColorValue(ColorID aID,
+ bool aUseStandinsForNativeColors,
+ nscolor& aResult) {
+ if (!sInitialized) Init();
+
+ // define DEBUG_SYSTEM_COLOR_USE if you want to debug system color
+ // use in a skin that uses them. When set, it will make all system
+ // color pairs that are appropriate for foreground/background
+ // pairing the same. This means if the skin is using system colors
+ // correctly you will not be able to see *any* text.
+#undef DEBUG_SYSTEM_COLOR_USE
+
+#ifdef DEBUG_SYSTEM_COLOR_USE
+ {
+ nsresult rv = NS_OK;
+ switch (aID) {
+ // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ case ColorID::Activecaption:
+ // active window caption background
+ case ColorID::Captiontext:
+ // text in active window caption
+ aResult = NS_RGB(0xff, 0x00, 0x00);
+ break;
+
+ case ColorID::Highlight:
+ // background of selected item
+ case ColorID::Highlighttext:
+ // text of selected item
+ aResult = NS_RGB(0xff, 0xff, 0x00);
+ break;
+
+ case ColorID::Inactivecaption:
+ // inactive window caption
+ case ColorID::Inactivecaptiontext:
+ // text in inactive window caption
+ aResult = NS_RGB(0x66, 0x66, 0x00);
+ break;
+
+ case ColorID::Infobackground:
+ // tooltip background color
+ case ColorID::Infotext:
+ // tooltip text color
+ aResult = NS_RGB(0x00, 0xff, 0x00);
+ break;
+
+ case ColorID::Menu:
+ // menu background
+ case ColorID::Menutext:
+ // menu text
+ aResult = NS_RGB(0x00, 0xff, 0xff);
+ break;
+
+ case ColorID::Threedface:
+ case ColorID::Buttonface:
+ // 3-D face color
+ case ColorID::Buttontext:
+ // text on push buttons
+ aResult = NS_RGB(0x00, 0x66, 0x66);
+ break;
+
+ case ColorID::Window:
+ case ColorID::Windowtext:
+ aResult = NS_RGB(0x00, 0x00, 0xff);
+ break;
+
+ // from the CSS3 working draft (not yet finalized)
+ // http://www.w3.org/tr/2000/wd-css3-userint-20000216.html#color
+
+ case ColorID::Field:
+ case ColorID::Fieldtext:
+ aResult = NS_RGB(0xff, 0x00, 0xff);
+ break;
+
+ case ColorID::MozDialog:
+ case ColorID::MozDialogtext:
+ aResult = NS_RGB(0x66, 0x00, 0x66);
+ break;
+
+ default:
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_SUCCEEDED(rv)) return rv;
+ }
+#endif // DEBUG_SYSTEM_COLOR_USE
+
+ if (aUseStandinsForNativeColors &&
+ (ColorIsNotCSSAccessible(aID) ||
+ !nsContentUtils::UseStandinsForNativeColors())) {
+ aUseStandinsForNativeColors = false;
+ }
+
+ if (!aUseStandinsForNativeColors && IS_COLOR_CACHED(aID)) {
+ aResult = sCachedColors[uint32_t(aID)];
+ return NS_OK;
+ }
+
+ // There are no system color settings for these, so set them manually
+#ifndef XP_MACOSX
+ if (aID == ColorID::TextSelectBackgroundDisabled) {
+ // This is used to gray out the selection when it's not focused
+ // Used with nsISelectionController::SELECTION_DISABLED
+ aResult = NS_RGB(0xb0, 0xb0, 0xb0);
+ return NS_OK;
+ }
+#endif
+
+ if (aID == ColorID::TextSelectBackgroundAttention) {
+ if (StaticPrefs::findbar_modalHighlight() && !mozilla::FissionAutostart()) {
+ aResult = NS_RGBA(0, 0, 0, 0);
+ return NS_OK;
+ }
+
+ // This makes the selection stand out when typeaheadfind is on
+ // Used with nsISelectionController::SELECTION_ATTENTION
+ aResult = NS_RGB(0x38, 0xd8, 0x78);
+ return NS_OK;
+ }
+
+ if (aID == ColorID::TextHighlightBackground) {
+ // This makes the matched text stand out when findbar highlighting is on
+ // Used with nsISelectionController::SELECTION_FIND
+ aResult = NS_RGB(0xef, 0x0f, 0xff);
+ return NS_OK;
+ }
+
+ if (aID == ColorID::TextHighlightForeground) {
+ // The foreground color for the matched text in findbar highlighting
+ // Used with nsISelectionController::SELECTION_FIND
+ aResult = NS_RGB(0xff, 0xff, 0xff);
+ return NS_OK;
+ }
+
+ if (StaticPrefs::ui_use_native_colors() && aUseStandinsForNativeColors) {
+ aResult = GetStandinForNativeColor(aID);
+ return NS_OK;
+ }
+
+ if (StaticPrefs::ui_use_native_colors() &&
+ NS_SUCCEEDED(NativeGetColor(aID, aResult))) {
+ if (!mozilla::ServoStyleSet::IsInServoTraversal()) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if ((gfxPlatform::GetCMSMode() == eCMSMode_All) &&
+ !IsSpecialColor(aID, aResult)) {
+ qcms_transform* transform = gfxPlatform::GetCMSInverseRGBTransform();
+ if (transform) {
+ uint8_t color[4];
+ color[0] = NS_GET_R(aResult);
+ color[1] = NS_GET_G(aResult);
+ color[2] = NS_GET_B(aResult);
+ color[3] = NS_GET_A(aResult);
+ qcms_transform_data(transform, color, color, 1);
+ aResult = NS_RGBA(color[0], color[1], color[2], color[3]);
+ }
+ }
+
+ CACHE_COLOR(aID, aResult);
+ }
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsXPLookAndFeel::GetIntValue(IntID aID, int32_t& aResult) {
+ if (!sInitialized) Init();
+
+ for (unsigned int i = 0; i < ArrayLength(sIntPrefs); ++i) {
+ if (sIntPrefs[i].isSet && (sIntPrefs[i].id == aID)) {
+ aResult = sIntPrefs[i].intVar;
+ return NS_OK;
+ }
+ }
+
+ return NativeGetInt(aID, aResult);
+}
+
+nsresult nsXPLookAndFeel::GetFloatValue(FloatID aID, float& aResult) {
+ if (!sInitialized) Init();
+
+ for (unsigned int i = 0; i < ArrayLength(sFloatPrefs); ++i) {
+ if (sFloatPrefs[i].isSet && sFloatPrefs[i].id == aID) {
+ aResult = sFloatPrefs[i].floatVar;
+ return NS_OK;
+ }
+ }
+
+ return NativeGetFloat(aID, aResult);
+}
+
+void nsXPLookAndFeel::RefreshImpl() {
+ // Wipe out our color cache.
+ uint32_t i;
+ for (i = 0; i < uint32_t(ColorID::End); i++) {
+ sCachedColors[i] = 0;
+ }
+ for (i = 0; i < COLOR_CACHE_SIZE; i++) {
+ sCachedColorBits[i] = 0;
+ }
+
+ // Reinit color cache from prefs.
+ for (i = 0; i < uint32_t(ColorID::End); ++i) {
+ InitColorFromPref(i);
+ }
+
+ // Clear any cached FullLookAndFeel data, which is now invalid.
+ if (XRE_IsParentProcess()) {
+ widget::RemoteLookAndFeel::ClearCachedData();
+ }
+}
+
+widget::LookAndFeelCache nsXPLookAndFeel::GetCacheImpl() {
+ return LookAndFeelCache{};
+}
+
+static bool sRecordedLookAndFeelTelemetry = false;
+
+void nsXPLookAndFeel::RecordTelemetry() {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ if (sRecordedLookAndFeelTelemetry) {
+ return;
+ }
+
+ sRecordedLookAndFeelTelemetry = true;
+
+ int32_t i;
+ Telemetry::ScalarSet(
+ Telemetry::ScalarID::WIDGET_DARK_MODE,
+ NS_SUCCEEDED(GetIntValue(IntID::SystemUsesDarkTheme, i)) && i != 0);
+
+ RecordLookAndFeelSpecificTelemetry();
+}
+
+namespace mozilla {
+
+// static
+void LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind aKind) {
+ if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
+ obs->NotifyObservers(nullptr, "look-and-feel-changed",
+ reinterpret_cast<char16_t*>(uintptr_t(aKind)));
+ }
+}
+
+// static
+nsresult LookAndFeel::GetColor(ColorID aID, nscolor* aResult) {
+ return nsLookAndFeel::GetInstance()->GetColorValue(aID, false, *aResult);
+}
+
+nsresult LookAndFeel::GetColor(ColorID aID, bool aUseStandinsForNativeColors,
+ nscolor* aResult) {
+ return nsLookAndFeel::GetInstance()->GetColorValue(
+ aID, aUseStandinsForNativeColors, *aResult);
+}
+
+// static
+nsresult LookAndFeel::GetInt(IntID aID, int32_t* aResult) {
+ return nsLookAndFeel::GetInstance()->GetIntValue(aID, *aResult);
+}
+
+// static
+nsresult LookAndFeel::GetFloat(FloatID aID, float* aResult) {
+ return nsLookAndFeel::GetInstance()->GetFloatValue(aID, *aResult);
+}
+
+// static
+bool LookAndFeel::GetFont(FontID aID, nsString& aName, gfxFontStyle& aStyle) {
+ return nsLookAndFeel::GetInstance()->GetFontValue(aID, aName, aStyle);
+}
+
+// static
+char16_t LookAndFeel::GetPasswordCharacter() {
+ return nsLookAndFeel::GetInstance()->GetPasswordCharacterImpl();
+}
+
+// static
+bool LookAndFeel::GetEchoPassword() {
+ if (StaticPrefs::editor_password_mask_delay() >= 0) {
+ return StaticPrefs::editor_password_mask_delay() > 0;
+ }
+ return nsLookAndFeel::GetInstance()->GetEchoPasswordImpl();
+}
+
+// static
+uint32_t LookAndFeel::GetPasswordMaskDelay() {
+ int32_t delay = StaticPrefs::editor_password_mask_delay();
+ if (delay < 0) {
+ return nsLookAndFeel::GetInstance()->GetPasswordMaskDelayImpl();
+ }
+ return delay;
+}
+
+// static
+void LookAndFeel::Refresh() { nsLookAndFeel::GetInstance()->RefreshImpl(); }
+
+// static
+void LookAndFeel::NativeInit() { nsLookAndFeel::GetInstance()->NativeInit(); }
+
+// static
+widget::LookAndFeelCache LookAndFeel::GetCache() {
+ return nsLookAndFeel::GetInstance()->GetCacheImpl();
+}
+
+// static
+void LookAndFeel::SetCache(const widget::LookAndFeelCache& aCache) {
+ nsLookAndFeel::GetInstance()->SetCacheImpl(aCache);
+}
+
+// static
+void LookAndFeel::SetData(widget::FullLookAndFeel&& aTables) {
+ nsLookAndFeel::GetInstance()->SetDataImpl(std::move(aTables));
+}
+
+} // namespace mozilla
diff --git a/widget/nsXPLookAndFeel.h b/widget/nsXPLookAndFeel.h
new file mode 100644
index 0000000000..66b22294f7
--- /dev/null
+++ b/widget/nsXPLookAndFeel.h
@@ -0,0 +1,131 @@
+/* -*- 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 __nsXPLookAndFeel
+#define __nsXPLookAndFeel
+
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "mozilla/widget/LookAndFeelTypes.h"
+#include "nsTArray.h"
+
+class nsLookAndFeel;
+
+struct nsLookAndFeelIntPref {
+ const char* name;
+ mozilla::LookAndFeel::IntID id;
+ bool isSet;
+ int32_t intVar;
+};
+
+struct nsLookAndFeelFloatPref {
+ const char* name;
+ mozilla::LookAndFeel::FloatID id;
+ bool isSet;
+ float floatVar;
+};
+
+#define CACHE_BLOCK(x) (uint32_t(x) >> 5)
+#define CACHE_BIT(x) (1 << (uint32_t(x) & 31))
+
+#define COLOR_CACHE_SIZE (CACHE_BLOCK(uint32_t(LookAndFeel::ColorID::End)) + 1)
+#define IS_COLOR_CACHED(x) \
+ (CACHE_BIT(x) & nsXPLookAndFeel::sCachedColorBits[CACHE_BLOCK(x)])
+#define CLEAR_COLOR_CACHE(x) \
+ nsXPLookAndFeel::sCachedColors[uint32_t(x)] = 0; \
+ nsXPLookAndFeel::sCachedColorBits[CACHE_BLOCK(x)] &= ~(CACHE_BIT(x));
+#define CACHE_COLOR(x, y) \
+ nsXPLookAndFeel::sCachedColors[uint32_t(x)] = y; \
+ nsXPLookAndFeel::sCachedColorBits[CACHE_BLOCK(x)] |= CACHE_BIT(x);
+
+class nsXPLookAndFeel : public mozilla::LookAndFeel {
+ public:
+ virtual ~nsXPLookAndFeel();
+
+ static nsXPLookAndFeel* GetInstance();
+ static void Shutdown();
+
+ void Init();
+
+ // These functions will return a value specified by an override pref, if it
+ // exists, and otherwise will call into the NativeGetXxx function to get the
+ // platform-specific value.
+ //
+ // NS_ERROR_NOT_AVAILABLE is returned if there is neither an override pref or
+ // a platform-specific value.
+ nsresult GetColorValue(ColorID aID, bool aUseStandinsForNativeColors,
+ nscolor& aResult);
+ nsresult GetIntValue(IntID aID, int32_t& aResult);
+ nsresult GetFloatValue(FloatID aID, float& aResult);
+ // Same, but returns false if there is no platform-specific value.
+ // (There are no override prefs for font values.)
+ bool GetFontValue(FontID aID, nsString& aName, gfxFontStyle& aStyle) {
+ return NativeGetFont(aID, aName, aStyle);
+ }
+
+ virtual nsresult NativeGetInt(IntID aID, int32_t& aResult) = 0;
+ virtual nsresult NativeGetFloat(FloatID aID, float& aResult) = 0;
+ virtual nsresult NativeGetColor(ColorID aID, nscolor& aResult) = 0;
+ virtual bool NativeGetFont(FontID aID, nsString& aName,
+ gfxFontStyle& aStyle) = 0;
+
+ virtual void RefreshImpl();
+
+ virtual char16_t GetPasswordCharacterImpl() { return char16_t('*'); }
+
+ virtual bool GetEchoPasswordImpl() { return false; }
+
+ virtual uint32_t GetPasswordMaskDelayImpl() { return 600; }
+
+ using FullLookAndFeel = mozilla::widget::FullLookAndFeel;
+ using LookAndFeelCache = mozilla::widget::LookAndFeelCache;
+ using LookAndFeelInt = mozilla::widget::LookAndFeelInt;
+ using LookAndFeelFont = mozilla::widget::LookAndFeelFont;
+ using LookAndFeelColor = mozilla::widget::LookAndFeelColor;
+ using LookAndFeelTheme = mozilla::widget::LookAndFeelTheme;
+
+ virtual LookAndFeelCache GetCacheImpl();
+ virtual void SetCacheImpl(const LookAndFeelCache& aCache) {}
+ virtual void SetDataImpl(FullLookAndFeel&& aTables) {}
+
+ virtual void NativeInit() = 0;
+
+ virtual void WithThemeConfiguredForContent(
+ const std::function<void(const LookAndFeelTheme& aTheme)>& aFn) {
+ aFn(LookAndFeelTheme{});
+ }
+
+ protected:
+ nsXPLookAndFeel() = default;
+
+ static void IntPrefChanged(nsLookAndFeelIntPref* data);
+ static void FloatPrefChanged(nsLookAndFeelFloatPref* data);
+ static void ColorPrefChanged(unsigned int index, const char* prefName);
+ void InitFromPref(nsLookAndFeelIntPref* aPref);
+ void InitFromPref(nsLookAndFeelFloatPref* aPref);
+ void InitColorFromPref(int32_t aIndex);
+ bool IsSpecialColor(ColorID aID, nscolor& aColor);
+ bool ColorIsNotCSSAccessible(ColorID aID);
+ nscolor GetStandinForNativeColor(ColorID aID);
+ void RecordTelemetry();
+ virtual void RecordLookAndFeelSpecificTelemetry() {}
+
+ static void OnPrefChanged(const char* aPref, void* aClosure);
+
+ static bool sInitialized;
+ static nsLookAndFeelIntPref sIntPrefs[];
+ static nsLookAndFeelFloatPref sFloatPrefs[];
+ /* this length must not be shorter than the length of the longest string in
+ * the array see nsXPLookAndFeel.cpp
+ */
+ static const char sColorPrefs[][41];
+ static int32_t sCachedColors[size_t(LookAndFeel::ColorID::End)];
+ static int32_t sCachedColorBits[COLOR_CACHE_SIZE];
+
+ static nsXPLookAndFeel* sInstance;
+ static bool sShutdown;
+};
+
+#endif
diff --git a/widget/reftests/664925.xhtml b/widget/reftests/664925.xhtml
new file mode 100644
index 0000000000..b4a11a7386
--- /dev/null
+++ b/widget/reftests/664925.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><div><td style="-moz-appearance: progressbar;"></td></div></html>
diff --git a/widget/reftests/meter-fallback-default-style-ref.html b/widget/reftests/meter-fallback-default-style-ref.html
new file mode 100644
index 0000000000..cf6c2e521e
--- /dev/null
+++ b/widget/reftests/meter-fallback-default-style-ref.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+ <style>
+ div.meter-element {
+ /**
+ * The purpose of this test is to not show the native style.
+ * -moz-appearance: meterbar;
+ */
+ display: inline-block;
+ height: 1em;
+ width: 5em;
+ vertical-align: -0.2em;
+
+ background: linear-gradient(#e6e6e6, #e6e6e6, #eeeeee 20%, #cccccc 45%, #cccccc 55%);
+ }
+
+ div.meter-bar {
+ /**
+ * The purpose of this test is to not show the native style.
+ * -moz-appearance: meterchunk;
+ */
+
+ height: 100%;
+ width: 100%;
+
+ background: linear-gradient(#ad7, #ad7, #cea 20%, #7a3 45%, #7a3 55%);
+ }
+
+ div.meter-element { padding: 5px; }
+ body > div:nth-child(1) { -moz-appearance: none; }
+ body > div:nth-child(2) > .meter-bar { -moz-appearance: none; }
+ body > div:nth-child(3) { background: red; }
+ body > div:nth-child(4) > .meter-bar { background: red; }
+ body > div:nth-child(5) { border: 2px solid red; }
+ body > div:nth-child(6) > .meter-bar { border: 5px solid red; width: calc(100% - 10px); }
+ </style>
+ <body>
+ <div class="meter-element">
+ <div class="meter-bar"></div>
+ </div>
+ <div class="meter-element">
+ <div class="meter-bar"></div>
+ </div>
+ <div class="meter-element">
+ <div class="meter-bar"></div>
+ </div>
+ <div class="meter-element">
+ <div class="meter-bar"></div>
+ </div>
+ <div class="meter-element">
+ <div class="meter-bar"></div>
+ </div>
+ <div class="meter-element">
+ <div class="meter-bar"></div>
+ </div>
+ </body>
+</html>
diff --git a/widget/reftests/meter-fallback-default-style.html b/widget/reftests/meter-fallback-default-style.html
new file mode 100644
index 0000000000..a5942d7e55
--- /dev/null
+++ b/widget/reftests/meter-fallback-default-style.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+ <style>
+ meter { padding: 5px }
+ body > meter:nth-child(1) { -moz-appearance: none; }
+ body > meter:nth-child(2)::-moz-meter-bar { -moz-appearance: none; }
+ body > meter:nth-child(3) { background: red; }
+ body > meter:nth-child(4)::-moz-meter-bar { background: red; }
+ body > meter:nth-child(5) { border: 2px solid red; }
+ body > meter:nth-child(6)::-moz-meter-bar { border: 5px solid red; }
+ </style>
+ <body>
+ <meter value=1></meter>
+ <meter value=1></meter>
+ <meter value=1></meter>
+ <meter value=1></meter>
+ <meter value=1></meter>
+ <meter value=1></meter>
+ </body>
+</html>
diff --git a/widget/reftests/meter-native-style-ref.html b/widget/reftests/meter-native-style-ref.html
new file mode 100644
index 0000000000..ce434e9f16
--- /dev/null
+++ b/widget/reftests/meter-native-style-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <!-- Empty meter, no bar. -->
+ <meter></meter>
+ <!-- Full meter green colored. -->
+ <meter min=0 low=0 high=1 optimum=2 max=10 value=10></meter>
+ <!-- Full meter orange colored. -->
+ <meter min=0 low=0 high=1 optimum=1 max=10 value=10></meter>
+ <!-- Full meter red colored. -->
+ <meter min=0 low=1 high=2 optimum=0 max=10 value=10></meter>
+ <!-- Half-empty orange colored. -->
+ <meter min=0 low=0 high=1 optimum=0 max=10 value=5></meter>
+ <!-- Half-empty orange colored. -->
+ <meter min=0 low=0 high=1 optimum=0 max=10 value=5></meter>
+ <!-- With RTL, the bar should begin on the right. -->
+ <meter style="-moz-transform: scale(-1, 1);" value=0.5></meter>
+ </body>
+</html>
diff --git a/widget/reftests/meter-native-style.html b/widget/reftests/meter-native-style.html
new file mode 100644
index 0000000000..91342772fe
--- /dev/null
+++ b/widget/reftests/meter-native-style.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <meter vaue=0></meter>
+ <!-- Should be green. -->
+ <meter min=0 low=0 high=10 optimum=10 max=10 value=10></meter>
+ <!-- Should be orange. -->
+ <meter min=0 low=9 high=10 optimum=8 max=10 value=10></meter>
+ <!-- Should be red. -->
+ <meter min=0 low=8 high=9 optimum=0 max=10 value=10></meter>
+ <!-- Half-full orange. -->
+ <meter min=0 low=3 high=4 optimum=4 max=10 value=5></meter>
+ <!-- Half-full orange. -->
+ <meter min=0 low=9 high=10 optimum=10 max=10 value=5></meter>
+ <!-- Test RTL -->
+ <meter dir='rtl' value=0.5></meter>
+ </body>
+</html>
diff --git a/widget/reftests/meter-vertical-native-style-ref.html b/widget/reftests/meter-vertical-native-style-ref.html
new file mode 100644
index 0000000000..4426fe93e0
--- /dev/null
+++ b/widget/reftests/meter-vertical-native-style-ref.html
@@ -0,0 +1,14 @@
+<html>
+ <style>
+ meter:nth-child(1) { -moz-transform: rotate(-90deg) translate(-2em, -2em); }
+ meter:nth-child(2) { -moz-transform: rotate(-90deg) translate(-2em, -6em); }
+ meter:nth-child(3) { -moz-transform: rotate(-90deg) translate(-2em, -10em); }
+ meter:nth-child(4) { -moz-transform: rotate(-90deg) translate(-2em, -14em); }
+ meter:nth-child(5) { -moz-transform: rotate(-90deg) translate(-2em, -18em); }
+ meter:nth-child(6) { -moz-transform: rotate(-90deg) translate(-2em, -22em); }
+ meter:nth-child(7) { -moz-transform: rotate(-90deg) translate(-2em, -26em); }
+ </style>
+<body>
+<meter></meter><meter min=0 low=0 high=1 optimum=2 max=10 value=10></meter><meter min=0 low=0 high=1 optimum=1 max=10 value=10></meter><meter min=0 low=1 high=2 optimum=0 max=10 value=10></meter><meter min=0 low=0 high=1 optimum=0 max=10 value=5></meter><meter min=0 low=0 high=1 optimum=0 max=10 value=5></meter><meter value=0.5></meter>
+</body>
+</html>
diff --git a/widget/reftests/meter-vertical-native-style.html b/widget/reftests/meter-vertical-native-style.html
new file mode 100644
index 0000000000..445671c5e4
--- /dev/null
+++ b/widget/reftests/meter-vertical-native-style.html
@@ -0,0 +1,13 @@
+<html>
+ <style>
+ meter { -moz-orient: vertical; }
+ </style>
+ <body>
+<!-- For some reasons, the ref has a small offset when there are spaces between meters.
+ Given that we don't want to test -moz-transform and even a perfect match but just
+ the general rendering, we are going to keep this dirty test.
+ It's very similar to the non-vertical test with a difference, for the RTL: RTL
+ does not apply for vertical meters. -->
+<meter vaue=0></meter><meter min=0 low=0 high=10 optimum=10 max=10 value=10></meter><meter min=0 low=9 high=10 optimum=8 max=10 value=10></meter><meter min=0 low=8 high=9 optimum=0 max=10 value=10></meter><meter min=0 low=3 high=4 optimum=4 max=10 value=5></meter><meter min=0 low=9 high=10 optimum=10 max=10 value=5></meter><meter value=0.5 dir=rtl></meter>
+ </body>
+</html>
diff --git a/widget/reftests/progressbar-fallback-default-style-ref.html b/widget/reftests/progressbar-fallback-default-style-ref.html
new file mode 100644
index 0000000000..22950ec583
--- /dev/null
+++ b/widget/reftests/progressbar-fallback-default-style-ref.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+ <link rel='stylesheet' href='resource://reftest/progress.css'>
+ <style>
+ div.progress-element { padding: 5px; }
+ body > div:nth-child(1) { -moz-appearance: none; }
+ body > div:nth-child(2) > .progress-bar { -moz-appearance: none; }
+ body > div:nth-child(3) { background-color: red; }
+ body > div:nth-child(4) > .progress-bar { background-color: red; }
+ body > div:nth-child(5) { border: 2px solid red; }
+ body > div:nth-child(6) > .progress-bar { border: 5px solid red; }
+ </style>
+ <body>
+ <div class="progress-element">
+ <div class="progress-bar"></div>
+ </div>
+ <div class="progress-element">
+ <div class="progress-bar"></div>
+ </div>
+ <div class="progress-element">
+ <div class="progress-bar"></div>
+ </div>
+ <div class="progress-element">
+ <div class="progress-bar"></div>
+ </div>
+ <div class="progress-element">
+ <div class="progress-bar"></div>
+ </div>
+ <div class="progress-element">
+ <div class="progress-bar"></div>
+ </div>
+ </body>
+</html>
diff --git a/widget/reftests/progressbar-fallback-default-style.html b/widget/reftests/progressbar-fallback-default-style.html
new file mode 100644
index 0000000000..7594e65556
--- /dev/null
+++ b/widget/reftests/progressbar-fallback-default-style.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+ <style>
+ progress { padding: 5px }
+ body > progress:nth-child(1) { -moz-appearance: none; }
+ body > progress:nth-child(2)::-moz-progress-bar { -moz-appearance: none; }
+ body > progress:nth-child(3) { background-color: red; }
+ body > progress:nth-child(4)::-moz-progress-bar { background-color: red; }
+ body > progress:nth-child(5) { border: 2px solid red; }
+ body > progress:nth-child(6)::-moz-progress-bar { border: 5px solid red; }
+ </style>
+ <body>
+ <progress></progress>
+ <progress></progress>
+ <progress></progress>
+ <progress></progress>
+ <progress></progress>
+ <progress></progress>
+ </body>
+</html>
diff --git a/widget/reftests/reftest.list b/widget/reftests/reftest.list
new file mode 100644
index 0000000000..7b2ba984e6
--- /dev/null
+++ b/widget/reftests/reftest.list
@@ -0,0 +1,9 @@
+== progressbar-fallback-default-style.html progressbar-fallback-default-style-ref.html
+fuzzy-if(Android,0-17,0-1120) fuzzy-if(webrender,0-8,0-480) == meter-native-style.html meter-native-style-ref.html
+skip-if(!cocoaWidget) == meter-vertical-native-style.html meter-vertical-native-style-ref.html # dithering
+== meter-fallback-default-style.html meter-fallback-default-style-ref.html
+load 664925.xhtml
+pref(apz.allow_zooming,true) pref(ui.useOverlayScrollbars,0) skip-if(!cocoaWidget) != scaled-scrollbar.html about:blank
+
+# Test that scrollbar buttons are inhibited on Linux using the non-native theme.
+skip-if(!gtkWidget) pref(widget.disable-native-theme-for-content,true) test-pref(ui.scrollArrowStyle,4097) ref-pref(ui.scrollArrowStyle,0) == scrollbar-buttons.html scrollbar-buttons.html
diff --git a/widget/reftests/scaled-scrollbar.html b/widget/reftests/scaled-scrollbar.html
new file mode 100644
index 0000000000..f94df78420
--- /dev/null
+++ b/widget/reftests/scaled-scrollbar.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html reftest-resolution="2.0" style="scrollbar-width: none;">
+<div style="overflow: scroll; width: 300px; height: 300px">
+ <div style="width: 1000px; height: 1000px"></div>
+</div>
+</html>
diff --git a/widget/reftests/scrollbar-buttons.html b/widget/reftests/scrollbar-buttons.html
new file mode 100644
index 0000000000..d54218272d
--- /dev/null
+++ b/widget/reftests/scrollbar-buttons.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<div style="width: 200px; height: 200px; overflow: scroll;">
+ <div style="width: 400px; height: 400px;"></div>
+</div>
diff --git a/widget/tests/.eslintrc.js b/widget/tests/.eslintrc.js
new file mode 100644
index 0000000000..19d9df957a
--- /dev/null
+++ b/widget/tests/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/chrome-test", "plugin:mozilla/mochitest-test"],
+};
diff --git a/widget/tests/TestChromeMargin.cpp b/widget/tests/TestChromeMargin.cpp
new file mode 100644
index 0000000000..0eed86b208
--- /dev/null
+++ b/widget/tests/TestChromeMargin.cpp
@@ -0,0 +1,130 @@
+/* -*- 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/. */
+
+/* This tests the margin parsing functionality in nsAttrValue.cpp, which
+ * is accessible via nsContentUtils, and is used in setting chromemargins
+ * to widget windows. It's located here due to linking issues in the
+ * content directory.
+ */
+
+/* This test no longer compiles now that we've removed nsIContentUtils (bug
+ * 647273). We need to be internal code in order to include nsContentUtils.h,
+ * but defining MOZILLA_INTERNAL_API is not enough to make us internal.
+ */
+
+#include "TestHarness.h"
+
+#ifndef MOZILLA_INTERNAL_API
+# error This test needs MOZILLA_INTERNAL_API (see bug 652123)
+#endif
+
+#include "nscore.h"
+#include "nsContentUtils.h"
+#include "nsString.h"
+
+struct DATA {
+ bool shouldfail;
+ const char* margins;
+ int top;
+ int right;
+ int bottom;
+ int left;
+};
+
+const bool SHOULD_FAIL = true;
+const int SHOULD_PASS = false;
+
+const DATA Data[] = {
+ {SHOULD_FAIL, "", 1, 2, 3, 4},
+ {SHOULD_FAIL, "1,0,0,0", 1, 2, 3, 4},
+ {SHOULD_FAIL, "1,2,0,0", 1, 2, 3, 4},
+ {SHOULD_FAIL, "1,2,3,0", 1, 2, 3, 4},
+ {SHOULD_FAIL, "4,3,2,1", 1, 2, 3, 4},
+ {SHOULD_FAIL, "azsasdasd", 0, 0, 0, 0},
+ {SHOULD_FAIL, ",azsasdasd", 0, 0, 0, 0},
+ {SHOULD_FAIL, " ", 1, 2, 3, 4},
+ {SHOULD_FAIL,
+ "azsdfsdfsdfsdfsdfsasdasd,asdasdasdasdasdasd,asdadasdasd,asdasdasdasd", 0,
+ 0, 0, 0},
+ {SHOULD_FAIL, "as,as,as,as", 0, 0, 0, 0},
+ {SHOULD_FAIL, "0,0,0", 0, 0, 0, 0},
+ {SHOULD_FAIL, "0,0", 0, 0, 0, 0},
+ {SHOULD_FAIL, "4.6,1,1,1", 0, 0, 0, 0},
+ {SHOULD_FAIL, ",,,,", 0, 0, 0, 0},
+ {SHOULD_FAIL, "1, , , ,", 0, 0, 0, 0},
+ {SHOULD_FAIL, "1, , ,", 0, 0, 0, 0},
+ {SHOULD_FAIL, "@!@%^&^*()", 1, 2, 3, 4},
+ {SHOULD_PASS, "4,3,2,1", 4, 3, 2, 1},
+ {SHOULD_PASS, "-4,-3,-2,-1", -4, -3, -2, -1},
+ {SHOULD_PASS, "10000,3,2,1", 10000, 3, 2, 1},
+ {SHOULD_PASS, "4 , 3 , 2 , 1", 4, 3, 2, 1},
+ {SHOULD_PASS, "4, 3 ,2,1", 4, 3, 2, 1},
+ {SHOULD_FAIL, "4,3,2,10000000000000 --", 4, 3, 2, 10000000000000},
+ {SHOULD_PASS, "4,3,2,1000", 4, 3, 2, 1000},
+ {SHOULD_PASS, "2147483647,3,2,1000", 2147483647, 3, 2, 1000},
+ {SHOULD_PASS, "2147483647,2147483647,2147483647,2147483647", 2147483647,
+ 2147483647, 2147483647, 2147483647},
+ {SHOULD_PASS, "-2147483647,3,2,1000", -2147483647, 3, 2, 1000},
+ {SHOULD_FAIL, "2147483648,3,2,1000", 1, 3, 2, 1000},
+ {0, nullptr, 0, 0, 0, 0}};
+
+void DoAttrValueTest() {
+ int idx = -1;
+ bool didFail = false;
+ while (Data[++idx].margins) {
+ nsAutoString str;
+ str.AssignLiteral(Data[idx].margins);
+ nsIntMargin values(99, 99, 99, 99);
+ bool result = nsContentUtils::ParseIntMarginValue(str, values);
+
+ // if the parse fails
+ if (!result) {
+ if (Data[idx].shouldfail) continue;
+ fail(Data[idx].margins);
+ didFail = true;
+ printf("*1\n");
+ continue;
+ }
+
+ if (Data[idx].shouldfail) {
+ if (Data[idx].top == values.top && Data[idx].right == values.right &&
+ Data[idx].bottom == values.bottom && Data[idx].left == values.left) {
+ // not likely
+ fail(Data[idx].margins);
+ didFail = true;
+ printf("*2\n");
+ continue;
+ }
+ // good failure, parse failed and that's what we expected.
+ continue;
+ }
+#if 0
+ printf("%d==%d %d==%d %d==%d %d==%d\n",
+ Data[idx].top, values.top,
+ Data[idx].right, values.right,
+ Data[idx].bottom, values.bottom,
+ Data[idx].left, values.left);
+#endif
+ if (Data[idx].top == values.top && Data[idx].right == values.right &&
+ Data[idx].bottom == values.bottom && Data[idx].left == values.left) {
+ // good parse results
+ continue;
+ } else {
+ fail(Data[idx].margins);
+ didFail = true;
+ printf("*3\n");
+ continue;
+ }
+ }
+
+ if (!didFail) passed("nsAttrValue margin parsing tests passed.");
+}
+
+int main(int argc, char** argv) {
+ ScopedXPCOM xpcom("");
+ if (xpcom.failed()) return 1;
+ DoAttrValueTest();
+ return 0;
+}
diff --git a/widget/tests/browser/browser.ini b/widget/tests/browser/browser.ini
new file mode 100644
index 0000000000..a50eec47e7
--- /dev/null
+++ b/widget/tests/browser/browser.ini
@@ -0,0 +1,2 @@
+[browser_test_clipboardcache.js]
+skip-if = os == 'android' || (os == 'linux' && ccov) || tsan # Bug 1613516, the test consistently timeouts on Linux coverage builds.
diff --git a/widget/tests/browser/browser_test_clipboardcache.js b/widget/tests/browser/browser_test_clipboardcache.js
new file mode 100644
index 0000000000..8364d48507
--- /dev/null
+++ b/widget/tests/browser/browser_test_clipboardcache.js
@@ -0,0 +1,145 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+// Note: widget/tests/test_bug1123480.xhtml checks whether nsTransferable behaves
+// as expected with regards to private browsing mode and the clipboard cache,
+// i.e. that the clipboard is not cached to the disk when private browsing mode
+// is enabled.
+//
+// This test tests that the clipboard is not cached to the disk by IPC,
+// as a regression test for bug 1396224.
+// It indirectly uses nsTransferable, via the async navigator.clipboard API.
+
+// Create over 1 MB of sample garbage text. JavaScript strings are represented
+// by UTF16 strings, so the size is twice as much as the actual string length.
+// This value is chosen such that the size of the memory for the string exceeds
+// the kLargeDatasetSize threshold in nsTransferable.h.
+// It is also not a round number to reduce the odds of having an accidental
+// collisions with another file (since the test below looks at the file size
+// to identify the file).
+var Ipsum = "0123456789".repeat(1234321);
+var IpsumByteLength = Ipsum.length * 2;
+var SHORT_STRING_NO_CACHE = "short string that will not be cached to the disk";
+
+// Get a list of open file descriptors that refer to a file with the same size
+// as the expected data (and assume that any mutations in file descriptor counts
+// are caused by our test).
+// TODO: This logic only counts file descriptors that are still open (e.g. when
+// data persists after a copy). It does not detect cache files that exist only
+// temporarily (e.g. after a paste).
+function getClipboardCacheFDCount() {
+ let dir;
+ if (AppConstants.platform === "win") {
+ // On Windows, nsAnonymousTemporaryFile does not immediately delete a file.
+ // Instead, the Windows-specific FILE_FLAG_DELETE_ON_CLOSE flag is used,
+ // which means that the file is deleted when the last handle is closed.
+ // Apparently, this flag is unreliable (e.g. when the application crashes),
+ // so nsAnonymousTemporaryFile stores the temporary files in a subdirectory,
+ // which is cleaned up some time after start-up.
+
+ // This is just a test, and during the test we deterministically close the
+ // handles, so if FILE_FLAG_DELETE_ON_CLOSE does the thing it promises, the
+ // file is actually removed when the handle is closed.
+
+ let { FileUtils } = ChromeUtils.import(
+ "resource://gre/modules/FileUtils.jsm"
+ );
+ // Path from nsAnonymousTemporaryFile.cpp, GetTempDir.
+ dir = FileUtils.getFile("TmpD", ["mozilla-temp-files"]);
+ } else {
+ dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath("/dev/fd");
+ }
+ let count = 0;
+ for (let fdFile of dir.directoryEntries) {
+ let fileSize;
+ try {
+ fileSize = fdFile.fileSize;
+ } catch (e) {
+ // This can happen on macOS.
+ continue;
+ }
+ if (fileSize === IpsumByteLength) {
+ // Assume that the file was created by us if the size matches.
+ ++count;
+ }
+ }
+ return count;
+}
+
+async function testCopyPaste(isPrivate) {
+ let win = await BrowserTestUtils.openNewBrowserWindow({ private: isPrivate });
+ let tab = await BrowserTestUtils.openNewForegroundTab(win);
+ let browser = tab.linkedBrowser;
+
+ // Sanitize environment
+ await ContentTask.spawn(browser, SHORT_STRING_NO_CACHE, async shortStr => {
+ await content.navigator.clipboard.writeText(shortStr);
+ });
+
+ let initialFdCount = getClipboardCacheFDCount();
+
+ await SpecialPowers.spawn(browser, [Ipsum], async largeString => {
+ await content.navigator.clipboard.writeText(largeString);
+ });
+
+ let fdCountAfterCopy = getClipboardCacheFDCount();
+ if (isPrivate) {
+ is(fdCountAfterCopy, initialFdCount, "Private write");
+ } else {
+ is(fdCountAfterCopy, initialFdCount + 1, "Cached write");
+ }
+
+ let readStr = await SpecialPowers.spawn(browser, [], async () => {
+ let { document } = content;
+ document.body.contentEditable = true;
+ document.body.focus();
+ let pastePromise = new Promise(resolve => {
+ document.addEventListener(
+ "paste",
+ e => {
+ resolve(e.clipboardData.getData("text/plain"));
+ },
+ { once: true }
+ );
+ });
+ document.execCommand("paste");
+ return pastePromise;
+ });
+ ok(readStr === Ipsum, "Read what we pasted");
+
+ if (isPrivate) {
+ is(getClipboardCacheFDCount(), fdCountAfterCopy, "Private read");
+ } else {
+ // Upon reading from the clipboard, a temporary nsTransferable is used, for
+ // which the cache is disabled. The content process does not cache clipboard
+ // data either. So the file descriptor count should be identical.
+ is(getClipboardCacheFDCount(), fdCountAfterCopy, "Read not cached");
+ }
+
+ // Cleanup.
+ await SpecialPowers.spawn(
+ browser,
+ [SHORT_STRING_NO_CACHE],
+ async shortStr => {
+ await content.navigator.clipboard.writeText(shortStr);
+ }
+ );
+ is(getClipboardCacheFDCount(), initialFdCount, "Drop clipboard cache if any");
+
+ BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+}
+
+add_task(async function test_private() {
+ await testCopyPaste(true);
+});
+
+add_task(async function test_non_private() {
+ await testCopyPaste(false);
+});
diff --git a/widget/tests/bug586713_window.xhtml b/widget/tests/bug586713_window.xhtml
new file mode 100644
index 0000000000..c180c00235
--- /dev/null
+++ b/widget/tests/bug586713_window.xhtml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="bug586713_window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="300"
+ height="300"
+ onload="onLoad();"
+ title="Bug 586713 Test">
+
+ <menubar id="nativemenubar">
+ <menu id="foo" label="Foo">
+ <menupopup>
+ <menuitem label="FooItem0"/>
+ </menupopup>
+ </menu>
+ </menubar>
+
+ <script type="application/javascript"><![CDATA[
+ function ok(condition, message) {
+ window.arguments[0].SimpleTest.ok(condition, message);
+ }
+
+ function onTestsFinished() {
+ window.close();
+ window.arguments[0].SimpleTest.finish();
+ }
+
+ var fooCallCount = 0;
+ function foo() {
+ fooCallCount++;
+ let instruction = document.createProcessingInstruction("xml-stylesheet", 'href="chrome://foo.css" type="text/css"');
+ document.insertBefore(instruction, document.documentElement);
+ if (fooCallCount == 2) {
+ ok(true, "If we got here we didn't crash, excellent.");
+ onTestsFinished();
+ }
+ }
+
+ function onLoad() {
+ foo();
+ setTimeout(() => foo(), 0);
+ }
+ ]]></script>
+</window>
diff --git a/widget/tests/chrome.ini b/widget/tests/chrome.ini
new file mode 100644
index 0000000000..aa700b25d2
--- /dev/null
+++ b/widget/tests/chrome.ini
@@ -0,0 +1,107 @@
+[DEFAULT]
+prefs =
+ plugin.load_flash_only=false
+skip-if = os == 'android'
+support-files =
+ empty_window.xhtml
+ utils.js
+
+[test_alwaysontop_focus.xhtml]
+[test_bug343416.xhtml]
+skip-if = debug
+[test_bug429954.xhtml]
+support-files = window_bug429954.xhtml
+[test_bug444800.xhtml]
+[test_bug478536.xhtml]
+skip-if = true # Bug 561929
+support-files = window_bug478536.xhtml
+[test_bug517396.xhtml]
+skip-if = (verify && (os == 'win'))
+[test_bug538242.xhtml]
+support-files = window_bug538242.xhtml
+[test_bug565392.html]
+skip-if = toolkit != "windows"
+[test_bug593307.xhtml]
+support-files = window_bug593307_offscreen.xhtml window_bug593307_centerscreen.xhtml
+[test_keycodes.xhtml]
+[test_wheeltransaction.xhtml]
+support-files = window_wheeltransaction.xhtml
+[test_imestate.html]
+support-files = window_imestate_iframes.html
+[test_composition_text_querycontent.xhtml]
+support-files = window_composition_text_querycontent.xhtml
+[test_input_events_on_deactive_window.xhtml]
+support-files = file_input_events_on_deactive_window.html
+[test_position_on_resize.xhtml]
+skip-if =
+ verify && (os == 'win')
+ webrender && (os == "linux" && bits == 64) # Bug 1616760
+[test_sizemode_events.xhtml]
+[test_taskbar_progress.xhtml]
+skip-if = toolkit != "cocoa" && toolkit != "windows" || (os == "win" && os_version == "10.0" && !ccov) # Bug 1456811
+[test_bug760802.xhtml]
+[test_clipboard.xhtml]
+[test_panel_mouse_coords.xhtml]
+skip-if = toolkit == "windows" # bug 1009955
+
+# Cocoa
+[test_native_menus.xhtml]
+skip-if = toolkit != "cocoa"
+support-files = native_menus_window.xhtml
+[test_native_mouse_mac.xhtml]
+skip-if = toolkit != "cocoa" || os_version == '10.14' # macosx1014: bug 1137575
+support-files = native_mouse_mac_window.xhtml
+[test_bug413277.html]
+skip-if = toolkit != "cocoa"
+[test_bug428405.xhtml]
+skip-if = toolkit != "cocoa"
+[test_bug466599.xhtml]
+skip-if = toolkit != "cocoa"
+[test_bug485118.xhtml]
+skip-if = toolkit != "cocoa"
+[test_bug522217.xhtml]
+tags = fullscreen
+skip-if = toolkit != "cocoa"
+support-files = window_bug522217.xhtml
+[test_platform_colors.xhtml]
+#skip-if = toolkit != "cocoa"
+skip-if = true # Bug 1207190
+[test_standalone_native_menu.xhtml]
+skip-if = toolkit != "cocoa"
+support-files = standalone_native_menu_window.xhtml
+[test_bug586713.xhtml]
+skip-if = toolkit != "cocoa"
+support-files = bug586713_window.xhtml
+[test_key_event_counts.xhtml]
+skip-if = toolkit != "cocoa"
+[test_bug596600.xhtml]
+support-files = file_bug596600.html
+skip-if = toolkit != "cocoa" || !debug # Bug 1661132 (disable on opt)
+[test_bug673301.xhtml]
+skip-if = toolkit != "cocoa"
+[test_secure_input.html]
+support-files = file_secure_input.html
+skip-if = toolkit != "cocoa"
+[test_native_key_bindings_mac.html]
+skip-if = toolkit != "cocoa" || verify
+[test_system_status_bar.xhtml]
+skip-if = toolkit != "cocoa"
+
+[test_system_font_changes.xhtml]
+support-files = system_font_changes.xhtml
+run-if = toolkit == 'gtk' # Currently the test works on only gtk3
+
+# Windows
+# taskbar_previews.xhtml
+# window_state_windows.xhtml
+[test_mouse_scroll.xhtml]
+skip-if = toolkit != "windows"
+support-files =
+ window_mouse_scroll_win.html
+ window_mouse_scroll_win_2.html
+
+# Privacy relevant
+[test_bug1123480.xhtml]
+[test_transferable_overflow.xhtml]
+skip-if = (verify && (os == 'mac' || os == 'linux'))
+
diff --git a/widget/tests/empty_window.xhtml b/widget/tests/empty_window.xhtml
new file mode 100644
index 0000000000..f0e01761d2
--- /dev/null
+++ b/widget/tests/empty_window.xhtml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Empty window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
diff --git a/widget/tests/file_bug596600.html b/widget/tests/file_bug596600.html
new file mode 100644
index 0000000000..1b178a6b68
--- /dev/null
+++ b/widget/tests/file_bug596600.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+Content page
+</body>
diff --git a/widget/tests/file_input_events_on_deactive_window.html b/widget/tests/file_input_events_on_deactive_window.html
new file mode 100644
index 0000000000..e733adbb9d
--- /dev/null
+++ b/widget/tests/file_input_events_on_deactive_window.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ this is an active window.
+</body>
+</html>
diff --git a/widget/tests/file_secure_input.html b/widget/tests/file_secure_input.html
new file mode 100644
index 0000000000..28fec7b44b
--- /dev/null
+++ b/widget/tests/file_secure_input.html
@@ -0,0 +1 @@
+<input id="text" type"text"><input id="password" type"password">
diff --git a/widget/tests/gtest/TestTimeConverter.cpp b/widget/tests/gtest/TestTimeConverter.cpp
new file mode 100644
index 0000000000..22cbc3f9e6
--- /dev/null
+++ b/widget/tests/gtest/TestTimeConverter.cpp
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/TimeStamp.h"
+#include "SystemTimeConverter.h"
+
+using mozilla::SystemTimeConverter;
+using mozilla::TimeDuration;
+using mozilla::TimeStamp;
+
+namespace {
+
+// This class provides a mock implementation of the CurrentTimeGetter template
+// type used in SystemTimeConverter. It can be constructed with a particular
+// Time and always returns that Time.
+template <typename Time>
+class MockCurrentTimeGetter {
+ public:
+ MockCurrentTimeGetter() : mTime(0) {}
+ explicit MockCurrentTimeGetter(Time aTime) : mTime(aTime) {}
+
+ // Methods needed for CurrentTimeGetter compatibility
+ Time GetCurrentTime() const { return mTime; }
+ void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {}
+
+ private:
+ Time mTime;
+};
+
+// This is another mock implementation of the CurrentTimeGetter template
+// type used in SystemTimeConverter, except this asserts that it will not be
+// used. i.e. it should only be used in calls to SystemTimeConverter that we
+// know will not invoke it.
+template <typename Time>
+class UnusedCurrentTimeGetter {
+ public:
+ Time GetCurrentTime() const {
+ EXPECT_TRUE(false);
+ return 0;
+ }
+
+ void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
+ EXPECT_TRUE(false);
+ }
+};
+
+// This class provides a mock implementation of the TimeStampNowProvider
+// template type used in SystemTimeConverter. It also has other things in it
+// that allow the test to better control time for testing purposes.
+class MockTimeStamp {
+ public:
+ // This should generally be called at the start of every test function, as
+ // it will initialize this class's static fields to sane values. In particular
+ // it will initialize the baseline TimeStamp against which all other
+ // TimeStamps are compared.
+ static void Init() {
+ sBaseline = TimeStamp::Now();
+ sTimeStamp = sBaseline;
+ }
+
+ // Advance the timestamp returned by `MockTimeStamp::Now()`
+ static void Advance(double ms) {
+ sTimeStamp += TimeDuration::FromMilliseconds(ms);
+ }
+
+ // Returns the baseline TimeStamp, that is used as a fixed reference point
+ // in time against which other TimeStamps can be compared. This is needed
+ // because mozilla::TimeStamp itself doesn't provide any conversion to
+ // human-readable strings, and we need to convert it to a TimeDuration in
+ // order to get that. This baseline TimeStamp can be used to turn an
+ // arbitrary TimeStamp into a TimeDuration.
+ static TimeStamp Baseline() { return sBaseline; }
+
+ // This is the method needed for TimeStampNowProvider compatibility, and
+ // simulates `TimeStamp::Now()`
+ static TimeStamp Now() { return sTimeStamp; }
+
+ private:
+ static TimeStamp sTimeStamp;
+ static TimeStamp sBaseline;
+};
+
+TimeStamp MockTimeStamp::sTimeStamp;
+TimeStamp MockTimeStamp::sBaseline;
+
+// Could have platform-specific implementations of this using DWORD, guint32,
+// etc behind ifdefs. But this is sufficient for now.
+using GTestTime = uint32_t;
+using TimeConverter = SystemTimeConverter<GTestTime, MockTimeStamp>;
+
+} // namespace
+
+// Checks the expectation that the TimeStamp `ts` is exactly `ms` milliseconds
+// after the baseline timestamp. This is a macro so gtest still gives us useful
+// line numbers for failures.
+#define EXPECT_TS(ts, ms) \
+ EXPECT_EQ((ts)-MockTimeStamp::Baseline(), TimeDuration::FromMilliseconds(ms))
+
+#define EXPECT_TS_FUZZY(ts, ms) \
+ EXPECT_DOUBLE_EQ(((ts)-MockTimeStamp::Baseline()).ToMilliseconds(), ms)
+
+TEST(TimeConverter, SanityCheck)
+{
+ MockTimeStamp::Init();
+
+ MockCurrentTimeGetter timeGetter(10);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+ TimeConverter converter;
+
+ // This call sets the reference time and timestamp
+ TimeStamp ts = converter.GetTimeStampFromSystemTime(10, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ // Advance "TimeStamp::Now" by 10ms, use the same event time and OS time.
+ // Since the event time is the same as before, we expect to get back the
+ // same TimeStamp as before too, despite Now() changing.
+ MockTimeStamp::Advance(10);
+ ts = converter.GetTimeStampFromSystemTime(10, unused);
+ EXPECT_TS(ts, 0);
+
+ // Now let's use an event time 20ms after the old event. This will trigger
+ // forward skew detection and resync the TimeStamp for the new event to Now().
+ ts = converter.GetTimeStampFromSystemTime(30, unused);
+ EXPECT_TS(ts, 10);
+}
+
+TEST(TimeConverter, Overflow)
+{
+ // This tests wrapping time around the max value supported in the GTestTime
+ // type and ensuring it is handled properly.
+
+ MockTimeStamp::Init();
+
+ const GTestTime max = std::numeric_limits<GTestTime>::max();
+ const GTestTime min = std::numeric_limits<GTestTime>::min();
+ double fullRange = (double)max - (double)min;
+ double wrapPeriod = fullRange + 1.0;
+
+ GTestTime almostOverflowed = max - 100;
+ GTestTime overflowed = max + 100;
+ MockCurrentTimeGetter timeGetter(almostOverflowed);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+ TimeConverter converter;
+
+ // Set reference time to 100ms before the overflow point
+ TimeStamp ts =
+ converter.GetTimeStampFromSystemTime(almostOverflowed, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ // Advance everything by 200ms and verify we get back a TimeStamp 200ms from
+ // the baseline despite wrapping an overflow.
+ MockTimeStamp::Advance(200);
+ ts = converter.GetTimeStampFromSystemTime(overflowed, unused);
+ EXPECT_TS(ts, 200);
+
+ // Advance by another full wraparound of the time. This loses some precision
+ // so we have to do the FUZZY match
+ MockTimeStamp::Advance(wrapPeriod);
+ ts = converter.GetTimeStampFromSystemTime(overflowed, unused);
+ EXPECT_TS_FUZZY(ts, 200.0 + wrapPeriod);
+}
+
+TEST(TimeConverter, InvertedOverflow)
+{
+ // This tests time going from near the min value of GTestTime to the max
+ // value of GTestTime
+
+ MockTimeStamp::Init();
+
+ const GTestTime max = std::numeric_limits<GTestTime>::max();
+ const GTestTime min = std::numeric_limits<GTestTime>::min();
+ double fullRange = (double)max - (double)min;
+ double wrapPeriod = fullRange + 1.0;
+
+ GTestTime nearRangeMin = min + 100;
+ GTestTime nearRangeMax = max - 100;
+ double gap = (double)nearRangeMax - (double)nearRangeMin;
+
+ MockCurrentTimeGetter timeGetter(nearRangeMin);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+ TimeConverter converter;
+
+ // Set reference time to value near min numeric limit
+ TimeStamp ts = converter.GetTimeStampFromSystemTime(nearRangeMin, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ // Advance to value near max numeric limit
+ MockTimeStamp::Advance(gap);
+ ts = converter.GetTimeStampFromSystemTime(nearRangeMax, unused);
+ EXPECT_TS(ts, gap);
+
+ // Advance by another full wraparound of the time. This loses some precision
+ // so we have to do the FUZZY match
+ MockTimeStamp::Advance(wrapPeriod);
+ ts = converter.GetTimeStampFromSystemTime(nearRangeMax, unused);
+ EXPECT_TS_FUZZY(ts, gap + wrapPeriod);
+}
+
+TEST(TimeConverter, HalfRangeBoundary)
+{
+ MockTimeStamp::Init();
+
+ GTestTime max = std::numeric_limits<GTestTime>::max();
+ GTestTime min = std::numeric_limits<GTestTime>::min();
+ double fullRange = (double)max - (double)min;
+ double wrapPeriod = fullRange + 1.0;
+ GTestTime halfRange = (GTestTime)(fullRange / 2.0);
+ GTestTime halfWrapPeriod = (GTestTime)(wrapPeriod / 2.0);
+
+ TimeConverter converter;
+
+ GTestTime firstEvent = 10;
+ MockCurrentTimeGetter timeGetter(firstEvent);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+
+ // Set reference time
+ TimeStamp ts = converter.GetTimeStampFromSystemTime(firstEvent, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ // Advance event time by just under the half-period, to trigger about as big
+ // a forwards skew as we possibly can.
+ GTestTime secondEvent = firstEvent + (halfWrapPeriod - 1);
+ ts = converter.GetTimeStampFromSystemTime(secondEvent, unused);
+ EXPECT_TS(ts, 0);
+
+ // The above forwards skew will have reset the reference timestamp. Now
+ // advance Now time by just under the half-range, to trigger about as big
+ // a backwards skew as we possibly can.
+ MockTimeStamp::Advance(halfRange - 1);
+ ts = converter.GetTimeStampFromSystemTime(secondEvent, unused);
+ EXPECT_TS(ts, 0);
+}
+
+TEST(TimeConverter, FractionalMillisBug1626734)
+{
+ MockTimeStamp::Init();
+
+ TimeConverter converter;
+
+ GTestTime eventTime = 10;
+ MockCurrentTimeGetter timeGetter(eventTime);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+
+ TimeStamp ts = converter.GetTimeStampFromSystemTime(eventTime, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ MockTimeStamp::Advance(0.2);
+ ts = converter.GetTimeStampFromSystemTime(eventTime, unused);
+ EXPECT_TS(ts, 0);
+
+ MockTimeStamp::Advance(0.9);
+ TimeStamp ts2 = converter.GetTimeStampFromSystemTime(eventTime, unused);
+ EXPECT_TS(ts2, 0);
+
+ // Since ts2 came from a "future" call relative to ts, we expect ts2 to not
+ // be "before" ts. (i.e. time shouldn't go backwards, even by fractional
+ // milliseconds). This assertion is technically already implied by the
+ // EXPECT_TS checks above, but fixing this assertion is the end result that
+ // we wanted in bug 1626734 so it feels appropriate to recheck it explicitly.
+ EXPECT_TRUE(ts <= ts2);
+}
diff --git a/widget/tests/gtest/TestTouchResampler.cpp b/widget/tests/gtest/TestTouchResampler.cpp
new file mode 100644
index 0000000000..1a5b8e2430
--- /dev/null
+++ b/widget/tests/gtest/TestTouchResampler.cpp
@@ -0,0 +1,941 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <initializer_list>
+#include "InputData.h"
+#include "Units.h"
+#include "gtest/gtest.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TimeStamp.h"
+#include "TouchResampler.h"
+
+using namespace mozilla;
+using widget::TouchResampler;
+
+class TouchResamplerTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() { baseTimeStamp = TimeStamp::Now(); }
+
+ TimeStamp Time(double aMilliseconds) {
+ return baseTimeStamp + TimeDuration::FromMilliseconds(aMilliseconds);
+ }
+
+ uint64_t ProcessEvent(
+ MultiTouchInput::MultiTouchType aType,
+ std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>>
+ aHistoricalData,
+ const TimeStamp& aTimeStamp, const ScreenIntPoint& aPosition) {
+ MultiTouchInput input(aType, 0, aTimeStamp, 0);
+ input.mTouches.AppendElement(SingleTouchData(1, aPosition, {}, 0.0f, 0.0f));
+ for (const auto& histData : aHistoricalData) {
+ input.mTouches[0].mHistoricalData.AppendElement(
+ SingleTouchData::HistoricalTouchData{
+ histData.first, histData.second, {}, {}, 0.0f, 0.0f});
+ }
+ return resampler.ProcessEvent(std::move(input));
+ }
+
+ void CheckTime(const TimeStamp& aTimeStamp,
+ const TimeStamp& aExpectedTimeStamp) {
+ EXPECT_EQ((aTimeStamp - baseTimeStamp).ToMilliseconds(),
+ (aExpectedTimeStamp - baseTimeStamp).ToMilliseconds());
+ }
+
+ void CheckEvent(const MultiTouchInput& aEvent,
+ MultiTouchInput::MultiTouchType aExpectedType,
+ std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>>
+ aExpectedHistoricalData,
+ const TimeStamp& aExpectedTimeStamp,
+ const ScreenIntPoint& aExpectedPosition) {
+ EXPECT_EQ(aEvent.mType, aExpectedType);
+ EXPECT_EQ(aEvent.mTouches.Length(), size_t(1));
+ EXPECT_EQ(aEvent.mTouches[0].mHistoricalData.Length(),
+ aExpectedHistoricalData.size());
+ for (size_t i = 0; i < aExpectedHistoricalData.size(); i++) {
+ CheckTime(aEvent.mTouches[0].mHistoricalData[i].mTimeStamp,
+ aExpectedHistoricalData.begin()[i].first);
+ EXPECT_EQ(aEvent.mTouches[0].mHistoricalData[i].mScreenPoint,
+ aExpectedHistoricalData.begin()[i].second);
+ }
+ CheckTime(aEvent.mTimeStamp, aExpectedTimeStamp);
+ EXPECT_EQ(aEvent.mTouches[0].mScreenPoint, aExpectedPosition);
+ }
+
+ struct ExpectedOutgoingEvent {
+ Maybe<uint64_t> mEventId;
+ MultiTouchInput::MultiTouchType mType = MultiTouchInput::MULTITOUCH_START;
+ std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>> mHistoricalData;
+ TimeStamp mTimeStamp;
+ ScreenIntPoint mPosition;
+ };
+
+ void CheckOutgoingEvents(
+ std::initializer_list<ExpectedOutgoingEvent> aExpectedEvents) {
+ auto outgoing = resampler.ConsumeOutgoingEvents();
+ EXPECT_EQ(outgoing.size(), aExpectedEvents.size());
+ for (const auto& expectedEvent : aExpectedEvents) {
+ auto outgoingEvent = std::move(outgoing.front());
+ outgoing.pop();
+
+ EXPECT_EQ(outgoingEvent.mEventId, expectedEvent.mEventId);
+ CheckEvent(outgoingEvent.mEvent, expectedEvent.mType,
+ expectedEvent.mHistoricalData, expectedEvent.mTimeStamp,
+ expectedEvent.mPosition);
+ }
+ }
+
+ TimeStamp baseTimeStamp;
+ TouchResampler resampler;
+};
+
+TEST_F(TouchResamplerTest, BasicExtrapolation) {
+ // Execute the following sequence:
+ //
+ // 0----------10-------16-----20---------------32------------
+ // * touchstart at (10, 10)
+ // * touchmove at (20, 20)
+ // * frame
+ // * touchend at (20, 20)
+ // * frame
+ //
+ // And expect the following output:
+ //
+ // 0----------10-------16-----20---------------32------------
+ // * touchstart at (10, 10)
+ // * touchmove at (26, 26)
+ // * touchmove at (20, 20)
+ // * touchend at (20, 20)
+ //
+ // The first frame should emit an extrapolated touchmove from the position
+ // data in the touchstart and touchmove events.
+ // The touchend should force a synthesized touchmove that returns back to a
+ // non-resampled position.
+
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(10, 10));
+ EXPECT_TRUE(resampler.InTouchingState());
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(20, 20));
+
+ resampler.NotifyFrame(Time(16.0));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(10, 10)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 20)}},
+ Time(16.0),
+ ScreenIntPoint(26, 26)},
+ });
+
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(20.0),
+ ScreenIntPoint(20, 20));
+
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ CheckOutgoingEvents({
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(16.0),
+ ScreenIntPoint(20, 20)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(20.0),
+ ScreenIntPoint(20, 20)},
+ });
+
+ // No more events should be produced from here on out.
+ resampler.NotifyFrame(Time(32.0));
+ auto outgoing = resampler.ConsumeOutgoingEvents();
+ EXPECT_TRUE(outgoing.empty());
+}
+
+TEST_F(TouchResamplerTest, BasicInterpolation) {
+ // Same test as BasicExtrapolation, but with a frame time that's 10ms earlier.
+ //
+ // Execute the following sequence:
+ //
+ // 0------6---10-----------20--22------------30-------------
+ // * touchstart at (10, 10)
+ // * touchmove at (20, 20)
+ // * frame
+ // * touchend at (20, 20)
+ // * frame
+ //
+ // And expect the following output:
+ //
+ // 0------6---10-----------20--22------------30-------------
+ // * touchstart at (10, 10)
+ // * touchmove (16, 16)
+ // * touchmove (20, 20)
+ // * touchend at (20, 20)
+ //
+ // The first frame should emit an interpolated touchmove from the position
+ // data in the touchstart and touchmove events.
+ // The touchend should create a touchmove that returns back to a non-resampled
+ // position.
+
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(10, 10));
+ EXPECT_TRUE(resampler.InTouchingState());
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(20, 20));
+
+ resampler.NotifyFrame(Time(6.0));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(10, 10)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(6.0),
+ ScreenIntPoint(16, 16)},
+ });
+
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(20.0),
+ ScreenIntPoint(20, 20));
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ CheckOutgoingEvents({
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(10.0),
+ ScreenIntPoint(20, 20)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(20.0),
+ ScreenIntPoint(20, 20)},
+ });
+
+ // No more events should be produced from here on out.
+ resampler.NotifyFrame(Time(22.0));
+ auto outgoing = resampler.ConsumeOutgoingEvents();
+ EXPECT_TRUE(outgoing.empty());
+}
+
+TEST_F(TouchResamplerTest, InterpolationFromHistoricalData) {
+ // Interpolate from the historical data in a touch move event.
+ //
+ // Execute the following sequence:
+ //
+ // 0----------10-------16-----20-----------30--32------------
+ // * touchstart at (10, 10)
+ // * [hist] at (20, 25) for
+ // `---------------* touchmove at (30, 30)
+ // * frame
+ // * touchend at (30, 30)
+ // * frame
+ //
+ // And expect the following output:
+ //
+ // 0----------10-------16-----20-----------30--32------------
+ // * touchstart at (10, 10)
+ // * [hist] at (20, 25) for
+ // `--------* touchmove at (26, 28)
+ // * touchmove at (30, 30)
+ // * touchend at (30, 30)
+ //
+ // The first frame should emit an interpolated touchmove from the position
+ // data in the touchmove event, and integrate the historical data point into
+ // the resampled event.
+ // The touchend should force a synthesized touchmove that returns back to a
+ // non-resampled position.
+
+ // This also tests that interpolation works for both x and y, by giving the
+ // historical datapoint different values for x and y.
+ // (26, 28) is 60% of the way from (20, 25) to (30, 30).
+
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(10, 10));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 25)}},
+ Time(20.0), ScreenIntPoint(30, 30));
+ resampler.NotifyFrame(Time(16.0));
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(30.0),
+ ScreenIntPoint(30, 30));
+ resampler.NotifyFrame(Time(32.0));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(10, 10)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 25)}},
+ Time(16.0),
+ ScreenIntPoint(26, 28)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(20.0),
+ ScreenIntPoint(30, 30)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(30.0),
+ ScreenIntPoint(30, 30)},
+ });
+}
+
+TEST_F(TouchResamplerTest, MultipleTouches) {
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ // Touch start
+ MultiTouchInput inputStart0(MultiTouchInput::MULTITOUCH_START, 0, Time(0.0),
+ 0);
+ inputStart0.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(10, 10), {}, 0.0f, 0.0f));
+ auto idStart0 = resampler.ProcessEvent(std::move(inputStart0));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Touch move
+ MultiTouchInput inputMove1(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(20.0),
+ 0);
+ inputMove1.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 30), {}, 0.0f, 0.0f));
+ inputMove1.mTouches[0].mHistoricalData.AppendElement(
+ SingleTouchData::HistoricalTouchData{
+ Time(10.0), ScreenIntPoint(20, 25), {}, {}, 0.0f, 0.0f});
+ auto idMove1 = resampler.ProcessEvent(std::move(inputMove1));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Frame
+ resampler.NotifyFrame(Time(16.0));
+
+ // Touch move
+ MultiTouchInput inputMove2(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(30.0),
+ 0);
+ inputMove2.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 40), {}, 0.0f, 0.0f));
+ auto idMove2 = resampler.ProcessEvent(std::move(inputMove2));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Touch start
+ MultiTouchInput inputStart3(MultiTouchInput::MULTITOUCH_START, 0, Time(30.0),
+ 0);
+ inputStart3.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 10), {}, 0.0f, 0.0f));
+ inputStart3.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 40), {}, 0.0f, 0.0f));
+ auto idStart3 = resampler.ProcessEvent(std::move(inputStart3));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Touch move
+ MultiTouchInput inputMove4(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(40.0),
+ 0);
+ inputMove4.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 50), {}, 0.0f, 0.0f));
+ inputMove4.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 30), {}, 0.0f, 0.0f));
+ auto idMove4 = resampler.ProcessEvent(std::move(inputMove4));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Frame
+ resampler.NotifyFrame(Time(32.0));
+
+ // Touch move
+ MultiTouchInput inputMove5(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(50.0),
+ 0);
+ inputMove5.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 60), {}, 0.0f, 0.0f));
+ inputMove5.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 40), {}, 0.0f, 0.0f));
+ auto idMove5 = resampler.ProcessEvent(std::move(inputMove5));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Touch end
+ MultiTouchInput inputEnd6(MultiTouchInput::MULTITOUCH_END, 0, Time(50.0), 0);
+ // Touch point with identifier 1 is lifted
+ inputEnd6.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 60), {}, 0.0f, 0.0f));
+ auto idEnd6 = resampler.ProcessEvent(std::move(inputEnd6));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Frame
+ resampler.NotifyFrame(Time(48.0));
+
+ // Touch move
+ MultiTouchInput inputMove7(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(60.0),
+ 0);
+ inputMove7.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 60), {}, 0.0f, 0.0f));
+ auto idMove7 = resampler.ProcessEvent(std::move(inputMove7));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Frame
+ resampler.NotifyFrame(Time(64.0));
+
+ // Touch end
+ MultiTouchInput inputEnd8(MultiTouchInput::MULTITOUCH_END, 0, Time(70.0), 0);
+ // Touch point with identifier 2 is lifted
+ inputEnd8.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 60), {}, 0.0f, 0.0f));
+ auto idEnd8 = resampler.ProcessEvent(std::move(inputEnd8));
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ // Check outgoing events
+ auto outgoing = resampler.ConsumeOutgoingEvents();
+ EXPECT_EQ(outgoing.size(), size_t(9));
+
+ auto outgoingStart0 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingStart0.mEventId, Some(idStart0));
+ CheckEvent(outgoingStart0.mEvent, MultiTouchInput::MULTITOUCH_START, {},
+ Time(0.0), ScreenIntPoint(10, 10));
+
+ auto outgoingMove1 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove1.mEventId, Some(idMove1));
+ // (26, 28) is 60% of the way from (20, 25) to (30, 30).
+ CheckEvent(outgoingMove1.mEvent, MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 25)}}, Time(16.0),
+ ScreenIntPoint(26, 28));
+
+ auto outgoingMove2 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove2.mEventId, Some(idMove2));
+ CheckEvent(outgoingMove2.mEvent, MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(30, 30)}}, Time(30.0),
+ ScreenIntPoint(30, 40));
+
+ auto outgoingStart3 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingStart3.mEventId, Some(idStart3));
+ EXPECT_EQ(outgoingStart3.mEvent.mType, MultiTouchInput::MULTITOUCH_START);
+ CheckTime(outgoingStart3.mEvent.mTimeStamp, Time(30.0));
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches.Length(), size_t(2));
+ // touch order should be taken from the original touch start event
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches[0].mIdentifier, 2);
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches[0].mScreenPoint,
+ ScreenIntPoint(100, 10));
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches[1].mIdentifier, 1);
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches[1].mScreenPoint,
+ ScreenIntPoint(30, 40));
+
+ auto outgoingMove4 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove4.mEventId, Some(idMove4));
+ EXPECT_EQ(outgoingMove4.mEvent.mType, MultiTouchInput::MULTITOUCH_MOVE);
+ CheckTime(outgoingMove4.mEvent.mTimeStamp, Time(32.0));
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches.Length(), size_t(2));
+ // Touch order should be taken from the original touch move event.
+ // Both touches should be resampled.
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches[0].mIdentifier, 1);
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches[0].mScreenPoint,
+ ScreenIntPoint(30, 42));
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches[1].mIdentifier, 2);
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches[1].mScreenPoint,
+ ScreenIntPoint(100, 14));
+
+ auto outgoingMove5 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove5.mEventId, Some(idMove5));
+ EXPECT_EQ(outgoingMove5.mEvent.mType, MultiTouchInput::MULTITOUCH_MOVE);
+ CheckTime(outgoingMove5.mEvent.mTimeStamp, Time(50.0));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches.Length(), size_t(2));
+ // touch order should be taken from the original touch move event
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mIdentifier, 1);
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mScreenPoint,
+ ScreenIntPoint(30, 60));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mHistoricalData.Length(),
+ size_t(1));
+ CheckTime(outgoingMove5.mEvent.mTouches[0].mHistoricalData[0].mTimeStamp,
+ Time(40.0));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mHistoricalData[0].mScreenPoint,
+ ScreenIntPoint(30, 50));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mIdentifier, 2);
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mScreenPoint,
+ ScreenIntPoint(100, 40));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mHistoricalData.Length(),
+ size_t(1));
+ CheckTime(outgoingMove5.mEvent.mTouches[1].mHistoricalData[0].mTimeStamp,
+ Time(40.0));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mHistoricalData[0].mScreenPoint,
+ ScreenIntPoint(100, 30));
+
+ auto outgoingEnd6 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingEnd6.mEventId, Some(idEnd6));
+ CheckEvent(outgoingEnd6.mEvent, MultiTouchInput::MULTITOUCH_END, {},
+ Time(50.0), ScreenIntPoint(30, 60));
+
+ auto outgoingMove7 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove7.mEventId, Some(idMove7));
+ // No extrapolation because the frame at 64.0 cleared the data points because
+ // there was no pending touch move event at that point
+ CheckEvent(outgoingMove7.mEvent, MultiTouchInput::MULTITOUCH_MOVE, {},
+ Time(60.0), ScreenIntPoint(100, 60));
+ EXPECT_EQ(outgoingMove7.mEvent.mTouches[0].mIdentifier, 2);
+
+ auto outgoingEnd8 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingEnd8.mEventId, Some(idEnd8));
+ CheckEvent(outgoingEnd8.mEvent, MultiTouchInput::MULTITOUCH_END, {},
+ Time(70.0), ScreenIntPoint(100, 60));
+}
+
+TEST_F(TouchResamplerTest, MovingPauses) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(10, 10));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(20, 20));
+ resampler.NotifyFrame(Time(16.0));
+ auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(30.0),
+ ScreenIntPoint(40, 40));
+ resampler.NotifyFrame(Time(32.0));
+ auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0),
+ ScreenIntPoint(50, 40));
+ resampler.NotifyFrame(Time(48.0));
+ resampler.NotifyFrame(Time(64.0));
+ auto idEnd4 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(50, 40));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(10, 10)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 20)}},
+ Time(16.0),
+ ScreenIntPoint(26, 26)},
+
+ {Some(idMove2),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(30.0), ScreenIntPoint(40, 40)}},
+ Time(32.0),
+ ScreenIntPoint(42, 42)},
+
+ {Some(idMove3),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(40.0), ScreenIntPoint(50, 40)}},
+ Time(48.0),
+ ScreenIntPoint(58, 40)},
+
+ // There was no event between two frames here, so we expect a reset event,
+ // so that we pause at a non-resampled position.
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(48.0),
+ ScreenIntPoint(50, 40)},
+
+ {Some(idEnd4),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(50, 40)},
+ });
+}
+
+TEST_F(TouchResamplerTest, MixedInterAndExtrapolation) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(0, 10));
+ resampler.NotifyFrame(Time(11.0)); // 16 - 5
+ auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}}, Time(30.0),
+ ScreenIntPoint(0, 30));
+ resampler.NotifyFrame(Time(27.0)); // 32 - 5
+ auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0),
+ ScreenIntPoint(0, 40));
+ resampler.NotifyFrame(Time(43.0)); // 48 - 5
+ auto idMove4 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(50.0), ScreenIntPoint(0, 50)}}, Time(60.0),
+ ScreenIntPoint(0, 60));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd5 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 60));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}},
+ Time(11.0),
+ ScreenIntPoint(0, 11)},
+
+ {Some(idMove2),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}},
+ Time(27.0),
+ ScreenIntPoint(0, 27)},
+
+ {Some(idMove3),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(30.0), ScreenIntPoint(0, 30)},
+ {Time(40.0), ScreenIntPoint(0, 40)}},
+ Time(43.0),
+ ScreenIntPoint(0, 43)},
+
+ {Some(idMove4),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(50.0), ScreenIntPoint(0, 50)}},
+ Time(59.0),
+ ScreenIntPoint(0, 59)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(60.0),
+ ScreenIntPoint(0, 60)},
+
+ {Some(idEnd5),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 60)},
+ });
+}
+
+TEST_F(TouchResamplerTest, MultipleMoveEvents) {
+ // Test what happens if multiple touch move events appear between two frames.
+ // This scenario shouldn't occur on Android but we should be able to deal with
+ // it anyway. Check that we don't discard any event IDs.
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(0, 10));
+ resampler.NotifyFrame(Time(11.0)); // 16 - 5
+ auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}}, Time(30.0),
+ ScreenIntPoint(0, 30));
+ auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0),
+ ScreenIntPoint(0, 40));
+ auto idMove4 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(45.0), ScreenIntPoint(0, 45)}}, Time(50.0),
+ ScreenIntPoint(0, 50));
+ auto idMove5 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(55.0),
+ ScreenIntPoint(0, 55));
+ resampler.NotifyFrame(Time(43.0)); // 48 - 5
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd5 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 60));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}},
+ Time(11.0),
+ ScreenIntPoint(0, 11)},
+
+ {Some(idMove2),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}},
+ Time(30.0),
+ ScreenIntPoint(0, 30)},
+
+ {Some(idMove3),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(40.0),
+ ScreenIntPoint(0, 40)},
+
+ {Some(idMove4),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(43.0),
+ ScreenIntPoint(0, 43)},
+
+ {Some(idMove5),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(45.0), ScreenIntPoint(0, 45)},
+ {Time(50.0), ScreenIntPoint(0, 50)},
+ {Time(55.0), ScreenIntPoint(0, 55)}},
+ Time(59.0),
+ ScreenIntPoint(0, 59)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(59.0),
+ ScreenIntPoint(0, 55)},
+
+ {Some(idEnd5),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 60)},
+ });
+}
+
+TEST_F(TouchResamplerTest, LimitFuturePrediction) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 44, then pause. UI thread is occupied until 64.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(32.0), ScreenIntPoint(0, 32)}},
+ Time(44.0), ScreenIntPoint(0, 44));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 44));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleMaxPredictMs == 8
+ // Refuse to predict more than 8ms into the future, the fingers might have
+ // paused. Make an event for time 52 (= 44 + 8) instead of 59.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(32.0), ScreenIntPoint(0, 32)},
+ {Time(44.0), ScreenIntPoint(0, 44)}},
+ Time(52.0),
+ ScreenIntPoint(0, 52)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(52.0),
+ ScreenIntPoint(0, 44)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 44)},
+ });
+}
+
+TEST_F(TouchResamplerTest, LimitBacksampling) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 44, then pause. UI thread is occupied until 64.
+ // Then we get a frame callback with a very outdated frametime.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(32.0), ScreenIntPoint(0, 32)}},
+ Time(44.0), ScreenIntPoint(0, 44));
+ resampler.NotifyFrame(Time(11.0)); // 16 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 44));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleMaxBacksampleMs == 20
+ // Refuse to sample further back than 20ms before the last data point.
+ // Make an event for time 24 (= 44 - 20) instead of time 11.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}},
+ Time(24.0),
+ ScreenIntPoint(0, 24)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(32.0), ScreenIntPoint(0, 32)}},
+ Time(44.0),
+ ScreenIntPoint(0, 44)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 44)},
+ });
+}
+
+TEST_F(TouchResamplerTest, DontExtrapolateFromOldTouch) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 40, then pause. UI thread is occupied until 64.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(30.0), ScreenIntPoint(0, 30)}},
+ Time(40.0), ScreenIntPoint(0, 40));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 44));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleOldTouchThresholdMs == 17
+ // Refuse to extrapolate from a data point that's more than 17ms older
+ // than the frame time.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(30.0), ScreenIntPoint(0, 30)}},
+ Time(40.0),
+ ScreenIntPoint(0, 40)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 44)},
+ });
+}
+
+TEST_F(TouchResamplerTest, DontExtrapolateIfTooOld) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 10, pause, and move again at 55.
+ // UI thread is occupied until 64.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(55.0),
+ ScreenIntPoint(0, 55));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 60));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleWindowSize == 40
+ // Refuse to resample between two data points that are more than 40ms
+ // apart.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}},
+ Time(55.0),
+ ScreenIntPoint(0, 55)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 60)},
+ });
+}
+
+TEST_F(TouchResamplerTest, DontInterpolateIfTooOld) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 10, pause, and move again at 60.
+ // UI thread is occupied until 64.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(60.0),
+ ScreenIntPoint(0, 60));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 60));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleWindowSize == 40
+ // Refuse to resample between two data points that are more than 40ms
+ // apart.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}},
+ Time(60.0),
+ ScreenIntPoint(0, 60)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 60)},
+ });
+}
+
+TEST_F(TouchResamplerTest, DiscardOutdatedHistoricalData) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(16.0),
+ ScreenIntPoint(0, 16));
+ resampler.NotifyFrame(Time(20.0));
+ auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(18.0), ScreenIntPoint(0, 18)}}, Time(25.0),
+ ScreenIntPoint(0, 25));
+ auto idEnd3 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(35.0),
+ ScreenIntPoint(0, 25));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)},
+ {Time(16.0), ScreenIntPoint(0, 16)}},
+ Time(20.0),
+ ScreenIntPoint(0, 20)},
+
+ // Discard the historical data point from time 18, because we've already
+ // sent out an event with time 20 and don't want to go back before that.
+ {Some(idMove2),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(25.0),
+ ScreenIntPoint(0, 25)},
+
+ {Some(idEnd3),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(35.0),
+ ScreenIntPoint(0, 25)},
+ });
+}
diff --git a/widget/tests/gtest/moz.build b/widget/tests/gtest/moz.build
new file mode 100644
index 0000000000..078b454e06
--- /dev/null
+++ b/widget/tests/gtest/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES = [
+ "TestTimeConverter.cpp",
+ "TestTouchResampler.cpp",
+]
+
+FINAL_LIBRARY = "xul-gtest"
+
+LOCAL_INCLUDES += [
+ "/widget",
+]
diff --git a/widget/tests/mochitest.ini b/widget/tests/mochitest.ini
new file mode 100644
index 0000000000..534310d279
--- /dev/null
+++ b/widget/tests/mochitest.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+support-files = utils.js
+
+[test_AltGr_key_events_in_web_content_on_windows.html]
+skip-if = toolkit != 'windows' || headless # headless: Bug 1410525
+[test_actionhint.html]
+[test_assign_event_data.html]
+skip-if = toolkit == "cocoa" || (toolkit == 'android' && debug) || android_version == '24' || (headless && os == "win") # Mac: Bug 933303, Android bug 1285414
+[test_autocapitalize.html]
+[test_keypress_event_with_alt_on_mac.html]
+skip-if = toolkit != "cocoa"
+[test_mouse_event_with_control_on_mac.html]
+skip-if = toolkit != "cocoa"
+[test_picker_no_crash.html]
+skip-if = toolkit != "windows" || e10s # Bug 1267491
+support-files = window_picker_no_crash_child.html
+[test_scrollbar_colors.html]
+skip-if = (os == 'linux' && headless) # bug 1460109
diff --git a/widget/tests/moz.build b/widget/tests/moz.build
new file mode 100644
index 0000000000..d3b1073713
--- /dev/null
+++ b/widget/tests/moz.build
@@ -0,0 +1,125 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget")
+
+with Files("unit/*macwebapputils*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("unit/*taskbar_jumplistitems*"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+
+with Files("TestChromeMargin.cpp"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+
+with Files("*413277*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*428405*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*429954*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*444800*"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+
+with Files("*466599*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*478536*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*485118*"):
+ BUG_COMPONENT = ("Toolkit", "XUL Widgets")
+
+with Files("*517396*"):
+ BUG_COMPONENT = ("Toolkit", "XUL Widgets")
+
+with Files("*522217*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*538242*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*565392*"):
+ BUG_COMPONENT = ("Core", "DOM: Serializers")
+
+with Files("*586713*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*593307*"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+
+with Files("*596600*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*673301*"):
+ BUG_COMPONENT = ("Firefox", "Bookmarks & History")
+
+with Files("test_assign_event_data.html"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("test_input_events_on_deactive_window.xhtml"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*chrome_context_menus_win*"):
+ BUG_COMPONENT = ("Core", "General")
+
+with Files("*composition_text_querycontent*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*key_event_counts*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*imestate*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*mouse_scroll*"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+
+with Files("*native*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*panel_mouse_coords*"):
+ BUG_COMPONENT = ("Core", "Widget: Gtk")
+
+with Files("*picker_no_crash*"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+
+with Files("*platform_colors*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*plugin*"):
+ BUG_COMPONENT = ("Core", "Plug-ins")
+
+with Files("*position_on_resize*"):
+ BUG_COMPONENT = ("Core", "Widget: Gtk")
+
+with Files("test_sizemode_events.xhtml"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*system_status_bar*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*taskbar_progress*"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+
+with Files("*wheeltransaction*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+
+XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.ini"]
+MOCHITEST_MANIFESTS += ["mochitest.ini"]
+MOCHITEST_CHROME_MANIFESTS += ["chrome.ini"]
+BROWSER_CHROME_MANIFESTS += ["browser/browser.ini"]
+
+# if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+#
+# Test disabled because it requires the internal API. Re-enabling this test
+# is bug 652123.
+# CPP_UNIT_TESTS += ['TestChromeMargin']
diff --git a/widget/tests/native_menus_window.xhtml b/widget/tests/native_menus_window.xhtml
new file mode 100644
index 0000000000..2b3d3f2aa3
--- /dev/null
+++ b/widget/tests/native_menus_window.xhtml
@@ -0,0 +1,282 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="NativeMenuWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="300"
+ height="300"
+ onload="onLoad();"
+ title="Native Menu Test">
+
+ <command id="cmd_FooItem0" oncommand="executedCommandID = 'cmd_FooItem0';"/>
+ <command id="cmd_FooItem1" oncommand="executedCommandID = 'cmd_FooItem1';"/>
+ <command id="cmd_BarItem0" oncommand="executedCommandID = 'cmd_BarItem0';"/>
+ <command id="cmd_BarItem1" oncommand="executedCommandID = 'cmd_BarItem1';"/>
+ <command id="cmd_NewItem0" oncommand="executedCommandID = 'cmd_NewItem0';"/>
+ <command id="cmd_NewItem1" oncommand="executedCommandID = 'cmd_NewItem1';"/>
+ <command id="cmd_NewItem2" oncommand="executedCommandID = 'cmd_NewItem2';"/>
+ <command id="cmd_NewItem3" oncommand="executedCommandID = 'cmd_NewItem3';"/>
+ <command id="cmd_NewItem4" oncommand="executedCommandID = 'cmd_NewItem4';"/>
+ <command id="cmd_NewItem5" oncommand="executedCommandID = 'cmd_NewItem5';"/>
+
+ <!-- We do not modify any menus or menu items defined here in testing. These
+ serve as a baseline structure for us to test after other modifications.
+ We add children to the menubar defined here and test by modifying those
+ children. -->
+ <menubar id="nativemenubar">
+ <menu id="foo" label="Foo">
+ <menupopup>
+ <menuitem label="FooItem0" command="cmd_FooItem0"/>
+ <menuitem label="FooItem1" command="cmd_FooItem1"/>
+ <menuseparator/>
+ <menu label="Bar">
+ <menupopup>
+ <menuitem label="BarItem0" command="cmd_BarItem0"/>
+ <menuitem label="BarItem1" command="cmd_BarItem1"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menubar>
+
+ <script type="application/javascript"><![CDATA[
+
+ function ok(condition, message) {
+ window.arguments[0].SimpleTest.ok(condition, message);
+ }
+
+ function onTestsFinished() {
+ window.close();
+ window.arguments[0].SimpleTest.finish();
+ }
+
+ // Force a menu to update itself. All of the menus parents will be updated
+ // as well. An empty string will force a complete menu system reload.
+ function forceUpdateNativeMenuAt(location) {
+ var utils = window.windowUtils;
+ try {
+ utils.forceUpdateNativeMenuAt(location);
+ }
+ catch (e) {
+ dump(e + "\n");
+ }
+ }
+
+ var executedCommandID = "";
+
+ function testItem(location, targetID) {
+ var utils = window.windowUtils;
+ var correctCommandHandler = false;
+ try {
+ utils.activateNativeMenuItemAt(location);
+ correctCommandHandler = executedCommandID == targetID;
+ }
+ catch (e) {
+ dump(e + "\n");
+ }
+ finally {
+ executedCommandID = "";
+ }
+ return correctCommandHandler;
+ }
+
+ function createXULMenuPopup() {
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var item = document.createElementNS(XUL_NS, "menupopup");
+ return item;
+ }
+
+ function createXULMenu(aLabel) {
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var item = document.createElementNS(XUL_NS, "menu");
+ item.setAttribute("label", aLabel);
+ return item;
+ }
+
+ function createXULMenuItem(aLabel, aCommandId) {
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var item = document.createElementNS(XUL_NS, "menuitem");
+ item.setAttribute("label", aLabel);
+ item.setAttribute("command", aCommandId);
+ return item;
+ }
+
+ function runBaseMenuTests() {
+ forceUpdateNativeMenuAt("0|3");
+ return testItem("0|0", "cmd_FooItem0") &&
+ testItem("0|1", "cmd_FooItem1") &&
+ testItem("0|3|0", "cmd_BarItem0") &&
+ testItem("0|3|1", "cmd_BarItem1");
+ }
+
+ function onLoad() {
+ var _delayedOnLoad = function() {
+ // First let's run the base menu tests.
+ ok(runBaseMenuTests());
+
+ // Set up some nodes that we'll use.
+ var menubarNode = document.getElementById("nativemenubar");
+ var newMenu0 = createXULMenu("NewMenu0");
+ var newMenu1 = createXULMenu("NewMenu1");
+ var newMenuPopup0 = createXULMenuPopup();
+ var newMenuPopup1 = createXULMenuPopup();
+ var newMenuItem0 = createXULMenuItem("NewMenuItem0", "cmd_NewItem0");
+ var newMenuItem1 = createXULMenuItem("NewMenuItem1", "cmd_NewItem1");
+ var newMenuItem2 = createXULMenuItem("NewMenuItem2", "cmd_NewItem2");
+ var newMenuItem3 = createXULMenuItem("NewMenuItem3", "cmd_NewItem3");
+ var newMenuItem4 = createXULMenuItem("NewMenuItem4", "cmd_NewItem4");
+ var newMenuItem5 = createXULMenuItem("NewMenuItem5", "cmd_NewItem5");
+
+ // Create another submenu with hierarchy via DOM manipulation.
+ // ******************
+ // * Foo * NewMenu0 * <- Menu bar
+ // ******************
+ // ****************
+ // * NewMenuItem0 * <- NewMenu0 submenu
+ // ****************
+ // * NewMenuItem1 *
+ // ****************
+ // * NewMenuItem2 *
+ // *******************************
+ // * NewMenu1 > * NewMenuItem3 * <- NewMenu1 submenu
+ // *******************************
+ // * NewMenuItem4 *
+ // ****************
+ // * NewMenuItem5 *
+ // ****************
+ newMenu0.appendChild(newMenuPopup0);
+ newMenuPopup0.appendChild(newMenuItem0);
+ newMenuPopup0.appendChild(newMenuItem1);
+ newMenuPopup0.appendChild(newMenuItem2);
+ newMenuPopup0.appendChild(newMenu1);
+ newMenu1.appendChild(newMenuPopup1);
+ newMenuPopup1.appendChild(newMenuItem3);
+ newMenuPopup1.appendChild(newMenuItem4);
+ newMenuPopup1.appendChild(newMenuItem5);
+ //XXX - we have to append the menu to the top-level of the menu bar
+ // only after constructing it. If we append before construction, it is
+ // invalid because it has no children and we don't validate it if we add
+ // children later.
+ menubarNode.appendChild(newMenu0);
+ forceUpdateNativeMenuAt("1|3");
+ // Run basic tests again.
+ ok(runBaseMenuTests());
+
+ // Error strings.
+ var sa = "Command handler(s) should have activated";
+ var sna = "Command handler(s) should not have activated";
+
+ // Test middle items.
+ ok(testItem("1|1", "cmd_NewItem1"), sa);
+ ok(testItem("1|3|1", "cmd_NewItem4"), sa);
+
+ // Hide newMenu0.
+ newMenu0.setAttribute("hidden", "true");
+ ok(runBaseMenuTests(), sa); // the base menu should still be unhidden
+ ok(!testItem("1|0", ""), sna);
+ ok(!testItem("1|1", ""), sna);
+ ok(!testItem("1|2", ""), sna);
+ ok(!testItem("1|3|0", ""), sna);
+ ok(!testItem("1|3|1", ""), sna);
+ ok(!testItem("1|3|2", ""), sna);
+
+ // Show newMenu0.
+ newMenu0.setAttribute("hidden", "false");
+ forceUpdateNativeMenuAt("1|3");
+ ok(runBaseMenuTests(), sa);
+ ok(testItem("1|0", "cmd_NewItem0"), sa);
+ ok(testItem("1|1", "cmd_NewItem1"), sa);
+ ok(testItem("1|2", "cmd_NewItem2"), sa);
+ ok(testItem("1|3|0", "cmd_NewItem3"), sa);
+ ok(testItem("1|3|1", "cmd_NewItem4"), sa);
+ ok(testItem("1|3|2", "cmd_NewItem5"), sa);
+
+ // Hide items.
+ newMenuItem1.setAttribute("hidden", "true");
+ newMenuItem4.setAttribute("hidden", "true");
+ forceUpdateNativeMenuAt("1|2");
+ ok(runBaseMenuTests(), sa);
+ ok(testItem("1|0", "cmd_NewItem0"), sa);
+ ok(testItem("1|1", "cmd_NewItem2"), sa);
+ ok(!testItem("1|2", ""), sna);
+ ok(testItem("1|2|0", "cmd_NewItem3"), sa);
+ ok(testItem("1|2|1", "cmd_NewItem5"), sa);
+ ok(!testItem("1|2|2", ""), sna);
+
+ // Show items.
+ newMenuItem1.setAttribute("hidden", "false");
+ newMenuItem4.setAttribute("hidden", "false");
+ forceUpdateNativeMenuAt("1|3");
+ ok(runBaseMenuTests(), sa);
+ ok(testItem("1|0", "cmd_NewItem0"), sa);
+ ok(testItem("1|1", "cmd_NewItem1"), sa);
+ ok(testItem("1|2", "cmd_NewItem2"), sa);
+ ok(testItem("1|3|0", "cmd_NewItem3"), sa);
+ ok(testItem("1|3|1", "cmd_NewItem4"), sa);
+ ok(testItem("1|3|2", "cmd_NewItem5"), sa);
+
+ // At this point in the tests the state of the menus has been returned
+ // to the originally diagramed state.
+
+ // Test command disabling
+ var cmd_NewItem0 = document.getElementById("cmd_NewItem0");
+ ok(testItem("1|0", "cmd_NewItem0"), sa);
+ cmd_NewItem0.setAttribute("disabled", "true");
+ ok(!testItem("1|0", "cmd_NewItem0"), sna);
+ cmd_NewItem0.removeAttribute("disabled");
+ ok(testItem("1|0", "cmd_NewItem0"), sa);
+
+ // Remove menu.
+ menubarNode.removeChild(newMenu0);
+ ok(runBaseMenuTests(), sa);
+ ok(!testItem("1|0", ""), sna);
+ ok(!testItem("1|1", ""), sna);
+ ok(!testItem("1|2", ""), sna);
+ ok(!testItem("1|3|0", ""), sna);
+ ok(!testItem("1|3|1", ""), sna);
+ ok(!testItem("1|3|2", ""), sna);
+ // return state to original diagramed state
+ menubarNode.appendChild(newMenu0);
+
+ // Test for bug 447042, make sure that adding a menu node with no children
+ // to the menu bar and then adding another menu node with children works.
+ // Menus with no children don't get their native menu items shown and that
+ // caused internal arrays to get out of sync and an append crashed.
+ var tmpMenu0 = createXULMenu("tmpMenu0");
+ menubarNode.removeChild(newMenu0);
+ menubarNode.appendChild(tmpMenu0);
+ menubarNode.appendChild(newMenu0);
+ forceUpdateNativeMenuAt("1|3");
+ ok(runBaseMenuTests());
+ ok(testItem("1|0", "cmd_NewItem0"), sa);
+ ok(testItem("1|1", "cmd_NewItem1"), sa);
+ ok(testItem("1|2", "cmd_NewItem2"), sa);
+ ok(testItem("1|3|0", "cmd_NewItem3"), sa);
+ ok(testItem("1|3|1", "cmd_NewItem4"), sa);
+ ok(testItem("1|3|2", "cmd_NewItem5"), sa);
+ // return state to original diagramed state
+ menubarNode.removeChild(tmpMenu0);
+
+ // This test is basically a crash test for bug 433858.
+ newMenuItem1.setAttribute("hidden", "true");
+ newMenuItem2.setAttribute("hidden", "true");
+ newMenu1.setAttribute("hidden", "true");
+ forceUpdateNativeMenuAt("1");
+ newMenuItem1.setAttribute("hidden", "false");
+ newMenuItem2.setAttribute("hidden", "false");
+ newMenu1.setAttribute("hidden", "false");
+ forceUpdateNativeMenuAt("1");
+
+ onTestsFinished();
+ }
+
+ setTimeout(_delayedOnLoad, 1000);
+ }
+
+ ]]></script>
+</window>
diff --git a/widget/tests/native_mouse_mac_window.xhtml b/widget/tests/native_mouse_mac_window.xhtml
new file mode 100644
index 0000000000..514664bc69
--- /dev/null
+++ b/widget/tests/native_mouse_mac_window.xhtml
@@ -0,0 +1,770 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="NativeMenuWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ width="600"
+ height="600"
+ title="Native Mouse Event Test"
+ orient="vertical">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <box height="200" id="box"/>
+ <menupopup id="popup" width="250" height="50"/>
+ <panel id="panel" width="250" height="50" noautohide="true"/>
+
+ <script type="application/javascript"><![CDATA[
+
+ function ok(condition, message) {
+ window.arguments[0].SimpleTest.ok(condition, message);
+ }
+
+ function is(a, b, message) {
+ window.arguments[0].SimpleTest.is(a, b, message);
+ }
+
+ function todo(condition, message) {
+ window.arguments[0].SimpleTest.todo(condition, message);
+ }
+
+ function todo_is(a, b, message) {
+ window.arguments[0].SimpleTest.todo_is(a, b, message);
+ }
+
+ function onTestsFinished() {
+ clearTimeout(gAfterLoopExecution);
+ observe(window, eventMonitor, false);
+ observe(gRightWindow, eventMonitor, false);
+ observe(gPopup, eventMonitor, false);
+ gRightWindow.close();
+ var openerSimpleTest = window.arguments[0].SimpleTest;
+ window.close();
+ openerSimpleTest.finish();
+ }
+
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ const xulWin = 'data:application/xhtml+xml,<?xml version="1.0"?><?xml-stylesheet href="chrome://global/skin" type="text/css"?><window xmlns="' + XUL_NS + '"/>';
+
+ const NSEventTypeLeftMouseDown = 1,
+ NSEventTypeLeftMouseUp = 2,
+ NSEventTypeRightMouseDown = 3,
+ NSEventTypeRightMouseUp = 4,
+ NSEventTypeMouseMoved = 5,
+ NSEventTypeLeftMouseDragged = 6,
+ NSEventTypeRightMouseDragged = 7,
+ NSEventTypeMouseEntered = 8,
+ NSEventTypeMouseExited = 9,
+ NSEventTypeKeyDown = 10,
+ NSEventTypeKeyUp = 11,
+ NSEventTypeFlagsChanged = 12,
+ NSEventTypeAppKitDefined = 13,
+ NSEventTypeSystemDefined = 14,
+ NSEventTypeApplicationDefined = 15,
+ NSEventTypePeriodic = 16,
+ NSEventTypeCursorUpdate = 17,
+ NSEventTypeScrollWheel = 22,
+ NSEventTypeTabletPoint = 23,
+ NSEventTypeTabletProximity = 24,
+ NSEventTypeOtherMouseDown = 25,
+ NSEventTypeOtherMouseUp = 26,
+ NSEventTypeOtherMouseDragged = 27,
+ NSEventTypeGesture = 29,
+ NSEventTypeMagnify = 30,
+ NSEventTypeSwipe = 31,
+ NSEventTypeRotate = 18,
+ NSEventTypeBeginGesture = 19,
+ NSEventTypeEndGesture = 20;
+
+ const NSEventModifierFlagCapsLock = 1 << 16,
+ NSEventModifierFlagShift = 1 << 17,
+ NSEventModifierFlagControl = 1 << 18,
+ NSEventModifierFlagOption = 1 << 19,
+ NSEventModifierFlagCommand = 1 << 20,
+ NSEventModifierFlagNumericPad = 1 << 21,
+ NSEventModifierFlagHelp = 1 << 22,
+ NSEventModifierFlagFunction = 1 << 23;
+
+ const gDebug = false;
+
+ function printDebug(msg) { if (gDebug) dump(msg); }
+
+ var gExpectedEvents = [];
+ var gRightWindow = null, gPopup = null;
+ var gCurrentMouseX = 0, gCurrentMouseY = 0;
+ var gAfterLoopExecution = 0;
+
+ function testMouse(x, y, msg, elem, win, exp, flags, callback) {
+ clearExpectedEvents();
+ var syntheticEvent = null;
+ exp.forEach(function (expEv) {
+ expEv.screenX = x;
+ expEv.screenY = y;
+ if (expEv.synthetic) {
+ is(syntheticEvent, null,
+ "Can't handle two synthetic events in a single testMouse call");
+ syntheticEvent = expEv;
+ }
+ gExpectedEvents.push(expEv);
+ });
+ printDebug("sending event: " + x + ", " + y + " (" + msg + ")\n");
+ gCurrentMouseX = x;
+ gCurrentMouseY = y;
+ var utils = win.windowUtils;
+ var callbackFunc = function() {
+ clearExpectedEvents();
+ callback();
+ }
+ if (syntheticEvent) {
+ // Set up this listener before we sendNativeMouseEvent, just
+ // in case that synchronously calls us.
+ eventListenOnce(syntheticEvent.target, syntheticEvent.type,
+ // Trigger callbackFunc async, so we're not assuming
+ // anything about how our listener gets ordered with
+ // others.
+ function () { SimpleTest.executeSoon(callbackFunc) });
+ }
+ utils.sendNativeMouseEvent(x, y, msg, flags || 0, elem);
+ if (!syntheticEvent) {
+ gAfterLoopExecution = setTimeout(callbackFunc, 0);
+ }
+ }
+
+ function eventListenOnce(elem, name, callback) {
+ elem.addEventListener(name, function(e) {
+ callback(e);
+ }, {once: true});
+ }
+
+ function focusAndThen(win, callback) {
+ eventListenOnce(win, "focus", callback);
+ printDebug("focusing a window\n");
+ win.focus();
+ }
+
+ function eventToString(e) {
+ return JSON.stringify({
+ type: e.type, target: e.target.nodeName, screenX: e.screenX, screenY: e.screenY
+ });
+ }
+
+ function clearExpectedEvents() {
+ while (!gExpectedEvents.length) {
+ var expectedEvent = gExpectedEvents.shift();
+ var errFun = expectedEvent.shouldFireButDoesnt ? todo : ok;
+ errFun(false, "Didn't receive expected event: " + eventToString(expectedEvent));
+ }
+ }
+
+ var gEventNum = 0;
+
+ function eventMonitor(e) {
+ printDebug("got event: " + eventToString(e) + "\n");
+ processEvent(e);
+ }
+
+ function processEvent(e) {
+ if (e.screenX != gCurrentMouseX || e.screenY != gCurrentMouseY) {
+ todo(false, "Oh no! Received a stray event from a confused tracking area. Aborting test.");
+ onTestsFinished();
+ return;
+ }
+ var expectedEvent = gExpectedEvents.shift();
+ if (!expectedEvent) {
+ ok(false, "received event I didn't expect: " + eventToString(e));
+ return;
+ }
+ if (e.type != expectedEvent.type) {
+ // Didn't get expectedEvent.
+ var errFun = expectedEvent.shouldFireButDoesnt ? todo : ok;
+ errFun(false, "Didn't receive expected event: " + eventToString(expectedEvent));
+ processEvent(e);
+ return;
+ }
+ gEventNum++;
+ is(e.screenX, expectedEvent.screenX, gEventNum + " | wrong X coord for event " + eventToString(e));
+ is(e.screenY, expectedEvent.screenY, gEventNum + " | wrong Y coord for event " + eventToString(e));
+ is(e.target, expectedEvent.target, gEventNum + " | wrong target for event " + eventToString(e));
+ if (expectedEvent.firesButShouldnt) {
+ todo(false, gEventNum + " | Got an event that should not have fired: " + eventToString(e));
+ }
+ }
+
+ function observe(elem, fun, add) {
+ var addOrRemove = add ? "addEventListener" : "removeEventListener";
+ elem[addOrRemove]("mousemove", fun, false);
+ elem[addOrRemove]("mouseover", fun, false);
+ elem[addOrRemove]("mouseout", fun, false);
+ elem[addOrRemove]("mousedown", fun, false);
+ elem[addOrRemove]("mouseup", fun, false);
+ elem[addOrRemove]("click", fun, false);
+ }
+
+ function start() {
+ window.resizeTo(200, 200);
+ window.moveTo(50, 50);
+ gRightWindow = open(xulWin, '', 'chrome,screenX=300,screenY=50,width=200,height=200');
+ eventListenOnce(gRightWindow, "focus", function () {
+ focusAndThen(window, runTests);
+ });
+ gPopup = document.getElementById("popup");
+ }
+
+ function runTests() {
+ observe(window, eventMonitor, true);
+ observe(gRightWindow, eventMonitor, true);
+ var left = window, right = gRightWindow;
+ var leftElem = document.getElementById("box");
+ var rightElem = gRightWindow.document.documentElement;
+ var panel = document.getElementById("panel");
+ var _tooltip = right.document.createElementNS(XUL_NS, "tooltip");
+ _tooltip.setAttribute("id", "tip");
+ _tooltip.setAttribute("width", "80");
+ _tooltip.setAttribute("height", "20");
+ right.document.documentElement.appendChild(_tooltip);
+ var tests = [
+
+ // Part 1: Disallow click-through
+
+ function blockClickThrough(callback) {
+ document.documentElement.setAttribute("clickthrough", "never");
+ gRightWindow.document.documentElement.setAttribute("clickthrough", "never");
+ callback();
+ },
+ // Enter the left window, which is focused.
+ [150, 150, NSEventTypeMouseMoved, null, left, [
+ { type: "mouseover", target: leftElem },
+ { type: "mousemove", target: leftElem }
+ ]],
+ // Test that moving inside the window fires mousemove events.
+ [170, 150, NSEventTypeMouseMoved, null, left, [
+ { type: "mousemove", target: leftElem },
+ ]],
+ // Leaving the window should fire a mouseout event...
+ [170, 20, NSEventTypeMouseMoved, null, left, [
+ { type: "mouseout", target: leftElem },
+ ]],
+ // ... and entering a mouseover event.
+ [170, 120, NSEventTypeMouseMoved, null, left, [
+ { type: "mouseover", target: leftElem },
+ { type: "mousemove", target: leftElem },
+ ]],
+ // Move over the right window, which is inactive.
+ // Inactive windows shouldn't respond to mousemove events when clickthrough="never",
+ // so we should only get a mouseout event, no mouseover event.
+ [400, 150, NSEventTypeMouseMoved, null, right, [
+ { type: "mouseout", target: leftElem },
+ ]],
+ // Left-clicking while holding Cmd and middle clicking should work even
+ // on inactive windows, but without making them active.
+ [400, 150, NSEventTypeLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ], NSEventModifierFlagCommand],
+ [400, 150, NSEventTypeLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ], NSEventModifierFlagCommand],
+ [400, 150, NSEventTypeOtherMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [400, 150, NSEventTypeOtherMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+ // Clicking an inactive window should make it active and fire a mouseover
+ // event.
+ [400, 150, NSEventTypeLeftMouseDown, null, right, [
+ { type: "mouseover", target: rightElem, synthetic: true },
+ ]],
+ [400, 150, NSEventTypeLeftMouseUp, null, right, [
+ ]],
+ // Now it's focused, so we should get a mousedown event when clicking.
+ [400, 150, NSEventTypeLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ // Let's drag to the right without letting the button go.
+ [410, 150, NSEventTypeLeftMouseDragged, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Let go of the mouse.
+ [410, 150, NSEventTypeLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+ // Move the mouse back over the left window, which is inactive.
+ [150, 170, NSEventTypeMouseMoved, null, left, [
+ { type: "mouseout", target: rightElem },
+ ]],
+ // Now we're being sneaky. The left window is inactive, but *right*-clicks to it
+ // should still get through. Test that.
+ // Ideally we'd be bracketing that event with over and out events, too, but it
+ // probably doesn't matter too much.
+ [150, 170, NSEventTypeRightMouseDown, null, left, [
+ { type: "mouseover", target: leftElem, shouldFireButDoesnt: true },
+ { type: "mousedown", target: leftElem },
+ { type: "mouseout", target: leftElem, shouldFireButDoesnt: true },
+ ]],
+ // Let go of the mouse.
+ [150, 170, NSEventTypeRightMouseUp, null, left, [
+ { type: "mouseover", target: leftElem, shouldFireButDoesnt: true },
+ { type: "mouseup", target: leftElem },
+ { type: "click", target: leftElem },
+ { type: "mouseout", target: leftElem, shouldFireButDoesnt: true },
+ ]],
+ // Right clicking hasn't focused it, so the window is still inactive.
+ // Let's focus it; this time without the mouse, for variaton's sake.
+ // Still, mouseout and mouseover events should fire.
+ function raiseLeftWindow(callback) {
+ clearExpectedEvents();
+ gExpectedEvents.push({ screenX: 150, screenY: 170, type: "mouseover", target: leftElem });
+ // We have to be a bit careful here. The synthetic mouse event may
+ // not fire for a bit after we focus the left window.
+ eventListenOnce(leftElem, "mouseover", function() {
+ // Trigger callback async, so we're not assuming
+ // anything about how our listener gets ordered with others.
+ SimpleTest.executeSoon(callback);
+ });
+ printDebug("focusing left window");
+ left.focus();
+ },
+ // It's active, so it should respond to mousemove events now.
+ [150, 170, NSEventTypeMouseMoved, null, left, [
+ { type: "mousemove", target: leftElem },
+ ]],
+ // This was boring... let's introduce a popup. It will overlap both the left
+ // and the right window.
+ function openPopupInLeftWindow(callback) {
+ eventListenOnce(gPopup, "popupshown", callback);
+ gPopup.openPopupAtScreen(150, 50, true);
+ },
+ // Move the mouse over the popup.
+ [200, 80, NSEventTypeMouseMoved, gPopup, left, [
+ { type: "mouseout", target: leftElem },
+ { type: "mouseover", target: gPopup },
+ { type: "mousemove", target: gPopup },
+ ]],
+ // Move the mouse back over the left window outside the popup.
+ [160, 170, NSEventTypeMouseMoved, null, left, [
+ { type: "mouseout", target: gPopup },
+ { type: "mouseover", target: leftElem },
+ { type: "mousemove", target: leftElem },
+ ]],
+ // Back over the popup...
+ [190, 80, NSEventTypeMouseMoved, gPopup, left, [
+ { type: "mouseout", target: leftElem },
+ { type: "mouseover", target: gPopup },
+ { type: "mousemove", target: gPopup },
+ ]],
+ // ...and over into the right window.
+ // It's inactive, so it shouldn't get mouseover events yet.
+ [400, 170, NSEventTypeMouseMoved, null, right, [
+ { type: "mouseout", target: gPopup },
+ ]],
+ // Again, no mouse events please, even though a popup is open. (bug 425556)
+ [400, 180, NSEventTypeMouseMoved, null, right, [
+ ]],
+ // Activate the right window with a click.
+ // This will close the popup and make the mouse enter the right window.
+ [400, 180, NSEventTypeLeftMouseDown, null, right, [
+ { type: "mouseover", target: rightElem, synthetic: true },
+ ]],
+ [400, 180, NSEventTypeLeftMouseUp, null, right, [
+ ]],
+ function verifyPopupClosed2(callback) {
+ is(gPopup.state, "closed", "popup should have closed when clicking");
+ callback();
+ },
+ // Now the right window is active; click it again, just for fun.
+ [400, 180, NSEventTypeLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [400, 180, NSEventTypeLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+
+ // Time for our next trick: a tooltip!
+ // Install the tooltip, but don't show it yet.
+ function setTooltip(callback) {
+ rightElem.setAttribute("tooltip", "tip");
+ gExpectedEvents.push({ screenX: 410, screenY: 180, type: "mousemove", target: rightElem });
+ eventListenOnce(rightElem, "popupshown", callback);
+ gCurrentMouseX = 410;
+ gCurrentMouseY = 180;
+ var utils = right.windowUtils;
+ utils.sendNativeMouseEvent(410, 180, NSEventTypeMouseMoved, 0, null);
+ },
+ // Now the tooltip is visible.
+ // Move the mouse a little to the right.
+ [411, 180, NSEventTypeMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Move another pixel.
+ [412, 180, NSEventTypeMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Move up and click to make the tooltip go away.
+ [412, 80, NSEventTypeMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ [412, 80, NSEventTypeLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [412, 80, NSEventTypeLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+ // OK, next round. Open a panel in the left window, which is inactive.
+ function openPanel(callback) {
+ eventListenOnce(panel, "popupshown", callback);
+ panel.openPopupAtScreen(150, 150, false);
+ },
+ // The panel is parented, so it will be z-ordered over its parent but
+ // under the active window.
+ // Now we move the mouse over the part where the panel rect intersects the
+ // right window's rect. Since the panel is under the window, all the events
+ // should target the right window.
+ [390, 170, NSEventTypeMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ [390, 171, NSEventTypeMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ [391, 171, NSEventTypeMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Now move off the right window, so that the mouse is directly over the
+ // panel.
+ [260, 170, NSEventTypeMouseMoved, panel, left, [
+ { type: "mouseout", target: rightElem },
+ ]],
+ [260, 171, NSEventTypeMouseMoved, panel, left, [
+ ]],
+ [261, 171, NSEventTypeMouseMoved, panel, left, [
+ ]],
+ // Let's be evil and click it.
+ [261, 171, NSEventTypeLeftMouseDown, panel, left, [
+ ]],
+ [261, 171, NSEventTypeLeftMouseUp, panel, left, [
+ ]],
+ // This didn't focus the window, unfortunately, so let's do it ourselves.
+ function raiseLeftWindowTakeTwo(callback) {
+ focusAndThen(left, callback);
+ },
+ // Now mouse events should get through to the panel (which is now over the
+ // right window).
+ [387, 170, NSEventTypeMouseMoved, panel, left, [
+ { type: "mouseover", target: panel },
+ { type: "mousemove", target: panel },
+ ]],
+ [387, 171, NSEventTypeMouseMoved, panel, left, [
+ { type: "mousemove", target: panel },
+ ]],
+ [388, 171, NSEventTypeMouseMoved, panel, left, [
+ { type: "mousemove", target: panel },
+ ]],
+ // Click the panel.
+ [388, 171, NSEventTypeLeftMouseDown, panel, left, [
+ { type: "mousedown", target: panel }
+ ]],
+ [388, 171, NSEventTypeLeftMouseUp, panel, left, [
+ { type: "mouseup", target: panel },
+ { type: "click", target: panel },
+ ]],
+
+ // Last test for this part: Hit testing in the Canyon of Nowhere -
+ // the pixel row directly south of the panel, over the left window.
+ // Before bug 515003 we wrongly thought the mouse wasn't over any window.
+ [173, 200, NSEventTypeMouseMoved, null, left, [
+ { type: "mouseout", target: panel },
+ { type: "mouseover", target: leftElem },
+ { type: "mousemove", target: leftElem },
+ ]],
+ [173, 201, NSEventTypeMouseMoved, null, left, [
+ { type: "mousemove", target: leftElem },
+ ]],
+
+ // Part 2: Allow click-through
+
+ function hideThatPanel(callback) {
+ eventListenOnce(panel, "popuphidden", callback);
+ panel.hidePopup();
+ },
+ function unblockClickThrough(callback) {
+ document.documentElement.removeAttribute("clickthrough");
+ gRightWindow.document.documentElement.removeAttribute("clickthrough");
+ callback();
+ },
+ // Enter the left window, which is focused.
+ [150, 150, NSEventTypeMouseMoved, null, left, [
+ { type: "mousemove", target: leftElem }
+ ]],
+ // Test that moving inside the window fires mousemove events.
+ [170, 150, NSEventTypeMouseMoved, null, left, [
+ { type: "mousemove", target: leftElem },
+ ]],
+ // Leaving the window should fire a mouseout event...
+ [170, 20, NSEventTypeMouseMoved, null, left, [
+ { type: "mouseout", target: leftElem },
+ ]],
+ // ... and entering a mouseover event.
+ [170, 120, NSEventTypeMouseMoved, null, left, [
+ { type: "mouseover", target: leftElem },
+ { type: "mousemove", target: leftElem },
+ ]],
+ // Move over the right window, which is inactive but still accepts
+ // mouse events.
+ [400, 150, NSEventTypeMouseMoved, null, right, [
+ { type: "mouseout", target: leftElem },
+ { type: "mouseover", target: rightElem },
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Left-clicking while holding Cmd and middle clicking should work
+ // on inactive windows, but without making them active.
+ [400, 150, NSEventTypeLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ], NSEventModifierFlagCommand],
+ [400, 150, NSEventTypeLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ], NSEventModifierFlagCommand],
+ [400, 150, NSEventTypeOtherMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [400, 150, NSEventTypeOtherMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+ // Clicking an inactive window should make it active
+ [400, 150, NSEventTypeLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [400, 150, NSEventTypeLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+ // Now it's focused.
+ [401, 150, NSEventTypeLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ // Let's drag to the right without letting the button go.
+ [410, 150, NSEventTypeLeftMouseDragged, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Let go of the mouse.
+ [410, 150, NSEventTypeLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+ // Move the mouse back over the left window, which is inactive.
+ [150, 170, NSEventTypeMouseMoved, null, left, [
+ { type: "mouseout", target: rightElem },
+ { type: "mouseover", target: leftElem },
+ { type: "mousemove", target: leftElem },
+ ]],
+ // Right-click it.
+ [150, 170, NSEventTypeRightMouseDown, null, left, [
+ { type: "mousedown", target: leftElem },
+ ]],
+ // Let go of the mouse.
+ [150, 170, NSEventTypeRightMouseUp, null, left, [
+ { type: "mouseup", target: leftElem },
+ { type: "click", target: leftElem },
+ ]],
+ // Right clicking hasn't focused it, so the window is still inactive.
+ // Let's focus it; this time without the mouse, for variaton's sake.
+ function raiseLeftWindow(callback) {
+ clearExpectedEvents();
+ focusAndThen(left, function () { SimpleTest.executeSoon(callback); });
+ },
+ // It's active and should still respond to mousemove events.
+ [150, 170, NSEventTypeMouseMoved, null, left, [
+ { type: "mousemove", target: leftElem },
+ ]],
+
+ // This was boring... let's introduce a popup. It will overlap both the left
+ // and the right window.
+ function openPopupInLeftWindow(callback) {
+ eventListenOnce(gPopup, "popupshown", callback);
+ gPopup.openPopupAtScreen(150, 50, true);
+ },
+ // Move the mouse over the popup.
+ [200, 80, NSEventTypeMouseMoved, gPopup, left, [
+ { type: "mouseout", target: leftElem },
+ { type: "mouseover", target: gPopup },
+ { type: "mousemove", target: gPopup },
+ ]],
+ // Move the mouse back over the left window outside the popup.
+ [160, 170, NSEventTypeMouseMoved, null, left, [
+ { type: "mouseout", target: gPopup },
+ { type: "mouseover", target: leftElem },
+ { type: "mousemove", target: leftElem },
+ ]],
+ // Back over the popup...
+ [190, 80, NSEventTypeMouseMoved, gPopup, left, [
+ { type: "mouseout", target: leftElem },
+ { type: "mouseover", target: gPopup },
+ { type: "mousemove", target: gPopup },
+ ]],
+ // ...and over into the right window.
+ [400, 170, NSEventTypeMouseMoved, null, right, [
+ { type: "mouseout", target: gPopup },
+ { type: "mouseover", target: rightElem },
+ { type: "mousemove", target: rightElem },
+ ]],
+ [400, 180, NSEventTypeMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Activate the right window with a click.
+ [400, 180, NSEventTypeLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [400, 180, NSEventTypeLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+ function verifyPopupClosed2(callback) {
+ is(gPopup.state, "closed", "popup should have closed when clicking");
+ callback();
+ },
+ // Now the right window is active; click it again, just for fun.
+ [400, 180, NSEventTypeLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [400, 180, NSEventTypeLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+
+ // Time for our next trick: a tooltip!
+ // Install the tooltip, but don't show it yet.
+ function setTooltip2(callback) {
+ rightElem.setAttribute("tooltip", "tip");
+ gExpectedEvents.push({ screenX: 410, screenY: 180, type: "mousemove", target: rightElem });
+ eventListenOnce(rightElem, "popupshown", callback);
+ gCurrentMouseX = 410;
+ gCurrentMouseY = 180;
+ var utils = right.windowUtils;
+ utils.sendNativeMouseEvent(410, 180, NSEventTypeMouseMoved, 0, null);
+ },
+ // Now the tooltip is visible.
+ // Move the mouse a little to the right.
+ [411, 180, NSEventTypeMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Move another pixel.
+ [412, 180, NSEventTypeMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Move up and click to make the tooltip go away.
+ [412, 80, NSEventTypeMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ [412, 80, NSEventTypeLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [412, 80, NSEventTypeLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+ // OK, next round. Open a panel in the left window, which is inactive.
+ function openPanel2(callback) {
+ eventListenOnce(panel, "popupshown", callback);
+ panel.openPopupAtScreen(150, 150, false);
+ },
+ // The panel is parented, so it will be z-ordered over its parent but
+ // under the active window.
+ // Now we move the mouse over the part where the panel rect intersects the
+ // right window's rect. Since the panel is under the window, all the events
+ // should target the right window.
+ [390, 170, NSEventTypeMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ [390, 171, NSEventTypeMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ [391, 171, NSEventTypeMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Now move off the right window, so that the mouse is directly over the
+ // panel.
+ [260, 170, NSEventTypeMouseMoved, panel, left, [
+ { type: "mouseout", target: rightElem },
+ { type: "mouseover", target: panel },
+ { type: "mousemove", target: panel },
+ ]],
+ [260, 171, NSEventTypeMouseMoved, panel, left, [
+ { type: "mousemove", target: panel },
+ ]],
+ [261, 171, NSEventTypeMouseMoved, panel, left, [
+ { type: "mousemove", target: panel },
+ ]],
+ // Let's be evil and click it.
+ [261, 171, NSEventTypeLeftMouseDown, panel, left, [
+ { type: "mousedown", target: panel },
+ ]],
+ [261, 171, NSEventTypeLeftMouseUp, panel, left, [
+ { type: "mouseup", target: panel },
+ { type: "click", target: panel },
+ ]],
+ // This didn't focus the window, unfortunately, so let's do it ourselves.
+ function raiseLeftWindowTakeTwo(callback) {
+ focusAndThen(left, callback);
+ },
+ [387, 170, NSEventTypeMouseMoved, panel, left, [
+ { type: "mousemove", target: panel },
+ ]],
+ [387, 171, NSEventTypeMouseMoved, panel, left, [
+ { type: "mousemove", target: panel },
+ ]],
+ [388, 171, NSEventTypeMouseMoved, panel, left, [
+ { type: "mousemove", target: panel },
+ ]],
+ // Click the panel.
+ [388, 171, NSEventTypeLeftMouseDown, panel, left, [
+ { type: "mousedown", target: panel }
+ ]],
+ [388, 171, NSEventTypeLeftMouseUp, panel, left, [
+ { type: "mouseup", target: panel },
+ { type: "click", target: panel },
+ ]],
+
+ // Last test for today: Hit testing in the Canyon of Nowhere -
+ // the pixel row directly south of the panel, over the left window.
+ // Before bug 515003 we wrongly thought the mouse wasn't over any window.
+ [173, 200, NSEventTypeMouseMoved, null, left, [
+ { type: "mouseout", target: panel },
+ { type: "mouseover", target: leftElem },
+ { type: "mousemove", target: leftElem },
+ ]],
+ [173, 201, NSEventTypeMouseMoved, null, left, [
+ { type: "mousemove", target: leftElem },
+ ]],
+ ];
+ // eslint-disable-next-line consistent-return
+ function runNextTest() {
+ if (!tests.length) {
+ onTestsFinished();
+ return;
+ }
+ var test = tests.shift();
+ if (typeof test == "function") {
+ test(runNextTest);
+ return;
+ }
+
+ var [x, y, msg, elem, win, exp, flags] = test;
+ testMouse(x, y, msg, elem, win, exp, flags, runNextTest);
+ }
+ runNextTest();
+ }
+
+ SimpleTest.waitForFocus(start);
+
+ ]]></script>
+</window>
diff --git a/widget/tests/standalone_native_menu_window.xhtml b/widget/tests/standalone_native_menu_window.xhtml
new file mode 100644
index 0000000000..14a325ff2b
--- /dev/null
+++ b/widget/tests/standalone_native_menu_window.xhtml
@@ -0,0 +1,332 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="StandaloneNativeMenuWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="300"
+ height="300"
+ onload="onLoad();"
+ title="nsIStandaloneNativeMenu Test">
+
+ <command id="cmd_FooItem0" oncommand="executedCommandID = 'cmd_FooItem0';"/>
+ <command id="cmd_FooItem1" oncommand="executedCommandID = 'cmd_FooItem1';"/>
+ <command id="cmd_BarItem0" oncommand="executedCommandID = 'cmd_BarItem0';"/>
+ <command id="cmd_BarItem1" oncommand="executedCommandID = 'cmd_BarItem1';"/>
+ <command id="cmd_NewItem0" oncommand="executedCommandID = 'cmd_NewItem0';"/>
+ <command id="cmd_NewItem1" oncommand="executedCommandID = 'cmd_NewItem1';"/>
+ <command id="cmd_NewItem2" oncommand="executedCommandID = 'cmd_NewItem2';"/>
+ <command id="cmd_NewItem3" oncommand="executedCommandID = 'cmd_NewItem3';"/>
+ <command id="cmd_NewItem4" oncommand="executedCommandID = 'cmd_NewItem4';"/>
+ <command id="cmd_NewItem5" oncommand="executedCommandID = 'cmd_NewItem5';"/>
+
+ <!-- We do not modify any menus or menu items defined here in testing. These
+ serve as a baseline structure for us to test after other modifications.
+ We add children to the menubar defined here and test by modifying those
+ children. -->
+ <popupset>
+ <menupopup id="standalonenativemenu">
+ <menu id="foo" label="Foo">
+ <menupopup>
+ <menuitem label="FooItem0" command="cmd_FooItem0"/>
+ <menuitem label="FooItem1" command="cmd_FooItem1"/>
+ <menuseparator/>
+ <menu label="Bar">
+ <menupopup>
+ <menuitem label="BarItem0" command="cmd_BarItem0"/>
+ <menuitem label="BarItem1" command="cmd_BarItem1"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </popupset>
+
+ <script type="application/javascript"><![CDATA[
+
+ function ok(condition, message) {
+ window.arguments[0].SimpleTest.ok(condition, message);
+ }
+
+ function todo(condition, message) {
+ window.arguments[0].SimpleTest.todo(condition, message);
+ }
+
+ function onTestsFinished() {
+ window.close();
+ window.arguments[0].SimpleTest.finish();
+ }
+
+ var executedCommandID = "";
+
+ function testItem(menu, location, targetID) {
+ var correctCommandHandler = false;
+ try {
+ menu.menuWillOpen();
+ menu.activateNativeMenuItemAt(location);
+ correctCommandHandler = executedCommandID == targetID;
+ }
+ catch (e) {
+ dump(e + "\n");
+ }
+ finally {
+ executedCommandID = "";
+ }
+ return correctCommandHandler;
+ }
+
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ function createXULMenuPopup() {
+ return document.createElementNS(XUL_NS, "menupopup");
+ }
+
+ function createXULMenu(aLabel) {
+ var item = document.createElementNS(XUL_NS, "menu");
+ item.setAttribute("label", aLabel);
+ return item;
+ }
+
+ function createXULMenuItem(aLabel, aCommandId) {
+ var item = document.createElementNS(XUL_NS, "menuitem");
+ item.setAttribute("label", aLabel);
+ item.setAttribute("command", aCommandId);
+ return item;
+ }
+
+ function runBaseMenuTests(menu) {
+ menu.forceUpdateNativeMenuAt("0|3");
+ return testItem(menu, "0|0", "cmd_FooItem0") &&
+ testItem(menu, "0|1", "cmd_FooItem1") &&
+ testItem(menu, "0|3|0", "cmd_BarItem0") &&
+ testItem(menu, "0|3|1", "cmd_BarItem1");
+ }
+
+ function createStandaloneNativeMenu(menuNode) {
+ try {
+ let menu = Cc["@mozilla.org/widget/standalonenativemenu;1"].createInstance(Ci.nsIStandaloneNativeMenu);
+ menu.init(menuNode);
+ return menu;
+ } catch (e) {
+ ok(false, "Failed creating nsIStandaloneNativeMenu instance");
+ throw e;
+ }
+ }
+
+ function runDetachedMenuTests(addMenupopupBeforeCreatingSNM) {
+ let menu = createXULMenu("Detached menu");
+ menu.setAttribute("image", 'data:image/svg+xml,<svg%20xmlns="http://www.w3.org/2000/svg"%20width="32"%20height="32"><circle%20cx="16"%20cy="16"%20r="16"/></svg>');
+ let menupopup = createXULMenuPopup();
+
+ let popupShowingFired = false;
+ let itemActivated = false;
+
+ menupopup.addEventListener("popupshowing", function (e) {
+ popupShowingFired = true;
+
+ let menuitem = document.createElementNS(XUL_NS, "menuitem");
+ menuitem.setAttribute("label", "detached menu item");
+ /* eslint-disable-next-line no-shadow */
+ menuitem.addEventListener("command", function (e) {
+ itemActivated = true;
+ })
+ menupopup.appendChild(menuitem);
+ })
+
+ // It shouldn't make a difference whether the menupopup is added to the
+ // menu element before or after we create the nsIStandaloneNativeMenu
+ // instance with it. We test both orders by calling this function twice
+ // with different values for addMenupopupBeforeCreatingSNM.
+
+ var menuSNM = null; // the nsIStandaloneNativeMenu object for "menu"
+ if (addMenupopupBeforeCreatingSNM) {
+ menu.appendChild(menupopup);
+ menuSNM = createStandaloneNativeMenu(menu);
+ } else {
+ menuSNM = createStandaloneNativeMenu(menu);
+ menu.appendChild(menupopup);
+ }
+
+ try {
+ ok(!popupShowingFired, "popupshowing shouldn't have fired before our call to menuWillOpen()");
+ menuSNM.menuWillOpen();
+ ok(popupShowingFired, "calling menuWillOpen() should have notified our popupshowing listener");
+
+ ok(!itemActivated, "our dynamically-added menuitem shouldn't have been activated yet");
+ menuSNM.activateNativeMenuItemAt("0");
+ ok(itemActivated, "the new menu item should have been activated now");
+ } catch (ex) {
+ ok(false, "dynamic menu test failed: " + ex);
+ }
+ }
+
+ function onLoad() {
+ var _delayedOnLoad = function() {
+ try {
+
+ var menuNode = document.getElementById("standalonenativemenu");
+ var menu = createStandaloneNativeMenu(menuNode);
+
+ // First let's run the base menu tests.
+ ok(runBaseMenuTests(menu), "base tests #1");
+
+ // Set up some nodes that we'll use.
+ var newMenu0 = createXULMenu("NewMenu0");
+ var newMenu1 = createXULMenu("NewMenu1");
+ var newMenuPopup0 = createXULMenuPopup();
+ var newMenuPopup1 = createXULMenuPopup();
+ var newMenuItem0 = createXULMenuItem("NewMenuItem0", "cmd_NewItem0");
+ var newMenuItem1 = createXULMenuItem("NewMenuItem1", "cmd_NewItem1");
+ var newMenuItem2 = createXULMenuItem("NewMenuItem2", "cmd_NewItem2");
+ var newMenuItem3 = createXULMenuItem("NewMenuItem3", "cmd_NewItem3");
+ var newMenuItem4 = createXULMenuItem("NewMenuItem4", "cmd_NewItem4");
+ var newMenuItem5 = createXULMenuItem("NewMenuItem5", "cmd_NewItem5");
+
+ // Create another submenu with hierarchy via DOM manipulation.
+ // ******************
+ // * Foo * NewMenu0 * <- Menu bar
+ // ******************
+ // ****************
+ // * NewMenuItem0 * <- NewMenu0 submenu
+ // ****************
+ // * NewMenuItem1 *
+ // ****************
+ // * NewMenuItem2 *
+ // *******************************
+ // * NewMenu1 > * NewMenuItem3 * <- NewMenu1 submenu
+ // *******************************
+ // * NewMenuItem4 *
+ // ****************
+ // * NewMenuItem5 *
+ // ****************
+ newMenu0.appendChild(newMenuPopup0);
+ newMenuPopup0.appendChild(newMenuItem0);
+ newMenuPopup0.appendChild(newMenuItem1);
+ newMenuPopup0.appendChild(newMenuItem2);
+ newMenuPopup0.appendChild(newMenu1);
+ newMenu1.appendChild(newMenuPopup1);
+ newMenuPopup1.appendChild(newMenuItem3);
+ newMenuPopup1.appendChild(newMenuItem4);
+ newMenuPopup1.appendChild(newMenuItem5);
+ //XXX - we have to append the menu to the top-level of the menu bar
+ // only after constructing it. If we append before construction, it is
+ // invalid because it has no children and we don't validate it if we add
+ // children later.
+ menuNode.appendChild(newMenu0);
+ menu.forceUpdateNativeMenuAt("1|3");
+ // Run basic tests again.
+ ok(runBaseMenuTests(menu), "base tests #2");
+
+ // Error strings.
+ var sa = "Command handler(s) should have activated";
+ var sna = "Command handler(s) should not have activated";
+
+ // Test middle items.
+ ok(testItem(menu, "1|1", "cmd_NewItem1"), "#1:" + sa);
+ ok(testItem(menu, "1|3|1", "cmd_NewItem4"), "#2:" + sa);
+
+ // Hide newMenu0.
+ newMenu0.setAttribute("hidden", "true");
+ ok(runBaseMenuTests(menu), "base tests #3: " + sa); // the base menu should still be unhidden
+ ok(!testItem(menu, "1|0", ""), "#3:" + sna);
+ ok(!testItem(menu, "1|1", ""), "#4:" + sna);
+ ok(!testItem(menu, "1|2", ""), "#5:" + sna);
+ ok(!testItem(menu, "1|3|0", ""), "#6:" + sna);
+ ok(!testItem(menu, "1|3|1", ""), "#7:" + sna);
+ ok(!testItem(menu, "1|3|2", ""), "#8:" + sna);
+
+ // Show newMenu0.
+ newMenu0.setAttribute("hidden", "false");
+ menu.forceUpdateNativeMenuAt("1|3");
+ ok(runBaseMenuTests(menu), "base tests #4:" + sa);
+ ok(testItem(menu, "1|0", "cmd_NewItem0"), "#9:" + sa);
+ ok(testItem(menu, "1|1", "cmd_NewItem1"), "#10:" + sa);
+ ok(testItem(menu, "1|2", "cmd_NewItem2"), "#11:" + sa);
+ ok(testItem(menu, "1|3|0", "cmd_NewItem3"), "#12:" + sa);
+ ok(testItem(menu, "1|3|1", "cmd_NewItem4"), "#13:" + sa);
+ ok(testItem(menu, "1|3|2", "cmd_NewItem5"), "#14:" + sa);
+
+ // Hide items.
+ newMenuItem1.setAttribute("hidden", "true");
+ newMenuItem4.setAttribute("hidden", "true");
+ menu.forceUpdateNativeMenuAt("1|2");
+ ok(runBaseMenuTests(menu), "base tests #5:" + sa);
+ ok(testItem(menu, "1|0", "cmd_NewItem0"), "#15:" + sa);
+ ok(testItem(menu, "1|1", "cmd_NewItem2"), "#16:" + sa);
+ ok(!testItem(menu, "1|2", ""), "#17:" + sna);
+ ok(testItem(menu, "1|2|0", "cmd_NewItem3"), "#18:" + sa);
+ ok(testItem(menu, "1|2|1", "cmd_NewItem5"), "#19:" + sa);
+ ok(!testItem(menu, "1|2|2", ""), "#20:" + sna);
+
+ // Show items.
+ newMenuItem1.setAttribute("hidden", "false");
+ newMenuItem4.setAttribute("hidden", "false");
+ //forceUpdateNativeMenuAt("1|3");
+ ok(runBaseMenuTests(menu), "base tests #6:" + sa);
+ ok(testItem(menu, "1|0", "cmd_NewItem0"), "#21:" + sa);
+ ok(testItem(menu, "1|1", "cmd_NewItem1"), "#22:" + sa);
+ ok(testItem(menu, "1|2", "cmd_NewItem2"), "#23:" + sa);
+ ok(testItem(menu, "1|3|0", "cmd_NewItem3"), "#24:" + sa);
+ ok(testItem(menu, "1|3|1", "cmd_NewItem4"), "#25:" + sa);
+ ok(testItem(menu, "1|3|2", "cmd_NewItem5"), "#26:" + sa);
+
+ // At this point in the tests the state of the menus has been returned
+ // to the originally diagramed state.
+
+ // Remove menu.
+ menuNode.removeChild(newMenu0);
+ ok(runBaseMenuTests(menu), "base tests #7:" + sa);
+ ok(!testItem(menu, "1|0", ""), "#27:" + sna);
+ ok(!testItem(menu, "1|1", ""), "#28:" + sna);
+ ok(!testItem(menu, "1|2", ""), "#29:" + sna);
+ ok(!testItem(menu, "1|3|0", ""), "#30:" + sna);
+ ok(!testItem(menu, "1|3|1", ""), "#31:" + sna);
+ ok(!testItem(menu, "1|3|2", ""), "#32:" + sna);
+ // return state to original diagramed state
+ menuNode.appendChild(newMenu0);
+
+ // Test for bug 447042, make sure that adding a menu node with no children
+ // to the menu bar and then adding another menu node with children works.
+ // Menus with no children don't get their native menu items shown and that
+ // caused internal arrays to get out of sync and an append crashed.
+ var tmpMenu0 = createXULMenu("tmpMenu0");
+ menuNode.removeChild(newMenu0);
+ menuNode.appendChild(tmpMenu0);
+ menuNode.appendChild(newMenu0);
+ menu.forceUpdateNativeMenuAt("1|3");
+ //todo(runBaseMenuTests(menu), "base tests #8");
+ todo(testItem(menu, "1|0", "cmd_NewItem0"), "#33:" +sa);
+ todo(testItem(menu, "1|1", "cmd_NewItem1"), "#34:" +sa);
+ todo(testItem(menu, "1|2", "cmd_NewItem2"), "#35:" +sa);
+ todo(testItem(menu, "1|3|0", "cmd_NewItem3"), "#36:" +sa);
+ todo(testItem(menu, "1|3|1", "cmd_NewItem4"), "#37:" +sa);
+ todo(testItem(menu, "1|3|2", "cmd_NewItem5"), "#38:" +sa);
+ // return state to original diagramed state
+ menuNode.removeChild(tmpMenu0);
+
+ // This test is basically a crash test for bug 433858.
+ newMenuItem1.setAttribute("hidden", "true");
+ newMenuItem2.setAttribute("hidden", "true");
+ newMenu1.setAttribute("hidden", "true");
+ menu.forceUpdateNativeMenuAt("1");
+ newMenuItem1.setAttribute("hidden", "false");
+ newMenuItem2.setAttribute("hidden", "false");
+ newMenu1.setAttribute("hidden", "false");
+ menu.forceUpdateNativeMenuAt("1");
+
+ // Run tests where the menu nodes are not in the document's node tree.
+ runDetachedMenuTests(false);
+ runDetachedMenuTests(true);
+
+ } catch (e) {
+ ok(false, "Caught an exception: " + e);
+ } finally {
+ onTestsFinished();
+ }
+ }
+
+ setTimeout(_delayedOnLoad, 1000);
+ }
+
+ ]]></script>
+</window>
diff --git a/widget/tests/system_font_changes.xhtml b/widget/tests/system_font_changes.xhtml
new file mode 100644
index 0000000000..1cef650015
--- /dev/null
+++ b/widget/tests/system_font_changes.xhtml
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="system_font_changes_window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="300"
+ height="300"
+ onload="start();">
+
+<span id="target" style="font:menu">Hello world</span>
+
+<script type="application/javascript"><![CDATA[
+ function is(condition, message) {
+ window.arguments[0].SimpleTest.is(condition, message);
+ }
+ function registerCleanupFunction(func) {
+ window.arguments[0].SimpleTest.registerCleanupFunction(func);
+ }
+
+ async function waitForFrame() {
+ return new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ }
+
+ let windowUtils = window.windowUtils;
+ async function start() {
+ await waitForFrame();
+
+ const originalSystemFont = windowUtils.systemFont;
+ registerCleanupFunction(() => {
+ windowUtils.systemFont = originalSystemFont;
+ });
+
+ windowUtils.systemFont = 'Sans 11';
+ is(windowUtils.systemFont, 'Sans 11');
+
+ // Wait for two frames for the safety since the notification for system
+ // font changes is asynchronously processed.
+ await waitForFrame();
+ await waitForFrame();
+
+ const target = document.getElementById('target');
+ is(getComputedStyle(target).fontFamily, 'Sans');
+
+ windowUtils.systemFont = 'Serif 11';
+ is(windowUtils.systemFont, 'Serif 11');
+
+ await waitForFrame();
+ await waitForFrame();
+
+ is(getComputedStyle(target).fontFamily, 'Serif');
+
+ window.close();
+ window.arguments[0].SimpleTest.finish();
+ }
+]]></script>
+</window>
diff --git a/widget/tests/taskbar_previews.xhtml b/widget/tests/taskbar_previews.xhtml
new file mode 100644
index 0000000000..c3f2f3890c
--- /dev/null
+++ b/widget/tests/taskbar_previews.xhtml
@@ -0,0 +1,118 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Taskbar Previews Test"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="loaded();">
+
+ <title>Previews - yeah!</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ let taskbar = Cc["@mozilla.org/windows-taskbar;1"].getService(Ci.nsIWinTaskbar);
+
+ function IsWin7OrHigher() {
+ try {
+ var ver = parseFloat(Services.sysinfo.getProperty("version"));
+ if (ver >= 6.1)
+ return true;
+ } catch (ex) { }
+ return false;
+ }
+ isnot(taskbar, null, "Taskbar service is defined");
+ is(taskbar.available, IsWin7OrHigher(), "Expected availability of taskbar");
+
+ SimpleTest.waitForExplicitFinish();
+
+ function stdPreviewSuite(p) {
+ p.visible = !p.visible;
+ p.visible = !p.visible;
+ p.visible = true;
+ p.invalidate();
+ p.visible = false;
+ }
+
+ function loaded()
+ {
+ if (!taskbar.available)
+ SimpleTest.finish();
+ let controller = {
+ width: 400,
+ height: 400,
+ thumbnailAspectRatio: 1.0,
+ get wrappedJSObject() { return this; }
+ }
+ // HACK from mconnor:
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let docShell = win.gBrowser.docShell;
+
+ let winPreview = taskbar.getTaskbarWindowPreview(docShell);
+ isnot(winPreview, null, "Window preview is not null");
+ winPreview.controller = controller;
+ let button = winPreview.getButton(0);
+ isnot(button, null, "Could get button at valid index");
+ try {
+ winPreview.getButton(-1);
+ ok(false, "Got button at negative index");
+ } catch (ex) {}
+ try {
+ winPreview.getButton(Ci.nsITaskbarWindowPreview.NUM_TOOLBAR_BUTTONS);
+ ok(false, "Got button at index that is too large");
+ } catch (ex) {}
+ button.image = null;
+ stdPreviewSuite(winPreview);
+ // Let's not perma-hide this window from the taskbar
+ winPreview.visible = true;
+
+ let tabP = taskbar.createTaskbarTabPreview(docShell, controller);
+ isnot(tabP, null, "Tab preview is not null");
+ is(tabP.controller.wrappedJSObject, controller, "Controllers match");
+ is(tabP.icon, null, "Default icon is null (windows default)");
+ tabP.icon = null;
+ tabP.move(null);
+ try {
+ tabP.move(tabP);
+ ok(false, "Moved a preview next to itself!");
+ } catch (ex) {}
+ stdPreviewSuite(tabP);
+
+ let tabP2 = taskbar.createTaskbarTabPreview(docShell, controller);
+ tabP.visible = true;
+ tabP2.visible = true;
+
+ isnot(tabP2, null, "2nd Tab preview is not null");
+ isnot(tabP,tabP2, "Tab previews are different");
+ tabP.active = true;
+ ok(tabP.active && !tabP2.active, "Only one tab is active (part 1)");
+ tabP2.active = true;
+ ok(!tabP.active && tabP2.active, "Only one tab is active (part 2)");
+ tabP.active = true;
+ ok(tabP.active && !tabP2.active, "Only one tab is active (part 3)");
+ tabP.active = false;
+ ok(!tabP.active && !tabP2.active, "Neither tab is active");
+ is(winPreview.active, false, "Window preview is not active");
+ tabP.active = true;
+ winPreview.active = true;
+ ok(winPreview.active && !tabP.active, "Tab preview takes activation from window");
+ tabP.active = true;
+ ok(tabP.active && !winPreview.active, "Tab preview takes activation from window");
+
+ tabP.visible = false;
+ tabP2.visible = false;
+
+ SimpleTest.finish();
+ }
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ </body>
+
+</window>
diff --git a/widget/tests/test_AltGr_key_events_in_web_content_on_windows.html b/widget/tests/test_AltGr_key_events_in_web_content_on_windows.html
new file mode 100644
index 0000000000..ff89f8fdad
--- /dev/null
+++ b/widget/tests/test_AltGr_key_events_in_web_content_on_windows.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Testing if AltGr keydown and keyup events are fired in web content on Windows</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/NativeKeyCodes.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="display">
+ <input id="input">
+</div>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function checkEvent(aEvent, aExpectedEvents, aDescription) {
+ if (!aExpectedEvents.length) {
+ ok(false, `${aDescription}: no more expected events ` +
+ `(type: ${aEvent.type}, code: ${aEvent.code}, key: ${aEvent.key}, keyCode: ${aEvent.keyCode}`);
+ }
+ let expectedEvent = aExpectedEvents.shift();
+ for (let property in expectedEvent) {
+ is(aEvent[property], expectedEvent[property], `${aDescription}: ${property}`);
+ }
+}
+
+async function runAltGrKeyTest() {
+ return new Promise(resolve => {
+ let target = document.getElementById("input");
+ target.focus();
+
+ let events = [
+ { type: "keydown", code: "ControlLeft", key: "Control", keyCode: KeyboardEvent.DOM_VK_CONTROL },
+ { type: "keydown", code: "AltRight", key: "AltGraph", keyCode: KeyboardEvent.DOM_VK_ALT },
+ { type: "keyup", code: "ControlLeft", key: "Control", keyCode: KeyboardEvent.DOM_VK_CONTROL },
+ { type: "keyup", code: "AltRight", key: "AltGraph", keyCode: KeyboardEvent.DOM_VK_ALT },
+ ];
+ function handleEvent(aEvent) {
+ checkEvent(aEvent, events, "runAltGrKeyTest");
+ if (aEvent.type === "keyup" && aEvent.code === "AltRight") {
+ is(events.length, 0, "runAltGrKeyTest: all expected events are fired");
+ SimpleTest.executeSoon(() => {
+ target.removeEventListener("keydown", handleEvent);
+ target.removeEventListener("keypress", handleEvent);
+ target.removeEventListener("keyup", handleEvent);
+ resolve();
+ });
+ }
+ }
+ target.addEventListener("keydown", handleEvent);
+ target.addEventListener("keypress", handleEvent);
+ target.addEventListener("keyup", handleEvent);
+
+ synthesizeNativeKey(KEYBOARD_LAYOUT_SPANISH, WIN_VK_RMENU, {},
+ "", "");
+ });
+}
+
+async function runEmulatingAltGrKeyTest() {
+ return new Promise(resolve => {
+ let target = document.getElementById("input");
+ target.focus();
+
+ let events = [
+ { type: "keydown", code: "ControlLeft", key: "Control", keyCode: KeyboardEvent.DOM_VK_CONTROL },
+ { type: "keydown", code: "AltLeft", key: "Alt", keyCode: KeyboardEvent.DOM_VK_ALT },
+ { type: "keyup", code: "AltLeft", key: "Alt", keyCode: KeyboardEvent.DOM_VK_ALT },
+ { type: "keyup", code: "ControlLeft", key: "Control", keyCode: KeyboardEvent.DOM_VK_CONTROL },
+ ];
+ function handleEvent(aEvent) {
+ checkEvent(aEvent, events, "runEmulatingAltGrKeyTest");
+ if (aEvent.type === "keyup" && aEvent.code === "ControlLeft") {
+ is(events.length, 0, "runAltGrKeyTest: all expected events are fired");
+ SimpleTest.executeSoon(() => {
+ target.removeEventListener("keydown", handleEvent);
+ target.removeEventListener("keypress", handleEvent);
+ target.removeEventListener("keyup", handleEvent);
+ resolve();
+ });
+ }
+ }
+ target.addEventListener("keydown", handleEvent);
+ target.addEventListener("keypress", handleEvent);
+ target.addEventListener("keyup", handleEvent);
+
+ synthesizeNativeKey(KEYBOARD_LAYOUT_SPANISH, WIN_VK_LMENU, { ctrlKey: true },
+ "", "");
+ });
+}
+
+async function runTests() {
+ await runAltGrKeyTest();
+ await runEmulatingAltGrKeyTest();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/widget/tests/test_actionhint.html b/widget/tests/test_actionhint.html
new file mode 100644
index 0000000000..d4a2285aab
--- /dev/null
+++ b/widget/tests/test_actionhint.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Tests for action hint that is used by software keyboard</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/SpecialPowers.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<div>
+<form><input type="text" id="a1"><input type="text" id="a2"><input type="submit"></form>
+<form><input type="search" id="b1"><input type="submit"></form>
+<form><input type="text" id="c1"></form>
+<form><input type="text" id="d1"><textarea></textarea><input type="submit"></form>
+<form><input type="text" id="e1"><input type="number"><input type="submit"></form>
+<form><input type="text" id="f1"><input type="date"><input type="submit"></form>
+<form><input type="text" id="g1"><input type="radio"><input type="submit"></form>
+<form><input type="text" id="h1"><input type="text" readonly><input type="submit"></form>
+<form><input type="text" id="i1"><input type="text" disabled><input type="submit"></form>
+<input type="text" id="j1"><input type="text"><input type="button">
+<form><input type="text" id="k1"><a href="#foo">foo</a><input type="text"><input type="submit"></form>
+<form>
+ <input id="l1" enterkeyhint="enter">
+ <input id="l2" enterkeyhint="DONE">
+ <input id="l3" enterkeyhint="go">
+ <input id="l4" enterkeyhint="Next">
+ <input id="l5" enterkeyhint="Previous">
+ <input id="l6" enterkeyhint="search">
+ <textarea id="l7" enterkeyhint="send"></textarea>
+ <input id="l8" enterkeyhint="NONE">
+</form>
+</div>
+<pre id="test">
+<script class=testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(async () => {
+ const tests = [
+ { id: "a1", hint: "maybenext", desc: "next element is type=text" },
+ { id: "a2", hint: "go", desc: "next element is type=submit" },
+ { id: "b1", hint: "search", desc: "current is type=search" },
+ { id: "c1", hint: "go", desc: "only this element" },
+ { id: "d1", hint: "maybenext", desc: "next element is textarea" },
+ { id: "e1", hint: "maybenext", desc: "next element is type=number" },
+ { id: "h1", hint: "go", desc: "next element is readonly" },
+ // XXX Feel free to change this result if you get some bugs reports
+ { id: "i1", hint: "go", desc: "next element is disabled" },
+ { id: "j1", hint: "", desc: "no form element" },
+ { id: "l1", hint: "enter", desc: "enterkeyhint=enter" },
+ { id: "l2", hint: "done", desc: "enterkeyhint=DONE" },
+ { id: "l3", hint: "go", desc: "enterkeyhint=go" },
+ { id: "l4", hint: "next", desc: "enterkeyhint=Next" },
+ { id: "l5", hint: "previous", desc: "enterkeyhint=Previous" },
+ { id: "l6", hint: "search", desc: "enterkeyhint=search" },
+ { id: "l7", hint: "send", desc: "enterkeyhint=send" },
+ // Since enterkeyhint is invalid, we infer action hint. So feel free to change this.
+ { id: "l8", hint: "go", desc: "enterkeyhint is invalid" },
+ ];
+
+ const todo_tests = [
+ { id: "f1", hint: "maybenext", desc: "next element is type=date" },
+ { id: "k1", hint: "", desc: "next is anchor link" },
+ ];
+
+ await SpecialPowers.setBoolPref("dom.forms.enterkeyhint", true);
+
+ for (let test of tests) {
+ document.getElementById(test.id).focus();
+ is(SpecialPowers.DOMWindowUtils.focusedActionHint, test.hint, test.desc);
+ }
+
+ for (let test of todo_tests) {
+ document.getElementById(test.id).focus();
+ todo_is(SpecialPowers.DOMWindowUtils.focusedActionHint, test.hint, test.desc);
+ }
+
+ SpecialPowers.clearUserPref("dom.forms.enterkeyhint");
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/widget/tests/test_alwaysontop_focus.xhtml b/widget/tests/test_alwaysontop_focus.xhtml
new file mode 100644
index 0000000000..bf0a61b69d
--- /dev/null
+++ b/widget/tests/test_alwaysontop_focus.xhtml
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta charset="utf-8" />
+ <title>Test that alwaysontop windows do not pull focus when opened.</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script><![CDATA[
+ add_task(async function testAlwaysOnTop() {
+ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ let topWin = window.docShell.rootTreeItem.domWindow;
+ await SimpleTest.promiseFocus(topWin);
+ is(Services.focus.activeWindow, topWin, "Top level window is focused");
+
+ let newWin = Services.ww.openWindow(
+ null,
+ "about:blank",
+ null,
+ "chrome,alwaysontop,width=300,height=300",
+ null
+ );
+ await new Promise(resolve => {
+ newWin.addEventListener("load", resolve, { once: true });
+ });
+
+ // Wait one tick of the event loop to give the window a chance to focus.
+ await new Promise(resolve => { SimpleTest.executeSoon(resolve); });
+
+ is(Services.focus.activeWindow, topWin, "Top level window is still focused");
+ newWin.close();
+ });
+ ]]></script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/widget/tests/test_assign_event_data.html b/widget/tests/test_assign_event_data.html
new file mode 100644
index 0000000000..709e0e647e
--- /dev/null
+++ b/widget/tests/test_assign_event_data.html
@@ -0,0 +1,759 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Testing ns*Event::Assign*EventData()</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/NativeKeyCodes.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style>
+ #a {
+ background-color: transparent;
+ transition: background-color 0.1s linear;
+ }
+ #a:focus {
+ background-color: red;
+ }
+ .slidin {
+ border: green 1px solid;
+ width: 10px;
+ height: 10px;
+ animation-name: slidein;
+ animation-duration: 1s;
+ }
+ @keyframes slidein {
+ from {
+ margin-left: 100%;
+ }
+ to {
+ margin-left: 0;
+ }
+ }
+ #pointer-target {
+ border: 1px dashed red;
+ background: yellow;
+ margin: 0px 10px;
+ padding: 0px 10px;
+ }
+ #scrollable-div {
+ background: green;
+ overflow: auto;
+ width: 30px;
+ height: 30px;
+ }
+ #scrolled-div {
+ background: magenta;
+ width: 10px;
+ height: 10px;
+ }
+ #form {
+ background: silver;
+ padding: 0px 10px;
+ }
+ #animated-div {
+ background: cyan;
+ padding: 0px 10px;
+ }
+ </style>
+</head>
+<body>
+<div id="display">
+ <input id="input-text">
+ <button id="button">button</button>
+ <a id="a" href="about:blank">hyper link</a>
+ <span id="pointer-target">span</span>
+ <div id="scrollable-div"><div id="scrolled-div"></div></div>
+ <form id="form">form</form>
+ <div id="animated-div">&nbsp;</div>
+</div>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.expectAssertions(0, 34);
+
+const kStrictKeyPressEvents =
+ SpecialPowers.getBoolPref("dom.keyboardevent.keypress.dispatch_non_printable_keys_only_system_group_in_content");
+
+const kIsMac = (navigator.platform.indexOf("Mac") == 0);
+const kIsWin = (navigator.platform.indexOf("Win") == 0);
+
+var gDescription = "";
+var gEvent = null;
+var gCopiedEvent = [];
+var gCallback = null;
+var gCallPreventDefault = false;
+
+function onEvent(aEvent) {
+ if (gCallPreventDefault) {
+ aEvent.preventDefault();
+ }
+ ok(gEvent === null, gDescription + `: We should receive only one event to check per test: already got: ${gEvent ? gEvent.type : "null"}, got: ${aEvent.type}`);
+ gEvent = aEvent;
+ for (var attr in aEvent) {
+ if (!attr.match(/^[A-Z0-9_]+$/) && // ignore const attributes
+ attr != "multipleActionsPrevented" && // multipleActionsPrevented isn't defined in any DOM event specs.
+ typeof(aEvent[attr]) != "function") {
+ gCopiedEvent.push({ name: attr, value: aEvent[attr]});
+ }
+ }
+ setTimeout(gCallback, 0);
+}
+
+function observeKeyUpOnContent(aKeyCode, aCallback) {
+ document.addEventListener("keyup", function keyUp(ev) {
+ if (ev.keyCode == aKeyCode) {
+ document.removeEventListener("keyup", keyUp);
+ SimpleTest.executeSoon(aCallback);
+ }
+ });
+}
+
+const kTests = [
+ { description: "InternalScrollPortEvent (overflow, vertical)",
+ targetID: "scrollable-div", eventType: "overflow",
+ dispatchEvent() {
+ document.getElementById("scrolled-div").style.height = "500px";
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalScrollPortEvent (overflow, horizontal)",
+ targetID: "scrollable-div", eventType: "overflow",
+ dispatchEvent() {
+ document.getElementById("scrolled-div").style.width = "500px";
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalScrollAreaEvent (MozScrolledAreaChanged, spreading)",
+ target() { return document; }, eventType: "MozScrolledAreaChanged",
+ dispatchEvent() {
+ document.getElementById("scrollable-div").style.width = "50000px";
+ document.getElementById("scrollable-div").style.height = "50000px";
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalScrollAreaEvent (MozScrolledAreaChanged, shrinking)",
+ target() { return document; }, eventType: "MozScrolledAreaChanged",
+ dispatchEvent() {
+ document.getElementById("scrollable-div").style.width = "30px";
+ document.getElementById("scrollable-div").style.height = "30px";
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetKeyboardEvent (keydown of 'a' key without modifiers)",
+ targetID: "input-text", eventType: "keydown",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_A : MAC_VK_ANSI_A,
+ {}, "a", "a");
+ observeKeyUpOnContent(KeyboardEvent.DOM_VK_A, runNextTest);
+ return true;
+ },
+ canRun() {
+ return (kIsMac || kIsWin);
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetKeyboardEvent (keyup of 'a' key without modifiers)",
+ targetID: "input-text", eventType: "keydown",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_A : MAC_VK_ANSI_A,
+ {}, "a", "a");
+ observeKeyUpOnContent(KeyboardEvent.DOM_VK_A, runNextTest);
+ return true;
+ },
+ canRun() {
+ return (kIsMac || kIsWin);
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetKeyboardEvent (keypress of 'b' key with Shift)",
+ targetID: "input-text", eventType: "keypress",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_B : MAC_VK_ANSI_B,
+ { shiftKey: true }, "B", "B");
+
+ // On Windows, synthesizeNativeKey will also fire keyup for shiftKey.
+ // We have to wait for it to prevent the key event break the next test case.
+ let waitKeyCode = _EU_isWin(window) ? KeyboardEvent.DOM_VK_SHIFT :
+ KeyboardEvent.DOM_VK_B;
+ observeKeyUpOnContent(waitKeyCode, runNextTest);
+ return true;
+ },
+ canRun() {
+ return (kIsMac || kIsWin);
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetKeyboardEvent (keypress of 'c' key with Accel)",
+ targetID: "input-text", eventType: "keypress",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_C : MAC_VK_ANSI_C,
+ { accelKey: true }, kIsWin ? "\u0003" : "c", "c");
+
+ // On Windows, synthesizeNativeKey will also fire keyup for accelKey
+ // (control key on Windows). We have to wait for it to prevent the key
+ // event break the next test case.
+ let waitKeyCode = _EU_isWin(window) ? KeyboardEvent.DOM_VK_CONTROL :
+ KeyboardEvent.DOM_VK_C;
+ observeKeyUpOnContent(waitKeyCode, runNextTest);
+ return true;
+ },
+ canRun() {
+ return !kStrictKeyPressEvents && (kIsMac || kIsWin);
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetKeyboardEvent (keyup during composition)",
+ targetID: "input-text", eventType: "keyup",
+ dispatchEvent() {
+ setAndObserveCompositionPref(true, () => {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeCompositionChange({ "composition":
+ { "string": "\u306D",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ ],
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ synthesizeComposition({ type: "compositioncommitasis", key: {} });
+ setAndObserveCompositionPref(null, runNextTest);
+ });
+ return true;
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetKeyboardEvent (keydown during composition)",
+ targetID: "input-text", eventType: "keydown",
+ dispatchEvent() {
+ setAndObserveCompositionPref(true, () => {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeCompositionChange({ "composition":
+ { "string": "\u306D",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ ],
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": {},
+ });
+ synthesizeComposition({ type: "compositioncommitasis",
+ key: { key: "KEY_Enter" } });
+ setAndObserveCompositionPref(null, runNextTest);
+ });
+ return true;
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseEvent (mousedown of left button without modifier)",
+ targetID: "button", eventType: "mousedown",
+ dispatchEvent() {
+ synthesizeMouseAtCenter(document.getElementById(this.targetID),
+ { button: 0 });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetMouseEvent (click of middle button with Shift)",
+ targetID: "button", eventType: "auxclick",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeMouseAtCenter(document.getElementById(this.targetID),
+ { button: 1, shiftKey: true, pressure: 0.5 });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetMouseEvent (mouseup of right button with Alt)",
+ targetID: "button", eventType: "mouseup",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeMouseAtCenter(document.getElementById(this.targetID),
+ { button: 2, altKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetDragEvent",
+ targetID: "input-text", eventType: "dragstart",
+ dispatchEvent() {
+
+ },
+ canRun() {
+ todo(false, "WidgetDragEvent isn't tested");
+ return false;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetCompositionEvent (compositionupdate)",
+ targetID: "input-text", eventType: "compositionupdate",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeComposition({ type: "compositioncommit", data: "\u30E9\u30FC\u30E1\u30F3", key: { key: "KEY_Enter" } });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "InternalEditorInputEvent (input at key input)",
+ targetID: "input-text", eventType: "input",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_B : MAC_VK_ANSI_B,
+ { shiftKey: true }, "B", "B");
+ observeKeyUpOnContent(KeyboardEvent.DOM_VK_B, runNextTest);
+ return true;
+ },
+ canRun() {
+ return (kIsMac || kIsWin);
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalEditorInputEvent (input at composing)",
+ targetID: "input-text", eventType: "input",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeCompositionChange({ "composition":
+ { "string": "\u30E9\u30FC\u30E1\u30F3",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ ],
+ },
+ "caret": { "start": 4, "length": 0 },
+ "key": { key: "y" },
+ });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "InternalEditorInputEvent (input at committing)",
+ targetID: "input-text", eventType: "input",
+ dispatchEvent() {
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseScrollEvent (DOMMouseScroll, vertical)",
+ targetID: "input-text", eventType: "DOMMouseScroll",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 3, 4,
+ { deltaY: 30, lineOrPageDeltaY: 2 });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseScrollEvent (DOMMouseScroll, horizontal)",
+ targetID: "input-text", eventType: "DOMMouseScroll",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 4, 5,
+ { deltaX: 30, lineOrPageDeltaX: 2, shiftKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseScrollEvent (MozMousePixelScroll, vertical)",
+ targetID: "input-text", eventType: "MozMousePixelScroll",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 3, 4,
+ { deltaY: 20, lineOrPageDeltaY: 1, altKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseScrollEvent (MozMousePixelScroll, horizontal)",
+ targetID: "input-text", eventType: "MozMousePixelScroll",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 4, 5,
+ { deltaX: 20, lineOrPageDeltaX: 1, ctrlKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetWheelEvent (wheel, vertical)",
+ targetID: "input-text", eventType: "wheel",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 3, 4,
+ { deltaY: 20, lineOrPageDeltaY: 1, altKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetWheelEvent (wheel, horizontal)",
+ targetID: "input-text", eventType: "wheel",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 4, 5,
+ { deltaX: 20, lineOrPageDeltaX: 1, ctrlKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetWheelEvent (wheel, both)",
+ targetID: "input-text", eventType: "wheel",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 4, 5,
+ { deltaX: 20, deltaY: 10,
+ lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetTouchEvent (touchstart)",
+ target() { return document; }, eventType: "touchstart",
+ dispatchEvent() {
+ synthesizeTouchAtPoint(1, 2, { id: 10, rx: 4, ry: 3, angle: 0, force: 1, shiftKey: true});
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetTouchEvent (touchend)",
+ target() { return document; }, eventType: "touchend",
+ dispatchEvent() {
+ synthesizeTouchAtPoint(4, 6, { id: 5, rx: 1, ry: 2, angle: 0.5, force: 0.8, ctrlKey: true});
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "InternalFormEvent (reset)",
+ targetID: "form", eventType: "reset",
+ dispatchEvent() {
+ document.getElementById("form").reset();
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetCommandEvent",
+ targetID: "input-text", eventType: "",
+ dispatchEvent() {
+
+ },
+ canRun() {
+ todo(false, "WidgetCommandEvent isn't tested");
+ return false;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalClipboardEvent (copy)",
+ targetID: "input-text", eventType: "copy",
+ dispatchEvent() {
+ document.getElementById("input-text").value = "go to clipboard!";
+ document.getElementById("input-text").focus();
+ document.getElementById("input-text").select();
+ synthesizeKey("c", { accelKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "InternalUIEvent (DOMActivate)",
+ targetID: "button", eventType: "DOMActivate",
+ dispatchEvent() {
+ synthesizeMouseAtCenter(document.getElementById(this.targetID),
+ { button: 0, shiftKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalFocusEvent (focus)",
+ targetID: "input-text", eventType: "focus",
+ dispatchEvent() {
+ document.getElementById(this.targetID).focus();
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalFocusEvent (blur)",
+ targetID: "input-text", eventType: "blur",
+ dispatchEvent() {
+ document.getElementById(this.targetID).blur();
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetSimpleGestureEvent",
+ targetID: "", eventType: "",
+ dispatchEvent() {
+
+ },
+ canRun() {
+ // Simple gesture event may be handled before it comes content.
+ // So, we cannot test it in this test.
+ todo(false, "WidgetSimpleGestureEvent isn't tested");
+ return false;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalTransitionEvent (transitionend)",
+ targetID: "a", eventType: "transitionend",
+ dispatchEvent() {
+ document.getElementById(this.targetID).focus();
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalAnimationEvent (animationend)",
+ targetID: "animated-div", eventType: "animationend",
+ dispatchEvent() {
+ document.getElementById(this.targetID).className = "slidin";
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalMutationEvent (DOMAttrModified)",
+ targetID: "animated-div", eventType: "DOMAttrModified",
+ dispatchEvent() {
+ document.getElementById(this.targetID).setAttribute("x-data", "foo");
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalMutationEvent (DOMNodeInserted)",
+ targetID: "animated-div", eventType: "DOMNodeInserted",
+ dispatchEvent() {
+ var div = document.createElement("div");
+ div.id = "inserted-div";
+ document.getElementById("animated-div").appendChild(div);
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalMutationEvent (DOMNodeRemoved)",
+ targetID: "animated-div", eventType: "DOMNodeRemoved",
+ dispatchEvent() {
+ document.getElementById("animated-div").removeChild(document.getElementById("inserted-div"));
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "PointerEvent (pointerdown)",
+ targetID: "pointer-target", eventType: "mousedown",
+ dispatchEvent() {
+ var elem = document.getElementById(this.targetID);
+ var rect = elem.getBoundingClientRect();
+ synthesizeMouse(elem, rect.width / 2, rect.height / 2,
+ { type: this.eventType, button: 1, clickCount: 1, inputSource: 2, pressure: 0.25, isPrimary: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "PointerEvent (pointerup)",
+ targetID: "pointer-target", eventType: "mouseup",
+ dispatchEvent() {
+ var elem = document.getElementById(this.targetID);
+ var rect = elem.getBoundingClientRect();
+ synthesizeMouse(elem, rect.width / 2, rect.height / 2,
+ { type: this.eventType, button: -1, ctrlKey: true, shiftKey: true, altKey: true, isSynthesized: false });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+];
+
+/**
+ * Sets or clears dom.keyboardevent.dispatch_during_composition and calls the
+ * given callback when the change is observed.
+ *
+ * @param aValue
+ * Pass null to clear the pref. Otherwise pass a bool.
+ * @param aCallback
+ * Called when the pref change is observed.
+ */
+function setAndObserveCompositionPref(aValue, aCallback) {
+ let pref = "dom.keyboardevent.dispatch_during_composition";
+ if (aValue === null) {
+ SpecialPowers.pushPrefEnv({"clear": [[pref]]}, aCallback);
+ } else {
+ SpecialPowers.pushPrefEnv({"set": [[pref, aValue]]}, aCallback);
+ }
+}
+
+function doTest(aTest) {
+ if (!aTest.canRun()) {
+ SimpleTest.executeSoon(runNextTest);
+ return;
+ }
+ gEvent = null;
+ gCopiedEvent = [];
+ gDescription = aTest.description + " (gCallPreventDefault=" + gCallPreventDefault + ")";
+ var target = aTest.target ? aTest.target() : document.getElementById(aTest.targetID);
+ target.addEventListener(aTest.eventType, onEvent, true);
+ gCallback = function() {
+ target.removeEventListener(aTest.eventType, onEvent, true);
+ ok(gEvent !== null, gDescription + ": failed to get duplicated event");
+ ok(gCopiedEvent.length > 0, gDescription + ": count of attribute of the event must be larger than 0");
+ for (var i = 0; i < gCopiedEvent.length; ++i) {
+ var name = gCopiedEvent[i].name;
+ if (name == "rangeOffset") {
+ todo(false, gDescription + ": " + name + " attribute value is never reset (" + gEvent[name] + ")");
+ } else if (name == "eventPhase") {
+ is(gEvent[name], 0, gDescription + ": mismatch with fixed value (" + name + ")");
+ } else if (name == "rangeParent" || name == "currentTarget") {
+ is(gEvent[name], null, gDescription + ": mismatch with fixed value (" + name + ")");
+ } else if (aTest.todoMismatch.includes(name)) {
+ todo_is(gEvent[name], gCopiedEvent[i].value, gDescription + ": mismatch (" + name + ")");
+ } else if (name == "offsetX" || name == "offsetY") {
+ // do nothing; these are defined to return different values during event dispatch
+ // vs not during event dispatch
+ } else {
+ is(gEvent[name], gCopiedEvent[i].value, gDescription + ": mismatch (" + name + ")");
+ }
+ }
+ if (!testWillCallRunNextTest) {
+ runNextTest();
+ }
+ };
+ var testWillCallRunNextTest = aTest.dispatchEvent();
+}
+
+var gIndex = -1;
+function runNextTest() {
+ if (++gIndex == kTests.length) {
+ if (gCallPreventDefault) {
+ finish();
+ return;
+ }
+ // Test with a call of preventDefault() of the events.
+ gCallPreventDefault = true;
+ gIndex = -1;
+ // Restoring the initial state of all elements.
+ document.getElementById("scrollable-div").style.height = "30px";
+ document.getElementById("scrollable-div").style.width = "30px";
+ document.getElementById("scrolled-div").style.height = "10px";
+ document.getElementById("scrolled-div").style.width = "10px";
+ document.getElementById("input-text").value = "";
+ document.getElementById("animated-div").className = "";
+ document.getElementById("animated-div").removeAttribute("x-data");
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ window.requestAnimationFrame(function() {
+ setTimeout(runNextTest, 0);
+ });
+ return;
+ }
+ doTest(kTests[gIndex]);
+}
+
+function init() {
+ SpecialPowers.pushPrefEnv({"set": [["middlemouse.contentLoadURL", false],
+ ["middlemouse.paste", false],
+ ["general.autoScroll", false],
+ ["dom.w3c_pointer_events.enabled", true],
+ ["mousewheel.default.action", 0],
+ ["mousewheel.default.action.override_x", -1],
+ ["mousewheel.with_shift.action", 0],
+ ["mousewheel.with_shift.action.override_x", -1],
+ ["mousewheel.with_control.action", 0],
+ ["mousewheel.with_control.action.override_x", -1],
+ ["mousewheel.with_alt.action", 0],
+ ["mousewheel.with_alt.action.override_x", -1],
+ ["mousewheel.with_meta.action", 0],
+ ["mousewheel.with_meta.action.override_x", -1]]}, runNextTest);
+}
+
+function finish() {
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(init);
+
+</script>
+</body>
diff --git a/widget/tests/test_autocapitalize.html b/widget/tests/test_autocapitalize.html
new file mode 100644
index 0000000000..e74eb5ac5a
--- /dev/null
+++ b/widget/tests/test_autocapitalize.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Tests for autocapitalize that is used by software keyboard</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/SpecialPowers.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+
+<div>
+<input type="text" id="a1"><br>
+<input type="text" id="a2" autocapitalize="characters"><br>
+<input type="text" id="a3" autocapitalize="sentences"><br>
+<input type="text" id="a4" autocapitalize="words"><br>
+<input type="text" id="a5" autocapitalize="off"><br>
+<input type="text" id="a6" autocapitalize="on"><br>
+<input type="url" id="a7" autocapitalize="on"><br>
+<input type="email" id="a8" autocapitalize="on"><br>
+<input type="password" id="a9" autocapitalize="on"><br>
+<textarea id="b1" autocapitalize="characters"></textarea><br>
+<div contenteditable id="c1" autocapitalize="sentences"></div><br>
+<form><input type="text" id="d1" autocapitalize="words"></form><br>
+<form autocapitalize="on"><input type="text" id="d2"></form><br>
+<form autocapitalize="off"><input type="text" id="d3" autocapitalize="on"></form><br>
+</div>
+
+<pre id="test">
+<script class=testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(async () => {
+ const tests = [
+ { id: "a1", autocapitalize: "", desc: "input without autocapitalize" },
+ { id: "a2", autocapitalize: "characters", desc: "input with autocapitalize=characters" },
+ { id: "a3", autocapitalize: "sentences", desc: "input with autocapitalize=sentences" },
+ { id: "a4", autocapitalize: "words", desc: "input with autocapitalize=words" },
+ { id: "a5", autocapitalize: "none", desc: "input with autocapitalize=off" },
+ { id: "a6", autocapitalize: "sentences", desc: "input with autocapitalize=on" },
+ { id: "a7", autocapitalize: "", desc: "input with type=url and autocapitalize=on" },
+ { id: "a8", autocapitalize: "", desc: "input with type=email and autocapitalize=on" },
+ { id: "a9", autocapitalize: "", desc: "input with type=password and autocapitalize=on" },
+ { id: "b1", autocapitalize: "characters", desc: "textarea with autocapitalize=characters" },
+ { id: "c1", autocapitalize: "sentences", desc: "contenteditable with autocapitalize=sentences" },
+ { id: "d1", autocapitalize: "words", desc: "input with autocapitalize=words in form" },
+ { id: "d2", autocapitalize: "sentences", desc: "input in form with autocapitalize=on" },
+ { id: "d3", autocapitalize: "sentences", desc: "input with autocapitalize=on in form" },
+ ];
+
+ await SpecialPowers.setBoolPref("dom.forms.autocapitalize", true);
+
+ for (let test of tests) {
+ document.getElementById(test.id).focus();
+ is(SpecialPowers.DOMWindowUtils.focusedAutocapitalize, test.autocapitalize, test.desc);
+ }
+
+ SpecialPowers.clearUserPref("dom.forms.autocapitalize");
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/widget/tests/test_bug1123480.xhtml b/widget/tests/test_bug1123480.xhtml
new file mode 100644
index 0000000000..dca9bcf207
--- /dev/null
+++ b/widget/tests/test_bug1123480.xhtml
@@ -0,0 +1,147 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1123480
+-->
+<window title="Mozilla Bug 1123480"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="RunTest();">
+ <title>nsTransferable PBM Overflow Selection Test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ // Create over 1 MB of sample garbage text. JavaScript strings are represented by
+ // UTF16 strings, so the size is twice as much as the actual string length.
+ // This value is chosen such that the size of the memory for the string exceeds
+ // the kLargeDatasetSize threshold in nsTransferable.h.
+ // It is also not a round number to reduce the odds of having an accidental
+ // collisions with another file (since the test below looks at the file size
+ // to identify the file).
+ var Ipsum = "0123456789".repeat(1234321);
+ var IpsumByteLength = Ipsum.length * 2;
+ var SHORT_STRING_NO_CACHE = "short string that will never be cached to the disk";
+
+ function isWindows() {
+ const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ return AppConstants.platform === 'win';
+ }
+
+ // Get a list of open file descriptors that refer to a file with the same size as
+ // the expected data (and assume that any mutations in file descriptor counts
+ // are caused by our test).
+ function getClipboardCacheFDCount() {
+ var dir;
+ if (isWindows()) {
+ // On Windows, nsAnonymousTemporaryFile does not immediately delete the file.
+ // Instead, the Windows-specific FILE_FLAG_DELETE_ON_CLOSE flag is used,
+ // which means that the file is deleted when the last handle is closed.
+ // Apparently, this flag is unreliable (e.g. when the application crashes),
+ // so nsAnonymousTemporaryFile stores the temporary files in a subdirectory,
+ // which is cleaned up some time after start-up.
+
+ // This is just a test, and during the test we deterministically close the
+ // handles, so if FILE_FLAG_DELETE_ON_CLOSE does the thing it promises, the
+ // file is actually removed when the handle is closed.
+
+ var {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
+ // Path from nsAnonymousTemporaryFile.cpp, GetTempDir.
+ dir = FileUtils.getFile("TmpD", ["mozilla-temp-files"]);
+ } else {
+ dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath("/dev/fd");
+ }
+ var count = 0;
+ for (var de = dir.directoryEntries; de.hasMoreElements(); ) {
+ var fdFile = de.nextFile;
+ var fileSize;
+ try {
+ fileSize = fdFile.fileSize;
+ } catch (e) {
+ // This can happen on macOS.
+ continue;
+ }
+ if (fileSize === IpsumByteLength) {
+ // Assume that the file was created by us if the size matches.
+ ++count;
+ }
+ }
+ return count;
+ }
+
+ function RunTest() {
+ const gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ // Sanitize environment
+ gClipboardHelper.copyString(SHORT_STRING_NO_CACHE);
+
+ var initialFdCount = getClipboardCacheFDCount();
+
+ // Overflow a nsTransferable region by using the clipboard helper
+ gClipboardHelper.copyString(Ipsum);
+
+ // gClipboardHelper.copyString also puts the data on the selection
+ // clipboard if the platform supports it.
+ var expectedFdDelta = Services.clipboard.supportsSelectionClipboard() ? 2 : 1;
+ // Undefined private browsing mode should cache to disk
+ is(getClipboardCacheFDCount(), initialFdCount + expectedFdDelta, "should cache to disk when PBM is undefined");
+
+ // Sanitize environment again.
+ gClipboardHelper.copyString(SHORT_STRING_NO_CACHE);
+ is(getClipboardCacheFDCount(), initialFdCount, "should have cleared the clipboard data");
+
+ // Repeat procedure of plain text selection with private browsing
+ // disabled and enabled
+ const {PrivateBrowsingUtils} = ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+ for (let private of [false, true]) {
+ var win = window.browsingContext.topChromeWindow.open("about:blank", "_blank", "chrome, width=500, height=200" + (private ? ", private" : ""));
+ ok(win, private ? "should open private window" : "should open non-private window");
+ is(PrivateBrowsingUtils.isContentWindowPrivate(win), private, "used correct window context");
+
+ // Select plaintext in private/non-private channel
+ const nsTransferable = Components.Constructor("@mozilla.org/widget/transferable;1", "nsITransferable");
+ const nsSupportsString = Components.Constructor("@mozilla.org/supports-string;1", "nsISupportsString");
+ var Loadctx = PrivateBrowsingUtils.privacyContextFromWindow(win);
+ var Transfer = nsTransferable();
+ var Suppstr = nsSupportsString();
+ Suppstr.data = Ipsum;
+ Transfer.init(Loadctx);
+ Transfer.addDataFlavor("text/unicode");
+ Transfer.setTransferData("text/unicode", Suppstr, IpsumByteLength);
+
+ // Enabled private browsing mode should not cache any selection to disk; disabled should
+ if (private) {
+ is(getClipboardCacheFDCount(), initialFdCount, "did not violate private browsing mode");
+ } else {
+ is(getClipboardCacheFDCount(), initialFdCount + 1, "should save memory by caching non-private clipboard data to disk");
+ }
+
+ // Share the transferable with the system.
+ Services.clipboard.setData(Transfer, null, Services.clipboard.kGlobalClipboard);
+ if (private) {
+ is(getClipboardCacheFDCount(), initialFdCount, "did not violate private browsing mode");
+ } else {
+ is(getClipboardCacheFDCount(), initialFdCount + 1, "should save memory by caching non-private clipboard data to disk");
+ }
+
+ // Sanitize the environment.
+ Suppstr = nsSupportsString();
+ Suppstr.data = SHORT_STRING_NO_CACHE;
+ Transfer.setTransferData("text/unicode", Suppstr, SHORT_STRING_NO_CACHE.length * 2);
+ is(getClipboardCacheFDCount(), initialFdCount, "should drop the cache file, if any.");
+
+ Services.clipboard.setData(Transfer, null, Services.clipboard.kGlobalClipboard);
+ is(getClipboardCacheFDCount(), initialFdCount, "should postsanitize the environment");
+ }
+ }
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1123480"
+ target="_blank">Mozilla Bug 1123480</a>
+ </body>
+</window>
diff --git a/widget/tests/test_bug343416.xhtml b/widget/tests/test_bug343416.xhtml
new file mode 100644
index 0000000000..2cc05986a7
--- /dev/null
+++ b/widget/tests/test_bug343416.xhtml
@@ -0,0 +1,191 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=343416
+-->
+<window title="Mozilla Bug 343416"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=343416">Mozilla Bug 343416</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 343416 **/
+SimpleTest.waitForExplicitFinish();
+
+// Observer:
+var idleObserver =
+{
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe: function _observe(subject, topic, data)
+ {
+ if (topic != "idle")
+ return;
+
+// var diff = Math.abs(data - newIdleSeconds * 1000);
+// ok (diff < 5000, "The idle time should have increased by roughly 6 seconds, " +
+// "as that's when we told this listener to fire.");
+// if (diff >= 5000)
+// alert(data + " " + newIdleSeconds);
+
+ // Attempt to get to the nsIUserIdleService
+ var subjectOK = false;
+ try {
+ var idleService = subject.QueryInterface(nsIUserIdleService);
+ subjectOK = true;
+ }
+ catch (ex)
+ {}
+ ok(subjectOK, "The subject of the notification should be the " +
+ "nsIUserIdleService.");
+
+ // Attempt to remove ourselves.
+ var removedObserver = false;
+ try {
+ idleService.removeIdleObserver(this, newIdleSeconds);
+ removedObserver = true;
+ }
+ catch (ex)
+ {}
+ ok(removedObserver, "We should be able to remove our observer here.");
+ finishedListenerOK = true;
+ if (finishedTimeoutOK)
+ {
+ clearTimeout(testBailout);
+ finishThisTest();
+ }
+ }
+};
+
+
+const nsIUserIdleService = Ci.nsIUserIdleService;
+const nsIISCID = "@mozilla.org/widget/useridleservice;1";
+var idleService = null;
+try
+{
+ idleService = Cc[nsIISCID].getService(nsIUserIdleService);
+}
+catch (ex)
+{}
+
+ok(idleService, "nsIUserIdleService should exist and be implemented on all tier 1 platforms.");
+
+var idleTime = null;
+var gotIdleTime = false;
+try
+{
+ idleTime = idleService.idleTime;
+ gotIdleTime = true;
+}
+catch (ex)
+{}
+
+ok (gotIdleTime, "Getting the idle time should not fail " +
+ "in normal circumstances on any tier 1 platform.");
+
+// Now we set up a timeout to sanity-test the idleTime after 5 seconds
+setTimeout(testIdleTime, 5000);
+var startTimeStamp = Date.now();
+
+// Now we add the listener:
+var newIdleSeconds = Math.floor(idleTime / 1000) + 6;
+var addedObserver = false;
+try
+{
+ idleService.addIdleObserver(idleObserver, newIdleSeconds);
+ addedObserver = true;
+}
+catch (ex)
+{}
+
+ok(addedObserver, "The nsIUserIdleService should allow us to add an observer.");
+
+addedObserver = false;
+try
+{
+ idleService.addIdleObserver(idleObserver, newIdleSeconds);
+ addedObserver = true;
+}
+catch (ex)
+{}
+
+ok(addedObserver, "The nsIUserIdleService should allow us to add the same observer again.");
+
+var removedObserver = false;
+try
+{
+ idleService.removeIdleObserver(idleObserver, newIdleSeconds);
+ removedObserver = true;
+}
+catch (ex)
+{}
+
+ok(removedObserver, "The nsIUserIdleService should allow us to remove the observer just once.");
+
+function testIdleTime()
+{
+ /* eslint-disable-next-line no-shadow */
+ var gotIdleTime = false
+ try
+ {
+ var newIdleTime = idleService.idleTime;
+ gotIdleTime = true
+ }
+ catch (ex)
+ {}
+ ok(gotIdleTime, "Getting the idle time should not fail " +
+ "in normal circumstances on any tier 1 platform.");
+ // Get the time difference, remove the approx. 5 seconds that we've waited,
+ // should be very close to 0 left.
+ var timeDiff = Math.abs((newIdleTime - idleTime) -
+ (Date.now() - startTimeStamp));
+
+ // 1.5 second leniency.
+ ok(timeDiff < 1500, "The idle time should have increased by roughly the " +
+ "amount of time it took for the timeout to fire. " +
+ "You didn't touch the mouse or keyboard during the " +
+ "test did you?");
+ finishedTimeoutOK = true;
+}
+
+// make sure we still exit when the listener and/or setTimeout don't fire:
+var testBailout = setTimeout(finishThisTest, 12000);
+var finishedTimeoutOK = false, finishedListenerOK = false;
+function finishThisTest()
+{
+ ok(finishedTimeoutOK, "We set a timeout and it should have fired by now.");
+ ok(finishedListenerOK, "We added a listener and it should have been called by now.");
+ if (!finishedListenerOK)
+ {
+ var removedListener = false;
+ try
+ {
+ idleService.removeIdleObserver(idleObserver, newIdleSeconds);
+ removedListener = true;
+ }
+ catch (ex)
+ {}
+
+ ok(removedListener, "We added a listener and we should be able to remove it.");
+ }
+ // Done:
+ SimpleTest.finish();
+}
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug413277.html b/widget/tests/test_bug413277.html
new file mode 100644
index 0000000000..d9f6aaa807
--- /dev/null
+++ b/widget/tests/test_bug413277.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=413277
+-->
+<head>
+ <title>Test for Bug 413277</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=413277">Mozilla Bug 413277</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+ var atts = "width=100, height=100, top=100, screenY=100";
+ atts += ", left=100, screenX=100, toolbar=no";
+ atts += ", location=no, directories=no, status=no";
+ atts += ", menubar=no, scrollbars=no, resizable=no";
+ var newWindow = window.open("_blank", "win_name", atts);
+
+ newWindow.resizeBy(1000000, 1000000);
+ SimpleTest.is(newWindow.outerWidth, newWindow.screen.availWidth, true);
+ SimpleTest.is(newWindow.outerHeight, newWindow.screen.availHeight, true);
+ SimpleTest.is(newWindow.screenY, newWindow.screen.availTop, true);
+ SimpleTest.is(newWindow.screenX, newWindow.screen.availLeft, true);
+
+ newWindow.close();
+</script>
+</pre>
+</body>
diff --git a/widget/tests/test_bug428405.xhtml b/widget/tests/test_bug428405.xhtml
new file mode 100644
index 0000000000..72d0ba17a0
--- /dev/null
+++ b/widget/tests/test_bug428405.xhtml
@@ -0,0 +1,167 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window id="window1" title="Test Bug 428405"
+ onload="setGlobals(); loadFirstTab();"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js"/>
+
+ <tabbox id="tabbox" flex="100%">
+ <tabs>
+ <tab label="Tab 1"/>
+ <tab label="Tab 2"/>
+ </tabs>
+ <tabpanels flex="100%">
+ <browser onload="configureFirstTab();" id="tab1browser" flex="100%"/>
+ <browser onload="configureSecondTab();" id="tab2browser" flex="100%"/>
+ </tabpanels>
+ </tabbox>
+
+ <script type="application/javascript"><![CDATA[
+ const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ SimpleTest.waitForExplicitFinish();
+
+ var gCmdOptYReceived = false;
+
+ // Look for a cmd-opt-y event.
+ function onKeyPress(aEvent) {
+ gCmdOptYReceived = false;
+ if (String.fromCharCode(aEvent.charCode) != 'y')
+ return;
+ if (aEvent.ctrlKey || aEvent.shiftKey || !aEvent.metaKey || !aEvent.altKey)
+ return;
+ gCmdOptYReceived = true;
+ }
+
+ function setGlobals() {
+ let chromeWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ // For some reason, a global <key> element's oncommand handler only gets
+ // invoked if the focus is outside both of the <browser> elements
+ // (tab1browser and tab2browser). So, to make sure we can see a
+ // cmd-opt-y event in window1 (if one is available), regardless of where
+ // the focus is in this window, we need to add a "keypress" event
+ // listener to gChromeWindow, and then check (in onKeyPress()) to see if
+ // it's a cmd-opt-y event.
+ chromeWindow.addEventListener("keypress", onKeyPress);
+ }
+
+ // 1) Start loading first tab.
+ // 6) Start reloading first tab.
+ function loadFirstTab() {
+ var browser = document.getElementById("tab1browser");
+ BrowserTestUtils.loadURI(browser, "data:text/html;charset=utf-8,<body><h2>First Tab</h2><p><input type='submit' value='Button' id='button1'/></body>");
+ }
+
+ function configureFirstTab() {
+ try {
+ var button = document.getElementById("tab1browser").contentDocument.getElementById("button1");
+ button.addEventListener("click", onFirstTabButtonClicked);
+ button.focus();
+ if (document.getElementById("tabbox").selectedIndex == 0) {
+ // 2) When first tab has finished loading (while first tab is
+ // focused), hit Return to trigger the action of first tab's
+ // button.
+ synthesizeNativeReturnKey();
+ } else {
+ // 7) When first tab has finished reloading (while second tab is
+ // focused), start loading second tab.
+ loadSecondTab();
+ }
+ } catch(e) {
+ }
+ }
+
+ // 8) Start loading second tab.
+ function loadSecondTab() {
+ var browser = document.getElementById("tab2browser");
+ BrowserTestUtils.loadURI(browser, "data:text/html;charset=utf-8,<body><h2>Second Tab</h2><p><input type='submit' value='Button' id='button1'/></body>");
+ }
+
+ function configureSecondTab() {
+ try {
+ var button = document.getElementById("tab2browser").contentDocument.getElementById("button1");
+ button.addEventListener("click", onSecondTabButtonClicked);
+ button.focus();
+ if (document.getElementById("tabbox").selectedIndex == 1) {
+ // 9) When second tab has finished loading (while second tab is
+ // focused), hit Return to trigger action of second tab's
+ // button.
+ synthesizeNativeReturnKey();
+ }
+ } catch(e) {
+ }
+ }
+
+ // 3) First tab's button clicked.
+ function onFirstTabButtonClicked() {
+ switchToSecondTabAndReloadFirst();
+ }
+
+ // 10) Second tab's button clicked.
+ function onSecondTabButtonClicked() {
+ switchToFirstTab();
+ }
+
+ function switchToSecondTabAndReloadFirst() {
+ // 4) Switch to second tab.
+ document.getElementById("tabbox").selectedIndex = 1;
+ // 5) Start reloading first tab (while second tab is focused).
+ loadFirstTab();
+ }
+
+ function switchToFirstTab() {
+ // 11) Switch back to first tab.
+ document.getElementById("tabbox").selectedIndex = 0;
+ doCmdY();
+ }
+
+ function doCmdY() {
+ // 12) Back in first tab, try cmd-y.
+ gCmdOptYReceived = false;
+ if (!synthesizeNativeCmdOptY(finishTest)) {
+ ok(false, "Failed to synthesize native key");
+ finishTest();
+ }
+ }
+
+ function finishTest() {
+ // 13) Check result.
+ is(gCmdOptYReceived, true);
+
+ SimpleTest.finish();
+ }
+
+ // synthesizeNativeReturnKey() and synthesizeNativeCmdOptY() are needed
+ // because their synthesizeKey() counterparts don't work properly -- the
+ // latter make this test succeed when it should fail.
+
+ // The 'aNativeKeyCode', 'aCharacters' and 'aUnmodifiedCharacters'
+ // parameters used below (in synthesizeNativeReturnKey() and
+ // synthesizeNativeCmdOptY()) were confirmed accurate using the
+ // DebugEventsPlugin v1.01 from bmo bug 441880.
+
+ function synthesizeNativeReturnKey() {
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_Return, {}, "\u000a", "\u000a");
+ }
+
+ function synthesizeNativeCmdOptY(aCallback) {
+ return synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_Y, {metaKey:1, altKey:1}, "y", "y", aCallback);
+ }
+
+ ]]></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ </body>
+
+</window>
diff --git a/widget/tests/test_bug429954.xhtml b/widget/tests/test_bug429954.xhtml
new file mode 100644
index 0000000000..989484b2cc
--- /dev/null
+++ b/widget/tests/test_bug429954.xhtml
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=429954
+-->
+<window title="Mozilla Bug 429954"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function () {
+ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ var win = Services.wm.getMostRecentWindow("navigator:browser");
+ win.maximize();
+ var maxX = win.screenX, maxY = win.screenY;
+ var maxWidth = win.outerWidth, maxHeight = win.outerHeight;
+ win.restore();
+
+ window.openDialog("window_bug429954.xhtml", "_blank",
+ "chrome,noopener,resizable,width=" + maxWidth + ",height=" + maxHeight +
+ ",screenX=" + maxX + "screenY=" + maxY,
+ window);
+});
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug444800.xhtml b/widget/tests/test_bug444800.xhtml
new file mode 100644
index 0000000000..f2fac6509d
--- /dev/null
+++ b/widget/tests/test_bug444800.xhtml
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin"
+ type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=444800
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Mozilla Bug 444800" onload="initAndRunTests()">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=444800"
+ target="_blank">Mozilla Bug 444800</a>
+ <p/>
+ <img id="bitmapImage" src="data:image/bmp;base64,Qk2KAwAAAAAAAIoAAAB8AAAADwAAABAAAAABABgAAAAAAAADAAASCwAAEgsAAAAAAAAAAAAAAAD%2FAAD%2FAAD%2FAAAAAAAA%2FwEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F0vf%2FAABc8tKY%2F%2F%2F%2FyNfq3Mi9%2F%2F%2F70vf%2FAABP8s2R%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fzff%2FAABB8s2R5f%2F%2FAAB5LgAA%2F%2B7Czff%2FAABB7s2R%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fzff%2FAABB99KRpdz%2FAAAAAAAA4Ktm0vv%2FAABB7s2R%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fzff%2FAABB7teYQZHNkS4AebfImAAA1%2FfyAABP7s2R%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fzff%2FAABByMiYAAB5159P0v%2F%2FAABBwtKrAABc7s2R%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fzff%2FAABPcIJwAAAA%2B%2BW3%2F%2F%2F%2FAHC3gnBBAABP7s2R%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fzff%2FAABcAAAAmE8A%2F%2F%2Fy%2F%2F%2F%2Fn9LyAAAAAAAA7s2Y%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FzfL%2FAABcAAAA4LFw%2F%2F%2F%2F%2F%2F%2F%2F4P%2F%2FAAB5AAAA7s2R%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F0vf%2FAABmXAAA%2F%2B7I%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FXJ%2FSAAAA8s2Y%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAA"/>
+ <p/>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+const knsIClipboard = Ci.nsIClipboard;
+
+function copyImageToClipboard()
+{
+ var tmpNode = document.popupNode;
+ document.popupNode = document.getElementById("bitmapImage");
+
+ const kCmd = "cmd_copyImageContents";
+ var controller = top.document.commandDispatcher
+ .getControllerForCommand(kCmd);
+ ok((controller && controller.isCommandEnabled(kCmd)), "have copy command");
+ controller.doCommand(kCmd);
+
+ document.popupNode = tmpNode;
+}
+
+function getLoadContext() {
+ return window.docShell.QueryInterface(Ci.nsILoadContext);
+}
+
+function runImageClipboardTests(aCBSvc, aImageType)
+{
+ // Verify that hasDataMatchingFlavors() is working correctly.
+ var typeArray = [ aImageType ];
+ var hasImage = aCBSvc.hasDataMatchingFlavors(typeArray,
+ knsIClipboard.kGlobalClipboard);
+ ok(hasImage, aImageType + " - hasDataMatchingFlavors()");
+
+ // Verify that getData() is working correctly.
+ var xfer = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+ xfer.init(getLoadContext());
+ xfer.addDataFlavor(aImageType);
+ aCBSvc.getData(xfer, knsIClipboard.kGlobalClipboard);
+
+ var typeObj = {}, dataObj = {};
+ xfer.getAnyTransferData(typeObj, dataObj);
+ var gotValue = (null != dataObj.value);
+ ok(gotValue, aImageType + " - getData() returned a value");
+ if (gotValue)
+ {
+ const knsIInputStream = Ci.nsIInputStream;
+ var imgStream = dataObj.value.QueryInterface(knsIInputStream);
+ ok((null != imgStream), aImageType + " - got an nsIInputStream");
+ var bytesAvailable = imgStream.available();
+ ok((bytesAvailable > 10), aImageType + " - got some data");
+ }
+}
+
+function initAndRunTests()
+{
+ SimpleTest.waitForExplicitFinish();
+
+ copyImageToClipboard();
+
+ var cbSvc = Cc["@mozilla.org/widget/clipboard;1"]
+ .getService(knsIClipboard);
+
+ // Work around a problem on Windows where clipboard is not ready after copy.
+ setTimeout(function() { runTests(cbSvc); }, 0);
+}
+
+function runTests(aCBSvc)
+{
+ runImageClipboardTests(aCBSvc, "image/png");
+ runImageClipboardTests(aCBSvc, "image/jpg");
+ runImageClipboardTests(aCBSvc, "image/jpeg");
+
+ SimpleTest.finish();
+}
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_bug466599.xhtml b/widget/tests/test_bug466599.xhtml
new file mode 100644
index 0000000000..b75fef0cc1
--- /dev/null
+++ b/widget/tests/test_bug466599.xhtml
@@ -0,0 +1,103 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=466599
+-->
+<window title="Mozilla Bug 466599"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="initAndRunTests()">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ </body>
+
+ <!-- test code goes here -->
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 466599 **/
+
+function getLoadContext() {
+ return window.docShell.QueryInterface(Ci.nsILoadContext);
+}
+
+function copyToClipboard(txt)
+{
+ var clipid = Ci.nsIClipboard;
+ var clip =
+ Cc['@mozilla.org/widget/clipboard;1'].createInstance(clipid);
+ if (!clip)
+ return false;
+ var trans =
+ Cc['@mozilla.org/widget/transferable;1'].createInstance(Ci.nsITransferable);
+ if (!trans)
+ return false;
+ trans.init(getLoadContext());
+ trans.addDataFlavor('text/html');
+ var str =
+ Cc['@mozilla.org/supports-string;1'].createInstance(Ci.nsISupportsString);
+ var copytext = txt;
+ str.data = copytext;
+ trans.setTransferData("text/html",str,copytext.length*2);
+ if (!clip)
+ return false;
+ clip.setData(trans,null,clipid.kGlobalClipboard);
+ return true;
+}
+
+function readFromClipboard()
+{
+ var clipid = Ci.nsIClipboard;
+ var clip =
+ Cc['@mozilla.org/widget/clipboard;1'].createInstance(clipid);
+ if (!clip)
+ return "";
+ var trans =
+ Cc['@mozilla.org/widget/transferable;1'].createInstance(Ci.nsITransferable);
+ if (!trans)
+ return "";
+ trans.init(getLoadContext());
+ trans.addDataFlavor('text/html');
+ clip.getData(trans,clipid.kGlobalClipboard);
+ var str = {};
+ trans.getTransferData("text/html",str);
+ if (str)
+ str = str.value.QueryInterface(Ci.nsISupportsString);
+ return str?.data;
+}
+
+function encodeHtmlEntities(s)
+{
+ var result = '';
+ for (var i = 0; i < s.length; i++) {
+ var c = s.charAt(i);
+ result += {'<':'&lt;', '>':'&gt;', '&':'&amp;', '"':'&quot;'}[c] || c;
+ }
+ return result;
+}
+
+function initAndRunTests()
+{
+ var source = '<p>Lorem ipsum</p>';
+ var expect = new RegExp('<html>.*charset=utf-8.*' + source + '.*</html>', 'im');
+
+ var result = copyToClipboard(source);
+ ok(result, "copied HTML data to system pasteboard");
+
+ result = readFromClipboard();
+ ok(expect.test(result), "data on system pasteboard is wrapped with charset metadata");
+
+ // eslint-disable-next-line no-unsanitized/property
+ $("display").innerHTML =
+ '<em>source:</em> <pre>' + encodeHtmlEntities(source) + '</pre><br/>' +
+ '<em>result:</em> <pre>' + encodeHtmlEntities(result) + '</pre>';
+}
+
+ ]]>
+ </script>
+</window>
diff --git a/widget/tests/test_bug478536.xhtml b/widget/tests/test_bug478536.xhtml
new file mode 100644
index 0000000000..383c0bb42f
--- /dev/null
+++ b/widget/tests/test_bug478536.xhtml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=478536
+-->
+<window title="Mozilla Bug 478536"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <title>Test for Bug 478536</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("window_bug478536.xhtml", "_blank",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug485118.xhtml b/widget/tests/test_bug485118.xhtml
new file mode 100644
index 0000000000..27e6659897
--- /dev/null
+++ b/widget/tests/test_bug485118.xhtml
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=485118
+-->
+<window title="Mozilla Bug 485118"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<hbox height="300">
+ <vbox width="300">
+ <scrollbar orient="horizontal"
+ maxpos="10000"
+ pageincrement="1"
+ id="horizontal"/>
+ <scrollbar orient="horizontal"
+ maxpos="10000"
+ pageincrement="1"
+ style="appearance: auto; -moz-default-appearance: scrollbar-small;"
+ id="horizontalSmall"/>
+ <hbox flex="1">
+ <scrollbar orient="vertical"
+ maxpos="10000"
+ pageincrement="1"
+ id="vertical"/>
+ <scrollbar orient="vertical"
+ maxpos="10000"
+ pageincrement="1"
+ style="appearance: auto; -moz-default-appearance: scrollbar-small;"
+ id="verticalSmall"/>
+ <spacer flex="1"/>
+ </hbox>
+ </vbox>
+</hbox>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ ["horizontal", "vertical"].forEach(function (orient) {
+ ["", "Small"].forEach(function (size) {
+ var elem = document.getElementById(orient + size);
+ var thumbRect = SpecialPowers.unwrap(
+ SpecialPowers.InspectorUtils.getChildrenForNode(elem, true)[2])
+ .childNodes[0].getBoundingClientRect();
+ var sizeToCheck = orient == "horizontal" ? "width" : "height";
+ // var expectedSize = size == "Small" ? 19 : 26;
+ var expectedSize = 26;
+ is(thumbRect[sizeToCheck], expectedSize, size + " scrollbar has wrong minimum " + sizeToCheck);
+ });
+ });
+ SimpleTest.finish();
+}
+window.addEventListener("load", runTest);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug517396.xhtml b/widget/tests/test_bug517396.xhtml
new file mode 100644
index 0000000000..c99182c85b
--- /dev/null
+++ b/widget/tests/test_bug517396.xhtml
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=517396
+-->
+<window title="Mozilla Bug 517396"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function () {
+ // this test fails on Linux, bug 526236
+ if (navigator.platform.includes("Lin")) {
+ ok(true, "disabled on Linux");
+ SimpleTest.finish();
+ return;
+ }
+
+ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ var win = Services.wm.getMostRecentWindow("navigator:browser");
+ var oldWidth = win.outerWidth, oldHeight = win.outerHeight;
+ win.maximize();
+ var newWidth = win.outerWidth, newHeight = win.outerHeight;
+ win.moveBy(10, 0);
+ var sizeShouldHaveChanged = !navigator.platform.match(/Mac/);
+ var compFunc = sizeShouldHaveChanged ? isnot : is;
+ var not = sizeShouldHaveChanged ? "" : "not ";
+ compFunc(win.outerWidth, newWidth, "moving a maximized window should " + not + "have changed its width");
+ compFunc(win.outerHeight, newHeight, "moving a maximized window should " + not + "have changed its height");
+ win.restore();
+ is(win.outerWidth, oldWidth, "restored window has wrong width");
+ is(win.outerHeight, oldHeight, "restored window has wrong height");
+ SimpleTest.finish();
+});
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug522217.xhtml b/widget/tests/test_bug522217.xhtml
new file mode 100644
index 0000000000..0fa55a65e8
--- /dev/null
+++ b/widget/tests/test_bug522217.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=522217
+-->
+<window title="Mozilla Bug 522217"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function () {
+ window.openDialog("window_bug522217.xhtml", "_blank",
+ "chrome,resizable,width=400,height=300,noopener", window);
+});
+
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug538242.xhtml b/widget/tests/test_bug538242.xhtml
new file mode 100644
index 0000000000..4608a74e35
--- /dev/null
+++ b/widget/tests/test_bug538242.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=538242
+-->
+<window title="Mozilla Bug 538242"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+if (navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function () {
+ if (navigator.platform.includes("Lin")) {
+ ok(true, "This test is disabled on Linux because it expects moving windows to be synchronous which is not guaranteed on Linux.");
+ SimpleTest.finish();
+ return;
+ }
+
+ var win = window.browsingContext.topChromeWindow.open(
+ "window_bug538242.xhtml", "_blank",
+ "chrome,width=400,height=300,left=100,top=100");
+ SimpleTest.waitForFocus(function () {
+ is(win.screenX, 100, "window should open at 100, 100");
+ is(win.screenY, 100, "window should open at 100, 100");
+ var [oldX, oldY] = [win.screenX, win.screenY];
+ win.moveTo(0, 0);
+ isnot(win.screenX, oldX, "window should have moved to a point near 0, 0");
+ isnot(win.screenY, oldY, "window should have moved to a point near 0, 0");
+ win.close();
+ SimpleTest.finish();
+ }, win);
+});
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug565392.html b/widget/tests/test_bug565392.html
new file mode 100644
index 0000000000..c19b0b6a92
--- /dev/null
+++ b/widget/tests/test_bug565392.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=565392
+-->
+<head>
+ <title>Test for Bug 565392</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=565392">Mozilla Bug 565392</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+/** Test for Bug 565392 **/
+
+var dir1 = Services.dirsvc.get("ProfD", Ci.nsIFile);
+var clipboard = Services.clipboard;
+
+ function getLoadContext() {
+ return window.docShell.QueryInterface(Ci.nsILoadContext);
+ }
+
+ function getTransferableFile(file) {
+ var transferable = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+ transferable.init(getLoadContext());
+ transferable.setTransferData("application/x-moz-file", file);
+ return transferable;
+ }
+
+ function setClipboardData(transferable) {
+ clipboard.setData(transferable, null, 1);
+ }
+
+ function getClipboardData(mime) {
+ var transferable = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+ transferable.init(getLoadContext());
+ transferable.addDataFlavor(mime);
+ clipboard.getData(transferable, 1);
+ var data = {};
+ transferable.getTransferData(mime, data);
+ return data;
+ }
+
+setClipboardData(getTransferableFile(dir1));
+is(clipboard.hasDataMatchingFlavors(["application/x-moz-file"], 1), true);
+var data = getClipboardData("application/x-moz-file");
+var file = data.value.QueryInterface(Ci.nsIFile);
+ok(file.isDirectory(), true);
+is(file.target, dir1.target, true);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/widget/tests/test_bug586713.xhtml b/widget/tests/test_bug586713.xhtml
new file mode 100644
index 0000000000..4733202264
--- /dev/null
+++ b/widget/tests/test_bug586713.xhtml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Native menu system tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug586713_window.xhtml", "bug586713_window",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug593307.xhtml b/widget/tests/test_bug593307.xhtml
new file mode 100644
index 0000000000..770dd390cb
--- /dev/null
+++ b/widget/tests/test_bug593307.xhtml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=593307
+-->
+<window title="Mozilla Bug 593307"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+function finish() {
+ offscreenWindow.close();
+ SimpleTest.finish();
+}
+
+var mainWindow = window.browsingContext.topChromeWindow;
+
+var offscreenWindow = mainWindow.openDialog("window_bug593307_offscreen.xhtml", "",
+ "dialog=no,chrome,width=200,height=200,screenX=-3000,screenY=-3000",
+ SimpleTest, finish);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug596600.xhtml b/widget/tests/test_bug596600.xhtml
new file mode 100644
index 0000000000..6312590876
--- /dev/null
+++ b/widget/tests/test_bug596600.xhtml
@@ -0,0 +1,168 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Native mouse event tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const NSEventTypeMouseMoved = 5;
+
+var gLeftWindow, gRightWindow, gBrowserElement;
+var gExpectedEvents = [];
+
+function moveMouseTo(x, y, andThen) {
+ var utils = gLeftWindow.windowUtils;
+ utils.sendNativeMouseEvent(x, y, NSEventTypeMouseMoved, 0, gLeftWindow.documentElement);
+ SimpleTest.executeSoon(andThen);
+}
+
+function openWindows() {
+ gLeftWindow = window.browsingContext.topChromeWindow
+ .open('empty_window.xhtml', '_blank', 'chrome,screenX=50,screenY=50,width=200,height=200');
+ SimpleTest.waitForFocus(function () {
+ gRightWindow = window.browsingContext.topChromeWindow
+ .open('empty_window.xhtml', '', 'chrome,screenX=300,screenY=50,width=200,height=200');
+ SimpleTest.waitForFocus(attachBrowserToLeftWindow, gRightWindow);
+ }, gLeftWindow);
+}
+
+function attachBrowserToLeftWindow() {
+ gBrowserElement = gLeftWindow.document.createXULElement("browser");
+ gBrowserElement.setAttribute("type", "content");
+ gBrowserElement.setAttribute("src", "file_bug596600.html");
+ gBrowserElement.style.width = "100px";
+ gBrowserElement.style.height = "100px";
+ gBrowserElement.style.margin = "50px";
+ gLeftWindow.document.documentElement.appendChild(gBrowserElement);
+ gBrowserElement.addEventListener("load", function (e) {
+ test1();
+ }, { capture: true, once: true });
+}
+
+function test1() {
+ // gRightWindow is active, gLeftWindow is inactive.
+ moveMouseTo(0, 0, function () {
+ var expectMouseOver = false, expectMouseOut = false;
+ function mouseOverListener(e) {
+ ok(expectMouseOver, "Got expected mouseover at " + e.screenX + ", " + e.screenY);
+ expectMouseOver = false;
+ }
+ function mouseOutListener(e) {
+ ok(expectMouseOut, "Got expected mouseout at " + e.screenX + ", " + e.screenY);
+ expectMouseOut = false;
+ }
+ gLeftWindow.addEventListener("mouseover", mouseOverListener);
+ gLeftWindow.addEventListener("mouseout", mouseOutListener);
+
+ // Move into the left window
+ expectMouseOver = true;
+ moveMouseTo(80, 80, function () {
+ ok(!expectMouseOver, "Should have got mouseover event");
+
+ // Move over the browser
+ expectMouseOut = true;
+ moveMouseTo(150, 150, function () {
+ ok (!expectMouseOut, "Should have got mouseout event");
+ gLeftWindow.removeEventListener("mouseover", mouseOverListener);
+ gLeftWindow.removeEventListener("mouseout", mouseOutListener);
+ test2();
+ });
+ });
+ });
+}
+
+function test2() {
+ // Make the browser cover the whole window.
+ gBrowserElement.style.margin = "0";
+ gBrowserElement.style.width = gBrowserElement.style.height = "200px";
+
+ // Add a box to the browser at the left edge.
+ var doc = gBrowserElement.contentDocument;
+ var box = doc.createElement("div");
+ box.setAttribute("id", "box");
+ box.style.position = "absolute";
+ box.style.left = "0";
+ box.style.top = "50px";
+ box.style.width = "100px";
+ box.style.height = "100px";
+ box.style.backgroundColor = "green";
+ doc.body.appendChild(box);
+
+ ok(!box.matches(":hover"), "Box shouldn't be hovered (since the mouse isn't over it and since it's in a non-clickthrough browser in a background window)");
+
+ // A function to waitForFocus and then wait for synthetic mouse
+ // events to happen. Note that those happen off the refresh driver,
+ // and happen after animation frame requests.
+ function changeFocusAndAwaitSyntheticMouse(callback, winToFocus,
+ elementToWatchForMouseEventOn) {
+ function mouseWatcher() {
+ elementToWatchForMouseEventOn.removeEventListener("mouseover",
+ mouseWatcher);
+ elementToWatchForMouseEventOn.removeEventListener("mouseout",
+ mouseWatcher);
+ SimpleTest.executeSoon(callback);
+ }
+ elementToWatchForMouseEventOn.addEventListener("mouseover",
+ mouseWatcher);
+ elementToWatchForMouseEventOn.addEventListener("mouseout",
+ mouseWatcher);
+ // Just pass a dummy function to waitForFocus; the mouseout/over listener
+ // will actually handle things for us.
+ SimpleTest.waitForFocus(function() {}, winToFocus);
+ }
+
+ // Move the mouse over the box.
+ moveMouseTo(100, 150, function () {
+ ok(!box.matches(":hover"), "Box shouldn't be hovered (since it's in a non-clickthrough browser in a background window)");
+ // Activate the left window.
+ changeFocusAndAwaitSyntheticMouse(function () {
+ ok(gBrowserElement.matches(":hover"), "browser should be hovered");
+ ok(box.matches(":hover"), "Box should be hovered");
+ // De-activate the window (by activating the right window).
+ changeFocusAndAwaitSyntheticMouse(function () {
+ ok(!gBrowserElement.matches(":hover"), "browser shouldn't be hovered");
+ ok(!box.matches(":hover"), "Box shouldn't be hovered");
+ // Re-activate it.
+ changeFocusAndAwaitSyntheticMouse(function () {
+ ok(gBrowserElement.matches(":hover"), "browser should be hovered");
+ ok(box.matches(":hover"), "Box should be hovered");
+ // Unhover box and browser by moving the mouse outside the window.
+ moveMouseTo(0, 150, function () {
+ ok(!gBrowserElement.matches(":hover"), "browser shouldn't be hovered");
+ ok(!box.matches(":hover"), "box shouldn't be hovered");
+ finalize();
+ });
+ }, gLeftWindow, box);
+ }, gRightWindow, box);
+ }, gLeftWindow, box);
+ });
+}
+
+function finalize() {
+ gRightWindow.close();
+ gLeftWindow.close();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(openWindows);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug673301.xhtml b/widget/tests/test_bug673301.xhtml
new file mode 100644
index 0000000000..7de1e9d3db
--- /dev/null
+++ b/widget/tests/test_bug673301.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display"></p>
+<div id="content" style="display: none"/>
+</body>
+
+<script type="application/javascript">
+function getLoadContext() {
+ return window.docShell.QueryInterface(Ci.nsILoadContext);
+}
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var transferable = Cc['@mozilla.org/widget/transferable;1']
+ .createInstance(Ci.nsITransferable);
+transferable.init(getLoadContext());
+
+transferable.addDataFlavor("text/unicode");
+transferable.setTransferData("text/unicode", document, 4);
+
+Services.clipboard.setData(transferable, null, Ci.nsIClipboard.kGlobalClipboard);
+
+transferable.setTransferData("text/unicode", null, 0);
+
+SimpleTest.ok(true, "Didn't crash setting non-text data for text/unicode type");
+</script>
+</window>
diff --git a/widget/tests/test_bug760802.xhtml b/widget/tests/test_bug760802.xhtml
new file mode 100644
index 0000000000..41c8a999d2
--- /dev/null
+++ b/widget/tests/test_bug760802.xhtml
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=760802
+-->
+<window title="Mozilla Bug 760802"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=760802"
+ target="_blank">Mozilla Bug 760802</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"/>
+ <iframe id="iframe_not_editable" width="300" height="150"
+ src="data:text/html,&lt;html&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;"/><br/>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+function getBaseWindowInterface(win) {
+ return win.docShell
+ .treeOwner
+ .nsIBaseWindow;
+}
+
+function getBaseWindowInterfaceFromDocShell(win) {
+ return win.docShell.QueryInterface(Ci.nsIBaseWindow);
+}
+
+function shouldThrowException(fun, exception) {
+ try {
+ fun.call();
+ return false;
+ } catch (e) {
+ // eslint-disable-next-line no-unsanitized/property
+ $("display").innerHTML += "<br/>OK thrown: "+e.message;
+ return (e instanceof Components.Exception &&
+ e.result === exception)
+ }
+}
+function doesntThrowException(fun) {
+ return !shouldThrowException(fun);
+}
+
+var baseWindow = getBaseWindowInterface(this);
+var nativeHandle = baseWindow.nativeHandle;
+// eslint-disable-next-line no-unsanitized/property
+$("display").innerHTML = "found nativeHandle for this window: "+nativeHandle;
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var win = Services.wm.getMostRecentWindow("navigator:browser");
+let docShell = getBaseWindowInterfaceFromDocShell(win);
+
+ok(
+ shouldThrowException(function(){docShell.nativeHandle;},
+ Cr.NS_ERROR_NOT_IMPLEMENTED),
+ "nativeHandle should not be implemented for nsDocShell"
+);
+
+ok(typeof(nativeHandle) === "string", "nativeHandle should be a string");
+ok(nativeHandle.match(/^0x[0-9a-f]+$/), "nativeHandle should have a memory address format");
+
+var iWin = document.getElementById("iframe_not_editable").contentWindow;
+is(getBaseWindowInterface(iWin).nativeHandle, nativeHandle,
+ "the nativeHandle of an iframe should be its parent's nativeHandle");
+
+var dialog = win.openDialog("data:text/plain,this is an active window.", "_blank",
+ "chrome,dialog=yes,width=100,height=100");
+
+isnot(getBaseWindowInterface(dialog).nativeHandle, "",
+ "the nativeHandle of a dialog should not be empty");
+
+dialog.close();
+
+todo(false, "the nativeHandle of a window without a mainWidget should be empty"); // how to build a window without a mainWidget ?
+
+SimpleTest.finish();
+ ]]></script>
+</window>
diff --git a/widget/tests/test_clipboard.xhtml b/widget/tests/test_clipboard.xhtml
new file mode 100644
index 0000000000..8a29faa08a
--- /dev/null
+++ b/widget/tests/test_clipboard.xhtml
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=948065
+-->
+<window title="Mozilla Bug 948065"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="initAndRunTests()">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ </body>
+
+ <!-- test code goes here -->
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 948065 **/
+
+ const kIsMac = navigator.platform.indexOf("Mac") == 0;
+
+ function getLoadContext() {
+ return window.docShell.QueryInterface(Ci.nsILoadContext);
+ }
+
+ // Get clipboard data to paste.
+ function paste(clipboard) {
+ let trans = Cc['@mozilla.org/widget/transferable;1']
+ .createInstance(Ci.nsITransferable);
+ trans.init(getLoadContext());
+ trans.addDataFlavor("text/unicode");
+ clipboard.getData(trans, Ci.nsIClipboard.kGlobalClipboard);
+ let str = {};
+ try {
+ trans.getTransferData('text/unicode', str);
+ } catch (e) {
+ str = '';
+ }
+ if (str) {
+ str = str.value.QueryInterface(Ci.nsISupportsString);
+ if (str) {
+ str = str.data;
+ }
+ }
+ return str;
+ }
+
+ function initAndRunTests() {
+ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ // Test copy.
+ const data = "random number: " + Math.random();
+ let helper = Cc['@mozilla.org/widget/clipboardhelper;1']
+ .getService(Ci.nsIClipboardHelper);
+ helper.copyString(data);
+ is(paste(Services.clipboard), data, 'Data was successfully copied.');
+
+ // Test emptyClipboard, disabled for OSX because bug 666254
+ if (!kIsMac) {
+ Services.clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
+ is(paste(Services.clipboard), '', 'Data was successfully cleared.');
+ }
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/widget/tests/test_composition_text_querycontent.xhtml b/widget/tests/test_composition_text_querycontent.xhtml
new file mode 100644
index 0000000000..bc9d626087
--- /dev/null
+++ b/widget/tests/test_composition_text_querycontent.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Testing composition, text and query content events"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+// 3 assertions are: If setting selection with eSetSelection event whose range
+// is larger than the actual range, hits "Can only call this on frames that have
+// been reflowed:
+// '!(GetStateBits() & NS_FRAME_FIRST_REFLOW) || (GetParent()->GetStateBits() &
+// NS_FRAME_TOO_DEEP_IN_FRAME_TREE)'" in nsTextFrame.cpp.
+// Strangely, this doesn't occur with RDP on Windows.
+// 12 assertions are: assertions in WSRunScanner::TextFragmentData::GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace()
+SimpleTest.expectAssertions(0, 3 + 12);
+SimpleTest.waitForExplicitFinish();
+window.openDialog("window_composition_text_querycontent.xhtml", "_blank",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_imestate.html b/widget/tests/test_imestate.html
new file mode 100644
index 0000000000..33fb80c067
--- /dev/null
+++ b/widget/tests/test_imestate.html
@@ -0,0 +1,1458 @@
+<html style="ime-mode: disabled;">
+<head>
+ <title>Test for IME state controling</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="utils.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body onload="setTimeout(runTests, 0);" style="ime-mode: disabled;">
+<div id="display" style="ime-mode: disabled;">
+ <!-- input elements -->
+ <input type="text" id="text"/><br/>
+ <input type="text" id="text_readonly" readonly="readonly"/><br/>
+ <input type="password" id="password"/><br/>
+ <input type="password" id="password_readonly" readonly="readonly"/><br/>
+ <input type="checkbox" id="checkbox"/><br/>
+ <input type="radio" id="radio"/><br/>
+ <input type="submit" id="submit"/><br/>
+ <input type="reset" id="reset"/><br/>
+ <input type="file" id="file"/><br/>
+ <input type="button" id="ibutton"/><br/>
+ <input type="image" id="image" alt="image"/><br/>
+
+ <!-- html5 input elements -->
+ <input type="url" id="url"/><br/>
+ <input type="email" id="email"/><br/>
+ <input type="search" id="search"/><br/>
+ <input type="tel" id="tel"/><br/>
+ <input type="number" id="number"/><br/>
+
+ <!-- form controls -->
+ <button id="button">button</button><br/>
+ <textarea id="textarea">textarea</textarea><br/>
+ <textarea id="textarea_readonly" readonly="readonly">textarea[readonly]</textarea><br/>
+ <select id="select">
+ <option value="option" selected="selected"/>
+ </select><br/>
+ <select id="select_multiple" multiple="multiple">
+ <option value="option" selected="selected"/>
+ </select><br/>
+ <isindex id="isindex" prompt="isindex"/><br/>
+
+ <!-- a element -->
+ <a id="a_href" href="about:blank">a[href]</a><br/>
+
+ <!-- ime-mode test -->
+ <input type="text" id="ime_mode_auto" style="ime-mode: auto;"/><br/>
+ <input type="text" id="ime_mode_normal" style="ime-mode: normal;"/><br/>
+ <input type="text" id="ime_mode_active" style="ime-mode: active;"/><br/>
+ <input type="text" id="ime_mode_inactive" style="ime-mode: inactive;"/><br/>
+ <input type="text" id="ime_mode_disabled" style="ime-mode: disabled;"/><br/>
+
+ <input type="text" id="ime_mode_auto_url" style="ime-mode: auto;"/><br/>
+ <input type="text" id="ime_mode_normal_url" style="ime-mode: normal;"/><br/>
+ <input type="text" id="ime_mode_active_url" style="ime-mode: active;"/><br/>
+ <input type="text" id="ime_mode_inactive_url" style="ime-mode: inactive;"/><br/>
+ <input type="text" id="ime_mode_disabled_url" style="ime-mode: disabled;"/><br/>
+
+ <input type="text" id="ime_mode_auto_email" style="ime-mode: auto;"/><br/>
+ <input type="text" id="ime_mode_normal_email" style="ime-mode: normal;"/><br/>
+ <input type="text" id="ime_mode_active_email" style="ime-mode: active;"/><br/>
+ <input type="text" id="ime_mode_inactive_email" style="ime-mode: inactive;"/><br/>
+ <input type="text" id="ime_mode_disabled_email" style="ime-mode: disabled;"/><br/>
+
+ <input type="text" id="ime_mode_auto_search" style="ime-mode: auto;"/><br/>
+ <input type="text" id="ime_mode_normal_search" style="ime-mode: normal;"/><br/>
+ <input type="text" id="ime_mode_active_search" style="ime-mode: active;"/><br/>
+ <input type="text" id="ime_mode_inactive_search" style="ime-mode: inactive;"/><br/>
+ <input type="text" id="ime_mode_disabled_search" style="ime-mode: disabled;"/><br/>
+
+ <input type="text" id="ime_mode_auto_tel" style="ime-mode: auto;"/><br/>
+ <input type="text" id="ime_mode_normal_tel" style="ime-mode: normal;"/><br/>
+ <input type="text" id="ime_mode_active_tel" style="ime-mode: active;"/><br/>
+ <input type="text" id="ime_mode_inactive_tel" style="ime-mode: inactive;"/><br/>
+ <input type="text" id="ime_mode_disabled_tel" style="ime-mode: disabled;"/><br/>
+
+ <input type="text" id="ime_mode_auto_number" style="ime-mode: auto;"/><br/>
+ <input type="text" id="ime_mode_normal_number" style="ime-mode: normal;"/><br/>
+ <input type="text" id="ime_mode_active_number" style="ime-mode: active;"/><br/>
+ <input type="text" id="ime_mode_inactive_number" style="ime-mode: inactive;"/><br/>
+ <input type="text" id="ime_mode_disabled_number" style="ime-mode: disabled;"/><br/>
+
+ <input type="password" id="ime_mode_auto_p" style="ime-mode: auto;"/><br/>
+ <input type="password" id="ime_mode_normal_p" style="ime-mode: normal;"/><br/>
+ <input type="password" id="ime_mode_active_p" style="ime-mode: active;"/><br/>
+ <input type="password" id="ime_mode_inactive_p" style="ime-mode: inactive;"/><br/>
+ <input type="password" id="ime_mode_disabled_p" style="ime-mode: disabled;"/><br/>
+ <textarea id="ime_mode_auto_t" style="ime-mode: auto;">textarea</textarea><br/>
+ <textarea id="ime_mode_normal_t" style="ime-mode: normal;">textarea</textarea><br/>
+ <textarea id="ime_mode_active_t" style="ime-mode: active;">textarea</textarea><br/>
+ <textarea id="ime_mode_inactive_t" style="ime-mode: inactive;">textarea</textarea><br/>
+ <textarea id="ime_mode_disabled_t" style="ime-mode: disabled;">textarea</textarea><br/>
+
+ <!-- plugin -->
+ <object type="application/x-test" id="plugin"></object><br/>
+
+ <!-- contenteditable editor -->
+ <div id="contenteditableEditor" contenteditable="true"></div>
+
+ <!-- designMode editor -->
+ <iframe id="designModeEditor"
+ onload="document.getElementById('designModeEditor').contentDocument.designMode = 'on';"
+ src="data:text/html,<html><body></body></html>"></iframe><br/>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+SimpleTest.waitForExplicitFinish();
+
+function hitEventLoop(aFunc, aTimes) {
+ if (--aTimes) {
+ setTimeout(hitEventLoop, 0, aFunc, aTimes);
+ } else {
+ setTimeout(aFunc, 20);
+ }
+}
+
+var gUtils = window.windowUtils;
+var gFM = Services.focus;
+const kIMEEnabledSupported = navigator.platform.indexOf("Mac") == 0 ||
+ navigator.platform.indexOf("Win") == 0 ||
+ navigator.platform.indexOf("Linux") == 0;
+
+// We support to control IME open state on Windows and Mac actually. However,
+// we cannot test it on Mac if the current keyboard layout is not CJK. And also
+// we cannot test it on Win32 if the system didn't be installed IME. So,
+// currently we should not run the open state testing.
+const kIMEOpenSupported = false;
+
+function runBasicTest(aIsEditable, aInDesignMode, aDescription) {
+ var onIMEFocusBlurHandler = null;
+ var TIPCallback = function(aTIP, aNotification) {
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ case "notify-focus":
+ case "notify-blur":
+ if (onIMEFocusBlurHandler) {
+ onIMEFocusBlurHandler(aNotification);
+ }
+ break;
+ }
+ return true;
+ };
+
+ var TIP = Cc["@mozilla.org/text-input-processor;1"]
+ .createInstance(Ci.nsITextInputProcessor);
+ if (!TIP.beginInputTransactionForTests(window, TIPCallback)) {
+ ok(false, "runBasicTest(): failed to begin input transaction");
+ return;
+ }
+
+ function test(aTest) {
+ function moveFocus(aTestInner, aFocusEventHandler) {
+ if (aInDesignMode) {
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ } else if (aIsEditable) {
+ document.getElementById("display").focus();
+ } else if (aTest.expectedEnabled == gUtils.IME_STATUS_ENABLED) {
+ document.getElementById("password").focus();
+ } else {
+ document.getElementById("text").focus();
+ }
+ var previousFocusedElement = gFM.focusedElement;
+ var element = document.getElementById(aTest.id);
+ var focusEventTarget = element;
+ if (element.contentDocument) {
+ focusEventTarget = element.contentDocument;
+ element = element.contentDocument.documentElement;
+ }
+
+ focusEventTarget.addEventListener("focus", aFocusEventHandler, true);
+ onIMEFocusBlurHandler = aFocusEventHandler;
+
+ element.focus();
+
+ focusEventTarget.removeEventListener("focus", aFocusEventHandler, true);
+ onIMEFocusBlurHandler = null;
+
+ var focusedElement = gFM.focusedElement;
+ // FIXME(emilio, bug 981248): This is needed just for <input type=number>
+ while (focusedElement && focusedElement.isNativeAnonymous) {
+ focusedElement = focusedElement.parentNode;
+ }
+ if (aTest.focusable) {
+ is(focusedElement, element,
+ aDescription + ": " + aTest.description + ", focus didn't move");
+ return (element == focusedElement);
+ }
+ is(focusedElement, previousFocusedElement,
+ aDescription + ": " + aTest.description + ", focus moved as unexpected");
+ return (previousFocusedElement == focusedElement);
+ }
+
+ function testOpened(aTestInner, aOpened) {
+ document.getElementById("text").focus();
+ gUtils.IMEIsOpen = aOpened;
+ if (!moveFocus(aTest)) {
+ return;
+ }
+ var message = aDescription + ": " + aTest.description +
+ ", wrong opened state";
+ is(gUtils.IMEIsOpen,
+ aTest.changeOpened ? aTest.expectedOpened : aOpened, message);
+ }
+
+ // IME Enabled state testing
+ var enabled = gUtils.IME_STATUS_ENABLED;
+ if (kIMEEnabledSupported) {
+ var focusEventCount = 0;
+ var IMEReceivesFocus = 0;
+ var IMEReceivesBlur = 0;
+ var IMEHasFocus = false;
+
+ function onFocus(aEvent) {
+ switch (aEvent.type) {
+ case "focus":
+ focusEventCount++;
+ is(gUtils.IMEStatus, aTest.expectedEnabled,
+ aDescription + ": " + aTest.description + ", wrong enabled state at focus event");
+ break;
+ case "notify-focus":
+ IMEReceivesFocus++;
+ IMEHasFocus = true;
+ is(gUtils.IMEStatus, aTest.expectedEnabled,
+ aDescription + ": " + aTest.description +
+ ", IME should receive a focus notification after IME state is updated");
+ break;
+ case "notify-blur":
+ IMEReceivesBlur++;
+ IMEHasFocus = false;
+ var changingStatus = !(aIsEditable && aTest.expectedEnabled == gUtils.IME_STATUS_ENABLED);
+ if (aTest.toDesignModeEditor) {
+ is(gUtils.IME_STATUS_ENABLED, aTest.expectedEnabled,
+ aDescription + ": " + aTest.description +
+ ", IME should receive a blur notification after IME state is updated");
+ } else if (changingStatus) {
+ isnot(gUtils.IMEStatus, aTest.expectedEnabled,
+ aDescription + ": " + aTest.description +
+ ", IME should receive a blur notification before IME state is updated");
+ } else {
+ is(gUtils.IMEStatus, aTest.expectedEnabled,
+ aDescription + ": " + aTest.description +
+ ", IME should receive a blur notification and its context has expected IME state if the state isn't being changed");
+ }
+ break;
+ }
+ }
+
+ if (!moveFocus(aTest, onFocus)) {
+ return;
+ }
+
+ if (aTest.focusable) {
+ if (!aTest.focusEventNotFired) {
+ ok(focusEventCount > 0,
+ aDescription + ": " + aTest.description + ", focus event is never fired");
+ if (aTest.expectedEnabled == gUtils.IME_STATUS_ENABLED || aTest.expectedEnabled == gUtils.IME_STATUS_PASSWORD) {
+ ok(IMEReceivesFocus > 0,
+ aDescription + ": " + aTest.description + ", IME should receive a focus notification");
+ if (aInDesignMode && !aTest.toDesignModeEditor) {
+ is(IMEReceivesBlur, 0,
+ aDescription + ": " + aTest.description +
+ ", IME shouldn't receive a blur notification in designMode since focus isn't moved from another editor");
+ } else {
+ ok(IMEReceivesBlur > 0,
+ aDescription + ": " + aTest.description +
+ ", IME should receive a blur notification for the previous focused editor");
+ }
+ ok(IMEHasFocus,
+ aDescription + ": " + aTest.description +
+ ", IME should have focus right now");
+ } else {
+ is(IMEReceivesFocus, 0,
+ aDescription + ": " + aTest.description +
+ ", IME shouldn't receive a focus notification");
+ ok(IMEReceivesBlur > 0,
+ aDescription + ": " + aTest.description +
+ ", IME should receive a blur notification");
+ ok(!IMEHasFocus,
+ aDescription + ": " + aTest.description +
+ ", IME shouldn't have focus right now");
+ }
+ } else {
+ todo(focusEventCount > 0,
+ aDescription + ": " + aTest.description + ", focus event should be fired");
+ }
+ } else {
+ is(IMEReceivesFocus, 0,
+ aDescription + ": " + aTest.description +
+ ", IME shouldn't receive a focus notification at testing non-focusable element");
+ is(IMEReceivesBlur, 0,
+ aDescription + ": " + aTest.description +
+ ", IME shouldn't receive a blur notification at testing non-focusable element");
+ }
+
+ enabled = gUtils.IMEStatus;
+ var inputtype = gUtils.focusedInputType;
+ is(enabled, aTest.expectedEnabled,
+ aDescription + ": " + aTest.description + ", wrong enabled state");
+ if (aTest.expectedType && !aInDesignMode) {
+ is(inputtype, aTest.expectedType,
+ aDescription + ": " + aTest.description + ", wrong input type");
+ } else if (aInDesignMode) {
+ is(inputtype, "",
+ aDescription + ": " + aTest.description + ", wrong input type");
+ }
+ }
+
+ if (!kIMEOpenSupported || enabled != gUtils.IME_STATUS_ENABLED ||
+ aTest.expectedEnabled != gUtils.IME_STATUS_ENABLED) {
+ return;
+ }
+
+ // IME Open state testing
+ testOpened(aTest, false);
+ testOpened(aTest, true);
+ }
+
+ if (kIMEEnabledSupported) {
+ // make sure there is an active element
+ document.getElementById("text").focus();
+ document.activeElement.blur();
+ is(gUtils.IMEStatus,
+ aInDesignMode ? gUtils.IME_STATUS_ENABLED : gUtils.IME_STATUS_DISABLED,
+ aDescription + ": unexpected enabled state when no element has focus");
+ }
+
+ // Form controls except text editable elements are "disable" in normal
+ // condition, however, if they are editable, they are "enabled".
+ // XXX Probably there are some bugs: If the form controls editable, they
+ // shouldn't be focusable.
+ const kEnabledStateOnNonEditableElement =
+ (aInDesignMode || aIsEditable) ? gUtils.IME_STATUS_ENABLED :
+ gUtils.IME_STATUS_DISABLED;
+ const kEnabledStateOnPasswordField =
+ aInDesignMode ? gUtils.IME_STATUS_ENABLED : gUtils.IME_STATUS_PASSWORD;
+ const kEnabledStateOnReadonlyField =
+ aInDesignMode ? gUtils.IME_STATUS_ENABLED : gUtils.IME_STATUS_DISABLED;
+ const kTests = [
+ { id: "text",
+ description: "input[type=text]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ expectedType: "text" },
+ { id: "text_readonly",
+ description: "input[type=text][readonly]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnReadonlyField },
+ { id: "password",
+ description: "input[type=password]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnPasswordField,
+ expectedType: "password" },
+ { id: "password_readonly",
+ description: "input[type=password][readonly]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnReadonlyField },
+ { id: "checkbox",
+ description: "input[type=checkbox]",
+ focusable: !aInDesignMode,
+ focusEventNotFired: aIsEditable && !aInDesignMode,
+ expectedEnabled: kEnabledStateOnNonEditableElement },
+ { id: "radio",
+ description: "input[type=radio]",
+ focusable: !aInDesignMode,
+ focusEventNotFired: aIsEditable && !aInDesignMode,
+ expectedEnabled: kEnabledStateOnNonEditableElement },
+ { id: "submit",
+ description: "input[type=submit]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnNonEditableElement },
+ { id: "reset",
+ description: "input[type=reset]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnNonEditableElement },
+ { id: "file",
+ description: "input[type=file]",
+ focusable: !aInDesignMode,
+ focusEventNotFired: aIsEditable && !aInDesignMode,
+ expectedEnabled: kEnabledStateOnNonEditableElement },
+ { id: "button",
+ description: "input[type=button]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnNonEditableElement },
+ { id: "image",
+ description: "input[type=image]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnNonEditableElement },
+ { id: "url",
+ description: "input[type=url]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ expectedType: "url" },
+ { id: "email",
+ description: "input[type=email]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ expectedType: "email" },
+ { id: "search",
+ description: "input[type=search]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ expectedType: "search" },
+ { id: "tel",
+ description: "input[type=tel]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ expectedType: "tel" },
+ { id: "number",
+ description: "input[type=number]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ expectedType: "number" },
+
+ // form controls
+ { id: "button",
+ description: "button",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnNonEditableElement },
+ { id: "textarea",
+ description: "textarea",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "textarea_readonly",
+ description: "textarea[readonly]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnReadonlyField },
+ { id: "select",
+ description: "select (dropdown list)",
+ focusable: !aInDesignMode,
+ focusEventNotFired: aIsEditable && !aInDesignMode,
+ expectedEnabled: kEnabledStateOnNonEditableElement },
+ { id: "select_multiple",
+ description: "select (list box)",
+ focusable: !aInDesignMode,
+ focusEventNotFired: aIsEditable && !aInDesignMode,
+ expectedEnabled: kEnabledStateOnNonEditableElement },
+
+ // a element
+ { id: "a_href",
+ description: "a[href]",
+ focusable: !aIsEditable && !aInDesignMode,
+ expectedEnabled: kEnabledStateOnNonEditableElement },
+
+ // ime-mode
+ { id: "ime_mode_auto",
+ description: "input[type=text][style=\"ime-mode: auto;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_normal",
+ description: "input[type=text][style=\"ime-mode: normal;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_active",
+ description: "input[type=text][style=\"ime-mode: active;\"]",
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ focusable: !aInDesignMode,
+ changeOpened: true, expectedOpened: true },
+ { id: "ime_mode_inactive",
+ description: "input[type=text][style=\"ime-mode: inactive;\"]",
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ focusable: !aInDesignMode,
+ changeOpened: true, expectedOpened: false },
+ { id: "ime_mode_disabled",
+ description: "input[type=text][style=\"ime-mode: disabled;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnPasswordField },
+
+ { id: "ime_mode_auto_url",
+ description: "input[type=url][style=\"ime-mode: auto;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_normal_url",
+ description: "input[type=url][style=\"ime-mode: normal;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_active_url",
+ description: "input[type=url][style=\"ime-mode: active;\"]",
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ focusable: !aInDesignMode,
+ changeOpened: true, expectedOpened: true },
+ { id: "ime_mode_inactive_url",
+ description: "input[type=url][style=\"ime-mode: inactive;\"]",
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ focusable: !aInDesignMode,
+ changeOpened: true, expectedOpened: false },
+ { id: "ime_mode_disabled_url",
+ description: "input[type=url][style=\"ime-mode: disabled;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnPasswordField },
+
+ { id: "ime_mode_auto_email",
+ description: "input[type=email][style=\"ime-mode: auto;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_normal_email",
+ description: "input[type=email][style=\"ime-mode: normal;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_active_email",
+ description: "input[type=email][style=\"ime-mode: active;\"]",
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ focusable: !aInDesignMode,
+ changeOpened: true, expectedOpened: true },
+ { id: "ime_mode_inactive_email",
+ description: "input[type=email][style=\"ime-mode: inactive;\"]",
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ focusable: !aInDesignMode,
+ changeOpened: true, expectedOpened: false },
+ { id: "ime_mode_disabled_email",
+ description: "input[type=email][style=\"ime-mode: disabled;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnPasswordField },
+
+ { id: "ime_mode_auto_search",
+ description: "input[type=search][style=\"ime-mode: auto;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_normal_search",
+ description: "input[type=search][style=\"ime-mode: normal;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_active_search",
+ description: "input[type=search][style=\"ime-mode: active;\"]",
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ focusable: !aInDesignMode,
+ changeOpened: true, expectedOpened: true },
+ { id: "ime_mode_inactive_search",
+ description: "input[type=search][style=\"ime-mode: inactive;\"]",
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ focusable: !aInDesignMode,
+ changeOpened: true, expectedOpened: false },
+ { id: "ime_mode_disabled_search",
+ description: "input[type=search][style=\"ime-mode: disabled;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnPasswordField },
+
+ { id: "ime_mode_auto_tel",
+ description: "input[type=tel][style=\"ime-mode: auto;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_normal_tel",
+ description: "input[type=tel][style=\"ime-mode: normal;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_active_tel",
+ description: "input[type=tel][style=\"ime-mode: active;\"]",
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ focusable: !aInDesignMode,
+ changeOpened: true, expectedOpened: true },
+ { id: "ime_mode_inactive_tel",
+ description: "input[type=tel][style=\"ime-mode: inactive;\"]",
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ focusable: !aInDesignMode,
+ changeOpened: true, expectedOpened: false },
+ { id: "ime_mode_disabled_tel",
+ description: "input[type=tel][style=\"ime-mode: disabled;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnPasswordField },
+
+ { id: "ime_mode_auto_number",
+ description: "input[type=number][style=\"ime-mode: auto;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_normal_number",
+ description: "input[type=number][style=\"ime-mode: normal;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_active_number",
+ description: "input[type=number][style=\"ime-mode: active;\"]",
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ focusable: !aInDesignMode,
+ changeOpened: true, expectedOpened: true },
+ { id: "ime_mode_inactive_number",
+ description: "input[type=number][style=\"ime-mode: inactive;\"]",
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ focusable: !aInDesignMode,
+ changeOpened: true, expectedOpened: false },
+ { id: "ime_mode_disabled_number",
+ description: "input[type=number][style=\"ime-mode: disabled;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnPasswordField },
+
+ { id: "ime_mode_auto_p",
+ description: "input[type=password][style=\"ime-mode: auto;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnPasswordField },
+ { id: "ime_mode_normal_p",
+ description: "input[type=password][style=\"ime-mode: normal;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_active_p",
+ description: "input[type=password][style=\"ime-mode: active;\"]",
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ focusable: !aInDesignMode,
+ changeOpened: true, expectedOpened: true },
+ { id: "ime_mode_inactive_p",
+ description: "input[type=password][style=\"ime-mode: inactive;\"]",
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ focusable: !aInDesignMode,
+ changeOpened: true, expectedOpened: false },
+ { id: "ime_mode_disabled_p",
+ description: "input[type=password][style=\"ime-mode: disabled;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnPasswordField },
+ { id: "ime_mode_auto",
+ description: "textarea[style=\"ime-mode: auto;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_normal",
+ description: "textarea[style=\"ime-mode: normal;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "ime_mode_active",
+ description: "textarea[style=\"ime-mode: active;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ changeOpened: true, expectedOpened: true },
+ { id: "ime_mode_inactive",
+ description: "textarea[style=\"ime-mode: inactive;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED,
+ changeOpened: true, expectedOpened: false },
+ { id: "ime_mode_disabled",
+ description: "textarea[style=\"ime-mode: disabled;\"]",
+ focusable: !aInDesignMode,
+ expectedEnabled: kEnabledStateOnPasswordField },
+
+ // HTML editors
+ { id: "contenteditableEditor",
+ description: "div[contenteditable=\"true\"]",
+ focusable: !aIsEditable && !aInDesignMode,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ { id: "designModeEditor",
+ description: "designMode editor",
+ focusable: true,
+ toDesignModeEditor: true,
+ expectedEnabled: gUtils.IME_STATUS_ENABLED },
+ ];
+
+ for (var i = 0; i < kTests.length; i++) {
+ test(kTests[i]);
+ }
+}
+
+function runPluginTest() {
+ if (!kIMEEnabledSupported) {
+ return;
+ }
+
+ var plugin = document.getElementById("plugin");
+
+ // Plugins are not supported and their elements should not accept focus;
+ // therefore, IME should not enable when we play with it.
+
+ document.activeElement.blur();
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+ "runPluginTest: unexpected enabled state when no element has focus");
+
+ plugin.focus();
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+ "runPluginTest: unexpected enabled state when attempting to give focus to plugin");
+
+ plugin.blur();
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+ "runPluginTest: unexpected enabled state after unfocusing plugin");
+
+ plugin.focus();
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+ "runPluginTest: unexpected enabled state when attempting to give focus to plugin #2");
+
+ var parent = plugin.parentNode;
+ parent.removeChild(plugin);
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+ "runPluginTest: unexpected enabled state when plugin is removed from tree");
+
+ document.getElementById("text").focus();
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+ "runPluginTest: unexpected enabled state when input[type=text] has focus");
+}
+
+function runTypeChangingTest() {
+ if (!kIMEEnabledSupported)
+ return;
+
+ const kInputControls = [
+ { id: "text",
+ type: "text", expected: gUtils.IME_STATUS_ENABLED,
+ description: "[type=\"text\"]" },
+ { id: "text_readonly",
+ type: "text", expected: gUtils.IME_STATUS_DISABLED, isReadonly: true,
+ description: "[type=\"text\"][readonly]" },
+ { id: "password",
+ type: "password", expected: gUtils.IME_STATUS_PASSWORD,
+ description: "[type=\"password\"]" },
+ { id: "password_readonly",
+ type: "password", expected: gUtils.IME_STATUS_DISABLED, isReadonly: true,
+ description: "[type=\"password\"][readonly]" },
+ { id: "checkbox",
+ type: "checkbox", expected: gUtils.IME_STATUS_DISABLED,
+ description: "[type=\"checkbox\"]" },
+ { id: "radio",
+ type: "radio", expected: gUtils.IME_STATUS_DISABLED,
+ description: "[type=\"radio\"]" },
+ { id: "submit",
+ type: "submit", expected: gUtils.IME_STATUS_DISABLED,
+ description: "[type=\"submit\"]" },
+ { id: "reset",
+ type: "reset", expected: gUtils.IME_STATUS_DISABLED,
+ description: "[type=\"reset\"]" },
+ { id: "file",
+ type: "file", expected: gUtils.IME_STATUS_DISABLED,
+ description: "[type=\"file\"]" },
+ { id: "ibutton",
+ type: "button", expected: gUtils.IME_STATUS_DISABLED,
+ description: "[type=\"button\"]" },
+ { id: "image",
+ type: "image", expected: gUtils.IME_STATUS_DISABLED,
+ description: "[type=\"image\"]" },
+ { id: "url",
+ type: "url", expected: gUtils.IME_STATUS_ENABLED,
+ description: "[type=\"url\"]" },
+ { id: "email",
+ type: "email", expected: gUtils.IME_STATUS_ENABLED,
+ description: "[type=\"email\"]" },
+ { id: "search",
+ type: "search", expected: gUtils.IME_STATUS_ENABLED,
+ description: "[type=\"search\"]" },
+ { id: "tel",
+ type: "tel", expected: gUtils.IME_STATUS_ENABLED,
+ description: "[type=\"tel\"]" },
+ { id: "number",
+ type: "number", expected: gUtils.IME_STATUS_ENABLED,
+ description: "[type=\"number\"]" },
+ { id: "ime_mode_auto",
+ type: "text", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"text\"][ime-mode: auto;]" },
+ { id: "ime_mode_normal",
+ type: "text", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"text\"][ime-mode: normal;]" },
+ { id: "ime_mode_active",
+ type: "text", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"text\"][ime-mode: active;]" },
+ { id: "ime_mode_inactive",
+ type: "text", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"text\"][ime-mode: inactive;]" },
+ { id: "ime_mode_disabled",
+ type: "text", expected: gUtils.IME_STATUS_PASSWORD, imeMode: true,
+ description: "[type=\"text\"][ime-mode: disabled;]" },
+
+ { id: "ime_mode_auto_url",
+ type: "url", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"url\"][ime-mode: auto;]" },
+ { id: "ime_mode_normal_url",
+ type: "url", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"url\"][ime-mode: normal;]" },
+ { id: "ime_mode_active_url",
+ type: "url", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"url\"][ime-mode: active;]" },
+ { id: "ime_mode_inactive_url",
+ type: "url", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"url\"][ime-mode: inactive;]" },
+ { id: "ime_mode_disabled_url",
+ type: "url", expected: gUtils.IME_STATUS_PASSWORD, imeMode: true,
+ description: "[type=\"url\"][ime-mode: disabled;]" },
+
+ { id: "ime_mode_auto_email",
+ type: "email", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"email\"][ime-mode: auto;]" },
+ { id: "ime_mode_normal_email",
+ type: "email", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"email\"][ime-mode: normal;]" },
+ { id: "ime_mode_active_email",
+ type: "email", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"email\"][ime-mode: active;]" },
+ { id: "ime_mode_inactive_email",
+ type: "email", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"email\"][ime-mode: inactive;]" },
+ { id: "ime_mode_disabled_email",
+ type: "email", expected: gUtils.IME_STATUS_PASSWORD, imeMode: true,
+ description: "[type=\"email\"][ime-mode: disabled;]" },
+
+ { id: "ime_mode_auto_search",
+ type: "search", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"search\"][ime-mode: auto;]" },
+ { id: "ime_mode_normal_search",
+ type: "search", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"search\"][ime-mode: normal;]" },
+ { id: "ime_mode_active_search",
+ type: "search", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"search\"][ime-mode: active;]" },
+ { id: "ime_mode_inactive_search",
+ type: "search", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"search\"][ime-mode: inactive;]" },
+ { id: "ime_mode_disabled_search",
+ type: "search", expected: gUtils.IME_STATUS_PASSWORD, imeMode: true,
+ description: "[type=\"search\"][ime-mode: disabled;]" },
+
+ { id: "ime_mode_auto_tel",
+ type: "tel", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"tel\"][ime-mode: auto;]" },
+ { id: "ime_mode_normal_tel",
+ type: "tel", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"tel\"][ime-mode: normal;]" },
+ { id: "ime_mode_active_tel",
+ type: "tel", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"tel\"][ime-mode: active;]" },
+ { id: "ime_mode_inactive_tel",
+ type: "tel", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"tel\"][ime-mode: inactive;]" },
+ { id: "ime_mode_disabled_tel",
+ type: "tel", expected: gUtils.IME_STATUS_PASSWORD, imeMode: true,
+ description: "[type=\"tel\"][ime-mode: disabled;]" },
+
+ { id: "ime_mode_auto_number",
+ type: "number", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"number\"][ime-mode: auto;]" },
+ { id: "ime_mode_normal_number",
+ type: "number", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"number\"][ime-mode: normal;]" },
+ { id: "ime_mode_active_number",
+ type: "number", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"number\"][ime-mode: active;]" },
+ { id: "ime_mode_inactive_number",
+ type: "number", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"number\"][ime-mode: inactive;]" },
+ { id: "ime_mode_disabled_number",
+ type: "number", expected: gUtils.IME_STATUS_PASSWORD, imeMode: true,
+ description: "[type=\"number\"][ime-mode: disabled;]" },
+
+ { id: "ime_mode_auto_p",
+ type: "password", expected: gUtils.IME_STATUS_PASSWORD, imeMode: true,
+ description: "[type=\"password\"][ime-mode: auto;]" },
+ { id: "ime_mode_normal_p",
+ type: "password", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"password\"][ime-mode: normal;]" },
+ { id: "ime_mode_active_p",
+ type: "password", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"password\"][ime-mode: active;]" },
+ { id: "ime_mode_inactive_p",
+ type: "password", expected: gUtils.IME_STATUS_ENABLED, imeMode: true,
+ description: "[type=\"password\"][ime-mode: inactive;]" },
+ { id: "ime_mode_disabled_p",
+ type: "password", expected: gUtils.IME_STATUS_PASSWORD, imeMode: true,
+ description: "[type=\"password\"][ime-mode: disabled;]" },
+ ];
+
+ const kInputTypes = [
+ { type: "", expected: gUtils.IME_STATUS_ENABLED },
+ { type: "text", expected: gUtils.IME_STATUS_ENABLED },
+ { type: "password", expected: gUtils.IME_STATUS_PASSWORD },
+ { type: "checkbox", expected: gUtils.IME_STATUS_DISABLED },
+ { type: "radio", expected: gUtils.IME_STATUS_DISABLED },
+ { type: "submit", expected: gUtils.IME_STATUS_DISABLED },
+ { type: "reset", expected: gUtils.IME_STATUS_DISABLED },
+ { type: "file", expected: gUtils.IME_STATUS_DISABLED },
+ { type: "button", expected: gUtils.IME_STATUS_DISABLED },
+ { type: "image", expected: gUtils.IME_STATUS_DISABLED },
+ { type: "url", expected: gUtils.IME_STATUS_ENABLED },
+ { type: "email", expected: gUtils.IME_STATUS_ENABLED },
+ { type: "search", expected: gUtils.IME_STATUS_ENABLED },
+ { type: "tel", expected: gUtils.IME_STATUS_ENABLED },
+ { type: "number", expected: gUtils.IME_STATUS_ENABLED },
+ ];
+
+ function getExpectedIMEEnabled(aNewType, aInputControl) {
+ if (aNewType.expected == gUtils.IME_STATUS_DISABLED ||
+ aInputControl.isReadonly)
+ return gUtils.IME_STATUS_DISABLED;
+ return aInputControl.imeMode ? aInputControl.expected : aNewType.expected;
+ }
+
+ const kOpenedState = [ true, false ];
+
+ for (var i = 0; i < kOpenedState.length; i++) {
+ const kOpened = kOpenedState[i];
+ for (var j = 0; j < kInputControls.length; j++) {
+ const kInput = kInputControls[j];
+ var e = document.getElementById(kInput.id);
+ e.focus();
+ for (var k = 0; k < kInputTypes.length; k++) {
+ const kType = kInputTypes[k];
+ var typeChangingDescription =
+ "\"" + e.getAttribute("type") + "\" to \"" + kInput.type + "\"";
+ e.setAttribute("type", kInput.type);
+ is(gUtils.IMEStatus, kInput.expected,
+ "type attr changing test (IMEStatus): " + typeChangingDescription +
+ " (" + kInput.description + ")");
+ is(gUtils.focusedInputType, kInput.type,
+ "type attr changing test (type): " + typeChangingDescription +
+ " (" + kInput.description + ")");
+
+ const kTestOpenState = kIMEOpenSupported &&
+ gUtils.IMEStatus == gUtils.IME_STATUS_ENABLED &&
+ getExpectedIMEEnabled(kType, kInput) == gUtils.IME_STATUS_ENABLED;
+ if (kTestOpenState) {
+ gUtils.IMEIsOpen = kOpened;
+ }
+
+ typeChangingDescription =
+ "\"" + e.getAttribute("type") + "\" to \"" + kType.type + "\"";
+ if (kType.type != "")
+ e.setAttribute("type", kType.type);
+ else
+ e.removeAttribute("type");
+
+ is(gUtils.IMEStatus, getExpectedIMEEnabled(kType, kInput),
+ "type attr changing test (IMEStatus): " + typeChangingDescription +
+ " (" + kInput.description + ")");
+ is(gUtils.focusedInputType, kType.type,
+ "type attr changing test (type): " + typeChangingDescription +
+ " (" + kInput.description + ")");
+ if (kTestOpenState && gUtils.IMEStatus == gUtils.IME_STATUS_ENABLED) {
+ is(gUtils.IMEIsOpen, kOpened,
+ "type attr changing test (open state is changed): " +
+ typeChangingDescription + " (" + kInput.description + ")");
+ }
+ }
+ // reset the type to default
+ e.setAttribute("type", kInput.type);
+ }
+ if (!kIMEOpenSupported)
+ break;
+ }
+}
+
+function runReadonlyChangingTest() {
+ if (!kIMEEnabledSupported)
+ return;
+
+ const kInputControls = [
+ { id: "text",
+ type: "text", expected: gUtils.IME_STATUS_ENABLED },
+ { id: "password",
+ type: "password", expected: gUtils.IME_STATUS_PASSWORD },
+ { id: "url",
+ type: "url", expected: gUtils.IME_STATUS_ENABLED },
+ { id: "email",
+ type: "email", expected: gUtils.IME_STATUS_ENABLED },
+ { id: "search",
+ type: "search", expected: gUtils.IME_STATUS_ENABLED },
+ { id: "tel",
+ type: "tel", expected: gUtils.IME_STATUS_ENABLED },
+ { id: "number",
+ type: "number", expected: gUtils.IME_STATUS_ENABLED },
+ { id: "textarea",
+ type: "textarea", expected: gUtils.IME_STATUS_ENABLED },
+ ];
+ const kOpenedState = [ true, false ];
+
+ for (var i = 0; i < kOpenedState.length; i++) {
+ const kOpened = kOpenedState[i];
+ for (var j = 0; j < kInputControls.length; j++) {
+ const kInput = kInputControls[j];
+ var e = document.getElementById(kInput.id);
+ e.focus();
+ if (kIMEOpenSupported && gUtils.IMEStatus == gUtils.IME_STATUS_ENABLED) {
+ gUtils.IMEIsOpen = kOpened;
+ }
+ e.setAttribute("readonly", "readonly");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+ "readonly attr setting test: type=" + kInput.type);
+ e.removeAttribute("readonly");
+ is(gUtils.IMEStatus, kInput.expected,
+ "readonly attr removing test: type=" + kInput.type);
+ if (kIMEOpenSupported && gUtils.IMEStatus == gUtils.IME_STATUS_ENABLED) {
+ is(gUtils.IMEIsOpen, kOpened,
+ "readonly attr removing test (open state is changed): type=" +
+ kInput.type);
+ }
+ }
+ if (!kIMEOpenSupported)
+ break;
+ }
+}
+
+function runComplexContenteditableTests() {
+ if (!kIMEEnabledSupported) {
+ return;
+ }
+
+ var description = "runReadonlyChangingOnContenteditable: ";
+
+ var container = document.getElementById("display");
+ var button = document.getElementById("button");
+
+ // the editor has focus directly.
+ container.setAttribute("contenteditable", "true");
+ container.focus();
+
+ is(gFM.focusedElement, container,
+ description + "The editor doesn't get focus");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+ description + "IME isn't enabled on HTML editor");
+ const kReadonly = Ci.nsIEditor.eEditorReadonlyMask;
+ var editor = window.docShell.editor;
+ var flags = editor.flags;
+ editor.flags = flags | kReadonly;
+ is(gFM.focusedElement, container,
+ description + "The editor loses focus by flag change");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+ description + "IME is still enabled on readonly HTML editor");
+ editor.flags = flags;
+ is(gFM.focusedElement, container,
+ description + "The editor loses focus by flag change #2");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+ description + "IME is still disabled, the editor isn't readonly now");
+ container.removeAttribute("contenteditable");
+ todo_is(gFM.focusedElement, null,
+ description + "The container still has focus, the editor has been no editable");
+ todo_is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+ description + "IME is still enabled on the editor, the editor has been no editable");
+
+ // a button which is in the editor has focus
+ button.focus();
+ is(gFM.focusedElement, button,
+ description + "The button doesn't get focus");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+ description + "IME is enabled on the button");
+ container.setAttribute("contenteditable", "true");
+ is(gFM.focusedElement, button,
+ description + "The button loses focus, the container is editable now");
+ todo_is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+ description + "IME is still disabled on the button, the container is editable now");
+ editor = window.docShell.editor;
+ flags = editor.flags;
+ editor.flags = flags | kReadonly;
+ is(gFM.focusedElement, button,
+ description + "The button loses focus by changing editor flags");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+ description + "IME is still enabled on the button, the container is readonly now");
+ editor.flags = flags;
+ is(gFM.focusedElement, button,
+ description + "The button loses focus by changing editor flags #2");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+ description + "IME is still disabled on the button, the container isn't readonly now");
+ container.removeAttribute("contenteditable");
+ is(gFM.focusedElement, button,
+ description + "The button loses focus, the container has been no editable");
+ todo_is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+ description + "IME is still enabled on the button, the container has been no editable");
+
+ description = "testOnIndependentEditor: ";
+ function testOnIndependentEditor(aEditor, aEditorDescription) {
+ var expectedState =
+ aEditor.readOnly ? gUtils.IME_STATUS_DISABLED : gUtils.IME_STATUS_ENABLED;
+ var unexpectedStateDescription =
+ expectedState != gUtils.IME_STATUS_ENABLED ? "enabled" : "disabled";
+ aEditor.focus();
+ is(gFM.focusedElement, aEditor,
+ description + "The " + aEditorDescription + " doesn't get focus");
+ is(gUtils.IMEStatus, expectedState,
+ description + "IME is " + unexpectedStateDescription +
+ " on the " + aEditorDescription);
+ container.setAttribute("contenteditable", "true");
+ is(gFM.focusedElement, aEditor,
+ description + "The " + aEditorDescription +
+ " loses focus, the container is editable now");
+ is(gUtils.IMEStatus, expectedState,
+ description + "IME becomes " + unexpectedStateDescription +
+ " on the " + aEditorDescription + ", the container is editable now");
+ editor = window.docShell.editor;
+ flags = editor.flags;
+ editor.flags = flags | kReadonly;
+ is(gFM.focusedElement, aEditor,
+ description + "The " + aEditorDescription +
+ " loses focus by changing editor flags");
+ is(gUtils.IMEStatus, expectedState,
+ description + "IME becomes " + unexpectedStateDescription + " on the " +
+ aEditorDescription + ", the container is readonly now");
+ editor.flags = flags;
+ is(gFM.focusedElement, aEditor,
+ description + "The " + aEditorDescription +
+ " loses focus by changing editor flags #2");
+ is(gUtils.IMEStatus, expectedState,
+ description + "IME becomes " + unexpectedStateDescription + " on the " +
+ aEditorDescription + ", the container isn't readonly now");
+ container.removeAttribute("contenteditable");
+ is(gFM.focusedElement, aEditor,
+ description + "The " + aEditorDescription +
+ " loses focus, the container has been no editable");
+ is(gUtils.IMEStatus, expectedState,
+ description + "IME becomes " + unexpectedStateDescription + " on the " +
+ aEditorDescription + ", the container has been no editable");
+ }
+
+ // a textarea which is in the editor has focus
+ testOnIndependentEditor(document.getElementById("textarea"),
+ "textarea");
+ // a readonly textarea which is in the editor has focus
+ testOnIndependentEditor(document.getElementById("textarea_readonly"),
+ "textarea[readonly]");
+ // an input field which is in the editor has focus
+ testOnIndependentEditor(document.getElementById("text"),
+ "input[type=\"text\"]");
+ // a readonly input field which is in the editor has focus
+ testOnIndependentEditor(document.getElementById("text_readonly"),
+ "input[type=\"text\"][readonly]");
+
+ description = "testOnOutsideOfEditor: ";
+ function testOnOutsideOfEditor(aFocusNode, aFocusNodeDescription, aEditor) {
+ if (aFocusNode) {
+ aFocusNode.focus();
+ is(gFM.focusedElement, aFocusNode,
+ description + "The " + aFocusNodeDescription + " doesn't get focus");
+ } else {
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ is(gFM.focusedElement, null,
+ description + "Unexpected element has focus");
+ }
+ var expectedState =
+ aFocusNode ? gUtils.IMEStatus : gUtils.IME_STATUS_DISABLED;
+ var unexpectedStateDescription =
+ expectedState != gUtils.IME_STATUS_ENABLED ? "enabled" : "disabled";
+
+ aEditor.setAttribute("contenteditable", "true");
+ is(gFM.focusedElement, aFocusNode,
+ description + "The " + aFocusNodeDescription +
+ " loses focus, a HTML editor is editable now");
+ is(gUtils.IMEStatus, expectedState,
+ description + "IME becomes " + unexpectedStateDescription +
+ " on the " + aFocusNodeDescription +
+ ", the HTML editor is editable now");
+ editor = window.docShell.editor;
+ flags = editor.flags;
+ editor.flags = flags | kReadonly;
+ is(gFM.focusedElement, aFocusNode,
+ description + aFocusNodeDescription +
+ " loses focus by changing HTML editor flags");
+ is(gUtils.IMEStatus, expectedState,
+ description + "IME becomes " + unexpectedStateDescription + " on " +
+ aFocusNodeDescription + ", the HTML editor is readonly now");
+ editor.flags = flags;
+ is(gFM.focusedElement, aFocusNode,
+ description + aFocusNodeDescription +
+ " loses focus by changing HTML editor flags #2");
+ is(gUtils.IMEStatus, expectedState,
+ description + "IME becomes " + unexpectedStateDescription + " on " +
+ aFocusNodeDescription + ", the HTML editor isn't readonly now");
+ container.removeAttribute("contenteditable");
+ is(gFM.focusedElement, aFocusNode,
+ description + aFocusNodeDescription +
+ " loses focus, the HTML editor has been no editable");
+ is(gUtils.IMEStatus, expectedState,
+ description + "IME becomes " + unexpectedStateDescription + " on " +
+ aFocusNodeDescription + ", the HTML editor has been no editable");
+ }
+
+ var div = document.getElementById("contenteditableEditor");
+ // a textarea which is outside of the editor has focus
+ testOnOutsideOfEditor(document.getElementById("textarea"), "textarea", div);
+ // a readonly textarea which is outside of the editor has focus
+ testOnOutsideOfEditor(document.getElementById("textarea_readonly"),
+ "textarea[readonly]", div);
+ // an input field which is outside of the editor has focus
+ testOnOutsideOfEditor(document.getElementById("text"),
+ "input[type=\"text\"]", div);
+ // a readonly input field which outside of the editor has focus
+ testOnOutsideOfEditor(document.getElementById("text_readonly"),
+ "input[type=\"text\"][readonly]", div);
+ // a readonly input field which outside of the editor has focus
+ testOnOutsideOfEditor(document.getElementById("button"), "button", div);
+ // nobody has focus.
+ testOnOutsideOfEditor(null, "nobody", div);
+}
+
+function runEditorFlagChangeTests() {
+ if (!kIMEEnabledSupported) {
+ return;
+ }
+
+ var description = "runEditorFlagChangeTests: ";
+
+ var container = document.getElementById("display");
+
+ // Reset selection from previous tests.
+ window.getSelection().collapse(container, 0);
+
+ // the editor has focus directly.
+ container.setAttribute("contenteditable", "true");
+ container.focus();
+
+ is(gFM.focusedElement, container,
+ description + "The editor doesn't get focus");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+ description + "IME isn't enabled on HTML editor");
+ const kIMEStateChangeFlags = Ci.nsIEditor.eEditorReadonlyMask;
+ var editor = window.docShell.editor;
+ var flags = editor.flags;
+
+ // input characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3078\u3093\u3057\u3093",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ ],
+ },
+ "caret": { "start": 4, "length": 0 },
+ });
+
+ editor.flags &= ~kIMEStateChangeFlags;
+ ok(editor.composing,
+ description + "#1 IME composition was committed unexpectedly");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+ description + "#1 IME isn't enabled on HTML editor");
+
+ editor.flags |=
+ ~(kIMEStateChangeFlags | Ci.nsIEditor.eEditorPasswordMask);
+ ok(editor.composing,
+ description + "#2 IME composition was committed unexpectedly");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+ description + "#2 IME isn't enabled on HTML editor");
+
+ editor.flags = flags;
+ ok(editor.composing,
+ description + "#3 IME composition was committed unexpectedly");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+ description + "#3 IME isn't enabled on HTML editor");
+
+ // cancel the composition
+ synthesizeComposition({ type: "compositioncommit", data: "" });
+
+ container.removeAttribute("contenteditable");
+}
+
+function runEditableSubframeTests() {
+ window.open("window_imestate_iframes.html", "_blank",
+ "width=600,height=600");
+}
+
+function runTestPasswordFieldOnDialog() {
+ if (!kIMEEnabledSupported) {
+ return;
+ }
+
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+
+ var dialog;
+
+ function WindowObserver() {
+ Services.obs.addObserver(this, "domwindowopened");
+ }
+
+ WindowObserver.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe(subject, topic, data) {
+ if (topic === "domwindowopened") {
+ ok(true, "dialog window is created");
+ dialog = subject;
+ dialog.addEventListener("load", onPasswordDialogLoad);
+ }
+ },
+ };
+
+ var observer = new WindowObserver();
+ var arg1 = {}, arg2 = {};
+ Services.prompt.promptPassword(window, "title", "text", arg1, "msg", arg2);
+
+ ok(true, "password dialog was closed");
+
+ Services.obs.removeObserver(observer, "domwindowopened");
+
+ var passwordField;
+
+ function onPasswordDialogLoad() {
+ ok(true, "onPasswordDialogLoad is called");
+ dialog.removeEventListener("load", onPasswordDialogLoad);
+ passwordField = dialog.document.getElementById("password1Textbox");
+ passwordField.addEventListener("focus", onPasswordFieldFocus);
+ }
+
+ function onPasswordFieldFocus() {
+ ok(true, "onPasswordFieldFocus is called");
+ passwordField.removeEventListener("focus", onPasswordFieldFocus);
+ var utils = dialog.windowUtils;
+ is(utils.IMEStatus, utils.IME_STATUS_PASSWORD,
+ "IME isn't disabled on a password field of password dialog");
+ synthesizeKey("VK_ESCAPE", { }, dialog);
+ }
+}
+
+// Bug 580388 and bug 808287
+async function runEditorReframeTests() {
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+
+ var IMEFocus = 0;
+ var IMEBlur = 0;
+ var IMEHasFocus = false;
+ var TIPCallback = function(aTIP, aNotification) {
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ case "notify-focus":
+ IMEFocus++;
+ IMEHasFocus = true;
+ break;
+ case "notify-blur":
+ IMEBlur++;
+ IMEHasFocus = false;
+ break;
+ }
+ return true;
+ };
+
+ var TIP = Cc["@mozilla.org/text-input-processor;1"]
+ .createInstance(Ci.nsITextInputProcessor);
+ if (!TIP.beginInputTransactionForTests(window, TIPCallback)) {
+ ok(false, "runEditorReframeTests(): failed to begin input transaction");
+ return;
+ }
+
+ var input = document.getElementById("text");
+ input.focus();
+
+ is(IMEFocus, 1, "runEditorReframeTests(): IME should receive a focus notification by a call of <input>.focus()");
+ is(IMEBlur, 0, "runEditorReframeTests(): IME shouldn't receive a blur notification by a call of <input>.focus()");
+ ok(IMEHasFocus, "runEditorReframeTests(): IME should have focus because <input>.focus() is called");
+
+ IMEFocus = IMEBlur = 0;
+
+ input.style.overflow = "visible";
+
+ var onInput = function(aEvent) {
+ aEvent.target.style.overflow = "hidden";
+ };
+ input.addEventListener("input", onInput, true);
+
+ var AKey = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP.keydown(AKey);
+ TIP.keyup(AKey);
+
+ await new Promise(r => hitEventLoop(r, 20));
+
+ is(IMEFocus, 0, "runEditorReframeTests(): IME shouldn't receive a focus notification during reframing");
+ is(IMEBlur, 0, "runEditorReframeTests(): IME shouldn't receive a blur notification during reframing");
+ ok(IMEHasFocus, "runEditorReframeTests(): IME must have focus even after reframing");
+
+ var onFocus = function(aEvent) {
+ // Perform a style change and query during focus to trigger reframing
+ input.style.overflow = "visible";
+ synthesizeQuerySelectedText();
+ };
+ input.addEventListener("focus", onFocus);
+ IMEFocus = IMEBlur = 0;
+
+ input.blur();
+ input.focus();
+ TIP.keydown(AKey);
+ TIP.keyup(AKey);
+
+ await new Promise(r => hitEventLoop(r, 20));
+
+ is(IMEFocus, 1, "runEditorReframeTests(): IME should receive a focus notification at focus but shouldn't receive it during reframing");
+ is(IMEBlur, 1, "runEditorReframeTests(): IME should receive a blur notification at blur but shouldn't receive it during reframing");
+ ok(IMEHasFocus, "runEditorReframeTests(): IME sould have focus after reframing during focus");
+
+ input.removeEventListener("input", onInput, true);
+ input.removeEventListener("focus", onFocus);
+
+ input.style.overflow = "visible";
+ input.value = "";
+
+ TIP = null;
+
+ await new Promise(r => hitEventLoop(r, 20));
+}
+
+async function runTests() {
+ if (!kIMEEnabledSupported && !kIMEOpenSupported)
+ return;
+
+ // test for normal contents.
+ runBasicTest(false, false, "Testing of normal contents");
+
+ // test for plugin contents
+ runPluginTest();
+
+ var container = document.getElementById("display");
+ // test for contentEditable="true"
+ container.setAttribute("contenteditable", "true");
+ runBasicTest(true, false, "Testing [contentEditable=\"true\"]");
+
+ // test for contentEditable="false"
+ container.setAttribute("contenteditable", "false");
+ runBasicTest(false, false, "Testing [contentEditable=\"false\"]");
+
+ // test for removing contentEditable
+ container.setAttribute("contenteditable", "true");
+ container.removeAttribute("contenteditable");
+ runBasicTest(false, false, "Testing after contentEditable to be removed");
+
+ // test designMode
+ document.designMode = "on";
+ runBasicTest(true, true, "Testing designMode=\"on\"");
+ document.designMode = "off";
+ document.getElementById("text").focus();
+ runBasicTest(false, false, "Testing designMode=\"off\"");
+
+ // changing input[type] values
+ // XXX currently, type attribute changing doesn't work fine. bug 559728.
+ // runTypeChangingTest();
+
+ // changing readonly attribute
+ runReadonlyChangingTest();
+
+ // complex contenteditable editor's tests
+ runComplexContenteditableTests();
+
+ // test whether the IME state and composition are not changed unexpectedly
+ runEditorFlagChangeTests();
+
+ // test password field on dialog
+ // XXX temporary disable against failure
+ // runTestPasswordFieldOnDialog();
+
+ // Asynchronous tests
+ await runEditorReframeTests();
+
+ // This will call onFinish(), so, this test must be the last.
+ runEditableSubframeTests();
+}
+
+function onFinish() {
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+
+</html>
diff --git a/widget/tests/test_input_events_on_deactive_window.xhtml b/widget/tests/test_input_events_on_deactive_window.xhtml
new file mode 100644
index 0000000000..afe6695687
--- /dev/null
+++ b/widget/tests/test_input_events_on_deactive_window.xhtml
@@ -0,0 +1,235 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Testing composition, text and query content events"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="content" style="display: none">
+</div>
+<p id="display">
+ <textarea id="textarea"></textarea>
+</p>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests, window);
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var textarea = document.getElementById("textarea");
+var otherWindow;
+var timer;
+
+function runTests()
+{
+ textarea.focus();
+ is(Services.focus.focusedElement, textarea, "we're deactive");
+ if (Services.focus.focusedElement != textarea) {
+ SimpleTest.finish();
+ return;
+ }
+
+ otherWindow =
+ window.browsingContext.topChromeWindow.open(
+ "./file_input_events_on_deactive_window.html", "_blank",
+ "chrome,width=100,height=100");
+ ok(otherWindow, "failed to open other window");
+ if (!otherWindow) {
+ SimpleTest.finish();
+ return;
+ }
+
+ SimpleTest.waitForFocus(startTests, otherWindow);
+ otherWindow.focus();
+}
+
+function startTests()
+{
+ clearTimeout(timer);
+ isnot(Services.focus.focusedWindow, window, "we're not deactive");
+ if (Services.focus.focusedWindow == window) {
+ otherWindow.close();
+ SimpleTest.finish();
+ return;
+ }
+
+ var keydownHandled, keypressHandled, keyupHandled, compositionstartHandled,
+ compositionendHandled, compositionupdateHandled, inputHandled;
+
+ function clear()
+ {
+ keydownHandled = false;
+ keypressHandled = false;
+ keyupHandled = false;
+ compositionstartHandled = false;
+ compositionendHandled = false;
+ compositionupdateHandled = false;
+ inputHandled = false;
+ }
+
+ function onEvent(aEvent)
+ {
+ if (aEvent.type == "keydown") {
+ keydownHandled = true;
+ } else if (aEvent.type == "keypress") {
+ keypressHandled = true;
+ } else if (aEvent.type == "keyup") {
+ keyupHandled = true;
+ } else if (aEvent.type == "compositionstart") {
+ compositionstartHandled = true;
+ } else if (aEvent.type == "compositionend") {
+ compositionendHandled = true;
+ } else if (aEvent.type == "compositionupdate") {
+ compositionupdateHandled = true;
+ } else if (aEvent.type == "input") {
+ inputHandled = true;
+ } else {
+ ok(false, "handled unknown event: " + aEvent.type);
+ }
+ }
+
+ textarea.addEventListener("keydown", onEvent);
+ textarea.addEventListener("keypress", onEvent);
+ textarea.addEventListener("keyup", onEvent);
+ textarea.addEventListener("compositionstart", onEvent);
+ textarea.addEventListener("compositionend", onEvent);
+ textarea.addEventListener("compositionupdate", onEvent);
+ textarea.addEventListener("input", onEvent);
+
+ startTestsInternal();
+
+ function startTestsInternal()
+ {
+ // key events
+ function checkKeyEvents(aKeydown, aKeypress, aKeyup, aInput, aDescription)
+ {
+ is(keydownHandled, aKeydown,
+ "keydown event is (not) handled: " + aDescription);
+ is(keypressHandled, aKeypress,
+ "keypress event is (not) handled: " + aDescription);
+ is(keyupHandled, aKeyup,
+ "keyup event is (not) handled: " + aDescription);
+ is(inputHandled, aInput,
+ "input event is (not) handled: " + aDescription);
+ }
+
+ function checkCompositionEvents(aStart, aEnd, aUpdate, aInput, aDescription)
+ {
+ is(compositionstartHandled, aStart,
+ "compositionstart event is (not) handled: " + aDescription);
+ is(compositionendHandled, aEnd,
+ "compositionend event is (not) handled: " + aDescription);
+ is(compositionupdateHandled, aUpdate,
+ "compositionupdate event is (not) handled: " + aDescription);
+ is(inputHandled, aInput,
+ "input event is (not) handled: " + aDescription);
+ }
+
+ clear();
+ synthesizeKey("a", {type: "keydown"});
+ checkKeyEvents(true, true, false, true, "a keydown and a keypress");
+ is(textarea.value, "a", "textarea value isn't 'a'");
+ clear();
+ synthesizeKey("a", {type: "keyup"});
+ checkKeyEvents(false, false, true, false, "a keyup");
+ clear();
+ synthesizeKey("KEY_Backspace");
+ checkKeyEvents(true, true, true, true, "KEY_Backspace key events");
+ is(textarea.value, "", "textarea value isn't empty");
+
+ // IME events
+ clear();
+ // input first character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ checkCompositionEvents(true, false, true, true, "starting to compose");
+ var queryText = synthesizeQueryTextContent(0, 100);
+ ok(queryText, "query text event result is null");
+ if (!queryText) {
+ return;
+ }
+ ok(queryText.succeeded, "query text event failed");
+ if (!queryText.succeeded) {
+ return;
+ }
+ is(queryText.text, "\u3089", "composing text is incorrect");
+ var querySelectedText = synthesizeQuerySelectedText();
+ ok(querySelectedText, "query selected text event result is null");
+ if (!querySelectedText) {
+ return;
+ }
+ ok(querySelectedText.succeeded, "query selected text event failed");
+ if (!querySelectedText.succeeded) {
+ return;
+ }
+ is(querySelectedText.offset, 1,
+ "query selected text event returns wrong offset");
+ is(querySelectedText.text, "",
+ "query selected text event returns wrong selected text");
+ clear();
+ // commit composition
+ synthesizeComposition({ type: "compositioncommitasis" });
+ checkCompositionEvents(false, true, false, true, "commit composition as is");
+ queryText = synthesizeQueryTextContent(0, 100);
+ ok(queryText, "query text event result is null after commit");
+ if (!queryText) {
+ return;
+ }
+ ok(queryText.succeeded, "query text event failed after commit");
+ if (!queryText.succeeded) {
+ return;
+ }
+ is(queryText.text, "\u3089", "composing text is incorrect after commit");
+ querySelectedText = synthesizeQuerySelectedText();
+ ok(querySelectedText,
+ "query selected text event result is null after commit");
+ if (!querySelectedText) {
+ return;
+ }
+ ok(querySelectedText.succeeded,
+ "query selected text event failed after commit");
+ if (!querySelectedText.succeeded) {
+ return;
+ }
+ is(querySelectedText.offset, 1,
+ "query selected text event returns wrong offset after commit");
+ is(querySelectedText.text, "",
+ "query selected text event returns wrong selected text after commit");
+ clear();
+ }
+
+ textarea.removeEventListener("keydown", onEvent);
+ textarea.removeEventListener("keypress", onEvent);
+ textarea.removeEventListener("keyup", onEvent);
+ textarea.removeEventListener("compositionstart", onEvent);
+ textarea.removeEventListener("compositionupdate", onEvent);
+ textarea.removeEventListener("compositionend", onEvent);
+ textarea.removeEventListener("input", onEvent);
+
+ otherWindow.close();
+
+ SimpleTest.finish();
+}
+
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_key_event_counts.xhtml b/widget/tests/test_key_event_counts.xhtml
new file mode 100644
index 0000000000..6eda6a52fb
--- /dev/null
+++ b/widget/tests/test_key_event_counts.xhtml
@@ -0,0 +1,90 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!-- We've had issues on Mac OS X where native key events either don't get processed
+ or they get processed twice. This test tests some of those scenarios. -->
+
+<window id="window1" title="Test Key Event Counts" onload="runTest()"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ </body>
+
+ <script type="application/javascript"><![CDATA[
+ var gKeyPressEventCount = 0;
+ var gKeyDownEventCount = 0;
+
+ function onKeyDown(e)
+ {
+ gKeyDownEventCount++;
+ }
+
+ function onKeyPress(e)
+ {
+ gKeyPressEventCount++;
+ e.preventDefault();
+ }
+
+ function* testBody()
+ {
+ window.addEventListener("keydown", onKeyDown);
+ window.addEventListener("keypress", onKeyPress);
+
+ // Test ctrl-tab
+ gKeyDownEventCount = 0;
+ gKeyPressEventCount = 0;
+ yield synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_Tab, {ctrlKey:1}, "\t", "\t", continueTest);
+ is(gKeyDownEventCount, 1);
+ is(gKeyPressEventCount, 0, "ctrl-tab should be consumed by tabbox of tabbrowser at keydown");
+
+ // Test cmd+shift+a
+ gKeyDownEventCount = 0;
+ gKeyPressEventCount = 0;
+ yield synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {metaKey:1, shiftKey:1}, "a", "A", continueTest);
+ is(gKeyDownEventCount, 1);
+ is(gKeyPressEventCount, 1);
+
+ // Test cmd-;
+ gKeyDownEventCount = 0;
+ gKeyPressEventCount = 0;
+ yield synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_Semicolon, {metaKey:1}, ";", ";", continueTest);
+ is(gKeyDownEventCount, 1);
+ is(gKeyPressEventCount, 1);
+
+ window.removeEventListener("keydown", onKeyDown);
+ window.removeEventListener("keypress", onKeyPress);
+ }
+
+ var gTestContinuation = null;
+
+ function continueTest()
+ {
+ if (!gTestContinuation) {
+ gTestContinuation = testBody();
+ }
+ var ret = gTestContinuation.next();
+ if (ret.done) {
+ SimpleTest.finish();
+ } else {
+ is(ret.value, true, "Key synthesized successfully");
+ }
+ }
+
+ function runTest()
+ {
+ SimpleTest.waitForExplicitFinish();
+ continueTest();
+ }
+ ]]></script>
+
+</window>
diff --git a/widget/tests/test_keycodes.xhtml b/widget/tests/test_keycodes.xhtml
new file mode 100644
index 0000000000..164ca9fc3d
--- /dev/null
+++ b/widget/tests/test_keycodes.xhtml
@@ -0,0 +1,5628 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Key event tests"
+ onload="runTest()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+<commandset>
+ <command id="expectedCommand" oncommand="this.activeCount++" disabled="true"/>
+ <command id="unexpectedCommand" oncommand="this.activeCount++" disabled="true"/>
+ <command id="expectedReservedCommand" oncommand="this.activeCount++" reserved="true" disabled="true"/>
+</commandset>
+<keyset>
+ <key id="unshiftedKey" key=";" modifiers="accel" command="unexpectedCommand"/>
+ <key id="shiftedKey" key=":" modifiers="accel" command="unexpectedCommand"/>
+ <key id="commandOptionF" key='f' modifiers="accel,alt" command="unexpectedCommand"/>
+ <key id="question" key='?' modifiers="accel" command="unexpectedCommand"/>
+ <key id="unshiftedX" key="x" modifiers="accel" command="unexpectedCommand"/>
+ <key id="shiftedX" key="X" modifiers="accel,shift" command="unexpectedCommand"/>
+ <key id="ctrlAltA" key="A" modifiers="accel,alt" command="unexpectedCommand"/>
+ <key id="unshiftedPlus" key="+" modifiers="accel" command="unexpectedCommand"/>
+ <key id="reservedUnshiftedKey" key="'" modifiers="accel" command="unexpectedCommand"/>
+ <key id="reservedShiftedKey" key='"' modifiers="accel" command="unexpectedCommand"/>
+</keyset>
+
+<browser id="browser" type="content" src="data:text/html;charset=utf-8,&lt;button id='content_button'&gt;button&lt;/button&gt;" width="200" height="32"/>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display">
+ <!-- for some reason, if we don't have 'accesskey' here, adding it dynamically later
+ doesn't work! -->
+ <button id="button" accesskey="z">Hello</button>
+ <input type="text" id="textbox" value=""/>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const IS_MAC = navigator.platform.indexOf("Mac") == 0;
+const IS_WIN = navigator.platform.indexOf("Win") == 0;
+const OS_VERSION =
+ IS_WIN ? parseFloat(Services.sysinfo.getProperty("version")) : 0;
+const WIN7 = 6.1;
+const WIN8 = 6.2;
+
+function isModifierKeyEvent(aEvent)
+{
+ switch (aEvent.key) {
+ case "Alt":
+ case "AltGraph":
+ case "CapsLock":
+ case "Control":
+ case "Fn":
+ case "FnLock":
+ case "Hyper":
+ case "Meta":
+ case "NumLock":
+ case "ScrollLock":
+ case "Shift":
+ case "Super":
+ case "Symbol":
+ case "SymbolLock":
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * Firefox infobar UI can have access keys which conflict with this test. Really
+ * stupid workaround until we can move this test into its own chrome window.
+ */
+function clearInfobars()
+{
+ var browser = window.top.docShell.chromeEventHandler;
+ var chromeWin = browser.ownerGlobal;
+ var nb = chromeWin.gBrowser.getNotificationBox(browser);
+ for (let n of nb.allNotifications) {
+ nb.removeNotification(n, true);
+ }
+}
+
+function eventToString(aEvent)
+{
+ var name = aEvent.layout.name + " keyCode=" +
+ aEvent.keyCode + " (0x" + aEvent.keyCode.toString(16).toUpperCase() +
+ ") chars='" + aEvent.chars + "'";
+ if (typeof aEvent.unmodifiedChars === "string") {
+ name += " unmodifiedChars='" + aEvent.unmodifiedChars + "'";
+ }
+ if (aEvent.modifiers.capsLockKey) {
+ name += " [CapsLock]";
+ }
+ if (aEvent.modifiers.shiftKey) {
+ name += " [Shift]";
+ }
+ if (aEvent.modifiers.shiftRightKey) {
+ name += " [Right Shift]";
+ }
+ if (aEvent.modifiers.ctrlKey) {
+ name += " [Ctrl]";
+ }
+ if (aEvent.modifiers.ctrlRightKey) {
+ name += " [Right Ctrl]";
+ }
+ if (aEvent.modifiers.altKey) {
+ name += " [Alt]";
+ }
+ if (aEvent.modifiers.altGrKey) {
+ name += " [AltGr]";
+ }
+ if (aEvent.modifiers.altRightKey) {
+ name += " [Right Alt]";
+ }
+ if (aEvent.modifiers.metaKey) {
+ name += " [Command]";
+ }
+ if (aEvent.modifiers.metaRightKey) {
+ name += " [Right Command]";
+ }
+
+ return name;
+}
+
+function getPhase(aDOMEvent)
+{
+ switch (aDOMEvent.eventPhase) {
+ case aDOMEvent.None:
+ return "none";
+ case aDOMEvent.CAPTURING_PHASE:
+ return "capture";
+ case aDOMEvent.AT_TARGET:
+ return "target";
+ case aDOMEvent.BUBBLING_PHASE:
+ return "bubble";
+ default:
+ return "";
+ }
+}
+
+function eventTargetToString(aEventTarget)
+{
+ if (aEventTarget.navigator) {
+ return "window";
+ }
+ switch (aEventTarget.nodeType) {
+ case Node.ELEMENT_NODE:
+ return "element (" + aEventTarget.tagName + ")";
+ case Node.DOCUMENT_NODE:
+ return "document";
+ default:
+ return "";
+ }
+}
+
+function synthesizeKey(aEvent, aFocusElementId, aCallback)
+{
+ if (aFocusElementId.startsWith("content_")) {
+ var browser = document.getElementById("browser");
+ browser.contentDocument.getElementById(aFocusElementId).focus();
+ } else {
+ document.getElementById(aFocusElementId).focus();
+ }
+
+ return synthesizeNativeKey(aEvent.layout, aEvent.keyCode,
+ aEvent.modifiers,
+ aEvent.chars, aEvent.unmodifiedChars,
+ aCallback);
+}
+
+// Test the charcodes and modifiers being delivered to keypress handlers and
+// also keydown/keyup events too.
+function* runKeyEventTests()
+{
+ var currentTestName;
+ var eventList, keyDownFlags, keyUpFlags, testingEvent, expectedDOMKeyCode;
+ const kShiftFlag = 0x1;
+ const kCtrlFlag = 0x2;
+ const kAltFlag = 0x4;
+ const kMetaFlag = 0x8;
+ const kNumLockFlag = 0x10;
+ const kCapsLockFlag = 0x20;
+ const kAltGraphFlag = 0x40;
+
+ function onKeyEvent(e)
+ {
+ /* eslint-disable-next-line no-shadow */
+ function removeFlag(e, aFlag)
+ {
+ if (e.type == "keydown") {
+ let oldValue = keyDownFlags;
+ keyDownFlags &= ~aFlag;
+ return oldValue != keyDownFlags;
+ } else if (e.type == "keyup") {
+ let oldValue = keyUpFlags;
+ keyUpFlags &= ~aFlag;
+ return oldValue != keyUpFlags;
+ }
+ return false;
+ }
+
+ /* eslint-disable-next-line no-shadow, complexity */
+ function isStateChangingModifierKeyEvent(e)
+ {
+ var flags = 0;
+ if (e.type == "keydown") {
+ flags = keyDownFlags ^ keyUpFlags;
+ } else if (e.type == "keyup") {
+ flags = keyUpFlags;
+ }
+ switch (e.key) {
+ case "Shift":
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0,
+ currentTestName + ", Ctrl of Shift " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0,
+ currentTestName + ", Command of Shift " + e.type + " event mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0,
+ currentTestName + ", Alt of Shift " + e.type + " event mismatch");
+ is(e.shiftKey, e.type == "keydown",
+ currentTestName + ", Shift of Shift " + e.type + " event mismatch");
+ // AltGr on Windows is always pressed after and released before Shift key operation.
+ is(e.getModifierState("AltGraph"), (IS_MAC && e.altKey),
+ currentTestName + ", AltGraph of Shift " + e.type + " event mismatch");
+ return (testingEvent.modifiers.shiftKey || testingEvent.modifiers.shiftRightKey) &&
+ removeFlag(e, kShiftFlag) && expectedDOMKeyCode != e.keyCode;
+ case "Control":
+ is(e.ctrlKey, e.type == "keydown",
+ currentTestName + ", Ctrl of Ctrl " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0,
+ currentTestName + ", Command of Ctrl " + e.type + " event mismatch");
+ // When AltGr key is released on Windows, ControlLeft keyup event
+ // is followed by AltRight keyup event. However, altKey should be
+ // false in such case.
+ is(e.altKey, (flags & kAltFlag) != 0 && !(IS_WIN && !!testingEvent.modifiers.altGrKey),
+ currentTestName + ", Alt of Ctrl " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0,
+ currentTestName + ", Shift of Ctrl " + e.type + " event mismatch");
+ is(e.getModifierState("AltGraph"),
+ (IS_WIN && !!testingEvent.modifiers.altGrKey && e.type == "keyup") || (IS_MAC && e.altKey),
+ currentTestName + ", AltGraph of Ctrl " + e.type + " event mismatch");
+ return (testingEvent.modifiers.ctrlKey || testingEvent.modifiers.ctrlRightKey ||
+ (IS_WIN && !!testingEvent.modifiers.altGrKey)) &&
+ removeFlag(e, kCtrlFlag) && expectedDOMKeyCode != e.keyCode;
+ case "Alt":
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0 && !(IS_WIN && !!testingEvent.modifiers.altGrKey),
+ currentTestName + ", Ctrl of Alt " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0,
+ currentTestName + ", Command of Alt " + e.type + " event mismatch");
+ is(e.altKey, e.type == "keydown" && !(IS_WIN && !!testingEvent.modifiers.altGrKey),
+ currentTestName + ", Alt of Alt " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0,
+ currentTestName + ", Shift of Alt " + e.type + " event mismatch");
+ is(e.getModifierState("AltGraph"),
+ e.type == "keydown" && ((IS_WIN && !!testingEvent.modifiers.altGrKey) || (IS_MAC && e.altKey)),
+ currentTestName + ", AltGraph of Alt " + e.type + " event mismatch");
+ return (testingEvent.modifiers.altKey || testingEvent.modifiers.altRightKey ||
+ (IS_WIN && !!testingEvent.modifiers.altGrKey)) &&
+ removeFlag(e, kAltFlag) && expectedDOMKeyCode != e.keyCode;
+ case "AltGraph":
+ // On Windows, AltGraph events are fired only when AltRight key is
+ // pressed when active keyboard layout maps AltGraph to AltRight.
+ // Note that AltGraph is represented with pressing both Control key
+ // and Alt key. Therefore, when AltGraph keyboard event is fired,
+ // both ctrlKey and altKey are always false on Windows.
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0 && !IS_WIN,
+ currentTestName + ", Ctrl of AltGraph " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0,
+ currentTestName + ", Command of AltGraph " + e.type + " event mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0 && !IS_WIN,
+ currentTestName + ", Alt of AltGraph " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0,
+ currentTestName + ", Shift of Ctrl " + e.type + " event mismatch");
+ is(e.getModifierState("AltGraph"), e.type === "keydown",
+ currentTestName + ", AltGraph of AltGraph " + e.type + " event mismatch");
+ return IS_WIN && testingEvent.modifiers.altGrKey &&
+ removeFlag(e, kAltGraphFlag) && expectedDOMKeyCode != e.keyCode;
+ case "Meta":
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0,
+ currentTestName + ", Ctrl of Command " + e.type + " event mismatch");
+ is(e.metaKey, e.type == "keydown",
+ currentTestName + ", Command of Command " + e.type + " event mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0,
+ currentTestName + ", Alt of Command " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0,
+ currentTestName + ", Shift of Command " + e.type + " event mismatch");
+ is(e.getModifierState("AltGraph"),
+ (IS_WIN && (flags & kAltGraphFlag) != 0) || (IS_MAC && e.altKey),
+ currentTestName + ", AltGraph of Meta " + e.type + " event mismatch");
+ return (testingEvent.modifiers.metaKey || testingEvent.modifiers.metaRightKey) &&
+ removeFlag(e, kMetaFlag) && expectedDOMKeyCode != e.keyCode;
+ case "NumLock":
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0,
+ currentTestName + ", Ctrl of NumLock " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0,
+ currentTestName + ", Command of NumLock " + e.type + " event mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0,
+ currentTestName + ", Alt of NumLock " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0,
+ currentTestName + ", Shift of NumLock " + e.type + " event mismatch");
+ is(e.getModifierState("AltGraph"), false,
+ currentTestName + ", AltGraph of NumLock " + e.type + " event mismatch");
+ // AltGr on Windows is always pressed after and released before NumLock key operation.
+ return (testingEvent.modifiers.numLockKey || testingEvent.modifiers.numericKeyPadKey) &&
+ removeFlag(e, kNumLockFlag) && expectedDOMKeyCode != e.keyCode;
+ case "CapsLock":
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0,
+ currentTestName + ", Ctrl of CapsLock " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0,
+ currentTestName + ", Command of CapsLock " + e.type + " event mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0,
+ currentTestName + ", Alt of CapsLock " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0,
+ currentTestName + ", Shift of CapsLock " + e.type + " event mismatch");
+ // AltGr on Windows is always pressed after and released before CapsLock key operation.
+ is(e.getModifierState("AltGraph"), false,
+ currentTestName + ", AltGraph of CapsLock " + e.type + " event mismatch");
+ return testingEvent.modifiers.capsLockKey &&
+ removeFlag(e, kCapsLockFlag) && expectedDOMKeyCode != e.keyCode;
+ }
+ return false;
+ }
+
+ // Ignore the state changing key events which is fired by the testing event.
+ if (!isStateChangingModifierKeyEvent(e))
+ eventList.push(e);
+ }
+
+ function consumer(aEvent)
+ {
+ aEvent.preventDefault();
+ }
+
+ const SHOULD_DELIVER_KEYDOWN = 0x1;
+ const SHOULD_DELIVER_KEYPRESS = 0x2;
+ const SHOULD_DELIVER_KEYUP = 0x4;
+ const SHOULD_DELIVER_ALL = SHOULD_DELIVER_KEYDOWN |
+ SHOULD_DELIVER_KEYPRESS |
+ SHOULD_DELIVER_KEYUP;
+ const SHOULD_DELIVER_KEYDOWN_KEYUP = SHOULD_DELIVER_KEYDOWN |
+ SHOULD_DELIVER_KEYUP;
+ const SHOULD_DELIVER_KEYDOWN_KEYPRESS = SHOULD_DELIVER_KEYDOWN |
+ SHOULD_DELIVER_KEYPRESS;
+
+ // The first parameter is the complete input event. The second parameter is
+ // what to test against. The third parameter is which key events should be
+ // delived for the event.
+ // @param aExpectedKeyValues Can be string or array of string.
+ // If all keyboard events have same key value,
+ // specify it as string. Otherwise, specify
+ // each key value in array.
+ function testKey(aEvent, aExpectedKeyValues, aExpectedCodeValue,
+ aExpectedGeckoKeyCode, aExpectGeckoChar,
+ aShouldDelivedEvent, aExpectLocation)
+ {
+ ok(aExpectedGeckoKeyCode != undefined, "keycode is undefined");
+ eventList = [];
+
+ // The modifier key events which are fired for state changing are har to
+ // test. We should ignore them for now.
+ keyDownFlags = keyUpFlags = 0;
+ if (!IS_MAC) {
+ // On Mac, nsChildView doesn't generate modifier keydown/keyup events for
+ // state changing for synthesizeNativeKeyEvent.
+ if (aEvent.modifiers.shiftKey || aEvent.modifiers.shiftRightKey) {
+ keyDownFlags |= kShiftFlag;
+ }
+ if (aEvent.modifiers.ctrlKey || aEvent.modifiers.ctrlRightKey ||
+ (IS_WIN && aEvent.modifiers.altGrKey)) {
+ keyDownFlags |= kCtrlFlag;
+ }
+ if (aEvent.modifiers.altKey || aEvent.modifiers.altRightKey) {
+ keyDownFlags |= kAltFlag;
+ }
+ if (aEvent.modifiers.altGrKey) {
+ keyDownFlags |= kAltGraphFlag;
+ }
+ if (aEvent.modifiers.metaKey || aEvent.modifiers.metaRightKey) {
+ keyDownFlags |= kMetaFlag;
+ }
+ if (aEvent.modifiers.numLockKey || aEvent.modifiers.numericKeyPadKey) {
+ keyDownFlags |= kNumLockFlag;
+ }
+ if (aEvent.modifiers.capsLockKey) {
+ keyDownFlags |= kCapsLockFlag;
+ }
+ keyUpFlags = keyDownFlags;
+ }
+
+ testingEvent = aEvent;
+ expectedDOMKeyCode = aExpectedGeckoKeyCode;
+
+ currentTestName = eventToString(aEvent);
+ ok(true, "Starting: " + currentTestName);
+
+ // eslint-disable-next-line complexity
+ return synthesizeKey(aEvent, "button", function() {
+
+ var expectEventTypeList = [];
+ if (aShouldDelivedEvent & SHOULD_DELIVER_KEYDOWN)
+ expectEventTypeList.push("keydown");
+ if (aShouldDelivedEvent & SHOULD_DELIVER_KEYPRESS) {
+ expectEventTypeList.push("keypress");
+ for (let i = 1; i < aExpectGeckoChar.length; i++) {
+ expectEventTypeList.push("keypress");
+ }
+ }
+ if (aShouldDelivedEvent & SHOULD_DELIVER_KEYUP)
+ expectEventTypeList.push("keyup");
+ is(eventList.length, expectEventTypeList.length,
+ currentTestName + ", wrong number of key events");
+
+ var longerLength = Math.max(eventList.length, expectEventTypeList.length);
+ var keypressCount = 0;
+ for (let i = 0; i < longerLength; i++) {
+ var firedEventType = i < eventList.length ? eventList[i].type : "";
+ var expectEventType = i < expectEventTypeList.length ? expectEventTypeList[i] : "";
+ if (firedEventType != "") {
+ is(firedEventType, expectEventType,
+ currentTestName + ", " + expectEventType + " should be fired");
+ } else {
+ is(firedEventType, expectEventType,
+ currentTestName + ", a needed event is not fired");
+ }
+
+ if (firedEventType != "") {
+ var expectedKeyValue =
+ // eslint-disable-next-line no-nested-ternary
+ typeof aExpectedKeyValues === "string" ? aExpectedKeyValues :
+ i < aExpectedKeyValues.length ? aExpectedKeyValues[i] :
+ undefined;
+
+ var e = eventList[i];
+ switch (e.key) {
+ case "Shift":
+ case "Control":
+ case "Alt":
+ case "AltGraph":
+ case "Meta":
+ case "CapsLock":
+ case "NumLock":
+ // XXX To check modifier state of modifiers, we need to check
+ // e.type since modifier key may change modifier state.
+ // However, doing it makes the following check more
+ // complicated. So, we ignore the modifier state of
+ // modifier keydown/keyup events for now.
+ break;
+ default:
+ is(e.shiftKey, !!(aEvent.modifiers.shiftKey || aEvent.modifiers.shiftRightKey),
+ currentTestName + ", Shift of " + e.type + " of " + e.code + " mismatch");
+ is(e.metaKey, !!(aEvent.modifiers.metaKey || aEvent.modifiers.metaRightKey),
+ currentTestName + ", Command of " + e.type + " of " + e.code + " mismatch");
+ var isControlPressed = !!(aEvent.modifiers.ctrlKey || aEvent.modifiers.ctrlRightKey);
+ var isAltPressed = !!(aEvent.modifiers.altKey || aEvent.modifiers.altRightKey);
+ var isAltGraphExpected =
+ !!aEvent.modifiers.altGrKey ||
+ (IS_WIN && aEvent.layout.hasAltGrOnWin &&
+ isControlPressed && isAltPressed &&
+ (aEvent.isInputtingCharacters || expectedKeyValue == "Dead")) ||
+ (IS_MAC && isAltPressed);
+ var isControlExpected = !(IS_WIN && isAltGraphExpected) && isControlPressed;
+ var isAltExpected = !(IS_WIN && isAltGraphExpected) && isAltPressed;
+ if (e.type == "keypress" && aEvent.isInputtingCharacters) {
+ isControlExpected = false;
+ isAltExpected = false;
+ }
+ is(e.ctrlKey, isControlExpected,
+ currentTestName + ", Ctrl of " + e.type + " of " + e.code + " mismatch");
+ is(e.altKey, isAltExpected,
+ currentTestName + ", Alt of " + e.type + " of " + e.code + " mismatch");
+ is(e.getModifierState("AltGraph"), isAltGraphExpected,
+ currentTestName + ", AltGraph of " + e.type + " of " + e.code + " mismatch");
+ break;
+ }
+
+ is(e.key, expectedKeyValue, currentTestName + ", wrong key value");
+ is(e.code, aExpectedCodeValue, currentTestName + ", wrong code value");
+
+ if (aExpectGeckoChar.length && e.type == "keypress") {
+ is(e.charCode, aExpectGeckoChar.charCodeAt(keypressCount++),
+ currentTestName + ", charcode");
+ if (aExpectedGeckoKeyCode >= 0) {
+ if (aExpectGeckoChar) {
+ is(e.keyCode, 0,
+ currentTestName + ", wrong keycode");
+ } else {
+ is(e.keyCode, aExpectedGeckoKeyCode,
+ currentTestName + ", wrong keycode");
+ }
+ }
+ } else {
+ is(e.charCode, 0,
+ currentTestName + ", no charcode");
+ if (aExpectedGeckoKeyCode >= 0) {
+ is(e.keyCode, aExpectedGeckoKeyCode,
+ currentTestName + ", wrong keycode");
+ }
+ }
+ is(e.location, aExpectLocation,
+ currentTestName + ", wrong location");
+ }
+ }
+
+ continueTest();
+ });
+ }
+
+ // These tests have to be per-plaform.
+ document.addEventListener("keydown", onKeyEvent);
+ document.addEventListener("keypress", onKeyEvent);
+ document.addEventListener("keyup", onKeyEvent);
+ // Prevent almost all shortcut key handlers.
+ SpecialPowers.addSystemEventListener(document, "keypress", consumer, true);
+
+ function cleanup()
+ {
+ document.removeEventListener("keydown", onKeyEvent);
+ document.removeEventListener("keypress", onKeyEvent);
+ document.removeEventListener("keyup", onKeyEvent);
+ SpecialPowers.removeSystemEventListener(document, "keypress", consumer, true);
+ }
+
+ function* testKeysOnMac()
+ {
+ // On Mac, you can produce event records for any desired keyboard input
+ // by running with NSPR_LOG_MODULES=TextInputHandlerWidgets:5 and typing
+ // into the browser. We will dump the key event fields to the console
+ // (Find TextInputHandler::HandleKeyDownEvent or
+ // TextInputHandler::HandleKeyUpEvent in the log). Use the International system
+ // preferences widget to enable other keyboard layouts and select them from the
+ // input menu to see what keyboard events they generate.
+ // Note that it's possible to send bogus key events here, e.g.
+ // {keyCode:0, chars:"z", unmodifiedChars:"P"} --- sendNativeKeyEvent
+ // makes no attempt to verify that the keyCode matches the characters. So only
+ // test key event records that you saw Cocoa send.
+
+ // Command keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1}, chars:"a", unmodifiedChars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Shift-cmd gives us the shifted character
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"a", unmodifiedChars:"A"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Ctrl-cmd gives us the unshifted character
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, ctrlKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Alt-cmd gives us the shifted character
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, altKey:1}, chars:"\u00e5", unmodifiedChars:"a"},
+ "\u00e5", "KeyA", KeyboardEvent.DOM_VK_A, "\u00e5", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, altKey:1, shiftKey:1}, chars:"\u00c5", unmodifiedChars:"a"},
+ "\u00c5", "KeyA", KeyboardEvent.DOM_VK_A, "\u00c5", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Greek ctrl keys produce Latin charcodes
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1}, chars:"\u0001", unmodifiedChars:"\u03b1"},
+ "\u03b1", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0001", unmodifiedChars:"\u0391"},
+ "\u0391", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Greek command keys
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1}, chars:"a", unmodifiedChars:"\u03b1"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Shift-cmd gives us the shifted character
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"a", unmodifiedChars:"\u0391"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Ctrl-cmd gives us the unshifted character
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, ctrlKey:1}, chars:"\u0001", unmodifiedChars:"\u03b1"},
+ "\u03b1", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Alt-cmd gives us the shifted character
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, altKey:1}, chars:"\u00a8", unmodifiedChars:"\u03b1"},
+ "\u00a8", "KeyA", KeyboardEvent.DOM_VK_A, "\u00a8", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, altKey:1, shiftKey:1}, chars:"\u00b9", unmodifiedChars:"\u0391"},
+ "\u00b9", "KeyA", KeyboardEvent.DOM_VK_A, "\u00b9", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // German
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_A,
+ modifiers: {}, chars:"a", unmodifiedChars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers: {}, chars:"\u00fc", unmodifiedChars:"\u00fc"},
+ "\u00fc", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "\u00fc", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_Minus,
+ modifiers: {}, chars:"\u00df", unmodifiedChars:"\u00df"},
+ "\u00df", "Minus", KeyboardEvent.DOM_VK_QUESTION_MARK, "\u00df", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{shiftKey:1}, chars:"?", unmodifiedChars:"?"},
+ "?", "Minus", KeyboardEvent.DOM_VK_QUESTION_MARK, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Note that Shift+SS is '?' but Cmd+Shift+SS is '/' on German layout.
+ // Therefore, when Cmd key is pressed, the SS key's keycode is changed.
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{metaKey:1}, chars:"\u00df", unmodifiedChars:"\u00df"},
+ "\u00df", "Minus", KeyboardEvent.DOM_VK_SLASH, "\u00df", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"/", unmodifiedChars:"?"},
+ "/", "Minus", KeyboardEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Caps Lock key event
+ // XXX keyup event of Caps Lock key is not fired.
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_CapsLock,
+ modifiers:{capsLockKey:1}, chars:"", unmodifiedChars:""},
+ "CapsLock", "CapsLock", KeyboardEvent.DOM_VK_CAPS_LOCK, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_CapsLock,
+ modifiers:{capsLockKey:0}, chars:"", unmodifiedChars:""},
+ "CapsLock", "CapsLock", KeyboardEvent.DOM_VK_CAPS_LOCK, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Shift/RightShift key event
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Shift,
+ modifiers:{shiftKey:1}, chars:"", unmodifiedChars:""},
+ "Shift", "ShiftLeft", KeyboardEvent.DOM_VK_SHIFT, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Shift,
+ modifiers:{shiftKey:0}, chars:"", unmodifiedChars:""},
+ "Shift", "ShiftLeft", KeyboardEvent.DOM_VK_SHIFT, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightShift,
+ modifiers:{shiftRightKey:1}, chars:"", unmodifiedChars:""},
+ "Shift", "ShiftRight", KeyboardEvent.DOM_VK_SHIFT, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightShift,
+ modifiers:{shiftRightKey:0}, chars:"", unmodifiedChars:""},
+ "Shift", "ShiftRight", KeyboardEvent.DOM_VK_SHIFT, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // Control/RightControl key event
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Control,
+ modifiers:{ctrlKey:1}, chars:"", unmodifiedChars:""},
+ "Control", "ControlLeft", KeyboardEvent.DOM_VK_CONTROL, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Control,
+ modifiers:{ctrlKey:0}, chars:"", unmodifiedChars:""},
+ "Control", "ControlLeft", KeyboardEvent.DOM_VK_CONTROL, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightControl,
+ modifiers:{ctrlRightKey:1}, chars:"", unmodifiedChars:""},
+ "Control", "ControlRight", KeyboardEvent.DOM_VK_CONTROL, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightControl,
+ modifiers:{ctrlRightKey:0}, chars:"", unmodifiedChars:""},
+ "Control", "ControlRight", KeyboardEvent.DOM_VK_CONTROL, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // Option/RightOption key event
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Option,
+ modifiers:{altKey:1}, chars:"", unmodifiedChars:""},
+ "Alt", "AltLeft", KeyboardEvent.DOM_VK_ALT, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Option,
+ modifiers:{altKey:0}, chars:"", unmodifiedChars:""},
+ "Alt", "AltLeft", KeyboardEvent.DOM_VK_ALT, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightOption,
+ modifiers:{altRightKey:1}, chars:"", unmodifiedChars:""},
+ "Alt", "AltRight", KeyboardEvent.DOM_VK_ALT, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightOption,
+ modifiers:{altRightKey:0}, chars:"", unmodifiedChars:""},
+ "Alt", "AltRight", KeyboardEvent.DOM_VK_ALT, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // Command/RightCommand key event
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Command,
+ modifiers:{metaKey:1}, chars:"", unmodifiedChars:""},
+ "Meta", "OSLeft", KeyboardEvent.DOM_VK_META, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Command,
+ modifiers:{metaKey:0}, chars:"", unmodifiedChars:""},
+ "Meta", "OSLeft", KeyboardEvent.DOM_VK_META, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightCommand,
+ modifiers:{metaRightKey:1}, chars:"", unmodifiedChars:""},
+ "Meta", "OSRight", KeyboardEvent.DOM_VK_META, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightCommand,
+ modifiers:{metaRightKey:0}, chars:"", unmodifiedChars:""},
+ "Meta", "OSRight", KeyboardEvent.DOM_VK_META, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // all keys on keyboard (keyCode test)
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Tab,
+ modifiers: {}, chars:"\t", unmodifiedChars:"\t"},
+ "Tab", "Tab", KeyboardEvent.DOM_VK_TAB, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadClear,
+ modifiers: {}, chars:"\uF739", unmodifiedChars:"\uF739"},
+ "Clear", "NumLock", KeyboardEvent.DOM_VK_CLEAR, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Return,
+ modifiers: {}, chars:"\u000D", unmodifiedChars:"\u000D"},
+ "Enter", "Enter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_PC_Pause,
+ modifiers: {}, chars:"\uF712", unmodifiedChars:"\uF712"},
+ "F15", "F15", KeyboardEvent.DOM_VK_PAUSE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Escape,
+ modifiers: {}, chars:"\u001B", unmodifiedChars:"\u001B"},
+ "Escape", "Escape", KeyboardEvent.DOM_VK_ESCAPE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Space,
+ modifiers: {}, chars:" ", unmodifiedChars:" "},
+ " ", "Space", KeyboardEvent.DOM_VK_SPACE, " ", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_PageUp,
+ modifiers: {}, chars:"\uF72C", unmodifiedChars:"\uF72C"},
+ "PageUp", "PageUp", KeyboardEvent.DOM_VK_PAGE_UP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_PageDown,
+ modifiers: {}, chars:"\uF72D", unmodifiedChars:"\uF72D"},
+ "PageDown", "PageDown", KeyboardEvent.DOM_VK_PAGE_DOWN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_End,
+ modifiers: {}, chars:"\uF72B", unmodifiedChars:"\uF72B"},
+ "End", "End", KeyboardEvent.DOM_VK_END, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Home,
+ modifiers: {}, chars:"\uF729", unmodifiedChars:"\uF729"},
+ "Home", "Home", KeyboardEvent.DOM_VK_HOME, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_LeftArrow,
+ modifiers: {}, chars:"\uF702", unmodifiedChars:"\uF702"},
+ "ArrowLeft", "ArrowLeft", KeyboardEvent.DOM_VK_LEFT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_UpArrow,
+ modifiers: {}, chars:"\uF700", unmodifiedChars:"\uF700"},
+ "ArrowUp", "ArrowUp", KeyboardEvent.DOM_VK_UP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightArrow,
+ modifiers: {}, chars:"\uF703", unmodifiedChars:"\uF703"},
+ "ArrowRight", "ArrowRight", KeyboardEvent.DOM_VK_RIGHT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_DownArrow,
+ modifiers: {}, chars:"\uF701", unmodifiedChars:"\uF701"},
+ "ArrowDown", "ArrowDown", KeyboardEvent.DOM_VK_DOWN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_PC_PrintScreen,
+ modifiers: {}, chars:"\uF710", unmodifiedChars:"\uF710"},
+ "F13", "F13", KeyboardEvent.DOM_VK_PRINTSCREEN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_PC_Delete,
+ modifiers: {}, chars:"\uF728", unmodifiedChars:"\uF728"},
+ "Delete", "Delete", KeyboardEvent.DOM_VK_DELETE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_PC_ScrollLock,
+ modifiers: {}, chars:"\uF711", unmodifiedChars:"\uF711"},
+ "F14", "F14", KeyboardEvent.DOM_VK_SCROLL_LOCK, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_PC_ContextMenu,
+ modifiers: {}, chars:"\u0010", unmodifiedChars:"\u0010"},
+ "ContextMenu", "ContextMenu", KeyboardEvent.DOM_VK_CONTEXT_MENU, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F1,
+ modifiers:{fnKey:1}, chars:"\uF704", unmodifiedChars:"\uF704"},
+ "F1", "F1", KeyboardEvent.DOM_VK_F1, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F2,
+ modifiers:{fnKey:1}, chars:"\uF705", unmodifiedChars:"\uF705"},
+ "F2", "F2", KeyboardEvent.DOM_VK_F2, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F3,
+ modifiers:{fnKey:1}, chars:"\uF706", unmodifiedChars:"\uF706"},
+ "F3", "F3", KeyboardEvent.DOM_VK_F3, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F4,
+ modifiers:{fnKey:1}, chars:"\uF707", unmodifiedChars:"\uF707"},
+ "F4", "F4", KeyboardEvent.DOM_VK_F4, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F5,
+ modifiers:{fnKey:1}, chars:"\uF708", unmodifiedChars:"\uF708"},
+ "F5", "F5", KeyboardEvent.DOM_VK_F5, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F6,
+ modifiers:{fnKey:1}, chars:"\uF709", unmodifiedChars:"\uF709"},
+ "F6", "F6", KeyboardEvent.DOM_VK_F6, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F7,
+ modifiers:{fnKey:1}, chars:"\uF70A", unmodifiedChars:"\uF70A"},
+ "F7", "F7", KeyboardEvent.DOM_VK_F7, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F8,
+ modifiers:{fnKey:1}, chars:"\uF70B", unmodifiedChars:"\uF70B"},
+ "F8", "F8", KeyboardEvent.DOM_VK_F8, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F9,
+ modifiers:{fnKey:1}, chars:"\uF70C", unmodifiedChars:"\uF70C"},
+ "F9", "F9", KeyboardEvent.DOM_VK_F9, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F10,
+ modifiers:{fnKey:1}, chars:"\uF70D", unmodifiedChars:"\uF70D"},
+ "F10", "F10", KeyboardEvent.DOM_VK_F10, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F11,
+ modifiers:{fnKey:1}, chars:"\uF70E", unmodifiedChars:"\uF70E"},
+ "F11", "F11", KeyboardEvent.DOM_VK_F11, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F12,
+ modifiers:{fnKey:1}, chars:"\uF70F", unmodifiedChars:"\uF70F"},
+ "F12", "F12", KeyboardEvent.DOM_VK_F12, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F16,
+ modifiers:{fnKey:1}, chars:"\uF713", unmodifiedChars:"\uF713"},
+ "F16", "F16", KeyboardEvent.DOM_VK_F16, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F17,
+ modifiers:{fnKey:1}, chars:"\uF714", unmodifiedChars:"\uF714"},
+ "F17", "F17", KeyboardEvent.DOM_VK_F17, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F18,
+ modifiers:{fnKey:1}, chars:"\uF715", unmodifiedChars:"\uF715"},
+ "F18", "F18", KeyboardEvent.DOM_VK_F18, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F19,
+ modifiers:{fnKey:1}, chars:"\uF716", unmodifiedChars:"\uF716"},
+ "F19", "F19", KeyboardEvent.DOM_VK_F19, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // US
+ // Alphabet
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers: {}, chars:"a", unmodifiedChars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{shiftKey:1}, chars:"A", unmodifiedChars:"A"},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0001", unmodifiedChars:"A"},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, capsLockKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, shiftKey:1, capsLockKey:1}, chars:"\u0001", unmodifiedChars:"A"},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{altKey:1}, chars:"\u00E5", unmodifiedChars:"a"},
+ "\u00E5", "KeyA", KeyboardEvent.DOM_VK_A, "\u00E5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C5", unmodifiedChars:"A"},
+ "\u00C5", "KeyA", KeyboardEvent.DOM_VK_A, "\u00C5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "\u00E5", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0001", unmodifiedChars:"A"},
+ "\u00C5", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1}, chars:"a", unmodifiedChars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{}, chars:"b", unmodifiedChars:"b"},
+ "b", "KeyB", KeyboardEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{shiftKey:1}, chars:"B", unmodifiedChars:"B"},
+ "B", "KeyB", KeyboardEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{ctrlKey:1}, chars:"\u0002", unmodifiedChars:"b"},
+ "b", "KeyB", KeyboardEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0002", unmodifiedChars:"B"},
+ "B", "KeyB", KeyboardEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{altKey:1}, chars:"\u222B", unmodifiedChars:"b"},
+ "\u222B", "KeyB", KeyboardEvent.DOM_VK_B, "\u222B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u0131", unmodifiedChars:"B"},
+ "\u0131", "KeyB", KeyboardEvent.DOM_VK_B, "\u0131", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0002", unmodifiedChars:"b"},
+ "\u222B", "KeyB", KeyboardEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0002", unmodifiedChars:"B"},
+ "\u0131", "KeyB", KeyboardEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{metaKey:1}, chars:"b", unmodifiedChars:"b"},
+ "b", "KeyB", KeyboardEvent.DOM_VK_B, "b", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{}, chars:"c", unmodifiedChars:"c"},
+ "c", "KeyC", KeyboardEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{shiftKey:1}, chars:"C", unmodifiedChars:"C"},
+ "C", "KeyC", KeyboardEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{ctrlKey:1}, chars:"\u0003", unmodifiedChars:"c"},
+ "c", "KeyC", KeyboardEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0003", unmodifiedChars:"C"},
+ "C", "KeyC", KeyboardEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{altKey:1}, chars:"\u00E7", unmodifiedChars:"c"},
+ "\u00E7", "KeyC", KeyboardEvent.DOM_VK_C, "\u00E7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C7", unmodifiedChars:"C"},
+ "\u00C7", "KeyC", KeyboardEvent.DOM_VK_C, "\u00C7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0003", unmodifiedChars:"c"},
+ "\u00E7", "KeyC", KeyboardEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0003", unmodifiedChars:"C"},
+ "\u00C7", "KeyC", KeyboardEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{metaKey:1}, chars:"c", unmodifiedChars:"c"},
+ "c", "KeyC", KeyboardEvent.DOM_VK_C, "c", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{}, chars:"d", unmodifiedChars:"d"},
+ "d", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{shiftKey:1}, chars:"D", unmodifiedChars:"D"},
+ "D", "KeyD", KeyboardEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{ctrlKey:1}, chars:"\u0004", unmodifiedChars:"d"},
+ "d", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0004", unmodifiedChars:"D"},
+ "D", "KeyD", KeyboardEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{altKey:1}, chars:"\u2202", unmodifiedChars:"d"},
+ "\u2202", "KeyD", KeyboardEvent.DOM_VK_D, "\u2202", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00CE", unmodifiedChars:"D"},
+ "\u00CE", "KeyD", KeyboardEvent.DOM_VK_D, "\u00CE", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0004", unmodifiedChars:"d"},
+ "\u2202", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0004", unmodifiedChars:"D"},
+ "\u00CE", "KeyD", KeyboardEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{metaKey:1}, chars:"d", unmodifiedChars:"d"},
+ "d", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{}, chars:"e", unmodifiedChars:"e"},
+ "e", "KeyE", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{shiftKey:1}, chars:"E", unmodifiedChars:"E"},
+ "E", "KeyE", KeyboardEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{ctrlKey:1}, chars:"\u0005", unmodifiedChars:"e"},
+ "e", "KeyE", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0005", unmodifiedChars:"E"},
+ "E", "KeyE", KeyboardEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{altKey:1}, chars:"", unmodifiedChars:"e"},
+ "Dead", "KeyE", KeyboardEvent.DOM_VK_E, "\u00B4", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // dead key
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00B4", unmodifiedChars:"E"},
+ "\u00B4", "KeyE", KeyboardEvent.DOM_VK_E, "\u00B4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0005", unmodifiedChars:"e"},
+ "Dead", "KeyE", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0005", unmodifiedChars:"E"},
+ "\u00B4", "KeyE", KeyboardEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{metaKey:1}, chars:"e", unmodifiedChars:"e"},
+ "e", "KeyE", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{}, chars:"f", unmodifiedChars:"f"},
+ "f", "KeyF", KeyboardEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{shiftKey:1}, chars:"F", unmodifiedChars:"F"},
+ "F", "KeyF", KeyboardEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{ctrlKey:1}, chars:"\u0006", unmodifiedChars:"f"},
+ "f", "KeyF", KeyboardEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0006", unmodifiedChars:"F"},
+ "F", "KeyF", KeyboardEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{altKey:1}, chars:"\u0192", unmodifiedChars:"f"},
+ "\u0192", "KeyF", KeyboardEvent.DOM_VK_F, "\u0192", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00CF", unmodifiedChars:"F"},
+ "\u00CF", "KeyF", KeyboardEvent.DOM_VK_F, "\u00CF", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0006", unmodifiedChars:"f"},
+ "\u0192", "KeyF", KeyboardEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0006", unmodifiedChars:"F"},
+ "\u00CF", "KeyF", KeyboardEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // XXX This test starts fullscreen mode.
+ // yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ // modifiers:{metaKey:1}, chars:"f", unmodifiedChars:"f"},
+ // "f", "KeyF", KeyboardEvent.DOM_VK_F, "f", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{}, chars:"g", unmodifiedChars:"g"},
+ "g", "KeyG", KeyboardEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{shiftKey:1}, chars:"G", unmodifiedChars:"G"},
+ "G", "KeyG", KeyboardEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{ctrlKey:1}, chars:"\u0007", unmodifiedChars:"g"},
+ "g", "KeyG", KeyboardEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0007", unmodifiedChars:"G"},
+ "G", "KeyG", KeyboardEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{altKey:1}, chars:"\u00A9", unmodifiedChars:"g"},
+ "\u00A9", "KeyG", KeyboardEvent.DOM_VK_G, "\u00A9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u02DD", unmodifiedChars:"G"},
+ "\u02DD", "KeyG", KeyboardEvent.DOM_VK_G, "\u02DD", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0007", unmodifiedChars:"g"},
+ "\u00A9", "KeyG", KeyboardEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0007", unmodifiedChars:"G"},
+ "\u02DD", "KeyG", KeyboardEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{metaKey:1}, chars:"g", unmodifiedChars:"g"},
+ "g", "KeyG", KeyboardEvent.DOM_VK_G, "g", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{}, chars:"h", unmodifiedChars:"h"},
+ "h", "KeyH", KeyboardEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{shiftKey:1}, chars:"H", unmodifiedChars:"H"},
+ "H", "KeyH", KeyboardEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{ctrlKey:1}, chars:"\u0008", unmodifiedChars:"h"},
+ "h", "KeyH", KeyboardEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0008", unmodifiedChars:"H"},
+ "H", "KeyH", KeyboardEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{altKey:1}, chars:"\u02D9", unmodifiedChars:"h"},
+ "\u02D9", "KeyH", KeyboardEvent.DOM_VK_H, "\u02D9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00D3", unmodifiedChars:"H"},
+ "\u00D3", "KeyH", KeyboardEvent.DOM_VK_H, "\u00D3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0008", unmodifiedChars:"h"},
+ "\u02D9", "KeyH", KeyboardEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0008", unmodifiedChars:"H"},
+ "\u00D3", "KeyH", KeyboardEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{metaKey:1}, chars:"h", unmodifiedChars:"h"},
+ "h", "KeyH", KeyboardEvent.DOM_VK_H, "h", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{}, chars:"i", unmodifiedChars:"i"},
+ "i", "KeyI", KeyboardEvent.DOM_VK_I, "i", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{shiftKey:1}, chars:"I", unmodifiedChars:"I"},
+ "I", "KeyI", KeyboardEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{ctrlKey:1}, chars:"\u0009", unmodifiedChars:"i"},
+ "i", "KeyI", KeyboardEvent.DOM_VK_I, "i", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0009", unmodifiedChars:"I"},
+ "I", "KeyI", KeyboardEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{altKey:1}, chars:"", unmodifiedChars:"i"},
+ "Dead", "KeyI", KeyboardEvent.DOM_VK_I, "\u02C6", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // dead key
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u02C6", unmodifiedChars:"I"},
+ "\u02C6", "KeyI", KeyboardEvent.DOM_VK_I, "\u02C6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0009", unmodifiedChars:"i"},
+ "Dead", "KeyI", KeyboardEvent.DOM_VK_I, "i", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0009", unmodifiedChars:"I"},
+ "\u02C6", "KeyI", KeyboardEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // XXX This test causes memory leak.
+ // yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ // modifiers:{metaKey:1}, chars:"i", unmodifiedChars:"i"},
+ // "i", "KeyI", KeyboardEvent.DOM_VK_I, "i", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{}, chars:"j", unmodifiedChars:"j"},
+ "j", "KeyJ", KeyboardEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{shiftKey:1}, chars:"J", unmodifiedChars:"J"},
+ "J", "KeyJ", KeyboardEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{ctrlKey:1}, chars:"\u000A", unmodifiedChars:"j"},
+ "j", "KeyJ", KeyboardEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000A", unmodifiedChars:"J"},
+ "J", "KeyJ", KeyboardEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{altKey:1}, chars:"\u2206", unmodifiedChars:"j"},
+ "\u2206", "KeyJ", KeyboardEvent.DOM_VK_J, "\u2206", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00D4", unmodifiedChars:"J"},
+ "\u00D4", "KeyJ", KeyboardEvent.DOM_VK_J, "\u00D4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u000A", unmodifiedChars:"j"},
+ "\u2206", "KeyJ", KeyboardEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u000A", unmodifiedChars:"J"},
+ "\u00D4", "KeyJ", KeyboardEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{metaKey:1}, chars:"j", unmodifiedChars:"j"},
+ "j", "KeyJ", KeyboardEvent.DOM_VK_J, "j", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{}, chars:"k", unmodifiedChars:"k"},
+ "k", "KeyK", KeyboardEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{shiftKey:1}, chars:"K", unmodifiedChars:"K"},
+ "K", "KeyK", KeyboardEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{ctrlKey:1}, chars:"\u000B", unmodifiedChars:"k"},
+ "k", "KeyK", KeyboardEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000B", unmodifiedChars:"K"},
+ "K", "KeyK", KeyboardEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{altKey:1}, chars:"\u02DA", unmodifiedChars:"k"},
+ "\u02DA", "KeyK", KeyboardEvent.DOM_VK_K, "\u02DA", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\uF8FF", unmodifiedChars:"K"},
+ "\uF8FF", "KeyK", KeyboardEvent.DOM_VK_K, "\uF8FF", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u000B", unmodifiedChars:"k"},
+ "\u02DA", "KeyK", KeyboardEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u000B", unmodifiedChars:"K"},
+ "\uF8FF", "KeyK", KeyboardEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{metaKey:1}, chars:"k", unmodifiedChars:"k"},
+ "k", "KeyK", KeyboardEvent.DOM_VK_K, "k", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{}, chars:"l", unmodifiedChars:"l"},
+ "l", "KeyL", KeyboardEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{shiftKey:1}, chars:"L", unmodifiedChars:"L"},
+ "L", "KeyL", KeyboardEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{ctrlKey:1}, chars:"\u000C", unmodifiedChars:"l"},
+ "l", "KeyL", KeyboardEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000C", unmodifiedChars:"L"},
+ "L", "KeyL", KeyboardEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{altKey:1}, chars:"\u00AC", unmodifiedChars:"l"},
+ "\u00AC", "KeyL", KeyboardEvent.DOM_VK_L, "\u00AC", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00D2", unmodifiedChars:"L"},
+ "\u00D2", "KeyL", KeyboardEvent.DOM_VK_L, "\u00D2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u000C", unmodifiedChars:"l"},
+ "\u00AC", "KeyL", KeyboardEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u000C", unmodifiedChars:"L"},
+ "\u00D2", "KeyL", KeyboardEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{metaKey:1}, chars:"l", unmodifiedChars:"l"},
+ "l", "KeyL", KeyboardEvent.DOM_VK_L, "l", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{}, chars:"m", unmodifiedChars:"m"},
+ "m", "KeyM", KeyboardEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{shiftKey:1}, chars:"M", unmodifiedChars:"M"},
+ "M", "KeyM", KeyboardEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{ctrlKey:1}, chars:"\u000D", unmodifiedChars:"m"},
+ "m", "KeyM", KeyboardEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000D", unmodifiedChars:"M"},
+ "M", "KeyM", KeyboardEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{altKey:1}, chars:"\u00B5", unmodifiedChars:"m"},
+ "\u00B5", "KeyM", KeyboardEvent.DOM_VK_M, "\u00B5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C2", unmodifiedChars:"M"},
+ "\u00C2", "KeyM", KeyboardEvent.DOM_VK_M, "\u00C2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u000D", unmodifiedChars:"m"},
+ "\u00B5", "KeyM", KeyboardEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u000D", unmodifiedChars:"M"},
+ "\u00C2", "KeyM", KeyboardEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{metaKey:1}, chars:"m", unmodifiedChars:"m"},
+ "m", "KeyM", KeyboardEvent.DOM_VK_M, "m", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{}, chars:"n", unmodifiedChars:"n"},
+ "n", "KeyN", KeyboardEvent.DOM_VK_N, "n", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{shiftKey:1}, chars:"N", unmodifiedChars:"N"},
+ "N", "KeyN", KeyboardEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{ctrlKey:1}, chars:"\u000E", unmodifiedChars:"n"},
+ "n", "KeyN", KeyboardEvent.DOM_VK_N, "n", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000E", unmodifiedChars:"N"},
+ "N", "KeyN", KeyboardEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{altKey:1}, chars:"", unmodifiedChars:"n"},
+ "Dead", "KeyN", KeyboardEvent.DOM_VK_N, "\u02DC", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // dead key
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u02DC", unmodifiedChars:"N"},
+ "\u02DC", "KeyN", KeyboardEvent.DOM_VK_N, "\u02DC", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u000E", unmodifiedChars:"n"},
+ "Dead", "KeyN", KeyboardEvent.DOM_VK_N, "n", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u000E", unmodifiedChars:"N"},
+ "\u02DC", "KeyN", KeyboardEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{metaKey:1}, chars:"n", unmodifiedChars:"n"},
+ "n", "KeyN", KeyboardEvent.DOM_VK_N, "n", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{}, chars:"o", unmodifiedChars:"o"},
+ "o", "KeyO", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{shiftKey:1}, chars:"O", unmodifiedChars:"O"},
+ "O", "KeyO", KeyboardEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{ctrlKey:1}, chars:"\u000F", unmodifiedChars:"o"},
+ "o", "KeyO", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000F", unmodifiedChars:"O"},
+ "O", "KeyO", KeyboardEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{altKey:1}, chars:"\u00F8", unmodifiedChars:"o"},
+ "\u00F8", "KeyO", KeyboardEvent.DOM_VK_O, "\u00F8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00D8", unmodifiedChars:"O"},
+ "\u00D8", "KeyO", KeyboardEvent.DOM_VK_O, "\u00D8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u000F", unmodifiedChars:"o"},
+ "\u00F8", "KeyO", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u000F", unmodifiedChars:"O"},
+ "\u00D8", "KeyO", KeyboardEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{metaKey:1}, chars:"o", unmodifiedChars:"o"},
+ "o", "KeyO", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{}, chars:"p", unmodifiedChars:"p"},
+ "p", "KeyP", KeyboardEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{shiftKey:1}, chars:"P", unmodifiedChars:"P"},
+ "P", "KeyP", KeyboardEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{ctrlKey:1}, chars:"\u0010", unmodifiedChars:"p"},
+ "p", "KeyP", KeyboardEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0010", unmodifiedChars:"P"},
+ "P", "KeyP", KeyboardEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{altKey:1}, chars:"\u03C0", unmodifiedChars:"p"},
+ "\u03C0", "KeyP", KeyboardEvent.DOM_VK_P, "\u03C0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u220F", unmodifiedChars:"P"},
+ "\u220F", "KeyP", KeyboardEvent.DOM_VK_P, "\u220F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0010", unmodifiedChars:"p"},
+ "\u03C0", "KeyP", KeyboardEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0010", unmodifiedChars:"P"},
+ "\u220F", "KeyP", KeyboardEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // XXX This test starts private browsing mode (stopped at the confirmation dialog)
+ // yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ // modifiers:{metaKey:1}, chars:"p", unmodifiedChars:"p"},
+ // "p", "KeyP", KeyboardEvent.DOM_VK_P, "p", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{}, chars:"q", unmodifiedChars:"q"},
+ "q", "KeyQ", KeyboardEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{shiftKey:1}, chars:"Q", unmodifiedChars:"Q"},
+ "Q", "KeyQ", KeyboardEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{ctrlKey:1}, chars:"\u0011", unmodifiedChars:"q"},
+ "q", "KeyQ", KeyboardEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0011", unmodifiedChars:"Q"},
+ "Q", "KeyQ", KeyboardEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{altKey:1}, chars:"\u0153", unmodifiedChars:"q"},
+ "\u0153", "KeyQ", KeyboardEvent.DOM_VK_Q, "\u0153", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u0152", unmodifiedChars:"Q"},
+ "\u0152", "KeyQ", KeyboardEvent.DOM_VK_Q, "\u0152", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0011", unmodifiedChars:"q"},
+ "\u0153", "KeyQ", KeyboardEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0011", unmodifiedChars:"Q"},
+ "\u0152", "KeyQ", KeyboardEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{metaKey:1}, chars:"q", unmodifiedChars:"q"},
+ "q", "KeyQ", KeyboardEvent.DOM_VK_Q, "q", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{}, chars:"r", unmodifiedChars:"r"},
+ "r", "KeyR", KeyboardEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{shiftKey:1}, chars:"R", unmodifiedChars:"R"},
+ "R", "KeyR", KeyboardEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{ctrlKey:1}, chars:"\u0012", unmodifiedChars:"r"},
+ "r", "KeyR", KeyboardEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0012", unmodifiedChars:"R"},
+ "R", "KeyR", KeyboardEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{altKey:1}, chars:"\u00AE", unmodifiedChars:"r"},
+ "\u00AE", "KeyR", KeyboardEvent.DOM_VK_R, "\u00AE", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u2030", unmodifiedChars:"R"},
+ "\u2030", "KeyR", KeyboardEvent.DOM_VK_R, "\u2030", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0012", unmodifiedChars:"r"},
+ "\u00AE", "KeyR", KeyboardEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0012", unmodifiedChars:"R"},
+ "\u2030", "KeyR", KeyboardEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // XXX This test makes some tabs and dialogs.
+ // yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ // modifiers:{metaKey:1}, chars:"r", unmodifiedChars:"r"},
+ // "r", "KeyR", KeyboardEvent.DOM_VK_R, "r", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{}, chars:"s", unmodifiedChars:"s"},
+ "s", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{shiftKey:1}, chars:"S", unmodifiedChars:"S"},
+ "S", "KeyS", KeyboardEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{ctrlKey:1}, chars:"\u0013", unmodifiedChars:"s"},
+ "s", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0013", unmodifiedChars:"S"},
+ "S", "KeyS", KeyboardEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{altKey:1}, chars:"\u00DF", unmodifiedChars:"s"},
+ "\u00DF", "KeyS", KeyboardEvent.DOM_VK_S, "\u00DF", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00CD", unmodifiedChars:"S"},
+ "\u00CD", "KeyS", KeyboardEvent.DOM_VK_S, "\u00CD", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0013", unmodifiedChars:"s"},
+ "\u00DF", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0013", unmodifiedChars:"S"},
+ "\u00CD", "KeyS", KeyboardEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{metaKey:1}, chars:"s", unmodifiedChars:"s"},
+ "s", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{}, chars:"t", unmodifiedChars:"t"},
+ "t", "KeyT", KeyboardEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{shiftKey:1}, chars:"T", unmodifiedChars:"T"},
+ "T", "KeyT", KeyboardEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{ctrlKey:1}, chars:"\u0014", unmodifiedChars:"t"},
+ "t", "KeyT", KeyboardEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0014", unmodifiedChars:"T"},
+ "T", "KeyT", KeyboardEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{altKey:1}, chars:"\u2020", unmodifiedChars:"t"},
+ "\u2020", "KeyT", KeyboardEvent.DOM_VK_T, "\u2020", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u02C7", unmodifiedChars:"T"},
+ "\u02C7", "KeyT", KeyboardEvent.DOM_VK_T, "\u02C7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0014", unmodifiedChars:"t"},
+ "\u2020", "KeyT", KeyboardEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0014", unmodifiedChars:"T"},
+ "\u02C7", "KeyT", KeyboardEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{metaKey:1}, chars:"t", unmodifiedChars:"t"},
+ "t", "KeyT", KeyboardEvent.DOM_VK_T, "t", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{}, chars:"u", unmodifiedChars:"u"},
+ "u", "KeyU", KeyboardEvent.DOM_VK_U, "u", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{shiftKey:1}, chars:"U", unmodifiedChars:"U"},
+ "U", "KeyU", KeyboardEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{ctrlKey:1}, chars:"\u0015", unmodifiedChars:"u"},
+ "u", "KeyU", KeyboardEvent.DOM_VK_U, "u", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0015", unmodifiedChars:"U"},
+ "U", "KeyU", KeyboardEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{altKey:1}, chars:"", unmodifiedChars:"u"},
+ "Dead", "KeyU", KeyboardEvent.DOM_VK_U, "\u00A8", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // dead key
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00A8", unmodifiedChars:"U"},
+ "\u00A8", "KeyU", KeyboardEvent.DOM_VK_U, "\u00A8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0015", unmodifiedChars:"u"},
+ "Dead", "KeyU", KeyboardEvent.DOM_VK_U, "u", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0015", unmodifiedChars:"U"},
+ "\u00A8", "KeyU", KeyboardEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{metaKey:1}, chars:"u", unmodifiedChars:"u"},
+ "u", "KeyU", KeyboardEvent.DOM_VK_U, "u", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{}, chars:"v", unmodifiedChars:"v"},
+ "v", "KeyV", KeyboardEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{shiftKey:1}, chars:"V", unmodifiedChars:"V"},
+ "V", "KeyV", KeyboardEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{ctrlKey:1}, chars:"\u0016", unmodifiedChars:"v"},
+ "v", "KeyV", KeyboardEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0016", unmodifiedChars:"V"},
+ "V", "KeyV", KeyboardEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{altKey:1}, chars:"\u221A", unmodifiedChars:"v"},
+ "\u221A", "KeyV", KeyboardEvent.DOM_VK_V, "\u221A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u25CA", unmodifiedChars:"V"},
+ "\u25CA", "KeyV", KeyboardEvent.DOM_VK_V, "\u25CA", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0016", unmodifiedChars:"v"},
+ "\u221A", "KeyV", KeyboardEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0016", unmodifiedChars:"V"},
+ "\u25CA", "KeyV", KeyboardEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{metaKey:1}, chars:"v", unmodifiedChars:"v"},
+ "v", "KeyV", KeyboardEvent.DOM_VK_V, "v", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{}, chars:"w", unmodifiedChars:"w"},
+ "w", "KeyW", KeyboardEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{shiftKey:1}, chars:"W", unmodifiedChars:"W"},
+ "W", "KeyW", KeyboardEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{ctrlKey:1}, chars:"\u0017", unmodifiedChars:"w"},
+ "w", "KeyW", KeyboardEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0017", unmodifiedChars:"W"},
+ "W", "KeyW", KeyboardEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{altKey:1}, chars:"\u2211", unmodifiedChars:"w"},
+ "\u2211", "KeyW", KeyboardEvent.DOM_VK_W, "\u2211", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u201E", unmodifiedChars:"W"},
+ "\u201E", "KeyW", KeyboardEvent.DOM_VK_W, "\u201E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0017", unmodifiedChars:"w"},
+ "\u2211", "KeyW", KeyboardEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0017", unmodifiedChars:"W"},
+ "\u201E", "KeyW", KeyboardEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{metaKey:1}, chars:"w", unmodifiedChars:"w"},
+ "w", "KeyW", KeyboardEvent.DOM_VK_W, "w", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{}, chars:"x", unmodifiedChars:"x"},
+ "x", "KeyX", KeyboardEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{shiftKey:1}, chars:"X", unmodifiedChars:"X"},
+ "X", "KeyX", KeyboardEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{ctrlKey:1}, chars:"\u0018", unmodifiedChars:"x"},
+ "x", "KeyX", KeyboardEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0018", unmodifiedChars:"X"},
+ "X", "KeyX", KeyboardEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{altKey:1}, chars:"\u2248", unmodifiedChars:"x"},
+ "\u2248", "KeyX", KeyboardEvent.DOM_VK_X, "\u2248", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u02DB", unmodifiedChars:"X"},
+ "\u02DB", "KeyX", KeyboardEvent.DOM_VK_X, "\u02DB", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0018", unmodifiedChars:"x"},
+ "\u2248", "KeyX", KeyboardEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0018", unmodifiedChars:"X"},
+ "\u02DB", "KeyX", KeyboardEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{metaKey:1}, chars:"x", unmodifiedChars:"x"},
+ "x", "KeyX", KeyboardEvent.DOM_VK_X, "x", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{}, chars:"y", unmodifiedChars:"y"},
+ "y", "KeyY", KeyboardEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{shiftKey:1}, chars:"Y", unmodifiedChars:"Y"},
+ "Y", "KeyY", KeyboardEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{ctrlKey:1}, chars:"\u0019", unmodifiedChars:"y"},
+ "y", "KeyY", KeyboardEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0019", unmodifiedChars:"Y"},
+ "Y", "KeyY", KeyboardEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{altKey:1}, chars:"\u00A5", unmodifiedChars:"y"},
+ "\u00A5", "KeyY", KeyboardEvent.DOM_VK_Y, "\u00A5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C1", unmodifiedChars:"Y"},
+ "\u00C1", "KeyY", KeyboardEvent.DOM_VK_Y, "\u00C1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0019", unmodifiedChars:"y"},
+ "\u00A5", "KeyY", KeyboardEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0019", unmodifiedChars:"Y"},
+ "\u00C1", "KeyY", KeyboardEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{metaKey:1}, chars:"y", unmodifiedChars:"y"},
+ "y", "KeyY", KeyboardEvent.DOM_VK_Y, "y", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{}, chars:"z", unmodifiedChars:"z"},
+ "z", "KeyZ", KeyboardEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{shiftKey:1}, chars:"Z", unmodifiedChars:"Z"},
+ "Z", "KeyZ", KeyboardEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{ctrlKey:1}, chars:"\u001A", unmodifiedChars:"z"},
+ "z", "KeyZ", KeyboardEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001A", unmodifiedChars:"Z"},
+ "Z", "KeyZ", KeyboardEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{altKey:1}, chars:"\u03A9", unmodifiedChars:"z"},
+ "\u03A9", "KeyZ", KeyboardEvent.DOM_VK_Z, "\u03A9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00B8", unmodifiedChars:"Z"},
+ "\u00B8", "KeyZ", KeyboardEvent.DOM_VK_Z, "\u00B8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u001A", unmodifiedChars:"z"},
+ "\u03A9", "KeyZ", KeyboardEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001A", unmodifiedChars:"Z"},
+ "\u00B8", "KeyZ", KeyboardEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{metaKey:1}, chars:"z", unmodifiedChars:"z"},
+ "z", "KeyZ", KeyboardEvent.DOM_VK_Z, "z", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // numeric
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{}, chars:"1", unmodifiedChars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{shiftKey:1}, chars:"!", unmodifiedChars:"!"},
+ "!", "Digit1", KeyboardEvent.DOM_VK_1, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{ctrlKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"!"},
+ "!", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1}, chars:"\u00A1", unmodifiedChars:"1"},
+ "\u00A1", "Digit1", KeyboardEvent.DOM_VK_1, "\u00A1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u2044", unmodifiedChars:"!"},
+ "\u2044", "Digit1", KeyboardEvent.DOM_VK_1, "\u2044", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"1", unmodifiedChars:"1"},
+ "\u00A1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"!"},
+ "\u2044", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{metaKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{}, chars:"2", unmodifiedChars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{shiftKey:1}, chars:"@", unmodifiedChars:"@"},
+ "@", "Digit2", KeyboardEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{ctrlKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0000", unmodifiedChars:"@"},
+ "@", "Digit2", KeyboardEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1}, chars:"\u2122", unmodifiedChars:"2"},
+ "\u2122", "Digit2", KeyboardEvent.DOM_VK_2, "\u2122", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u20AC", unmodifiedChars:"@"},
+ "\u20AC", "Digit2", KeyboardEvent.DOM_VK_2, "\u20AC", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"2", unmodifiedChars:"2"},
+ "\u2122", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0000", unmodifiedChars:"@"},
+ "\u20AC", "Digit2", KeyboardEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{metaKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{}, chars:"3", unmodifiedChars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{shiftKey:1}, chars:"#", unmodifiedChars:"#"},
+ "#", "Digit3", KeyboardEvent.DOM_VK_3, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{ctrlKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"#"},
+ "#", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1}, chars:"\u00A3", unmodifiedChars:"3"},
+ "\u00A3", "Digit3", KeyboardEvent.DOM_VK_3, "\u00A3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u2039", unmodifiedChars:"#"},
+ "\u2039", "Digit3", KeyboardEvent.DOM_VK_3, "\u2039", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"3", unmodifiedChars:"3"},
+ "\u00A3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"#"},
+ "\u2039", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{metaKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{}, chars:"4", unmodifiedChars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{shiftKey:1}, chars:"$", unmodifiedChars:"$"},
+ "$", "Digit4", KeyboardEvent.DOM_VK_4, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{ctrlKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"$"},
+ "$", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1}, chars:"\u00A2", unmodifiedChars:"4"},
+ "\u00A2", "Digit4", KeyboardEvent.DOM_VK_4, "\u00A2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u203A", unmodifiedChars:"$"},
+ "\u203A", "Digit4", KeyboardEvent.DOM_VK_4, "\u203A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"4", unmodifiedChars:"4"},
+ "\u00A2", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"$"},
+ "\u203A", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{metaKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{}, chars:"5", unmodifiedChars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{shiftKey:1}, chars:"%", unmodifiedChars:"%"},
+ "%", "Digit5", KeyboardEvent.DOM_VK_5, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{ctrlKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"%"},
+ "%", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1}, chars:"\u221E", unmodifiedChars:"5"},
+ "\u221E", "Digit5", KeyboardEvent.DOM_VK_5, "\u221E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\uFB01", unmodifiedChars:"%"},
+ "\uFB01", "Digit5", KeyboardEvent.DOM_VK_5, "\uFB01", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"5", unmodifiedChars:"5"},
+ "\u221E", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"%"},
+ "\uFB01", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{metaKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{}, chars:"6", unmodifiedChars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{shiftKey:1}, chars:"^", unmodifiedChars:"^"},
+ "^", "Digit6", KeyboardEvent.DOM_VK_6, "^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{ctrlKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001E", unmodifiedChars:"^"},
+ "^", "Digit6", KeyboardEvent.DOM_VK_6, "\u0000", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1}, chars:"\u00A7", unmodifiedChars:"6"},
+ "\u00A7", "Digit6", KeyboardEvent.DOM_VK_6, "\u00A7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\uFB02", unmodifiedChars:"^"},
+ "\uFB02", "Digit6", KeyboardEvent.DOM_VK_6, "\uFB02", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"6", unmodifiedChars:"6"},
+ "\u00A7", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001E", unmodifiedChars:"^"},
+ "\uFB02", "Digit6", KeyboardEvent.DOM_VK_6, "\u0000", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{metaKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{}, chars:"7", unmodifiedChars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{shiftKey:1}, chars:"&", unmodifiedChars:"&"},
+ "&", "Digit7", KeyboardEvent.DOM_VK_7, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{ctrlKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"&"},
+ "&", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1}, chars:"\u00B6", unmodifiedChars:"7"},
+ "\u00B6", "Digit7", KeyboardEvent.DOM_VK_7, "\u00B6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u2021", unmodifiedChars:"&"},
+ "\u2021", "Digit7", KeyboardEvent.DOM_VK_7, "\u2021", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"7", unmodifiedChars:"7"},
+ "\u00B6", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"&"},
+ "\u2021", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{metaKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{}, chars:"8", unmodifiedChars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{shiftKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "Digit8", KeyboardEvent.DOM_VK_8, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{ctrlKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"*"},
+ "*", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1}, chars:"\u2022", unmodifiedChars:"8"},
+ "\u2022", "Digit8", KeyboardEvent.DOM_VK_8, "\u2022", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00B0", unmodifiedChars:"*"},
+ "\u00B0", "Digit8", KeyboardEvent.DOM_VK_8, "\u00B0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"8", unmodifiedChars:"8"},
+ "\u2022", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"*"},
+ "\u00B0", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{metaKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{}, chars:"9", unmodifiedChars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{shiftKey:1}, chars:"(", unmodifiedChars:"("},
+ "(", "Digit9", KeyboardEvent.DOM_VK_9, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{ctrlKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"9", unmodifiedChars:"("},
+ "(", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1}, chars:"\u00AA", unmodifiedChars:"9"},
+ "\u00AA", "Digit9", KeyboardEvent.DOM_VK_9, "\u00AA", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00B7", unmodifiedChars:"("},
+ "\u00B7", "Digit9", KeyboardEvent.DOM_VK_9, "\u00B7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"9", unmodifiedChars:"9"},
+ "\u00AA", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"9", unmodifiedChars:"("},
+ "\u00B7", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{metaKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{}, chars:"0", unmodifiedChars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{shiftKey:1}, chars:")", unmodifiedChars:")"},
+ ")", "Digit0", KeyboardEvent.DOM_VK_0, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{ctrlKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"0", unmodifiedChars:")"},
+ ")", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1}, chars:"\u00BA", unmodifiedChars:"0"},
+ "\u00BA", "Digit0", KeyboardEvent.DOM_VK_0, "\u00BA", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u201A", unmodifiedChars:")"},
+ "\u201A", "Digit0", KeyboardEvent.DOM_VK_0, "\u201A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"0", unmodifiedChars:"0"},
+ "\u00BA", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"0", unmodifiedChars:")"},
+ "\u201A", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{metaKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // other chracters
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{}, chars:"`", unmodifiedChars:"`"},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{shiftKey:1}, chars:"~", unmodifiedChars:"~"},
+ "~", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "~", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{ctrlKey:1}, chars:"`", unmodifiedChars:"`"},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"`", unmodifiedChars:"~"},
+ "~", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{altKey:1}, chars:"", unmodifiedChars:"`"},
+ "Dead", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // dead key
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{altKey:1, shiftKey:1}, chars:"`", unmodifiedChars:"~"},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"`", unmodifiedChars:"`"},
+ "Dead", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"`", unmodifiedChars:"~"},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{metaKey:1}, chars:"`", unmodifiedChars:"`"},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{}, chars:"-", unmodifiedChars:"-"},
+ "-", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{shiftKey:1}, chars:"_", unmodifiedChars:"_"},
+ "_", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{ctrlKey:1}, chars:"\u001F", unmodifiedChars:"-"},
+ "-", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001F", unmodifiedChars:"_"},
+ "_", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{altKey:1}, chars:"\u2013", unmodifiedChars:"-"},
+ "\u2013", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "\u2013", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u2014", unmodifiedChars:"_"},
+ "\u2014", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "\u2014", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u001F", unmodifiedChars:"-"},
+ "\u2013", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001F", unmodifiedChars:"_"},
+ "\u2014", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{metaKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "-", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{}, chars:"=", unmodifiedChars:"="},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{shiftKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "Equal", KeyboardEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{ctrlKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"=", unmodifiedChars:"+"},
+ "+", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{altKey:1}, chars:"\u2260", unmodifiedChars:"="},
+ "\u2260", "Equal", KeyboardEvent.DOM_VK_EQUALS, "\u2260", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00B1", unmodifiedChars:"+"},
+ "\u00B1", "Equal", KeyboardEvent.DOM_VK_EQUALS, "\u00B1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"=", unmodifiedChars:"="},
+ "\u2260", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"=", unmodifiedChars:"+"},
+ "\u00B1", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{metaKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{}, chars:"[", unmodifiedChars:"["},
+ "[", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{shiftKey:1}, chars:"{", unmodifiedChars:"{"},
+ "{", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{ctrlKey:1}, chars:"\u001B", unmodifiedChars:"["},
+ "[", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001B", unmodifiedChars:"{"},
+ "{", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{altKey:1}, chars:"\u201C", unmodifiedChars:"["},
+ "\u201C", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "\u201C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u201D", unmodifiedChars:"{"},
+ "\u201D", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "\u201D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u001B", unmodifiedChars:"["},
+ "\u201C", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001B", unmodifiedChars:"{"},
+ "\u201D", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{metaKey:1}, chars:"[", unmodifiedChars:"["},
+ "[", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "[", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{}, chars:"]", unmodifiedChars:"]"},
+ "]", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "]", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{shiftKey:1}, chars:"}", unmodifiedChars:"}"},
+ "}", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{ctrlKey:1}, chars:"\u001D", unmodifiedChars:"]"},
+ "]", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001D", unmodifiedChars:"}"},
+ "}", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{altKey:1}, chars:"\u2018", unmodifiedChars:"]"},
+ "\u2018", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "\u2018", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u2019", unmodifiedChars:"}"},
+ "\u2019", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "\u2019", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u001D", unmodifiedChars:"]"},
+ "\u2018", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001D", unmodifiedChars:"}"},
+ "\u2019", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{metaKey:1}, chars:"]", unmodifiedChars:"]"},
+ "]", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "]", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{}, chars:"\\", unmodifiedChars:"\\"},
+ "\\", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\\", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{shiftKey:1}, chars:"|", unmodifiedChars:"|"},
+ "|", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "|", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{ctrlKey:1}, chars:"\u001C", unmodifiedChars:"\\"},
+ "\\", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001C", unmodifiedChars:"|"},
+ "|", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{altKey:1}, chars:"\u00AB", unmodifiedChars:"\\"},
+ "\u00AB", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\u00AB", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00BB", unmodifiedChars:"|"},
+ "\u00BB", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\u00BB", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u001C", unmodifiedChars:"\\"},
+ "\u00AB", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001C", unmodifiedChars:"|"},
+ "\u00BB", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{metaKey:1}, chars:"\\", unmodifiedChars:"\\"},
+ "\\", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\\", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{}, chars:";", unmodifiedChars:";"},
+ ";", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{shiftKey:1}, chars:":", unmodifiedChars:":"},
+ ":", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{ctrlKey:1}, chars:";", unmodifiedChars:";"},
+ ";", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:";", unmodifiedChars:":"},
+ ":", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{altKey:1}, chars:"\u2026", unmodifiedChars:";"},
+ "\u2026", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, "\u2026", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00DA", unmodifiedChars:":"},
+ "\u00DA", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, "\u00DA", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{altKey:1, ctrlKey:1}, chars:";", unmodifiedChars:";"},
+ "\u2026", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:";", unmodifiedChars:":"},
+ "\u00DA", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{metaKey:1}, chars:";", unmodifiedChars:";"},
+ ";", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{}, chars:"'", unmodifiedChars:"'"},
+ "'", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{shiftKey:1}, chars:"\"", unmodifiedChars:"\""},
+ "\"", "Quote", KeyboardEvent.DOM_VK_QUOTE, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{ctrlKey:1}, chars:"'", unmodifiedChars:"'"},
+ "'", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"'", unmodifiedChars:"\""},
+ "\"", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{altKey:1}, chars:"\u00E6", unmodifiedChars:"'"},
+ "\u00E6", "Quote", KeyboardEvent.DOM_VK_QUOTE, "\u00E6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C6", unmodifiedChars:"\""},
+ "\u00C6", "Quote", KeyboardEvent.DOM_VK_QUOTE, "\u00C6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"'", unmodifiedChars:"'"},
+ "\u00E6", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"'", unmodifiedChars:"\""},
+ "\u00C6", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{metaKey:1}, chars:"'", unmodifiedChars:"'"},
+ "'", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{}, chars:",", unmodifiedChars:","},
+ ",", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{shiftKey:1}, chars:"<", unmodifiedChars:"<"},
+ "<", "Comma", KeyboardEvent.DOM_VK_COMMA, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{ctrlKey:1}, chars:",", unmodifiedChars:","},
+ ",", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:",", unmodifiedChars:"<"},
+ "<", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{altKey:1}, chars:"\u2264", unmodifiedChars:","},
+ "\u2264", "Comma", KeyboardEvent.DOM_VK_COMMA, "\u2264", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00AF", unmodifiedChars:"<"},
+ "\u00AF", "Comma", KeyboardEvent.DOM_VK_COMMA, "\u00AF", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{altKey:1, ctrlKey:1}, chars:",", unmodifiedChars:","},
+ "\u2264", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:",", unmodifiedChars:"<"},
+ "\u00AF", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{metaKey:1}, chars:",", unmodifiedChars:","},
+ ",", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{}, chars:".", unmodifiedChars:"."},
+ ".", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{shiftKey:1}, chars:">", unmodifiedChars:">"},
+ ">", "Period", KeyboardEvent.DOM_VK_PERIOD, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{ctrlKey:1}, chars:".", unmodifiedChars:"."},
+ ".", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:".", unmodifiedChars:">"},
+ ">", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{altKey:1}, chars:"\u2265", unmodifiedChars:"."},
+ "\u2265", "Period", KeyboardEvent.DOM_VK_PERIOD, "\u2265", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u02D8", unmodifiedChars:">"},
+ "\u02D8", "Period", KeyboardEvent.DOM_VK_PERIOD, "\u02D8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{altKey:1, ctrlKey:1}, chars:".", unmodifiedChars:"."},
+ "\u2265", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:".", unmodifiedChars:">"},
+ "\u02D8", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{metaKey:1}, chars:".", unmodifiedChars:"."},
+ ".", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{}, chars:"/", unmodifiedChars:"/"},
+ "/", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{shiftKey:1}, chars:"?", unmodifiedChars:"?"},
+ "?", "Slash", KeyboardEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{ctrlKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"/", unmodifiedChars:"?"},
+ "?", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{altKey:1}, chars:"\u00F7", unmodifiedChars:"/"},
+ "\u00F7", "Slash", KeyboardEvent.DOM_VK_SLASH, "\u00F7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00BF", unmodifiedChars:"?"},
+ "\u00BF", "Slash", KeyboardEvent.DOM_VK_SLASH, "\u00BF", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"/", unmodifiedChars:"/"},
+ "\u00F7", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"/", unmodifiedChars:"?"},
+ "\u00BF", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{metaKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // numpad
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+
+ // French, numeric
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{}, chars:"&", unmodifiedChars:"&"},
+ "&", "Digit1", KeyboardEvent.DOM_VK_1, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{ctrlKey:1}, chars:"1", unmodifiedChars:"&"},
+ "&", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1}, chars:"\uF8FF", unmodifiedChars:"&"},
+ "\uF8FF", "Digit1", KeyboardEvent.DOM_VK_1, "\uF8FF", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:"", unmodifiedChars:"1"},
+ "Dead", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"1", unmodifiedChars:"&"},
+ "\uF8FF", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "Dead", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{metaKey:1}, chars:"&", unmodifiedChars:"&"},
+ "&", "Digit1", KeyboardEvent.DOM_VK_1, "&", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{}, chars:"\u00E9", unmodifiedChars:"\u00E9"},
+ "\u00E9", "Digit2", KeyboardEvent.DOM_VK_2, "\u00E9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{ctrlKey:1}, chars:"2", unmodifiedChars:"\u00E9"},
+ "\u00E9", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1}, chars:"\u00EB", unmodifiedChars:"\u00E9"},
+ "\u00EB", "Digit2", KeyboardEvent.DOM_VK_2, "\u00EB", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u201E", unmodifiedChars:"2"},
+ "\u201E", "Digit2", KeyboardEvent.DOM_VK_2, "\u201E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"2", unmodifiedChars:"\u00E9"},
+ "\u00EB", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "\u201E", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{metaKey:1}, chars:"\u00E9", unmodifiedChars:"\u00E9"},
+ "\u00E9", "Digit2", KeyboardEvent.DOM_VK_2, "\u00E9", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{}, chars:"\"", unmodifiedChars:"\""},
+ "\"", "Digit3", KeyboardEvent.DOM_VK_3, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{shiftKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{ctrlKey:1}, chars:"3", unmodifiedChars:"\""},
+ "\"", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1}, chars:"\u201C", unmodifiedChars:"\""},
+ "\u201C", "Digit3", KeyboardEvent.DOM_VK_3, "\u201C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u201D", unmodifiedChars:"3"},
+ "\u201D", "Digit3", KeyboardEvent.DOM_VK_3, "\u201D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"3", unmodifiedChars:"\""},
+ "\u201C", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"3"},
+ "\u201D", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{metaKey:1}, chars:"\"", unmodifiedChars:"\""},
+ "\"", "Digit3", KeyboardEvent.DOM_VK_3, "\"", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Cmd+Shift+3 is a shortcut key of taking a snapshot
+ // yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ // modifiers:{metaKey:1, shiftKey:1}, chars:"\"", unmodifiedChars:"\""},
+ // "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{}, chars:"'", unmodifiedChars:"'"},
+ "'", "Digit4", KeyboardEvent.DOM_VK_4, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{ctrlKey:1}, chars:"4", unmodifiedChars:"'"},
+ "'", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1}, chars:"\u2018", unmodifiedChars:"'"},
+ "\u2018", "Digit4", KeyboardEvent.DOM_VK_4, "\u2018", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u2019", unmodifiedChars:"4"},
+ "\u2019", "Digit4", KeyboardEvent.DOM_VK_4, "\u2019", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"4", unmodifiedChars:"'"},
+ "\u2018", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ "\u2019", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{metaKey:1}, chars:"'", unmodifiedChars:"'"},
+ "'", "Digit4", KeyboardEvent.DOM_VK_4, "'", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Cmd+Shift+4 is a shortcut key of taking a snapshot in specific range
+ // yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ // modifiers:{metaKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ // "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{}, chars:"(", unmodifiedChars:"("},
+ "(", "Digit5", KeyboardEvent.DOM_VK_5, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{ctrlKey:1}, chars:"5", unmodifiedChars:"("},
+ "(", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1}, chars:"{", unmodifiedChars:"("},
+ "{", "Digit5", KeyboardEvent.DOM_VK_5, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1, shiftKey:1}, chars:"[", unmodifiedChars:"5"},
+ "[", "Digit5", KeyboardEvent.DOM_VK_5, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"5", unmodifiedChars:"("},
+ "{", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "[", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{metaKey:1}, chars:"(", unmodifiedChars:"("},
+ "(", "Digit5", KeyboardEvent.DOM_VK_5, "(", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{}, chars:"\u00A7", unmodifiedChars:"\u00A7"},
+ "\u00A7", "Digit6", KeyboardEvent.DOM_VK_6, "\u00A7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{shiftKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{ctrlKey:1}, chars:"\u001D", unmodifiedChars:"\u00A7"},
+ "\u00A7", "Digit6", KeyboardEvent.DOM_VK_6, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001D", unmodifiedChars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1}, chars:"\u00B6", unmodifiedChars:"\u00A7"},
+ "\u00B6", "Digit6", KeyboardEvent.DOM_VK_6, "\u00B6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00E5", unmodifiedChars:"6"},
+ "\u00E5", "Digit6", KeyboardEvent.DOM_VK_6, "\u00E5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u001D", unmodifiedChars:"\u00A7"},
+ "\u00B6", "Digit6", KeyboardEvent.DOM_VK_6, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001D", unmodifiedChars:"6"},
+ "\u00E5", "Digit6", KeyboardEvent.DOM_VK_6, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{metaKey:1}, chars:"\u00A7", unmodifiedChars:"\u00A7"},
+ "\u00A7", "Digit6", KeyboardEvent.DOM_VK_6, "\u00A7", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{}, chars:"\u00E8", unmodifiedChars:"\u00E8"},
+ "\u00E8", "Digit7", KeyboardEvent.DOM_VK_7, "\u00E8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{ctrlKey:1}, chars:"7", unmodifiedChars:"\u00E8"},
+ "\u00E8", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1}, chars:"\u00AB", unmodifiedChars:"\u00E8"},
+ "\u00AB", "Digit7", KeyboardEvent.DOM_VK_7, "\u00AB", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00BB", unmodifiedChars:"7"},
+ "\u00BB", "Digit7", KeyboardEvent.DOM_VK_7, "\u00BB", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"7", unmodifiedChars:"\u00E8"},
+ "\u00AB", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "\u00BB", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{metaKey:1}, chars:"\u00E8", unmodifiedChars:"\u00E8"},
+ "\u00E8", "Digit7", KeyboardEvent.DOM_VK_7, "\u00E8", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{}, chars:"!", unmodifiedChars:"!"},
+ "!", "Digit8", KeyboardEvent.DOM_VK_8, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{ctrlKey:1}, chars:"8", unmodifiedChars:"!"},
+ "!", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1}, chars:"\u00A1", unmodifiedChars:"!"},
+ "\u00A1", "Digit8", KeyboardEvent.DOM_VK_8, "\u00A1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00DB", unmodifiedChars:"8"},
+ "\u00DB", "Digit8", KeyboardEvent.DOM_VK_8, "\u00DB", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"8", unmodifiedChars:"!"},
+ "\u00A1", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "\u00DB", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{metaKey:1}, chars:"!", unmodifiedChars:"!"},
+ "!", "Digit8", KeyboardEvent.DOM_VK_8, "!", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{}, chars:"\u00E7", unmodifiedChars:"\u00E7"},
+ "\u00E7", "Digit9", KeyboardEvent.DOM_VK_9, "\u00E7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{shiftKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{ctrlKey:1}, chars:"\u001C", unmodifiedChars:"\u00E7"},
+ "\u00E7", "Digit9", KeyboardEvent.DOM_VK_9, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001C", unmodifiedChars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1}, chars:"\u00C7", unmodifiedChars:"\u00E7"},
+ "\u00C7", "Digit9", KeyboardEvent.DOM_VK_9, "\u00C7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C1", unmodifiedChars:"9"},
+ "\u00C1", "Digit9", KeyboardEvent.DOM_VK_9, "\u00C1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u001C", unmodifiedChars:"\u00E7"},
+ "\u00C7", "Digit9", KeyboardEvent.DOM_VK_9, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001C", unmodifiedChars:"9"},
+ "\u00C1", "Digit9", KeyboardEvent.DOM_VK_9, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{metaKey:1}, chars:"\u00E7", unmodifiedChars:"\u00E7"},
+ "\u00E7", "Digit9", KeyboardEvent.DOM_VK_9, "\u00E7", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{}, chars:"\u00E0", unmodifiedChars:"\u00E0"},
+ "\u00E0", "Digit0", KeyboardEvent.DOM_VK_0, "\u00E0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{shiftKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{ctrlKey:1}, chars:"", unmodifiedChars:"\u00E0"},
+ "\u00E0", "Digit0", KeyboardEvent.DOM_VK_0, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"", unmodifiedChars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1}, chars:"\u00F8", unmodifiedChars:"\u00E0"},
+ "\u00F8", "Digit0", KeyboardEvent.DOM_VK_0, "\u00F8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00D8", unmodifiedChars:"0"},
+ "\u00D8", "Digit0", KeyboardEvent.DOM_VK_0, "\u00D8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"", unmodifiedChars:"\u00E0"},
+ "\u00F8", "Digit0", KeyboardEvent.DOM_VK_0, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"", unmodifiedChars:"0"},
+ "\u00D8", "Digit0", KeyboardEvent.DOM_VK_0, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{metaKey:1}, chars:"\u00E0", unmodifiedChars:"\u00E0"},
+ "\u00E0", "Digit0", KeyboardEvent.DOM_VK_0, "\u00E0", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Thai
+ // keycode should be DOM_VK_[A-Z] of the key on the latest ASCII capable keyboard layout is for alphabet
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_A,
+ modifiers:{}, chars:"\u0E1F", unmodifiedChars:"\u0E1F"},
+ "\u0E1F", "KeyA", KeyboardEvent.DOM_VK_A, "\u0E1F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // keycode should be shifted character if unshifted character isn't an ASCII character
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{}, chars:"\u0E07", unmodifiedChars:"\u0E07"},
+ "\u0E07", "Quote", KeyboardEvent.DOM_VK_PERIOD, "\u0E07", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // keycode should be same as ANSI keyboard layout's key which causes the native virtual keycode
+ // if the character of the key on the latest ASCII capable keyboard layout isn't for alphabet
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{}, chars:"\u0E43", unmodifiedChars:"\u0E43"},
+ "\u0E43", "Period", KeyboardEvent.DOM_VK_PERIOD, "\u0E43", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // keycode should be DOM_VK_[0-9] if the key on the latest ASCII capable keyboard layout is for numeric
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_1,
+ modifiers:{}, chars:"\u0E45", unmodifiedChars:"\u0E45"},
+ "\u0E45", "Digit1", KeyboardEvent.DOM_VK_1, "\u0E45", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_2,
+ modifiers:{}, chars:"/", unmodifiedChars:"/"},
+ "/", "Digit2", KeyboardEvent.DOM_VK_2, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_3,
+ modifiers:{}, chars:"_", unmodifiedChars:"_"},
+ "_", "Digit3", KeyboardEvent.DOM_VK_3, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_4,
+ modifiers:{}, chars:"\u0E20", unmodifiedChars:"\u0E20"},
+ "\u0E20", "Digit4", KeyboardEvent.DOM_VK_4, "\u0E20", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_5,
+ modifiers:{}, chars:"\u0E16", unmodifiedChars:"\u0E16"},
+ "\u0E16", "Digit5", KeyboardEvent.DOM_VK_5, "\u0E16", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_6,
+ modifiers:{}, chars:"\u0E38", unmodifiedChars:"\u0E38"},
+ "\u0E38", "Digit6", KeyboardEvent.DOM_VK_6, "\u0E38", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_7,
+ modifiers:{}, chars:"\u0E36", unmodifiedChars:"\u0E36"},
+ "\u0E36", "Digit7", KeyboardEvent.DOM_VK_7, "\u0E36", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_8,
+ modifiers:{}, chars:"\u0E04", unmodifiedChars:"\u0E04"},
+ "\u0E04", "Digit8", KeyboardEvent.DOM_VK_8, "\u0E04", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_9,
+ modifiers:{}, chars:"\u0E15", unmodifiedChars:"\u0E15"},
+ "\u0E15", "Digit9", KeyboardEvent.DOM_VK_9, "\u0E15", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_0,
+ modifiers:{}, chars:"\u0E08", unmodifiedChars:"\u0E08"},
+ "\u0E08", "Digit0", KeyboardEvent.DOM_VK_0, "\u0E08", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Dvorak-Qwerty, layout should be changed when Command key is pressed.
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_S,
+ modifiers:{}, chars:"o", unmodifiedChars:"o"},
+ "o", "KeyS", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_S,
+ modifiers:{shiftKey:1}, chars:"O", unmodifiedChars:"O"},
+ "O", "KeyS", KeyboardEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_S,
+ modifiers:{ctrlKey:1}, chars:"\u000F", unmodifiedChars:"o"},
+ "o", "KeyS", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_S,
+ modifiers:{altKey:1}, chars:"\u00F8", unmodifiedChars:"o"},
+ "\u00F8", "KeyS", KeyboardEvent.DOM_VK_O, "\u00F8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_S,
+ modifiers:{metaKey:1}, chars:"s", unmodifiedChars:"o"},
+ "s", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_D,
+ modifiers:{}, chars:"e", unmodifiedChars:"e"},
+ "e", "KeyD", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_D,
+ modifiers:{shiftKey:1}, chars:"E", unmodifiedChars:"E"},
+ "E", "KeyD", KeyboardEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_D,
+ modifiers:{ctrlKey:1}, chars:"\u0005", unmodifiedChars:"e"},
+ "e", "KeyD", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_D,
+ modifiers:{altKey:1}, chars:"", unmodifiedChars:"e"},
+ "Dead", "KeyD", KeyboardEvent.DOM_VK_E, "\u00B4", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // dead key
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_D,
+ modifiers:{metaKey:1}, chars:"d", unmodifiedChars:"e"},
+ "d", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_I,
+ modifiers:{metaKey:1, altKey:1}, chars:"^", unmodifiedChars:"c"},
+ "^", "KeyI", KeyboardEvent.DOM_VK_I, "^", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_I,
+ modifiers:{metaKey:1, altKey:1, shiftKey:1}, chars:"\u02C6", unmodifiedChars:"C"},
+ "\u02C6", "KeyI", KeyboardEvent.DOM_VK_I, "\u02C6", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Arabic - PC keyboard layout inputs 2 or more characters with some key.
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_G,
+ modifiers:{shiftKey:1}, chars:"\u0644\u0623", unmodifiedChars:"\u0644\u0623"},
+ ["\u0644\u0623", "\u0644", "\u0623", "\u0644\u0623"], "KeyG", KeyboardEvent.DOM_VK_G, "\u0644\u0623", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_T,
+ modifiers:{shiftKey:1}, chars:"\u0644\u0625", unmodifiedChars:"\u0644\u0625"},
+ ["\u0644\u0625", "\u0644", "\u0625", "\u0644\u0625"], "KeyT", KeyboardEvent.DOM_VK_T, "\u0644\u0625", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_B,
+ modifiers:{shiftKey:1}, chars:"\u0644\u0622", unmodifiedChars:"\u0644\u0622"},
+ ["\u0644\u0622", "\u0644", "\u0622", "\u0644\u0622"], "KeyB", KeyboardEvent.DOM_VK_B, "\u0644\u0622", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_B,
+ modifiers:{}, chars:"\u0644\u0627", unmodifiedChars:"\u0644\u0627"},
+ ["\u0644\u0627", "\u0644", "\u0627", "\u0644\u0627"], "KeyB", KeyboardEvent.DOM_VK_B, "\u0644\u0627", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ cleanup();
+ }
+
+ function* testKeysOnWindows()
+ {
+ // On Windows, you can use Spy++ or Winspector (free) to watch window messages.
+ // The keyCode is given by the wParam of the last WM_KEYDOWN message. The
+ // chars string is given by the wParam of the WM_CHAR message. unmodifiedChars
+ // is not needed on Windows.
+
+ // Shift-ctrl-alt generates no WM_CHAR, but we still get a keypress
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:""},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Greek plain text
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u03b1"},
+ "\u03b1", "KeyA", KeyboardEvent.DOM_VK_A, "\u03b1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"\u0391"},
+ "\u0391", "KeyA", KeyboardEvent.DOM_VK_A, "\u0391", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Greek ctrl keys produce Latin charcodes
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1}, chars:"\u0001"},
+ "\u03b1", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0001"},
+ "\u0391", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Caps Lock key event
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_CAPITAL,
+ modifiers:{capsLockKey:1}, chars:""},
+ "CapsLock", "CapsLock", KeyboardEvent.DOM_VK_CAPS_LOCK, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_CAPITAL,
+ modifiers:{capsLockKey:0}, chars:""},
+ "CapsLock", "CapsLock", KeyboardEvent.DOM_VK_CAPS_LOCK, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Shift keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_LSHIFT,
+ modifiers:{shiftKey:1}, chars:""},
+ "Shift", "ShiftLeft", KeyboardEvent.DOM_VK_SHIFT, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RSHIFT,
+ modifiers:{shiftRightKey:1}, chars:""},
+ "Shift", "ShiftRight", KeyboardEvent.DOM_VK_SHIFT, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // Ctrl keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_LCONTROL,
+ modifiers:{ctrlKey:1}, chars:""},
+ "Control", "ControlLeft", KeyboardEvent.DOM_VK_CONTROL, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RCONTROL,
+ modifiers:{ctrlRightKey:1}, chars:""},
+ "Control", "ControlRight", KeyboardEvent.DOM_VK_CONTROL, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // Alt keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_LMENU,
+ modifiers:{altKey:1}, chars:""},
+ "Alt", "AltLeft", KeyboardEvent.DOM_VK_ALT, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RMENU,
+ modifiers:{altRightKey:1}, chars:""},
+ "Alt", "AltRight", KeyboardEvent.DOM_VK_ALT, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // Win keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_LWIN,
+ modifiers:{}, chars:""},
+ "OS" /* bug 1232918 */, "OSLeft", KeyboardEvent.DOM_VK_WIN, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RWIN,
+ modifiers:{}, chars:""},
+ "OS" /* bug 1232918 */, "OSRight", KeyboardEvent.DOM_VK_WIN, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // all keys on keyboard (keyCode test)
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_BACK,
+ modifiers:{}, chars:"\u0008"},
+ "Backspace", "Backspace", KeyboardEvent.DOM_VK_BACK_SPACE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_TAB,
+ modifiers:{}, chars:"\t"},
+ "Tab", "Tab", KeyboardEvent.DOM_VK_TAB, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RETURN,
+ modifiers:{}, chars:"\r"},
+ "Enter", "Enter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_PAUSE,
+ modifiers:{}, chars:""},
+ "Pause", "Pause", KeyboardEvent.DOM_VK_PAUSE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_KANA,
+ modifiers:{}, chars:""},
+ "Unidentified", "", KeyboardEvent.DOM_VK_KANA, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_JUNJA,
+ modifiers:{}, chars:""},
+ "JunjaMode", "", KeyboardEvent.DOM_VK_JUNJA, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_FINAL,
+ modifiers:{}, chars:""},
+ "FinalMode", "", KeyboardEvent.DOM_VK_FINAL, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_KANJI,
+ modifiers:{}, chars:""},
+ "Unidentified", "", KeyboardEvent.DOM_VK_KANJI, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_ESCAPE,
+ modifiers:{}, chars:""},
+ "Escape", "Escape", KeyboardEvent.DOM_VK_ESCAPE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_CONVERT,
+ modifiers:{}, chars:""},
+ "Convert", "", KeyboardEvent.DOM_VK_CONVERT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NONCONVERT,
+ modifiers:{}, chars:""},
+ "NonConvert", "", KeyboardEvent.DOM_VK_NONCONVERT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_ACCEPT,
+ modifiers:{}, chars:""},
+ "Accept", "", KeyboardEvent.DOM_VK_ACCEPT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_MODECHANGE,
+ modifiers:{}, chars:""},
+ "ModeChange", "", KeyboardEvent.DOM_VK_MODECHANGE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SPACE,
+ modifiers:{}, chars:" "},
+ " ", "Space", KeyboardEvent.DOM_VK_SPACE, " ", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Ctrl+Space causes WM_CHAR with ' '. However, its keypress event's ctrlKey state shouldn't be false.
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SPACE,
+ modifiers:{ctrlKey:1}, chars:" "},
+ " ", "Space", KeyboardEvent.DOM_VK_SPACE, " ", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SELECT,
+ modifiers:{}, chars:""},
+ "Select", "", KeyboardEvent.DOM_VK_SELECT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_PRINT,
+ modifiers:{}, chars:""},
+ "Unidentified", "", KeyboardEvent.DOM_VK_PRINT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_EXECUTE,
+ modifiers:{}, chars:""},
+ "Execute", "", KeyboardEvent.DOM_VK_EXECUTE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SNAPSHOT,
+ modifiers:{}, chars:""},
+ "PrintScreen", "PrintScreen", KeyboardEvent.DOM_VK_PRINTSCREEN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_HELP,
+ modifiers:{}, chars:""},
+ "Help", "", KeyboardEvent.DOM_VK_HELP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SLEEP,
+ modifiers:{}, chars:""},
+ "Standby", "", KeyboardEvent.DOM_VK_SLEEP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_PRIOR,
+ modifiers:{}, chars:""},
+ "PageUp", "PageUp", KeyboardEvent.DOM_VK_PAGE_UP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NEXT,
+ modifiers:{}, chars:""},
+ "PageDown", "PageDown", KeyboardEvent.DOM_VK_PAGE_DOWN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_END,
+ modifiers:{}, chars:""},
+ "End", "End", KeyboardEvent.DOM_VK_END, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_HOME,
+ modifiers:{}, chars:""},
+ "Home", "Home", KeyboardEvent.DOM_VK_HOME, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_LEFT,
+ modifiers:{}, chars:""},
+ "ArrowLeft", "ArrowLeft", KeyboardEvent.DOM_VK_LEFT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_UP,
+ modifiers:{}, chars:""},
+ "ArrowUp", "ArrowUp", KeyboardEvent.DOM_VK_UP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RIGHT,
+ modifiers:{}, chars:""},
+ "ArrowRight", "ArrowRight", KeyboardEvent.DOM_VK_RIGHT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_DOWN,
+ modifiers:{}, chars:""},
+ "ArrowDown", "ArrowDown", KeyboardEvent.DOM_VK_DOWN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_INSERT,
+ modifiers:{}, chars:""},
+ "Insert", "Insert", KeyboardEvent.DOM_VK_INSERT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_DELETE,
+ modifiers:{}, chars:""},
+ "Delete", "Delete", KeyboardEvent.DOM_VK_DELETE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Backspace and Enter are handled with special path in mozilla::widget::NativeKey. So, let's test them with modifiers too.
+ // Note that when both Ctrl and Alt are pressed, they don't cause WM_(SYS)CHAR message.
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_BACK,
+ modifiers:{ctrlKey:1}, chars:"\u007F"},
+ "Backspace", "Backspace", KeyboardEvent.DOM_VK_BACK_SPACE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_BACK,
+ modifiers:{altKey:1}, chars:"\u0008"},
+ "Backspace", "Backspace", KeyboardEvent.DOM_VK_BACK_SPACE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_BACK,
+ modifiers:{ctrl:1, altKey:1}, chars:""},
+ "Backspace", "Backspace", KeyboardEvent.DOM_VK_BACK_SPACE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RETURN,
+ modifiers:{ctrlKey:1}, chars:"\n"},
+ "Enter", "Enter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RETURN,
+ modifiers:{altKey:1}, chars:"\r"},
+ "Enter", "Enter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RETURN,
+ modifiers:{ctrl:1, altKey:1}, chars:""},
+ "Enter", "Enter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Even non-printable key could be mapped as a printable key.
+ // Only "keyup" event cannot know if it *did* cause inputting text.
+ // Therefore, only "keydown" and "keypress" event's key value should be the character inputted by the key.
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F4,
+ modifiers:{}, chars:"a"},
+ ["a", "a", "F4"], "F4", KeyboardEvent.DOM_VK_F4, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Even if key message is processed by IME, when the key causes inputting text,
+ // keypress event(s) should be fired.
+ const WIN_VK_PROCESSKEY_WITH_SC_A = WIN_VK_PROCESSKEY | 0x001E0000;
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_PROCESSKEY_WITH_SC_A,
+ modifiers:{}, chars:"a"},
+ ["a", "a", "Process"], "KeyA", KeyboardEvent.DOM_VK_PROCESSKEY, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_PROCESSKEY_WITH_SC_A,
+ modifiers:{altKey:1}, chars:"a"},
+ ["a", "a", "Process"], "KeyA", KeyboardEvent.DOM_VK_PROCESSKEY, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // US
+ // Alphabet
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"A"},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1}, chars:"\u0001"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0001"},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{altKey:1}, chars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{altKey:1, shiftKey:1}, chars:"A"},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+ modifiers:{}, chars:"b"},
+ "b", "KeyB", KeyboardEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+ modifiers:{shiftKey:1}, chars:"B"},
+ "B", "KeyB", KeyboardEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+ modifiers:{ctrlKey:1}, chars:"\u0002"},
+ "b", "KeyB", KeyboardEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0002"},
+ "B", "KeyB", KeyboardEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+ modifiers:{altKey:1}, chars:"b"},
+ "b", "KeyB", KeyboardEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+ modifiers:{altKey:1, shiftKey:1}, chars:"B"},
+ "B", "KeyB", KeyboardEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+ modifiers:{}, chars:"c"},
+ "c", "KeyC", KeyboardEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+ modifiers:{shiftKey:1}, chars:"C"},
+ "C", "KeyC", KeyboardEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+ modifiers:{ctrlKey:1}, chars:"\u0003"},
+ "c", "KeyC", KeyboardEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0003"},
+ "C", "KeyC", KeyboardEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+ modifiers:{altKey:1}, chars:"c"},
+ "c", "KeyC", KeyboardEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+ modifiers:{altKey:1, shiftKey:1}, chars:"C"},
+ "C", "KeyC", KeyboardEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+ modifiers:{}, chars:"d"},
+ "d", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+ modifiers:{shiftKey:1}, chars:"D"},
+ "D", "KeyD", KeyboardEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+ modifiers:{ctrlKey:1}, chars:"\u0004"},
+ "d", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0004"},
+ "D", "KeyD", KeyboardEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+ modifiers:{altKey:1}, chars:"d"},
+ "d", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+ modifiers:{altKey:1, shiftKey:1}, chars:"D"},
+ "D", "KeyD", KeyboardEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+ modifiers:{}, chars:"e"},
+ "e", "KeyE", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+ modifiers:{shiftKey:1}, chars:"E"},
+ "E", "KeyE", KeyboardEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+ modifiers:{ctrlKey:1}, chars:"\u0005"},
+ "e", "KeyE", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0005"},
+ "E", "KeyE", KeyboardEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+ modifiers:{altKey:1}, chars:"e"},
+ "e", "KeyE", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+ modifiers:{altKey:1, shiftKey:1}, chars:"E"},
+ "E", "KeyE", KeyboardEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+ modifiers:{}, chars:"f"},
+ "f", "KeyF", KeyboardEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+ modifiers:{shiftKey:1}, chars:"F"},
+ "F", "KeyF", KeyboardEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+ modifiers:{ctrlKey:1}, chars:"\u0006"},
+ "f", "KeyF", KeyboardEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0006"},
+ "F", "KeyF", KeyboardEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+ modifiers:{altKey:1}, chars:"f"},
+ "f", "KeyF", KeyboardEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+ modifiers:{altKey:1, shiftKey:1}, chars:"F"},
+ "F", "KeyF", KeyboardEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+ modifiers:{}, chars:"g"},
+ "g", "KeyG", KeyboardEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+ modifiers:{shiftKey:1}, chars:"G"},
+ "G", "KeyG", KeyboardEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+ modifiers:{ctrlKey:1}, chars:"\u0007"},
+ "g", "KeyG", KeyboardEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0007"},
+ "G", "KeyG", KeyboardEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+ modifiers:{altKey:1}, chars:"g"},
+ "g", "KeyG", KeyboardEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+ modifiers:{altKey:1, shiftKey:1}, chars:"G"},
+ "G", "KeyG", KeyboardEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+ modifiers:{}, chars:"h"},
+ "h", "KeyH", KeyboardEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+ modifiers:{shiftKey:1}, chars:"H"},
+ "H", "KeyH", KeyboardEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+ modifiers:{ctrlKey:1}, chars:"\u0008"},
+ "h", "KeyH", KeyboardEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0008"},
+ "H", "KeyH", KeyboardEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+ modifiers:{altKey:1}, chars:"h"},
+ "h", "KeyH", KeyboardEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+ modifiers:{altKey:1, shiftKey:1}, chars:"H"},
+ "H", "KeyH", KeyboardEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+ modifiers:{}, chars:"i"},
+ "i", "KeyI", KeyboardEvent.DOM_VK_I, "i", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+ modifiers:{shiftKey:1}, chars:"I"},
+ "I", "KeyI", KeyboardEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+ modifiers:{ctrlKey:1}, chars:"\u0009"},
+ "i", "KeyI", KeyboardEvent.DOM_VK_I, "i", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0009"},
+ "I", "KeyI", KeyboardEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+ modifiers:{altKey:1}, chars:"i"},
+ "i", "KeyI", KeyboardEvent.DOM_VK_I, "i", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+ modifiers:{altKey:1, shiftKey:1}, chars:"I"},
+ "I", "KeyI", KeyboardEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+ modifiers:{}, chars:"j"},
+ "j", "KeyJ", KeyboardEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+ modifiers:{shiftKey:1}, chars:"J"},
+ "J", "KeyJ", KeyboardEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+ modifiers:{ctrlKey:1}, chars:"\u000A"},
+ "j", "KeyJ", KeyboardEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000A"},
+ "J", "KeyJ", KeyboardEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+ modifiers:{altKey:1}, chars:"j"},
+ "j", "KeyJ", KeyboardEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+ modifiers:{altKey:1, shiftKey:1}, chars:"J"},
+ "J", "KeyJ", KeyboardEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+ modifiers:{}, chars:"k"},
+ "k", "KeyK", KeyboardEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+ modifiers:{shiftKey:1}, chars:"K"},
+ "K", "KeyK", KeyboardEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+ modifiers:{ctrlKey:1}, chars:"\u000B"},
+ "k", "KeyK", KeyboardEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000B"},
+ "K", "KeyK", KeyboardEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+ modifiers:{altKey:1}, chars:"k"},
+ "k", "KeyK", KeyboardEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+ modifiers:{altKey:1, shiftKey:1}, chars:"K"},
+ "K", "KeyK", KeyboardEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+ modifiers:{}, chars:"l"},
+ "l", "KeyL", KeyboardEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+ modifiers:{shiftKey:1}, chars:"L"},
+ "L", "KeyL", KeyboardEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+ modifiers:{ctrlKey:1}, chars:"\u000C"},
+ "l", "KeyL", KeyboardEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000C"},
+ "L", "KeyL", KeyboardEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+ modifiers:{altKey:1}, chars:"l"},
+ "l", "KeyL", KeyboardEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+ modifiers:{altKey:1, shiftKey:1}, chars:"L"},
+ "L", "KeyL", KeyboardEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+ modifiers:{}, chars:"m"},
+ "m", "KeyM", KeyboardEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+ modifiers:{shiftKey:1}, chars:"M"},
+ "M", "KeyM", KeyboardEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+ modifiers:{ctrlKey:1}, chars:"\u000D"},
+ "m", "KeyM", KeyboardEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000D"},
+ "M", "KeyM", KeyboardEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+ modifiers:{altKey:1}, chars:"m"},
+ "m", "KeyM", KeyboardEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+ modifiers:{altKey:1, shiftKey:1}, chars:"M"},
+ "M", "KeyM", KeyboardEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+ modifiers:{}, chars:"n"},
+ "n", "KeyN", KeyboardEvent.DOM_VK_N, "n", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+ modifiers:{shiftKey:1}, chars:"N"},
+ "N", "KeyN", KeyboardEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+ modifiers:{ctrlKey:1}, chars:"\u000E"},
+ "n", "KeyN", KeyboardEvent.DOM_VK_N, "n", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000E"},
+ "N", "KeyN", KeyboardEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+ modifiers:{altKey:1}, chars:"n"},
+ "n", "KeyN", KeyboardEvent.DOM_VK_N, "n", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+ modifiers:{altKey:1, shiftKey:1}, chars:"N"},
+ "N", "KeyN", KeyboardEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+ modifiers:{}, chars:"o"},
+ "o", "KeyO", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+ modifiers:{shiftKey:1}, chars:"O"},
+ "O", "KeyO", KeyboardEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+ modifiers:{ctrlKey:1}, chars:"\u000F"},
+ "o", "KeyO", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000F"},
+ "O", "KeyO", KeyboardEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+ modifiers:{altKey:1}, chars:"o"},
+ "o", "KeyO", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+ modifiers:{altKey:1, shiftKey:1}, chars:"O"},
+ "O", "KeyO", KeyboardEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+ modifiers:{}, chars:"p"},
+ "p", "KeyP", KeyboardEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+ modifiers:{shiftKey:1}, chars:"P"},
+ "P", "KeyP", KeyboardEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+ modifiers:{ctrlKey:1}, chars:"\u0010"},
+ "p", "KeyP", KeyboardEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0010"},
+ "P", "KeyP", KeyboardEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+ modifiers:{altKey:1}, chars:"p"},
+ "p", "KeyP", KeyboardEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+ modifiers:{altKey:1, shiftKey:1}, chars:"P"},
+ "P", "KeyP", KeyboardEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+ modifiers:{}, chars:"q"},
+ "q", "KeyQ", KeyboardEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+ modifiers:{shiftKey:1}, chars:"Q"},
+ "Q", "KeyQ", KeyboardEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+ modifiers:{ctrlKey:1}, chars:"\u0011"},
+ "q", "KeyQ", KeyboardEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0011"},
+ "Q", "KeyQ", KeyboardEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+ modifiers:{altKey:1}, chars:"q"},
+ "q", "KeyQ", KeyboardEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+ modifiers:{altKey:1, shiftKey:1}, chars:"Q"},
+ "Q", "KeyQ", KeyboardEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+ modifiers:{}, chars:"r"},
+ "r", "KeyR", KeyboardEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+ modifiers:{shiftKey:1}, chars:"R"},
+ "R", "KeyR", KeyboardEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+ modifiers:{ctrlKey:1}, chars:"\u0012"},
+ "r", "KeyR", KeyboardEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0012"},
+ "R", "KeyR", KeyboardEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+ modifiers:{altKey:1}, chars:"r"},
+ "r", "KeyR", KeyboardEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+ modifiers:{altKey:1, shiftKey:1}, chars:"R"},
+ "R", "KeyR", KeyboardEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+ modifiers:{}, chars:"s"},
+ "s", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+ modifiers:{shiftKey:1}, chars:"S"},
+ "S", "KeyS", KeyboardEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+ modifiers:{ctrlKey:1}, chars:"\u0013"},
+ "s", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0013"},
+ "S", "KeyS", KeyboardEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+ modifiers:{altKey:1}, chars:"s"},
+ "s", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+ modifiers:{altKey:1, shiftKey:1}, chars:"S"},
+ "S", "KeyS", KeyboardEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+ modifiers:{}, chars:"t"},
+ "t", "KeyT", KeyboardEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+ modifiers:{shiftKey:1}, chars:"T"},
+ "T", "KeyT", KeyboardEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+ modifiers:{ctrlKey:1}, chars:"\u0014"},
+ "t", "KeyT", KeyboardEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0014"},
+ "T", "KeyT", KeyboardEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+ modifiers:{altKey:1}, chars:"t"},
+ "t", "KeyT", KeyboardEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+ modifiers:{altKey:1, shiftKey:1}, chars:"T"},
+ "T", "KeyT", KeyboardEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+ modifiers:{}, chars:"u"},
+ "u", "KeyU", KeyboardEvent.DOM_VK_U, "u", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+ modifiers:{shiftKey:1}, chars:"U"},
+ "U", "KeyU", KeyboardEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+ modifiers:{ctrlKey:1}, chars:"\u0015"},
+ "u", "KeyU", KeyboardEvent.DOM_VK_U, "u", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0015"},
+ "U", "KeyU", KeyboardEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+ modifiers:{altKey:1}, chars:"u"},
+ "u", "KeyU", KeyboardEvent.DOM_VK_U, "u", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+ modifiers:{altKey:1, shiftKey:1}, chars:"U"},
+ "U", "KeyU", KeyboardEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+ modifiers:{}, chars:"v"},
+ "v", "KeyV", KeyboardEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+ modifiers:{shiftKey:1}, chars:"V"},
+ "V", "KeyV", KeyboardEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+ modifiers:{ctrlKey:1}, chars:"\u0016"},
+ "v", "KeyV", KeyboardEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0016"},
+ "V", "KeyV", KeyboardEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+ modifiers:{altKey:1}, chars:"v"},
+ "v", "KeyV", KeyboardEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+ modifiers:{altKey:1, shiftKey:1}, chars:"V"},
+ "V", "KeyV", KeyboardEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+ modifiers:{}, chars:"w"},
+ "w", "KeyW", KeyboardEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+ modifiers:{shiftKey:1}, chars:"W"},
+ "W", "KeyW", KeyboardEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+ modifiers:{ctrlKey:1}, chars:"\u0017"},
+ "w", "KeyW", KeyboardEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0017"},
+ "W", "KeyW", KeyboardEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+ modifiers:{altKey:1}, chars:"w"},
+ "w", "KeyW", KeyboardEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+ modifiers:{altKey:1, shiftKey:1}, chars:"W"},
+ "W", "KeyW", KeyboardEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{}, chars:"x"},
+ "x", "KeyX", KeyboardEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{shiftKey:1}, chars:"X"},
+ "X", "KeyX", KeyboardEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1}, chars:"\u0018"},
+ "x", "KeyX", KeyboardEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0018"},
+ "X", "KeyX", KeyboardEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{altKey:1}, chars:"x"},
+ "x", "KeyX", KeyboardEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{altKey:1, shiftKey:1}, chars:"X"},
+ "X", "KeyX", KeyboardEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+ modifiers:{}, chars:"y"},
+ "y", "KeyY", KeyboardEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+ modifiers:{shiftKey:1}, chars:"Y"},
+ "Y", "KeyY", KeyboardEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+ modifiers:{ctrlKey:1}, chars:"\u0019"},
+ "y", "KeyY", KeyboardEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0019"},
+ "Y", "KeyY", KeyboardEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+ modifiers:{altKey:1}, chars:"y"},
+ "y", "KeyY", KeyboardEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+ modifiers:{altKey:1, shiftKey:1}, chars:"Y"},
+ "Y", "KeyY", KeyboardEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+ modifiers:{}, chars:"z"},
+ "z", "KeyZ", KeyboardEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+ modifiers:{shiftKey:1}, chars:"Z"},
+ "Z", "KeyZ", KeyboardEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+ modifiers:{ctrlKey:1}, chars:"\u001A"},
+ "z", "KeyZ", KeyboardEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001A"},
+ "Z", "KeyZ", KeyboardEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+ modifiers:{altKey:1}, chars:"z"},
+ "z", "KeyZ", KeyboardEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+ modifiers:{altKey:1, shiftKey:1}, chars:"Z"},
+ "Z", "KeyZ", KeyboardEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Numeric
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+ modifiers:{}, chars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+ modifiers:{shiftKey:1}, chars:")"},
+ ")", "Digit0", KeyboardEvent.DOM_VK_0, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+ modifiers:{ctrlKey:1}, chars:""},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ ")", "Digit0", KeyboardEvent.DOM_VK_0, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+ modifiers:{altKey:1}, chars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+ modifiers:{altKey:1, shiftKey:1}, chars:")"},
+ ")", "Digit0", KeyboardEvent.DOM_VK_0, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+ modifiers:{}, chars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+ modifiers:{shiftKey:1}, chars:"!"},
+ "!", "Digit1", KeyboardEvent.DOM_VK_1, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+ modifiers:{ctrlKey:1}, chars:""},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "!", "Digit1", KeyboardEvent.DOM_VK_1, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+ modifiers:{altKey:1}, chars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:"!"},
+ "!", "Digit1", KeyboardEvent.DOM_VK_1, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+ modifiers:{}, chars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+ modifiers:{shiftKey:1}, chars:"@"},
+ "@", "Digit2", KeyboardEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+ modifiers:{ctrlKey:1}, chars:""},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "@", "Digit2", KeyboardEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+ modifiers:{altKey:1}, chars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+ modifiers:{altKey:1, shiftKey:1}, chars:"@"},
+ "@", "Digit2", KeyboardEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+ modifiers:{}, chars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+ modifiers:{shiftKey:1}, chars:"#"},
+ "#", "Digit3", KeyboardEvent.DOM_VK_3, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+ modifiers:{ctrlKey:1}, chars:""},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "#", "Digit3", KeyboardEvent.DOM_VK_3, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+ modifiers:{altKey:1}, chars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+ modifiers:{altKey:1, shiftKey:1}, chars:"#"},
+ "#", "Digit3", KeyboardEvent.DOM_VK_3, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+ modifiers:{}, chars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+ modifiers:{shiftKey:1}, chars:"$"},
+ "$", "Digit4", KeyboardEvent.DOM_VK_4, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+ modifiers:{ctrlKey:1}, chars:""},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "$", "Digit4", KeyboardEvent.DOM_VK_4, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+ modifiers:{altKey:1}, chars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+ modifiers:{altKey:1, shiftKey:1}, chars:"$"},
+ "$", "Digit4", KeyboardEvent.DOM_VK_4, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+ modifiers:{}, chars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+ modifiers:{shiftKey:1}, chars:"%"},
+ "%", "Digit5", KeyboardEvent.DOM_VK_5, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+ modifiers:{ctrlKey:1}, chars:""},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "%", "Digit5", KeyboardEvent.DOM_VK_5, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+ modifiers:{altKey:1}, chars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+ modifiers:{altKey:1, shiftKey:1}, chars:"%"},
+ "%", "Digit5", KeyboardEvent.DOM_VK_5, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+ modifiers:{}, chars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+ modifiers:{shiftKey:1}, chars:"^"},
+ "^", "Digit6", KeyboardEvent.DOM_VK_6, "^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+ modifiers:{ctrlKey:1}, chars:""},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001E"},
+ "^", "Digit6", KeyboardEvent.DOM_VK_6, "^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+ modifiers:{altKey:1}, chars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+ modifiers:{altKey:1, shiftKey:1}, chars:"^"},
+ "^", "Digit6", KeyboardEvent.DOM_VK_6, "^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+ modifiers:{}, chars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+ modifiers:{shiftKey:1}, chars:"&"},
+ "&", "Digit7", KeyboardEvent.DOM_VK_7, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+ modifiers:{ctrlKey:1}, chars:""},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "&", "Digit7", KeyboardEvent.DOM_VK_7, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+ modifiers:{altKey:1}, chars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+ modifiers:{altKey:1, shiftKey:1}, chars:"&"},
+ "&", "Digit7", KeyboardEvent.DOM_VK_7, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+ modifiers:{}, chars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+ modifiers:{shiftKey:1}, chars:"*"},
+ "*", "Digit8", KeyboardEvent.DOM_VK_8, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+ modifiers:{ctrlKey:1, }, chars:""},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "*", "Digit8", KeyboardEvent.DOM_VK_8, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+ modifiers:{altKey:1}, chars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+ modifiers:{altKey:1, shiftKey:1}, chars:"*"},
+ "*", "Digit8", KeyboardEvent.DOM_VK_8, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+ modifiers:{}, chars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+ modifiers:{shiftKey:1}, chars:"("},
+ "(", "Digit9", KeyboardEvent.DOM_VK_9, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+ modifiers:{ctrlKey:1}, chars:""},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "(", "Digit9", KeyboardEvent.DOM_VK_9, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+ modifiers:{altKey:1}, chars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+ modifiers:{altKey:1, shiftKey:1}, chars:"("},
+ "(", "Digit9", KeyboardEvent.DOM_VK_9, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // OEM keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+ modifiers:{}, chars:"-"},
+ "-", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+ modifiers:{shiftKey:1}, chars:"_"},
+ "_", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+ modifiers:{ctrlKey:1}, chars:""},
+ "-", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001F"},
+ "_", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+ modifiers:{altKey:1}, chars:"-"},
+ "-", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+ modifiers:{altKey:1, shiftKey:1}, chars:"_"},
+ "_", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{}, chars:"="},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{shiftKey:1}, chars:"+"},
+ "+", "Equal", KeyboardEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{ctrlKey:1}, chars:""},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "+", "Equal", KeyboardEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{altKey:1}, chars:"="},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{altKey:1, shiftKey:1}, chars:"+"},
+ "+", "Equal", KeyboardEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+ modifiers:{}, chars:"["},
+ "[", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+ modifiers:{shiftKey:1}, chars:"{"},
+ "{", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+ modifiers:{ctrlKey:1}, chars:"\u001B"},
+ "[", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "{", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+ modifiers:{altKey:1}, chars:"["},
+ "[", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+ modifiers:{altKey:1, shiftKey:1}, chars:"{"},
+ "{", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+ modifiers:{}, chars:"]"},
+ "]", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "]", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+ modifiers:{shiftKey:1}, chars:"}"},
+ "}", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+ modifiers:{ctrlKey:1}, chars:"\u001D"},
+ "]", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "]", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "}", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+ modifiers:{altKey:1}, chars:"]"},
+ "]", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "]", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+ modifiers:{altKey:1, shiftKey:1}, chars:"}"},
+ "}", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{}, chars:";"},
+ ";", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{shiftKey:1}, chars:":"},
+ ":", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{ctrlKey:1}, chars:""},
+ ";", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ ":", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1}, chars:";"},
+ ";", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:":"},
+ ":", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:"'"},
+ "'", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{shiftKey:1}, chars:"\""},
+ "\"", "Quote", KeyboardEvent.DOM_VK_QUOTE, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{ctrlKey:1}, chars:""},
+ "'", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "\"", "Quote", KeyboardEvent.DOM_VK_QUOTE, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{altKey:1}, chars:"'"},
+ "'", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\""},
+ "\"", "Quote", KeyboardEvent.DOM_VK_QUOTE, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,
+ modifiers:{}, chars:"\\"},
+ "\\", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\\", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,
+ modifiers:{shiftKey:1}, chars:"|"},
+ "|", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "|", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,
+ modifiers:{ctrlKey:1}, chars:"\u001C"},
+ "\\", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\\", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "|", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "|", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,
+ modifiers:{altKey:1}, chars:"\\"},
+ "\\", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\\", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,
+ modifiers:{altKey:1, shiftKey:1}, chars:"|"},
+ "|", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "|", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{}, chars:","},
+ ",", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{shiftKey:1}, chars:"<"},
+ "<", "Comma", KeyboardEvent.DOM_VK_COMMA, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{ctrlKey:1}, chars:""},
+ ",", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "<", "Comma", KeyboardEvent.DOM_VK_COMMA, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{altKey:1}, chars:","},
+ ",", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{altKey:1, shiftKey:1}, chars:"<"},
+ "<", "Comma", KeyboardEvent.DOM_VK_COMMA, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{}, chars:"."},
+ ".", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{shiftKey:1}, chars:">"},
+ ">", "Period", KeyboardEvent.DOM_VK_PERIOD, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{ctrlKey:1}, chars:""},
+ ".", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ ">", "Period", KeyboardEvent.DOM_VK_PERIOD, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{altKey:1}, chars:"."},
+ ".", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{altKey:1, shiftKey:1}, chars:">"},
+ ">", "Period", KeyboardEvent.DOM_VK_PERIOD, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+ modifiers:{}, chars:"/"},
+ "/", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+ modifiers:{shiftKey:1}, chars:"?"},
+ "?", "Slash", KeyboardEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+ modifiers:{ctrlKey:1}, chars:""},
+ "/", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "?", "Slash", KeyboardEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+ modifiers:{altKey:1}, chars:"/"},
+ "/", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+ modifiers:{altKey:1, shiftKey:1}, chars:"?"},
+ "?", "Slash", KeyboardEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+ modifiers:{}, chars:"`"},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+ modifiers:{shiftKey:1}, chars:"~"},
+ "~", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "~", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+ modifiers:{ctrlKey:1}, chars:""},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "~", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "~", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+ modifiers:{altKey:1}, chars:"`"},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+ modifiers:{altKey:1, shiftKey:1}, chars:"~"},
+ "~", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "~", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Numpad
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD0,
+ modifiers:{numLockKey:1}, chars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD1,
+ modifiers:{numLockKey:1}, chars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD2,
+ modifiers:{numLockKey:1}, chars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD3,
+ modifiers:{numLockKey:1}, chars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD4,
+ modifiers:{numLockKey:1}, chars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD5,
+ modifiers:{numLockKey:1}, chars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD6,
+ modifiers:{numLockKey:1}, chars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD7,
+ modifiers:{numLockKey:1}, chars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD8,
+ modifiers:{numLockKey:1}, chars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD9,
+ modifiers:{numLockKey:1}, chars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_MULTIPLY,
+ modifiers:{numLockKey:1}, chars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_MULTIPLY,
+ modifiers:{numLockKey:1, shiftKey:1}, chars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_ADD,
+ modifiers:{numLockKey:1}, chars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_ADD,
+ modifiers:{numLockKey:1, shiftKey:1}, chars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ // VK_SEPARATOR is keycode for NEC's PC-98 series whose keyboard layout was
+ // different from current PC's keyboard layout and it cannot connect to
+ // current PC. Note that even if we synthesize WM_KEYDOWN with
+ // VK_SEPARATOR, it doesn't work on Win7.
+ //yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SEPARATOR,
+ // modifiers:{numLockKey:1}, chars:""},
+ // "", "", KeyboardEvent.DOM_VK_SEPARATOR, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ //yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SEPARATOR,
+ // modifiers:{numLockKey:1, shiftKey:1}, chars:""},
+ // "", "", KeyboardEvent.DOM_VK_SEPARATOR, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SUBTRACT,
+ modifiers:{numLockKey:1}, chars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SUBTRACT,
+ modifiers:{numLockKey:1, shiftKey:1}, chars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_DECIMAL,
+ modifiers:{numLockKey:1}, chars:"."},
+ ".", "NumpadDecimal", KeyboardEvent.DOM_VK_DECIMAL, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_DECIMAL,
+ modifiers:{numLockKey:1, shiftKey:1}, chars:"."},
+ ".", "NumpadDecimal", KeyboardEvent.DOM_VK_DECIMAL, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_DIVIDE,
+ modifiers:{numLockKey:1}, chars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_DIVIDE,
+ modifiers:{numLockKey:1, shiftKey:1}, chars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_RETURN,
+ modifiers:{numLockKey:1}, chars:"\r"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_RETURN,
+ modifiers:{numLockKey:1, shiftKey:1}, chars:"\r"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+
+ // Numpad without NumLock
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_PRIOR,
+ modifiers:{}, chars:""},
+ "PageUp", "Numpad9", KeyboardEvent.DOM_VK_PAGE_UP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_NEXT,
+ modifiers:{}, chars:""},
+ "PageDown", "Numpad3", KeyboardEvent.DOM_VK_PAGE_DOWN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_END,
+ modifiers:{}, chars:""},
+ "End", "Numpad1", KeyboardEvent.DOM_VK_END, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_HOME,
+ modifiers:{}, chars:""},
+ "Home", "Numpad7", KeyboardEvent.DOM_VK_HOME, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_LEFT,
+ modifiers:{}, chars:""},
+ "ArrowLeft", "Numpad4", KeyboardEvent.DOM_VK_LEFT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_UP,
+ modifiers:{}, chars:""},
+ "ArrowUp", "Numpad8", KeyboardEvent.DOM_VK_UP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_RIGHT,
+ modifiers:{}, chars:""},
+ "ArrowRight", "Numpad6", KeyboardEvent.DOM_VK_RIGHT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_DOWN,
+ modifiers:{}, chars:""},
+ "ArrowDown", "Numpad2", KeyboardEvent.DOM_VK_DOWN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_INSERT,
+ modifiers:{}, chars:""},
+ "Insert", "Numpad0", KeyboardEvent.DOM_VK_INSERT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_DELETE,
+ modifiers:{}, chars:""},
+ "Delete", "NumpadDecimal", KeyboardEvent.DOM_VK_DELETE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_CLEAR,
+ modifiers:{}, chars:""},
+ "Clear", "Numpad5", KeyboardEvent.DOM_VK_CLEAR, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+
+ // Even if widget receives unknown keycode, it should dispatch key events
+ // whose keycode is 0 rather than native keycode.
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:0x3A,
+ modifiers:{numLockKey:1}, chars:""},
+ "Unidentified", "", 0, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // French
+ // Numeric
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+ modifiers:{}, chars:"\u00E0"},
+ "\u00E0", "Digit0", KeyboardEvent.DOM_VK_0, "\u00E0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+ modifiers:{shiftKey:1}, chars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{}, chars:"&"},
+ "&", "Digit1", KeyboardEvent.DOM_VK_1, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{shiftKey:1}, chars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_2,
+ modifiers:{}, chars:"\u00E9"},
+ "\u00E9", "Digit2", KeyboardEvent.DOM_VK_2, "\u00E9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_2,
+ modifiers:{shiftKey:1}, chars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_3,
+ modifiers:{}, chars:"\""},
+ "\"", "Digit3", KeyboardEvent.DOM_VK_3, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_3,
+ modifiers:{shiftKey:1}, chars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_4,
+ modifiers:{}, chars:"'"},
+ "'", "Digit4", KeyboardEvent.DOM_VK_4, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_4,
+ modifiers:{shiftKey:1}, chars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_5,
+ modifiers:{}, chars:"("},
+ "(", "Digit5", KeyboardEvent.DOM_VK_5, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_5,
+ modifiers:{shiftKey:1}, chars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_6,
+ modifiers:{}, chars:"-"},
+ "-", "Digit6", KeyboardEvent.DOM_VK_6, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_6,
+ modifiers:{shiftKey:1}, chars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_7,
+ modifiers:{}, chars:"\u00E8"},
+ "\u00E8", "Digit7", KeyboardEvent.DOM_VK_7, "\u00E8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_7,
+ modifiers:{shiftKey:1}, chars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_8,
+ modifiers:{}, chars:"_"},
+ "_", "Digit8", KeyboardEvent.DOM_VK_8, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_8,
+ modifiers:{shiftKey:1}, chars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_9,
+ modifiers:{}, chars:"\u00E7"},
+ "\u00E7", "Digit9", KeyboardEvent.DOM_VK_9, "\u00E7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_9,
+ modifiers:{shiftKey:1}, chars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Numeric with ShiftLock
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+ modifiers:{capsLockKey:1}, chars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"\u00E0"},
+ "\u00E0", "Digit0", KeyboardEvent.DOM_VK_0, "\u00E0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{capsLockKey:1}, chars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"&"},
+ "&", "Digit1", KeyboardEvent.DOM_VK_1, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_2,
+ modifiers:{capsLockKey:1}, chars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_2,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"\u00E9"},
+ "\u00E9", "Digit2", KeyboardEvent.DOM_VK_2, "\u00E9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_3,
+ modifiers:{capsLockKey:1}, chars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_3,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"\""},
+ "\"", "Digit3", KeyboardEvent.DOM_VK_3, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_4,
+ modifiers:{capsLockKey:1}, chars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_4,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"'"},
+ "'", "Digit4", KeyboardEvent.DOM_VK_4, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_5,
+ modifiers:{capsLockKey:1}, chars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_5,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"("},
+ "(", "Digit5", KeyboardEvent.DOM_VK_5, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_6,
+ modifiers:{capsLockKey:1}, chars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_6,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"-"},
+ "-", "Digit6", KeyboardEvent.DOM_VK_6, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_7,
+ modifiers:{capsLockKey:1}, chars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_7,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"\u00E8"},
+ "\u00E8", "Digit7", KeyboardEvent.DOM_VK_7, "\u00E8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_8,
+ modifiers:{capsLockKey:1}, chars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_8,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"_"},
+ "_", "Digit8", KeyboardEvent.DOM_VK_8, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_9,
+ modifiers:{capsLockKey:1}, chars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_9,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"\u00E7"},
+ "\u00E7", "Digit9", KeyboardEvent.DOM_VK_9, "\u00E7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // OEM keys
+ // If the key doesn't cause ASCII character even with or without Shift key, keyCode value should be same as
+ // the key which causes the virtual keycode on ANSI keyboard layout.
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:"\u00B2"},
+ "\u00B2", "Backquote", KeyboardEvent.DOM_VK_QUOTE, "\u00B2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_7,
+ modifiers:{shiftKey:1}, chars:""},
+ "", "Backquote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_4,
+ modifiers:{}, chars:")"},
+ ")", "Minus", KeyboardEvent.DOM_VK_CLOSE_PAREN, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_4,
+ modifiers:{shiftKey:1}, chars:"\u00B0"},
+ "\u00B0", "Minus", KeyboardEvent.DOM_VK_CLOSE_PAREN, "\u00B0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{}, chars:"="},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{shiftKey:1}, chars:"+"},
+ "+", "Equal", KeyboardEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ //yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ // modifiers:{}, chars:""},
+ // "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Dead-key
+ //yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ // modifiers:{shiftKey:1}, chars:""},
+ // ["^^", "^", "^", "^"], "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Dead-key
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_1,
+ modifiers:{}, chars:"$"},
+ "$", "BracketRight", KeyboardEvent.DOM_VK_DOLLAR, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_1,
+ modifiers:{shiftKey:1}, chars:"\u00A3"},
+ "\u00A3", "BracketRight", KeyboardEvent.DOM_VK_DOLLAR, "\u00A3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_3,
+ modifiers:{}, chars:"\u00F9"},
+ "\u00F9", "Quote", KeyboardEvent.DOM_VK_PERCENT, "\u00F9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_3,
+ modifiers:{shiftKey:1}, chars:"%"},
+ "%", "Quote", KeyboardEvent.DOM_VK_PERCENT, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_5,
+ modifiers:{}, chars:"*"},
+ "*", "Backslash", KeyboardEvent.DOM_VK_ASTERISK, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_5,
+ modifiers:{shiftKey:1}, chars:"\u00B5"},
+ "\u00B5", "Backslash", KeyboardEvent.DOM_VK_ASTERISK, "\u00B5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_102,
+ modifiers:{}, chars:"<"},
+ "<", "IntlBackslash", KeyboardEvent.DOM_VK_LESS_THAN, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_102,
+ modifiers:{shiftKey:1}, chars:">"},
+ ">", "IntlBackslash", KeyboardEvent.DOM_VK_LESS_THAN, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{}, chars:","},
+ ",", "KeyM", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{shiftKey:1}, chars:"?"},
+ "?", "KeyM", KeyboardEvent.DOM_VK_COMMA, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{}, chars:";"},
+ ";", "Comma", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{shiftKey:1}, chars:"."},
+ ".", "Comma", KeyboardEvent.DOM_VK_SEMICOLON, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_2,
+ modifiers:{}, chars:":"},
+ ":", "Period", KeyboardEvent.DOM_VK_COLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_2,
+ modifiers:{shiftKey:1}, chars:"/"},
+ "/", "Period", KeyboardEvent.DOM_VK_COLON, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_8,
+ modifiers:{}, chars:"!"},
+ "!", "Slash", KeyboardEvent.DOM_VK_EXCLAMATION, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_8,
+ modifiers:{shiftKey:1}, chars:"\u00A7"},
+ "\u00A7", "Slash", KeyboardEvent.DOM_VK_EXCLAMATION, "\u00A7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // OEM keys with ShiftLock
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_7,
+ modifiers:{capsLockKey:1}, chars:"\u00B2"},
+ "\u00B2", "Backquote", KeyboardEvent.DOM_VK_QUOTE, "\u00B2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_7,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:""},
+ "", "Backquote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_4,
+ modifiers:{capsLockKey:1}, chars:"\u00B0"},
+ "\u00B0", "Minus", KeyboardEvent.DOM_VK_CLOSE_PAREN, "\u00B0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_4,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:")"},
+ ")", "Minus", KeyboardEvent.DOM_VK_CLOSE_PAREN, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{capsLockKey:1}, chars:"+"},
+ "+", "Equal", KeyboardEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"="},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ //yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ // modifiers:{capsLockKey:1}, chars:""},
+ // "Dead", "BracketLeft", KeyboardLayout.DOM_VK_CLOSE_BRACKET, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Dead-key
+ //yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ // modifiers:{capsLockKey:1, shiftKey:1}, chars:""},
+ // ["\u00A8\u00A8", "\u00A8", "\u00A8", "\u00A8"], "BracketLeft", KeyboardLayout.DOM_VK_CLOSE_BRACKET, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Dead-key
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_1,
+ modifiers:{capsLockKey:1}, chars:"\u00A3"},
+ "\u00A3", "BracketRight", KeyboardEvent.DOM_VK_DOLLAR, "\u00A3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_1,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"$"},
+ "$", "BracketRight", KeyboardEvent.DOM_VK_DOLLAR, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_3,
+ modifiers:{capsLockKey:1}, chars:"%"},
+ "%", "Quote", KeyboardEvent.DOM_VK_PERCENT, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_3,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"\u00F9"},
+ "\u00F9", "Quote", KeyboardEvent.DOM_VK_PERCENT, "\u00F9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_5,
+ modifiers:{capsLockKey:1}, chars:"\u00B5"},
+ "\u00B5", "Backslash", KeyboardEvent.DOM_VK_ASTERISK, "\u00B5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_5,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"*"},
+ "*", "Backslash", KeyboardEvent.DOM_VK_ASTERISK, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_102,
+ modifiers:{capsLockKey:1}, chars:"<"},
+ "<", "IntlBackslash", KeyboardEvent.DOM_VK_LESS_THAN, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_102,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:">"},
+ ">", "IntlBackslash", KeyboardEvent.DOM_VK_LESS_THAN, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{capsLockKey:1}, chars:"?"},
+ "?", "KeyM", KeyboardEvent.DOM_VK_COMMA, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:","},
+ ",", "KeyM", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{capsLockKey:1}, chars:"."},
+ ".", "Comma", KeyboardEvent.DOM_VK_SEMICOLON, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:";"},
+ ";", "Comma", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_2,
+ modifiers:{capsLockKey:1}, chars:"/"},
+ "/", "Period", KeyboardEvent.DOM_VK_COLON, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_2,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:":"},
+ ":", "Period", KeyboardEvent.DOM_VK_COLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_8,
+ modifiers:{capsLockKey:1}, chars:"\u00A7"},
+ "\u00A7", "Slash", KeyboardEvent.DOM_VK_EXCLAMATION, "\u00A7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_8,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"!"},
+ "!", "Slash", KeyboardEvent.DOM_VK_EXCLAMATION, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // AltGr
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+ modifiers:{altGrKey:1}, chars:"@"},
+ "@", "Digit0", KeyboardEvent.DOM_VK_0, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // AltGr + Digit1 does not cause text input in French layout. In this case,
+ // AltGr shouldn't be used for a modifier of shortcut. Therefore, not
+ // receiving `keypress` event even in the system group is fine.
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{altGrKey:1}, chars:""},
+ "&", "Digit1", KeyboardEvent.DOM_VK_1, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ //yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_2,
+ // modifiers:{altGrKey:1}, chars:""},
+ // "Dead", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Dead-key
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_3,
+ modifiers:{altGrKey:1}, chars:"#"},
+ "#", "Digit3", KeyboardEvent.DOM_VK_3, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_4,
+ modifiers:{altGrKey:1}, chars:"{"},
+ "{", "Digit4", KeyboardEvent.DOM_VK_4, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_5,
+ modifiers:{altGrKey:1}, chars:"["},
+ "[", "Digit5", KeyboardEvent.DOM_VK_5, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_6,
+ modifiers:{altGrKey:1}, chars:"|"},
+ "|", "Digit6", KeyboardEvent.DOM_VK_6, "|", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ //yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_7,
+ // modifiers:{altGrKey:1}, chars:""},
+ // "Dead", "Digit7", KeyboardEvent.DOM_VK_7, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Dead-key
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_8,
+ modifiers:{altGrKey:1}, chars:"\\"},
+ "\\", "Digit8", KeyboardEvent.DOM_VK_8, "\\", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_9,
+ modifiers:{altGrKey:1}, chars:"^"},
+ "^", "Digit9", KeyboardEvent.DOM_VK_9, "^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_4,
+ modifiers:{altGrKey:1}, chars:"]"},
+ "]", "Minus", KeyboardEvent.DOM_VK_CLOSE_PAREN, "]", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{altGrKey:1}, chars:"}"},
+ "}", "Equal", KeyboardEvent.DOM_VK_EQUALS, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // AltGr emulated with Ctrl and Alt
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"@", isInputtingCharacters:true},
+ "@", "Digit0", KeyboardEvent.DOM_VK_0, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+ modifiers:{ctrlKey:1, altKey:1, shiftKey:1}, chars:"", isInputtingCharacters:false},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Different from AltGr + Digit1 case, Ctrl + Alt + Digit1 should be
+ // available as a shortcut key. Therefore, `keypress` event needs to be
+ // fired.
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"", isInputtingCharacters:false},
+ "&", "Digit1", KeyboardEvent.DOM_VK_1, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{ctrlKey:1, altKey:1, shiftKey:1}, chars:"", isInputtingCharacters:false},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // German
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_OEM_2,
+ modifiers:{}, chars:"#"},
+ "#", "Backslash", KeyboardEvent.DOM_VK_HASH, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_OEM_2,
+ modifiers:{shiftKey:1}, chars:"'"},
+ "'", "Backslash", KeyboardEvent.DOM_VK_HASH, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Khmer
+ if (OS_VERSION >= WIN7) {
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2,
+ modifiers:{}, chars:"\u17E2"},
+ "\u17E2", "Digit2", KeyboardEvent.DOM_VK_2, "\u17E2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2,
+ modifiers:{shiftKey:1}, chars:"\u17D7"},
+ "\u17D7", "Digit2", KeyboardEvent.DOM_VK_2, "\u17D7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2, // Ctrl+2 should cause inputting Euro sign.
+ modifiers:{ctrlKey:1}, chars:"\u20AC", isInputtingCharacters:true},
+ "\u20AC", "Digit2", KeyboardEvent.DOM_VK_2, "\u20AC", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2, // Ctrl+Shift+2 shouldn't cause any input.
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "\u17D7", "Digit2", KeyboardEvent.DOM_VK_2, "\u17D7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2,
+ modifiers:{altKey:1}, chars:"\u17E2"},
+ "\u17E2", "Digit2", KeyboardEvent.DOM_VK_2, "\u17E2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u17D7"},
+ "\u17D7", "Digit2", KeyboardEvent.DOM_VK_2, "\u17D7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2,
+ modifiers:{altGrKey:1}, chars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2,
+ modifiers:{altGrKey:1, shiftKey:1}, chars:"\u19E2"},
+ "\u19E2", "Digit2", KeyboardEvent.DOM_VK_2, "\u19E2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ }
+
+ // Norwegian
+ yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_OEM_5,
+ modifiers:{}, chars:"|"},
+ "|", "Backquote", KeyboardEvent.DOM_VK_PIPE, "|", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_OEM_5,
+ modifiers:{shiftKey:1}, chars:"\u00A7"},
+ "\u00A7", "Backquote", KeyboardEvent.DOM_VK_PIPE, "\u00A7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Brazilian ABNT
+ yield testKey({layout:KEYBOARD_LAYOUT_BRAZILIAN_ABNT, keyCode:WIN_VK_ABNT_C1,
+ modifiers:{}, chars:"/"},
+ "/", "IntlBackslash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_BRAZILIAN_ABNT, keyCode:WIN_VK_ABNT_C1,
+ modifiers:{shiftKey:1}, chars:"?"},
+ "?", "IntlBackslash", KeyboardEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_BRAZILIAN_ABNT, keyCode:WIN_VK_ABNT_C2,
+ modifiers:{numLockKey:1}, chars:"."},
+ ".", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_BRAZILIAN_ABNT, keyCode:WIN_VK_DECIMAL,
+ modifiers:{numLockKey:1}, chars:","},
+ ",", "NumpadDecimal", KeyboardEvent.DOM_VK_DECIMAL, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+
+ // Mac JIS keyboard
+ // The separator key on JIS keyboard for Mac doesn't cause any character even with Japanese keyboard layout.
+ yield testKey({layout:KEYBOARD_LAYOUT_JAPANESE, keyCode:WIN_VK_ABNT_C2,
+ modifiers:{numLockKey:1}, chars:""},
+ "", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_JAPANESE, keyCode:WIN_VK_DECIMAL,
+ modifiers:{numLockKey:1}, chars:"."},
+ ".", "NumpadDecimal", KeyboardEvent.DOM_VK_DECIMAL, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+
+ // Dead keys on any layouts
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{}, chars:"^^"},
+ ["^^", "^", "^", "^"], "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "^^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E2"},
+ ["\u00E2", "\u00E2", "a"], "KeyQ", KeyboardEvent.DOM_VK_A, "\u00E2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"\u00C2"},
+ ["\u00C2", "\u00C2", "A"], "KeyQ", KeyboardEvent.DOM_VK_A, "\u00C2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_Q,
+ modifiers:{}, chars:"^q"},
+ ["^q", "^", "q", "q"], "KeyA", KeyboardEvent.DOM_VK_Q, "^q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{shiftKey:1}, chars:"\u00A8\u00A8"},
+ ["\u00A8\u00A8", "\u00A8", "\u00A8", "\u00A8"], "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "\u00A8\u00A8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"\u00C4"},
+ ["\u00C4", "\u00C4", "A"], "KeyQ", KeyboardEvent.DOM_VK_A, "\u00C4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E4"},
+ ["\u00E4", "\u00E4", "a"], "KeyQ", KeyboardEvent.DOM_VK_A, "\u00E4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_Q,
+ modifiers:{shiftKey:1}, chars:"\u00A8Q"},
+ ["\u00A8Q", "\u00A8", "Q", "Q"], "KeyA", KeyboardEvent.DOM_VK_Q, "\u00A8Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{}, chars:"``"},
+ ["``", "`", "`", "`"], "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "``", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E0"},
+ ["\u00E0", "\u00E0", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"\u00C0"},
+ ["\u00C0", "\u00C0", "A"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00C0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_Q,
+ modifiers:{}, chars:"`q"},
+ ["`q", "`", "q", "q"], "KeyQ", KeyboardEvent.DOM_VK_Q, "`q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{shiftKey:1}, chars:"^^"},
+ ["^^", "^", "^", "^"], "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "^^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"\u00C2"},
+ ["\u00C2", "\u00C2", "A"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00C2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E2"},
+ ["\u00E2", "\u00E2", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_Q,
+ modifiers:{shiftKey:1}, chars:"^Q"},
+ ["^Q", "^", "Q", "Q"], "KeyQ", KeyboardEvent.DOM_VK_Q, "^Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:"\u00B4\u00B4"},
+ ["\u00B4\u00B4", "\u00B4", "\u00B4", "\u00B4"], "Quote", KeyboardEvent.DOM_VK_QUOTE, "\u00B4\u00B4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E1"},
+ ["\u00E1", "\u00E1", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"\u00C1"},
+ ["\u00C1", "\u00C1", "A"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00C1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_Q,
+ modifiers:{}, chars:"\u00B4q"},
+ ["\u00B4q", "\u00B4", "q", "q"], "KeyQ", KeyboardEvent.DOM_VK_Q, "\u00B4q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{shiftKey:1}, chars:"\u00A8\u00A8"},
+ ["\u00A8\u00A8", "\u00A8", "\u00A8", "\u00A8"], "Quote", KeyboardEvent.DOM_VK_QUOTE, "\u00A8\u00A8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"\u00C4"},
+ ["\u00C4", "\u00C4", "A"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00C4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E4"},
+ ["\u00E4", "\u00E4", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_Q,
+ modifiers:{shiftKey:1}, chars:"\u00A8Q"},
+ ["\u00A8Q", "\u00A8", "Q", "Q"], "KeyQ", KeyboardEvent.DOM_VK_Q, "\u00A8Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_OEM_1,
+ modifiers:{altGrKey:1}, chars:""},
+ "Dead", "BracketRight", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E3"},
+ ["\u00E3", "\u00E3", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_OEM_1,
+ modifiers:{ctrlKey:1, altKey:1}, chars:""},
+ "Dead", "BracketRight", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E3"},
+ ["\u00E3", "\u00E3", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ if (OS_VERSION >= WIN8) {
+ // On Russian Mnemonic layout, both 'KeyS' and 'KeyC' are dead key. However, the sequence 'KeyS' -> 'KeyC' causes a composite character.
+ yield testKey({layout:KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC, keyCode:WIN_VK_S,
+ modifiers:{}, chars:""},
+ "Dead", "KeyS", KeyboardEvent.DOM_VK_S, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC, keyCode:WIN_VK_S,
+ modifiers:{}, chars:"\u0441\u0441"},
+ ["\u0441\u0441", "\u0441", "\u0441", "\u0441"], "KeyS", KeyboardEvent.DOM_VK_S, "\u0441\u0441", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC, keyCode:WIN_VK_C,
+ modifiers:{}, chars:""},
+ "Dead", "KeyC", KeyboardEvent.DOM_VK_C, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC, keyCode:WIN_VK_C,
+ modifiers:{}, chars:"\u0446\u0446"},
+ ["\u0446\u0446", "\u0446", "\u0446", "\u0446"], "KeyC", KeyboardEvent.DOM_VK_C, "\u0446\u0446", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC, keyCode:WIN_VK_S,
+ modifiers:{}, chars:""},
+ "Dead", "KeyS", KeyboardEvent.DOM_VK_S, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC, keyCode:WIN_VK_C,
+ modifiers:{}, chars:"\u0449"},
+ ["\u0449", "\u0449", "\u0446"], "KeyC", KeyboardEvent.DOM_VK_C, "\u0449", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ }
+
+ // When Alt key is pressed, dead key sequence is generated with WM_SYSKEYDOWN, WM_SYSDEADCHAR, WM_SYSCHAR and WM_SYSKEYUP.
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1}, chars:"``"},
+ ["``", "`", "`", "`"], "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "``", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{altKey:1}, chars:"\u00E0"},
+ ["\u00E0", "\u00E0", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C0"},
+ ["\u00C0", "\u00C0", "A"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00C0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_Q,
+ modifiers:{altKey:1}, chars:"`q"},
+ ["`q", "`", "q", "q"], "KeyQ", KeyboardEvent.DOM_VK_Q, "`q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:"^^"},
+ ["^^", "^", "^", "^"], "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "^^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C2"},
+ ["\u00C2", "\u00C2", "A"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00C2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{altKey:1}, chars:"\u00E2"},
+ ["\u00E2", "\u00E2", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_Q,
+ modifiers:{altKey:1, shiftKey:1}, chars:"^Q"},
+ ["^Q", "^", "Q", "Q"], "KeyQ", KeyboardEvent.DOM_VK_Q, "^Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ cleanup();
+ }
+
+
+ if (IS_WIN) {
+ yield* testKeysOnWindows();
+ } else if (IS_MAC) {
+ yield* testKeysOnMac();
+ } else {
+ cleanup();
+ }
+}
+
+// Test the activation (or not) of an HTML accesskey
+function* runAccessKeyTests()
+{
+ var button = document.getElementById("button");
+ var activationCount;
+
+ function onClick(e)
+ {
+ ++activationCount;
+ }
+
+ // The first parameter is the complete input event. The second and third parameters are
+ // what to test against.
+ function testKey(aEvent, aAccessKey, aShouldActivate)
+ {
+ activationCount = 0;
+ button.setAttribute("accesskey", aAccessKey);
+
+ return synthesizeKey(aEvent, "button", function() {
+
+ var currentTestName = eventToString(aEvent);
+
+ is(activationCount, aShouldActivate ? 1 : 0,
+ currentTestName + ", activating '" + aAccessKey + "'");
+
+ continueTest();
+ });
+ }
+
+ button.addEventListener("click", onClick);
+
+ // These tests have to be per-plaform.
+ if (IS_MAC) {
+ // Basic sanity checks
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{}, chars:"a", unmodifiedChars:"a"},
+ "a", false);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "a", false);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "A", false);
+
+ // Shift-ctrl does not activate accesskeys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0001", unmodifiedChars:"A"},
+ "a", false);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0001", unmodifiedChars:"A"},
+ "A", false);
+ // Alt-ctrl activate accesskeys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "a", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "A", true);
+
+ // Greek layout can activate a Latin accesskey
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u0001", unmodifiedChars:"\u03b1"},
+ "a", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u0001", unmodifiedChars:"\u03b1"},
+ "A", true);
+ // ... and a Greek accesskey!
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u0001", unmodifiedChars:"\u03b1"},
+ "\u03b1", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u0001", unmodifiedChars:"\u03b1"},
+ "\u0391", true);
+
+ // bug 359638
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{ctrlKey:1, altKey:1}, chars:".", unmodifiedChars:"."},
+ ".", true);
+
+ // German (KCHR/KeyTranslate case)
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"a", unmodifiedChars:"a"},
+ "a", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"a", unmodifiedChars:"a"},
+ "A", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u00fc", unmodifiedChars:"\u00fc"},
+ "\u00fc", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u00fc", unmodifiedChars:"\u00fc"},
+ "\u00dc", true);
+ }
+ else if (IS_WIN) {
+ // Basic sanity checks
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"a"},
+ "a", false);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1, altKey:1}, chars:"A"},
+ "a", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1, altKey:1}, chars:"A"},
+ "A", true);
+
+ // shift-alt-ctrl does not activate accesskeys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, shiftKey:1, altKey:1}, chars:""},
+ "a", false);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, shiftKey:1, altKey:1}, chars:""},
+ "A", false);
+
+ // Greek layout can activate a Latin accesskey
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1, altKey:1}, chars:"\u0391"},
+ "a", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1, altKey:1}, chars:"\u0391"},
+ "A", true);
+ // ... and a Greek accesskey!
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1, altKey:1}, chars:"\u0391"},
+ "\u03b1", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1, altKey:1}, chars:"\u0391"},
+ "\u0391", true);
+
+ // bug 359638
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{shiftKey:1, altKey:1}, chars:".", unmodifiedChars:"."},
+ ".", true);
+ }
+
+ button.removeEventListener("click", onClick);
+}
+
+function* runXULKeyTests()
+{
+ var commandElements = {
+ expectedCommand: document.getElementById("expectedCommand"),
+ unexpectedCommand: document.getElementById("unexpectedCommand"),
+ expectedReservedCommand: document.getElementById("expectedReservedCommand")
+ };
+ // Enable all command elements.
+ for (var id in commandElements) {
+ commandElements[id].removeAttribute("disabled");
+ }
+
+ var keyEvents = [];
+
+ function onKeyInDefaultEventGroup(aDOMEvent)
+ {
+ if (isModifierKeyEvent(aDOMEvent)) {
+ return;
+ }
+ keyEvents.push({ type: aDOMEvent.type, group: "default", phase: getPhase(aDOMEvent), currentTarget: eventTargetToString(aDOMEvent.currentTarget) });
+ }
+
+ function onKeyInSystemEventGroup(aDOMEvent)
+ {
+ if (isModifierKeyEvent(aDOMEvent)) {
+ return;
+ }
+ keyEvents.push({ type: aDOMEvent.type, group: "system", phase: getPhase(aDOMEvent), currentTarget: eventTargetToString(aDOMEvent.currentTarget) });
+ }
+
+ var buttonParent = document.getElementById("button").parentNode;
+ buttonParent.addEventListener("keydown", onKeyInDefaultEventGroup, true);
+ buttonParent.addEventListener("keypress", onKeyInDefaultEventGroup, true);
+ buttonParent.addEventListener("keydown", onKeyInDefaultEventGroup);
+ buttonParent.addEventListener("keypress", onKeyInDefaultEventGroup);
+ SpecialPowers.addSystemEventListener(buttonParent, "keydown", onKeyInSystemEventGroup, true);
+ SpecialPowers.addSystemEventListener(buttonParent, "keypress", onKeyInSystemEventGroup, true);
+ SpecialPowers.addSystemEventListener(buttonParent, "keydown", onKeyInSystemEventGroup, false);
+ SpecialPowers.addSystemEventListener(buttonParent, "keypress", onKeyInSystemEventGroup, false);
+
+ function finializeKeyElementTest()
+ {
+ buttonParent.removeEventListener("keydown", onKeyInDefaultEventGroup, true);
+ buttonParent.removeEventListener("keypress", onKeyInDefaultEventGroup, true);
+ buttonParent.removeEventListener("keydown", onKeyInDefaultEventGroup);
+ buttonParent.removeEventListener("keypress", onKeyInDefaultEventGroup);
+ SpecialPowers.removeSystemEventListener(buttonParent, "keydown", onKeyInSystemEventGroup, true);
+ SpecialPowers.removeSystemEventListener(buttonParent, "keypress", onKeyInSystemEventGroup, true);
+ SpecialPowers.removeSystemEventListener(buttonParent, "keydown", onKeyInSystemEventGroup, false);
+ SpecialPowers.removeSystemEventListener(buttonParent, "keypress", onKeyInSystemEventGroup, false);
+ }
+
+ // If aKeyElement is empty string, this function tests if the event kicks
+ // no key elements.
+ function testKeyElement(aEvent, aKeyElementId)
+ {
+ var testName = "testKeyElement (with non-reserved command element): " + eventToString(aEvent) + " ";
+ var keyElement = aKeyElementId == "" ? null : document.getElementById(aKeyElementId);
+ if (keyElement) {
+ keyElement.setAttribute("command", "expectedCommand");
+ }
+
+ /* eslint-disable-next-line no-shadow */
+ for (var id in commandElements) {
+ commandElements[id].activeCount = 0;
+ }
+
+ keyEvents = [];
+ return synthesizeKey(aEvent, "button", function() {
+ if (keyElement) {
+ is(commandElements.expectedCommand.activeCount, 1, testName + "the command element (id='expectedCommand') should be preformed");
+ } else {
+ is(commandElements.expectedCommand.activeCount, 0, testName + "the command element (id='expectedCommand') shouldn't be preformed");
+ }
+ is(commandElements.unexpectedCommand.activeCount, 0, testName + "the command element (id='unexpectedCommand') shouldn't be preformed");
+ is(commandElements.expectedReservedCommand.activeCount, 0, testName + "the command element (id='expectedReservedCommand') shouldn't be preformed");
+
+ function checkFiredEvents()
+ {
+ let expectKeyPressEvent = aKeyElementId != "" ||
+ ((aEvent.modifiers.ctrlKey || aEvent.modifiers.altKey || aEvent.modifiers.metaKey) &&
+ (!IS_WIN || !aEvent.modifiers.altGrKey));
+ is(keyEvents.length, expectKeyPressEvent ? 8 : 4, testName + "wrong number events fired");
+ is(JSON.stringify(keyEvents[0]), JSON.stringify({ type: "keydown", group: "default", phase: "capture", currentTarget: eventTargetToString(buttonParent) }), testName + "keydown event should be fired on chrome in the default event group #0");
+ is(JSON.stringify(keyEvents[1]), JSON.stringify({ type: "keydown", group: "default", phase: "bubble", currentTarget: eventTargetToString(buttonParent) }), testName + "keydown event should be fired on chrome in the default event group #1");
+
+ is(JSON.stringify(keyEvents[2]), JSON.stringify({ type: "keydown", group: "system", phase: "capture", currentTarget: eventTargetToString(buttonParent) }), testName + "keydown event should be fired on chrome in the system event group #2");
+ is(JSON.stringify(keyEvents[3]), JSON.stringify({ type: "keydown", group: "system", phase: "bubble", currentTarget: eventTargetToString(buttonParent) }), testName + "keydown event should be fired on chrome in the system event group #3");
+
+ if (expectKeyPressEvent) {
+ is(JSON.stringify(keyEvents[4]), JSON.stringify({ type: "keypress", group: "default", phase: "capture", currentTarget: eventTargetToString(buttonParent) }), testName + "keypress event should be fired on chrome in the default event group #4");
+ is(JSON.stringify(keyEvents[5]), JSON.stringify({ type: "keypress", group: "default", phase: "bubble", currentTarget: eventTargetToString(buttonParent) }), testName + "keypress event should be fired on chrome in the default event group #5");
+
+ is(JSON.stringify(keyEvents[6]), JSON.stringify({ type: "keypress", group: "system", phase: "capture", currentTarget: eventTargetToString(buttonParent) }), testName + "keypress event should be fired on chrome in the system event group #6");
+ is(JSON.stringify(keyEvents[7]), JSON.stringify({ type: "keypress", group: "system", phase: "bubble", currentTarget: eventTargetToString(buttonParent) }), testName + "keypress event should be fired on chrome in the system event group #7");
+ }
+ }
+
+ checkFiredEvents();
+
+ if (keyElement) {
+ testName = "testKeyElement (with reserved command element): " + eventToString(aEvent) + " ";
+ keyElement.setAttribute("command", "expectedReservedCommand");
+
+ for (id in commandElements) {
+ commandElements[id].activeCount = 0;
+ }
+ keyEvents = [];
+ synthesizeKey(aEvent, "button", function() {
+ is(commandElements.expectedCommand.activeCount, 0, testName + "the command element (id='expectedCommand') shouldn't be preformed");
+ is(commandElements.unexpectedCommand.activeCount, 0, testName + "the command element (id='unexpectedCommand') shouldn't be preformed");
+ is(commandElements.expectedReservedCommand.activeCount, 1, testName + "the command element (id='expectedReservedCommand') should be preformed");
+
+ checkFiredEvents();
+
+ if (keyElement) {
+ keyElement.setAttribute("command", "unexpectedCommand");
+ }
+ continueTest();
+ });
+ } else {
+ if (keyElement) {
+ keyElement.setAttribute("command", "unexpectedCommand");
+ }
+ continueTest();
+ }
+ });
+ }
+
+ if (IS_MAC) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{metaKey:1}, chars:";", unmodifiedChars:";"},
+ "unshiftedKey");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{metaKey:1, shiftKey:1}, chars:";", unmodifiedChars:":"},
+ "shiftedKey");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{metaKey:1}, chars:"'", unmodifiedChars:"'"},
+ "reservedUnshiftedKey");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"\"", unmodifiedChars:"'"},
+ "reservedShiftedKey");
+ }
+ else if (IS_WIN) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{ctrlKey:1}, chars:""},
+ "unshiftedKey");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "shiftedKey");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{ctrlKey:1}, chars:""},
+ "reservedUnshiftedKey");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "reservedShiftedKey");
+ }
+
+ // 429160
+ if (IS_MAC) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{metaKey:1, altKey:1}, chars:"\u0192", unmodifiedChars:"f"},
+ "commandOptionF");
+ }
+ else if (IS_WIN) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u0006"},
+ "commandOptionF");
+ }
+
+ // 432112
+ if (IS_MAC) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_SWEDISH, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"+", unmodifiedChars:"?"},
+ "question");
+ }
+ else if (IS_WIN) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_SWEDISH, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "question");
+ // For testing if Ctrl+? is kicked without Shift state, temporarily disable
+ // Ctrl-+ key element.
+ var unshiftedPlusKeyElement = document.getElementById("unshiftedPlus");
+ unshiftedPlusKeyElement.setAttribute("disabled", "true");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_SWEDISH, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{ctrlKey:1}, chars:""},
+ "");
+ unshiftedPlusKeyElement.removeAttribute("disabled");
+ }
+
+ // bug 433192
+ if (IS_WIN) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1}, chars:"\u0018"},
+ "unshiftedX");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0018"},
+ "shiftedX");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_ARABIC, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1}, chars:"\u0018"},
+ "unshiftedX");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_ARABIC, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0018"},
+ "shiftedX");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_HEBREW, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1}, chars:"\u0018"},
+ "unshiftedX");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_HEBREW, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0018"},
+ "shiftedX");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_JAPANESE, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "unshiftedPlus");
+ }
+
+ // bug 759346
+ if (IS_WIN) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_THAI, keyCode:WIN_VK_1,
+ modifiers:{ctrlKey:1}, chars:""},
+ "");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_THAI, keyCode:WIN_VK_1,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "unshiftedPlus");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_THAI, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{ctrlKey:1}, chars:""},
+ "unshiftedPlus");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_THAI, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "");
+ }
+
+ // bug 1596916
+ if (IS_WIN) {
+ // Ctrl + Alt + foo should be performed only when AltGr key is not
+ // pressed, i.e., only when Ctrl key and left Alt key are pressed if
+ // active keyboard layout has AltGr key.
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_A,
+ modifiers:{altGrKey:1}, chars:""},
+ "");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, altGrKey:1}, chars:""},
+ "");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_A,
+ modifiers:{altKey:1, altGrKey:1}, chars:""},
+ "");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:""},
+ "ctrlAltA");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, altKey:1, altGrKey:1}, chars:""},
+ "");
+ }
+
+ for (id in commandElements) {
+ commandElements[id].setAttribute("disabled", "true");
+ }
+ finializeKeyElementTest();
+}
+
+function* runReservedKeyTests()
+{
+ var browser = document.getElementById("browser");
+ var contents = [
+ browser.contentWindow,
+ browser.contentDocument,
+ browser.contentDocument.documentElement,
+ browser.contentDocument.body,
+ browser.contentDocument.getElementById("content_button")
+ ];
+
+ for (var i = 0; i < contents.length; i++) {
+ contents[i].addEventListener("keydown", onKeyInDefaultEventGroup, true);
+ contents[i].addEventListener("keypress", onKeyInDefaultEventGroup, true);
+ contents[i].addEventListener("keydown", onKeyInDefaultEventGroup);
+ contents[i].addEventListener("keypress", onKeyInDefaultEventGroup);
+ SpecialPowers.addSystemEventListener(contents[i], "keydown", onKeyInSystemEventGroup, true);
+ SpecialPowers.addSystemEventListener(contents[i], "keypress", onKeyInSystemEventGroup, true);
+ SpecialPowers.addSystemEventListener(contents[i], "keydown", onKeyInSystemEventGroup, false);
+ SpecialPowers.addSystemEventListener(contents[i], "keypress", onKeyInSystemEventGroup, false);
+ }
+
+ var keyEvents = [];
+
+ function onKeyInDefaultEventGroup(aDOMEvent)
+ {
+ if (isModifierKeyEvent(aDOMEvent)) {
+ return;
+ }
+ keyEvents.push({ type: aDOMEvent.type, group: "default", phase: getPhase(aDOMEvent), currentTarget: eventTargetToString(aDOMEvent.currentTarget) });
+ }
+
+ function onKeyInSystemEventGroup(aDOMEvent)
+ {
+ if (isModifierKeyEvent(aDOMEvent)) {
+ return;
+ }
+ keyEvents.push({ type: aDOMEvent.type, group: "system", phase: getPhase(aDOMEvent), currentTarget: eventTargetToString(aDOMEvent.currentTarget) });
+ // prevents reserved default action
+ if (aDOMEvent.type == "keypress" &&
+ aDOMEvent.eventPhase == aDOMEvent.BUBBLING_PHASE &&
+ aDOMEvent.currentTarget == browser.contentWindow) {
+ aDOMEvent.preventDefault();
+ }
+ }
+
+ function finializeKeyElementTest()
+ {
+ /* eslint-disable-next-line no-shadow */
+ for (var i = 0; i < contents.length; i++) {
+ contents[i].removeEventListener("keydown", onKeyInDefaultEventGroup, true);
+ contents[i].removeEventListener("keypress", onKeyInDefaultEventGroup, true);
+ contents[i].removeEventListener("keydown", onKeyInDefaultEventGroup);
+ contents[i].removeEventListener("keypress", onKeyInDefaultEventGroup);
+ SpecialPowers.removeSystemEventListener(contents[i], "keydown", onKeyInSystemEventGroup, true);
+ SpecialPowers.removeSystemEventListener(contents[i], "keypress", onKeyInSystemEventGroup, true);
+ SpecialPowers.removeSystemEventListener(contents[i], "keydown", onKeyInSystemEventGroup, false);
+ SpecialPowers.removeSystemEventListener(contents[i], "keypress", onKeyInSystemEventGroup, false);
+ }
+ }
+
+ function testReservedKey(aEvent)
+ {
+ keyEvents = [];
+ return synthesizeKey(aEvent, "content_button", function() {
+ let testName = "testReservedKey: " + eventToString(aEvent) + " ";
+ is(keyEvents.length, 20, testName + "wrong number events fired");
+ is(JSON.stringify(keyEvents[0]), JSON.stringify({ type: "keydown", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[0]) }), testName + "keydown event should be fired on content only in the system event group #0");
+ is(JSON.stringify(keyEvents[1]), JSON.stringify({ type: "keydown", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[1]) }), testName + "keydown event should be fired on content only in the system event group #1");
+ is(JSON.stringify(keyEvents[2]), JSON.stringify({ type: "keydown", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[2]) }), testName + "keydown event should be fired on content only in the system event group #2");
+ is(JSON.stringify(keyEvents[3]), JSON.stringify({ type: "keydown", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[3]) }), testName + "keydown event should be fired on content only in the system event group #3");
+
+ is(JSON.stringify(keyEvents[4]), JSON.stringify({ type: "keydown", group: "system", phase: "target", currentTarget: eventTargetToString(contents[4]) }), testName + "keydown event should be fired on content only in the system event group #4");
+ is(JSON.stringify(keyEvents[5]), JSON.stringify({ type: "keydown", group: "system", phase: "target", currentTarget: eventTargetToString(contents[4]) }), testName + "keydown event should be fired on content only in the system event group #5");
+
+ is(JSON.stringify(keyEvents[6]), JSON.stringify({ type: "keydown", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[3]) }), testName + "keydown event should be fired on content only in the system event group #6");
+ is(JSON.stringify(keyEvents[7]), JSON.stringify({ type: "keydown", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[2]) }), testName + "keydown event should be fired on content only in the system event group #7");
+ is(JSON.stringify(keyEvents[8]), JSON.stringify({ type: "keydown", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[1]) }), testName + "keydown event should be fired on content only in the system event group #8");
+ is(JSON.stringify(keyEvents[9]), JSON.stringify({ type: "keydown", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[0]) }), testName + "keydown event should be fired on content only in the system event group #9");
+
+ is(JSON.stringify(keyEvents[10]), JSON.stringify({ type: "keypress", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[0]) }), testName + "keypress event should be fired on content only in the system event group #10");
+ is(JSON.stringify(keyEvents[11]), JSON.stringify({ type: "keypress", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[1]) }), testName + "keypress event should be fired on content only in the system event group #11");
+ is(JSON.stringify(keyEvents[12]), JSON.stringify({ type: "keypress", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[2]) }), testName + "keypress event should be fired on content only in the system event group #12");
+ is(JSON.stringify(keyEvents[13]), JSON.stringify({ type: "keypress", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[3]) }), testName + "keypress event should be fired on content only in the system event group #13");
+
+ is(JSON.stringify(keyEvents[14]), JSON.stringify({ type: "keypress", group: "system", phase: "target", currentTarget: eventTargetToString(contents[4]) }), testName + "keypress event should be fired on content only in the system event group #14");
+ is(JSON.stringify(keyEvents[15]), JSON.stringify({ type: "keypress", group: "system", phase: "target", currentTarget: eventTargetToString(contents[4]) }), testName + "keypress event should be fired on content only in the system event group #15");
+
+ is(JSON.stringify(keyEvents[16]), JSON.stringify({ type: "keypress", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[3]) }), testName + "keypress event should be fired on content only in the system event group #16");
+ is(JSON.stringify(keyEvents[17]), JSON.stringify({ type: "keypress", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[2]) }), testName + "keypress event should be fired on content only in the system event group #17");
+ is(JSON.stringify(keyEvents[18]), JSON.stringify({ type: "keypress", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[1]) }), testName + "keypress event should be fired on content only in the system event group #18");
+ is(JSON.stringify(keyEvents[19]), JSON.stringify({ type: "keypress", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[0]) }), testName + "keypress event should be fired on content only in the system event group #19");
+
+ continueTest();
+ });
+ }
+
+ if (IS_MAC) {
+ // Cmd+T is reserved for opening new tab.
+ yield testReservedKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{metaKey:1}, chars:"t", unmodifiedChars:"t"});
+ } else if (IS_WIN) {
+ // Ctrl+T is reserved for opening new tab.
+ yield testReservedKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+ modifiers:{ctrlKey:1}, chars:"\u0014"});
+ yield testReservedKey({layout:KEYBOARD_LAYOUT_ARABIC, keyCode:WIN_VK_T,
+ modifiers:{ctrlKey:1}, chars:"\u0014"});
+ }
+
+ finializeKeyElementTest();
+}
+
+function* runTextInputTests()
+{
+ var textbox = document.getElementById("textbox");
+
+ function testKey(aEvent, aExpectText) {
+ textbox.value = "";
+ textbox.focus();
+
+ /* eslint-disable-next-line no-shadow */
+ var currentTestName = eventToString(aEvent);
+
+ // Check if the text comes with keypress events rather than composition events.
+ var keypress = 0;
+ /* eslint-disable-next-line no-shadow */
+ function onKeypress(aEvent) {
+ keypress++;
+ if (keypress == 1 && aExpectText == "") {
+ if (!aEvent.ctrlKey && !aEvent.altKey) {
+ is(aEvent.charCode, 0,
+ currentTestName + ", the charCode value should be 0 when it shouldn't cause inputting text");
+ }
+ return;
+ }
+ if (keypress > aExpectText.length) {
+ ok(false, currentTestName + " causes too many keypress events");
+ return;
+ }
+ is(aEvent.key, aExpectText[keypress - 1],
+ currentTestName + ", " + keypress + "th keypress event's key value should be '" + aExpectText[keypress - 1] + "'");
+ is(aEvent.charCode, aExpectText.charCodeAt(keypress - 1),
+ currentTestName + ", " + keypress + "th keypress event's charCode value should be 0x" + parseInt(aExpectText.charCodeAt(keypress - 1), 16));
+ }
+ textbox.addEventListener("keypress", onKeypress, true);
+
+ return synthesizeKey(aEvent, "textbox", () => {
+ textbox.removeEventListener("keypress", onKeypress, true);
+ if (aExpectText == "") {
+ if (aEvent.modifiers.ctrlKey || aEvent.modifiers.altKey) {
+ is(keypress, 1,
+ currentTestName + " should cause one keypress event because it should be available for shortcut key");
+ } else {
+ is(keypress, 0,
+ currentTestName + " should cause no keypress event because simple key press only with Shift/AltGraph " +
+ "or without any modifiers shouldn't match with a shortcut key");
+ }
+ } else {
+ is(keypress, aExpectText.length,
+ currentTestName + " should cause " + aExpectText.length + " keypress events");
+ is(textbox.value, aExpectText,
+ currentTestName + " does not input correct text.");
+ }
+
+ continueTest();
+ });
+ }
+
+ if (IS_MAC) {
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_G,
+ modifiers:{shiftKey:1}, chars:"\u0644\u0623", unmodifiedChars:"\u0644\u0623"},
+ "\u0644\u0623");
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_T,
+ modifiers:{shiftKey:1}, chars:"\u0644\u0625", unmodifiedChars:"\u0644\u0625"},
+ "\u0644\u0625");
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_B,
+ modifiers:{shiftKey:1}, chars:"\u0644\u0622", unmodifiedChars:"\u0644\u0622"},
+ "\u0644\u0622");
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_B,
+ modifiers:{}, chars:"\u0644\u0627", unmodifiedChars:"\u0644\u0627"},
+ "\u0644\u0627");
+ } else if (IS_WIN) {
+ // Basic sanity checks
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"a"},
+ "a");
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"A"},
+ "A");
+ // When Ctrl+Alt are pressed, any text should not be inputted.
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:""},
+ "");
+ // AltGr should input only when it's mapped to a character
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_0,
+ modifiers:{altGrKey:1}, chars:"}"},
+ "}");
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_A,
+ modifiers:{altGrKey:1}, chars:""},
+ "");
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:""},
+ "");
+
+ // Lithuanian AltGr should be consumed at 9/0 keys pressed
+ yield testKey({layout:KEYBOARD_LAYOUT_LITHUANIAN, keyCode:WIN_VK_8,
+ modifiers:{}, chars:"\u016B"},
+ "\u016B");
+ yield testKey({layout:KEYBOARD_LAYOUT_LITHUANIAN, keyCode:WIN_VK_9,
+ modifiers:{}, chars:"9"},
+ "9");
+ yield testKey({layout:KEYBOARD_LAYOUT_LITHUANIAN, keyCode:WIN_VK_0,
+ modifiers:{}, chars:"0"},
+ "0");
+ yield testKey({layout:KEYBOARD_LAYOUT_LITHUANIAN, keyCode:WIN_VK_8,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"8"},
+ "8");
+ yield testKey({layout:KEYBOARD_LAYOUT_LITHUANIAN, keyCode:WIN_VK_9,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"9"},
+ "9");
+ yield testKey({layout:KEYBOARD_LAYOUT_LITHUANIAN, keyCode:WIN_VK_0,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"0"},
+ "0");
+ }
+
+ // XXX We need to move focus for canceling to search the autocomplete
+ // result. If we don't do here, Fx will crash at end of this tests.
+ document.getElementById("button").focus();
+}
+
+function* runAltRightKeyOnWindows()
+{
+ if (!IS_WIN) {
+ return;
+ }
+
+ var button = document.getElementById("button");
+ button.focus();
+
+ const kKeyboardLayouts = [
+ { layout: KEYBOARD_LAYOUT_ARABIC },
+ { layout: KEYBOARD_LAYOUT_BRAZILIAN_ABNT },
+ { layout: KEYBOARD_LAYOUT_EN_US },
+ { layout: KEYBOARD_LAYOUT_FRENCH },
+ { layout: KEYBOARD_LAYOUT_GREEK },
+ { layout: KEYBOARD_LAYOUT_GERMAN },
+ { layout: KEYBOARD_LAYOUT_HEBREW },
+ { layout: KEYBOARD_LAYOUT_JAPANESE },
+ { layout: KEYBOARD_LAYOUT_KHMER },
+ { layout: KEYBOARD_LAYOUT_LITHUANIAN },
+ { layout: KEYBOARD_LAYOUT_NORWEGIAN },
+ { layout: KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC,
+ canTestIt () { return OS_VERSION >= WIN8; } },
+ { layout: KEYBOARD_LAYOUT_SPANISH },
+ { layout: KEYBOARD_LAYOUT_SWEDISH },
+ { layout: KEYBOARD_LAYOUT_THAI },
+ ];
+ var events = [];
+ function pushEvent(aEvent) {
+ events.push(aEvent);
+ if (aEvent.key === "Alt") {
+ // Prevent working the menubar.
+ aEvent.preventDefault();
+ }
+ }
+ button.addEventListener("keydown", pushEvent);
+ button.addEventListener("keyup", pushEvent);
+
+ function testKey(aKeyboardLayout) {
+ return synthesizeKey({layout: aKeyboardLayout.layout, keyCode: WIN_VK_RMENU,
+ modifiers: {}, chars: ""}, "button", function() {
+ const kDescription =
+ "runAltRightKeyOnWindows(" + aKeyboardLayout.layout.currentTestName + "): ";
+ if (aKeyboardLayout.layout.hasAltGrOnWin) {
+ is(events.length, 4,
+ kDescription + "AltRight should fire 2 pairs of keydown and keyup events");
+ is(events[0].type, "keydown",
+ kDescription + "First event should be keydown of ControlLeft");
+ is(events[0].key, "Control",
+ kDescription + "First event should be keydown of ControlLeft whose key should be Control");
+ is(events[0].code, "ControlLeft",
+ kDescription + "First event should be keydown of ControlLeft");
+ is(events[0].location, KeyboardEvent.DOM_KEY_LOCATION_LEFT,
+ kDescription + "First event should be keydown of ControlLeft whose location should be DOM_KEY_LOCATION_LEFT");
+ is(events[0].keyCode, KeyboardEvent.DOM_VK_CONTROL,
+ kDescription + "First event should be keydown of ControlLeft whose keyCode should be DOM_VK_CONTROL");
+ is(events[0].ctrlKey, true,
+ kDescription + "First event should be keydown of ControlLeft whose ctrlKey should be true");
+ is(events[0].altKey, false,
+ kDescription + "First event should be keydown of ControlLeft whose altKey should be false");
+ is(events[0].getModifierState("AltGraph"), false,
+ kDescription + "First event should be keydown of ControlLeft whose getModifierState(\"AltGraph\") should be false");
+ is(events[1].type, "keydown",
+ kDescription + "Second event should be keydown of AltRight");
+ is(events[1].key, "AltGraph",
+ kDescription + "Second event should be keydown of AltRight whose key should be AltGraph");
+ is(events[1].code, "AltRight",
+ kDescription + "Second event should be keydown of AltRight");
+ is(events[1].location, KeyboardEvent.DOM_KEY_LOCATION_RIGHT,
+ kDescription + "Second event should be keydown of AltRight whose location should be DOM_KEY_LOCATION_RIGHT");
+ is(events[1].keyCode, KeyboardEvent.DOM_VK_ALT,
+ kDescription + "Second event should be keydown of AltRight whose keyCode should be DOM_VK_ALT");
+ is(events[1].ctrlKey, false,
+ kDescription + "Second event should be keydown of AltRight whose ctrlKey should be false");
+ is(events[1].altKey, false,
+ kDescription + "Second event should be keydown of AltRight whose altKey should be false");
+ is(events[1].getModifierState("AltGraph"), true,
+ kDescription + "Second event should be keydown of AltRight whose getModifierState(\"AltGraph\") should be true");
+ is(events[2].type, "keyup",
+ kDescription + "Third event should be keyup of ControlLeft");
+ is(events[2].key, "Control",
+ kDescription + "Third event should be keyup of ControlLeft whose key should be Control");
+ is(events[2].code, "ControlLeft",
+ kDescription + "Third event should be keyup of ControlLeft");
+ is(events[2].location, KeyboardEvent.DOM_KEY_LOCATION_LEFT,
+ kDescription + "Third event should be keyup of ControlLeft whose location should be DOM_KEY_LOCATION_LEFT");
+ is(events[2].keyCode, KeyboardEvent.DOM_VK_CONTROL,
+ kDescription + "Third event should be keyup of ControlLeft whose keyCode should be DOM_VK_CONTROL");
+ is(events[2].ctrlKey, false,
+ kDescription + "Third event should be keyup of ControlLeft whose ctrlKey should be false");
+ is(events[2].altKey, false,
+ kDescription + "Third event should be keyup of ControlLeft whose altKey should be false");
+ is(events[2].getModifierState("AltGraph"), true,
+ kDescription + "Third event should be keyup of ControlLeft whose getModifierState(\"AltGraph\") should be true");
+ is(events[3].type, "keyup",
+ kDescription + "Forth event should be keyup of AltRight");
+ is(events[3].key, "AltGraph",
+ kDescription + "Forth event should be keyup of AltRight whose key should be AltGraph");
+ is(events[3].code, "AltRight",
+ kDescription + "Forth event should be keyup of AltRight");
+ is(events[3].location, KeyboardEvent.DOM_KEY_LOCATION_RIGHT,
+ kDescription + "Forth event should be keyup of AltRight whose location should be DOM_KEY_LOCATION_RIGHT");
+ is(events[3].keyCode, KeyboardEvent.DOM_VK_ALT,
+ kDescription + "Forth event should be keyup of AltRight whose keyCode should be DOM_VK_ALT");
+ is(events[3].ctrlKey, false,
+ kDescription + "Third event should be keyup of AltRight whose ctrlKey should be false");
+ is(events[3].altKey, false,
+ kDescription + "Third event should be keyup of AltRight whose altKey should be false");
+ is(events[3].getModifierState("AltGraph"), false,
+ kDescription + "Third event should be keyup of AltRight whose getModifierState(\"AltGraph\") should be false");
+ } else {
+ is(events.length, 2,
+ kDescription + "AltRight should fire a pair of keydown and keyup events");
+ is(events[0].type, "keydown",
+ kDescription + "First event should be keydown of AltRight");
+ is(events[0].key, "Alt",
+ kDescription + "First event should be keydown of AltRight whose key should be Alt");
+ is(events[0].code, "AltRight",
+ kDescription + "First event should be keydown of AltRight");
+ is(events[0].location, KeyboardEvent.DOM_KEY_LOCATION_RIGHT,
+ kDescription + "First event should be keydown of AltRight whose location should be DOM_KEY_LOCATION_RIGHT");
+ is(events[0].keyCode, KeyboardEvent.DOM_VK_ALT,
+ kDescription + "First event should be keydown of AltRight whose keyCode should be DOM_VK_ALT");
+ is(events[0].ctrlKey, false,
+ kDescription + "First event should be keydown of AltRight whose ctrlKey should be false");
+ is(events[0].altKey, true,
+ kDescription + "First event should be keydown of AltRight whose altKey should be true");
+ is(events[0].getModifierState("AltGraph"), false,
+ kDescription + "First event should be keydown of AltRight whose getModifierState(\"AltGraph\") should be false");
+ is(events[1].type, "keyup",
+ kDescription + "Second event should be keyup of AltRight");
+ is(events[1].key, "Alt",
+ kDescription + "Second event should be keyup of AltRight whose key should be Alt");
+ is(events[1].code, "AltRight",
+ kDescription + "Second event should be keyup of AltRight");
+ is(events[1].location, KeyboardEvent.DOM_KEY_LOCATION_RIGHT,
+ kDescription + "Second event should be keyup of AltRight whose location should be DOM_KEY_LOCATION_RIGHT");
+ is(events[1].keyCode, KeyboardEvent.DOM_VK_ALT,
+ kDescription + "Second event should be keyup of AltRight whose keyCode should be DOM_VK_ALT");
+ is(events[1].ctrlKey, false,
+ kDescription + "Second event should be keyup of AltRight whose ctrlKey should be false");
+ is(events[1].altKey, false,
+ kDescription + "Second event should be keyup of AltRight whose altKey should be false");
+ is(events[1].getModifierState("AltGraph"), false,
+ kDescription + "Second event should be keyup of AltRight whose getModifierState(\"AltGraph\") should be false");
+ }
+
+ continueTest();
+ });
+ }
+
+ for (const kKeyboardLayout of kKeyboardLayouts) {
+ if (typeof kKeyboardLayout.canTestIt === "function" &&
+ !kKeyboardLayout.canTestIt()) {
+ continue;
+ }
+ events = [];
+ yield testKey(kKeyboardLayout);
+ }
+
+ button.addEventListener("keydown", pushEvent);
+ button.addEventListener("keyup", pushEvent);
+}
+
+function* runAllTests() {
+ yield* runKeyEventTests();
+ yield* runAccessKeyTests();
+ yield* runXULKeyTests();
+ yield* runReservedKeyTests();
+ yield* runTextInputTests();
+ yield* runAltRightKeyOnWindows();
+}
+
+var gTestContinuation = null;
+
+function continueTest()
+{
+ if (!gTestContinuation) {
+ gTestContinuation = runAllTests();
+ }
+ var ret = gTestContinuation.next();
+ if (ret.done) {
+ SimpleTest.finish();
+ } else {
+ is(ret.value, true, "Key synthesized successfully");
+ }
+}
+
+function runTest()
+{
+ if (!IS_MAC && !IS_WIN) {
+ todo(false, "This test is supported on MacOSX and Windows only. (Bug 431503)");
+ return;
+ }
+
+ if (IS_WIN && OS_VERSION >= WIN8) {
+ // Switching keyboard layout to Russian - Mnemonic causes 2 assertions in
+ // KeyboardLayout::LoadLayout().
+ const kAssertionCountDueToRussainMnemonic = 2 * 2;
+ SimpleTest.expectAssertions(kAssertionCountDueToRussainMnemonic,
+ kAssertionCountDueToRussainMnemonic);
+ }
+ SimpleTest.waitForExplicitFinish();
+
+ clearInfobars();
+
+ continueTest();
+}
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_keypress_event_with_alt_on_mac.html b/widget/tests/test_keypress_event_with_alt_on_mac.html
new file mode 100644
index 0000000000..b5bdfcc4fd
--- /dev/null
+++ b/widget/tests/test_keypress_event_with_alt_on_mac.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Testing if keypress event is fired when alt key is pressed</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/NativeKeyCodes.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="display">
+ <input id="input">
+ <input id="password" type="password">
+ <input id="readonly-input" readonly>
+ <textarea id="textarea"></textarea>
+ <textarea id="readonly-textarea" readonly></textarea>
+ <button id="button">button</button>
+</div>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+async function testNativeKey(aKeyboardLayout, aNativeKeyCode, aModifiers,
+ aChars, aUnmodifiedChars) {
+ // XXX Need to listen keyup event here because synthesizeNativeKey() does not
+ // guarantee that its callback will be called after "keypress" and "keyup".
+ let waitForKeyUp = new Promise(resolve => {
+ document.addEventListener("keyup", resolve, {once: true});
+ });
+ let keypress;
+ document.addEventListener("keypress", (aKeyPressEvent) => {
+ keypress = aKeyPressEvent;
+ }, {once: true});
+ synthesizeNativeKey(aKeyboardLayout, aNativeKeyCode, aModifiers, aChars, aUnmodifiedChars);
+ await waitForKeyUp;
+ return keypress;
+}
+
+async function runTests() {
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["dom.keyboardevent.keypress.dispatch_non_printable_keys_only_system_group_in_content", true],
+ ]});
+ const kTests =
+ [ { target: "input", isEditable: true },
+ { target: "password", isEditable: true },
+ { target: "readonly-input", isEditable: false },
+ { target: "textarea", isEditable: true },
+ { target: "readonly-textarea", isEditable: false },
+ { target: "button", isEditable: false } ];
+ for (const kTest of kTests) {
+ let element = document.getElementById(kTest.target);
+ element.focus();
+
+ const kDescription = kTest.target + ": ";
+
+ let keypress = await testNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {}, "a", "a");
+ ok(keypress,
+ kDescription + "'a' key press should cause firing keypress event");
+
+ keypress = await testNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {shiftKey: true}, "A", "A");
+ ok(keypress,
+ kDescription + "'a' key press with shift key should cause firing keypress event");
+ ok(keypress.shiftKey,
+ kDescription + "shiftKey of 'a' key press with shift key should be true");
+
+ // When a key inputs a character with option key, we need to unset altKey for our editor.
+ // Otherwise, altKey should be true for compatibility with the other browsers.
+ keypress = await testNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {altKey: true}, "\u00E5", "a");
+ ok(keypress,
+ kDescription + "'a' key press with option key should cause firing keypress event");
+ is(keypress.altKey, !kTest.isEditable,
+ kDescription + "altKey of 'a' key press with option key should be " + !kTest.isEditable);
+
+ keypress = await testNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {altKey: true, shiftKey: true}, "\u00C5", "A");
+ ok(keypress,
+ kDescription + "'a' key press with option key and shift key should cause firing keypress event");
+ is(keypress.altKey, !kTest.isEditable,
+ kDescription + "altKey of 'a' key press with option key and shift key should be " + !kTest.isEditable);
+ ok(keypress.shiftKey,
+ kDescription + "shiftKey of 'a' key press with option key and shift key should be true");
+
+ keypress = await testNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {ctrlKey: true}, "\u0001", "a");
+ ok(!keypress,
+ kDescription + "'a' key press with control key should not cause firing keypress event");
+
+ keypress = await testNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {altKey: true, ctrlKey: true}, "\u0001", "a");
+ ok(!keypress,
+ kDescription + "'a' key press with option key and control key should not cause firing keypress event");
+
+ // XXX Cannot test with command key for now since keyup event won't be fired due to macOS's limitation.
+
+ // Some keys of Arabic - PC keyboard layout do not input any character with option key.
+ // In such case, we shouldn't dispatch keypress event.
+ keypress = await testNativeKey(KEYBOARD_LAYOUT_ARABIC_PC, MAC_VK_ANSI_7, {altKey: true}, "", "\u0667");
+ ok(!keypress,
+ kDescription + "'7' key press with option key should not cause firing keypress event");
+ }
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/widget/tests/test_mouse_event_with_control_on_mac.html b/widget/tests/test_mouse_event_with_control_on_mac.html
new file mode 100644
index 0000000000..85e206ea95
--- /dev/null
+++ b/widget/tests/test_mouse_event_with_control_on_mac.html
@@ -0,0 +1,114 @@
+<html>
+<head>
+ <title>Test control+click on Mac</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/SpecialPowers.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style>
+ #target {
+ width: 100px;
+ height: 100px;
+ background-color: lightgreen;
+ };
+ </style>
+</head>
+<body>
+<div id="target"></div>
+<script type="application/javascript">
+
+function synthesizeNativeMouseClickWithControl(aTarget, aOffsetX, aOffsetY) {
+ const NSEventTypeLeftMouseDown = 1;
+ const NSEventTypeLeftMouseUp = 2;
+
+ const NSEventModifierFlagControl = 1 << 18;
+
+ let utils = SpecialPowers.getDOMWindowUtils(window);
+
+ let rect = aTarget.getBoundingClientRect();
+ let x = aOffsetX + window.mozInnerScreenX + rect.left;
+ let y = aOffsetY + window.mozInnerScreenY + rect.top;
+ let scale = utils.screenPixelsPerCSSPixel;
+
+ utils.sendNativeMouseEvent(x * scale, y * scale, NSEventTypeLeftMouseDown, NSEventModifierFlagControl, aTarget);
+ utils.sendNativeMouseEvent(x * scale, y * scale, NSEventTypeLeftMouseUp, NSEventModifierFlagControl, aTarget);
+}
+
+function waitAndCheckMouseEvents(aTarget, aExpectedEvents) {
+ return new Promise((aResolve, aReject) => {
+ let timer;
+ let cleanup = function() {
+ if (timer) {
+ clearTimeout(timer);
+ timer = null;
+ }
+ aTarget.removeEventListener("mousedown", listener);
+ aTarget.removeEventListener("mouseup", listener);
+ aTarget.removeEventListener("contextmenu", listener);
+ aTarget.removeEventListener("click", listener);
+ aTarget.removeEventListener("auxclick", listener);
+ };
+
+ let listener = function(aEvent) {
+ aEvent.preventDefault();
+ let expectedEvent = aExpectedEvents.shift();
+ if (!expectedEvent) {
+ cleanup();
+ ok(false, `receive unexpected ${aEvent.type} event`);
+ aReject(new Error(`receive unexpected ${aEvent.type} event`));
+ return;
+ }
+
+ isDeeply([aEvent.type, aEvent.button, aEvent.ctrlKey], expectedEvent,
+ `check received ${aEvent.type} event`);
+ if (!aExpectedEvents.length) {
+ // Wait a bit to see if there is any unexpected event.
+ timer = setTimeout(function() {
+ cleanup();
+ aResolve();
+ }, 0);
+ }
+ };
+
+ aTarget.addEventListener("mousedown", listener);
+ aTarget.addEventListener("mouseup", listener);
+ aTarget.addEventListener("contextmenu", listener);
+ aTarget.addEventListener("click", listener);
+ aTarget.addEventListener("auxclick", listener);
+ });
+}
+
+add_task(async function Init() {
+ await SimpleTest.promiseFocus();
+});
+
+add_task(async function TestMouseClickWithControl() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.event.treat_ctrl_click_as_right_click.disabled", true]],
+ });
+
+ let target = document.getElementById("target");
+ let promise = waitAndCheckMouseEvents(target, [["mousedown", 0, true],
+ ["contextmenu", 0, true],
+ ["mouseup", 0, true],
+ ["click", 0, true]]);
+ synthesizeNativeMouseClickWithControl(target, 10, 10);
+ await promise;
+});
+
+add_task(async function TestOldBehavior() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.event.treat_ctrl_click_as_right_click.disabled", false]],
+ });
+
+ let target = document.getElementById("target");
+ let promise = waitAndCheckMouseEvents(target, [["mousedown", 2, true],
+ ["contextmenu", 2, true],
+ ["mouseup", 2, true],
+ ["auxclick", 2, true]]);
+ synthesizeNativeMouseClickWithControl(target, 10, 10);
+ await promise;
+});
+</script>
+</body>
+</html>
diff --git a/widget/tests/test_mouse_scroll.xhtml b/widget/tests/test_mouse_scroll.xhtml
new file mode 100644
index 0000000000..82cb6a3ea3
--- /dev/null
+++ b/widget/tests/test_mouse_scroll.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Testing composition, text and query content events"
+ onload="setTimeout(onLoad, 0);"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+function onLoad()
+{
+ runTest();
+}
+
+function runTest()
+{
+ window.openDialog("window_mouse_scroll_win.html", "_blank",
+ "chrome,width=600,height=600,noopener", window);
+}
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_native_key_bindings_mac.html b/widget/tests/test_native_key_bindings_mac.html
new file mode 100644
index 0000000000..8767a5a77d
--- /dev/null
+++ b/widget/tests/test_native_key_bindings_mac.html
@@ -0,0 +1,336 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'/>
+ <title>Native Key Bindings for Cocoa Test</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js"></script>
+ </head>
+ <body>
+ <div id="editable" contenteditable>
+ <p>Stretching attack nullam stuck in a tree zzz, suspendisse cras nec
+ suspendisse lick suscipit. Nunc egestas amet litter box, nullam climb the
+ curtains biting I don't like that food tristique biting sleep on your
+ keyboard non. Lay down in your way cras nec tempus chase the red dot cras
+ nec, pharetra pharetra eat the grass leap run orci turpis attack.
+ Consectetur sleep in the sink eat I don't like that food, knock over the
+ lamp catnip in viverra tail flick zzz meow etiam enim. Ac ac hiss shed
+ everywhere kittens rhoncus, attack your ankles zzz iaculis kittens. Nullam
+ pellentesque rip the couch iaculis rhoncus nibh, give me fish orci turpis
+ purr sleep on your face quis nunc bibendum.</p>
+
+ <p>Neque jump on the table bat iaculis, adipiscing sleep on your keyboard
+ jump vel justo shed everywhere suspendisse lick. Zzz enim faucibus
+ hairball faucibus, pharetra sunbathe biting bat leap rip the couch attack.
+ Tortor nibh in viverra quis hairball nam, vulputate adipiscing sleep on
+ your keyboard purr knock over the lamp orci turpis. Vestibulum I don't
+ like that food et chase the red dot, adipiscing neque bibendum rutrum
+ accumsan quis rhoncus claw. Leap accumsan vehicula enim biting sleep on
+ your face, pharetra nam accumsan egestas kittens sunbathe. Pharetra chase
+ the red dot sniff non eat the grass, vulputate fluffy fur aliquam puking
+ judging you.</p>
+
+ <p>Claw purr sollicitudin sollicitudin lay down in your way consectetur,
+ pellentesque vehicula zzz orci turpis consectetur. I don't like that food
+ rhoncus pellentesque sniff attack, rhoncus tortor attack your ankles
+ iaculis scratched hiss vel. Tortor zzz tortor nullam rip the couch rutrum,
+ bat enim ut leap hairball iaculis. Bibendum sunbathe elit suspendisse
+ nibh, puking adipiscing sleep on your face sleep on your face zzz catnip.
+ Judging you rutrum bat sunbathe sleep on your face, jump on the table leap
+ tincidunt a faucibus sleep in the sink. Stuck in a tree tristique zzz hiss
+ in viverra nullam, quis tortor pharetra attack.</p>
+ </div>
+
+ <textarea id="textarea" cols="80">
+ Stretching attack nullam stuck in a tree zzz, suspendisse cras nec
+ suspendisse lick suscipit. Nunc egestas amet litter box, nullam climb the
+ curtains biting I don't like that food tristique biting sleep on your
+ keyboard non. Lay down in your way cras nec tempus chase the red dot cras
+ nec, pharetra pharetra eat the grass leap run orci turpis attack.
+ Consectetur sleep in the sink eat I don't like that food, knock over the
+ lamp catnip in viverra tail flick zzz meow etiam enim. Ac ac hiss shed
+ everywhere kittens rhoncus, attack your ankles zzz iaculis kittens. Nullam
+ pellentesque rip the couch iaculis rhoncus nibh, give me fish orci turpis
+ purr sleep on your face quis nunc bibendum.
+
+ Neque jump on the table bat iaculis, adipiscing sleep on your keyboard
+ jump vel justo shed everywhere suspendisse lick. Zzz enim faucibus
+ hairball faucibus, pharetra sunbathe biting bat leap rip the couch attack.
+ Tortor nibh in viverra quis hairball nam, vulputate adipiscing sleep on
+ your keyboard purr knock over the lamp orci turpis. Vestibulum I don't
+ like that food et chase the red dot, adipiscing neque bibendum rutrum
+ accumsan quis rhoncus claw. Leap accumsan vehicula enim biting sleep on
+ your face, pharetra nam accumsan egestas kittens sunbathe. Pharetra chase
+ the red dot sniff non eat the grass, vulputate fluffy fur aliquam puking
+ judging you.
+
+ Claw purr sollicitudin sollicitudin lay down in your way consectetur,
+ pellentesque vehicula zzz orci turpis consectetur. I don't like that food
+ rhoncus pellentesque sniff attack, rhoncus tortor attack your ankles
+ iaculis scratched hiss vel. Tortor zzz tortor nullam rip the couch rutrum,
+ bat enim ut leap hairball iaculis. Bibendum sunbathe elit suspendisse
+ nibh, puking adipiscing sleep on your face sleep on your face zzz catnip.
+ Judging you rutrum bat sunbathe sleep on your face, jump on the table leap
+ tincidunt a faucibus sleep in the sink. Stuck in a tree tristique zzz hiss
+ in viverra nullam, quis tortor pharetra attack.
+ </textarea>
+
+ <input id="input" type="text"
+ value="Stretching attack nullam stuck in a tree zzz, suspendisse cras nec
+ suspendisse lick suscipit. Nunc egestas amet litter box, nullam climb the
+ curtains biting I don't like that food tristique biting sleep on your
+ keyboard non. Lay down in your way cras nec tempus chase the red dot cras
+ nec, pharetra pharetra eat the grass leap run orci turpis attack.
+ Consectetur sleep in the sink eat I don't like that food, knock over the
+ lamp catnip in viverra tail flick zzz meow etiam enim. Ac ac hiss shed
+ everywhere kittens rhoncus, attack your ankles zzz iaculis kittens.
+ Nullam pellentesque rip the couch iaculis rhoncus nibh, give me fish orci
+ turpis purr sleep on your face quis nunc bibendum.">
+
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ let synthesizedKeys = [];
+ let expectations = [];
+
+ // Move to beginning of line
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_LeftArrow,
+ {ctrlKey: true}, "\uf702", "\uf702"]);
+ expectations.push({
+ editable: [0, 0],
+ textarea: [0, 0],
+ input: [0, 0],
+ });
+
+ // Move to end of line
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_RightArrow,
+ {ctrlKey: true}, "\uf703", "\uf703"]);
+ expectations.push({
+ editable: [73, 73],
+ textarea: [72, 72],
+ input: [732, 732],
+ });
+
+ // Move down
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_N,
+ {ctrlKey: true}, "\u000e", "n"]);
+ expectations.push({
+ editable: [140, 140],
+ textarea: [145, 145],
+ input: [732, 732],
+ });
+
+ // Move to beginning of line
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_LeftArrow,
+ {ctrlKey: true}, "\uf702", "\uf702"]);
+ expectations.push({
+ editable: [73, 73],
+ textarea: [73, 73],
+ input: [0, 0],
+ });
+
+ // Move word right and modify selection
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_RightArrow,
+ {altKey: true, shiftKey: true}, "\uf703", "\uf703"]);
+ expectations.push({
+ editable: [73, 84],
+ textarea: [73, 90],
+ input: [0, 10],
+ });
+
+ // Move word right
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_RightArrow,
+ {altKey: true}, "\uf703", "\uf703"]);
+ expectations.push({
+ editable: [84, 84],
+ textarea: [90, 90],
+ input: [10, 10],
+ });
+
+ // Move word right
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_RightArrow,
+ {altKey: true}, "\uf703", "\uf703"]);
+ expectations.push({
+ editable: [89, 89],
+ textarea: [95, 95],
+ input: [17, 17],
+ });
+
+ // Move down and modify selection
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_DownArrow,
+ {shiftKey: true}, "\uf701", "\uf701"]);
+ expectations.push({
+ editable: [89, 171],
+ textarea: [95, 175],
+ input: [17, 732],
+ });
+
+ // Move backward and modify selection
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_B,
+ {ctrlKey: true, shiftKey: true}, "\u0002", "B"]);
+ expectations.push({
+ editable: [89, 170],
+ textarea: [95, 174],
+ input: [17, 731],
+ });
+
+ // Delete forward
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_D,
+ {ctrlKey: true}, "\u0004", "d"]);
+ expectations.push({
+ editable: [89, 89],
+ textarea: [95, 95],
+ input: [17, 17],
+ });
+
+ // Delete backward
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_H,
+ {ctrlKey: true}, "\u0008", "h"]);
+ expectations.push({
+ editable: [88, 88],
+ textarea: [94, 94],
+ input: [16, 16],
+ });
+
+ // Move backward
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_B,
+ {ctrlKey: true}, "\u0002", "b"]);
+ expectations.push({
+ editable: [87, 87],
+ textarea: [93, 93],
+ input: [15, 15],
+ });
+
+ // Move to beginning of paragraph (line for now)
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A,
+ {ctrlKey: true}, "\u0001", "a"]);
+ expectations.push({
+ editable: [73, 73],
+ textarea: [73, 73],
+ input: [0, 0],
+ });
+
+ // Move forward
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_F,
+ {ctrlKey: true}, "\u0006", "f"]);
+ expectations.push({
+ editable: [74, 74],
+ textarea: [74, 74],
+ input: [1, 1],
+ });
+
+ // Move word right
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_RightArrow,
+ {altKey: true}, "\uf703", "\uf703"]);
+ expectations.push({
+ editable: [84, 84],
+ textarea: [90, 90],
+ input: [10, 10],
+ });
+
+ // Move word right
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_RightArrow,
+ {altKey: true}, "\uf703", "\uf703"]);
+ expectations.push({
+ editable: [88, 88],
+ textarea: [94, 94],
+ input: [17, 17],
+ });
+
+ // Delete to end of paragraph (line for now)
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_K,
+ {ctrlKey: true}, "\u000b", "k"]);
+ expectations.push({
+ editable: [88, 88],
+ textarea: [94, 94],
+ input: [17, 17],
+ });
+
+ // Move backward and modify selection
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_B,
+ {ctrlKey: true, shiftKey: true}, "\u0002", "B"]);
+ expectations.push({
+ editable: [88, 87],
+ textarea: [93, 94],
+ input: [16, 17],
+ });
+
+ // Move to end of paragraph (line for now)
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_E,
+ {ctrlKey: true}, "\u0005", "e"]);
+ expectations.push({
+ editable: [139, 139],
+ textarea: [94, 94],
+ input: [17, 17],
+ });
+
+ // Move up
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_P,
+ {ctrlKey: true}, "\u0010", "p"]);
+ expectations.push({
+ editable: [73, 73],
+ textarea: [21, 21],
+ input: [0, 0],
+ });
+
+ function checkWindowSelection(aElement, aSelection) {
+ let selection = window.getSelection();
+
+ is(selection.anchorOffset, aSelection[aElement.id][0],
+ aElement.id + ": Incorrect anchor offset");
+ is(selection.focusOffset, aSelection[aElement.id][1],
+ aElement.id + ": Incorrect focus offset");
+ }
+
+ function checkElementSelection(aElement, aSelection) {
+ is(aElement.selectionStart, aSelection[aElement.id][0],
+ aElement.id + ": Incorrect selection start");
+ is(aElement.selectionEnd, aSelection[aElement.id][1],
+ aElement.id + ": Incorrect selection end");
+ }
+
+ function* testRun(aElement, aSelectionCheck, aCallback) {
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+
+ aElement.focus();
+
+ for (let i = 0; i < synthesizedKeys.length; i++) {
+ synthesizedKeys[i].push(function() {
+ aSelectionCheck(aElement, expectations[i]);
+ continueTest();
+ });
+ var synthOk = synthesizeNativeKey.apply(null, synthesizedKeys[i]);
+ synthesizedKeys[i].pop();
+ yield synthOk;
+ }
+ }
+
+ function* doTest() {
+ yield* testRun(document.getElementById("editable"), checkWindowSelection);
+ yield* testRun(document.getElementById("textarea"), checkElementSelection);
+ yield* testRun(document.getElementById("input"), checkElementSelection);
+ }
+
+ let gTestContinuation = null;
+
+ function continueTest() {
+ if (!gTestContinuation) {
+ gTestContinuation = doTest();
+ }
+ var ret = gTestContinuation.next();
+ if (ret.done) {
+ SimpleTest.finish();
+ } else {
+ is(ret.value, true, "Successfully synthesized key");
+ }
+ }
+
+ SimpleTest.waitForFocus(continueTest);
+ </script>
+ </body>
+</html>
diff --git a/widget/tests/test_native_menus.xhtml b/widget/tests/test_native_menus.xhtml
new file mode 100644
index 0000000000..d62c57f21c
--- /dev/null
+++ b/widget/tests/test_native_menus.xhtml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Native menu system tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("native_menus_window.xhtml", "NativeMenuWindow",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_native_mouse_mac.xhtml b/widget/tests/test_native_mouse_mac.xhtml
new file mode 100644
index 0000000000..07a93ae674
--- /dev/null
+++ b/widget/tests/test_native_mouse_mac.xhtml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Native mouse event tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("native_mouse_mac_window.xhtml", "NativeMouseWindow",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_panel_mouse_coords.xhtml b/widget/tests/test_panel_mouse_coords.xhtml
new file mode 100644
index 0000000000..84957a1361
--- /dev/null
+++ b/widget/tests/test_panel_mouse_coords.xhtml
@@ -0,0 +1,78 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=835044
+-->
+<window title="Mozilla Bug 835044"
+ onload="startTest()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+<panel id="thepanel" level="parent"
+ onpopupshown="sendMouseEvent();"
+ onmousemove="checkCoords(event);"
+ width="80" height="80">
+</panel>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=835044"
+ id="anchor"
+ target="_blank">Mozilla Bug 835044</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+let utils = window.windowUtils;
+let panel = document.getElementById('thepanel');
+let nativeMouseMove;
+let rect;
+
+function startTest() {
+ let widgetToolkit = SpecialPowers.Services.appinfo.widgetToolkit;
+
+ if (widgetToolkit == "cocoa") {
+ nativeMouseMove = 5; // NSEventTypeMouseMoved
+ } else if (widgetToolkit == "windows") {
+ nativeMouseMove = 1; // MOUSEEVENTF_MOVE
+ } else if (/^gtk/.test(widgetToolkit)) {
+ nativeMouseMove = 3; // GDK_MOTION_NOTIFY
+ } else {
+ todo_is("widgetToolkit", widgetToolkit, "Platform not supported");
+ done();
+ }
+
+ // This first event is to ensure that the next event will have different
+ // coordinates to the previous mouse position, and so actually generates
+ // mouse events. The mouse is not moved off the window, as that might
+ // move focus to another application.
+ utils.sendNativeMouseEvent(window.mozInnerScreenX, window.mozInnerScreenY,
+ nativeMouseMove, 0, window.documentElement);
+
+ panel.openPopup(document.getElementById("anchor"), "after_start");
+}
+
+function sendMouseEvent() {
+ rect = panel.getBoundingClientRect();
+ let x = window.mozInnerScreenX + rect.left + 1;
+ let y = window.mozInnerScreenY + rect.top + 2;
+ utils.sendNativeMouseEvent(x, y, nativeMouseMove, 0,
+ window.documentElement);
+}
+
+function checkCoords(event) {
+ is(event.clientX, rect.left + 1, "Motion x coordinate");
+ is(event.clientY, rect.top + 2, "Motion y coordinate");
+ done();
+}
+
+function done() {
+ SimpleTest.finish();
+}
+ ]]>
+ </script>
+</window>
diff --git a/widget/tests/test_picker_no_crash.html b/widget/tests/test_picker_no_crash.html
new file mode 100644
index 0000000000..b23ca53def
--- /dev/null
+++ b/widget/tests/test_picker_no_crash.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for crashes when the parent window of a file picker is closed via script</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var childWindow;
+
+function testStepOne() {
+ childWindow = window.browsingContext.topChromeWindow.open("window_picker_no_crash_child.html", "childWindow", "width=300,height=150");
+ SimpleTest.waitForFocus(testStepTwo, childWindow);
+}
+
+function testStepTwo() {
+ childWindow.document.form1.uploadbox.click();
+ // This should not crash the browser
+ childWindow.close();
+ setTimeout(testStepThree, 5000);
+}
+
+function testStepThree() {
+ ok(true, "browser didn't crash");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(testStepOne);
+</script>
+</body>
+</html>
diff --git a/widget/tests/test_platform_colors.xhtml b/widget/tests/test_platform_colors.xhtml
new file mode 100644
index 0000000000..7bda123a3b
--- /dev/null
+++ b/widget/tests/test_platform_colors.xhtml
@@ -0,0 +1,106 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Mac platform colors"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=518395">Mozilla Bug 518395</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<box id="colorbox"></box>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+var colors = {
+ "activeborder": ["rgb(0, 0, 0)"],
+ "activecaption": ["rgb(204, 204, 204)"],
+ "appworkspace": ["rgb(255, 255, 255)"],
+ "background": ["rgb(99, 99, 206)"],
+ "buttonface": ["rgb(240, 240, 240)"],
+ "buttonhighlight": ["rgb(255, 255, 255)"],
+ "buttonshadow": ["rgb(220, 220, 220)"],
+ "buttontext": ["rgb(0, 0, 0)"],
+ "captiontext": ["rgb(0, 0, 0)"],
+ "graytext": ["rgb(127, 127, 127)"],
+ "highlight": ["rgb(115, 132, 153)", "rgb(127, 127, 127)", "rgb(56, 117, 215)", "rgb(255, 193, 31)", "rgb(243, 70, 72)", "rgb(255, 138, 34)", "rgb(102, 197, 71)", "rgb(140, 78, 184)"],
+ "highlighttext": ["rgb(255, 255, 255)", "rgb(255, 254, 254)", "rgb(254, 255, 254)"],
+ "inactiveborder": ["rgb(255, 255, 255)"],
+ "inactivecaption": ["rgb(255, 255, 255)"],
+ "inactivecaptiontext": ["rgb(69, 69, 69)"],
+ "infobackground": ["rgb(255, 255, 199)"],
+ "infotext": ["rgb(0, 0, 0)"],
+ "menu": ["rgb(255, 255, 255)", "rgb(254, 255, 254)", "rgb(255, 254, 254)"],
+ "menutext": ["rgb(0, 0, 0)"],
+ "scrollbar": ["rgb(170, 170, 170)"],
+ "threeddarkshadow": ["rgb(220, 220, 220)"],
+ "threedface": ["rgb(240, 240, 240)"],
+ "threedhighlight": ["rgb(255, 255, 255)"],
+ "threedlightshadow": ["rgb(218, 218, 218)"],
+ "threedshadow": ["rgb(224, 224, 224)"],
+ "window": ["rgb(255, 255, 255)"],
+ "windowframe": ["rgb(204, 204, 204)"],
+ "windowtext": ["rgb(0, 0, 0)"],
+ "-moz-activehyperlinktext": ["rgb(238, 0, 0)"],
+ "-moz-buttondefault": ["rgb(220, 220, 220)"],
+ "-moz-buttonhoverface": ["rgb(240, 240, 240)"],
+ "-moz-buttonhovertext": ["rgb(0, 0, 0)"],
+ "-moz-cellhighlight": ["rgb(212, 212, 212)", "rgb(220, 220, 220)"],
+ "-moz-cellhighlighttext": ["rgb(0, 0, 0)"],
+ "-moz-eventreerow": ["rgb(255, 255, 255)"],
+ "-moz-field": ["rgb(255, 255, 255)"],
+ "-moz-fieldtext": ["rgb(0, 0, 0)"],
+ "-moz-dialog": ["rgb(232, 232, 232)"],
+ "-moz-dialogtext": ["rgb(0, 0, 0)"],
+ "-moz-dragtargetzone": ["rgb(199, 208, 218)", "rgb(198, 198, 198)", "rgb(180, 213, 255)", "rgb(250, 236, 115)", "rgb(255, 176, 139)", "rgb(255, 209, 129)", "rgb(194, 249, 144)", "rgb(232, 184, 255)"],
+ "-moz-hyperlinktext": ["rgb(0, 0, 238)"],
+ "-moz-html-cellhighlight": ["rgb(212, 212, 212)"],
+ "-moz-html-cellhighlighttext": ["rgb(0, 0, 0)"],
+ "-moz-mac-buttonactivetext": ["rgb(0, 0, 0)", "rgb(255, 255, 255)"],
+ "-moz-mac-chrome-active": ["rgb(150, 150, 150)", "rgb(167, 167, 167)", "rgb(178, 178, 178)"],
+ "-moz-mac-chrome-inactive": ["rgb(202, 202, 202)", "rgb(216, 216, 216)", "rgb(225, 225, 225)"],
+ "-moz-mac-defaultbuttontext": ["rgb(0, 0, 0)", "rgb(255, 255, 255)"],
+ //"-moz-mac-focusring": ["rgb(83, 144, 210)", "rgb(95, 112, 130)", "rgb(63, 152, 221)", "rgb(108, 126, 141)"],
+ "-moz-mac-menuselect": ["rgb(115, 132, 153)", "rgb(127, 127, 127)", "rgb(56, 117, 215)", "rgb(255, 193, 31)", "rgb(243, 70, 72)", "rgb(255, 138, 34)", "rgb(102, 197, 71)", "rgb(140, 78, 184)"],
+ "-moz-mac-menushadow": ["rgb(163, 163, 163)"],
+ "-moz-mac-menutextdisable": ["rgb(152, 152, 152)"],
+ "-moz-mac-menutextselect": ["rgb(255, 255, 255)"],
+ "-moz-mac-disabledtoolbartext": ["rgb(127, 127, 127)"],
+ "-moz-mac-secondaryhighlight": ["rgb(212, 212, 212)"],
+ "-moz-menuhover": ["rgb(115, 132, 153)", "rgb(127, 127, 127)", "rgb(56, 117, 215)", "rgb(255, 193, 31)", "rgb(243, 70, 72)", "rgb(255, 138, 34)", "rgb(102, 197, 71)", "rgb(140, 78, 184)"],
+ "-moz-menuhovertext": ["rgb(255, 255, 255)", "rgb(255, 254, 254)", "rgb(254, 255, 254)"],
+ "-moz-menubartext": ["rgb(0, 0, 0)"],
+ //"-moz-menubarhovertext": ["rgb(255, 255, 255)"],
+ "-moz-oddtreerow": ["rgb(236, 242, 254)", "rgb(240, 240, 240)", "rgb(243, 245, 250)", "rgb(243, 246, 250)", "rgb(245, 245, 245)"],
+ "-moz-visitedhyperlinktext": ["rgb(85, 26, 139)"],
+ "currentcolor": ["rgb(0, 0, 0)"],
+ //"-moz-win-mediatext": ["rgb(255, 255, 255)"],
+ //"-moz-win-communicationstext": ["rgb(255, 255, 255)"],
+ "-moz-nativehyperlinktext": ["rgb(20, 79, 174)"],
+ "-moz-comboboxtext": ["rgb(0, 0, 0)"],
+ "-moz-combobox": ["rgb(255, 255, 255)"]
+};
+
+var colorbox = document.getElementById('colorbox');
+
+for (var c in colors) {
+ dump("testing color " + c + "\n");
+ colorbox.style.backgroundColor = c;
+ var rgb = document.defaultView.getComputedStyle(colorbox).getPropertyValue('background-color');
+ ok(colors[c].includes(rgb) || colors[c].length == 8, 'platform color ' + c + ' is wrong: ' + rgb);
+}
+
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_plugin_scroll_consistency.html b/widget/tests/test_plugin_scroll_consistency.html
new file mode 100644
index 0000000000..4e8033d4ac
--- /dev/null
+++ b/widget/tests/test_plugin_scroll_consistency.html
@@ -0,0 +1,59 @@
+<html>
+<head>
+ <title>Test for plugin child widgets not being messed up by scrolling</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="utils.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body onload="setTimeout(runTests, 0)">
+<script type="application/javascript">
+setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+</script>
+
+<p id="display">
+ <div style="overflow:hidden; height:100px;" id="scroll">
+ <embed type="application/x-test" wmode="window" width="100" height="800" id="plugin"></object>
+ <div style="height:1000px;"></div>
+ </div>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var plugin = document.getElementById("plugin");
+
+function consistencyCheck(state) {
+ var s = plugin.doInternalConsistencyCheck();
+ ok(s == "", "Consistency check: " + state + ": " + s);
+}
+
+function runTests() {
+ consistencyCheck("Initial state");
+
+ var scroll = document.getElementById("scroll");
+ scroll.scrollTop = 10;
+ consistencyCheck("Scrolled down a bit");
+
+ setTimeout(function() {
+ consistencyCheck("Before scrolling back to top");
+ scroll.scrollTop = 0;
+ consistencyCheck("Scrolled to top");
+
+ setTimeout(function() {
+ consistencyCheck("After scrolling to top");
+
+ SimpleTest.finish();
+ }, 0);
+ }, 0);
+}
+
+</script>
+</body>
+
+</html>
diff --git a/widget/tests/test_position_on_resize.xhtml b/widget/tests/test_position_on_resize.xhtml
new file mode 100644
index 0000000000..a7c5551018
--- /dev/null
+++ b/widget/tests/test_position_on_resize.xhtml
@@ -0,0 +1,90 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window title="Window Position On Resize Test"
+ onload="startTest()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+<script class="testbody" type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ let win, x, y;
+
+ function startTest() {
+ win = window.browsingContext.topChromeWindow.openDialog(
+ "about:blank",
+ null,
+ "chrome,dialog=no,outerHeight=170,outerWidth=200");
+ waitForSuccess(function() { return SpecialPowers.DOMWindowUtils.paintCount },
+ "No paint received", checkInitialSize);
+ }
+
+ function checkInitialSize() {
+ is(win.outerHeight,170, "initial outerHeight");
+ is(win.outerWidth, 200, "initial outerWidth");
+ x = win.screenX;
+ y = win.screenY;
+ shrink();
+ }
+ function shrink() {
+ win.resizeTo(180, 160);
+ waitForSuccess(function() { return win.outerHeight == 160 },
+ "outerHeight did not change to 160", checkShrink);
+ }
+ function checkShrink() {
+ is(win.outerWidth, 180, "resized outerWidth");
+ is(win.screenY, y, "resized window top should not change");
+ y = win.screenY;
+ restore();
+ }
+ function restore() {
+ win.resizeBy(20, 10);
+ waitForSuccess(function() { return win.outerHeight == 170 },
+ "outerHeight did not change to 170", checkRestore);
+ }
+ function checkRestore() {
+ is(win.outerWidth, 200, "restored outerWidth");
+ is(win.screenX, x, "restored window left should not change");
+ is(win.screenY, y, "restored window top should not change");
+ done();
+ }
+ function done() {
+ win.close();
+ SimpleTest.finish();
+ }
+
+ function waitForSuccess(testForSuccess, failureMsg, nextFunc) {
+ var waitCount = 0;
+
+ function repeatWait() {
+ ++waitCount;
+
+ if (testForSuccess()) {
+ nextFunc();
+ }
+ else if (waitCount > 50) {
+ ok(false, failureMsg);
+ nextFunc();
+ } else {
+ setTimeout(repeatWait, 100);
+ }
+ }
+
+ repeatWait();
+ }
+]]></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display">
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+
+</window>
diff --git a/widget/tests/test_scrollbar_colors.html b/widget/tests/test_scrollbar_colors.html
new file mode 100644
index 0000000000..1acd7e94da
--- /dev/null
+++ b/widget/tests/test_scrollbar_colors.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>Test for scrollbar-*-color properties</title>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+<style>
+ .outer {
+ width: 100px;
+ height: 100px;
+ background: yellow;
+ overflow: scroll;
+ }
+ .inner {
+ width: 200px;
+ height: 200px;
+ }
+</style>
+<style id="style"></style>
+<div class="outer">
+ <div class="inner">
+ </div>
+</div>
+<script>
+function countPixels(canvas) {
+ let result = new Map;
+ let ctx = canvas.getContext("2d");
+ let image = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ let data = image.data;
+ let size = image.width * image.height;
+ for (let i = 0; i < size; i++) {
+ let key = data.subarray(i * 4, i * 4 + 3).toString();
+ let value = result.get(key);
+ value = value ? value : 0;
+ result.set(key, value + 1);
+ }
+ return result;
+}
+
+// == Native theme ==
+
+const WIN_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 6889],
+ // Blue scrollbar face
+ ["0,0,255", 540],
+ // Cyan scrollbar track
+ ["0,255,255", 2487],
+];
+
+const MAC_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 7225],
+ // Blue scrollbar face
+ ["0,0,255", 416],
+ // Cyan scrollbar track
+ ["0,255,255", 1760],
+];
+
+// Values have been updated from 8100, 720, 1180 for linux1804
+const LINUX_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 7744],
+ // Blue scrollbar face
+ ["0,0,255", 1104],
+ // Cyan scrollbar track
+ ["0,255,255", 1152],
+];
+
+// == Non-native theme ==
+
+const WIN_NNT_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 7396],
+ // Blue scrollbar face
+ ["0,0,255", 1272],
+ // Cyan scrollbar track
+ ["0,255,255", 1204],
+];
+
+const MAC_NNT_REFERENCES = MAC_REFERENCES;
+
+const LINUX_NNT_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 7744],
+ // Blue scrollbar face
+ ["0,0,255", 368],
+ // Cyan scrollbar track
+ ["0,255,255", 1852],
+];
+
+let outer = document.querySelector(".outer");
+let outerRect = outer.getBoundingClientRect();
+if (outerRect.width == outer.clientWidth &&
+ outerRect.height == outer.clientHeight) {
+ ok(true, "Using overlay scrollbar, skip this test");
+} else {
+ document.querySelector("#style").textContent = `
+ .outer { scrollbar-color: blue cyan; }
+ `;
+
+ let canvas = snapshotRect(window, outerRect);
+ let stats = countPixels(canvas);
+ let isNNT = SpecialPowers.getBoolPref("widget.disable-native-theme-for-content");
+ let references;
+ if (navigator.platform.startsWith("Win")) {
+ references = isNNT ? WIN_NNT_REFERENCES : WIN_REFERENCES;
+ } else if (navigator.platform.startsWith("Mac")) {
+ references = isNNT ? MAC_NNT_REFERENCES : MAC_REFERENCES;
+ } else if (navigator.platform.startsWith("Linux")) {
+ references = isNNT ? LINUX_NNT_REFERENCES : LINUX_REFERENCES;
+ } else {
+ ok(false, "Unsupported platform");
+ }
+ for (let [color, count] of references) {
+ let value = stats.get(color);
+ is(value, count, `Pixel count of color ${color}`);
+ }
+}
+</script>
diff --git a/widget/tests/test_secure_input.html b/widget/tests/test_secure_input.html
new file mode 100644
index 0000000000..846465b4c2
--- /dev/null
+++ b/widget/tests/test_secure_input.html
@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for secure input mode</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<p>
+ <input id="input_text" type="text"><br>
+ <input id="input_password" type="password"><br>
+ <input id="input_text_readonly" type="text" readonly><br>
+ <input id="input_text_ime_mode_disabled" type="text" style="ime-mode: disabled;"><br>
+ <input id="input_change" type="text"><br>
+ <textarea id="textarea"></textarea><br>
+</p>
+<div id="contenteditable" contenteditable style="min-height: 3em;"></div>
+
+<script class="testbody" type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ function sendAKeyEvent() {
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {}, "a", "a");
+ }
+
+ function isFocused(aElement) {
+ return (SpecialPowers.focusManager.focusedElement == aElement);
+ }
+
+ function runTest() {
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on the document");
+ $("input_text").focus();
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <input type=\"text\">");
+ $("input_password").focus();
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <input type=\"password\">");
+ $("input_password").blur();
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on the document after blur() of <input type=\"password\">");
+ $("input_password").focus();
+ $("input_text_readonly").focus();
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <input type=\"text\" readonly>");
+ $("input_password").focus();
+ $("input_text_ime_mode_disabled").focus();
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <input type=\"text\" style=\"ime-mode: disabled;\">");
+ $("input_password").focus();
+ $("textarea").focus();
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <textarea>");
+ $("input_password").focus();
+ $("contenteditable").focus();
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <div contenteditable>");
+
+ $("input_change").focus();
+ $("input_change").type = "password";
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <input type=\"password\"> changed from type=\"text\"");
+ $("input_change").type = "text";
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <input type=\"text\"> changed from type=\"password\"");
+
+ var otherWindow =
+ window.browsingContext.topChromeWindow.open("file_secure_input.html",
+ "_blank", "chrome,width=100,height=100");
+ ok(otherWindow, "failed to open other window");
+ if (!otherWindow) {
+ SimpleTest.finish();
+ return;
+ }
+
+ $("input_text").focus();
+ otherWindow.focus();
+
+ SimpleTest.waitForFocus(function() {
+ window.focus();
+ sendAKeyEvent();
+ ok(isFocused($("input_text")), "focused element isn't <input type=\"text\">");
+ ok(true, "Not crashed: input on <input type=\"text\"> after the other document has focus");
+
+ $("input_password").focus();
+ otherWindow.focus();
+ window.focus();
+ sendAKeyEvent();
+ ok(isFocused($("input_password")), "focused element isn't <input type=\"password\">");
+ ok(true, "Not crashed: input on <input type=\"password\"> after the other document has focus");
+
+ $("input_text").focus();
+ otherWindow.focus();
+ otherWindow.document.getElementById("text").focus();
+ window.focus();
+ sendAKeyEvent();
+ ok(isFocused($("input_text")), "focused element isn't <input type=\"text\">");
+ ok(true, "Not crashed: input on <input type=\"text\"> after the other document's <input type=\"text\"> has focus");
+
+ $("input_password").focus();
+ otherWindow.focus();
+ otherWindow.document.getElementById("text").focus();
+ window.focus();
+ sendAKeyEvent();
+ ok(isFocused($("input_password")), "focused element isn't <input type=\"password\">");
+ ok(true, "Not crashed: input on <input type=\"password\"> after the other document's <input type=\"text\"> has focus");
+
+ $("input_text").focus();
+ otherWindow.focus();
+ otherWindow.document.getElementById("password").focus();
+ window.focus();
+ sendAKeyEvent();
+ ok(isFocused($("input_text")), "focused element isn't <input type=\"text\">");
+ ok(true, "Not crashed: input on <input type=\"text\"> after the other document's <input type=\"password\"> has focus");
+
+ $("input_password").focus();
+ otherWindow.focus();
+ otherWindow.document.getElementById("password").focus();
+ window.focus();
+ sendAKeyEvent();
+ ok(isFocused($("input_password")), "focused element isn't <input type=\"password\">");
+ ok(true, "Not crashed: input on <input type=\"password\"> after the other document's <input type=\"password\"> has focus");
+
+ SimpleTest.finish();
+ }, otherWindow);
+ }
+
+ SimpleTest.waitForFocus(runTest);
+</script>
+</body>
+</html>
diff --git a/widget/tests/test_sizemode_events.xhtml b/widget/tests/test_sizemode_events.xhtml
new file mode 100644
index 0000000000..b79acaa369
--- /dev/null
+++ b/widget/tests/test_sizemode_events.xhtml
@@ -0,0 +1,105 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Test for bug 715867"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+let gWindow = null;
+
+let gSizeModeDidChange = false;
+let gSizeModeDidChangeTo = 0;
+
+function sizemodeChanged(e) {
+ gSizeModeDidChange = true;
+ gSizeModeDidChangeTo = gWindow.windowState;
+}
+
+function expectSizeModeChange(newMode, duringActionCallback) {
+ gSizeModeDidChange = false;
+
+ duringActionCallback();
+
+ if (newMode == 0) {
+ // No change should have taken place, no event should have fired.
+ ok(!gSizeModeDidChange, "No sizemodechange event should have fired.");
+ } else {
+ // Size mode change event was expected to fire.
+ ok(gSizeModeDidChange, "A sizemodechanged event should have fired.");
+ is(gSizeModeDidChangeTo, newMode, "The new sizemode should have the expected value.");
+ }
+}
+
+function startTest() {
+ if (navigator.platform.includes("Lin")) {
+ ok(true, "This test is disabled on Linux because it expects window sizemode changes to be synchronous (which is not the case on Linux).");
+ SimpleTest.finish();
+ return;
+ };
+ openWindow();
+}
+
+function openWindow() {
+ gWindow = window.browsingContext.topChromeWindow
+ .open('empty_window.xhtml', '_blank', 'chrome,screenX=50,screenY=50,width=200,height=200,resizable');
+ SimpleTest.waitForFocus(runTest, gWindow);
+}
+
+function runTest() {
+ // Install event handler.
+ gWindow.addEventListener("sizemodechange", sizemodeChanged);
+
+ // Run tests.
+ expectSizeModeChange(gWindow.STATE_MINIMIZED, function () {
+ gWindow.minimize();
+ });
+
+ expectSizeModeChange(gWindow.STATE_NORMAL, function () {
+ gWindow.restore();
+ });
+
+ expectSizeModeChange(gWindow.STATE_MAXIMIZED, function () {
+ gWindow.maximize();
+ });
+
+ expectSizeModeChange(gWindow.STATE_NORMAL, function () {
+ gWindow.restore();
+ });
+
+ // Normal window resizing shouldn't fire a sizemodechanged event, bug 715867.
+ expectSizeModeChange(0, function () {
+ gWindow.resizeTo(gWindow.outerWidth + 10, gWindow.outerHeight);
+ });
+
+ expectSizeModeChange(0, function () {
+ gWindow.resizeTo(gWindow.outerWidth, gWindow.outerHeight + 10);
+ });
+
+ gWindow.removeEventListener("sizemodechange", sizemodeChanged);
+ gWindow.close();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(startTest);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_standalone_native_menu.xhtml b/widget/tests/test_standalone_native_menu.xhtml
new file mode 100644
index 0000000000..96e41036c3
--- /dev/null
+++ b/widget/tests/test_standalone_native_menu.xhtml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Standalone Native Menu tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("standalone_native_menu_window.xhtml", "StandaloneNativeMenuWindow",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_system_font_changes.xhtml b/widget/tests/test_system_font_changes.xhtml
new file mode 100644
index 0000000000..036c775463
--- /dev/null
+++ b/widget/tests/test_system_font_changes.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Native menu system tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("system_font_changes.xhtml", "system_font_changes_window",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_system_status_bar.xhtml b/widget/tests/test_system_status_bar.xhtml
new file mode 100644
index 0000000000..f2348fa6f5
--- /dev/null
+++ b/widget/tests/test_system_status_bar.xhtml
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Testing composition, text and query content events"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<menupopup id="menuContainer">
+ <menu id="menu1" image="data:image/svg+xml,&lt;svg%20xmlns=&quot;http://www.w3.org/2000/svg&quot;%20width=&quot;32&quot;%20height=&quot;32&quot;>&lt;circle%20cx=&quot;16&quot;%20cy=&quot;16&quot;%20r=&quot;16&quot;/>&lt;/svg>">
+ <menupopup>
+ <menuitem label="Item 1 in menu 1"/>
+ <menuitem label="Item 2 in menu 1"/>
+ </menupopup>
+ </menu>
+ <menu id="menu2" image="data:image/svg+xml,&lt;svg%20xmlns=&quot;http://www.w3.org/2000/svg&quot;%20width=&quot;32&quot;%20height=&quot;32&quot;>&lt;path%20d=&quot;M0 16 L 16 0 L 32 16 L 16 32 Z&quot;/>&lt;/svg>">
+ <menupopup>
+ <menuitem label="Item 1 in menu 2"/>
+ <menuitem label="Item 2 in menu 2"/>
+ </menupopup>
+ </menu>
+</menupopup>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+ let systemStatusBar = Cc["@mozilla.org/widget/systemstatusbar;1"].getService(Ci.nsISystemStatusBar);
+ ok(systemStatusBar, "should have got an nsISystemStatusBar instance");
+
+ let menu1 = document.getElementById("menu1");
+ let menu2 = document.getElementById("menu2");
+
+ // Add and remove the item, just to get basic leak testing coverage.
+ systemStatusBar.addItem(menu1);
+ systemStatusBar.removeItem(menu1);
+
+ // Make sure that calling addItem twice with the same element doesn't leak.
+ systemStatusBar.addItem(menu2);
+ systemStatusBar.addItem(menu2);
+ systemStatusBar.removeItem(menu2);
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_taskbar_progress.xhtml b/widget/tests/test_taskbar_progress.xhtml
new file mode 100644
index 0000000000..0b9f49e441
--- /dev/null
+++ b/widget/tests/test_taskbar_progress.xhtml
@@ -0,0 +1,119 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Taskbar Previews Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="loaded();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ let TP = Ci.nsITaskbarProgress;
+
+ function IsWin7OrHigher() {
+ try {
+ var ver = parseFloat(Services.sysinfo.getProperty("version"));
+ if (ver >= 6.1)
+ return true;
+ } catch (ex) { }
+ return false;
+ }
+
+ function winProgress() {
+ let taskbar = Cc["@mozilla.org/windows-taskbar;1"];
+ if (!taskbar) {
+ ok(false, "Taskbar service is always available");
+ return null;
+ }
+ taskbar = taskbar.getService(Ci.nsIWinTaskbar);
+
+ is(taskbar.available, IsWin7OrHigher(), "Expected availability of taskbar");
+ if (!taskbar.available)
+ return null;
+
+ // HACK from mconnor:
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let docShell = win.docShell;
+
+ let progress = taskbar.getTaskbarProgress(docShell);
+ isnot(progress, null, "Progress is not null");
+
+ try {
+ taskbar.getTaskbarProgress(null);
+ ok(false, "Got progress for null docshell");
+ } catch (e) {
+ ok(true, "Cannot get progress for null docshell");
+ }
+
+ return progress;
+ }
+
+ function macProgress() {
+ let progress = Cc["@mozilla.org/widget/macdocksupport;1"];
+ if (!progress) {
+ ok(false, "Should have gotten Mac progress service.");
+ return null;
+ }
+ return progress.getService(TP);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ function loaded()
+ {
+ let isWin = /Win/.test(navigator.platform);
+ let progress = isWin ? winProgress() : macProgress();
+ if (!TP || !progress) {
+ SimpleTest.finish();
+ return;
+ }
+
+ function shouldThrow(s,c,m) {
+ try {
+ progress.setProgressState(s,c,m);
+ return false;
+ } catch (e) {
+ return true;
+ }
+ }
+
+ function doesntThrow(s,c,m) {
+ return !shouldThrow(s,c,m);
+ }
+
+ ok(doesntThrow(TP.STATE_NO_PROGRESS, 0, 0), "No progress state can be set");
+ ok(doesntThrow(TP.STATE_INDETERMINATE, 0, 0), "Indeterminate state can be set");
+ ok(doesntThrow(TP.STATE_NORMAL, 0, 0), "Normal state can be set");
+ ok(doesntThrow(TP.STATE_ERROR, 0, 0), "Error state can be set");
+ ok(doesntThrow(TP.STATE_PAUSED, 0, 0), "Paused state can be set");
+
+ ok(shouldThrow(TP.STATE_NO_PROGRESS, 1, 1), "Cannot set no progress with values");
+ ok(shouldThrow(TP.STATE_INDETERMINATE, 1, 1), "Cannot set indeterminate with values");
+
+ // Technically passes since unsigned(-1) > 10
+ ok(shouldThrow(TP.STATE_NORMAL, -1, 10), "Cannot set negative progress");
+ todo(shouldThrow(TP.STATE_NORMAL, 1, -1), "Cannot set negative progress");
+ todo(shouldThrow(TP.STATE_NORMAL, -1, -1), "Cannot set negative progress");
+ todo(shouldThrow(TP.STATE_NORMAL, -2, -1), "Cannot set negative progress");
+
+ ok(shouldThrow(TP.STATE_NORMAL, 5, 3), "Cannot set progress greater than max");
+
+ ok(doesntThrow(TP.STATE_NORMAL, 1, 5), "Normal state can be set with values");
+ ok(doesntThrow(TP.STATE_ERROR, 3, 6), "Error state can be set with values");
+ ok(doesntThrow(TP.STATE_PAUSED, 2, 9), "Paused state can be set with values");
+
+ SimpleTest.finish();
+ }
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ </body>
+
+</window>
diff --git a/widget/tests/test_transferable_overflow.xhtml b/widget/tests/test_transferable_overflow.xhtml
new file mode 100644
index 0000000000..ad1650f00a
--- /dev/null
+++ b/widget/tests/test_transferable_overflow.xhtml
@@ -0,0 +1,152 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window title="nsTransferable with large string"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="RunTest();">
+ <title>nsTransferable with large string</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ // This value is chosen such that the size of the memory for the string exceeds
+ // the kLargeDatasetSize threshold in nsTransferable.h (one million).
+ // Each character of a JS string is internally represented by two bytes,
+ // so the following string of length 500 001 uses 1 000 002 bytes.
+ const BIG_STRING = "x" + "BIGGY".repeat(100000);
+
+ // Some value with a length that is exactly kLargeDatasetSize (1 000 000).
+ const SMALL_STRING = "small".repeat(100000);
+
+ const nsTransferable = Components.Constructor("@mozilla.org/widget/transferable;1", "nsITransferable");
+ const nsSupportsString = Components.Constructor("@mozilla.org/supports-string;1", "nsISupportsString");
+
+ function assignTextToTransferable(transferable, string) {
+ var Suppstr = nsSupportsString();
+ Suppstr.data = string;
+ transferable.setTransferData("text/unicode", Suppstr, string.length * 2);
+ }
+
+ function checkTransferableText(transferable, expectedString, description) {
+ var data = {};
+ transferable.getTransferData("text/unicode", data);
+ var actualValue = data.value.QueryInterface(Ci.nsISupportsString).data;
+ // Use ok + shortenString instead of is(...) to avoid dumping millions of characters in the output.
+ ok(actualValue === expectedString, description + ": text should match. " +
+ "Expected " + shortenString(expectedString) + ", got " + shortenString(actualValue));
+
+ function shortenString(str) {
+ return str && str.length > 30 ? str.slice(0, 10) + "..." + str.slice(-10) : String(str);
+ }
+ }
+
+ function isFDCountingSupported() {
+ // On on-Windows we can count the number of file handles for the current process,
+ // while on Windows we need to count the number of files in ${TempD}\mozilla-temp-files\,
+ // which can be unreliable, especially because nsAnonymousTemporaryFile has documented
+ // that the deletion might not be immediate.
+ //
+ // To avoid intermittents, we only check the file descriptor counts on non-Windows.
+ // test_bug1123480.xhtml will do some basic testing for Windows.
+ const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ return AppConstants.platform !== 'win';
+ }
+
+ function getClipboardCacheFDCount() {
+ var dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath("/dev/fd");
+ var count = 0;
+ for (var de = dir.directoryEntries; de.hasMoreElements(); ) {
+ var fdFile = de.nextFile;
+ var fileSize;
+ try {
+ fileSize = fdFile.fileSize;
+ } catch (e) {
+ // This can happen on macOS.
+ continue;
+ }
+ if (fileSize === BIG_STRING.length * 2 ||
+ // We are not expecting files of this small size,
+ // but include them in the count anyway
+ // in case the files are unexpectedly created.
+ fileSize === SMALL_STRING.length * 2) {
+ // Assume that the file was created by us if the size matches.
+ ++count;
+ }
+ }
+ return count;
+ }
+
+ function RunTest() {
+ const {PrivateBrowsingUtils} = ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ var win = window.browsingContext.topChromeWindow.open("about:blank", "_blank", "chrome, width=500, height=200");
+ ok(win, "should open window");
+ is(PrivateBrowsingUtils.isContentWindowPrivate(win), false, "used correct window context");
+
+ // ### Part 1 - Writing to the clipboard.
+
+ var Loadctx = PrivateBrowsingUtils.privacyContextFromWindow(win);
+ var Transfer = nsTransferable();
+ Transfer.init(Loadctx);
+ Transfer.addDataFlavor("text/unicode");
+ var initialFdCount = isFDCountingSupported() ? getClipboardCacheFDCount() : -1;
+
+ assignTextToTransferable(Transfer, BIG_STRING);
+ checkTransferableText(Transfer, BIG_STRING, "transferable after assigning BIG_STRING");
+ if (isFDCountingSupported()) {
+ is(getClipboardCacheFDCount(), initialFdCount + 1, "should use a file for BIG_STRING");
+ }
+
+ // Share the transferable with the system.
+ Services.clipboard.setData(Transfer, null, Services.clipboard.kGlobalClipboard);
+
+ // Sanity check: Copying to the clipboard should not have altered the transferable.
+ checkTransferableText(Transfer, BIG_STRING, "transferable after copying to clipboard");
+ if (isFDCountingSupported()) {
+ // We are only counting file descriptors for the current process,
+ // so even if the test were to be multi-process and the parent process creates another
+ // nsTransferable, then the count should still be the same.
+ is(getClipboardCacheFDCount(), initialFdCount + 1, "should still be using files for previously stored BIG_STRING");
+
+ // Re-establish baseline for the second part of the test below.
+ initialFdCount = getClipboardCacheFDCount();
+ }
+
+ // ### Part 2 - Reading from the clipboard.
+
+ var Transfer2 = nsTransferable();
+ Transfer2.init(Loadctx);
+ Transfer2.addDataFlavor("text/unicode");
+
+ // Iniitalize with a small string, so we can see that mData -> mCacheFD works.
+ assignTextToTransferable(Transfer2, SMALL_STRING);
+ checkTransferableText(Transfer2, SMALL_STRING, "transferable after assigning SMALL_STRING");
+ if (isFDCountingSupported()) {
+ is(getClipboardCacheFDCount(), initialFdCount, "should not use file to store SMALL_STRING.");
+ }
+
+ // Check whether the clipboard data can be read, and simulatenously trigger mData -> mCacheFD.
+ Services.clipboard.getData(Transfer2, Services.clipboard.kGlobalClipboard);
+ checkTransferableText(Transfer2, BIG_STRING, "transferable after retrieving from clipboard");
+ if (isFDCountingSupported()) {
+ is(getClipboardCacheFDCount(), initialFdCount + 1, "should use a file for BIG_STRING (read from clipboard).");
+ }
+
+ // Store a small string, to exercise the code path from mCacheFD -> mData.
+ assignTextToTransferable(Transfer2, SMALL_STRING);
+ checkTransferableText(Transfer2, SMALL_STRING, "transferable after assigning SMALL_STRING");
+ if (isFDCountingSupported()) {
+ is(getClipboardCacheFDCount(), initialFdCount, "should release the file after clearing the transferable.");
+ }
+ }
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ This test checks whether a big string can be copied to the clipboard, and then retrieved in the same form.
+ On non-Windows, the test also checks whether the data of the transferable is really stored in a file.
+ </body>
+</window>
diff --git a/widget/tests/test_wheeltransaction.xhtml b/widget/tests/test_wheeltransaction.xhtml
new file mode 100644
index 0000000000..23e855c39b
--- /dev/null
+++ b/widget/tests/test_wheeltransaction.xhtml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Wheel scroll transaction tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("window_wheeltransaction.xhtml", "_blank",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/unit/test_macsharingservice.js b/widget/tests/unit/test_macsharingservice.js
new file mode 100644
index 0000000000..d85b9ae737
--- /dev/null
+++ b/widget/tests/unit/test_macsharingservice.js
@@ -0,0 +1,29 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Basic tests to verify that MacSharingService returns expected data
+
+function test_getSharingProviders() {
+ let sharingService = Cc["@mozilla.org/widget/macsharingservice;1"].getService(
+ Ci.nsIMacSharingService
+ );
+ let providers = sharingService.getSharingProviders("http://example.org");
+ Assert.greater(providers.length, 1, "There are providers returned");
+ providers.forEach(provider => {
+ Assert.ok("name" in provider, "Provider has name");
+ Assert.ok("menuItemTitle" in provider, "Provider has menuItemTitle");
+ Assert.ok("image" in provider, "Provider has image");
+
+ Assert.notEqual(
+ provider.title,
+ "Mail",
+ "Known filtered provider not returned"
+ );
+ });
+}
+
+function run_test() {
+ test_getSharingProviders();
+}
diff --git a/widget/tests/unit/test_macwebapputils.js b/widget/tests/unit/test_macwebapputils.js
new file mode 100644
index 0000000000..8967f8a593
--- /dev/null
+++ b/widget/tests/unit/test_macwebapputils.js
@@ -0,0 +1,34 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Basic tests to verify that MacWebAppUtils works
+
+function test_find_app() {
+ var mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"].createInstance(
+ Ci.nsIMacWebAppUtils
+ );
+ let sig = "com.apple.TextEdit";
+
+ let path;
+ path = mwaUtils.pathForAppWithIdentifier(sig);
+ info("TextEdit path: " + path + "\n");
+ Assert.notEqual(path, "");
+}
+
+function test_dont_find_fake_app() {
+ var mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"].createInstance(
+ Ci.nsIMacWebAppUtils
+ );
+ let sig = "calliope.penitentiary.dramamine";
+
+ let path;
+ path = mwaUtils.pathForAppWithIdentifier(sig);
+ Assert.equal(path, "");
+}
+
+function run_test() {
+ test_find_app();
+ test_dont_find_fake_app();
+}
diff --git a/widget/tests/unit/test_taskbar_jumplistitems.js b/widget/tests/unit/test_taskbar_jumplistitems.js
new file mode 100644
index 0000000000..fad9680e02
--- /dev/null
+++ b/widget/tests/unit/test_taskbar_jumplistitems.js
@@ -0,0 +1,328 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This tests taskbar jump list functionality available on win7 and up.
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+function test_basics() {
+ var item = Cc["@mozilla.org/windows-jumplistitem;1"].createInstance(
+ Ci.nsIJumpListItem
+ );
+
+ var sep = Cc["@mozilla.org/windows-jumplistseparator;1"].createInstance(
+ Ci.nsIJumpListSeparator
+ );
+
+ var shortcut = Cc["@mozilla.org/windows-jumplistshortcut;1"].createInstance(
+ Ci.nsIJumpListShortcut
+ );
+
+ var link = Cc["@mozilla.org/windows-jumplistlink;1"].createInstance(
+ Ci.nsIJumpListLink
+ );
+
+ Assert.ok(!item.equals(sep));
+ Assert.ok(!item.equals(shortcut));
+ Assert.ok(!item.equals(link));
+
+ Assert.ok(!sep.equals(item));
+ Assert.ok(!sep.equals(shortcut));
+ Assert.ok(!sep.equals(link));
+
+ Assert.ok(!shortcut.equals(item));
+ Assert.ok(!shortcut.equals(sep));
+ Assert.ok(!shortcut.equals(link));
+
+ Assert.ok(!link.equals(item));
+ Assert.ok(!link.equals(sep));
+ Assert.ok(!link.equals(shortcut));
+
+ Assert.ok(item.equals(item));
+ Assert.ok(sep.equals(sep));
+ Assert.ok(link.equals(link));
+ Assert.ok(shortcut.equals(shortcut));
+}
+
+function test_separator() {
+ // separators:
+
+ var item = Cc["@mozilla.org/windows-jumplistseparator;1"].createInstance(
+ Ci.nsIJumpListSeparator
+ );
+
+ Assert.ok(item.type == Ci.nsIJumpListItem.JUMPLIST_ITEM_SEPARATOR);
+}
+
+function test_hashes() {
+ var link = Cc["@mozilla.org/windows-jumplistlink;1"].createInstance(
+ Ci.nsIJumpListLink
+ );
+ var uri1 = Cc["@mozilla.org/network/simple-uri-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec("http://www.123.com/")
+ .finalize();
+ var uri2 = Cc["@mozilla.org/network/simple-uri-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec("http://www.123.com/")
+ .finalize();
+
+ link.uri = uri1;
+
+ Assert.ok(link.compareHash(uri2));
+ uri2 = uri2
+ .mutate()
+ .setSpec("http://www.456.com/")
+ .finalize();
+ Assert.ok(!link.compareHash(uri2));
+ uri2 = uri2
+ .mutate()
+ .setSpec("http://www.123.com/")
+ .finalize();
+ Assert.ok(link.compareHash(uri2));
+ uri2 = uri2
+ .mutate()
+ .setSpec("https://www.123.com/")
+ .finalize();
+ Assert.ok(!link.compareHash(uri2));
+ uri2 = uri2
+ .mutate()
+ .setSpec("http://www.123.com/test/")
+ .finalize();
+ Assert.ok(!link.compareHash(uri2));
+ uri1 = uri1
+ .mutate()
+ .setSpec("http://www.123.com/test/")
+ .finalize();
+ link.uri = uri1;
+ uri2 = uri2
+ .mutate()
+ .setSpec("http://www.123.com/test/")
+ .finalize();
+ Assert.ok(link.compareHash(uri2));
+ uri1 = uri1
+ .mutate()
+ .setSpec("https://www.123.com/test/")
+ .finalize();
+ link.uri = uri1;
+ uri2 = uri2
+ .mutate()
+ .setSpec("https://www.123.com/test/")
+ .finalize();
+ Assert.ok(link.compareHash(uri2));
+ uri2 = uri2
+ .mutate()
+ .setSpec("ftp://www.123.com/test/")
+ .finalize();
+ Assert.ok(!link.compareHash(uri2));
+ uri2 = uri2
+ .mutate()
+ .setSpec("http://123.com/test/")
+ .finalize();
+ Assert.ok(!link.compareHash(uri2));
+ uri1 = uri1
+ .mutate()
+ .setSpec("https://www.123.com/test/")
+ .finalize();
+ link.uri = uri1;
+ uri2 = uri2
+ .mutate()
+ .setSpec("https://www.123.com/Test/")
+ .finalize();
+ Assert.ok(!link.compareHash(uri2));
+
+ uri1 = uri1
+ .mutate()
+ .setSpec("http://www.123.com/")
+ .finalize();
+ link.uri = uri1;
+ Assert.equal(link.uriHash, "QGLmWuwuTozr3tOfXSf5mg==");
+ uri1 = uri1
+ .mutate()
+ .setSpec("http://www.123.com/test/")
+ .finalize();
+ link.uri = uri1;
+ Assert.equal(link.uriHash, "AG87Ls+GmaUYSUJFETRr3Q==");
+ uri1 = uri1
+ .mutate()
+ .setSpec("https://www.123.com/")
+ .finalize();
+ link.uri = uri1;
+ Assert.equal(link.uriHash, "iSx6UH1a9enVPzUA9JZ42g==");
+}
+
+function test_links() {
+ // links:
+ var link1 = Cc["@mozilla.org/windows-jumplistlink;1"].createInstance(
+ Ci.nsIJumpListLink
+ );
+ var link2 = Cc["@mozilla.org/windows-jumplistlink;1"].createInstance(
+ Ci.nsIJumpListLink
+ );
+
+ var uri1 = Cc["@mozilla.org/network/simple-uri-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec("http://www.test.com/")
+ .finalize();
+ var uri2 = Cc["@mozilla.org/network/simple-uri-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec("http://www.test.com/")
+ .finalize();
+
+ link1.uri = uri1;
+ link1.uriTitle = "Test";
+ link2.uri = uri2;
+ link2.uriTitle = "Test";
+
+ Assert.ok(link1.equals(link2));
+
+ link2.uriTitle = "Testing";
+
+ Assert.ok(!link1.equals(link2));
+
+ link2.uriTitle = "Test";
+ uri2 = uri2
+ .mutate()
+ .setSpec("http://www.testing.com/")
+ .finalize();
+ link2.uri = uri2;
+
+ Assert.ok(!link1.equals(link2));
+}
+
+function test_shortcuts() {
+ // shortcuts:
+ var sc = Cc["@mozilla.org/windows-jumplistshortcut;1"].createInstance(
+ Ci.nsIJumpListShortcut
+ );
+
+ var handlerApp = Cc[
+ "@mozilla.org/uriloader/local-handler-app;1"
+ ].createInstance(Ci.nsILocalHandlerApp);
+
+ handlerApp.name = "TestApp";
+ handlerApp.detailedDescription = "TestApp detailed description.";
+ handlerApp.appendParameter("-test");
+
+ sc.iconIndex = 1;
+ Assert.equal(sc.iconIndex, 1);
+
+ var faviconPageUri = Cc["@mozilla.org/network/simple-uri-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec("http://www.123.com/")
+ .finalize();
+ sc.faviconPageUri = faviconPageUri;
+ Assert.equal(sc.faviconPageUri, faviconPageUri);
+
+ var notepad = Services.dirsvc.get("WinD", Ci.nsIFile);
+ notepad.append("notepad.exe");
+ if (notepad.exists()) {
+ handlerApp.executable = notepad;
+ sc.app = handlerApp;
+ Assert.equal(sc.app.detailedDescription, "TestApp detailed description.");
+ Assert.equal(sc.app.name, "TestApp");
+ Assert.ok(sc.app.parameterExists("-test"));
+ Assert.ok(!sc.app.parameterExists("-notset"));
+ }
+}
+
+async function test_jumplist() {
+ // Jump lists can't register links unless the application is the default
+ // protocol handler for the protocol of the link, so we skip off testing
+ // those in these tests. We'll init the jump list for the xpc shell harness,
+ // add a task item, and commit it.
+
+ // not compiled in
+ if (Ci.nsIWinTaskbar == null) {
+ return;
+ }
+
+ var taskbar = Cc["@mozilla.org/windows-taskbar;1"].getService(
+ Ci.nsIWinTaskbar
+ );
+
+ var builder = taskbar.createJumpListBuilder();
+
+ Assert.notEqual(builder, null);
+
+ // Win7 and up only
+ try {
+ var ver = parseFloat(Services.sysinfo.getProperty("version"));
+ if (ver < 6.1) {
+ Assert.ok(!builder.available);
+ return;
+ }
+ } catch (ex) {}
+
+ Assert.ok(taskbar.available);
+
+ builder.deleteActiveList();
+
+ var items = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+
+ var sc = Cc["@mozilla.org/windows-jumplistshortcut;1"].createInstance(
+ Ci.nsIJumpListShortcut
+ );
+
+ var handlerApp = Cc[
+ "@mozilla.org/uriloader/local-handler-app;1"
+ ].createInstance(Ci.nsILocalHandlerApp);
+
+ handlerApp.name = "Notepad";
+ handlerApp.detailedDescription = "Testing detailed description.";
+
+ var notepad = Services.dirsvc.get("WinD", Ci.nsIFile);
+ notepad.append("notepad.exe");
+ if (notepad.exists()) {
+ // To ensure "profile-before-change" will fire before
+ // "xpcom-shutdown-threads"
+ do_get_profile();
+
+ handlerApp.executable = notepad;
+ sc.app = handlerApp;
+ items.appendElement(sc);
+
+ var removed = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ Assert.ok(builder.initListBuild(removed));
+ Assert.ok(builder.addListToBuild(builder.JUMPLIST_CATEGORY_TASKS, items));
+ Assert.ok(builder.addListToBuild(builder.JUMPLIST_CATEGORY_RECENT));
+ Assert.ok(builder.addListToBuild(builder.JUMPLIST_CATEGORY_FREQUENT));
+ let rv = new Promise(resolve => {
+ builder.commitListBuild(resolve);
+ });
+ Assert.ok(await rv);
+
+ builder.deleteActiveList();
+
+ Assert.ok(builder.initListBuild(removed));
+ Assert.ok(
+ builder.addListToBuild(
+ builder.JUMPLIST_CATEGORY_CUSTOMLIST,
+ items,
+ "Custom List"
+ )
+ );
+ rv = new Promise(resolve => {
+ builder.commitListBuild(resolve);
+ });
+ Assert.ok(await rv);
+
+ builder.deleteActiveList();
+ }
+}
+
+function run_test() {
+ if (mozinfo.os != "win") {
+ return;
+ }
+ test_basics();
+ test_separator();
+ test_hashes();
+ test_links();
+ test_shortcuts();
+
+ run_next_test();
+}
+
+add_task(test_jumplist);
diff --git a/widget/tests/unit/xpcshell.ini b/widget/tests/unit/xpcshell.ini
new file mode 100644
index 0000000000..46beea87e0
--- /dev/null
+++ b/widget/tests/unit/xpcshell.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+head =
+
+[test_taskbar_jumplistitems.js]
+skip-if = os == "win" && os_version == "10.0" # Bug 1457329
+[test_macsharingservice.js]
+skip-if = os != "mac"
+[test_macwebapputils.js]
+skip-if = os != "mac"
diff --git a/widget/tests/utils.js b/widget/tests/utils.js
new file mode 100644
index 0000000000..d2d68ce8d3
--- /dev/null
+++ b/widget/tests/utils.js
@@ -0,0 +1,27 @@
+function getTestPlugin(pluginName) {
+ var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"].getService(
+ SpecialPowers.Ci.nsIPluginHost
+ );
+ var tags = ph.getPluginTags();
+ var name = pluginName || "Test Plug-in";
+ for (var tag of tags) {
+ if (tag.name == name) {
+ return tag;
+ }
+ }
+
+ ok(false, "Could not find plugin tag with plugin name '" + name + "'");
+ return null;
+}
+
+// call this to set the test plugin(s) initially expected enabled state.
+// it will automatically be reset to it's previous value after the test
+// ends
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+ var plugin = getTestPlugin(pluginName);
+ var oldEnabledState = plugin.enabledState;
+ plugin.enabledState = newEnabledState;
+ SimpleTest.registerCleanupFunction(function() {
+ getTestPlugin(pluginName).enabledState = oldEnabledState;
+ });
+}
diff --git a/widget/tests/window_bug429954.xhtml b/widget/tests/window_bug429954.xhtml
new file mode 100644
index 0000000000..ca26d52621
--- /dev/null
+++ b/widget/tests/window_bug429954.xhtml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 478536"
+ onload="start();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml" id="body">
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+function ok(aCondition, aMessage)
+{
+ window.browsingContext.topChromeWindow.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ window.arguments[0].SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage)
+{
+ window.arguments[0].SimpleTest.isnot(aLeft, aRight, aMessage);
+}
+
+function start() {
+ var oldWidth = window.outerWidth, oldHeight = window.outerHeight;
+ window.maximize();
+ window.restore();
+ is(window.outerWidth, oldWidth, "wrong window width after maximize+restore");
+ is(window.outerHeight, oldHeight, "wrong window height after maximize+restore");
+ window.arguments[0].SimpleTest.finish();
+ window.close();
+}
+
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_bug478536.xhtml b/widget/tests/window_bug478536.xhtml
new file mode 100644
index 0000000000..7318eb0bff
--- /dev/null
+++ b/widget/tests/window_bug478536.xhtml
@@ -0,0 +1,211 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 478536"
+ width="600" height="600"
+ onload="onload();"
+ onunload="onunload();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml" id="body">
+<style type="text/css">
+ #view {
+ overflow: auto;
+ width: 100px;
+ height: 100px;
+ border: 1px solid;
+ margin: 0;
+ }
+</style>
+<pre id="view" onscroll="onScrollView(event);">
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+function ok(aCondition, aMessage)
+{
+ window.arguments[0].SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ window.arguments[0].SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage)
+{
+ window.arguments[0].SimpleTest.isnot(aLeft, aRight, aMessage);
+}
+
+var gBody = document.getElementById("body");
+var gView = document.getElementById("view");
+
+/**
+ * Description:
+ *
+ * First, lock the wheel scrolling target to "view" at first step.
+ * Next, scroll back to top most of the "view" at second step.
+ * Finally, scroll back again at third step. This fails to scroll the "view",
+ * then, |onMouseScrollFailed| event should be fired. And at that time, we
+ * can remove the "view". So, in post processing of the event firere, the
+ * "view" should not be referred.
+ *
+ * For suppressing random test failure, all tests will be retried if we handle
+ * unexpected timeout event.
+ */
+
+var gTests = [
+ { scrollToForward: true, shouldScroll: true },
+ { scrollToForward: false, shouldScroll: true },
+ { scrollToForward: false, shouldScroll: false }
+];
+var gCurrentTestIndex = -1;
+var gIgnoreScrollEvent = true;
+
+var gPrefSvc = SpecialPowers.Services.prefs;
+const kPrefSmoothScroll = "general.smoothScroll";
+const kPrefNameTimeout = "mousewheel.transaction.timeout";
+const kDefaultTimeout = gPrefSvc.getIntPref(kPrefNameTimeout);
+
+gPrefSvc.setBoolPref(kPrefSmoothScroll, false);
+
+var gTimeout = kDefaultTimeout;
+
+gBody.addEventListener("MozMouseScrollFailed", onMouseScrollFailed);
+gBody.addEventListener("MozMouseScrollTransactionTimeout",
+ onTransactionTimeout);
+
+function setTimeoutPrefs(aTimeout)
+{
+ gPrefSvc.setIntPref(kPrefNameTimeout, aTimeout);
+ gTimeout = aTimeout;
+}
+
+function resetTimeoutPrefs()
+{
+ if (gTimeout == kDefaultTimeout)
+ return;
+ setTimeoutPrefs(kDefaultTimeout);
+}
+
+function growUpTimeoutPrefs()
+{
+ if (gTimeout != kDefaultTimeout)
+ return;
+ setTimeoutPrefs(5000);
+}
+
+function onload()
+{
+ disableNonTestMouseEvents(true);
+ setTimeout(runNextTest, 0);
+}
+
+function onunload()
+{
+ resetTimeoutPrefs();
+ disableNonTestMouseEvents(false);
+ gPrefSvc.clearUserPref(kPrefSmoothScroll);
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+ window.arguments[0].SimpleTest.finish();
+}
+
+function finish()
+{
+ window.close();
+}
+
+// testing code
+
+var gTimer;
+function clearTimer()
+{
+ clearTimeout(gTimer);
+ gTimer = 0;
+}
+
+function runNextTest()
+{
+ clearTimer();
+ if (++gCurrentTestIndex >= gTests.length) {
+ ok(true, "didn't crash, succeeded");
+ finish();
+ return;
+ }
+ fireWheelScrollEvent(gTests[gCurrentTestIndex].scrollToForward);
+}
+
+var gRetryCount = 5;
+function retryAllTests()
+{
+ clearTimer();
+ if (--gRetryCount >= 0) {
+ gView.scrollTop = 0;
+ gView.scrollLeft = 0;
+ gCurrentTestIndex = -1;
+ growUpTimeoutPrefs();
+ ok(true, "WARNING: retry current test-list...");
+ gTimer = setTimeout(runNextTest, 0);
+ } else {
+ ok(false, "Failed by unexpected timeout");
+ finish();
+ }
+}
+
+function fireWheelScrollEvent(aForward)
+{
+ gIgnoreScrollEvent = false;
+ var event = { deltaY: aForward ? 4.0 : -4.0,
+ deltaMode: WheelEvent.DOM_DELTA_LINE };
+ sendWheelAndPaint(gView, 5, 5, event, function() {
+ // No callback - we're just forcing the refresh driver to tick.
+ }, window);
+}
+
+function onScrollView(aEvent)
+{
+ if (gIgnoreScrollEvent)
+ return;
+ gIgnoreScrollEvent = true;
+ clearTimer();
+ ok(gTests[gCurrentTestIndex].shouldScroll, "The view is scrolled");
+ gTimer = setTimeout(runNextTest, 0);
+}
+
+function onMouseScrollFailed(aEvent)
+{
+ clearTimer();
+ gIgnoreScrollEvent = true;
+ ok(!gTests[gCurrentTestIndex].shouldScroll, "The view is not scrolled");
+ if (!gTests[gCurrentTestIndex].shouldScroll)
+ gBody.removeChild(gView);
+ runNextTest();
+}
+
+function onTransactionTimeout(aEvent)
+{
+ if (!gTimer)
+ return;
+ gIgnoreScrollEvent = true;
+ retryAllTests();
+}
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_bug522217.xhtml b/widget/tests/window_bug522217.xhtml
new file mode 100644
index 0000000000..8d03879521
--- /dev/null
+++ b/widget/tests/window_bug522217.xhtml
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 522217"
+ onload="start();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml" id="body">
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+function ok(aCondition, aMessage)
+{
+ window.arguments[0].SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ window.arguments[0].SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage)
+{
+ window.arguments[0].SimpleTest.isnot(aLeft, aRight, aMessage);
+}
+
+function executeSoon() {
+ return new Promise(resolve => {
+ window.arguments[0].SimpleTest.executeSoon(resolve);
+ });
+}
+
+function waitForEvent(obj, name) {
+ return new Promise(resolve => {
+ obj.addEventListener(name, resolve, { once: true });
+ });
+}
+
+async function start() {
+ await waitForEvent(window, "focus");
+ var oldOuterWidth = window.outerWidth, oldOuterHeight = window.outerHeight;
+ var oldInnerWidth = window.innerWidth, oldInnerHeight = window.innerHeight;
+ document.documentElement.setAttribute("drawintitlebar", "true");
+
+ await executeSoon();
+ is(window.outerWidth, oldOuterWidth, "drawintitlebar shouldn't change the window's outerWidth");
+ is(window.outerHeight, oldOuterHeight, "drawintitlebar shouldn't change the window's outerHeight");
+ is(window.innerWidth, oldOuterWidth, "if drawintitlebar is set, innerWidth and outerWidth should be the same");
+ is(window.innerHeight, oldOuterHeight, "if drawintitlebar is set, innerHeight and outerHeight should be the same");
+
+ // Wait for going full screen and back.
+ let sizemodeChange = waitForEvent(window, "sizemodechange");
+ window.fullScreen = true;
+ await sizemodeChange;
+ sizemodeChange = waitForEvent(window, "sizemodechange");
+ window.fullScreen = false;
+ await sizemodeChange;
+ is(window.outerWidth, oldOuterWidth, "wrong outerWidth after fullscreen mode");
+ is(window.outerHeight, oldOuterHeight, "wrong outerHeight after fullscreen mode");
+ is(window.innerWidth, oldOuterWidth, "wrong innerWidth after fullscreen mode");
+ is(window.innerHeight, oldOuterHeight, "wrong innerHeight after fullscreen mode");
+ document.documentElement.removeAttribute("drawintitlebar");
+
+ await executeSoon();
+ is(window.outerWidth, oldOuterWidth, "wrong outerWidth after removing drawintitlebar");
+ is(window.outerHeight, oldOuterHeight, "wrong outerHeight after removing drawintitlebar");
+ is(window.innerWidth, oldInnerWidth, "wrong innerWidth after removing drawintitlebar");
+ is(window.innerHeight, oldInnerHeight, "wrong innerHeight after removing drawintitlebar");
+ window.arguments[0].SimpleTest.finish();
+ window.close();
+}
+
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_bug538242.xhtml b/widget/tests/window_bug538242.xhtml
new file mode 100644
index 0000000000..fb878b1383
--- /dev/null
+++ b/widget/tests/window_bug538242.xhtml
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<window title="Window for Test for Mozilla Bug 538242"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
diff --git a/widget/tests/window_bug593307_centerscreen.xhtml b/widget/tests/window_bug593307_centerscreen.xhtml
new file mode 100644
index 0000000000..816e314262
--- /dev/null
+++ b/widget/tests/window_bug593307_centerscreen.xhtml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 593307"
+ width="100" height="100"
+ onload="onload();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml" id="body">
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+function onload()
+{
+ var SimpleTest = window.opener.SimpleTest;
+ SimpleTest.ok(window.screenX >= 0, "centerscreen window should not start offscreen (X coordinate)");
+ SimpleTest.ok(window.screenY >= 0, "centerscreen window should not start offscreen (Y coordinate)");
+ window.opener.finished();
+}
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_bug593307_offscreen.xhtml b/widget/tests/window_bug593307_offscreen.xhtml
new file mode 100644
index 0000000000..422c406812
--- /dev/null
+++ b/widget/tests/window_bug593307_offscreen.xhtml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 593307"
+ width="100" height="100"
+ onload="onLoad();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml" id="body">
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+var centerscreen = null;
+var SimpleTest = window.arguments[0];
+var finish = window.arguments[1];
+
+function onLoad()
+{
+ centerscreen = window.openDialog('window_bug593307_centerscreen.xhtml','', 'chrome,centerscreen,dependent,dialog=no');
+}
+
+function finished() {
+ centerscreen.close();
+ finish();
+}
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_composition_text_querycontent.xhtml b/widget/tests/window_composition_text_querycontent.xhtml
new file mode 100644
index 0000000000..5a91f7dc44
--- /dev/null
+++ b/widget/tests/window_composition_text_querycontent.xhtml
@@ -0,0 +1,9632 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Testing composition, text and query content events"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js" />
+
+ <panel id="panel" hidden="true" orient="vertical">
+ <vbox id="vbox">
+ <html:textarea id="textbox" cols="20" rows="4" style="font-size: 36px;"/>
+ </vbox>
+ </panel>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display">
+<div id="div" style="margin: 0; padding: 0; font-size: 36px;">Here is a text frame.</div>
+<textarea style="margin: 0; font-family: -moz-fixed;" id="textarea" cols="20" rows="4"></textarea><br/>
+<iframe id="iframe" width="300" height="150"
+ src="data:text/html,&lt;textarea id='textarea' cols='20' rows='4'&gt;&lt;/textarea&gt;"></iframe><br/>
+<iframe id="iframe2" width="300" height="150"
+ src="data:text/html,&lt;body onload='document.designMode=%22on%22'&gt;body content&lt;/body&gt;"></iframe><br/>
+<iframe id="iframe3" width="300" height="150"
+ src="data:text/html,&lt;body onload='document.designMode=%22on%22'&gt;body content&lt;/body&gt;"></iframe><br/>
+<iframe id="iframe4" width="300" height="150"
+ src="data:text/html,&lt;div contenteditable id='contenteditable'&gt;&lt;/div&gt;"></iframe><br/>
+<iframe id="iframe5" width="200" height="50" src="data:text/html,&lt;input id='input'&gt;"></iframe>
+<iframe id="iframe6" width="200" height="50" src="data:text/html,&lt;input id='password' type='password'&gt;"></iframe><br/>
+<iframe id="iframe7" width="300" height="150"
+ src="data:text/html,&lt;span contenteditable id='contenteditable'&gt;&lt;/span&gt;"></iframe><br/>
+<input id="input" type="text"/><br/>
+<input id="password" type="password"/><br/>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+function ok(aCondition, aMessage)
+{
+ window.arguments[0].SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ window.arguments[0].SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage)
+{
+ window.arguments[0].SimpleTest.isnot(aLeft, aRight, aMessage);
+}
+
+function isfuzzy(aLeft, aRight, aEpsilon, aMessage) {
+ window.arguments[0].SimpleTest.isfuzzy(aLeft, aRight, aEpsilon, aMessage);
+}
+
+function todo(aCondition, aMessage)
+{
+ window.arguments[0].SimpleTest.todo(aCondition, aMessage);
+}
+
+function todo_is(aLeft, aRight, aMessage)
+{
+ window.arguments[0].SimpleTest.todo_is(aLeft, aRight, aMessage);
+}
+
+function todo_isnot(aLeft, aRight, aMessage)
+{
+ window.arguments[0].SimpleTest.todo_isnot(aLeft, aRight, aMessage);
+}
+
+function isSimilarTo(aLeft, aRight, aAllowedDifference, aMessage)
+{
+ if (Math.abs(aLeft - aRight) <= aAllowedDifference) {
+ ok(true, aMessage);
+ } else {
+ ok(false, aMessage + ", got=" + aLeft + ", expected=" + (aRight - aAllowedDifference) + "~" + (aRight + aAllowedDifference));
+ }
+}
+
+function isGreaterThan(aLeft, aRight, aMessage)
+{
+ ok(aLeft > aRight, aMessage + ", got=" + aLeft + ", expected minimum value=" + aRight);
+}
+
+var div = document.getElementById("div");
+var textarea = document.getElementById("textarea");
+var panel = document.getElementById("panel");
+var textbox = document.getElementById("textbox");
+var iframe = document.getElementById("iframe");
+var iframe2 = document.getElementById("iframe2");
+var iframe3 = document.getElementById("iframe3");
+var contenteditable;
+var windowOfContenteditable;
+var contenteditableBySpan;
+var windowOfContenteditableBySpan;
+var input = document.getElementById("input");
+var password = document.getElementById("password");
+var textareaInFrame;
+
+const nsITextInputProcessorCallback = Ci.nsITextInputProcessorCallback;
+const nsIInterfaceRequestor = Ci.nsIInterfaceRequestor;
+const nsIWebNavigation = Ci.nsIWebNavigation;
+const nsIDocShell = Ci.nsIDocShell;
+
+function waitForTick() {
+ return new Promise(resolve => { SimpleTest.executeSoon(resolve); });
+}
+
+async function waitForEventLoops(aTimes)
+{
+ for (let i = 1; i < aTimes; i++) {
+ await waitForTick();
+ }
+ await new Promise(resolve => { setTimeout(resolve, 20); });
+}
+
+function getEditor(aNode)
+{
+ return aNode.editor;
+}
+
+function getHTMLEditorIMESupport(aWindow)
+{
+ return aWindow.docShell.editor;
+}
+
+const kIsWin = (navigator.platform.indexOf("Win") == 0);
+const kIsMac = (navigator.platform.indexOf("Mac") == 0);
+
+const kLFLen = kIsWin ? 2 : 1;
+const kLF = kIsWin ? "\r\n" : "\n";
+
+function checkQueryContentResult(aResult, aMessage)
+{
+ ok(aResult, aMessage + ": the result is null");
+ if (!aResult) {
+ return false;
+ }
+ ok(aResult.succeeded, aMessage + ": the query content failed");
+ return aResult.succeeded;
+}
+
+function checkContent(aExpectedText, aMessage, aID)
+{
+ if (!aID) {
+ aID = "";
+ }
+ let textContent = synthesizeQueryTextContent(0, 100);
+ if (!checkQueryContentResult(textContent, aMessage +
+ ": synthesizeQueryTextContent " + aID)) {
+ return false;
+ }
+ is(textContent.text, aExpectedText,
+ aMessage + ": composition string is wrong " + aID);
+ return textContent.text == aExpectedText;
+}
+
+function checkContentRelativeToSelection(aRelativeOffset, aLength, aExpectedOffset, aExpectedText, aMessage, aID)
+{
+ if (!aID) {
+ aID = "";
+ }
+ aMessage += " (aRelativeOffset=" + aRelativeOffset + "): "
+ let textContent = synthesizeQueryTextContent(aRelativeOffset, aLength, true);
+ if (!checkQueryContentResult(textContent, aMessage +
+ "synthesizeQueryTextContent " + aID)) {
+ return false;
+ }
+ is(textContent.offset, aExpectedOffset,
+ aMessage + "offset is wrong " + aID);
+ is(textContent.text, aExpectedText,
+ aMessage + "text is wrong " + aID);
+ return textContent.offset == aExpectedOffset &&
+ textContent.text == aExpectedText;
+}
+
+function checkSelection(aExpectedOffset, aExpectedText, aMessage, aID)
+{
+ if (!aID) {
+ aID = "";
+ }
+ let selectedText = synthesizeQuerySelectedText();
+ if (!checkQueryContentResult(selectedText, aMessage +
+ ": synthesizeQuerySelectedText " + aID)) {
+ return false;
+ }
+ is(selectedText.offset, aExpectedOffset,
+ aMessage + ": selection offset is wrong " + aID);
+ is(selectedText.text, aExpectedText,
+ aMessage + ": selected text is wrong " + aID);
+ return selectedText.offset == aExpectedOffset &&
+ selectedText.text == aExpectedText;
+}
+
+function checkIMESelection(aSelectionType, aExpectedFound, aExpectedOffset, aExpectedText, aMessage, aID)
+{
+ if (!aID) {
+ aID = "";
+ }
+ aMessage += " (" + aSelectionType + ")";
+ let selectionType = 0;
+ switch (aSelectionType) {
+ case "RawClause":
+ selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_RAWINPUT;
+ break;
+ case "SelectedRawClause":
+ selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDRAWTEXT;
+ break;
+ case "ConvertedClause":
+ selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_CONVERTEDTEXT;
+ break;
+ case "SelectedClause":
+ selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDCONVERTEDTEXT;
+ break;
+ default:
+ ok(false, aMessage + ": invalid selection type, " + aSelectionType);
+ }
+ isnot(selectionType, 0, aMessage + ": wrong value");
+ let selectedText = synthesizeQuerySelectedText(selectionType);
+ if (!checkQueryContentResult(selectedText, aMessage +
+ ": synthesizeQuerySelectedText " + aID)) {
+ return false;
+ }
+ is(selectedText.notFound, !aExpectedFound,
+ aMessage + ": selection should " + (aExpectedFound ? "" : "not") + " be found " + aID);
+ if (selectedText.notFound) {
+ return selectedText.notFound == !aExpectedFound;
+ }
+
+ is(selectedText.offset, aExpectedOffset,
+ aMessage + ": selection offset is wrong " + aID);
+ is(selectedText.text, aExpectedText,
+ aMessage + ": selected text is wrong " + aID);
+ return selectedText.offset == aExpectedOffset &&
+ selectedText.text == aExpectedText;
+}
+
+function checkRect(aRect, aExpectedRect, aMessage)
+{
+ is(aRect.left, aExpectedRect.left, aMessage + ": left is wrong");
+ is(aRect.top, aExpectedRect.top, aMessage + " top is wrong");
+ is(aRect.width, aExpectedRect.width, aMessage + ": width is wrong");
+ is(aRect.height, aExpectedRect.height, aMessage + ": height is wrong");
+ return aRect.left == aExpectedRect.left &&
+ aRect.top == aExpectedRect.top &&
+ aRect.width == aExpectedRect.width &&
+ aRect.height == aExpectedRect.height;
+}
+
+function checkRectFuzzy(aRect, aExpectedRect, aEpsilon, aMessage) {
+ isfuzzy(aRect.left, aExpectedRect.left, aEpsilon.left, aMessage + ": left is wrong");
+ isfuzzy(aRect.top, aExpectedRect.top, aEpsilon.top, aMessage + " top is wrong");
+ isfuzzy(aRect.width, aExpectedRect.width, aEpsilon.width, aMessage + ": width is wrong");
+ isfuzzy(aRect.height, aExpectedRect.height, aEpsilon.height, aMessage + ": height is wrong");
+ return aRect.left <= aExpectedRect.left - aEpsilon.left &&
+ aRect.left <= aExpectedRect.left + aEpsilon.left &&
+ aRect.top == aExpectedRect.top - aEpsilon.top &&
+ aRect.top == aExpectedRect.top + aEpsilon.top &&
+ aRect.width == aExpectedRect.width - aEpsilon.width &&
+ aRect.width == aExpectedRect.width + aEpsilon.width &&
+ aRect.height == aExpectedRect.height - aEpsilon.height &&
+ aRect.height == aExpectedRect.height + aEpsilon.height;
+}
+
+function checkRectArray(aQueryTextRectArrayResult, aExpectedTextRectArray, aMessage)
+{
+ for (let i = 1; i < aExpectedTextRectArray.length; ++i) {
+ let rect = { left: {}, top: {}, width: {}, height: {} };
+ try {
+ aQueryTextRectArrayResult.getCharacterRect(i, rect.left, rect.top, rect.width, rect.height);
+ } catch (e) {
+ ok(false, aMessage + ": failed to retrieve " + i + "th rect (" + e + ")");
+ return false;
+ }
+ function toRect(aRect)
+ {
+ return { left: aRect.left.value, top: aRect.top.value, width: aRect.width.value, height: aRect.height.value };
+ }
+ if (!checkRect(toRect(rect), aExpectedTextRectArray[i], aMessage + " " + i + "th rect")) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function checkRectContainsRect(aRect, aContainer, aMessage)
+{
+ let container = { left: Math.ceil(aContainer.left),
+ top: Math.ceil(aContainer.top),
+ width: Math.floor(aContainer.width),
+ height: Math.floor(aContainer.height) };
+
+ let ret = container.left <= aRect.left &&
+ container.top <= aRect.top &&
+ container.left + container.width >= aRect.left + aRect.width &&
+ container.top + container.height >= aRect.top + aRect.height;
+ ret = ret && aMessage;
+ ok(ret, aMessage + " container={ left=" + container.left + ", top=" +
+ container.top + ", width=" + container.width + ", height=" +
+ container.height + " } rect={ left=" + aRect.left + ", top=" + aRect.top +
+ ", width=" + aRect.width + ", height=" + aRect.height + " }");
+ return ret;
+}
+
+// eslint-disable-next-line complexity
+function runUndoRedoTest()
+{
+ textarea.value = "";
+ textarea.focus();
+
+ // input raw characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306D",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "," },
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306D\u3053",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: "b" },
+ });
+
+ // convert
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u732B",
+ "clauses":
+ [
+ { "length": 1,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: " " },
+ });
+
+ // commit
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ // input raw characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u307E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "j" },
+ });
+
+ // cancel the composition
+ synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Escape" } });
+
+ // input raw characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3080",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "]" },
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3080\u3059",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: "r" },
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3080\u3059\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 },
+ "key": { key: "/" },
+ });
+
+ // convert
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u5A18",
+ "clauses":
+ [
+ { "length": 1,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: " " },
+ });
+
+ // commit
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ sendString(" meant");
+ synthesizeKey("KEY_Backspace");
+ synthesizeKey("s \"cat-girl\". She is a ");
+
+ // input raw characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3088",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "9" },
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3088\u3046",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: "4" },
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3088\u3046\u304b",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 },
+ "key": { key: "t" },
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3088\u3046\u304b\u3044",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 },
+ "key": { key: "e" },
+ });
+
+ // convert
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u5996\u602a",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: " " },
+ });
+
+ // commit
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "Enter" } });
+
+ synthesizeKey("KEY_Backspace", {repeat: 12});
+
+ let i = 0;
+ if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a \u5996\u602A",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(32, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a ",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(30, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("\u732B\u5A18 mean",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(7, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("\u732B\u5A18 meant",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(8, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("\u732B\u5A18",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(2, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("\u732B",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ // XXX this is unexpected behavior, see bug 258291
+ if (!checkContent("\u732B",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(0, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(0, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ // XXX this is unexpected behavior, see bug 258291
+ if (!checkContent("\u732B",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B\u5A18",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(2, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B\u5A18 meant",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(8, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B\u5A18 mean",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(7, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a ",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(30, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a \u5996\u602A",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(32, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
+ // eslint-disable-next-line no-useless-return
+ return;
+ }
+}
+
+function checkInputEvent(aEvent, aIsComposing, aInputType, aData, aTargetRanges, aDescription) {
+ if (aEvent.type !== "input" && aEvent.type !== "beforeinput") {
+ throw new Error(`${aDescription}: "${aEvent.type}" is not InputEvent`);
+ }
+ ok(aEvent instanceof InputEvent, `"${aEvent.type}" event should be dispatched with InputEvent interface: ${aDescription}`);
+ let cancelable = aEvent.type === "beforeinput" &&
+ aInputType !== "insertCompositionText" &&
+ aInputType !== "deleteCompositionText";
+ is(aEvent.cancelable, cancelable, `"${aEvent.type}" event should ${cancelable ? "be" : "be never"} cancelable: ${aDescription}`);
+ is(aEvent.bubbles, true, `"${aEvent.type}" event should always bubble: ${aDescription}`);
+ is(aEvent.isComposing, aIsComposing, `isComposing of "${aEvent.type}" event should be ${aIsComposing}: ${aDescription}`);
+ is(aEvent.inputType, aInputType, `inputType of "${aEvent.type}" event should be "${aInputType}": ${aDescription}`);
+ is(aEvent.data, aData, `data of "${aEvent.type}" event should be ${aData}: ${aDescription}`);
+ is(aEvent.dataTransfer, null, `dataTransfer of "${aEvent.type}" event should be null: ${aDescription}`);
+ let targetRanges = aEvent.getTargetRanges();
+ if (aTargetRanges.length === 0) {
+ is(targetRanges.length, 0,
+ `getTargetRange() of "${aEvent.type}" event should return empty array: ${aDescription}`);
+ } else {
+ is(targetRanges.length, aTargetRanges.length,
+ `getTargetRange() of "${aEvent.type}" event should return static range array: ${aDescription}`);
+ if (targetRanges.length == aTargetRanges.length) {
+ for (let i = 0; i < targetRanges.length; i++) {
+ is(targetRanges[i].startContainer, aTargetRanges[i].startContainer,
+ `startContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
+ is(targetRanges[i].startOffset, aTargetRanges[i].startOffset,
+ `startOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
+ is(targetRanges[i].endContainer, aTargetRanges[i].endContainer,
+ `endContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
+ is(targetRanges[i].endOffset, aTargetRanges[i].endOffset,
+ `endOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
+ }
+ }
+ }
+}
+
+function runCompositionCommitAsIsTest()
+{
+ textarea.focus();
+
+ let result = [];
+ function clearResult()
+ {
+ result = [];
+ }
+
+ function handler(aEvent)
+ {
+ result.push(aEvent);
+ }
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", handler, true);
+ textarea.addEventListener("beforeinput", handler, true);
+ textarea.addEventListener("input", handler, true);
+ textarea.addEventListener("text", handler, true);
+
+ // compositioncommitasis with composing string.
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #1");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "Enter" } });
+
+ is(result.length, 4,
+ "runCompositionCommitAsIsTest: 4 events should be fired after dispatching compositioncommitasis #1");
+ is(result[0].type, "text",
+ "runCompositionCommitAsIsTest: text should be fired after dispatching compositioncommitasis because it's dispatched when there is composing string #1");
+ is(result[1].type, "beforeinput",
+ "runCompositionCommitAsIsTest: beforeinput should be fired after dispatching compositioncommitasis because it's dispatched when there is composing string #1");
+ checkInputEvent(result[1], true, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #1");
+ is(result[2].type, "compositionend",
+ "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #1");
+ is(result[3].type, "input",
+ "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #1");
+ checkInputEvent(result[3], false, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #1");
+ is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #1");
+
+ // compositioncommitasis with committed string.
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #2");
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "KEY_Enter", type: "keydown" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter", type: "keyup" } });
+
+ is(result.length, 2,
+ "runCompositionCommitAsIsTest: 2 events should be fired after dispatching compositioncommitasis #2");
+ // XXX Do we need a "beforeinput" event here? Not sure.
+ is(result[0].type, "compositionend",
+ "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #2");
+ is(result[1].type, "input",
+ "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #2");
+ checkInputEvent(result[1], false, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #2");
+ is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");
+
+ // compositioncommitasis with committed string.
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #3");
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "KEY_Escape", type: "keydown" },
+ });
+ is(textarea.value, "", "runCompositionCommitAsIsTest: textarea has non-empty composition string #3");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape", type: "keyup" } });
+
+ is(result.length, 2,
+ "runCompositionCommitAsIsTest: 2 events should be fired after dispatching compositioncommitasis #3");
+ // XXX Do we need a "beforeinput" event here? Not sure.
+ is(result[0].type, "compositionend",
+ "runCompositionCommitAsIsTest: compositionend shouldn't be fired after dispatching compositioncommitasis #3");
+ is(result[1].type, "input",
+ "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #3");
+ checkInputEvent(result[1], false, "insertCompositionText", "", [],
+ "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #3");
+ is(textarea.value, "", "runCompositionCommitAsIsTest: textarea doesn't have committed string #3");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+ textarea.removeEventListener("beforeinput", handler, true);
+ textarea.removeEventListener("input", handler, true);
+ textarea.removeEventListener("text", handler, true);
+}
+
+function runCompositionCommitTest()
+{
+ textarea.focus();
+
+ let result = [];
+ function clearResult()
+ {
+ result = [];
+ }
+
+ function handler(aEvent)
+ {
+ result.push(aEvent);
+ }
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", handler, true);
+ textarea.addEventListener("beforeinput", handler, true);
+ textarea.addEventListener("input", handler, true);
+ textarea.addEventListener("text", handler, true);
+
+ // compositioncommit with different composing string.
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a", type: "keydown" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #1");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "a", type: "keyup" } });
+
+ is(result.length, 5,
+ "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #1");
+ is(result[0].type, "compositionupdate",
+ "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit because it's dispatched when there is composing string #1");
+ is(result[1].type, "text",
+ "runCompositionCommitTest: text should be fired after dispatching compositioncommit #1");
+ is(result[2].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit because it's dispatched when there is composing string #1");
+ checkInputEvent(result[2], true, "insertCompositionText", "\u3043", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #1");
+ is(result[3].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #1");
+ is(result[4].type, "input",
+ "runCompositionCommitTest: input should be fired after dispatching compositioncommit #1");
+ checkInputEvent(result[4], false, "insertCompositionText", "\u3043", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #1");
+ is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #1");
+
+ // compositioncommit with different committed string when there is already committed string
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #2");
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "KEY_Enter", type: "keydown" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have committed string #2");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "KEY_Enter", type: "keyup" } });
+
+ is(result.length, 5,
+ "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #2");
+ is(result[0].type, "compositionupdate",
+ "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #2");
+ is(result[1].type, "text",
+ "runCompositionCommitTest: text should be fired after dispatching compositioncommit #2");
+ is(result[2].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #2");
+ checkInputEvent(result[2], true, "insertCompositionText", "\u3043", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #2");
+ is(result[3].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #2");
+ is(result[4].type, "input",
+ "runCompositionCommitTest: input should be fired after dispatching compositioncommit #2");
+ checkInputEvent(result[4], false, "insertCompositionText", "\u3043", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #2");
+ is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #2");
+
+ // compositioncommit with empty composition string.
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #3");
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "KEY_Enter", type: "keydown" },
+ });
+ is(textarea.value, "", "runCompositionCommitTest: textarea has non-empty composition string #3");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "KEY_Enter", type: "keyup" } });
+
+ is(result.length, 5,
+ "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #3");
+ is(result[0].type, "compositionupdate",
+ "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #3");
+ is(result[1].type, "text",
+ "runCompositionCommitTest: text should be fired after dispatching compositioncommit #3");
+ is(result[2].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #3");
+ checkInputEvent(result[2], true, "insertCompositionText", "\u3043", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #3");
+ is(result[3].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #3");
+ is(result[4].type, "input",
+ "runCompositionCommitTest: input should be fired after dispatching compositioncommit #3");
+ checkInputEvent(result[4], false, "insertCompositionText", "\u3043", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #3");
+ is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #3");
+
+ // inserting empty string with simple composition.
+ textarea.value = "abc";
+ textarea.setSelectionRange(3, 3);
+ synthesizeComposition({ type: "compositionstart" });
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "" });
+
+ is(result.length, 4,
+ "runCompositionCommitTest: 4 events should be fired when inserting empty string with composition");
+ is(result[0].type, "text",
+ "runCompositionCommitTest: text should be fired when inserting empty string with composition");
+ is(result[1].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired when inserting empty string with composition");
+ checkInputEvent(result[1], true, "insertCompositionText", "", [],
+ "runCompositionCommitTest: when inserting empty string with composition");
+ is(result[2].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired when inserting empty string with composition");
+ is(result[3].type, "input",
+ "runCompositionCommitTest: input should be fired when inserting empty string with composition");
+ checkInputEvent(result[3], false, "insertCompositionText", "", [],
+ "runCompositionCommitTest: when inserting empty string with composition");
+ is(textarea.value, "abc",
+ "runCompositionCommitTest: textarea should keep original value when inserting empty string with composition");
+
+ // replacing selection with empty string with simple composition.
+ textarea.value = "abc";
+ textarea.setSelectionRange(0, 3);
+ synthesizeComposition({ type: "compositionstart" });
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "" });
+
+ is(result.length, 4,
+ "runCompositionCommitTest: 4 events should be fired when replacing with empty string with composition");
+ is(result[0].type, "text",
+ "runCompositionCommitTest: text should be fired when replacing with empty string with composition");
+ is(result[1].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired when replacing with empty string with composition");
+ checkInputEvent(result[1], true, "insertCompositionText", "", [],
+ "runCompositionCommitTest: when replacing with empty string with composition");
+ is(result[2].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired when replacing with empty string with composition");
+ is(result[3].type, "input",
+ "runCompositionCommitTest: input should be fired when replacing with empty string with composition");
+ checkInputEvent(result[3], false, "insertCompositionText", "", [],
+ "runCompositionCommitTest: when replacing with empty string with composition");
+ is(textarea.value, "",
+ "runCompositionCommitTest: textarea should become empty when replacing selection with empty string with composition");
+
+ // replacing selection with same string with simple composition.
+ textarea.value = "abc";
+ textarea.setSelectionRange(0, 3);
+ synthesizeComposition({ type: "compositionstart" });
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "abc" });
+
+ is(result.length, 5,
+ "runCompositionCommitTest: 5 events should be fired when replacing selection with same string with composition");
+ is(result[0].type, "compositionupdate",
+ "runCompositionCommitTest: compositionupdate should be fired when replacing selection with same string with composition");
+ is(result[1].type, "text",
+ "runCompositionCommitTest: text should be fired when replacing selection with same string with composition");
+ is(result[2].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired when replacing selection with same string with composition");
+ checkInputEvent(result[2], true, "insertCompositionText", "abc", [],
+ "runCompositionCommitTest: when replacing selection with same string with composition");
+ is(result[3].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired when replacing selection with same string with composition");
+ is(result[4].type, "input",
+ "runCompositionCommitTest: input should be fired when replacing selection with same string with composition");
+ checkInputEvent(result[4], false, "insertCompositionText", "abc", [],
+ "runCompositionCommitTest: when replacing selection with same string with composition");
+ is(textarea.value, "abc",
+ "runCompositionCommitTest: textarea should keep same value when replacing selection with same string with composition");
+
+ // compositioncommit with non-empty composition string.
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #4");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Enter" } });
+
+ is(result.length, 5,
+ "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #4");
+ is(result[0].type, "compositionupdate",
+ "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #4");
+ is(result[1].type, "text",
+ "runCompositionCommitTest: text should be fired after dispatching compositioncommit #4");
+ is(result[2].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #4");
+ checkInputEvent(result[2], true, "insertCompositionText", "", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #4");
+ is(result[3].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #4");
+ is(result[4].type, "input",
+ "runCompositionCommitTest: input should be fired after dispatching compositioncommit #4");
+ checkInputEvent(result[4], false, "insertCompositionText", "", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #4");
+ is(textarea.value, "", "runCompositionCommitTest: textarea should be empty #4");
+
+ // compositioncommit immediately without compositionstart
+ textarea.value = "";
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "a" } });
+
+ is(result.length, 5,
+ "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #5");
+ is(result[0].type, "compositionupdate",
+ "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #5");
+ is(result[1].type, "text",
+ "runCompositionCommitTest: text should be fired after dispatching compositioncommit #5");
+ is(result[2].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #5");
+ checkInputEvent(result[2], true, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #5");
+ is(result[3].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #5");
+ is(result[4].type, "input",
+ "runCompositionCommitTest: input should be fired after dispatching compositioncommit #5");
+ checkInputEvent(result[4], false, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #5");
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should be empty #5");
+
+ // compositioncommit with same composition string.
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #5");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter" } });
+
+ is(result.length, 4,
+ "runCompositionCommitTest: 4 events should be fired after dispatching compositioncommit #6");
+ is(result[0].type, "text",
+ "runCompositionCommitTest: text should be fired after dispatching compositioncommit #6");
+ is(result[1].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #6");
+ checkInputEvent(result[1], true, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #6");
+ is(result[2].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #6");
+ is(result[3].type, "input",
+ "runCompositionCommitTest: input should be fired after dispatching compositioncommit #6");
+ checkInputEvent(result[3], false, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #6");
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");
+
+ // compositioncommit with same composition string when there is committed string
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #6");
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "KEY_Enter", type: "keydown" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #6");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter", type: "keyup" } });
+
+ is(result.length, 2,
+ "runCompositionCommitTest: 2 events should be fired after dispatching compositioncommit #7");
+ // XXX Do we need a "beforeinput" event here? Not sure.
+ is(result[0].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #7");
+ is(result[1].type, "input",
+ "runCompositionCommitTest: input should be fired after dispatching compositioncommit #7");
+ checkInputEvent(result[1], false, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #7");
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+ textarea.removeEventListener("beforeinput", handler, true);
+ textarea.removeEventListener("input", handler, true);
+ textarea.removeEventListener("text", handler, true);
+}
+
+// eslint-disable-next-line complexity
+function runCompositionTest()
+{
+ textarea.value = "";
+ textarea.focus();
+ let caretRects = [];
+
+ let caretRect = synthesizeQueryCaretRect(0);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #0")) {
+ return;
+ }
+ caretRects[0] = caretRect;
+
+ // input first character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "o" },
+ });
+
+ if (!checkContent("\u3089", "runCompositionTest", "#1-1") ||
+ !checkSelection(1, "", "runCompositionTest", "#1-1")) {
+ return;
+ }
+
+ caretRect = synthesizeQueryCaretRect(1);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #1-1")) {
+ return;
+ }
+ caretRects[1] = caretRect;
+
+ // input second character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: "\\", code: "IntlYen", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
+ });
+
+ if (!checkContent("\u3089\u30FC", "runCompositionTest", "#1-2") ||
+ !checkSelection(2, "", "runCompositionTest", "#1-2")) {
+ return;
+ }
+
+ caretRect = synthesizeQueryCaretRect(2);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #1-2")) {
+ return;
+ }
+ caretRects[2] = caretRect;
+
+ isnot(caretRects[2].left, caretRects[1].left,
+ "runCompositionTest: caret isn't moved (#1-2)");
+ is(caretRects[2].top, caretRects[1].top,
+ "runCompositionTest: caret is moved to another line (#1-2)");
+ is(caretRects[2].width, caretRects[1].width,
+ "runCompositionTest: caret width is wrong (#1-2)");
+ is(caretRects[2].height, caretRects[1].height,
+ "runCompositionTest: caret width is wrong (#1-2)");
+
+ // input third character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 },
+ "key": { key: "/" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3") ||
+ !checkSelection(3, "", "runCompositionTest", "#1-3")) {
+ return;
+ }
+
+ caretRect = synthesizeQueryCaretRect(3);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #1-3")) {
+ return;
+ }
+ caretRects[3] = caretRect;
+
+ isnot(caretRects[3].left, caretRects[2].left,
+ "runCompositionTest: caret isn't moved (#1-3)");
+ is(caretRects[3].top, caretRects[2].top,
+ "runCompositionTest: caret is moved to another line (#1-3)");
+ is(caretRects[3].width, caretRects[2].width,
+ "runCompositionTest: caret width is wrong (#1-3)");
+ is(caretRects[3].height, caretRects[2].height,
+ "runCompositionTest: caret height is wrong (#1-3)");
+
+ // moves the caret left
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: "KEY_ArrowLeft" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3-1") ||
+ !checkSelection(2, "", "runCompositionTest", "#1-3-1")) {
+ return;
+ }
+
+
+ caretRect = synthesizeQueryCaretRect(2);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #1-3-1")) {
+ return;
+ }
+
+ is(caretRect.left, caretRects[2].left,
+ "runCompositionTest: caret rects are different (#1-3-1, left)");
+ is(caretRect.top, caretRects[2].top,
+ "runCompositionTest: caret rects are different (#1-3-1, top)");
+ // by bug 335359, the caret width depends on the right side's character.
+ is(caretRect.width, caretRects[2].width + Math.round(window.devicePixelRatio),
+ "runCompositionTest: caret rects are different (#1-3-1, width)");
+ is(caretRect.height, caretRects[2].height,
+ "runCompositionTest: caret rects are different (#1-3-1, height)");
+
+ // moves the caret left
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "KEY_ArrowLeft" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3-2") ||
+ !checkSelection(1, "", "runCompositionTest", "#1-3-2")) {
+ return;
+ }
+
+
+ caretRect = synthesizeQueryCaretRect(1);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #1-3-2")) {
+ return;
+ }
+
+ is(caretRect.left, caretRects[1].left,
+ "runCompositionTest: caret rects are different (#1-3-2, left)");
+ is(caretRect.top, caretRects[1].top,
+ "runCompositionTest: caret rects are different (#1-3-2, top)");
+ // by bug 335359, the caret width depends on the right side's character.
+ is(caretRect.width, caretRects[1].width + Math.round(window.devicePixelRatio),
+ "runCompositionTest: caret rects are different (#1-3-2, width)");
+ is(caretRect.height, caretRects[1].height,
+ "runCompositionTest: caret rects are different (#1-3-2, height)");
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 },
+ "key": { key: "y" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093", "runCompositionTest", "#1-4") ||
+ !checkSelection(4, "", "runCompositionTest", "#1-4")) {
+ return;
+ }
+
+
+ // backspace
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 },
+ "key": { key: "KEY_Backspace" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-5") ||
+ !checkSelection(3, "", "runCompositionTest", "#1-5")) {
+ return;
+ }
+
+ // re-input
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 },
+ "key": { key: "y" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093", "runCompositionTest", "#1-6") ||
+ !checkSelection(4, "", "runCompositionTest", "#1-6")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055",
+ "clauses":
+ [
+ { "length": 5, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 5, "length": 0 },
+ "key": { key: "x" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055", "runCompositionTest", "#1-7") ||
+ !checkSelection(5, "", "runCompositionTest", "#1-7")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044",
+ "clauses":
+ [
+ { "length": 6, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 6, "length": 0 },
+ "key": { key: "e" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044", "runCompositionTest", "#1-8") ||
+ !checkSelection(6, "", "runCompositionTest", "#1-8")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
+ "clauses":
+ [
+ { "length": 7, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 7, "length": 0 },
+ "key": { key: "b" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053", "runCompositionTest", "#1-8") ||
+ !checkSelection(7, "", "runCompositionTest", "#1-8")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
+ "clauses":
+ [
+ { "length": 8, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 8, "length": 0 },
+ "key": { key: "4" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
+ "runCompositionTest", "#1-9") ||
+ !checkSelection(8, "", "runCompositionTest", "#1-9")) {
+ return;
+ }
+
+ // convert
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
+ "clauses":
+ [
+ { "length": 4,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 2,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 },
+ "key": { key: " " },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
+ "runCompositionTest", "#1-10") ||
+ !checkSelection(4, "", "runCompositionTest", "#1-10")) {
+ return;
+ }
+
+ // change the selected clause
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
+ "clauses":
+ [
+ { "length": 4,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": 2,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 6, "length": 0 },
+ "key": { key: "KEY_ArrowLeft", shiftKey: true },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
+ "runCompositionTest", "#1-11") ||
+ !checkSelection(6, "", "runCompositionTest", "#1-11")) {
+ return;
+ }
+
+ // reset clauses
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
+ "clauses":
+ [
+ { "length": 5,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 3,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 5, "length": 0 },
+ "key": { key: "KEY_ArrowRight" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
+ "runCompositionTest", "#1-12") ||
+ !checkSelection(5, "", "runCompositionTest", "#1-12")) {
+ return;
+ }
+
+
+ let textRect1 = synthesizeQueryTextRect(0, 1);
+ let textRect2 = synthesizeQueryTextRect(1, 1);
+ if (!checkQueryContentResult(textRect1,
+ "runCompositionTest: synthesizeQueryTextRect #1-12-1") ||
+ !checkQueryContentResult(textRect2,
+ "runCompositionTest: synthesizeQueryTextRect #1-12-2")) {
+ return;
+ }
+
+ // commit the composition string
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
+ "runCompositionTest", "#1-13") ||
+ !checkSelection(8, "", "runCompositionTest", "#1-13")) {
+ return;
+ }
+
+ let textRect3 = synthesizeQueryTextRect(0, 1);
+ let textRect4 = synthesizeQueryTextRect(1, 1);
+
+ if (!checkQueryContentResult(textRect3,
+ "runCompositionTest: synthesizeQueryTextRect #1-13-1") ||
+ !checkQueryContentResult(textRect4,
+ "runCompositionTest: synthesizeQueryTextRect #1-13-2")) {
+ return;
+ }
+
+ checkRect(textRect3, textRect1, "runCompositionTest: textRect #1-13-1");
+ checkRect(textRect4, textRect2, "runCompositionTest: textRect #1-13-2");
+
+ // restart composition and input characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3057",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "d" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3057",
+ "runCompositionTest", "#2-1") ||
+ !checkSelection(8 + 1, "", "runCompositionTest", "#2-1")) {
+ return;
+ }
+
+ let textRect3QueriedWithRelativeOffset = synthesizeQueryTextRect(-8, 1, true);
+ let textRect4QueriedWithRelativeOffset = synthesizeQueryTextRect(-8 + 1, 1, true);
+ checkRect(textRect3QueriedWithRelativeOffset, textRect3, "runCompositionTest: textRect #2-1-2");
+ checkRect(textRect4QueriedWithRelativeOffset, textRect4, "runCompositionTest: textRect #2-1-3");
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3058",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "r" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058",
+ "runCompositionTest", "#2-2") ||
+ !checkSelection(8 + 1, "", "runCompositionTest", "#2-2")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3058\u3087",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: ")", code: "Digit9", keyCode: KeyboardEvent.DOM_VK_9, shiftKey: true },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087",
+ "runCompositionTest", "#2-3") ||
+ !checkSelection(8 + 2, "", "runCompositionTest", "#2-3")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3058\u3087\u3046",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 },
+ "key": { key: "4" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087\u3046",
+ "runCompositionTest", "#2-4") ||
+ !checkSelection(8 + 3, "", "runCompositionTest", "#2-4")) {
+ return;
+ }
+
+ // commit the composition string
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087\u3046",
+ "runCompositionTest", "#2-4") ||
+ !checkSelection(8 + 3, "", "runCompositionTest", "#2-4")) {
+ return;
+ }
+
+ // set selection
+ let selectionSetTest = synthesizeSelectionSet(4, 7, false);
+ ok(selectionSetTest, "runCompositionTest: selectionSetTest failed");
+
+ if (!checkSelection(4, "\u3055\u884C\u3053\u3046\u3058\u3087\u3046", "runCompositionTest", "#3-1")) {
+ return;
+ }
+
+ // start composition with selection
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u304A",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "6" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u304A",
+ "runCompositionTest", "#3-2") ||
+ !checkSelection(4 + 1, "", "runCompositionTest", "#3-2")) {
+ return;
+ }
+
+ // remove the composition string
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "KEY_Backspace" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#3-3") ||
+ !checkSelection(4, "", "runCompositionTest", "#3-3")) {
+ return;
+ }
+
+ // re-input the composition string
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3046",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "4" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3046",
+ "runCompositionTest", "#3-4") ||
+ !checkSelection(4 + 1, "", "runCompositionTest", "#3-4")) {
+ return;
+ }
+
+ // cancel the composition
+ synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Escape" } });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#3-5") ||
+ !checkSelection(4, "", "runCompositionTest", "#3-5")) {
+ return;
+ }
+
+ // bug 271815, some Chinese IMEs for Linux make empty composition string
+ // and compty clause information when it lists up Chinese characters on
+ // its candidate window.
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "a" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#4-1") ||
+ !checkSelection(4, "", "runCompositionTest", "#4-1")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "b" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#4-2") ||
+ !checkSelection(4, "", "runCompositionTest", "#4-2")) {
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommit", data: "\u6700", key: { key: "KEY_Enter" } });
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
+ "runCompositionTest", "#4-3") ||
+ !checkSelection(5, "", "runCompositionTest", "#4-3")) {
+ return;
+ }
+
+ // testing the canceling case
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "a" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
+ "runCompositionTest", "#4-5") ||
+ !checkSelection(5, "", "runCompositionTest", "#4-5")) {
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape" } });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
+ "runCompositionTest", "#4-6") ||
+ !checkSelection(5, "", "runCompositionTest", "#4-6")) {
+ return;
+ }
+
+ // testing whether the empty composition string deletes selected string.
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "a" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#4-8") ||
+ !checkSelection(4, "", "runCompositionTest", "#4-8")) {
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommit", data: "\u9AD8", key: { key: "KEY_Enter" } });
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u9AD8",
+ "runCompositionTest", "#4-9") ||
+ !checkSelection(5, "", "runCompositionTest", "#4-9")) {
+ return;
+ }
+
+ synthesizeKey("KEY_Backspace");
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#4-11") ||
+ !checkSelection(4, "", "runCompositionTest", "#4-11")) {
+ return;
+ }
+
+ // bug 23558, ancient Japanese IMEs on Window may send empty text event
+ // twice at canceling composition.
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u6700",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
+ "runCompositionTest", "#5-1") ||
+ !checkSelection(4 + 1, "", "runCompositionTest", "#5-1")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "KEY_Backspace", type: "keydown" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#5-2") ||
+ !checkSelection(4, "", "runCompositionTest", "#5-2")) {
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Backspace", type: "keyup" } });
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#5-3") ||
+ !checkSelection(4, "", "runCompositionTest", "#5-3")) {
+ return;
+ }
+
+ // Undo tests for the testcases for bug 23558 and bug 271815
+ synthesizeKey("z", { accelKey: true });
+
+ // XXX this is unexpected behavior, see bug 258291
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#6-1") ||
+ !checkSelection(4, "", "runCompositionTest", "#6-1")) {
+ return;
+ }
+
+ synthesizeKey("z", { accelKey: true });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u9AD8",
+ "runCompositionTest", "#6-2") ||
+ !checkSelection(5, "", "runCompositionTest", "#6-2")) {
+ return;
+ }
+
+ synthesizeKey("z", { accelKey: true });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
+ "runCompositionTest", "#6-3") ||
+ !checkSelection(4, "\u6700", "runCompositionTest", "#6-3")) {
+ return;
+ }
+
+ synthesizeKey("z", { accelKey: true });
+
+ // XXX this is unexpected behavior, see bug 258291
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
+ "runCompositionTest", "#6-4") ||
+ !checkSelection(5, "", "runCompositionTest", "#6-4")) {
+ return;
+ }
+
+ synthesizeKey("z", { accelKey: true });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#6-5") ||
+ !checkSelection(4, "", "runCompositionTest", "#6-5")) {
+ // eslint-disable-next-line no-useless-return
+ return;
+ }
+}
+
+function runCompositionEventTest()
+{
+ const kDescription = "runCompositionEventTest: ";
+ const kEvents = ["compositionstart", "compositionupdate", "compositionend",
+ "input"];
+
+ input.value = "";
+ input.focus();
+
+ let windowEventCounts = [], windowEventData = [], windowEventLocale = [];
+ let inputEventCounts = [], inputEventData = [], inputEventLocale = [];
+ let preventDefault = false;
+ let stopPropagation = false;
+
+ function initResults()
+ {
+ for (let i = 0; i < kEvents.length; i++) {
+ windowEventCounts[kEvents[i]] = 0;
+ windowEventData[kEvents[i]] = "";
+ windowEventLocale[kEvents[i]] = "";
+ inputEventCounts[kEvents[i]] = 0;
+ inputEventData[kEvents[i]] = "";
+ inputEventLocale[kEvents[i]] = "";
+ }
+ }
+
+ function compositionEventHandlerForWindow(aEvent)
+ {
+ windowEventCounts[aEvent.type]++;
+ windowEventData[aEvent.type] = aEvent.data;
+ windowEventLocale[aEvent.type] = aEvent.locale;
+ if (preventDefault) {
+ aEvent.preventDefault();
+ }
+ if (stopPropagation) {
+ aEvent.stopPropagation();
+ }
+ }
+
+ function formEventHandlerForWindow(aEvent)
+ {
+ ok(aEvent.isTrusted, "input events must be trusted events");
+ windowEventCounts[aEvent.type]++;
+ windowEventData[aEvent.type] = input.value;
+ }
+
+ function compositionEventHandlerForInput(aEvent)
+ {
+ inputEventCounts[aEvent.type]++;
+ inputEventData[aEvent.type] = aEvent.data;
+ inputEventLocale[aEvent.type] = aEvent.locale;
+ if (preventDefault) {
+ aEvent.preventDefault();
+ }
+ if (stopPropagation) {
+ aEvent.stopPropagation();
+ }
+ }
+
+ function formEventHandlerForInput(aEvent)
+ {
+ inputEventCounts[aEvent.type]++;
+ inputEventData[aEvent.type] = input.value;
+ }
+
+ window.addEventListener("compositionstart", compositionEventHandlerForWindow,
+ true, true);
+ window.addEventListener("compositionend", compositionEventHandlerForWindow,
+ true, true);
+ window.addEventListener("compositionupdate", compositionEventHandlerForWindow,
+ true, true);
+ window.addEventListener("input", formEventHandlerForWindow,
+ true, true);
+
+ input.addEventListener("compositionstart", compositionEventHandlerForInput,
+ true, true);
+ input.addEventListener("compositionend", compositionEventHandlerForInput,
+ true, true);
+ input.addEventListener("compositionupdate", compositionEventHandlerForInput,
+ true, true);
+ input.addEventListener("input", formEventHandlerForInput,
+ true, true);
+
+ // test for normal case
+ initResults();
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "o" },
+ });
+
+ is(windowEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by window #1");
+ is(windowEventData.compositionstart, "",
+ kDescription + "data of compositionstart isn't empty (window) #1");
+ is(windowEventLocale.compositionstart, "",
+ kDescription + "locale of compositionstart isn't empty (window) #1");
+ is(inputEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by input #1");
+ is(inputEventData.compositionstart, "",
+ kDescription + "data of compositionstart isn't empty (input) #1");
+ is(inputEventLocale.compositionstart, "",
+ kDescription + "locale of compositionstart isn't empty (input) #1");
+
+ is(windowEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by window #1");
+ is(windowEventData.compositionupdate, "\u3089",
+ kDescription + "data of compositionupdate doesn't match (window) #1");
+ is(windowEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (window) #1");
+ is(inputEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by input #1");
+ is(inputEventData.compositionupdate, "\u3089",
+ kDescription + "data of compositionupdate doesn't match (input) #1");
+ is(inputEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (input) #1");
+
+ is(windowEventCounts.compositionend, 0,
+ kDescription + "compositionend has been handled by window #1");
+ is(inputEventCounts.compositionend, 0,
+ kDescription + "compositionend has been handled by input #1");
+
+ is(windowEventCounts.input, 1,
+ kDescription + "input hasn't been handled by window #1");
+ is(windowEventData.input, "\u3089",
+ kDescription + "value of input element wasn't modified (window) #1");
+ is(inputEventCounts.input, 1,
+ kDescription + "input hasn't been handled by input #1");
+ is(inputEventData.input, "\u3089",
+ kDescription + "value of input element wasn't modified (input) #1");
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: "\\", code: "IntlYen", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
+ });
+
+ is(windowEventCounts.compositionstart, 1,
+ kDescription + "compositionstart has been handled more than once by window #2");
+ is(inputEventCounts.compositionstart, 1,
+ kDescription + "compositionstart has been handled more than once by input #2");
+
+ is(windowEventCounts.compositionupdate, 2,
+ kDescription + "compositionupdate hasn't been handled by window #2");
+ is(windowEventData.compositionupdate, "\u3089\u30FC",
+ kDescription + "data of compositionupdate doesn't match (window) #2");
+ is(windowEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (window) #2");
+ is(inputEventCounts.compositionupdate, 2,
+ kDescription + "compositionupdate hasn't been handled by input #2");
+ is(inputEventData.compositionupdate, "\u3089\u30FC",
+ kDescription + "data of compositionupdate doesn't match (input) #2");
+ is(inputEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (input) #2");
+
+ is(windowEventCounts.compositionend, 0,
+ kDescription + "compositionend has been handled during composition by window #2");
+ is(inputEventCounts.compositionend, 0,
+ kDescription + "compositionend has been handled during composition by input #2");
+
+ is(windowEventCounts.input, 2,
+ kDescription + "input hasn't been handled by window #2");
+ is(windowEventData.input, "\u3089\u30FC",
+ kDescription + "value of input element wasn't modified (window) #2");
+ is(inputEventCounts.input, 2,
+ kDescription + "input hasn't been handled by input #2");
+ is(inputEventData.input, "\u3089\u30FC",
+ kDescription + "value of input element wasn't modified (input) #2");
+
+ // text event shouldn't cause composition update, e.g., at committing.
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ is(windowEventCounts.compositionstart, 1,
+ kDescription + "compositionstart has been handled more than once by window #3");
+ is(inputEventCounts.compositionstart, 1,
+ kDescription + "compositionstart has been handled more than once by input #3");
+
+ is(windowEventCounts.compositionupdate, 2,
+ kDescription + "compositionupdate has been fired unexpectedly on window #3");
+ is(inputEventCounts.compositionupdate, 2,
+ kDescription + "compositionupdate has been fired unexpectedly on input #3");
+
+ is(windowEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by window #3");
+ is(windowEventData.compositionend, "\u3089\u30FC",
+ kDescription + "data of compositionend doesn't match (window) #3");
+ is(windowEventLocale.compositionend, "",
+ kDescription + "locale of compositionend isn't empty (window) #3");
+ is(inputEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by input #3");
+ is(inputEventData.compositionend, "\u3089\u30FC",
+ kDescription + "data of compositionend doesn't match (input) #3");
+ is(inputEventLocale.compositionend, "",
+ kDescription + "locale of compositionend isn't empty (input) #3");
+
+ is(windowEventCounts.input, 3,
+ kDescription + "input hasn't been handled by window #3");
+ is(windowEventData.input, "\u3089\u30FC",
+ kDescription + "value of input element wasn't modified (window) #3");
+ is(inputEventCounts.input, 3,
+ kDescription + "input hasn't been handled by input #3");
+ is(inputEventData.input, "\u3089\u30FC",
+ kDescription + "value of input element wasn't modified (input) #3");
+
+ // select the second character, then, data of composition start should be
+ // the selected character.
+ initResults();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "o" },
+ });
+
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ is(windowEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by window #4");
+ is(windowEventData.compositionstart, "\u30FC",
+ kDescription + "data of compositionstart is empty (window) #4");
+ is(windowEventLocale.compositionstart, "",
+ kDescription + "locale of compositionstart isn't empty (window) #4");
+ is(inputEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by input #4");
+ is(inputEventData.compositionstart, "\u30FC",
+ kDescription + "data of compositionstart is empty (input) #4");
+ is(inputEventLocale.compositionstart, "",
+ kDescription + "locale of compositionstart isn't empty (input) #4");
+
+ is(windowEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by window #4");
+ is(windowEventData.compositionupdate, "\u3089",
+ kDescription + "data of compositionupdate doesn't match (window) #4");
+ is(windowEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (window) #4");
+ is(inputEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by input #4");
+ is(inputEventData.compositionupdate, "\u3089",
+ kDescription + "data of compositionupdate doesn't match (input) #4");
+ is(inputEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (input) #4");
+
+ is(windowEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by window #4");
+ is(windowEventData.compositionend, "\u3089",
+ kDescription + "data of compositionend doesn't match (window) #4");
+ is(windowEventLocale.compositionend, "",
+ kDescription + "locale of compositionend isn't empty (window) #4");
+ is(inputEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by input #4");
+ is(inputEventData.compositionend, "\u3089",
+ kDescription + "data of compositionend doesn't match (input) #4");
+ is(inputEventLocale.compositionend, "",
+ kDescription + "locale of compositionend isn't empty (input) #4");
+
+ is(windowEventCounts.input, 2,
+ kDescription + "input hasn't been handled by window #4");
+ is(windowEventData.input, "\u3089\u3089",
+ kDescription + "value of input element wasn't modified (window) #4");
+ is(inputEventCounts.input, 2,
+ kDescription + "input hasn't been handled by input #4");
+ is(inputEventData.input, "\u3089\u3089",
+ kDescription + "value of input element wasn't modified (input) #4");
+
+ // preventDefault() should effect nothing.
+ preventDefault = true;
+
+ initResults();
+ synthesizeKey("a", { accelKey: true }); // Select All
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306D",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "," },
+ });
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ is(windowEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by window #5");
+ is(windowEventData.compositionstart, "\u3089\u3089",
+ kDescription + "data of compositionstart is empty (window) #5");
+ is(windowEventLocale.compositionstart, "",
+ kDescription + "locale of compositionstart isn't empty (window) #5");
+ is(inputEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by input #5");
+ is(inputEventData.compositionstart, "\u3089\u3089",
+ kDescription + "data of compositionstart is empty (input) #5");
+ is(inputEventLocale.compositionstart, "",
+ kDescription + "locale of compositionstart isn't empty (input) #5");
+
+ is(windowEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by window #5");
+ is(windowEventData.compositionupdate, "\u306D",
+ kDescription + "data of compositionupdate doesn't match (window) #5");
+ is(windowEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (window) #5");
+ is(inputEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by input #5");
+ is(inputEventData.compositionupdate, "\u306D",
+ kDescription + "data of compositionupdate doesn't match (input) #5");
+ is(inputEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (input) #5");
+
+ is(windowEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by window #5");
+ is(windowEventData.compositionend, "\u306D",
+ kDescription + "data of compositionend doesn't match (window) #5");
+ is(windowEventLocale.compositionend, "",
+ kDescription + "locale of compositionend isn't empty (window) #5");
+ is(inputEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by input #5");
+ is(inputEventData.compositionend, "\u306D",
+ kDescription + "data of compositionend doesn't match (input) #5");
+ is(inputEventLocale.compositionend, "",
+ kDescription + "locale of compositionend isn't empty (input) #5");
+
+ is(windowEventCounts.input, 2,
+ kDescription + "input hasn't been handled by window #5");
+ is(windowEventData.input, "\u306D",
+ kDescription + "value of input element wasn't modified (window) #5");
+ is(inputEventCounts.input, 2,
+ kDescription + "input hasn't been handled by input #5");
+ is(inputEventData.input, "\u306D",
+ kDescription + "value of input element wasn't modified (input) #5");
+
+ preventDefault = false;
+
+ // stopPropagation() should effect nothing (except event count)
+ stopPropagation = true;
+
+ initResults();
+ synthesizeKey("a", { accelKey: true }); // Select All
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "\\", code: "IntlRo", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
+ });
+
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ is(windowEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by window #6");
+ is(windowEventData.compositionstart, "\u306D",
+ kDescription + "data of compositionstart is empty #6");
+ is(windowEventLocale.compositionstart, "",
+ kDescription + "locale of compositionstart isn't empty #6");
+ is(inputEventCounts.compositionstart, 0,
+ kDescription + "compositionstart has been handled by input #6");
+
+ is(windowEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by window #6");
+ is(windowEventData.compositionupdate, "\u306E",
+ kDescription + "data of compositionupdate doesn't match #6");
+ is(windowEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty #6");
+ is(inputEventCounts.compositionupdate, 0,
+ kDescription + "compositionupdate has been handled by input #6");
+
+ is(windowEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by window #6");
+ is(windowEventData.compositionend, "\u306E",
+ kDescription + "data of compositionend doesn't match #6");
+ is(windowEventLocale.compositionend, "",
+ kDescription + "locale of compositionend isn't empty #6");
+ is(inputEventCounts.compositionend, 0,
+ kDescription + "compositionend has been handled by input #6");
+
+ is(windowEventCounts.input, 2,
+ kDescription + "input hasn't been handled by window #6");
+ is(windowEventData.input, "\u306E",
+ kDescription + "value of input element wasn't modified (window) #6");
+ is(inputEventCounts.input, 2,
+ kDescription + "input hasn't been handled by input #6");
+ is(inputEventData.input, "\u306E",
+ kDescription + "value of input element wasn't modified (input) #6");
+
+ stopPropagation = false;
+
+ // create event and dispatch it.
+ initResults();
+
+ input.value = "value of input";
+ synthesizeKey("a", { accelKey: true }); // Select All
+
+ let compositionstart = document.createEvent("CompositionEvent");
+ compositionstart.initCompositionEvent("compositionstart",
+ true, true, document.defaultView,
+ "start data", "start locale");
+ is(compositionstart.type, "compositionstart",
+ kDescription + "type doesn't match #7");
+ is(compositionstart.data, "start data",
+ kDescription + "data doesn't match #7");
+ is(compositionstart.locale, "start locale",
+ kDescription + "locale doesn't match #7");
+ is(compositionstart.detail, 0,
+ kDescription + "detail isn't 0 #7");
+
+ input.dispatchEvent(compositionstart);
+
+ is(windowEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by window #7");
+ is(windowEventData.compositionstart, "start data",
+ kDescription + "data of compositionstart was changed (window) #7");
+ is(windowEventLocale.compositionstart, "start locale",
+ kDescription + "locale of compositionstart was changed (window) #7");
+ is(inputEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by input #7");
+ is(inputEventData.compositionstart, "start data",
+ kDescription + "data of compositionstart was changed (input) #7");
+ is(inputEventLocale.compositionstart, "start locale",
+ kDescription + "locale of compositionstart was changed (input) #7");
+
+ is(input.value, "value of input",
+ kDescription + "input value was changed #7");
+
+ let compositionupdate1 = document.createEvent("compositionevent");
+ compositionupdate1.initCompositionEvent("compositionupdate",
+ true, false, document.defaultView,
+ "composing string", "composing locale");
+ is(compositionupdate1.type, "compositionupdate",
+ kDescription + "type doesn't match #8");
+ is(compositionupdate1.data, "composing string",
+ kDescription + "data doesn't match #8");
+ is(compositionupdate1.locale, "composing locale",
+ kDescription + "locale doesn't match #8");
+ is(compositionupdate1.detail, 0,
+ kDescription + "detail isn't 0 #8");
+
+ input.dispatchEvent(compositionupdate1);
+
+ is(windowEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by window #8");
+ is(windowEventData.compositionupdate, "composing string",
+ kDescription + "data of compositionupdate was changed (window) #8");
+ is(windowEventLocale.compositionupdate, "composing locale",
+ kDescription + "locale of compositionupdate was changed (window) #8");
+ is(inputEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by input #8");
+ is(inputEventData.compositionupdate, "composing string",
+ kDescription + "data of compositionupdate was changed (input) #8");
+ is(inputEventLocale.compositionupdate, "composing locale",
+ kDescription + "locale of compositionupdate was changed (input) #8");
+
+ is(input.value, "value of input",
+ kDescription + "input value was changed #8");
+
+ let compositionupdate2 = document.createEvent("compositionEvent");
+ compositionupdate2.initCompositionEvent("compositionupdate",
+ true, false, document.defaultView,
+ "commit string", "commit locale");
+ is(compositionupdate2.type, "compositionupdate",
+ kDescription + "type doesn't match #9");
+ is(compositionupdate2.data, "commit string",
+ kDescription + "data doesn't match #9");
+ is(compositionupdate2.locale, "commit locale",
+ kDescription + "locale doesn't match #9");
+ is(compositionupdate2.detail, 0,
+ kDescription + "detail isn't 0 #9");
+
+ input.dispatchEvent(compositionupdate2);
+
+ is(windowEventCounts.compositionupdate, 2,
+ kDescription + "compositionupdate hasn't been handled by window #9");
+ is(windowEventData.compositionupdate, "commit string",
+ kDescription + "data of compositionupdate was changed (window) #9");
+ is(windowEventLocale.compositionupdate, "commit locale",
+ kDescription + "locale of compositionupdate was changed (window) #9");
+ is(inputEventCounts.compositionupdate, 2,
+ kDescription + "compositionupdate hasn't been handled by input #9");
+ is(inputEventData.compositionupdate, "commit string",
+ kDescription + "data of compositionupdate was changed (input) #9");
+ is(inputEventLocale.compositionupdate, "commit locale",
+ kDescription + "locale of compositionupdate was changed (input) #9");
+
+ is(input.value, "value of input",
+ kDescription + "input value was changed #9");
+
+ let compositionend = document.createEvent("Compositionevent");
+ compositionend.initCompositionEvent("compositionend",
+ true, false, document.defaultView,
+ "end data", "end locale");
+ is(compositionend.type, "compositionend",
+ kDescription + "type doesn't match #10");
+ is(compositionend.data, "end data",
+ kDescription + "data doesn't match #10");
+ is(compositionend.locale, "end locale",
+ kDescription + "locale doesn't match #10");
+ is(compositionend.detail, 0,
+ kDescription + "detail isn't 0 #10");
+
+ input.dispatchEvent(compositionend);
+
+ is(windowEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by window #10");
+ is(windowEventData.compositionend, "end data",
+ kDescription + "data of compositionend was changed (window) #10");
+ is(windowEventLocale.compositionend, "end locale",
+ kDescription + "locale of compositionend was changed (window) #10");
+ is(inputEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by input #10");
+ is(inputEventData.compositionend, "end data",
+ kDescription + "data of compositionend was changed (input) #10");
+ is(inputEventLocale.compositionend, "end locale",
+ kDescription + "locale of compositionend was changed (input) #10");
+
+ is(input.value, "value of input",
+ kDescription + "input value was changed #10");
+
+ window.removeEventListener("compositionstart",
+ compositionEventHandlerForWindow, true);
+ window.removeEventListener("compositionend",
+ compositionEventHandlerForWindow, true);
+ window.removeEventListener("compositionupdate",
+ compositionEventHandlerForWindow, true);
+ window.removeEventListener("input",
+ formEventHandlerForWindow, true);
+
+ input.removeEventListener("compositionstart",
+ compositionEventHandlerForInput, true);
+ input.removeEventListener("compositionend",
+ compositionEventHandlerForInput, true);
+ input.removeEventListener("compositionupdate",
+ compositionEventHandlerForInput, true);
+ input.removeEventListener("input",
+ formEventHandlerForInput, true);
+}
+
+// eslint-disable-next-line complexity
+function runQueryTextRectInContentEditableTest()
+{
+ contenteditable.focus();
+
+ contenteditable.innerHTML = "<p>abc</p><p>def</p>";
+ // \n 0 123 4 567
+ // \r\n 01 234 56 789
+
+ let description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
+
+ // "a"
+ let a = synthesizeQueryTextRect(kLFLen, 1);
+ if (!checkQueryContentResult(a, description + "rect for 'a'")) {
+ return;
+ }
+
+ // "b"
+ let b = synthesizeQueryTextRect(kLFLen + 1, 1);
+ if (!checkQueryContentResult(b, description + "rect for 'b'")) {
+ return;
+ }
+
+ is(b.top, a.top, description + "'a' and 'b' should be at same top");
+ isSimilarTo(b.left, a.left + a.width, 2, description + "left of 'b' should be at similar to right of 'a'");
+ is(b.height, a.height, description + "'a' and 'b' should be same height");
+
+ // "c"
+ let c = synthesizeQueryTextRect(kLFLen + 2, 1);
+ if (!checkQueryContentResult(c, description + "rect for 'c'")) {
+ return;
+ }
+
+ is(c.top, b.top, description + "'b' and 'c' should be at same top");
+ isSimilarTo(c.left, b.left + b.width, 2, description + "left of 'c' should be at similar to right of 'b'");
+ is(c.height, b.height, description + "'b' and 'c' should be same height");
+
+ // "abc" as array
+ let abcAsArray = synthesizeQueryTextRectArray(kLFLen, 3);
+ if (!checkQueryContentResult(abcAsArray, description + "rect array for 'abc'") ||
+ !checkRectArray(abcAsArray, [a, b, c], description + "query text rect array result of 'abc' should match with each query text rect result")) {
+ return;
+ }
+
+ // 2nd <p> (can be computed with the rect of 'c')
+ let p2 = synthesizeQueryTextRect(kLFLen + 3, 1);
+ if (!checkQueryContentResult(p2, description + "rect for 2nd <p>")) {
+ return;
+ }
+
+ is(p2.top, c.top, description + "'c' and a line breaker caused by 2nd <p> should be at same top");
+ isSimilarTo(p2.left, c.left + c.width, 2, description + "left of a line breaker caused by 2nd <p> should be at similar to right of 'c'");
+ is(p2.height, c.height, description + "'c' and a line breaker caused by 2nd <p> should be same height");
+
+ // 2nd <p> as array
+ let p2AsArray = synthesizeQueryTextRectArray(kLFLen + 3, 1);
+ if (!checkQueryContentResult(p2AsArray, description + "2nd <p>'s line breaker as array") ||
+ !checkRectArray(p2AsArray, [p2], description + "query text rect array result of 2nd <p> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let p2_2 = synthesizeQueryTextRect(kLFLen + 4, 1);
+ if (!checkQueryContentResult(p2_2, description + "rect for \\n of \\r\\n caused by 2nd <p>")) {
+ return;
+ }
+
+ is(p2_2.top, p2.top, description + "'\\r' and '\\n' should be at same top");
+ is(p2_2.left, p2.left, description + "'\\r' and '\\n' should be at same top");
+ is(p2_2.height, p2.height, description + "'\\r' and '\\n' should be same height");
+ is(p2_2.width, p2.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let p2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 4, 1);
+ if (!checkQueryContentResult(p2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <p>") ||
+ !checkRectArray(p2_2AsArray, [p2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd <p> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // "d"
+ let d = synthesizeQueryTextRect(kLFLen * 2 + 3, 1);
+ if (!checkQueryContentResult(d, description + "rect for 'd'")) {
+ return;
+ }
+
+ isGreaterThan(d.top, a.top + a.height, description + "top of 'd' should be greater than bottom of 'a'");
+ is(d.left, a.left, description + "'a' and 'd' should be same at same left");
+ is(d.height, a.height, description + "'a' and 'd' should be same height");
+
+ // "e"
+ let e = synthesizeQueryTextRect(kLFLen * 2 + 4, 1);
+ if (!checkQueryContentResult(e, description + "rect for 'e'")) {
+ return;
+ }
+
+ is(e.top, d.top, description + "'d' and 'd' should be at same top");
+ isSimilarTo(e.left, d.left + d.width, 2, description + "left of 'e' should be at similar to right of 'd'");
+ is(e.height, d.height, description + "'d' and 'e' should be same height");
+
+ // "f"
+ let f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
+ if (!checkQueryContentResult(f, description + "rect for 'f'")) {
+ return;
+ }
+
+ is(f.top, e.top, description + "'e' and 'f' should be at same top");
+ isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
+ is(f.height, e.height, description + "'e' and 'f' should be same height");
+
+ // "def" as array
+ let defAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 3, 3);
+ if (!checkQueryContentResult(defAsArray, description + "rect array for 'def'") ||
+ !checkRectArray(defAsArray, [d, e, f], description + "query text rect array result of 'def' should match with each query text rect result")) {
+ return;
+ }
+
+ // next of "f" (can be computed with rect of 'f')
+ let next_f = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(next_f, description + "rect for next of 'f'")) {
+ return;
+ }
+
+ is(next_f.top, d.top, 2, description + "'f' and next of 'f' should be at same top");
+ isSimilarTo(next_f.left, f.left + f.width, 2, description + "left of next of 'f' should be at similar to right of 'f'");
+ is(next_f.height, d.height, description + "'f' and next of 'f' should be same height");
+
+ // next of "f" as array
+ let next_fAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(next_fAsArray, description + "rect array for next of 'f'") ||
+ !checkRectArray(next_fAsArray, [next_f], description + "query text rect array result of next of 'f' should match with each query text rect result")) {
+ return;
+ }
+
+ // too big offset for the editor
+ let tooBigOffset = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
+ return;
+ }
+
+ is(tooBigOffset.top, next_f.top, description + "too big offset and next of 'f' should be at same top");
+ is(tooBigOffset.left, next_f.left, description + "too big offset and next of 'f' should be at same left");
+ is(tooBigOffset.height, next_f.height, description + "too big offset and next of 'f' should be same height");
+ is(tooBigOffset.width, next_f.width, description + "too big offset and next of 'f' should be same width");
+
+ // too big offset for the editors as array
+ let tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
+ !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
+ return;
+ }
+
+ contenteditable.innerHTML = "<p>abc</p><p>def</p><p><br></p>";
+ // \n 0 123 4 567 8 9
+ // \r\n 01 234 56 789 01 23
+
+ description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
+
+ // "f"
+ f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
+ if (!checkQueryContentResult(f, description + "rect for 'f'")) {
+ return;
+ }
+
+ is(f.top, e.top, description + "'e' and 'f' should be at same top");
+ is(f.height, e.height, description + "'e' and 'f' should be same height");
+ isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
+
+ // 3rd <p> (can be computed with rect of 'f')
+ let p3 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(p3, description + "rect for 3rd <p>")) {
+ return;
+ }
+
+ is(p3.top, f.top, description + "'f' and a line breaker caused by 3rd <p> should be at same top");
+ is(p3.height, f.height, description + "'f' and a line breaker caused by 3rd <p> should be same height");
+ isSimilarTo(p3.left, f.left + f.width, 2, description + "left of a line breaker caused by 3rd <p> should be similar to right of 'f'");
+
+ // 3rd <p> as array
+ let p3AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(p3AsArray, description + "3rd <p>'s line breaker as array") ||
+ !checkRectArray(p3AsArray, [p3], description + "query text rect array result of 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let p3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(p3_2, description + "rect for \\n of \\r\\n caused by 3rd <p>")) {
+ return;
+ }
+
+ is(p3_2.top, p3.top, description + "'\\r' and '\\n' should be at same top");
+ is(p3_2.left, p3.left, description + "'\\r' and '\\n' should be at same top");
+ is(p3_2.height, p3.height, description + "'\\r' and '\\n' should be same height");
+ is(p3_2.width, p3.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let p3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(p3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <p>") ||
+ !checkRectArray(p3_2AsArray, [p3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // <br> in 3rd <p>
+ let br = synthesizeQueryTextRect(kLFLen * 3 + 6, 1);
+ if (!checkQueryContentResult(br, description + "rect for <br> in 3rd <p>")) {
+ return;
+ }
+
+ isGreaterThan(br.top, d.top + d.height, description + "a line breaker caused by <br> in 3rd <p> should be greater than bottom of 'd'");
+ isSimilarTo(br.height, d.height, 2, description + "'d' and a line breaker caused by <br> in 3rd <p> should be similar height");
+ is(br.left, d.left, description + "left of a line breaker caused by <br> in 3rd <p> should be same left of 'd'");
+
+ // <br> in 3rd <p> as array
+ let brAsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
+ if (!checkQueryContentResult(brAsArray, description + "<br> in 3rd <p> as array") ||
+ !checkRectArray(brAsArray, [br], description + "query text rect array result of <br> in 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let br_2 = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
+ if (!checkQueryContentResult(br_2, description + "rect for \\n of \\r\\n caused by <br> in 3rd <p>")) {
+ return;
+ }
+
+ is(br_2.top, br.top, description + "'\\r' and '\\n' should be at same top");
+ is(br_2.left, br.left, description + "'\\r' and '\\n' should be at same top");
+ is(br_2.height, br.height, description + "'\\r' and '\\n' should be same height");
+ is(br_2.width, br.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let br_2AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 7, 1);
+ if (!checkQueryContentResult(br_2AsArray, description + "rect array for \\n of \\r\\n caused by <br> in 3rd <p>") ||
+ !checkRectArray(br_2AsArray, [br_2], description + "query text rect array result of \\n of \\r\\n caused by <br> in 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // next of <br> in 3rd <p>
+ let next_br = synthesizeQueryTextRect(kLFLen * 4 + 6, 1);
+ if (!checkQueryContentResult(next_br, description + "rect for next of <br> in 3rd <p>")) {
+ return;
+ }
+
+ is(next_br.top, br.top, description + "next of <br> and <br> should be at same top");
+ is(next_br.left, br.left, description + "next of <br> and <br> should be at same left");
+ is(next_br.height, br.height, description + "next of <br> and <br> should be same height");
+ is(next_br.width, br.width, description + "next of <br> and <br> should be same width");
+
+ // next of <br> in 3rd <p> as array
+ let next_brAsArray = synthesizeQueryTextRectArray(kLFLen * 4 + 6, 1);
+ if (!checkQueryContentResult(next_brAsArray, description + "rect array for next of <br> in 3rd <p>") ||
+ !checkRectArray(next_brAsArray, [next_br], description + "query text rect array result of next of <br> in 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+
+ // too big offset for the editor
+ tooBigOffset = synthesizeQueryTextRect(kLFLen * 4 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
+ return;
+ }
+
+ is(tooBigOffset.top, next_br.top, description + "too big offset and next of 3rd <p> should be at same top");
+ is(tooBigOffset.left, next_br.left, description + "too big offset and next of 3rd <p> should be at same left");
+ is(tooBigOffset.height, next_br.height, description + "too big offset and next of 3rd <p> should be same height");
+ is(tooBigOffset.width, next_br.width, description + "too big offset and next of 3rd <p> should be same width");
+
+ // too big offset for the editors as array
+ tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 4 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
+ !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
+ return;
+ }
+
+ contenteditable.innerHTML = "<p>abc</p><p>def</p><p></p>";
+ // \n 0 123 4 567 8
+ // \r\n 01 234 56 789 0
+
+ description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
+
+ // "f"
+ f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
+ if (!checkQueryContentResult(f, description + "rect for 'f'")) {
+ return;
+ }
+
+ is(f.top, e.top, description + "'e' and 'f' should be at same top");
+ isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
+ is(f.height, e.height, description + "'e' and 'f' should be same height");
+
+ // 3rd <p> (can be computed with rect of 'f')
+ p3 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(p3, description + "rect for 3rd <p>")) {
+ return;
+ }
+
+ is(p3.top, f.top, description + "'f' and a line breaker caused by 3rd <p> should be at same top");
+ is(p3.height, f.height, description + "'f' and a line breaker caused by 3rd <p> should be same height");
+ isSimilarTo(p3.left, f.left + f.width, 2, description + "left of a line breaker caused by 3rd <p> should be similar to right of 'f'");
+
+ // 3rd <p> as array
+ p3AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(p3AsArray, description + "3rd <p>'s line breaker as array") ||
+ !checkRectArray(p3AsArray, [p3], description + "query text rect array result of 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let p3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(p3_2, description + "rect for \\n of \\r\\n caused by 3rd <p>")) {
+ return;
+ }
+
+ is(p3_2.top, p3.top, description + "'\\r' and '\\n' should be at same top");
+ is(p3_2.left, p3.left, description + "'\\r' and '\\n' should be at same top");
+ is(p3_2.height, p3.height, description + "'\\r' and '\\n' should be same height");
+ is(p3_2.width, p3.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let p3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(p3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <p>") ||
+ !checkRectArray(p3_2AsArray, [p3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // next of 3rd <p>
+ let next_p3 = synthesizeQueryTextRect(kLFLen * 3 + 6, 1);
+ if (!checkQueryContentResult(next_p3, description + "rect for next of 3rd <p>")) {
+ return;
+ }
+
+ isGreaterThan(next_p3.top, d.top + d.height, description + "top of next of 3rd <p> should equal to or be bigger than bottom of 'd'");
+ isSimilarTo(next_p3.left, d.left, 2, description + "left of next of 3rd <p> should be at similar to left of 'd'");
+ isSimilarTo(next_p3.height, d.height, 2, description + "next of 3rd <p> and 'd' should be similar height");
+
+ // next of 3rd <p> as array
+ let next_p3AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
+ if (!checkQueryContentResult(next_p3AsArray, description + "next of 3rd <p> as array") ||
+ !checkRectArray(next_p3AsArray, [next_p3], description + "query text rect array result of next of 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+
+ // too big offset for the editor
+ tooBigOffset = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
+ return;
+ }
+
+ is(tooBigOffset.top, next_p3.top, description + "too big offset and next of 3rd <p> should be at same top");
+ is(tooBigOffset.left, next_p3.left, description + "too big offset and next of 3rd <p> should be at same left");
+ is(tooBigOffset.height, next_p3.height, description + "too big offset and next of 3rd <p> should be same height");
+ is(tooBigOffset.width, next_p3.width, description + "too big offset and next of 3rd <p> should be same width");
+
+ // too big offset for the editors as array
+ tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
+ !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
+ return;
+ }
+
+ contenteditable.innerHTML = "abc<br>def";
+ // \n 0123 456
+ // \r\n 01234 567
+
+ description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
+
+ // "a"
+ a = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(a, description + "rect for 'a'")) {
+ return;
+ }
+
+ // "b"
+ b = synthesizeQueryTextRect(1, 1);
+ if (!checkQueryContentResult(b, description + "rect for 'b'")) {
+ return;
+ }
+
+ is(b.top, a.top, description + "'a' and 'b' should be at same top");
+ isSimilarTo(b.left, a.left + a.width, 2, description + "left of 'b' should be at similar to right of 'a'");
+ is(b.height, a.height, description + "'a' and 'b' should be same height");
+
+ // "c"
+ c = synthesizeQueryTextRect(2, 1);
+ if (!checkQueryContentResult(c, description + "rect for 'c'")) {
+ return;
+ }
+
+ is(c.top, b.top, description + "'b' and 'c' should be at same top");
+ isSimilarTo(c.left, b.left + b.width, 2, description + "left of 'c' should be at similar to right of 'b'");
+ is(c.height, b.height, description + "'b' and 'c' should be same height");
+
+ // "abc" as array
+ abcAsArray = synthesizeQueryTextRectArray(0, 3);
+ if (!checkQueryContentResult(abcAsArray, description + "rect array for 'abc'") ||
+ !checkRectArray(abcAsArray, [a, b, c], description + "query text rect array result of 'abc' should match with each query text rect result")) {
+ return;
+ }
+
+ // <br> (can be computed with the rect of 'c')
+ br = synthesizeQueryTextRect(3, 1);
+ if (!checkQueryContentResult(br, description + "rect for <br>")) {
+ return;
+ }
+
+ is(br.top, c.top, description + "'c' and a line breaker caused by <br> should be at same top");
+ isSimilarTo(br.left, c.left + c.width, 2, description + "left of a line breaker caused by <br> should be at similar to right of 'c'");
+ is(br.height, c.height, description + "'c' and a line breaker caused by <br> should be same height");
+
+ // <br> as array
+ brAsArray = synthesizeQueryTextRectArray(3, 1);
+ if (!checkQueryContentResult(brAsArray, description + "<br>'s line breaker as array") ||
+ !checkRectArray(brAsArray, [br], description + "query text rect array result of <br> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let br_2 = synthesizeQueryTextRect(4, 1);
+ if (!checkQueryContentResult(br_2, description + "rect for \n of \r\n caused by <br>")) {
+ return;
+ }
+
+ is(br_2.top, br.top, description + "'\\r' and '\\n' should be at same top");
+ is(br_2.left, br.left, description + "'\\r' and '\\n' should be at same top");
+ is(br_2.height, br.height, description + "'\\r' and '\\n' should be same height");
+ is(br_2.width, br.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let br_2AsArray = synthesizeQueryTextRectArray(4, 1);
+ if (!checkQueryContentResult(br_2AsArray, description + "rect array for \\n of \\r\\n caused by <br>") ||
+ !checkRectArray(br_2AsArray, [br_2], description + "query text rect array result of \\n of \\r\\n caused by <br> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // "d"
+ d = synthesizeQueryTextRect(kLFLen + 3, 1);
+ if (!checkQueryContentResult(d, description + "rect for 'd'")) {
+ return;
+ }
+
+ isSimilarTo(d.top, a.top + a.height, 2, description + "top of 'd' should be at similar to bottom of 'a'");
+ is(d.left, a.left, description + "'a' and 'd' should be same at same left");
+ is(d.height, a.height, description + "'a' and 'd' should be same height");
+
+ // "e"
+ e = synthesizeQueryTextRect(kLFLen + 4, 1);
+ if (!checkQueryContentResult(e, description + "rect for 'e'")) {
+ return;
+ }
+
+ is(e.top, d.top, description + "'d' and 'd' should be at same top");
+ isSimilarTo(e.left, d.left + d.width, 2, description + "left of 'e' should be at similar to right of 'd'");
+ is(e.height, d.height, description + "'d' and 'e' should be same height");
+
+ // "f"
+ f = synthesizeQueryTextRect(kLFLen + 5, 1);
+ if (!checkQueryContentResult(f, description + "rect for 'f'")) {
+ return;
+ }
+
+ is(f.top, e.top, description + "'e' and 'f' should be at same top");
+ isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
+ is(f.height, e.height, description + "'e' and 'f' should be same height");
+
+ // "def" as array
+ defAsArray = synthesizeQueryTextRectArray(kLFLen + 3, 3);
+ if (!checkQueryContentResult(defAsArray, description + "rect array for 'def'") ||
+ !checkRectArray(defAsArray, [d, e, f], description + "query text rect array result of 'def' should match with each query text rect result")) {
+ return;
+ }
+
+ // next of "f" (can be computed with rect of 'f')
+ next_f = synthesizeQueryTextRect(kLFLen + 6, 1);
+ if (!checkQueryContentResult(next_f, description + "rect for next of 'f'")) {
+ return;
+ }
+
+ is(next_f.top, d.top, 2, description + "'f' and next of 'f' should be at same top");
+ isSimilarTo(next_f.left, f.left + f.width, 2, description + "left of next of 'f' should be at similar to right of 'f'");
+ is(next_f.height, d.height, description + "'f' and next of 'f' should be same height");
+
+ // next of "f" as array
+ next_fAsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
+ if (!checkQueryContentResult(next_fAsArray, description + "rect array for next of 'f'") ||
+ !checkRectArray(next_fAsArray, [next_f], description + "query text rect array result of next of 'f' should match with each query text rect result")) {
+ return;
+ }
+
+ // too big offset for the editor
+ tooBigOffset = synthesizeQueryTextRect(kLFLen + 7, 1);
+ if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
+ return;
+ }
+
+ is(tooBigOffset.top, next_f.top, description + "too big offset and next of 'f' should be at same top");
+ is(tooBigOffset.left, next_f.left, description + "too big offset and next of 'f' should be at same left");
+ is(tooBigOffset.height, next_f.height, description + "too big offset and next of 'f' should be same height");
+ is(tooBigOffset.width, next_f.width, description + "too big offset and next of 'f' should be same width");
+
+ // too big offset for the editors as array
+ tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
+ if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
+ !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
+ return;
+ }
+
+ // Note that this case does not have an empty line at the end.
+ contenteditable.innerHTML = "abc<br>def<br>";
+ // \n 0123 4567
+ // \r\n 01234 56789
+
+ description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
+
+ // "f"
+ f = synthesizeQueryTextRect(kLFLen + 5, 1);
+ if (!checkQueryContentResult(f, description + "rect for 'f'")) {
+ return;
+ }
+
+ is(f.top, e.top, description + "'e' and 'f' should be at same top");
+ is(f.height, e.height, description + "'e' and 'f' should be same height");
+ isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
+
+ // 2nd <br> (can be computed with rect of 'f')
+ let br2 = synthesizeQueryTextRect(kLFLen + 6, 1);
+ if (!checkQueryContentResult(br2, description + "rect for 2nd <br>")) {
+ return;
+ }
+
+ is(br2.top, f.top, description + "'f' and a line breaker caused by 2nd <br> should be at same top");
+ is(br2.height, f.height, description + "'f' and a line breaker caused by 2nd <br> should be same height");
+ isSimilarTo(br2.left, f.left + f.width, 2, description + "left of a line breaker caused by 2nd <br> should be similar to right of 'f'");
+
+ // 2nd <br> as array
+ let br2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(br2AsArray, description + "2nd <br>'s line breaker as array") ||
+ !checkRectArray(br2AsArray, [br2], description + "query text rect array result of 2nd <br> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let br2_2 = synthesizeQueryTextRect(kLFLen + 7, 1);
+ if (!checkQueryContentResult(br2_2, description + "rect for \\n of \\r\\n caused by 2nd <br>")) {
+ return;
+ }
+
+ is(br2_2.top, br2.top, description + "'\\r' and '\\n' should be at same top");
+ is(br2_2.left, br2.left, description + "'\\r' and '\\n' should be at same top");
+ is(br2_2.height, br2.height, description + "'\\r' and '\\n' should be same height");
+ is(br2_2.width, br2.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let br2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
+ if (!checkQueryContentResult(br2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <br>") ||
+ !checkRectArray(br2_2AsArray, [br2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd <br> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // next of 2nd <br>
+ let next_br2 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(next_br2, description + "rect for next of 2nd <br>")) {
+ return;
+ }
+
+ is(next_br2.top, br2.top, description + "2nd <br> and next of 2nd <br> should be at same top");
+ is(next_br2.left, br2.left, description + "2nd <br> and next of 2nd <br> should be at same top");
+ is(next_br2.height, br2.height, description + "2nd <br> and next of 2nd <br> should be same height");
+ is(next_br2.width, br2.width, description + "2nd <br> and next of 2nd <br> should be same width");
+
+ // next of 2nd <br> as array
+ let next_br2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(next_br2AsArray, description + "rect array for next of 2nd <br>") ||
+ !checkRectArray(next_br2AsArray, [next_br2], description + "query text rect array result of next of 2nd <br> should match with each query text rect result")) {
+ return;
+ }
+
+ // too big offset for the editor
+ tooBigOffset = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
+ return;
+ }
+
+ is(tooBigOffset.top, next_br2.top, description + "too big offset and next of 2nd <br> should be at same top");
+ is(tooBigOffset.left, next_br2.left, description + "too big offset and next of 2nd <br> should be at same left");
+ is(tooBigOffset.height, next_br2.height, description + "too big offset and next of 2nd <br> should be same height");
+ is(tooBigOffset.width, next_br2.width, description + "too big offset and next of 2nd <br> should be same width");
+
+ // too big offset for the editors as array
+ tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
+ !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
+ return;
+ }
+
+ contenteditable.innerHTML = "abc<br>def<br><br>";
+ // \n 0123 4567 8
+ // \r\n 01234 56789 01
+
+ description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
+
+ // "f"
+ f = synthesizeQueryTextRect(kLFLen + 5, 1);
+ if (!checkQueryContentResult(f, description + "rect for 'f'")) {
+ return;
+ }
+
+ is(f.top, e.top, description + "'e' and 'f' should be at same top");
+ isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
+ is(f.height, e.height, description + "'e' and 'f' should be same height");
+
+ // 2nd <br>
+ br2 = synthesizeQueryTextRect(kLFLen + 6, 1);
+ if (!checkQueryContentResult(br2, description + "rect for 2nd <br>")) {
+ return;
+ }
+
+ is(br2.top, f.top, description + "'f' and a line breaker caused by 2nd <br> should be at same top");
+ is(br2.height, f.height, description + "'f' and a line breaker caused by 2nd <br> should be same height");
+ ok(f.left < br2.left, description + "left of a line breaker caused by 2nd <br> should be bigger than left of 'f', f.left=" + f.left + ", br2.left=" + br2.left);
+
+ // 2nd <br> as array
+ br2AsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
+ if (!checkQueryContentResult(br2AsArray, description + "2nd <br>'s line breaker as array") ||
+ !checkRectArray(br2AsArray, [br2], description + "query text rect array result of 2nd <br> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let br2_2 = synthesizeQueryTextRect(kLFLen + 7, 1);
+ if (!checkQueryContentResult(br2_2, description + "rect for \\n of \\r\\n caused by 2nd <br>")) {
+ return;
+ }
+
+ is(br2_2.top, br2.top, description + "'\\r' and '\\n' should be at same top");
+ is(br2_2.left, br2.left, description + "'\\r' and '\\n' should be at same top");
+ is(br2_2.height, br2.height, description + "'\\r' and '\\n' should be same height");
+ is(br2_2.width, br2.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let br2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
+ if (!checkQueryContentResult(br2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <br>") ||
+ !checkRectArray(br2_2AsArray, [br2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd <br> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // 3rd <br>
+ let br3 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(br3, description + "rect for next of 3rd <br>")) {
+ return;
+ }
+
+ isSimilarTo(br3.top, d.top + d.height, 3, description + "top of next of 3rd <br> should at similar to bottom of 'd'");
+ isSimilarTo(br3.left, d.left, 2, description + "left of next of 3rd <br> should be at similar to left of 'd'");
+ isSimilarTo(br3.height, d.height, 2, description + "next of 3rd <br> and 'd' should be similar height");
+
+ // 3rd <br> as array
+ let br3AsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
+ if (!checkQueryContentResult(br3AsArray, description + "3rd <br>'s line breaker as array") ||
+ !checkRectArray(br3AsArray, [br3], description + "query text rect array result of 3rd <br> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let br3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(br3_2, description + "rect for \\n of \\r\\n caused by 3rd <br>")) {
+ return;
+ }
+
+ is(br3_2.top, br3.top, description + "'\\r' and '\\n' should be at same top");
+ is(br3_2.left, br3.left, description + "'\\r' and '\\n' should be at same left");
+ is(br3_2.height, br3.height, description + "'\\r' and '\\n' should be same height");
+ is(br3_2.width, br3.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let br3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(br3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <br>") ||
+ !checkRectArray(br3_2AsArray, [br3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <br> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // next of 3rd <br>
+ let next_br3 = synthesizeQueryTextRect(kLFLen * 3 + 6, 1);
+ if (!checkQueryContentResult(next_br3, description + "rect for next of 3rd <br>")) {
+ return;
+ }
+
+ is(next_br3.top, br3.top, description + "3rd <br> and next of 3rd <br> should be at same top");
+ is(next_br3.left, br3.left, description + "3rd <br> and next of 3rd <br> should be at same left");
+ is(next_br3.height, br3.height, description + "3rd <br> and next of 3rd <br> should be same height");
+ is(next_br3.width, br3.width, description + "3rd <br> and next of 3rd <br> should be same width");
+
+ // next of 3rd <br> as array
+ let next_br3AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
+ if (!checkQueryContentResult(next_br3AsArray, description + "rect array for next of 3rd <br>") ||
+ !checkRectArray(next_br3AsArray, [next_br3], description + "query text rect array result of next of 3rd <br> should match with each query text rect result")) {
+ return;
+ }
+
+ // too big offset for the editor
+ tooBigOffset = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
+ return;
+ }
+
+ is(tooBigOffset.top, next_br3.top, description + "too big offset and next of 3rd <br> should be at same top");
+ is(tooBigOffset.left, next_br3.left, description + "too big offset and next of 3rd <br> should be at same left");
+ is(tooBigOffset.height, next_br3.height, description + "too big offset and next of 3rd <br> should be same height");
+ is(tooBigOffset.width, next_br3.width, description + "too big offset and next of 3rd <br> should be same width");
+
+ // too big offset for the editors as array
+ tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
+ if (checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset")) {
+ checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result");
+ }
+}
+
+function runCharAtPointTest(aFocusedEditor, aTargetName)
+{
+ aFocusedEditor.value = "This is a test of the\nContent Events";
+ // 012345678901234567890 12345678901234
+ // 0 1 2 3
+
+ aFocusedEditor.focus();
+
+ const kNone = -1;
+ const kTestingOffset = [ 0, 10, 20, 21 + kLFLen, 34 + kLFLen];
+ const kLeftSideOffset = [ kNone, 9, 19, kNone, 33 + kLFLen];
+ const kRightSideOffset = [ 1, 11, kNone, 22 + kLFLen, kNone];
+ const kLeftTentativeCaretOffset = [ 0, 10, 20, 21 + kLFLen, 34 + kLFLen];
+ const kRightTentativeCaretOffset = [ 1, 11, 21, 22 + kLFLen, 35 + kLFLen];
+
+ let editorRect = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRect,
+ "runCharAtPointTest (" + aTargetName + "): editorRect")) {
+ return;
+ }
+
+ for (let i = 0; i < kTestingOffset.length; i++) {
+ let textRect = synthesizeQueryTextRect(kTestingOffset[i], 1);
+ if (!checkQueryContentResult(textRect,
+ "runCharAtPointTest (" + aTargetName + "): textRect: i=" + i)) {
+ continue;
+ }
+
+ checkRectContainsRect(textRect, editorRect,
+ "runCharAtPointTest (" + aTargetName +
+ "): the text rect isn't in the editor");
+
+ // Test #1, getting same character rect by the point near the top-left.
+ let charAtPt1 = synthesizeCharAtPoint(textRect.left + 1,
+ textRect.top + 1);
+ if (checkQueryContentResult(charAtPt1,
+ "runCharAtPointTest (" + aTargetName + "): charAtPt1: i=" + i)) {
+ ok(!charAtPt1.notFound,
+ "runCharAtPointTest (" + aTargetName + "): charAtPt1 isn't found: i=" + i);
+ if (!charAtPt1.notFound) {
+ is(charAtPt1.offset, kTestingOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): charAtPt1 offset is wrong: i=" + i);
+ checkRect(charAtPt1, textRect, "runCharAtPointTest (" + aTargetName +
+ "): charAtPt1 left is wrong: i=" + i);
+ }
+ ok(!charAtPt1.tentativeCaretOffsetNotFound,
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt1 isn't found: i=" + i);
+ if (!charAtPt1.tentativeCaretOffsetNotFound) {
+ is(charAtPt1.tentativeCaretOffset, kLeftTentativeCaretOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt1 is wrong: i=" + i);
+ }
+ }
+
+ // Test #2, getting same character rect by the point near the bottom-right.
+ let charAtPt2 = synthesizeCharAtPoint(textRect.left + textRect.width - 2,
+ textRect.top + textRect.height - 2);
+ if (checkQueryContentResult(charAtPt2,
+ "runCharAtPointTest (" + aTargetName + "): charAtPt2: i=" + i)) {
+ ok(!charAtPt2.notFound,
+ "runCharAtPointTest (" + aTargetName + "): charAtPt2 isn't found: i=" + i);
+ if (!charAtPt2.notFound) {
+ is(charAtPt2.offset, kTestingOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): charAtPt2 offset is wrong: i=" + i);
+ checkRect(charAtPt2, textRect, "runCharAtPointTest (" + aTargetName +
+ "): charAtPt1 left is wrong: i=" + i);
+ }
+ ok(!charAtPt2.tentativeCaretOffsetNotFound,
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt2 isn't found: i=" + i);
+ if (!charAtPt2.tentativeCaretOffsetNotFound) {
+ is(charAtPt2.tentativeCaretOffset, kRightTentativeCaretOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt2 is wrong: i=" + i);
+ }
+ }
+
+ // Test #3, getting left character offset.
+ let charAtPt3 = synthesizeCharAtPoint(textRect.left - 2,
+ textRect.top + 1);
+ if (checkQueryContentResult(charAtPt3,
+ "runCharAtPointTest (" + aTargetName + "): charAtPt3: i=" + i)) {
+ is(charAtPt3.notFound, kLeftSideOffset[i] == kNone,
+ kLeftSideOffset[i] == kNone ?
+ "runCharAtPointTest (" + aTargetName + "): charAtPt3 is found: i=" + i :
+ "runCharAtPointTest (" + aTargetName + "): charAtPt3 isn't found: i=" + i);
+ if (!charAtPt3.notFound) {
+ is(charAtPt3.offset, kLeftSideOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): charAtPt3 offset is wrong: i=" + i);
+ }
+ if (kLeftSideOffset[i] == kNone) {
+ // There may be no enough padding-left (depends on platform)
+ todo(false,
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 isn't tested: i=" + i);
+ } else {
+ ok(!charAtPt3.tentativeCaretOffsetNotFound,
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 isn't found: i=" + i);
+ if (!charAtPt3.tentativeCaretOffsetNotFound) {
+ is(charAtPt3.tentativeCaretOffset, kLeftTentativeCaretOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 is wrong: i=" + i);
+ }
+ }
+ }
+
+ // Test #4, getting right character offset.
+ let charAtPt4 = synthesizeCharAtPoint(textRect.left + textRect.width + 1,
+ textRect.top + textRect.height - 2);
+ if (checkQueryContentResult(charAtPt4,
+ "runCharAtPointTest (" + aTargetName + "): charAtPt4: i=" + i)) {
+ is(charAtPt4.notFound, kRightSideOffset[i] == kNone,
+ kRightSideOffset[i] == kNone ?
+ "runCharAtPointTest (" + aTargetName + "): charAtPt4 is found: i=" + i :
+ "runCharAtPointTest (" + aTargetName + "): charAtPt4 isn't found: i=" + i);
+ if (!charAtPt4.notFound) {
+ is(charAtPt4.offset, kRightSideOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): charAtPt4 offset is wrong: i=" + i);
+ }
+ ok(!charAtPt4.tentativeCaretOffsetNotFound,
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt4 isn't found: i=" + i);
+ if (!charAtPt4.tentativeCaretOffsetNotFound) {
+ is(charAtPt4.tentativeCaretOffset, kRightTentativeCaretOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt4 is wrong: i=" + i);
+ }
+ }
+ }
+}
+
+function runCharAtPointAtOutsideTest()
+{
+ textarea.focus();
+ textarea.value = "some text";
+ let editorRect = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRect,
+ "runCharAtPointAtOutsideTest: editorRect")) {
+ return;
+ }
+ // Check on a text node which is at the outside of editor.
+ let charAtPt = synthesizeCharAtPoint(editorRect.left + 20,
+ editorRect.top - 10);
+ if (checkQueryContentResult(charAtPt,
+ "runCharAtPointAtOutsideTest: charAtPt")) {
+ ok(charAtPt.notFound,
+ "runCharAtPointAtOutsideTest: charAtPt is found on outside of editor");
+ ok(charAtPt.tentativeCaretOffsetNotFound,
+ "runCharAtPointAtOutsideTest: tentative caret offset for charAtPt is found on outside of editor");
+ }
+}
+
+function runSetSelectionEventTest()
+{
+ contenteditable.focus();
+
+ let selection = windowOfContenteditable.getSelection();
+
+ // #1
+ contenteditable.innerHTML = "abc<br>def";
+
+ synthesizeSelectionSet(0, 100);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node of the editor");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of children");
+ checkSelection(0, "abc" + kLF + "def", "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(2, 2 + kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, 2,
+ "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 2");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(2, "c" + kLF + "d", "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(1, 2);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node");
+ is(selection.focusOffset, contenteditable.firstChild.wholeText.length,
+ "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node");
+ checkSelection(1, "bc", "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(3, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, contenteditable.firstChild.wholeText.length,
+ "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the first text node");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 2,
+ "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the index of the last text node");
+ checkSelection(3, kLF, "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(6+kLFLen, 0);
+ is(selection.anchorNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, contenteditable.lastChild.wholeText.length,
+ "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.anchorOffset, contenteditable.lastChild.wholeText.length,
+ "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(6 + kLFLen, "", "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(100, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node of the editor");
+ is(selection.anchorOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of children");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node of the editor");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of children");
+ checkSelection(6 + kLFLen, "", "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #2
+ contenteditable.innerHTML = "<p>a<b>b</b>c</p><p>def</p>";
+
+ synthesizeSelectionSet(kLFLen, 4+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <p> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first <p> node");
+ is(selection.focusNode, contenteditable.lastChild.firstChild,
+ "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the second <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(kLFLen, "abc" + kLF + "d", "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen, 2);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <p> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first <p> node");
+ is(selection.focusNode, contenteditable.firstChild.childNodes.item(1).firstChild,
+ "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <b> node");
+ is(selection.focusOffset, contenteditable.firstChild.childNodes.item(1).firstChild.wholeText.length,
+ "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node in the <b> node");
+ checkSelection(kLFLen, "ab", "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(1+kLFLen, 2);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node in the first <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node in the first <p> node");
+ checkSelection(1+kLFLen, "bc", "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(2+kLFLen, 2+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.childNodes.item(1).firstChild,
+ "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <b> node");
+ is(selection.anchorOffset, contenteditable.firstChild.childNodes.item(1).firstChild.wholeText.length,
+ "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node in the <b> node");
+ is(selection.focusNode, contenteditable.lastChild.firstChild,
+ "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the last <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(2+kLFLen, "c" + kLF + "d", "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(3+kLFLen*2, 1);
+ is(selection.anchorNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the second <p> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the second <p> node");
+ is(selection.focusNode, contenteditable.lastChild.firstChild,
+ "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the second <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(3+kLFLen*2, "d", "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first <p> node");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the first <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, kLF, "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(2+kLFLen, 1+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.childNodes.item(1).firstChild,
+ "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node of the <b> node");
+ is(selection.anchorOffset, contenteditable.firstChild.childNodes.item(1).firstChild.wholeText.length,
+ "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node of the first <b> node");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(2+kLFLen, "c" + kLF, "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(3+kLFLen, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node of the first <p> node");
+ is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node of the first <p> node");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(3+kLFLen, kLF, "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(3+kLFLen, 1+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node of the first <p> node");
+ is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node of the first <p> node");
+ is(selection.focusNode, contenteditable.lastChild.firstChild,
+ "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node of the second <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(3+kLFLen, kLF + "d", "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ // #3
+ contenteditable.innerHTML = "<div>abc<p>def</p></div>";
+
+ synthesizeSelectionSet(1+kLFLen, 2);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node");
+ is(selection.focusOffset, contenteditable.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the first text node");
+ checkSelection(1+kLFLen, "bc", "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(1+kLFLen, 3+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(1+kLFLen, "bc" + kLF + "d", "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(3+kLFLen, 0);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, contenteditable.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the first text node");
+ is(selection.focusNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node");
+ is(selection.focusOffset, contenteditable.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the first text node");
+ checkSelection(3+kLFLen, "", "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, 6+kLFLen*2);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(0, kLF + "abc" + kLF + "def", "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, 100);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(0, kLF + "abc" + kLF + "def", "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(4+kLFLen*2, 2);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(4+kLFLen*2, 100);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(6+kLFLen*2, 0);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
+ is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(6+kLFLen*2, 1);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first text node");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <div> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, kLF, "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, 1+kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
+ is(selection.focusNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node of the <div> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(0, kLF + "a", "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(2+kLFLen, 1+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node of the <div> node");
+ is(selection.anchorOffset, 2,
+ "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 2");
+ is(selection.focusNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(2+kLFLen, "c" + kLF, "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(3+kLFLen, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node of the <div> node");
+ is(selection.anchorOffset, contenteditable.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node of the <div> node");
+ is(selection.focusNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(3+kLFLen, kLF, "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(3+kLFLen, 1+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node of the <div> node");
+ is(selection.anchorOffset, contenteditable.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node of the <div> node");
+ is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node of the <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(3+kLFLen, kLF + "d", "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ // #4
+ contenteditable.innerHTML = "<div><p>abc</p>def</div>";
+
+ synthesizeSelectionSet(1+kLFLen*2, 2);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <p> node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node in the <p> node");
+ checkSelection(1+kLFLen*2, "bc", "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(1+kLFLen*2, 3);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <p> node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(1+kLFLen*2, "bcd", "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(3+kLFLen*2, 0);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <p> node");
+ is(selection.anchorOffset, contenteditable.firstChild.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node in the <p> node");
+ is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node in the <p> node");
+ checkSelection(3+kLFLen*2, "", "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, 6+kLFLen*2);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(0, kLF + kLF + "abcdef", "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, 100);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(0, kLF + kLF + "abcdef", "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(4+kLFLen*2, 2);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(4+kLFLen*2, 100);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(6+kLFLen*2, 0);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
+ is(selection.focusNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(6+kLFLen*2, 1);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <div> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, kLF, "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, kLFLen*2);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
+ is(selection.focusNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, kLF + kLF, "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, 1+kLFLen*2);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
+ is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(0, kLF + kLF + "a", "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen, 0);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <div> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <div> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(kLFLen, "", "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <div> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node");
+ is(selection.focusNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(kLFLen, kLF, "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen, 1+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <div> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node");
+ is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(kLFLen, kLF +"a", "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ // #5
+ contenteditable.innerHTML = "<br>";
+
+ synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(0, kLF, "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(kLFLen, "", "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen, 1);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(kLFLen, "", "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\"");
+
+ // #6
+ contenteditable.innerHTML = "<p><br></p>";
+
+ synthesizeSelectionSet(kLFLen, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
+ checkSelection(kLFLen, kLF, "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen*2, 0);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the <p>'s children");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
+ checkSelection(kLFLen*2, "", "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen*2, 1);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(kLFLen*2, "", "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, kLF, "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, kLFLen*2);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
+ checkSelection(0, kLF + kLF, "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen, 0);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(kLFLen, "", "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #7
+ contenteditable.innerHTML = "<br><br>";
+
+ synthesizeSelectionSet(0, kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(0, kLF, "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, kLFLen * 2);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(0, kLF + kLF, "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(kLFLen, "", "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen, kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #7 (kLFLen, kLFLen) selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(kLFLen, kLF, "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen * 2, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(kLFLen * 2, "", "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #8
+ contenteditable.innerHTML = "<p><br><br></p>";
+
+ synthesizeSelectionSet(kLFLen, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(kLFLen, kLF, "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen, kLFLen * 2);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
+ checkSelection(kLFLen, kLF + kLF, "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen*2, 0);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(kLFLen*2, "", "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen*2, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen*2, kLFLen) selection focus node should be the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
+ checkSelection(kLFLen*2, kLF, "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen*3, 0);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the <p>'s children");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
+ checkSelection(kLFLen*3, "", "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #9 (ContentEventHandler cannot distinguish if <p> can have children, so, the result is same as case #5, "<br>")
+ contenteditable.innerHTML = "<p></p>";
+
+ synthesizeSelectionSet(kLFLen, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node + 1");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the index of the <p> node + 1");
+ checkSelection(kLFLen, "", "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(kLFLen, 1);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node + 1");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #9 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #9 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(kLFLen, "", "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #10
+ contenteditable.innerHTML = "";
+
+ synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, 1);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\"");
+
+ // #11
+ contenteditable.innerHTML = "<span></span><i><u></u></i>";
+
+ synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(0, 1);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(0, "", "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\"");
+
+ // #12
+ contenteditable.innerHTML = "<span>abc</span><i><u></u></i>";
+
+ synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #13
+ contenteditable.innerHTML = "<span></span><i>abc<u></u></i>";
+
+ synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable.childNodes.item(1).firstChild,
+ "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.childNodes.item(1).firstChild,
+ "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #14
+ contenteditable.innerHTML = "<span></span><i><u>abc</u></i>";
+
+ synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable.childNodes.item(1).firstChild.firstChild,
+ "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.childNodes.item(1).firstChild.firstChild,
+ "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #15
+ contenteditable.innerHTML = "<span></span><i><u></u>abc</i>";
+
+ synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable.childNodes.item(1).lastChild,
+ "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.childNodes.item(1).lastChild,
+ "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #16
+ contenteditable.innerHTML = "a<blink>b</blink>c";
+ synthesizeSelectionSet(0, 3);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, contenteditable.lastChild.wholeText.length,
+ "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(0, "abc", "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\"");
+
+ // #17 (bug 1319660 - incorrect adjustment of content iterator last node)
+ contenteditable.innerHTML = "<div>a</div><div><br></div>";
+
+ synthesizeSelectionSet(kLFLen, 1+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <div> element");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <div> element");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(kLFLen, "a" + kLF, "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ synthesizeSelectionSet(1+2*kLFLen, 0);
+ is(selection.anchorNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the second <div> element");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <div> element");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(1+2*kLFLen, "", "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #18 (bug 1319660 - content iterator start node regression)
+ contenteditable.innerHTML = "<div><br></div><div><br></div>";
+
+ synthesizeSelectionSet(2*kLFLen, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <div> element");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <div> element");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(2*kLFLen, kLF, "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+}
+
+function runQueryTextContentEventTest()
+{
+ contenteditable.focus();
+
+ let result;
+
+ // #1
+ contenteditable.innerHTML = "abc<br>def";
+
+ result = synthesizeQueryTextContent(0, 6 + kLFLen);
+ is(result.text, "abc" + kLF + "def", "runQueryTextContentEventTest #1 (0, 6+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, 100);
+ is(result.text, "abc" + kLF + "def", "runQueryTextContentEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(2, 2 + kLFLen);
+ is(result.text, "c" + kLF + "d", "runQueryTextContentEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(1, 2);
+ is(result.text, "bc", "runQueryTextContentEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(6 + kLFLen, 1);
+ is(result.text, "", "runQueryTextContentEventTest #1 (6 + kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #2
+ contenteditable.innerHTML = "<p>a<b>b</b>c</p><p>def</p>";
+
+ result = synthesizeQueryTextContent(kLFLen, 4+kLFLen);
+ is(result.text, "abc" + kLF + "d", "runQueryTextContentEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen, 2);
+ is(result.text, "ab", "runQueryTextContentEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(1+kLFLen, 2);
+ is(result.text, "bc", "runQueryTextContentEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(2+kLFLen, 2+kLFLen);
+ is(result.text, "c" + kLF + "d", "runQueryTextContentEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3+kLFLen*2, 1);
+ is(result.text, "d", "runQueryTextContentEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(2+kLFLen, 1+kLFLen);
+ is(result.text, "c" + kLF, "runQueryTextContentEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3+kLFLen, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3+kLFLen, 1+kLFLen);
+ is(result.text, kLF + "d", "runQueryTextContentEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ // #3
+ contenteditable.innerHTML = "<div>abc<p>def</p></div>";
+
+ result = synthesizeQueryTextContent(1+kLFLen, 2);
+ is(result.text, "bc", "runQueryTextContentEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(1+kLFLen, 3+kLFLen);
+ is(result.text, "bc" + kLF + "d", "runQueryTextContentEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3+kLFLen*2, 1);
+ is(result.text, "d", "runQueryTextContentEventTest #3 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, 6+kLFLen*2);
+ is(result.text, kLF + "abc" + kLF + "def", "runQueryTextContentEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, 100);
+ is(result.text, kLF + "abc" + kLF + "def", "runQueryTextContentEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(4+kLFLen*2, 2);
+ is(result.text, "ef", "runQueryTextContentEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(4+kLFLen*2, 100);
+ is(result.text, "ef", "runQueryTextContentEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(6+kLFLen*2, 1);
+ is(result.text, "", "runQueryTextContentEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, 1+kLFLen);
+ is(result.text, kLF + "a", "runQueryTextContentEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(2+kLFLen, 1+kLFLen);
+ is(result.text, "c" + kLF, "runQueryTextContentEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3+kLFLen, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3+kLFLen, 1+kLFLen);
+ is(result.text, kLF + "d", "runQueryTextContentEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ // #4
+ contenteditable.innerHTML = "<div><p>abc</p>def</div>";
+
+ result = synthesizeQueryTextContent(1+kLFLen*2, 2);
+ is(result.text, "bc", "runQueryTextContentEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(1+kLFLen*2, 3);
+ is(result.text, "bcd", "runQueryTextContentEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3+kLFLen*2, 1);
+ is(result.text, "d", "runQueryTextContentEventTest #4 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, 6+kLFLen*2);
+ is(result.text, kLF + kLF + "abcdef", "runQueryTextContentEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, 100);
+ is(result.text, kLF + kLF + "abcdef", "runQueryTextContentEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(4+kLFLen*2, 2);
+ is(result.text, "ef", "runQueryTextContentEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(4+kLFLen*2, 100);
+ is(result.text, "ef", "runQueryTextContentEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(6+kLFLen*2, 1);
+ is(result.text, "", "runQueryTextContentEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, kLFLen*2);
+ is(result.text, kLF + kLF, "runQueryTextContentEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, 1+kLFLen*2);
+ is(result.text, kLF + kLF + "a", "runQueryTextContentEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen, 1+kLFLen);
+ is(result.text, kLF + "a", "runQueryTextContentEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ // #5
+ contenteditable.innerHTML = "<br>";
+
+ result = synthesizeQueryTextContent(0, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen, 1);
+ is(result.text, "", "runQueryTextContentEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\"");
+
+ // #6
+ contenteditable.innerHTML = "<p><br></p>";
+
+ result = synthesizeQueryTextContent(kLFLen, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen*2, 1);
+ is(result.text, "", "runQueryTextContentEventTest #5 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, kLFLen*2);
+ is(result.text, kLF + kLF, "runQueryTextContentEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ // #7
+ contenteditable.innerHTML = "<br><br>";
+
+ result = synthesizeQueryTextContent(0, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, kLFLen * 2);
+ is(result.text, kLF + kLF, "runQueryTextContentEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen * 2, 1);
+ is(result.text, "", "runQueryTextContentEventTest #7 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ // #8
+ contenteditable.innerHTML = "<p><br><br></p>";
+
+ result = synthesizeQueryTextContent(kLFLen, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen, kLFLen * 2);
+ is(result.text, kLF + kLF, "runQueryTextContentEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen*2, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen*3, 1);
+ is(result.text, "", "runQueryTextContentEventTest #8 (kLFLen*3, 1), \"" + contenteditable.innerHTML + "\"");
+
+ // #16
+ contenteditable.innerHTML = "a<blink>b</blink>c";
+
+ result = synthesizeQueryTextContent(0, 3);
+ is(result.text, "abc", "runQueryTextContentEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\"");
+}
+
+function runQuerySelectionEventTest()
+{
+ contenteditable.focus();
+
+ let selection = windowOfContenteditable.getSelection();
+
+ // #1
+ contenteditable.innerHTML = "<br/>a";
+ selection.setBaseAndExtent(contenteditable.firstChild, 0, contenteditable.lastChild, 1);
+ checkSelection(0, kLF + "a", "runQuerySelectionEventTest #1, \"" + contenteditable.innerHTML + "\"");
+
+ // #2
+ contenteditable.innerHTML = "<p></p><p>abc</p>";
+ selection.setBaseAndExtent(contenteditable.firstChild, 0, contenteditable.lastChild.firstChild, 1);
+ checkSelection(kLFLen, kLF + "a", "runQuerySelectionEventTest #2, \"" + contenteditable.innerHTML + "\"");
+
+ // #3
+ contenteditable.innerHTML = "<p>abc</p><p>def</p>";
+ selection.setBaseAndExtent(contenteditable.firstChild, 0, contenteditable.lastChild.firstChild, 1);
+ checkSelection(kLFLen, "abc" + kLF + "d", "runQuerySelectionEventTest #3, \"" + contenteditable.innerHTML + "\"");
+}
+
+function runQueryIMESelectionTest()
+{
+ textarea.focus();
+ textarea.value = "before after";
+ let startoffset = textarea.selectionStart = textarea.selectionEnd = "before ".length;
+
+ if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: before starting composition") ||
+ !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: before starting composition") ||
+ !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: before starting composition") ||
+ !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: before starting composition")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "a",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ if (!checkIMESelection("RawClause", true, startoffset, "a", "runQueryIMESelectionTest: inputting raw text") ||
+ !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: inputting raw text") ||
+ !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: inputting raw text") ||
+ !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: inputting raw text")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "abcdefgh",
+ "clauses":
+ [
+ { "length": 8, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 8, "length": 0 }
+ });
+
+ if (!checkIMESelection("RawClause", true, startoffset, "abcdefgh", "runQueryIMESelectionTest: updating raw text") ||
+ !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: updating raw text") ||
+ !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: updating raw text") ||
+ !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: updating raw text")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABCDEFGH",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 3, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": 3, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: starting to convert") ||
+ !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: starting to convert") ||
+ !checkIMESelection("ConvertedClause", true, startoffset + 2, "CDE", "runQueryIMESelectionTest: starting to convert") ||
+ !checkIMESelection("SelectedClause", true, startoffset, "AB", "runQueryIMESelectionTest: starting to convert")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABCDEFGH",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": 3, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 3, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ ]
+ },
+ "caret": { "start": 5, "length": 0 }
+ });
+
+ if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: changing selected clause") ||
+ !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: changing selected clause") ||
+ !checkIMESelection("ConvertedClause", true, startoffset, "AB", "runQueryIMESelectionTest: changing selected clause") ||
+ !checkIMESelection("SelectedClause", true, startoffset + 2, "CDE", "runQueryIMESelectionTest: changing selected clause")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: after committing composition") ||
+ !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: after committing composition") ||
+ !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: after committing composition") ||
+ !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: after committing composition")) {
+ return;
+ }
+
+ startoffset = textarea.selectionStart;
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "abcdefgh",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ ]
+ },
+ "caret": { "start": 8, "length": 0 }
+ });
+
+ if (!checkIMESelection("RawClause", true, startoffset, "a", "runQueryIMESelectionTest: unrealistic testcase") ||
+ !checkIMESelection("SelectedRawClause", true, startoffset + 1, "b", "runQueryIMESelectionTest: unrealistic testcase") ||
+ !checkIMESelection("ConvertedClause", true, startoffset + 2, "c", "runQueryIMESelectionTest: unrealistic testcase") ||
+ !checkIMESelection("SelectedClause", true, startoffset + 3, "d", "runQueryIMESelectionTest: unrealistic testcase")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+}
+
+function runQueryPasswordTest() {
+ function checkRange(aOffset, aLength, aExpectedResult, aDescription) {
+ password.focus();
+ let result = synthesizeQueryTextContent(aOffset, aLength);
+ is(result.text, aExpectedResult,
+ `${aDescription}: synthesizeQueryTextContent(${aOffset}, ${aLength})`);
+ password.setSelectionRange(aOffset, aOffset + aLength);
+ result = synthesizeQuerySelectedText();
+ is(result.text, aExpectedResult,
+ `${aDescription}: synthesizeQuerySelectedText(${aOffset}, ${aLength})`);
+ }
+
+ let editor = password.editor;
+ const kMask = editor.passwordMask;
+ password.value = "abcdef";
+
+ editor.mask();
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range is not specified #1");
+ checkRange(0, 3, `${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range is not specified #2");
+ checkRange(3, 3, `${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range is not specified #3");
+ checkRange(2, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range is not specified #4");
+
+ editor.unmask(0, 6);
+ checkRange(0, 6, "abcdef",
+ "runQueryPasswordTest: unmasked range 0-6 #1");
+ checkRange(0, 3, "abc",
+ "runQueryPasswordTest: unmasked range 0-6 #2");
+ checkRange(3, 3, "def",
+ "runQueryPasswordTest: unmasked range 0-6 #3");
+ checkRange(2, 2, "cd",
+ "runQueryPasswordTest: unmasked range 0-6 #4");
+
+ editor.unmask(0, 3);
+ checkRange(0, 6, `abc${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range 0-3 #1");
+ checkRange(0, 3, "abc",
+ "runQueryPasswordTest: unmasked range 0-3 #2");
+ checkRange(3, 3, `${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range 0-3 #3");
+ checkRange(2, 2, `c${kMask}`,
+ "runQueryPasswordTest: unmasked range 0-3 #4");
+
+ editor.unmask(3, 6);
+ checkRange(0, 6, `${kMask}${kMask}${kMask}def`,
+ "runQueryPasswordTest: unmasked range 3-6 #1");
+ checkRange(0, 3, `${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range 3-6 #2");
+ checkRange(3, 3, `def`,
+ "runQueryPasswordTest: unmasked range 3-6 #3");
+ checkRange(2, 2, `${kMask}d`,
+ "runQueryPasswordTest: unmasked range 3-6 #4");
+
+ editor.unmask(2, 4);
+ checkRange(0, 6, `${kMask}${kMask}cd${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range 3-4 #1");
+ checkRange(1, 2, `${kMask}c`,
+ "runQueryPasswordTest: unmasked range 3-4 #2");
+ checkRange(1, 3, `${kMask}cd`,
+ "runQueryPasswordTest: unmasked range 3-4 #3");
+ checkRange(1, 4, `${kMask}cd${kMask}`,
+ "runQueryPasswordTest: unmasked range 3-4 #4");
+ checkRange(2, 2, "cd",
+ "runQueryPasswordTest: unmasked range 3-4 #5");
+ checkRange(2, 3, `cd${kMask}`,
+ "runQueryPasswordTest: unmasked range 3-4 #6");
+
+
+ const kEmoji = String.fromCodePoint(0x1f914);
+ password.value = `${kEmoji}${kEmoji}${kEmoji}`
+
+ editor.mask();
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range is not specified");
+
+ editor.unmask(0, 2);
+ checkRange(0, 6, `${kEmoji}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 0-2 #1");
+ checkRange(0, 2, `${kEmoji}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 0-2 #2");
+ checkRange(2, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 0-2 #3");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 0-2 #4");
+
+ editor.unmask(2, 4);
+ checkRange(0, 6, `${kMask}${kMask}${kEmoji}${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 2-4 #1");
+ checkRange(0, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 2-4 #2");
+ checkRange(2, 2, `${kEmoji}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 2-4 #3");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 2-4 #4");
+
+ editor.unmask(4, 6);
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kEmoji}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 4-6 #1");
+ checkRange(0, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 4-6 #2");
+ checkRange(2, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 4-6 #3");
+ checkRange(4, 2, `${kEmoji}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 4-6 #4");
+
+ editor.unmask(0, 1);
+ checkRange(0, 6, `${kEmoji}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 0-1");
+
+ editor.unmask(1, 2);
+ checkRange(0, 6, `${kEmoji}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 1-2");
+
+ editor.unmask(2, 3);
+ checkRange(0, 6, `${kMask}${kMask}${kEmoji}${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 2-3");
+
+ editor.unmask(3, 4);
+ checkRange(0, 6, `${kMask}${kMask}${kEmoji}${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 3-4");
+
+ editor.unmask(4, 5);
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kEmoji}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 4-5");
+
+ editor.unmask(5, 6);
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kEmoji}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 5-6");
+
+
+ const kEmojiSuperhero = String.fromCodePoint(0x1f9b8);
+ const kEmojiMediumSkinTone = String.fromCodePoint(0x1f3fd);
+ const kZeroWidthJoiner = "\u200d";
+ const kFemaleSign = "\u2640";
+ const kVariationSelector16 = "\ufe0f";
+ const kComplicatedEmoji = `${kEmojiSuperhero}${kEmojiMediumSkinTone}${kZeroWidthJoiner}${kFemaleSign}${kVariationSelector16}`;
+ password.value = `${kComplicatedEmoji}${kComplicatedEmoji}${kComplicatedEmoji}`
+ editor.mask();
+ checkRange(0, 21, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range is not specified");
+
+ editor.unmask(0, 7);
+ checkRange(0, 21, `${kComplicatedEmoji}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 0-7 #1");
+ checkRange(0, 7, `${kComplicatedEmoji}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 0-7 #2");
+ checkRange(7, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 0-7 #3");
+ checkRange(14, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 0-7 #4");
+
+ editor.unmask(7, 14);
+ checkRange(0, 21, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kComplicatedEmoji}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 7-14 #1");
+ checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 7-14 #2");
+ checkRange(7, 7, `${kComplicatedEmoji}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 7-14 #3");
+ checkRange(14, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 7-14 #4");
+
+ editor.unmask(14, 21);
+ checkRange(0, 21, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kComplicatedEmoji}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 14-21 #1");
+ checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 14-21 #2");
+ checkRange(7, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 14-21 #3");
+ checkRange(14, 7, `${kComplicatedEmoji}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 14-21 #4");
+
+ password.value = `${kComplicatedEmoji}`
+ editor.unmask(0, 1);
+ checkRange(0, 7, `${kEmojiSuperhero}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emoji in password, unmasked range 0-1");
+
+ editor.unmask(1, 2);
+ checkRange(0, 7, `${kEmojiSuperhero}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emoji in password, unmasked range 1-2");
+
+ editor.unmask(2, 3);
+ checkRange(0, 7, `${kMask}${kMask}${kEmojiMediumSkinTone}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emoji in password, unmasked range 2-3");
+
+ editor.unmask(3, 4);
+ checkRange(0, 7, `${kMask}${kMask}${kEmojiMediumSkinTone}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emoji in password, unmasked range 3-4");
+
+ editor.unmask(4, 5);
+ checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kZeroWidthJoiner}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emoji in password, unmasked range 4-5");
+
+ editor.unmask(5, 6);
+ checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kFemaleSign}${kMask}`,
+ "runQueryPasswordTest: Complicated emoji in password, unmasked range 5-6");
+
+ editor.unmask(6, 7);
+ checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kVariationSelector16}`,
+ "runQueryPasswordTest: Complicated emoji in password, unmasked range 6-7");
+
+
+ const kKanji = "\u8fba";
+ const kIVS = String.fromCodePoint(0xe0101);
+ const kKanjiWithIVS = `${kKanji}${kIVS}`;
+ password.value = `${kKanjiWithIVS}${kKanjiWithIVS}${kKanjiWithIVS}`
+
+ editor.mask();
+ checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range is not specified");
+
+ editor.unmask(0, 3);
+ checkRange(0, 9, `${kKanjiWithIVS}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #1");
+ checkRange(0, 3, `${kKanjiWithIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #2");
+ checkRange(1, 3, `${kIVS}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #3");
+ checkRange(0, 1, `${kKanji}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #4");
+ checkRange(1, 2, `${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #5");
+ checkRange(3, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #6");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #7");
+ checkRange(6, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #8");
+ checkRange(7, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #9");
+
+ editor.unmask(0, 1);
+ checkRange(0, 9, `${kKanji}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #1");
+ checkRange(0, 1, `${kKanji}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #2");
+ checkRange(1, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #3");
+ checkRange(3, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #4");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #5");
+ checkRange(6, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #6");
+ checkRange(7, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #7");
+
+ editor.unmask(1, 3);
+ checkRange(0, 9, `${kMask}${kIVS}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #1");
+ checkRange(0, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #2");
+ checkRange(1, 2, `${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #3");
+ checkRange(3, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #4");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #5");
+ checkRange(6, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #6");
+ checkRange(7, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #7");
+
+ editor.unmask(3, 6);
+ checkRange(0, 9, `${kMask}${kMask}${kMask}${kKanjiWithIVS}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #1");
+ checkRange(3, 3, `${kKanjiWithIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #2");
+ checkRange(4, 3, `${kIVS}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #3");
+ checkRange(0, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #4");
+ checkRange(1, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #5");
+ checkRange(3, 1, `${kKanji}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #6");
+ checkRange(4, 2, `${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #7");
+ checkRange(6, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #8");
+ checkRange(7, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #9");
+
+ editor.unmask(3, 4);
+ checkRange(0, 9, `${kMask}${kMask}${kMask}${kKanji}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #1");
+ checkRange(0, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #2");
+ checkRange(1, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #3");
+ checkRange(3, 1, `${kKanji}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #4");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #5");
+ checkRange(6, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #6");
+ checkRange(7, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #7");
+
+ editor.unmask(4, 6);
+ checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kIVS}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #1");
+ checkRange(0, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #2");
+ checkRange(1, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #3");
+ checkRange(3, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #4");
+ checkRange(4, 2, `${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #5");
+ checkRange(6, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #6");
+ checkRange(7, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #7");
+
+ editor.unmask(6, 9);
+ checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kKanjiWithIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #1");
+ checkRange(6, 3, `${kKanjiWithIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #2");
+ checkRange(4, 3, `${kMask}${kMask}${kKanji}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #3");
+ checkRange(0, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #4");
+ checkRange(1, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #5");
+ checkRange(3, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #6");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #7");
+ checkRange(6, 1, `${kKanji}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #8");
+ checkRange(7, 2, `${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #9");
+
+ editor.unmask(6, 7);
+ checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kKanji}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #1");
+ checkRange(0, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #2");
+ checkRange(1, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #3");
+ checkRange(3, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #4");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #5");
+ checkRange(6, 1, `${kKanji}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #6");
+ checkRange(7, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #7");
+
+ editor.unmask(7, 9);
+ checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #1");
+ checkRange(0, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #2");
+ checkRange(1, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #3");
+ checkRange(3, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #4");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #5");
+ checkRange(6, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #6");
+ checkRange(7, 2, `${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #7");
+
+ password.value = `${kKanjiWithIVS}${kKanjiWithIVS}`;
+ editor.unmask(0, 2);
+ checkRange(0, 6, `${kKanjiWithIVS}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-2");
+
+ editor.unmask(1, 2);
+ checkRange(0, 6, `${kMask}${kIVS}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-2");
+
+ editor.unmask(2, 3);
+ checkRange(0, 6, `${kMask}${kIVS}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 2-3");
+
+ editor.unmask(3, 5);
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kKanjiWithIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-5");
+
+ editor.unmask(4, 5);
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-5");
+
+ editor.unmask(5, 6);
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 5-6");
+
+ editor.mask();
+}
+
+function runQueryContentEventRelativeToInsertionPoint()
+{
+ textarea.focus();
+ textarea.value = "0123456789";
+
+ // "[]0123456789"
+ let startOffset = textarea.selectionStart = textarea.selectionEnd = 0;
+ if (!checkContentRelativeToSelection(0, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, 1, "1", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, 5, "56789", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#4") ||
+ !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#5")) {
+ return;
+ }
+
+ // "[01234]56789"
+ textarea.selectionEnd = 5;
+ if (!checkContentRelativeToSelection(0, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, 1, "1", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, 5, "56789", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#4") ||
+ !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#5")) {
+ return;
+ }
+
+ // "0123[]456789"
+ startOffset = textarea.selectionStart = textarea.selectionEnd = 4;
+ if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "4", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, startOffset + 1, "5", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, startOffset + 5, "9", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#4") ||
+ !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#5")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "a",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ // "0123[a]456789"
+ if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "a", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, startOffset + 1, "4", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, startOffset + 5, "89", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#4") ||
+ !checkContentRelativeToSelection(11, 1, 11, "", "runQueryContentEventRelativeToInsertionPoint[composition at 4]")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ // Move start of composition at first compositionupdate event.
+ function onCompositionUpdate(aEvent)
+ {
+ startOffset = textarea.selectionStart = textarea.selectionEnd = textarea.selectionStart - 1;
+ textarea.removeEventListener("compositionupdate", onCompositionUpdate);
+ }
+ textarea.addEventListener("compositionupdate", onCompositionUpdate);
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "b",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ // "0123[b]a456789"
+ if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "b", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, startOffset + 1, "a", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, startOffset + 5, "789", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#4") ||
+ !checkContentRelativeToSelection(12, 1, 12, "", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#5")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+}
+
+function runBug1375825Test()
+{
+ contenteditable.focus();
+
+ // #1
+ contenteditable.innerHTML = "abc<span contenteditable=\"false\">defgh</span>";
+
+ let ret = synthesizeQueryTextRect(2, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #1 (2, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "c", "runBug1375825Test #1 (2, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'c'");
+
+ ret = synthesizeQueryTextRect(3, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #1 (3, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "d", "runBug1375825Test #1 (3, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'd'");
+
+ ret = synthesizeQueryTextRect(4, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #1 (4, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "e", "runBug1375825Test #1 (4, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'e'");
+
+ ret = synthesizeQueryTextRect(5, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #1 (5, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "f", "runBug1375825Test #1 (5, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'f'");
+
+ ret = synthesizeQueryTextRect(6, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #1 (6, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "g", "runBug1375825Test #1 (6, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'g'");
+
+ ret = synthesizeQueryTextRect(7, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #1 (7, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "h", "runBug1375825Test #1 (7, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'h'");
+
+ // #2
+ contenteditable.innerHTML = "abc<span style=\"user-select: all;\">defgh</span>";
+
+ ret = synthesizeQueryTextRect(2, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #2 (2, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "c", "runBug1375825Test #2 (2, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'c'");
+
+ ret = synthesizeQueryTextRect(3, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #2 (3, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "d", "runBug1375825Test #2 (3, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'd'");
+
+ ret = synthesizeQueryTextRect(4, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #2 (4, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "e", "runBug1375825Test #2 (4, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'e'");
+
+ ret = synthesizeQueryTextRect(5, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #2 (5, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "f", "runBug1375825Test #2 (5, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'f'");
+
+ ret = synthesizeQueryTextRect(6, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #2 (6, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "g", "runBug1375825Test #2 (6, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'g'");
+
+ ret = synthesizeQueryTextRect(7, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #2 (7, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "h", "runBug1375825Test #2 (7, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'h'");
+}
+
+function runBug1530649Test()
+{
+ // Vietnamese IME on macOS commits composition with typing space key.
+ // Then, typing new word shouldn't trim the trailing whitespace.
+ contenteditable.focus();
+ contenteditable.innerHTML = "";
+ synthesizeCompositionChange(
+ {composition: {string: "abc", clauses: [{length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ caret: {start: 3, length: 0}});
+ synthesizeComposition({type: "compositioncommit", data: "abc ", key: " "});
+
+ is(contenteditable.innerHTML, "abc <br>",
+ "runBug1530649Test: The trailing space shouldn't be removed");
+
+ synthesizeCompositionChange(
+ {composition: {string: "d", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ caret: {start: 1, length: 0}});
+
+ is(contenteditable.innerHTML, "abc d<br>",
+ "runBug1530649Test: The new composition string shouldn't remove the last space");
+
+ synthesizeComposition({type: "compositioncommitasis", key: "KEY_Enter"});
+
+ is(contenteditable.innerHTML, "abc d<br>",
+ "runBug1530649Test: Committing the new composition string shouldn't remove the last space");
+}
+
+function runBug1571375Test()
+{
+ let selection = windowOfContenteditableBySpan.getSelection();
+ let doc = document.getElementById("iframe7").contentDocument;
+
+ contenteditableBySpan.focus();
+
+ contenteditableBySpan.innerHTML = "hello world";
+ let range = doc.createRange();
+ range.setStart(contenteditableBySpan.firstChild, 6);
+ range.setEnd(contenteditableBySpan.firstChild, 11);
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ synthesizeCompositionChange({
+ composition: {string: "world", clauses: [{length: 5, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ caret: { start: 5, length: 0 },
+ });
+ synthesizeComposition({type: "compositioncommit", data: "world", key: " "});
+ is(contenteditableBySpan.innerHTML, "hello world",
+ "runBug1571375Test: space must not be removed by commit");
+
+ contenteditableBySpan.innerHTML = "hello world";
+ range = doc.createRange();
+ range.setStart(contenteditableBySpan.firstChild, 0);
+ range.setEnd(contenteditableBySpan.firstChild, 5);
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ synthesizeCompositionChange({
+ composition: {string: "hello", clauses: [{length: 5, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ caret: { start: 5, length: 0 },
+ });
+ synthesizeComposition({type: "compositioncommit", data: "hello", key: " "});
+ is(contenteditableBySpan.innerHTML, "hello world",
+ "runBug1571375Test: space must not be removed by commit");
+
+ contenteditableBySpan.innerHTML = "hello world<div>.</div>";
+ range = doc.createRange();
+ range.setStart(contenteditableBySpan.firstChild, 6);
+ range.setEnd(contenteditableBySpan.firstChild, 11);
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ synthesizeCompositionChange({
+ composition: {string: "world", clauses: [{length: 5, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ caret: {start: 0, length: 0}}
+ );
+ synthesizeComposition({type: "compositioncommit", data: "world", key: " "});
+ is(contenteditableBySpan.innerHTML, "hello world<div>.</div>",
+ "runBug1571375Test: space must not be removed by commit");
+}
+
+async function runBug1584901Test()
+{
+ contenteditableBySpan.focus();
+ contenteditableBySpan.innerHTML = "";
+
+ // XXX synthesizeCompositionChange won't work without wait.
+ await waitForTick();
+
+ synthesizeCompositionChange({
+ composition: {string: "a ", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ });
+ synthesizeComposition({type: "compositioncommitasis", key: " "});
+
+ is(contenteditableBySpan.innerHTML, "a&nbsp;",
+ "runBug1584901Test: space must not be removed by composition change");
+
+ synthesizeCompositionChange({
+ composition: {string: "b ", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ });
+ synthesizeComposition({type: "compositioncommitasis", key: " "});
+
+ is(contenteditableBySpan.innerHTML, "a b&nbsp;",
+ "runBug1584901Test: space must not be removed by composition change");
+}
+
+function runBug1675313Test()
+{
+ input.value = "";
+ input.focus();
+ let count = 0;
+
+ function handler() {
+ input.focus();
+ count++;
+ }
+
+ input.addEventListener("keydown", handler);
+ input.addEventListener("keyup", handler);
+
+ synthesizeCompositionChange({
+ composition: {
+ string: "a",
+ clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
+ key: { key: "a", type: "keyup" },
+ },
+ });
+ synthesizeCompositionChange({
+ composition: {
+ string: "b",
+ clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
+ key: { key: "b", type: "keyup" },
+ },
+ });
+ synthesizeComposition({type: "compositioncommitasis"});
+
+ is(count, 6, "runBug1675313Test: keydown event and keyup event are fired correctly");
+ is(input.value, "b",
+ "runBug1675313Test: re-focus element doesn't commit composition if re-focus isn't click by user");
+
+ input.removeEventListener("keyup", handler);
+}
+
+function runCommitCompositionWithSpaceKey()
+{
+ contenteditable.focus();
+ contenteditable.innerHTML = "";
+
+ // Last white space might be &nbsp; if last child is no <br>
+ // Actually, our implementation will insert <br> element at last child, so
+ // white space will be ASCII space.
+
+ synthesizeCompositionChange({
+ composition: {string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ });
+ synthesizeComposition({type: "compositioncommit", data: "a"});
+ synthesizeKey(" ");
+
+ is(contenteditable.innerHTML, "a <br>",
+ "runCommitCompositionWithSpaceKey: last single space should be kept");
+
+ synthesizeCompositionChange({
+ composition: {string: "b", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ });
+ synthesizeComposition({type: "compositioncommit", data: "b"});
+ synthesizeKey(" ");
+
+ is(contenteditable.innerHTML, "a b <br>",
+ "runCommitCompositionWithSpaceKey: inserting composition shouldn't remove last single space.");
+
+ synthesizeCompositionChange({
+ composition: {string: "c", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ });
+ synthesizeComposition({type: "compositioncommit", data: "c"});
+ synthesizeKey(" ");
+
+ is(contenteditable.innerHTML, "a b c <br>",
+ "runCommitCompositionWithSpaceKey: inserting composition shouldn't remove last single space.");
+
+ contenteditable.innerHTML = "a";
+ windowOfContenteditable.getSelection().collapse(contenteditable.firstChild, contenteditable.firstChild.length);
+ is(contenteditable.innerHTML, "a",
+ "runCommitCompositionWithSpaceKey: contenteditable should be initialized with text ending with a space and without following <br> element");
+
+ synthesizeCompositionChange({
+ composition: {string: "b", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ });
+ synthesizeComposition({type: "compositioncommit", data: "b ", key: { key: " ", code: "Space" }});
+
+ is(contenteditable.innerHTML, "ab <br>",
+ "runCommitCompositionWithSpaceKey: contenteditable should end with a padding <br> element after inserting commit string ending with a space");
+}
+
+function runCSSTransformTest()
+{
+ textarea.focus();
+ textarea.value = "some text";
+ textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
+ let editorRect = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRect,
+ "runCSSTransformTest: editorRect")) {
+ return;
+ }
+ let firstCharRect = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(firstCharRect,
+ "runCSSTransformTest: firstCharRect")) {
+ return;
+ }
+ let lastCharRect = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRect,
+ "runCSSTransformTest: lastCharRect")) {
+ return;
+ }
+ let caretRect = synthesizeQueryCaretRect(textarea.selectionStart);
+ if (!checkQueryContentResult(caretRect,
+ "runCSSTransformTest: caretRect")) {
+ return;
+ }
+ let caretRectBeforeFirstChar = synthesizeQueryCaretRect(0);
+ if (!checkQueryContentResult(caretRectBeforeFirstChar,
+ "runCSSTransformTest: caretRectBeforeFirstChar")) {
+ return;
+ }
+
+ try {
+ textarea.style.transform = "translate(10px, 15px)";
+ function movedRect(aRect, aCSS_CX, aCSS_CY)
+ {
+ return {
+ left: aRect.left + Math.round(aCSS_CX * window.devicePixelRatio),
+ top: aRect.top + Math.round(aCSS_CY * window.devicePixelRatio),
+ width: aRect.width,
+ height: aRect.height
+ };
+ }
+
+ let editorRectTranslated = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRectTranslated,
+ "runCSSTransformTest: editorRectTranslated, " + textarea.style.transform) ||
+ !checkRectFuzzy(editorRectTranslated, movedRect(editorRect, 10, 15), {left: 1, top: 1, width: 1, height: 1},
+ "runCSSTransformTest: editorRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ let firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(firstCharRectTranslated,
+ "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform) ||
+ !checkRectFuzzy(firstCharRectTranslated, movedRect(firstCharRect, 10, 15), {left: 1, top: 1, width: 1, height: 1},
+ "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ let lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRectTranslated,
+ "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform) ||
+ !checkRectFuzzy(lastCharRectTranslated, movedRect(lastCharRect, 10, 15), {left: 1, top: 1, width: 1, height: 1},
+ "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ let caretRectTranslated = synthesizeQueryCaretRect(textarea.selectionStart);
+ if (!checkQueryContentResult(caretRectTranslated,
+ "runCSSTransformTest: caretRectTranslated, " + textarea.style.transform) ||
+ !checkRectFuzzy(caretRectTranslated, movedRect(caretRect, 10, 15), {left: 1, top: 1, width: 1, height: 1},
+ "runCSSTransformTest: caretRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ let caretRectBeforeFirstCharTranslated = synthesizeQueryCaretRect(0);
+ if (!checkQueryContentResult(caretRectBeforeFirstCharTranslated,
+ "runCSSTransformTest: caretRectBeforeFirstCharTranslated, " + textarea.style.transform) ||
+ !checkRectFuzzy(caretRectBeforeFirstCharTranslated, movedRect(caretRectBeforeFirstChar, 10, 15), {left: 1, top: 1, width: 1, height: 1},
+ "runCSSTransformTest: caretRectBeforeFirstCharTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ let firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
+ if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
+ !checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
+ return;
+ }
+ let lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
+ !checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
+ return;
+ }
+
+ // XXX It's too difficult to check the result with scale and rotate...
+ // For now, let's check if query text rect and query text rect array returns same rect.
+ textarea.style.transform = "scale(1.5)";
+ firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(firstCharRectTranslated,
+ "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRectTranslated,
+ "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
+ if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
+ !checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
+ return;
+ }
+ lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
+ !checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
+ return;
+ }
+
+ textarea.style.transform = "rotate(30deg)";
+ firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(firstCharRectTranslated,
+ "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRectTranslated,
+ "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
+ if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
+ !checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
+ return;
+ }
+ lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
+ !checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
+ return;
+ }
+ } finally {
+ textarea.style.transform = "";
+ }
+}
+
+function runBug722639Test()
+{
+ textarea.focus();
+ textarea.value = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
+ textarea.value += textarea.value;
+ textarea.value += textarea.value; // 80 characters
+
+ let firstLine = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(firstLine,
+ "runBug722639Test: firstLine")) {
+ return;
+ }
+ ok(true, "runBug722639Test: 1st line, top=" + firstLine.top + ", left=" + firstLine.left);
+ let firstLineAsArray = synthesizeQueryTextRectArray(0, 1);
+ if (!checkQueryContentResult(firstLineAsArray, "runBug722639Test: 1st line as array") ||
+ !checkRectArray(firstLineAsArray, [firstLine], "runBug722639Test: 1st line as array should match with text rect result")) {
+ return;
+ }
+ if (kLFLen > 1) {
+ let firstLineLF = synthesizeQueryTextRect(1, 1);
+ if (!checkQueryContentResult(firstLineLF,
+ "runBug722639Test: firstLineLF")) {
+ return;
+ }
+ is(firstLineLF.top, firstLine.top, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
+ is(firstLineLF.left, firstLine.left, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
+ isfuzzy(firstLineLF.height, firstLine.height, 1,
+ "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
+ is(firstLineLF.width, firstLine.width, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
+ let firstLineLFAsArray = synthesizeQueryTextRectArray(1, 1);
+ if (!checkQueryContentResult(firstLineLFAsArray, "runBug722639Test: 1st line's \\n rect as array") ||
+ !checkRectArray(firstLineLFAsArray, [firstLineLF], "runBug722639Test: 1st line's rect as array should match with text rect result")) {
+ return;
+ }
+ }
+ let secondLine = synthesizeQueryTextRect(kLFLen, 1);
+ if (!checkQueryContentResult(secondLine,
+ "runBug722639Test: secondLine")) {
+ return;
+ }
+ ok(true, "runBug722639Test: 2nd line, top=" + secondLine.top + ", left=" + secondLine.left);
+ let secondLineAsArray = synthesizeQueryTextRectArray(kLFLen, 1);
+ if (!checkQueryContentResult(secondLineAsArray, "runBug722639Test: 2nd line as array") ||
+ !checkRectArray(secondLineAsArray, [secondLine], "runBug722639Test: 2nd line as array should match with text rect result")) {
+ return;
+ }
+ if (kLFLen > 1) {
+ let secondLineLF = synthesizeQueryTextRect(kLFLen + 1, 1);
+ if (!checkQueryContentResult(secondLineLF,
+ "runBug722639Test: secondLineLF")) {
+ return;
+ }
+ is(secondLineLF.top, secondLine.top, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
+ is(secondLineLF.left, secondLine.left, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
+ isfuzzy(secondLineLF.height, secondLine.height, 1,
+ "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
+ is(secondLineLF.width, secondLine.width, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
+ let secondLineLFAsArray = synthesizeQueryTextRectArray(kLFLen + 1, 1);
+ if (!checkQueryContentResult(secondLineLFAsArray, "runBug722639Test: 2nd line's \\n rect as array") ||
+ !checkRectArray(secondLineLFAsArray, [secondLineLF], "runBug722639Test: 2nd line's rect as array should match with text rect result")) {
+ return;
+ }
+ }
+ let lineHeight = secondLine.top - firstLine.top;
+ ok(lineHeight > 0,
+ "runBug722639Test: lineHeight must be positive");
+ is(secondLine.left, firstLine.left,
+ "runBug722639Test: the left value must be always same value");
+ isfuzzy(secondLine.height, firstLine.height, 1,
+ "runBug722639Test: the height must be always same value");
+ let previousTop = secondLine.top;
+ for (let i = 3; i <= textarea.value.length + 1; i++) {
+ let currentLine = synthesizeQueryTextRect(kLFLen * (i - 1), 1);
+ if (!checkQueryContentResult(currentLine,
+ "runBug722639Test: " + i + "th currentLine")) {
+ return;
+ }
+ ok(true, "runBug722639Test: " + i + "th line, top=" + currentLine.top + ", left=" + currentLine.left);
+ let currentLineAsArray = synthesizeQueryTextRectArray(kLFLen * (i - 1), 1);
+ if (!checkQueryContentResult(currentLineAsArray, "runBug722639Test: " + i + "th line as array") ||
+ !checkRectArray(currentLineAsArray, [currentLine], "runBug722639Test: " + i + "th line as array should match with text rect result")) {
+ return;
+ }
+ // NOTE: the top position may be 1px larger or smaller than other lines
+ // due to sub pixel positioning.
+ if (Math.abs(currentLine.top - (previousTop + lineHeight)) <= 1) {
+ ok(true, "runBug722639Test: " + i + "th line's top is expected");
+ } else {
+ is(currentLine.top, previousTop + lineHeight,
+ "runBug722639Test: " + i + "th line's top is unexpected");
+ }
+ is(currentLine.left, firstLine.left,
+ "runBug722639Test: " + i + "th line's left is unexpected");
+ isfuzzy(currentLine.height, firstLine.height, 1,
+ `runBug722639Test: ${i}th line's height is unexpected`);
+ if (kLFLen > 1) {
+ let currentLineLF = synthesizeQueryTextRect(kLFLen * (i - 1) + 1, 1);
+ if (!checkQueryContentResult(currentLineLF,
+ "runBug722639Test: " + i + "th currentLineLF")) {
+ return;
+ }
+ is(currentLineLF.top, currentLine.top, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
+ is(currentLineLF.left, currentLine.left, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
+ isfuzzy(currentLineLF.height, currentLine.height, 1,
+ `runBug722639Test: ${i}th line's \\n rect should be same as same line's \\r rect`);
+ is(currentLineLF.width, currentLine.width, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
+ let currentLineLFAsArray = synthesizeQueryTextRectArray(kLFLen * (i - 1) + 1, 1);
+ if (!checkQueryContentResult(currentLineLFAsArray, "runBug722639Test: " + i + "th line's \\n rect as array") ||
+ !checkRectArray(currentLineLFAsArray, [currentLineLF], "runBug722639Test: " + i + "th line's rect as array should match with text rect result")) {
+ return;
+ }
+ }
+ previousTop = currentLine.top;
+ }
+}
+
+function runForceCommitTest()
+{
+ let events;
+ function eventHandler(aEvent)
+ {
+ events.push(aEvent);
+ }
+ window.addEventListener("compositionstart", eventHandler, true);
+ window.addEventListener("compositionupdate", eventHandler, true);
+ window.addEventListener("compositionend", eventHandler, true);
+ window.addEventListener("beforeinput", eventHandler, true);
+ window.addEventListener("input", eventHandler, true);
+ window.addEventListener("text", eventHandler, true);
+
+ // Make the composition in textarea commit by click in the textarea
+ textarea.focus();
+ textarea.value = "";
+
+ events = [];
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ is(events.length, 5,
+ "runForceCommitTest: wrong event count #1");
+ is(events[0].type, "compositionstart",
+ "runForceCommitTest: the 1st event must be compositionstart #1");
+ is(events[1].type, "compositionupdate",
+ "runForceCommitTest: the 2nd event must be compositionupdate #1");
+ is(events[2].type, "text",
+ "runForceCommitTest: the 3rd event must be text #1");
+ is(events[3].type, "beforeinput",
+ "runForceCommitTest: the 4th event must be beforeinput #1");
+ checkInputEvent(events[3], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #1");
+ is(events[4].type, "input",
+ "runForceCommitTest: the 5th event must be input #1");
+ checkInputEvent(events[4], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #1");
+
+ events = [];
+ synthesizeMouseAtCenter(textarea, {});
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #2");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #2");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #2`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #2");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #2`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #2");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #2");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #2`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #2");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #2");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #2`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #2");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #2");
+ is(textarea.value, "\u306E",
+ "runForceCommitTest: the textarea doesn't have the committed text #2");
+
+ // Make the composition in textarea commit by click in another editor (input)
+ textarea.focus();
+ textarea.value = "";
+ input.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ synthesizeMouseAtCenter(input, {});
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #3");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #3");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #3`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #3");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #3`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #3");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #3");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #3`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #3");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #3");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #3`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #3");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #3");
+ ok(!getEditor(input).isComposing,
+ "runForceCommitTest: the input has composition #3");
+ is(textarea.value, "\u306E",
+ "runForceCommitTest: the textarea doesn't have the committed text #3");
+ is(input.value, "",
+ "runForceCommitTest: the input has the committed text? #3");
+
+ // Make the composition in textarea commit by blur()
+ textarea.focus();
+ textarea.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ textarea.blur();
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #4");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #4");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #4`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #4");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #4`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #4");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #4");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #4`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #4");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #4");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #4`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #4");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #4");
+ is(textarea.value, "\u306E",
+ "runForceCommitTest: the textarea doesn't have the committed text #4");
+
+ // Make the composition in textarea commit by input.focus()
+ textarea.focus();
+ textarea.value = "";
+ input.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ input.focus();
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #5");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #5");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #5`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #5");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #5`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #5");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #5");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #5`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #5");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #5");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #5`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #5");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #5");
+ ok(!getEditor(input).isComposing,
+ "runForceCommitTest: the input has composition #5");
+ is(textarea.value, "\u306E",
+ "runForceCommitTest: the textarea doesn't have the committed text #5");
+ is(input.value, "",
+ "runForceCommitTest: the input has the committed text? #5");
+
+ // Make the composition in textarea commit by click in another document's editor
+ textarea.focus();
+ textarea.value = "";
+ textareaInFrame.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ synthesizeMouseAtCenter(textareaInFrame, {}, iframe.contentWindow);
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #6");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #6");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #6`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #6");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #6`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #6");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #6");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #6`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #6");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #6");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #6`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #6");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #6");
+ ok(!getEditor(textareaInFrame).isComposing,
+ "runForceCommitTest: the textarea in frame has composition #6");
+ is(textarea.value, "\u306E",
+ "runForceCommitTest: the textarea doesn't have the committed text #6");
+ is(textareaInFrame.value, "",
+ "runForceCommitTest: the textarea in frame has the committed text? #6");
+
+ // Make the composition in textarea commit by another document's editor's focus()
+ textarea.focus();
+ textarea.value = "";
+ textareaInFrame.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ textareaInFrame.focus();
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #7");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #7");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #7`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #7");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #7`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #7");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #7");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #7`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #7");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #7");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #7`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #7");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #7");
+ ok(!getEditor(textareaInFrame).isComposing,
+ "runForceCommitTest: the textarea in frame has composition #7");
+ is(textarea.value, "\u306E",
+ "runForceCommitTest: the textarea doesn't have the committed text #7");
+ is(textareaInFrame.value, "",
+ "runForceCommitTest: the textarea in frame has the committed text? #7");
+
+ // Make the composition in a textarea commit by click in another editable document
+ textarea.focus();
+ textarea.value = "";
+ iframe2.contentDocument.body.innerHTML = "Text in the Body";
+ let iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ synthesizeMouseAtCenter(iframe2.contentDocument.body, {}, iframe2.contentWindow);
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #8");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #8");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The ${events[0].type} event was fired on wrong event target #8`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #8");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The ${events[1].type} event was fired on wrong event target #8`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #8");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #8");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The ${events[2].type} event was fired on wrong event target #8`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #8");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #8");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The ${events[3].type} event was fired on wrong event target #8`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #8");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #8");
+ ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
+ "runForceCommitTest: the editable document has composition #8");
+ is(textarea.value, "\u306E",
+ "runForceCommitTest: the textarea doesn't have the committed text #8");
+ is(iframe2.contentDocument.body.innerHTML, iframe2BodyInnerHTML,
+ "runForceCommitTest: the editable document has the committed text? #8");
+
+ // Make the composition in an editable document commit by click in it
+ iframe2.contentWindow.focus();
+ iframe2.contentDocument.body.innerHTML = "Text in the Body";
+ iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ }, iframe2.contentWindow);
+
+ events = [];
+ synthesizeMouseAtCenter(iframe2.contentDocument.body, {}, iframe2.contentWindow);
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #9");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #9");
+ is(events[0].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #9`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #9");
+ is(events[1].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #9`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E",
+ [{startContainer: iframe2.contentDocument.body.firstChild,
+ startOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E"),
+ endContainer: iframe2.contentDocument.body.firstChild,
+ endOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E") + 1}],
+ "runForceCommitTest #9");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #9");
+ is(events[2].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #9`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #9");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #9");
+ is(events[3].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #9`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #9");
+ ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
+ "runForceCommitTest: the editable document still has composition #9");
+ ok(iframe2.contentDocument.body.innerHTML != iframe2BodyInnerHTML &&
+ iframe2.contentDocument.body.innerHTML.includes("\u306E"),
+ "runForceCommitTest: the editable document doesn't have the committed text #9");
+
+ // Make the composition in an editable document commit by click in another document's editor
+ textarea.value = "";
+ iframe2.contentWindow.focus();
+ iframe2.contentDocument.body.innerHTML = "Text in the Body";
+ iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ }, iframe2.contentWindow);
+
+ events = [];
+ synthesizeMouseAtCenter(textarea, {});
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #10");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #10");
+ is(events[0].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The ${events[0].type} event was fired on wrong event target #10`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #10");
+ is(events[1].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The ${events[1].type} event was fired on wrong event target #10`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E",
+ [{startContainer: iframe2.contentDocument.body.firstChild,
+ startOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E"),
+ endContainer: iframe2.contentDocument.body.firstChild,
+ endOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E") + 1}],
+ "runForceCommitTest #10");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #10");
+ is(events[2].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The ${events[2].type} event was fired on wrong event target #10`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #10");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #10");
+ is(events[3].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The ${events[3].type} event was fired on wrong event target #10`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #10");
+ ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
+ "runForceCommitTest: the editable document still has composition #10");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea has composition #10");
+ ok(iframe2.contentDocument.body.innerHTML != iframe2BodyInnerHTML &&
+ iframe2.contentDocument.body.innerHTML.includes("\u306E"),
+ "runForceCommitTest: the editable document doesn't have the committed text #10");
+ is(textarea.value, "",
+ "runForceCommitTest: the textarea has the committed text? #10");
+
+ // Make the composition in an editable document commit by click in the another editable document
+ iframe2.contentWindow.focus();
+ iframe2.contentDocument.body.innerHTML = "Text in the Body";
+ iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
+ iframe3.contentDocument.body.innerHTML = "Text in the Body";
+ let iframe3BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ }, iframe2.contentWindow);
+
+ events = [];
+ synthesizeMouseAtCenter(iframe3.contentDocument.body, {}, iframe3.contentWindow);
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #11");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #11");
+ is(events[0].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #11`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #11");
+ is(events[1].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #11`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E",
+ [{startContainer: iframe2.contentDocument.body.firstChild,
+ startOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E"),
+ endContainer: iframe2.contentDocument.body.firstChild,
+ endOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E") + 1}],
+ "runForceCommitTest #11");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #11");
+ is(events[2].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #11`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #11");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #11");
+ is(events[3].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #11`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #11");
+ ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
+ "runForceCommitTest: the editable document still has composition #11");
+ ok(!getHTMLEditorIMESupport(iframe3.contentWindow).isComposing,
+ "runForceCommitTest: the other editable document has composition #11");
+ ok(iframe2.contentDocument.body.innerHTML != iframe2BodyInnerHTML &&
+ iframe2.contentDocument.body.innerHTML.includes("\u306E"),
+ "runForceCommitTest: the editable document doesn't have the committed text #11");
+ is(iframe3.contentDocument.body.innerHTML, iframe3BodyInnerHTML,
+ "runForceCommitTest: the other editable document has the committed text? #11");
+
+ input.focus();
+ input.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ input.value = "set value";
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #12");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #12");
+ is(events[0].target, input,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #12`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #12");
+ is(events[1].target, input,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #12`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #12");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #12");
+ is(events[2].target, input,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #12`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #12");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #12");
+ is(events[3].target, input,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #12`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #12");
+ ok(!getEditor(input).isComposing,
+ "runForceCommitTest: the input still has composition #12");
+ is(input.value, "set value",
+ "runForceCommitTest: the input doesn't have the set text #12");
+
+ textarea.focus();
+ textarea.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ textarea.value = "set value";
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #13");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #13");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #13`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #13");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #13`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #13");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #13");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #13`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #13");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #13");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #13`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #13");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #13");
+ is(textarea.value, "set value",
+ "runForceCommitTest: the textarea doesn't have the set text #13");
+
+ input.focus();
+ input.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ input.value += " appended value";
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #14");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #14");
+ is(events[0].target, input,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #14`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #14");
+ is(events[1].target, input,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #14`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #14");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #14");
+ is(events[2].target, input,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #14`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #14");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #14");
+ is(events[3].target, input,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #14`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #14");
+ ok(!getEditor(input).isComposing,
+ "runForceCommitTest: the input still has composition #14");
+ is(input.value, "\u306E appended value",
+ "runForceCommitTest: the input should have both composed text and appended text #14");
+
+ input.focus();
+ input.value = "abcd";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ input.value = "abcd\u306E";
+
+ is(events.length, 0,
+ "runForceCommitTest: setting same value to input with composition shouldn't cause any events #15");
+ is(input.value, "abcd\u306E",
+ "runForceCommitTest: the input has unexpected value #15");
+
+ input.blur(); // commit composition
+
+ textarea.focus();
+ textarea.value = "abcd";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ textarea.value = "abcd\u306E";
+
+ is(events.length, 0,
+ "runForceCommitTest: setting same value to textarea with composition shouldn't cause any events #16");
+ is(textarea.value, "abcd\u306E",
+ "runForceCommitTest: the input has unexpected value #16");
+
+ textarea.blur(); // commit composition
+
+ window.removeEventListener("compositionstart", eventHandler, true);
+ window.removeEventListener("compositionupdate", eventHandler, true);
+ window.removeEventListener("compositionend", eventHandler, true);
+ window.removeEventListener("beforeinput", eventHandler, true);
+ window.removeEventListener("input", eventHandler, true);
+ window.removeEventListener("text", eventHandler, true);
+}
+
+function runNestedSettingValue()
+{
+ let isTesting = false;
+ let events = [];
+ function eventHandler(aEvent)
+ {
+ events.push(aEvent);
+ if (isTesting) {
+ aEvent.target.value += aEvent.type + ", ";
+ }
+ }
+ window.addEventListener("compositionstart", eventHandler, true);
+ window.addEventListener("compositionupdate", eventHandler, true);
+ window.addEventListener("compositionend", eventHandler, true);
+ window.addEventListener("beforeinput", eventHandler, true);
+ window.addEventListener("input", eventHandler, true);
+ window.addEventListener("text", eventHandler, true);
+
+ textarea.focus();
+ textarea.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ isTesting = true;
+ textarea.value = "first setting value, ";
+ isTesting = false;
+
+ is(events.length, 4,
+ "runNestedSettingValue: wrong event count #1");
+ is(events[0].type, "text",
+ "runNestedSettingValue: the 1st event must be text #1");
+ is(events[0].target, textarea,
+ `runNestedSettingValue: The "${events[0].type}" event was fired on wrong event target #1`);
+ is(events[1].type, "beforeinput",
+ "runNestedSettingValue: the 2nd event must be beforeinput #1");
+ is(events[1].target, textarea,
+ `runNestedSettingValue: The "${events[1].type}" event was fired on wrong event target #1`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #1");
+ is(events[2].type, "compositionend",
+ "runNestedSettingValue: the 3rd event must be compositionend #1");
+ is(events[2].target, textarea,
+ `runNestedSettingValue: The "${events[2].type}" event was fired on wrong event target #1`);
+ is(events[2].data, "\u306E",
+ "runNestedSettingValue: compositionend has wrong data #1");
+ is(events[3].type, "input",
+ "runNestedSettingValue: the 4th event must be input #1");
+ is(events[3].target, textarea,
+ `runNestedSettingValue: The "${events[3].type}" event was fired on wrong event target #1`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #1");
+ ok(!getEditor(textarea).isComposing,
+ "runNestedSettingValue: the textarea still has composition #1");
+ is(textarea.value, "first setting value, text, beforeinput, compositionend, input, ",
+ "runNestedSettingValue: the textarea should have all string set to value attribute");
+
+ input.focus();
+ input.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ isTesting = true;
+ input.value = "first setting value, ";
+ isTesting = false;
+
+ is(events.length, 4,
+ "runNestedSettingValue: wrong event count #2");
+ is(events[0].type, "text",
+ "runNestedSettingValue: the 1st event must be text #2");
+ is(events[0].target, input,
+ `runNestedSettingValue: The "${events[0].type}" event was fired on wrong event target #2`);
+ is(events[1].type, "beforeinput",
+ "runNestedSettingValue: the 2nd event must be beforeinput #2");
+ is(events[1].target, input,
+ `runNestedSettingValue: The "${events[1].type}" event was fired on wrong event target #2`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #2");
+ is(events[2].type, "compositionend",
+ "runNestedSettingValue: the 3rd event must be compositionend #2");
+ is(events[2].target, input,
+ `runNestedSettingValue: The "${events[2].type}" event was fired on wrong event target #2`);
+ is(events[2].data, "\u306E",
+ "runNestedSettingValue: compositionend has wrong data #2");
+ is(events[3].type, "input",
+ "runNestedSettingValue: the 4th event must be input #2");
+ is(events[3].target, input,
+ `runNestedSettingValue: The "${events[3].type}" event was fired on wrong event target #2`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #2");
+ ok(!getEditor(input).isComposing,
+ "runNestedSettingValue: the input still has composition #2");
+ is(textarea.value, "first setting value, text, beforeinput, compositionend, input, ",
+ "runNestedSettingValue: the input should have all string set to value attribute #2");
+
+ textarea.focus();
+ textarea.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ isTesting = true;
+ textarea.setRangeText("first setting value, ");
+ isTesting = false;
+
+ is(events.length, 4,
+ "runNestedSettingValue: wrong event count #3");
+ is(events[0].type, "text",
+ "runNestedSettingValue: the 1st event must be text #3");
+ is(events[0].target, textarea,
+ `runNestedSettingValue: The ${events[0].type} event was fired on wrong event target #3`);
+ is(events[1].type, "beforeinput",
+ "runNestedSettingValue: the 2nd event must be beforeinput #3");
+ is(events[1].target, textarea,
+ `runNestedSettingValue: The ${events[1].type} event was fired on wrong event target #3`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #3");
+ is(events[2].type, "compositionend",
+ "runNestedSettingValue: the 3rd event must be compositionend #3");
+ is(events[2].target, textarea,
+ `runNestedSettingValue: The ${events[2].type} event was fired on wrong event target #3`);
+ is(events[2].data, "\u306E",
+ "runNestedSettingValue: compositionend has wrong data #3");
+ is(events[3].type, "input",
+ "runNestedSettingValue: the 4th event must be input #3");
+ is(events[3].target, textarea,
+ `runNestedSettingValue: The ${events[3].type} event was fired on wrong event target #3`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #3");
+ ok(!getEditor(textarea).isComposing,
+ "runNestedSettingValue: the textarea still has composition #3");
+ is(textarea.value, "\u306Efirst setting value, text, beforeinput, compositionend, input, ",
+ "runNestedSettingValue: the textarea should have appended by setRangeText() and all string set to value attribute #3");
+
+ input.focus();
+ input.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ isTesting = true;
+ input.setRangeText("first setting value, ");
+ isTesting = false;
+
+ is(events.length, 4,
+ "runNestedSettingValue: wrong event count #4");
+ is(events[0].type, "text",
+ "runNestedSettingValue: the 1st event must be text #4");
+ is(events[0].target, input,
+ `runNestedSettingValue: The "${events[0].type}" event was fired on wrong event target #4`);
+ is(events[1].type, "beforeinput",
+ "runNestedSettingValue: the 2nd event must be beforeinput #4");
+ is(events[1].target, input,
+ `runNestedSettingValue: The "${events[1].type}" event was fired on wrong event target #4`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #4");
+ is(events[2].type, "compositionend",
+ "runNestedSettingValue: the 3rd event must be compositionend #4");
+ is(events[2].target, input,
+ `runNestedSettingValue: The "${events[2].type}" event was fired on wrong event target #4`);
+ is(events[2].data, "\u306E",
+ "runNestedSettingValue: compositionend has wrong data #4");
+ is(events[3].type, "input",
+ "runNestedSettingValue: the 4th event must be input #4");
+ is(events[3].target, input,
+ `runNestedSettingValue: The "${events[3].type}" event was fired on wrong event target #4`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #4");
+ ok(!getEditor(input).isComposing,
+ "runNestedSettingValue: the input still has composition #4");
+ is(textarea.value, "\u306Efirst setting value, text, beforeinput, compositionend, input, ",
+ "runNestedSettingValue: the input should have all string appended by setRangeText() and set to value attribute #4");
+
+ window.removeEventListener("compositionstart", eventHandler, true);
+ window.removeEventListener("compositionupdate", eventHandler, true);
+ window.removeEventListener("compositionend", eventHandler, true);
+ window.removeEventListener("beforeinput", eventHandler, true);
+ window.removeEventListener("input", eventHandler, true);
+ window.removeEventListener("text", eventHandler, true);
+
+}
+
+async function runAsyncForceCommitTest()
+{
+ let events;
+ function eventHandler(aEvent)
+ {
+ events.push(aEvent);
+ };
+
+ // If IME commits composition for a request, TextComposition commits
+ // composition automatically because most web apps must expect that active
+ // composition should be committed synchronously. Therefore, in this case,
+ // a click during composition should cause committing composition
+ // synchronously and delayed commit shouldn't cause composition events.
+ let commitRequested = false;
+ let onFinishTest = null;
+ function callback(aTIP, aNotification)
+ {
+ ok(true, aNotification.type);
+ if (aNotification.type != "request-to-commit") {
+ return true;
+ }
+ commitRequested = true;
+ if (onFinishTest) {
+ let resolve = onFinishTest;
+ onFinishTest = null;
+
+ SimpleTest.executeSoon(() => {
+ events = [];
+ aTIP.commitComposition();
+
+ is(events.length, 0,
+ "runAsyncForceCommitTest: composition events shouldn't been fired by asynchronous call of nsITextInputProcessor.commitComposition()");
+
+ SimpleTest.executeSoon(resolve);
+ });
+ }
+ return true;
+ };
+
+ function promiseCleanUp() {
+ return new Promise(resolve => { onFinishTest = resolve; });
+ }
+
+ window.addEventListener("compositionstart", eventHandler, true);
+ window.addEventListener("compositionupdate", eventHandler, true);
+ window.addEventListener("compositionend", eventHandler, true);
+ window.addEventListener("beforeinput", eventHandler, true);
+ window.addEventListener("input", eventHandler, true);
+ window.addEventListener("text", eventHandler, true);
+
+ // Make the composition in textarea commit by click in the textarea
+ textarea.focus();
+ textarea.value = "";
+
+ events = [];
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ }, window, callback);
+
+ is(events.length, 5,
+ "runAsyncForceCommitTest: wrong event count #1");
+ is(events[0].type, "compositionstart",
+ "runAsyncForceCommitTest: the 1st event must be compositionstart #1");
+ is(events[1].type, "compositionupdate",
+ "runAsyncForceCommitTest: the 2nd event must be compositionupdate #1");
+ is(events[2].type, "text",
+ "runAsyncForceCommitTest: the 3rd event must be text #1");
+ is(events[3].type, "beforeinput",
+ "runAsyncForceCommitTest: the 4th event must be beforeinput #1");
+ checkInputEvent(events[3], true, "insertCompositionText", "\u306E", [],
+ "runAsyncForceCommitTest #1");
+ is(events[4].type, "input",
+ "runAsyncForceCommitTest: the 5th event must be input #1");
+ checkInputEvent(events[4], true, "insertCompositionText", "\u306E", [],
+ "runAsyncForceCommitTest #1");
+
+ events = [];
+ let waitCleanState = promiseCleanUp();
+
+ synthesizeMouseAtCenter(textarea, {});
+
+ ok(commitRequested,
+ "runAsyncForceCommitTest: \"request-to-commit\" should've been notified");
+ is(events.length, 4,
+ "runAsyncForceCommitTest: wrong event count #2");
+ is(events[0].type, "text",
+ "runAsyncForceCommitTest: the 1st event must be text #2");
+ is(events[0].target, textarea,
+ `runAsyncForceCommitTest: The "${events[0].type}" event was fired on wrong event target #2`);
+ is(events[1].type, "beforeinput",
+ "runAsyncForceCommitTest: the 2nd event must be beforeinput #2");
+ is(events[1].target, textarea,
+ `runAsyncForceCommitTest: The "${events[1].type}" event was fired on wrong event target #2`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runAsyncForceCommitTest #2");
+ is(events[2].type, "compositionend",
+ "runAsyncForceCommitTest: the 3rd event must be compositionend #2");
+ is(events[2].target, textarea,
+ `runAsyncForceCommitTest: The "${events[2].type}" event was fired on wrong event target #2`);
+ is(events[2].data, "\u306E",
+ "runAsyncForceCommitTest: compositionend has wrong data #2");
+ is(events[3].type, "input",
+ "runAsyncForceCommitTest: the 4th event must be input #2");
+ is(events[3].target, textarea,
+ `runAsyncForceCommitTest: The "${events[3].type}" event was fired on wrong event target #2`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runAsyncForceCommitTest #2");
+ ok(!getEditor(textarea).isComposing,
+ "runAsyncForceCommitTest: the textarea still has composition #2");
+ is(textarea.value, "\u306E",
+ "runAsyncForceCommitTest: the textarea doesn't have the committed text #2");
+
+ await waitCleanState;
+
+ window.removeEventListener("compositionstart", eventHandler, true);
+ window.removeEventListener("compositionupdate", eventHandler, true);
+ window.removeEventListener("compositionend", eventHandler, true);
+ window.removeEventListener("beforeinput", eventHandler, true);
+ window.removeEventListener("input", eventHandler, true);
+ window.removeEventListener("text", eventHandler, true);
+}
+
+function runBug811755Test()
+{
+ iframe2.contentDocument.body.innerHTML = "<div>content<br/></div>";
+ iframe2.contentWindow.focus();
+ // Query everything
+ let textContent = synthesizeQueryTextContent(0, 10);
+ if (!checkQueryContentResult(textContent, "runBug811755Test: synthesizeQueryTextContent #1")) {
+ return;
+ }
+ // Query everything but specify exact end offset, which should be immediately after the <br> node
+ // If PreContentIterator is used, the next node after <br> is the node after </div>.
+ // If ContentIterator is used, the next node is the <div> node itself. In this case, the end
+ // node ends up being before the start node, and an empty string is returned.
+ let queryContent = synthesizeQueryTextContent(0, textContent.text.length);
+ if (!checkQueryContentResult(queryContent, "runBug811755Test: synthesizeQueryTextContent #2")) {
+ return;
+ }
+ is(queryContent.text, textContent.text, "runBug811755Test: two queried texts don't match");
+}
+
+function runIsComposingTest()
+{
+ let expectedIsComposing = false;
+ let description = "";
+
+ function eventHandler(aEvent)
+ {
+ if (aEvent.type == "keydown" || aEvent.type == "keyup") {
+ is(aEvent.isComposing, expectedIsComposing,
+ "runIsComposingTest: " + description + " (type=" + aEvent.type + ", key=" + aEvent.key + ")");
+ } else if (aEvent.type == "keypress") {
+ // keypress event shouldn't be fired during composition so that isComposing should be always false.
+ is(aEvent.isComposing, false,
+ "runIsComposingTest: " + description + " (type=" + aEvent.type + ")");
+ } else {
+ checkInputEvent(aEvent, expectedIsComposing, "insertCompositionText", "\u3042", [],
+ `runIsComposingTest: ${description}`);
+ }
+ }
+
+ function onComposition(aEvent)
+ {
+ if (aEvent.type == "compositionstart") {
+ expectedIsComposing = true;
+ } else if (aEvent.type == "compositionend") {
+ expectedIsComposing = false;
+ }
+ }
+
+ textarea.addEventListener("keydown", eventHandler, true);
+ textarea.addEventListener("keypress", eventHandler, true);
+ textarea.addEventListener("keyup", eventHandler, true);
+ textarea.addEventListener("beforeinput", eventHandler, true);
+ textarea.addEventListener("input", eventHandler, true);
+ textarea.addEventListener("compositionstart", onComposition, true);
+ textarea.addEventListener("compositionend", onComposition, true);
+
+ textarea.focus();
+ textarea.value = "";
+
+ // XXX These cases shouldn't occur in actual native key events because we
+ // don't dispatch key events while composition (bug 354358).
+ Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+ description = "events before dispatching compositionstart";
+ synthesizeKey("KEY_ArrowLeft");
+
+ description = "events after dispatching compositionchange";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A },
+ });
+
+ // Although, firing keypress event during composition is a bug.
+ synthesizeKey("KEY_Insert");
+
+ description = "events for committing composition string";
+
+ synthesizeComposition({ type: "compositioncommitasis",
+ key: { key: "KEY_Enter", code: "Enter", type: "keydown" } });
+
+ // input event will be fired by synthesizing compositionend event.
+ // Then, its isComposing should be false.
+ description = "events after dispatching compositioncommitasis";
+ synthesizeKey("KEY_Enter", {type: "keyup"});
+
+ Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+
+ textarea.removeEventListener("keydown", eventHandler, true);
+ textarea.removeEventListener("keypress", eventHandler, true);
+ textarea.removeEventListener("keyup", eventHandler, true);
+ textarea.removeEventListener("beforeinput", eventHandler, true);
+ textarea.removeEventListener("input", eventHandler, true);
+ textarea.removeEventListener("compositionstart", onComposition, true);
+ textarea.removeEventListener("compositionend", onComposition, true);
+
+ textarea.value = "";
+}
+
+function runRedundantChangeTest()
+{
+ textarea.focus();
+
+ let result = [];
+ function clearResult()
+ {
+ result = [];
+ }
+
+ function handler(aEvent)
+ {
+ result.push(aEvent);
+ }
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", handler, true);
+ textarea.addEventListener("beforeinput", handler, true);
+ textarea.addEventListener("input", handler, true);
+ textarea.addEventListener("text", handler, true);
+
+ textarea.value = "";
+
+ // synthesize change event
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ is(result.length, 4,
+ "runRedundantChangeTest: 4 events should be fired after synthesizing composition change #1");
+ is(result[0].type, "compositionupdate",
+ "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #1");
+ is(result[1].type, "text",
+ "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #1");
+ is(result[2].type, "beforeinput",
+ "runRedundantChangeTest: beforeinput should be fired after synthesizing composition change #1");
+ checkInputEvent(result[2], true, "insertCompositionText", "\u3042", [],
+ "runRedundantChangeTest: after synthesizing composition change #1");
+ is(result[3].type, "input",
+ "runRedundantChangeTest: input should be fired after synthesizing composition change #1");
+ checkInputEvent(result[3], true, "insertCompositionText", "\u3042", [],
+ "runRedundantChangeTest: after synthesizing composition change #1");
+ is(textarea.value, "\u3042", "runRedundantChangeTest: textarea has uncommitted string #1");
+
+ // synthesize another change event
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042\u3044",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ is(result.length, 4,
+ "runRedundantChangeTest: 4 events should be fired after synthesizing composition change #2");
+ is(result[0].type, "compositionupdate",
+ "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #2");
+ is(result[1].type, "text",
+ "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #2");
+ is(result[2].type, "beforeinput",
+ "runRedundantChangeTest: beforeinput should be fired after synthesizing composition change #2");
+ checkInputEvent(result[2], true, "insertCompositionText", "\u3042\u3044", [],
+ "runRedundantChangeTest: after synthesizing composition change #2");
+ is(result[3].type, "input",
+ "runRedundantChangeTest: input should be fired after synthesizing composition change #2");
+ checkInputEvent(result[3], true, "insertCompositionText", "\u3042\u3044", [],
+ "runRedundantChangeTest: after synthesizing composition change #2");
+ is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has uncommitted string #2");
+
+ // synthesize same change event again
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042\u3044",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ is(result.length, 0, "runRedundantChangeTest: no events should be fired after synthesizing composition change again");
+ is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has uncommitted string #3");
+
+ // synthesize commit-as-is
+ clearResult();
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(result.length, 4,
+ "runRedundantChangeTest: 4 events should be fired after synthesizing composition commit-as-is");
+ is(result[0].type, "text",
+ "runRedundantChangeTest: text should be fired after synthesizing composition commit-as-is for removing the ranges");
+ is(result[1].type, "beforeinput",
+ "runRedundantChangeTest: beforeinput should be fired after synthesizing composition commit-as-is for removing the ranges");
+ checkInputEvent(result[1], true, "insertCompositionText", "\u3042\u3044", [],
+ "runRedundantChangeTest: at synthesizing commit-as-is");
+ is(result[2].type, "compositionend",
+ "runRedundantChangeTest: compositionend should be fired after synthesizing composition commit-as-is");
+ is(result[3].type, "input",
+ "runRedundantChangeTest: input should be fired before compositionend at synthesizing commit-as-is");
+ checkInputEvent(result[3], false, "insertCompositionText", "\u3042\u3044", [],
+ "runRedundantChangeTest: at synthesizing commit-as-is");
+ is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has the commit string");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+ textarea.removeEventListener("beforeinput", handler, true);
+ textarea.removeEventListener("input", handler, true);
+ textarea.removeEventListener("text", handler, true);
+}
+
+function runNotRedundantChangeTest()
+{
+ textarea.focus();
+
+ let result = [];
+ function clearResult()
+ {
+ result = [];
+ }
+
+ function handler(aEvent)
+ {
+ result.push(aEvent);
+ }
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", handler, true);
+ textarea.addEventListener("beforeinput", handler, true);
+ textarea.addEventListener("input", handler, true);
+ textarea.addEventListener("text", handler, true);
+
+ textarea.value = "abcde";
+
+ // synthesize change event with non-null ranges
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABCDE",
+ "clauses":
+ [
+ { "length": 5, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 5, "length": 0 }
+ });
+
+ is(result.length, 4,
+ "runNotRedundantChangeTest: 4 events should be fired after synthesizing composition change with non-null ranges");
+ is(result[0].type, "compositionupdate",
+ "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with non-null ranges");
+ is(result[1].type, "text",
+ "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges");
+ is(result[2].type, "beforeinput",
+ "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change with non-null ranges");
+ checkInputEvent(result[2], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges");
+ is(result[3].type, "input",
+ "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges");
+ checkInputEvent(result[3], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges");
+ is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #1");
+
+ // synthesize change event with null ranges
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABCDE",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ });
+ is(result.length, 3,
+ "runNotRedundantChangeTest: 3 events should be fired after synthesizing composition change with null ranges after non-null ranges");
+ is(result[0].type, "text",
+ "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with null ranges after non-null ranges");
+ is(result[1].type, "beforeinput",
+ "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change because it's dispatched when there is composing string with null ranges after non-null ranges");
+ checkInputEvent(result[1], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with null ranges after non-null ranges");
+ is(result[2].type, "input",
+ "runNotRedundantChangeTest: input should be fired after synthesizing composition change with null ranges after non-null ranges");
+ checkInputEvent(result[2], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with null ranges after non-null ranges");
+ is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #2");
+
+ // synthesize change event with non-null ranges
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABCDE",
+ "clauses":
+ [
+ { "length": 5, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 5, "length": 0 }
+ });
+
+ is(result.length, 3,
+ "runNotRedundantChangeTest: 3 events should be fired after synthesizing composition change with null ranges after non-null ranges");
+ is(result[0].type, "text",
+ "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges after null ranges");
+ is(result[1].type, "beforeinput",
+ "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges after null ranges");
+ checkInputEvent(result[1], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after null ranges");
+ is(result[2].type, "input",
+ "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges after null ranges");
+ checkInputEvent(result[2], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after null ranges");
+ is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #3");
+
+ // synthesize change event with empty data and null ranges
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ });
+ is(result.length, 4,
+ "runNotRedundantChangeTest: 4 events should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
+ is(result[0].type, "compositionupdate",
+ "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
+ is(result[1].type, "text",
+ "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with empty data and null ranges after non-null ranges");
+ is(result[2].type, "beforeinput",
+ "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
+ checkInputEvent(result[2], true, "insertCompositionText", "", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with empty data and null ranges after non-null ranges");
+ is(result[3].type, "input",
+ "runNotRedundantChangeTest: input should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
+ checkInputEvent(result[3], true, "insertCompositionText", "", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with empty data and null ranges after non-null ranges");
+ is(textarea.value, "abcde", "runNotRedundantChangeTest: textarea doesn't have uncommitted string #1");
+
+ // synthesize change event with non-null ranges
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABCDE",
+ "clauses":
+ [
+ { "length": 5, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 5, "length": 0 }
+ });
+
+ is(result.length, 4,
+ "runNotRedundantChangeTest: 4 events should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
+ is(result[0].type, "compositionupdate",
+ "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
+ is(result[1].type, "text",
+ "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges after empty data and null ranges");
+ is(result[2].type, "beforeinput",
+ "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
+ checkInputEvent(result[2], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after empty data and null ranges");
+ is(result[3].type, "input",
+ "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
+ checkInputEvent(result[3], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after empty data and null ranges");
+ is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #4");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "" });
+
+ is(result.length, 5,
+ "runNotRedundantChangeTest: 5 events should be fired after synthesizing composition commit with empty data after non-empty data");
+ is(result[0].type, "compositionupdate",
+ "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition commit with empty data after non-empty data");
+ is(result[1].type, "text",
+ "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with empty data after non-empty data");
+ is(result[2].type, "beforeinput",
+ "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition commit with empty data after non-empty data");
+ checkInputEvent(result[2], true, "insertCompositionText", "", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with empty data after non-empty data");
+ is(result[3].type, "compositionend",
+ "runNotRedundantChangeTest: compositionend should be fired after synthesizing composition commit with empty data after non-empty data");
+ is(result[4].type, "input",
+ "runNotRedundantChangeTest: input should be fired after compositionend after synthesizing composition change with empty data after non-empty data");
+ checkInputEvent(result[4], false, "insertCompositionText", "", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with empty data after non-empty data");
+ is(textarea.value, "abcde", "runNotRedundantChangeTest: textarea doesn't have uncommitted string #2");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+ textarea.removeEventListener("beforeinput", handler, true);
+ textarea.removeEventListener("input", handler, true);
+ textarea.removeEventListener("text", handler, true);
+}
+
+function runNativeLineBreakerTest()
+{
+ textarea.focus();
+
+ let result = {};
+ function clearResult()
+ {
+ result = { compositionupdate: null, compositionend: null };
+ }
+
+ function handler(aEvent)
+ {
+ result[aEvent.type] = aEvent.data;
+ }
+
+ Services.prefs.setBoolPref("dom.compositionevent.allow_control_characters", false);
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", handler, true);
+
+ // '\n' in composition string shouldn't be changed.
+ clearResult();
+ textarea.value = "";
+ let clauses = [ "abc\n", "def\n\ng", "hi\n", "\njkl" ];
+ let caret = clauses[0] + clauses[1] + clauses[2];
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": clauses.join(''),
+ "clauses":
+ [
+ { "length": clauses[0].length,
+ "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ { "length": clauses[1].length,
+ "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
+ { "length": clauses[2].length,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": clauses[3].length,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ ]
+ },
+ "caret": { "start": caret.length, "length": 0 }
+ });
+
+ checkSelection(caret.replace(/\n/g, "\n").length + (kLFLen - 1) * 4, "", "runNativeLineBreakerTest", "#1");
+ checkIMESelection("RawClause", true, 0, clauses[0].replace(/\n/g, kLF), "runNativeLineBreakerTest: \\n shouldn't be replaced with any character #1");
+ checkIMESelection("SelectedRawClause", true, clauses[0].replace(/\n/g, kLF).length, clauses[1].replace(/\n/g, kLF), "runNativeLineBreakerTest: \\n shouldn't be replaced with any character #1");
+ checkIMESelection("ConvertedClause", true, (clauses[0] + clauses[1]).replace(/\n/g, kLF).length, clauses[2].replace(/\n/g, kLF), "runNativeLineBreakerTest: \\n shouldn't be replaced with any character #1");
+ checkIMESelection("SelectedClause", true, (clauses[0] + clauses[1] + clauses[2]).replace(/\n/g, kLF).length, clauses[3].replace(/\n/g, kLF), "runNativeLineBreakerTest: \\n shouldn't be replaced with any character #1");
+ is(result.compositionupdate, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in compositionupdate.data shouldn't be removed nor replaced with other characters #1");
+ is(textarea.value, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in textarea.value shouldn't be removed nor replaced with other characters #1");
+
+ synthesizeComposition({ type: "compositioncommit", data: clauses.join('') });
+ checkSelection(clauses.join('').replace(/\n/g, "\n").length + (kLFLen - 1) * 5, "", "runNativeLineBreakerTest", "#2");
+ is(result.compositionend, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in compositionend.data shouldn't be removed nor replaced with other characters #2");
+ is(textarea.value, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in textarea.value shouldn't be removed nor replaced with other characters #2");
+
+ // \r\n in composition string should be replaced with \n.
+ clearResult();
+ textarea.value = "";
+ clauses = [ "abc\r\n", "def\r\n\r\ng", "hi\r\n", "\r\njkl" ];
+ caret = clauses[0] + clauses[1] + clauses[2];
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": clauses.join(''),
+ "clauses":
+ [
+ { "length": clauses[0].length,
+ "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ { "length": clauses[1].length,
+ "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
+ { "length": clauses[2].length,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": clauses[3].length,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ ]
+ },
+ "caret": { "start": caret.length, "length": 0 }
+ });
+
+ checkSelection(caret.replace(/\r\n/g, "\n").length + (kLFLen - 1) * 4, "", "runNativeLineBreakerTest", "#3");
+ checkIMESelection("RawClause", true, 0, clauses[0].replace(/\r\n/g, kLF), "runNativeLineBreakerTest: \\r\\n should be replaced with \\n #3");
+ checkIMESelection("SelectedRawClause", true, clauses[0].replace(/\r\n/g, kLF).length, clauses[1].replace(/\r\n/g, kLF), "runNativeLineBreakerTest: \\r\\n should be replaced with \\n #3");
+ checkIMESelection("ConvertedClause", true, (clauses[0] + clauses[1]).replace(/\r\n/g, kLF).length, clauses[2].replace(/\r\n/g, kLF), "runNativeLineBreakerTest: \\r\\n should be replaced with \\n #3");
+ checkIMESelection("SelectedClause", true, (clauses[0] + clauses[1] + clauses[2]).replace(/\r\n/g, kLF).length, clauses[3].replace(/\r\n/g, kLF), "runNativeLineBreakerTest: \\r\\n should be replaced with \\n #3");
+ is(result.compositionupdate, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in compositionudpate.data should be replaced with \\n #3");
+ is(textarea.value, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in textarea.value should be replaced with \\n #3");
+
+ synthesizeComposition({ type: "compositioncommit", data: clauses.join('') });
+ checkSelection(clauses.join('').replace(/\r\n/g, "\n").length + (kLFLen - 1) * 5, "", "runNativeLineBreakerTest", "#4");
+ is(result.compositionend, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in compositionend.data should be replaced with \\n #4");
+ is(textarea.value, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in textarea.value should be replaced with \\n #4");
+
+ // \r (not followed by \n) in composition string should be replaced with \n.
+ clearResult();
+ textarea.value = "";
+ clauses = [ "abc\r", "def\r\rg", "hi\r", "\rjkl" ];
+ caret = clauses[0] + clauses[1] + clauses[2];
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": clauses.join(''),
+ "clauses":
+ [
+ { "length": clauses[0].length,
+ "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ { "length": clauses[1].length,
+ "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
+ { "length": clauses[2].length,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": clauses[3].length,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ ]
+ },
+ "caret": { "start": caret.length, "length": 0 }
+ });
+
+ checkSelection(caret.replace(/\r/g, "\n").length + (kLFLen - 1) * 4, "", "runNativeLineBreakerTest", "#5");
+ checkIMESelection("RawClause", true, 0, clauses[0].replace(/\r/g, kLF), "runNativeLineBreakerTest: \\r should be replaced with \\n #5");
+ checkIMESelection("SelectedRawClause", true, clauses[0].replace(/\r/g, kLF).length, clauses[1].replace(/\r/g, kLF), "runNativeLineBreakerTest: \\r should be replaced with \\n #5");
+ checkIMESelection("ConvertedClause", true, (clauses[0] + clauses[1]).replace(/\r/g, kLF).length, clauses[2].replace(/\r/g, kLF), "runNativeLineBreakerTest: \\r should be replaced with \\n #5");
+ checkIMESelection("SelectedClause", true, (clauses[0] + clauses[1] + clauses[2]).replace(/\r/g, kLF).length, clauses[3].replace(/\r/g, kLF), "runNativeLineBreakerTest: \\r should be replaced with \\n #5");
+ is(result.compositionupdate, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in compositionupdate.data should be replaced with \\n #5");
+ is(textarea.value, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in textarea.value should be replaced with \\n #5");
+
+ synthesizeComposition({ type: "compositioncommit", data: clauses.join('') });
+ checkSelection(clauses.join('').replace(/\r/g, "\n").length + (kLFLen - 1) * 5, "", "runNativeLineBreakerTest", "#6");
+ is(result.compositionend, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in compositionend.data should be replaced with \\n #6");
+ is(textarea.value, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in textarea.value should be replaced with \\n #6");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+
+ Services.prefs.clearUserPref("dom.compositionevent.allow_control_characters");
+}
+
+function runControlCharTest()
+{
+ textarea.focus();
+
+ let result = {};
+ function clearResult()
+ {
+ result = { compositionupdate: null, compositionend: null };
+ }
+
+ function handler(aEvent)
+ {
+ result[aEvent.type] = aEvent.data;
+ }
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", handler, true);
+
+ textarea.value = "";
+
+ let controlChars = String.fromCharCode.apply(null, Object.keys(Array.from({length:0x20}))) + "\x7F";
+ let allowedChars = "\t\n\n";
+
+ let data = "AB" + controlChars + "CD" + controlChars + "EF";
+ let removedData = "AB" + allowedChars + "CD" + allowedChars + "EF";
+
+ let DIndex = data.indexOf("D");
+ let removedDIndex = removedData.indexOf("D");
+
+ // input string contains control characters
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": data,
+ "clauses":
+ [
+ { "length": DIndex,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": data.length - DIndex,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": DIndex, "length": 0 }
+ });
+
+ checkSelection(removedDIndex + (kLFLen - 1) * 2, "", "runControlCharTest", "#1")
+
+ is(result.compositionupdate, removedData, "runControlCharTest: control characters in event.data should be removed in compositionupdate event #1");
+ is(textarea.value, removedData, "runControlCharTest: control characters should not appear in textarea #1");
+
+ synthesizeComposition({ type: "compositioncommit", data });
+
+ is(result.compositionend, removedData, "runControlCharTest: control characters in event.data should be removed in compositionend event #2");
+ is(textarea.value, removedData, "runControlCharTest: control characters should not appear in textarea #2");
+
+ textarea.value = "";
+
+ clearResult();
+
+ Services.prefs.setBoolPref("dom.compositionevent.allow_control_characters", true);
+
+ // input string contains control characters, allowing control characters
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": data,
+ "clauses":
+ [
+ { "length": DIndex,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": data.length - DIndex,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": DIndex, "length": 0 }
+ });
+
+ checkSelection(DIndex + (kLFLen - 1) * 2, "", "runControlCharTest", "#3")
+
+ is(result.compositionupdate, data.replace(/\r/g, "\n"), "runControlCharTest: control characters in event.data should not be removed in compositionupdate event #3");
+ is(textarea.value, data.replace(/\r/g, "\n"), "runControlCharTest: control characters should appear in textarea #3");
+
+ synthesizeComposition({ type: "compositioncommit", data });
+
+ is(result.compositionend, data.replace(/\r/g, "\n"), "runControlCharTest: control characters in event.data should not be removed in compositionend event #4");
+ is(textarea.value, data.replace(/\r/g, "\n"), "runControlCharTest: control characters should appear in textarea #4");
+
+ Services.prefs.clearUserPref("dom.compositionevent.allow_control_characters");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+}
+
+async function runRemoveContentTest()
+{
+ let events = [];
+ function eventHandler(aEvent)
+ {
+ events.push(aEvent);
+ }
+ textarea.addEventListener("compositionstart", eventHandler, true);
+ textarea.addEventListener("compositionupdate", eventHandler, true);
+ textarea.addEventListener("compositionend", eventHandler, true);
+ textarea.addEventListener("beforeinput", eventHandler, true);
+ textarea.addEventListener("input", eventHandler, true);
+ textarea.addEventListener("text", eventHandler, true);
+
+ textarea.focus();
+ textarea.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ let nextSibling = textarea.nextSibling;
+ let parent = textarea.parentElement;
+
+ events = [];
+ parent.removeChild(textarea);
+
+ await waitForEventLoops(50);
+
+ // XXX Currently, "input" event and "beforeinput" event are not fired on removed content.
+ is(events.length, 3,
+ "runRemoveContentTest: wrong event count #1");
+ is(events[0].type, "compositionupdate",
+ "runRemoveContentTest: the 1st event must be compositionupdate #1");
+ is(events[0].target, textarea,
+ `runRemoveContentTest: The "${events[0].type}" event was fired on wrong event target #1`);
+ is(events[0].data, "",
+ "runRemoveContentTest: compositionupdate has wrong data #1");
+ is(events[1].type, "text",
+ "runRemoveContentTest: the 2nd event must be text #1");
+ is(events[1].target, textarea,
+ `runRemoveContentTest: The "${events[1].type}" event was fired on wrong event target #1`);
+ todo_is(events[2].type, "beforeinput",
+ "runRemoveContentTest: the 3rd event must be beforeinput #1");
+ // is(events[2].target, textarea,
+ // `runRemoveContentTest: The "${events[2].type}" event was fired on wrong event target #1`);
+ // checkInputEvent(events[2], true, "insertCompositionText", "", [],
+ // "runRemoveContentTest: #1");
+ is(events[2].type, "compositionend",
+ "runRemoveContentTest: the 4th event must be compositionend #1");
+ is(events[2].target, textarea,
+ `runRemoveContentTest: The "${events[2].type}" event was fired on wrong event target #1`);
+ is(events[2].data, "",
+ "runRemoveContentTest: compositionend has wrong data #1");
+ ok(!getEditor(textarea).isComposing,
+ "runRemoveContentTest: the textarea still has composition #1");
+ todo_is(textarea.value, "",
+ "runRemoveContentTest: the textarea has the committed text? #1");
+
+ parent.insertBefore(textarea, nextSibling);
+
+ textarea.focus();
+ textarea.value = "";
+
+ synthesizeComposition({ type: "compositionstart" });
+
+ events = [];
+ parent.removeChild(textarea);
+
+ await waitForEventLoops(50);
+
+ // XXX Currently, "input" event and "beforeinput" event are not fired on removed content.
+ is(events.length, 2,
+ "runRemoveContentTest: wrong event count #2");
+ is(events[0].type, "text",
+ "runRemoveContentTest: the 1st event must be text #2");
+ is(events[0].target, textarea,
+ `runRemoveContentTest: The ${events[0].type} event was fired on wrong event target #2`);
+ todo_is(events[1].type, "beforeinput",
+ "runRemoveContentTest: the 2nd event must be beforeinput #2");
+ // is(events[1].target, textarea,
+ // `runRemoveContentTest: The ${events[1].type} event was fired on wrong event target #2`);
+ // checkInputEvent(events[1], true, "insertCompositionText", "", [],
+ // "runRemoveContentTest: #2");
+ is(events[1].type, "compositionend",
+ "runRemoveContentTest: the 3rd event must be compositionend #2");
+ is(events[1].target, textarea,
+ `runRemoveContentTest: The ${events[1].type} event was fired on wrong event target #2`);
+ is(events[1].data, "",
+ "runRemoveContentTest: compositionupdate has wrong data #2");
+ ok(!getEditor(textarea).isComposing,
+ "runRemoveContentTest: the textarea still has composition #2");
+ is(textarea.value, "",
+ "runRemoveContentTest: the textarea has the committed text? #2");
+
+ parent.insertBefore(textarea, nextSibling);
+
+ textarea.removeEventListener("compositionstart", eventHandler, true);
+ textarea.removeEventListener("compositionupdate", eventHandler, true);
+ textarea.removeEventListener("compositionend", eventHandler, true);
+ textarea.removeEventListener("beforeinput", eventHandler, true);
+ textarea.removeEventListener("input", eventHandler, true);
+ textarea.removeEventListener("text", eventHandler, true);
+
+ await waitForTick();
+}
+
+function runTestOnAnotherContext(aPanelOrFrame, aFocusedEditor, aTestName)
+{
+ aFocusedEditor.value = "";
+
+ // The frames and panel are cross-origin, and we no longer
+ // propagate flushes to parent cross-origin iframes explicitly,
+ // so flush our own layout here so the positions are correct.
+ document.documentElement.getBoundingClientRect();
+
+ let editorRect = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRect, aTestName + ": editorRect")) {
+ return;
+ }
+
+ let r = aPanelOrFrame.getBoundingClientRect();
+ let parentRect = {
+ left: r.left * window.devicePixelRatio,
+ top: r.top * window.devicePixelRatio,
+ width: (r.right - r.left) * window.devicePixelRatio,
+ height: (r.bottom - r.top) * window.devicePixelRatio,
+ };
+ checkRectContainsRect(editorRect, parentRect, aTestName +
+ ": the editor rect coordinates are wrong");
+
+ // input characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3078\u3093\u3057\u3093",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 }
+ });
+
+ if (!checkContent("\u3078\u3093\u3057\u3093", aTestName, "#1-1") ||
+ !checkSelection(4, "", aTestName, "#1-1")) {
+ return;
+ }
+
+ // convert them #1
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u8FD4\u4FE1",
+ "clauses":
+ [
+ { "length": 2,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ if (!checkContent("\u8FD4\u4FE1", aTestName, "#1-2") ||
+ !checkSelection(2, "", aTestName, "#1-2")) {
+ return;
+ }
+
+ // convert them #2
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u5909\u8EAB",
+ "clauses":
+ [
+ { "length": 2,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ if (!checkContent("\u5909\u8EAB", aTestName, "#1-3") ||
+ !checkSelection(2, "", aTestName, "#1-3")) {
+ return;
+ }
+
+ // commit them
+ synthesizeComposition({ type: "compositioncommitasis" });
+ if (!checkContent("\u5909\u8EAB", aTestName, "#1-4") ||
+ !checkSelection(2, "", aTestName, "#1-4")) {
+ return;
+ }
+
+ is(aFocusedEditor.value, "\u5909\u8EAB",
+ aTestName + ": composition isn't in the focused editor");
+ if (aFocusedEditor.value != "\u5909\u8EAB") {
+ return;
+ }
+
+ let textRect = synthesizeQueryTextRect(0, 1);
+ let caretRect = synthesizeQueryCaretRect(2);
+ if (!checkQueryContentResult(textRect,
+ aTestName + ": synthesizeQueryTextRect") ||
+ !checkQueryContentResult(caretRect,
+ aTestName + ": synthesizeQueryCaretRect")) {
+ return;
+ }
+ checkRectContainsRect(textRect, editorRect, aTestName + ":testRect");
+ checkRectContainsRect(caretRect, editorRect, aTestName + ":caretRect");
+}
+
+function runFrameTest()
+{
+ textareaInFrame.focus();
+ runTestOnAnotherContext(iframe, textareaInFrame, "runFrameTest");
+ runCharAtPointTest(textareaInFrame, "textarea in the iframe");
+}
+
+async function runPanelTest()
+{
+ panel.hidden = false;
+ let waitOpenPopup = new Promise(resolve => {
+ panel.addEventListener("popupshown", resolve, {once: true});
+ });
+ let waitFocusTextBox = new Promise(resolve => {
+ textbox.addEventListener("focus", resolve, {once: true});
+ });
+ panel.openPopupAtScreen(window.screenX + window.outerWidth, 0, false);
+ await waitOpenPopup;
+ textbox.focus();
+ await waitFocusTextBox;
+ is(panel.state, "open", "The panel should be open (after textbox.focus())");
+ await waitForTick();
+ is(panel.state, "open", "The panel should be open (after waitForTick())");
+ runTestOnAnotherContext(panel, textbox, "runPanelTest");
+ is(panel.state, "open", "The panel should be open (after runTestOnAnotherContext())");
+ runCharAtPointTest(textbox, "textbox in the panel");
+ is(panel.state, "open", "The panel should be open (after runCharAtPointTest())");
+ let waitClosePopup = new Promise(resolve => {
+ panel.addEventListener("popuphidden", resolve, {once: true});
+ });
+ panel.hidePopup();
+ await waitClosePopup;
+ await waitForTick();
+}
+
+// eslint-disable-next-line complexity
+function runMaxLengthTest()
+{
+ input.maxLength = 1;
+ input.value = "";
+ input.focus();
+
+ let kDesc ="runMaxLengthTest";
+
+ // input first character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ if (!checkContent("\u3089", kDesc, "#1-1") ||
+ !checkSelection(1, "", kDesc, "#1-1")) {
+ return;
+ }
+
+ // input second character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC", kDesc, "#1-2") ||
+ !checkSelection(2, "", kDesc, "#1-2")) {
+ return;
+ }
+
+ // input third character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081", kDesc, "#1-3") ||
+ !checkSelection(3, "", kDesc, "#1-3")) {
+ return;
+ }
+
+ // input fourth character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093", kDesc, "#1-4") ||
+ !checkSelection(4, "", kDesc, "#1-4")) {
+ return;
+ }
+
+
+ // backspace
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081", kDesc, "#1-5") ||
+ !checkSelection(3, "", kDesc, "#1-5")) {
+ return;
+ }
+
+ // re-input
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093", kDesc, "#1-6") ||
+ !checkSelection(4, "", kDesc, "#1-6")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055",
+ "clauses":
+ [
+ { "length": 5, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 5, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055", kDesc, "#1-7") ||
+ !checkSelection(5, "", kDesc, "#1-7")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044",
+ "clauses":
+ [
+ { "length": 6, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 6, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044", kDesc, "#1-8") ||
+ !checkSelection(6, "", kDesc, "#1-8")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
+ "clauses":
+ [
+ { "length": 7, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 7, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
+ kDesc, "#1-8") ||
+ !checkSelection(7, "", kDesc, "#1-8")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
+ "clauses":
+ [
+ { "length": 8, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 8, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
+ kDesc, "#1-9") ||
+ !checkSelection(8, "", kDesc, "#1-9")) {
+ return;
+ }
+
+ // convert
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
+ "clauses":
+ [
+ { "length": 4,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 2,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 }
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8", kDesc, "#1-10") ||
+ !checkSelection(4, "", kDesc, "#1-10")) {
+ return;
+ }
+
+ // commit the composition string
+ synthesizeComposition({ type: "compositioncommitasis" });
+ if (!checkContent("\u30E9", kDesc, "#1-11") ||
+ !checkSelection(1, "", kDesc, "#1-11")) {
+ return;
+ }
+
+ // input characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3057",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ if (!checkContent("\u30E9\u3057", kDesc, "#2-1") ||
+ !checkSelection(1 + 1, "", kDesc, "#2-1")) {
+ return;
+ }
+
+ // commit the composition string
+ synthesizeComposition({ type: "compositioncommit", data: "\u3058" });
+ if (!checkContent("\u30E9", kDesc, "#2-2") ||
+ !checkSelection(1 + 0, "", kDesc, "#2-2")) {
+ return;
+ }
+
+ // Undo
+ synthesizeKey("Z", {accelKey: true});
+
+ // XXX this is unexpected behavior, see bug 258291
+ if (!checkContent("\u30E9", kDesc, "#3-1") ||
+ !checkSelection(1 + 0, "", kDesc, "#3-1")) {
+ return;
+ }
+
+ // Undo
+ synthesizeKey("Z", {accelKey: true});
+ if (!checkContent("", kDesc, "#3-2") ||
+ !checkSelection(0, "", kDesc, "#3-2")) {
+ return;
+ }
+
+ // Redo
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+ if (!checkContent("\u30E9", kDesc, "#3-3") ||
+ !checkSelection(1, "", kDesc, "#3-3")) {
+ return;
+ }
+
+ // Redo
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+ if (!checkContent("\u30E9", kDesc, "#3-4") ||
+ !checkSelection(1 + 0, "", kDesc, "#3-4")) {
+ return;
+ }
+
+ // The input element whose content length is already maxlength and
+ // the carest is at start of the content.
+ input.value = "X";
+ input.selectionStart = input.selectionEnd = 0;
+
+ // input characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u9B54",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ if (!checkContent("\u9B54X", kDesc, "#4-1") ||
+ !checkSelection(1, "", kDesc, "#4-1")) {
+ return;
+ }
+
+ // commit the composition string
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ // The input text must be discarded. Then, the caret position shouldn't be
+ // updated from its position at compositionstart.
+ if (!checkContent("X", kDesc, "#4-2") ||
+ !checkSelection(0, "", kDesc, "#4-2")) {
+ return;
+ }
+
+ // input characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u9B54\u6CD5",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ if (!checkContent("\u9B54\u6CD5X", kDesc, "#5-1") ||
+ !checkSelection(2, "", kDesc, "#5-1")) {
+ return;
+ }
+
+ // commit the composition string
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ if (checkContent("X", kDesc, "#5-2")) {
+ checkSelection(0, "", kDesc, "#5-2");
+ }
+}
+
+async function runEditorReframeTests()
+{
+ async function runEditorReframeTest(aEditor, aWindow, aEventType)
+ {
+ function getValue()
+ {
+ return aEditor == contenteditable ?
+ aEditor.innerHTML.replace("<br>", "") : aEditor.value;
+ }
+
+ let description = "runEditorReframeTest(" + aEditor.id + ", \"" + aEventType + "\"): ";
+
+ let tests = [
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "a",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "a", description + "Typing 'a'");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ab",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ab", description + "Typing 'b' next to 'a'");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "abc",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "abc", description + "Typing 'c' next to 'ab'");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "abc",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "abc", description + "Starting to convert 'ab][c'");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABc",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABc", description + "Starting to convert 'AB][c'");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABC",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABC", description + "Starting to convert 'AB][C'");
+ },
+ },
+ { test () {
+ // Commit composition
+ synthesizeComposition({ type: "compositioncommitasis" });
+ },
+ check () {
+ is(getValue(aEditor), "ABC", description + "Committed as 'ABC'");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "d",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABCd", description + "Typing 'd' next to ABC");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "de",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABCde", description + "Typing 'e' next to ABCd");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "def",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABCdef", description + "Typing 'f' next to ABCde");
+ },
+ },
+ { test () {
+ // Commit composition
+ synthesizeComposition({ type: "compositioncommitasis" });
+ },
+ check () {
+ is(getValue(aEditor), "ABCdef", description + "Commit 'def' without convert");
+ },
+ },
+ { test () {
+ // Select "Cd"
+ synthesizeKey("KEY_ArrowLeft");
+ synthesizeKey("KEY_ArrowLeft");
+ synthesizeKey("KEY_Shift", {type: "keydown", shiftKey: true});
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
+ synthesizeKey("KEY_Shift", {type: "keyup"});
+ },
+ check () {
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "g",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABgef", description + "Typing 'g' next to AB");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "gh",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABghef", description + "Typing 'h' next to ABg");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ghi",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABghief", description + "Typing 'i' next to ABgh");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "GHI",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABGHIef", description + "Convert 'ghi' to 'GHI'");
+ },
+ },
+ { test () {
+ // Commit composition
+ synthesizeComposition({ type: "compositioncommitasis" });
+ },
+ check () {
+ is(getValue(aEditor), "ABGHIef", description + "Commit 'GHI'");
+ },
+ },
+ ];
+
+ function doReframe(aEvent)
+ {
+ aEvent.target.style.overflow =
+ aEvent.target.style.overflow != "hidden" ? "hidden" : "auto";
+ }
+ aEditor.focus();
+ aEditor.addEventListener(aEventType, doReframe);
+
+ for (const currentTest of tests) {
+ currentTest.test();
+ await waitForEventLoops(20);
+ currentTest.check();
+ await waitForTick();
+ }
+
+ await new Promise(resolve => {
+ aEditor.style.overflow = "auto";
+ aEditor.removeEventListener(aEventType, doReframe);
+ requestAnimationFrame(() => { SimpleTest.executeSoon(resolve); });
+ });
+ }
+
+ // TODO: Add "beforeinput" case.
+ input.value = "";
+ await runEditorReframeTest(input, window, "input");
+ input.value = "";
+ await runEditorReframeTest(input, window, "compositionupdate");
+ textarea.value = "";
+ await runEditorReframeTest(textarea, window, "input");
+ textarea.value = "";
+ await runEditorReframeTest(textarea, window, "compositionupdate");
+ contenteditable.innerHTML = "";
+ await runEditorReframeTest(contenteditable, windowOfContenteditable, "input");
+ contenteditable.innerHTML = "";
+ await runEditorReframeTest(contenteditable, windowOfContenteditable, "compositionupdate");
+}
+
+async function runIMEContentObserverTest()
+{
+ let notifications = [];
+ let onReceiveNotifications = null;
+ function callback(aTIP, aNotification)
+ {
+ if (aNotification.type != "notify-end-input-transaction") {
+ notifications.push(aNotification);
+ }
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ }
+ if (onReceiveNotifications) {
+ let resolve = onReceiveNotifications;
+ onReceiveNotifications = null;
+ SimpleTest.executeSoon(() => {
+ resolve();
+ });
+ }
+ return true;
+ }
+
+ function dumpUnexpectedNotifications(aDescription, aExpectedCount)
+ {
+ if (notifications.length <= aExpectedCount) {
+ return;
+ }
+ for (let i = aExpectedCount; i < notifications.length; i++) {
+ ok(false,
+ aDescription + " caused unexpected notification: " + notifications[i].type);
+ }
+ }
+
+ function promiseReceiveNotifications()
+ {
+ notifications = [];
+ return new Promise(resolve => {
+ onReceiveNotifications = resolve;
+ });
+ }
+
+ function flushNotifications()
+ {
+ return new Promise(resolve => {
+ // FYI: Dispatching non-op keyboard events causes forcibly flushing pending
+ // notifications.
+ synthesizeKey("KEY_Unidentified", { code: "" });
+ SimpleTest.executeSoon(()=>{
+ notifications = [];
+ resolve();
+ });
+ });
+ }
+
+ function ensureToRemovePrecedingPositionChangeNotification(aDescription)
+ {
+ if (!notifications.length) {
+ return;
+ }
+ if (notifications[0].type != "notify-position-change") {
+ return;
+ }
+ // Sometimes, notify-position-change is notified first separately if
+ // the operation causes scroll or something. Tests can ignore this.
+ ok(true, "notify-position-change", aDescription + "Unnecessary notify-position-change occurred, ignoring it");
+ notifications.shift();
+ }
+
+ // Bug 1374057 - On ubuntu 16.04 there are notify-position-change events that are
+ // recorded after all the other events so we remove them through this function.
+ function ensureToRemovePostPositionChangeNotification(aDescription, expectedCount)
+ {
+ if (!notifications.length) {
+ return;
+ }
+ if (notifications.length <= expectedCount) {
+ return;
+ }
+ if (notifications[notifications.length-1].type != "notify-position-change") {
+ return;
+ }
+ ok(true, "notify-position-change", aDescription + "Unnecessary notify-position-change occurred, ignoring it");
+ notifications.pop();
+ }
+
+ function getNativeText(aXPText)
+ {
+ if (kLF == "\n") {
+ return aXPText;
+ }
+ return aXPText.replace(/\n/g, kLF);
+ }
+
+ function checkPositionChangeNotification(aNotification, aDescription)
+ {
+ is(!aNotification || aNotification.type, "notify-position-change",
+ aDescription + " should cause position change notification");
+ }
+
+ function checkSelectionChangeNotification(aNotification, aDescription, aExpected)
+ {
+ is(!aNotification || aNotification.type, "notify-selection-change",
+ aDescription + " should cause selection change notification");
+ if (!aNotification || (aNotification.type != "notify-selection-change")) {
+ return;
+ }
+ is(aNotification.offset, aExpected.offset,
+ aDescription + " should cause selection change notification whose offset is " + aExpected.offset);
+ is(aNotification.text, aExpected.text,
+ aDescription + " should cause selection change notification whose text is '" + aExpected.text + "'");
+ is(aNotification.collapsed, !aExpected.text.length,
+ aDescription + " should cause selection change notification whose collapsed is " + (!aExpected.text.length));
+ is(aNotification.length, aExpected.text.length,
+ aDescription + " should cause selection change notification whose length is " + aExpected.text.length);
+ is(aNotification.reversed, aExpected.reversed || false,
+ aDescription + " should cause selection change notification whose reversed is " + (aExpected.reversed || false));
+ is(aNotification.writingMode, aExpected.writingMode || "horizontal-tb",
+ aDescription + " should cause selection change notification whose writingMode is '" + (aExpected.writingMode || "horizontal-tb"));
+ }
+
+ function checkTextChangeNotification(aNotification, aDescription, aExpected)
+ {
+ is(!aNotification || aNotification.type, "notify-text-change",
+ aDescription + " should cause text change notification");
+ if (!aNotification || aNotification.type != "notify-text-change") {
+ return;
+ }
+ is(aNotification.offset, aExpected.offset,
+ aDescription + " should cause text change notification whose offset is " + aExpected.offset);
+ is(aNotification.removedLength, aExpected.removedLength,
+ aDescription + " should cause text change notification whose removedLength is " + aExpected.removedLength);
+ is(aNotification.addedLength, aExpected.addedLength,
+ aDescription + " should cause text change notification whose addedLength is " + aExpected.addedLength);
+ }
+
+ async function testWithPlaintextEditor(aDescription, aElement, aTestLineBreaker)
+ {
+ aElement.value = "";
+ aElement.blur();
+ let doc = aElement.ownerDocument;
+ let win = doc.defaultView;
+ aElement.focus();
+ await flushNotifications();
+
+ // "a[]"
+ let description = aDescription + "typing 'a'";
+ let waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("a", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 0, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 1, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "ab[]"
+ description = aDescription + "typing 'b'";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("b", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 0, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "abc[]"
+ description = aDescription + "typing 'c'";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("c", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: 0, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 3, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "ab[c]"
+ description = aDescription + "selecting 'c' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 2, text: "c", reversed: true });
+ ensureToRemovePostPositionChangeNotification(description, 1);
+ dumpUnexpectedNotifications(description, 1);
+
+ // "a[bc]"
+ description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 1, text: "bc", reversed: true });
+ ensureToRemovePostPositionChangeNotification(description, 1);
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[abc]"
+ description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "abc", reversed: true });
+ ensureToRemovePostPositionChangeNotification(description, 1);
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[]abc"
+ description = aDescription + "collapsing selection to the left-most with pressing ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "" });
+ ensureToRemovePostPositionChangeNotification(description, 1);
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[a]bc"
+ description = aDescription + "selecting 'a' with pressing Shift+ArrowRight";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "a" });
+ ensureToRemovePostPositionChangeNotification(description, 1);
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[ab]c"
+ description = aDescription + "selecting 'ab' with pressing Shift+ArrowRight";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "ab" });
+ ensureToRemovePostPositionChangeNotification(description, 1);
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[]c"
+ description = aDescription + "deleting 'ab' with pressing Delete";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 2, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "[]"
+ description = aDescription + "deleting following 'c' with pressing Delete";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 1, addedLength: 0 });
+ checkPositionChangeNotification(notifications[1], description);
+ ensureToRemovePostPositionChangeNotification(description, 2);
+ dumpUnexpectedNotifications(description, 2);
+
+ // "abc[]"
+ synthesizeKey("a", {}, win, callback);
+ synthesizeKey("b", {}, win, callback);
+ synthesizeKey("c", {}, win, callback);
+ await flushNotifications();
+
+ // "ab[]"
+ description = aDescription + "deleting 'c' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: 1, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "[ab]"
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "[]"
+ description = aDescription + "deleting 'ab' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 2, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "abcd[]"
+ synthesizeKey("a", {}, win, callback);
+ synthesizeKey("b", {}, win, callback);
+ synthesizeKey("c", {}, win, callback);
+ synthesizeKey("d", {}, win, callback);
+ await flushNotifications();
+
+ // "a[bc]d"
+ synthesizeKey("KEY_ArrowLeft", {}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "a[]d"
+ description = aDescription + "deleting 'bc' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 2, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 1, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "a[bc]d"
+ synthesizeKey("b", {}, win, callback);
+ synthesizeKey("c", {}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "aB[]d"
+ description = aDescription + "replacing 'bc' with 'B' with pressing Shift+B";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("B", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 2, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ if (!aTestLineBreaker) {
+ return;
+ }
+
+ // "aB\n[]d"
+ description = aDescription + "inserting a line break after 'B' with pressing Enter";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Enter", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: 0, addedLength: kLFLen });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("aB\n").length, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "aB[]d"
+ description = aDescription + "removing a line break after 'B' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: kLFLen, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "a[B]d"
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "a\n[]d"
+ description = aDescription + "replacing 'B' with a line break with pressing Enter";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Enter", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 1, addedLength: kLFLen });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("a\n").length, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "a[\n]d"
+ description = aDescription + "selecting '\n' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 1, text: kLF, reversed: true });
+ ensureToRemovePostPositionChangeNotification(description, 1);
+ dumpUnexpectedNotifications(description, 1);
+
+ // "a[]d"
+ description = aDescription + "removing selected '\n' with pressing Delete";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: kLFLen, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 1, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // ab\ncd\nef\ngh\n[]
+ description = aDescription + "setting the value property to 'ab\ncd\nef\ngh\n'";
+ waitNotifications = promiseReceiveNotifications();
+ aElement.value = "ab\ncd\nef\ngh\n";
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 2, addedLength: getNativeText("ab\ncd\nef\ngh\n").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("ab\ncd\nef\ngh\n").length, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // []
+ description = aDescription + "setting the value property to ''";
+ waitNotifications = promiseReceiveNotifications();
+ aElement.value = "";
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: getNativeText("ab\ncd\nef\ngh\n").length, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+ }
+
+ async function testWithHTMLEditor(aDescription, aElement, aDefaultParagraphSeparator)
+ {
+ let doc = aElement.ownerDocument;
+ let win = doc.defaultView;
+ let sel = doc.getSelection();
+ let inDesignMode = doc.designMode == "on";
+ let offsetAtStart = 0;
+ let offsetAtContainer = 0;
+ let isDefaultParagraphSeparatorBlock = aDefaultParagraphSeparator != "br";
+ doc.execCommand("defaultParagraphSeparator", false, aDefaultParagraphSeparator);
+
+ // "[]", "<p>[]</p>" or "<div>[]</div>"
+ switch (aDefaultParagraphSeparator) {
+ case "br":
+ aElement.innerHTML = "";
+ break;
+ case "p":
+ case "div":
+ // eslint-disable-next-line no-unsanitized/property
+ aElement.innerHTML = "<" + aDefaultParagraphSeparator + "></" + aDefaultParagraphSeparator + ">";
+ sel.collapse(aElement.firstChild, 0);
+ offsetAtContainer = offsetAtStart + kLFLen;
+ break;
+ default:
+ ok(false, aDescription + "aDefaultParagraphSeparator is illegal value");
+ await flushNotifications();
+ return;
+ }
+
+ if (inDesignMode) {
+ win.focus();
+ } else {
+ aElement.focus();
+ }
+ await flushNotifications();
+
+ // "a[]"
+ let description = aDescription + "typing 'a'";
+ let waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("a", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 0, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 1 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "ab[]"
+ description = aDescription + "typing 'b'";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("b", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: 0, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "abc[]"
+ description = aDescription + "typing 'c'";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("c", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 2 + offsetAtContainer, removedLength: 0, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 3 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "ab[c]"
+ description = aDescription + "selecting 'c' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 2 + offsetAtContainer, text: "c", reversed: true });
+ dumpUnexpectedNotifications(description, 1);
+
+ // "a[bc]"
+ description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, text: "bc", reversed: true });
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[abc]"
+ description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "abc", reversed: true });
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[]abc"
+ description = aDescription + "collapsing selection to the left-most with pressing ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "" });
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[a]bc"
+ description = aDescription + "selecting 'a' with pressing Shift+ArrowRight";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "a" });
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[ab]c"
+ description = aDescription + "selecting 'ab' with pressing Shift+ArrowRight";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "ab" });
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[]c"
+ description = aDescription + "deleting 'ab' with pressing Delete";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 2, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 0 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "[]"
+ description = aDescription + "deleting following 'c' with pressing Delete";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 1, addedLength: kLFLen });
+ checkPositionChangeNotification(notifications[1], description);
+ dumpUnexpectedNotifications(description, 2);
+
+ // "abc[]"
+ synthesizeKey("a", {}, win, callback);
+ synthesizeKey("b", {}, win, callback);
+ synthesizeKey("c", {}, win, callback);
+ await flushNotifications();
+
+ // "ab[]"
+ description = aDescription + "deleting 'c' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 2 + offsetAtContainer, removedLength: 1, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "[ab]"
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "[]"
+ description = aDescription + "deleting 'ab' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 2, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 0 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "abcd[]"
+ synthesizeKey("a", {}, win, callback);
+ synthesizeKey("b", {}, win, callback);
+ synthesizeKey("c", {}, win, callback);
+ synthesizeKey("d", {}, win, callback);
+ await flushNotifications();
+
+ // "a[bc]d"
+ synthesizeKey("KEY_ArrowLeft", {}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "a[]d"
+ description = aDescription + "deleting 'bc' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: 2, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 1 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "a[bc]d"
+ synthesizeKey("b", {}, win, callback);
+ synthesizeKey("c", {}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "aB[]d"
+ description = aDescription + "replacing 'bc' with 'B' with pressing Shift+B";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("B", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: 2, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "aB<br>[]d" or "<block>ab</block><block>[]d</block>"
+ description = aDescription + "inserting a line break after 'B' with pressing Enter";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Enter", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ if (isDefaultParagraphSeparatorBlock) {
+ // Splitting current block causes removing "<block>aB" and inserting "<block>aB</block><block>".
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer - kLFLen, removedLength: getNativeText("\naB").length, addedLength: getNativeText("\naB\n").length });
+ } else {
+ // Oddly, inserting <br> causes removing "aB" and inserting "ab<br>".
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 2, addedLength: getNativeText("ab\n").length });
+ }
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("aB\n").length + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "aB[]d"
+ description = aDescription + "removing a line break after 'B' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ if (isDefaultParagraphSeparatorBlock) {
+ // Joining two blocks causes removing both block elements and inserting new block element.
+ checkTextChangeNotification(notifications[0], description, { offset: offsetAtContainer - kLFLen, removedLength: getNativeText("\naB\nd").length, addedLength: getNativeText("\naBd").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+ } else {
+ checkTextChangeNotification(notifications[0], description, { offset: 2 + offsetAtContainer, removedLength: kLFLen, addedLength: 0 });
+ is(notifications.length, 3, description + " should cause 3 notifications");
+ is(notifications[1] && notifications[1].type, "notify-selection-change", description + " should cause selection change notification");
+ is(notifications[2] && notifications[2].type, "notify-position-change", description + " should cause position change notification");
+ }
+
+ // "a[B]d"
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "a<br>[]d" or "<block>a</block><block>[]d</block>"
+ description = aDescription + "replacing 'B' with a line break with pressing Enter";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Enter", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ if (isDefaultParagraphSeparatorBlock) {
+ // Splitting current block causes removing "<block>aB" and inserting "<block>aB</block><block>".
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer - kLFLen, removedLength: getNativeText("\naB").length, addedLength: getNativeText("\na\n").length });
+ } else {
+ checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: 1, addedLength: kLFLen });
+ }
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("a\n").length + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "a[<br>]d" or "<block>a[</block><block>]d</block>"
+ description = aDescription + "selecting '\\n' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, text: kLF, reversed: true });
+ dumpUnexpectedNotifications(description, 1);
+
+ // "a[]d"
+ description = aDescription + "removing selected '\\n' with pressing Delete";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ if (isDefaultParagraphSeparatorBlock) {
+ // Joining the blocks causes removing "<block>a</block><block>d</block>" and inserting "<block>ad</block>".
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer - kLFLen, removedLength: getNativeText("\na\nd").length, addedLength: getNativeText("\nad").length });
+ } else {
+ checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: kLFLen, addedLength: 0 });
+ }
+ checkSelectionChangeNotification(notifications[1], description, { offset: 1 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>"
+ description = aDescription + "inserting HTML which has nested block elements";
+ waitNotifications = promiseReceiveNotifications();
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ // There is <br> after the end of the line. Therefore, removed length includes a line breaker length.
+ if (isDefaultParagraphSeparatorBlock) {
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer - kLFLen, removedLength: getNativeText("\nad\n").length, addedLength: getNativeText("\n1\n2\n345").length });
+ } else {
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 2 + kLFLen, addedLength: getNativeText("\n1\n2\n345").length });
+ }
+ checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1[<div>2<div>3</div>4</div>]5</div>" and removing selection
+ sel.setBaseAndExtent(aElement.firstChild.firstChild, 1, aElement.firstChild.childNodes.item(2), 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>15</div>", description + " should remove '<div>2<div>3</div>4</div>'");
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1").length + offsetAtStart, removedLength: getNativeText("\n2\n34").length, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1[<div>2<div>3</div>]4</div>5</div>" and removing selection
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
+ sel.setBaseAndExtent(aElement.firstChild.firstChild, 1, aElement.firstChild.childNodes.item(1).childNodes.item(2), 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (partially #1) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>145</div>", description + " should remove '<div>2<div>3</div></div>'");
+ // It causes removing '<div>2<div>3</div>4</div>' and inserting '4'.
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1").length + offsetAtStart, removedLength: getNativeText("\n2\n34").length, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1[<div>2<div>]3</div>4</div>5</div>" and removing selection
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
+ sel.setBaseAndExtent(aElement.firstChild.firstChild, 1, aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (partially #2) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>13<div>4</div>5</div>", description + " should remove '<div>2</div>'");
+ // It causes removing '1<div>2<div>3</div></div>' and inserting '13<div>'.
+ checkTextChangeNotification(notifications[0], description, { offset: kLFLen + offsetAtStart, removedLength: getNativeText("1\n2\n3").length, addedLength: getNativeText("13\n").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1<div>2<div>3[</div>4</div>]5</div>" and removing selection
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
+ sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(2), 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (partially #3) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>1<div>2<div>35</div></div></div>", description + " should remove '4'");
+ // It causes removing '45' and inserting '5'.
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, removedLength: 2, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>"
+ description = aDescription + "inserting HTML which has a pair of nested block elements";
+ waitNotifications = promiseReceiveNotifications();
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtStart, removedLength: getNativeText("\n1\n2\n35").length, addedLength: getNativeText("\n1\n2\n345\n6\n789").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 0 + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1<div>2<div>3[</div>4</div>5<div>6<div>]7</div>8</div>9</div>" and removing selection
+ sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(3).childNodes.item(1).firstChild, 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (between same level descendants) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>1<div>2<div>37</div></div><div>8</div>9</div>", description + " should remove '456<div>7'");
+ // It causes removing '<div>3</div>4</div>5<div>6<div>7</div>' and inserting '<div>37</div><div>'.
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, removedLength: getNativeText("\n345\n6\n7").length, addedLength: getNativeText("\n37\n").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1<div>2[<div>3</div>4</div>5<div>6<div>]7</div>8</div>9</div>" and removing selection
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
+ sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(3).childNodes.item(1).firstChild, 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (between different level descendants #1) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>1<div>27</div><div>8</div>9</div>", description + " should remove '<div>2<div>3</div>4</div>5<div>6<div>7</div>'");
+ // It causes removing '<div>2<div>3</div>4</div>5<div>6<div>7</div>' and inserting '<div>27</div>'.
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1").length + offsetAtStart, removedLength: getNativeText("\n2\n345\n6\n7").length, addedLength: getNativeText("\n27\n").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1<div>2[<div>3</div>4</div>5<div>6<div>7</div>8</div>]9</div>" and removing selection
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
+ sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(4), 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (between different level descendants #2) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>1<div>29</div></div>", description + " should remove '<div>3</div>4</div>5<div>6<div>7</div>8</div>'");
+ // It causes removing '<div>3</div>4</div>5</div>6<div>7</div>8</div>9' and inserting '9</div>'.
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, removedLength: getNativeText("\n345\n6\n789").length, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1<div>2<div>3[</div>4</div>5<div>]6<div>7</div>8</div>9</div>" and removing selection
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
+ sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(3).firstChild, 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (between different level descendants #3) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>1<div>2<div>36<div>7</div>8</div></div>9</div>", description + " should remove '<div>36<div>7</div>8</div>'");
+ // It causes removing '<div>3</div>4</div>5<div>6<div>7</div>8</div>' and inserting '<div>36<div>7</div>8</div>'.
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, removedLength: getNativeText("\n345\n6\n78").length, addedLength: getNativeText("\n36\n78").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1<div>2<div>3[</div>4</div>5<div>6<div>7</div>8</div>]9</div>" and removing selection
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
+ sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(4), 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (between different level descendants #4) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>1<div>2<div>39</div></div></div>", description + " should remove '</div>4</div>5<div>6<div>7</div>8</div>'");
+ // It causes removing '</div>4</div>5<div>6<div>7</div>8</div>' and inserting '<div>36<div>7</div>8</div>'.
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, removedLength: getNativeText("45\n6\n789").length, addedLength: getNativeText("9").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<p>abc</p><p><br></p><p>{<br>}</p>" and removing second paragraph with DOM API
+ aElement.innerHTML = "<p>abc</p><p><br></p><p><br></p>";
+ sel.collapse(aElement.firstChild.nextSibling.nextSibling, 0);
+ await flushNotifications();
+ description = aDescription + "deleting previous paragraph with DOM API";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Unidentified", { code: "" }, win, callback); // For setting the callback to recode notifications
+ aElement.firstChild.nextSibling.remove();
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<p>abc</p><p><br></p>", description + " the second paragraph should've been removed");
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\nabc").length + offsetAtStart, removedLength: getNativeText("\n\n").length, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\nabc\n").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<p>abc</p><p>{<br>}</p><p><br></p>" and removing last paragraph with DOM API
+ aElement.innerHTML = "<p>abc</p><p><br></p><p><br></p>";
+ sel.collapse(aElement.firstChild.nextSibling, 0);
+ await flushNotifications();
+ description = aDescription + "deleting next paragraph with DOM API";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Unidentified", { code: "" }, win, callback); // For setting the callback to recode notifications
+ aElement.firstChild.nextSibling.nextSibling.remove();
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<p>abc</p><p><br></p>", description + " the last paragraph should've been removed");
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\nabc\n\n").length + offsetAtStart, removedLength: getNativeText("\n\n").length, addedLength: 0 });
+ checkPositionChangeNotification(notifications[1], description);
+ dumpUnexpectedNotifications(description, 2);
+ }
+
+ await testWithPlaintextEditor("runIMEContentObserverTest with input element: ", input, false);
+ await testWithPlaintextEditor("runIMEContentObserverTest with textarea element: ", textarea, true);
+ await testWithHTMLEditor("runIMEContentObserverTest with contenteditable (defaultParagraphSeparator is br): ", contenteditable, "br");
+ await testWithHTMLEditor("runIMEContentObserverTest with contenteditable (defaultParagraphSeparator is p): ", contenteditable, "p");
+ await testWithHTMLEditor("runIMEContentObserverTest with contenteditable (defaultParagraphSeparator is div): ", contenteditable, "div");
+ // XXX Due to the difference of HTML editor behavior between designMode and contenteditable,
+ // testWithHTMLEditor() gets some unexpected behavior. However, IMEContentObserver is
+ // not depend on editor's detail. So, we should investigate this issue later. It's not
+ // so important for now.
+ // await testWithHTMLEditor("runIMEContentObserverTest in designMode (defaultParagraphSeparator is br): ", iframe2.contentDocument.body, "br");
+ // await testWithHTMLEditor("runIMEContentObserverTest in designMode (defaultParagraphSeparator is p): ", iframe2.contentDocument.body, "p");
+ // await testWithHTMLEditor("runIMEContentObserverTest in designMode (defaultParagraphSeparator is div): ", iframe2.contentDocument.body, "div");
+}
+
+async function runPasswordMaskDelayTest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["editor.password.mask_delay", 600],
+ ["editor.password.testing.mask_delay", true],
+ ],
+ });
+
+ let iframe5 = document.getElementById("iframe5");
+ let iframe6 = document.getElementById("iframe6");
+ let inputWindow = iframe5.contentWindow;
+ let passwordWindow = iframe6.contentWindow;
+
+ let inputElement = iframe5.contentDocument.getElementById("input");
+ let passwordElement = iframe6.contentDocument.getElementById("password");
+
+ const kMask = passwordElement.editor.passwordMask;
+
+ function promiseAllPasswordMasked() {
+ return new Promise(resolve => {
+ passwordElement.addEventListener("MozLastInputMasked", resolve, {once: true});
+ });
+ }
+
+ function checkSnapshots(aResult, aReference, aMatch, aDescription) {
+ let [correct, data1, data2] = compareSnapshots(aResult, aReference, true);
+ is(correct, aMatch, `${aDescription}\nREFTEST IMAGE 1 (TEST): ${data1}\nREFTEST IMAGE 2 (REFERENCE): ${data2}`);
+ }
+
+ // First character input
+ passwordElement.value = "";
+ passwordElement.focus();
+ let waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeKey("a");
+ let unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ let maskedResult = await snapshotWindow(passwordWindow, true);
+
+ inputElement.value = "a";
+ inputElement.focus();
+ inputElement.setSelectionRange(1, 1);
+ let unmaskedReference = await snapshotWindow(inputWindow, true);
+ inputElement.value = kMask;
+ inputElement.setSelectionRange(1, 1);
+ let maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): first inputted character should be unmasked for a while");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): first inputted character should be masked after a while");
+
+ // Second character input
+ passwordElement.value = "a";
+ passwordElement.focus();
+ passwordElement.setSelectionRange(1, 1);
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeKey("b");
+ unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ maskedResult = await snapshotWindow(passwordWindow, true);
+
+ inputElement.value = `${kMask}b`;
+ inputElement.focus();
+ inputElement.setSelectionRange(2, 2);
+ unmaskedReference = await snapshotWindow(inputWindow, true);
+ inputElement.value = `${kMask}${kMask}`;
+ inputElement.setSelectionRange(2, 2);
+ maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): second inputted character should be unmasked for a while");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): second inputted character should be masked after a while");
+
+ // Typing new character should mask the previous unmasked characters
+ passwordElement.value = "ab";
+ passwordElement.focus();
+ passwordElement.setSelectionRange(2, 2);
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeKey("c");
+ synthesizeKey("d");
+ unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ maskedResult = await snapshotWindow(passwordWindow, true);
+
+ inputElement.value = `${kMask}${kMask}${kMask}d`;
+ inputElement.focus();
+ inputElement.setSelectionRange(4, 4);
+ unmaskedReference = await snapshotWindow(inputWindow, true);
+ inputElement.value = `${kMask}${kMask}${kMask}${kMask}`;
+ inputElement.setSelectionRange(4, 4);
+ maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): forth character input should mask the third character");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): forth inputted character should be masked after a while");
+
+ // Typing middle of password should unmask the last input character
+ passwordElement.value = "abcd";
+ passwordElement.focus();
+ passwordElement.setSelectionRange(2, 2);
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeKey("e");
+ unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ maskedResult = await snapshotWindow(passwordWindow, true);
+
+ inputElement.value = `${kMask}${kMask}e${kMask}${kMask}`;
+ inputElement.focus();
+ inputElement.setSelectionRange(3, 3);
+ unmaskedReference = await snapshotWindow(inputWindow, true);
+ inputElement.value = `${kMask}${kMask}${kMask}${kMask}${kMask}`;
+ inputElement.setSelectionRange(3, 3);
+ maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): inserted character should be unmasked for a while");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): inserted character should be masked after a while");
+
+ // Composition string should be unmasked for a while, and shouldn't be committed at masking
+ passwordElement.value = "ab";
+ passwordElement.focus();
+ passwordElement.setSelectionRange(1, 1);
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeCompositionChange(
+ { composition:
+ { string: "c",
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ maskedResult = await snapshotWindow(passwordWindow, true);
+ is(getEditor(passwordElement).composing, true,
+ "runPasswordMaskDelayTest(): composition shouldn't be commited at masking the composing string #1");
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ inputElement.value = `${kMask}${kMask}`;
+ inputElement.focus();
+ inputElement.setSelectionRange(1, 1);
+ synthesizeCompositionChange(
+ { composition:
+ { string: "c",
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ unmaskedReference = await snapshotWindow(inputWindow, true);
+ synthesizeCompositionChange(
+ { composition:
+ { string: kMask,
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): composing character should be unmasked for a while");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): composing character should be masked after a while");
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ // Updating composition string should unmask the composition string for a while
+ passwordElement.value = "ab";
+ passwordElement.focus();
+ passwordElement.setSelectionRange(1, 1);
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeCompositionChange(
+ { composition:
+ { string: "c",
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ await waitForMaskingLastInput;
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeCompositionChange(
+ { composition:
+ { string: "d",
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ maskedResult = await snapshotWindow(passwordWindow, true);
+ is(getEditor(passwordElement).composing, true,
+ "runPasswordMaskDelayTest(): composition shouldn't be commited at masking the composing string #2");
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ inputElement.value = `${kMask}${kMask}`;
+ inputElement.focus();
+ inputElement.setSelectionRange(1, 1);
+ synthesizeCompositionChange(
+ { composition:
+ { string: "d",
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ unmaskedReference = await snapshotWindow(inputWindow, true);
+ synthesizeCompositionChange(
+ { composition:
+ { string: kMask,
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): updated composing character should be unmasked for a while");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): updated composing character should be masked after a while");
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ // Composing multi-characters should be unmasked for a while.
+ passwordElement.value = "ab";
+ passwordElement.focus();
+ passwordElement.setSelectionRange(1, 1);
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeCompositionChange(
+ { composition:
+ { string: "c",
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ await waitForMaskingLastInput;
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeCompositionChange(
+ { composition:
+ { string: "cd",
+ clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 2, length: 0 },
+ });
+ unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ maskedResult = await snapshotWindow(passwordWindow, true);
+ is(getEditor(passwordElement).composing, true,
+ "runPasswordMaskDelayTest(): composition shouldn't be commited at masking the composing string #3");
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ inputElement.value = `${kMask}${kMask}`;
+ inputElement.focus();
+ inputElement.setSelectionRange(1, 1);
+ synthesizeCompositionChange(
+ { composition:
+ { string: "cd",
+ clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 2, length: 0 },
+ });
+ unmaskedReference = await snapshotWindow(inputWindow, true);
+ synthesizeCompositionChange(
+ { composition:
+ { string: `${kMask}${kMask}`,
+ clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 2, length: 0 },
+ });
+ maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): all of composing string should be unmasked for a while");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): all of composing string should be masked after a while");
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ // Committing composition should make the commit string unmasked.
+ passwordElement.value = "ab";
+ passwordElement.focus();
+ passwordElement.setSelectionRange(1, 1);
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeCompositionChange(
+ { composition:
+ { string: "cd",
+ clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 2, length: 0 },
+ });
+ await waitForMaskingLastInput;
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+ unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ maskedResult = await snapshotWindow(passwordWindow, true);
+
+ inputElement.value = `${kMask}cd${kMask}`;
+ inputElement.focus();
+ inputElement.setSelectionRange(3, 3);
+ unmaskedReference = await snapshotWindow(inputWindow, true);
+ inputElement.value = `${kMask}${kMask}${kMask}${kMask}`;
+ inputElement.setSelectionRange(3, 3);
+ maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): committed string should be unmasked for a while");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): committed string should be masked after a while");
+}
+
+async function runTest()
+{
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.input_events.beforeinput.enabled", true]],
+ });
+
+ window.addEventListener("unload", window.arguments[0].SimpleTest.finish, {once: true, capture: true});
+
+ contenteditable = document.getElementById("iframe4").contentDocument.getElementById("contenteditable");
+ windowOfContenteditable = document.getElementById("iframe4").contentWindow;
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+
+ contenteditableBySpan = document.getElementById("iframe7").contentDocument.getElementById("contenteditable");
+ windowOfContenteditableBySpan = document.getElementById("iframe7").contentWindow;
+
+ await runIMEContentObserverTest();
+ await runEditorReframeTests();
+ await runAsyncForceCommitTest();
+ await runRemoveContentTest();
+ await runPanelTest();
+ await runPasswordMaskDelayTest();
+ await runBug1584901Test();
+
+ runUndoRedoTest();
+ runCompositionTest();
+ runCompositionCommitAsIsTest();
+ runCompositionCommitTest();
+ runCompositionEventTest();
+ runQueryTextRectInContentEditableTest();
+ runCharAtPointTest(textarea, "textarea in the document");
+ runCharAtPointAtOutsideTest();
+ runSetSelectionEventTest();
+ runQueryTextContentEventTest();
+ runQuerySelectionEventTest();
+ runQueryIMESelectionTest();
+ runQueryContentEventRelativeToInsertionPoint();
+ runQueryPasswordTest();
+ runCSSTransformTest();
+ runBug722639Test();
+ runBug1375825Test();
+ runBug1530649Test();
+ runBug1571375Test();
+ runBug1675313Test();
+ runCommitCompositionWithSpaceKey();
+ runForceCommitTest();
+ runNestedSettingValue();
+ runBug811755Test();
+ runIsComposingTest();
+ runRedundantChangeTest();
+ runNotRedundantChangeTest();
+ runNativeLineBreakerTest();
+ runControlCharTest();
+ runFrameTest();
+ runMaxLengthTest();
+
+ window.close();
+}
+
+window.arguments[0].SimpleTest.waitForFocus(runTest, window);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_imestate_iframes.html b/widget/tests/window_imestate_iframes.html
new file mode 100644
index 0000000000..015303a55a
--- /dev/null
+++ b/widget/tests/window_imestate_iframes.html
@@ -0,0 +1,360 @@
+<html>
+<head>
+ <title>Test for IME state controling and focus moving for iframes</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ iframe {
+ border: none;
+ height: 100px;
+ }
+ </style>
+</head>
+<body onunload="onUnload();">
+<p id="display">
+ <!-- Use input[readonly] because it isn't affected by the partial focus
+ movement on Mac -->
+ <input id="prev" readonly><br>
+ <iframe id="iframe_not_editable"
+ src="data:text/html,&lt;html&gt;&lt;body&gt;&lt;input id='editor'&gt;&lt;/body&gt;&lt;/html&gt;"></iframe><br>
+
+ <!-- Testing IME state and focus movement, the anchor elements cannot get focus -->
+ <iframe id="iframe_html"
+ src="data:text/html,&lt;html id='editor' contenteditable='true'&gt;&lt;body&gt;&lt;a href='about:blank'&gt;about:blank;&lt;/a&gt;&lt;/body&gt;&lt;/html&gt;"></iframe><br>
+ <iframe id="iframe_designMode"
+ src="data:text/html,&lt;body id='editor' onload='document.designMode=&quot;on&quot;;'&gt;&lt;a href='about:blank'&gt;about:blank;&lt;/a&gt;&lt;/body&gt;"></iframe><br>
+ <iframe id="iframe_body"
+ src="data:text/html,&lt;body id='editor' contenteditable='true'&gt;&lt;a href='about:blank'&gt;about:blank;&lt;/a&gt;&lt;/body&gt;"></iframe><br>
+ <iframe id="iframe_p"
+ src="data:text/html,&lt;body&gt;&lt;p id='editor' contenteditable='true'&gt;&lt;a href='about:blank'&gt;about:blank;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;"></iframe><br>
+
+ <input id="next" readonly><br>
+</p>
+<script class="testbody" type="application/javascript">
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+window.opener.SimpleTest.waitForFocus(runTests, window);
+
+function ok(aCondition, aMessage) {
+ window.opener.SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage) {
+ window.opener.SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function onUnload() {
+ window.opener.onFinish();
+}
+
+var gFocusObservingElement = null;
+var gBlurObservingElement = null;
+
+function onFocus(aEvent) {
+ if (aEvent.target != gFocusObservingElement) {
+ return;
+ }
+ ok(gFocusObservingElement.willFocus,
+ "focus event is fired on unexpected element");
+ gFocusObservingElement.willFocus = false;
+}
+
+function onBlur(aEvent) {
+ if (aEvent.target != gBlurObservingElement) {
+ return;
+ }
+ ok(gBlurObservingElement.willBlur,
+ "blur event is fired on unexpected element");
+ gBlurObservingElement.willBlur = false;
+}
+
+function observeFocusBlur(aNextFocusedNode, aWillFireFocusEvent,
+ aNextBlurredNode, aWillFireBlurEvent) {
+ if (gFocusObservingElement) {
+ if (gFocusObservingElement.willFocus) {
+ ok(false, "focus event was never fired on " + gFocusObservingElement);
+ }
+ gFocusObservingElement.removeEventListener("focus", onFocus, true);
+ gFocusObservingElement.willFocus = NaN;
+ gFocusObservingElement = null;
+ }
+ if (gBlurObservingElement) {
+ if (gBlurObservingElement.willBlur) {
+ ok(false, "blur event was never fired on " + gBlurObservingElement);
+ }
+ gBlurObservingElement.removeEventListener("blur", onBlur, true);
+ gBlurObservingElement.willBlur = NaN;
+ gBlurObservingElement = null;
+ }
+ if (aNextFocusedNode) {
+ gFocusObservingElement = aNextFocusedNode;
+ gFocusObservingElement.willFocus = aWillFireFocusEvent;
+ gFocusObservingElement.addEventListener("focus", onFocus, true);
+ }
+ if (aNextBlurredNode) {
+ gBlurObservingElement = aNextBlurredNode;
+ gBlurObservingElement.willBlur = aWillFireBlurEvent;
+ gBlurObservingElement.addEventListener("blur", onBlur, true);
+ }
+}
+
+function runTests() {
+ var utils = window.windowUtils;
+ var fm = Services.focus;
+
+ var iframe, editor, root;
+ var prev = document.getElementById("prev");
+ var next = document.getElementById("next");
+ var html = document.documentElement;
+
+ function resetFocusToInput(aDescription) {
+ observeFocusBlur(null, false, null, false);
+ prev.focus();
+ is(fm.focusedElement, prev,
+ "input#prev[readonly] element didn't get focus: " + aDescription);
+ is(utils.IMEStatus, utils.IME_STATUS_DISABLED,
+ "IME enabled on input#prev[readonly]: " + aDescription);
+ }
+
+ function resetFocusToParentHTML(aDescription) {
+ observeFocusBlur(null, false, null, false);
+ html.focus();
+ is(fm.focusedElement, html,
+ "Parent html element didn't get focus: " + aDescription);
+ is(utils.IMEStatus, utils.IME_STATUS_DISABLED,
+ "IME enabled on parent html element: " + aDescription);
+ }
+
+ function testTabKey(aForward,
+ aNextFocusedNode, aWillFireFocusEvent,
+ aNextBlurredNode, aWillFireBlurEvent,
+ aIMEShouldBeEnabled, aTestingCaseDescription) {
+ observeFocusBlur(aNextFocusedNode, aWillFireFocusEvent,
+ aNextBlurredNode, aWillFireBlurEvent);
+ synthesizeKey("VK_TAB", { shiftKey: !aForward });
+ var description = "Tab key test: ";
+ if (!aForward) {
+ description = "Shift-" + description;
+ }
+ description += aTestingCaseDescription + ": ";
+ is(fm.focusedElement, aNextFocusedNode,
+ description + "didn't move focus as expected");
+ is(utils.IMEStatus,
+ aIMEShouldBeEnabled ?
+ utils.IME_STATUS_ENABLED : utils.IME_STATUS_DISABLED,
+ description + "didn't set IME state as expected");
+ }
+
+ function testMouseClick(aNextFocusedNode, aWillFireFocusEvent,
+ aWillAllNodeLostFocus,
+ aNextBlurredNode, aWillFireBlurEvent,
+ aIMEShouldBeEnabled, aTestingCaseDescription) {
+ observeFocusBlur(aNextFocusedNode, aWillFireFocusEvent,
+ aNextBlurredNode, aWillFireBlurEvent);
+ // We're relying on layout inside the iframe being up to date, so make it so
+ iframe.contentDocument.documentElement.getBoundingClientRect();
+ synthesizeMouse(iframe, 10, 80, { });
+ var description = "Click test: " + aTestingCaseDescription + ": ";
+ is(fm.focusedElement, !aWillAllNodeLostFocus ? aNextFocusedNode : null,
+ description + "didn't move focus as expected");
+ is(utils.IMEStatus,
+ aIMEShouldBeEnabled ?
+ utils.IME_STATUS_ENABLED : utils.IME_STATUS_DISABLED,
+ description + "didn't set IME state as expected");
+ }
+
+ function testOnEditorFlagChange(aDescription, aIsInDesignMode) {
+ const kReadonly = Ci.nsIEditor.eEditorReadonlyMask;
+ var description = "testOnEditorFlagChange: " + aDescription;
+ resetFocusToParentHTML(description);
+ var htmlEditor = iframe.contentWindow.docShell.editor;
+ var e = aIsInDesignMode ? root : editor;
+ e.focus();
+ is(fm.focusedElement, e,
+ description + ": focus() of editor didn't move focus as expected");
+ is(utils.IMEStatus, utils.IME_STATUS_ENABLED,
+ description + ": IME isn't enabled when the editor gets focus");
+ var flags = htmlEditor.flags;
+ htmlEditor.flags |= kReadonly;
+ is(fm.focusedElement, e,
+ description + ": when editor becomes readonly, focus moved unexpectedly");
+ is(utils.IMEStatus, utils.IME_STATUS_DISABLED,
+ description + ": when editor becomes readonly, IME is still enabled");
+ htmlEditor.flags = flags;
+ is(fm.focusedElement, e,
+ description + ": when editor becomes read-write, focus moved unexpectedly");
+ is(utils.IMEStatus, utils.IME_STATUS_ENABLED,
+ description + ": when editor becomes read-write, IME is still disabled");
+ }
+
+ // hide all iframes
+ document.getElementById("iframe_not_editable").style.display = "none";
+ document.getElementById("iframe_html").style.display = "none";
+ document.getElementById("iframe_designMode").style.display = "none";
+ document.getElementById("iframe_body").style.display = "none";
+ document.getElementById("iframe_p").style.display = "none";
+
+ // non editable HTML element and input element can get focus.
+ iframe = document.getElementById("iframe_not_editable");
+ iframe.style.display = "inline";
+ editor = iframe.contentDocument.getElementById("editor");
+ root = iframe.contentDocument.documentElement;
+ resetFocusToInput("initializing for iframe_not_editable");
+
+ testTabKey(true, root, false, prev, true,
+ false, "input#prev[readonly] -> html");
+ testTabKey(true, editor, true, root, false,
+ true, "html -> input in the subdoc");
+ testTabKey(true, next, true, editor, true,
+ false, "input in the subdoc -> input#next[readonly]");
+ testTabKey(false, editor, true, next, true,
+ true, "input#next[readonly] -> input in the subdoc");
+ testTabKey(false, root, false, editor, true,
+ false, "input in the subdoc -> html");
+ testTabKey(false, prev, true, root, false,
+ false, "html -> input#next[readonly]");
+
+ iframe.style.display = "none";
+
+ // HTML element (of course, it's root) must enables IME.
+ iframe = document.getElementById("iframe_html");
+ iframe.style.display = "inline";
+ editor = iframe.contentDocument.getElementById("editor");
+ root = iframe.contentDocument.documentElement;
+ resetFocusToInput("initializing for iframe_html");
+
+ testTabKey(true, editor, true, prev, true,
+ true, "input#prev[readonly] -> html[contentediable=true]");
+ testTabKey(true, next, true, editor, true,
+ false, "html[contentediable=true] -> input#next[readonly]");
+ testTabKey(false, editor, true, next, true,
+ true, "input#next[readonly] -> html[contentediable=true]");
+ testTabKey(false, prev, true, editor, true,
+ false, "html[contenteditable=true] -> input[readonly]");
+
+ prev.style.display = "none";
+ resetFocusToParentHTML("testing iframe_html");
+ testTabKey(true, editor, true, html, false,
+ true, "html of parent -> html[contentediable=true]");
+ testTabKey(false, html, false, editor, true,
+ false, "html[contenteditable=true] -> html of parent");
+ prev.style.display = "inline";
+ resetFocusToInput("after parent html <-> html[contenteditable=true]");
+
+ testMouseClick(editor, true, false, prev, true, true, "iframe_html");
+
+ testOnEditorFlagChange("html[contentediable=true]", false);
+
+ iframe.style.display = "none";
+
+ // designMode should behave like <html contenteditable="true"></html>
+ // but focus/blur events shouldn't be fired on its root element because
+ // any elements shouldn't be focused state in designMode.
+ iframe = document.getElementById("iframe_designMode");
+ iframe.style.display = "inline";
+ iframe.contentDocument.designMode = "on";
+ editor = iframe.contentDocument.getElementById("editor");
+ root = iframe.contentDocument.documentElement;
+ resetFocusToInput("initializing for iframe_designMode");
+
+ testTabKey(true, root, false, prev, true,
+ true, "input#prev[readonly] -> html in designMode");
+ testTabKey(true, next, true, root, false,
+ false, "html in designMode -> input#next[readonly]");
+ testTabKey(false, root, false, next, true,
+ true, "input#next[readonly] -> html in designMode");
+ testTabKey(false, prev, true, root, false,
+ false, "html in designMode -> input#prev[readonly]");
+
+ prev.style.display = "none";
+ resetFocusToParentHTML("testing iframe_designMode");
+ testTabKey(true, root, false, html, false,
+ true, "html of parent -> html in designMode");
+ testTabKey(false, html, false, root, false,
+ false, "html in designMode -> html of parent");
+ prev.style.display = "inline";
+ resetFocusToInput("after parent html <-> html in designMode");
+
+ testMouseClick(editor, false, true, prev, true, true, "iframe_designMode");
+
+ testOnEditorFlagChange("html in designMode", true);
+
+ iframe.style.display = "none";
+
+ // When there is no HTML element but the BODY element is editable,
+ // the body element should get focus and enables IME.
+ iframe = document.getElementById("iframe_body");
+ iframe.style.display = "inline";
+ editor = iframe.contentDocument.getElementById("editor");
+ root = iframe.contentDocument.documentElement;
+ resetFocusToInput("initializing for iframe_body");
+
+ testTabKey(true, editor, true, prev, true,
+ true, "input#prev[readonly] -> body[contentediable=true]");
+ testTabKey(true, next, true, editor, true,
+ false, "body[contentediable=true] -> input#next[readonly]");
+ testTabKey(false, editor, true, next, true,
+ true, "input#next[readonly] -> body[contentediable=true]");
+ testTabKey(false, prev, true, editor, true,
+ false, "body[contenteditable=true] -> input#prev[readonly]");
+
+ prev.style.display = "none";
+ resetFocusToParentHTML("testing iframe_body");
+ testTabKey(true, editor, true, html, false,
+ true, "html of parent -> body[contentediable=true]");
+ testTabKey(false, html, false, editor, true,
+ false, "body[contenteditable=true] -> html of parent");
+ prev.style.display = "inline";
+ resetFocusToInput("after parent html <-> body[contenteditable=true]");
+
+ testMouseClick(editor, true, false, prev, true, true, "iframe_body");
+
+ testOnEditorFlagChange("body[contentediable=true]", false);
+
+ iframe.style.display = "none";
+
+ // When HTML/BODY elements are not editable, focus shouldn't be moved to
+ // the editable content directly.
+ iframe = document.getElementById("iframe_p");
+ iframe.style.display = "inline";
+ editor = iframe.contentDocument.getElementById("editor");
+ root = iframe.contentDocument.documentElement;
+ resetFocusToInput("initializing for iframe_p");
+
+ testTabKey(true, root, false, prev, true,
+ false, "input#prev[readonly] -> html (has p[contenteditable=true])");
+ testTabKey(true, editor, true, root, false,
+ true, "html (has p[contenteditable=true]) -> p[contentediable=true]");
+ testTabKey(true, next, true, editor, true,
+ false, "p[contentediable=true] -> input#next[readonly]");
+ testTabKey(false, editor, true, next, true,
+ true, "input#next[readonly] -> p[contentediable=true]");
+ testTabKey(false, root, false, editor, true,
+ false, "p[contenteditable=true] -> html (has p[contenteditable=true])");
+ testTabKey(false, prev, true, root, false,
+ false, "html (has p[contenteditable=true]) -> input#prev[readonly]");
+ prev.style.display = "none";
+
+ resetFocusToParentHTML("testing iframe_p");
+ testTabKey(true, root, false, html, false,
+ false, "html of parent -> html (has p[contentediable=true])");
+ testTabKey(false, html, false, root, false,
+ false, "html (has p[contentediable=true]) -> html of parent");
+ prev.style.display = "inline";
+ resetFocusToInput("after parent html <-> html (has p[contentediable=true])");
+
+ testMouseClick(root, false, true, prev, true, false, "iframe_p");
+
+ testOnEditorFlagChange("p[contenteditable=true]", false);
+
+ iframe.style.display = "none";
+
+ window.close();
+}
+
+</script>
+</body>
+
+</html>
diff --git a/widget/tests/window_mouse_scroll_win.html b/widget/tests/window_mouse_scroll_win.html
new file mode 100644
index 0000000000..d982061076
--- /dev/null
+++ b/widget/tests/window_mouse_scroll_win.html
@@ -0,0 +1,1518 @@
+<html lang="en-US"
+ style="font-family: Arial; font-size: 10px; line-height: 16px;">
+<head>
+ <title>Test for mouse scroll handling on Windows</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body onunload="onUnload();">
+<div id="display" style="width: 5000px; height: 5000px;">
+<p id="p1" style="font-size: 16px; width: 100px; height: 100px;">1st &lt;p&gt;.</p>
+<p id="p2" style="font-size: 32px; width: 100px; height: 100px;">2nd &lt;p&gt;.</p>
+</div>
+<script class="testbody" type="application/javascript">
+
+window.arguments[0].SimpleTest.waitForFocus(prepareTests, window);
+
+const nsIDOMWindowUtils = Ci.nsIDOMWindowUtils;
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const WHEEL_PAGESCROLL = 4294967295;
+
+const WM_VSCROLL = 0x0115;
+const WM_HSCROLL = 0x0114;
+const WM_MOUSEWHEEL = 0x020A;
+const WM_MOUSEHWHEEL = 0x020E;
+
+const SB_LINEUP = 0;
+const SB_LINELEFT = 0;
+const SB_LINEDOWN = 1;
+const SB_LINERIGHT = 1;
+const SB_PAGEUP = 2;
+const SB_PAGELEFT = 2;
+const SB_PAGEDOWN = 3;
+const SB_PAGERIGHT = 3;
+
+const SHIFT_L = 0x0100;
+const SHIFT_R = 0x0200;
+const CTRL_L = 0x0400;
+const CTRL_R = 0x0800;
+const ALT_L = 0x1000;
+const ALT_R = 0x2000;
+
+const DOM_PAGE_SCROLL_DELTA = 32768;
+
+const kSystemScrollSpeedOverridePref = "mousewheel.system_scroll_override_on_root_content.enabled";
+
+const kAltKeyActionPref = "mousewheel.with_alt.action";
+const kCtrlKeyActionPref = "mousewheel.with_control.action";
+const kShiftKeyActionPref = "mousewheel.with_shift.action";
+const kWinKeyActionPref = "mousewheel.with_win.action";
+
+const kAltKeyDeltaMultiplierXPref = "mousewheel.with_alt.delta_multiplier_x";
+const kAltKeyDeltaMultiplierYPref = "mousewheel.with_alt.delta_multiplier_y";
+const kCtrlKeyDeltaMultiplierXPref = "mousewheel.with_control.delta_multiplier_x";
+const kCtrlKeyDeltaMultiplierYPref = "mousewheel.with_control.delta_multiplier_y";
+const kShiftKeyDeltaMultiplierXPref = "mousewheel.with_shift.delta_multiplier_x";
+const kShiftKeyDeltaMultiplierYPref = "mousewheel.with_shift.delta_multiplier_y";
+const kWinKeyDeltaMultiplierXPref = "mousewheel.with_win.delta_multiplier_x";
+const kWinKeyDeltaMultiplierYPref = "mousewheel.with_win.delta_multiplier_y";
+
+const kEmulateWheelByWMSCROLLPref = "mousewheel.emulate_at_wm_scroll";
+const kVAmountPref = "mousewheel.windows.vertical_amount_override";
+const kHAmountPref = "mousewheel.windows.horizontal_amount_override";
+const kTimeoutPref = "mousewheel.windows.transaction.timeout";
+
+const kMouseLineScrollEvent = "DOMMouseScroll";
+const kMousePixelScrollEvent = "MozMousePixelScroll";
+
+const kVAxis = MouseScrollEvent.VERTICAL_AXIS;
+const kHAxis = MouseScrollEvent.HORIZONTAL_AXIS;
+
+var gLineHeight = 0;
+var gCharWidth = 0;
+var gPageHeight = 0;
+var gPageWidth = 0;
+
+var gP1 = document.getElementById("p1");
+var gP2 = document.getElementById("p2");
+
+var gOtherWindow;
+
+function ok(aCondition, aMessage) {
+ window.arguments[0].SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage) {
+ window.arguments[0].SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage) {
+ window.arguments[0].SimpleTest.isnot(aLeft, aRight, aMessage);
+}
+
+function todo_is(aLeft, aRight, aMessage) {
+ window.arguments[0].SimpleTest.todo_is(aLeft, aRight, aMessage);
+}
+
+function onUnload() {
+ SpecialPowers.clearUserPref(kAltKeyActionPref);
+ SpecialPowers.clearUserPref(kCtrlKeyActionPref);
+ SpecialPowers.clearUserPref(kShiftKeyActionPref);
+ SpecialPowers.clearUserPref(kWinKeyActionPref);
+
+ SpecialPowers.clearUserPref(kAltKeyDeltaMultiplierXPref);
+ SpecialPowers.clearUserPref(kAltKeyDeltaMultiplierYPref);
+ SpecialPowers.clearUserPref(kCtrlKeyDeltaMultiplierXPref);
+ SpecialPowers.clearUserPref(kCtrlKeyDeltaMultiplierYPref);
+ SpecialPowers.clearUserPref(kShiftKeyDeltaMultiplierXPref);
+ SpecialPowers.clearUserPref(kShiftKeyDeltaMultiplierYPref);
+ SpecialPowers.clearUserPref(kWinKeyDeltaMultiplierXPref);
+ SpecialPowers.clearUserPref(kWinKeyDeltaMultiplierYPref);
+
+ SpecialPowers.clearUserPref(kSystemScrollSpeedOverridePref);
+ SpecialPowers.clearUserPref(kEmulateWheelByWMSCROLLPref);
+ SpecialPowers.clearUserPref(kVAmountPref);
+ SpecialPowers.clearUserPref(kHAmountPref);
+ SpecialPowers.clearUserPref(kTimeoutPref);
+ window.arguments[0].SimpleTest.finish();
+}
+
+function getWindowUtils(aWindow) {
+ if (!aWindow) {
+ aWindow = window;
+ }
+ return aWindow.windowUtils;
+}
+
+function getPointInScreen(aElement, aWindow) {
+ if (!aWindow) {
+ aWindow = window;
+ }
+ var bounds = aElement.getBoundingClientRect();
+ return { x: bounds.left + aWindow.mozInnerScreenX,
+ y: bounds.top + aWindow.mozInnerScreenY };
+}
+
+function cut(aNum) {
+ return (aNum >= 0) ? Math.floor(aNum) : Math.ceil(aNum);
+}
+
+/**
+ * Make each steps for the tests in following arrays in global scope. Each item
+ * of the arrays will be executed after previous test is finished.
+ *
+ * description:
+ * Set the description of the test. This will be used for the message of is()
+ * or the others.
+ *
+ * message:
+ * aNativeMessage of nsIDOMWindowUtils.sendNativeMouseScrollEvent().
+ * Must be WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL or WM_HSCROLL.
+ *
+ * delta:
+ * The native delta value for WM_MOUSEWHEEL or WM_MOUSEHWHEEL.
+ * Or one of the SB_* const value for WM_VSCROLL or WM_HSCROLL.
+ *
+ * target:
+ * The target element, under the mouse cursor.
+ *
+ * window:
+ * The window which is used for getting nsIDOMWindowUtils.
+ *
+ * modifiers:
+ * Pressed modifier keys, 0 means no modifier key is pressed.
+ * Otherwise, one or more values of SHIFT_L, SHIFT_R, CTRL_L, CTRL_R,
+ * ALT_L or ALT_R.
+ *
+ * additionalFlags:
+ * aAdditionalFlags of nsIDOMWindowUtils.sendNativeMouseScrollEvent().
+ * See the document of nsIDOMWindowUtils for the detail of the values.
+ *
+ * onLineScrollEvent:
+ * Must be a function or null.
+ * If the value is a function, it will be called when DOMMouseScroll event
+ * is received by the synthesized event.
+ * If return true, the common checks are canceled.
+ *
+ * onPixelScrollEvent:
+ * Must be a function or null.
+ * If the value is a function, it will be called when MozMousePixelScroll
+ * event is received by the synthesized event.
+ * If return true, the common checks are canceled.
+ *
+ * expected:
+ * Must not be null and this must have:
+ * axis:
+ * kVAxis if the synthesized event causes vertical scroll. Otherwise,
+ * it causes horizontal scroll, kHAxis.
+ * lines:
+ * Integer value which is expected detail attribute value of
+ * DOMMouseScroll. If the event shouldn't be fired, must be 0.
+ * pixels:
+ * Integer value or a function which returns double value. The value is
+ * expected detail attribute value of MozMousePixelScroll.
+ * If the event shouldn't be fired, must be 0.
+ *
+ * Note that if both lines and pixels are 0, the test framework waits
+ * a few seconds. After that, go to next test.
+ *
+ * init:
+ * Must be a function or null. If this value is a function, it's called
+ * before synthesizing the native event.
+ *
+ * finish:
+ * Must be a function or null. If this value is a function, it's called
+ * after received all expected events or timeout if no events are expected.
+ */
+
+// First, get the computed line height, char width, page height and page width.
+var gPreparingSteps = [
+ { description: "Preparing gLineHeight",
+ message: WM_MOUSEWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ onLineScrollEvent(aEvent) {
+ return true;
+ },
+ onPixelScrollEvent(aEvent) {
+ gLineHeight = aEvent.detail;
+ return true;
+ },
+ expected: {
+ axis: kVAxis, lines: 1, pixels: 1,
+ },
+ init() {
+ SpecialPowers.setIntPref(kVAmountPref, 1);
+ SpecialPowers.setIntPref(kHAmountPref, 1);
+ },
+ },
+ { description: "Preparing gCharWidth",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ onLineScrollEvent(aEvent) {
+ return true;
+ },
+ onPixelScrollEvent(aEvent) {
+ gCharWidth = aEvent.detail;
+ return true;
+ },
+ expected: {
+ axis: kVAxis, lines: 1, pixels: 1,
+ },
+ },
+ { description: "Preparing gPageHeight",
+ message: WM_MOUSEWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ onLineScrollEvent(aEvent) {
+ return true;
+ },
+ onPixelScrollEvent(aEvent) {
+ gPageHeight = aEvent.detail;
+ return true;
+ },
+ expected: {
+ axis: kHAxis, lines: 1, pixels: 1,
+ },
+ init() {
+ SpecialPowers.setIntPref(kVAmountPref, 0xFFFF);
+ SpecialPowers.setIntPref(kHAmountPref, 0xFFFF);
+ },
+ },
+ { description: "Preparing gPageWidth",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ onLineScrollEvent(aEvent) {
+ return true;
+ },
+ onPixelScrollEvent(aEvent) {
+ gPageWidth = aEvent.detail;
+ return true;
+ },
+ expected: {
+ axis: kHAxis, lines: 1, pixels: 1,
+ },
+ finish() {
+ ok(gLineHeight > 0, "gLineHeight isn't positive got " + gLineHeight);
+ ok(gCharWidth > 0, "gCharWidth isn't positive got " + gCharWidth);
+ ok(gPageHeight > 0, "gPageHeight isn't positive got " + gPageHeight);
+ ok(gPageWidth > 0, "gPageWidth isn't positive got " + gPageWidth);
+
+ ok(gPageHeight > gLineHeight,
+ "gPageHeight must be larger than gLineHeight");
+ ok(gPageWidth > gCharWidth,
+ "gPageWidth must be larger than gCharWidth");
+ runNextTest(gBasicTests, 0);
+ },
+ },
+];
+
+var gBasicTests = [
+ // Widget shouldn't dispatch a pixel event if the delta can be devided by
+ // lines to be scrolled. However, pixel events should be fired by ESM.
+ { description: "WM_MOUSEWHEEL, -120, 3 lines",
+ message: WM_MOUSEWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 3, pixels() { return gLineHeight * 3; },
+ },
+ init() {
+ SpecialPowers.setIntPref(kVAmountPref, 3);
+ SpecialPowers.setIntPref(kHAmountPref, 3);
+ },
+ },
+
+ { description: "WM_MOUSEWHEEL, 120, -3 lines",
+ message: WM_MOUSEWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -3, pixels() { return gLineHeight * -3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 120, 3 chars",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 3, pixels() { return gCharWidth * 3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, -120, -3 chars",
+ message: WM_MOUSEHWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -3, pixels() { return gCharWidth * -3; },
+ },
+ },
+
+ // Pixel scroll event should be fired always but line scroll event should be
+ // fired only when accumulated delta value is over a line.
+ { description: "WM_MOUSEWHEEL, -20, 0.5 lines",
+ message: WM_MOUSEWHEEL, delta: -20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gLineHeight / 2; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -20, 0.5 lines (pending: 0.5 lines)",
+ message: WM_MOUSEWHEEL, delta: -20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight / 2; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -20, 0.5 lines",
+ message: WM_MOUSEWHEEL, delta: -20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gLineHeight / 2; },
+ },
+ },
+
+ { description: "WM_MOUSEWHEEL, 20, -0.5 lines (pending: 0.5 lines)",
+ message: WM_MOUSEWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gLineHeight / -2; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, 20, -0.5 lines (pending: -0.5 lines)",
+ message: WM_MOUSEWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -1, pixels() { return -gLineHeight / 2; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, 20, -0.5 lines",
+ message: WM_MOUSEWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gLineHeight / -2; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 20, 0.5 chars",
+ message: WM_MOUSEHWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gCharWidth / 2; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 20, 0.5 chars (pending: 0.5 chars)",
+ message: WM_MOUSEHWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth / 2; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 20, 0.5 chars",
+ message: WM_MOUSEHWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gCharWidth / 2; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, -20, -0.5 chars (pending: 0.5 chars)",
+ message: WM_MOUSEHWHEEL, delta: -20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gCharWidth / -2; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, -20, -0.5 chars (pending: -0.5 chars)",
+ message: WM_MOUSEHWHEEL, delta: -20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -1, pixels() { return -gCharWidth / 2; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, -20, -0.5 chars",
+ message: WM_MOUSEHWHEEL, delta: -20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gCharWidth / -2; },
+ },
+ },
+
+ // Even if the mouse cursor is an element whose font-size is different than
+ // the scrollable element, the pixel scroll amount shouldn't be changed.
+ // Widget shouldn't dispatch a pixel event if the delta can be devided by
+ // lines to be scrolled. However, pixel events should be fired by ESM.
+ { description: "WM_MOUSEWHEEL, -120, 3 lines, on the other div whose font-size is larger",
+ message: WM_MOUSEWHEEL, delta: -120,
+ target: gP2, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 3, pixels() { return gLineHeight * 3; },
+ },
+ },
+
+ { description: "WM_MOUSEWHEEL, 120, -3 lines, on the other div whose font-size is larger",
+ message: WM_MOUSEWHEEL, delta: 120,
+ target: gP2, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -3, pixels() { return gLineHeight * -3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 120, 3 chars, on the other div whose font-size is larger",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP2, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 3, pixels() { return gCharWidth * 3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, -120, -3 chars, on the other div whose font-size is larger",
+ message: WM_MOUSEHWHEEL, delta: -120,
+ target: gP2, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -3, pixels() { return gCharWidth * -3; },
+ },
+ },
+
+ // Modifier key tests
+ { description: "WM_MOUSEWHEEL, -40, 1 line with left Shift",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with right Shift",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with left Ctrl",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with right Ctrl",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with left Alt",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with right Alt",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with left Shift",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with right Shift",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with left Ctrl",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with right Ctrl",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with left Alt",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with right Alt",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+
+ finish() {
+ runNextTest(gScrollMessageTests, 0);
+ },
+ },
+];
+
+var gPageScrllTests = [
+ // Pixel scroll event should be fired always but line scroll event should be
+ // fired only when accumulated delta value is over a line.
+ { description: "WM_MOUSEWHEEL, -60, 0.5 pages",
+ message: WM_MOUSEWHEEL, delta: -60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gPageHeight / 2; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -60, 0.5 pages (pending: 0.5 pages)",
+ message: WM_MOUSEWHEEL, delta: -60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels() { return ((gPageHeight / 2) + (gPageHeight % 2)); },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -60, 0.5 pages",
+ message: WM_MOUSEWHEEL, delta: -60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gPageHeight / 2; },
+ },
+ },
+
+ { description: "WM_MOUSEWHEEL, 60, -0.5 pages (pending: 0.5 pages)",
+ message: WM_MOUSEWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gPageHeight / -2; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, 60, -0.5 pages (pending: -0.5 pages)",
+ message: WM_MOUSEWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels() { return -((gPageHeight / 2) + (gPageHeight % 2)); },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, 60, -0.5 pages",
+ message: WM_MOUSEWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gPageHeight / -2; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 60, 0.5 pages",
+ message: WM_MOUSEHWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gPageWidth / 2; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 60, 0.5 pages (pending: 0.5 pages)",
+ message: WM_MOUSEHWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels() { return ((gPageWidth / 2) + (gPageWidth % 2)); },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 60, 0.5 pages",
+ message: WM_MOUSEHWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gPageWidth / 2; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, -60, -0.5 pages (pending: 0.5 pages)",
+ message: WM_MOUSEHWHEEL, delta: -60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gCharWidth / -2; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, -60, -0.5 pages (pending: -0.5 pages)",
+ message: WM_MOUSEHWHEEL, delta: -60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels() { return -((gCharWidth / 2) + (gCharWidth % 2)); },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, -60, -0.5 pages",
+ message: WM_MOUSEHWHEEL, delta: -60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gCharWidth / -2; },
+ },
+ },
+];
+
+var gScrollMessageTests = [
+ // Widget should dispatch neither line scroll event nor pixel scroll event if
+ // the WM_*SCROLL's lParam is NULL and mouse wheel emulation is disabled.
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is NULL, emulation disabled",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels: 0,
+ },
+ init() {
+ SpecialPowers.setIntPref(kVAmountPref, 3);
+ SpecialPowers.setIntPref(kHAmountPref, 3);
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, false);
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_LINEUP, lParam is NULL, emulation disabled",
+ message: WM_VSCROLL, delta: SB_LINEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels: 0,
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is NULL, emulation disabled",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels: 0,
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINELEFT, lParam is NULL, emulation disabled",
+ message: WM_HSCROLL, delta: SB_LINELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels: 0,
+ },
+ },
+
+ // Widget should emulate mouse wheel behavior for WM_*SCROLL even if the
+ // kEmulateWheelByWMSCROLLPref is disabled but the message's lParam is not
+ // NULL. Then, widget doesn't dispatch a pixel event for WM_*SCROLL messages,
+ // but ESM dispatches it instead.
+ { description: "WM_VSCROLL, SB_LINEUP, lParam is not NULL, emulation disabled",
+ message: WM_VSCROLL, delta: SB_LINEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: -1, pixels() { return -gLineHeight; },
+ },
+ init() {
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, false);
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINELEFT, lParam is not NULL, emulation disabled",
+ message: WM_HSCROLL, delta: SB_LINELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: -1, pixels() { return -gCharWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_PAGEUP, lParam is not NULL, emulation disabled",
+ message: WM_VSCROLL, delta: SB_PAGEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels() { return -gPageHeight; },
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_PAGEDOWN, lParam is not NULL, emulation disabled",
+ message: WM_VSCROLL, delta: SB_PAGEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels() { return gPageHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_PAGELEFT, lParam is not NULL, emulation disabled",
+ message: WM_HSCROLL, delta: SB_PAGELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels() { return -gPageWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_PAGERIGHT, lParam is not NULL, emulation disabled",
+ message: WM_HSCROLL, delta: SB_PAGERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels() { return gPageWidth; },
+ },
+ },
+
+ // Widget should emulate mouse wheel behavior for WM_*SCROLL when the
+ // kEmulateWheelByWMSCROLLPref is enabled even if the message's lParam is
+ // NULL. Then, widget doesn't dispatch a pixel event for WM_*SCROLL messages,
+ // but ESM dispatches it instead.
+ { description: "WM_VSCROLL, SB_LINEUP, lParam is NULL, emulation enabled",
+ message: WM_VSCROLL, delta: SB_LINEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -1, pixels() { return -gLineHeight; },
+ },
+ init() {
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, true);
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is NULL, emulation enabled",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINELEFT, lParam is NULL, emulation enabled",
+ message: WM_HSCROLL, delta: SB_LINELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -1, pixels() { return -gCharWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is NULL, emulation enabled",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_PAGEUP, lParam is NULL, emulation enabled",
+ message: WM_VSCROLL, delta: SB_PAGEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels() { return -gPageHeight; },
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_PAGEDOWN, lParam is NULL, emulation enabled",
+ message: WM_VSCROLL, delta: SB_PAGEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels() { return gPageHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_PAGELEFT, lParam is NULL, emulation enabled",
+ message: WM_HSCROLL, delta: SB_PAGELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels() { return -gPageWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_PAGERIGHT, lParam is NULL, emulation enabled",
+ message: WM_HSCROLL, delta: SB_PAGERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels() { return gPageWidth; },
+ },
+ },
+
+ // Modifier key tests for WM_*SCROLL
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, with left Shift",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ init() {
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, false);
+ },
+ },
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, with right Shift",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_R,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, with left Ctrl",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, with right Ctrl",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, with left Alt",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, with right Alt",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_R,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, with left Shift",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, with right Shift",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_R,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, with left Ctrl",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, with right Ctrl",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, with left Alt",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, with right Alt",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_R,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+
+ finish() {
+ runDeactiveWindowTests();
+ },
+ },
+];
+
+var gDeactiveWindowTests = [
+ // Typically, mouse drivers send wheel messages to focused window.
+ // However, we prefer to scroll a scrollable element under the mouse cursor.
+ { description: "WM_MOUSEWHEEL, -120, 3 lines, window is deactive",
+ message: WM_MOUSEWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 3, pixels() { return gLineHeight * 3; },
+ },
+ init() {
+ SpecialPowers.setIntPref(kVAmountPref, 3);
+ SpecialPowers.setIntPref(kHAmountPref, 3);
+ },
+ onLineScrollEvent(aEvent) {
+ var fm = Services.focus;
+ is(fm.activeWindow, gOtherWindow, "The other window isn't activated");
+ },
+ },
+
+ { description: "WM_MOUSEWHEEL, 120, -3 lines, window is deactive",
+ message: WM_MOUSEWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -3, pixels() { return gLineHeight * -3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 120, 3 chars, window is deactive",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 3, pixels() { return gCharWidth * 3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, -120, -3 chars, window is deactive",
+ message: WM_MOUSEHWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -3, pixels() { return gCharWidth * -3; },
+ },
+ },
+
+ // Of course, even if some drivers prefer the cursor position, we don't need
+ // to change anything.
+ { description: "WM_MOUSEWHEEL, -120, 3 lines, window is deactive (receive the message directly)",
+ message: WM_MOUSEWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: 3, pixels() { return gLineHeight * 3; },
+ },
+ },
+
+ { description: "WM_MOUSEWHEEL, 120, -3 lines, window is deactive (receive the message directly)",
+ message: WM_MOUSEWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: -3, pixels() { return gLineHeight * -3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 120, 3 chars, window is deactive (receive the message directly)",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: 3, pixels() { return gCharWidth * 3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, -120, -3 chars, window is deactive (receive the message directly)",
+ message: WM_MOUSEHWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: -3, pixels() { return gCharWidth * -3; },
+ },
+ },
+
+ // Same for WM_*SCROLL if lParam is not NULL
+ { description: "WM_VSCROLL, SB_LINEUP, lParam is not NULL, emulation disabled, window is deactive",
+ message: WM_VSCROLL, delta: SB_LINEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: -1, pixels() { return -gLineHeight; },
+ },
+ init() {
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, false);
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, window is deactive",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINELEFT, lParam is not NULL, emulation disabled, window is deactive",
+ message: WM_HSCROLL, delta: SB_LINELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: -1, pixels() { return -gCharWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, window is deactive",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+
+ // Same for WM_*SCROLL if lParam is NULL but emulation is enabled
+ { description: "WM_VSCROLL, SB_LINEUP, lParam is NULL, emulation enabled, window is deactive",
+ message: WM_VSCROLL, delta: SB_LINEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -1, pixels() { return -gLineHeight; },
+ },
+ init() {
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, true);
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is NULL, emulation enabled, window is deactive",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINELEFT, lParam is NULL, emulation enabled, window is deactive",
+ message: WM_HSCROLL, delta: SB_LINELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -1, pixels() { return -gCharWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is NULL, emulation enabled, window is deactive",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+
+ // Same for WM_*SCROLL if lParam is not NULL and message sent to the deactive window directly
+ { description: "WM_VSCROLL, SB_LINEUP, lParam is not NULL, emulation disabled, window is deactive (receive the message directly)",
+ message: WM_VSCROLL, delta: SB_LINEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL |
+ nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: -1, pixels() { return -gLineHeight; },
+ },
+ init() {
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, false);
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, window is deactive (receive the message directly)",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL |
+ nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINELEFT, lParam is not NULL, emulation disabled, window is deactive (receive the message directly)",
+ message: WM_HSCROLL, delta: SB_LINELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL |
+ nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: -1, pixels() { return -gCharWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, window is deactive (receive the message directly)",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL |
+ nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+
+ // Same for WM_*SCROLL if lParam is NULL but emulation is enabled, and message sent to the deactive window directly
+ { description: "WM_VSCROLL, SB_LINEUP, lParam is NULL, emulation enabled, window is deactive (receive the message directly)",
+ message: WM_VSCROLL, delta: SB_LINEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: -1, pixels() { return -gLineHeight; },
+ },
+ init() {
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, true);
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is NULL, emulation enabled, window is deactive (receive the message directly)",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINELEFT, lParam is NULL, emulation enabled, window is deactive (receive the message directly)",
+ message: WM_HSCROLL, delta: SB_LINELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: -1, pixels() { return -gCharWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is NULL, emulation enabled, window is deactive (receive the message directly)",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+
+ finish() {
+ gOtherWindow.close();
+ gOtherWindow = null;
+ window.close();
+ },
+ },
+];
+
+function runDeactiveWindowTests() {
+ gOtherWindow = window.open("window_mouse_scroll_win_2.html", "_blank",
+ "chrome,width=100,height=100,top=700,left=700");
+
+ window.arguments[0].SimpleTest.waitForFocus(function() {
+ runNextTest(gDeactiveWindowTests, 0);
+ }, gOtherWindow);
+}
+
+function runNextTest(aTests, aIndex) {
+ if (aIndex > 0 && aTests[aIndex - 1] && aTests[aIndex - 1].finish) {
+ aTests[aIndex - 1].finish();
+ }
+
+ if (aTests.length == aIndex) {
+ return;
+ }
+
+ var test = aTests[aIndex++];
+ if (test.init) {
+ test.init();
+ }
+ test.handled = { lines: false, pixels: false };
+
+ switch (test.message) {
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL:
+ case WM_VSCROLL:
+ case WM_HSCROLL:
+ var expectedLines = test.expected.lines;
+ var expectedPixels =
+ cut((typeof test.expected.pixels == "function") ?
+ test.expected.pixels() : test.expected.pixels);
+ var handler = function(aEvent) {
+ var doCommonTests = true;
+
+ if (!aEvent) {
+ ok(!test.handled.lines,
+ test.description + ", line scroll event has been handled");
+ ok(!test.handled.pixels,
+ test.description + ", pixel scroll event has been handled");
+ doCommonTests = false;
+ } else if (aEvent.type == kMouseLineScrollEvent) {
+ ok(!test.handled.lines,
+ test.description + ":(" + aEvent.type + "), same event has already been handled");
+ test.handled.lines = true;
+ isnot(expectedLines, 0,
+ test.description + ":(" + aEvent.type + "), event shouldn't be fired");
+ if (test.onLineScrollEvent && test.onLineScrollEvent(aEvent)) {
+ doCommonTests = false;
+ }
+ } else if (aEvent.type == kMousePixelScrollEvent) {
+ ok(!test.handled.pixels,
+ test.description + ":(" + aEvent.type + "), same event has already been handled");
+ test.handled.pixels = true;
+ isnot(expectedPixels, 0,
+ test.description + ":(" + aEvent.type + "), event shouldn't be fired");
+ if (test.onPixelScrollEvent && test.onPixelScrollEvent(aEvent)) {
+ doCommonTests = false;
+ }
+ }
+
+ if (doCommonTests) {
+ var expectedDelta =
+ (aEvent.type == kMouseLineScrollEvent) ?
+ expectedLines : expectedPixels;
+ is(aEvent.target.id, test.target.id,
+ test.description + ":(" + aEvent.type + "), ID mismatch");
+ is(aEvent.axis, test.expected.axis,
+ test.description + ":(" + aEvent.type + "), axis mismatch");
+ ok(aEvent.detail != 0,
+ test.description + ":(" + aEvent.type + "), delta must not be 0");
+ is(aEvent.detail, expectedDelta,
+ test.description + ":(" + aEvent.type + "), delta mismatch");
+ is(aEvent.shiftKey, (test.modifiers & (SHIFT_L | SHIFT_R)) != 0,
+ test.description + ":(" + aEvent.type + "), shiftKey mismatch");
+ is(aEvent.ctrlKey, (test.modifiers & (CTRL_L | CTRL_R)) != 0,
+ test.description + ":(" + aEvent.type + "), ctrlKey mismatch");
+ is(aEvent.altKey, (test.modifiers & (ALT_L | ALT_R)) != 0,
+ test.description + ":(" + aEvent.type + "), altKey mismatch");
+ }
+
+ if (!aEvent || (test.handled.lines || expectedLines == 0) &&
+ (test.handled.pixels || expectedPixels == 0)) {
+ // Don't scroll actually.
+ if (aEvent) {
+ aEvent.preventDefault();
+ }
+ test.target.removeEventListener(kMouseLineScrollEvent, handler, true);
+ test.target.removeEventListener(kMousePixelScrollEvent, handler, true);
+ setTimeout(runNextTest, 0, aTests, aIndex);
+ }
+ };
+
+ test.target.addEventListener(kMouseLineScrollEvent, handler, true);
+ test.target.addEventListener(kMousePixelScrollEvent, handler, true);
+
+ if (expectedLines == 0 && expectedPixels == 0) {
+ // The timeout might not be enough if system is slow by other process,
+ // so, the test might be passed unexpectedly. However, it must be able
+ // to be detected by random orange.
+ setTimeout(handler, 500);
+ }
+
+ var utils = getWindowUtils(test.window);
+ var ptInScreen = getPointInScreen(test.target, test.window);
+ var isVertical =
+ ((test.message == WM_MOUSEWHEEL) || (test.message == WM_VSCROLL));
+ var deltaX = !isVertical ? test.delta : 0;
+ var deltaY = isVertical ? test.delta : 0;
+ utils.sendNativeMouseScrollEvent(ptInScreen.x + test.x,
+ ptInScreen.y + test.y,
+ test.message, deltaX, deltaY, 0,
+ test.modifiers,
+ test.additionalFlags,
+ test.target);
+ break;
+ default:
+ ok(false, test.description + ": invalid message");
+ // Let's timeout.
+ }
+}
+
+function prepareTests() {
+ // Disable special action with modifier key
+ SpecialPowers.setIntPref(kAltKeyActionPref, 1);
+ SpecialPowers.setIntPref(kCtrlKeyActionPref, 1);
+ SpecialPowers.setIntPref(kShiftKeyActionPref, 1);
+ SpecialPowers.setIntPref(kWinKeyActionPref, 1);
+
+ SpecialPowers.setIntPref(kAltKeyDeltaMultiplierXPref, 100);
+ SpecialPowers.setIntPref(kAltKeyDeltaMultiplierYPref, 100);
+ SpecialPowers.setIntPref(kCtrlKeyDeltaMultiplierXPref, 100);
+ SpecialPowers.setIntPref(kCtrlKeyDeltaMultiplierYPref, 100);
+ SpecialPowers.setIntPref(kShiftKeyDeltaMultiplierXPref, 100);
+ SpecialPowers.setIntPref(kShiftKeyDeltaMultiplierYPref, 100);
+ SpecialPowers.setIntPref(kWinKeyDeltaMultiplierXPref, 100);
+ SpecialPowers.setIntPref(kWinKeyDeltaMultiplierYPref, 100);
+
+ SpecialPowers.setBoolPref(kSystemScrollSpeedOverridePref, false);
+ SpecialPowers.setIntPref(kTimeoutPref, -1);
+
+ runNextTest(gPreparingSteps, 0);
+}
+
+</script>
+</body>
+
+</html>
diff --git a/widget/tests/window_mouse_scroll_win_2.html b/widget/tests/window_mouse_scroll_win_2.html
new file mode 100644
index 0000000000..c8d3762405
--- /dev/null
+++ b/widget/tests/window_mouse_scroll_win_2.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ Helper file for window_mouse_scroll_win.html
+</body>
+</html>
diff --git a/widget/tests/window_picker_no_crash_child.html b/widget/tests/window_picker_no_crash_child.html
new file mode 100644
index 0000000000..51bf1b1e63
--- /dev/null
+++ b/widget/tests/window_picker_no_crash_child.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+ <title>Picker window</title>
+</head>
+<body>
+<form name="form1">
+<input type="file" name="uploadbox">
+</form>
+</body>
+</html>
diff --git a/widget/tests/window_state_windows.xhtml b/widget/tests/window_state_windows.xhtml
new file mode 100644
index 0000000000..f9a70eb004
--- /dev/null
+++ b/widget/tests/window_state_windows.xhtml
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window id="NativeWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="300"
+ height="300"
+ onload="onLoad();"
+ title="Window State Tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript">
+ <![CDATA[
+
+ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ SimpleTest.waitForExplicitFinish();
+
+ function onLoad() {
+ var win = Services.wm.getMostRecentWindow("navigator:browser");
+
+ /*
+ switch(win.windowState) {
+ case win.STATE_FULLSCREEN:
+ dump("STATE_FULLSCREEN \n");
+ break;
+ case win.STATE_MAXIMIZED:
+ dump("STATE_MAXIMIZED \n");
+ break;
+ case win.STATE_MINIMIZED:
+ dump("STATE_MINIMIZED \n");
+ break;
+ case win.STATE_NORMAL:
+ dump("STATE_NORMAL \n");
+ break;
+ }
+ */
+
+ // Make sure size mode changes are reflected in the widget.
+ win.restore();
+ ok(win.windowState == win.STATE_NORMAL, "window state is restored.");
+ win.minimize();
+ ok(win.windowState == win.STATE_MINIMIZED, "window state is minimized.");
+
+ // Windows resizes children to 0x0. Code in nsWindow filters these changes out. Without
+ // this all sorts of screwy things can happen in child widgets.
+ ok(document.documentElement.clientHeight > 0, "document height should not be zero for a minimized window!");
+ ok(document.documentElement.clientWidth > 0, "document width should not be zero for a minimized window!");
+
+ // Make sure size mode changes are reflected in the widget.
+ win.restore();
+ ok(win.windowState == win.STATE_NORMAL, "window state is restored.");
+ win.maximize();
+ ok(win.windowState == win.STATE_MAXIMIZED, "window state is maximized.");
+ win.restore();
+ ok(win.windowState == win.STATE_NORMAL, "window state is restored.");
+
+ /*
+ dump(win.screenX + "\n");
+ win.minimize();
+ dump(win.screenX + "\n");
+ win.restore();
+ dump(win.screenX + "\n");
+ */
+
+ SimpleTest.finish();
+ }
+
+ ]]>
+ </script>
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ </body>
+</window>
diff --git a/widget/tests/window_wheeltransaction.xhtml b/widget/tests/window_wheeltransaction.xhtml
new file mode 100644
index 0000000000..f3c081b105
--- /dev/null
+++ b/widget/tests/window_wheeltransaction.xhtml
@@ -0,0 +1,1569 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Wheel scroll tests"
+ width="600" height="600"
+ onload="onload();"
+ onunload="onunload();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<style type="text/css">
+ #rootview {
+ overflow: auto;
+ width: 400px;
+ height: 400px;
+ border: 1px solid;
+ }
+ #container {
+ overflow: auto;
+ width: 600px;
+ height: 600px;
+ }
+ #rootview pre {
+ margin: 20px 0 20px 20px;
+ padding: 0;
+ overflow: auto;
+ display: block;
+ width: 100px;
+ height: 100.5px;
+ font-size: 16px;
+ }
+</style>
+<div id="rootview" onscroll="onScrollView(event);">
+ <div id="container">
+ <pre id="subview1" onscroll="onScrollView(event);">
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+ </pre>
+ <pre id="subview2" onscroll="onScrollView(event);">
+Text.
+Text.
+Text.
+Text.
+Text.
+Text.
+Text.
+Text.
+Text.
+Text.
+ </pre>
+ <pre id="subview3" onscroll="onScrollView(event);">
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+ </pre>
+ </div>
+</div>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+function ok(aCondition, aMessage)
+{
+ window.arguments[0].SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ window.arguments[0].SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage)
+{
+ window.arguments[0].SimpleTest.isnot(aLeft, aRight, aMessage);
+}
+
+var gCurrentTestListStatus = { nextListIndex: 0 };
+var gCurrentTest;
+
+const kListenEvent_None = 0;
+const kListenEvent_OnScroll = 1;
+const kListenEvent_OnScrollFailed = 2;
+const kListenEvent_OnTransactionTimeout = 4;
+const kListenEvent_All = kListenEvent_OnScroll |
+ kListenEvent_OnScrollFailed |
+ kListenEvent_OnTransactionTimeout;
+var gLitesnEvents = kListenEvent_None;
+
+/**
+ * At unexpected transaction timeout, we need to stop *all* timers. But it is
+ * difficult and it can be create more complex testing code. So, we should use
+ * only one timer at one time. For that, we must store the timer id to this
+ * variable. And the functions which may be called via a timer must clear the
+ * current timer by |_clearTimer| function.
+ */
+var gTimer;
+
+var gPrefSvc = SpecialPowers.Services.prefs;
+const kPrefSmoothScroll = "general.smoothScroll";
+const kPrefNameTimeout = "mousewheel.transaction.timeout";
+const kPrefNameIgnoreMoveDelay = "mousewheel.transaction.ignoremovedelay";
+const kPrefTestEventsAsyncEnabled = "test.events.async.enabled";
+
+const kDefaultTimeout = gPrefSvc.getIntPref(kPrefNameTimeout);
+const kDefaultIgnoreMoveDelay = gPrefSvc.getIntPref(kPrefNameIgnoreMoveDelay);
+
+gPrefSvc.setBoolPref(kPrefSmoothScroll, false);
+gPrefSvc.setBoolPref(kPrefTestEventsAsyncEnabled, true);
+
+var gTimeout, gIgnoreMoveDelay;
+var gEnoughForTimeout, gEnoughForIgnoreMoveDelay;
+
+function setTimeoutPrefs(aTimeout, aIgnoreMoveDelay)
+{
+ gPrefSvc.setIntPref(kPrefNameTimeout, aTimeout);
+ gPrefSvc.setIntPref(kPrefNameIgnoreMoveDelay, aIgnoreMoveDelay);
+ gTimeout = aTimeout;
+ gIgnoreMoveDelay = aIgnoreMoveDelay;
+ gEnoughForTimeout = gTimeout * 2;
+ gEnoughForIgnoreMoveDelay = gIgnoreMoveDelay * 1.2;
+}
+
+function resetTimeoutPrefs()
+{
+ if (gTimeout == kDefaultTimeout)
+ return;
+ setTimeoutPrefs(kDefaultTimeout, kDefaultIgnoreMoveDelay);
+ initTestList();
+}
+
+function growUpTimeoutPrefs()
+{
+ if (gTimeout != kDefaultTimeout)
+ return;
+ setTimeoutPrefs(5000, 1000);
+ initTestList();
+}
+
+// setting enough time for testing.
+gPrefSvc.setIntPref(kPrefNameTimeout, gTimeout);
+gPrefSvc.setIntPref(kPrefNameIgnoreMoveDelay, gIgnoreMoveDelay);
+
+var gRootView = document.getElementById("rootview");
+var gSubView1 = document.getElementById("subview1");
+var gSubView2 = document.getElementById("subview2");
+var gSubView3 = document.getElementById("subview3");
+
+gRootView.addEventListener("MozMouseScrollFailed", onMouseScrollFailed);
+gRootView.addEventListener("MozMouseScrollTransactionTimeout",
+ onTransactionTimeout);
+
+function finish()
+{
+ window.close();
+}
+
+async function onload()
+{
+ // Before actually running tests, we disable auto-dir scrolling, becasue the
+ // tests in this file are meant to test scrolling transactions, not meant to
+ // test default actions for wheel events, so we simply disabled auto-dir
+ // scrolling, which are well tested in
+ // dom/events/test/window_wheel_default_action.html.
+ await SpecialPowers.pushPrefEnv({"set": [["mousewheel.autodir.enabled",
+ false]]});
+
+ runNextTestList();
+}
+
+function onunload()
+{
+ resetTimeoutPrefs();
+ gPrefSvc.clearUserPref(kPrefSmoothScroll);
+ gPrefSvc.clearUserPref(kPrefTestEventsAsyncEnabled);
+ disableNonTestMouseEvents(false);
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+ window.arguments[0].SimpleTest.finish();
+}
+
+function offsetForRootView()
+{
+ let rootViewRect = gRootView.getBoundingClientRect();
+ let subView1Rect = gSubView1.getBoundingClientRect();
+ return {
+ x: (subView1Rect.left - rootViewRect.left) / 2,
+ y: (subView1Rect.top - rootViewRect.top) / 2,
+ }
+}
+
+function _offsetFor(aSubView)
+{
+ let rootViewRect = gRootView.getBoundingClientRect();
+ let subViewRect = aSubView.getBoundingClientRect();
+ return {
+ x: subViewRect.left - rootViewRect.left + subViewRect.width / 2,
+ y: subViewRect.top - rootViewRect.top + subViewRect.height / 2,
+ }
+}
+
+function offsetForSubView1()
+{
+ return _offsetFor(gSubView1);
+}
+
+function offsetForSubView2()
+{
+ return _offsetFor(gSubView2);
+}
+
+function offsetForSubView3()
+{
+ return _offsetFor(gSubView3);
+}
+
+/**
+ * Define the tests here:
+ * Scrolls are processed async always. Therefore, we need to call all tests
+ * by timer. gTestLists is array of testing lists. In other words, an item
+ * of gTestList is a group of one or more testing. Each items has following
+ * properties:
+ *
+ * - retryWhenTransactionTimeout
+ * The testing of wheel transaction might be fialed randomly by
+ * timeout. Then, automatically the failed test list will be retested
+ * automatically only this number of times.
+ *
+ * - steps
+ * This property is array of testing. Each steps must have following
+ * properties at least.
+ *
+ * - func
+ * This property means function which will be called via
+ * |setTimeout|. The function cannot have params. If you need
+ * some additional parameters, you can specify some original
+ * properties for the test function. If you do so, you should
+ * document it in the testing function.
+ * - delay
+ * This property means delay time until the function to be called.
+ * I.e., the value used for the second param of |setTimeout|.
+ *
+ * And also you need one more property when you call a testing function.
+ *
+ * - description
+ * This property is description of the test. This is used for
+ * logging.
+ *
+ * At testing, you can access to current step via |gCurrentTest|.
+ */
+
+var gTestLists;
+function initTestList()
+{
+ gTestLists = [
+ /**************************************************************************
+ * Continuous scrolling test for |gRootView|
+ * |gRootView| has both scrollbars and it has three children which are
+ * |gSubView1|, |gSubView2| and |gSubView3|. They have scrollbars. If
+ * the current transaction targets |gRootView|, other children should not
+ * be scrolled even if the wheel events are fired on them.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Vertical wheel events should scroll |gRootView| even if the position
+ // of wheel events in a child view which has scrollbar.
+ { func: testContinuousScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Continuous scrolling test for root view (vertical/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ description: "Continuous scrolling test for root view (vertical/backward)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Horizontal wheel events should scroll |gRootView| even if the
+ // position of wheel events in a child view which has scrollbar.
+ { func: testContinuousScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Continuous scrolling test for root view (horizontal/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ description: "Continuous scrolling test for root view (horizontal/backward)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Continuous scrolling test for |gSubView1|
+ * |gSubView1| has both scrollbars.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Vertical wheel events should scroll |gSubView1|.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ description: "Continuous scrolling test for sub view 1 (vertical/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: true, expectedView: gSubView1,
+ description: "Continuous scrolling test for sub view 1 (vertical/backward)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Horitontal wheel events should scroll |gSubView1|.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ description: "Continuous scrolling test for sub view 1 (horizontal/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: false, expectedView: gSubView1,
+ description: "Continuous scrolling test for sub view 1 (horizontal/backward)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Continuous scrolling test for |gSubView2|
+ * |gSubView2| has only vertical scrollbar.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Vertical wheel events should scroll |gSubView2|.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView2,
+ isForward: true, isVertical: true, expectedView: gSubView2,
+ description: "Continuous scrolling test for sub view 2 (vertical/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView2,
+ isForward: false, isVertical: true, expectedView: gSubView2,
+ description: "Continuous scrolling test for sub view 2 (vertical/backward)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Horizontal wheel events should scroll its nearest scrollable ancestor
+ // view, i.e., it is |gRootView|.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView2,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Continuous scrolling test for sub view 2 (horizontal/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView2,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ description: "Continuous scrolling test for sub view 2 (horizontal/backward)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Continuous scrolling test for |gSubView3|
+ * |gSubView3| has only horizontal scrollbar.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Vertical wheel events should scroll its nearest scrollable ancestor
+ // view, i.e., it is |gRootView|.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView3,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Continuous scrolling test for sub view 3 (vertical/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView3,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ description: "Continuous scrolling test for sub view 3 (vertical/backward)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Horitontal wheel events should scroll |gSubView3|.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView3,
+ isForward: true, isVertical: false, expectedView: gSubView3,
+ description: "Continuous scrolling test for sub view 3 (horizontal/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView3,
+ isForward: false, isVertical: false, expectedView: gSubView3,
+ description: "Continuous scrolling test for sub view 3 (horizontal/backward)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Don't reset transaction by a different direction wheel event
+ * Even if a wheel event doesn't same direction as last wheel event, the
+ * current transaction should not be reset.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical -> Horizontal
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView| by a vertical wheel
+ // event.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Don't reset transaction by a different direction wheel event (1-1)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ description: "Don't reset transaction by a different direction wheel event (1-2)" },
+ // Send a horizontal wheel event over |gSubView1| but |gRootView| should
+ // be scrolled.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Don't reset transaction by a different direction wheel event (1-3)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal -> Vertical
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView| by a horizontal wheel
+ // event.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Don't reset transaction by a different direction wheel event (2-1)" },
+ // Scroll back to left-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ description: "Don't reset transaction by a different direction wheel event (2-2)" },
+ // Send a vertical wheel event over |gSubView1| but |gRootView| should
+ // be scrolled.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Don't reset transaction by a different direction wheel event (2-3)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Don't reset transaction even if a wheel event cannot scroll
+ * Even if a wheel event cannot scroll to specified direction in the
+ * current target view, the transaction should not be reset. E.g., there
+ * are some devices which can scroll obliquely. If so, probably, users
+ * cannot input only intended direction.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // A view only has vertical scrollbar case.
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gSubView2|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView2,
+ isForward: true, isVertical: true, expectedView: gSubView2,
+ description: "Don't reset transaction even if a wheel event cannot scroll (1-1)" },
+ // |gSubView2| doesn't have horizontal scrollbar but should not scroll
+ // any views.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView2,
+ isForward: true, isVertical: false, expectedView: null,
+ description: "Don't reset transaction even if a wheel event cannot scroll (1-2)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // A view only has horizontal scrollbar case.
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gSubView3|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView3,
+ isForward: true, isVertical: false, expectedView: gSubView3,
+ description: "Don't reset transaction even if a wheel event cannot scroll (2-1)" },
+ // |gSubView3| doesn't have vertical scrollbar but should not scroll any
+ // views.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView3,
+ isForward: true, isVertical: true, expectedView: null,
+ description: "Don't reset transaction even if a wheel event cannot scroll (2-2)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Reset transaction by mouse down/mouse up events
+ * Mouse down and mouse up events should cause resetting the current
+ * transaction.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by mouse down/mouse up events (v-1)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by mouse down/mouse up events (v-2)" },
+ // Send mouse button events which should reset the current transaction.
+ // So, the next wheel event should scroll |gSubView1|.
+ { func: sendMouseButtonEvents, delay: 0,
+ description: "sendMouseButtonEvents" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ description: "Reset transaction by mouse down/mouse up events (v-3)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Reset transaction by mouse down/mouse up events (h-1)" },
+ // Scroll back to left-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ description: "Reset transaction by mouse down/mouse up events (h-2)" },
+ // Send mouse button events which should reset the current transaction.
+ // So, the next wheel event should scroll |gSubView1|.
+ { func: sendMouseButtonEvents, delay: 0,
+ description: "sendMouseButtonEvents" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ description: "Reset transaction by mouse down/mouse up events (h-3)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Reset transaction by a key event
+ * A key event should cause resetting the current transaction.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by a key event (v-1)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by a key event (v-2)" },
+ // Send a key event which should reset the current transaction. So, the
+ // next wheel event should scroll |gSubView1|.
+ { func: sendKeyEvents, delay: 0, key: "a",
+ description: "sendKeyEvents" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ description: "Reset transaction by a key event (v-3)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Reset transaction by a key event (h-1)" },
+ // Scroll back to left-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ description: "Reset transaction by a key event (h-2)" },
+ // Send a key event which should reset the current transaction. So, the
+ // next wheel event should scroll |gSubView1|.
+ { func: sendKeyEvents, delay: 0, key: "a",
+ description: "sendKeyEvents" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ description: "Reset transaction by a key event (h-3)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Reset transaction by a mouse move event
+ * A mouse move event can cause reseting the current transaction even if
+ * mouse cursor is inside the target view of current transaction. Only
+ * when a wheel event is fired after |gIgnoreMoveDelay| milliseconds since
+ * the first mouse move event from last wheel event, the transaction
+ * should be reset.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by a mouse move event (v-1)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by a mouse move event (v-2)" },
+ // Send a mouse move event immediately after last wheel event, then,
+ // current transaction should be kept.
+ { func: sendMouseMoveEvent, delay: 0, offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (v-3)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (v-4)" },
+ // Send a mouse move event after |gIgnoreMoveDelay| milliseconds since
+ // last wheel event, then, current transaction should be kept.
+ { func: sendMouseMoveEvent, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (v-5)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (v-6)" },
+ // Send a wheel event after |gIgnoreMoveDelay| milliseconds since last
+ // mouse move event but it is fired immediately after the last wheel
+ // event, then, current transaction should be kept.
+ { func: sendMouseMoveEvent, delay: 0, offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (v-7)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (v-8)" },
+ // Send a wheel event after |gIgnoreMoveDelay| milliseconds have passed
+ // since last mouse move event which is fired after |gIgnoreMoveDelay|
+ // milliseconds since last wheel event, then, current transaction should
+ // be reset.
+ { func: sendMouseMoveEvent, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ canFailRandomly: { possibleView: gRootView },
+ description: "Reset transaction by a mouse move event (v-9)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-1)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-2)" },
+ // Send a mouse move event immediately after last wheel event, then,
+ // current transaction should be kept.
+ { func: sendMouseMoveEvent, delay: 0, offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-3)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-4)" },
+ // Send a mouse move event after |gIgnoreMoveDelay| milliseconds since
+ // last wheel event, then, current transaction should be kept.
+ { func: sendMouseMoveEvent, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-5)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-6)" },
+ // Send a wheel event after |gIgnoreMoveDelay| milliseconds since last
+ // mouse move event but it is fired immediately after the last wheel
+ // event, then, current transaction should be kept.
+ { func: sendMouseMoveEvent, delay: 0, offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-7)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-8)" },
+ // Send a wheel event after |gIgnoreMoveDelay| milliseconds have passed
+ // since last mouse move event which is fired after |gIgnoreMoveDelay|
+ // milliseconds since last wheel event, then, current transaction should
+ // be reset.
+ { func: sendMouseMoveEvent, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ canFailRandomly: { possibleView: gRootView },
+ description: "Reset transaction by a mouse move event (h-9)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Reset transaction by a mouse move event on outside of view
+ * When mouse cursor is moved to outside of the current target view, the
+ * transaction should be reset immediately.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gSubView1|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ description: "Reset transaction by a mouse move event on outside of view (v-1)" },
+ // Send mouse move event over |gRootView|.
+ { func: sendMouseMoveEvent, delay: 0, offset: offsetForRootView,
+ description: "sendMouseMoveEvent" },
+ // Send Wheel event over |gRootView| which should be scrolled.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by a mouse move event on outside of view (v-2)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gSubView1|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ description: "Reset transaction by a mouse move event on outside of view (h-1)" },
+ // Send mouse move event over |gRootView|.
+ { func: sendMouseMoveEvent, delay: 0, offset: offsetForRootView,
+ description: "sendMouseMoveEvent" },
+ // Send Wheel event over |gRootView| which should be scrolled.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by a mouse move event on outside of view (h-2)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Timeout test
+ * A view should not be scrolled during another to be transaction for
+ * another view scrolling. However, a wheel event which is sent after
+ * timeout, a view which is under the mouse cursor should be scrolled.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // First, create a transaction which should target the |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Timeout test (v-1)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ description: "Timeout test (v-2)" },
+ // A wheel event over |gSubView1| should not scroll it during current
+ // transaction.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Timeout test (v-3)" },
+ // Scroll back to top-most again.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Timeout test (v-4)" },
+ // A wheel event over |gSubView1| after timeout should scroll
+ // |gSubView1|.
+ { func: testOneTimeScroll, delay: gEnoughForTimeout,
+ offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ isTimeoutTesting: true,
+ description: "Timeout test (v-5)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // First, create a transaction which should target the |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Timeout test (h-1)" },
+ // Scroll back to left-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ description: "Timeout test (h-2)" },
+ // A wheel event over |gSubView1| should not scroll it during current
+ // transaction.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Timeout test (h-3)" },
+ // Scroll back to left-most again.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Timeout test (h-4)" },
+ // A wheel event over |gSubView1| after timeout should scroll
+ // |gSubView1|.
+ { func: testOneTimeScroll, delay: gEnoughForTimeout,
+ offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ isTimeoutTesting: true,
+ description: "Timeout test (h-5)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Timeout test even with many wheel events
+ * This tests whether timeout is occurred event if wheel events are sent.
+ * The transaction should not be updated by non-scrollable wheel events.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Scroll |gSubView1| to bottom-most.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ description: "Timeout test even with many wheel events (v-1)" },
+ // Don't scroll any views before timeout.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: null,
+ canFailRandomly: { possibleView: gRootView },
+ description: "Timeout test even with many wheel events (v-2)" },
+ // Recreate a transaction which is scrolling |gRootView| after time out.
+ { func: testRestartScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Timeout test even with many wheel events (v-3)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Scroll |gSubView1| to right-most.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ description: "Timeout test even with many wheel events (h-1)" },
+ // Don't scroll any views before timeout.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: null,
+ canFailRandomly: { possibleView: gRootView },
+ description: "Timeout test even with many wheel events (h-2)" },
+ // Recreate a transaction which is scrolling |gRootView| after time out.
+ { func: testRestartScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Timeout test even with many wheel events (h-3)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Very large scrolling wheel event
+ * If the delta value is larger than the scrolling page size, it should be
+ * scrolled only one page instead of the delta value.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ delta: 5000,
+ description: "Very large delta scrolling (v-1)" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ delta: 5000,
+ description: "Very large delta scrolling (v-2)" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ delta: 5000,
+ description: "Very large delta scrolling (h-1)" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ delta: 5000,
+ description: "Very large delta scrolling (h-2)" }
+ ]
+ }
+ ];
+}
+
+/******************************************************************************
+ * Actions for preparing tests
+ ******************************************************************************/
+
+function initElements()
+{
+ _clearTimer();
+
+ function resetScrollPosition(aElement)
+ {
+ aElement.scrollTop = 0;
+ aElement.scrollLeft = 0;
+ }
+
+ const kDisplay = gCurrentTest.forVertical ? "block" : "inline-block";
+ gSubView1.style.display = kDisplay;
+ gSubView2.style.display = kDisplay;
+ gSubView3.style.display = kDisplay;
+
+ resetScrollPosition(gRootView);
+ resetScrollPosition(gSubView1);
+ resetScrollPosition(gSubView2);
+ resetScrollPosition(gSubView3);
+ _getDOMWindowUtils(window).advanceTimeAndRefresh(0);
+
+ runNextTestStep();
+}
+
+function clearWheelTransaction()
+{
+ _clearTimer();
+ _clearTransaction();
+ runNextTestStep();
+}
+
+function sendKeyEvents()
+{
+ _clearTimer();
+ synthesizeKey(gCurrentTest.key, {}, window);
+ runNextTestStep();
+}
+
+function sendMouseButtonEvents()
+{
+ _clearTimer();
+ synthesizeMouse(gRootView, -1, -1, { type:"mousedown" }, window);
+ synthesizeMouse(gRootView, -1, -1, { type:"mouseup" }, window);
+ runNextTestStep();
+}
+
+function sendMouseMoveEvent()
+{
+ _clearTimer();
+ _fireMouseMoveEvent(gCurrentTest.offset());
+ runNextTestStep();
+}
+
+/******************************************************************************
+ * Utilities for testing functions
+ ******************************************************************************/
+
+function _clearTransaction()
+{
+ synthesizeMouse(gRootView, -1, -1, { type:"mousedown" }, window);
+ synthesizeMouse(gRootView, -1, -1, { type:"mouseup" }, window);
+}
+
+function _saveScrollPositions()
+{
+ function save(aElement)
+ {
+ aElement.prevTop = aElement.scrollTop;
+ aElement.prevLeft = aElement.scrollLeft;
+ }
+ save(gRootView);
+ save(gSubView1);
+ save(gSubView2);
+ save(gSubView3);
+}
+
+function _fireMouseMoveEvent(aOffset)
+{
+ synthesizeMouse(gRootView, aOffset.x, aOffset.y, { type:"mousemove" }, window);
+}
+
+function _fireWheelScrollEvent(aOffset, aIsVertical, aForward, aDelta)
+{
+ var event = { deltaMode: WheelEvent.DOM_DELTA_LINE };
+ if (aIsVertical) {
+ event.deltaY = aForward ? aDelta : -aDelta;
+ } else {
+ event.deltaX = aForward ? aDelta : -aDelta;
+ }
+ sendWheelAndPaint(gRootView, aOffset.x, aOffset.y, event, null, window);
+}
+
+function _canScroll(aElement, aIsVertical, aForward)
+{
+ if (aIsVertical) {
+ if (!aForward)
+ return aElement.scrollTop > 0;
+ return aElement.scrollHeight > aElement.scrollTop + aElement.clientHeight;
+ }
+ if (!aForward)
+ return aElement.scrollLeft > 0;
+ return aElement.scrollWidth > aElement.scrollLeft + aElement.clientWidth;
+}
+
+const kNotScrolled = 0;
+const kScrolledToTop = 1;
+const kScrolledToBottom = 2;
+const kScrolledToLeft = 4;
+const kScrolledToRight = 8;
+
+const kScrolledVertical = kScrolledToTop | kScrolledToBottom;
+const kScrolledHorizontal = kScrolledToLeft | kScrolledToRight;
+
+function _getScrolledState(aElement)
+{
+ var ret = kNotScrolled;
+ if (aElement.scrollTop != aElement.prevTop) {
+ ret |= aElement.scrollTop < aElement.prevTop ? kScrolledToTop :
+ kScrolledToBottom;
+ }
+ if (aElement.scrollLeft != aElement.prevLeft) {
+ ret |= aElement.scrollLeft < aElement.prevLeft ? kScrolledToLeft :
+ kScrolledToRight;
+ }
+ return ret;
+}
+
+function _getExpectedScrolledState()
+{
+ // eslint-disable-next-line no-nested-ternary
+ return gCurrentTest.isVertical ?
+ gCurrentTest.isForward ? kScrolledToBottom : kScrolledToTop :
+ gCurrentTest.isForward ? kScrolledToRight : kScrolledToLeft;
+}
+
+function _getScrolledStateText(aScrolledState)
+{
+ if (aScrolledState == kNotScrolled)
+ return "Not scrolled";
+
+ var s = "scrolled to ";
+ if (aScrolledState & kScrolledVertical) {
+ s += aScrolledState & kScrolledToTop ? "backward" : "forward";
+ s += " (vertical)"
+ if (aScrolledState & kScrolledHorizontal)
+ s += " and to ";
+ }
+ if (aScrolledState & kScrolledHorizontal) {
+ s += aScrolledState & kScrolledToLeft ? "backward" : "forward";
+ s += " (horizontal)"
+ }
+ return s;
+}
+
+function _getCurrentTestList()
+{
+ return gTestLists[gCurrentTestListStatus.nextListIndex - 1];
+}
+
+function _clearTimer()
+{
+ clearTimeout(gTimer);
+ gTimer = 0;
+}
+
+/******************************************************************************
+ * Testing functions
+ ******************************************************************************/
+
+/**
+ * Note that testing functions must set following variables:
+ *
+ * gCurrentTest.repeatTest: See comment in |continueTest|.
+ * gCurrentTest.autoRepeatDelay: See comment in |continueTest|.
+ * gListenScrollEvent: When this is not true, the event handlers ignores the
+ * events.
+ */
+
+function testContinuousScroll()
+{
+ /**
+ * Testing continuous scrolling. This function synthesizes a wheel event. If
+ * the test was success, this function will be recalled automatically.
+ * And when a generating wheel event cannot scroll the expected view, this
+ * function fires the wheel event only one time.
+ *
+ * @param gCurrentTest.offset
+ * A function to compute the cursor position of firing wheel event.
+ * The values are offset from |gRootView|.
+ * @param gCurrentTest.isVertical
+ * Whether the wheel event is for virtical scrolling or horizontal.
+ * @param gCurrentTest.isForward
+ * Whether the wheel event is to forward or to backward.
+ * @param gCurrentTest.expectedView
+ * The expected view which will be scrolled by wheel event. This
+ * value must not be null.
+ */
+
+ _clearTimer();
+ _saveScrollPositions();
+ if (!gCurrentTest.expectedView) {
+ runNextTestStep();
+ return;
+ }
+
+ gLitesnEvents = kListenEvent_All;
+ gCurrentTest.repeatTest = true;
+ gCurrentTest.autoRepeatDelay = 0;
+
+ if (!_canScroll(gCurrentTest.expectedView,
+ gCurrentTest.isVertical, gCurrentTest.isForward)) {
+ gCurrentTest.expectedView = null;
+ }
+ var delta = gCurrentTest.delta ? gCurrentTest.delta : 4;
+ _fireWheelScrollEvent(gCurrentTest.offset(),
+ gCurrentTest.isVertical, gCurrentTest.isForward, delta);
+}
+
+function testOneTimeScroll()
+{
+ /**
+ * Testing one wheel event. |runNextTestStep| will be called immediately
+ * after this function by |onScrollView| or |onTimeout|.
+ *
+ * @param gCurrentTest.offset
+ * A function to compute the cursor position of firing wheel event.
+ * The values are offset from |gRootView|.
+ * @param gCurrentTest.isVertical
+ * Whether the wheel event is for virtical scrolling or horizontal.
+ * @param gCurrentTest.isForward
+ * Whether the wheel event is to forward or to backward.
+ * @param gCurrentTest.expectedView
+ * The expected view which will be scrolled by wheel event. This
+ * value can be null. It means any views should not be scrolled.
+ */
+
+ _clearTimer();
+ _saveScrollPositions();
+
+ gLitesnEvents = kListenEvent_All;
+ gCurrentTest.repeatTest = false;
+ gCurrentTest.autoRepeatDelay = 0;
+
+ var delta = gCurrentTest.delta ? gCurrentTest.delta : 4;
+ _fireWheelScrollEvent(gCurrentTest.offset(),
+ gCurrentTest.isVertical, gCurrentTest.isForward, delta);
+}
+
+function testRestartScroll()
+{
+ /**
+ * Testing restart to scroll in expected view after timeout from the current
+ * transaction. This function recall this itself until to success this test
+ * or timeout from this test.
+ *
+ * @param gCurrentTest.offset
+ * A function to compute the cursor position of firing wheel event.
+ * The values are offset from |gRootView|.
+ * @param gCurrentTest.isVertical
+ * Whether the wheel event is for virtical scrolling or horizontal.
+ * @param gCurrentTest.isForward
+ * Whether the wheel event is to forward or to backward.
+ * @param gCurrentTest.expectedView
+ * The expected view which will be scrolled by wheel event. This
+ * value must not be null.
+ */
+
+ _clearTimer();
+ _saveScrollPositions();
+
+ if (!gCurrentTest.wasTransactionTimeout) {
+ gCurrentTest.repeatTest = true;
+ gCurrentTest.autoRepeatDelay = gTimeout / 3;
+ gLitesnEvents = kListenEvent_All;
+ gCurrentTest.isTimeoutTesting = true;
+ if (gCurrentTest.expectedView) {
+ gCurrentTest.expectedViewAfterTimeout = gCurrentTest.expectedView;
+ gCurrentTest.expectedView = null;
+ }
+ } else {
+ gCurrentTest.repeatTest = false;
+ gCurrentTest.autoRepeatDelay = 0;
+ gLitesnEvents = kListenEvent_All;
+ gCurrentTest.isTimeoutTesting = false;
+ gCurrentTest.expectedView = gCurrentTest.expectedViewAfterTimeout;
+ }
+
+ var delta = gCurrentTest.delta ? gCurrentTest.delta : 4;
+ _fireWheelScrollEvent(gCurrentTest.offset(),
+ gCurrentTest.isVertical, gCurrentTest.isForward, delta);
+}
+
+/******************************************************************************
+ * Event handlers
+ ******************************************************************************/
+
+function onScrollView(aEvent)
+{
+ /**
+ * Scroll event handler of |gRootView|, |gSubView1|, |gSubView2| and
+ * |gSubView3|. If testing is failed, this function cancels all left tests.
+ * For checking the event is expected, the event firer must call
+ * |_saveScrollPositions|.
+ *
+ * @param gCurrentTest.expectedView
+ * The expected view which should be scrolled by the wheel event.
+ * This value can be null. It means any views should not be
+ * scrolled.
+ * @param gCurrentTest.isVertical
+ * The expected view should be scrolled vertical or horizontal.
+ * @param gCurrentTest.isForward
+ * The expected view should be scrolled to forward or backward.
+ * @param gCurrentTest.canFailRandomly
+ * If this is not undefined, this test can fail by unexpected view
+ * scrolling which is caused by unexpected timeout. If this is
+ * defined, |gCurrentTest.possibleView| must be set. If the view is
+ * same as the event target, the failure can be random. At this
+ * time, we should retry the current test list.
+ */
+
+ if (!(gLitesnEvents & kListenEvent_OnScroll))
+ return;
+
+ // Now testing a timeout, but a view is scrolled before timeout.
+ if (gCurrentTest.isTimeoutTesting && !gCurrentTest.wasTransactionTimeout) {
+ is(aEvent.target.id, "",
+ "The view scrolled before timeout (the expected view after timeout is " +
+ gCurrentTest.expectedView ? gCurrentTest.expectedView.id : "null" +
+ "): " + gCurrentTest.description);
+ runNextTestList();
+ return;
+ }
+
+ // Check whether the scrolled event should be fired or not.
+ if (!gCurrentTest.expectedView) {
+ is(aEvent.target.id, "",
+ "no views should be scrolled (" +
+ _getScrolledStateText(_getScrolledState(aEvent.target)) + "): " +
+ gCurrentTest.description);
+ runNextTestList();
+ return;
+ }
+
+ // Check whether the scrolled view is expected or not.
+ if (aEvent.target != gCurrentTest.expectedView) {
+ // If current test can fail randomly and the possible view is same as the
+ // event target, this failure may be caused by unexpected timeout.
+ // At this time, we should retry the current tests with slower settings.
+ if (gCurrentTest.canFailRandomly &&
+ gCurrentTest.canFailRandomly.possibleView == aEvent.target &&
+ gCurrentTestListStatus.retryWhenTransactionTimeout > 0) {
+ gCurrentTestListStatus.retryWhenTransactionTimeout--;
+ retryCurrentTestList();
+ return;
+ }
+ is(aEvent.target.id, gCurrentTest.expectedView.id,
+ "wrong view was scrolled: " + gCurrentTest.description);
+ runNextTestList();
+ return;
+ }
+
+ // Check whether the scrolling direction is expected or not.
+ var expectedState = _getExpectedScrolledState();
+ var currentState = _getScrolledState(aEvent.target);
+ if (expectedState != currentState) {
+ is(_getScrolledStateText(currentState),
+ _getScrolledStateText(expectedState),
+ "scrolled to wrong direction: " + gCurrentTest.description);
+ runNextTestList();
+ return;
+ }
+
+ ok(true, "passed: " + gCurrentTest.description);
+ continueTest();
+}
+
+function onMouseScrollFailed()
+{
+ /**
+ * Scroll failed event handler. If testing is failed, this function cancels
+ * all remains of current test-list, and go to next test-list.
+ *
+ * NOTE: This event is fired immediately after |_fireWheelScrollEvent|.
+ *
+ * @param gCurrentTest.expectedView
+ * The expected view which should be scrolled by the wheel event.
+ * This value can be null. It means any views should not be
+ * scrolled. When this is not null, this event means the test may
+ * be failed.
+ */
+
+ if (!(gLitesnEvents & kListenEvent_OnScrollFailed))
+ return;
+
+ ok(!gCurrentTest.expectedView,
+ "failed to scroll on current target: " + gCurrentTest.description);
+ if (gCurrentTest.expectedView) {
+ runNextTestList();
+ return;
+ }
+
+ continueTest();
+}
+
+function onTransactionTimeout()
+{
+ /**
+ * Scroll transaction timeout event handler. If the timeout is unexpected,
+ * i.e., |gCurrentTest.isTimeoutTesting| is not true, this function retry
+ * the current test-list. However, if the current test-list failed by timeout
+ * |gCurrentTestListStatus.retryWhenTransactionTimeout| times already, marking
+ * to failed the current test-list, and go to next test-list.
+ *
+ * @param gCurrentTest.expectedView
+ * The expected view which should be scrolled by the wheel event.
+ * This value can be null. It means any views should not be
+ * scrolled. When this is not null, this event means the testing may
+ * be failed.
+ * @param gCurrentTest.isTimeoutTesting
+ * If this value is true, the current testing have waited this
+ * event. Otherwise, the testing may be failed.
+ * @param gCurrentTestListStatus.retryWhenTransactionTimeout
+ * If |gCurrentTest.isTimeoutTesting| is not true but this event is
+ * fired, the failure may be randomly. Then, this event handler
+ * retry to test the current test-list until this cound will be zero.
+ */
+
+ if (!gCurrentTest.isTimeoutTesting &&
+ gCurrentTestListStatus.retryWhenTransactionTimeout > 0) {
+ gCurrentTestListStatus.retryWhenTransactionTimeout--;
+ // retry current test list
+ retryCurrentTestList();
+ return;
+ }
+
+ gCurrentTest.wasTransactionTimeout = true;
+
+ if (!(gLitesnEvents & kListenEvent_OnTransactionTimeout))
+ return;
+
+ ok(gCurrentTest.isTimeoutTesting,
+ "transaction timeout: " + gCurrentTest.description);
+ if (!gCurrentTest.isTimeoutTesting) {
+ runNextTestList();
+ return;
+ }
+
+ continueTest();
+}
+
+/******************************************************************************
+ * Main function for this tests
+ ******************************************************************************/
+
+function runNextTestStep()
+{
+ // When this is first time or the current test list is finised, load next
+ // test-list.
+ _clearTimer();
+ if (!gCurrentTest)
+ runNextTestList();
+ else
+ runTestStepAt(gCurrentTestListStatus.nextStepIndex);
+}
+
+function runNextTestList()
+{
+ _clearTimer();
+
+ gLitesnEvents = kListenEvent_None;
+ _clearTransaction();
+ resetTimeoutPrefs();
+ if (gCurrentTestListStatus.nextListIndex >= gTestLists.length) {
+ finish();
+ return;
+ }
+
+ gCurrentTestListStatus.nextListIndex++;
+ gCurrentTestListStatus.retryWhenTransactionTimeout =
+ _getCurrentTestList().retryWhenTransactionTimeout;
+ runTestStepAt(0);
+}
+
+function runTestStepAt(aStepIndex)
+{
+ _clearTimer();
+
+ disableNonTestMouseEvents(true);
+
+ // load a step of testing.
+ gCurrentTestListStatus.nextStepIndex = aStepIndex;
+ gCurrentTest =
+ _getCurrentTestList().steps[gCurrentTestListStatus.nextStepIndex++];
+ if (gCurrentTest) {
+ gCurrentTest.wasTransactionTimeout = false;
+ gTimer = setTimeout(gCurrentTest.func, gCurrentTest.delay);
+ } else {
+ // If current test-list doesn't have more testing, go to next test-list
+ // after cleaning up the current transaction.
+ _clearTransaction();
+ runNextTestList();
+ }
+}
+
+function retryCurrentTestList()
+{
+ _clearTimer();
+
+ gLitesnEvents = kListenEvent_None;
+ _clearTransaction();
+ ok(true, "WARNING: retry current test-list...");
+ growUpTimeoutPrefs(); // retry the test with longer timeout settings.
+ runTestStepAt(0);
+}
+
+function continueTest()
+{
+ /**
+ * This function is called from an event handler when a test succeeded.
+ *
+ * @param gCurrentTest.repeatTest
+ * When this is true, onScrollView calls |gCurrentTest.func|. So,
+ * same test can repeat. Otherwise, this calls |runNextTestStep|.
+ * @param gCurrentTest.autoRepeatDelay
+ * The delay value in milliseconds, this is used to call
+ * |gCurrentTest.func| via |setTimeout|.
+ */
+
+ _clearTimer();
+ gLitesnEvents = kListenEvent_OnTransactionTimeout;
+
+ // We should call each functions via setTimeout. Because sometimes this test
+ // is broken by stack overflow.
+ if (gCurrentTest.repeatTest) {
+ gTimer = setTimeout(gCurrentTest.func, gCurrentTest.autoRepeatDelay);
+ } else {
+ gTimer = setTimeout(runNextTestStep, 0);
+ }
+}
+
+]]>
+</script>
+
+</window>
diff --git a/widget/uikit/GfxInfo.cpp b/widget/uikit/GfxInfo.cpp
new file mode 100644
index 0000000000..d797e4f4a0
--- /dev/null
+++ b/widget/uikit/GfxInfo.cpp
@@ -0,0 +1,174 @@
+/* -*- 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(uint32_t* aAdapterRAM) { return NS_ERROR_FAILURE; }
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(uint32_t* aAdapterRAM) { return NS_ERROR_FAILURE; }
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver(nsAString& aAdapterDriver) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver2(nsAString& aAdapterDriver) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion2(nsAString& aAdapterDriverVersion) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate(nsAString& aAdapterDriverDate) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate2(nsAString& aAdapterDriverDate) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID(nsAString& aAdapterVendorID) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString& aAdapterVendorID) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString& aAdapterDeviceID) {
+ return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID2(nsAString& aAdapterDeviceID) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID(nsAString& aAdapterSubsysID) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID2(nsAString& aAdapterSubsysID) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active) { return NS_ERROR_FAILURE; }
+
+const nsTArray<GfxDriverInfo>& GfxInfo::GetGfxDriverInfo() {
+ if (sDriverInfo->IsEmpty()) {
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Ios, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_STATUS_OK,
+ DRIVER_COMPARISON_IGNORED, GfxDriverInfo::allDriverVersions);
+ }
+
+ return *sDriverInfo;
+}
+
+nsresult GfxInfo::GetFeatureStatusImpl(
+ int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ OperatingSystem* aOS /* = nullptr */) {
+ NS_ENSURE_ARG_POINTER(aStatus);
+ aSuggestedDriverVersion.SetIsVoid(true);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ if (aOS) *aOS = OperatingSystem::Ios;
+
+ if (sShutdownOccurred) {
+ return NS_OK;
+ }
+
+ // OpenGL layers are never blocklisted 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) {
+ *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
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/uikit/GfxInfo.h b/widget/uikit/GfxInfo.h
new file mode 100644
index 0000000000..fe75e28d23
--- /dev/null
+++ b/widget/uikit/GfxInfo.h
@@ -0,0 +1,75 @@
+/* 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) override;
+ NS_IMETHOD GetDWriteEnabled(bool* aDWriteEnabled) override;
+ NS_IMETHOD GetDWriteVersion(nsAString& aDwriteVersion) override;
+ NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override;
+ NS_IMETHOD GetAdapterDescription(nsAString& aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver(nsAString& aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID(nsAString& aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID(nsAString& aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID(nsAString& aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM(uint32_t* aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate(nsAString& aAdapterDriverDate) override;
+ NS_IMETHOD GetAdapterDescription2(nsAString& aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver2(nsAString& aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID2(nsAString& aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID2(nsAString& aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID2(nsAString& aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM2(uint32_t* aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion2(
+ nsAString& aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate2(nsAString& aAdapterDriverDate) override;
+ NS_IMETHOD GetIsGPU2Active(bool* aIsGPU2Active) override;
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+
+#ifdef DEBUG
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+ protected:
+ virtual nsresult GetFeatureStatusImpl(
+ int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ OperatingSystem* aOS = nullptr);
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_GfxInfo_h__ */
diff --git a/widget/uikit/moz.build b/widget/uikit/moz.build
new file mode 100644
index 0000000000..3b1b05c10e
--- /dev/null
+++ b/widget/uikit/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget")
+
+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..fd46a51025
--- /dev/null
+++ b/widget/uikit/nsAppShell.h
@@ -0,0 +1,56 @@
+/* -*- 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/. */
+
+/*
+ * Runs the main native UIKit run loop, interrupting it as needed to process
+ * Gecko events.
+ */
+
+#ifndef nsAppShell_h_
+#define nsAppShell_h_
+
+#include "nsBaseAppShell.h"
+#include "nsTArray.h"
+
+#include <Foundation/NSAutoreleasePool.h>
+#include <CoreFoundation/CFRunLoop.h>
+#include <UIKit/UIWindow.h>
+
+@class AppShellDelegate;
+
+class nsAppShell : public nsBaseAppShell {
+ public:
+ NS_IMETHOD ResumeNative(void) override;
+
+ nsAppShell();
+
+ nsresult Init();
+
+ NS_IMETHOD Run(void) override;
+ NS_IMETHOD Exit(void) override;
+ // 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..2af03be1d5
--- /dev/null
+++ b/widget/uikit/nsAppShell.mm
@@ -0,0 +1,241 @@
+/* -*- 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/. */
+
+#import <UIKit/UIApplication.h>
+#import <UIKit/UIScreen.h>
+#import <UIKit/UIWindow.h>
+#import <UIKit/UIViewController.h>
+
+#include "nsAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsString.h"
+#include "nsIRollupListener.h"
+#include "nsIWidget.h"
+#include "nsThreadUtils.h"
+#include "nsMemoryPressure.h"
+#include "nsServiceManagerUtils.h"
+
+nsAppShell* nsAppShell::gAppShell = NULL;
+UIWindow* nsAppShell::gWindow = nil;
+NSMutableArray* nsAppShell::gTopLevelViews = [[NSMutableArray alloc] init];
+
+#define ALOG(args...) \
+ fprintf(stderr, args); \
+ fprintf(stderr, "\n")
+
+// ViewController
+@interface ViewController : UIViewController
+@end
+
+@implementation ViewController
+
+- (void)loadView {
+ ALOG("[ViewController loadView]");
+ CGRect r = {{0, 0}, {100, 100}};
+ self.view = [[UIView alloc] initWithFrame:r];
+ [self.view setBackgroundColor:[UIColor lightGrayColor]];
+ // add all of the top level views as children
+ for (UIView* v in nsAppShell::gTopLevelViews) {
+ ALOG("[ViewController.view addSubView:%p]", v);
+ [self.view addSubview:v];
+ }
+ [nsAppShell::gTopLevelViews release];
+ nsAppShell::gTopLevelViews = nil;
+}
+@end
+
+// AppShellDelegate
+//
+// Acts as a delegate for the UIApplication
+
+@interface AppShellDelegate : NSObject <UIApplicationDelegate> {
+}
+@property(strong, nonatomic) UIWindow* window;
+@end
+
+@implementation AppShellDelegate
+
+- (BOOL)application:(UIApplication*)application
+ didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
+ ALOG("[AppShellDelegate application:didFinishLaunchingWithOptions:]");
+ // We only create one window, since we can only display one window at
+ // a time anyway. Also, iOS 4 fails to display UIWindows if you
+ // create them before calling UIApplicationMain, so this makes more sense.
+ nsAppShell::gWindow =
+ [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] retain];
+ self.window = nsAppShell::gWindow;
+
+ self.window.rootViewController = [[ViewController alloc] init];
+
+ // just to make things more visible for now
+ nsAppShell::gWindow.backgroundColor = [UIColor blueColor];
+ [nsAppShell::gWindow makeKeyAndVisible];
+
+ return YES;
+}
+
+- (void)applicationWillTerminate:(UIApplication*)application {
+ ALOG("[AppShellDelegate applicationWillTerminate:]");
+ nsAppShell::gAppShell->WillTerminate();
+}
+
+- (void)applicationDidBecomeActive:(UIApplication*)application {
+ ALOG("[AppShellDelegate applicationDidBecomeActive:]");
+}
+
+- (void)applicationWillResignActive:(UIApplication*)application {
+ ALOG("[AppShellDelegate applicationWillResignActive:]");
+}
+
+- (void)applicationDidReceiveMemoryWarning:(UIApplication*)application {
+ ALOG("[AppShellDelegate applicationDidReceiveMemoryWarning:]");
+ NS_DispatchMemoryPressure(MemPressure_New);
+}
+@end
+
+// nsAppShell implementation
+
+NS_IMETHODIMP
+nsAppShell::ResumeNative(void) { return nsBaseAppShell::ResumeNative(); }
+
+nsAppShell::nsAppShell()
+ : mAutoreleasePool(NULL),
+ mDelegate(NULL),
+ mCFRunLoop(NULL),
+ mCFRunLoopSource(NULL),
+ mTerminated(false),
+ mNotifiedWillTerminate(false) {
+ gAppShell = this;
+}
+
+nsAppShell::~nsAppShell() {
+ if (mAutoreleasePool) {
+ [mAutoreleasePool release];
+ mAutoreleasePool = NULL;
+ }
+
+ if (mCFRunLoop) {
+ if (mCFRunLoopSource) {
+ ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
+ ::CFRelease(mCFRunLoopSource);
+ }
+ ::CFRelease(mCFRunLoop);
+ }
+
+ gAppShell = NULL;
+}
+
+// Init
+//
+// public
+nsresult nsAppShell::Init() {
+ mAutoreleasePool = [[NSAutoreleasePool alloc] init];
+
+ // Add a CFRunLoopSource to the main native run loop. The source is
+ // responsible for interrupting the run loop when Gecko events are ready.
+
+ mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
+ NS_ENSURE_STATE(mCFRunLoop);
+ ::CFRetain(mCFRunLoop);
+
+ CFRunLoopSourceContext context;
+ bzero(&context, sizeof(context));
+ // context.version = 0;
+ context.info = this;
+ context.perform = ProcessGeckoEvents;
+
+ mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
+ NS_ENSURE_STATE(mCFRunLoopSource);
+
+ ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
+
+ return nsBaseAppShell::Init();
+}
+
+// ProcessGeckoEvents
+//
+// The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
+// signalled from ScheduleNativeEventCallback.
+//
+// protected static
+void nsAppShell::ProcessGeckoEvents(void* aInfo) {
+ nsAppShell* self = static_cast<nsAppShell*>(aInfo);
+ self->NativeEventCallback();
+ self->Release();
+}
+
+// WillTerminate
+//
+// public
+void nsAppShell::WillTerminate() {
+ mNotifiedWillTerminate = true;
+ if (mTerminated) return;
+ mTerminated = true;
+ // We won't get another chance to process events
+ NS_ProcessPendingEvents(NS_GetCurrentThread());
+
+ // Unless we call nsBaseAppShell::Exit() here, it might not get called
+ // at all.
+ nsBaseAppShell::Exit();
+}
+
+// ScheduleNativeEventCallback
+//
+// protected virtual
+void nsAppShell::ScheduleNativeEventCallback() {
+ if (mTerminated) return;
+
+ NS_ADDREF_THIS();
+
+ // This will invoke ProcessGeckoEvents on the main thread.
+ ::CFRunLoopSourceSignal(mCFRunLoopSource);
+ ::CFRunLoopWakeUp(mCFRunLoop);
+}
+
+// ProcessNextNativeEvent
+//
+// protected virtual
+bool nsAppShell::ProcessNextNativeEvent(bool aMayWait) {
+ if (mTerminated) return false;
+
+ NSString* currentMode = nil;
+ NSDate* waitUntil = nil;
+ if (aMayWait) waitUntil = [NSDate distantFuture];
+ NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
+
+ BOOL eventProcessed = NO;
+ do {
+ currentMode = [currentRunLoop currentMode];
+ if (!currentMode) currentMode = NSDefaultRunLoopMode;
+
+ if (aMayWait)
+ eventProcessed = [currentRunLoop runMode:currentMode beforeDate:waitUntil];
+ else
+ [currentRunLoop acceptInputForMode:currentMode beforeDate:waitUntil];
+ } while (eventProcessed && aMayWait);
+
+ return false;
+}
+
+// Run
+//
+// public
+NS_IMETHODIMP
+nsAppShell::Run(void) {
+ ALOG("nsAppShell::Run");
+ char argv[1][4] = {"app"};
+ UIApplicationMain(1, (char**)argv, nil, @"AppShellDelegate");
+ // UIApplicationMain doesn't exit. :-(
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppShell::Exit(void) {
+ if (mTerminated) return NS_OK;
+
+ mTerminated = true;
+ return nsBaseAppShell::Exit();
+}
diff --git a/widget/uikit/nsLookAndFeel.h b/widget/uikit/nsLookAndFeel.h
new file mode 100644
index 0000000000..0f3d628096
--- /dev/null
+++ b/widget/uikit/nsLookAndFeel.h
@@ -0,0 +1,39 @@
+/* -*- 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 __nsLookAndFeel
+#define __nsLookAndFeel
+
+#include "nsXPLookAndFeel.h"
+
+class nsLookAndFeel final : public nsXPLookAndFeel {
+ public:
+ explicit nsLookAndFeel(const LookAndFeelCache* aCache);
+ virtual ~nsLookAndFeel();
+
+ void NativeInit() final;
+ virtual void RefreshImpl();
+ nsresult NativeGetImpl(IntID aID, int32_t& aResult) override;
+ nsresult NativeGetFloat(FloatID aID, float& aResult) override;
+ nsresult NativeGetColor(const ColorID aID, nscolor& aResult) override;
+ bool NativeGetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) override;
+ virtual char16_t GetPasswordCharacterImpl() {
+ // unicode value for the bullet character, used for password textfields.
+ return 0x2022;
+ }
+
+ static bool UseOverlayScrollbars() { return true; }
+
+ private:
+ nscolor mColorTextSelectForeground;
+ nscolor mColorDarkText;
+
+ bool mInitialized;
+
+ void EnsureInit();
+};
+
+#endif
diff --git a/widget/uikit/nsLookAndFeel.mm b/widget/uikit/nsLookAndFeel.mm
new file mode 100644
index 0000000000..3351a6eb23
--- /dev/null
+++ b/widget/uikit/nsLookAndFeel.mm
@@ -0,0 +1,414 @@
+/* -*- 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/. */
+
+#import <UIKit/UIColor.h>
+#import <UIKit/UIInterface.h>
+
+#include "nsLookAndFeel.h"
+
+#include "mozilla/FontPropertyTypes.h"
+#include "nsStyleConsts.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+
+nsLookAndFeel::nsLookAndFeel(LookAndFeelCache* aCache) : nsXPLookAndFeel(), mInitialized(false) {}
+
+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));
+ }
+ MOZ_ASSERT_UNREACHABLE("Unhandled color space!");
+ return 0;
+}
+
+void nsLookAndFeel::NativeInit() { EnsureInit(); }
+
+void nsLookAndFeel::RefreshImpl() {
+ nsXPLookAndFeel::RefreshImpl();
+
+ mInitialized = false;
+}
+
+nsresult nsLookAndFeel::NativeGetColor(const ColorID aID, nscolor& aResult) {
+ EnsureInit();
+
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ case ColorID::WindowBackground:
+ aResult = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::WindowForeground:
+ aResult = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::WidgetBackground:
+ aResult = NS_RGB(0xdd, 0xdd, 0xdd);
+ break;
+ case ColorID::WidgetForeground:
+ aResult = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::WidgetSelectBackground:
+ aResult = NS_RGB(0x80, 0x80, 0x80);
+ break;
+ case ColorID::WidgetSelectForeground:
+ aResult = NS_RGB(0x00, 0x00, 0x80);
+ break;
+ case ColorID::Widget3DHighlight:
+ aResult = NS_RGB(0xa0, 0xa0, 0xa0);
+ break;
+ case ColorID::Widget3DShadow:
+ aResult = NS_RGB(0x40, 0x40, 0x40);
+ break;
+ case ColorID::TextBackground:
+ aResult = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::TextForeground:
+ aResult = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::TextSelectBackground:
+ case ColorID::Highlight: // CSS2 color
+ aResult = NS_RGB(0xaa, 0xaa, 0xaa);
+ break;
+ case ColorID::MozMenuhover:
+ aResult = NS_RGB(0xee, 0xee, 0xee);
+ break;
+ case ColorID::TextSelectForeground:
+ case ColorID::Highlighttext: // CSS2 color
+ case ColorID::MozMenuhovertext:
+ aResult = mColorTextSelectForeground;
+ break;
+ case ColorID::IMESelectedRawTextBackground:
+ case ColorID::IMESelectedConvertedTextBackground:
+ case ColorID::IMERawInputBackground:
+ case ColorID::IMEConvertedTextBackground:
+ aResult = NS_TRANSPARENT;
+ break;
+ case ColorID::IMESelectedRawTextForeground:
+ case ColorID::IMESelectedConvertedTextForeground:
+ case ColorID::IMERawInputForeground:
+ case ColorID::IMEConvertedTextForeground:
+ aResult = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case ColorID::IMERawInputUnderline:
+ case ColorID::IMEConvertedTextUnderline:
+ aResult = NS_40PERCENT_FOREGROUND_COLOR;
+ break;
+ case ColorID::IMESelectedRawTextUnderline:
+ case ColorID::IMESelectedConvertedTextUnderline:
+ aResult = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case ColorID::SpellCheckerUnderline:
+ aResult = NS_RGB(0xff, 0, 0);
+ break;
+
+ //
+ // css2 system colors http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ //
+ case ColorID::Buttontext:
+ case ColorID::MozButtonhovertext:
+ case ColorID::Captiontext:
+ case ColorID::Menutext:
+ case ColorID::Infotext:
+ case ColorID::MozMenubartext:
+ case ColorID::Windowtext:
+ aResult = mColorDarkText;
+ break;
+ case ColorID::Activecaption:
+ aResult = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::Activeborder:
+ aResult = NS_RGB(0x00, 0x00, 0x00);
+ break;
+ case ColorID::Appworkspace:
+ aResult = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::Background:
+ aResult = NS_RGB(0x63, 0x63, 0xCE);
+ break;
+ case ColorID::Buttonface:
+ case ColorID::MozButtonhoverface:
+ aResult = NS_RGB(0xF0, 0xF0, 0xF0);
+ break;
+ case ColorID::Buttonhighlight:
+ aResult = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case ColorID::Buttonshadow:
+ aResult = NS_RGB(0xDC, 0xDC, 0xDC);
+ break;
+ case ColorID::Graytext:
+ aResult = NS_RGB(0x44, 0x44, 0x44);
+ break;
+ case ColorID::Inactiveborder:
+ aResult = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::Inactivecaption:
+ aResult = NS_RGB(0xaa, 0xaa, 0xaa);
+ break;
+ case ColorID::Inactivecaptiontext:
+ aResult = NS_RGB(0x45, 0x45, 0x45);
+ break;
+ case ColorID::Scrollbar:
+ aResult = NS_RGB(0, 0, 0); // XXX
+ break;
+ case ColorID::Threeddarkshadow:
+ aResult = NS_RGB(0xDC, 0xDC, 0xDC);
+ break;
+ case ColorID::Threedshadow:
+ aResult = NS_RGB(0xE0, 0xE0, 0xE0);
+ break;
+ case ColorID::Threedface:
+ aResult = NS_RGB(0xF0, 0xF0, 0xF0);
+ break;
+ case ColorID::Threedhighlight:
+ aResult = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::Threedlightshadow:
+ aResult = NS_RGB(0xDA, 0xDA, 0xDA);
+ break;
+ case ColorID::Menu:
+ aResult = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::Infobackground:
+ aResult = NS_RGB(0xFF, 0xFF, 0xC7);
+ break;
+ case ColorID::Windowframe:
+ aResult = NS_RGB(0xaa, 0xaa, 0xaa);
+ break;
+ case ColorID::Window:
+ case ColorID::MozField:
+ case ColorID::MozCombobox:
+ aResult = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::MozFieldtext:
+ case ColorID::MozComboboxtext:
+ aResult = mColorDarkText;
+ break;
+ case ColorID::MozDialog:
+ aResult = NS_RGB(0xaa, 0xaa, 0xaa);
+ break;
+ case ColorID::MozDialogtext:
+ case ColorID::MozCellhighlighttext:
+ case ColorID::MozHtmlCellhighlighttext:
+ case ColorID::MozColheadertext:
+ case ColorID::MozColheaderhovertext:
+ aResult = mColorDarkText;
+ break;
+ case ColorID::MozDragtargetzone:
+ case ColorID::MozMacChromeActive:
+ case ColorID::MozMacChromeInactive:
+ aResult = NS_RGB(0xaa, 0xaa, 0xaa);
+ break;
+ case ColorID::MozMacFocusring:
+ aResult = NS_RGB(0x3F, 0x98, 0xDD);
+ break;
+ case ColorID::MozMacMenushadow:
+ aResult = NS_RGB(0xA3, 0xA3, 0xA3);
+ break;
+ case ColorID::MozMacMenutextdisable:
+ aResult = NS_RGB(0x88, 0x88, 0x88);
+ break;
+ case ColorID::MozMacMenutextselect:
+ aResult = NS_RGB(0xaa, 0xaa, 0xaa);
+ break;
+ case ColorID::MozMacDisabledtoolbartext:
+ aResult = NS_RGB(0x3F, 0x3F, 0x3F);
+ break;
+ case ColorID::MozMacMenuselect:
+ aResult = NS_RGB(0xaa, 0xaa, 0xaa);
+ break;
+ case ColorID::MozButtondefault:
+ aResult = NS_RGB(0xDC, 0xDC, 0xDC);
+ break;
+ case ColorID::MozCellhighlight:
+ case ColorID::MozHtmlCellhighlight:
+ case ColorID::MozMacSecondaryhighlight:
+ // For inactive list selection
+ aResult = NS_RGB(0xaa, 0xaa, 0xaa);
+ break;
+ case ColorID::MozEventreerow:
+ // Background color of even list rows.
+ aResult = NS_RGB(0xff, 0xff, 0xff);
+ break;
+ case ColorID::MozOddtreerow:
+ // Background color of odd list rows.
+ aResult = NS_TRANSPARENT;
+ break;
+ case ColorID::MozNativehyperlinktext:
+ // 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::NativeGetInt(IntID aID, int32_t& aResult) {
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ case IntID::ScrollButtonLeftMouseButtonAction:
+ aResult = 0;
+ break;
+ case IntID::ScrollButtonMiddleMouseButtonAction:
+ case IntID::ScrollButtonRightMouseButtonAction:
+ aResult = 3;
+ break;
+ case IntID::CaretBlinkTime:
+ aResult = 567;
+ break;
+ case IntID::CaretWidth:
+ aResult = 1;
+ break;
+ case IntID::ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case IntID::SelectTextfieldsOnKeyFocus:
+ // Select textfield content when focused by kbd
+ // used by nsEventStateManager::sTextfieldSelectModel
+ aResult = 1;
+ break;
+ case IntID::SubmenuDelay:
+ aResult = 200;
+ break;
+ case IntID::MenusCanOverlapOSBar:
+ // xul popups are not allowed to overlap the menubar.
+ aResult = 0;
+ break;
+ case IntID::SkipNavigatingDisabledMenuItem:
+ aResult = 1;
+ break;
+ case IntID::DragThresholdX:
+ case IntID::DragThresholdY:
+ aResult = 4;
+ break;
+ case IntID::ScrollArrowStyle:
+ aResult = eScrollArrow_None;
+ break;
+ case IntID::ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+ case IntID::TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case IntID::TreeCloseDelay:
+ aResult = 1000;
+ break;
+ case IntID::TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case IntID::TreeScrollDelay:
+ aResult = 100;
+ break;
+ case IntID::TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case IntID::DWMCompositor:
+ case IntID::WindowsClassic:
+ case IntID::WindowsDefaultTheme:
+ case IntID::TouchEnabled:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case IntID::MacGraphiteTheme:
+ aResult = 0;
+ break;
+ case IntID::TabFocusModel:
+ aResult = 1; // default to just textboxes
+ break;
+ case IntID::ScrollToClick:
+ aResult = 0;
+ break;
+ case IntID::ChosenMenuItemsShouldBlink:
+ aResult = 1;
+ break;
+ case IntID::IMERawInputUnderlineStyle:
+ case IntID::IMEConvertedTextUnderlineStyle:
+ case IntID::IMESelectedRawTextUnderlineStyle:
+ case IntID::IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ break;
+ case IntID::SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_DOTTED;
+ break;
+ case IntID::ContextMenuOffsetVertical:
+ case IntID::ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+ default:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+NS_IMETHODIMP
+nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ case FloatID::IMEUnderlineRelativeSize:
+ aResult = 2.0f;
+ break;
+ case FloatID::SpellCheckerUnderlineRelativeSize:
+ aResult = 2.0f;
+ break;
+ default:
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ }
+
+ return res;
+}
+
+bool nsLookAndFeel::NativeGetFont(FontID aID, nsString& aFontName, gfxFontStyle& aFontStyle) {
+ // hack for now
+ if (aID == FontID::Window || aID == FontID::Document) {
+ aFontStyle.style = FontSlantStyle::Normal();
+ aFontStyle.weight = FontWeight::Normal();
+ aFontStyle.stretch = FontStretch::Normal();
+ aFontStyle.size = 14;
+ aFontStyle.systemFont = true;
+
+ aFontName.AssignLiteral("sans-serif");
+ return true;
+ }
+
+ // TODO: implement more here?
+ return false;
+}
+
+void nsLookAndFeel::EnsureInit() {
+ if (mInitialized) {
+ return;
+ }
+ mInitialized = true;
+
+ nscolor color;
+ GetColor(ColorID::TextSelectBackground, color);
+ if (color == 0x000000) {
+ mColorTextSelectForeground = NS_RGB(0xff, 0xff, 0xff);
+ } else {
+ mColorTextSelectForeground = NS_DONT_CHANGE_COLOR;
+ }
+
+ mColorDarkText = GetColorFromUIColor([UIColor darkTextColor]);
+
+ RecordTelemetry();
+}
diff --git a/widget/uikit/nsScreenManager.h b/widget/uikit/nsScreenManager.h
new file mode 100644
index 0000000000..db4d7ed8e9
--- /dev/null
+++ b/widget/uikit/nsScreenManager.h
@@ -0,0 +1,60 @@
+/* -*- 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 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) override {
+ *outId = 0;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight) override;
+ NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth,
+ int32_t* aHeight) override;
+ NS_IMETHOD GetRectDisplayPix(int32_t* aLeft, int32_t* aTop, int32_t* aWidth,
+ int32_t* aHeight) override;
+ NS_IMETHOD GetAvailRectDisplayPix(int32_t* aLeft, int32_t* aTop, int32_t* aWidth,
+ int32_t* aHeight) override;
+ NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth) override;
+ NS_IMETHOD GetColorDepth(int32_t* aColorDepth) override;
+ NS_IMETHOD GetContentsScaleFactor(double* aContentsScaleFactor) override;
+ NS_IMETHOD GetDefaultCSSScaleFactor(double* aScaleFactor) override {
+ return GetContentsScaleFactor(aScaleFactor);
+ }
+
+ private:
+ UIScreen* mScreen;
+};
+
+class UIKitScreenManager : public nsIScreenManager {
+ public:
+ UIKitScreenManager();
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSISCREENMANAGER
+
+ static LayoutDeviceIntRect GetBounds();
+
+ private:
+ virtual ~UIKitScreenManager() {}
+ // TODO: support >1 screen, iPad supports external displays
+ nsCOMPtr<nsIScreen> mScreen;
+};
+
+#endif // nsScreenManager_h_
diff --git a/widget/uikit/nsScreenManager.mm b/widget/uikit/nsScreenManager.mm
new file mode 100644
index 0000000000..a9ed0f3d66
--- /dev/null
+++ b/widget/uikit/nsScreenManager.mm
@@ -0,0 +1,98 @@
+/* -*- 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/. */
+
+#import <UIKit/UIScreen.h>
+
+#include "gfxPoint.h"
+#include "nsScreenManager.h"
+#include "nsAppShell.h"
+
+static LayoutDeviceIntRect gScreenBounds;
+static bool gScreenBoundsSet = false;
+
+UIKitScreen::UIKitScreen(UIScreen* aScreen) { mScreen = [aScreen retain]; }
+
+NS_IMETHODIMP
+UIKitScreen::GetRect(int32_t* outX, int32_t* outY, int32_t* outWidth, int32_t* outHeight) {
+ return GetRectDisplayPix(outX, outY, outWidth, outHeight);
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetAvailRect(int32_t* outX, int32_t* outY, int32_t* outWidth, int32_t* outHeight) {
+ return GetAvailRectDisplayPix(outX, outY, outWidth, outHeight);
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetRectDisplayPix(int32_t* outX, int32_t* outY, int32_t* outWidth,
+ int32_t* outHeight) {
+ nsIntRect rect = UIKitScreenManager::GetBounds();
+ *outX = rect.x;
+ *outY = rect.y;
+ *outWidth = rect.width;
+ *outHeight = rect.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetAvailRectDisplayPix(int32_t* outX, int32_t* outY, int32_t* outWidth,
+ int32_t* outHeight) {
+ CGRect rect = [mScreen applicationFrame];
+ CGFloat scale = [mScreen scale];
+
+ *outX = rect.origin.x * scale;
+ *outY = rect.origin.y * scale;
+ *outWidth = rect.size.width * scale;
+ *outHeight = rect.size.height * scale;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetPixelDepth(int32_t* aPixelDepth) {
+ // Close enough.
+ *aPixelDepth = 24;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetColorDepth(int32_t* aColorDepth) { return GetPixelDepth(aColorDepth); }
+
+NS_IMETHODIMP
+UIKitScreen::GetContentsScaleFactor(double* aContentsScaleFactor) {
+ *aContentsScaleFactor = [mScreen scale];
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(UIKitScreenManager, nsIScreenManager)
+
+UIKitScreenManager::UIKitScreenManager() : mScreen(new UIKitScreen([UIScreen mainScreen])) {}
+
+LayoutDeviceIntRect UIKitScreenManager::GetBounds() {
+ if (!gScreenBoundsSet) {
+ CGRect rect = [[UIScreen mainScreen] bounds];
+ CGFloat scale = [[UIScreen mainScreen] scale];
+ gScreenBounds.x = rect.origin.x * scale;
+ gScreenBounds.y = rect.origin.y * scale;
+ gScreenBounds.width = rect.size.width * scale;
+ gScreenBounds.height = rect.size.height * scale;
+ gScreenBoundsSet = true;
+ }
+ printf("UIKitScreenManager::GetBounds: %d %d %d %d\n", gScreenBounds.x, gScreenBounds.y,
+ gScreenBounds.width, gScreenBounds.height);
+ return gScreenBounds;
+}
+
+NS_IMETHODIMP
+UIKitScreenManager::GetPrimaryScreen(nsIScreen** outScreen) {
+ NS_IF_ADDREF(*outScreen = mScreen.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UIKitScreenManager::ScreenForRect(int32_t inLeft, int32_t inTop, int32_t inWidth, int32_t inHeight,
+ nsIScreen** outScreen) {
+ return GetPrimaryScreen(outScreen);
+}
diff --git a/widget/uikit/nsWidgetFactory.mm b/widget/uikit/nsWidgetFactory.mm
new file mode 100644
index 0000000000..bdf1beceeb
--- /dev/null
+++ b/widget/uikit/nsWidgetFactory.mm
@@ -0,0 +1,49 @@
+/* -*- 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 "nsISupports.h"
+#include "mozilla/ModuleUtils.h"
+
+#include "nsWidgetsCID.h"
+
+#include "nsAppShell.h"
+#include "nsAppShellSingleton.h"
+#include "nsLookAndFeel.h"
+#include "nsScreenManager.h"
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(UIKitScreenManager)
+
+#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_APPSHELL_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ {&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/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();
+}
+
+extern const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion, kWidgetCIDs, kWidgetContracts, nullptr, nullptr, nsAppShellInit,
+ nsWidgetUIKitModuleDtor};
diff --git a/widget/uikit/nsWindow.h b/widget/uikit/nsWindow.h
new file mode 100644
index 0000000000..d1c3a04cfe
--- /dev/null
+++ b/widget/uikit/nsWindow.h
@@ -0,0 +1,109 @@
+/* -*- 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 NSWINDOW_H_
+#define NSWINDOW_H_
+
+#include "nsBaseWidget.h"
+#include "gfxPoint.h"
+
+#include "nsTArray.h"
+
+@class UIWindow;
+@class UIView;
+@class ChildView;
+
+class nsWindow final : public nsBaseWidget {
+ typedef nsBaseWidget Inherited;
+
+ public:
+ nsWindow();
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsWindow, Inherited)
+
+ //
+ // nsIWidget
+ //
+
+ [[nodiscard]] virtual nsresult Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr) override;
+ virtual void Destroy() override;
+ virtual void Show(bool aState) override;
+ virtual void Enable(bool aState) override {}
+ virtual bool IsEnabled() const override { return true; }
+ virtual bool IsVisible() const override { return mVisible; }
+ virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+
+ virtual void SetBackgroundColor(const nscolor& aColor) override;
+ virtual void* GetNativeData(uint32_t aDataType) override;
+
+ virtual void Move(double aX, double aY) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ void EnteredFullScreen(bool aFullScreen);
+ virtual void Resize(double aWidth, double aHeight, bool aRepaint) override;
+ virtual void 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;
+
+ virtual nsresult SetTitle(const nsAString& aTitle) override { return NS_OK; }
+
+ virtual void Invalidate(const LayoutDeviceIntRect& aRect) override;
+ virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
+ virtual nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent, nsEventStatus& aStatus) override;
+
+ void WillPaintWindow();
+ bool PaintWindow(LayoutDeviceIntRegion aRegion);
+
+ bool HasModalDescendents() { return false; }
+
+ // virtual nsresult
+ // NotifyIME(const IMENotification& aIMENotification) override;
+ virtual void SetInputContext(const InputContext& aContext, const InputContextAction& aAction);
+ virtual InputContext GetInputContext();
+ /*
+ virtual bool ExecuteNativeKeyBinding(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData) override;
+ */
+
+ protected:
+ virtual ~nsWindow();
+ void BringToFront();
+ nsWindow* FindTopLevel();
+ bool IsTopLevel();
+ nsresult GetCurrentOffset(uint32_t& aOffset, uint32_t& aLength);
+ nsresult DeleteRange(int aOffset, int aLen);
+
+ void TearDownView();
+
+ ChildView* mNativeView;
+ bool mVisible;
+ nsTArray<nsWindow*> mChildren;
+ nsWindow* mParent;
+ InputContext mInputContext;
+
+ void OnSizeChanged(const mozilla::gfx::IntSize& aSize);
+
+ static void DumpWindows();
+ static void DumpWindows(const nsTArray<nsWindow*>& wins, int indent = 0);
+ static void LogWindow(nsWindow* win, int index, int indent);
+};
+
+#endif /* NSWINDOW_H_ */
diff --git a/widget/uikit/nsWindow.mm b/widget/uikit/nsWindow.mm
new file mode 100644
index 0000000000..1dfc40dd85
--- /dev/null
+++ b/widget/uikit/nsWindow.mm
@@ -0,0 +1,754 @@
+/* -*- 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/. */
+
+#import <UIKit/UIEvent.h>
+#import <UIKit/UIGraphics.h>
+#import <UIKit/UIInterface.h>
+#import <UIKit/UIScreen.h>
+#import <UIKit/UITapGestureRecognizer.h>
+#import <UIKit/UITouch.h>
+#import <UIKit/UIView.h>
+#import <UIKit/UIViewController.h>
+#import <UIKit/UIWindow.h>
+#import <QuartzCore/QuartzCore.h>
+
+#include <algorithm>
+
+#include "nsWindow.h"
+#include "nsScreenManager.h"
+#include "nsAppShell.h"
+
+#include "nsWidgetsCID.h"
+#include "nsGfxCIID.h"
+
+#include "gfxQuartzSurface.h"
+#include "gfxUtils.h"
+#include "gfxImageSurface.h"
+#include "gfxContext.h"
+#include "nsRegion.h"
+#include "Layers.h"
+#include "nsTArray.h"
+
+#include "mozilla/BasicEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/MouseEventBinding.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 = MouseButton::ePrimary;
+ event.mTime = PR_IntervalNow();
+ event.inputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
+
+ nsEventStatus status;
+ aWindow->DispatchEvent(&event, status);
+}
+
+- (void)handleTap:(UITapGestureRecognizer*)sender {
+ if (sender.state == UIGestureRecognizerStateEnded) {
+ ALOG("[ChildView[%p] handleTap]", self);
+ LayoutDeviceIntPoint lp =
+ UIKitPointsToDevPixels([sender locationInView:self], [self contentScaleFactor]);
+ [self sendMouseEvent:eMouseMove point:lp widget:mGeckoChild];
+ [self sendMouseEvent:eMouseDown point:lp widget:mGeckoChild];
+ [self sendMouseEvent:eMouseUp point:lp widget:mGeckoChild];
+ }
+}
+
+- (void)sendTouchEvent:(EventMessage)aType touches:(NSSet*)aTouches widget:(nsWindow*)aWindow {
+ WidgetTouchEvent event(true, aType, aWindow);
+ // XXX: I think nativeEvent.timestamp * 1000 is probably usable here but
+ // I don't care that much right now.
+ event.mTime = PR_IntervalNow();
+ event.mTouches.SetCapacity(aTouches.count);
+ for (UITouch* touch in aTouches) {
+ LayoutDeviceIntPoint loc =
+ UIKitPointsToDevPixels([touch locationInView:self], [self contentScaleFactor]);
+ LayoutDeviceIntPoint radius = UIKitPointsToDevPixels([touch majorRadius], [touch majorRadius]);
+ void* value;
+ if (!CFDictionaryGetValueIfPresent(mTouches, touch, (const void**)&value)) {
+ // This shouldn't happen.
+ NS_ASSERTION(false, "Got a touch that we didn't know about");
+ continue;
+ }
+ int id = reinterpret_cast<int>(value);
+ RefPtr<Touch> t = new Touch(id, loc, radius, 0.0f, 1.0f);
+ event.mRefPoint = loc;
+ event.mTouches.AppendElement(t);
+ }
+ aWindow->DispatchInputEvent(&event);
+}
+
+- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
+ ALOG("[ChildView[%p] touchesBegan", self);
+ if (!mGeckoChild) return;
+
+ for (UITouch* touch : touches) {
+ CFDictionaryAddValue(mTouches, touch, (void*)mNextTouchID);
+ mNextTouchID++;
+ }
+ [self sendTouchEvent:eTouchStart touches:[event allTouches] widget:mGeckoChild];
+}
+
+- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
+ ALOG("[ChildView[%p] touchesCancelled", self);
+ [self sendTouchEvent:eTouchCancel touches:touches widget:mGeckoChild];
+ for (UITouch* touch : touches) {
+ CFDictionaryRemoveValue(mTouches, touch);
+ }
+ if (CFDictionaryGetCount(mTouches) == 0) {
+ mNextTouchID = 0;
+ }
+}
+
+- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
+ ALOG("[ChildView[%p] touchesEnded", self);
+ if (!mGeckoChild) return;
+
+ [self sendTouchEvent:eTouchEnd touches:touches widget:mGeckoChild];
+ for (UITouch* touch : touches) {
+ CFDictionaryRemoveValue(mTouches, touch);
+ }
+ if (CFDictionaryGetCount(mTouches) == 0) {
+ mNextTouchID = 0;
+ }
+}
+
+- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
+ ALOG("[ChildView[%p] touchesMoved", self);
+ if (!mGeckoChild) return;
+
+ [self sendTouchEvent:eTouchMove touches:[event allTouches] widget:mGeckoChild];
+}
+
+- (void)setNeedsDisplayInRect:(CGRect)aRect {
+ if ([self isUsingMainThreadOpenGL]) {
+ // Draw without calling drawRect. This prevent us from
+ // needing to access the normal window buffer surface unnecessarily, so we
+ // waste less time synchronizing the two surfaces.
+ if (!mWaitingForPaint) {
+ mWaitingForPaint = YES;
+ // Use NSRunLoopCommonModes instead of the default NSDefaultRunLoopMode
+ // so that the timer also fires while a native menu is open.
+ [self performSelector:@selector(drawUsingOpenGLCallback)
+ withObject:nil
+ afterDelay:0
+ inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
+ }
+ }
+}
+
+- (BOOL)isUsingMainThreadOpenGL {
+ if (!mGeckoChild || ![self window]) return NO;
+
+ return mGeckoChild->GetLayerManager(nullptr)->GetBackendType() ==
+ mozilla::layers::LayersBackend::LAYERS_OPENGL;
+}
+
+- (void)drawUsingOpenGL {
+ ALOG("drawUsingOpenGL");
+ AUTO_PROFILER_LABEL("ChildView::drawUsingOpenGL", OTHER);
+
+ 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;
+ }
+ AUTO_PROFILER_LABEL("ChildView::drawRect", OTHER);
+
+ // The CGContext that drawRect supplies us with comes with a transform that
+ // scales one user space unit to one Cocoa point, which can consist of
+ // multiple dev pixels. But Gecko expects its supplied context to be scaled
+ // to device pixels, so we need to reverse the scaling.
+ double scale = mGeckoChild->BackingScaleFactor();
+ CGContextSaveGState(aContext);
+ CGContextScaleCTM(aContext, 1.0 / scale, 1.0 / scale);
+
+ CGSize viewSize = [self bounds].size;
+ gfx::IntSize backingSize(viewSize.width * scale, viewSize.height * scale);
+
+ CGContextSaveGState(aContext);
+
+ LayoutDeviceIntRegion region = LayoutDeviceIntRect(
+ NSToIntRound(aRect.origin.x * scale), NSToIntRound(aRect.origin.y * scale),
+ NSToIntRound(aRect.size.width * scale), NSToIntRound(aRect.size.height * scale));
+
+ // Create Cairo objects.
+ RefPtr<gfxQuartzSurface> targetSurface;
+
+ RefPtr<gfxContext> targetContext;
+ if (gfxPlatform::GetPlatform()->SupportsAzureContentForType(gfx::BackendType::CAIRO)) {
+ // This is dead code unless you mess with prefs, but keep it around for
+ // debugging.
+ targetSurface = new gfxQuartzSurface(aContext, backingSize);
+ targetSurface->SetAllowUseAsSource(false);
+ RefPtr<gfx::DrawTarget> dt =
+ gfxPlatform::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
+
+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;
+}
+
+nsresult nsWindow::ConfigureChildren(const nsTArray<nsIWidget::Configuration>& config) {
+ for (uint32_t i = 0; i < config.Length(); ++i) {
+ nsWindow* childWin = (nsWindow*)config[i].mChild.get();
+ childWin->Resize(config[i].mBounds.x, config[i].mBounds.y, config[i].mBounds.width,
+ config[i].mBounds.height, false);
+ }
+
+ return NS_OK;
+}
+
+void 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;
+ }
+}
+
+void nsWindow::Move(double aX, double aY) {
+ if (!mNativeView || (mBounds.x == aX && mBounds.y == aY)) return;
+
+ // 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();
+}
+
+void 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;
+
+ 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();
+}
+
+void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
+ if (!mNativeView || (mBounds.width == aWidth && mBounds.height == aHeight)) return;
+
+ mBounds.width = aWidth;
+ mBounds.height = aHeight;
+
+ [mNativeView setFrame:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())];
+
+ if (mVisible && aRepaint) [mNativeView setNeedsDisplay];
+
+ ReportSizeEvent();
+}
+
+void nsWindow::SetSizeMode(nsSizeMode aMode) {
+ if (aMode == static_cast<int32_t>(mSizeMode)) {
+ return;
+ }
+
+ mSizeMode = static_cast<nsSizeMode>(aMode);
+ if (aMode == nsSizeMode_Maximized || aMode == nsSizeMode_Fullscreen) {
+ // Resize to fill screen
+ nsBaseWidget::InfallibleMakeFullScreen(true);
+ }
+ ReportSizeModeEvent(aMode);
+}
+
+void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
+ if (!mNativeView || !mVisible) return;
+
+ MOZ_RELEASE_ASSERT(GetLayerManager()->GetBackendType() != LayersBackend::LAYERS_CLIENT,
+ "Shouldn't need to invalidate with accelerated OMTC layers!");
+
+ [mNativeView setNeedsLayout];
+ [mNativeView setNeedsDisplayInRect:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())];
+}
+
+void nsWindow::SetFocus(Raise) {
+ [[mNativeView window] makeKeyWindow];
+ [mNativeView becomeFirstResponder];
+}
+
+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;
+}
+
+nsresult nsWindow::DispatchEvent(mozilla::WidgetGUIEvent* aEvent, nsEventStatus& aStatus) {
+ aStatus = nsEventStatus_eIgnore;
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(aEvent->mWidget);
+
+ if (mWidgetListener) aStatus = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
+
+ return NS_OK;
+}
+
+void nsWindow::SetInputContext(const InputContext& aContext, const InputContextAction& aAction) {
+ // TODO: actually show VKB
+ mInputContext = aContext;
+}
+
+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;
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow();
+ return window.forget();
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow();
+ return window.forget();
+}
diff --git a/widget/windows/AudioSession.cpp b/widget/windows/AudioSession.cpp
new file mode 100644
index 0000000000..0975aa065e
--- /dev/null
+++ b/widget/windows/AudioSession.cpp
@@ -0,0 +1,443 @@
+/* -*- 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 <windows.h>
+#include <audiopolicy.h>
+#include <mmdeviceapi.h>
+
+#include "mozilla/RefPtr.h"
+#include "nsIStringBundle.h"
+#include "nsIUUIDGenerator.h"
+
+//#include "AudioSession.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/WindowsVersion.h"
+
+#include <objbase.h>
+
+namespace mozilla {
+namespace widget {
+
+/*
+ * To take advantage of what Vista+ have to offer with respect to audio,
+ * we need to maintain an audio session. This class wraps IAudioSessionControl
+ * and implements IAudioSessionEvents (for callbacks from Windows)
+ */
+class AudioSession final : public IAudioSessionEvents {
+ private:
+ AudioSession();
+ ~AudioSession();
+
+ public:
+ static AudioSession* GetSingleton();
+
+ // COM IUnknown
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP QueryInterface(REFIID, void**);
+ STDMETHODIMP_(ULONG) Release();
+
+ // IAudioSessionEvents
+ STDMETHODIMP OnChannelVolumeChanged(DWORD aChannelCount,
+ float aChannelVolumeArray[],
+ DWORD aChangedChannel, LPCGUID aContext);
+ STDMETHODIMP OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext);
+ STDMETHODIMP OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext);
+ STDMETHODIMP OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext);
+ STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason aReason);
+
+ private:
+ nsresult OnSessionDisconnectedInternal();
+ nsresult CommitAudioSessionData();
+
+ public:
+ STDMETHODIMP OnSimpleVolumeChanged(float aVolume, BOOL aMute,
+ LPCGUID aContext);
+ STDMETHODIMP OnStateChanged(AudioSessionState aState);
+
+ nsresult Start();
+ nsresult Stop();
+ void StopInternal();
+
+ nsresult GetSessionData(nsID& aID, nsString& aSessionName,
+ nsString& aIconPath);
+
+ nsresult SetSessionData(const nsID& aID, const nsString& aSessionName,
+ const nsString& aIconPath);
+
+ enum SessionState {
+ UNINITIALIZED, // Has not been initialized yet
+ STARTED, // Started
+ CLONED, // SetSessionInfoCalled, Start not called
+ FAILED, // The audio session failed to start
+ STOPPED, // Stop called
+ AUDIO_SESSION_DISCONNECTED // Audio session disconnected
+ };
+
+ protected:
+ RefPtr<IAudioSessionControl> mAudioSessionControl;
+ nsString mDisplayName;
+ nsString mIconPath;
+ nsID mSessionGroupingParameter;
+ SessionState mState;
+ // Guards the IAudioSessionControl
+ mozilla::Mutex mMutex;
+
+ ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+
+ static AudioSession* sService;
+};
+
+nsresult StartAudioSession() { return AudioSession::GetSingleton()->Start(); }
+
+nsresult StopAudioSession() { return AudioSession::GetSingleton()->Stop(); }
+
+nsresult GetAudioSessionData(nsID& aID, nsString& aSessionName,
+ nsString& aIconPath) {
+ return AudioSession::GetSingleton()->GetSessionData(aID, aSessionName,
+ aIconPath);
+}
+
+nsresult RecvAudioSessionData(const nsID& aID, const nsString& aSessionName,
+ const nsString& aIconPath) {
+ return AudioSession::GetSingleton()->SetSessionData(aID, aSessionName,
+ aIconPath);
+}
+
+AudioSession* AudioSession::sService = nullptr;
+
+AudioSession::AudioSession() : mMutex("AudioSessionControl") {
+ mState = UNINITIALIZED;
+}
+
+AudioSession::~AudioSession() {}
+
+AudioSession* AudioSession::GetSingleton() {
+ if (!(AudioSession::sService)) {
+ RefPtr<AudioSession> service = new AudioSession();
+ service.forget(&AudioSession::sService);
+ }
+
+ // We don't refcount AudioSession on the Gecko side, we hold one single ref
+ // as long as the appshell is running.
+ return AudioSession::sService;
+}
+
+// It appears Windows will use us on a background thread ...
+NS_IMPL_ADDREF(AudioSession)
+NS_IMPL_RELEASE(AudioSession)
+
+STDMETHODIMP
+AudioSession::QueryInterface(REFIID iid, void** ppv) {
+ const IID IID_IAudioSessionEvents = __uuidof(IAudioSessionEvents);
+ if ((IID_IUnknown == iid) || (IID_IAudioSessionEvents == iid)) {
+ *ppv = static_cast<IAudioSessionEvents*>(this);
+ AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+// Once we are started Windows will hold a reference to us through our
+// IAudioSessionEvents interface that will keep us alive until the appshell
+// calls Stop.
+nsresult AudioSession::Start() {
+ MOZ_ASSERT(mState == UNINITIALIZED || mState == CLONED ||
+ mState == AUDIO_SESSION_DISCONNECTED,
+ "State invariants violated");
+
+ const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
+ const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
+ const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager);
+
+ HRESULT hr;
+
+ // There's a matching CoUninit in Stop() for this tied to a state of
+ // UNINITIALIZED.
+ hr = CoInitialize(nullptr);
+ MOZ_ASSERT(SUCCEEDED(hr),
+ "CoInitialize failure in audio session control, unexpected");
+
+ if (mState == UNINITIALIZED) {
+ mState = FAILED;
+
+ // Content processes should be CLONED
+ if (XRE_IsContentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess(),
+ "Should only get here in a chrome process!");
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_FAILURE);
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_TRUE(bundle, NS_ERROR_FAILURE);
+
+ bundle->GetStringFromName("brandFullName", mDisplayName);
+
+ wchar_t* buffer;
+ mIconPath.GetMutableData(&buffer, MAX_PATH);
+ ::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
+
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ do_GetService("@mozilla.org/uuid-generator;1");
+ NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
+ uuidgen->GenerateUUIDInPlace(&mSessionGroupingParameter);
+ }
+
+ mState = FAILED;
+
+ MOZ_ASSERT(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(),
+ "Should never happen ...");
+
+ RefPtr<IMMDeviceEnumerator> enumerator;
+ hr = ::CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL,
+ IID_IMMDeviceEnumerator, getter_AddRefs(enumerator));
+ if (FAILED(hr)) return NS_ERROR_NOT_AVAILABLE;
+
+ RefPtr<IMMDevice> device;
+ hr = enumerator->GetDefaultAudioEndpoint(
+ EDataFlow::eRender, ERole::eMultimedia, getter_AddRefs(device));
+ if (FAILED(hr)) {
+ if (hr == E_NOTFOUND) return NS_ERROR_NOT_AVAILABLE;
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<IAudioSessionManager> manager;
+ hr = device->Activate(IID_IAudioSessionManager, CLSCTX_ALL, nullptr,
+ getter_AddRefs(manager));
+ if (FAILED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MutexAutoLock lock(mMutex);
+ hr = manager->GetAudioSessionControl(&GUID_NULL, 0,
+ getter_AddRefs(mAudioSessionControl));
+
+ if (FAILED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Increments refcount of 'this'.
+ hr = mAudioSessionControl->RegisterAudioSessionNotification(this);
+ if (FAILED(hr)) {
+ StopInternal();
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod("AudioSession::CommitAudioSessionData", this,
+ &AudioSession::CommitAudioSessionData);
+ NS_DispatchToMainThread(runnable);
+
+ mState = STARTED;
+
+ return NS_OK;
+}
+
+void SpawnASCReleaseThread(RefPtr<IAudioSessionControl>&& aASC) {
+ // Fake moving to the other thread by circumventing the ref count.
+ // (RefPtrs don't play well with C++11 lambdas and we don't want to use
+ // XPCOM here.)
+ IAudioSessionControl* rawPtr = nullptr;
+ aASC.forget(&rawPtr);
+ MOZ_ASSERT(rawPtr);
+ PRThread* thread = PR_CreateThread(
+ PR_USER_THREAD,
+ [](void* aRawPtr) {
+ NS_SetCurrentThreadName("AudioASCReleaser");
+ static_cast<IAudioSessionControl*>(aRawPtr)->Release();
+ },
+ rawPtr, PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_UNJOINABLE_THREAD, 0);
+ if (!thread) {
+ // We can't make a thread so just destroy the IAudioSessionControl here.
+ rawPtr->Release();
+ }
+}
+
+void AudioSession::StopInternal() {
+ mMutex.AssertCurrentThreadOwns();
+
+ if (mAudioSessionControl && (mState == STARTED || mState == STOPPED)) {
+ // Decrement refcount of 'this'
+ mAudioSessionControl->UnregisterAudioSessionNotification(this);
+ }
+
+ if (mAudioSessionControl) {
+ // Avoid hanging when destroying AudioSessionControl. We do that by
+ // moving the AudioSessionControl to a worker thread (that we never
+ // 'join') for destruction.
+ SpawnASCReleaseThread(std::move(mAudioSessionControl));
+ }
+}
+
+nsresult AudioSession::Stop() {
+ MOZ_ASSERT(mState == STARTED || mState == UNINITIALIZED || // XXXremove this
+ mState == FAILED,
+ "State invariants violated");
+ SessionState state = mState;
+ mState = STOPPED;
+
+ {
+ RefPtr<AudioSession> kungFuDeathGrip;
+ kungFuDeathGrip.swap(sService);
+
+ MutexAutoLock lock(mMutex);
+ StopInternal();
+ }
+
+ if (state != UNINITIALIZED) {
+ ::CoUninitialize();
+ }
+ return NS_OK;
+}
+
+void CopynsID(nsID& lhs, const nsID& rhs) {
+ lhs.m0 = rhs.m0;
+ lhs.m1 = rhs.m1;
+ lhs.m2 = rhs.m2;
+ for (int i = 0; i < 8; i++) {
+ lhs.m3[i] = rhs.m3[i];
+ }
+}
+
+nsresult AudioSession::GetSessionData(nsID& aID, nsString& aSessionName,
+ nsString& aIconPath) {
+ MOZ_ASSERT(mState == FAILED || mState == STARTED || mState == CLONED,
+ "State invariants violated");
+
+ CopynsID(aID, mSessionGroupingParameter);
+ aSessionName = mDisplayName;
+ aIconPath = mIconPath;
+
+ if (mState == FAILED) return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+nsresult AudioSession::SetSessionData(const nsID& aID,
+ const nsString& aSessionName,
+ const nsString& aIconPath) {
+ MOZ_ASSERT(mState == UNINITIALIZED, "State invariants violated");
+ MOZ_ASSERT(!XRE_IsParentProcess(),
+ "Should never get here in a chrome process!");
+ mState = CLONED;
+
+ CopynsID(mSessionGroupingParameter, aID);
+ mDisplayName = aSessionName;
+ mIconPath = aIconPath;
+ return NS_OK;
+}
+
+nsresult AudioSession::CommitAudioSessionData() {
+ MutexAutoLock lock(mMutex);
+
+ if (!mAudioSessionControl) {
+ // Stop() was called before we had a chance to do this.
+ return NS_OK;
+ }
+
+ HRESULT hr = mAudioSessionControl->SetGroupingParam(
+ (LPGUID)&mSessionGroupingParameter, nullptr);
+ if (FAILED(hr)) {
+ StopInternal();
+ return NS_ERROR_FAILURE;
+ }
+
+ hr = mAudioSessionControl->SetDisplayName(mDisplayName.get(), nullptr);
+ if (FAILED(hr)) {
+ StopInternal();
+ return NS_ERROR_FAILURE;
+ }
+
+ hr = mAudioSessionControl->SetIconPath(mIconPath.get(), nullptr);
+ if (FAILED(hr)) {
+ StopInternal();
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+STDMETHODIMP
+AudioSession::OnChannelVolumeChanged(DWORD aChannelCount,
+ float aChannelVolumeArray[],
+ DWORD aChangedChannel, LPCGUID aContext) {
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext) {
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext) {
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext) {
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnSessionDisconnected(AudioSessionDisconnectReason aReason) {
+ // Run our code asynchronously. Per MSDN we can't do anything interesting
+ // in this callback.
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod("widget::AudioSession::OnSessionDisconnectedInternal",
+ this, &AudioSession::OnSessionDisconnectedInternal);
+ NS_DispatchToMainThread(runnable);
+ return S_OK;
+}
+
+nsresult AudioSession::OnSessionDisconnectedInternal() {
+ // When successful, UnregisterAudioSessionNotification will decrement the
+ // refcount of 'this'. Start will re-increment it. In the interim,
+ // we'll need to reference ourselves.
+ RefPtr<AudioSession> kungFuDeathGrip(this);
+
+ {
+ // We need to release the mutex before we call Start().
+ MutexAutoLock lock(mMutex);
+
+ if (!mAudioSessionControl) return NS_OK;
+
+ mAudioSessionControl->UnregisterAudioSessionNotification(this);
+ mAudioSessionControl = nullptr;
+ }
+
+ mState = AUDIO_SESSION_DISCONNECTED;
+ CoUninitialize();
+ Start(); // If it fails there's not much we can do.
+ return NS_OK;
+}
+
+STDMETHODIMP
+AudioSession::OnSimpleVolumeChanged(float aVolume, BOOL aMute,
+ LPCGUID aContext) {
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnStateChanged(AudioSessionState aState) {
+ return S_OK; // NOOP
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/AudioSession.h b/widget/windows/AudioSession.h
new file mode 100644
index 0000000000..f715ec5458
--- /dev/null
+++ b/widget/windows/AudioSession.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsString.h"
+
+namespace mozilla {
+namespace widget {
+
+// Start the audio session in the current process
+nsresult StartAudioSession();
+
+// Pass the information necessary to start an audio session in another process
+nsresult GetAudioSessionData(nsID& aID, nsString& aSessionName,
+ nsString& aIconPath);
+
+// Receive the information necessary to start an audio session in a non-chrome
+// process
+nsresult RecvAudioSessionData(const nsID& aID, const nsString& aSessionName,
+ const nsString& aIconPath);
+
+// Stop the audio session in the current process
+nsresult StopAudioSession();
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/CompositorWidgetChild.cpp b/widget/windows/CompositorWidgetChild.cpp
new file mode 100644
index 0000000000..9adfac64bd
--- /dev/null
+++ b/widget/windows/CompositorWidgetChild.cpp
@@ -0,0 +1,109 @@
+/* -*- 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 "CompositorWidgetChild.h"
+#include "mozilla/Unused.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/widget/CompositorWidgetVsyncObserver.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsBaseWidget.h"
+#include "VsyncDispatcher.h"
+#include "gfxPlatform.h"
+#include "RemoteBackbuffer.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidgetChild::CompositorWidgetChild(
+ RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver,
+ const CompositorWidgetInitData& aInitData)
+ : mVsyncDispatcher(aVsyncDispatcher),
+ mVsyncObserver(aVsyncObserver),
+ mCompositorWnd(nullptr),
+ mWnd(reinterpret_cast<HWND>(
+ aInitData.get_WinCompositorWidgetInitData().hWnd())),
+ mTransparencyMode(
+ aInitData.get_WinCompositorWidgetInitData().transparencyMode()),
+ mRemoteBackbufferProvider() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!gfxPlatform::IsHeadless());
+ MOZ_ASSERT(mWnd && ::IsWindow(mWnd));
+}
+
+CompositorWidgetChild::~CompositorWidgetChild() {}
+
+bool CompositorWidgetChild::Initialize() {
+ mRemoteBackbufferProvider = std::make_unique<remote_backbuffer::Provider>();
+ if (!mRemoteBackbufferProvider->Initialize(mWnd, OtherPid(),
+ mTransparencyMode)) {
+ return false;
+ }
+
+ auto maybeRemoteHandles = mRemoteBackbufferProvider->CreateRemoteHandles();
+ if (!maybeRemoteHandles) {
+ return false;
+ }
+
+ Unused << SendInitialize(*maybeRemoteHandles);
+
+ return true;
+}
+
+void CompositorWidgetChild::EnterPresentLock() {
+ Unused << SendEnterPresentLock();
+}
+
+void CompositorWidgetChild::LeavePresentLock() {
+ Unused << SendLeavePresentLock();
+}
+
+void CompositorWidgetChild::OnDestroyWindow() {}
+
+bool CompositorWidgetChild::OnWindowResize(const LayoutDeviceIntSize& aSize) {
+ return true;
+}
+
+void CompositorWidgetChild::OnWindowModeChange(nsSizeMode aSizeMode) {}
+
+void CompositorWidgetChild::UpdateTransparency(nsTransparencyMode aMode) {
+ mTransparencyMode = aMode;
+ mRemoteBackbufferProvider->UpdateTransparencyMode(aMode);
+ Unused << SendUpdateTransparency(aMode);
+}
+
+void CompositorWidgetChild::ClearTransparentWindow() {
+ Unused << SendClearTransparentWindow();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetChild::RecvObserveVsync() {
+ mVsyncDispatcher->SetCompositorVsyncObserver(mVsyncObserver);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetChild::RecvUnobserveVsync() {
+ mVsyncDispatcher->SetCompositorVsyncObserver(nullptr);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetChild::RecvUpdateCompositorWnd(
+ const WindowsHandle& aCompositorWnd, const WindowsHandle& aParentWnd,
+ UpdateCompositorWndResolver&& aResolve) {
+ HWND parentWnd = reinterpret_cast<HWND>(aParentWnd);
+ if (mWnd == parentWnd) {
+ mCompositorWnd = reinterpret_cast<HWND>(aCompositorWnd);
+ ::SetParent(mCompositorWnd, mWnd);
+ aResolve(true);
+ } else {
+ aResolve(false);
+ gfxCriticalNote << "Parent winow does not match";
+ MOZ_ASSERT_UNREACHABLE("unexpected to happen");
+ }
+
+ return IPC_OK();
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/CompositorWidgetChild.h b/widget/windows/CompositorWidgetChild.h
new file mode 100644
index 0000000000..2de686b706
--- /dev/null
+++ b/widget/windows/CompositorWidgetChild.h
@@ -0,0 +1,60 @@
+/* -*- 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 widget_windows_CompositorWidgetChild_h
+#define widget_windows_CompositorWidgetChild_h
+
+#include "WinCompositorWidget.h"
+#include "mozilla/widget/PCompositorWidgetChild.h"
+#include "mozilla/widget/CompositorWidgetVsyncObserver.h"
+
+namespace mozilla {
+class CompositorVsyncDispatcher;
+
+namespace widget {
+
+namespace remote_backbuffer {
+class Provider;
+}
+
+class CompositorWidgetChild final : public PCompositorWidgetChild,
+ public PlatformCompositorWidgetDelegate {
+ public:
+ CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver,
+ const CompositorWidgetInitData& aInitData);
+ ~CompositorWidgetChild() override;
+
+ bool Initialize();
+
+ void EnterPresentLock() override;
+ void LeavePresentLock() override;
+ void OnDestroyWindow() override;
+ bool OnWindowResize(const LayoutDeviceIntSize& aSize) override;
+ void OnWindowModeChange(nsSizeMode aSizeMode) override;
+ void UpdateTransparency(nsTransparencyMode aMode) override;
+ void ClearTransparentWindow() override;
+
+ mozilla::ipc::IPCResult RecvObserveVsync() override;
+ mozilla::ipc::IPCResult RecvUnobserveVsync() override;
+ mozilla::ipc::IPCResult RecvUpdateCompositorWnd(
+ const WindowsHandle& aCompositorWnd, const WindowsHandle& aParentWnd,
+ UpdateCompositorWndResolver&& aResolve) override;
+
+ private:
+ RefPtr<CompositorVsyncDispatcher> mVsyncDispatcher;
+ RefPtr<CompositorWidgetVsyncObserver> mVsyncObserver;
+ HWND mCompositorWnd;
+
+ HWND mWnd;
+ nsTransparencyMode mTransparencyMode;
+
+ std::unique_ptr<remote_backbuffer::Provider> mRemoteBackbufferProvider;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_CompositorWidgetChild_h
diff --git a/widget/windows/CompositorWidgetParent.cpp b/widget/windows/CompositorWidgetParent.cpp
new file mode 100644
index 0000000000..308f9cfa0e
--- /dev/null
+++ b/widget/windows/CompositorWidgetParent.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 "CompositorWidgetParent.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/layers/Compositor.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsWindow.h"
+#include "VsyncDispatcher.h"
+#include "WinCompositorWindowThread.h"
+#include "VRShMem.h"
+#include "RemoteBackbuffer.h"
+
+#include <ddraw.h>
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::gfx;
+using namespace mozilla;
+
+CompositorWidgetParent::CompositorWidgetParent(
+ const CompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions)
+ : WinCompositorWidget(aInitData.get_WinCompositorWidgetInitData(),
+ aOptions),
+ mWnd(reinterpret_cast<HWND>(
+ aInitData.get_WinCompositorWidgetInitData().hWnd())),
+ mTransparencyMode(
+ aInitData.get_WinCompositorWidgetInitData().transparencyMode()),
+ mRemoteBackbufferClient() {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+ MOZ_ASSERT(mWnd && ::IsWindow(mWnd));
+}
+
+CompositorWidgetParent::~CompositorWidgetParent() {}
+
+bool CompositorWidgetParent::Initialize(
+ const RemoteBackbufferHandles& aRemoteHandles) {
+ mRemoteBackbufferClient = std::make_unique<remote_backbuffer::Client>();
+ if (!mRemoteBackbufferClient->Initialize(aRemoteHandles)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool CompositorWidgetParent::PreRender(WidgetRenderingContext* aContext) {
+ // This can block waiting for WM_SETTEXT to finish
+ // Using PreRender is unnecessarily pessimistic because
+ // we technically only need to block during the present call
+ // not all of compositor rendering
+ mPresentLock.Enter();
+ return true;
+}
+
+void CompositorWidgetParent::PostRender(WidgetRenderingContext* aContext) {
+ mPresentLock.Leave();
+}
+
+LayoutDeviceIntSize CompositorWidgetParent::GetClientSize() {
+ RECT r;
+ if (!::GetClientRect(mWnd, &r)) {
+ return LayoutDeviceIntSize();
+ }
+ return LayoutDeviceIntSize(r.right - r.left, r.bottom - r.top);
+}
+
+already_AddRefed<gfx::DrawTarget>
+CompositorWidgetParent::StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion, layers::BufferMode* aBufferMode) {
+ MOZ_ASSERT(mRemoteBackbufferClient);
+ MOZ_ASSERT(aBufferMode);
+
+ // Because we use remote backbuffering, there is no need to use a local
+ // backbuffer too.
+ (*aBufferMode) = layers::BufferMode::BUFFER_NONE;
+
+ return mRemoteBackbufferClient->BorrowDrawTarget();
+}
+
+void CompositorWidgetParent::EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
+ Unused << mRemoteBackbufferClient->PresentDrawTarget(
+ aInvalidRegion.ToUnknownRegion());
+}
+
+bool CompositorWidgetParent::NeedsToDeferEndRemoteDrawing() { return false; }
+
+already_AddRefed<gfx::DrawTarget>
+CompositorWidgetParent::GetBackBufferDrawTarget(gfx::DrawTarget* aScreenTarget,
+ const gfx::IntRect& aRect,
+ bool* aOutIsCleared) {
+ MOZ_CRASH(
+ "Unexpected call to GetBackBufferDrawTarget() with remote "
+ "backbuffering in use");
+}
+
+already_AddRefed<gfx::SourceSurface>
+CompositorWidgetParent::EndBackBufferDrawing() {
+ MOZ_CRASH(
+ "Unexpected call to EndBackBufferDrawing() with remote "
+ "backbuffering in use");
+}
+
+bool CompositorWidgetParent::InitCompositor(layers::Compositor* aCompositor) {
+ if (aCompositor->GetBackendType() == layers::LayersBackend::LAYERS_BASIC) {
+ DeviceManagerDx::Get()->InitializeDirectDraw();
+ }
+ return true;
+}
+
+bool CompositorWidgetParent::HasGlass() const {
+ MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread() ||
+ wr::RenderThread::IsInRenderThread());
+
+ nsTransparencyMode transparencyMode = mTransparencyMode;
+ return transparencyMode == eTransparencyGlass ||
+ transparencyMode == eTransparencyBorderlessGlass;
+}
+
+bool CompositorWidgetParent::IsHidden() const { return ::IsIconic(mWnd); }
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvInitialize(
+ const RemoteBackbufferHandles& aRemoteHandles) {
+ Unused << Initialize(aRemoteHandles);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvEnterPresentLock() {
+ mPresentLock.Enter();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvLeavePresentLock() {
+ mPresentLock.Leave();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvUpdateTransparency(
+ const nsTransparencyMode& aMode) {
+ mTransparencyMode = aMode;
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvClearTransparentWindow() {
+ gfx::CriticalSectionAutoEnter lock(&mPresentLock);
+
+ RefPtr<DrawTarget> drawTarget = mRemoteBackbufferClient->BorrowDrawTarget();
+ if (!drawTarget) {
+ return IPC_OK();
+ }
+
+ IntSize size = drawTarget->GetSize();
+ if (size.IsEmpty()) {
+ return IPC_OK();
+ }
+
+ drawTarget->ClearRect(Rect(0, 0, size.width, size.height));
+
+ Unused << mRemoteBackbufferClient->PresentDrawTarget(
+ IntRect(0, 0, size.width, size.height));
+
+ return IPC_OK();
+}
+
+nsIWidget* CompositorWidgetParent::RealWidget() { return nullptr; }
+
+void CompositorWidgetParent::ObserveVsync(VsyncObserver* aObserver) {
+ if (aObserver) {
+ Unused << SendObserveVsync();
+ } else {
+ Unused << SendUnobserveVsync();
+ }
+ mVsyncObserver = aObserver;
+}
+
+RefPtr<VsyncObserver> CompositorWidgetParent::GetVsyncObserver() const {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+ return mVsyncObserver;
+}
+
+void CompositorWidgetParent::UpdateCompositorWnd(const HWND aCompositorWnd,
+ const HWND aParentWnd) {
+ MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(mRootLayerTreeID.isSome());
+
+ RefPtr<CompositorWidgetParent> self = this;
+ SendUpdateCompositorWnd(reinterpret_cast<WindowsHandle>(aCompositorWnd),
+ reinterpret_cast<WindowsHandle>(aParentWnd))
+ ->Then(
+ layers::CompositorThread(), __func__,
+ [self](const bool& aSuccess) {
+ if (aSuccess && self->mRootLayerTreeID.isSome()) {
+ self->mSetParentCompleted = true;
+ // Schedule composition after ::SetParent() call in parent
+ // process.
+ layers::CompositorBridgeParent::ScheduleForcedComposition(
+ self->mRootLayerTreeID.ref());
+ }
+ },
+ [self](const mozilla::ipc::ResponseRejectReason&) {});
+}
+
+void CompositorWidgetParent::SetRootLayerTreeID(
+ const layers::LayersId& aRootLayerTreeId) {
+ mRootLayerTreeID = Some(aRootLayerTreeId);
+}
+
+void CompositorWidgetParent::ActorDestroy(ActorDestroyReason aWhy) {}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/CompositorWidgetParent.h b/widget/windows/CompositorWidgetParent.h
new file mode 100644
index 0000000000..a4da4e1192
--- /dev/null
+++ b/widget/windows/CompositorWidgetParent.h
@@ -0,0 +1,84 @@
+/* -*- 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 widget_windows_CompositorWidgetParent_h
+#define widget_windows_CompositorWidgetParent_h
+
+#include "WinCompositorWidget.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/widget/PCompositorWidgetParent.h"
+
+namespace mozilla {
+namespace widget {
+
+namespace remote_backbuffer {
+class Client;
+}
+
+class CompositorWidgetParent final : public PCompositorWidgetParent,
+ public WinCompositorWidget {
+ public:
+ explicit CompositorWidgetParent(const CompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions);
+ ~CompositorWidgetParent() override;
+
+ bool Initialize(const RemoteBackbufferHandles& aRemoteHandles);
+
+ bool PreRender(WidgetRenderingContext*) override;
+ void PostRender(WidgetRenderingContext*) override;
+ already_AddRefed<gfx::DrawTarget> StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode) override;
+ void EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget,
+ const LayoutDeviceIntRegion& aInvalidRegion) override;
+ bool NeedsToDeferEndRemoteDrawing() override;
+ LayoutDeviceIntSize GetClientSize() override;
+ already_AddRefed<gfx::DrawTarget> GetBackBufferDrawTarget(
+ gfx::DrawTarget* aScreenTarget, const gfx::IntRect& aRect,
+ bool* aOutIsCleared) override;
+ already_AddRefed<gfx::SourceSurface> EndBackBufferDrawing() override;
+ bool InitCompositor(layers::Compositor* aCompositor) override;
+ bool IsHidden() const override;
+
+ bool HasGlass() const override;
+
+ mozilla::ipc::IPCResult RecvInitialize(
+ const RemoteBackbufferHandles& aRemoteHandles) override;
+ mozilla::ipc::IPCResult RecvEnterPresentLock() override;
+ mozilla::ipc::IPCResult RecvLeavePresentLock() override;
+ mozilla::ipc::IPCResult RecvUpdateTransparency(
+ const nsTransparencyMode& aMode) override;
+ mozilla::ipc::IPCResult RecvClearTransparentWindow() override;
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ nsIWidget* RealWidget() override;
+ void ObserveVsync(VsyncObserver* aObserver) override;
+ RefPtr<VsyncObserver> GetVsyncObserver() const override;
+
+ // PlatformCompositorWidgetDelegate Overrides
+ void UpdateCompositorWnd(const HWND aCompositorWnd,
+ const HWND aParentWnd) override;
+ void SetRootLayerTreeID(const layers::LayersId& aRootLayerTreeId) override;
+
+ private:
+ RefPtr<VsyncObserver> mVsyncObserver;
+ Maybe<layers::LayersId> mRootLayerTreeID;
+
+ HWND mWnd;
+
+ gfx::CriticalSection mPresentLock;
+
+ // Transparency handling.
+ mozilla::Atomic<nsTransparencyMode, MemoryOrdering::Relaxed>
+ mTransparencyMode;
+
+ std::unique_ptr<remote_backbuffer::Client> mRemoteBackbufferClient;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_CompositorWidgetParent_h
diff --git a/widget/windows/DirectManipulationOwner.cpp b/widget/windows/DirectManipulationOwner.cpp
new file mode 100644
index 0000000000..b43148152f
--- /dev/null
+++ b/widget/windows/DirectManipulationOwner.cpp
@@ -0,0 +1,718 @@
+/* -*- 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 "DirectManipulationOwner.h"
+#include "nsWindow.h"
+#include "WinModifierKeyState.h"
+#include "InputData.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/VsyncDispatcher.h"
+
+#if !defined(__MINGW32__) && !defined(__MINGW64__)
+
+// Direct Manipulation is only defined for Win8 and newer.
+# if defined(_WIN32_WINNT)
+# undef _WIN32_WINNT
+# define _WIN32_WINNT _WIN32_WINNT_WIN8
+# endif // defined(_WIN32_WINNT)
+# if defined(NTDDI_VERSION)
+# undef NTDDI_VERSION
+# define NTDDI_VERSION NTDDI_WIN8
+# endif // defined(NTDDI_VERSION)
+
+# include "directmanipulation.h"
+
+#endif // !defined(__MINGW32__) && !defined(__MINGW64__)
+
+namespace mozilla {
+namespace widget {
+
+#if !defined(__MINGW32__) && !defined(__MINGW64__)
+
+class DManipEventHandler : public IDirectManipulationViewportEventHandler,
+ public IDirectManipulationInteractionEventHandler {
+ public:
+ typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
+
+ friend class DirectManipulationOwner;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DManipEventHandler)
+
+ STDMETHODIMP QueryInterface(REFIID, void**) override;
+
+ friend class DirectManipulationOwner;
+
+ explicit DManipEventHandler(nsWindow* aWindow,
+ DirectManipulationOwner* aOwner,
+ const LayoutDeviceIntRect& aBounds);
+
+ HRESULT STDMETHODCALLTYPE OnViewportStatusChanged(
+ IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current,
+ DIRECTMANIPULATION_STATUS previous) override;
+
+ HRESULT STDMETHODCALLTYPE
+ OnViewportUpdated(IDirectManipulationViewport* viewport) override;
+
+ HRESULT STDMETHODCALLTYPE
+ OnContentUpdated(IDirectManipulationViewport* viewport,
+ IDirectManipulationContent* content) override;
+
+ HRESULT STDMETHODCALLTYPE
+ OnInteraction(IDirectManipulationViewport2* viewport,
+ DIRECTMANIPULATION_INTERACTION_TYPE interaction) override;
+
+ void Update();
+
+ class VObserver final : public mozilla::VsyncObserver {
+ public:
+ bool NotifyVsync(const mozilla::VsyncEvent& aVsync) override {
+ if (mOwner) {
+ mOwner->Update();
+ }
+ return true;
+ }
+ explicit VObserver(DManipEventHandler* aOwner) : mOwner(aOwner) {}
+
+ void ClearOwner() { mOwner = nullptr; }
+
+ private:
+ virtual ~VObserver() {}
+ DManipEventHandler* mOwner;
+ };
+
+ enum class State { eNone, ePanning, eInertia, ePinching };
+ void TransitionToState(State aNewState);
+
+ enum class Phase { eStart, eMiddle, eEnd };
+ // Return value indicates if we sent an event or not and hence if we should
+ // update mLastScale. (We only want to send pinch events if the computed
+ // deltaY for the corresponding WidgetWheelEvent would be non-zero.)
+ bool SendPinch(Phase aPhase, float aScale);
+ void SendPan(Phase aPhase, float x, float y, bool aIsInertia);
+
+ private:
+ virtual ~DManipEventHandler() = default;
+
+ nsWindow* mWindow;
+ DirectManipulationOwner* mOwner;
+ RefPtr<VObserver> mObserver;
+ float mLastScale;
+ float mLastXOffset;
+ float mLastYOffset;
+ LayoutDeviceIntRect mBounds;
+ bool mShouldSendPanStart;
+ bool mShouldSendPinchStart;
+ State mState = State::eNone;
+};
+
+DManipEventHandler::DManipEventHandler(nsWindow* aWindow,
+ DirectManipulationOwner* aOwner,
+ const LayoutDeviceIntRect& aBounds)
+ : mWindow(aWindow),
+ mOwner(aOwner),
+ mLastScale(1.f),
+ mLastXOffset(0.f),
+ mLastYOffset(0.f),
+ mBounds(aBounds),
+ mShouldSendPanStart(false),
+ mShouldSendPinchStart(false) {}
+
+STDMETHODIMP
+DManipEventHandler::QueryInterface(REFIID iid, void** ppv) {
+ const IID IID_IDirectManipulationViewportEventHandler =
+ __uuidof(IDirectManipulationViewportEventHandler);
+ const IID IID_IDirectManipulationInteractionEventHandler =
+ __uuidof(IDirectManipulationInteractionEventHandler);
+
+ if ((IID_IUnknown == iid) ||
+ (IID_IDirectManipulationViewportEventHandler == iid)) {
+ *ppv = static_cast<IDirectManipulationViewportEventHandler*>(this);
+ AddRef();
+ return S_OK;
+ }
+ if (IID_IDirectManipulationInteractionEventHandler == iid) {
+ *ppv = static_cast<IDirectManipulationInteractionEventHandler*>(this);
+ AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+HRESULT
+DManipEventHandler::OnViewportStatusChanged(
+ IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current,
+ DIRECTMANIPULATION_STATUS previous) {
+ if (current == previous) {
+ return S_OK;
+ }
+
+ if (current == DIRECTMANIPULATION_INERTIA) {
+ if (previous != DIRECTMANIPULATION_RUNNING || mState != State::ePanning) {
+ // xxx transition to none?
+ return S_OK;
+ }
+
+ TransitionToState(State::eInertia);
+ }
+
+ if (current == DIRECTMANIPULATION_RUNNING) {
+ // INERTIA -> RUNNING, should start a new sequence.
+ if (previous == DIRECTMANIPULATION_INERTIA) {
+ TransitionToState(State::eNone);
+ }
+ }
+
+ if (current != DIRECTMANIPULATION_ENABLED &&
+ current != DIRECTMANIPULATION_READY) {
+ return S_OK;
+ }
+
+ // A session has ended, reset the transform.
+ if (mLastScale != 1.f || mLastXOffset != 0.f || mLastYOffset != 0.f) {
+ HRESULT hr =
+ viewport->ZoomToRect(0, 0, mBounds.width, mBounds.height, false);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("ZoomToRect failed");
+ }
+ }
+ mLastScale = 1.f;
+ mLastXOffset = 0.f;
+ mLastYOffset = 0.f;
+
+ TransitionToState(State::eNone);
+
+ return S_OK;
+}
+
+HRESULT
+DManipEventHandler::OnViewportUpdated(IDirectManipulationViewport* viewport) {
+ return S_OK;
+}
+
+void DManipEventHandler::TransitionToState(State aNewState) {
+ if (mState == aNewState) {
+ return;
+ }
+
+ State prevState = mState;
+ mState = aNewState;
+
+ // End the previous sequence.
+ switch (prevState) {
+ case State::ePanning: {
+ // ePanning -> eNone, ePinching: PanEnd
+ // ePanning -> eInertia: we don't want to end the current scroll sequence.
+ if (aNewState != State::eInertia) {
+ SendPan(Phase::eEnd, 0.f, 0.f, false);
+ }
+ break;
+ }
+ case State::eInertia: {
+ // eInertia -> *: MomentumEnd
+ SendPan(Phase::eEnd, 0.f, 0.f, true);
+ break;
+ }
+ case State::ePinching: {
+ MOZ_ASSERT(aNewState == State::eNone);
+ // ePinching -> eNone: PinchEnd. ePinching should only transition to
+ // eNone.
+ SendPinch(Phase::eEnd, 0.f);
+ break;
+ }
+ case State::eNone: {
+ // eNone -> *: no cleanup is needed.
+ break;
+ }
+ default:
+ MOZ_ASSERT(false);
+ }
+
+ // Start the new sequence.
+ switch (aNewState) {
+ case State::ePanning: {
+ // eInertia, eNone -> ePanning: PanStart.
+ // We're being called from OnContentUpdated, it has the coords we need to
+ // pass to SendPan(Phase::eStart), so set mShouldSendPanStart and when we
+ // return OnContentUpdated will check it and call SendPan(Phase::eStart).
+ mShouldSendPanStart = true;
+ break;
+ }
+ case State::eInertia: {
+ // Only ePanning can transition to eInertia.
+ MOZ_ASSERT(prevState == State::ePanning);
+ SendPan(Phase::eStart, 0.f, 0.f, true);
+ break;
+ }
+ case State::ePinching: {
+ // * -> ePinching: PinchStart.
+ // Pinch gesture may begin with some scroll events.
+ // We're being called from OnContentUpdated, it has the scale we need to
+ // pass to SendPinch(Phase::eStart), so set mShouldSendPinchStart and when
+ // we return OnContentUpdated will check it and call
+ // SendPinch(Phase::eStart).
+ mShouldSendPinchStart = true;
+ break;
+ }
+ case State::eNone: {
+ // * -> eNone: only cleanup is needed.
+ break;
+ }
+ default:
+ MOZ_ASSERT(false);
+ }
+}
+
+HRESULT
+DManipEventHandler::OnContentUpdated(IDirectManipulationViewport* viewport,
+ IDirectManipulationContent* content) {
+ float transform[6];
+ HRESULT hr = content->GetContentTransform(transform, ARRAYSIZE(transform));
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("GetContentTransform failed");
+ return S_OK;
+ }
+
+ float windowScale = mWindow ? mWindow->GetDefaultScale().scale : 1.f;
+
+ float scale = transform[0];
+ float xoffset = transform[4] * windowScale;
+ float yoffset = transform[5] * windowScale;
+
+ // Not different from last time.
+ if (FuzzyEqualsMultiplicative(scale, mLastScale) && xoffset == mLastXOffset &&
+ yoffset == mLastYOffset) {
+ return S_OK;
+ }
+
+ // Consider this is a Scroll when scale factor equals 1.0.
+ if (FuzzyEqualsMultiplicative(scale, 1.f)) {
+ if (mState == State::eNone || mState == State::eInertia) {
+ TransitionToState(State::ePanning);
+ }
+ } else {
+ // Pinch gesture may begin with some scroll events.
+ TransitionToState(State::ePinching);
+ }
+
+ if (mState == State::ePanning || mState == State::eInertia) {
+ // Accumulate the offset (by not updating mLastX/YOffset) until we have at
+ // least one pixel both before and after scaling by the window scale.
+ float dx = std::abs(mLastXOffset - xoffset);
+ float dy = std::abs(mLastYOffset - yoffset);
+ float minDelta = std::max(1.f, windowScale);
+ if (dx < minDelta && dy < minDelta) {
+ return S_OK;
+ }
+ }
+
+ bool updateLastScale = true;
+ if (mState == State::ePanning) {
+ if (mShouldSendPanStart) {
+ SendPan(Phase::eStart, mLastXOffset - xoffset, mLastYOffset - yoffset,
+ false);
+ mShouldSendPanStart = false;
+ } else {
+ SendPan(Phase::eMiddle, mLastXOffset - xoffset, mLastYOffset - yoffset,
+ false);
+ }
+ } else if (mState == State::eInertia) {
+ SendPan(Phase::eMiddle, mLastXOffset - xoffset, mLastYOffset - yoffset,
+ true);
+ } else if (mState == State::ePinching) {
+ if (mShouldSendPinchStart) {
+ updateLastScale = SendPinch(Phase::eStart, scale);
+ // If we get here our current scale is not fuzzy equal to the previous
+ // scale, so SendPinch should return true.
+ MOZ_ASSERT(updateLastScale);
+ mShouldSendPinchStart = false;
+ } else {
+ updateLastScale = SendPinch(Phase::eMiddle, scale);
+ }
+ }
+
+ if (updateLastScale) {
+ mLastScale = scale;
+ }
+ mLastXOffset = xoffset;
+ mLastYOffset = yoffset;
+
+ return S_OK;
+}
+
+HRESULT
+DManipEventHandler::OnInteraction(
+ IDirectManipulationViewport2* viewport,
+ DIRECTMANIPULATION_INTERACTION_TYPE interaction) {
+ if (interaction == DIRECTMANIPULATION_INTERACTION_BEGIN) {
+ if (!mObserver) {
+ mObserver = new VObserver(this);
+ }
+
+ gfxWindowsPlatform::GetPlatform()->GetHardwareVsync()->AddGenericObserver(
+ mObserver);
+ }
+
+ if (mObserver && interaction == DIRECTMANIPULATION_INTERACTION_END) {
+ gfxWindowsPlatform::GetPlatform()
+ ->GetHardwareVsync()
+ ->RemoveGenericObserver(mObserver);
+ }
+
+ return S_OK;
+}
+
+void DManipEventHandler::Update() {
+ if (mOwner) {
+ mOwner->Update();
+ }
+}
+
+#endif // !defined(__MINGW32__) && !defined(__MINGW64__)
+
+void DirectManipulationOwner::Update() {
+#if !defined(__MINGW32__) && !defined(__MINGW64__)
+ if (mDmUpdateManager) {
+ mDmUpdateManager->Update(nullptr);
+ }
+#endif
+}
+
+DirectManipulationOwner::DirectManipulationOwner(nsWindow* aWindow)
+ : mWindow(aWindow) {}
+
+DirectManipulationOwner::~DirectManipulationOwner() { Destroy(); }
+
+#if !defined(__MINGW32__) && !defined(__MINGW64__)
+
+bool DManipEventHandler::SendPinch(Phase aPhase, float aScale) {
+ if (!mWindow) {
+ return false;
+ }
+
+ if (aScale == mLastScale && aPhase != Phase::eEnd) {
+ return false;
+ }
+
+ PinchGestureInput::PinchGestureType pinchGestureType =
+ PinchGestureInput::PINCHGESTURE_SCALE;
+ switch (aPhase) {
+ case Phase::eStart:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
+ break;
+ case Phase::eMiddle:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
+ break;
+ case Phase::eEnd:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("handle all enum values");
+ }
+
+ PRIntervalTime eventIntervalTime = PR_IntervalNow();
+ TimeStamp eventTimeStamp = TimeStamp::Now();
+
+ ModifierKeyState modifierKeyState;
+ Modifiers mods = modifierKeyState.GetModifiers();
+
+ ExternalPoint screenOffset = ViewAs<ExternalPixel>(
+ mWindow->WidgetToScreenOffset(),
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+
+ POINT cursor_pos;
+ ::GetCursorPos(&cursor_pos);
+ HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW));
+ ::ScreenToClient(wnd, &cursor_pos);
+ ScreenPoint position = {(float)cursor_pos.x, (float)cursor_pos.y};
+
+ PinchGestureInput event{pinchGestureType,
+ PinchGestureInput::TRACKPAD,
+ eventIntervalTime,
+ eventTimeStamp,
+ screenOffset,
+ position,
+ 100.0 * ((aPhase == Phase::eEnd) ? 1.f : aScale),
+ 100.0 * ((aPhase == Phase::eEnd) ? 1.f : mLastScale),
+ mods};
+
+ double deltaY = event.ComputeDeltaY(mWindow);
+ if (deltaY == 0.0) {
+ return false;
+ }
+ gfx::IntPoint lineOrPageDelta = PinchGestureInput::GetIntegerDeltaForEvent(
+ (aPhase == Phase::eStart), 0, deltaY);
+ event.mLineOrPageDeltaY = lineOrPageDelta.y;
+
+ mWindow->SendAnAPZEvent(event);
+
+ return true;
+}
+
+void DManipEventHandler::SendPan(Phase aPhase, float x, float y,
+ bool aIsInertia) {
+ if (!mWindow) {
+ return;
+ }
+
+ PanGestureInput::PanGestureType panGestureType =
+ PanGestureInput::PANGESTURE_PAN;
+ if (aIsInertia) {
+ switch (aPhase) {
+ case Phase::eStart:
+ panGestureType = PanGestureInput::PANGESTURE_MOMENTUMSTART;
+ break;
+ case Phase::eMiddle:
+ panGestureType = PanGestureInput::PANGESTURE_MOMENTUMPAN;
+ break;
+ case Phase::eEnd:
+ panGestureType = PanGestureInput::PANGESTURE_MOMENTUMEND;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("handle all enum values");
+ }
+ } else {
+ switch (aPhase) {
+ case Phase::eStart:
+ panGestureType = PanGestureInput::PANGESTURE_START;
+ break;
+ case Phase::eMiddle:
+ panGestureType = PanGestureInput::PANGESTURE_PAN;
+ break;
+ case Phase::eEnd:
+ panGestureType = PanGestureInput::PANGESTURE_END;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("handle all enum values");
+ }
+ }
+
+ PRIntervalTime eventIntervalTime = PR_IntervalNow();
+ TimeStamp eventTimeStamp = TimeStamp::Now();
+
+ ModifierKeyState modifierKeyState;
+ Modifiers mods = modifierKeyState.GetModifiers();
+
+ POINT cursor_pos;
+ ::GetCursorPos(&cursor_pos);
+ HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW));
+ ::ScreenToClient(wnd, &cursor_pos);
+ ScreenPoint position = {(float)cursor_pos.x, (float)cursor_pos.y};
+
+ PanGestureInput event{panGestureType, eventIntervalTime, eventTimeStamp,
+ position, ScreenPoint(x, y), mods};
+
+ gfx::IntPoint lineOrPageDelta =
+ PanGestureInput::GetIntegerDeltaForEvent((aPhase == Phase::eStart), x, y);
+ event.mLineOrPageDeltaX = lineOrPageDelta.x;
+ event.mLineOrPageDeltaY = lineOrPageDelta.y;
+
+ mWindow->SendAnAPZEvent(event);
+}
+
+#endif // !defined(__MINGW32__) && !defined(__MINGW64__)
+
+void DirectManipulationOwner::Init(const LayoutDeviceIntRect& aBounds) {
+#if !defined(__MINGW32__) && !defined(__MINGW64__)
+ HRESULT hr = CoCreateInstance(
+ CLSID_DirectManipulationManager, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IDirectManipulationManager, getter_AddRefs(mDmManager));
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("CoCreateInstance(CLSID_DirectManipulationManager failed");
+ mDmManager = nullptr;
+ return;
+ }
+
+ hr = mDmManager->GetUpdateManager(IID_IDirectManipulationUpdateManager,
+ getter_AddRefs(mDmUpdateManager));
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("GetUpdateManager failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ return;
+ }
+
+ HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW));
+
+ hr = mDmManager->CreateViewport(nullptr, wnd, IID_IDirectManipulationViewport,
+ getter_AddRefs(mDmViewport));
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("CreateViewport failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ return;
+ }
+
+ DIRECTMANIPULATION_CONFIGURATION configuration =
+ DIRECTMANIPULATION_CONFIGURATION_INTERACTION |
+ DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X |
+ DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y |
+ DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA |
+ DIRECTMANIPULATION_CONFIGURATION_RAILS_X |
+ DIRECTMANIPULATION_CONFIGURATION_RAILS_Y;
+ if (StaticPrefs::apz_allow_zooming()) {
+ configuration |= DIRECTMANIPULATION_CONFIGURATION_SCALING;
+ }
+
+ hr = mDmViewport->ActivateConfiguration(configuration);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("ActivateConfiguration failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ return;
+ }
+
+ hr = mDmViewport->SetViewportOptions(
+ DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("SetViewportOptions failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ return;
+ }
+
+ mDmHandler = new DManipEventHandler(mWindow, this, aBounds);
+
+ hr = mDmViewport->AddEventHandler(wnd, mDmHandler.get(),
+ &mDmViewportHandlerCookie);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("AddEventHandler failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ mDmHandler = nullptr;
+ return;
+ }
+
+ RECT rect = {0, 0, aBounds.Width(), aBounds.Height()};
+ hr = mDmViewport->SetViewportRect(&rect);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("SetViewportRect failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ mDmHandler = nullptr;
+ return;
+ }
+
+ hr = mDmManager->Activate(wnd);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("manager Activate failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ mDmHandler = nullptr;
+ return;
+ }
+
+ hr = mDmViewport->Enable();
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("mDmViewport->Enable failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ mDmHandler = nullptr;
+ return;
+ }
+
+ hr = mDmUpdateManager->Update(nullptr);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("mDmUpdateManager->Update failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ mDmHandler = nullptr;
+ return;
+ }
+#endif // !defined(__MINGW32__) && !defined(__MINGW64__)
+}
+
+void DirectManipulationOwner::ResizeViewport(
+ const LayoutDeviceIntRect& aBounds) {
+#if !defined(__MINGW32__) && !defined(__MINGW64__)
+ if (mDmHandler) {
+ mDmHandler->mBounds = aBounds;
+ }
+
+ if (mDmViewport) {
+ RECT rect = {0, 0, aBounds.Width(), aBounds.Height()};
+ HRESULT hr = mDmViewport->SetViewportRect(&rect);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("SetViewportRect failed");
+ }
+ }
+#endif
+}
+
+void DirectManipulationOwner::Destroy() {
+#if !defined(__MINGW32__) && !defined(__MINGW64__)
+ if (mDmHandler) {
+ mDmHandler->mWindow = nullptr;
+ mDmHandler->mOwner = nullptr;
+ if (mDmHandler->mObserver) {
+ gfxWindowsPlatform::GetPlatform()
+ ->GetHardwareVsync()
+ ->RemoveGenericObserver(mDmHandler->mObserver);
+ mDmHandler->mObserver->ClearOwner();
+ mDmHandler->mObserver = nullptr;
+ }
+ }
+
+ HRESULT hr;
+ if (mDmViewport) {
+ hr = mDmViewport->Stop();
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("mDmViewport->Stop() failed");
+ }
+
+ hr = mDmViewport->Disable();
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("mDmViewport->Disable() failed");
+ }
+
+ hr = mDmViewport->RemoveEventHandler(mDmViewportHandlerCookie);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("mDmViewport->RemoveEventHandler() failed");
+ }
+
+ hr = mDmViewport->Abandon();
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("mDmViewport->Abandon() failed");
+ }
+ }
+
+ if (mWindow) {
+ HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW));
+
+ if (mDmManager) {
+ hr = mDmManager->Deactivate(wnd);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("mDmManager->Deactivate() failed");
+ }
+ }
+ }
+
+ mDmHandler = nullptr;
+ mDmViewport = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmManager = nullptr;
+#endif // !defined(__MINGW32__) && !defined(__MINGW64__)
+ mWindow = nullptr;
+}
+
+void DirectManipulationOwner::SetContact(UINT aContactId) {
+#if !defined(__MINGW32__) && !defined(__MINGW64__)
+ if (mDmViewport) {
+ mDmViewport->SetContact(aContactId);
+ }
+#endif
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/DirectManipulationOwner.h b/widget/windows/DirectManipulationOwner.h
new file mode 100644
index 0000000000..dd78dcd099
--- /dev/null
+++ b/widget/windows/DirectManipulationOwner.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 DirectManipulationOwner_h__
+#define DirectManipulationOwner_h__
+
+#include <windows.h>
+#include "Units.h"
+
+class nsWindow;
+class IDirectManipulationManager;
+class IDirectManipulationUpdateManager;
+class IDirectManipulationViewport;
+
+namespace mozilla {
+namespace widget {
+
+class DManipEventHandler;
+
+class DirectManipulationOwner {
+ public:
+ typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
+
+ explicit DirectManipulationOwner(nsWindow* aWindow);
+ ~DirectManipulationOwner();
+ void Init(const LayoutDeviceIntRect& aBounds);
+ void ResizeViewport(const LayoutDeviceIntRect& aBounds);
+ void Destroy();
+
+ void SetContact(UINT aContactId);
+
+ void Update();
+
+ private:
+ nsWindow* mWindow;
+#if !defined(__MINGW32__) && !defined(__MINGW64__)
+ DWORD mDmViewportHandlerCookie;
+ RefPtr<IDirectManipulationManager> mDmManager;
+ RefPtr<IDirectManipulationUpdateManager> mDmUpdateManager;
+ RefPtr<IDirectManipulationViewport> mDmViewport;
+ RefPtr<DManipEventHandler> mDmHandler;
+#endif
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef DirectManipulationOwner_h__
diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp
new file mode 100644
index 0000000000..bf2b083729
--- /dev/null
+++ b/widget/windows/GfxInfo.cpp
@@ -0,0 +1,2058 @@
+/* -*- 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 <windows.h>
+#include <devguid.h> // for GUID_DEVCLASS_BATTERY
+#include <setupapi.h> // for SetupDi*
+#include <winioctl.h> // for IOCTL_*
+#include <batclass.h> // for BATTERY_*
+#include "gfxConfig.h"
+#include "gfxWindowsPlatform.h"
+#include "GfxInfo.h"
+#include "nsUnicharUtils.h"
+#include "prenv.h"
+#include "prprf.h"
+#include "GfxDriverInfo.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/SSE.h"
+#include "nsExceptionHandler.h"
+#include "nsPrintfCString.h"
+#include "jsapi.h"
+#include <intrin.h>
+
+#define NS_CRASHREPORTER_CONTRACTID "@mozilla.org/toolkit/crash-reporter;1"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+
+#ifdef DEBUG
+NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug)
+#endif
+
+GfxInfo::GfxInfo()
+ : mWindowsVersion(0),
+ mWindowsBuildNumber(0),
+ mActiveGPUIndex(0),
+ mHasDualGPU(false),
+ mHasBattery(false) {}
+
+/* GetD2DEnabled and GetDwriteEnabled shouldn't be called until after
+ * gfxPlatform initialization has occurred because they depend on it for
+ * information. (See bug 591561) */
+nsresult GfxInfo::GetD2DEnabled(bool* aEnabled) {
+ // Telemetry queries this during XPCOM initialization, and there's no
+ // gfxPlatform by then. Just bail out if gfxPlatform isn't initialized.
+ if (!gfxPlatform::Initialized()) {
+ *aEnabled = false;
+ return NS_OK;
+ }
+
+ // We check gfxConfig rather than the actual render mode, since the UI
+ // process does not use Direct2D if the GPU process is enabled. However,
+ // content processes can still use Direct2D.
+ *aEnabled = gfx::gfxConfig::IsEnabled(gfx::Feature::DIRECT2D);
+ return NS_OK;
+}
+
+nsresult GfxInfo::GetDWriteEnabled(bool* aEnabled) {
+ *aEnabled = gfxWindowsPlatform::GetPlatform()->DWriteEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDWriteVersion(nsAString& aDwriteVersion) {
+ gfxWindowsPlatform::GetDLLVersion(L"dwrite.dll", aDwriteVersion);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetHasBattery(bool* aHasBattery) {
+ *aHasBattery = mHasBattery;
+ return NS_OK;
+}
+
+int32_t GfxInfo::GetMaxRefreshRate(bool* aMixed) {
+ int32_t maxRefreshRate = -1;
+ if (aMixed) {
+ *aMixed = false;
+ }
+
+ for (auto displayInfo : mDisplayInfo) {
+ int32_t refreshRate = int32_t(displayInfo.mRefreshRate);
+ if (aMixed && maxRefreshRate > 0 && maxRefreshRate != refreshRate) {
+ *aMixed = true;
+ }
+ maxRefreshRate = std::max(maxRefreshRate, refreshRate);
+ }
+ return maxRefreshRate;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetEmbeddedInFirefoxReality(bool* aEmbeddedInFirefoxReality) {
+ *aEmbeddedInFirefoxReality = gfxVars::FxREmbedded();
+ return NS_OK;
+}
+
+#define PIXEL_STRUCT_RGB 1
+#define PIXEL_STRUCT_BGR 2
+
+NS_IMETHODIMP
+GfxInfo::GetCleartypeParameters(nsAString& aCleartypeParams) {
+ nsTArray<ClearTypeParameterInfo> clearTypeParams;
+
+ gfxWindowsPlatform::GetPlatform()->GetCleartypeParams(clearTypeParams);
+ uint32_t d, numDisplays = clearTypeParams.Length();
+ bool displayNames = (numDisplays > 1);
+ bool foundData = false;
+ nsString outStr;
+
+ for (d = 0; d < numDisplays; d++) {
+ ClearTypeParameterInfo& params = clearTypeParams[d];
+
+ if (displayNames) {
+ outStr.AppendPrintf("%S [ ", params.displayName.get());
+ }
+
+ if (params.gamma >= 0) {
+ foundData = true;
+ outStr.AppendPrintf("Gamma: %.4g ", params.gamma / 1000.0);
+ }
+
+ if (params.pixelStructure >= 0) {
+ foundData = true;
+ if (params.pixelStructure == PIXEL_STRUCT_RGB ||
+ params.pixelStructure == PIXEL_STRUCT_BGR) {
+ outStr.AppendPrintf(
+ "Pixel Structure: %S ",
+ (params.pixelStructure == PIXEL_STRUCT_RGB ? u"RGB" : u"BGR"));
+ } else {
+ outStr.AppendPrintf("Pixel Structure: %d ", params.pixelStructure);
+ }
+ }
+
+ if (params.clearTypeLevel >= 0) {
+ foundData = true;
+ outStr.AppendPrintf("ClearType Level: %d ", params.clearTypeLevel);
+ }
+
+ if (params.enhancedContrast >= 0) {
+ foundData = true;
+ outStr.AppendPrintf("Enhanced Contrast: %d ", params.enhancedContrast);
+ }
+
+ if (displayNames) {
+ outStr.Append(u"] ");
+ }
+ }
+
+ if (foundData) {
+ aCleartypeParams.Assign(outStr);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetWindowProtocol(nsAString& aWindowProtocol) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDesktopEnvironment(nsAString& aDesktopEnvironment) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+static nsresult GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName,
+ uint32_t& destValue, int type) {
+ MOZ_ASSERT(type == REG_DWORD || type == REG_QWORD);
+ HKEY key;
+ DWORD dwcbData;
+ DWORD dValue;
+ DWORD resultType;
+ LONG result;
+ nsresult retval = NS_OK;
+
+ result =
+ RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key);
+ if (result != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ switch (type) {
+ case REG_DWORD: {
+ // We only use this for vram size
+ dwcbData = sizeof(dValue);
+ result = RegQueryValueExW(key, keyName, nullptr, &resultType,
+ (LPBYTE)&dValue, &dwcbData);
+ if (result == ERROR_SUCCESS && resultType == REG_DWORD) {
+ destValue = (uint32_t)(dValue / 1024 / 1024);
+ } else {
+ retval = NS_ERROR_FAILURE;
+ }
+ break;
+ }
+ case REG_QWORD: {
+ // We only use this for vram size
+ LONGLONG qValue;
+ dwcbData = sizeof(qValue);
+ result = RegQueryValueExW(key, keyName, nullptr, &resultType,
+ (LPBYTE)&qValue, &dwcbData);
+ if (result == ERROR_SUCCESS && resultType == REG_QWORD) {
+ destValue = (uint32_t)(qValue / 1024 / 1024);
+ } else {
+ retval = NS_ERROR_FAILURE;
+ }
+ break;
+ }
+ }
+ RegCloseKey(key);
+
+ return retval;
+}
+
+static nsresult GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName,
+ nsAString& destString, int type) {
+ MOZ_ASSERT(type == REG_MULTI_SZ);
+
+ HKEY key;
+ DWORD dwcbData;
+ DWORD resultType;
+ LONG result;
+ nsresult retval = NS_OK;
+
+ result =
+ RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key);
+ if (result != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // A chain of null-separated strings; we convert the nulls to spaces
+ WCHAR wCharValue[1024];
+ dwcbData = sizeof(wCharValue);
+
+ result = RegQueryValueExW(key, keyName, nullptr, &resultType,
+ (LPBYTE)wCharValue, &dwcbData);
+ if (result == ERROR_SUCCESS && resultType == REG_MULTI_SZ) {
+ // This bit here could probably be cleaner.
+ bool isValid = false;
+
+ DWORD strLen = dwcbData / sizeof(wCharValue[0]);
+ for (DWORD i = 0; i < strLen; i++) {
+ if (wCharValue[i] == '\0') {
+ if (i < strLen - 1 && wCharValue[i + 1] == '\0') {
+ isValid = true;
+ break;
+ } else {
+ wCharValue[i] = ' ';
+ }
+ }
+ }
+
+ // ensure wCharValue is null terminated
+ wCharValue[strLen - 1] = '\0';
+
+ if (isValid) destString = wCharValue;
+
+ } else {
+ retval = NS_ERROR_FAILURE;
+ }
+
+ RegCloseKey(key);
+
+ return retval;
+}
+
+static nsresult GetKeyValues(const WCHAR* keyLocation, const WCHAR* keyName,
+ nsTArray<nsString>& destStrings) {
+ // First ask for the size of the value
+ DWORD size;
+ LONG rv = RegGetValueW(HKEY_LOCAL_MACHINE, keyLocation, keyName,
+ RRF_RT_REG_MULTI_SZ, nullptr, nullptr, &size);
+ if (rv != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Create a buffer with the proper size and retrieve the value
+ WCHAR* wCharValue = new WCHAR[size / sizeof(WCHAR)];
+ rv = RegGetValueW(HKEY_LOCAL_MACHINE, keyLocation, keyName,
+ RRF_RT_REG_MULTI_SZ, nullptr, (LPBYTE)wCharValue, &size);
+ if (rv != ERROR_SUCCESS) {
+ delete[] wCharValue;
+ return NS_ERROR_FAILURE;
+ }
+
+ // The value is a sequence of null-terminated strings, usually terminated by
+ // an empty string (\0). RegGetValue ensures that the value is properly
+ // terminated with a null character.
+ DWORD i = 0;
+ DWORD strLen = size / sizeof(WCHAR);
+ while (i < strLen) {
+ nsString value(wCharValue + i);
+ if (!value.IsEmpty()) {
+ destStrings.AppendElement(value);
+ }
+ i += value.Length() + 1;
+ }
+ delete[] wCharValue;
+
+ return NS_OK;
+}
+
+// The device ID is a string like PCI\VEN_15AD&DEV_0405&SUBSYS_040515AD
+// this function is used to extract the id's out of it
+uint32_t ParseIDFromDeviceID(const nsAString& key, const char* prefix,
+ int length) {
+ nsAutoString id(key);
+ ToUpperCase(id);
+ int32_t start = id.Find(prefix);
+ if (start != -1) {
+ id.Cut(0, start + strlen(prefix));
+ id.Truncate(length);
+ }
+ if (id.Equals(L"QCOM", nsCaseInsensitiveStringComparator)) {
+ // String format assumptions are broken, so use a Qualcomm PCI Vendor ID
+ // for now. See also GfxDriverInfo::GetDeviceVendor.
+ return 0x5143;
+ }
+ nsresult err;
+ return id.ToInteger(&err, 16);
+}
+
+// OS version in 16.16 major/minor form
+// based on http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx
+enum {
+ kWindowsUnknown = 0,
+ kWindows7 = 0x60001,
+ kWindows8 = 0x60002,
+ kWindows8_1 = 0x60003,
+ kWindows10 = 0xA0000
+};
+
+static bool HasBattery() {
+ // Helper classes to manage lifetimes of Windows structs.
+ class MOZ_STACK_CLASS HDevInfoHolder final {
+ public:
+ explicit HDevInfoHolder(HDEVINFO aHandle) : mHandle(aHandle) {}
+
+ ~HDevInfoHolder() { ::SetupDiDestroyDeviceInfoList(mHandle); }
+
+ private:
+ HDEVINFO mHandle;
+ };
+
+ class MOZ_STACK_CLASS HandleHolder final {
+ public:
+ explicit HandleHolder(HANDLE aHandle) : mHandle(aHandle) {}
+
+ ~HandleHolder() { ::CloseHandle(mHandle); }
+
+ private:
+ HANDLE mHandle;
+ };
+
+ HDEVINFO hdev =
+ ::SetupDiGetClassDevs(&GUID_DEVCLASS_BATTERY, nullptr, nullptr,
+ DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+ if (hdev == INVALID_HANDLE_VALUE) {
+ return true;
+ }
+
+ HDevInfoHolder hdevHolder(hdev);
+
+ DWORD i = 0;
+ SP_DEVICE_INTERFACE_DATA did = {0};
+ did.cbSize = sizeof(did);
+
+ while (::SetupDiEnumDeviceInterfaces(hdev, nullptr, &GUID_DEVCLASS_BATTERY, i,
+ &did)) {
+ DWORD bufferSize = 0;
+ ::SetupDiGetDeviceInterfaceDetail(hdev, &did, nullptr, 0, &bufferSize,
+ nullptr);
+ if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ return true;
+ }
+
+ UniquePtr<uint8_t[]> buffer(new (std::nothrow) uint8_t[bufferSize]);
+ if (!buffer) {
+ return true;
+ }
+
+ PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd =
+ reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(buffer.get());
+ pdidd->cbSize = sizeof(*pdidd);
+ if (!::SetupDiGetDeviceInterfaceDetail(hdev, &did, pdidd, bufferSize,
+ &bufferSize, nullptr)) {
+ return true;
+ }
+
+ HANDLE hbat = ::CreateFile(pdidd->DevicePath, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
+ if (hbat == INVALID_HANDLE_VALUE) {
+ return true;
+ }
+
+ HandleHolder hbatHolder(hbat);
+
+ BATTERY_QUERY_INFORMATION bqi = {0};
+ DWORD dwWait = 0;
+ DWORD dwOut;
+
+ // We need the tag to query the information below.
+ if (!::DeviceIoControl(hbat, IOCTL_BATTERY_QUERY_TAG, &dwWait,
+ sizeof(dwWait), &bqi.BatteryTag,
+ sizeof(bqi.BatteryTag), &dwOut, nullptr) ||
+ !bqi.BatteryTag) {
+ return true;
+ }
+
+ BATTERY_INFORMATION bi = {0};
+ bqi.InformationLevel = BatteryInformation;
+
+ if (!::DeviceIoControl(hbat, IOCTL_BATTERY_QUERY_INFORMATION, &bqi,
+ sizeof(bqi), &bi, sizeof(bi), &dwOut, nullptr)) {
+ return true;
+ }
+
+ // If a battery intended for general use (i.e. system use) is not a UPS
+ // (i.e. short term), then we know for certain we have a battery.
+ if ((bi.Capabilities & BATTERY_SYSTEM_BATTERY) &&
+ !(bi.Capabilities & BATTERY_IS_SHORT_TERM)) {
+ return true;
+ }
+
+ // Otherwise we check the next battery.
+ ++i;
+ }
+
+ // If we fail to enumerate because there are no more batteries to check, then
+ // we can safely say there are indeed no system batteries.
+ return ::GetLastError() != ERROR_NO_MORE_ITEMS;
+}
+
+/* Other interesting places for info:
+ * IDXGIAdapter::GetDesc()
+ * IDirectDraw7::GetAvailableVidMem()
+ * e->GetAvailableTextureMem()
+ * */
+
+#define DEVICE_KEY_PREFIX L"\\Registry\\Machine\\"
+nsresult GfxInfo::Init() {
+ nsresult rv = GfxInfoBase::Init();
+ mHasBattery = HasBattery();
+
+ DISPLAY_DEVICEW displayDevice;
+ displayDevice.cb = sizeof(displayDevice);
+ int deviceIndex = 0;
+
+ const char* spoofedWindowsVersion =
+ PR_GetEnv("MOZ_GFX_SPOOF_WINDOWS_VERSION");
+ if (spoofedWindowsVersion) {
+ PR_sscanf(spoofedWindowsVersion, "%x,%u", &mWindowsVersion,
+ &mWindowsBuildNumber);
+ } else {
+ OSVERSIONINFO vinfo;
+ vinfo.dwOSVersionInfoSize = sizeof(vinfo);
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable : 4996)
+#endif
+ if (!GetVersionEx(&vinfo)) {
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+ mWindowsVersion = kWindowsUnknown;
+ } else {
+ mWindowsVersion =
+ int32_t(vinfo.dwMajorVersion << 16) + vinfo.dwMinorVersion;
+ mWindowsBuildNumber = vinfo.dwBuildNumber;
+ }
+ }
+
+ mDeviceKeyDebug = u"PrimarySearch"_ns;
+
+ while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0)) {
+ if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
+ mDeviceKeyDebug = u"NullSearch"_ns;
+ break;
+ }
+ deviceIndex++;
+ }
+
+ // make sure the string is nullptr terminated
+ if (wcsnlen(displayDevice.DeviceKey, ArrayLength(displayDevice.DeviceKey)) ==
+ ArrayLength(displayDevice.DeviceKey)) {
+ // we did not find a nullptr
+ return rv;
+ }
+
+ mDeviceKeyDebug = displayDevice.DeviceKey;
+
+ /* DeviceKey is "reserved" according to MSDN so we'll be careful with it */
+ /* check that DeviceKey begins with DEVICE_KEY_PREFIX */
+ /* some systems have a DeviceKey starting with \REGISTRY\Machine\ so we need
+ * to compare case insenstively */
+ /* If the device key is empty, we are most likely in a remote desktop
+ * environment. In this case we set the devicekey to an empty string so
+ * it can be handled later.
+ */
+ if (displayDevice.DeviceKey[0] != '\0') {
+ if (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX,
+ ArrayLength(DEVICE_KEY_PREFIX) - 1) != 0) {
+ return rv;
+ }
+
+ // chop off DEVICE_KEY_PREFIX
+ mDeviceKey[0] =
+ displayDevice.DeviceKey + ArrayLength(DEVICE_KEY_PREFIX) - 1;
+ } else {
+ mDeviceKey[0].Truncate();
+ }
+
+ mDeviceID[0] = displayDevice.DeviceID;
+ mDeviceString[0] = displayDevice.DeviceString;
+
+ // On Windows 8 and Server 2012 hosts, we want to not block RDP
+ // sessions from attempting hardware acceleration. RemoteFX
+ // provides features and functionaltiy that can give a good D3D10 +
+ // D2D + DirectWrite experience emulated via a software GPU.
+ //
+ // Unfortunately, the Device ID is nullptr, and we can't enumerate
+ // it using the setup infrastructure (SetupDiGetClassDevsW below
+ // will return INVALID_HANDLE_VALUE).
+ UINT flags = DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES;
+ if (mWindowsVersion >= kWindows8 && mDeviceID[0].Length() == 0 &&
+ mDeviceString[0].EqualsLiteral("RDPUDD Chained DD")) {
+ WCHAR sysdir[255];
+ UINT len = GetSystemDirectory(sysdir, sizeof(sysdir));
+ if (len < sizeof(sysdir)) {
+ nsString rdpudd(sysdir);
+ rdpudd.AppendLiteral("\\rdpudd.dll");
+ gfxWindowsPlatform::GetDLLVersion(rdpudd.BeginReading(),
+ mDriverVersion[0]);
+ mDriverDate[0].AssignLiteral("01-01-1970");
+
+ // 0x1414 is Microsoft; 0xfefe is an invented (and unused) code
+ mDeviceID[0].AssignLiteral("PCI\\VEN_1414&DEV_FEFE&SUBSYS_00000000");
+ flags |= DIGCF_DEVICEINTERFACE;
+ }
+ }
+
+ /* create a device information set composed of the current display device */
+ HDEVINFO devinfo =
+ SetupDiGetClassDevsW(nullptr, mDeviceID[0].get(), nullptr, flags);
+
+ if (devinfo != INVALID_HANDLE_VALUE) {
+ HKEY key;
+ LONG result;
+ WCHAR value[255];
+ DWORD dwcbData;
+ SP_DEVINFO_DATA devinfoData;
+ DWORD memberIndex = 0;
+
+ devinfoData.cbSize = sizeof(devinfoData);
+ constexpr auto driverKeyPre =
+ u"System\\CurrentControlSet\\Control\\Class\\"_ns;
+ /* enumerate device information elements in the device information set */
+ while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData)) {
+ /* get a string that identifies the device's driver key */
+ if (SetupDiGetDeviceRegistryPropertyW(devinfo, &devinfoData, SPDRP_DRIVER,
+ nullptr, (PBYTE)value,
+ sizeof(value), nullptr)) {
+ nsAutoString driverKey(driverKeyPre);
+ driverKey += value;
+ result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, driverKey.get(), 0,
+ KEY_QUERY_VALUE, &key);
+ if (result == ERROR_SUCCESS) {
+ /* we've found the driver we're looking for */
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
+ (LPBYTE)value, &dwcbData);
+ if (result == ERROR_SUCCESS) {
+ mDriverVersion[0] = value;
+ } else {
+ // If the entry wasn't found, assume the worst (0.0.0.0).
+ mDriverVersion[0].AssignLiteral("0.0.0.0");
+ }
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
+ (LPBYTE)value, &dwcbData);
+ if (result == ERROR_SUCCESS) {
+ mDriverDate[0] = value;
+ } else {
+ // Again, assume the worst
+ mDriverDate[0].AssignLiteral("01-01-1970");
+ }
+ RegCloseKey(key);
+ break;
+ }
+ }
+ }
+
+ SetupDiDestroyDeviceInfoList(devinfo);
+ }
+
+ // It is convenient to have these as integers
+ uint32_t adapterVendorID[2] = {0, 0};
+ uint32_t adapterDeviceID[2] = {0, 0};
+ uint32_t adapterSubsysID[2] = {0, 0};
+
+ adapterVendorID[0] = ParseIDFromDeviceID(mDeviceID[0], "VEN_", 4);
+ adapterDeviceID[0] = ParseIDFromDeviceID(mDeviceID[0], "&DEV_", 4);
+ adapterSubsysID[0] = ParseIDFromDeviceID(mDeviceID[0], "&SUBSYS_", 8);
+
+ // Sometimes we don't get the valid device using this method. For now,
+ // allow zero vendor or device as valid, as long as the other value is
+ // non-zero.
+ bool foundValidDevice = (adapterVendorID[0] != 0 || adapterDeviceID[0] != 0);
+
+ // We now check for second display adapter. If we didn't find the valid
+ // device using the original approach, we will try the alternative.
+
+ // Device interface class for display adapters.
+ CLSID GUID_DISPLAY_DEVICE_ARRIVAL;
+ HRESULT hresult = CLSIDFromString(L"{1CA05180-A699-450A-9A0C-DE4FBE3DDD89}",
+ &GUID_DISPLAY_DEVICE_ARRIVAL);
+ if (hresult == NOERROR) {
+ devinfo =
+ SetupDiGetClassDevsW(&GUID_DISPLAY_DEVICE_ARRIVAL, nullptr, nullptr,
+ DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
+
+ if (devinfo != INVALID_HANDLE_VALUE) {
+ HKEY key;
+ LONG result;
+ WCHAR value[255];
+ DWORD dwcbData;
+ SP_DEVINFO_DATA devinfoData;
+ DWORD memberIndex = 0;
+ devinfoData.cbSize = sizeof(devinfoData);
+
+ nsAutoString adapterDriver2;
+ nsAutoString deviceID2;
+ nsAutoString driverVersion2;
+ nsAutoString driverDate2;
+
+ constexpr auto driverKeyPre =
+ u"System\\CurrentControlSet\\Control\\Class\\"_ns;
+ /* enumerate device information elements in the device information set */
+ while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData)) {
+ /* get a string that identifies the device's driver key */
+ if (SetupDiGetDeviceRegistryPropertyW(
+ devinfo, &devinfoData, SPDRP_DRIVER, nullptr, (PBYTE)value,
+ sizeof(value), nullptr)) {
+ nsAutoString driverKey2(driverKeyPre);
+ driverKey2 += value;
+ result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, driverKey2.get(), 0,
+ KEY_QUERY_VALUE, &key);
+ if (result == ERROR_SUCCESS) {
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"MatchingDeviceId", nullptr,
+ nullptr, (LPBYTE)value, &dwcbData);
+ if (result != ERROR_SUCCESS) {
+ continue;
+ }
+ deviceID2 = value;
+ adapterVendorID[1] = ParseIDFromDeviceID(deviceID2, "VEN_", 4);
+ adapterDeviceID[1] = ParseIDFromDeviceID(deviceID2, "&DEV_", 4);
+ // Skip the devices we already considered, as well as any
+ // "zero" ones.
+ if ((adapterVendorID[0] == adapterVendorID[1] &&
+ adapterDeviceID[0] == adapterDeviceID[1]) ||
+ (adapterVendorID[1] == 0 && adapterDeviceID[1] == 0)) {
+ RegCloseKey(key);
+ continue;
+ }
+
+ // If this device is missing driver information, it is unlikely to
+ // be a real display adapter.
+ if (NS_FAILED(GetKeyValue(driverKey2.get(),
+ L"InstalledDisplayDrivers",
+ adapterDriver2, REG_MULTI_SZ))) {
+ RegCloseKey(key);
+ continue;
+ }
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
+ (LPBYTE)value, &dwcbData);
+ if (result != ERROR_SUCCESS) {
+ RegCloseKey(key);
+ continue;
+ }
+ driverVersion2 = value;
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
+ (LPBYTE)value, &dwcbData);
+ if (result != ERROR_SUCCESS) {
+ RegCloseKey(key);
+ continue;
+ }
+ driverDate2 = value;
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"Device Description", nullptr,
+ nullptr, (LPBYTE)value, &dwcbData);
+ if (result != ERROR_SUCCESS) {
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverDesc", nullptr, nullptr,
+ (LPBYTE)value, &dwcbData);
+ }
+ RegCloseKey(key);
+ if (result == ERROR_SUCCESS) {
+ // If we didn't find a valid device with the original method
+ // take this one, and continue looking for the second GPU.
+ if (!foundValidDevice) {
+ foundValidDevice = true;
+ adapterVendorID[0] = adapterVendorID[1];
+ adapterDeviceID[0] = adapterDeviceID[1];
+ mDeviceString[0] = value;
+ mDeviceID[0] = deviceID2;
+ mDeviceKey[0] = driverKey2;
+ mDriverVersion[0] = driverVersion2;
+ mDriverDate[0] = driverDate2;
+ adapterSubsysID[0] =
+ ParseIDFromDeviceID(mDeviceID[0], "&SUBSYS_", 8);
+ continue;
+ }
+
+ mHasDualGPU = true;
+ mDeviceString[1] = value;
+ mDeviceID[1] = deviceID2;
+ mDeviceKey[1] = driverKey2;
+ mDriverVersion[1] = driverVersion2;
+ mDriverDate[1] = driverDate2;
+ adapterSubsysID[1] =
+ ParseIDFromDeviceID(mDeviceID[1], "&SUBSYS_", 8);
+ mAdapterVendorID[1].AppendPrintf("0x%04x", adapterVendorID[1]);
+ mAdapterDeviceID[1].AppendPrintf("0x%04x", adapterDeviceID[1]);
+ mAdapterSubsysID[1].AppendPrintf("%08x", adapterSubsysID[1]);
+ break;
+ }
+ }
+ }
+ }
+
+ SetupDiDestroyDeviceInfoList(devinfo);
+ }
+ }
+
+ mAdapterVendorID[0].AppendPrintf("0x%04x", adapterVendorID[0]);
+ mAdapterDeviceID[0].AppendPrintf("0x%04x", adapterDeviceID[0]);
+ mAdapterSubsysID[0].AppendPrintf("%08x", adapterSubsysID[0]);
+
+ // Sometimes, the enumeration is not quite right and the two adapters
+ // end up being swapped. Actually enumerate the adapters that come
+ // back from the DXGI factory to check, and tag the second as active
+ // if found.
+ if (mHasDualGPU) {
+ nsModuleHandle dxgiModule(LoadLibrarySystem32(L"dxgi.dll"));
+ decltype(CreateDXGIFactory)* createDXGIFactory =
+ (decltype(CreateDXGIFactory)*)GetProcAddress(dxgiModule,
+ "CreateDXGIFactory");
+
+ if (createDXGIFactory) {
+ RefPtr<IDXGIFactory> factory = nullptr;
+ createDXGIFactory(__uuidof(IDXGIFactory), (void**)(&factory));
+ if (factory) {
+ RefPtr<IDXGIAdapter> adapter;
+ if (SUCCEEDED(factory->EnumAdapters(0, getter_AddRefs(adapter)))) {
+ DXGI_ADAPTER_DESC desc;
+ PodZero(&desc);
+ if (SUCCEEDED(adapter->GetDesc(&desc))) {
+ if (desc.VendorId != adapterVendorID[0] &&
+ desc.DeviceId != adapterDeviceID[0] &&
+ desc.VendorId == adapterVendorID[1] &&
+ desc.DeviceId == adapterDeviceID[1]) {
+ mActiveGPUIndex = 1;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ mHasDriverVersionMismatch = false;
+ if (mAdapterVendorID[mActiveGPUIndex] ==
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::Intel)) {
+ // we've had big crashers (bugs 590373 and 595364) apparently correlated
+ // with bad Intel driver installations where the DriverVersion reported
+ // by the registry was not the version of the DLL.
+
+ // Note that these start without the .dll extension but eventually gain it.
+ bool is64bitApp = sizeof(void*) == 8;
+ nsAutoString dllFileName(is64bitApp ? u"igd10umd64" : u"igd10umd32");
+ nsAutoString dllFileName2(is64bitApp ? u"igd10iumd64" : u"igd10iumd32");
+
+ nsString dllVersion, dllVersion2;
+ uint64_t dllNumericVersion = 0, dllNumericVersion2 = 0,
+ driverNumericVersion = 0, knownSafeMismatchVersion = 0;
+
+ // Only parse the DLL version for those found in the driver list
+ nsAutoString eligibleDLLs;
+ if (NS_SUCCEEDED(GetAdapterDriver(eligibleDLLs))) {
+ if (FindInReadable(dllFileName, eligibleDLLs)) {
+ dllFileName += u".dll"_ns;
+ gfxWindowsPlatform::GetDLLVersion(dllFileName.get(), dllVersion);
+ ParseDriverVersion(dllVersion, &dllNumericVersion);
+ }
+ if (FindInReadable(dllFileName2, eligibleDLLs)) {
+ dllFileName2 += u".dll"_ns;
+ gfxWindowsPlatform::GetDLLVersion(dllFileName2.get(), dllVersion2);
+ ParseDriverVersion(dllVersion2, &dllNumericVersion2);
+ }
+ }
+
+ // Sometimes the DLL is not in the System32 nor SysWOW64 directories. But
+ // UserModeDriverName (or UserModeDriverNameWow, if available) might provide
+ // the full path to the DLL in some DriverStore FileRepository.
+ if (dllNumericVersion == 0 && dllNumericVersion2 == 0) {
+ nsTArray<nsString> eligibleDLLpaths;
+ const WCHAR* keyLocation = mDeviceKey[mActiveGPUIndex].get();
+ GetKeyValues(keyLocation, L"UserModeDriverName", eligibleDLLpaths);
+ GetKeyValues(keyLocation, L"UserModeDriverNameWow", eligibleDLLpaths);
+ size_t length = eligibleDLLpaths.Length();
+ for (size_t i = 0;
+ i < length && dllNumericVersion == 0 && dllNumericVersion2 == 0;
+ ++i) {
+ if (FindInReadable(dllFileName, eligibleDLLpaths[i])) {
+ gfxWindowsPlatform::GetDLLVersion(eligibleDLLpaths[i].get(),
+ dllVersion);
+ ParseDriverVersion(dllVersion, &dllNumericVersion);
+ } else if (FindInReadable(dllFileName2, eligibleDLLpaths[i])) {
+ gfxWindowsPlatform::GetDLLVersion(eligibleDLLpaths[i].get(),
+ dllVersion2);
+ ParseDriverVersion(dllVersion2, &dllNumericVersion2);
+ }
+ }
+ }
+
+ ParseDriverVersion(mDriverVersion[mActiveGPUIndex], &driverNumericVersion);
+ ParseDriverVersion(u"9.17.10.0"_ns, &knownSafeMismatchVersion);
+
+ // If there's a driver version mismatch, consider this harmful only when
+ // the driver version is less than knownSafeMismatchVersion. See the
+ // above comment about crashes with old mismatches. If the GetDllVersion
+ // call fails, we are not calling it a mismatch.
+ if ((dllNumericVersion != 0 && dllNumericVersion != driverNumericVersion) ||
+ (dllNumericVersion2 != 0 &&
+ dllNumericVersion2 != driverNumericVersion)) {
+ if (driverNumericVersion < knownSafeMismatchVersion ||
+ std::max(dllNumericVersion, dllNumericVersion2) <
+ knownSafeMismatchVersion) {
+ mHasDriverVersionMismatch = true;
+ gfxCriticalNoteOnce
+ << "Mismatched driver versions between the registry "
+ << NS_ConvertUTF16toUTF8(mDriverVersion[mActiveGPUIndex]).get()
+ << " and DLL(s) " << NS_ConvertUTF16toUTF8(dllVersion).get() << ", "
+ << NS_ConvertUTF16toUTF8(dllVersion2).get() << " reported.";
+ }
+ } else if (dllNumericVersion == 0 && dllNumericVersion2 == 0) {
+ // Leave it as an asserting error for now, to see if we can find
+ // a system that exhibits this kind of a problem internally.
+ gfxCriticalErrorOnce()
+ << "Potential driver version mismatch ignored due to missing DLLs "
+ << NS_ConvertUTF16toUTF8(dllFileName).get()
+ << " v=" << NS_ConvertUTF16toUTF8(dllVersion).get() << " and "
+ << NS_ConvertUTF16toUTF8(dllFileName2).get()
+ << " v=" << NS_ConvertUTF16toUTF8(dllVersion2).get();
+ }
+ }
+
+ // Get monitor information
+ for (int deviceIndex = 0;; deviceIndex++) {
+ DISPLAY_DEVICEA device;
+ device.cb = sizeof(device);
+ if (!::EnumDisplayDevicesA(nullptr, deviceIndex, &device, 0)) {
+ break;
+ }
+
+ if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE)) {
+ continue;
+ }
+
+ DEVMODEA mode;
+ mode.dmSize = sizeof(mode);
+ mode.dmDriverExtra = 0;
+ if (!::EnumDisplaySettingsA(device.DeviceName, ENUM_CURRENT_SETTINGS,
+ &mode)) {
+ continue;
+ }
+
+ DisplayInfo displayInfo;
+
+ displayInfo.mScreenWidth = mode.dmPelsWidth;
+ displayInfo.mScreenHeight = mode.dmPelsHeight;
+ displayInfo.mRefreshRate = mode.dmDisplayFrequency;
+ displayInfo.mIsPseudoDisplay =
+ !!(device.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER);
+
+ mDisplayInfo.AppendElement(displayInfo);
+ }
+
+ const char* spoofedDriverVersionString =
+ PR_GetEnv("MOZ_GFX_SPOOF_DRIVER_VERSION");
+ if (spoofedDriverVersionString) {
+ mDriverVersion[mActiveGPUIndex].AssignASCII(spoofedDriverVersionString);
+ }
+
+ const char* spoofedVendor = PR_GetEnv("MOZ_GFX_SPOOF_VENDOR_ID");
+ if (spoofedVendor) {
+ mAdapterVendorID[mActiveGPUIndex].AssignASCII(spoofedVendor);
+ }
+
+ const char* spoofedDevice = PR_GetEnv("MOZ_GFX_SPOOF_DEVICE_ID");
+ if (spoofedDevice) {
+ mAdapterDeviceID[mActiveGPUIndex].AssignASCII(spoofedDevice);
+ }
+
+ AddCrashReportAnnotations();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription(nsAString& aAdapterDescription) {
+ aAdapterDescription = mDeviceString[mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription2(nsAString& aAdapterDescription) {
+ aAdapterDescription = mDeviceString[1 - mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM(uint32_t* aAdapterRAM) {
+ uint32_t result = 0;
+ if (NS_FAILED(GetKeyValue(mDeviceKey[mActiveGPUIndex].get(),
+ L"HardwareInformation.qwMemorySize", result,
+ REG_QWORD)) ||
+ result == 0) {
+ if (NS_FAILED(GetKeyValue(mDeviceKey[mActiveGPUIndex].get(),
+ L"HardwareInformation.MemorySize", result,
+ REG_DWORD))) {
+ result = 0;
+ }
+ }
+ *aAdapterRAM = result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(uint32_t* aAdapterRAM) {
+ uint32_t result = 0;
+ if (mHasDualGPU) {
+ if (NS_FAILED(GetKeyValue(mDeviceKey[1 - mActiveGPUIndex].get(),
+ L"HardwareInformation.qwMemorySize", result,
+ REG_QWORD)) ||
+ result == 0) {
+ if (NS_FAILED(GetKeyValue(mDeviceKey[1 - mActiveGPUIndex].get(),
+ L"HardwareInformation.MemorySize", result,
+ REG_DWORD))) {
+ result = 0;
+ }
+ }
+ }
+ *aAdapterRAM = result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver(nsAString& aAdapterDriver) {
+ if (NS_FAILED(GetKeyValue(mDeviceKey[mActiveGPUIndex].get(),
+ L"InstalledDisplayDrivers", aAdapterDriver,
+ REG_MULTI_SZ)))
+ aAdapterDriver = L"Unknown";
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver2(nsAString& aAdapterDriver) {
+ if (!mHasDualGPU) {
+ aAdapterDriver.Truncate();
+ } else if (NS_FAILED(GetKeyValue(mDeviceKey[1 - mActiveGPUIndex].get(),
+ L"InstalledDisplayDrivers", aAdapterDriver,
+ REG_MULTI_SZ))) {
+ aAdapterDriver = L"Unknown";
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) {
+ aAdapterDriverVendor.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) {
+ aAdapterDriverVersion = mDriverVersion[mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate(nsAString& aAdapterDriverDate) {
+ aAdapterDriverDate = mDriverDate[mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) {
+ aAdapterDriverVendor.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion2(nsAString& aAdapterDriverVersion) {
+ aAdapterDriverVersion = mDriverVersion[1 - mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate2(nsAString& aAdapterDriverDate) {
+ aAdapterDriverDate = mDriverDate[1 - mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID(nsAString& aAdapterVendorID) {
+ aAdapterVendorID = mAdapterVendorID[mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString& aAdapterVendorID) {
+ aAdapterVendorID = mAdapterVendorID[1 - mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString& aAdapterDeviceID) {
+ aAdapterDeviceID = mAdapterDeviceID[mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID2(nsAString& aAdapterDeviceID) {
+ aAdapterDeviceID = mAdapterDeviceID[1 - mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID(nsAString& aAdapterSubsysID) {
+ aAdapterSubsysID = mAdapterSubsysID[mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID2(nsAString& aAdapterSubsysID) {
+ aAdapterSubsysID = mAdapterSubsysID[1 - mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active) {
+ // This is never the case, as the active GPU ends up being
+ // the first one. It should probably be removed.
+ *aIsGPU2Active = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDisplayInfo(nsTArray<nsString>& aDisplayInfo) {
+ for (auto displayInfo : mDisplayInfo) {
+ nsString value;
+ value.AppendPrintf("%dx%d@%dHz %s", displayInfo.mScreenWidth,
+ displayInfo.mScreenHeight, displayInfo.mRefreshRate,
+ displayInfo.mIsPseudoDisplay ? "Pseudo Display" : "");
+
+ aDisplayInfo.AppendElement(value);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDisplayWidth(nsTArray<uint32_t>& aDisplayWidth) {
+ for (auto displayInfo : mDisplayInfo) {
+ aDisplayWidth.AppendElement((uint32_t)displayInfo.mScreenWidth);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDisplayHeight(nsTArray<uint32_t>& aDisplayHeight) {
+ for (auto displayInfo : mDisplayInfo) {
+ aDisplayHeight.AppendElement((uint32_t)displayInfo.mScreenHeight);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDrmRenderDevice(nsACString& aDrmRenderDevice) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* Cisco's VPN software can cause corruption of the floating point state.
+ * Make a note of this in our crash reports so that some weird crashes
+ * make more sense */
+static void CheckForCiscoVPN() {
+ LONG result;
+ HKEY key;
+ /* This will give false positives, but hopefully no false negatives */
+ result =
+ RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Cisco Systems\\VPN Client",
+ 0, KEY_QUERY_VALUE, &key);
+ if (result == ERROR_SUCCESS) {
+ RegCloseKey(key);
+ CrashReporter::AppendAppNotesToCrashReport("Cisco VPN\n"_ns);
+ }
+}
+
+void GfxInfo::AddCrashReportAnnotations() {
+ CheckForCiscoVPN();
+
+ if (mHasDriverVersionMismatch) {
+ CrashReporter::AppendAppNotesToCrashReport("DriverVersionMismatch\n"_ns);
+ }
+
+ nsString deviceID, vendorID, driverVersion, subsysID;
+ nsCString narrowDeviceID, narrowVendorID, narrowDriverVersion, narrowSubsysID;
+
+ GetAdapterDeviceID(deviceID);
+ CopyUTF16toUTF8(deviceID, narrowDeviceID);
+ GetAdapterVendorID(vendorID);
+ CopyUTF16toUTF8(vendorID, narrowVendorID);
+ GetAdapterDriverVersion(driverVersion);
+ CopyUTF16toUTF8(driverVersion, narrowDriverVersion);
+ GetAdapterSubsysID(subsysID);
+ CopyUTF16toUTF8(subsysID, narrowSubsysID);
+
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterVendorID,
+ narrowVendorID);
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterDeviceID,
+ narrowDeviceID);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::AdapterDriverVersion, narrowDriverVersion);
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterSubsysID,
+ narrowSubsysID);
+
+ /* Add an App Note, this contains extra information. */
+ nsAutoCString note;
+
+ // TODO: We should probably convert this into a proper annotation
+ if (vendorID == GfxDriverInfo::GetDeviceVendor(DeviceVendor::All)) {
+ /* if we didn't find a valid vendorID lets append the mDeviceID string to
+ * try to find out why */
+ LossyAppendUTF16toASCII(mDeviceID[mActiveGPUIndex], note);
+ note.AppendLiteral(", ");
+ LossyAppendUTF16toASCII(mDeviceKeyDebug, note);
+ }
+ note.AppendLiteral("\n");
+
+ if (mHasDualGPU) {
+ nsString deviceID2, vendorID2, subsysID2;
+ nsAutoString adapterDriverVersionString2;
+ nsCString narrowDeviceID2, narrowVendorID2, narrowSubsysID2;
+
+ // Make a slight difference between the two cases so that we
+ // can see it in the crash reports. It may come in handy.
+ if (mActiveGPUIndex == 1) {
+ note.AppendLiteral("Has dual GPUs. GPU-#2: ");
+ } else {
+ note.AppendLiteral("Has dual GPUs. GPU #2: ");
+ }
+ GetAdapterDeviceID2(deviceID2);
+ CopyUTF16toUTF8(deviceID2, narrowDeviceID2);
+ GetAdapterVendorID2(vendorID2);
+ CopyUTF16toUTF8(vendorID2, narrowVendorID2);
+ GetAdapterDriverVersion2(adapterDriverVersionString2);
+ GetAdapterSubsysID(subsysID2);
+ CopyUTF16toUTF8(subsysID2, narrowSubsysID2);
+ note.AppendLiteral("AdapterVendorID2: ");
+ note.Append(narrowVendorID2);
+ note.AppendLiteral(", AdapterDeviceID2: ");
+ note.Append(narrowDeviceID2);
+ note.AppendLiteral(", AdapterSubsysID2: ");
+ note.Append(narrowSubsysID2);
+ note.AppendLiteral(", AdapterDriverVersion2: ");
+ note.Append(NS_LossyConvertUTF16toASCII(adapterDriverVersionString2));
+ }
+ CrashReporter::AppendAppNotesToCrashReport(note);
+}
+
+static OperatingSystem WindowsVersionToOperatingSystem(
+ int32_t aWindowsVersion) {
+ switch (aWindowsVersion) {
+ case kWindows7:
+ return OperatingSystem::Windows7;
+ case kWindows8:
+ return OperatingSystem::Windows8;
+ case kWindows8_1:
+ return OperatingSystem::Windows8_1;
+ case kWindows10:
+ return OperatingSystem::Windows10;
+ case kWindowsUnknown:
+ default:
+ return OperatingSystem::Unknown;
+ }
+}
+
+static bool OnlyAllowFeatureOnWhitelistedVendor(int32_t aFeature) {
+ switch (aFeature) {
+ // The GPU process doesn't need hardware acceleration and can run on
+ // devices that we normally block from not being on our whitelist.
+ case nsIGfxInfo::FEATURE_GPU_PROCESS:
+ // We can mostly assume that ANGLE will work
+ case nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE:
+ // Software WebRender is our Basic compositor replacement. It needs to
+ // always work.
+ case nsIGfxInfo::FEATURE_WEBRENDER_SOFTWARE:
+ return false;
+ default:
+ return true;
+ }
+}
+
+// Return true if the CPU supports AVX, but the operating system does not.
+#if defined(_M_X64)
+static inline bool DetectBrokenAVX() {
+ int regs[4];
+ __cpuid(regs, 0);
+ if (regs[0] == 0) {
+ // Level not supported.
+ return false;
+ }
+
+ __cpuid(regs, 1);
+
+ const unsigned AVX = 1u << 28;
+ const unsigned XSAVE = 1u << 26;
+ if ((regs[2] & (AVX | XSAVE)) != (AVX | XSAVE)) {
+ // AVX is not supported on this CPU.
+ return false;
+ }
+
+ const unsigned OSXSAVE = 1u << 27;
+ if ((regs[2] & OSXSAVE) != OSXSAVE) {
+ // AVX is supported, but the OS didn't enable it.
+ // This can be forced via bcdedit /set xsavedisable 1.
+ return true;
+ }
+
+ const unsigned AVX_CTRL_BITS = (1 << 1) | (1 << 2);
+ return (xgetbv(0) & AVX_CTRL_BITS) != AVX_CTRL_BITS;
+}
+#endif
+
+const nsTArray<GfxDriverInfo>& GfxInfo::GetGfxDriverInfo() {
+ if (!sDriverInfo->Length()) {
+ /*
+ * It should be noted here that more specialized rules on certain features
+ * should be inserted -before- more generalized restriction. As the first
+ * match for feature/OS/device found in the list will be used for the final
+ * blocklisting call.
+ */
+
+ /*
+ * NVIDIA entries
+ */
+ /*
+ * The last 5 digit of the NVIDIA driver version maps to the version that
+ * NVIDIA uses. The minor version (15, 16, 17) corresponds roughtly to the
+ * OS (Vista, Win7, Win7) but they show up in smaller numbers across all
+ * OS versions (perhaps due to OS upgrades). So we want to support
+ * October 2009+ drivers across all these minor versions.
+ *
+ * 187.45 (late October 2009) and earlier contain a bug which can cause us
+ * to crash on shutdown.
+ */
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::NvidiaAll,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN_OR_EQUAL, V(8, 15, 11, 8745),
+ "FEATURE_FAILURE_NV_W7_15", "nVidia driver > 187.45");
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows7, DeviceFamily::NvidiaAll,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_INCLUSIVE_START, V(8, 16, 10, 0000), V(8, 16, 11, 8745),
+ "FEATURE_FAILURE_NV_W7_16", "nVidia driver > 187.45");
+ // Telemetry doesn't show any driver in this range so it might not even be
+ // required.
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows7, DeviceFamily::NvidiaAll,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_INCLUSIVE_START, V(8, 17, 10, 0000), V(8, 17, 11, 8745),
+ "FEATURE_FAILURE_NV_W7_17", "nVidia driver > 187.45");
+
+ /*
+ * AMD/ATI entries. 8.56.1.15 is the driver that shipped with Windows 7 RTM
+ */
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows, DeviceFamily::AtiAll,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN, V(8, 56, 1, 15), "FEATURE_FAILURE_AMD1", "8.56.1.15");
+
+ // Bug 1099252
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows7, DeviceFamily::AtiAll,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_EQUAL, V(8, 832, 0, 0), "FEATURE_FAILURE_BUG_1099252");
+
+ // Bug 1118695
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows7, DeviceFamily::AtiAll,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_EQUAL, V(8, 783, 2, 2000), "FEATURE_FAILURE_BUG_1118695");
+
+ // Bug 1587155
+ //
+ // There are a several reports of strange rendering corruptions with this
+ // driver version, with and without webrender. We weren't able to
+ // reproduce these problems, but the users were able to update their
+ // drivers and it went away. So just to be safe, let's blocklist all
+ // gpu use with this particular (very old) driver, restricted
+ // to Win10 since we only have reports from that platform.
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows10, DeviceFamily::AtiAll,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_EQUAL, V(22, 19, 162, 4), "FEATURE_FAILURE_BUG_1587155");
+
+ // Bug 1198815
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(15, 200, 0, 0), V(15, 200, 1062, 1004), "FEATURE_FAILURE_BUG_1198815",
+ "15.200.0.0-15.200.1062.1004");
+
+ // Bug 1267970
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows10, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(15, 200, 0, 0), V(15, 301, 2301, 1002), "FEATURE_FAILURE_BUG_1267970",
+ "15.200.0.0-15.301.2301.1002");
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows10, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(16, 100, 0, 0), V(16, 300, 2311, 0), "FEATURE_FAILURE_BUG_1267970",
+ "16.100.0.0-16.300.2311.0");
+
+ /*
+ * Bug 783517 - crashes in AMD driver on Windows 8
+ */
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows8, DeviceFamily::AtiAll,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_INCLUSIVE_START, V(8, 982, 0, 0), V(8, 983, 0, 0),
+ "FEATURE_FAILURE_BUG_783517_AMD", "!= 8.982.*.*");
+
+ /*
+ * Bug 1599981 - crashes in AMD driver on Windows 10
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows10, DeviceFamily::RadeonCaicos,
+ nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(15, 301, 1901, 0), "FEATURE_FAILURE_BUG_1599981");
+
+ /* OpenGL on any ATI/AMD hardware is discouraged
+ * See:
+ * bug 619773 - WebGL: Crash with blue screen : "NMI: Parity Check / Memory
+ * Parity Error" bugs 584403, 584404, 620924 - crashes in atioglxx
+ * + many complaints about incorrect rendering
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_DISCOURAGED,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_OGL_ATI_DIS");
+
+/*
+ * Intel entries
+ */
+
+/* The driver versions used here come from bug 594877. They might not
+ * be particularly relevant anymore.
+ */
+#define IMPLEMENT_INTEL_DRIVER_BLOCKLIST(winVer, devFamily, driverVer, ruleId) \
+ APPEND_TO_DRIVER_BLOCKLIST2(winVer, devFamily, GfxDriverInfo::allFeatures, \
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, \
+ DRIVER_LESS_THAN, driverVer, ruleId)
+
+#define IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(winVer, devFamily, driverVer, \
+ ruleId) \
+ APPEND_TO_DRIVER_BLOCKLIST2(winVer, devFamily, nsIGfxInfo::FEATURE_DIRECT2D, \
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, \
+ DRIVER_BUILD_ID_LESS_THAN, driverVer, ruleId)
+
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7,
+ DeviceFamily::IntelGMA500, 2026,
+ "FEATURE_FAILURE_594877_7");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMA900,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_594877_8");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7,
+ DeviceFamily::IntelGMA950, 1930,
+ "FEATURE_FAILURE_594877_9");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7,
+ DeviceFamily::IntelGMA3150, 2117,
+ "FEATURE_FAILURE_594877_10");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7,
+ DeviceFamily::IntelGMAX3000, 1930,
+ "FEATURE_FAILURE_594877_11");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(
+ OperatingSystem::Windows7, DeviceFamily::IntelHDGraphicsToSandyBridge,
+ 2202, "FEATURE_FAILURE_594877_12");
+
+ /* Disable Direct2D on Intel GMAX4500 devices because of rendering
+ * corruption discovered in bug 1180379. These seems to affect even the most
+ * recent drivers. We're black listing all of the devices to be safe even
+ * though we've only confirmed the issue on the G45
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelGMAX4500HD,
+ nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_1180379");
+
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMA500, V(5, 0, 0, 2026),
+ "FEATURE_FAILURE_INTEL_16");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMA900,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_INTEL_17");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMA950,
+ V(8, 15, 10, 1930), "FEATURE_FAILURE_INTEL_18");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMA3150,
+ V(8, 14, 10, 1972), "FEATURE_FAILURE_INTEL_19");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMAX3000,
+ V(7, 15, 10, 1666), "FEATURE_FAILURE_INTEL_20");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMAX4500HD,
+ V(7, 15, 10, 1666), "FEATURE_FAILURE_INTEL_21");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelHDGraphicsToSandyBridge,
+ V(7, 15, 10, 1666), "FEATURE_FAILURE_INTEL_22");
+
+ // Bug 1074378
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMAX4500HD,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_EQUAL, V(8, 15, 10, 1749), "FEATURE_FAILURE_BUG_1074378_1",
+ "8.15.10.2342");
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelHDGraphicsToSandyBridge,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_EQUAL, V(8, 15, 10, 1749), "FEATURE_FAILURE_BUG_1074378_2",
+ "8.15.10.2342");
+
+ /* OpenGL on any Intel hardware is discouraged */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_DISCOURAGED,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_INTEL_OGL_DIS");
+
+ /**
+ * Disable acceleration on Intel HD 3000 for graphics drivers
+ * <= 8.15.10.2321. See bug 1018278 and bug 1060736.
+ */
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows, DeviceFamily::IntelSandyBridge,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 2321, "FEATURE_FAILURE_BUG_1018278",
+ "X.X.X.2342");
+
+ /**
+ * Disable D2D on Win7 on Intel Haswell for graphics drivers build id <=
+ * 4578. See bug 1432610
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7,
+ DeviceFamily::IntelHaswell,
+ nsIGfxInfo::FEATURE_DIRECT2D,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 4578,
+ "FEATURE_FAILURE_BUG_1432610");
+
+ /* Disable D2D on Win7 on Intel HD Graphics on driver <= 8.15.10.2302
+ * See bug 806786
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows7, DeviceFamily::IntelMobileHDGraphics,
+ nsIGfxInfo::FEATURE_DIRECT2D,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL,
+ V(8, 15, 10, 2302), "FEATURE_FAILURE_BUG_806786");
+
+ /* Disable D2D on Win8 on Intel HD Graphics on driver <= 8.15.10.2302
+ * See bug 804144 and 863683
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows8, DeviceFamily::IntelMobileHDGraphics,
+ nsIGfxInfo::FEATURE_DIRECT2D,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL,
+ V(8, 15, 10, 2302), "FEATURE_FAILURE_BUG_804144");
+
+ /* Disable D2D on Win7 on Intel HD Graphics on driver == 8.15.10.2418
+ * See bug 1433790
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows7, DeviceFamily::IntelHDGraphicsToSandyBridge,
+ nsIGfxInfo::FEATURE_DIRECT2D,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL,
+ V(8, 15, 10, 2418), "FEATURE_FAILURE_BUG_1433790");
+
+ /* Disable D3D11 layers on Intel G41 express graphics and Intel GM965, Intel
+ * X3100, for causing device resets. See bug 1116812.
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::Bug1116812,
+ nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1116812");
+
+ /* Disable D3D11 layers on Intel GMA 3150 for failing to allocate a shared
+ * handle for textures. See bug 1207665. Additionally block D2D so we don't
+ * accidentally use WARP.
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::Bug1207665,
+ nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1207665_1");
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::Bug1207665,
+ nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_BUG_1207665_2");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows10, DeviceFamily::QualcommAll,
+ nsIGfxInfo::FEATURE_DIRECT2D,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_QUALCOMM");
+
+ // Bug 1548410. Disable hardware accelerated video decoding on
+ // Qualcomm drivers used on Windows on ARM64 which are known to
+ // cause BSOD's and output suprious green frames while decoding video.
+ // Bug 1592826 expands the blocklist.
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows10, DeviceFamily::QualcommAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL,
+ V(25, 18, 10440, 0), "FEATURE_FAILURE_BUG_1592826");
+
+ /* Disable D2D on AMD Catalyst 14.4 until 14.6
+ * See bug 984488
+ */
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_DIRECT2D,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_INCLUSIVE_START, V(14, 1, 0, 0), V(14, 2, 0, 0),
+ "FEATURE_FAILURE_BUG_984488_1", "ATI Catalyst 14.6+");
+
+ /* Disable D3D9 layers on NVIDIA 6100/6150/6200 series due to glitches
+ * whilst scrolling. See bugs: 612007, 644787 & 645872.
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::NvidiaBlockD3D9Layers,
+ nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_612007");
+
+ /* Microsoft RemoteFX; blocked less than 6.2.0.0 */
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows, DeviceFamily::MicrosoftAll,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN, V(6, 2, 0, 0), "< 6.2.0.0",
+ "FEATURE_FAILURE_REMOTE_FX");
+
+ /* Bug 1008759: Optimus (NVidia) crash. Disable D2D on NV 310M. */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::Nvidia310M,
+ nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_BUG_1008759");
+
+ /* Bug 1139503: DXVA crashes with ATI cards on windows 10. */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows10, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL,
+ V(15, 200, 1006, 0), "FEATURE_FAILURE_BUG_1139503");
+
+ /* Bug 1213107: D3D9 crashes with ATI cards on Windows 7. */
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows7, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(8, 861, 0, 0), V(8, 862, 6, 5000), "FEATURE_FAILURE_BUG_1213107_1",
+ "Radeon driver > 8.862.6.5000");
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows7, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_WEBGL_ANGLE,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(8, 861, 0, 0), V(8, 862, 6, 5000), "FEATURE_FAILURE_BUG_1213107_2",
+ "Radeon driver > 8.862.6.5000");
+
+ /* This may not be needed at all */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows7, DeviceFamily::Bug1155608,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(8, 15, 10, 2869), "FEATURE_FAILURE_INTEL_W7_HW_DECODING");
+
+ /* Bug 1203199/1092166: DXVA startup crashes on some intel drivers. */
+ APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 2849,
+ "FEATURE_FAILURE_BUG_1203199_1",
+ "Intel driver > X.X.X.2849");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::Nvidia8800GTS,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL,
+ V(9, 18, 13, 4052), "FEATURE_FAILURE_BUG_1203199_2");
+
+ /* Bug 1137716: XXX this should really check for the matching Intel piece as
+ * well. Unfortunately, we don't have the infrastructure to do that */
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2(
+ OperatingSystem::Windows7, DeviceFamily::Bug1137716,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_INCLUSIVE, V(8, 17, 12, 5730), V(8, 17, 12, 6901),
+ "FEATURE_FAILURE_BUG_1137716", "Nvidia driver > 8.17.12.6901");
+
+ /* Bug 1153381: WebGL issues with D3D11 ANGLE on Intel. These may be fixed
+ * by an ANGLE update. */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelGMAX4500HD,
+ nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1153381");
+
+ /* Bug 1336710: Crash in rx::Blit9::initialize. */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::WindowsXP, DeviceFamily::IntelGMAX4500HD,
+ nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_BUG_1336710");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::WindowsXP, DeviceFamily::IntelHDGraphicsToSandyBridge,
+ nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_BUG_1336710");
+
+ /* Bug 1304360: Graphical artifacts with D3D9 on Windows 7. */
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7,
+ DeviceFamily::IntelGMAX3000,
+ nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 1749,
+ "FEATURE_FAILURE_INTEL_W7_D3D9_LAYERS");
+
+#if defined(_M_X64)
+ if (DetectBrokenAVX()) {
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows7, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1403353");
+ }
+#endif
+
+ ////////////////////////////////////
+ // WebGL
+
+ // Older than 5-15-2016
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_DISCOURAGED,
+ DRIVER_LESS_THAN, V(16, 200, 1010, 1002), "WEBGL_NATIVE_GL_OLD_AMD");
+
+ // Older than 11-18-2015
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_DISCOURAGED,
+ DRIVER_BUILD_ID_LESS_THAN, 4331, "WEBGL_NATIVE_GL_OLD_INTEL");
+
+ // Older than 2-23-2016
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_DISCOURAGED,
+ DRIVER_LESS_THAN, V(10, 18, 13, 6200), "WEBGL_NATIVE_GL_OLD_NVIDIA");
+
+ ////////////////////////////////////
+ // FEATURE_DX_INTEROP2
+
+ // All AMD.
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_DX_INTEROP2,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "DX_INTEROP2_AMD_CRASH");
+
+ ////////////////////////////////////
+ // FEATURE_D3D11_KEYED_MUTEX
+
+ // bug 1359416
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelHDGraphicsToSandyBridge,
+ nsIGfxInfo::FEATURE_D3D11_KEYED_MUTEX,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1359416");
+
+ // bug 1419264
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows7, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_ADVANCED_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(23, 21, 13, 8569), V(23, 21, 13, 9135), "FEATURE_FAILURE_BUG_1419264",
+ "Windows 10");
+
+ // Bug 1447141, for causing device creation crashes.
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows7, DeviceFamily::Bug1447141,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_EQUAL, V(15, 201, 2201, 0), "FEATURE_FAILURE_BUG_1447141_1");
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows7, DeviceFamily::Bug1447141,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_EQUAL, V(15, 201, 1701, 0), "FEATURE_FAILURE_BUG_1447141_1");
+
+ // bug 1457758
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::NvidiaAll,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_EQUAL, V(24, 21, 13, 9731), "FEATURE_FAILURE_BUG_1457758");
+
+ ////////////////////////////////////
+ // FEATURE_DX_NV12
+
+ // Bug 1437334
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelHDGraphicsToSandyBridge,
+ nsIGfxInfo::FEATURE_DX_NV12, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 4459,
+ "FEATURE_BLOCKED_DRIVER_VERSION");
+
+ ////////////////////////////////////
+ // FEATURE_DX_P010
+
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_DX_P010, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_UNQUALIFIED_P010_NVIDIA");
+
+ ////////////////////////////////////
+ // FEATURE_WEBRENDER
+
+ // Block some specific Nvidia cards for being too low-powered.
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::NvidiaBlockWebRender,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_UNQUALIFIED_WEBRENDER_NVIDIA_BLOCKED");
+
+ // Block 8.56.1.15/16
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN_OR_EQUAL, V(8, 56, 1, 16),
+ "CRASHY_DRIVERS_BUG_1678808");
+
+ ////////////////////////////////////
+ // FEATURE_WEBRENDER - ALLOWLIST
+ APPEND_TO_DRIVER_BLOCKLIST2_EXT(
+ OperatingSystem::Windows, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::All,
+ DeviceFamily::AmdR600, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_ROLLOUT_AMD_R600");
+
+ APPEND_TO_DRIVER_BLOCKLIST2_EXT(
+ OperatingSystem::Windows, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::All,
+ DeviceFamily::AtiRolloutWebRender, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_ROLLOUT_DESKTOP_AMD");
+
+ APPEND_TO_DRIVER_BLOCKLIST2_EXT(
+ OperatingSystem::Windows, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::All,
+ DeviceFamily::NvidiaRolloutWebRender, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_ROLLOUT_NV");
+
+ APPEND_TO_DRIVER_BLOCKLIST2_EXT(
+ OperatingSystem::Windows, ScreenSizeStatus::All, BatteryStatus::All,
+ DesktopEnvironment::All, WindowProtocol::All, DriverVendor::All,
+ DeviceFamily::IntelRolloutWebRender, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_ROLLOUT_INTEL");
+
+ ////////////////////////////////////
+ // FEATURE_WEBRENDER_COMPOSITOR
+
+#ifndef EARLY_BETA_OR_EARLIER
+ // See also bug 161687
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_EQUAL, V(24, 20, 100, 6293),
+ "FEATURE_FAILURE_BUG_1602511");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN_OR_EQUAL, V(8, 17, 10, 1129),
+ "FEATURE_FAILURE_CHROME_BUG_800950");
+#endif
+
+ // WebRender is unable to use scissored clears in some cases
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_WEBRENDER_SCISSORED_CACHE_CLEARS,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_FAILURE_BUG_1603515");
+
+ ////////////////////////////////////
+ // FEATURE_WEBRENDER_SOFTWARE
+
+ // TODO(aosmond): Bug 1678044 - wdspec tests ignore enable/disable-webrender
+ // Once the test infrastructure is fixed, we can remove this blocklist rule
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::AmazonAll,
+ nsIGfxInfo::FEATURE_WEBRENDER_SOFTWARE,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_FAILURE_BUG_1678044");
+
+ ////////////////////////////////////
+ // FEATURE_WEBRENDER_SOFTWARE - ALLOWLIST
+#ifdef EARLY_BETA_OR_EARLIER
+# if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || \
+ defined(__i386) || defined(__amd64__)
+ APPEND_TO_DRIVER_BLOCKLIST2_EXT(
+ OperatingSystem::Windows, ScreenSizeStatus::SmallAndMedium,
+ BatteryStatus::All, DesktopEnvironment::All, WindowProtocol::All,
+ DriverVendor::All, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_WEBRENDER_SOFTWARE,
+ nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_ROLLOUT_NIGHTLY_SOFTWARE_WR_S_M_SCRN");
+# endif
+#endif
+ }
+ return *sDriverInfo;
+}
+
+nsresult GfxInfo::GetFeatureStatusImpl(
+ int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
+ OperatingSystem* aOS /* = nullptr */) {
+ NS_ENSURE_ARG_POINTER(aStatus);
+ aSuggestedDriverVersion.SetIsVoid(true);
+ OperatingSystem os = WindowsVersionToOperatingSystem(mWindowsVersion);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ if (aOS) *aOS = os;
+
+ if (sShutdownOccurred) {
+ return NS_OK;
+ }
+
+ // Don't evaluate special cases if we're checking the downloaded blocklist.
+ if (!aDriverInfo.Length()) {
+ nsAutoString adapterVendorID;
+ nsAutoString adapterDeviceID;
+ nsAutoString adapterDriverVersionString;
+ if (NS_FAILED(GetAdapterVendorID(adapterVendorID)) ||
+ NS_FAILED(GetAdapterDeviceID(adapterDeviceID)) ||
+ NS_FAILED(GetAdapterDriverVersion(adapterDriverVersionString))) {
+ aFailureId = "FEATURE_FAILURE_GET_ADAPTER";
+ *aStatus = FEATURE_BLOCKED_DEVICE;
+ return NS_OK;
+ }
+
+ if (OnlyAllowFeatureOnWhitelistedVendor(aFeature) &&
+ !adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::Intel),
+ nsCaseInsensitiveStringComparator) &&
+ !adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::NVIDIA),
+ nsCaseInsensitiveStringComparator) &&
+ !adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::ATI),
+ nsCaseInsensitiveStringComparator) &&
+ !adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::Microsoft),
+ nsCaseInsensitiveStringComparator) &&
+ !adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::Parallels),
+ nsCaseInsensitiveStringComparator) &&
+ !adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::Qualcomm),
+ nsCaseInsensitiveStringComparator) &&
+ // FIXME - these special hex values are currently used in xpcshell tests
+ // introduced by bug 625160 patch 8/8. Maybe these tests need to be
+ // adjusted now that we're only whitelisting intel/ati/nvidia.
+ !adapterVendorID.LowerCaseEqualsLiteral("0xabcd") &&
+ !adapterVendorID.LowerCaseEqualsLiteral("0xdcba") &&
+ !adapterVendorID.LowerCaseEqualsLiteral("0xabab") &&
+ !adapterVendorID.LowerCaseEqualsLiteral("0xdcdc")) {
+ if (adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::MicrosoftHyperV),
+ nsCaseInsensitiveStringComparator) ||
+ adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::VMWare),
+ nsCaseInsensitiveStringComparator) ||
+ adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::VirtualBox),
+ nsCaseInsensitiveStringComparator)) {
+ aFailureId = "FEATURE_FAILURE_VM_VENDOR";
+ } else if (adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(
+ DeviceVendor::MicrosoftBasic),
+ nsCaseInsensitiveStringComparator)) {
+ aFailureId = "FEATURE_FAILURE_MICROSOFT_BASIC_VENDOR";
+ } else if (adapterVendorID.IsEmpty()) {
+ aFailureId = "FEATURE_FAILURE_EMPTY_DEVICE_VENDOR";
+ } else {
+ aFailureId = "FEATURE_FAILURE_UNKNOWN_DEVICE_VENDOR";
+ }
+ *aStatus = FEATURE_BLOCKED_DEVICE;
+ return NS_OK;
+ }
+
+ uint64_t driverVersion;
+ if (!ParseDriverVersion(adapterDriverVersionString, &driverVersion)) {
+ aFailureId = "FEATURE_FAILURE_PARSE_DRIVER";
+ *aStatus = FEATURE_BLOCKED_DRIVER_VERSION;
+ return NS_OK;
+ }
+
+ if (mHasDriverVersionMismatch) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_MISMATCHED_VERSION;
+ return NS_OK;
+ }
+ }
+
+ return GfxInfoBase::GetFeatureStatusImpl(
+ aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, aFailureId, &os);
+}
+
+nsresult GfxInfo::FindMonitors(JSContext* aCx, JS::HandleObject aOutArray) {
+ int deviceCount = 0;
+ for (auto displayInfo : mDisplayInfo) {
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+
+ JS::Rooted<JS::Value> screenWidth(aCx,
+ JS::Int32Value(displayInfo.mScreenWidth));
+ JS_SetProperty(aCx, obj, "screenWidth", screenWidth);
+
+ JS::Rooted<JS::Value> screenHeight(
+ aCx, JS::Int32Value(displayInfo.mScreenHeight));
+ JS_SetProperty(aCx, obj, "screenHeight", screenHeight);
+
+ JS::Rooted<JS::Value> refreshRate(aCx,
+ JS::Int32Value(displayInfo.mRefreshRate));
+ JS_SetProperty(aCx, obj, "refreshRate", refreshRate);
+
+ JS::Rooted<JS::Value> pseudoDisplay(
+ aCx, JS::BooleanValue(displayInfo.mIsPseudoDisplay));
+ JS_SetProperty(aCx, obj, "pseudoDisplay", pseudoDisplay);
+
+ JS::Rooted<JS::Value> element(aCx, JS::ObjectValue(*obj));
+ JS_SetElement(aCx, aOutArray, deviceCount++, element);
+ }
+ return NS_OK;
+}
+
+void GfxInfo::DescribeFeatures(JSContext* aCx, JS::Handle<JSObject*> aObj) {
+ // Add the platform neutral features
+ GfxInfoBase::DescribeFeatures(aCx, aObj);
+
+ JS::Rooted<JSObject*> obj(aCx);
+
+ gfx::FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING);
+ if (!InitFeatureObject(aCx, aObj, "d3d11", d3d11, &obj)) {
+ return;
+ }
+ if (d3d11.GetValue() == gfx::FeatureStatus::Available) {
+ DeviceManagerDx* dm = DeviceManagerDx::Get();
+ JS::Rooted<JS::Value> val(aCx,
+ JS::Int32Value(dm->GetCompositorFeatureLevel()));
+ JS_SetProperty(aCx, obj, "version", val);
+
+ val = JS::BooleanValue(dm->IsWARP());
+ JS_SetProperty(aCx, obj, "warp", val);
+
+ val = JS::BooleanValue(dm->TextureSharingWorks());
+ JS_SetProperty(aCx, obj, "textureSharing", val);
+
+ bool blocklisted = false;
+ if (nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo()) {
+ int32_t status;
+ nsCString discardFailureId;
+ if (SUCCEEDED(
+ gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
+ discardFailureId, &status))) {
+ blocklisted = (status != nsIGfxInfo::FEATURE_STATUS_OK);
+ }
+ }
+
+ val = JS::BooleanValue(blocklisted);
+ JS_SetProperty(aCx, obj, "blocklisted", val);
+ }
+
+ gfx::FeatureState& d2d = gfxConfig::GetFeature(Feature::DIRECT2D);
+ if (!InitFeatureObject(aCx, aObj, "d2d", d2d, &obj)) {
+ return;
+ }
+ {
+ const char* version = "1.1";
+ JS::Rooted<JSString*> str(aCx, JS_NewStringCopyZ(aCx, version));
+ JS::Rooted<JS::Value> val(aCx, JS::StringValue(str));
+ JS_SetProperty(aCx, obj, "version", val);
+ }
+}
+
+#ifdef DEBUG
+
+// Implement nsIGfxInfoDebug
+
+NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString& aVendorID) {
+ mAdapterVendorID[mActiveGPUIndex] = aVendorID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString& aDeviceID) {
+ mAdapterDeviceID[mActiveGPUIndex] = aDeviceID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString& aDriverVersion) {
+ mDriverVersion[mActiveGPUIndex] = aDriverVersion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion) {
+ mWindowsVersion = aVersion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::FireTestProcess() { return NS_OK; }
+
+#endif
diff --git a/widget/windows/GfxInfo.h b/widget/windows/GfxInfo.h
new file mode 100644
index 0000000000..b2c02162fe
--- /dev/null
+++ b/widget/windows/GfxInfo.h
@@ -0,0 +1,114 @@
+/* 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"
+
+namespace mozilla {
+namespace widget {
+
+class GfxInfo : public GfxInfoBase {
+ ~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) override;
+ NS_IMETHOD GetDWriteEnabled(bool* aDWriteEnabled) override;
+ NS_IMETHOD GetDWriteVersion(nsAString& aDwriteVersion) override;
+ NS_IMETHOD GetEmbeddedInFirefoxReality(
+ bool* aEmbeddedInFirefoxReality) override;
+ NS_IMETHOD GetHasBattery(bool* aHasBattery) override;
+ NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override;
+ NS_IMETHOD GetWindowProtocol(nsAString& aWindowProtocol) override;
+ NS_IMETHOD GetDesktopEnvironment(nsAString& aDesktopEnvironment) override;
+ NS_IMETHOD GetAdapterDescription(nsAString& aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver(nsAString& aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID(nsAString& aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID(nsAString& aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID(nsAString& aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM(uint32_t* aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) override;
+ NS_IMETHOD GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate(nsAString& aAdapterDriverDate) override;
+ NS_IMETHOD GetAdapterDescription2(nsAString& aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver2(nsAString& aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID2(nsAString& aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID2(nsAString& aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID2(nsAString& aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM2(uint32_t* aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) override;
+ NS_IMETHOD GetAdapterDriverVersion2(
+ nsAString& aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate2(nsAString& aAdapterDriverDate) override;
+ NS_IMETHOD GetIsGPU2Active(bool* aIsGPU2Active) override;
+ NS_IMETHOD GetDisplayInfo(nsTArray<nsString>& aDisplayInfo) override;
+ NS_IMETHOD GetDisplayWidth(nsTArray<uint32_t>& aDisplayWidth) override;
+ NS_IMETHOD GetDisplayHeight(nsTArray<uint32_t>& aDisplayHeight) override;
+ NS_IMETHOD GetDrmRenderDevice(nsACString& aDrmRenderDevice) override;
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+
+ nsresult Init() override;
+ NS_IMETHOD_(int32_t) GetMaxRefreshRate(bool* aMixed) override;
+
+ uint32_t OperatingSystemVersion() override { return mWindowsVersion; }
+ uint32_t OperatingSystemBuild() override { return mWindowsBuildNumber; }
+
+ nsresult FindMonitors(JSContext* cx, JS::HandleObject array) override;
+
+#ifdef DEBUG
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+ protected:
+ virtual nsresult GetFeatureStatusImpl(
+ int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
+ OperatingSystem* aOS = nullptr) override;
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() override;
+
+ void DescribeFeatures(JSContext* cx, JS::Handle<JSObject*> aOut) override;
+
+ private:
+ struct DisplayInfo {
+ uint32_t mScreenWidth;
+ uint32_t mScreenHeight;
+ uint32_t mRefreshRate;
+ bool mIsPseudoDisplay;
+ };
+
+ private:
+ void AddCrashReportAnnotations();
+
+ nsString mDeviceString[2];
+ nsString mDeviceID[2];
+ nsString mDriverVersion[2];
+ nsString mDriverDate[2];
+ nsString mDeviceKey[2];
+ nsString mDeviceKeyDebug;
+ nsString mAdapterVendorID[2];
+ nsString mAdapterDeviceID[2];
+ nsString mAdapterSubsysID[2];
+ uint32_t mWindowsVersion;
+ uint32_t mWindowsBuildNumber;
+ uint32_t mActiveGPUIndex; // This must be 0 or 1
+ nsTArray<DisplayInfo> mDisplayInfo;
+ bool mHasDualGPU;
+ bool mHasDriverVersionMismatch;
+ bool mHasBattery;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_GfxInfo_h__ */
diff --git a/widget/windows/IEnumFE.cpp b/widget/windows/IEnumFE.cpp
new file mode 100644
index 0000000000..8fe7138609
--- /dev/null
+++ b/widget/windows/IEnumFE.cpp
@@ -0,0 +1,139 @@
+/* -*- 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 "IEnumFE.h"
+#include "nsAlgorithm.h"
+#include <algorithm>
+
+CEnumFormatEtc::CEnumFormatEtc() : mRefCnt(0), mCurrentIdx(0) {}
+
+// Constructor used by Clone()
+CEnumFormatEtc::CEnumFormatEtc(nsTArray<FormatEtc>& aArray)
+ : mRefCnt(0), mCurrentIdx(0) {
+ // a deep copy, calls FormatEtc's copy constructor on each
+ mFormatList.AppendElements(aArray);
+}
+
+CEnumFormatEtc::~CEnumFormatEtc() {}
+
+/* IUnknown impl. */
+
+STDMETHODIMP
+CEnumFormatEtc::QueryInterface(REFIID riid, LPVOID* ppv) {
+ *ppv = nullptr;
+
+ if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IEnumFORMATETC))
+ *ppv = (LPVOID)this;
+
+ if (*ppv == nullptr) return E_NOINTERFACE;
+
+ // AddRef any interface we'll return.
+ ((LPUNKNOWN)*ppv)->AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG)
+CEnumFormatEtc::AddRef() {
+ ++mRefCnt;
+ NS_LOG_ADDREF(this, mRefCnt, "CEnumFormatEtc", sizeof(*this));
+ return mRefCnt;
+}
+
+STDMETHODIMP_(ULONG)
+CEnumFormatEtc::Release() {
+ uint32_t refReturn;
+
+ refReturn = --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "CEnumFormatEtc");
+
+ if (mRefCnt == 0) delete this;
+
+ return refReturn;
+}
+
+/* IEnumFORMATETC impl. */
+
+STDMETHODIMP
+CEnumFormatEtc::Next(ULONG aMaxToFetch, FORMATETC* aResult,
+ ULONG* aNumFetched) {
+ // If the method retrieves the number of items requested, the return
+ // value is S_OK. Otherwise, it is S_FALSE.
+
+ if (aNumFetched) *aNumFetched = 0;
+
+ // aNumFetched can be null if aMaxToFetch is 1
+ if (!aNumFetched && aMaxToFetch > 1) return S_FALSE;
+
+ if (!aResult) return S_FALSE;
+
+ // We're done walking the list
+ if (mCurrentIdx >= mFormatList.Length()) return S_FALSE;
+
+ uint32_t left = mFormatList.Length() - mCurrentIdx;
+
+ if (!aMaxToFetch) return S_FALSE;
+
+ uint32_t count = std::min(static_cast<uint32_t>(aMaxToFetch), left);
+
+ uint32_t idx = 0;
+ while (count > 0) {
+ // Copy out to aResult
+ mFormatList[mCurrentIdx++].CopyOut(&aResult[idx++]);
+ count--;
+ }
+
+ if (aNumFetched) *aNumFetched = idx;
+
+ return S_OK;
+}
+
+STDMETHODIMP
+CEnumFormatEtc::Skip(ULONG aSkipNum) {
+ // If the method skips the number of items requested, the return value is
+ // S_OK. Otherwise, it is S_FALSE.
+
+ if ((mCurrentIdx + aSkipNum) >= mFormatList.Length()) return S_FALSE;
+
+ mCurrentIdx += aSkipNum;
+
+ return S_OK;
+}
+
+STDMETHODIMP
+CEnumFormatEtc::Reset(void) {
+ mCurrentIdx = 0;
+ return S_OK;
+}
+
+STDMETHODIMP
+CEnumFormatEtc::Clone(LPENUMFORMATETC* aResult) {
+ // Must return a new IEnumFORMATETC interface with the same iterative state.
+
+ if (!aResult) return E_INVALIDARG;
+
+ CEnumFormatEtc* pEnumObj = new CEnumFormatEtc(mFormatList);
+
+ if (!pEnumObj) return E_OUTOFMEMORY;
+
+ pEnumObj->AddRef();
+ pEnumObj->SetIndex(mCurrentIdx);
+
+ *aResult = pEnumObj;
+
+ return S_OK;
+}
+
+/* utils */
+
+void CEnumFormatEtc::AddFormatEtc(LPFORMATETC aFormat) {
+ if (!aFormat) return;
+ FormatEtc* etc = mFormatList.AppendElement();
+ // Make a copy of aFormat
+ if (etc) etc->CopyIn(aFormat);
+}
+
+/* private */
+
+void CEnumFormatEtc::SetIndex(uint32_t aIdx) { mCurrentIdx = aIdx; }
diff --git a/widget/windows/IEnumFE.h b/widget/windows/IEnumFE.h
new file mode 100644
index 0000000000..b8cb6ad9d0
--- /dev/null
+++ b/widget/windows/IEnumFE.h
@@ -0,0 +1,88 @@
+/* -*- 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 IEnumeFE_h__
+#define IEnumeFE_h__
+
+/*
+ * CEnumFormatEtc - implements IEnumFORMATETC
+ */
+
+#include <ole2.h>
+
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+
+// FORMATETC container
+class FormatEtc {
+ public:
+ FormatEtc() { memset(&mFormat, 0, sizeof(FORMATETC)); }
+ FormatEtc(const FormatEtc& copy) { CopyIn(&copy.mFormat); }
+ ~FormatEtc() {
+ if (mFormat.ptd) CoTaskMemFree(mFormat.ptd);
+ }
+
+ void CopyIn(const FORMATETC* aSrc) {
+ if (!aSrc) {
+ memset(&mFormat, 0, sizeof(FORMATETC));
+ return;
+ }
+ mFormat = *aSrc;
+ if (aSrc->ptd) {
+ mFormat.ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE));
+ *(mFormat.ptd) = *(aSrc->ptd);
+ }
+ }
+
+ void CopyOut(LPFORMATETC aDest) {
+ if (!aDest) return;
+ *aDest = mFormat;
+ if (mFormat.ptd) {
+ aDest->ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE));
+ *(aDest->ptd) = *(mFormat.ptd);
+ }
+ }
+
+ private:
+ FORMATETC mFormat;
+};
+
+/*
+ * CEnumFormatEtc is created within IDataObject::EnumFormatEtc. This object
+ * lives on its own, that is, QueryInterface only knows IUnknown and
+ * IEnumFormatEtc, nothing more. We still use an outer unknown for reference
+ * counting, because as long as this enumerator lives, the data object should
+ * live, thereby keeping the application up.
+ */
+
+class CEnumFormatEtc final : public IEnumFORMATETC {
+ public:
+ explicit CEnumFormatEtc(nsTArray<FormatEtc>& aArray);
+ CEnumFormatEtc();
+ ~CEnumFormatEtc();
+
+ // IUnknown impl.
+ STDMETHODIMP QueryInterface(REFIID riid, LPVOID* ppv);
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP_(ULONG) Release();
+
+ // IEnumFORMATETC impl.
+ STDMETHODIMP Next(ULONG aMaxToFetch, FORMATETC* aResult, ULONG* aNumFetched);
+ STDMETHODIMP Skip(ULONG aSkipNum);
+ STDMETHODIMP Reset();
+ STDMETHODIMP Clone(LPENUMFORMATETC* aResult); // Addrefs
+
+ // Utils
+ void AddFormatEtc(LPFORMATETC aFormat);
+
+ private:
+ nsTArray<FormatEtc> mFormatList; // Formats
+ ULONG mRefCnt; // Object reference count
+ ULONG mCurrentIdx; // Current element
+
+ void SetIndex(uint32_t aIdx);
+};
+
+#endif //_IENUMFE_H_
diff --git a/widget/windows/IMMHandler.cpp b/widget/windows/IMMHandler.cpp
new file mode 100644
index 0000000000..338c3a64fd
--- /dev/null
+++ b/widget/windows/IMMHandler.cpp
@@ -0,0 +1,2384 @@
+/* -*- 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 "mozilla/Logging.h"
+
+#include "IMMHandler.h"
+#include "nsWindow.h"
+#include "nsWindowDefs.h"
+#include "WinIMEHandler.h"
+#include "WinUtils.h"
+#include "KeyboardLayout.h"
+#include <algorithm>
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/WindowsVersion.h"
+
+#ifndef IME_PROP_ACCEPT_WIDE_VKEY
+# define IME_PROP_ACCEPT_WIDE_VKEY 0x20
+#endif
+
+//-------------------------------------------------------------------------
+//
+// from
+// http://download.microsoft.com/download/6/0/9/60908e9e-d2c1-47db-98f6-216af76a235f/msime.h
+// The document for this has been removed from MSDN...
+//
+//-------------------------------------------------------------------------
+
+#define RWM_MOUSE TEXT("MSIMEMouseOperation")
+
+#define IMEMOUSE_NONE 0x00 // no mouse button was pushed
+#define IMEMOUSE_LDOWN 0x01
+#define IMEMOUSE_RDOWN 0x02
+#define IMEMOUSE_MDOWN 0x04
+#define IMEMOUSE_WUP 0x10 // wheel up
+#define IMEMOUSE_WDOWN 0x20 // wheel down
+
+static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
+
+static void HandleSeparator(nsACString& aDesc) {
+ if (!aDesc.IsEmpty()) {
+ aDesc.AppendLiteral(" | ");
+ }
+}
+
+class GetIMEGeneralPropertyName : public nsAutoCString {
+ public:
+ explicit GetIMEGeneralPropertyName(DWORD aFlags) {
+ if (!aFlags) {
+ AppendLiteral("no flags");
+ return;
+ }
+ if (aFlags & IME_PROP_AT_CARET) {
+ AppendLiteral("IME_PROP_AT_CARET");
+ }
+ if (aFlags & IME_PROP_SPECIAL_UI) {
+ HandleSeparator(*this);
+ AppendLiteral("IME_PROP_SPECIAL_UI");
+ }
+ if (aFlags & IME_PROP_CANDLIST_START_FROM_1) {
+ HandleSeparator(*this);
+ AppendLiteral("IME_PROP_CANDLIST_START_FROM_1");
+ }
+ if (aFlags & IME_PROP_UNICODE) {
+ HandleSeparator(*this);
+ AppendLiteral("IME_PROP_UNICODE");
+ }
+ if (aFlags & IME_PROP_COMPLETE_ON_UNSELECT) {
+ HandleSeparator(*this);
+ AppendLiteral("IME_PROP_COMPLETE_ON_UNSELECT");
+ }
+ if (aFlags & IME_PROP_ACCEPT_WIDE_VKEY) {
+ HandleSeparator(*this);
+ AppendLiteral("IME_PROP_ACCEPT_WIDE_VKEY");
+ }
+ }
+ virtual ~GetIMEGeneralPropertyName() {}
+};
+
+class GetIMEUIPropertyName : public nsAutoCString {
+ public:
+ explicit GetIMEUIPropertyName(DWORD aFlags) {
+ if (!aFlags) {
+ AppendLiteral("no flags");
+ return;
+ }
+ if (aFlags & UI_CAP_2700) {
+ AppendLiteral("UI_CAP_2700");
+ }
+ if (aFlags & UI_CAP_ROT90) {
+ HandleSeparator(*this);
+ AppendLiteral("UI_CAP_ROT90");
+ }
+ if (aFlags & UI_CAP_ROTANY) {
+ HandleSeparator(*this);
+ AppendLiteral("UI_CAP_ROTANY");
+ }
+ }
+ virtual ~GetIMEUIPropertyName() {}
+};
+
+class GetWritingModeName : public nsAutoCString {
+ public:
+ explicit GetWritingModeName(const WritingMode& aWritingMode) {
+ if (!aWritingMode.IsVertical()) {
+ AssignLiteral("Horizontal");
+ return;
+ }
+ if (aWritingMode.IsVerticalLR()) {
+ AssignLiteral("Vertical (LR)");
+ return;
+ }
+ AssignLiteral("Vertical (RL)");
+ }
+ virtual ~GetWritingModeName() {}
+};
+
+class GetReconvertStringLog : public nsAutoCString {
+ public:
+ explicit GetReconvertStringLog(RECONVERTSTRING* aReconv) {
+ AssignLiteral("{ dwSize=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwSize));
+ AppendLiteral(", dwVersion=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwVersion));
+ AppendLiteral(", dwStrLen=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwStrLen));
+ AppendLiteral(", dwStrOffset=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwStrOffset));
+ AppendLiteral(", dwCompStrLen=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwCompStrLen));
+ AppendLiteral(", dwCompStrOffset=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwCompStrOffset));
+ AppendLiteral(", dwTargetStrLen=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwTargetStrLen));
+ AppendLiteral(", dwTargetStrOffset=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwTargetStrOffset));
+ AppendLiteral(", result str=\"");
+ if (aReconv->dwStrLen) {
+ char16_t* strStart = reinterpret_cast<char16_t*>(
+ reinterpret_cast<char*>(aReconv) + aReconv->dwStrOffset);
+ nsDependentString str(strStart, aReconv->dwStrLen);
+ Append(NS_ConvertUTF16toUTF8(str));
+ }
+ AppendLiteral("\" }");
+ }
+ virtual ~GetReconvertStringLog() {}
+};
+
+namespace mozilla {
+namespace widget {
+
+static IMMHandler* gIMMHandler = nullptr;
+
+LazyLogModule gIMMLog("nsIMM32HandlerWidgets");
+
+/******************************************************************************
+ * IMEContext
+ ******************************************************************************/
+
+IMEContext::IMEContext(HWND aWnd) : mWnd(aWnd), mIMC(::ImmGetContext(aWnd)) {}
+
+IMEContext::IMEContext(nsWindowBase* aWindowBase)
+ : mWnd(aWindowBase->GetWindowHandle()),
+ mIMC(::ImmGetContext(aWindowBase->GetWindowHandle())) {}
+
+void IMEContext::Init(HWND aWnd) {
+ Clear();
+ mWnd = aWnd;
+ mIMC = ::ImmGetContext(mWnd);
+}
+
+void IMEContext::Init(nsWindowBase* aWindowBase) {
+ Init(aWindowBase->GetWindowHandle());
+}
+
+void IMEContext::Clear() {
+ if (mWnd && mIMC) {
+ ::ImmReleaseContext(mWnd, mIMC);
+ }
+ mWnd = nullptr;
+ mIMC = nullptr;
+}
+
+/******************************************************************************
+ * IMMHandler
+ ******************************************************************************/
+
+static UINT sWM_MSIME_MOUSE = 0; // mouse message for MSIME 98/2000
+
+WritingMode IMMHandler::sWritingModeOfCompositionFont;
+nsString IMMHandler::sIMEName;
+UINT IMMHandler::sCodePage = 0;
+DWORD IMMHandler::sIMEProperty = 0;
+DWORD IMMHandler::sIMEUIProperty = 0;
+bool IMMHandler::sAssumeVerticalWritingModeNotSupported = false;
+bool IMMHandler::sHasFocus = false;
+
+#define IMPL_IS_IME_ACTIVE(aReadableName, aActualName) \
+ bool IMMHandler::Is##aReadableName##Active() { \
+ return sIMEName.Equals(aActualName); \
+ }
+
+IMPL_IS_IME_ACTIVE(ATOK2006, u"ATOK 2006")
+IMPL_IS_IME_ACTIVE(ATOK2007, u"ATOK 2007")
+IMPL_IS_IME_ACTIVE(ATOK2008, u"ATOK 2008")
+IMPL_IS_IME_ACTIVE(ATOK2009, u"ATOK 2009")
+IMPL_IS_IME_ACTIVE(ATOK2010, u"ATOK 2010")
+// NOTE: Even on Windows for en-US, the name of Google Japanese Input is
+// written in Japanese.
+IMPL_IS_IME_ACTIVE(GoogleJapaneseInput,
+ u"Google \x65E5\x672C\x8A9E\x5165\x529B "
+ u"IMM32 \x30E2\x30B8\x30E5\x30FC\x30EB")
+IMPL_IS_IME_ACTIVE(Japanist2003, u"Japanist 2003")
+
+#undef IMPL_IS_IME_ACTIVE
+
+// static
+bool IMMHandler::IsActiveIMEInBlockList() {
+ if (sIMEName.IsEmpty()) {
+ return false;
+ }
+#ifdef _WIN64
+ // ATOK started to be TIP of TSF since 2011. Older than it, i.e., ATOK 2010
+ // and earlier have a lot of problems even for daily use. Perhaps, the
+ // reason is Win 8 has a lot of changes around IMM-IME support and TSF,
+ // and ATOK 2010 is released earlier than Win 8.
+ // ATOK 2006 crashes while converting a word with candidate window.
+ // ATOK 2007 doesn't paint and resize suggest window and candidate window
+ // correctly (showing white window or too big window).
+ // ATOK 2008 and ATOK 2009 crash when user just opens their open state.
+ // ATOK 2010 isn't installable newly on Win 7 or later, but we have a lot of
+ // crash reports.
+ if (IsWin8OrLater() &&
+ (IsATOK2006Active() || IsATOK2007Active() || IsATOK2008Active() ||
+ IsATOK2009Active() || IsATOK2010Active())) {
+ return true;
+ }
+#endif // #ifdef _WIN64
+ return false;
+}
+
+// static
+void IMMHandler::EnsureHandlerInstance() {
+ if (!gIMMHandler) {
+ gIMMHandler = new IMMHandler();
+ }
+}
+
+// static
+void IMMHandler::Initialize() {
+ if (!sWM_MSIME_MOUSE) {
+ sWM_MSIME_MOUSE = ::RegisterWindowMessage(RWM_MOUSE);
+ }
+ sAssumeVerticalWritingModeNotSupported = Preferences::GetBool(
+ "intl.imm.vertical_writing.always_assume_not_supported", false);
+ InitKeyboardLayout(nullptr, ::GetKeyboardLayout(0));
+}
+
+// static
+void IMMHandler::Terminate() {
+ if (!gIMMHandler) return;
+ delete gIMMHandler;
+ gIMMHandler = nullptr;
+}
+
+// static
+bool IMMHandler::IsComposingOnOurEditor() {
+ return gIMMHandler && gIMMHandler->mIsComposing;
+}
+
+// static
+bool IMMHandler::IsComposingWindow(nsWindow* aWindow) {
+ return gIMMHandler && gIMMHandler->mComposingWindow == aWindow;
+}
+
+// static
+bool IMMHandler::IsTopLevelWindowOfComposition(nsWindow* aWindow) {
+ if (!gIMMHandler || !gIMMHandler->mComposingWindow) {
+ return false;
+ }
+ HWND wnd = gIMMHandler->mComposingWindow->GetWindowHandle();
+ return WinUtils::GetTopLevelHWND(wnd, true) == aWindow->GetWindowHandle();
+}
+
+// static
+bool IMMHandler::ShouldDrawCompositionStringOurselves() {
+ // If current IME has special UI or its composition window should not
+ // positioned to caret position, we should now draw composition string
+ // ourselves.
+ return !(sIMEProperty & IME_PROP_SPECIAL_UI) &&
+ (sIMEProperty & IME_PROP_AT_CARET);
+}
+
+// static
+bool IMMHandler::IsVerticalWritingSupported() {
+ // Even if IME claims that they support vertical writing mode but it may not
+ // support vertical writing mode for its candidate window.
+ if (sAssumeVerticalWritingModeNotSupported) {
+ return false;
+ }
+ // Google Japanese Input doesn't support vertical writing mode. We should
+ // return false if it's active IME.
+ if (IsGoogleJapaneseInputActive()) {
+ return false;
+ }
+ return !!(sIMEUIProperty & (UI_CAP_2700 | UI_CAP_ROT90 | UI_CAP_ROTANY));
+}
+
+// static
+void IMMHandler::InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout) {
+ UINT IMENameLength = ::ImmGetDescriptionW(aKeyboardLayout, nullptr, 0);
+ if (IMENameLength) {
+ // Add room for the terminating null character
+ sIMEName.SetLength(++IMENameLength);
+ IMENameLength =
+ ::ImmGetDescriptionW(aKeyboardLayout, sIMEName.get(), IMENameLength);
+ // Adjust the length to ignore the terminating null character
+ sIMEName.SetLength(IMENameLength);
+ } else {
+ sIMEName.Truncate();
+ }
+
+ WORD langID = LOWORD(aKeyboardLayout);
+ ::GetLocaleInfoW(MAKELCID(langID, SORT_DEFAULT),
+ LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER,
+ (PWSTR)&sCodePage, sizeof(sCodePage) / sizeof(WCHAR));
+ sIMEProperty = ::ImmGetProperty(aKeyboardLayout, IGP_PROPERTY);
+ sIMEUIProperty = ::ImmGetProperty(aKeyboardLayout, IGP_UI);
+
+ // If active IME is a TIP of TSF, we cannot retrieve the name with IMM32 API.
+ // For hacking some bugs of some TIP, we should set an IME name from the
+ // pref.
+ if (sCodePage == 932 && sIMEName.IsEmpty()) {
+ Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as",
+ sIMEName);
+ }
+
+ // Whether the IME supports vertical writing mode might be changed or
+ // some IMEs may need specific font for their UI. Therefore, we should
+ // update composition font forcibly here.
+ if (aWindow) {
+ MaybeAdjustCompositionFont(aWindow, sWritingModeOfCompositionFont, true);
+ }
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("InitKeyboardLayout, aKeyboardLayout=%08x (\"%s\"), sCodePage=%lu, "
+ "sIMEProperty=%s, sIMEUIProperty=%s",
+ aKeyboardLayout, NS_ConvertUTF16toUTF8(sIMEName).get(), sCodePage,
+ GetIMEGeneralPropertyName(sIMEProperty).get(),
+ GetIMEUIPropertyName(sIMEUIProperty).get()));
+}
+
+// static
+UINT IMMHandler::GetKeyboardCodePage() { return sCodePage; }
+
+// static
+IMENotificationRequests IMMHandler::GetIMENotificationRequests() {
+ return IMENotificationRequests(
+ IMENotificationRequests::NOTIFY_POSITION_CHANGE |
+ IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR);
+}
+
+// used for checking the lParam of WM_IME_COMPOSITION
+#define IS_COMPOSING_LPARAM(lParam) \
+ ((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS))
+#define IS_COMMITTING_LPARAM(lParam) ((lParam)&GCS_RESULTSTR)
+// Some IMEs (e.g., the standard IME for Korean) don't have caret position,
+// then, we should not set caret position to compositionchange event.
+#define NO_IME_CARET -1
+
+IMMHandler::IMMHandler()
+ : mComposingWindow(nullptr),
+ mCursorPosition(NO_IME_CARET),
+ mCompositionStart(0),
+ mIsComposing(false) {
+ MOZ_LOG(gIMMLog, LogLevel::Debug, ("IMMHandler is created"));
+}
+
+IMMHandler::~IMMHandler() {
+ if (mIsComposing) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("~IMMHandler, ERROR, the instance is still composing"));
+ }
+ MOZ_LOG(gIMMLog, LogLevel::Debug, ("IMMHandler is destroyed"));
+}
+
+nsresult IMMHandler::EnsureClauseArray(int32_t aCount) {
+ NS_ENSURE_ARG_MIN(aCount, 0);
+ mClauseArray.SetCapacity(aCount + 32);
+ return NS_OK;
+}
+
+nsresult IMMHandler::EnsureAttributeArray(int32_t aCount) {
+ NS_ENSURE_ARG_MIN(aCount, 0);
+ mAttributeArray.SetCapacity(aCount + 64);
+ return NS_OK;
+}
+
+// static
+void IMMHandler::CommitComposition(nsWindow* aWindow, bool aForce) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("CommitComposition, aForce=%s, aWindow=%p, hWnd=%08x, "
+ "mComposingWindow=%p%s",
+ GetBoolName(aForce), aWindow, aWindow->GetWindowHandle(),
+ gIMMHandler ? gIMMHandler->mComposingWindow : nullptr,
+ gIMMHandler && gIMMHandler->mComposingWindow
+ ? IsComposingOnOurEditor() ? " (composing on editor)"
+ : " (composing on plug-in)"
+ : ""));
+ if (!aForce && !IsComposingWindow(aWindow)) {
+ return;
+ }
+
+ IMEContext context(aWindow);
+ bool associated = context.AssociateDefaultContext();
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("CommitComposition, associated=%s", GetBoolName(associated)));
+
+ if (context.IsValid()) {
+ ::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
+ ::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0);
+ }
+
+ if (associated) {
+ context.Disassociate();
+ }
+}
+
+// static
+void IMMHandler::CancelComposition(nsWindow* aWindow, bool aForce) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("CancelComposition, aForce=%s, aWindow=%p, hWnd=%08x, "
+ "mComposingWindow=%p%s",
+ GetBoolName(aForce), aWindow, aWindow->GetWindowHandle(),
+ gIMMHandler ? gIMMHandler->mComposingWindow : nullptr,
+ gIMMHandler && gIMMHandler->mComposingWindow
+ ? IsComposingOnOurEditor() ? " (composing on editor)"
+ : " (composing on plug-in)"
+ : ""));
+ if (!aForce && !IsComposingWindow(aWindow)) {
+ return;
+ }
+
+ IMEContext context(aWindow);
+ bool associated = context.AssociateDefaultContext();
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("CancelComposition, associated=%s", GetBoolName(associated)));
+
+ if (context.IsValid()) {
+ ::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0);
+ }
+
+ if (associated) {
+ context.Disassociate();
+ }
+}
+
+// static
+void IMMHandler::OnFocusChange(bool aFocus, nsWindow* aWindow) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnFocusChange(aFocus=%s, aWindow=%p), sHasFocus=%s, "
+ "IsComposingWindow(aWindow)=%s, aWindow->Destroyed()=%s",
+ GetBoolName(aFocus), aWindow, GetBoolName(sHasFocus),
+ GetBoolName(IsComposingWindow(aWindow)),
+ GetBoolName(aWindow->Destroyed())));
+
+ if (!aFocus) {
+ IMEHandler::MaybeDestroyNativeCaret();
+ if (IsComposingWindow(aWindow) && aWindow->Destroyed()) {
+ CancelComposition(aWindow);
+ }
+ }
+ if (gIMMHandler) {
+ gIMMHandler->mSelection.Clear();
+ }
+ sHasFocus = aFocus;
+}
+
+// static
+void IMMHandler::OnUpdateComposition(nsWindow* aWindow) {
+ if (!gIMMHandler) {
+ return;
+ }
+
+ IMEContext context(aWindow);
+ gIMMHandler->SetIMERelatedWindowsPos(aWindow, context);
+}
+
+// static
+void IMMHandler::OnSelectionChange(nsWindow* aWindow,
+ const IMENotification& aIMENotification,
+ bool aIsIMMActive) {
+ if (!aIMENotification.mSelectionChangeData.mCausedByComposition &&
+ aIsIMMActive) {
+ MaybeAdjustCompositionFont(
+ aWindow, aIMENotification.mSelectionChangeData.GetWritingMode());
+ }
+ // MaybeAdjustCompositionFont() may create gIMMHandler. So, check it
+ // after a call of MaybeAdjustCompositionFont().
+ if (gIMMHandler) {
+ gIMMHandler->mSelection.Update(aIMENotification);
+ }
+}
+
+// static
+void IMMHandler::MaybeAdjustCompositionFont(nsWindow* aWindow,
+ const WritingMode& aWritingMode,
+ bool aForceUpdate) {
+ switch (sCodePage) {
+ case 932: // Japanese Shift-JIS
+ case 936: // Simlified Chinese GBK
+ case 949: // Korean
+ case 950: // Traditional Chinese Big5
+ EnsureHandlerInstance();
+ break;
+ default:
+ // If there is no instance of nsIMM32Hander, we shouldn't waste footprint.
+ if (!gIMMHandler) {
+ return;
+ }
+ }
+
+ // Like Navi-Bar of ATOK, some IMEs may require proper composition font even
+ // before sending WM_IME_STARTCOMPOSITION.
+ IMEContext context(aWindow);
+ gIMMHandler->AdjustCompositionFont(aWindow, context, aWritingMode,
+ aForceUpdate);
+}
+
+// static
+bool IMMHandler::ProcessInputLangChangeMessage(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam,
+ MSGResult& aResult) {
+ aResult.mResult = 0;
+ aResult.mConsumed = false;
+ // We don't need to create the instance of the handler here.
+ if (gIMMHandler) {
+ gIMMHandler->OnInputLangChange(aWindow, wParam, lParam, aResult);
+ }
+ InitKeyboardLayout(aWindow, reinterpret_cast<HKL>(lParam));
+ // We can release the instance here, because the instance may be never
+ // used. E.g., the new keyboard layout may not use IME, or it may use TSF.
+ Terminate();
+ // Don't return as "processed", the messages should be processed on nsWindow
+ // too.
+ return false;
+}
+
+// static
+bool IMMHandler::ProcessMessage(nsWindow* aWindow, UINT msg, WPARAM& wParam,
+ LPARAM& lParam, MSGResult& aResult) {
+ // XXX We store the composing window in mComposingWindow. If IME messages are
+ // sent to different window, we should commit the old transaction. And also
+ // if the new window handle is not focused, probably, we should not start
+ // the composition, however, such case should not be, it's just bad scenario.
+
+ aResult.mResult = 0;
+ switch (msg) {
+ case WM_INPUTLANGCHANGE:
+ return ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult);
+ case WM_IME_STARTCOMPOSITION:
+ EnsureHandlerInstance();
+ return gIMMHandler->OnIMEStartComposition(aWindow, aResult);
+ case WM_IME_COMPOSITION:
+ EnsureHandlerInstance();
+ return gIMMHandler->OnIMEComposition(aWindow, wParam, lParam, aResult);
+ case WM_IME_ENDCOMPOSITION:
+ EnsureHandlerInstance();
+ return gIMMHandler->OnIMEEndComposition(aWindow, aResult);
+ case WM_IME_CHAR:
+ return OnIMEChar(aWindow, wParam, lParam, aResult);
+ case WM_IME_NOTIFY:
+ return OnIMENotify(aWindow, wParam, lParam, aResult);
+ case WM_IME_REQUEST:
+ EnsureHandlerInstance();
+ return gIMMHandler->OnIMERequest(aWindow, wParam, lParam, aResult);
+ case WM_IME_SELECT:
+ return OnIMESelect(aWindow, wParam, lParam, aResult);
+ case WM_IME_SETCONTEXT:
+ return OnIMESetContext(aWindow, wParam, lParam, aResult);
+ case WM_KEYDOWN:
+ return OnKeyDownEvent(aWindow, wParam, lParam, aResult);
+ case WM_CHAR:
+ if (!gIMMHandler) {
+ return false;
+ }
+ return gIMMHandler->OnChar(aWindow, wParam, lParam, aResult);
+ default:
+ return false;
+ };
+}
+
+/****************************************************************************
+ * message handlers
+ ****************************************************************************/
+
+void IMMHandler::OnInputLangChange(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam, MSGResult& aResult) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnInputLangChange, hWnd=%08x, wParam=%08x, lParam=%08x",
+ aWindow->GetWindowHandle(), wParam, lParam));
+
+ aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
+ NS_ASSERTION(!mIsComposing, "ResetInputState failed");
+
+ if (mIsComposing) {
+ HandleEndComposition(aWindow);
+ }
+
+ aResult.mConsumed = false;
+}
+
+bool IMMHandler::OnIMEStartComposition(nsWindow* aWindow, MSGResult& aResult) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMEStartComposition, hWnd=%08x, mIsComposing=%s",
+ aWindow->GetWindowHandle(), GetBoolName(mIsComposing)));
+ aResult.mConsumed = ShouldDrawCompositionStringOurselves();
+ if (mIsComposing) {
+ NS_WARNING("Composition has been already started");
+ return true;
+ }
+
+ IMEContext context(aWindow);
+ HandleStartComposition(aWindow, context);
+ return true;
+}
+
+bool IMMHandler::OnIMEComposition(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam, MSGResult& aResult) {
+ MOZ_LOG(
+ gIMMLog, LogLevel::Info,
+ ("OnIMEComposition, hWnd=%08x, lParam=%08x, mIsComposing=%s, "
+ "GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, "
+ "GCS_CURSORPOS=%s,",
+ aWindow->GetWindowHandle(), lParam, GetBoolName(mIsComposing),
+ GetBoolName(lParam & GCS_RESULTSTR), GetBoolName(lParam & GCS_COMPSTR),
+ GetBoolName(lParam & GCS_COMPATTR), GetBoolName(lParam & GCS_COMPCLAUSE),
+ GetBoolName(lParam & GCS_CURSORPOS)));
+
+ IMEContext context(aWindow);
+ aResult.mConsumed = HandleComposition(aWindow, context, lParam);
+ return true;
+}
+
+bool IMMHandler::OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMEEndComposition, hWnd=%08x, mIsComposing=%s",
+ aWindow->GetWindowHandle(), GetBoolName(mIsComposing)));
+
+ aResult.mConsumed = ShouldDrawCompositionStringOurselves();
+ if (!mIsComposing) {
+ return true;
+ }
+
+ // Korean IME posts WM_IME_ENDCOMPOSITION first when we hit space during
+ // composition. Then, we should ignore the message and commit the composition
+ // string at following WM_IME_COMPOSITION.
+ MSG compositionMsg;
+ if (WinUtils::PeekMessage(&compositionMsg, aWindow->GetWindowHandle(),
+ WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION,
+ PM_NOREMOVE) &&
+ compositionMsg.message == WM_IME_COMPOSITION &&
+ IS_COMMITTING_LPARAM(compositionMsg.lParam)) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMEEndComposition, WM_IME_ENDCOMPOSITION is followed by "
+ "WM_IME_COMPOSITION, ignoring the message..."));
+ return true;
+ }
+
+ // Otherwise, e.g., ChangJie doesn't post WM_IME_COMPOSITION before
+ // WM_IME_ENDCOMPOSITION when composition string becomes empty.
+ // Then, we should dispatch a compositionupdate event, a compositionchange
+ // event and a compositionend event.
+ // XXX Shouldn't we dispatch the compositionchange event with actual or
+ // latest composition string?
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMEEndComposition, mCompositionString=\"%s\"%s",
+ NS_ConvertUTF16toUTF8(mCompositionString).get(),
+ mCompositionString.IsEmpty() ? "" : ", but canceling it..."));
+
+ HandleEndComposition(aWindow, &EmptyString());
+
+ return true;
+}
+
+// static
+bool IMMHandler::OnIMEChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ MOZ_LOG(
+ gIMMLog, LogLevel::Info,
+ ("OnIMEChar, hWnd=%08x, char=%08x", aWindow->GetWindowHandle(), wParam));
+
+ // We don't need to fire any compositionchange events from here. This method
+ // will be called when the composition string of the current IME is not drawn
+ // by us and some characters are committed. In that case, the committed
+ // string was processed in nsWindow::OnIMEComposition already.
+
+ // We need to consume the message so that Windows don't send two WM_CHAR msgs
+ aResult.mConsumed = true;
+ return true;
+}
+
+// static
+bool IMMHandler::OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMECompositionFull, hWnd=%08x", aWindow->GetWindowHandle()));
+
+ // not implement yet
+ aResult.mConsumed = false;
+ return true;
+}
+
+// static
+bool IMMHandler::OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ switch (wParam) {
+ case IMN_CHANGECANDIDATE:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_CHANGECANDIDATE, lParam=%08x",
+ aWindow->GetWindowHandle(), lParam));
+ break;
+ case IMN_CLOSECANDIDATE:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_CLOSECANDIDATE, lParam=%08x",
+ aWindow->GetWindowHandle(), lParam));
+ break;
+ case IMN_CLOSESTATUSWINDOW:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_CLOSESTATUSWINDOW",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_GUIDELINE:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_GUIDELINE",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_OPENCANDIDATE:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_OPENCANDIDATE, lParam=%08x",
+ aWindow->GetWindowHandle(), lParam));
+ break;
+ case IMN_OPENSTATUSWINDOW:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_OPENSTATUSWINDOW",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETCANDIDATEPOS:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_SETCANDIDATEPOS, lParam=%08x",
+ aWindow->GetWindowHandle(), lParam));
+ break;
+ case IMN_SETCOMPOSITIONFONT:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONFONT",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETCOMPOSITIONWINDOW:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONWINDOW",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETCONVERSIONMODE:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_SETCONVERSIONMODE",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETOPENSTATUS:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_SETOPENSTATUS",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETSENTENCEMODE:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_SETSENTENCEMODE",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETSTATUSWINDOWPOS:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_SETSTATUSWINDOWPOS",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_PRIVATE:
+ MOZ_LOG(
+ gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_PRIVATE", aWindow->GetWindowHandle()));
+ break;
+ }
+
+ // not implement yet
+ aResult.mConsumed = false;
+ return true;
+}
+
+bool IMMHandler::OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ switch (wParam) {
+ case IMR_RECONVERTSTRING:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMERequest, hWnd=%08x, IMR_RECONVERTSTRING",
+ aWindow->GetWindowHandle()));
+ aResult.mConsumed = HandleReconvert(aWindow, lParam, &aResult.mResult);
+ return true;
+ case IMR_QUERYCHARPOSITION:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMERequest, hWnd=%08x, IMR_QUERYCHARPOSITION",
+ aWindow->GetWindowHandle()));
+ aResult.mConsumed =
+ HandleQueryCharPosition(aWindow, lParam, &aResult.mResult);
+ return true;
+ case IMR_DOCUMENTFEED:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMERequest, hWnd=%08x, IMR_DOCUMENTFEED",
+ aWindow->GetWindowHandle()));
+ aResult.mConsumed = HandleDocumentFeed(aWindow, lParam, &aResult.mResult);
+ return true;
+ default:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMERequest, hWnd=%08x, wParam=%08x",
+ aWindow->GetWindowHandle(), wParam));
+ aResult.mConsumed = false;
+ return true;
+ }
+}
+
+// static
+bool IMMHandler::OnIMESelect(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMESelect, hWnd=%08x, wParam=%08x, lParam=%08x",
+ aWindow->GetWindowHandle(), wParam, lParam));
+
+ // not implement yet
+ aResult.mConsumed = false;
+ return true;
+}
+
+// static
+bool IMMHandler::OnIMESetContext(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam, MSGResult& aResult) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMESetContext, hWnd=%08x, %s, lParam=%08x",
+ aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam));
+
+ aResult.mConsumed = false;
+
+ // NOTE: If the aWindow is top level window of the composing window because
+ // when a window on deactive window gets focus, WM_IME_SETCONTEXT (wParam is
+ // TRUE) is sent to the top level window first. After that,
+ // WM_IME_SETCONTEXT (wParam is FALSE) is sent to the top level window.
+ // Finally, WM_IME_SETCONTEXT (wParam is TRUE) is sent to the focused window.
+ // The top level window never becomes composing window, so, we can ignore
+ // the WM_IME_SETCONTEXT on the top level window.
+ if (IsTopLevelWindowOfComposition(aWindow)) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMESetContext, hWnd=%08x is top level window"));
+ return true;
+ }
+
+ // When IME context is activating on another window,
+ // we should commit the old composition on the old window.
+ bool cancelComposition = false;
+ if (wParam && gIMMHandler) {
+ cancelComposition = gIMMHandler->CommitCompositionOnPreviousWindow(aWindow);
+ }
+
+ if (wParam && (lParam & ISC_SHOWUICOMPOSITIONWINDOW) &&
+ ShouldDrawCompositionStringOurselves()) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMESetContext, ISC_SHOWUICOMPOSITIONWINDOW is removed"));
+ lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
+ }
+
+ // We should sent WM_IME_SETCONTEXT to the DefWndProc here because the
+ // ancestor windows shouldn't receive this message. If they receive the
+ // message, we cannot know whether which window is the target of the message.
+ aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(),
+ WM_IME_SETCONTEXT, wParam, lParam);
+
+ // Cancel composition on the new window if we committed our composition on
+ // another window.
+ if (cancelComposition) {
+ CancelComposition(aWindow, true);
+ }
+
+ aResult.mConsumed = true;
+ return true;
+}
+
+bool IMMHandler::OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ // The return value must be same as aResult.mConsumed because only when we
+ // consume the message, the caller shouldn't do anything anymore but
+ // otherwise, the caller should handle the message.
+ aResult.mConsumed = false;
+ if (IsIMECharRecordsEmpty()) {
+ return aResult.mConsumed;
+ }
+ WPARAM recWParam;
+ LPARAM recLParam;
+ DequeueIMECharRecords(recWParam, recLParam);
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnChar, aWindow=%p, wParam=%08x, lParam=%08x, "
+ "recorded: wParam=%08x, lParam=%08x",
+ aWindow->GetWindowHandle(), wParam, lParam, recWParam, recLParam));
+ // If an unexpected char message comes, we should reset the records,
+ // of course, this shouldn't happen.
+ if (recWParam != wParam || recLParam != lParam) {
+ ResetIMECharRecords();
+ return aResult.mConsumed;
+ }
+ // Eat the char message which is caused by WM_IME_CHAR because we should
+ // have processed the IME messages, so, this message could be come from
+ // a windowless plug-in.
+ aResult.mConsumed = true;
+ return aResult.mConsumed;
+}
+
+/****************************************************************************
+ * others
+ ****************************************************************************/
+
+TextEventDispatcher* IMMHandler::GetTextEventDispatcherFor(nsWindow* aWindow) {
+ return aWindow == mComposingWindow && mDispatcher
+ ? mDispatcher.get()
+ : aWindow->GetTextEventDispatcher();
+}
+
+void IMMHandler::HandleStartComposition(nsWindow* aWindow,
+ const IMEContext& aContext) {
+ MOZ_ASSERT(!mIsComposing,
+ "HandleStartComposition is called but mIsComposing is TRUE");
+
+ Selection& selection = GetSelection();
+ if (!selection.EnsureValidSelection(aWindow)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleStartComposition, FAILED, due to "
+ "Selection::EnsureValidSelection() failure"));
+ return;
+ }
+
+ AdjustCompositionFont(aWindow, aContext, selection.mWritingMode);
+
+ mCompositionStart = selection.mOffset;
+ mCursorPosition = NO_IME_CARET;
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleStartComposition, FAILED due to "
+ "TextEventDispatcher::BeginNativeInputTransaction() failure"));
+ return;
+ }
+ WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = dispatcher->StartComposition(status, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleStartComposition, FAILED, due to "
+ "TextEventDispatcher::StartComposition() failure"));
+ return;
+ }
+
+ mIsComposing = true;
+ mComposingWindow = aWindow;
+ mDispatcher = dispatcher;
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleStartComposition, START composition, mCompositionStart=%ld",
+ mCompositionStart));
+}
+
+bool IMMHandler::HandleComposition(nsWindow* aWindow,
+ const IMEContext& aContext, LPARAM lParam) {
+ // for bug #60050
+ // MS-IME 95/97/98/2000 may send WM_IME_COMPOSITION with non-conversion
+ // mode before it send WM_IME_STARTCOMPOSITION.
+ // However, ATOK sends a WM_IME_COMPOSITION before WM_IME_STARTCOMPOSITION,
+ // and if we access ATOK via some APIs, ATOK will sometimes fail to
+ // initialize its state. If WM_IME_STARTCOMPOSITION is already in the
+ // message queue, we should ignore the strange WM_IME_COMPOSITION message and
+ // skip to the next. So, we should look for next composition message
+ // (WM_IME_STARTCOMPOSITION or WM_IME_ENDCOMPOSITION or WM_IME_COMPOSITION),
+ // and if it's WM_IME_STARTCOMPOSITION, and one more next composition message
+ // is WM_IME_COMPOSITION, current IME is ATOK, probably. Otherwise, we
+ // should start composition forcibly.
+ if (!mIsComposing) {
+ MSG msg1, msg2;
+ HWND wnd = aWindow->GetWindowHandle();
+ if (WinUtils::PeekMessage(&msg1, wnd, WM_IME_STARTCOMPOSITION,
+ WM_IME_COMPOSITION, PM_NOREMOVE) &&
+ msg1.message == WM_IME_STARTCOMPOSITION &&
+ WinUtils::PeekMessage(&msg2, wnd, WM_IME_ENDCOMPOSITION,
+ WM_IME_COMPOSITION, PM_NOREMOVE) &&
+ msg2.message == WM_IME_COMPOSITION) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleComposition, Ignores due to find a "
+ "WM_IME_STARTCOMPOSITION"));
+ return ShouldDrawCompositionStringOurselves();
+ }
+ }
+
+ bool startCompositionMessageHasBeenSent = mIsComposing;
+
+ //
+ // This catches a fixed result
+ //
+ if (IS_COMMITTING_LPARAM(lParam)) {
+ if (!mIsComposing) {
+ HandleStartComposition(aWindow, aContext);
+ }
+
+ GetCompositionString(aContext, GCS_RESULTSTR, mCompositionString);
+
+ MOZ_LOG(gIMMLog, LogLevel::Info, ("HandleComposition, GCS_RESULTSTR"));
+
+ HandleEndComposition(aWindow, &mCompositionString);
+
+ if (!IS_COMPOSING_LPARAM(lParam)) {
+ return ShouldDrawCompositionStringOurselves();
+ }
+ }
+
+ //
+ // This provides us with a composition string
+ //
+ if (!mIsComposing) {
+ HandleStartComposition(aWindow, aContext);
+ }
+
+ //--------------------------------------------------------
+ // 1. Get GCS_COMPSTR
+ //--------------------------------------------------------
+ MOZ_LOG(gIMMLog, LogLevel::Info, ("HandleComposition, GCS_COMPSTR"));
+
+ nsAutoString previousCompositionString(mCompositionString);
+ GetCompositionString(aContext, GCS_COMPSTR, mCompositionString);
+
+ if (!IS_COMPOSING_LPARAM(lParam)) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleComposition, lParam doesn't indicate composing, "
+ "mCompositionString=\"%s\", previousCompositionString=\"%s\"",
+ NS_ConvertUTF16toUTF8(mCompositionString).get(),
+ NS_ConvertUTF16toUTF8(previousCompositionString).get()));
+
+ // If composition string isn't changed, we can trust the lParam.
+ // So, we need to do nothing.
+ if (previousCompositionString == mCompositionString) {
+ return ShouldDrawCompositionStringOurselves();
+ }
+
+ // IME may send WM_IME_COMPOSITION without composing lParam values
+ // when composition string becomes empty (e.g., using Backspace key).
+ // If composition string is empty, we should dispatch a compositionchange
+ // event with empty string and clear the clause information.
+ if (mCompositionString.IsEmpty()) {
+ mClauseArray.Clear();
+ mAttributeArray.Clear();
+ mCursorPosition = 0;
+ DispatchCompositionChangeEvent(aWindow, aContext);
+ return ShouldDrawCompositionStringOurselves();
+ }
+
+ // Otherwise, we cannot trust the lParam value. We might need to
+ // dispatch compositionchange event with the latest composition string
+ // information.
+ }
+
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=296339
+ if (mCompositionString.IsEmpty() && !startCompositionMessageHasBeenSent) {
+ // In this case, maybe, the sender is MSPinYin. That sends *only*
+ // WM_IME_COMPOSITION with GCS_COMP* and GCS_RESULT* when
+ // user inputted the Chinese full stop. So, that doesn't send
+ // WM_IME_STARTCOMPOSITION and WM_IME_ENDCOMPOSITION.
+ // If WM_IME_STARTCOMPOSITION was not sent and the composition
+ // string is null (it indicates the composition transaction ended),
+ // WM_IME_ENDCOMPOSITION may not be sent. If so, we cannot run
+ // HandleEndComposition() in other place.
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleComposition, Aborting GCS_COMPSTR"));
+ HandleEndComposition(aWindow);
+ return IS_COMMITTING_LPARAM(lParam);
+ }
+
+ //--------------------------------------------------------
+ // 2. Get GCS_COMPCLAUSE
+ //--------------------------------------------------------
+ long clauseArrayLength =
+ ::ImmGetCompositionStringW(aContext.get(), GCS_COMPCLAUSE, nullptr, 0);
+ clauseArrayLength /= sizeof(uint32_t);
+
+ if (clauseArrayLength > 0) {
+ nsresult rv = EnsureClauseArray(clauseArrayLength);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Intelligent ABC IME (Simplified Chinese IME, the code page is 936)
+ // will crash in ImmGetCompositionStringW for GCS_COMPCLAUSE (bug 424663).
+ // See comment 35 of the bug for the detail. Therefore, we should use A
+ // API for it, however, we should not kill Unicode support on all IMEs.
+ bool useA_API = !(sIMEProperty & IME_PROP_UNICODE);
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleComposition, GCS_COMPCLAUSE, useA_API=%s",
+ useA_API ? "TRUE" : "FALSE"));
+
+ long clauseArrayLength2 =
+ useA_API ? ::ImmGetCompositionStringA(
+ aContext.get(), GCS_COMPCLAUSE, mClauseArray.Elements(),
+ mClauseArray.Capacity() * sizeof(uint32_t))
+ : ::ImmGetCompositionStringW(
+ aContext.get(), GCS_COMPCLAUSE, mClauseArray.Elements(),
+ mClauseArray.Capacity() * sizeof(uint32_t));
+ clauseArrayLength2 /= sizeof(uint32_t);
+
+ if (clauseArrayLength != clauseArrayLength2) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleComposition, GCS_COMPCLAUSE, clauseArrayLength=%ld but "
+ "clauseArrayLength2=%ld",
+ clauseArrayLength, clauseArrayLength2));
+ if (clauseArrayLength > clauseArrayLength2)
+ clauseArrayLength = clauseArrayLength2;
+ }
+
+ if (useA_API && clauseArrayLength > 0) {
+ // Convert each values of sIMECompClauseArray. The values mean offset of
+ // the clauses in ANSI string. But we need the values in Unicode string.
+ nsAutoCString compANSIStr;
+ if (ConvertToANSIString(mCompositionString, GetKeyboardCodePage(),
+ compANSIStr)) {
+ uint32_t maxlen = compANSIStr.Length();
+ mClauseArray.SetLength(clauseArrayLength);
+ mClauseArray[0] = 0; // first value must be 0
+ for (int32_t i = 1; i < clauseArrayLength; i++) {
+ uint32_t len = std::min(mClauseArray[i], maxlen);
+ mClauseArray[i] =
+ ::MultiByteToWideChar(GetKeyboardCodePage(), MB_PRECOMPOSED,
+ (LPCSTR)compANSIStr.get(), len, nullptr, 0);
+ }
+ }
+ }
+ }
+ // compClauseArrayLength may be negative. I.e., ImmGetCompositionStringW
+ // may return an error code.
+ mClauseArray.SetLength(std::max<long>(0, clauseArrayLength));
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleComposition, GCS_COMPCLAUSE, mClauseLength=%ld",
+ mClauseArray.Length()));
+
+ //--------------------------------------------------------
+ // 3. Get GCS_COMPATTR
+ //--------------------------------------------------------
+ // This provides us with the attribute string necessary
+ // for doing hiliting
+ long attrArrayLength =
+ ::ImmGetCompositionStringW(aContext.get(), GCS_COMPATTR, nullptr, 0);
+ attrArrayLength /= sizeof(uint8_t);
+
+ if (attrArrayLength > 0) {
+ nsresult rv = EnsureAttributeArray(attrArrayLength);
+ NS_ENSURE_SUCCESS(rv, false);
+ attrArrayLength = ::ImmGetCompositionStringW(
+ aContext.get(), GCS_COMPATTR, mAttributeArray.Elements(),
+ mAttributeArray.Capacity() * sizeof(uint8_t));
+ }
+
+ // attrStrLen may be negative. I.e., ImmGetCompositionStringW may return an
+ // error code.
+ mAttributeArray.SetLength(std::max<long>(0, attrArrayLength));
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleComposition, GCS_COMPATTR, mAttributeLength=%ld",
+ mAttributeArray.Length()));
+
+ //--------------------------------------------------------
+ // 4. Get GCS_CURSOPOS
+ //--------------------------------------------------------
+ // Some IMEs (e.g., the standard IME for Korean) don't have caret position.
+ if (lParam & GCS_CURSORPOS) {
+ mCursorPosition =
+ ::ImmGetCompositionStringW(aContext.get(), GCS_CURSORPOS, nullptr, 0);
+ if (mCursorPosition < 0) {
+ mCursorPosition = NO_IME_CARET; // The result is error
+ }
+ } else {
+ mCursorPosition = NO_IME_CARET;
+ }
+
+ NS_ASSERTION(mCursorPosition <= (long)mCompositionString.Length(),
+ "illegal pos");
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleComposition, GCS_CURSORPOS, mCursorPosition=%d",
+ mCursorPosition));
+
+ //--------------------------------------------------------
+ // 5. Send the compositionchange event
+ //--------------------------------------------------------
+ DispatchCompositionChangeEvent(aWindow, aContext);
+
+ return ShouldDrawCompositionStringOurselves();
+}
+
+void IMMHandler::HandleEndComposition(nsWindow* aWindow,
+ const nsAString* aCommitString) {
+ MOZ_ASSERT(mIsComposing,
+ "HandleEndComposition is called but mIsComposing is FALSE");
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleEndComposition(aWindow=0x%p, aCommitString=0x%p (\"%s\"))",
+ aWindow, aCommitString,
+ aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : ""));
+
+ IMEHandler::MaybeDestroyNativeCaret();
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleEndComposition, FAILED due to "
+ "TextEventDispatcher::BeginNativeInputTransaction() failure"));
+ return;
+ }
+ WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = dispatcher->CommitComposition(status, aCommitString, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleStartComposition, FAILED, due to "
+ "TextEventDispatcher::CommitComposition() failure"));
+ return;
+ }
+ mIsComposing = false;
+ // XXX aWindow and mComposingWindow are always same??
+ mComposingWindow = nullptr;
+ mDispatcher = nullptr;
+}
+
+bool IMMHandler::HandleReconvert(nsWindow* aWindow, LPARAM lParam,
+ LRESULT* oResult) {
+ *oResult = 0;
+ RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
+
+ Selection& selection = GetSelection();
+ if (!selection.EnsureValidSelection(aWindow)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleReconvert, FAILED, due to "
+ "Selection::EnsureValidSelection() failure"));
+ return false;
+ }
+
+ uint32_t len = selection.Length();
+ uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
+
+ if (!pReconv) {
+ // Return need size to reconvert.
+ if (len == 0) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleReconvert, There are not selected text"));
+ return false;
+ }
+ *oResult = needSize;
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleReconvert, succeeded, result=%ld", *oResult));
+ return true;
+ }
+
+ if (pReconv->dwSize < needSize) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleReconvert, FAILED, pReconv->dwSize=%ld, needSize=%ld",
+ pReconv->dwSize, needSize));
+ return false;
+ }
+
+ *oResult = needSize;
+
+ // Fill reconvert struct
+ pReconv->dwVersion = 0;
+ pReconv->dwStrLen = len;
+ pReconv->dwStrOffset = sizeof(RECONVERTSTRING);
+ pReconv->dwCompStrLen = len;
+ pReconv->dwCompStrOffset = 0;
+ pReconv->dwTargetStrLen = len;
+ pReconv->dwTargetStrOffset = 0;
+
+ ::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)),
+ selection.mString.get(), len * sizeof(WCHAR));
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleReconvert, SUCCEEDED, pReconv=%s, result=%ld",
+ GetReconvertStringLog(pReconv).get(), *oResult));
+
+ return true;
+}
+
+bool IMMHandler::HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam,
+ LRESULT* oResult) {
+ uint32_t len = mIsComposing ? mCompositionString.Length() : 0;
+ *oResult = false;
+ IMECHARPOSITION* pCharPosition = reinterpret_cast<IMECHARPOSITION*>(lParam);
+ if (!pCharPosition) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleQueryCharPosition, FAILED, due to pCharPosition is null"));
+ return false;
+ }
+ if (pCharPosition->dwSize < sizeof(IMECHARPOSITION)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleReconvert, FAILED, pCharPosition->dwSize=%ld, "
+ "sizeof(IMECHARPOSITION)=%ld",
+ pCharPosition->dwSize, sizeof(IMECHARPOSITION)));
+ return false;
+ }
+ if (::GetFocus() != aWindow->GetWindowHandle()) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleReconvert, FAILED, ::GetFocus()=%08x, OurWindowHandle=%08x",
+ ::GetFocus(), aWindow->GetWindowHandle()));
+ return false;
+ }
+ if (pCharPosition->dwCharPos > len) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleQueryCharPosition, FAILED, pCharPosition->dwCharPos=%ld, "
+ "len=%ld",
+ pCharPosition->dwCharPos, len));
+ return false;
+ }
+
+ LayoutDeviceIntRect r;
+ bool ret =
+ GetCharacterRectOfSelectedTextAt(aWindow, pCharPosition->dwCharPos, r);
+ NS_ENSURE_TRUE(ret, false);
+
+ LayoutDeviceIntRect screenRect;
+ // We always need top level window that is owner window of the popup window
+ // even if the content of the popup window has focus.
+ ResolveIMECaretPos(aWindow->GetTopLevelWindow(false), r, nullptr, screenRect);
+
+ // XXX This might need to check writing mode. However, MSDN doesn't explain
+ // how to set the values in vertical writing mode. Additionally, IME
+ // doesn't work well with top-left of the character (this is explicitly
+ // documented) and its horizontal width. So, it might be better to set
+ // top-right corner of the character and horizontal width, but we're not
+ // sure if it doesn't cause any problems with a lot of IMEs...
+ pCharPosition->pt.x = screenRect.X();
+ pCharPosition->pt.y = screenRect.Y();
+
+ pCharPosition->cLineHeight = r.Height();
+
+ WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, aWindow);
+ aWindow->InitEvent(queryEditorRectEvent);
+ DispatchEvent(aWindow, queryEditorRectEvent);
+ if (NS_WARN_IF(queryEditorRectEvent.Failed())) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleQueryCharPosition, eQueryEditorRect failed"));
+ ::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument);
+ } else {
+ LayoutDeviceIntRect editorRectInWindow = queryEditorRectEvent.mReply->mRect;
+ nsWindow* window = !!queryEditorRectEvent.mReply->mFocusedWidget
+ ? static_cast<nsWindow*>(
+ queryEditorRectEvent.mReply->mFocusedWidget)
+ : aWindow;
+ LayoutDeviceIntRect editorRectInScreen;
+ ResolveIMECaretPos(window, editorRectInWindow, nullptr, editorRectInScreen);
+ ::SetRect(&pCharPosition->rcDocument, editorRectInScreen.X(),
+ editorRectInScreen.Y(), editorRectInScreen.XMost(),
+ editorRectInScreen.YMost());
+ }
+
+ *oResult = TRUE;
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleQueryCharPosition, SUCCEEDED, pCharPosition={ pt={ x=%d, "
+ "y=%d }, cLineHeight=%d, rcDocument={ left=%d, top=%d, right=%d, "
+ "bottom=%d } }",
+ pCharPosition->pt.x, pCharPosition->pt.y, pCharPosition->cLineHeight,
+ pCharPosition->rcDocument.left, pCharPosition->rcDocument.top,
+ pCharPosition->rcDocument.right, pCharPosition->rcDocument.bottom));
+ return true;
+}
+
+bool IMMHandler::HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam,
+ LRESULT* oResult) {
+ *oResult = 0;
+ RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
+
+ LayoutDeviceIntPoint point(0, 0);
+
+ bool hasCompositionString =
+ mIsComposing && ShouldDrawCompositionStringOurselves();
+
+ int32_t targetOffset, targetLength;
+ if (!hasCompositionString) {
+ Selection& selection = GetSelection();
+ if (!selection.EnsureValidSelection(aWindow)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleDocumentFeed, FAILED, due to "
+ "Selection::EnsureValidSelection() failure"));
+ return false;
+ }
+ targetOffset = int32_t(selection.mOffset);
+ targetLength = int32_t(selection.Length());
+ } else {
+ targetOffset = int32_t(mCompositionStart);
+ targetLength = int32_t(mCompositionString.Length());
+ }
+
+ // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
+ // we cannot support this message when the current offset is larger than
+ // INT32_MAX.
+ if (targetOffset < 0 || targetLength < 0 || targetOffset + targetLength < 0) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleDocumentFeed, FAILED, due to the selection is out of "
+ "range"));
+ return false;
+ }
+
+ // Get all contents of the focused editor.
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
+ aWindow);
+ queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
+ aWindow->InitEvent(queryTextContentEvent, &point);
+ DispatchEvent(aWindow, queryTextContentEvent);
+ if (NS_WARN_IF(queryTextContentEvent.Failed())) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleDocumentFeed, FAILED, due to eQueryTextContent failure"));
+ return false;
+ }
+
+ nsAutoString str(queryTextContentEvent.mReply->DataRef());
+ if (targetOffset > static_cast<int32_t>(str.Length())) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleDocumentFeed, FAILED, due to the caret offset is invalid"));
+ return false;
+ }
+
+ // Get the focused paragraph, we decide that it starts from the previous CRLF
+ // (or start of the editor) to the next one (or the end of the editor).
+ int32_t paragraphStart = str.RFind("\n", false, targetOffset, -1) + 1;
+ int32_t paragraphEnd = str.Find("\r", false, targetOffset + targetLength, -1);
+ if (paragraphEnd < 0) {
+ paragraphEnd = str.Length();
+ }
+ nsDependentSubstring paragraph(str, paragraphStart,
+ paragraphEnd - paragraphStart);
+
+ uint32_t len = paragraph.Length();
+ uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
+
+ if (!pReconv) {
+ *oResult = needSize;
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleDocumentFeed, succeeded, result=%ld", *oResult));
+ return true;
+ }
+
+ if (pReconv->dwSize < needSize) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleDocumentFeed, FAILED, pReconv->dwSize=%ld, needSize=%ld",
+ pReconv->dwSize, needSize));
+ return false;
+ }
+
+ // Fill reconvert struct
+ pReconv->dwVersion = 0;
+ pReconv->dwStrLen = len;
+ pReconv->dwStrOffset = sizeof(RECONVERTSTRING);
+ if (hasCompositionString) {
+ pReconv->dwCompStrLen = targetLength;
+ pReconv->dwCompStrOffset = (targetOffset - paragraphStart) * sizeof(WCHAR);
+ // Set composition target clause information
+ uint32_t offset, length;
+ if (!GetTargetClauseRange(&offset, &length)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleDocumentFeed, FAILED, due to GetTargetClauseRange() "
+ "failure"));
+ return false;
+ }
+ pReconv->dwTargetStrLen = length;
+ pReconv->dwTargetStrOffset = (offset - paragraphStart) * sizeof(WCHAR);
+ } else {
+ pReconv->dwTargetStrLen = targetLength;
+ pReconv->dwTargetStrOffset =
+ (targetOffset - paragraphStart) * sizeof(WCHAR);
+ // There is no composition string, so, the length is zero but we should
+ // set the cursor offset to the composition str offset.
+ pReconv->dwCompStrLen = 0;
+ pReconv->dwCompStrOffset = pReconv->dwTargetStrOffset;
+ }
+
+ *oResult = needSize;
+ ::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)),
+ paragraph.BeginReading(), len * sizeof(WCHAR));
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleDocumentFeed, SUCCEEDED, pReconv=%s, result=%ld",
+ GetReconvertStringLog(pReconv).get(), *oResult));
+
+ return true;
+}
+
+bool IMMHandler::CommitCompositionOnPreviousWindow(nsWindow* aWindow) {
+ if (!mComposingWindow || mComposingWindow == aWindow) {
+ return false;
+ }
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("CommitCompositionOnPreviousWindow, mIsComposing=%s",
+ GetBoolName(mIsComposing)));
+
+ // If we have composition, we should dispatch composition events internally.
+ if (mIsComposing) {
+ IMEContext context(mComposingWindow);
+ NS_ASSERTION(context.IsValid(), "IME context must be valid");
+
+ HandleEndComposition(mComposingWindow);
+ return true;
+ }
+
+ return false;
+}
+
+static TextRangeType PlatformToNSAttr(uint8_t aAttr) {
+ switch (aAttr) {
+ case ATTR_INPUT_ERROR:
+ // case ATTR_FIXEDCONVERTED:
+ case ATTR_INPUT:
+ return TextRangeType::eRawClause;
+ case ATTR_CONVERTED:
+ return TextRangeType::eConvertedClause;
+ case ATTR_TARGET_NOTCONVERTED:
+ return TextRangeType::eSelectedRawClause;
+ case ATTR_TARGET_CONVERTED:
+ return TextRangeType::eSelectedClause;
+ default:
+ NS_ASSERTION(false, "unknown attribute");
+ return TextRangeType::eCaret;
+ }
+}
+
+// static
+void IMMHandler::DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent) {
+ MOZ_LOG(
+ gIMMLog, LogLevel::Info,
+ ("DispatchEvent(aWindow=0x%p, aEvent={ mMessage=%s }, "
+ "aWindow->Destroyed()=%s",
+ aWindow, ToChar(aEvent.mMessage), GetBoolName(aWindow->Destroyed())));
+
+ if (aWindow->Destroyed()) {
+ return;
+ }
+
+ aWindow->DispatchWindowEvent(&aEvent);
+}
+
+void IMMHandler::DispatchCompositionChangeEvent(nsWindow* aWindow,
+ const IMEContext& aContext) {
+ NS_ASSERTION(mIsComposing, "conflict state");
+ MOZ_LOG(gIMMLog, LogLevel::Info, ("DispatchCompositionChangeEvent"));
+
+ // If we don't need to draw composition string ourselves, we don't need to
+ // fire compositionchange event during composing.
+ if (!ShouldDrawCompositionStringOurselves()) {
+ // But we need to adjust composition window pos and native caret pos, here.
+ SetIMERelatedWindowsPos(aWindow, aContext);
+ return;
+ }
+
+ RefPtr<nsWindow> kungFuDeathGrip(aWindow);
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("DispatchCompositionChangeEvent, FAILED due to "
+ "TextEventDispatcher::BeginNativeInputTransaction() failure"));
+ return;
+ }
+
+ // NOTE: Calling SetIMERelatedWindowsPos() from this method will be failure
+ // in e10s mode. compositionchange event will notify this of
+ // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED, then
+ // SetIMERelatedWindowsPos() will be called.
+
+ // XXX Sogou (Simplified Chinese IME) returns contradictory values:
+ // The cursor position is actual cursor position. However, other values
+ // (composition string and attributes) are empty.
+
+ if (mCompositionString.IsEmpty()) {
+ // Don't append clause information if composition string is empty.
+ } else if (mClauseArray.IsEmpty()) {
+ // Some IMEs don't return clause array information, then, we assume that
+ // all characters in the composition string are in one clause.
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("DispatchCompositionChangeEvent, mClauseArray.Length()=0"));
+ rv = dispatcher->SetPendingComposition(mCompositionString, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("DispatchCompositionChangeEvent, FAILED due to"
+ "TextEventDispatcher::SetPendingComposition() failure"));
+ return;
+ }
+ } else {
+ // iterate over the attributes
+ rv = dispatcher->SetPendingCompositionString(mCompositionString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("DispatchCompositionChangeEvent, FAILED due to"
+ "TextEventDispatcher::SetPendingCompositionString() failure"));
+ return;
+ }
+ uint32_t lastOffset = 0;
+ for (uint32_t i = 0; i < mClauseArray.Length() - 1; i++) {
+ uint32_t current = mClauseArray[i + 1];
+ if (current > mCompositionString.Length()) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("DispatchCompositionChangeEvent, mClauseArray[%ld]=%lu. "
+ "This is larger than mCompositionString.Length()=%lu",
+ i + 1, current, mCompositionString.Length()));
+ current = int32_t(mCompositionString.Length());
+ }
+
+ uint32_t length = current - lastOffset;
+ if (NS_WARN_IF(lastOffset >= mAttributeArray.Length())) {
+ MOZ_LOG(
+ gIMMLog, LogLevel::Error,
+ ("DispatchCompositionChangeEvent, FAILED due to invalid data of "
+ "mClauseArray or mAttributeArray"));
+ return;
+ }
+ TextRangeType textRangeType =
+ PlatformToNSAttr(mAttributeArray[lastOffset]);
+ rv = dispatcher->AppendClauseToPendingComposition(length, textRangeType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("DispatchCompositionChangeEvent, FAILED due to"
+ "TextEventDispatcher::AppendClauseToPendingComposition() "
+ "failure"));
+ return;
+ }
+
+ lastOffset = current;
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("DispatchCompositionChangeEvent, index=%ld, rangeType=%s, "
+ "range length=%lu",
+ i, ToChar(textRangeType), length));
+ }
+ }
+
+ if (mCursorPosition == NO_IME_CARET) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("DispatchCompositionChangeEvent, no caret"));
+ } else {
+ uint32_t cursor = static_cast<uint32_t>(mCursorPosition);
+ if (cursor > mCompositionString.Length()) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("CreateTextRangeArray, mCursorPosition=%ld. "
+ "This is larger than mCompositionString.Length()=%lu",
+ mCursorPosition, mCompositionString.Length()));
+ cursor = mCompositionString.Length();
+ }
+
+ // If caret is in the target clause, the target clause will be painted as
+ // normal selection range. Since caret shouldn't be in selection range on
+ // Windows, we shouldn't append caret range in such case.
+ const TextRangeArray* clauses = dispatcher->GetPendingCompositionClauses();
+ const TextRange* targetClause =
+ clauses ? clauses->GetTargetClause() : nullptr;
+ if (targetClause && cursor >= targetClause->mStartOffset &&
+ cursor <= targetClause->mEndOffset) {
+ // Forget the caret position specified by IME since Gecko's caret position
+ // will be at the end of composition string.
+ mCursorPosition = NO_IME_CARET;
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("CreateTextRangeArray, no caret due to it's in the target "
+ "clause, now, mCursorPosition is NO_IME_CARET"));
+ }
+
+ if (mCursorPosition != NO_IME_CARET) {
+ rv = dispatcher->SetCaretInPendingComposition(cursor, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(
+ gIMMLog, LogLevel::Error,
+ ("DispatchCompositionChangeEvent, FAILED due to"
+ "TextEventDispatcher::SetCaretInPendingComposition() failure"));
+ return;
+ }
+ }
+ }
+
+ WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = dispatcher->FlushPendingComposition(status, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("DispatchCompositionChangeEvent, FAILED due to"
+ "TextEventDispatcher::FlushPendingComposition() failure"));
+ return;
+ }
+}
+
+void IMMHandler::GetCompositionString(const IMEContext& aContext, DWORD aIndex,
+ nsAString& aCompositionString) const {
+ aCompositionString.Truncate();
+
+ // Retrieve the size of the required output buffer.
+ long lRtn = ::ImmGetCompositionStringW(aContext.get(), aIndex, nullptr, 0);
+ if (lRtn < 0 || !aCompositionString.SetLength((lRtn / sizeof(WCHAR)) + 1,
+ mozilla::fallible)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("GetCompositionString, FAILED, due to OOM"));
+ return; // Error or out of memory.
+ }
+
+ // Actually retrieve the composition string information.
+ lRtn = ::ImmGetCompositionStringW(aContext.get(), aIndex,
+ (LPVOID)aCompositionString.BeginWriting(),
+ lRtn + sizeof(WCHAR));
+ aCompositionString.SetLength(lRtn / sizeof(WCHAR));
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("GetCompositionString, succeeded, aCompositionString=\"%s\"",
+ NS_ConvertUTF16toUTF8(aCompositionString).get()));
+}
+
+bool IMMHandler::GetTargetClauseRange(uint32_t* aOffset, uint32_t* aLength) {
+ NS_ENSURE_TRUE(aOffset, false);
+ NS_ENSURE_TRUE(mIsComposing, false);
+ NS_ENSURE_TRUE(ShouldDrawCompositionStringOurselves(), false);
+
+ bool found = false;
+ *aOffset = mCompositionStart;
+ for (uint32_t i = 0; i < mAttributeArray.Length(); i++) {
+ if (mAttributeArray[i] == ATTR_TARGET_NOTCONVERTED ||
+ mAttributeArray[i] == ATTR_TARGET_CONVERTED) {
+ *aOffset = mCompositionStart + i;
+ found = true;
+ break;
+ }
+ }
+
+ if (!aLength) {
+ return true;
+ }
+
+ if (!found) {
+ // The all composition string is targetted when there is no ATTR_TARGET_*
+ // clause. E.g., there is only ATTR_INPUT
+ *aLength = mCompositionString.Length();
+ return true;
+ }
+
+ uint32_t offsetInComposition = *aOffset - mCompositionStart;
+ *aLength = mCompositionString.Length() - offsetInComposition;
+ for (uint32_t i = offsetInComposition; i < mAttributeArray.Length(); i++) {
+ if (mAttributeArray[i] != ATTR_TARGET_NOTCONVERTED &&
+ mAttributeArray[i] != ATTR_TARGET_CONVERTED) {
+ *aLength = i - offsetInComposition;
+ break;
+ }
+ }
+ return true;
+}
+
+bool IMMHandler::ConvertToANSIString(const nsString& aStr, UINT aCodePage,
+ nsACString& aANSIStr) {
+ int len = ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(),
+ aStr.Length(), nullptr, 0, nullptr, nullptr);
+ NS_ENSURE_TRUE(len >= 0, false);
+
+ if (!aANSIStr.SetLength(len, mozilla::fallible)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("ConvertToANSIString, FAILED, due to OOM"));
+ return false;
+ }
+ ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), aStr.Length(),
+ (LPSTR)aANSIStr.BeginWriting(), len, nullptr, nullptr);
+ return true;
+}
+
+bool IMMHandler::GetCharacterRectOfSelectedTextAt(
+ nsWindow* aWindow, uint32_t aOffset, LayoutDeviceIntRect& aCharRect,
+ WritingMode* aWritingMode) {
+ LayoutDeviceIntPoint point(0, 0);
+
+ Selection& selection = GetSelection();
+ if (!selection.EnsureValidSelection(aWindow)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("GetCharacterRectOfSelectedTextAt, FAILED, due to "
+ "Selection::EnsureValidSelection() failure"));
+ return false;
+ }
+
+ // If the offset is larger than the end of composition string or selected
+ // string, we should return false since such case must be a bug of the caller
+ // or the active IME. If it's an IME's bug, we need to set targetLength to
+ // aOffset.
+ uint32_t targetLength =
+ mIsComposing ? mCompositionString.Length() : selection.Length();
+ if (NS_WARN_IF(aOffset > targetLength)) {
+ MOZ_LOG(
+ gIMMLog, LogLevel::Error,
+ ("GetCharacterRectOfSelectedTextAt, FAILED, due to "
+ "aOffset is too large (aOffset=%u, targetLength=%u, mIsComposing=%s)",
+ aOffset, targetLength, GetBoolName(mIsComposing)));
+ return false;
+ }
+
+ // If there is caret, we might be able to use caret rect.
+ uint32_t caretOffset = UINT32_MAX;
+ // There is a caret only when the normal selection is collapsed.
+ if (selection.Collapsed()) {
+ if (mIsComposing) {
+ // If it's composing, mCursorPosition is the offset to caret in
+ // the composition string.
+ if (mCursorPosition != NO_IME_CARET) {
+ MOZ_ASSERT(mCursorPosition >= 0);
+ caretOffset = mCursorPosition;
+ } else if (!ShouldDrawCompositionStringOurselves() ||
+ mCompositionString.IsEmpty()) {
+ // Otherwise, if there is no composition string, we should assume that
+ // there is a caret at the start of composition string.
+ caretOffset = 0;
+ }
+ } else {
+ // If there is no composition, the selection offset is the caret offset.
+ caretOffset = 0;
+ }
+ }
+
+ // If there is a caret and retrieving offset is same as the caret offset,
+ // we should use the caret rect.
+ if (aOffset != caretOffset) {
+ WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, aWindow);
+ WidgetQueryContentEvent::Options options;
+ options.mRelativeToInsertionPoint = true;
+ queryTextRectEvent.InitForQueryTextRect(aOffset, 1, options);
+ aWindow->InitEvent(queryTextRectEvent, &point);
+ DispatchEvent(aWindow, queryTextRectEvent);
+ if (queryTextRectEvent.Succeeded()) {
+ aCharRect = queryTextRectEvent.mReply->mRect;
+ if (aWritingMode) {
+ *aWritingMode = queryTextRectEvent.mReply->WritingModeRef();
+ }
+ MOZ_LOG(
+ gIMMLog, LogLevel::Debug,
+ ("GetCharacterRectOfSelectedTextAt, Succeeded, aOffset=%u, "
+ "aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
+ "queryTextRectEvent={ mReply=%s }",
+ aOffset, aCharRect.X(), aCharRect.Y(), aCharRect.Width(),
+ aCharRect.Height(), ToString(queryTextRectEvent.mReply).c_str()));
+ return true;
+ }
+ }
+
+ return GetCaretRect(aWindow, aCharRect, aWritingMode);
+}
+
+bool IMMHandler::GetCaretRect(nsWindow* aWindow,
+ LayoutDeviceIntRect& aCaretRect,
+ WritingMode* aWritingMode) {
+ LayoutDeviceIntPoint point(0, 0);
+
+ WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, aWindow);
+ WidgetQueryContentEvent::Options options;
+ options.mRelativeToInsertionPoint = true;
+ queryCaretRectEvent.InitForQueryCaretRect(0, options);
+ aWindow->InitEvent(queryCaretRectEvent, &point);
+ DispatchEvent(aWindow, queryCaretRectEvent);
+ if (queryCaretRectEvent.Failed()) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("GetCaretRect, FAILED, due to eQueryCaretRect failure"));
+ return false;
+ }
+ aCaretRect = queryCaretRectEvent.mReply->mRect;
+ if (aWritingMode) {
+ *aWritingMode = queryCaretRectEvent.mReply->WritingModeRef();
+ }
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("GetCaretRect, SUCCEEDED, "
+ "aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
+ "queryCaretRectEvent={ mReply=%s }",
+ aCaretRect.X(), aCaretRect.Y(), aCaretRect.Width(),
+ aCaretRect.Height(), ToString(queryCaretRectEvent.mReply).c_str()));
+ return true;
+}
+
+bool IMMHandler::SetIMERelatedWindowsPos(nsWindow* aWindow,
+ const IMEContext& aContext) {
+ // Get first character rect of current a normal selected text or a composing
+ // string.
+ WritingMode writingMode;
+ LayoutDeviceIntRect firstSelectedCharRectRelativeToWindow;
+ bool ret = GetCharacterRectOfSelectedTextAt(
+ aWindow, 0, firstSelectedCharRectRelativeToWindow, &writingMode);
+ NS_ENSURE_TRUE(ret, false);
+ nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
+ LayoutDeviceIntRect firstSelectedCharRect;
+ ResolveIMECaretPos(toplevelWindow, firstSelectedCharRectRelativeToWindow,
+ aWindow, firstSelectedCharRect);
+
+ // Set native caret size/position to our caret. Some IMEs honor it. E.g.,
+ // "Intelligent ABC" (Simplified Chinese) and "MS PinYin 3.0" (Simplified
+ // Chinese) on XP. But if a11y module is handling native caret, we shouldn't
+ // touch it.
+ if (!IMEHandler::IsA11yHandlingNativeCaret()) {
+ LayoutDeviceIntRect caretRect(firstSelectedCharRect),
+ caretRectRelativeToWindow;
+ if (GetCaretRect(aWindow, caretRectRelativeToWindow)) {
+ ResolveIMECaretPos(toplevelWindow, caretRectRelativeToWindow, aWindow,
+ caretRect);
+ } else {
+ NS_WARNING("failed to get caret rect");
+ caretRect.SetWidth(1);
+ }
+ IMEHandler::CreateNativeCaret(aWindow, caretRect);
+ }
+
+ if (ShouldDrawCompositionStringOurselves()) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("SetIMERelatedWindowsPos, Set candidate window"));
+
+ // Get a rect of first character in current target in composition string.
+ LayoutDeviceIntRect firstTargetCharRect, lastTargetCharRect;
+ if (mIsComposing && !mCompositionString.IsEmpty()) {
+ // If there are no targetted selection, we should use it's first character
+ // rect instead.
+ uint32_t offset, length;
+ if (!GetTargetClauseRange(&offset, &length)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("SetIMERelatedWindowsPos, FAILED, due to "
+ "GetTargetClauseRange() failure"));
+ return false;
+ }
+ ret =
+ GetCharacterRectOfSelectedTextAt(aWindow, offset - mCompositionStart,
+ firstTargetCharRect, &writingMode);
+ NS_ENSURE_TRUE(ret, false);
+ if (length) {
+ ret = GetCharacterRectOfSelectedTextAt(
+ aWindow, offset + length - 1 - mCompositionStart,
+ lastTargetCharRect);
+ NS_ENSURE_TRUE(ret, false);
+ } else {
+ lastTargetCharRect = firstTargetCharRect;
+ }
+ } else {
+ // If there are no composition string, we should use a first character
+ // rect.
+ ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, firstTargetCharRect,
+ &writingMode);
+ NS_ENSURE_TRUE(ret, false);
+ lastTargetCharRect = firstTargetCharRect;
+ }
+ ResolveIMECaretPos(toplevelWindow, firstTargetCharRect, aWindow,
+ firstTargetCharRect);
+ ResolveIMECaretPos(toplevelWindow, lastTargetCharRect, aWindow,
+ lastTargetCharRect);
+ LayoutDeviceIntRect targetClauseRect;
+ targetClauseRect.UnionRect(firstTargetCharRect, lastTargetCharRect);
+
+ // Move the candidate window to proper position from the target clause as
+ // far as possible.
+ CANDIDATEFORM candForm;
+ candForm.dwIndex = 0;
+ if (!writingMode.IsVertical() || IsVerticalWritingSupported()) {
+ candForm.dwStyle = CFS_EXCLUDE;
+ // Candidate window shouldn't overlap the target clause in any writing
+ // mode.
+ candForm.rcArea.left = targetClauseRect.X();
+ candForm.rcArea.right = targetClauseRect.XMost();
+ candForm.rcArea.top = targetClauseRect.Y();
+ candForm.rcArea.bottom = targetClauseRect.YMost();
+ if (!writingMode.IsVertical()) {
+ // In horizontal layout, current point of interest should be top-left
+ // of the first character.
+ candForm.ptCurrentPos.x = firstTargetCharRect.X();
+ candForm.ptCurrentPos.y = firstTargetCharRect.Y();
+ } else if (writingMode.IsVerticalRL()) {
+ // In vertical layout (RL), candidate window should be positioned right
+ // side of target clause. However, we don't set vertical writing font
+ // to the IME. Therefore, the candidate window may be positioned
+ // bottom-left of target clause rect with these information.
+ candForm.ptCurrentPos.x = targetClauseRect.X();
+ candForm.ptCurrentPos.y = targetClauseRect.Y();
+ } else {
+ MOZ_ASSERT(writingMode.IsVerticalLR(), "Did we miss some causes?");
+ // In vertical layout (LR), candidate window should be poisitioned left
+ // side of target clause. Although, we don't set vertical writing font
+ // to the IME, the candidate window may be positioned bottom-right of
+ // the target clause rect with these information.
+ candForm.ptCurrentPos.x = targetClauseRect.XMost();
+ candForm.ptCurrentPos.y = targetClauseRect.Y();
+ }
+ } else {
+ // If vertical writing is not supported by IME, let's set candidate
+ // window position to the bottom-left of the target clause because
+ // the position must be the safest position to prevent the candidate
+ // window to overlap with the target clause.
+ candForm.dwStyle = CFS_CANDIDATEPOS;
+ candForm.ptCurrentPos.x = targetClauseRect.X();
+ candForm.ptCurrentPos.y = targetClauseRect.YMost();
+ }
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("SetIMERelatedWindowsPos, Calling ImmSetCandidateWindow()... "
+ "ptCurrentPos={ x=%d, y=%d }, "
+ "rcArea={ left=%d, top=%d, right=%d, bottom=%d }, "
+ "writingMode=%s",
+ candForm.ptCurrentPos.x, candForm.ptCurrentPos.y,
+ candForm.rcArea.left, candForm.rcArea.top, candForm.rcArea.right,
+ candForm.rcArea.bottom, GetWritingModeName(writingMode).get()));
+ ::ImmSetCandidateWindow(aContext.get(), &candForm);
+ } else {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("SetIMERelatedWindowsPos, Set composition window"));
+
+ // Move the composition window to caret position (if selected some
+ // characters, we should use first character rect of them).
+ // And in this mode, IME adjusts the candidate window position
+ // automatically. So, we don't need to set it.
+ COMPOSITIONFORM compForm;
+ compForm.dwStyle = CFS_POINT;
+ compForm.ptCurrentPos.x = !writingMode.IsVerticalLR()
+ ? firstSelectedCharRect.X()
+ : firstSelectedCharRect.XMost();
+ compForm.ptCurrentPos.y = firstSelectedCharRect.Y();
+ ::ImmSetCompositionWindow(aContext.get(), &compForm);
+ }
+
+ return true;
+}
+
+void IMMHandler::ResolveIMECaretPos(nsIWidget* aReferenceWidget,
+ LayoutDeviceIntRect& aCursorRect,
+ nsIWidget* aNewOriginWidget,
+ LayoutDeviceIntRect& aOutRect) {
+ aOutRect = aCursorRect;
+
+ if (aReferenceWidget == aNewOriginWidget) return;
+
+ if (aReferenceWidget)
+ aOutRect.MoveBy(aReferenceWidget->WidgetToScreenOffset());
+
+ if (aNewOriginWidget)
+ aOutRect.MoveBy(-aNewOriginWidget->WidgetToScreenOffset());
+}
+
+static void SetHorizontalFontToLogFont(const nsAString& aFontFace,
+ LOGFONTW& aLogFont) {
+ aLogFont.lfEscapement = aLogFont.lfOrientation = 0;
+ if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 1)) {
+ memcpy(aLogFont.lfFaceName, L"System", sizeof(L"System"));
+ return;
+ }
+ memcpy(aLogFont.lfFaceName, aFontFace.BeginReading(),
+ aFontFace.Length() * sizeof(wchar_t));
+ aLogFont.lfFaceName[aFontFace.Length()] = 0;
+}
+
+static void SetVerticalFontToLogFont(const nsAString& aFontFace,
+ LOGFONTW& aLogFont) {
+ aLogFont.lfEscapement = aLogFont.lfOrientation = 2700;
+ if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 2)) {
+ memcpy(aLogFont.lfFaceName, L"@System", sizeof(L"@System"));
+ return;
+ }
+ aLogFont.lfFaceName[0] = '@';
+ memcpy(&aLogFont.lfFaceName[1], aFontFace.BeginReading(),
+ aFontFace.Length() * sizeof(wchar_t));
+ aLogFont.lfFaceName[aFontFace.Length() + 1] = 0;
+}
+
+void IMMHandler::AdjustCompositionFont(nsWindow* aWindow,
+ const IMEContext& aContext,
+ const WritingMode& aWritingMode,
+ bool aForceUpdate) {
+ // An instance of IMMHandler is destroyed when active IME is changed.
+ // Therefore, we need to store the information which are set to the IM
+ // context to static variables since IM context is never recreated.
+ static bool sCompositionFontsInitialized = false;
+ static nsString sCompositionFont;
+ static bool sCompositionFontPrefDone = false;
+ if (!sCompositionFontPrefDone) {
+ sCompositionFontPrefDone = true;
+ Preferences::GetString("intl.imm.composition_font", sCompositionFont);
+ }
+
+ // If composition font is customized by pref, we need to modify the
+ // composition font of the IME context at first time even if the writing mode
+ // is horizontal.
+ bool setCompositionFontForcibly =
+ aForceUpdate ||
+ (!sCompositionFontsInitialized && !sCompositionFont.IsEmpty());
+
+ static WritingMode sCurrentWritingMode;
+ static nsString sCurrentIMEName;
+ if (!setCompositionFontForcibly &&
+ sWritingModeOfCompositionFont == aWritingMode &&
+ sCurrentIMEName == sIMEName) {
+ // Nothing to do if writing mode isn't being changed.
+ return;
+ }
+
+ // Decide composition fonts for both horizontal writing mode and vertical
+ // writing mode. If the font isn't specified by the pref, use default
+ // font which is already set to the IM context. And also in vertical writing
+ // mode, insert '@' to the start of the font.
+ if (!sCompositionFontsInitialized) {
+ sCompositionFontsInitialized = true;
+ // sCompositionFontH must not start with '@' and its length is less than
+ // LF_FACESIZE since it needs to end with null terminating character.
+ if (sCompositionFont.IsEmpty() ||
+ sCompositionFont.Length() > LF_FACESIZE - 1 ||
+ sCompositionFont[0] == '@') {
+ LOGFONTW defaultLogFont;
+ if (NS_WARN_IF(
+ !::ImmGetCompositionFont(aContext.get(), &defaultLogFont))) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("AdjustCompositionFont, ::ImmGetCompositionFont() failed"));
+ sCompositionFont.AssignLiteral("System");
+ } else {
+ // The font face is typically, "System".
+ sCompositionFont.Assign(defaultLogFont.lfFaceName);
+ }
+ }
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("AdjustCompositionFont, sCompositionFont=\"%s\" is initialized",
+ NS_ConvertUTF16toUTF8(sCompositionFont).get()));
+ }
+
+ static nsString sCompositionFontForJapanist2003;
+ if (IsJapanist2003Active() && sCompositionFontForJapanist2003.IsEmpty()) {
+ const char* kCompositionFontForJapanist2003 =
+ "intl.imm.composition_font.japanist_2003";
+ Preferences::GetString(kCompositionFontForJapanist2003,
+ sCompositionFontForJapanist2003);
+ // If the font name is not specified properly, let's use
+ // "MS PGothic" instead.
+ if (sCompositionFontForJapanist2003.IsEmpty() ||
+ sCompositionFontForJapanist2003.Length() > LF_FACESIZE - 2 ||
+ sCompositionFontForJapanist2003[0] == '@') {
+ sCompositionFontForJapanist2003.AssignLiteral("MS PGothic");
+ }
+ }
+
+ sWritingModeOfCompositionFont = aWritingMode;
+ sCurrentIMEName = sIMEName;
+
+ LOGFONTW logFont;
+ memset(&logFont, 0, sizeof(logFont));
+ if (!::ImmGetCompositionFont(aContext.get(), &logFont)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("AdjustCompositionFont, ::ImmGetCompositionFont() failed"));
+ logFont.lfFaceName[0] = 0;
+ }
+ // Need to reset some information which should be recomputed with new font.
+ logFont.lfWidth = 0;
+ logFont.lfWeight = FW_DONTCARE;
+ logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
+ logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+ logFont.lfPitchAndFamily = DEFAULT_PITCH;
+
+ if (aWritingMode.IsVertical() && IsVerticalWritingSupported()) {
+ SetVerticalFontToLogFont(IsJapanist2003Active()
+ ? sCompositionFontForJapanist2003
+ : sCompositionFont,
+ logFont);
+ } else {
+ SetHorizontalFontToLogFont(IsJapanist2003Active()
+ ? sCompositionFontForJapanist2003
+ : sCompositionFont,
+ logFont);
+ }
+ MOZ_LOG(gIMMLog, LogLevel::Warning,
+ ("AdjustCompositionFont, calling ::ImmSetCompositionFont(\"%s\")",
+ NS_ConvertUTF16toUTF8(nsDependentString(logFont.lfFaceName)).get()));
+ ::ImmSetCompositionFontW(aContext.get(), &logFont);
+}
+
+// static
+nsresult IMMHandler::OnMouseButtonEvent(
+ nsWindow* aWindow, const IMENotification& aIMENotification) {
+ // We don't need to create the instance of the handler here.
+ if (!gIMMHandler) {
+ return NS_OK;
+ }
+
+ if (!sWM_MSIME_MOUSE || !IsComposingOnOurEditor() ||
+ !ShouldDrawCompositionStringOurselves()) {
+ return NS_OK;
+ }
+
+ // We need to handle only mousedown event.
+ if (aIMENotification.mMouseButtonEventData.mEventMessage != eMouseDown) {
+ return NS_OK;
+ }
+
+ // If the character under the cursor is not in the composition string,
+ // we don't need to notify IME of it.
+ uint32_t compositionStart = gIMMHandler->mCompositionStart;
+ uint32_t compositionEnd =
+ compositionStart + gIMMHandler->mCompositionString.Length();
+ if (aIMENotification.mMouseButtonEventData.mOffset < compositionStart ||
+ aIMENotification.mMouseButtonEventData.mOffset >= compositionEnd) {
+ return NS_OK;
+ }
+
+ BYTE button;
+ switch (aIMENotification.mMouseButtonEventData.mButton) {
+ case MouseButton::ePrimary:
+ button = IMEMOUSE_LDOWN;
+ break;
+ case MouseButton::eMiddle:
+ button = IMEMOUSE_MDOWN;
+ break;
+ case MouseButton::eSecondary:
+ button = IMEMOUSE_RDOWN;
+ break;
+ default:
+ return NS_OK;
+ }
+
+ // calcurate positioning and offset
+ // char : JCH1|JCH2|JCH3
+ // offset: 0011 1122 2233
+ // positioning: 2301 2301 2301
+ nsIntPoint cursorPos =
+ aIMENotification.mMouseButtonEventData.mCursorPos.AsIntPoint();
+ nsIntRect charRect =
+ aIMENotification.mMouseButtonEventData.mCharRect.AsIntRect();
+ int32_t cursorXInChar = cursorPos.x - charRect.X();
+ // The event might hit to zero-width character, see bug 694913.
+ // The reason might be:
+ // * There are some zero-width characters are actually.
+ // * font-size is specified zero.
+ // But nobody reproduced this bug actually...
+ // We should assume that user clicked on right most of the zero-width
+ // character in such case.
+ int positioning = 1;
+ if (charRect.Width() > 0) {
+ positioning = cursorXInChar * 4 / charRect.Width();
+ positioning = (positioning + 2) % 4;
+ }
+
+ int offset =
+ aIMENotification.mMouseButtonEventData.mOffset - compositionStart;
+ if (positioning < 2) {
+ offset++;
+ }
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnMouseButtonEvent, x,y=%ld,%ld, offset=%ld, positioning=%ld",
+ cursorPos.x, cursorPos.y, offset, positioning));
+
+ // send MS_MSIME_MOUSE message to default IME window.
+ HWND imeWnd = ::ImmGetDefaultIMEWnd(aWindow->GetWindowHandle());
+ IMEContext context(aWindow);
+ if (::SendMessageW(imeWnd, sWM_MSIME_MOUSE,
+ MAKELONG(MAKEWORD(button, positioning), offset),
+ (LPARAM)context.get()) == 1) {
+ return NS_SUCCESS_EVENT_CONSUMED;
+ }
+ return NS_OK;
+}
+
+// static
+bool IMMHandler::OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnKeyDownEvent, hWnd=%08x, wParam=%08x, lParam=%08x",
+ aWindow->GetWindowHandle(), wParam, lParam));
+ aResult.mConsumed = false;
+ switch (wParam) {
+ case VK_TAB:
+ case VK_PRIOR:
+ case VK_NEXT:
+ case VK_END:
+ case VK_HOME:
+ case VK_LEFT:
+ case VK_UP:
+ case VK_RIGHT:
+ case VK_DOWN:
+ case VK_RETURN:
+ // If IME didn't process the key message (the virtual key code wasn't
+ // converted to VK_PROCESSKEY), and the virtual key code event causes
+ // moving caret or editing text with keeping composing state, we should
+ // cancel the composition here because we cannot support moving
+ // composition string with DOM events (IE also cancels the composition
+ // in same cases). Then, this event will be dispatched.
+ if (IsComposingOnOurEditor()) {
+ // NOTE: We don't need to cancel the composition on another window.
+ CancelComposition(aWindow, false);
+ }
+ return false;
+ default:
+ return false;
+ }
+}
+
+/******************************************************************************
+ * IMMHandler::Selection
+ ******************************************************************************/
+
+bool IMMHandler::Selection::IsValid() const {
+ if (!mIsValid || NS_WARN_IF(mOffset == UINT32_MAX)) {
+ return false;
+ }
+ CheckedInt<uint32_t> endOffset = CheckedInt<uint32_t>(mOffset) + Length();
+ return endOffset.isValid();
+}
+
+bool IMMHandler::Selection::Update(const IMENotification& aIMENotification) {
+ mOffset = aIMENotification.mSelectionChangeData.mOffset;
+ mString = aIMENotification.mSelectionChangeData.String();
+ mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
+ mIsValid = true;
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("Selection::Update, aIMENotification={ mSelectionChangeData={ "
+ "mOffset=%u, mLength=%u, GetWritingMode()=%s } }",
+ mOffset, mString.Length(), GetWritingModeName(mWritingMode).get()));
+
+ if (!IsValid()) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("Selection::Update, FAILED, due to invalid range"));
+ Clear();
+ return false;
+ }
+ return true;
+}
+
+bool IMMHandler::Selection::Init(nsWindow* aWindow) {
+ Clear();
+
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ aWindow);
+ LayoutDeviceIntPoint point(0, 0);
+ aWindow->InitEvent(querySelectedTextEvent, &point);
+ DispatchEvent(aWindow, querySelectedTextEvent);
+ if (NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection())) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("Selection::Init, FAILED, due to eQuerySelectedText failure"));
+ return false;
+ }
+ // If the window is destroyed during querying selected text, we shouldn't
+ // do anymore.
+ if (aWindow->Destroyed()) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("Selection::Init, FAILED, due to the widget destroyed"));
+ return false;
+ }
+
+ MOZ_ASSERT(querySelectedTextEvent.mReply->mOffsetAndData.isSome());
+ mOffset = querySelectedTextEvent.mReply->StartOffset();
+ mString = querySelectedTextEvent.mReply->DataRef();
+ mWritingMode = querySelectedTextEvent.mReply->WritingModeRef();
+ mIsValid = true;
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("Selection::Init, querySelectedTextEvent={ mReply=%s }",
+ ToString(querySelectedTextEvent.mReply).c_str()));
+
+ if (!IsValid()) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("Selection::Init, FAILED, due to invalid range"));
+ Clear();
+ return false;
+ }
+ return true;
+}
+
+bool IMMHandler::Selection::EnsureValidSelection(nsWindow* aWindow) {
+ if (IsValid()) {
+ return true;
+ }
+ return Init(aWindow);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/IMMHandler.h b/widget/windows/IMMHandler.h
new file mode 100644
index 0000000000..54182c0e2e
--- /dev/null
+++ b/widget/windows/IMMHandler.h
@@ -0,0 +1,436 @@
+/* -*- 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 IMMHandler_h_
+#define IMMHandler_h_
+
+#include "nscore.h"
+#include <windows.h>
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIWidget.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "nsRect.h"
+#include "WritingModes.h"
+#include "npapi.h"
+
+class nsWindow;
+class nsWindowBase;
+
+namespace mozilla {
+namespace widget {
+
+struct MSGResult;
+
+class IMEContext final {
+ public:
+ IMEContext() : mWnd(nullptr), mIMC(nullptr) {}
+
+ explicit IMEContext(HWND aWnd);
+ explicit IMEContext(nsWindowBase* aWindowBase);
+
+ ~IMEContext() { Clear(); }
+
+ HIMC get() const { return mIMC; }
+
+ void Init(HWND aWnd);
+ void Init(nsWindowBase* aWindowBase);
+ void Clear();
+
+ bool IsValid() const { return !!mIMC; }
+
+ void SetOpenState(bool aOpen) const {
+ if (!mIMC) {
+ return;
+ }
+ ::ImmSetOpenStatus(mIMC, aOpen);
+ }
+
+ bool GetOpenState() const {
+ if (!mIMC) {
+ return false;
+ }
+ return (::ImmGetOpenStatus(mIMC) != FALSE);
+ }
+
+ bool AssociateDefaultContext() {
+ // We assume that there is only default IMC, no new IMC has been created.
+ if (mIMC) {
+ return false;
+ }
+ if (!::ImmAssociateContextEx(mWnd, nullptr, IACE_DEFAULT)) {
+ return false;
+ }
+ mIMC = ::ImmGetContext(mWnd);
+ return (mIMC != nullptr);
+ }
+
+ bool Disassociate() {
+ if (!mIMC) {
+ return false;
+ }
+ if (!::ImmAssociateContextEx(mWnd, nullptr, 0)) {
+ return false;
+ }
+ ::ImmReleaseContext(mWnd, mIMC);
+ mIMC = nullptr;
+ return true;
+ }
+
+ protected:
+ IMEContext(const IMEContext& aOther) { MOZ_CRASH("Don't copy IMEContext"); }
+
+ HWND mWnd;
+ HIMC mIMC;
+};
+
+class IMMHandler final {
+ public:
+ static void Initialize();
+ static void Terminate();
+
+ // If Process*() returns true, the caller shouldn't do anything anymore.
+ static bool ProcessMessage(nsWindow* aWindow, UINT msg, WPARAM& wParam,
+ LPARAM& lParam, MSGResult& aResult);
+ static bool IsComposing() { return IsComposingOnOurEditor(); }
+ static bool IsComposingOn(nsWindow* aWindow) {
+ return IsComposing() && IsComposingWindow(aWindow);
+ }
+
+#ifdef DEBUG
+ /**
+ * IsIMEAvailable() returns TRUE when current keyboard layout has IME.
+ * Otherwise, FALSE.
+ */
+ static bool IsIMEAvailable() { return !!::ImmIsIME(::GetKeyboardLayout(0)); }
+#endif
+
+ // If aForce is TRUE, these methods doesn't check whether we have composition
+ // or not. If you don't set it to TRUE, these method doesn't commit/cancel
+ // the composition on uexpected window.
+ static void CommitComposition(nsWindow* aWindow, bool aForce = false);
+ static void CancelComposition(nsWindow* aWindow, bool aForce = false);
+ static void OnFocusChange(bool aFocus, nsWindow* aWindow);
+ static void OnUpdateComposition(nsWindow* aWindow);
+ static void OnSelectionChange(nsWindow* aWindow,
+ const IMENotification& aIMENotification,
+ bool aIsIMMActive);
+
+ static IMENotificationRequests GetIMENotificationRequests();
+
+ // Returns NS_SUCCESS_EVENT_CONSUMED if the mouse button event is consumed by
+ // IME. Otherwise, NS_OK.
+ static nsresult OnMouseButtonEvent(nsWindow* aWindow,
+ const IMENotification& aIMENotification);
+
+#define DECL_IS_IME_ACTIVE(aReadableName) \
+ static bool Is##aReadableName##Active();
+
+ // Japanese IMEs
+ DECL_IS_IME_ACTIVE(ATOK2006)
+ DECL_IS_IME_ACTIVE(ATOK2007)
+ DECL_IS_IME_ACTIVE(ATOK2008)
+ DECL_IS_IME_ACTIVE(ATOK2009)
+ DECL_IS_IME_ACTIVE(ATOK2010)
+ DECL_IS_IME_ACTIVE(GoogleJapaneseInput)
+ DECL_IS_IME_ACTIVE(Japanist2003)
+
+#undef DECL_IS_IME_ACTIVE
+
+ /**
+ * IsActiveIMEInBlockList() returns true if we know active keyboard layout's
+ * IME has some crash bugs or something which make some damage to us. When
+ * this returns true, IMC shouldn't be associated with any windows.
+ */
+ static bool IsActiveIMEInBlockList();
+
+ protected:
+ static void EnsureHandlerInstance();
+
+ static bool IsComposingOnOurEditor();
+ static bool IsComposingWindow(nsWindow* aWindow);
+
+ static bool ShouldDrawCompositionStringOurselves();
+ static bool IsVerticalWritingSupported();
+ // aWindow can be nullptr if it's called without receiving WM_INPUTLANGCHANGE.
+ static void InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout);
+ static UINT GetKeyboardCodePage();
+
+ /**
+ * Checks whether the window is top level window of the composing window.
+ * In this method, the top level window means in all windows, not only in all
+ * OUR windows. I.e., if the aWindow is embedded, this always returns FALSE.
+ */
+ static bool IsTopLevelWindowOfComposition(nsWindow* aWindow);
+
+ static bool ProcessInputLangChangeMessage(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam, MSGResult& aResult);
+
+ IMMHandler();
+ ~IMMHandler();
+
+ // On*() methods return true if the caller of message handler shouldn't do
+ // anything anymore. Otherwise, false.
+ static bool OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+
+ bool OnIMEStartComposition(nsWindow* aWindow, MSGResult& aResult);
+ bool OnIMEComposition(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ bool OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult);
+ bool OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ bool OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ void OnInputLangChange(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+
+ // These message handlers don't use instance members, we should not create
+ // the instance by the messages. So, they should be static.
+ static bool OnIMEChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ static bool OnIMESetContext(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ static bool OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult);
+ static bool OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ static bool OnIMESelect(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+
+ // The result of Handle* method mean "Processed" when it's TRUE.
+ void HandleStartComposition(nsWindow* aWindow, const IMEContext& aContext);
+ bool HandleComposition(nsWindow* aWindow, const IMEContext& aContext,
+ LPARAM lParam);
+ // If aCommitString is null, this commits composition with the latest
+ // dispatched data. Otherwise, commits composition with the value.
+ void HandleEndComposition(nsWindow* aWindow,
+ const nsAString* aCommitString = nullptr);
+ bool HandleReconvert(nsWindow* aWindow, LPARAM lParam, LRESULT* oResult);
+ bool HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam,
+ LRESULT* oResult);
+ bool HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam, LRESULT* oResult);
+
+ /**
+ * When a window's IME context is activating but we have composition on
+ * another window, we should commit our composition because IME context is
+ * shared by all our windows (including plug-ins).
+ * @param aWindow is a new activated window.
+ * If aWindow is our composing window, this method does nothing.
+ * Otherwise, this commits the composition on the previous window.
+ * If this method did commit a composition, this returns TRUE.
+ */
+ bool CommitCompositionOnPreviousWindow(nsWindow* aWindow);
+
+ /**
+ * ResolveIMECaretPos
+ * Convert the caret rect of a composition event to another widget's
+ * coordinate system.
+ *
+ * @param aReferenceWidget The origin widget of aCursorRect.
+ * Typically, this is mReferenceWidget of the
+ * composing events. If the aCursorRect is in screen
+ * coordinates, set nullptr.
+ * @param aCursorRect The cursor rect.
+ * @param aNewOriginWidget aOutRect will be in this widget's coordinates. If
+ * this is nullptr, aOutRect will be in screen
+ * coordinates.
+ * @param aOutRect The converted cursor rect.
+ */
+ void ResolveIMECaretPos(nsIWidget* aReferenceWidget,
+ mozilla::LayoutDeviceIntRect& aCursorRect,
+ nsIWidget* aNewOriginWidget,
+ mozilla::LayoutDeviceIntRect& aOutRect);
+
+ bool ConvertToANSIString(const nsString& aStr, UINT aCodePage,
+ nsACString& aANSIStr);
+
+ bool SetIMERelatedWindowsPos(nsWindow* aWindow, const IMEContext& aContext);
+ /**
+ * GetCharacterRectOfSelectedTextAt() returns character rect of the offset
+ * from the selection start or the start of composition string if there is
+ * a composition.
+ *
+ * @param aWindow The window which has focus.
+ * @param aOffset Offset from the selection start or the start of
+ * composition string when there is a composition.
+ * This must be in the selection range or
+ * the composition string.
+ * @param aCharRect The result.
+ * @param aWritingMode The writing mode of current selection. When this
+ * is nullptr, this assumes that the selection is in
+ * horizontal writing mode.
+ * @return true if this succeeded to retrieve the rect.
+ * Otherwise, false.
+ */
+ bool GetCharacterRectOfSelectedTextAt(
+ nsWindow* aWindow, uint32_t aOffset,
+ mozilla::LayoutDeviceIntRect& aCharRect,
+ mozilla::WritingMode* aWritingMode = nullptr);
+ /**
+ * GetCaretRect() returns caret rect at current selection start.
+ *
+ * @param aWindow The window which has focus.
+ * @param aCaretRect The result.
+ * @param aWritingMode The writing mode of current selection. When this
+ * is nullptr, this assumes that the selection is in
+ * horizontal writing mode.
+ * @return true if this succeeded to retrieve the rect.
+ * Otherwise, false.
+ */
+ bool GetCaretRect(nsWindow* aWindow, mozilla::LayoutDeviceIntRect& aCaretRect,
+ mozilla::WritingMode* aWritingMode = nullptr);
+ void GetCompositionString(const IMEContext& aContext, DWORD aIndex,
+ nsAString& aCompositionString) const;
+
+ /**
+ * AdjustCompositionFont() makes IME vertical writing mode if it's supported.
+ * If aForceUpdate is true, it will update composition font even if writing
+ * mode isn't being changed.
+ */
+ void AdjustCompositionFont(nsWindow* aWindow, const IMEContext& aContext,
+ const mozilla::WritingMode& aWritingMode,
+ bool aForceUpdate = false);
+
+ /**
+ * MaybeAdjustCompositionFont() calls AdjustCompositionFont() when the
+ * locale of active IME is CJK. Note that this creates an instance even
+ * when there is no composition but the locale is CJK.
+ */
+ static void MaybeAdjustCompositionFont(
+ nsWindow* aWindow, const mozilla::WritingMode& aWritingMode,
+ bool aForceUpdate = false);
+
+ /**
+ * Get the current target clause of composition string.
+ * If there are one or more characters whose attribute is ATTR_TARGET_*,
+ * this returns the first character's offset and its length.
+ * Otherwise, e.g., the all characters are ATTR_INPUT, this returns
+ * the composition string range because the all is the current target.
+ *
+ * aLength can be null (default), but aOffset must not be null.
+ *
+ * The aOffset value is offset in the contents. So, when you need offset
+ * in the composition string, you need to subtract mCompositionStart from it.
+ */
+ bool GetTargetClauseRange(uint32_t* aOffset, uint32_t* aLength = nullptr);
+
+ /**
+ * DispatchEvent() dispatches aEvent if aWidget hasn't been destroyed yet.
+ */
+ static void DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent);
+
+ /**
+ * DispatchCompositionChangeEvent() dispatches eCompositionChange event
+ * with clause information (it'll be retrieved by CreateTextRangeArray()).
+ * I.e., this should be called only during composing. If a composition is
+ * being committed, only HandleCompositionEnd() should be called.
+ *
+ * @param aWindow The window which has the composition.
+ * @param aContext Native IME context which has the composition.
+ */
+ void DispatchCompositionChangeEvent(nsWindow* aWindow,
+ const IMEContext& aContext);
+
+ nsresult EnsureClauseArray(int32_t aCount);
+ nsresult EnsureAttributeArray(int32_t aCount);
+
+ /**
+ * When WM_IME_CHAR is received and passed to DefWindowProc, we need to
+ * record the messages. In other words, we should record the messages
+ * when we receive WM_IME_CHAR on windowless plug-in (if we have focus,
+ * we always eat them). When focus is moved from a windowless plug-in to
+ * our window during composition, WM_IME_CHAR messages were received when
+ * the plug-in has focus. However, WM_CHAR messages are received after the
+ * plug-in lost focus. So, we need to ignore the WM_CHAR messages because
+ * they make unexpected text input events on us.
+ */
+ nsTArray<MSG> mPassedIMEChar;
+
+ bool IsIMECharRecordsEmpty() { return mPassedIMEChar.IsEmpty(); }
+ void ResetIMECharRecords() { mPassedIMEChar.Clear(); }
+ void DequeueIMECharRecords(WPARAM& wParam, LPARAM& lParam) {
+ MSG msg = mPassedIMEChar.ElementAt(0);
+ wParam = msg.wParam;
+ lParam = msg.lParam;
+ mPassedIMEChar.RemoveElementAt(0);
+ }
+ void EnqueueIMECharRecords(WPARAM wParam, LPARAM lParam) {
+ MSG msg;
+ msg.wParam = wParam;
+ msg.lParam = lParam;
+ mPassedIMEChar.AppendElement(msg);
+ }
+
+ TextEventDispatcher* GetTextEventDispatcherFor(nsWindow* aWindow);
+
+ nsWindow* mComposingWindow;
+ RefPtr<TextEventDispatcher> mDispatcher;
+ nsString mCompositionString;
+ nsTArray<uint32_t> mClauseArray;
+ nsTArray<uint8_t> mAttributeArray;
+
+ int32_t mCursorPosition;
+ uint32_t mCompositionStart;
+
+ struct Selection {
+ nsString mString;
+ uint32_t mOffset;
+ mozilla::WritingMode mWritingMode;
+ bool mIsValid;
+
+ Selection() : mOffset(UINT32_MAX), mIsValid(false) {}
+
+ void Clear() {
+ mOffset = UINT32_MAX;
+ mIsValid = false;
+ }
+ uint32_t Length() const { return mString.Length(); }
+ bool Collapsed() const { return !Length(); }
+
+ bool IsValid() const;
+ bool Update(const IMENotification& aIMENotification);
+ bool Init(nsWindow* aWindow);
+ bool EnsureValidSelection(nsWindow* aWindow);
+
+ private:
+ Selection(const Selection& aOther) = delete;
+ void operator=(const Selection& aOther) = delete;
+ };
+ // mSelection stores the latest selection data only when sHasFocus is true.
+ // Don't access mSelection directly. You should use GetSelection() for
+ // getting proper state.
+ Selection mSelection;
+
+ Selection& GetSelection() {
+ // When IME has focus, mSelection is automatically updated by
+ // NOTIFY_IME_OF_SELECTION_CHANGE.
+ if (sHasFocus) {
+ return mSelection;
+ }
+ // Otherwise, i.e., While IME doesn't have focus, we cannot observe
+ // selection changes. So, in such case, we need to query selection
+ // when it's necessary.
+ static Selection sTempSelection;
+ sTempSelection.Clear();
+ return sTempSelection;
+ }
+
+ bool mIsComposing;
+
+ static mozilla::WritingMode sWritingModeOfCompositionFont;
+ static nsString sIMEName;
+ static UINT sCodePage;
+ static DWORD sIMEProperty;
+ static DWORD sIMEUIProperty;
+ static bool sAssumeVerticalWritingModeNotSupported;
+ static bool sHasFocus;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // IMMHandler_h_
diff --git a/widget/windows/IconLoaderHelperWin.cpp b/widget/windows/IconLoaderHelperWin.cpp
new file mode 100644
index 0000000000..a6be500fe4
--- /dev/null
+++ b/widget/windows/IconLoaderHelperWin.cpp
@@ -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/. */
+
+/*
+ * Retrieves and displays icons in native menu items on Windows.
+ */
+
+#include "gfxPlatform.h"
+#include "imgIContainer.h"
+#include "imgLoader.h"
+#include "imgRequestProxy.h"
+#include "mozilla/dom/Document.h"
+#include "nsContentUtils.h"
+#include "nsIContent.h"
+#include "nsNameSpaceManager.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "nsToolkit.h"
+#include "nsWindowGfx.h"
+#include "IconLoaderHelperWin.h"
+
+using namespace mozilla;
+
+using mozilla::gfx::SourceSurface;
+using mozilla::widget::IconLoader;
+using mozilla::widget::IconLoaderListenerWin;
+
+namespace mozilla::widget {
+
+NS_IMPL_CYCLE_COLLECTION(IconLoaderHelperWin, mLoadListener)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IconLoaderHelperWin)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IconLoaderHelperWin)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IconLoaderHelperWin)
+
+IconLoaderHelperWin::IconLoaderHelperWin(IconLoaderListenerWin* aListener)
+ : mLoadListener(aListener) {
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+IconLoaderHelperWin::~IconLoaderHelperWin() { Destroy(); }
+
+nsresult IconLoaderHelperWin::OnComplete(imgIContainer* aImage,
+ const nsIntRect& aRect) {
+ NS_ENSURE_ARG_POINTER(aImage);
+
+ nsresult rv = nsWindowGfx::CreateIcon(
+ aImage, false, LayoutDeviceIntPoint(),
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon),
+ &mNativeIconImage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mLoadListener->OnComplete();
+ return NS_OK;
+}
+
+HICON IconLoaderHelperWin::GetNativeIconImage() {
+ if (mNativeIconImage) {
+ return mNativeIconImage;
+ }
+ return ::LoadIcon(::GetModuleHandle(NULL), IDI_APPLICATION);
+}
+
+void IconLoaderHelperWin::Destroy() {
+ if (mNativeIconImage) {
+ ::DestroyIcon(mNativeIconImage);
+ mNativeIconImage = nullptr;
+ }
+ if (mLoadListener) {
+ mLoadListener = nullptr;
+ }
+}
+
+} // namespace mozilla::widget
diff --git a/widget/windows/IconLoaderHelperWin.h b/widget/windows/IconLoaderHelperWin.h
new file mode 100644
index 0000000000..94eba152e4
--- /dev/null
+++ b/widget/windows/IconLoaderHelperWin.h
@@ -0,0 +1,70 @@
+/* -*- 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_IconLoaderHelperWin_h
+#define mozilla_widget_IconLoaderHelperWin_h
+
+#include "mozilla/widget/IconLoader.h"
+#include "nsISupports.h"
+
+namespace mozilla::widget {
+
+/**
+ * Classes that want to hear about when icons load should subclass
+ * IconLoaderListenerWin, and implement the OnComplete() method,
+ * which will be called once the load of the icon has completed.
+ */
+class IconLoaderListenerWin : public nsISupports {
+ public:
+ IconLoaderListenerWin() = default;
+
+ // IconLoaderListenerWin needs to implement nsISupports in order for its
+ // subclasses to participate in cycle collection.
+
+ virtual nsresult OnComplete() = 0;
+
+ protected:
+ virtual ~IconLoaderListenerWin() = default;
+};
+
+/**
+ * This is a Helper used with mozilla::widget::IconLoader that implements the
+ * Windows-specific functionality for converting a loaded icon into an HICON.
+ */
+class IconLoaderHelperWin final : public mozilla::widget::IconLoader::Helper {
+ public:
+ explicit IconLoaderHelperWin(
+ mozilla::widget::IconLoaderListenerWin* aLoadListener);
+
+ // IconLoaderHelperWin needs to implement nsISupports in order for its
+ // subclasses to participate in cycle collection.
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(IconLoaderHelperWin)
+
+ nsresult OnComplete(imgIContainer* aImage, const nsIntRect& aRect) override;
+
+ /**
+ * IconLoaderHelperWin will default the HICON returned by GetNativeIconImage
+ * to the application icon. Once the load of the icon by IconLoader has
+ * completed, GetNativeIconImage will return the loaded icon.
+ *
+ * Note that IconLoaderHelperWin owns this HICON. If you don't need it to hold
+ * onto the HICON anymore, call Destroy on it to deallocate. The
+ * IconLoaderHelperWin destructor will also deallocate the HICON if necessary.
+ */
+ HICON GetNativeIconImage();
+ void Destroy();
+
+ protected:
+ ~IconLoaderHelperWin();
+
+ private:
+ RefPtr<mozilla::widget::IconLoaderListenerWin> mLoadListener;
+ HICON mNativeIconImage;
+};
+
+} // namespace mozilla::widget
+
+#endif // mozilla_widget_IconLoaderHelperWin_h
diff --git a/widget/windows/InProcessWinCompositorWidget.cpp b/widget/windows/InProcessWinCompositorWidget.cpp
new file mode 100644
index 0000000000..c65a203a40
--- /dev/null
+++ b/widget/windows/InProcessWinCompositorWidget.cpp
@@ -0,0 +1,356 @@
+/* -*- 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 "InProcessWinCompositorWidget.h"
+
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/layers/Compositor.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "gfxPlatform.h"
+#include "HeadlessCompositorWidget.h"
+#include "HeadlessWidget.h"
+#include "nsWindow.h"
+#include "VsyncDispatcher.h"
+#include "WinCompositorWindowThread.h"
+#include "VRShMem.h"
+
+#include <ddraw.h>
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::gfx;
+using namespace mozilla;
+
+/* static */
+RefPtr<CompositorWidget> CompositorWidget::CreateLocal(
+ const CompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions, nsIWidget* aWidget) {
+ if (aInitData.type() ==
+ CompositorWidgetInitData::THeadlessCompositorWidgetInitData) {
+ return new HeadlessCompositorWidget(
+ aInitData.get_HeadlessCompositorWidgetInitData(), aOptions,
+ static_cast<HeadlessWidget*>(aWidget));
+ } else {
+ return new InProcessWinCompositorWidget(
+ aInitData.get_WinCompositorWidgetInitData(), aOptions,
+ static_cast<nsWindow*>(aWidget));
+ }
+}
+
+InProcessWinCompositorWidget::InProcessWinCompositorWidget(
+ const WinCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions, nsWindow* aWindow)
+ : WinCompositorWidget(aInitData, aOptions),
+ mWindow(aWindow),
+ mWnd(reinterpret_cast<HWND>(aInitData.hWnd())),
+ mTransparentSurfaceLock("mTransparentSurfaceLock"),
+ mTransparencyMode(aInitData.transparencyMode()),
+ mMemoryDC(nullptr),
+ mCompositeDC(nullptr),
+ mLockedBackBufferData(nullptr) {
+ MOZ_ASSERT(mWindow);
+ MOZ_ASSERT(mWnd && ::IsWindow(mWnd));
+
+ // mNotDeferEndRemoteDrawing is set on the main thread during init,
+ // but is only accessed after on the compositor thread.
+ mNotDeferEndRemoteDrawing =
+ StaticPrefs::layers_offmainthreadcomposition_frame_rate() == 0 ||
+ gfxPlatform::IsInLayoutAsapMode() || gfxPlatform::ForceSoftwareVsync();
+}
+
+void InProcessWinCompositorWidget::OnDestroyWindow() {
+ EnterPresentLock();
+ MutexAutoLock lock(mTransparentSurfaceLock);
+ mTransparentSurface = nullptr;
+ mMemoryDC = nullptr;
+ LeavePresentLock();
+}
+
+bool InProcessWinCompositorWidget::OnWindowResize(
+ const LayoutDeviceIntSize& aSize) {
+ return true;
+}
+
+void InProcessWinCompositorWidget::OnWindowModeChange(nsSizeMode aSizeMode) {}
+
+bool InProcessWinCompositorWidget::PreRender(WidgetRenderingContext* aContext) {
+ // This can block waiting for WM_SETTEXT to finish
+ // Using PreRender is unnecessarily pessimistic because
+ // we technically only need to block during the present call
+ // not all of compositor rendering
+ mPresentLock.Enter();
+ return true;
+}
+
+void InProcessWinCompositorWidget::PostRender(
+ WidgetRenderingContext* aContext) {
+ mPresentLock.Leave();
+}
+
+LayoutDeviceIntSize InProcessWinCompositorWidget::GetClientSize() {
+ RECT r;
+ if (!::GetClientRect(mWnd, &r)) {
+ return LayoutDeviceIntSize();
+ }
+ return LayoutDeviceIntSize(r.right - r.left, r.bottom - r.top);
+}
+
+already_AddRefed<gfx::DrawTarget>
+InProcessWinCompositorWidget::StartRemoteDrawing() {
+ MutexAutoLock lock(mTransparentSurfaceLock);
+
+ MOZ_ASSERT(!mCompositeDC);
+
+ RefPtr<gfxASurface> surf;
+ if (mTransparencyMode == eTransparencyTransparent) {
+ surf = EnsureTransparentSurface();
+ }
+
+ // Must call this after EnsureTransparentSurface(), since it could update
+ // the DC.
+ HDC dc = GetWindowSurface();
+ if (!surf) {
+ if (!dc) {
+ return nullptr;
+ }
+ uint32_t flags = (mTransparencyMode == eTransparencyOpaque)
+ ? 0
+ : gfxWindowsSurface::FLAG_IS_TRANSPARENT;
+ surf = new gfxWindowsSurface(dc, flags);
+ }
+
+ IntSize size = surf->GetSize();
+ if (size.width <= 0 || size.height <= 0) {
+ if (dc) {
+ FreeWindowSurface(dc);
+ }
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> dt =
+ mozilla::gfx::Factory::CreateDrawTargetForCairoSurface(
+ surf->CairoSurface(), size);
+ if (dt) {
+ mCompositeDC = dc;
+ } else {
+ FreeWindowSurface(dc);
+ }
+
+ return dt.forget();
+}
+
+void InProcessWinCompositorWidget::EndRemoteDrawing() {
+ MOZ_ASSERT(!mLockedBackBufferData);
+
+ if (mTransparencyMode == eTransparencyTransparent) {
+ MOZ_ASSERT(mTransparentSurface);
+ RedrawTransparentWindow();
+ }
+ if (mCompositeDC) {
+ FreeWindowSurface(mCompositeDC);
+ }
+ mCompositeDC = nullptr;
+}
+
+bool InProcessWinCompositorWidget::NeedsToDeferEndRemoteDrawing() {
+ if (mNotDeferEndRemoteDrawing) {
+ return false;
+ }
+
+ IDirectDraw7* ddraw = DeviceManagerDx::Get()->GetDirectDraw();
+ if (!ddraw) {
+ return false;
+ }
+
+ DWORD scanLine = 0;
+ int height = ::GetSystemMetrics(SM_CYSCREEN);
+ HRESULT ret = ddraw->GetScanLine(&scanLine);
+ if (ret == DDERR_VERTICALBLANKINPROGRESS) {
+ scanLine = 0;
+ } else if (ret != DD_OK) {
+ return false;
+ }
+
+ // Check if there is a risk of tearing with GDI.
+ if (static_cast<int>(scanLine) > height / 2) {
+ // No need to defer.
+ return false;
+ }
+
+ return true;
+}
+
+already_AddRefed<gfx::DrawTarget>
+InProcessWinCompositorWidget::GetBackBufferDrawTarget(
+ gfx::DrawTarget* aScreenTarget, const gfx::IntRect& aRect,
+ bool* aOutIsCleared) {
+ MOZ_ASSERT(!mLockedBackBufferData);
+
+ RefPtr<gfx::DrawTarget> target = CompositorWidget::GetBackBufferDrawTarget(
+ aScreenTarget, aRect, aOutIsCleared);
+ if (!target) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(target->GetBackendType() == BackendType::CAIRO);
+
+ uint8_t* destData;
+ IntSize destSize;
+ int32_t destStride;
+ SurfaceFormat destFormat;
+ if (!target->LockBits(&destData, &destSize, &destStride, &destFormat)) {
+ // LockBits is not supported. Use original DrawTarget.
+ return target.forget();
+ }
+
+ RefPtr<gfx::DrawTarget> dataTarget = Factory::CreateDrawTargetForData(
+ BackendType::CAIRO, destData, destSize, destStride, destFormat);
+ mLockedBackBufferData = destData;
+
+ return dataTarget.forget();
+}
+
+already_AddRefed<gfx::SourceSurface>
+InProcessWinCompositorWidget::EndBackBufferDrawing() {
+ if (mLockedBackBufferData) {
+ MOZ_ASSERT(mLastBackBuffer);
+ mLastBackBuffer->ReleaseBits(mLockedBackBufferData);
+ mLockedBackBufferData = nullptr;
+ }
+ return CompositorWidget::EndBackBufferDrawing();
+}
+
+bool InProcessWinCompositorWidget::InitCompositor(
+ layers::Compositor* aCompositor) {
+ if (aCompositor->GetBackendType() == layers::LayersBackend::LAYERS_BASIC) {
+ DeviceManagerDx::Get()->InitializeDirectDraw();
+ }
+ return true;
+}
+
+void InProcessWinCompositorWidget::EnterPresentLock() { mPresentLock.Enter(); }
+
+void InProcessWinCompositorWidget::LeavePresentLock() { mPresentLock.Leave(); }
+
+RefPtr<gfxASurface> InProcessWinCompositorWidget::EnsureTransparentSurface() {
+ mTransparentSurfaceLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mTransparencyMode == eTransparencyTransparent);
+
+ IntSize size = GetClientSize().ToUnknownSize();
+ if (!mTransparentSurface || mTransparentSurface->GetSize() != size) {
+ mTransparentSurface = nullptr;
+ mMemoryDC = nullptr;
+ CreateTransparentSurface(size);
+ }
+
+ RefPtr<gfxASurface> surface = mTransparentSurface;
+ return surface.forget();
+}
+
+void InProcessWinCompositorWidget::CreateTransparentSurface(
+ const gfx::IntSize& aSize) {
+ mTransparentSurfaceLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(!mTransparentSurface && !mMemoryDC);
+ RefPtr<gfxWindowsSurface> surface =
+ new gfxWindowsSurface(aSize, SurfaceFormat::A8R8G8B8_UINT32);
+ mTransparentSurface = surface;
+ mMemoryDC = surface->GetDC();
+}
+
+void InProcessWinCompositorWidget::UpdateTransparency(
+ nsTransparencyMode aMode) {
+ EnterPresentLock();
+ MutexAutoLock lock(mTransparentSurfaceLock);
+ if (mTransparencyMode == aMode) {
+ return;
+ }
+
+ mTransparencyMode = aMode;
+ mTransparentSurface = nullptr;
+ mMemoryDC = nullptr;
+
+ if (mTransparencyMode == eTransparencyTransparent) {
+ EnsureTransparentSurface();
+ }
+ LeavePresentLock();
+}
+
+bool InProcessWinCompositorWidget::HasGlass() const {
+ MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread() ||
+ wr::RenderThread::IsInRenderThread());
+
+ nsTransparencyMode transparencyMode = mTransparencyMode;
+ return transparencyMode == eTransparencyGlass ||
+ transparencyMode == eTransparencyBorderlessGlass;
+}
+
+void InProcessWinCompositorWidget::ClearTransparentWindow() {
+ EnterPresentLock();
+ MutexAutoLock lock(mTransparentSurfaceLock);
+ if (!mTransparentSurface) {
+ return;
+ }
+
+ EnsureTransparentSurface();
+
+ IntSize size = mTransparentSurface->GetSize();
+ if (!size.IsEmpty()) {
+ RefPtr<DrawTarget> drawTarget =
+ gfxPlatform::CreateDrawTargetForSurface(mTransparentSurface, size);
+ if (!drawTarget) {
+ return;
+ }
+ drawTarget->ClearRect(Rect(0, 0, size.width, size.height));
+ RedrawTransparentWindow();
+ }
+ LeavePresentLock();
+}
+
+bool InProcessWinCompositorWidget::RedrawTransparentWindow() {
+ MOZ_ASSERT(mTransparencyMode == eTransparencyTransparent);
+
+ LayoutDeviceIntSize size = GetClientSize();
+
+ ::GdiFlush();
+
+ BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
+ SIZE winSize = {size.width, size.height};
+ POINT srcPos = {0, 0};
+ HWND hWnd = WinUtils::GetTopLevelHWND(mWnd, true);
+ RECT winRect;
+ ::GetWindowRect(hWnd, &winRect);
+
+ // perform the alpha blend
+ return !!::UpdateLayeredWindow(hWnd, nullptr, (POINT*)&winRect, &winSize,
+ mMemoryDC, &srcPos, 0, &bf, ULW_ALPHA);
+}
+
+HDC InProcessWinCompositorWidget::GetWindowSurface() {
+ return eTransparencyTransparent == mTransparencyMode ? mMemoryDC
+ : ::GetDC(mWnd);
+}
+
+void InProcessWinCompositorWidget::FreeWindowSurface(HDC dc) {
+ if (eTransparencyTransparent != mTransparencyMode) ::ReleaseDC(mWnd, dc);
+}
+
+bool InProcessWinCompositorWidget::IsHidden() const { return ::IsIconic(mWnd); }
+
+nsIWidget* InProcessWinCompositorWidget::RealWidget() { return mWindow; }
+
+void InProcessWinCompositorWidget::ObserveVsync(VsyncObserver* aObserver) {
+ if (RefPtr<CompositorVsyncDispatcher> cvd =
+ mWindow->GetCompositorVsyncDispatcher()) {
+ cvd->SetCompositorVsyncObserver(aObserver);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/InProcessWinCompositorWidget.h b/widget/windows/InProcessWinCompositorWidget.h
new file mode 100644
index 0000000000..25ce85eadc
--- /dev/null
+++ b/widget/windows/InProcessWinCompositorWidget.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 widget_windows_InProcessWinCompositorWidget_h
+#define widget_windows_InProcessWinCompositorWidget_h
+
+#include "WinCompositorWidget.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+// This is the Windows-specific implementation of CompositorWidget. For
+// the most part it only requires an HWND, however it maintains extra state
+// for transparent windows, as well as for synchronizing WM_SETTEXT messages
+// with the compositor.
+class InProcessWinCompositorWidget final
+ : public WinCompositorWidget,
+ public PlatformCompositorWidgetDelegate {
+ public:
+ InProcessWinCompositorWidget(const WinCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions,
+ nsWindow* aWindow);
+
+ bool PreRender(WidgetRenderingContext*) override;
+ void PostRender(WidgetRenderingContext*) override;
+ already_AddRefed<gfx::DrawTarget> StartRemoteDrawing() override;
+ void EndRemoteDrawing() override;
+ bool NeedsToDeferEndRemoteDrawing() override;
+ LayoutDeviceIntSize GetClientSize() override;
+ already_AddRefed<gfx::DrawTarget> GetBackBufferDrawTarget(
+ gfx::DrawTarget* aScreenTarget, const gfx::IntRect& aRect,
+ bool* aOutIsCleared) override;
+ already_AddRefed<gfx::SourceSurface> EndBackBufferDrawing() override;
+ bool InitCompositor(layers::Compositor* aCompositor) override;
+ CompositorWidgetDelegate* AsDelegate() override { return this; }
+ bool IsHidden() const override;
+
+ // PlatformCompositorWidgetDelegate Overrides
+
+ void EnterPresentLock() override;
+ void LeavePresentLock() override;
+ void OnDestroyWindow() override;
+ bool OnWindowResize(const LayoutDeviceIntSize& aSize) override;
+ void OnWindowModeChange(nsSizeMode aSizeMode) override;
+ void UpdateTransparency(nsTransparencyMode aMode) override;
+ void ClearTransparentWindow() override;
+
+ bool RedrawTransparentWindow();
+
+ // Ensure that a transparent surface exists, then return it.
+ RefPtr<gfxASurface> EnsureTransparentSurface();
+
+ HDC GetTransparentDC() const { return mMemoryDC; }
+
+ mozilla::Mutex& GetTransparentSurfaceLock() {
+ return mTransparentSurfaceLock;
+ }
+
+ bool HasGlass() const override;
+
+ void ObserveVsync(VsyncObserver* aObserver) override;
+ nsIWidget* RealWidget() override;
+
+ void UpdateCompositorWnd(const HWND aCompositorWnd,
+ const HWND aParentWnd) override {}
+ void SetRootLayerTreeID(const layers::LayersId& aRootLayerTreeId) override {}
+
+ private:
+ HDC GetWindowSurface();
+ void FreeWindowSurface(HDC dc);
+
+ void CreateTransparentSurface(const gfx::IntSize& aSize);
+
+ nsWindow* mWindow;
+
+ HWND mWnd;
+
+ gfx::CriticalSection mPresentLock;
+
+ // Transparency handling.
+ mozilla::Mutex mTransparentSurfaceLock;
+ mozilla::Atomic<nsTransparencyMode, MemoryOrdering::Relaxed>
+ mTransparencyMode;
+ RefPtr<gfxASurface> mTransparentSurface;
+ HDC mMemoryDC;
+ HDC mCompositeDC;
+
+ // Locked back buffer of BasicCompositor
+ uint8_t* mLockedBackBufferData;
+
+ bool mNotDeferEndRemoteDrawing;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_InProcessWinCompositorWidget_h
diff --git a/widget/windows/InkCollector.cpp b/widget/windows/InkCollector.cpp
new file mode 100644
index 0000000000..4c2dfbf387
--- /dev/null
+++ b/widget/windows/InkCollector.cpp
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=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/.
+ */
+
+#include "InkCollector.h"
+
+// Msinkaut_i.c and Msinkaut.h should both be included
+// https://msdn.microsoft.com/en-us/library/windows/desktop/ms695519.aspx
+#include <msinkaut_i.c>
+
+StaticAutoPtr<InkCollector> InkCollector::sInkCollector;
+
+InkCollector::~InkCollector() {
+ Shutdown();
+ MOZ_ASSERT(!mCookie && !mEnabled && !mComInitialized && !mMarshaller &&
+ !mInkCollector && !mConnectionPoint && !mInkCollectorEvent);
+}
+
+void InkCollector::Initialize() {
+ // Possibly, we can use mConnectionPoint for checking,
+ // But if errors exist (perhaps COM object is unavailable),
+ // Initialize() will be called more times.
+ static bool sInkCollectorCreated = false;
+ if (sInkCollectorCreated) {
+ return;
+ }
+ sInkCollectorCreated = true;
+
+ // COM could get uninitialized due to previous initialization.
+ mComInitialized = SUCCEEDED(::CoInitialize(nullptr));
+
+ // Set up instance of InkCollectorEvent.
+ mInkCollectorEvent = new InkCollectorEvent();
+
+ // Set up a free threaded marshaler.
+ if (FAILED(::CoCreateFreeThreadedMarshaler(mInkCollectorEvent,
+ getter_AddRefs(mMarshaller)))) {
+ return;
+ }
+
+ // Create the ink collector.
+ if (FAILED(::CoCreateInstance(CLSID_InkCollector, NULL, CLSCTX_INPROC_SERVER,
+ IID_IInkCollector,
+ getter_AddRefs(mInkCollector)))) {
+ return;
+ }
+
+ // Set up connection between sink and InkCollector.
+ RefPtr<IConnectionPointContainer> connPointContainer;
+
+ // Get the connection point container.
+ if (SUCCEEDED(mInkCollector->QueryInterface(
+ IID_IConnectionPointContainer, getter_AddRefs(connPointContainer)))) {
+ // Find the connection point for Ink Collector events.
+ if (SUCCEEDED(connPointContainer->FindConnectionPoint(
+ __uuidof(_IInkCollectorEvents),
+ getter_AddRefs(mConnectionPoint)))) {
+ // Hook up sink to connection point.
+ if (SUCCEEDED(mConnectionPoint->Advise(mInkCollectorEvent, &mCookie))) {
+ OnInitialize();
+ }
+ }
+ }
+}
+
+void InkCollector::Shutdown() {
+ Enable(false);
+ if (mConnectionPoint) {
+ // Remove the connection of the sink to the Ink Collector.
+ mConnectionPoint->Unadvise(mCookie);
+ mCookie = 0;
+ mConnectionPoint = nullptr;
+ }
+ mInkCollector = nullptr;
+ mMarshaller = nullptr;
+ mInkCollectorEvent = nullptr;
+
+ // Let uninitialization get handled in a place where it got inited.
+ if (mComInitialized) {
+ CoUninitialize();
+ mComInitialized = false;
+ }
+}
+
+void InkCollector::OnInitialize() {
+ // Suppress all events to do not allow performance decreasing.
+ // https://msdn.microsoft.com/en-us/library/ms820347.aspx
+ mInkCollector->SetEventInterest(InkCollectorEventInterest::ICEI_AllEvents,
+ VARIANT_FALSE);
+
+ // Sets a value that indicates whether an object or control has interest in a
+ // specified event.
+ mInkCollector->SetEventInterest(
+ InkCollectorEventInterest::ICEI_CursorOutOfRange, VARIANT_TRUE);
+
+ // If the MousePointer property is set to IMP_Custom and the MouseIcon
+ // property is NULL, Then the ink collector no longer handles mouse cursor
+ // settings.
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms700686.aspx
+ mInkCollector->put_MouseIcon(nullptr);
+ mInkCollector->put_MousePointer(InkMousePointer::IMP_Custom);
+
+ // This mode allows an ink collector to collect ink from any tablet attached
+ // to the Tablet PC. The Boolean value that indicates whether to use the mouse
+ // as an input device. If TRUE, the mouse is used for input.
+ // https://msdn.microsoft.com/en-us/library/ms820346.aspx
+ mInkCollector->SetAllTabletsMode(VARIANT_FALSE);
+
+ // Sets the value that specifies whether ink is rendered as it is drawn.
+ // VARIANT_TRUE to render ink as it is drawn on the display.
+ // VARIANT_FALSE to not have the ink appear on the display as strokes are
+ // made.
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd314598.aspx
+ mInkCollector->put_DynamicRendering(VARIANT_FALSE);
+
+ // Set AutoRedraw to false to prevent repainting the ink when the window is
+ // invalidated.
+ mInkCollector->put_AutoRedraw(VARIANT_FALSE);
+}
+
+// Sets a value that specifies whether the InkCollector object collects pen
+// input. This property must be set to FALSE before setting or calling specific
+// properties and methods of the object.
+// https://msdn.microsoft.com/en-us/library/windows/desktop/ms701721.aspx
+void InkCollector::Enable(bool aNewState) {
+ if (aNewState != mEnabled) {
+ if (mInkCollector) {
+ if (SUCCEEDED(mInkCollector->put_Enabled(aNewState ? VARIANT_TRUE
+ : VARIANT_FALSE))) {
+ mEnabled = aNewState;
+ } else {
+ NS_WARNING("InkCollector did not change status successfully");
+ }
+ } else {
+ NS_WARNING("InkCollector should be exist");
+ }
+ }
+}
+
+HWND InkCollector::GetTarget() { return mTargetWindow; }
+
+void InkCollector::SetTarget(HWND aTargetWindow) {
+ NS_ASSERTION(aTargetWindow, "aTargetWindow should be exist");
+ if (aTargetWindow && (aTargetWindow != mTargetWindow)) {
+ Initialize();
+ if (mInkCollector) {
+ Enable(false);
+ if (SUCCEEDED(mInkCollector->put_hWnd((LONG_PTR)aTargetWindow))) {
+ mTargetWindow = aTargetWindow;
+ } else {
+ NS_WARNING("InkCollector did not change window property successfully");
+ }
+ Enable(true);
+ }
+ }
+}
+
+void InkCollector::ClearTarget() {
+ if (mTargetWindow && mInkCollector) {
+ Enable(false);
+ if (SUCCEEDED(mInkCollector->put_hWnd(0))) {
+ mTargetWindow = 0;
+ } else {
+ NS_WARNING("InkCollector did not clear window property successfully");
+ }
+ }
+}
+
+uint16_t InkCollector::GetPointerId() { return mPointerId; }
+
+void InkCollector::SetPointerId(uint16_t aPointerId) {
+ mPointerId = aPointerId;
+}
+
+void InkCollector::ClearPointerId() { mPointerId = 0; }
+
+// The display and the digitizer have quite different properties.
+// The display has CursorMustTouch, the mouse pointer alway touches the display
+// surface. The digitizer lists Integrated and HardProximity. When the stylus is
+// in the proximity of the tablet its movements are also detected. An external
+// tablet will only list HardProximity.
+bool InkCollectorEvent::IsHardProximityTablet(IInkTablet* aTablet) const {
+ if (aTablet) {
+ TabletHardwareCapabilities caps;
+ if (SUCCEEDED(aTablet->get_HardwareCapabilities(&caps))) {
+ return (TabletHardwareCapabilities::THWC_HardProximity & caps);
+ }
+ }
+ return false;
+}
+
+HRESULT __stdcall InkCollectorEvent::QueryInterface(REFIID aRiid,
+ void** aObject) {
+ // Validate the input
+ if (!aObject) {
+ return E_POINTER;
+ }
+ HRESULT result = E_NOINTERFACE;
+ // This object supports IUnknown/IDispatch/IInkCollectorEvents
+ if ((IID_IUnknown == aRiid) || (IID_IDispatch == aRiid) ||
+ (DIID__IInkCollectorEvents == aRiid)) {
+ *aObject = this;
+ // AddRef should be called when we give info about interface
+ NS_ADDREF_THIS();
+ result = S_OK;
+ }
+ return result;
+}
+
+HRESULT InkCollectorEvent::Invoke(DISPID aDispIdMember, REFIID /*aRiid*/,
+ LCID /*aId*/, WORD /*wFlags*/,
+ DISPPARAMS* aDispParams,
+ VARIANT* /*aVarResult*/,
+ EXCEPINFO* /*aExcepInfo*/,
+ UINT* /*aArgErr*/) {
+ switch (aDispIdMember) {
+ case DISPID_ICECursorOutOfRange: {
+ if (aDispParams && aDispParams->cArgs) {
+ CursorOutOfRange(
+ static_cast<IInkCursor*>(aDispParams->rgvarg[0].pdispVal));
+ }
+ break;
+ }
+ }
+ return S_OK;
+}
+
+void InkCollectorEvent::CursorOutOfRange(IInkCursor* aCursor) const {
+ IInkTablet* curTablet = nullptr;
+ if (FAILED(aCursor->get_Tablet(&curTablet))) {
+ return;
+ }
+ // All events should be suppressed except
+ // from tablets with hard proximity.
+ if (!IsHardProximityTablet(curTablet)) {
+ return;
+ }
+ // Notify current target window.
+ if (HWND targetWindow = InkCollector::sInkCollector->GetTarget()) {
+ ::SendMessage(targetWindow, MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER, 0, 0);
+ }
+}
diff --git a/widget/windows/InkCollector.h b/widget/windows/InkCollector.h
new file mode 100644
index 0000000000..f13bfbf570
--- /dev/null
+++ b/widget/windows/InkCollector.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=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/.
+ */
+
+#ifndef InkCollector_h__
+#define InkCollector_h__
+
+#include <msinkaut.h>
+#include "mozilla/StaticPtr.h"
+
+#define MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER WM_USER + 0x83
+
+class InkCollectorEvent final : public _IInkCollectorEvents {
+ public:
+ // IUnknown
+ HRESULT __stdcall QueryInterface(REFIID aRiid, void** aObject);
+ virtual ULONG STDMETHODCALLTYPE AddRef() { return ++mRefCount; }
+ virtual ULONG STDMETHODCALLTYPE Release() {
+ MOZ_ASSERT(mRefCount);
+ if (!--mRefCount) {
+ delete this;
+ return 0;
+ }
+ return mRefCount;
+ }
+
+ protected:
+ // IDispatch
+ STDMETHOD(GetTypeInfoCount)(UINT* aInfo) { return E_NOTIMPL; }
+ STDMETHOD(GetTypeInfo)(UINT aInfo, LCID aId, ITypeInfo** aTInfo) {
+ return E_NOTIMPL;
+ }
+ STDMETHOD(GetIDsOfNames)
+ (REFIID aRiid, LPOLESTR* aStrNames, UINT aNames, LCID aId, DISPID* aDispId) {
+ return E_NOTIMPL;
+ }
+ STDMETHOD(Invoke)
+ (DISPID aDispIdMember, REFIID aRiid, LCID aId, WORD wFlags,
+ DISPPARAMS* aDispParams, VARIANT* aVarResult, EXCEPINFO* aExcepInfo,
+ UINT* aArgErr);
+
+ // InkCollectorEvent
+ void CursorOutOfRange(IInkCursor* aCursor) const;
+ bool IsHardProximityTablet(IInkTablet* aTablet) const;
+
+ private:
+ uint32_t mRefCount = 0;
+
+ ~InkCollectorEvent() = default;
+};
+
+class InkCollector {
+ public:
+ ~InkCollector();
+ void Shutdown();
+
+ HWND GetTarget();
+ void SetTarget(HWND aTargetWindow);
+ void ClearTarget();
+
+ uint16_t GetPointerId(); // 0 shows that there is no existing pen.
+ void SetPointerId(uint16_t aPointerId);
+ void ClearPointerId();
+
+ static mozilla::StaticAutoPtr<InkCollector> sInkCollector;
+
+ protected:
+ void Initialize();
+ void OnInitialize();
+ void Enable(bool aNewState);
+
+ private:
+ RefPtr<IUnknown> mMarshaller;
+ RefPtr<IInkCollector> mInkCollector;
+ RefPtr<IConnectionPoint> mConnectionPoint;
+ RefPtr<InkCollectorEvent> mInkCollectorEvent;
+
+ HWND mTargetWindow = 0;
+ DWORD mCookie = 0;
+ bool mComInitialized = false;
+ bool mEnabled = false;
+
+ // This value holds the previous pointerId of the pen, and is used by the
+ // nsWindow when processing a MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER which
+ // indicates that a pen leaves the digitizer.
+
+ // TODO: If we move our implementation to window pointer input messages, then
+ // we no longer need this value, since the pointerId can be retrieved from the
+ // window message, please refer to
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/hh454916(v=vs.85).aspx
+
+ // NOTE: The pointerId of a pen shouldn't be 0 on a Windows platform, since 0
+ // is reserved of the mouse, please refer to
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx
+ uint16_t mPointerId = 0;
+};
+
+#endif // InkCollector_h__
diff --git a/widget/windows/InputDeviceUtils.cpp b/widget/windows/InputDeviceUtils.cpp
new file mode 100644
index 0000000000..3636b93d6a
--- /dev/null
+++ b/widget/windows/InputDeviceUtils.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; 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 "InputDeviceUtils.h"
+
+#define INITGUID
+#include <dbt.h>
+#include <hidclass.h>
+#include <ntddmou.h>
+#include <setupapi.h>
+
+namespace mozilla {
+namespace widget {
+
+HDEVNOTIFY
+InputDeviceUtils::RegisterNotification(HWND aHwnd) {
+ DEV_BROADCAST_DEVICEINTERFACE filter = {};
+
+ filter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
+ filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
+ // Some touchsreen devices are not GUID_DEVINTERFACE_MOUSE, so here we use
+ // GUID_DEVINTERFACE_HID instead.
+ filter.dbcc_classguid = GUID_DEVINTERFACE_HID;
+ return RegisterDeviceNotification(aHwnd, &filter,
+ DEVICE_NOTIFY_WINDOW_HANDLE);
+}
+
+void InputDeviceUtils::UnregisterNotification(HDEVNOTIFY aHandle) {
+ if (!aHandle) {
+ return;
+ }
+ UnregisterDeviceNotification(aHandle);
+}
+
+DWORD
+InputDeviceUtils::CountMouseDevices() {
+ HDEVINFO hdev =
+ SetupDiGetClassDevs(&GUID_DEVINTERFACE_MOUSE, nullptr, nullptr,
+ DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
+ if (hdev == INVALID_HANDLE_VALUE) {
+ return 0;
+ }
+
+ DWORD count = 0;
+ SP_INTERFACE_DEVICE_DATA info = {};
+ info.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);
+ while (SetupDiEnumDeviceInterfaces(hdev, nullptr, &GUID_DEVINTERFACE_MOUSE,
+ count, &info)) {
+ if (info.Flags & SPINT_ACTIVE) {
+ count++;
+ }
+ }
+ SetupDiDestroyDeviceInfoList(hdev);
+ return count;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/InputDeviceUtils.h b/widget/windows/InputDeviceUtils.h
new file mode 100644
index 0000000000..d16698a03a
--- /dev/null
+++ b/widget/windows/InputDeviceUtils.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_InputDeviceUtils_h__
+#define mozilla_widget_InputDeviceUtils_h__
+
+#include <windows.h>
+
+namespace mozilla {
+namespace widget {
+
+class InputDeviceUtils {
+ public:
+ static HDEVNOTIFY RegisterNotification(HWND aHwnd);
+ static void UnregisterNotification(HDEVNOTIFY aHandle);
+
+ // Returns the number of mouse type devices connected to this system.
+ static DWORD CountMouseDevices();
+};
+
+} // namespace widget
+} // namespace mozilla
+#endif // mozilla_widget_InputDeviceUtils_h__
diff --git a/widget/windows/JumpListBuilder.cpp b/widget/windows/JumpListBuilder.cpp
new file mode 100644
index 0000000000..fa14dd7cb5
--- /dev/null
+++ b/widget/windows/JumpListBuilder.cpp
@@ -0,0 +1,635 @@
+/* -*- 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 "JumpListBuilder.h"
+
+#include "nsError.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsArrayUtils.h"
+#include "nsWidgetsCID.h"
+#include "WinTaskbar.h"
+#include "nsDirectoryServiceUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsStringStream.h"
+#include "nsThreadUtils.h"
+#include "mozilla/LazyIdleThread.h"
+#include "nsIObserverService.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/mscom/ApartmentRegion.h"
+#include "mozilla/mscom/EnsureMTA.h"
+
+#include <shellapi.h>
+#include "WinUtils.h"
+
+using mozilla::dom::Promise;
+
+// The amount of time, in milliseconds, that our IO thread will stay alive after
+// the last event it processes.
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
+namespace mozilla {
+namespace widget {
+
+// defined in WinTaskbar.cpp
+extern const wchar_t* gMozillaJumpListIDGeneric;
+
+Atomic<bool> JumpListBuilder::sBuildingList(false);
+const char kPrefTaskbarEnabled[] = "browser.taskbar.lists.enabled";
+
+NS_IMPL_ISUPPORTS(JumpListBuilder, nsIJumpListBuilder, nsIObserver)
+#define TOPIC_PROFILE_BEFORE_CHANGE "profile-before-change"
+#define TOPIC_CLEAR_PRIVATE_DATA "clear-private-data"
+
+namespace detail {
+
+class DoneCommitListBuildCallback final : public nsIRunnable {
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ public:
+ DoneCommitListBuildCallback(nsIJumpListCommittedCallback* aCallback,
+ JumpListBuilder* aBuilder)
+ : mCallback(aCallback), mBuilder(aBuilder), mResult(false) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mCallback) {
+ Unused << mCallback->Done(mResult);
+ }
+ // Ensure we are releasing on the main thread.
+ Destroy();
+ return NS_OK;
+ }
+
+ void SetResult(bool aResult) { mResult = aResult; }
+
+ private:
+ ~DoneCommitListBuildCallback() {
+ // Destructor does not always call on the main thread.
+ MOZ_ASSERT(!mCallback);
+ MOZ_ASSERT(!mBuilder);
+ }
+
+ void Destroy() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mCallback = nullptr;
+ mBuilder = nullptr;
+ }
+
+ // These two references MUST be released on the main thread.
+ RefPtr<nsIJumpListCommittedCallback> mCallback;
+ RefPtr<JumpListBuilder> mBuilder;
+ bool mResult;
+};
+
+NS_IMPL_ISUPPORTS(DoneCommitListBuildCallback, nsIRunnable);
+
+} // namespace detail
+
+JumpListBuilder::JumpListBuilder()
+ : mMaxItems(0), mHasCommit(false), mMonitor("JumpListBuilderMonitor") {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Instantiate mJumpListMgr in the multithreaded apartment so that proxied
+ // calls on that object do not need to interact with the main thread's message
+ // pump.
+ mscom::EnsureMTA([this]() {
+ RefPtr<ICustomDestinationList> jumpListMgr;
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ICustomDestinationList, getter_AddRefs(jumpListMgr));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ // Since we are accessing mJumpListMgr across different threads
+ // (ie, different apartments), mJumpListMgr must be an agile reference.
+ mJumpListMgr = jumpListMgr;
+ });
+
+ if (!mJumpListMgr) {
+ return;
+ }
+
+ // Make a lazy thread for any IO
+ mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "Jump List"_ns,
+ LazyIdleThread::ManualShutdown);
+ Preferences::AddStrongObserver(this, kPrefTaskbarEnabled);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (observerService) {
+ observerService->AddObserver(this, TOPIC_PROFILE_BEFORE_CHANGE, false);
+ observerService->AddObserver(this, TOPIC_CLEAR_PRIVATE_DATA, false);
+ }
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return;
+ }
+
+ // GetAppUserModelID can only be called once we're back on the main thread.
+ nsString modelId;
+ if (mozilla::widget::WinTaskbar::GetAppUserModelID(modelId)) {
+ jumpListMgr->SetAppID(modelId.get());
+ }
+}
+
+JumpListBuilder::~JumpListBuilder() {
+ Preferences::RemoveObserver(this, kPrefTaskbarEnabled);
+}
+
+NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t* aAvailable) {
+ *aAvailable = false;
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (mJumpListMgr) *aAvailable = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::GetIsListCommitted(bool* aCommit) {
+ *aCommit = mHasCommit;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::GetMaxListItems(int16_t* aMaxItems) {
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ *aMaxItems = 0;
+
+ if (sBuildingList) {
+ *aMaxItems = mMaxItems;
+ return NS_OK;
+ }
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ IObjectArray* objArray;
+ if (SUCCEEDED(jumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
+ *aMaxItems = mMaxItems;
+
+ if (objArray) objArray->Release();
+
+ jumpListMgr->AbortList();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::InitListBuild(JSContext* aCx,
+ Promise** aPromise) {
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!globalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(globalObject, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod<StoreCopyPassByRRef<RefPtr<Promise>>>(
+ "InitListBuild", this, &JumpListBuilder::DoInitListBuild, promise);
+ nsresult rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
+void JumpListBuilder::DoInitListBuild(RefPtr<Promise>&& aPromise) {
+ // Since we're invoking COM interfaces to talk to the shell on a background
+ // thread, we need to be running inside a multithreaded apartment.
+ mscom::MTARegion mta;
+ MOZ_ASSERT(mta.IsValid());
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ MOZ_ASSERT(mJumpListMgr);
+
+ if (sBuildingList) {
+ AbortListBuild();
+ }
+
+ HRESULT hr = E_UNEXPECTED;
+ auto errorHandler = MakeScopeExit([&aPromise, &hr]() {
+ if (SUCCEEDED(hr)) {
+ return;
+ }
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "InitListBuildReject", [promise = std::move(aPromise)]() {
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ }));
+ });
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return;
+ }
+
+ nsTArray<nsString> urisToRemove;
+ RefPtr<IObjectArray> objArray;
+ hr = jumpListMgr->BeginList(
+ &mMaxItems,
+ IID_PPV_ARGS(static_cast<IObjectArray**>(getter_AddRefs(objArray))));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ // The returned objArray of removed items are for manually removed items.
+ // This does not return items which are removed because they were previously
+ // part of the jump list but are no longer part of the jump list.
+ sBuildingList = true;
+ RemoveIconCacheAndGetJumplistShortcutURIs(objArray, urisToRemove);
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "InitListBuildResolve", [urisToRemove = std::move(urisToRemove),
+ promise = std::move(aPromise)]() {
+ promise->MaybeResolve(urisToRemove);
+ }));
+}
+
+// Ensures that we have no old ICO files left in the jump list cache
+nsresult JumpListBuilder::RemoveIconCacheForAllItems() {
+ // Construct the path of our jump list cache
+ nsCOMPtr<nsIFile> jumpListCacheDir;
+ nsresult rv =
+ NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(jumpListCacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = jumpListCacheDir->AppendNative(
+ nsDependentCString(mozilla::widget::FaviconHelper::kJumpListCacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDirectoryEnumerator> entries;
+ rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Loop through each directory entry and remove all ICO files found
+ do {
+ nsCOMPtr<nsIFile> currFile;
+ if (NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile))) || !currFile)
+ break;
+
+ nsAutoString path;
+ if (NS_FAILED(currFile->GetPath(path))) continue;
+
+ if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) {
+ // Check if the cached ICO file exists
+ bool exists;
+ if (NS_FAILED(currFile->Exists(&exists)) || !exists) continue;
+
+ // We found an ICO file that exists, so we should remove it
+ currFile->Remove(false);
+ }
+ } while (true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::AddListToBuild(int16_t aCatType, nsIArray* items,
+ const nsAString& catName,
+ bool* _retval) {
+ nsresult rv;
+
+ *_retval = false;
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ switch (aCatType) {
+ case nsIJumpListBuilder::JUMPLIST_CATEGORY_TASKS: {
+ NS_ENSURE_ARG_POINTER(items);
+
+ HRESULT hr;
+ RefPtr<IObjectCollection> collection;
+ hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IObjectCollection,
+ getter_AddRefs(collection));
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ // Build the list
+ uint32_t length;
+ items->GetLength(&length);
+ for (uint32_t i = 0; i < length; ++i) {
+ nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
+ if (!item) continue;
+ // Check for separators
+ if (IsSeparator(item)) {
+ RefPtr<IShellLinkW> link;
+ rv = JumpListSeparator::GetSeparator(link);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(link);
+ continue;
+ }
+ // These should all be ShellLinks
+ RefPtr<IShellLinkW> link;
+ rv = JumpListShortcut::GetShellLink(item, link, mIOThread);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(link);
+ }
+
+ // We need IObjectArray to submit
+ RefPtr<IObjectArray> pArray;
+ hr = collection->QueryInterface(IID_IObjectArray, getter_AddRefs(pArray));
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ // Add the tasks
+ hr = jumpListMgr->AddUserTasks(pArray);
+ if (SUCCEEDED(hr)) *_retval = true;
+ return NS_OK;
+ } break;
+ case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT: {
+ if (SUCCEEDED(jumpListMgr->AppendKnownCategory(KDC_RECENT)))
+ *_retval = true;
+ return NS_OK;
+ } break;
+ case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT: {
+ if (SUCCEEDED(jumpListMgr->AppendKnownCategory(KDC_FREQUENT)))
+ *_retval = true;
+ return NS_OK;
+ } break;
+ case nsIJumpListBuilder::JUMPLIST_CATEGORY_CUSTOMLIST: {
+ NS_ENSURE_ARG_POINTER(items);
+
+ if (catName.IsEmpty()) return NS_ERROR_INVALID_ARG;
+
+ HRESULT hr;
+ RefPtr<IObjectCollection> collection;
+ hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IObjectCollection,
+ getter_AddRefs(collection));
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ uint32_t length;
+ items->GetLength(&length);
+ for (uint32_t i = 0; i < length; ++i) {
+ nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
+ if (!item) continue;
+ int16_t type;
+ if (NS_FAILED(item->GetType(&type))) continue;
+ switch (type) {
+ case nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR: {
+ RefPtr<IShellLinkW> shellItem;
+ rv = JumpListSeparator::GetSeparator(shellItem);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(shellItem);
+ } break;
+ case nsIJumpListItem::JUMPLIST_ITEM_LINK: {
+ RefPtr<IShellItem2> shellItem;
+ rv = JumpListLink::GetShellItem(item, shellItem);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(shellItem);
+ } break;
+ case nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT: {
+ RefPtr<IShellLinkW> shellItem;
+ rv = JumpListShortcut::GetShellLink(item, shellItem, mIOThread);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(shellItem);
+ } break;
+ }
+ }
+
+ // We need IObjectArray to submit
+ RefPtr<IObjectArray> pArray;
+ hr = collection->QueryInterface(IID_IObjectArray, (LPVOID*)&pArray);
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ // Add the tasks
+ hr = jumpListMgr->AppendCategory(
+ reinterpret_cast<const wchar_t*>(catName.BeginReading()), pArray);
+ if (SUCCEEDED(hr)) *_retval = true;
+
+ // Get rid of the old icons
+ nsCOMPtr<nsIRunnable> event =
+ new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(true);
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+ } break;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::AbortListBuild() {
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ jumpListMgr->AbortList();
+ sBuildingList = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::CommitListBuild(
+ nsIJumpListCommittedCallback* aCallback) {
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ // Also holds a strong reference to this to prevent use-after-free.
+ RefPtr<detail::DoneCommitListBuildCallback> callback =
+ new detail::DoneCommitListBuildCallback(aCallback, this);
+
+ // The builder has a strong reference in the callback already, so we do not
+ // need to do it for this runnable again.
+ RefPtr<nsIRunnable> event =
+ NewNonOwningRunnableMethod<RefPtr<detail::DoneCommitListBuildCallback>>(
+ "JumpListBuilder::DoCommitListBuild", this,
+ &JumpListBuilder::DoCommitListBuild, std::move(callback));
+ Unused << mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+void JumpListBuilder::DoCommitListBuild(
+ RefPtr<detail::DoneCommitListBuildCallback> aCallback) {
+ // Since we're invoking COM interfaces to talk to the shell on a background
+ // thread, we need to be running inside a multithreaded apartment.
+ mscom::MTARegion mta;
+ MOZ_ASSERT(mta.IsValid());
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ MOZ_ASSERT(mJumpListMgr);
+ MOZ_ASSERT(aCallback);
+
+ HRESULT hr = E_UNEXPECTED;
+ auto onExit = MakeScopeExit([&hr, &aCallback]() {
+ // XXX We might want some specific error data here.
+ aCallback->SetResult(SUCCEEDED(hr));
+ Unused << NS_DispatchToMainThread(aCallback);
+ });
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return;
+ }
+
+ hr = jumpListMgr->CommitList();
+ sBuildingList = false;
+
+ if (SUCCEEDED(hr)) {
+ mHasCommit = true;
+ }
+}
+
+NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool* _retval) {
+ *_retval = false;
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ if (sBuildingList) {
+ AbortListBuild();
+ }
+
+ nsAutoString uid;
+ if (!WinTaskbar::GetAppUserModelID(uid)) return NS_OK;
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (SUCCEEDED(jumpListMgr->DeleteList(uid.get()))) {
+ *_retval = true;
+ }
+
+ return NS_OK;
+}
+
+/* internal */
+
+bool JumpListBuilder::IsSeparator(nsCOMPtr<nsIJumpListItem>& item) {
+ int16_t type;
+ item->GetType(&type);
+ if (NS_FAILED(item->GetType(&type))) return false;
+
+ if (type == nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR) return true;
+ return false;
+}
+
+// RemoveIconCacheAndGetJumplistShortcutURIs - does multiple things to
+// avoid unnecessary extra XPCOM incantations. For each object in the input
+// array, if it's an IShellLinkW, this deletes the cached icon and adds the
+// url param to a list of URLs to be removed from the places database.
+void JumpListBuilder::RemoveIconCacheAndGetJumplistShortcutURIs(
+ IObjectArray* aObjArray, nsTArray<nsString>& aURISpecs) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Early return here just in case some versions of Windows don't populate this
+ if (!aObjArray) {
+ return;
+ }
+
+ uint32_t count = 0;
+ aObjArray->GetCount(&count);
+
+ for (uint32_t idx = 0; idx < count; idx++) {
+ RefPtr<IShellLinkW> pLink;
+
+ if (FAILED(aObjArray->GetAt(idx, IID_IShellLinkW,
+ static_cast<void**>(getter_AddRefs(pLink))))) {
+ continue;
+ }
+
+ wchar_t buf[MAX_PATH];
+ HRESULT hres = pLink->GetArguments(buf, MAX_PATH);
+ if (SUCCEEDED(hres)) {
+ LPWSTR* arglist;
+ int32_t numArgs;
+
+ arglist = ::CommandLineToArgvW(buf, &numArgs);
+ if (arglist && numArgs > 0) {
+ nsString spec(arglist[0]);
+ aURISpecs.AppendElement(std::move(spec));
+ ::LocalFree(arglist);
+ }
+ }
+
+ int iconIdx = 0;
+ hres = pLink->GetIconLocation(buf, MAX_PATH, &iconIdx);
+ if (SUCCEEDED(hres)) {
+ nsDependentString spec(buf);
+ DeleteIconFromDisk(spec);
+ }
+ }
+}
+
+void JumpListBuilder::DeleteIconFromDisk(const nsAString& aPath) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Check that we aren't deleting some arbitrary file that is not an icon
+ if (StringTail(aPath, 4).LowerCaseEqualsASCII(".ico")) {
+ // Construct the parent path of the passed in path
+ nsCOMPtr<nsIFile> icoFile;
+ nsresult rv = NS_NewLocalFile(aPath, true, getter_AddRefs(icoFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ icoFile->Remove(false);
+ }
+}
+
+NS_IMETHODIMP JumpListBuilder::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ NS_ENSURE_ARG_POINTER(aTopic);
+ if (strcmp(aTopic, TOPIC_PROFILE_BEFORE_CHANGE) == 0) {
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (observerService) {
+ observerService->RemoveObserver(this, TOPIC_PROFILE_BEFORE_CHANGE);
+ }
+ mIOThread->Shutdown();
+ // Clear out mJumpListMgr, as MSCOM services won't be available soon.
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ mJumpListMgr = nullptr;
+ } else if (strcmp(aTopic, "nsPref:changed") == 0 &&
+ nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) {
+ bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true);
+ if (!enabled) {
+ nsCOMPtr<nsIRunnable> event =
+ new mozilla::widget::AsyncDeleteAllFaviconsFromDisk();
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ } else if (strcmp(aTopic, TOPIC_CLEAR_PRIVATE_DATA) == 0) {
+ // Delete JumpListCache icons from Disk, if any.
+ nsCOMPtr<nsIRunnable> event =
+ new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(false);
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/JumpListBuilder.h b/widget/windows/JumpListBuilder.h
new file mode 100644
index 0000000000..ce6e71b305
--- /dev/null
+++ b/widget/windows/JumpListBuilder.h
@@ -0,0 +1,69 @@
+/* -*- 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 __JumpListBuilder_h__
+#define __JumpListBuilder_h__
+
+#include <windows.h>
+
+#undef NTDDI_VERSION
+#define NTDDI_VERSION NTDDI_WIN7
+// Needed for various com interfaces
+#include <shobjidl.h>
+#undef LogSeverity // SetupAPI.h #defines this as DWORD
+
+#include "nsString.h"
+
+#include "nsIJumpListBuilder.h"
+#include "nsIJumpListItem.h"
+#include "JumpListItem.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/mscom/AgileReference.h"
+#include "mozilla/ReentrantMonitor.h"
+
+namespace mozilla {
+namespace widget {
+
+namespace detail {
+class DoneCommitListBuildCallback;
+} // namespace detail
+
+class JumpListBuilder : public nsIJumpListBuilder, public nsIObserver {
+ virtual ~JumpListBuilder();
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIJUMPLISTBUILDER
+ NS_DECL_NSIOBSERVER
+
+ JumpListBuilder();
+
+ protected:
+ static Atomic<bool> sBuildingList;
+
+ private:
+ mscom::AgileReference mJumpListMgr;
+ uint32_t mMaxItems;
+ bool mHasCommit;
+ nsCOMPtr<nsIThread> mIOThread;
+ ReentrantMonitor mMonitor;
+
+ bool IsSeparator(nsCOMPtr<nsIJumpListItem>& item);
+ void RemoveIconCacheAndGetJumplistShortcutURIs(IObjectArray* aObjArray,
+ nsTArray<nsString>& aURISpecs);
+ void DeleteIconFromDisk(const nsAString& aPath);
+ nsresult RemoveIconCacheForAllItems();
+ void DoCommitListBuild(RefPtr<detail::DoneCommitListBuildCallback> aCallback);
+ void DoInitListBuild(RefPtr<dom::Promise>&& aPromise);
+
+ friend class WinTaskbar;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __JumpListBuilder_h__ */
diff --git a/widget/windows/JumpListItem.cpp b/widget/windows/JumpListItem.cpp
new file mode 100644
index 0000000000..941a479203
--- /dev/null
+++ b/widget/windows/JumpListItem.cpp
@@ -0,0 +1,575 @@
+/* -*- 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 "JumpListItem.h"
+
+#include <shellapi.h>
+#include <propvarutil.h>
+#include <propkey.h>
+
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsCRT.h"
+#include "nsNetCID.h"
+#include "nsCExternalHandlerService.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Preferences.h"
+#include "JumpListBuilder.h"
+#include "WinUtils.h"
+
+namespace mozilla {
+namespace widget {
+
+// ISUPPORTS Impl's
+NS_IMPL_ISUPPORTS(JumpListItem, nsIJumpListItem)
+
+NS_INTERFACE_MAP_BEGIN(JumpListSeparator)
+ NS_INTERFACE_MAP_ENTRY(nsIJumpListSeparator)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIJumpListItem, JumpListItemBase)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, JumpListItemBase)
+NS_INTERFACE_MAP_END
+NS_IMPL_ADDREF(JumpListSeparator)
+NS_IMPL_RELEASE(JumpListSeparator)
+
+NS_INTERFACE_MAP_BEGIN(JumpListLink)
+ NS_INTERFACE_MAP_ENTRY(nsIJumpListLink)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIJumpListItem, JumpListItemBase)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, JumpListItemBase)
+NS_INTERFACE_MAP_END
+NS_IMPL_ADDREF(JumpListLink)
+NS_IMPL_RELEASE(JumpListLink)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JumpListShortcut)
+ NS_INTERFACE_MAP_ENTRY(nsIJumpListShortcut)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIJumpListItem, JumpListItemBase)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJumpListShortcut)
+NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTING_ADDREF(JumpListShortcut)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(JumpListShortcut)
+NS_IMPL_CYCLE_COLLECTION(JumpListShortcut, mHandlerApp)
+
+NS_IMETHODIMP JumpListItemBase::GetType(int16_t* aType) {
+ NS_ENSURE_ARG_POINTER(aType);
+
+ *aType = mItemType;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListItemBase::Equals(nsIJumpListItem* aItem, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ *aResult = false;
+
+ int16_t theType = nsIJumpListItem::JUMPLIST_ITEM_EMPTY;
+ if (NS_FAILED(aItem->GetType(&theType))) return NS_OK;
+
+ // Make sure the types match.
+ if (Type() != theType) return NS_OK;
+
+ *aResult = true;
+
+ return NS_OK;
+}
+
+/* link impl. */
+
+NS_IMETHODIMP JumpListLink::GetUri(nsIURI** aURI) {
+ NS_IF_ADDREF(*aURI = mURI);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListLink::SetUri(nsIURI* aURI) {
+ mURI = aURI;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListLink::SetUriTitle(const nsAString& aUriTitle) {
+ mUriTitle.Assign(aUriTitle);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListLink::GetUriTitle(nsAString& aUriTitle) {
+ aUriTitle.Assign(mUriTitle);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListLink::GetUriHash(nsACString& aUriHash) {
+ if (!mURI) return NS_ERROR_NOT_AVAILABLE;
+
+ return mozilla::widget::FaviconHelper::HashURI(mCryptoHash, mURI, aUriHash);
+}
+
+NS_IMETHODIMP JumpListLink::CompareHash(nsIURI* aUri, bool* aResult) {
+ nsresult rv;
+
+ if (!mURI) {
+ *aResult = !aUri;
+ return NS_OK;
+ }
+
+ NS_ENSURE_ARG_POINTER(aUri);
+
+ nsAutoCString hash1, hash2;
+
+ rv = mozilla::widget::FaviconHelper::HashURI(mCryptoHash, mURI, hash1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mozilla::widget::FaviconHelper::HashURI(mCryptoHash, aUri, hash2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = hash1.Equals(hash2);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListLink::Equals(nsIJumpListItem* aItem, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ nsresult rv;
+
+ *aResult = false;
+
+ int16_t theType = nsIJumpListItem::JUMPLIST_ITEM_EMPTY;
+ if (NS_FAILED(aItem->GetType(&theType))) return NS_OK;
+
+ // Make sure the types match.
+ if (Type() != theType) return NS_OK;
+
+ nsCOMPtr<nsIJumpListLink> link = do_QueryInterface(aItem, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // Check the titles
+ nsAutoString title;
+ link->GetUriTitle(title);
+ if (!mUriTitle.Equals(title)) return NS_OK;
+
+ // Call the internal object's equals() method to check.
+ nsCOMPtr<nsIURI> theUri;
+ bool equals = false;
+ if (NS_SUCCEEDED(link->GetUri(getter_AddRefs(theUri)))) {
+ if (!theUri) {
+ if (!mURI) *aResult = true;
+ return NS_OK;
+ }
+ if (NS_SUCCEEDED(theUri->Equals(mURI, &equals)) && equals) {
+ *aResult = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+/* shortcut impl. */
+
+NS_IMETHODIMP JumpListShortcut::GetApp(nsILocalHandlerApp** aApp) {
+ NS_IF_ADDREF(*aApp = mHandlerApp);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListShortcut::SetApp(nsILocalHandlerApp* aApp) {
+ mHandlerApp = aApp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListShortcut::GetIconIndex(int32_t* aIconIndex) {
+ NS_ENSURE_ARG_POINTER(aIconIndex);
+
+ *aIconIndex = mIconIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListShortcut::SetIconIndex(int32_t aIconIndex) {
+ mIconIndex = aIconIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListShortcut::GetFaviconPageUri(nsIURI** aFaviconPageURI) {
+ NS_IF_ADDREF(*aFaviconPageURI = mFaviconPageURI);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListShortcut::SetFaviconPageUri(nsIURI* aFaviconPageURI) {
+ mFaviconPageURI = aFaviconPageURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListShortcut::Equals(nsIJumpListItem* aItem, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ nsresult rv;
+
+ *aResult = false;
+
+ int16_t theType = nsIJumpListItem::JUMPLIST_ITEM_EMPTY;
+ if (NS_FAILED(aItem->GetType(&theType))) return NS_OK;
+
+ // Make sure the types match.
+ if (Type() != theType) return NS_OK;
+
+ nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(aItem, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // Check the icon index
+ // int32_t idx;
+ // shortcut->GetIconIndex(&idx);
+ // if (mIconIndex != idx)
+ // return NS_OK;
+ // No need to check the icon page URI either
+
+ // Call the internal object's equals() method to check.
+ nsCOMPtr<nsILocalHandlerApp> theApp;
+ bool equals = false;
+ if (NS_SUCCEEDED(shortcut->GetApp(getter_AddRefs(theApp)))) {
+ if (!theApp) {
+ if (!mHandlerApp) *aResult = true;
+ return NS_OK;
+ }
+ if (NS_SUCCEEDED(theApp->Equals(mHandlerApp, &equals)) && equals) {
+ *aResult = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+/* internal helpers */
+
+// (static) Creates a ShellLink that encapsulate a separator.
+nsresult JumpListSeparator::GetSeparator(RefPtr<IShellLinkW>& aShellLink) {
+ HRESULT hr;
+ IShellLinkW* psl;
+
+ // Create a IShellLink.
+ hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW, (LPVOID*)&psl);
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ IPropertyStore* pPropStore = nullptr;
+ hr = psl->QueryInterface(IID_IPropertyStore, (LPVOID*)&pPropStore);
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ PROPVARIANT pv;
+ InitPropVariantFromBoolean(TRUE, &pv);
+
+ pPropStore->SetValue(PKEY_AppUserModel_IsDestListSeparator, pv);
+ pPropStore->Commit();
+ pPropStore->Release();
+
+ PropVariantClear(&pv);
+
+ aShellLink = dont_AddRef(psl);
+
+ return NS_OK;
+}
+
+// (static) Creates a ShellLink that encapsulate a shortcut to local apps.
+nsresult JumpListShortcut::GetShellLink(nsCOMPtr<nsIJumpListItem>& item,
+ RefPtr<IShellLinkW>& aShellLink,
+ nsCOMPtr<nsIThread>& aIOThread) {
+ HRESULT hr;
+ IShellLinkW* psl;
+ nsresult rv;
+
+ // Shell links:
+ // http://msdn.microsoft.com/en-us/library/bb776891(VS.85).aspx
+ // http://msdn.microsoft.com/en-us/library/bb774950(VS.85).aspx
+
+ int16_t type;
+ if (NS_FAILED(item->GetType(&type))) return NS_ERROR_INVALID_ARG;
+
+ if (type != nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT)
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(item, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILocalHandlerApp> handlerApp;
+ rv = shortcut->GetApp(getter_AddRefs(handlerApp));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create a IShellLink
+ hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW, (LPVOID*)&psl);
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ // Retrieve the app path, title, description and optional command line args.
+ nsAutoString appPath, appTitle, appDescription, appArgs;
+ int32_t appIconIndex = 0;
+
+ // Path
+ nsCOMPtr<nsIFile> executable;
+ handlerApp->GetExecutable(getter_AddRefs(executable));
+
+ rv = executable->GetPath(appPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Command line parameters
+ uint32_t count = 0;
+ handlerApp->GetParameterCount(&count);
+ for (uint32_t idx = 0; idx < count; idx++) {
+ if (idx > 0) appArgs.Append(' ');
+ nsAutoString param;
+ rv = handlerApp->GetParameter(idx, param);
+ if (NS_FAILED(rv)) return rv;
+ appArgs.Append(param);
+ }
+
+ handlerApp->GetName(appTitle);
+ handlerApp->GetDetailedDescription(appDescription);
+
+ bool useUriIcon = false; // if we want to use the URI icon
+ bool usedUriIcon = false; // if we did use the URI icon
+ shortcut->GetIconIndex(&appIconIndex);
+
+ nsCOMPtr<nsIURI> iconUri;
+ rv = shortcut->GetFaviconPageUri(getter_AddRefs(iconUri));
+ if (NS_SUCCEEDED(rv) && iconUri) {
+ useUriIcon = true;
+ }
+
+ // Store the title of the app
+ if (appTitle.Length() > 0) {
+ IPropertyStore* pPropStore = nullptr;
+ hr = psl->QueryInterface(IID_IPropertyStore, (LPVOID*)&pPropStore);
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ PROPVARIANT pv;
+ InitPropVariantFromString(appTitle.get(), &pv);
+
+ pPropStore->SetValue(PKEY_Title, pv);
+ pPropStore->Commit();
+ pPropStore->Release();
+
+ PropVariantClear(&pv);
+ }
+
+ // Store the rest of the params
+ psl->SetPath(appPath.get());
+ psl->SetDescription(appDescription.get());
+ psl->SetArguments(appArgs.get());
+
+ if (useUriIcon) {
+ nsString icoFilePath;
+ rv = mozilla::widget::FaviconHelper::ObtainCachedIconFile(
+ iconUri, icoFilePath, aIOThread, false);
+ if (NS_SUCCEEDED(rv)) {
+ // Always use the first icon in the ICO file
+ // our encoded icon only has 1 resource
+ psl->SetIconLocation(icoFilePath.get(), 0);
+ usedUriIcon = true;
+ }
+ }
+
+ // We didn't use an ICO via URI so fall back to the app icon
+ if (!usedUriIcon) {
+ psl->SetIconLocation(appPath.get(), appIconIndex);
+ }
+
+ aShellLink = dont_AddRef(psl);
+
+ return NS_OK;
+}
+
+// If successful fills in the aSame parameter
+// aSame will be true if the path is in our icon cache
+static nsresult IsPathInOurIconCache(nsCOMPtr<nsIJumpListShortcut>& aShortcut,
+ wchar_t* aPath, bool* aSame) {
+ NS_ENSURE_ARG_POINTER(aPath);
+ NS_ENSURE_ARG_POINTER(aSame);
+
+ *aSame = false;
+
+ // Construct the path of our jump list cache
+ nsCOMPtr<nsIFile> jumpListCache;
+ nsresult rv =
+ NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(jumpListCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = jumpListCache->AppendNative(
+ nsDependentCString(FaviconHelper::kJumpListCacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoString jumpListCachePath;
+ rv = jumpListCache->GetPath(jumpListCachePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Construct the parent path of the passed in path
+ nsCOMPtr<nsIFile> passedInFile =
+ do_CreateInstance("@mozilla.org/file/local;1");
+ NS_ENSURE_TRUE(passedInFile, NS_ERROR_FAILURE);
+ nsAutoString passedInPath(aPath);
+ rv = passedInFile->InitWithPath(passedInPath);
+ nsCOMPtr<nsIFile> passedInParentFile;
+ passedInFile->GetParent(getter_AddRefs(passedInParentFile));
+ nsAutoString passedInParentPath;
+ rv = jumpListCache->GetPath(passedInParentPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aSame = jumpListCachePath.Equals(passedInParentPath);
+ return NS_OK;
+}
+
+// (static) For a given IShellLink, create and return a populated
+// nsIJumpListShortcut.
+nsresult JumpListShortcut::GetJumpListShortcut(
+ IShellLinkW* pLink, nsCOMPtr<nsIJumpListShortcut>& aShortcut) {
+ NS_ENSURE_ARG_POINTER(pLink);
+
+ nsresult rv;
+ HRESULT hres;
+
+ nsCOMPtr<nsILocalHandlerApp> handlerApp =
+ do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ wchar_t buf[MAX_PATH];
+
+ // Path
+ hres = pLink->GetPath(buf, MAX_PATH, nullptr, SLGP_UNCPRIORITY);
+ if (FAILED(hres)) return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIFile> file;
+ nsDependentString filepath(buf);
+ rv = NS_NewLocalFile(filepath, false, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = handlerApp->SetExecutable(file);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Parameters
+ hres = pLink->GetArguments(buf, MAX_PATH);
+ if (SUCCEEDED(hres)) {
+ LPWSTR* arglist;
+ int32_t numArgs;
+ int32_t idx;
+
+ arglist = ::CommandLineToArgvW(buf, &numArgs);
+ if (arglist) {
+ for (idx = 0; idx < numArgs; idx++) {
+ // szArglist[i] is null terminated
+ nsDependentString arg(arglist[idx]);
+ handlerApp->AppendParameter(arg);
+ }
+ ::LocalFree(arglist);
+ }
+ }
+
+ rv = aShortcut->SetApp(handlerApp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Icon index or file location
+ int iconIdx = 0;
+ hres = pLink->GetIconLocation(buf, MAX_PATH, &iconIdx);
+ if (SUCCEEDED(hres)) {
+ // XXX How do we handle converting local files to images here? Do we need
+ // to?
+ aShortcut->SetIconIndex(iconIdx);
+
+ // Obtain the local profile directory and construct the output icon file
+ // path We only set the Icon Uri if we're sure it was from our icon cache.
+ bool isInOurCache;
+ if (NS_SUCCEEDED(IsPathInOurIconCache(aShortcut, buf, &isInOurCache)) &&
+ isInOurCache) {
+ nsCOMPtr<nsIURI> iconUri;
+ nsAutoString path(buf);
+ rv = NS_NewURI(getter_AddRefs(iconUri), path);
+ if (NS_SUCCEEDED(rv)) {
+ aShortcut->SetFaviconPageUri(iconUri);
+ }
+ }
+ }
+
+ // Do we need the title and description? Probably not since handler app
+ // doesn't compare these in equals.
+
+ return NS_OK;
+}
+
+// (static) ShellItems are used to encapsulate links to things. We currently
+// only support URI links, but more support could be added, such as local file
+// and directory links.
+nsresult JumpListLink::GetShellItem(nsCOMPtr<nsIJumpListItem>& item,
+ RefPtr<IShellItem2>& aShellItem) {
+ IShellItem2* psi = nullptr;
+ nsresult rv;
+
+ int16_t type;
+ if (NS_FAILED(item->GetType(&type))) return NS_ERROR_INVALID_ARG;
+
+ if (type != nsIJumpListItem::JUMPLIST_ITEM_LINK) return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIJumpListLink> link = do_QueryInterface(item, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = link->GetUri(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create the IShellItem
+ if (FAILED(SHCreateItemFromParsingName(NS_ConvertASCIItoUTF16(spec).get(),
+ nullptr, IID_PPV_ARGS(&psi)))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Set the title
+ nsAutoString linkTitle;
+ link->GetUriTitle(linkTitle);
+
+ IPropertyStore* pPropStore = nullptr;
+ HRESULT hres = psi->GetPropertyStore(GPS_DEFAULT, IID_IPropertyStore,
+ (void**)&pPropStore);
+ if (FAILED(hres)) return NS_ERROR_UNEXPECTED;
+
+ PROPVARIANT pv;
+ InitPropVariantFromString(linkTitle.get(), &pv);
+
+ // May fail due to shell item access permissions.
+ pPropStore->SetValue(PKEY_ItemName, pv);
+ pPropStore->Commit();
+ pPropStore->Release();
+
+ PropVariantClear(&pv);
+
+ aShellItem = dont_AddRef(psi);
+
+ return NS_OK;
+}
+
+// (static) For a given IShellItem, create and return a populated
+// nsIJumpListLink.
+nsresult JumpListLink::GetJumpListLink(IShellItem* pItem,
+ nsCOMPtr<nsIJumpListLink>& aLink) {
+ NS_ENSURE_ARG_POINTER(pItem);
+
+ // We assume for now these are URI links, but through properties we could
+ // query and create other types.
+ nsresult rv;
+ LPWSTR lpstrName = nullptr;
+
+ if (SUCCEEDED(pItem->GetDisplayName(SIGDN_URL, &lpstrName))) {
+ nsCOMPtr<nsIURI> uri;
+ nsAutoString spec(lpstrName);
+
+ rv = NS_NewURI(getter_AddRefs(uri), NS_ConvertUTF16toUTF8(spec));
+ if (NS_FAILED(rv)) return NS_ERROR_INVALID_ARG;
+
+ aLink->SetUri(uri);
+
+ ::CoTaskMemFree(lpstrName);
+ }
+
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/JumpListItem.h b/widget/windows/JumpListItem.h
new file mode 100644
index 0000000000..67d9d50d50
--- /dev/null
+++ b/widget/windows/JumpListItem.h
@@ -0,0 +1,131 @@
+/* -*- 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 __JumpListItem_h__
+#define __JumpListItem_h__
+
+#include <windows.h>
+#include <shobjidl.h>
+#undef LogSeverity // SetupAPI.h #defines this as DWORD
+
+#include "mozilla/RefPtr.h"
+#include "nsIJumpListItem.h" // defines nsIJumpListItem
+#include "nsIMIMEInfo.h" // defines nsILocalHandlerApp
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+#include "nsICryptoHash.h"
+#include "nsString.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsIThread;
+
+namespace mozilla {
+namespace widget {
+
+class JumpListItemBase : public nsIJumpListItem {
+ public:
+ JumpListItemBase() : mItemType(nsIJumpListItem::JUMPLIST_ITEM_EMPTY) {}
+
+ explicit JumpListItemBase(int32_t type) : mItemType(type) {}
+
+ NS_DECL_NSIJUMPLISTITEM
+
+ static const char kJumpListCacheDir[];
+
+ protected:
+ virtual ~JumpListItemBase() {}
+
+ short Type() { return mItemType; }
+ short mItemType;
+};
+
+class JumpListItem : public JumpListItemBase {
+ ~JumpListItem() {}
+
+ public:
+ using JumpListItemBase::JumpListItemBase;
+
+ NS_DECL_ISUPPORTS
+};
+
+class JumpListSeparator : public JumpListItemBase, public nsIJumpListSeparator {
+ ~JumpListSeparator() {}
+
+ public:
+ JumpListSeparator()
+ : JumpListItemBase(nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR) {}
+
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSIJUMPLISTITEM(JumpListItemBase::)
+
+ static nsresult GetSeparator(RefPtr<IShellLinkW>& aShellLink);
+};
+
+class JumpListLink : public JumpListItemBase, public nsIJumpListLink {
+ ~JumpListLink() {}
+
+ public:
+ JumpListLink() : JumpListItemBase(nsIJumpListItem::JUMPLIST_ITEM_LINK) {}
+
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD GetType(int16_t* aType) override {
+ return JumpListItemBase::GetType(aType);
+ }
+ NS_IMETHOD Equals(nsIJumpListItem* item, bool* _retval) override;
+ NS_DECL_NSIJUMPLISTLINK
+
+ static nsresult GetShellItem(nsCOMPtr<nsIJumpListItem>& item,
+ RefPtr<IShellItem2>& aShellItem);
+ static nsresult GetJumpListLink(IShellItem* pItem,
+ nsCOMPtr<nsIJumpListLink>& aLink);
+
+ protected:
+ nsString mUriTitle;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsICryptoHash> mCryptoHash;
+};
+
+class JumpListShortcut : public JumpListItemBase, public nsIJumpListShortcut {
+ ~JumpListShortcut() {}
+
+ public:
+ JumpListShortcut()
+ : JumpListItemBase(nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(JumpListShortcut, JumpListItemBase)
+ NS_IMETHOD GetType(int16_t* aType) override {
+ return JumpListItemBase::GetType(aType);
+ }
+ NS_IMETHOD Equals(nsIJumpListItem* item, bool* _retval) override;
+ NS_DECL_NSIJUMPLISTSHORTCUT
+
+ static nsresult GetShellLink(nsCOMPtr<nsIJumpListItem>& item,
+ RefPtr<IShellLinkW>& aShellLink,
+ nsCOMPtr<nsIThread>& aIOThread);
+ static nsresult GetJumpListShortcut(IShellLinkW* pLink,
+ nsCOMPtr<nsIJumpListShortcut>& aShortcut);
+ static nsresult GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsCOMPtr<nsIFile>& aICOFile);
+
+ protected:
+ int32_t mIconIndex;
+ nsCOMPtr<nsIURI> mFaviconPageURI;
+ nsCOMPtr<nsILocalHandlerApp> mHandlerApp;
+
+ bool ExecutableExists(nsCOMPtr<nsILocalHandlerApp>& handlerApp);
+ static nsresult ObtainCachedIconFile(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsString& aICOFilePath,
+ nsCOMPtr<nsIThread>& aIOThread);
+ static nsresult CacheIconFileFromFaviconURIAsync(
+ nsCOMPtr<nsIURI> aFaviconPageURI, nsCOMPtr<nsIFile> aICOFile,
+ nsCOMPtr<nsIThread>& aIOThread);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __JumpListItem_h__ */
diff --git a/widget/windows/KeyboardLayout.cpp b/widget/windows/KeyboardLayout.cpp
new file mode 100644
index 0000000000..b08d9c8dc6
--- /dev/null
+++ b/widget/windows/KeyboardLayout.cpp
@@ -0,0 +1,5418 @@
+/* -*- 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/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEvents.h"
+
+#include "nsAlgorithm.h"
+#include "nsExceptionHandler.h"
+#include "nsGkAtoms.h"
+#include "nsIUserIdleServiceInternal.h"
+#include "nsIWindowsRegKey.h"
+#include "nsMemory.h"
+#include "nsPrintfCString.h"
+#include "nsQuickSort.h"
+#include "nsReadableUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsToolkit.h"
+#include "nsUnicharUtils.h"
+#include "nsWindowDbg.h"
+
+#include "KeyboardLayout.h"
+#include "WidgetUtils.h"
+#include "WinUtils.h"
+
+#include "npapi.h"
+
+#include <windows.h>
+#include <winuser.h>
+#include <algorithm>
+
+#ifndef WINABLEAPI
+# include <winable.h>
+#endif
+
+// In WinUser.h, MAPVK_VK_TO_VSC_EX is defined only when WINVER >= 0x0600
+#ifndef MAPVK_VK_TO_VSC_EX
+# define MAPVK_VK_TO_VSC_EX (4)
+#endif
+
+namespace mozilla {
+namespace widget {
+
+static const char* const kVirtualKeyName[] = {
+ "NULL",
+ "VK_LBUTTON",
+ "VK_RBUTTON",
+ "VK_CANCEL",
+ "VK_MBUTTON",
+ "VK_XBUTTON1",
+ "VK_XBUTTON2",
+ "0x07",
+ "VK_BACK",
+ "VK_TAB",
+ "0x0A",
+ "0x0B",
+ "VK_CLEAR",
+ "VK_RETURN",
+ "0x0E",
+ "0x0F",
+
+ "VK_SHIFT",
+ "VK_CONTROL",
+ "VK_MENU",
+ "VK_PAUSE",
+ "VK_CAPITAL",
+ "VK_KANA, VK_HANGUL",
+ "0x16",
+ "VK_JUNJA",
+ "VK_FINAL",
+ "VK_HANJA, VK_KANJI",
+ "0x1A",
+ "VK_ESCAPE",
+ "VK_CONVERT",
+ "VK_NONCONVERT",
+ "VK_ACCEPT",
+ "VK_MODECHANGE",
+
+ "VK_SPACE",
+ "VK_PRIOR",
+ "VK_NEXT",
+ "VK_END",
+ "VK_HOME",
+ "VK_LEFT",
+ "VK_UP",
+ "VK_RIGHT",
+ "VK_DOWN",
+ "VK_SELECT",
+ "VK_PRINT",
+ "VK_EXECUTE",
+ "VK_SNAPSHOT",
+ "VK_INSERT",
+ "VK_DELETE",
+ "VK_HELP",
+
+ "VK_0",
+ "VK_1",
+ "VK_2",
+ "VK_3",
+ "VK_4",
+ "VK_5",
+ "VK_6",
+ "VK_7",
+ "VK_8",
+ "VK_9",
+ "0x3A",
+ "0x3B",
+ "0x3C",
+ "0x3D",
+ "0x3E",
+ "0x3F",
+
+ "0x40",
+ "VK_A",
+ "VK_B",
+ "VK_C",
+ "VK_D",
+ "VK_E",
+ "VK_F",
+ "VK_G",
+ "VK_H",
+ "VK_I",
+ "VK_J",
+ "VK_K",
+ "VK_L",
+ "VK_M",
+ "VK_N",
+ "VK_O",
+
+ "VK_P",
+ "VK_Q",
+ "VK_R",
+ "VK_S",
+ "VK_T",
+ "VK_U",
+ "VK_V",
+ "VK_W",
+ "VK_X",
+ "VK_Y",
+ "VK_Z",
+ "VK_LWIN",
+ "VK_RWIN",
+ "VK_APPS",
+ "0x5E",
+ "VK_SLEEP",
+
+ "VK_NUMPAD0",
+ "VK_NUMPAD1",
+ "VK_NUMPAD2",
+ "VK_NUMPAD3",
+ "VK_NUMPAD4",
+ "VK_NUMPAD5",
+ "VK_NUMPAD6",
+ "VK_NUMPAD7",
+ "VK_NUMPAD8",
+ "VK_NUMPAD9",
+ "VK_MULTIPLY",
+ "VK_ADD",
+ "VK_SEPARATOR",
+ "VK_SUBTRACT",
+ "VK_DECIMAL",
+ "VK_DIVIDE",
+
+ "VK_F1",
+ "VK_F2",
+ "VK_F3",
+ "VK_F4",
+ "VK_F5",
+ "VK_F6",
+ "VK_F7",
+ "VK_F8",
+ "VK_F9",
+ "VK_F10",
+ "VK_F11",
+ "VK_F12",
+ "VK_F13",
+ "VK_F14",
+ "VK_F15",
+ "VK_F16",
+
+ "VK_F17",
+ "VK_F18",
+ "VK_F19",
+ "VK_F20",
+ "VK_F21",
+ "VK_F22",
+ "VK_F23",
+ "VK_F24",
+ "0x88",
+ "0x89",
+ "0x8A",
+ "0x8B",
+ "0x8C",
+ "0x8D",
+ "0x8E",
+ "0x8F",
+
+ "VK_NUMLOCK",
+ "VK_SCROLL",
+ "VK_OEM_NEC_EQUAL, VK_OEM_FJ_JISHO",
+ "VK_OEM_FJ_MASSHOU",
+ "VK_OEM_FJ_TOUROKU",
+ "VK_OEM_FJ_LOYA",
+ "VK_OEM_FJ_ROYA",
+ "0x97",
+ "0x98",
+ "0x99",
+ "0x9A",
+ "0x9B",
+ "0x9C",
+ "0x9D",
+ "0x9E",
+ "0x9F",
+
+ "VK_LSHIFT",
+ "VK_RSHIFT",
+ "VK_LCONTROL",
+ "VK_RCONTROL",
+ "VK_LMENU",
+ "VK_RMENU",
+ "VK_BROWSER_BACK",
+ "VK_BROWSER_FORWARD",
+ "VK_BROWSER_REFRESH",
+ "VK_BROWSER_STOP",
+ "VK_BROWSER_SEARCH",
+ "VK_BROWSER_FAVORITES",
+ "VK_BROWSER_HOME",
+ "VK_VOLUME_MUTE",
+ "VK_VOLUME_DOWN",
+ "VK_VOLUME_UP",
+
+ "VK_MEDIA_NEXT_TRACK",
+ "VK_MEDIA_PREV_TRACK",
+ "VK_MEDIA_STOP",
+ "VK_MEDIA_PLAY_PAUSE",
+ "VK_LAUNCH_MAIL",
+ "VK_LAUNCH_MEDIA_SELECT",
+ "VK_LAUNCH_APP1",
+ "VK_LAUNCH_APP2",
+ "0xB8",
+ "0xB9",
+ "VK_OEM_1",
+ "VK_OEM_PLUS",
+ "VK_OEM_COMMA",
+ "VK_OEM_MINUS",
+ "VK_OEM_PERIOD",
+ "VK_OEM_2",
+
+ "VK_OEM_3",
+ "VK_ABNT_C1",
+ "VK_ABNT_C2",
+ "0xC3",
+ "0xC4",
+ "0xC5",
+ "0xC6",
+ "0xC7",
+ "0xC8",
+ "0xC9",
+ "0xCA",
+ "0xCB",
+ "0xCC",
+ "0xCD",
+ "0xCE",
+ "0xCF",
+
+ "0xD0",
+ "0xD1",
+ "0xD2",
+ "0xD3",
+ "0xD4",
+ "0xD5",
+ "0xD6",
+ "0xD7",
+ "0xD8",
+ "0xD9",
+ "0xDA",
+ "VK_OEM_4",
+ "VK_OEM_5",
+ "VK_OEM_6",
+ "VK_OEM_7",
+ "VK_OEM_8",
+
+ "0xE0",
+ "VK_OEM_AX",
+ "VK_OEM_102",
+ "VK_ICO_HELP",
+ "VK_ICO_00",
+ "VK_PROCESSKEY",
+ "VK_ICO_CLEAR",
+ "VK_PACKET",
+ "0xE8",
+ "VK_OEM_RESET",
+ "VK_OEM_JUMP",
+ "VK_OEM_PA1",
+ "VK_OEM_PA2",
+ "VK_OEM_PA3",
+ "VK_OEM_WSCTRL",
+ "VK_OEM_CUSEL",
+
+ "VK_OEM_ATTN",
+ "VK_OEM_FINISH",
+ "VK_OEM_COPY",
+ "VK_OEM_AUTO",
+ "VK_OEM_ENLW",
+ "VK_OEM_BACKTAB",
+ "VK_ATTN",
+ "VK_CRSEL",
+ "VK_EXSEL",
+ "VK_EREOF",
+ "VK_PLAY",
+ "VK_ZOOM",
+ "VK_NONAME",
+ "VK_PA1",
+ "VK_OEM_CLEAR",
+ "0xFF"};
+
+static_assert(sizeof(kVirtualKeyName) / sizeof(const char*) == 0x100,
+ "The virtual key name must be defined just 256 keys");
+
+static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
+
+static const nsCString GetCharacterCodeName(WPARAM aCharCode) {
+ switch (aCharCode) {
+ case 0x0000:
+ return "NULL (0x0000)"_ns;
+ case 0x0008:
+ return "BACKSPACE (0x0008)"_ns;
+ case 0x0009:
+ return "CHARACTER TABULATION (0x0009)"_ns;
+ case 0x000A:
+ return "LINE FEED (0x000A)"_ns;
+ case 0x000B:
+ return "LINE TABULATION (0x000B)"_ns;
+ case 0x000C:
+ return "FORM FEED (0x000C)"_ns;
+ case 0x000D:
+ return "CARRIAGE RETURN (0x000D)"_ns;
+ case 0x0018:
+ return "CANCEL (0x0018)"_ns;
+ case 0x001B:
+ return "ESCAPE (0x001B)"_ns;
+ case 0x0020:
+ return "SPACE (0x0020)"_ns;
+ case 0x007F:
+ return "DELETE (0x007F)"_ns;
+ case 0x00A0:
+ return "NO-BREAK SPACE (0x00A0)"_ns;
+ case 0x00AD:
+ return "SOFT HYPHEN (0x00AD)"_ns;
+ case 0x2000:
+ return "EN QUAD (0x2000)"_ns;
+ case 0x2001:
+ return "EM QUAD (0x2001)"_ns;
+ case 0x2002:
+ return "EN SPACE (0x2002)"_ns;
+ case 0x2003:
+ return "EM SPACE (0x2003)"_ns;
+ case 0x2004:
+ return "THREE-PER-EM SPACE (0x2004)"_ns;
+ case 0x2005:
+ return "FOUR-PER-EM SPACE (0x2005)"_ns;
+ case 0x2006:
+ return "SIX-PER-EM SPACE (0x2006)"_ns;
+ case 0x2007:
+ return "FIGURE SPACE (0x2007)"_ns;
+ case 0x2008:
+ return "PUNCTUATION SPACE (0x2008)"_ns;
+ case 0x2009:
+ return "THIN SPACE (0x2009)"_ns;
+ case 0x200A:
+ return "HAIR SPACE (0x200A)"_ns;
+ case 0x200B:
+ return "ZERO WIDTH SPACE (0x200B)"_ns;
+ case 0x200C:
+ return "ZERO WIDTH NON-JOINER (0x200C)"_ns;
+ case 0x200D:
+ return "ZERO WIDTH JOINER (0x200D)"_ns;
+ case 0x200E:
+ return "LEFT-TO-RIGHT MARK (0x200E)"_ns;
+ case 0x200F:
+ return "RIGHT-TO-LEFT MARK (0x200F)"_ns;
+ case 0x2029:
+ return "PARAGRAPH SEPARATOR (0x2029)"_ns;
+ case 0x202A:
+ return "LEFT-TO-RIGHT EMBEDDING (0x202A)"_ns;
+ case 0x202B:
+ return "RIGHT-TO-LEFT EMBEDDING (0x202B)"_ns;
+ case 0x202D:
+ return "LEFT-TO-RIGHT OVERRIDE (0x202D)"_ns;
+ case 0x202E:
+ return "RIGHT-TO-LEFT OVERRIDE (0x202E)"_ns;
+ case 0x202F:
+ return "NARROW NO-BREAK SPACE (0x202F)"_ns;
+ case 0x205F:
+ return "MEDIUM MATHEMATICAL SPACE (0x205F)"_ns;
+ case 0x2060:
+ return "WORD JOINER (0x2060)"_ns;
+ case 0x2066:
+ return "LEFT-TO-RIGHT ISOLATE (0x2066)"_ns;
+ case 0x2067:
+ return "RIGHT-TO-LEFT ISOLATE (0x2067)"_ns;
+ case 0x3000:
+ return "IDEOGRAPHIC SPACE (0x3000)"_ns;
+ case 0xFEFF:
+ return "ZERO WIDTH NO-BREAK SPACE (0xFEFF)"_ns;
+ default: {
+ if (aCharCode < ' ' || (aCharCode >= 0x80 && aCharCode < 0xA0)) {
+ return nsPrintfCString("control (0x%04X)", aCharCode);
+ }
+ if (NS_IS_HIGH_SURROGATE(aCharCode)) {
+ return nsPrintfCString("high surrogate (0x%04X)", aCharCode);
+ }
+ if (NS_IS_LOW_SURROGATE(aCharCode)) {
+ return nsPrintfCString("low surrogate (0x%04X)", aCharCode);
+ }
+ return IS_IN_BMP(aCharCode)
+ ? nsPrintfCString(
+ "'%s' (0x%04X)",
+ NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(),
+ aCharCode)
+ : nsPrintfCString(
+ "'%s' (0x%08X)",
+ NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(),
+ aCharCode);
+ }
+ }
+}
+
+static const nsCString GetKeyLocationName(uint32_t aLocation) {
+ switch (aLocation) {
+ case eKeyLocationLeft:
+ return "KEY_LOCATION_LEFT"_ns;
+ case eKeyLocationRight:
+ return "KEY_LOCATION_RIGHT"_ns;
+ case eKeyLocationStandard:
+ return "KEY_LOCATION_STANDARD"_ns;
+ case eKeyLocationNumpad:
+ return "KEY_LOCATION_NUMPAD"_ns;
+ default:
+ return nsPrintfCString("Unknown (0x%04X)", aLocation);
+ }
+}
+
+static const nsCString GetCharacterCodeNames(const char16_t* aChars,
+ uint32_t aLength) {
+ if (!aLength) {
+ return ""_ns;
+ }
+ nsCString result;
+ result.AssignLiteral("\"");
+ StringJoinAppend(result, ", "_ns, Span{aChars, aLength},
+ [](nsACString& dest, const char16_t charValue) {
+ dest.Append(GetCharacterCodeName(charValue));
+ });
+ result.AppendLiteral("\"");
+ return result;
+}
+
+static const nsCString GetCharacterCodeNames(
+ const UniCharsAndModifiers& aUniCharsAndModifiers) {
+ if (aUniCharsAndModifiers.IsEmpty()) {
+ return ""_ns;
+ }
+ nsCString result;
+ result.AssignLiteral("\"");
+ StringJoinAppend(result, ", "_ns, Span{aUniCharsAndModifiers.ToString()},
+ [](nsACString& dest, const char16_t charValue) {
+ dest.Append(GetCharacterCodeName(charValue));
+ });
+ result.AppendLiteral("\"");
+ return result;
+}
+
+class MOZ_STACK_CLASS GetShiftStateName final : public nsAutoCString {
+ public:
+ explicit GetShiftStateName(VirtualKey::ShiftState aShiftState) {
+ if (!aShiftState) {
+ AssignLiteral("none");
+ return;
+ }
+ if (aShiftState & VirtualKey::STATE_SHIFT) {
+ AssignLiteral("Shift");
+ aShiftState &= ~VirtualKey::STATE_SHIFT;
+ }
+ if (aShiftState & VirtualKey::STATE_CONTROL) {
+ MaybeAppendSeparator();
+ AssignLiteral("Ctrl");
+ aShiftState &= ~VirtualKey::STATE_CONTROL;
+ }
+ if (aShiftState & VirtualKey::STATE_ALT) {
+ MaybeAppendSeparator();
+ AssignLiteral("Alt");
+ aShiftState &= ~VirtualKey::STATE_ALT;
+ }
+ if (aShiftState & VirtualKey::STATE_CAPSLOCK) {
+ MaybeAppendSeparator();
+ AssignLiteral("CapsLock");
+ aShiftState &= ~VirtualKey::STATE_CAPSLOCK;
+ }
+ MOZ_ASSERT(!aShiftState);
+ }
+
+ private:
+ void MaybeAppendSeparator() {
+ if (!IsEmpty()) {
+ AppendLiteral(" | ");
+ }
+ }
+};
+
+static const nsCString GetMessageName(UINT aMessage) {
+ switch (aMessage) {
+ case WM_NULL:
+ return "WM_NULL"_ns;
+ case WM_KEYDOWN:
+ return "WM_KEYDOWN"_ns;
+ case WM_KEYUP:
+ return "WM_KEYUP"_ns;
+ case WM_SYSKEYDOWN:
+ return "WM_SYSKEYDOWN"_ns;
+ case WM_SYSKEYUP:
+ return "WM_SYSKEYUP"_ns;
+ case WM_CHAR:
+ return "WM_CHAR"_ns;
+ case WM_UNICHAR:
+ return "WM_UNICHAR"_ns;
+ case WM_SYSCHAR:
+ return "WM_SYSCHAR"_ns;
+ case WM_DEADCHAR:
+ return "WM_DEADCHAR"_ns;
+ case WM_SYSDEADCHAR:
+ return "WM_SYSDEADCHAR"_ns;
+ case MOZ_WM_KEYDOWN:
+ return "MOZ_WM_KEYDOWN"_ns;
+ case MOZ_WM_KEYUP:
+ return "MOZ_WM_KEYUP"_ns;
+ case WM_APPCOMMAND:
+ return "WM_APPCOMMAND"_ns;
+ case WM_QUIT:
+ return "WM_QUIT"_ns;
+ default:
+ return nsPrintfCString("Unknown Message (0x%04X)", aMessage);
+ }
+}
+
+static const nsCString GetVirtualKeyCodeName(WPARAM aVK) {
+ if (aVK >= ArrayLength(kVirtualKeyName)) {
+ return nsPrintfCString("Invalid (0x%08X)", aVK);
+ }
+ return nsCString(kVirtualKeyName[aVK]);
+}
+
+static const nsCString GetAppCommandName(WPARAM aCommand) {
+ switch (aCommand) {
+ case APPCOMMAND_BASS_BOOST:
+ return "APPCOMMAND_BASS_BOOST"_ns;
+ case APPCOMMAND_BASS_DOWN:
+ return "APPCOMMAND_BASS_DOWN"_ns;
+ case APPCOMMAND_BASS_UP:
+ return "APPCOMMAND_BASS_UP"_ns;
+ case APPCOMMAND_BROWSER_BACKWARD:
+ return "APPCOMMAND_BROWSER_BACKWARD"_ns;
+ case APPCOMMAND_BROWSER_FAVORITES:
+ return "APPCOMMAND_BROWSER_FAVORITES"_ns;
+ case APPCOMMAND_BROWSER_FORWARD:
+ return "APPCOMMAND_BROWSER_FORWARD"_ns;
+ case APPCOMMAND_BROWSER_HOME:
+ return "APPCOMMAND_BROWSER_HOME"_ns;
+ case APPCOMMAND_BROWSER_REFRESH:
+ return "APPCOMMAND_BROWSER_REFRESH"_ns;
+ case APPCOMMAND_BROWSER_SEARCH:
+ return "APPCOMMAND_BROWSER_SEARCH"_ns;
+ case APPCOMMAND_BROWSER_STOP:
+ return "APPCOMMAND_BROWSER_STOP"_ns;
+ case APPCOMMAND_CLOSE:
+ return "APPCOMMAND_CLOSE"_ns;
+ case APPCOMMAND_COPY:
+ return "APPCOMMAND_COPY"_ns;
+ case APPCOMMAND_CORRECTION_LIST:
+ return "APPCOMMAND_CORRECTION_LIST"_ns;
+ case APPCOMMAND_CUT:
+ return "APPCOMMAND_CUT"_ns;
+ case APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE:
+ return "APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE"_ns;
+ case APPCOMMAND_FIND:
+ return "APPCOMMAND_FIND"_ns;
+ case APPCOMMAND_FORWARD_MAIL:
+ return "APPCOMMAND_FORWARD_MAIL"_ns;
+ case APPCOMMAND_HELP:
+ return "APPCOMMAND_HELP"_ns;
+ case APPCOMMAND_LAUNCH_APP1:
+ return "APPCOMMAND_LAUNCH_APP1"_ns;
+ case APPCOMMAND_LAUNCH_APP2:
+ return "APPCOMMAND_LAUNCH_APP2"_ns;
+ case APPCOMMAND_LAUNCH_MAIL:
+ return "APPCOMMAND_LAUNCH_MAIL"_ns;
+ case APPCOMMAND_LAUNCH_MEDIA_SELECT:
+ return "APPCOMMAND_LAUNCH_MEDIA_SELECT"_ns;
+ case APPCOMMAND_MEDIA_CHANNEL_DOWN:
+ return "APPCOMMAND_MEDIA_CHANNEL_DOWN"_ns;
+ case APPCOMMAND_MEDIA_CHANNEL_UP:
+ return "APPCOMMAND_MEDIA_CHANNEL_UP"_ns;
+ case APPCOMMAND_MEDIA_FAST_FORWARD:
+ return "APPCOMMAND_MEDIA_FAST_FORWARD"_ns;
+ case APPCOMMAND_MEDIA_NEXTTRACK:
+ return "APPCOMMAND_MEDIA_NEXTTRACK"_ns;
+ case APPCOMMAND_MEDIA_PAUSE:
+ return "APPCOMMAND_MEDIA_PAUSE"_ns;
+ case APPCOMMAND_MEDIA_PLAY:
+ return "APPCOMMAND_MEDIA_PLAY"_ns;
+ case APPCOMMAND_MEDIA_PLAY_PAUSE:
+ return "APPCOMMAND_MEDIA_PLAY_PAUSE"_ns;
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK:
+ return "APPCOMMAND_MEDIA_PREVIOUSTRACK"_ns;
+ case APPCOMMAND_MEDIA_RECORD:
+ return "APPCOMMAND_MEDIA_RECORD"_ns;
+ case APPCOMMAND_MEDIA_REWIND:
+ return "APPCOMMAND_MEDIA_REWIND"_ns;
+ case APPCOMMAND_MEDIA_STOP:
+ return "APPCOMMAND_MEDIA_STOP"_ns;
+ case APPCOMMAND_MIC_ON_OFF_TOGGLE:
+ return "APPCOMMAND_MIC_ON_OFF_TOGGLE"_ns;
+ case APPCOMMAND_MICROPHONE_VOLUME_DOWN:
+ return "APPCOMMAND_MICROPHONE_VOLUME_DOWN"_ns;
+ case APPCOMMAND_MICROPHONE_VOLUME_MUTE:
+ return "APPCOMMAND_MICROPHONE_VOLUME_MUTE"_ns;
+ case APPCOMMAND_MICROPHONE_VOLUME_UP:
+ return "APPCOMMAND_MICROPHONE_VOLUME_UP"_ns;
+ case APPCOMMAND_NEW:
+ return "APPCOMMAND_NEW"_ns;
+ case APPCOMMAND_OPEN:
+ return "APPCOMMAND_OPEN"_ns;
+ case APPCOMMAND_PASTE:
+ return "APPCOMMAND_PASTE"_ns;
+ case APPCOMMAND_PRINT:
+ return "APPCOMMAND_PRINT"_ns;
+ case APPCOMMAND_REDO:
+ return "APPCOMMAND_REDO"_ns;
+ case APPCOMMAND_REPLY_TO_MAIL:
+ return "APPCOMMAND_REPLY_TO_MAIL"_ns;
+ case APPCOMMAND_SAVE:
+ return "APPCOMMAND_SAVE"_ns;
+ case APPCOMMAND_SEND_MAIL:
+ return "APPCOMMAND_SEND_MAIL"_ns;
+ case APPCOMMAND_SPELL_CHECK:
+ return "APPCOMMAND_SPELL_CHECK"_ns;
+ case APPCOMMAND_TREBLE_DOWN:
+ return "APPCOMMAND_TREBLE_DOWN"_ns;
+ case APPCOMMAND_TREBLE_UP:
+ return "APPCOMMAND_TREBLE_UP"_ns;
+ case APPCOMMAND_UNDO:
+ return "APPCOMMAND_UNDO"_ns;
+ case APPCOMMAND_VOLUME_DOWN:
+ return "APPCOMMAND_VOLUME_DOWN"_ns;
+ case APPCOMMAND_VOLUME_MUTE:
+ return "APPCOMMAND_VOLUME_MUTE"_ns;
+ case APPCOMMAND_VOLUME_UP:
+ return "APPCOMMAND_VOLUME_UP"_ns;
+ default:
+ return nsPrintfCString("Unknown app command (0x%08X)", aCommand);
+ }
+}
+
+static const nsCString GetAppCommandDeviceName(LPARAM aDevice) {
+ switch (aDevice) {
+ case FAPPCOMMAND_KEY:
+ return "FAPPCOMMAND_KEY"_ns;
+ case FAPPCOMMAND_MOUSE:
+ return "FAPPCOMMAND_MOUSE"_ns;
+ case FAPPCOMMAND_OEM:
+ return "FAPPCOMMAND_OEM"_ns;
+ default:
+ return nsPrintfCString("Unknown app command device (0x%04X)", aDevice);
+ }
+};
+
+class MOZ_STACK_CLASS GetAppCommandKeysName final : public nsAutoCString {
+ public:
+ explicit GetAppCommandKeysName(WPARAM aKeys) {
+ if (aKeys & MK_CONTROL) {
+ AppendLiteral("MK_CONTROL");
+ aKeys &= ~MK_CONTROL;
+ }
+ if (aKeys & MK_LBUTTON) {
+ MaybeAppendSeparator();
+ AppendLiteral("MK_LBUTTON");
+ aKeys &= ~MK_LBUTTON;
+ }
+ if (aKeys & MK_MBUTTON) {
+ MaybeAppendSeparator();
+ AppendLiteral("MK_MBUTTON");
+ aKeys &= ~MK_MBUTTON;
+ }
+ if (aKeys & MK_RBUTTON) {
+ MaybeAppendSeparator();
+ AppendLiteral("MK_RBUTTON");
+ aKeys &= ~MK_RBUTTON;
+ }
+ if (aKeys & MK_SHIFT) {
+ MaybeAppendSeparator();
+ AppendLiteral("MK_SHIFT");
+ aKeys &= ~MK_SHIFT;
+ }
+ if (aKeys & MK_XBUTTON1) {
+ MaybeAppendSeparator();
+ AppendLiteral("MK_XBUTTON1");
+ aKeys &= ~MK_XBUTTON1;
+ }
+ if (aKeys & MK_XBUTTON2) {
+ MaybeAppendSeparator();
+ AppendLiteral("MK_XBUTTON2");
+ aKeys &= ~MK_XBUTTON2;
+ }
+ if (aKeys) {
+ MaybeAppendSeparator();
+ AppendPrintf("Unknown Flags (0x%04X)", aKeys);
+ }
+ if (IsEmpty()) {
+ AssignLiteral("none (0x0000)");
+ }
+ }
+
+ private:
+ void MaybeAppendSeparator() {
+ if (!IsEmpty()) {
+ AppendLiteral(" | ");
+ }
+ }
+};
+
+static const nsCString ToString(const MSG& aMSG) {
+ nsCString result;
+ result.AssignLiteral("{ message=");
+ result.Append(GetMessageName(aMSG.message).get());
+ result.AppendLiteral(", ");
+ switch (aMSG.message) {
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ case WM_SYSKEYDOWN:
+ case WM_SYSKEYUP:
+ case MOZ_WM_KEYDOWN:
+ case MOZ_WM_KEYUP:
+ result.AppendPrintf(
+ "virtual keycode=%s, repeat count=%d, "
+ "scancode=0x%02X, extended key=%s, "
+ "context code=%s, previous key state=%s, "
+ "transition state=%s",
+ GetVirtualKeyCodeName(aMSG.wParam).get(), aMSG.lParam & 0xFFFF,
+ WinUtils::GetScanCode(aMSG.lParam),
+ GetBoolName(WinUtils::IsExtendedScanCode(aMSG.lParam)),
+ GetBoolName((aMSG.lParam & (1 << 29)) != 0),
+ GetBoolName((aMSG.lParam & (1 << 30)) != 0),
+ GetBoolName((aMSG.lParam & (1 << 31)) != 0));
+ break;
+ case WM_CHAR:
+ case WM_DEADCHAR:
+ case WM_SYSCHAR:
+ case WM_SYSDEADCHAR:
+ result.AppendPrintf(
+ "character code=%s, repeat count=%d, "
+ "scancode=0x%02X, extended key=%s, "
+ "context code=%s, previous key state=%s, "
+ "transition state=%s",
+ GetCharacterCodeName(aMSG.wParam).get(), aMSG.lParam & 0xFFFF,
+ WinUtils::GetScanCode(aMSG.lParam),
+ GetBoolName(WinUtils::IsExtendedScanCode(aMSG.lParam)),
+ GetBoolName((aMSG.lParam & (1 << 29)) != 0),
+ GetBoolName((aMSG.lParam & (1 << 30)) != 0),
+ GetBoolName((aMSG.lParam & (1 << 31)) != 0));
+ break;
+ case WM_APPCOMMAND:
+ result.AppendPrintf(
+ "window handle=0x%p, app command=%s, device=%s, dwKeys=%s",
+ aMSG.wParam,
+ GetAppCommandName(GET_APPCOMMAND_LPARAM(aMSG.lParam)).get(),
+ GetAppCommandDeviceName(GET_DEVICE_LPARAM(aMSG.lParam)).get(),
+ GetAppCommandKeysName(GET_KEYSTATE_LPARAM(aMSG.lParam)).get());
+ break;
+ default:
+ result.AppendPrintf("wParam=%u, lParam=%u", aMSG.wParam, aMSG.lParam);
+ break;
+ }
+ result.AppendPrintf(", hwnd=0x%p", aMSG.hwnd);
+ return result;
+}
+
+static const nsCString ToString(
+ const UniCharsAndModifiers& aUniCharsAndModifiers) {
+ if (aUniCharsAndModifiers.IsEmpty()) {
+ return "{}"_ns;
+ }
+ nsCString result;
+ result.AssignLiteral("{ ");
+ result.Append(GetCharacterCodeName(aUniCharsAndModifiers.CharAt(0)));
+ for (size_t i = 1; i < aUniCharsAndModifiers.Length(); ++i) {
+ if (aUniCharsAndModifiers.ModifiersAt(i - 1) !=
+ aUniCharsAndModifiers.ModifiersAt(i)) {
+ result.AppendLiteral(" [");
+ result.Append(GetModifiersName(aUniCharsAndModifiers.ModifiersAt(0)));
+ result.AppendLiteral("]");
+ }
+ result.AppendLiteral(", ");
+ result.Append(GetCharacterCodeName(aUniCharsAndModifiers.CharAt(i)));
+ }
+ result.AppendLiteral(" [");
+ uint32_t lastIndex = aUniCharsAndModifiers.Length() - 1;
+ result.Append(GetModifiersName(aUniCharsAndModifiers.ModifiersAt(lastIndex)));
+ result.AppendLiteral("] }");
+ return result;
+}
+
+const nsCString ToString(const ModifierKeyState& aModifierKeyState) {
+ nsCString result;
+ result.AssignLiteral("{ ");
+ result.Append(GetModifiersName(aModifierKeyState.GetModifiers()).get());
+ result.AppendLiteral(" }");
+ return result;
+}
+
+// Unique id counter associated with a keydown / keypress events. Used in
+// identifing keypress events for removal from async event dispatch queue
+// in metrofx after preventDefault is called on keydown events.
+static uint32_t sUniqueKeyEventId = 0;
+
+/*****************************************************************************
+ * mozilla::widget::ModifierKeyState
+ *****************************************************************************/
+
+ModifierKeyState::ModifierKeyState() { Update(); }
+
+ModifierKeyState::ModifierKeyState(Modifiers aModifiers)
+ : mModifiers(aModifiers) {
+ MOZ_ASSERT(!(mModifiers & MODIFIER_ALTGRAPH) || (!IsControl() && !IsAlt()),
+ "Neither MODIFIER_CONTROL nor MODIFIER_ALT should be set "
+ "if MODIFIER_ALTGRAPH is set");
+}
+
+void ModifierKeyState::Update() {
+ mModifiers = 0;
+ if (IS_VK_DOWN(VK_SHIFT)) {
+ mModifiers |= MODIFIER_SHIFT;
+ }
+ // If AltGr key (i.e., VK_RMENU on some keyboard layout) is pressed, only
+ // MODIFIER_ALTGRAPH should be set. Otherwise, i.e., if both Ctrl and Alt
+ // keys are pressed to emulate AltGr key, MODIFIER_CONTROL and MODIFIER_ALT
+ // keys should be set separately.
+ if (KeyboardLayout::GetInstance()->HasAltGr() && IS_VK_DOWN(VK_RMENU)) {
+ mModifiers |= MODIFIER_ALTGRAPH;
+ } else {
+ if (IS_VK_DOWN(VK_CONTROL)) {
+ mModifiers |= MODIFIER_CONTROL;
+ }
+ if (IS_VK_DOWN(VK_MENU)) {
+ mModifiers |= MODIFIER_ALT;
+ }
+ }
+ if (IS_VK_DOWN(VK_LWIN) || IS_VK_DOWN(VK_RWIN)) {
+ mModifiers |= MODIFIER_OS;
+ }
+ if (::GetKeyState(VK_CAPITAL) & 1) {
+ mModifiers |= MODIFIER_CAPSLOCK;
+ }
+ if (::GetKeyState(VK_NUMLOCK) & 1) {
+ mModifiers |= MODIFIER_NUMLOCK;
+ }
+ if (::GetKeyState(VK_SCROLL) & 1) {
+ mModifiers |= MODIFIER_SCROLLLOCK;
+ }
+}
+
+void ModifierKeyState::Unset(Modifiers aRemovingModifiers) {
+ mModifiers &= ~aRemovingModifiers;
+}
+
+void ModifierKeyState::Set(Modifiers aAddingModifiers) {
+ mModifiers |= aAddingModifiers;
+ MOZ_ASSERT(!(mModifiers & MODIFIER_ALTGRAPH) || (!IsControl() && !IsAlt()),
+ "Neither MODIFIER_CONTROL nor MODIFIER_ALT should be set "
+ "if MODIFIER_ALTGRAPH is set");
+}
+
+void ModifierKeyState::InitInputEvent(WidgetInputEvent& aInputEvent) const {
+ aInputEvent.mModifiers = mModifiers;
+
+ switch (aInputEvent.mClass) {
+ case eMouseEventClass:
+ case eMouseScrollEventClass:
+ case eWheelEventClass:
+ case eDragEventClass:
+ case eSimpleGestureEventClass:
+ InitMouseEvent(aInputEvent);
+ break;
+ default:
+ break;
+ }
+}
+
+void ModifierKeyState::InitMouseEvent(WidgetInputEvent& aMouseEvent) const {
+ NS_ASSERTION(aMouseEvent.mClass == eMouseEventClass ||
+ aMouseEvent.mClass == eWheelEventClass ||
+ aMouseEvent.mClass == eDragEventClass ||
+ aMouseEvent.mClass == eSimpleGestureEventClass,
+ "called with non-mouse event");
+
+ WidgetMouseEventBase& mouseEvent = *aMouseEvent.AsMouseEventBase();
+ mouseEvent.mButtons = 0;
+ if (::GetKeyState(VK_LBUTTON) < 0) {
+ mouseEvent.mButtons |= MouseButtonsFlag::ePrimaryFlag;
+ }
+ if (::GetKeyState(VK_RBUTTON) < 0) {
+ mouseEvent.mButtons |= MouseButtonsFlag::eSecondaryFlag;
+ }
+ if (::GetKeyState(VK_MBUTTON) < 0) {
+ mouseEvent.mButtons |= MouseButtonsFlag::eMiddleFlag;
+ }
+ if (::GetKeyState(VK_XBUTTON1) < 0) {
+ mouseEvent.mButtons |= MouseButtonsFlag::e4thFlag;
+ }
+ if (::GetKeyState(VK_XBUTTON2) < 0) {
+ mouseEvent.mButtons |= MouseButtonsFlag::e5thFlag;
+ }
+}
+
+bool ModifierKeyState::IsShift() const {
+ return (mModifiers & MODIFIER_SHIFT) != 0;
+}
+
+bool ModifierKeyState::IsControl() const {
+ return (mModifiers & MODIFIER_CONTROL) != 0;
+}
+
+bool ModifierKeyState::IsAlt() const {
+ return (mModifiers & MODIFIER_ALT) != 0;
+}
+
+bool ModifierKeyState::IsWin() const { return (mModifiers & MODIFIER_OS) != 0; }
+
+bool ModifierKeyState::MaybeMatchShortcutKey() const {
+ // If Windows key is pressed, even if both Ctrl key and Alt key are pressed,
+ // it's possible to match a shortcut key.
+ if (IsWin()) {
+ return true;
+ }
+ // Otherwise, when both Ctrl key and Alt key are pressed, it shouldn't be
+ // a shortcut key for Windows since it means pressing AltGr key on
+ // some keyboard layouts.
+ if (IsControl() ^ IsAlt()) {
+ return true;
+ }
+ // If no modifier key is active except a lockable modifier nor Shift key,
+ // the key shouldn't match any shortcut keys (there are Space and
+ // Shift+Space, though, let's ignore these special case...).
+ return false;
+}
+
+bool ModifierKeyState::IsCapsLocked() const {
+ return (mModifiers & MODIFIER_CAPSLOCK) != 0;
+}
+
+bool ModifierKeyState::IsNumLocked() const {
+ return (mModifiers & MODIFIER_NUMLOCK) != 0;
+}
+
+bool ModifierKeyState::IsScrollLocked() const {
+ return (mModifiers & MODIFIER_SCROLLLOCK) != 0;
+}
+
+/*****************************************************************************
+ * mozilla::widget::UniCharsAndModifiers
+ *****************************************************************************/
+
+void UniCharsAndModifiers::Append(char16_t aUniChar, Modifiers aModifiers) {
+ mChars.Append(aUniChar);
+ mModifiers.AppendElement(aModifiers);
+}
+
+void UniCharsAndModifiers::FillModifiers(Modifiers aModifiers) {
+ for (size_t i = 0; i < Length(); i++) {
+ mModifiers[i] = aModifiers;
+ }
+}
+
+void UniCharsAndModifiers::OverwriteModifiersIfBeginsWith(
+ const UniCharsAndModifiers& aOther) {
+ if (!BeginsWith(aOther)) {
+ return;
+ }
+ for (size_t i = 0; i < aOther.Length(); ++i) {
+ mModifiers[i] = aOther.mModifiers[i];
+ }
+}
+
+bool UniCharsAndModifiers::UniCharsEqual(
+ const UniCharsAndModifiers& aOther) const {
+ return mChars.Equals(aOther.mChars);
+}
+
+bool UniCharsAndModifiers::UniCharsCaseInsensitiveEqual(
+ const UniCharsAndModifiers& aOther) const {
+ return mChars.Equals(aOther.mChars, nsCaseInsensitiveStringComparator);
+}
+
+bool UniCharsAndModifiers::BeginsWith(
+ const UniCharsAndModifiers& aOther) const {
+ return StringBeginsWith(mChars, aOther.mChars);
+}
+
+UniCharsAndModifiers& UniCharsAndModifiers::operator+=(
+ const UniCharsAndModifiers& aOther) {
+ mChars.Append(aOther.mChars);
+ mModifiers.AppendElements(aOther.mModifiers);
+ return *this;
+}
+
+UniCharsAndModifiers UniCharsAndModifiers::operator+(
+ const UniCharsAndModifiers& aOther) const {
+ UniCharsAndModifiers result(*this);
+ result += aOther;
+ return result;
+}
+
+/*****************************************************************************
+ * mozilla::widget::VirtualKey
+ *****************************************************************************/
+
+// static
+VirtualKey::ShiftState VirtualKey::ModifiersToShiftState(Modifiers aModifiers) {
+ ShiftState state = 0;
+ if (aModifiers & MODIFIER_SHIFT) {
+ state |= STATE_SHIFT;
+ }
+ if (aModifiers & MODIFIER_ALTGRAPH) {
+ state |= STATE_ALTGRAPH;
+ } else {
+ if (aModifiers & MODIFIER_CONTROL) {
+ state |= STATE_CONTROL;
+ }
+ if (aModifiers & MODIFIER_ALT) {
+ state |= STATE_ALT;
+ }
+ }
+ if (aModifiers & MODIFIER_CAPSLOCK) {
+ state |= STATE_CAPSLOCK;
+ }
+ return state;
+}
+
+// static
+Modifiers VirtualKey::ShiftStateToModifiers(ShiftState aShiftState) {
+ Modifiers modifiers = 0;
+ if (aShiftState & STATE_SHIFT) {
+ modifiers |= MODIFIER_SHIFT;
+ }
+ if (aShiftState & STATE_ALTGRAPH) {
+ modifiers |= MODIFIER_ALTGRAPH;
+ } else {
+ if (aShiftState & STATE_CONTROL) {
+ modifiers |= MODIFIER_CONTROL;
+ }
+ if (aShiftState & STATE_ALT) {
+ modifiers |= MODIFIER_ALT;
+ }
+ }
+ if (aShiftState & STATE_CAPSLOCK) {
+ modifiers |= MODIFIER_CAPSLOCK;
+ }
+ return modifiers;
+}
+
+const DeadKeyTable* VirtualKey::MatchingDeadKeyTable(
+ const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) const {
+ if (!mIsDeadKey) {
+ return nullptr;
+ }
+
+ for (ShiftState shiftState = 0; shiftState < 16; shiftState++) {
+ if (!IsDeadKey(shiftState)) {
+ continue;
+ }
+ const DeadKeyTable* dkt = mShiftStates[shiftState].DeadKey.Table;
+ if (dkt && dkt->IsEqual(aDeadKeyArray, aEntries)) {
+ return dkt;
+ }
+ }
+
+ return nullptr;
+}
+
+void VirtualKey::SetNormalChars(ShiftState aShiftState, const char16_t* aChars,
+ uint32_t aNumOfChars) {
+ MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState));
+
+ SetDeadKey(aShiftState, false);
+
+ for (uint32_t index = 0; index < aNumOfChars; index++) {
+ // Ignore legacy non-printable control characters
+ mShiftStates[aShiftState].Normal.Chars[index] =
+ (aChars[index] >= 0x20) ? aChars[index] : 0;
+ }
+
+ uint32_t len = ArrayLength(mShiftStates[aShiftState].Normal.Chars);
+ for (uint32_t index = aNumOfChars; index < len; index++) {
+ mShiftStates[aShiftState].Normal.Chars[index] = 0;
+ }
+}
+
+void VirtualKey::SetDeadChar(ShiftState aShiftState, char16_t aDeadChar) {
+ MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState));
+
+ SetDeadKey(aShiftState, true);
+
+ mShiftStates[aShiftState].DeadKey.DeadChar = aDeadChar;
+ mShiftStates[aShiftState].DeadKey.Table = nullptr;
+}
+
+UniCharsAndModifiers VirtualKey::GetUniChars(ShiftState aShiftState) const {
+ UniCharsAndModifiers result = GetNativeUniChars(aShiftState);
+
+ const uint8_t kShiftStateIndex = ToShiftStateIndex(aShiftState);
+ if (!(kShiftStateIndex & STATE_CONTROL_ALT)) {
+ // If neither Alt nor Ctrl key is pressed, just return stored data
+ // for the key.
+ return result;
+ }
+
+ if (result.IsEmpty()) {
+ // If Alt and/or Control are pressed and the key produces no
+ // character, return characters which is produced by the key without
+ // Alt and Control, and return given modifiers as is.
+ result = GetNativeUniChars(kShiftStateIndex & ~STATE_CONTROL_ALT);
+ result.FillModifiers(ShiftStateToModifiers(aShiftState));
+ return result;
+ }
+
+ if (IsAltGrIndex(kShiftStateIndex)) {
+ // If AltGr or both Ctrl and Alt are pressed and the key produces
+ // character(s), we need to clear MODIFIER_ALT and MODIFIER_CONTROL
+ // since TextEditor won't handle eKeyPress event whose mModifiers
+ // has MODIFIER_ALT or MODIFIER_CONTROL. Additionally, we need to
+ // use MODIFIER_ALTGRAPH when a key produces character(s) with
+ // AltGr or both Ctrl and Alt on Windows. See following spec issue:
+ // <https://github.com/w3c/uievents/issues/147>
+ Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
+ finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ finalModifiers |= MODIFIER_ALTGRAPH;
+ result.FillModifiers(finalModifiers);
+ return result;
+ }
+
+ // Otherwise, i.e., Alt or Ctrl is pressed and it produces character(s),
+ // check if different character(s) is produced by the key without Alt/Ctrl.
+ // If it produces different character, we need to consume the Alt and
+ // Ctrl modifier for TextEditor. Otherwise, the key does not produces the
+ // character actually. So, keep setting Alt and Ctrl modifiers.
+ UniCharsAndModifiers unmodifiedReslt =
+ GetNativeUniChars(kShiftStateIndex & ~STATE_CONTROL_ALT);
+ if (!result.UniCharsEqual(unmodifiedReslt)) {
+ Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
+ finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ result.FillModifiers(finalModifiers);
+ }
+ return result;
+}
+
+UniCharsAndModifiers VirtualKey::GetNativeUniChars(
+ ShiftState aShiftState) const {
+ const uint8_t kShiftStateIndex = ToShiftStateIndex(aShiftState);
+ UniCharsAndModifiers result;
+ Modifiers modifiers = ShiftStateToModifiers(aShiftState);
+ if (IsDeadKey(aShiftState)) {
+ result.Append(mShiftStates[kShiftStateIndex].DeadKey.DeadChar, modifiers);
+ return result;
+ }
+
+ uint32_t len = ArrayLength(mShiftStates[kShiftStateIndex].Normal.Chars);
+ for (uint32_t i = 0;
+ i < len && mShiftStates[kShiftStateIndex].Normal.Chars[i]; i++) {
+ result.Append(mShiftStates[kShiftStateIndex].Normal.Chars[i], modifiers);
+ }
+ return result;
+}
+
+// static
+void VirtualKey::FillKbdState(PBYTE aKbdState, const ShiftState aShiftState) {
+ if (aShiftState & STATE_SHIFT) {
+ aKbdState[VK_SHIFT] |= 0x80;
+ } else {
+ aKbdState[VK_SHIFT] &= ~0x80;
+ aKbdState[VK_LSHIFT] &= ~0x80;
+ aKbdState[VK_RSHIFT] &= ~0x80;
+ }
+
+ if (aShiftState & STATE_ALTGRAPH) {
+ aKbdState[VK_CONTROL] |= 0x80;
+ aKbdState[VK_LCONTROL] |= 0x80;
+ aKbdState[VK_RCONTROL] &= ~0x80;
+ aKbdState[VK_MENU] |= 0x80;
+ aKbdState[VK_LMENU] &= ~0x80;
+ aKbdState[VK_RMENU] |= 0x80;
+ } else {
+ if (aShiftState & STATE_CONTROL) {
+ aKbdState[VK_CONTROL] |= 0x80;
+ } else {
+ aKbdState[VK_CONTROL] &= ~0x80;
+ aKbdState[VK_LCONTROL] &= ~0x80;
+ aKbdState[VK_RCONTROL] &= ~0x80;
+ }
+
+ if (aShiftState & STATE_ALT) {
+ aKbdState[VK_MENU] |= 0x80;
+ } else {
+ aKbdState[VK_MENU] &= ~0x80;
+ aKbdState[VK_LMENU] &= ~0x80;
+ aKbdState[VK_RMENU] &= ~0x80;
+ }
+ }
+
+ if (aShiftState & STATE_CAPSLOCK) {
+ aKbdState[VK_CAPITAL] |= 0x01;
+ } else {
+ aKbdState[VK_CAPITAL] &= ~0x01;
+ }
+}
+
+/*****************************************************************************
+ * mozilla::widget::NativeKey
+ *****************************************************************************/
+
+uint8_t NativeKey::sDispatchedKeyOfAppCommand = 0;
+NativeKey* NativeKey::sLatestInstance = nullptr;
+const MSG NativeKey::sEmptyMSG = {};
+MSG NativeKey::sLastKeyOrCharMSG = {};
+MSG NativeKey::sLastKeyMSG = {};
+
+LazyLogModule sNativeKeyLogger("NativeKeyWidgets");
+
+NativeKey::NativeKey(nsWindowBase* aWidget, const MSG& aMessage,
+ const ModifierKeyState& aModKeyState,
+ HKL aOverrideKeyboardLayout,
+ nsTArray<FakeCharMsg>* aFakeCharMsgs)
+ : mLastInstance(sLatestInstance),
+ mRemovingMsg(sEmptyMSG),
+ mReceivedMsg(sEmptyMSG),
+ mWidget(aWidget),
+ mDispatcher(aWidget->GetTextEventDispatcher()),
+ mMsg(aMessage),
+ mFocusedWndBeforeDispatch(::GetFocus()),
+ mDOMKeyCode(0),
+ mKeyNameIndex(KEY_NAME_INDEX_Unidentified),
+ mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN),
+ mModKeyState(aModKeyState),
+ mVirtualKeyCode(0),
+ mOriginalVirtualKeyCode(0),
+ mShiftedLatinChar(0),
+ mUnshiftedLatinChar(0),
+ mScanCode(0),
+ mIsExtended(false),
+ mIsRepeat(false),
+ mIsDeadKey(false),
+ mIsPrintableKey(false),
+ mIsSkippableInRemoteProcess(false),
+ mCharMessageHasGone(false),
+ mCanIgnoreModifierStateAtKeyPress(true),
+ mFakeCharMsgs(aFakeCharMsgs && aFakeCharMsgs->Length() ? aFakeCharMsgs
+ : nullptr) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::NativeKey(aWidget=0x%p { GetWindowHandle()=0x%p }, "
+ "aMessage=%s, aModKeyState=%s), sLatestInstance=0x%p",
+ this, aWidget, aWidget->GetWindowHandle(), ToString(aMessage).get(),
+ ToString(aModKeyState).get(), sLatestInstance));
+
+ MOZ_ASSERT(aWidget);
+ MOZ_ASSERT(mDispatcher);
+ sLatestInstance = this;
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+ mKeyboardLayout = keyboardLayout->GetLayout();
+ if (aOverrideKeyboardLayout && mKeyboardLayout != aOverrideKeyboardLayout) {
+ keyboardLayout->OverrideLayout(aOverrideKeyboardLayout);
+ mKeyboardLayout = keyboardLayout->GetLayout();
+ MOZ_ASSERT(mKeyboardLayout == aOverrideKeyboardLayout);
+ mIsOverridingKeyboardLayout = true;
+ } else {
+ mIsOverridingKeyboardLayout = false;
+ sLastKeyOrCharMSG = aMessage;
+ }
+
+ if (mMsg.message == WM_APPCOMMAND) {
+ InitWithAppCommand();
+ } else {
+ InitWithKeyOrChar();
+ }
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::NativeKey(), mKeyboardLayout=0x%08X, "
+ "mFocusedWndBeforeDispatch=0x%p, mDOMKeyCode=%s, "
+ "mKeyNameIndex=%s, mCodeNameIndex=%s, mModKeyState=%s, "
+ "mVirtualKeyCode=%s, mOriginalVirtualKeyCode=%s, "
+ "mCommittedCharsAndModifiers=%s, mInputtingStringAndModifiers=%s, "
+ "mShiftedString=%s, mUnshiftedString=%s, mShiftedLatinChar=%s, "
+ "mUnshiftedLatinChar=%s, mScanCode=0x%04X, mIsExtended=%s, "
+ "mIsRepeat=%s, mIsDeadKey=%s, mIsPrintableKey=%s, "
+ "mIsSkippableInRemoteProcess=%s, mCharMessageHasGone=%s, "
+ "mIsOverridingKeyboardLayout=%s",
+ this, mKeyboardLayout, mFocusedWndBeforeDispatch,
+ GetDOMKeyCodeName(mDOMKeyCode).get(), ToString(mKeyNameIndex).get(),
+ ToString(mCodeNameIndex).get(), ToString(mModKeyState).get(),
+ GetVirtualKeyCodeName(mVirtualKeyCode).get(),
+ GetVirtualKeyCodeName(mOriginalVirtualKeyCode).get(),
+ ToString(mCommittedCharsAndModifiers).get(),
+ ToString(mInputtingStringAndModifiers).get(),
+ ToString(mShiftedString).get(), ToString(mUnshiftedString).get(),
+ GetCharacterCodeName(mShiftedLatinChar).get(),
+ GetCharacterCodeName(mUnshiftedLatinChar).get(), mScanCode,
+ GetBoolName(mIsExtended), GetBoolName(mIsRepeat),
+ GetBoolName(mIsDeadKey), GetBoolName(mIsPrintableKey),
+ GetBoolName(mIsSkippableInRemoteProcess),
+ GetBoolName(mCharMessageHasGone),
+ GetBoolName(mIsOverridingKeyboardLayout)));
+}
+
+void NativeKey::InitIsSkippableForKeyOrChar(const MSG& aLastKeyMSG) {
+ mIsSkippableInRemoteProcess = false;
+
+ if (!mIsRepeat) {
+ // If the message is not repeated key message, the event should be always
+ // handled in remote process even if it's too old.
+ return;
+ }
+
+ // Keyboard utilities may send us some generated messages and such messages
+ // may be marked as "repeated", e.g., SendInput() calls with
+ // KEYEVENTF_UNICODE but without KEYEVENTF_KEYUP. However, key sequence
+ // comes from such utilities may be really important. For example, utilities
+ // may send WM_KEYDOWN for VK_BACK to remove previous character and send
+ // WM_KEYDOWN for VK_PACKET to insert a composite character. Therefore, we
+ // should check if current message and previous key message are caused by
+ // same physical key. If not, the message may be generated by such
+ // utility.
+ // XXX With this approach, if VK_BACK messages are generated with known
+ // scancode, we cannot distinguish whether coming VK_BACK message is
+ // actually repeated by the auto-repeat feature. Currently, we need
+ // this hack only for "SinhalaTamil IME" and fortunately, it generates
+ // VK_BACK messages with odd scancode. So, we don't need to handle
+ // VK_BACK specially at least for now.
+
+ if (mCodeNameIndex == CODE_NAME_INDEX_UNKNOWN) {
+ // If current event is not caused by physical key operation, it may be
+ // caused by a keyboard utility. If so, the event shouldn't be ignored by
+ // BrowserChild since it want to insert the character, delete a character or
+ // move caret.
+ return;
+ }
+
+ if (mOriginalVirtualKeyCode == VK_PACKET) {
+ // If the message is VK_PACKET, that means that a keyboard utility
+ // tries to insert a character.
+ return;
+ }
+
+ switch (mMsg.message) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ case MOZ_WM_KEYDOWN:
+ case WM_CHAR:
+ case WM_SYSCHAR:
+ case WM_DEADCHAR:
+ case WM_SYSDEADCHAR:
+ // However, some keyboard layouts may send some keyboard messages with
+ // activating the bit. If we dispatch repeated keyboard events, they
+ // may be ignored by BrowserChild due to performance reason. So, we need
+ // to check if actually a physical key is repeated by the auto-repeat
+ // feature.
+ switch (aLastKeyMSG.message) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ case MOZ_WM_KEYDOWN:
+ if (aLastKeyMSG.wParam == VK_PACKET) {
+ // If the last message was VK_PACKET, that means that a keyboard
+ // utility tried to insert a character. So, current message is
+ // not repeated key event of the previous event.
+ return;
+ }
+ // Let's check whether current message and previous message are
+ // caused by same physical key.
+ mIsSkippableInRemoteProcess =
+ mScanCode == WinUtils::GetScanCode(aLastKeyMSG.lParam) &&
+ mIsExtended == WinUtils::IsExtendedScanCode(aLastKeyMSG.lParam);
+ return;
+ default:
+ // If previous message is not a keydown, this must not be generated
+ // by the auto-repeat feature.
+ return;
+ }
+ return;
+ case WM_APPCOMMAND:
+ MOZ_ASSERT_UNREACHABLE(
+ "WM_APPCOMMAND should be handled in "
+ "InitWithAppCommand()");
+ return;
+ default:
+ // keyup message shouldn't be repeated by the auto-repeat feature.
+ return;
+ }
+}
+
+void NativeKey::InitWithKeyOrChar() {
+ MSG lastKeyMSG = sLastKeyMSG;
+ mScanCode = WinUtils::GetScanCode(mMsg.lParam);
+ mIsExtended = WinUtils::IsExtendedScanCode(mMsg.lParam);
+ switch (mMsg.message) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ case WM_KEYUP:
+ case WM_SYSKEYUP:
+ case MOZ_WM_KEYDOWN:
+ case MOZ_WM_KEYUP: {
+ // Modify sLastKeyMSG now since retrieving following char messages may
+ // cause sending another key message if odd tool hooks GetMessage(),
+ // PeekMessage().
+ sLastKeyMSG = mMsg;
+
+ // Note that we don't need to compute raw virtual keycode here even when
+ // it's VK_PROCESS (i.e., already handled by IME) because we need to
+ // export it as DOM_VK_PROCESS and KEY_NAME_INDEX_Process.
+ mOriginalVirtualKeyCode = static_cast<uint8_t>(mMsg.wParam);
+
+ // If the key message is sent from other application like a11y tools, the
+ // scancode value might not be set proper value. Then, probably the value
+ // is 0.
+ // NOTE: If the virtual keycode can be caused by both non-extended key
+ // and extended key, the API returns the non-extended key's
+ // scancode. E.g., VK_LEFT causes "4" key on numpad.
+ if (!mScanCode && mOriginalVirtualKeyCode != VK_PACKET) {
+ uint16_t scanCodeEx = ComputeScanCodeExFromVirtualKeyCode(mMsg.wParam);
+ if (scanCodeEx) {
+ mScanCode = static_cast<uint8_t>(scanCodeEx & 0xFF);
+ uint8_t extended = static_cast<uint8_t>((scanCodeEx & 0xFF00) >> 8);
+ mIsExtended = (extended == 0xE0) || (extended == 0xE1);
+ }
+ }
+
+ // Most keys are not distinguished as left or right keys.
+ bool isLeftRightDistinguishedKey = false;
+
+ // mOriginalVirtualKeyCode must not distinguish left or right of
+ // Shift, Control or Alt.
+ switch (mOriginalVirtualKeyCode) {
+ case VK_SHIFT:
+ case VK_CONTROL:
+ case VK_MENU:
+ isLeftRightDistinguishedKey = true;
+ break;
+ case VK_LSHIFT:
+ case VK_RSHIFT:
+ mVirtualKeyCode = mOriginalVirtualKeyCode;
+ mOriginalVirtualKeyCode = VK_SHIFT;
+ isLeftRightDistinguishedKey = true;
+ break;
+ case VK_LCONTROL:
+ case VK_RCONTROL:
+ mVirtualKeyCode = mOriginalVirtualKeyCode;
+ mOriginalVirtualKeyCode = VK_CONTROL;
+ isLeftRightDistinguishedKey = true;
+ break;
+ case VK_LMENU:
+ case VK_RMENU:
+ mVirtualKeyCode = mOriginalVirtualKeyCode;
+ mOriginalVirtualKeyCode = VK_MENU;
+ isLeftRightDistinguishedKey = true;
+ break;
+ }
+
+ // If virtual keycode (left-right distinguished keycode) is already
+ // computed, we don't need to do anymore.
+ if (mVirtualKeyCode) {
+ break;
+ }
+
+ // If the keycode doesn't have LR distinguished keycode, we just set
+ // mOriginalVirtualKeyCode to mVirtualKeyCode. Note that don't compute
+ // it from MapVirtualKeyEx() because the scan code might be wrong if
+ // the message is sent/posted by other application. Then, we will compute
+ // unexpected keycode from the scan code.
+ if (!isLeftRightDistinguishedKey) {
+ break;
+ }
+
+ NS_ASSERTION(!mVirtualKeyCode,
+ "mVirtualKeyCode has been computed already");
+
+ // Otherwise, compute the virtual keycode with MapVirtualKeyEx().
+ mVirtualKeyCode = ComputeVirtualKeyCodeFromScanCodeEx();
+
+ // Following code shouldn't be used now because we compute scancode value
+ // if we detect that the sender doesn't set proper scancode.
+ // However, the detection might fail. Therefore, let's keep using this.
+ switch (mOriginalVirtualKeyCode) {
+ case VK_CONTROL:
+ if (mVirtualKeyCode != VK_LCONTROL &&
+ mVirtualKeyCode != VK_RCONTROL) {
+ mVirtualKeyCode = mIsExtended ? VK_RCONTROL : VK_LCONTROL;
+ }
+ break;
+ case VK_MENU:
+ if (mVirtualKeyCode != VK_LMENU && mVirtualKeyCode != VK_RMENU) {
+ mVirtualKeyCode = mIsExtended ? VK_RMENU : VK_LMENU;
+ }
+ break;
+ case VK_SHIFT:
+ if (mVirtualKeyCode != VK_LSHIFT && mVirtualKeyCode != VK_RSHIFT) {
+ // Neither left shift nor right shift is an extended key,
+ // let's use VK_LSHIFT for unknown mapping.
+ mVirtualKeyCode = VK_LSHIFT;
+ }
+ break;
+ default:
+ MOZ_CRASH("Unsupported mOriginalVirtualKeyCode");
+ }
+ break;
+ }
+ case WM_CHAR:
+ case WM_UNICHAR:
+ case WM_SYSCHAR:
+ // If there is another instance and it is trying to remove a char message
+ // from the queue, this message should be handled in the old instance.
+ if (IsAnotherInstanceRemovingCharMessage()) {
+ // XXX Do we need to make mReceivedMsg an array?
+ MOZ_ASSERT(IsEmptyMSG(mLastInstance->mReceivedMsg));
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::InitWithKeyOrChar(), WARNING, detecting another "
+ "instance is trying to remove a char message, so, this instance "
+ "should do nothing, mLastInstance=0x%p, mRemovingMsg=%s, "
+ "mReceivedMsg=%s",
+ this, mLastInstance, ToString(mLastInstance->mRemovingMsg).get(),
+ ToString(mLastInstance->mReceivedMsg).get()));
+ mLastInstance->mReceivedMsg = mMsg;
+ return;
+ }
+
+ // NOTE: If other applications like a11y tools sends WM_*CHAR without
+ // scancode, we cannot compute virtual keycode. I.e., with such
+ // applications, we cannot generate proper KeyboardEvent.code value.
+
+ mVirtualKeyCode = mOriginalVirtualKeyCode =
+ ComputeVirtualKeyCodeFromScanCodeEx();
+ NS_ASSERTION(mVirtualKeyCode, "Failed to compute virtual keycode");
+ break;
+ default: {
+ MOZ_CRASH_UNSAFE_PRINTF("Unsupported message: 0x%04X", mMsg.message);
+ break;
+ }
+ }
+
+ if (!mVirtualKeyCode) {
+ mVirtualKeyCode = mOriginalVirtualKeyCode;
+ }
+
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+ mDOMKeyCode =
+ keyboardLayout->ConvertNativeKeyCodeToDOMKeyCode(mVirtualKeyCode);
+ // Be aware, keyboard utilities can change non-printable keys to printable
+ // keys. In such case, we should make the key value as a printable key.
+ // FYI: IsFollowedByPrintableCharMessage() returns true only when it's
+ // handling a keydown message.
+ mKeyNameIndex =
+ IsFollowedByPrintableCharMessage()
+ ? KEY_NAME_INDEX_USE_STRING
+ : keyboardLayout->ConvertNativeKeyCodeToKeyNameIndex(mVirtualKeyCode);
+ mCodeNameIndex = KeyboardLayout::ConvertScanCodeToCodeNameIndex(
+ GetScanCodeWithExtendedFlag());
+
+ // If next message of WM_(SYS)KEYDOWN is WM_*CHAR message and the key
+ // combination is not reserved by the system, let's consume it now.
+ // TODO: We cannot initialize mCommittedCharsAndModifiers for VK_PACKET
+ // if the message is WM_KEYUP because we don't have preceding
+ // WM_CHAR message.
+ // TODO: Like Edge, we shouldn't dispatch two sets of keyboard events
+ // for a Unicode character in non-BMP because its key value looks
+ // broken and not good thing for our editor if only one keydown or
+ // keypress event's default is prevented. I guess, we should store
+ // key message information globally and we should wait following
+ // WM_KEYDOWN if following WM_CHAR is a part of a Unicode character.
+ if ((mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN) &&
+ !IsReservedBySystem()) {
+ MSG charMsg;
+ while (GetFollowingCharMessage(charMsg)) {
+ // Although, got message shouldn't be WM_NULL in desktop apps,
+ // we should keep checking this. FYI: This was added for Metrofox.
+ if (charMsg.message == WM_NULL) {
+ continue;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::InitWithKeyOrChar(), removed char message, %s",
+ this, ToString(charMsg).get()));
+ Unused << NS_WARN_IF(charMsg.hwnd != mMsg.hwnd);
+ mFollowingCharMsgs.AppendElement(charMsg);
+ }
+ }
+
+ keyboardLayout->InitNativeKey(*this);
+
+ // Now, we can know if the key produces character(s) or a dead key with
+ // AltGraph modifier. When user emulates AltGr key press with pressing
+ // both Ctrl and Alt and the key produces character(s) or a dead key, we
+ // need to replace Control and Alt state with AltGraph if the keyboard
+ // layout has AltGr key.
+ // Note that if Ctrl and/or Alt are pressed (not to emulate to press AltGr),
+ // we need to set actual modifiers to eKeyDown and eKeyUp.
+ if (MaybeEmulatingAltGraph() &&
+ (mCommittedCharsAndModifiers.IsProducingCharsWithAltGr() ||
+ mKeyNameIndex == KEY_NAME_INDEX_Dead)) {
+ mModKeyState.Unset(MODIFIER_CONTROL | MODIFIER_ALT);
+ mModKeyState.Set(MODIFIER_ALTGRAPH);
+ }
+
+ mIsDeadKey =
+ (IsFollowedByDeadCharMessage() ||
+ keyboardLayout->IsDeadKey(mOriginalVirtualKeyCode, mModKeyState));
+ mIsPrintableKey = mKeyNameIndex == KEY_NAME_INDEX_USE_STRING ||
+ KeyboardLayout::IsPrintableCharKey(mOriginalVirtualKeyCode);
+ // The repeat count in mMsg.lParam isn't useful to check whether the event
+ // is caused by the auto-repeat feature because it's not incremented even
+ // if it's repeated twice or more (i.e., always 1). Therefore, we need to
+ // check previous key state (31th bit) instead. If it's 1, the key was down
+ // before the message was sent.
+ mIsRepeat = (mMsg.lParam & (1 << 30)) != 0;
+ InitIsSkippableForKeyOrChar(lastKeyMSG);
+
+ if (IsKeyDownMessage()) {
+ // Compute some strings which may be inputted by the key with various
+ // modifier state if this key event won't cause text input actually.
+ // They will be used for setting mAlternativeCharCodes in the callback
+ // method which will be called by TextEventDispatcher.
+ if (!IsFollowedByPrintableCharMessage()) {
+ ComputeInputtingStringWithKeyboardLayout();
+ }
+ // Remove odd char messages if there are.
+ RemoveFollowingOddCharMessages();
+ }
+}
+
+void NativeKey::InitCommittedCharsAndModifiersWithFollowingCharMessages() {
+ mCommittedCharsAndModifiers.Clear();
+ // This should cause inputting text in focused editor. However, it
+ // ignores keypress events whose altKey or ctrlKey is true.
+ // Therefore, we need to remove these modifier state here.
+ Modifiers modifiers = mModKeyState.GetModifiers();
+ if (IsFollowedByPrintableCharMessage()) {
+ modifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ if (MaybeEmulatingAltGraph()) {
+ modifiers |= MODIFIER_ALTGRAPH;
+ }
+ }
+ // NOTE: This method assumes that WM_CHAR and WM_SYSCHAR are never retrieved
+ // at same time.
+ for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
+ // Ignore non-printable char messages.
+ if (!IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) {
+ continue;
+ }
+ char16_t ch = static_cast<char16_t>(mFollowingCharMsgs[i].wParam);
+ mCommittedCharsAndModifiers.Append(ch, modifiers);
+ }
+}
+
+NativeKey::~NativeKey() {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::~NativeKey(), destroyed", this));
+ if (mIsOverridingKeyboardLayout) {
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+ keyboardLayout->RestoreLayout();
+ }
+ sLatestInstance = mLastInstance;
+}
+
+void NativeKey::InitWithAppCommand() {
+ if (GET_DEVICE_LPARAM(mMsg.lParam) != FAPPCOMMAND_KEY) {
+ return;
+ }
+
+ uint32_t appCommand = GET_APPCOMMAND_LPARAM(mMsg.lParam);
+ switch (GET_APPCOMMAND_LPARAM(mMsg.lParam)) {
+#undef NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX
+#define NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX(aAppCommand, aKeyNameIndex) \
+ case aAppCommand: \
+ mKeyNameIndex = aKeyNameIndex; \
+ break;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ mKeyNameIndex = KEY_NAME_INDEX_Unidentified;
+ }
+
+ // Guess the virtual keycode which caused this message.
+ switch (appCommand) {
+ case APPCOMMAND_BROWSER_BACKWARD:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_BACK;
+ break;
+ case APPCOMMAND_BROWSER_FORWARD:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_FORWARD;
+ break;
+ case APPCOMMAND_BROWSER_REFRESH:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_REFRESH;
+ break;
+ case APPCOMMAND_BROWSER_STOP:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_STOP;
+ break;
+ case APPCOMMAND_BROWSER_SEARCH:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_SEARCH;
+ break;
+ case APPCOMMAND_BROWSER_FAVORITES:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_FAVORITES;
+ break;
+ case APPCOMMAND_BROWSER_HOME:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_HOME;
+ break;
+ case APPCOMMAND_VOLUME_MUTE:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_MUTE;
+ break;
+ case APPCOMMAND_VOLUME_DOWN:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_DOWN;
+ break;
+ case APPCOMMAND_VOLUME_UP:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_UP;
+ break;
+ case APPCOMMAND_MEDIA_NEXTTRACK:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_NEXT_TRACK;
+ break;
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_PREV_TRACK;
+ break;
+ case APPCOMMAND_MEDIA_STOP:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_STOP;
+ break;
+ case APPCOMMAND_MEDIA_PLAY_PAUSE:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_PLAY_PAUSE;
+ break;
+ case APPCOMMAND_LAUNCH_MAIL:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_MAIL;
+ break;
+ case APPCOMMAND_LAUNCH_MEDIA_SELECT:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_MEDIA_SELECT;
+ break;
+ case APPCOMMAND_LAUNCH_APP1:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_APP1;
+ break;
+ case APPCOMMAND_LAUNCH_APP2:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_APP2;
+ break;
+ default:
+ return;
+ }
+
+ uint16_t scanCodeEx = ComputeScanCodeExFromVirtualKeyCode(mVirtualKeyCode);
+ mScanCode = static_cast<uint8_t>(scanCodeEx & 0xFF);
+ uint8_t extended = static_cast<uint8_t>((scanCodeEx & 0xFF00) >> 8);
+ mIsExtended = (extended == 0xE0) || (extended == 0xE1);
+ mDOMKeyCode = KeyboardLayout::GetInstance()->ConvertNativeKeyCodeToDOMKeyCode(
+ mOriginalVirtualKeyCode);
+ mCodeNameIndex = KeyboardLayout::ConvertScanCodeToCodeNameIndex(
+ GetScanCodeWithExtendedFlag());
+ // If we can map the WM_APPCOMMAND to a virtual keycode, we can trust
+ // the result of GetKeyboardState(). Otherwise, we dispatch both
+ // keydown and keyup events from WM_APPCOMMAND handler. Therefore,
+ // even if WM_APPCOMMAND is caused by auto key repeat, web apps receive
+ // a pair of DOM keydown and keyup events. I.e., KeyboardEvent.repeat
+ // should be never true of such keys.
+ // XXX Isn't the key state always true? If the key press caused this
+ // WM_APPCOMMAND, that means it's pressed at that time.
+ if (mVirtualKeyCode) {
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+ ::GetKeyboardState(kbdState);
+ mIsSkippableInRemoteProcess = mIsRepeat = !!kbdState[mVirtualKeyCode];
+ }
+}
+
+bool NativeKey::MaybeEmulatingAltGraph() const {
+ return IsControl() && IsAlt() && KeyboardLayout::GetInstance()->HasAltGr();
+}
+
+// static
+bool NativeKey::IsControlChar(char16_t aChar) {
+ static const char16_t U_SPACE = 0x20;
+ static const char16_t U_DELETE = 0x7F;
+ return aChar < U_SPACE || aChar == U_DELETE;
+}
+
+bool NativeKey::IsFollowedByDeadCharMessage() const {
+ if (mFollowingCharMsgs.IsEmpty()) {
+ return false;
+ }
+ return IsDeadCharMessage(mFollowingCharMsgs[0]);
+}
+
+bool NativeKey::IsFollowedByPrintableCharMessage() const {
+ for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
+ if (IsPrintableCharMessage(mFollowingCharMsgs[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool NativeKey::IsFollowedByPrintableCharOrSysCharMessage() const {
+ for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
+ if (IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool NativeKey::IsReservedBySystem() const {
+ // Alt+Space key is handled by OS, we shouldn't touch it.
+ if (mModKeyState.IsAlt() && !mModKeyState.IsControl() &&
+ mVirtualKeyCode == VK_SPACE) {
+ return true;
+ }
+
+ // XXX How about Alt+F4? We receive WM_SYSKEYDOWN for F4 before closing the
+ // window. Although, we don't prevent to close the window but the key
+ // event shouldn't be exposed to the web.
+
+ return false;
+}
+
+bool NativeKey::IsIMEDoingKakuteiUndo() const {
+ // Following message pattern is caused by "Kakutei-Undo" of ATOK or WXG:
+ // ---------------------------------------------------------------------------
+ // WM_KEYDOWN * n (wParam = VK_BACK, lParam = 0x1)
+ // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC0000001) # ATOK
+ // WM_IME_STARTCOMPOSITION * 1 (wParam = 0x0, lParam = 0x0)
+ // WM_IME_COMPOSITION * 1 (wParam = 0x0, lParam = 0x1BF)
+ // WM_CHAR * n (wParam = VK_BACK, lParam = 0x1)
+ // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC00E0001)
+ // ---------------------------------------------------------------------------
+ // This doesn't match usual key message pattern such as:
+ // WM_KEYDOWN -> WM_CHAR -> WM_KEYDOWN -> WM_CHAR -> ... -> WM_KEYUP
+ // See following bugs for the detail.
+ // https://bugzilla.mozilla.gr.jp/show_bug.cgi?id=2885 (written in Japanese)
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=194559 (written in English)
+ MSG startCompositionMsg, compositionMsg, charMsg;
+ return WinUtils::PeekMessage(&startCompositionMsg, mMsg.hwnd,
+ WM_IME_STARTCOMPOSITION, WM_IME_STARTCOMPOSITION,
+ PM_NOREMOVE | PM_NOYIELD) &&
+ WinUtils::PeekMessage(&compositionMsg, mMsg.hwnd, WM_IME_COMPOSITION,
+ WM_IME_COMPOSITION, PM_NOREMOVE | PM_NOYIELD) &&
+ WinUtils::PeekMessage(&charMsg, mMsg.hwnd, WM_CHAR, WM_CHAR,
+ PM_NOREMOVE | PM_NOYIELD) &&
+ startCompositionMsg.wParam == 0x0 &&
+ startCompositionMsg.lParam == 0x0 && compositionMsg.wParam == 0x0 &&
+ compositionMsg.lParam == 0x1BF && charMsg.wParam == VK_BACK &&
+ charMsg.lParam == 0x1 &&
+ startCompositionMsg.time <= compositionMsg.time &&
+ compositionMsg.time <= charMsg.time;
+}
+
+void NativeKey::RemoveFollowingOddCharMessages() {
+ MOZ_ASSERT(IsKeyDownMessage());
+
+ // If the keydown message is synthesized for automated tests, there is
+ // nothing to do here.
+ if (mFakeCharMsgs) {
+ return;
+ }
+
+ // If there are some following char messages before another key message,
+ // there is nothing to do here.
+ if (!mFollowingCharMsgs.IsEmpty()) {
+ return;
+ }
+
+ // If the handling key isn't Backspace, there is nothing to do here.
+ if (mOriginalVirtualKeyCode != VK_BACK) {
+ return;
+ }
+
+ // If we don't see the odd message pattern, there is nothing to do here.
+ if (!IsIMEDoingKakuteiUndo()) {
+ return;
+ }
+
+ // Otherwise, we need to remove odd WM_CHAR messages for ATOK or WXG (both
+ // of them are Japanese IME).
+ MSG msg;
+ while (WinUtils::PeekMessage(&msg, mMsg.hwnd, WM_CHAR, WM_CHAR,
+ PM_REMOVE | PM_NOYIELD)) {
+ if (msg.message != WM_CHAR) {
+ MOZ_RELEASE_ASSERT(msg.message == WM_NULL,
+ "Unexpected message was removed");
+ continue;
+ }
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::RemoveFollowingOddCharMessages(), removed odd char "
+ "message, %s",
+ this, ToString(msg).get()));
+ mRemovedOddCharMsgs.AppendElement(msg);
+ }
+}
+
+UINT NativeKey::GetScanCodeWithExtendedFlag() const {
+ if (!mIsExtended) {
+ return mScanCode;
+ }
+ return (0xE000 | mScanCode);
+}
+
+uint32_t NativeKey::GetKeyLocation() const {
+ switch (mVirtualKeyCode) {
+ case VK_LSHIFT:
+ case VK_LCONTROL:
+ case VK_LMENU:
+ case VK_LWIN:
+ return eKeyLocationLeft;
+
+ case VK_RSHIFT:
+ case VK_RCONTROL:
+ case VK_RMENU:
+ case VK_RWIN:
+ return eKeyLocationRight;
+
+ case VK_RETURN:
+ // XXX This code assumes that all keyboard drivers use same mapping.
+ return !mIsExtended ? eKeyLocationStandard : eKeyLocationNumpad;
+
+ case VK_INSERT:
+ case VK_DELETE:
+ case VK_END:
+ case VK_DOWN:
+ case VK_NEXT:
+ case VK_LEFT:
+ case VK_CLEAR:
+ case VK_RIGHT:
+ case VK_HOME:
+ case VK_UP:
+ case VK_PRIOR:
+ // XXX This code assumes that all keyboard drivers use same mapping.
+ return mIsExtended ? eKeyLocationStandard : eKeyLocationNumpad;
+
+ // NumLock key isn't included due to IE9's behavior.
+ case VK_NUMPAD0:
+ case VK_NUMPAD1:
+ case VK_NUMPAD2:
+ case VK_NUMPAD3:
+ case VK_NUMPAD4:
+ case VK_NUMPAD5:
+ case VK_NUMPAD6:
+ case VK_NUMPAD7:
+ case VK_NUMPAD8:
+ case VK_NUMPAD9:
+ case VK_DECIMAL:
+ case VK_DIVIDE:
+ case VK_MULTIPLY:
+ case VK_SUBTRACT:
+ case VK_ADD:
+ // Separator key of Brazilian keyboard or JIS keyboard for Mac
+ case VK_ABNT_C2:
+ return eKeyLocationNumpad;
+
+ case VK_SHIFT:
+ case VK_CONTROL:
+ case VK_MENU:
+ NS_WARNING("Failed to decide the key location?");
+
+ default:
+ return eKeyLocationStandard;
+ }
+}
+
+uint8_t NativeKey::ComputeVirtualKeyCodeFromScanCode() const {
+ return static_cast<uint8_t>(
+ ::MapVirtualKeyEx(mScanCode, MAPVK_VSC_TO_VK, mKeyboardLayout));
+}
+
+uint8_t NativeKey::ComputeVirtualKeyCodeFromScanCodeEx() const {
+ // MapVirtualKeyEx() has been improved for supporting extended keys since
+ // Vista. When we call it for mapping a scancode of an extended key and
+ // a virtual keycode, we need to add 0xE000 to the scancode.
+ return static_cast<uint8_t>(::MapVirtualKeyEx(
+ GetScanCodeWithExtendedFlag(), MAPVK_VSC_TO_VK_EX, mKeyboardLayout));
+}
+
+uint16_t NativeKey::ComputeScanCodeExFromVirtualKeyCode(
+ UINT aVirtualKeyCode) const {
+ return static_cast<uint16_t>(
+ ::MapVirtualKeyEx(aVirtualKeyCode, MAPVK_VK_TO_VSC_EX, mKeyboardLayout));
+}
+
+char16_t NativeKey::ComputeUnicharFromScanCode() const {
+ return static_cast<char16_t>(::MapVirtualKeyEx(
+ ComputeVirtualKeyCodeFromScanCode(), MAPVK_VK_TO_CHAR, mKeyboardLayout));
+}
+
+nsEventStatus NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent) const {
+ return InitKeyEvent(aKeyEvent, mModKeyState);
+}
+
+nsEventStatus NativeKey::InitKeyEvent(
+ WidgetKeyboardEvent& aKeyEvent,
+ const ModifierKeyState& aModKeyState) const {
+ if (mWidget->Destroyed()) {
+ MOZ_CRASH("NativeKey tries to dispatch a key event on destroyed widget");
+ }
+
+ LayoutDeviceIntPoint point(0, 0);
+ mWidget->InitEvent(aKeyEvent, &point);
+
+ switch (aKeyEvent.mMessage) {
+ case eKeyDown:
+ // If it was followed by a char message but it was consumed by somebody,
+ // we should mark it as consumed because somebody must have handled it
+ // and we should prevent to do "double action" for the key operation.
+ // However, for compatibility with older version and other browsers,
+ // we should dispatch the events even in the web content.
+ if (mCharMessageHasGone) {
+ aKeyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow);
+ }
+ [[fallthrough]];
+ case eKeyDownOnPlugin:
+ aKeyEvent.mKeyCode = mDOMKeyCode;
+ // Unique id for this keydown event and its associated keypress.
+ sUniqueKeyEventId++;
+ aKeyEvent.mUniqueId = sUniqueKeyEventId;
+ break;
+ case eKeyUp:
+ case eKeyUpOnPlugin:
+ aKeyEvent.mKeyCode = mDOMKeyCode;
+ // Set defaultPrevented of the key event if the VK_MENU is not a system
+ // key release, so that the menu bar does not trigger. This helps avoid
+ // triggering the menu bar for ALT key accelerators used in assistive
+ // technologies such as Window-Eyes and ZoomText or for switching open
+ // state of IME. On the other hand, we should dispatch the events even
+ // in the web content for compatibility with older version and other
+ // browsers.
+ if (mOriginalVirtualKeyCode == VK_MENU && mMsg.message != WM_SYSKEYUP) {
+ aKeyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow);
+ }
+ break;
+ case eKeyPress:
+ MOZ_ASSERT(!mCharMessageHasGone,
+ "If following char message was consumed by somebody, "
+ "keydown event should be consumed above");
+ aKeyEvent.mUniqueId = sUniqueKeyEventId;
+ break;
+ default:
+ MOZ_CRASH("Invalid event message");
+ }
+
+ aKeyEvent.mIsRepeat = mIsRepeat;
+ aKeyEvent.mMaybeSkippableInRemoteProcess = mIsSkippableInRemoteProcess;
+ aKeyEvent.mKeyNameIndex = mKeyNameIndex;
+ if (mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
+ aKeyEvent.mKeyValue = mCommittedCharsAndModifiers.ToString();
+ }
+ aKeyEvent.mCodeNameIndex = mCodeNameIndex;
+ MOZ_ASSERT(mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
+ aKeyEvent.mLocation = GetKeyLocation();
+ aModKeyState.InitInputEvent(aKeyEvent);
+
+ KeyboardLayout::NotifyIdleServiceOfUserActivity();
+
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::InitKeyEvent(), initialized, aKeyEvent={ "
+ "mMessage=%s, mKeyNameIndex=%s, mKeyValue=\"%s\", mCodeNameIndex=%s, "
+ "mKeyCode=%s, mLocation=%s, mModifiers=%s, DefaultPrevented()=%s }",
+ this, ToChar(aKeyEvent.mMessage),
+ ToString(aKeyEvent.mKeyNameIndex).get(),
+ NS_ConvertUTF16toUTF8(aKeyEvent.mKeyValue).get(),
+ ToString(aKeyEvent.mCodeNameIndex).get(),
+ GetDOMKeyCodeName(aKeyEvent.mKeyCode).get(),
+ GetKeyLocationName(aKeyEvent.mLocation).get(),
+ GetModifiersName(aKeyEvent.mModifiers).get(),
+ GetBoolName(aKeyEvent.DefaultPrevented())));
+
+ return aKeyEvent.DefaultPrevented() ? nsEventStatus_eConsumeNoDefault
+ : nsEventStatus_eIgnore;
+}
+
+bool NativeKey::DispatchCommandEvent(uint32_t aEventCommand) const {
+ RefPtr<nsAtom> command;
+ switch (aEventCommand) {
+ case APPCOMMAND_BROWSER_BACKWARD:
+ command = nsGkAtoms::Back;
+ break;
+ case APPCOMMAND_BROWSER_FORWARD:
+ command = nsGkAtoms::Forward;
+ break;
+ case APPCOMMAND_BROWSER_REFRESH:
+ command = nsGkAtoms::Reload;
+ break;
+ case APPCOMMAND_BROWSER_STOP:
+ command = nsGkAtoms::Stop;
+ break;
+ case APPCOMMAND_BROWSER_SEARCH:
+ command = nsGkAtoms::Search;
+ break;
+ case APPCOMMAND_BROWSER_FAVORITES:
+ command = nsGkAtoms::Bookmarks;
+ break;
+ case APPCOMMAND_BROWSER_HOME:
+ command = nsGkAtoms::Home;
+ break;
+ case APPCOMMAND_CLOSE:
+ command = nsGkAtoms::Close;
+ break;
+ case APPCOMMAND_FIND:
+ command = nsGkAtoms::Find;
+ break;
+ case APPCOMMAND_HELP:
+ command = nsGkAtoms::Help;
+ break;
+ case APPCOMMAND_NEW:
+ command = nsGkAtoms::New;
+ break;
+ case APPCOMMAND_OPEN:
+ command = nsGkAtoms::Open;
+ break;
+ case APPCOMMAND_PRINT:
+ command = nsGkAtoms::Print;
+ break;
+ case APPCOMMAND_SAVE:
+ command = nsGkAtoms::Save;
+ break;
+ case APPCOMMAND_FORWARD_MAIL:
+ command = nsGkAtoms::ForwardMail;
+ break;
+ case APPCOMMAND_REPLY_TO_MAIL:
+ command = nsGkAtoms::ReplyToMail;
+ break;
+ case APPCOMMAND_SEND_MAIL:
+ command = nsGkAtoms::SendMail;
+ break;
+ case APPCOMMAND_MEDIA_NEXTTRACK:
+ command = nsGkAtoms::NextTrack;
+ break;
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK:
+ command = nsGkAtoms::PreviousTrack;
+ break;
+ case APPCOMMAND_MEDIA_STOP:
+ command = nsGkAtoms::MediaStop;
+ break;
+ case APPCOMMAND_MEDIA_PLAY_PAUSE:
+ command = nsGkAtoms::PlayPause;
+ break;
+ default:
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchCommandEvent(), doesn't dispatch command "
+ "event",
+ this));
+ return false;
+ }
+ WidgetCommandEvent appCommandEvent(true, command, mWidget);
+
+ mWidget->InitEvent(appCommandEvent);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchCommandEvent(), dispatching "
+ "%s app command event...",
+ this, nsAtomCString(command).get()));
+ bool ok =
+ mWidget->DispatchWindowEvent(&appCommandEvent) || mWidget->Destroyed();
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchCommandEvent(), dispatched app command event, "
+ "result=%s, mWidget->Destroyed()=%s",
+ this, GetBoolName(ok), GetBoolName(mWidget->Destroyed())));
+ return ok;
+}
+
+bool NativeKey::HandleAppCommandMessage() const {
+ // If the widget has gone, we should do nothing.
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::HandleAppCommandMessage(), WARNING, not handled "
+ "due to "
+ "destroyed the widget",
+ this));
+ return false;
+ }
+
+ // NOTE: Typical behavior of WM_APPCOMMAND caused by key is, WM_APPCOMMAND
+ // message is _sent_ first. Then, the DefaultWndProc will _post_
+ // WM_KEYDOWN message and WM_KEYUP message if the keycode for the
+ // command is available (i.e., mVirtualKeyCode is not 0).
+
+ // NOTE: IntelliType (Microsoft's keyboard utility software) always consumes
+ // WM_KEYDOWN and WM_KEYUP.
+
+ // Let's dispatch keydown message before our chrome handles the command
+ // when the message is caused by a keypress. This behavior makes handling
+ // WM_APPCOMMAND be a default action of the keydown event. This means that
+ // web applications can handle multimedia keys and prevent our default action.
+ // This allow web applications to provide better UX for multimedia keyboard
+ // users.
+ bool dispatchKeyEvent = (GET_DEVICE_LPARAM(mMsg.lParam) == FAPPCOMMAND_KEY);
+ if (dispatchKeyEvent) {
+ // If a plug-in window has focus but it didn't consume the message, our
+ // window receive WM_APPCOMMAND message. In this case, we shouldn't
+ // dispatch KeyboardEvents because an event handler may access the
+ // plug-in process synchronously.
+ dispatchKeyEvent =
+ WinUtils::IsOurProcessWindow(reinterpret_cast<HWND>(mMsg.wParam));
+ }
+
+ bool consumed = false;
+
+ if (dispatchKeyEvent) {
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleAppCommandMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure",
+ this));
+ return true;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), initializing keydown "
+ "event...",
+ this));
+ WidgetKeyboardEvent keydownEvent(true, eKeyDown, mWidget);
+ nsEventStatus status = InitKeyEvent(keydownEvent, mModKeyState);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), tries to dispatch "
+ "keydown event...",
+ this));
+ // NOTE: If the keydown event is consumed by web contents, we shouldn't
+ // continue to handle the command.
+ if (!mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status,
+ const_cast<NativeKey*>(this))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), keydown event isn't "
+ "dispatched",
+ this));
+ // If keyboard event wasn't fired, there must be composition.
+ // So, we don't need to dispatch a command event.
+ return true;
+ }
+ consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), keydown event was "
+ "dispatched, consumed=%s",
+ this, GetBoolName(consumed)));
+ sDispatchedKeyOfAppCommand = mVirtualKeyCode;
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), keydown event caused "
+ "destroying the widget",
+ this));
+ return true;
+ }
+ }
+
+ // Dispatch a command event or a content command event if the command is
+ // supported.
+ if (!consumed) {
+ uint32_t appCommand = GET_APPCOMMAND_LPARAM(mMsg.lParam);
+ EventMessage contentCommandMessage = eVoidEvent;
+ switch (appCommand) {
+ case APPCOMMAND_BROWSER_BACKWARD:
+ case APPCOMMAND_BROWSER_FORWARD:
+ case APPCOMMAND_BROWSER_REFRESH:
+ case APPCOMMAND_BROWSER_STOP:
+ case APPCOMMAND_BROWSER_SEARCH:
+ case APPCOMMAND_BROWSER_FAVORITES:
+ case APPCOMMAND_BROWSER_HOME:
+ case APPCOMMAND_CLOSE:
+ case APPCOMMAND_FIND:
+ case APPCOMMAND_HELP:
+ case APPCOMMAND_NEW:
+ case APPCOMMAND_OPEN:
+ case APPCOMMAND_PRINT:
+ case APPCOMMAND_SAVE:
+ case APPCOMMAND_FORWARD_MAIL:
+ case APPCOMMAND_REPLY_TO_MAIL:
+ case APPCOMMAND_SEND_MAIL:
+ case APPCOMMAND_MEDIA_NEXTTRACK:
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK:
+ case APPCOMMAND_MEDIA_STOP:
+ case APPCOMMAND_MEDIA_PLAY_PAUSE:
+ // We shouldn't consume the message always because if we don't handle
+ // the message, the sender (typically, utility of keyboard or mouse)
+ // may send other key messages which indicate well known shortcut key.
+ consumed = DispatchCommandEvent(appCommand);
+ break;
+
+ // Use content command for following commands:
+ case APPCOMMAND_COPY:
+ contentCommandMessage = eContentCommandCopy;
+ break;
+ case APPCOMMAND_CUT:
+ contentCommandMessage = eContentCommandCut;
+ break;
+ case APPCOMMAND_PASTE:
+ contentCommandMessage = eContentCommandPaste;
+ break;
+ case APPCOMMAND_REDO:
+ contentCommandMessage = eContentCommandRedo;
+ break;
+ case APPCOMMAND_UNDO:
+ contentCommandMessage = eContentCommandUndo;
+ break;
+ }
+
+ if (contentCommandMessage) {
+ MOZ_ASSERT(!mWidget->Destroyed());
+ WidgetContentCommandEvent contentCommandEvent(true, contentCommandMessage,
+ mWidget);
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatching %s event...",
+ this, ToChar(contentCommandMessage)));
+ mWidget->DispatchWindowEvent(&contentCommandEvent);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatched %s event",
+ this, ToChar(contentCommandMessage)));
+ consumed = true;
+
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), %s event caused "
+ "destroying the widget",
+ this, ToChar(contentCommandMessage)));
+ return true;
+ }
+ } else {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), doesn't dispatch "
+ "content "
+ "command event",
+ this));
+ }
+ }
+
+ // Dispatch a keyup event if the command is caused by pressing a key and
+ // the key isn't mapped to a virtual keycode.
+ if (dispatchKeyEvent && !mVirtualKeyCode) {
+ MOZ_ASSERT(!mWidget->Destroyed());
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleAppCommandMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure",
+ this));
+ return true;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), initializing keyup "
+ "event...",
+ this));
+ WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
+ nsEventStatus status = InitKeyEvent(keyupEvent, mModKeyState);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatching keyup "
+ "event...",
+ this));
+ // NOTE: Ignore if the keyup event is consumed because keyup event
+ // represents just a physical key event state change.
+ mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status,
+ const_cast<NativeKey*>(this));
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatched keyup event",
+ this));
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), %s event caused "
+ "destroying the widget",
+ this));
+ return true;
+ }
+ }
+
+ return consumed;
+}
+
+bool NativeKey::HandleKeyDownMessage(bool* aEventDispatched) const {
+ MOZ_ASSERT(IsKeyDownMessage());
+
+ if (aEventDispatched) {
+ *aEventDispatched = false;
+ }
+
+ if (sDispatchedKeyOfAppCommand &&
+ sDispatchedKeyOfAppCommand == mOriginalVirtualKeyCode) {
+ // The multimedia key event has already been dispatch from
+ // HandleAppCommandMessage().
+ sDispatchedKeyOfAppCommand = 0;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keydown "
+ "event due to already dispatched from HandleAppCommandMessage(), ",
+ this));
+ if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) {
+ RedirectedKeyDownMessageManager::Forget();
+ }
+ return true;
+ }
+
+ if (IsReservedBySystem()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keydown "
+ "event because the key combination is reserved by the system",
+ this));
+ if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) {
+ RedirectedKeyDownMessageManager::Forget();
+ }
+ return false;
+ }
+
+ // If the widget has gone, we should do nothing.
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::HandleKeyDownMessage(), WARNING, not handled due to "
+ "destroyed the widget",
+ this));
+ if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) {
+ RedirectedKeyDownMessageManager::Forget();
+ }
+ return false;
+ }
+
+ bool defaultPrevented = false;
+ if (mFakeCharMsgs || IsKeyMessageOnPlugin() ||
+ !RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) {
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleKeyDownMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure",
+ this));
+ return true;
+ }
+
+ bool isIMEEnabled = WinUtils::IsIMEEnabled(mWidget->GetInputContext());
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::HandleKeyDownMessage(), initializing keydown "
+ "event...",
+ this));
+
+ EventMessage keyDownMessage =
+ IsKeyMessageOnPlugin() ? eKeyDownOnPlugin : eKeyDown;
+ WidgetKeyboardEvent keydownEvent(true, keyDownMessage, mWidget);
+ nsEventStatus status = InitKeyEvent(keydownEvent, mModKeyState);
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), dispatching keydown event...",
+ this));
+ bool dispatched = mDispatcher->DispatchKeyboardEvent(
+ keyDownMessage, keydownEvent, status, const_cast<NativeKey*>(this));
+ if (aEventDispatched) {
+ *aEventDispatched = dispatched;
+ }
+ if (!dispatched) {
+ // If the keydown event wasn't fired, there must be composition.
+ // we don't need to do anything anymore.
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keypress "
+ "event(s) because keydown event isn't dispatched actually",
+ this));
+ return false;
+ }
+ defaultPrevented = status == nsEventStatus_eConsumeNoDefault;
+
+ // We don't need to handle key messages on plugin for eKeyPress since
+ // eKeyDownOnPlugin is handled as both eKeyDown and eKeyPress.
+ if (IsKeyMessageOnPlugin()) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keypress "
+ "event(s) because it's a keydown message on windowed plugin, "
+ "defaultPrevented=%s",
+ this, GetBoolName(defaultPrevented)));
+ return defaultPrevented;
+ }
+
+ if (mWidget->Destroyed() || IsFocusedWindowChanged()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), keydown event caused "
+ "destroying the widget",
+ this));
+ return true;
+ }
+
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), dispatched keydown event, "
+ "dispatched=%s, defaultPrevented=%s",
+ this, GetBoolName(dispatched), GetBoolName(defaultPrevented)));
+
+ // If IMC wasn't associated to the window but is associated it now (i.e.,
+ // focus is moved from a non-editable editor to an editor by keydown
+ // event handler), WM_CHAR and WM_SYSCHAR shouldn't cause first character
+ // inputting if IME is opened. But then, we should redirect the native
+ // keydown message to IME.
+ // However, note that if focus has been already moved to another
+ // application, we shouldn't redirect the message to it because the keydown
+ // message is processed by us, so, nobody shouldn't process it.
+ HWND focusedWnd = ::GetFocus();
+ if (!defaultPrevented && !mFakeCharMsgs && !IsKeyMessageOnPlugin() &&
+ focusedWnd && !isIMEEnabled &&
+ WinUtils::IsIMEEnabled(mWidget->GetInputContext())) {
+ RedirectedKeyDownMessageManager::RemoveNextCharMessage(focusedWnd);
+
+ INPUT keyinput;
+ keyinput.type = INPUT_KEYBOARD;
+ keyinput.ki.wVk = mOriginalVirtualKeyCode;
+ keyinput.ki.wScan = mScanCode;
+ keyinput.ki.dwFlags = KEYEVENTF_SCANCODE;
+ if (mIsExtended) {
+ keyinput.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
+ }
+ keyinput.ki.time = 0;
+ keyinput.ki.dwExtraInfo = 0;
+
+ RedirectedKeyDownMessageManager::WillRedirect(mMsg, defaultPrevented);
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), redirecting %s...",
+ this, ToString(mMsg).get()));
+
+ ::SendInput(1, &keyinput, sizeof(keyinput));
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), redirected %s", this,
+ ToString(mMsg).get()));
+
+ // Return here. We shouldn't dispatch keypress event for this WM_KEYDOWN.
+ // If it's needed, it will be dispatched after next (redirected)
+ // WM_KEYDOWN.
+ return true;
+ }
+ } else {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), received a redirected %s",
+ this, ToString(mMsg).get()));
+
+ defaultPrevented = RedirectedKeyDownMessageManager::DefaultPrevented();
+ // If this is redirected keydown message, we have dispatched the keydown
+ // event already.
+ if (aEventDispatched) {
+ *aEventDispatched = true;
+ }
+ }
+
+ RedirectedKeyDownMessageManager::Forget();
+
+ MOZ_ASSERT(!mWidget->Destroyed());
+
+ // If the key was processed by IME and didn't cause WM_(SYS)CHAR messages, we
+ // shouldn't dispatch keypress event.
+ if (mOriginalVirtualKeyCode == VK_PROCESSKEY &&
+ !IsFollowedByPrintableCharOrSysCharMessage()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress "
+ "event because the key was already handled by IME, "
+ "defaultPrevented=%s",
+ this, GetBoolName(defaultPrevented)));
+ return defaultPrevented;
+ }
+
+ if (defaultPrevented) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress "
+ "event because preceding keydown event was consumed",
+ this));
+ return true;
+ }
+
+ MOZ_ASSERT(!mCharMessageHasGone,
+ "If following char message was consumed by somebody, "
+ "keydown event should have been consumed before dispatch");
+
+ // If mCommittedCharsAndModifiers was initialized with following char
+ // messages, we should dispatch keypress events with its information.
+ if (IsFollowedByPrintableCharOrSysCharMessage()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching "
+ "keypress events with retrieved char messages...",
+ this));
+ return DispatchKeyPressEventsWithRetrievedCharMessages();
+ }
+
+ // If we won't be getting a WM_CHAR, WM_SYSCHAR or WM_DEADCHAR, synthesize a
+ // keypress for almost all keys
+ if (NeedsToHandleWithoutFollowingCharMessages()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching "
+ "keypress events...",
+ this));
+ return DispatchKeyPressEventsWithoutCharMessage();
+ }
+
+ // If WM_KEYDOWN of VK_PACKET isn't followed by WM_CHAR, we don't need to
+ // dispatch keypress events.
+ if (mVirtualKeyCode == VK_PACKET) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress "
+ "event "
+ "because the key is VK_PACKET and there are no char messages",
+ this));
+ return false;
+ }
+
+ if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() &&
+ !mModKeyState.IsWin() && mIsPrintableKey) {
+ // If this is simple KeyDown event but next message is not WM_CHAR,
+ // this event may not input text, so we should ignore this event.
+ // See bug 314130.
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress "
+ "event "
+ "because the key event is simple printable key's event but not "
+ "followed "
+ "by char messages",
+ this));
+ return false;
+ }
+
+ if (mIsDeadKey) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress "
+ "event "
+ "because the key is a dead key and not followed by char messages",
+ this));
+ return false;
+ }
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching "
+ "keypress events due to no following char messages...",
+ this));
+ return DispatchKeyPressEventsWithoutCharMessage();
+}
+
+bool NativeKey::HandleCharMessage(bool* aEventDispatched) const {
+ MOZ_ASSERT(IsCharOrSysCharMessage(mMsg));
+ return HandleCharMessage(mMsg, aEventDispatched);
+}
+
+bool NativeKey::HandleCharMessage(const MSG& aCharMsg,
+ bool* aEventDispatched) const {
+ MOZ_ASSERT(IsKeyDownMessage() || IsCharOrSysCharMessage(mMsg));
+ MOZ_ASSERT(IsCharOrSysCharMessage(aCharMsg.message));
+
+ if (aEventDispatched) {
+ *aEventDispatched = false;
+ }
+
+ if ((IsCharOrSysCharMessage(mMsg) || IsEnterKeyPressCharMessage(mMsg)) &&
+ IsAnotherInstanceRemovingCharMessage()) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::HandleCharMessage(), WARNING, does nothing because "
+ "the message should be handled in another instance removing this "
+ "message",
+ this));
+ // Consume this for now because it will be handled by another instance.
+ return true;
+ }
+
+ // If the key combinations is reserved by the system, we shouldn't dispatch
+ // eKeyPress event for it and passes the message to next wndproc.
+ if (IsReservedBySystem()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), doesn't dispatch keypress "
+ "event because the key combination is reserved by the system",
+ this));
+ return false;
+ }
+
+ // If the widget has gone, we should do nothing.
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::HandleCharMessage(), WARNING, not handled due to "
+ "destroyed the widget",
+ this));
+ return false;
+ }
+
+ // When a control key is inputted by a key, it should be handled without
+ // WM_*CHAR messages at receiving WM_*KEYDOWN message. So, when we receive
+ // WM_*CHAR message directly, we see a control character here.
+ // Note that when the char is '\r', it means that the char message should
+ // cause "Enter" keypress event for inserting a line break.
+ if (IsControlCharMessage(aCharMsg) && !IsEnterKeyPressCharMessage(aCharMsg)) {
+ // In this case, we don't need to dispatch eKeyPress event because:
+ // 1. We're the only browser which dispatches "keypress" event for
+ // non-printable characters (Although, both Chrome and Edge dispatch
+ // "keypress" event for some keys accidentally. For example, "IntlRo"
+ // key with Ctrl of Japanese keyboard layout).
+ // 2. Currently, we may handle shortcut keys with "keydown" event if
+ // it's reserved or something. So, we shouldn't dispatch "keypress"
+ // event without it.
+ // Note that this does NOT mean we stop dispatching eKeyPress event for
+ // key presses causes a control character when Ctrl is pressed. In such
+ // case, DispatchKeyPressEventsWithoutCharMessage() dispatches eKeyPress
+ // instead of this method.
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), doesn't dispatch keypress "
+ "event because received a control character input without WM_KEYDOWN",
+ this));
+ return false;
+ }
+
+ // XXXmnakano I think that if mMsg is WM_CHAR, i.e., it comes without
+ // preceding WM_KEYDOWN, we should should dispatch composition
+ // events instead of eKeyPress because they are not caused by
+ // actual keyboard operation.
+
+ // First, handle normal text input or non-printable key case here.
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
+ if (IsEnterKeyPressCharMessage(aCharMsg)) {
+ keypressEvent.mKeyCode = NS_VK_RETURN;
+ } else {
+ keypressEvent.mCharCode = static_cast<uint32_t>(aCharMsg.wParam);
+ }
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleCharMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure",
+ this));
+ return true;
+ }
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::HandleCharMessage(), initializing keypress "
+ "event...",
+ this));
+
+ ModifierKeyState modKeyState(mModKeyState);
+ // When AltGr is pressed, both Alt and Ctrl are active. However, when they
+ // are active, TextEditor won't treat the keypress event as inputting a
+ // character. Therefore, when AltGr is pressed and the key tries to input
+ // a character, let's set them to false.
+ if (modKeyState.IsControl() && modKeyState.IsAlt() &&
+ IsPrintableCharMessage(aCharMsg)) {
+ modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
+ }
+ nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), dispatching keypress event...",
+ this));
+ bool dispatched = mDispatcher->MaybeDispatchKeypressEvents(
+ keypressEvent, status, const_cast<NativeKey*>(this));
+ if (aEventDispatched) {
+ *aEventDispatched = dispatched;
+ }
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), keypress event caused "
+ "destroying the widget",
+ this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), dispatched keypress event, "
+ "dispatched=%s, consumed=%s",
+ this, GetBoolName(dispatched), GetBoolName(consumed)));
+ return consumed;
+}
+
+bool NativeKey::HandleKeyUpMessage(bool* aEventDispatched) const {
+ MOZ_ASSERT(IsKeyUpMessage());
+
+ if (aEventDispatched) {
+ *aEventDispatched = false;
+ }
+
+ // If the key combinations is reserved by the system, we shouldn't dispatch
+ // eKeyUp event for it and passes the message to next wndproc.
+ if (IsReservedBySystem()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), doesn't dispatch keyup "
+ "event because the key combination is reserved by the system",
+ this));
+ return false;
+ }
+
+ // If the widget has gone, we should do nothing.
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::HandleKeyUpMessage(), WARNING, not handled due to "
+ "destroyed the widget",
+ this));
+ return false;
+ }
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleKeyUpMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure",
+ this));
+ return true;
+ }
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::HandleKeyUpMessage(), initializing keyup event...",
+ this));
+ EventMessage keyUpMessage = IsKeyMessageOnPlugin() ? eKeyUpOnPlugin : eKeyUp;
+ WidgetKeyboardEvent keyupEvent(true, keyUpMessage, mWidget);
+ nsEventStatus status = InitKeyEvent(keyupEvent, mModKeyState);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), dispatching keyup event...",
+ this));
+ bool dispatched = mDispatcher->DispatchKeyboardEvent(
+ keyUpMessage, keyupEvent, status, const_cast<NativeKey*>(this));
+ if (aEventDispatched) {
+ *aEventDispatched = dispatched;
+ }
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), keyup event caused "
+ "destroying the widget",
+ this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), dispatched keyup event, "
+ "dispatched=%s, consumed=%s",
+ this, GetBoolName(dispatched), GetBoolName(consumed)));
+ return consumed;
+}
+
+bool NativeKey::NeedsToHandleWithoutFollowingCharMessages() const {
+ MOZ_ASSERT(IsKeyDownMessage());
+
+ // We cannot know following char messages of key messages in a plugin
+ // process. So, let's compute the character to be inputted with every
+ // printable key should be computed with the keyboard layout.
+ if (IsKeyMessageOnPlugin()) {
+ return true;
+ }
+
+ // If the key combination is reserved by the system, the caller shouldn't
+ // do anything with following WM_*CHAR messages. So, let's return true here.
+ if (IsReservedBySystem()) {
+ return true;
+ }
+
+ // If the keydown message is generated for inputting some Unicode characters
+ // via SendInput() API, we need to handle it only with WM_*CHAR messages.
+ if (mVirtualKeyCode == VK_PACKET) {
+ return false;
+ }
+
+ // If following char message is for a control character, it should be handled
+ // without WM_CHAR message. This is typically Ctrl + [a-z].
+ if (mFollowingCharMsgs.Length() == 1 &&
+ IsControlCharMessage(mFollowingCharMsgs[0])) {
+ return true;
+ }
+
+ // If keydown message is followed by WM_CHAR or WM_SYSCHAR whose wParam isn't
+ // a control character, we should dispatch keypress event with the char
+ // message even with any modifier state.
+ if (IsFollowedByPrintableCharOrSysCharMessage()) {
+ return false;
+ }
+
+ // If any modifier keys which may cause printable keys becoming non-printable
+ // are not pressed, we don't need special handling for the key.
+ // Note that if the key does not produce a character with AltGr and when
+ // AltGr key is pressed, we don't need to dispatch eKeyPress event for it
+ // because AltGr shouldn't be used for a modifier for a shortcut without
+ // Ctrl, Alt or Win. That means that we should treat it in same path for
+ // Shift key.
+ if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() &&
+ !mModKeyState.IsWin()) {
+ return false;
+ }
+
+ // If the key event causes dead key event, we don't need to dispatch keypress
+ // event.
+ if (mIsDeadKey && mCommittedCharsAndModifiers.IsEmpty()) {
+ return false;
+ }
+
+ // Even if the key is a printable key, it might cause non-printable character
+ // input with modifier key(s).
+ return mIsPrintableKey;
+}
+
+static nsCString GetResultOfInSendMessageEx() {
+ DWORD ret = ::InSendMessageEx(nullptr);
+ if (!ret) {
+ return "ISMEX_NOSEND"_ns;
+ }
+ nsCString result;
+ if (ret & ISMEX_CALLBACK) {
+ result = "ISMEX_CALLBACK";
+ }
+ if (ret & ISMEX_NOTIFY) {
+ if (!result.IsEmpty()) {
+ result += " | ";
+ }
+ result += "ISMEX_NOTIFY";
+ }
+ if (ret & ISMEX_REPLIED) {
+ if (!result.IsEmpty()) {
+ result += " | ";
+ }
+ result += "ISMEX_REPLIED";
+ }
+ if (ret & ISMEX_SEND) {
+ if (!result.IsEmpty()) {
+ result += " | ";
+ }
+ result += "ISMEX_SEND";
+ }
+ return result;
+}
+
+bool NativeKey::MayBeSameCharMessage(const MSG& aCharMsg1,
+ const MSG& aCharMsg2) const {
+ // NOTE: Although, we don't know when this case occurs, the scan code value
+ // in lParam may be changed from 0 to something. The changed value
+ // is different from the scan code of handling keydown message.
+ static const LPARAM kScanCodeMask = 0x00FF0000;
+ return aCharMsg1.message == aCharMsg2.message &&
+ aCharMsg1.wParam == aCharMsg2.wParam &&
+ (aCharMsg1.lParam & ~kScanCodeMask) ==
+ (aCharMsg2.lParam & ~kScanCodeMask);
+}
+
+bool NativeKey::IsSamePhysicalKeyMessage(const MSG& aKeyOrCharMsg1,
+ const MSG& aKeyOrCharMsg2) const {
+ if (NS_WARN_IF(aKeyOrCharMsg1.message < WM_KEYFIRST) ||
+ NS_WARN_IF(aKeyOrCharMsg1.message > WM_KEYLAST) ||
+ NS_WARN_IF(aKeyOrCharMsg2.message < WM_KEYFIRST) ||
+ NS_WARN_IF(aKeyOrCharMsg2.message > WM_KEYLAST)) {
+ return false;
+ }
+ return WinUtils::GetScanCode(aKeyOrCharMsg1.lParam) ==
+ WinUtils::GetScanCode(aKeyOrCharMsg2.lParam) &&
+ WinUtils::IsExtendedScanCode(aKeyOrCharMsg1.lParam) ==
+ WinUtils::IsExtendedScanCode(aKeyOrCharMsg2.lParam);
+}
+
+bool NativeKey::GetFollowingCharMessage(MSG& aCharMsg) {
+ MOZ_ASSERT(IsKeyDownMessage());
+ MOZ_ASSERT(!IsKeyMessageOnPlugin());
+
+ aCharMsg.message = WM_NULL;
+
+ if (mFakeCharMsgs) {
+ for (size_t i = 0; i < mFakeCharMsgs->Length(); i++) {
+ FakeCharMsg& fakeCharMsg = mFakeCharMsgs->ElementAt(i);
+ if (fakeCharMsg.mConsumed) {
+ continue;
+ }
+ MSG charMsg = fakeCharMsg.GetCharMsg(mMsg.hwnd);
+ fakeCharMsg.mConsumed = true;
+ if (!IsCharMessage(charMsg)) {
+ return false;
+ }
+ aCharMsg = charMsg;
+ return true;
+ }
+ return false;
+ }
+
+ // If next key message is not char message, we should give up to find a
+ // related char message for the handling keydown event for now.
+ // Note that it's possible other applications may send other key message
+ // after we call TranslateMessage(). That may cause PeekMessage() failing
+ // to get char message for the handling keydown message.
+ MSG nextKeyMsg;
+ if (!WinUtils::PeekMessage(&nextKeyMsg, mMsg.hwnd, WM_KEYFIRST, WM_KEYLAST,
+ PM_NOREMOVE | PM_NOYIELD) ||
+ !IsCharMessage(nextKeyMsg)) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Verbose,
+ ("%p NativeKey::GetFollowingCharMessage(), there are no char "
+ "messages",
+ this));
+ return false;
+ }
+ const MSG kFoundCharMsg = nextKeyMsg;
+
+ AutoRestore<MSG> saveLastRemovingMsg(mRemovingMsg);
+ mRemovingMsg = nextKeyMsg;
+
+ mReceivedMsg = sEmptyMSG;
+ AutoRestore<MSG> ensureToClearRecivedMsg(mReceivedMsg);
+
+ // On Metrofox, PeekMessage() sometimes returns WM_NULL even if we specify
+ // the message range. So, if it returns WM_NULL, we should retry to get
+ // the following char message it was found above.
+ for (uint32_t i = 0; i < 50; i++) {
+ MSG removedMsg, nextKeyMsgInAllWindows;
+ bool doCrash = false;
+ if (!WinUtils::PeekMessage(&removedMsg, mMsg.hwnd, nextKeyMsg.message,
+ nextKeyMsg.message, PM_REMOVE | PM_NOYIELD)) {
+ // We meets unexpected case. We should collect the message queue state
+ // and crash for reporting the bug.
+ doCrash = true;
+
+ // If another instance was created for the removing message during trying
+ // to remove a char message, the instance didn't handle it for preventing
+ // recursive handling. So, let's handle it in this instance.
+ if (!IsEmptyMSG(mReceivedMsg)) {
+ // If focus is moved to different window, we shouldn't handle it on
+ // the widget. Let's discard it for now.
+ if (mReceivedMsg.hwnd != nextKeyMsg.hwnd) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, received a "
+ "char message during removing it from the queue, but it's for "
+ "different window, mReceivedMsg=%s, nextKeyMsg=%s, "
+ "kFoundCharMsg=%s",
+ this, ToString(mReceivedMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ // There might still exist char messages, the loop of calling
+ // this method should be continued.
+ aCharMsg.message = WM_NULL;
+ return true;
+ }
+ // Even if the received message is different from what we tried to
+ // remove from the queue, let's take the received message as a part of
+ // the result of this key sequence.
+ if (mReceivedMsg.message != nextKeyMsg.message ||
+ mReceivedMsg.wParam != nextKeyMsg.wParam ||
+ mReceivedMsg.lParam != nextKeyMsg.lParam) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, received a "
+ "char message during removing it from the queue, but it's "
+ "differnt from what trying to remove from the queue, "
+ "aCharMsg=%s, nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(mReceivedMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ } else {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Verbose,
+ ("%p NativeKey::GetFollowingCharMessage(), succeeded to "
+ "retrieve "
+ "next char message via another instance, aCharMsg=%s, "
+ "kFoundCharMsg=%s",
+ this, ToString(mReceivedMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ }
+ aCharMsg = mReceivedMsg;
+ return true;
+ }
+
+ // The char message is redirected to different thread's window by focus
+ // move or something or just cancelled by external application.
+ if (!WinUtils::PeekMessage(&nextKeyMsgInAllWindows, 0, WM_KEYFIRST,
+ WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD)) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, but it's already gone from all message "
+ "queues, nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ return true; // XXX should return false in this case
+ }
+ // The next key message is redirected to different window created by our
+ // thread, we should do nothing because we must not have focus.
+ if (nextKeyMsgInAllWindows.hwnd != mMsg.hwnd) {
+ aCharMsg = nextKeyMsgInAllWindows;
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, but found in another message queue, "
+ "nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(nextKeyMsgInAllWindows).get(),
+ ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ return true;
+ }
+ // If next key message becomes non-char message, this key operation
+ // may have already been consumed or canceled.
+ if (!IsCharMessage(nextKeyMsgInAllWindows)) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message and next key message becomes non-char "
+ "message, nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, "
+ "kFoundCharMsg=%s",
+ this, ToString(nextKeyMsgInAllWindows).get(),
+ ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ // If next key message is still a char message but different key message,
+ // we should treat current key operation is consumed or canceled and
+ // next char message should be handled as an orphan char message later.
+ if (!IsSamePhysicalKeyMessage(nextKeyMsgInAllWindows, kFoundCharMsg)) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message and next key message becomes differnt "
+ "key's "
+ "char message, nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, "
+ "kFoundCharMsg=%s",
+ this, ToString(nextKeyMsgInAllWindows).get(),
+ ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ // If next key message is still a char message but the message is changed,
+ // we should retry to remove the new message with PeekMessage() again.
+ if (nextKeyMsgInAllWindows.message != nextKeyMsg.message) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message due to message change, let's retry to "
+ "remove the message with newly found char message, ",
+ "nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, kFoundCharMsg=%s", this,
+ ToString(nextKeyMsgInAllWindows).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ nextKeyMsg = nextKeyMsgInAllWindows;
+ continue;
+ }
+ // If there is still existing a char message caused by same physical key
+ // in the queue but PeekMessage(PM_REMOVE) failed to remove it from the
+ // queue, it might be possible that the odd keyboard layout or utility
+ // hooks only PeekMessage(PM_NOREMOVE) and GetMessage(). So, let's try
+ // remove the char message with GetMessage() again.
+ // FYI: The wParam might be different from the found message, but it's
+ // okay because we assume that odd keyboard layouts return actual
+ // inputting character at removing the char message.
+ if (WinUtils::GetMessage(&removedMsg, mMsg.hwnd, nextKeyMsg.message,
+ nextKeyMsg.message)) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, but succeeded with GetMessage(), "
+ "removedMsg=%s, kFoundCharMsg=%s",
+ this, ToString(removedMsg).get(), ToString(kFoundCharMsg).get()));
+ // Cancel to crash, but we need to check the removed message value.
+ doCrash = false;
+ }
+ // If we've already removed some WM_NULL messages from the queue and
+ // the found message has already gone from the queue, let's treat the key
+ // as inputting no characters and already consumed.
+ else if (i > 0) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, but removed %d WM_NULL messages",
+ this, i));
+ // If the key is a printable key or a control key but tried to input
+ // a character, mark mCharMessageHasGone true for handling the keydown
+ // event as inputting empty string.
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::GetFollowingCharMessage(), FAILED, lost target "
+ "message to remove, nextKeyMsg=%s",
+ this, ToString(nextKeyMsg).get()));
+ }
+
+ if (doCrash) {
+ nsPrintfCString info(
+ "\nPeekMessage() failed to remove char message! "
+ "\nActive keyboard layout=0x%08X (%s), "
+ "\nHandling message: %s, InSendMessageEx()=%s, "
+ "\nFound message: %s, "
+ "\nWM_NULL has been removed: %d, "
+ "\nNext key message in all windows: %s, "
+ "time=%d, ",
+ KeyboardLayout::GetActiveLayout(),
+ KeyboardLayout::GetActiveLayoutName().get(), ToString(mMsg).get(),
+ GetResultOfInSendMessageEx().get(), ToString(kFoundCharMsg).get(), i,
+ ToString(nextKeyMsgInAllWindows).get(), nextKeyMsgInAllWindows.time);
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ MSG nextMsg;
+ if (WinUtils::PeekMessage(&nextMsg, 0, 0, 0, PM_NOREMOVE | PM_NOYIELD)) {
+ nsPrintfCString info("\nNext message in all windows: %s, time=%d",
+ ToString(nextMsg).get(), nextMsg.time);
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ } else {
+ CrashReporter::AppendAppNotesToCrashReport(
+ "\nThere is no message in any window"_ns);
+ }
+
+ MOZ_CRASH("We lost the following char message");
+ }
+
+ // We're still not sure why ::PeekMessage() may return WM_NULL even with
+ // its first message and its last message are same message. However,
+ // at developing Metrofox, we met this case even with usual keyboard
+ // layouts. So, it might be possible in desktop application or it really
+ // occurs with some odd keyboard layouts which perhaps hook API.
+ if (removedMsg.message == WM_NULL) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, instead, removed WM_NULL message, ",
+ "removedMsg=%s", this, ToString(removedMsg).get()));
+ // Check if there is the message which we're trying to remove.
+ MSG newNextKeyMsg;
+ if (!WinUtils::PeekMessage(&newNextKeyMsg, mMsg.hwnd, WM_KEYFIRST,
+ WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD)) {
+ // If there is no key message, we should mark this keydown as consumed
+ // because the key operation may have already been handled or canceled.
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message because it's gone during removing it from "
+ "the queue, nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ if (!IsCharMessage(newNextKeyMsg)) {
+ // If next key message becomes a non-char message, we should mark this
+ // keydown as consumed because the key operation may have already been
+ // handled or canceled.
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message because it's gone during removing it from "
+ "the queue, nextKeyMsg=%s, newNextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(nextKeyMsg).get(), ToString(newNextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::GetFollowingCharMessage(), there is the message "
+ "which is being tried to be removed from the queue, trying again...",
+ this));
+ continue;
+ }
+
+ // Typically, this case occurs with WM_DEADCHAR. If the removed message's
+ // wParam becomes 0, that means that the key event shouldn't cause text
+ // input. So, let's ignore the strange char message.
+ if (removedMsg.message == nextKeyMsg.message && !removedMsg.wParam) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to "
+ "remove a char message, but the removed message's wParam is 0, "
+ "removedMsg=%s",
+ this, ToString(removedMsg).get()));
+ return false;
+ }
+
+ // This is normal case.
+ if (MayBeSameCharMessage(removedMsg, nextKeyMsg)) {
+ aCharMsg = removedMsg;
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Verbose,
+ ("%p NativeKey::GetFollowingCharMessage(), succeeded to retrieve "
+ "next char message, aCharMsg=%s",
+ this, ToString(aCharMsg).get()));
+ return true;
+ }
+
+ // Even if removed message is different char message from the found char
+ // message, when the scan code is same, we can assume that the message
+ // is overwritten by somebody who hooks API. See bug 1336028 comment 0 for
+ // the possible scenarios.
+ if (IsCharMessage(removedMsg) &&
+ IsSamePhysicalKeyMessage(removedMsg, nextKeyMsg)) {
+ aCharMsg = removedMsg;
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to "
+ "remove a char message, but the removed message was changed from "
+ "the found message except their scancode, aCharMsg=%s, "
+ "nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(aCharMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ return true;
+ }
+
+ // When found message's wParam is 0 and its scancode is 0xFF, we may remove
+ // usual char message actually. In such case, we should use the removed
+ // char message.
+ if (IsCharMessage(removedMsg) && !nextKeyMsg.wParam &&
+ WinUtils::GetScanCode(nextKeyMsg.lParam) == 0xFF) {
+ aCharMsg = removedMsg;
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to "
+ "remove a char message, but the removed message was changed from "
+ "the found message but the found message was odd, so, ignoring the "
+ "odd found message and respecting the removed message, aCharMsg=%s, "
+ "nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(aCharMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ return true;
+ }
+
+ // NOTE: Although, we don't know when this case occurs, the scan code value
+ // in lParam may be changed from 0 to something. The changed value
+ // is different from the scan code of handling keydown message.
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::GetFollowingCharMessage(), FAILED, removed message "
+ "is really different from what we have already found, removedMsg=%s, "
+ "nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(removedMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ nsPrintfCString info(
+ "\nPeekMessage() removed unexpcted char message! "
+ "\nActive keyboard layout=0x%08X (%s), "
+ "\nHandling message: %s, InSendMessageEx()=%s, "
+ "\nFound message: %s, "
+ "\nRemoved message: %s, ",
+ KeyboardLayout::GetActiveLayout(),
+ KeyboardLayout::GetActiveLayoutName().get(), ToString(mMsg).get(),
+ GetResultOfInSendMessageEx().get(), ToString(kFoundCharMsg).get(),
+ ToString(removedMsg).get());
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ // What's the next key message?
+ MSG nextKeyMsgAfter;
+ if (WinUtils::PeekMessage(&nextKeyMsgAfter, mMsg.hwnd, WM_KEYFIRST,
+ WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD)) {
+ nsPrintfCString info(
+ "\nNext key message after unexpected char message "
+ "removed: %s, ",
+ ToString(nextKeyMsgAfter).get());
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ } else {
+ CrashReporter::AppendAppNotesToCrashReport(
+ nsLiteralCString("\nThere is no key message after unexpected char "
+ "message removed, "));
+ }
+ // Another window has a key message?
+ if (WinUtils::PeekMessage(&nextKeyMsgInAllWindows, 0, WM_KEYFIRST,
+ WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD)) {
+ nsPrintfCString info("\nNext key message in all windows: %s.",
+ ToString(nextKeyMsgInAllWindows).get());
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ } else {
+ CrashReporter::AppendAppNotesToCrashReport(
+ "\nThere is no key message in any windows."_ns);
+ }
+
+ MOZ_CRASH("PeekMessage() removed unexpected message");
+ }
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::GetFollowingCharMessage(), FAILED, removed messages "
+ "are all WM_NULL, nextKeyMsg=%s",
+ this, ToString(nextKeyMsg).get()));
+ nsPrintfCString info(
+ "\nWe lost following char message! "
+ "\nActive keyboard layout=0x%08X (%s), "
+ "\nHandling message: %s, InSendMessageEx()=%s, \n"
+ "Found message: %s, removed a lot of WM_NULL",
+ KeyboardLayout::GetActiveLayout(),
+ KeyboardLayout::GetActiveLayoutName().get(), ToString(mMsg).get(),
+ GetResultOfInSendMessageEx().get(), ToString(kFoundCharMsg).get());
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ MOZ_CRASH("We lost the following char message");
+ return false;
+}
+
+void NativeKey::ComputeInputtingStringWithKeyboardLayout() {
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+
+ if (KeyboardLayout::IsPrintableCharKey(mVirtualKeyCode) ||
+ mCharMessageHasGone) {
+ mInputtingStringAndModifiers = mCommittedCharsAndModifiers;
+ } else {
+ mInputtingStringAndModifiers.Clear();
+ }
+ mShiftedString.Clear();
+ mUnshiftedString.Clear();
+ mShiftedLatinChar = mUnshiftedLatinChar = 0;
+
+ // XXX How about when Win key is pressed?
+ if (mModKeyState.IsControl() == mModKeyState.IsAlt()) {
+ return;
+ }
+
+ // If user is inputting a Unicode character with typing Alt + Numpad
+ // keys, we shouldn't set alternative key codes because the key event
+ // shouldn't match with a mnemonic. However, we should set only
+ // mUnshiftedString for keeping traditional behavior at WM_SYSKEYDOWN.
+ // FYI: I guess that it's okay that mUnshiftedString stays empty. So,
+ // if its value breaks something, you must be able to just return here.
+ if (MaybeTypingUnicodeScalarValue()) {
+ if (!mCommittedCharsAndModifiers.IsEmpty()) {
+ MOZ_ASSERT(mMsg.message == WM_SYSKEYDOWN);
+ char16_t num = mCommittedCharsAndModifiers.CharAt(0);
+ MOZ_ASSERT(num >= '0' && num <= '9');
+ mUnshiftedString.Append(num, MODIFIER_NONE);
+ return;
+ }
+ // If user presses a function key without NumLock or 3rd party utility
+ // synthesizes a function key on numpad, we should handle it as-is because
+ // the user's intention may be performing `Alt` + foo.
+ MOZ_ASSERT(!KeyboardLayout::IsPrintableCharKey(mVirtualKeyCode));
+ return;
+ }
+
+ ModifierKeyState capsLockState(mModKeyState.GetModifiers() &
+ MODIFIER_CAPSLOCK);
+
+ mUnshiftedString =
+ keyboardLayout->GetUniCharsAndModifiers(mVirtualKeyCode, capsLockState);
+ capsLockState.Set(MODIFIER_SHIFT);
+ mShiftedString =
+ keyboardLayout->GetUniCharsAndModifiers(mVirtualKeyCode, capsLockState);
+
+ // The current keyboard cannot input alphabets or numerics,
+ // we should append them for Shortcut/Access keys.
+ // E.g., for Cyrillic keyboard layout.
+ capsLockState.Unset(MODIFIER_SHIFT);
+ WidgetUtils::GetLatinCharCodeForKeyCode(
+ mDOMKeyCode, capsLockState.GetModifiers(), &mUnshiftedLatinChar,
+ &mShiftedLatinChar);
+
+ // If the mShiftedLatinChar isn't 0, the key code is NS_VK_[A-Z].
+ if (mShiftedLatinChar) {
+ // If the produced characters of the key on current keyboard layout
+ // are same as computed Latin characters, we shouldn't append the
+ // Latin characters to alternativeCharCode.
+ if (mUnshiftedLatinChar == mUnshiftedString.CharAt(0) &&
+ mShiftedLatinChar == mShiftedString.CharAt(0)) {
+ mShiftedLatinChar = mUnshiftedLatinChar = 0;
+ }
+ } else if (mUnshiftedLatinChar) {
+ // If the mShiftedLatinChar is 0, the mKeyCode doesn't produce
+ // alphabet character. At that time, the character may be produced
+ // with Shift key. E.g., on French keyboard layout, NS_VK_PERCENT
+ // key produces LATIN SMALL LETTER U WITH GRAVE (U+00F9) without
+ // Shift key but with Shift key, it produces '%'.
+ // If the mUnshiftedLatinChar is produced by the key on current
+ // keyboard layout, we shouldn't append it to alternativeCharCode.
+ if (mUnshiftedLatinChar == mUnshiftedString.CharAt(0) ||
+ mUnshiftedLatinChar == mShiftedString.CharAt(0)) {
+ mUnshiftedLatinChar = 0;
+ }
+ }
+
+ if (!mModKeyState.IsControl()) {
+ return;
+ }
+
+ // If the mCharCode is not ASCII character, we should replace the
+ // mCharCode with ASCII character only when Ctrl is pressed.
+ // But don't replace the mCharCode when the mCharCode is not same as
+ // unmodified characters. In such case, Ctrl is sometimes used for a
+ // part of character inputting key combination like Shift.
+ uint32_t ch =
+ mModKeyState.IsShift() ? mShiftedLatinChar : mUnshiftedLatinChar;
+ if (!ch) {
+ return;
+ }
+ if (mInputtingStringAndModifiers.IsEmpty() ||
+ mInputtingStringAndModifiers.UniCharsCaseInsensitiveEqual(
+ mModKeyState.IsShift() ? mShiftedString : mUnshiftedString)) {
+ mInputtingStringAndModifiers.Clear();
+ mInputtingStringAndModifiers.Append(ch, mModKeyState.GetModifiers());
+ }
+}
+
+bool NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages() const {
+ MOZ_ASSERT(IsKeyDownMessage());
+ MOZ_ASSERT(IsFollowedByPrintableCharOrSysCharMessage());
+ MOZ_ASSERT(!mWidget->Destroyed());
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "FAILED due to BeginNativeInputTransaction() failure",
+ this));
+ return true;
+ }
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "initializing keypress event...",
+ this));
+ ModifierKeyState modKeyState(mModKeyState);
+ if (mCanIgnoreModifierStateAtKeyPress && IsFollowedByPrintableCharMessage()) {
+ // If eKeyPress event should cause inputting text in focused editor,
+ // we need to remove Alt and Ctrl state.
+ modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
+ }
+ // We don't need to send char message here if there are two or more retrieved
+ // messages because we need to set each message to each eKeyPress event.
+ bool needsCallback = mFollowingCharMsgs.Length() > 1;
+ nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "dispatching keypress event(s)...",
+ this));
+ bool dispatched = mDispatcher->MaybeDispatchKeypressEvents(
+ keypressEvent, status, const_cast<NativeKey*>(this), needsCallback);
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "keypress event(s) caused destroying the widget",
+ this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "dispatched keypress event(s), dispatched=%s, consumed=%s",
+ this, GetBoolName(dispatched), GetBoolName(consumed)));
+ return consumed;
+}
+
+bool NativeKey::DispatchKeyPressEventsWithoutCharMessage() const {
+ MOZ_ASSERT(IsKeyDownMessage());
+ MOZ_ASSERT(!mIsDeadKey || !mCommittedCharsAndModifiers.IsEmpty());
+ MOZ_ASSERT(!mWidget->Destroyed());
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), "
+ "FAILED due "
+ "to BeginNativeInputTransaction() failure",
+ this));
+ return true;
+ }
+
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
+ if (mInputtingStringAndModifiers.IsEmpty() && mShiftedString.IsEmpty() &&
+ mUnshiftedString.IsEmpty()) {
+ keypressEvent.mKeyCode = mDOMKeyCode;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), "
+ "initializing "
+ "keypress event...",
+ this));
+ nsEventStatus status = InitKeyEvent(keypressEvent, mModKeyState);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), "
+ "dispatching "
+ "keypress event(s)...",
+ this));
+ bool dispatched = mDispatcher->MaybeDispatchKeypressEvents(
+ keypressEvent, status, const_cast<NativeKey*>(this));
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), "
+ "keypress event(s) caused destroying the widget",
+ this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), dispatched "
+ "keypress event(s), dispatched=%s, consumed=%s",
+ this, GetBoolName(dispatched), GetBoolName(consumed)));
+ return consumed;
+}
+
+void NativeKey::WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndex) {
+ // If it's an eKeyPress event and it's generated from retrieved char message,
+ // we need to set raw message information for plugins.
+ if (aKeyboardEvent.mMessage == eKeyPress &&
+ IsFollowedByPrintableCharOrSysCharMessage()) {
+ MOZ_RELEASE_ASSERT(aIndex < mCommittedCharsAndModifiers.Length());
+ uint32_t foundPrintableCharMessages = 0;
+ for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
+ if (!IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) {
+ // XXX Should we dispatch a plugin event for WM_*DEADCHAR messages and
+ // WM_CHAR with a control character here? But we're not sure
+ // how can we create such message queue (i.e., WM_CHAR or
+ // WM_SYSCHAR with a printable character and such message are
+ // generated by a keydown). So, let's ignore such case until
+ // we'd get some bug reports.
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), WARNING, "
+ "ignoring %uth message due to non-printable char message, %s",
+ this, i + 1, ToString(mFollowingCharMsgs[i]).get()));
+ continue;
+ }
+ if (foundPrintableCharMessages++ == aIndex) {
+ // Found message which caused the eKeyPress event. Let's set the
+ // message for plugin if it's necessary.
+ break;
+ }
+ }
+ // Set modifier state from mCommittedCharsAndModifiers because some of them
+ // might be different. For example, Shift key was pressed at inputting
+ // dead char but Shift key was released before inputting next character.
+ if (mCanIgnoreModifierStateAtKeyPress) {
+ ModifierKeyState modKeyState(mModKeyState);
+ modKeyState.Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT |
+ MODIFIER_ALTGRAPH | MODIFIER_CAPSLOCK);
+ modKeyState.Set(mCommittedCharsAndModifiers.ModifiersAt(aIndex));
+ modKeyState.InitInputEvent(aKeyboardEvent);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), "
+ "setting %uth modifier state to %s",
+ this, aIndex + 1, ToString(modKeyState).get()));
+ }
+ }
+ size_t longestLength =
+ std::max(mInputtingStringAndModifiers.Length(),
+ std::max(mShiftedString.Length(), mUnshiftedString.Length()));
+ size_t skipUniChars = longestLength - mInputtingStringAndModifiers.Length();
+ size_t skipShiftedChars = longestLength - mShiftedString.Length();
+ size_t skipUnshiftedChars = longestLength - mUnshiftedString.Length();
+ if (aIndex >= longestLength) {
+ MOZ_LOG(
+ sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), does nothing for %uth "
+ "%s event",
+ this, aIndex + 1, ToChar(aKeyboardEvent.mMessage)));
+ return;
+ }
+
+ // Check if aKeyboardEvent is the last event for a key press.
+ // So, if it's not an eKeyPress event, it's always the last event.
+ // Otherwise, check if the index is the last character of
+ // mCommittedCharsAndModifiers.
+ bool isLastIndex = aKeyboardEvent.mMessage != eKeyPress ||
+ mCommittedCharsAndModifiers.IsEmpty() ||
+ mCommittedCharsAndModifiers.Length() - 1 == aIndex;
+
+ nsTArray<AlternativeCharCode>& altArray =
+ aKeyboardEvent.mAlternativeCharCodes;
+
+ // Set charCode and adjust modifier state for every eKeyPress event.
+ // This is not necessary for the other keyboard events because the other
+ // keyboard events shouldn't have non-zero charCode value and should have
+ // current modifier state.
+ if (aKeyboardEvent.mMessage == eKeyPress && skipUniChars <= aIndex) {
+ // XXX Modifying modifier state of aKeyboardEvent is illegal, but no way
+ // to set different modifier state per keypress event except this
+ // hack. Note that ideally, dead key should cause composition events
+ // instead of keypress events, though.
+ if (aIndex - skipUniChars < mInputtingStringAndModifiers.Length()) {
+ ModifierKeyState modKeyState(mModKeyState);
+ // If key in combination with Alt and/or Ctrl produces a different
+ // character than without them then do not report these flags
+ // because it is separate keyboard layout shift state. If dead-key
+ // and base character does not produce a valid composite character
+ // then both produced dead-key character and following base
+ // character may have different modifier flags, too.
+ modKeyState.Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT |
+ MODIFIER_ALTGRAPH | MODIFIER_CAPSLOCK);
+ modKeyState.Set(
+ mInputtingStringAndModifiers.ModifiersAt(aIndex - skipUniChars));
+ modKeyState.InitInputEvent(aKeyboardEvent);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), "
+ "setting %uth modifier state to %s",
+ this, aIndex + 1, ToString(modKeyState).get()));
+ }
+ uint16_t uniChar =
+ mInputtingStringAndModifiers.CharAt(aIndex - skipUniChars);
+
+ // The mCharCode was set from mKeyValue. However, for example, when Ctrl key
+ // is pressed, its value should indicate an ASCII character for backward
+ // compatibility rather than inputting character without the modifiers.
+ // Therefore, we need to modify mCharCode value here.
+ aKeyboardEvent.SetCharCode(uniChar);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), "
+ "setting %uth charCode to %s",
+ this, aIndex + 1, GetCharacterCodeName(uniChar).get()));
+ }
+
+ // We need to append alterntaive charCode values:
+ // - if the event is eKeyPress, we need to append for the index because
+ // eKeyPress event is dispatched for every character inputted by a
+ // key press.
+ // - if the event is not eKeyPress, we need to append for all characters
+ // inputted by the key press because the other keyboard events (e.g.,
+ // eKeyDown are eKeyUp) are fired only once for a key press.
+ size_t count;
+ if (aKeyboardEvent.mMessage == eKeyPress) {
+ // Basically, append alternative charCode values only for the index.
+ count = 1;
+ // However, if it's the last eKeyPress event but different shift state
+ // can input longer string, the last eKeyPress event should have all
+ // remaining alternative charCode values.
+ if (isLastIndex) {
+ count = longestLength - aIndex;
+ }
+ } else {
+ count = longestLength;
+ }
+ for (size_t i = 0; i < count; ++i) {
+ uint16_t shiftedChar = 0, unshiftedChar = 0;
+ if (skipShiftedChars <= aIndex + i) {
+ shiftedChar = mShiftedString.CharAt(aIndex + i - skipShiftedChars);
+ }
+ if (skipUnshiftedChars <= aIndex + i) {
+ unshiftedChar = mUnshiftedString.CharAt(aIndex + i - skipUnshiftedChars);
+ }
+
+ if (shiftedChar || unshiftedChar) {
+ AlternativeCharCode chars(unshiftedChar, shiftedChar);
+ altArray.AppendElement(chars);
+ }
+
+ if (!isLastIndex) {
+ continue;
+ }
+
+ if (mUnshiftedLatinChar || mShiftedLatinChar) {
+ AlternativeCharCode chars(mUnshiftedLatinChar, mShiftedLatinChar);
+ altArray.AppendElement(chars);
+ }
+
+ // Typically, following virtual keycodes are used for a key which can
+ // input the character. However, these keycodes are also used for
+ // other keys on some keyboard layout. E.g., in spite of Shift+'1'
+ // inputs '+' on Thai keyboard layout, a key which is at '=/+'
+ // key on ANSI keyboard layout is VK_OEM_PLUS. Native applications
+ // handle it as '+' key if Ctrl key is pressed.
+ char16_t charForOEMKeyCode = 0;
+ switch (mVirtualKeyCode) {
+ case VK_OEM_PLUS:
+ charForOEMKeyCode = '+';
+ break;
+ case VK_OEM_COMMA:
+ charForOEMKeyCode = ',';
+ break;
+ case VK_OEM_MINUS:
+ charForOEMKeyCode = '-';
+ break;
+ case VK_OEM_PERIOD:
+ charForOEMKeyCode = '.';
+ break;
+ }
+ if (charForOEMKeyCode && charForOEMKeyCode != mUnshiftedString.CharAt(0) &&
+ charForOEMKeyCode != mShiftedString.CharAt(0) &&
+ charForOEMKeyCode != mUnshiftedLatinChar &&
+ charForOEMKeyCode != mShiftedLatinChar) {
+ AlternativeCharCode OEMChars(charForOEMKeyCode, charForOEMKeyCode);
+ altArray.AppendElement(OEMChars);
+ }
+ }
+}
+
+/*****************************************************************************
+ * mozilla::widget::KeyboardLayout
+ *****************************************************************************/
+
+KeyboardLayout* KeyboardLayout::sInstance = nullptr;
+nsIUserIdleServiceInternal* KeyboardLayout::sIdleService = nullptr;
+
+// This log is very noisy if you don't want to retrieve the mapping table
+// of specific keyboard layout. LogLevel::Debug and LogLevel::Verbose are
+// used to log the layout mapping. If you need to log some behavior of
+// KeyboardLayout class, you should use LogLevel::Info or lower level.
+LazyLogModule sKeyboardLayoutLogger("KeyboardLayoutWidgets");
+
+// static
+KeyboardLayout* KeyboardLayout::GetInstance() {
+ if (!sInstance) {
+ sInstance = new KeyboardLayout();
+ nsCOMPtr<nsIUserIdleServiceInternal> idleService =
+ do_GetService("@mozilla.org/widget/useridleservice;1");
+ // The refcount will be decreased at shut down.
+ sIdleService = idleService.forget().take();
+ }
+ return sInstance;
+}
+
+// static
+void KeyboardLayout::Shutdown() {
+ delete sInstance;
+ sInstance = nullptr;
+ NS_IF_RELEASE(sIdleService);
+}
+
+// static
+void KeyboardLayout::NotifyIdleServiceOfUserActivity() {
+ sIdleService->ResetIdleTimeOut(0);
+}
+
+KeyboardLayout::KeyboardLayout()
+ : mKeyboardLayout(0),
+ mIsOverridden(false),
+ mIsPendingToRestoreKeyboardLayout(false),
+ mHasAltGr(false) {
+ mDeadKeyTableListHead = nullptr;
+ // A dead key sequence should be made from up to 5 keys. Therefore, 4 is
+ // enough and makes sense because the item is uint8_t.
+ // (Although, even if it's possible to be 6 keys or more in a sequence,
+ // this array will be re-allocated).
+ mActiveDeadKeys.SetCapacity(4);
+ mDeadKeyShiftStates.SetCapacity(4);
+
+ // NOTE: LoadLayout() should be called via OnLayoutChange().
+}
+
+KeyboardLayout::~KeyboardLayout() { ReleaseDeadKeyTables(); }
+
+bool KeyboardLayout::IsPrintableCharKey(uint8_t aVirtualKey) {
+ return GetKeyIndex(aVirtualKey) >= 0;
+}
+
+WORD KeyboardLayout::ComputeScanCodeForVirtualKeyCode(
+ uint8_t aVirtualKeyCode) const {
+ return static_cast<WORD>(
+ ::MapVirtualKeyEx(aVirtualKeyCode, MAPVK_VK_TO_VSC, GetLayout()));
+}
+
+bool KeyboardLayout::IsDeadKey(uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const {
+ int32_t virtualKeyIndex = GetKeyIndex(aVirtualKey);
+
+ // XXX KeyboardLayout class doesn't support unusual keyboard layout which
+ // maps some function keys as dead keys.
+ if (virtualKeyIndex < 0) {
+ return false;
+ }
+
+ return mVirtualKeys[virtualKeyIndex].IsDeadKey(
+ VirtualKey::ModifiersToShiftState(aModKeyState.GetModifiers()));
+}
+
+bool KeyboardLayout::IsSysKey(uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const {
+ // If Alt key is not pressed, it's never a system key combination.
+ // Additionally, if Ctrl key is pressed, it's never a system key combination
+ // too.
+ // FYI: Windows logo key state won't affect if it's a system key.
+ if (!aModKeyState.IsAlt() || aModKeyState.IsControl()) {
+ return false;
+ }
+
+ int32_t virtualKeyIndex = GetKeyIndex(aVirtualKey);
+ if (virtualKeyIndex < 0) {
+ return true;
+ }
+
+ UniCharsAndModifiers inputCharsAndModifiers =
+ GetUniCharsAndModifiers(aVirtualKey, aModKeyState);
+ if (inputCharsAndModifiers.IsEmpty()) {
+ return true;
+ }
+
+ // If the Alt key state isn't consumed, that means that the key with Alt
+ // doesn't cause text input. So, the combination is a system key.
+ return !!(inputCharsAndModifiers.ModifiersAt(0) & MODIFIER_ALT);
+}
+
+void KeyboardLayout::InitNativeKey(NativeKey& aNativeKey) {
+ if (mIsPendingToRestoreKeyboardLayout) {
+ LoadLayout(::GetKeyboardLayout(0));
+ }
+
+ // If the aNativeKey is initialized with WM_CHAR, the key information
+ // should be discarded because mKeyValue should have the string to be
+ // inputted.
+ if (aNativeKey.mMsg.message == WM_CHAR) {
+ char16_t ch = static_cast<char16_t>(aNativeKey.mMsg.wParam);
+ // But don't set key value as printable key if the character is a control
+ // character such as 0x0D at pressing Enter key.
+ if (!NativeKey::IsControlChar(ch)) {
+ aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ Modifiers modifiers =
+ aNativeKey.GetModifiers() & ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ aNativeKey.mCommittedCharsAndModifiers.Append(ch, modifiers);
+ return;
+ }
+ }
+
+ // If the aNativeKey is in a sequence to input a Unicode character with
+ // Alt + numpad keys, we should just set the number as the inputting charcter.
+ // Note that we should compute the key value from the virtual key code
+ // because they may be mapped to alphabets, but they should be treated as
+ // Alt + [0-9] even by web apps.
+ // However, we shouldn't touch the key value if given virtual key code is
+ // not a printable key because it may be synthesized by 3rd party utility
+ // or just NumLock is unlocked and user tries to use shortcut key. In the
+ // latter case, we cannot solve the conflict issue with Alt+foo shortcut key
+ // and inputting a Unicode scalar value like reported to bug 1606655, though,
+ // I have no better idea. Perhaps, `Alt` shouldn't be used for shortcut key
+ // combination on Windows.
+ if (aNativeKey.MaybeTypingUnicodeScalarValue() &&
+ KeyboardLayout::IsPrintableCharKey(aNativeKey.mVirtualKeyCode)) {
+ // If the key code value is mapped to a Numpad key, let's compute the key
+ // value with it. This is same behavior as Chrome. In strictly speaking,
+ // I think that the else block's computation is better because it seems
+ // that Windows does not refer virtual key code value, but we should avoid
+ // web-compat issues.
+ char16_t num = '0';
+ if (aNativeKey.mVirtualKeyCode >= VK_NUMPAD0 &&
+ aNativeKey.mVirtualKeyCode <= VK_NUMPAD9) {
+ num = '0' + aNativeKey.mVirtualKeyCode - VK_NUMPAD0;
+ }
+ // Otherwise, let's use fake key value for making never match with
+ // mnemonic.
+ else {
+ switch (aNativeKey.mScanCode) {
+ case 0x0052: // Numpad0
+ num = '0';
+ break;
+ case 0x004F: // Numpad1
+ num = '1';
+ break;
+ case 0x0050: // Numpad2
+ num = '2';
+ break;
+ case 0x0051: // Numpad3
+ num = '3';
+ break;
+ case 0x004B: // Numpad4
+ num = '4';
+ break;
+ case 0x004C: // Numpad5
+ num = '5';
+ break;
+ case 0x004D: // Numpad6
+ num = '6';
+ break;
+ case 0x0047: // Numpad7
+ num = '7';
+ break;
+ case 0x0048: // Numpad8
+ num = '8';
+ break;
+ case 0x0049: // Numpad9
+ num = '9';
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "IsTypingUnicodeScalarValue() must have returned true for wrong "
+ "scancode");
+ break;
+ }
+ }
+ aNativeKey.mCommittedCharsAndModifiers.Append(num,
+ aNativeKey.GetModifiers());
+ aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ return;
+ }
+
+ // When it's followed by non-dead char message(s) for printable character(s),
+ // aNativeKey should dispatch eKeyPress events for them rather than
+ // information from keyboard layout because respecting WM_(SYS)CHAR messages
+ // guarantees that we can always input characters which is expected by
+ // the user even if the user uses odd keyboard layout.
+ // Or, when it was followed by non-dead char message for a printable character
+ // but it's gone at removing the message from the queue, let's treat it
+ // as a key inputting empty string.
+ if (aNativeKey.IsFollowedByPrintableCharOrSysCharMessage() ||
+ aNativeKey.mCharMessageHasGone) {
+ MOZ_ASSERT(!aNativeKey.IsCharMessage(aNativeKey.mMsg));
+ if (aNativeKey.IsFollowedByPrintableCharOrSysCharMessage()) {
+ // Initialize mCommittedCharsAndModifiers with following char messages.
+ aNativeKey.InitCommittedCharsAndModifiersWithFollowingCharMessages();
+ MOZ_ASSERT(!aNativeKey.mCommittedCharsAndModifiers.IsEmpty());
+
+ // Currently, we are doing a ugly hack to keypress events to cause
+ // inputting character even if Ctrl or Alt key is pressed, that is, we
+ // remove Ctrl and Alt modifier state from keypress event. However, for
+ // example, Ctrl+Space which causes ' ' of WM_CHAR message never causes
+ // keypress event whose ctrlKey is true. For preventing this problem,
+ // we should mark as not removable if Ctrl or Alt key does not cause
+ // changing inputting character.
+ if (IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode) &&
+ (aNativeKey.IsControl() ^ aNativeKey.IsAlt())) {
+ ModifierKeyState state = aNativeKey.ModifierKeyStateRef();
+ state.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
+ UniCharsAndModifiers charsWithoutModifier =
+ GetUniCharsAndModifiers(aNativeKey.GenericVirtualKeyCode(), state);
+ aNativeKey.mCanIgnoreModifierStateAtKeyPress =
+ !charsWithoutModifier.UniCharsEqual(
+ aNativeKey.mCommittedCharsAndModifiers);
+ }
+ } else {
+ aNativeKey.mCommittedCharsAndModifiers.Clear();
+ }
+ aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+
+ // If it's not in dead key sequence, we don't need to do anymore here.
+ if (!IsInDeadKeySequence()) {
+ return;
+ }
+
+ // If it's in dead key sequence and dead char is inputted as is, we need to
+ // set the previous modifier state which is stored when preceding dead key
+ // is pressed.
+ UniCharsAndModifiers deadChars = GetDeadUniCharsAndModifiers();
+ aNativeKey.mCommittedCharsAndModifiers.OverwriteModifiersIfBeginsWith(
+ deadChars);
+ // Finish the dead key sequence.
+ DeactivateDeadKeyState();
+ return;
+ }
+
+ // If it's a dead key, aNativeKey will be initialized by
+ // MaybeInitNativeKeyAsDeadKey().
+ if (MaybeInitNativeKeyAsDeadKey(aNativeKey)) {
+ return;
+ }
+
+ // If the key is not a usual printable key, KeyboardLayout class assume that
+ // it's not cause dead char nor printable char. Therefore, there are nothing
+ // to do here fore such keys (e.g., function keys).
+ // However, this should keep dead key state even if non-printable key is
+ // pressed during a dead key sequence.
+ if (!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode)) {
+ return;
+ }
+
+ MOZ_ASSERT(aNativeKey.mOriginalVirtualKeyCode != VK_PACKET,
+ "At handling VK_PACKET, we shouldn't refer keyboard layout");
+ MOZ_ASSERT(
+ aNativeKey.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING,
+ "Printable key's key name index must be KEY_NAME_INDEX_USE_STRING");
+
+ // If it's in dead key handling and the pressed key causes a composite
+ // character, aNativeKey will be initialized by
+ // MaybeInitNativeKeyWithCompositeChar().
+ if (MaybeInitNativeKeyWithCompositeChar(aNativeKey)) {
+ return;
+ }
+
+ UniCharsAndModifiers baseChars = GetUniCharsAndModifiers(aNativeKey);
+
+ // If the key press isn't related to any dead keys, initialize aNativeKey
+ // with the characters which should be caused by the key.
+ if (!IsInDeadKeySequence()) {
+ aNativeKey.mCommittedCharsAndModifiers = baseChars;
+ return;
+ }
+
+ // If the key doesn't cause a composite character with preceding dead key,
+ // initialize aNativeKey with the dead-key character followed by current
+ // key's character.
+ UniCharsAndModifiers deadChars = GetDeadUniCharsAndModifiers();
+ aNativeKey.mCommittedCharsAndModifiers = deadChars + baseChars;
+ if (aNativeKey.IsKeyDownMessage()) {
+ DeactivateDeadKeyState();
+ }
+}
+
+bool KeyboardLayout::MaybeInitNativeKeyAsDeadKey(NativeKey& aNativeKey) {
+ // Only when it's not in dead key sequence, we can trust IsDeadKey() result.
+ if (!IsInDeadKeySequence() && !IsDeadKey(aNativeKey)) {
+ return false;
+ }
+
+ // When keydown message is followed by a dead char message, it should be
+ // initialized as dead key.
+ bool isDeadKeyDownEvent =
+ aNativeKey.IsKeyDownMessage() && aNativeKey.IsFollowedByDeadCharMessage();
+
+ // When keyup message is received, let's check if it's one of preceding
+ // dead keys because keydown message order and keyup message order may be
+ // different.
+ bool isDeadKeyUpEvent =
+ !aNativeKey.IsKeyDownMessage() &&
+ mActiveDeadKeys.Contains(aNativeKey.GenericVirtualKeyCode());
+
+ if (isDeadKeyDownEvent || isDeadKeyUpEvent) {
+ ActivateDeadKeyState(aNativeKey);
+ // Any dead key events don't generate characters. So, a dead key should
+ // cause only keydown event and keyup event whose KeyboardEvent.key
+ // values are "Dead".
+ aNativeKey.mCommittedCharsAndModifiers.Clear();
+ aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_Dead;
+ return true;
+ }
+
+ // At keydown message handling, we need to forget the first dead key
+ // because there is no guarantee coming WM_KEYUP for the second dead
+ // key before next WM_KEYDOWN. E.g., due to auto key repeat or pressing
+ // another dead key before releasing current key. Therefore, we can
+ // set only a character for current key for keyup event.
+ if (!IsInDeadKeySequence()) {
+ aNativeKey.mCommittedCharsAndModifiers =
+ GetUniCharsAndModifiers(aNativeKey);
+ return true;
+ }
+
+ // When non-printable key event comes during a dead key sequence, that must
+ // be a modifier key event. So, such events shouldn't be handled as a part
+ // of the dead key sequence.
+ if (!IsDeadKey(aNativeKey)) {
+ return false;
+ }
+
+ // FYI: Following code may run when the user doesn't input text actually
+ // but the key sequence is a dead key sequence. For example,
+ // ` -> Ctrl+` with Spanish keyboard layout. Let's keep using this
+ // complicated code for now because this runs really rarely.
+
+ // Dead key followed by another dead key may cause a composed character
+ // (e.g., "Russian - Mnemonic" keyboard layout's 's' -> 'c').
+ if (MaybeInitNativeKeyWithCompositeChar(aNativeKey)) {
+ return true;
+ }
+
+ // Otherwise, dead key followed by another dead key causes inputting both
+ // character.
+ UniCharsAndModifiers prevDeadChars = GetDeadUniCharsAndModifiers();
+ UniCharsAndModifiers newChars = GetUniCharsAndModifiers(aNativeKey);
+ // But keypress events should be fired for each committed character.
+ aNativeKey.mCommittedCharsAndModifiers = prevDeadChars + newChars;
+ if (aNativeKey.IsKeyDownMessage()) {
+ DeactivateDeadKeyState();
+ }
+ return true;
+}
+
+bool KeyboardLayout::MaybeInitNativeKeyWithCompositeChar(
+ NativeKey& aNativeKey) {
+ if (!IsInDeadKeySequence()) {
+ return false;
+ }
+
+ if (NS_WARN_IF(!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode))) {
+ return false;
+ }
+
+ UniCharsAndModifiers baseChars = GetUniCharsAndModifiers(aNativeKey);
+ if (baseChars.IsEmpty() || !baseChars.CharAt(0)) {
+ return false;
+ }
+
+ char16_t compositeChar = GetCompositeChar(baseChars.CharAt(0));
+ if (!compositeChar) {
+ return false;
+ }
+
+ // Active dead-key and base character does produce exactly one composite
+ // character.
+ aNativeKey.mCommittedCharsAndModifiers.Append(compositeChar,
+ baseChars.ModifiersAt(0));
+ if (aNativeKey.IsKeyDownMessage()) {
+ DeactivateDeadKeyState();
+ }
+ return true;
+}
+
+UniCharsAndModifiers KeyboardLayout::GetUniCharsAndModifiers(
+ uint8_t aVirtualKey, VirtualKey::ShiftState aShiftState) const {
+ UniCharsAndModifiers result;
+ int32_t key = GetKeyIndex(aVirtualKey);
+ if (key < 0) {
+ return result;
+ }
+ return mVirtualKeys[key].GetUniChars(aShiftState);
+}
+
+UniCharsAndModifiers KeyboardLayout::GetDeadUniCharsAndModifiers() const {
+ MOZ_RELEASE_ASSERT(mActiveDeadKeys.Length() == mDeadKeyShiftStates.Length());
+
+ if (NS_WARN_IF(mActiveDeadKeys.IsEmpty())) {
+ return UniCharsAndModifiers();
+ }
+
+ UniCharsAndModifiers result;
+ for (size_t i = 0; i < mActiveDeadKeys.Length(); ++i) {
+ result +=
+ GetUniCharsAndModifiers(mActiveDeadKeys[i], mDeadKeyShiftStates[i]);
+ }
+ return result;
+}
+
+char16_t KeyboardLayout::GetCompositeChar(char16_t aBaseChar) const {
+ if (NS_WARN_IF(mActiveDeadKeys.IsEmpty())) {
+ return 0;
+ }
+ // XXX Currently, we don't support computing a composite character with
+ // two or more dead keys since it needs big table for supporting
+ // long chained dead keys. However, this should be a minor bug
+ // because this runs only when the latest keydown event does not cause
+ // WM_(SYS)CHAR messages. So, when user wants to input a character,
+ // this path never runs.
+ if (mActiveDeadKeys.Length() > 1) {
+ return 0;
+ }
+ int32_t key = GetKeyIndex(mActiveDeadKeys[0]);
+ if (key < 0) {
+ return 0;
+ }
+ return mVirtualKeys[key].GetCompositeChar(mDeadKeyShiftStates[0], aBaseChar);
+}
+
+// static
+HKL KeyboardLayout::GetActiveLayout() { return GetInstance()->mKeyboardLayout; }
+
+// static
+nsCString KeyboardLayout::GetActiveLayoutName() {
+ return GetInstance()->GetLayoutName(GetActiveLayout());
+}
+
+static bool IsValidKeyboardLayoutsChild(const nsAString& aChildName) {
+ if (aChildName.Length() != 8) {
+ return false;
+ }
+ for (size_t i = 0; i < aChildName.Length(); i++) {
+ if ((aChildName[i] >= '0' && aChildName[i] <= '9') ||
+ (aChildName[i] >= 'a' && aChildName[i] <= 'f') ||
+ (aChildName[i] >= 'A' && aChildName[i] <= 'F')) {
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+nsCString KeyboardLayout::GetLayoutName(HKL aLayout) const {
+ const wchar_t kKeyboardLayouts[] =
+ L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\";
+ uint16_t language = reinterpret_cast<uintptr_t>(aLayout) & 0xFFFF;
+ uint16_t layout = (reinterpret_cast<uintptr_t>(aLayout) >> 16) & 0xFFFF;
+ // If the layout is less than 0xA000XXXX (normal keyboard layout for the
+ // language) or 0xEYYYXXXX (IMM-IME), we can retrieve its name simply.
+ if (layout < 0xA000 || (layout & 0xF000) == 0xE000) {
+ nsAutoString key(kKeyboardLayouts);
+ key.AppendPrintf("%08X", layout < 0xA000
+ ? layout
+ : reinterpret_cast<uintptr_t>(aLayout));
+ wchar_t buf[256];
+ if (NS_WARN_IF(!WinUtils::GetRegistryKey(
+ HKEY_LOCAL_MACHINE, key.get(), L"Layout Text", buf, sizeof(buf)))) {
+ return "No name or too long name"_ns;
+ }
+ return NS_ConvertUTF16toUTF8(buf);
+ }
+
+ if (NS_WARN_IF((layout & 0xF000) != 0xF000)) {
+ nsCString result;
+ result.AppendPrintf("Odd HKL: 0x%08X",
+ reinterpret_cast<uintptr_t>(aLayout));
+ return result;
+ }
+
+ // Otherwise, we need to walk the registry under "Keyboard Layouts".
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (NS_WARN_IF(!regKey)) {
+ return ""_ns;
+ }
+ nsresult rv =
+ regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
+ nsString(kKeyboardLayouts), nsIWindowsRegKey::ACCESS_READ);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return ""_ns;
+ }
+ uint32_t childCount = 0;
+ if (NS_WARN_IF(NS_FAILED(regKey->GetChildCount(&childCount))) ||
+ NS_WARN_IF(!childCount)) {
+ return ""_ns;
+ }
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsAutoString childName;
+ if (NS_WARN_IF(NS_FAILED(regKey->GetChildName(i, childName))) ||
+ !IsValidKeyboardLayoutsChild(childName)) {
+ continue;
+ }
+ uint32_t childNum = static_cast<uint32_t>(childName.ToInteger64(&rv, 16));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ // Ignore normal keyboard layouts for each language.
+ if (childNum <= 0xFFFF) {
+ continue;
+ }
+ // If it doesn't start with 'A' nor 'a', language should be matched.
+ if ((childNum & 0xFFFF) != language &&
+ (childNum & 0xF0000000) != 0xA0000000) {
+ continue;
+ }
+ // Then, the child should have "Layout Id" which is "YYY" of 0xFYYYXXXX.
+ nsAutoString key(kKeyboardLayouts);
+ key += childName;
+ wchar_t buf[256];
+ if (NS_WARN_IF(!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, key.get(),
+ L"Layout Id", buf, sizeof(buf)))) {
+ continue;
+ }
+ uint16_t layoutId = wcstol(buf, nullptr, 16);
+ if (layoutId != (layout & 0x0FFF)) {
+ continue;
+ }
+ if (NS_WARN_IF(!WinUtils::GetRegistryKey(
+ HKEY_LOCAL_MACHINE, key.get(), L"Layout Text", buf, sizeof(buf)))) {
+ continue;
+ }
+ return NS_ConvertUTF16toUTF8(buf);
+ }
+ return ""_ns;
+}
+
+void KeyboardLayout::LoadLayout(HKL aLayout) {
+ mIsPendingToRestoreKeyboardLayout = false;
+
+ if (mKeyboardLayout == aLayout) {
+ return;
+ }
+
+ mKeyboardLayout = aLayout;
+ mHasAltGr = false;
+
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Info,
+ ("KeyboardLayout::LoadLayout(aLayout=0x%08X (%s))", aLayout,
+ GetLayoutName(aLayout).get()));
+
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+
+ BYTE originalKbdState[256];
+ // Bitfield with all shift states that have at least one dead-key.
+ uint16_t shiftStatesWithDeadKeys = 0;
+ // Bitfield with all shift states that produce any possible dead-key base
+ // characters.
+ uint16_t shiftStatesWithBaseChars = 0;
+
+ mActiveDeadKeys.Clear();
+ mDeadKeyShiftStates.Clear();
+
+ ReleaseDeadKeyTables();
+
+ ::GetKeyboardState(originalKbdState);
+
+ // For each shift state gather all printable characters that are produced
+ // for normal case when no any dead-key is active.
+
+ for (VirtualKey::ShiftState shiftState = 0; shiftState < 16; shiftState++) {
+ VirtualKey::FillKbdState(kbdState, shiftState);
+ bool isAltGr = VirtualKey::IsAltGrIndex(shiftState);
+ for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) {
+ int32_t vki = GetKeyIndex(virtualKey);
+ if (vki < 0) {
+ continue;
+ }
+ NS_ASSERTION(uint32_t(vki) < ArrayLength(mVirtualKeys), "invalid index");
+ char16_t uniChars[5];
+ int32_t ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)uniChars,
+ ArrayLength(uniChars), 0, mKeyboardLayout);
+ // dead-key
+ if (ret < 0) {
+ shiftStatesWithDeadKeys |= (1 << shiftState);
+ // Repeat dead-key to deactivate it and get its character
+ // representation.
+ char16_t deadChar[2];
+ ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)deadChar,
+ ArrayLength(deadChar), 0, mKeyboardLayout);
+ NS_ASSERTION(ret == 2, "Expecting twice repeated dead-key character");
+ mVirtualKeys[vki].SetDeadChar(shiftState, deadChar[0]);
+
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Debug,
+ (" %s (%d): DeadChar(%s, %s) (ret=%d)",
+ kVirtualKeyName[virtualKey], vki,
+ GetShiftStateName(shiftState).get(),
+ GetCharacterCodeNames(deadChar, 1).get(), ret));
+ } else {
+ if (ret == 1) {
+ // dead-key can pair only with exactly one base character.
+ shiftStatesWithBaseChars |= (1 << shiftState);
+ }
+ mVirtualKeys[vki].SetNormalChars(shiftState, uniChars, ret);
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose,
+ (" %s (%d): NormalChar(%s, %s) (ret=%d)",
+ kVirtualKeyName[virtualKey], vki,
+ GetShiftStateName(shiftState).get(),
+ GetCharacterCodeNames(uniChars, ret).get(), ret));
+ }
+
+ // If the key inputs at least one character with AltGr modifier,
+ // check if AltGr changes inputting character. If it does, mark
+ // this keyboard layout has AltGr modifier actually.
+ if (!mHasAltGr && ret > 0 && isAltGr &&
+ mVirtualKeys[vki].IsChangedByAltGr(shiftState)) {
+ mHasAltGr = true;
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Info,
+ (" Found a key (%s) changed by AltGr: %s -> %s (%s) (ret=%d)",
+ kVirtualKeyName[virtualKey],
+ GetCharacterCodeNames(
+ mVirtualKeys[vki].GetNativeUniChars(
+ shiftState - VirtualKey::ShiftStateIndex::eAltGr))
+ .get(),
+ GetCharacterCodeNames(
+ mVirtualKeys[vki].GetNativeUniChars(shiftState))
+ .get(),
+ GetShiftStateName(shiftState).get(), ret));
+ }
+ }
+ }
+
+ // Now process each dead-key to find all its base characters and resulting
+ // composite characters.
+ for (VirtualKey::ShiftState shiftState = 0; shiftState < 16; shiftState++) {
+ if (!(shiftStatesWithDeadKeys & (1 << shiftState))) {
+ continue;
+ }
+
+ VirtualKey::FillKbdState(kbdState, shiftState);
+
+ for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) {
+ int32_t vki = GetKeyIndex(virtualKey);
+ if (vki >= 0 && mVirtualKeys[vki].IsDeadKey(shiftState)) {
+ DeadKeyEntry deadKeyArray[256];
+ int32_t n = GetDeadKeyCombinations(
+ virtualKey, kbdState, shiftStatesWithBaseChars, deadKeyArray,
+ ArrayLength(deadKeyArray));
+ const DeadKeyTable* dkt =
+ mVirtualKeys[vki].MatchingDeadKeyTable(deadKeyArray, n);
+ if (!dkt) {
+ dkt = AddDeadKeyTable(deadKeyArray, n);
+ }
+ mVirtualKeys[vki].AttachDeadKeyTable(shiftState, dkt);
+ }
+ }
+ }
+
+ ::SetKeyboardState(originalKbdState);
+
+ if (MOZ_LOG_TEST(sKeyboardLayoutLogger, LogLevel::Verbose)) {
+ static const UINT kExtendedScanCode[] = {0x0000, 0xE000};
+ static const UINT kMapType = MAPVK_VSC_TO_VK_EX;
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose,
+ ("Logging virtual keycode values for scancode (0x%p)...",
+ mKeyboardLayout));
+ for (uint32_t i = 0; i < ArrayLength(kExtendedScanCode); i++) {
+ for (uint32_t j = 1; j <= 0xFF; j++) {
+ UINT scanCode = kExtendedScanCode[i] + j;
+ UINT virtualKeyCode =
+ ::MapVirtualKeyEx(scanCode, kMapType, mKeyboardLayout);
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose,
+ ("0x%04X, %s", scanCode, kVirtualKeyName[virtualKeyCode]));
+ }
+ }
+ }
+
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Info,
+ (" AltGr key is %s in %s", mHasAltGr ? "found" : "not found",
+ GetLayoutName(aLayout).get()));
+}
+
+inline int32_t KeyboardLayout::GetKeyIndex(uint8_t aVirtualKey) {
+ // Currently these 68 (NS_NUM_OF_KEYS) virtual keys are assumed
+ // to produce visible representation:
+ // 0x20 - VK_SPACE ' '
+ // 0x30..0x39 '0'..'9'
+ // 0x41..0x5A 'A'..'Z'
+ // 0x60..0x69 '0'..'9' on numpad
+ // 0x6A - VK_MULTIPLY '*' on numpad
+ // 0x6B - VK_ADD '+' on numpad
+ // 0x6D - VK_SUBTRACT '-' on numpad
+ // 0x6E - VK_DECIMAL '.' on numpad
+ // 0x6F - VK_DIVIDE '/' on numpad
+ // 0x6E - VK_DECIMAL '.'
+ // 0xBA - VK_OEM_1 ';:' for US
+ // 0xBB - VK_OEM_PLUS '+' any country
+ // 0xBC - VK_OEM_COMMA ',' any country
+ // 0xBD - VK_OEM_MINUS '-' any country
+ // 0xBE - VK_OEM_PERIOD '.' any country
+ // 0xBF - VK_OEM_2 '/?' for US
+ // 0xC0 - VK_OEM_3 '`~' for US
+ // 0xC1 - VK_ABNT_C1 '/?' for Brazilian
+ // 0xC2 - VK_ABNT_C2 separator key on numpad (Brazilian or JIS for Mac)
+ // 0xDB - VK_OEM_4 '[{' for US
+ // 0xDC - VK_OEM_5 '\|' for US
+ // 0xDD - VK_OEM_6 ']}' for US
+ // 0xDE - VK_OEM_7 ''"' for US
+ // 0xDF - VK_OEM_8
+ // 0xE1 - no name
+ // 0xE2 - VK_OEM_102 '\_' for JIS
+ // 0xE3 - no name
+ // 0xE4 - no name
+
+ static const int8_t xlat[256] = {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ //-----------------------------------------------------------------------
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10
+ 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, -1, -1, -1, -1, -1, // 30
+ -1, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 40
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, -1, -1, // 50
+ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, -1, 49, 50, 51, // 60
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 70
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 90
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // A0
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 52, 53, 54, 55, 56, 57, // B0
+ 58, 59, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // C0
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 61, 62, 63, 64, 65, // D0
+ -1, 66, 67, 68, 69, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // E0
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // F0
+ };
+
+ return xlat[aVirtualKey];
+}
+
+int KeyboardLayout::CompareDeadKeyEntries(const void* aArg1, const void* aArg2,
+ void*) {
+ const DeadKeyEntry* arg1 = static_cast<const DeadKeyEntry*>(aArg1);
+ const DeadKeyEntry* arg2 = static_cast<const DeadKeyEntry*>(aArg2);
+
+ return arg1->BaseChar - arg2->BaseChar;
+}
+
+const DeadKeyTable* KeyboardLayout::AddDeadKeyTable(
+ const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) {
+ DeadKeyTableListEntry* next = mDeadKeyTableListHead;
+
+ const size_t bytes = offsetof(DeadKeyTableListEntry, data) +
+ DeadKeyTable::SizeInBytes(aEntries);
+ uint8_t* p = new uint8_t[bytes];
+
+ mDeadKeyTableListHead = reinterpret_cast<DeadKeyTableListEntry*>(p);
+ mDeadKeyTableListHead->next = next;
+
+ DeadKeyTable* dkt =
+ reinterpret_cast<DeadKeyTable*>(mDeadKeyTableListHead->data);
+
+ dkt->Init(aDeadKeyArray, aEntries);
+
+ return dkt;
+}
+
+void KeyboardLayout::ReleaseDeadKeyTables() {
+ while (mDeadKeyTableListHead) {
+ uint8_t* p = reinterpret_cast<uint8_t*>(mDeadKeyTableListHead);
+ mDeadKeyTableListHead = mDeadKeyTableListHead->next;
+
+ delete[] p;
+ }
+}
+
+bool KeyboardLayout::EnsureDeadKeyActive(bool aIsActive, uint8_t aDeadKey,
+ const PBYTE aDeadKeyKbdState) {
+ int32_t ret;
+ do {
+ char16_t dummyChars[5];
+ ret =
+ ::ToUnicodeEx(aDeadKey, 0, (PBYTE)aDeadKeyKbdState, (LPWSTR)dummyChars,
+ ArrayLength(dummyChars), 0, mKeyboardLayout);
+ // returned values:
+ // <0 - Dead key state is active. The keyboard driver will wait for next
+ // character.
+ // 1 - Previous pressed key was a valid base character that produced
+ // exactly one composite character.
+ // >1 - Previous pressed key does not produce any composite characters.
+ // Return dead-key character followed by base character(s).
+ } while ((ret < 0) != aIsActive);
+
+ return (ret < 0);
+}
+
+void KeyboardLayout::ActivateDeadKeyState(const NativeKey& aNativeKey) {
+ // Dead-key state should be activated at keydown.
+ if (!aNativeKey.IsKeyDownMessage()) {
+ return;
+ }
+
+ mActiveDeadKeys.AppendElement(aNativeKey.mOriginalVirtualKeyCode);
+ mDeadKeyShiftStates.AppendElement(aNativeKey.GetShiftState());
+}
+
+void KeyboardLayout::DeactivateDeadKeyState() {
+ if (mActiveDeadKeys.IsEmpty()) {
+ return;
+ }
+
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+
+ // Assume that the last dead key can finish dead key sequence.
+ VirtualKey::FillKbdState(kbdState, mDeadKeyShiftStates.LastElement());
+ EnsureDeadKeyActive(false, mActiveDeadKeys.LastElement(), kbdState);
+ mActiveDeadKeys.Clear();
+ mDeadKeyShiftStates.Clear();
+}
+
+bool KeyboardLayout::AddDeadKeyEntry(char16_t aBaseChar,
+ char16_t aCompositeChar,
+ DeadKeyEntry* aDeadKeyArray,
+ uint32_t aEntries) {
+ for (uint32_t index = 0; index < aEntries; index++) {
+ if (aDeadKeyArray[index].BaseChar == aBaseChar) {
+ return false;
+ }
+ }
+
+ aDeadKeyArray[aEntries].BaseChar = aBaseChar;
+ aDeadKeyArray[aEntries].CompositeChar = aCompositeChar;
+
+ return true;
+}
+
+uint32_t KeyboardLayout::GetDeadKeyCombinations(
+ uint8_t aDeadKey, const PBYTE aDeadKeyKbdState,
+ uint16_t aShiftStatesWithBaseChars, DeadKeyEntry* aDeadKeyArray,
+ uint32_t aMaxEntries) {
+ bool deadKeyActive = false;
+ uint32_t entries = 0;
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+
+ for (uint32_t shiftState = 0; shiftState < 16; shiftState++) {
+ if (!(aShiftStatesWithBaseChars & (1 << shiftState))) {
+ continue;
+ }
+
+ VirtualKey::FillKbdState(kbdState, shiftState);
+
+ for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) {
+ int32_t vki = GetKeyIndex(virtualKey);
+ // Dead-key can pair only with such key that produces exactly one base
+ // character.
+ if (vki >= 0 &&
+ mVirtualKeys[vki].GetNativeUniChars(shiftState).Length() == 1) {
+ // Ensure dead-key is in active state, when it swallows entered
+ // character and waits for the next pressed key.
+ if (!deadKeyActive) {
+ deadKeyActive = EnsureDeadKeyActive(true, aDeadKey, aDeadKeyKbdState);
+ }
+
+ // Depending on the character the followed the dead-key, the keyboard
+ // driver can produce one composite character, or a dead-key character
+ // followed by a second character.
+ char16_t compositeChars[5];
+ int32_t ret =
+ ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)compositeChars,
+ ArrayLength(compositeChars), 0, mKeyboardLayout);
+ switch (ret) {
+ case 0:
+ // This key combination does not produce any characters. The
+ // dead-key is still in active state.
+ break;
+ case 1: {
+ char16_t baseChars[5];
+ ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)baseChars,
+ ArrayLength(baseChars), 0, mKeyboardLayout);
+ if (entries < aMaxEntries) {
+ switch (ret) {
+ case 1:
+ // Exactly one composite character produced. Now, when
+ // dead-key is not active, repeat the last character one more
+ // time to determine the base character.
+ if (AddDeadKeyEntry(baseChars[0], compositeChars[0],
+ aDeadKeyArray, entries)) {
+ entries++;
+ }
+ deadKeyActive = false;
+ break;
+ case -1: {
+ // If pressing another dead-key produces different character,
+ // we should register the dead-key entry with first character
+ // produced by current key.
+
+ // First inactivate the dead-key state completely.
+ deadKeyActive =
+ EnsureDeadKeyActive(false, aDeadKey, aDeadKeyKbdState);
+ if (NS_WARN_IF(deadKeyActive)) {
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Error,
+ (" failed to deactivating the dead-key state..."));
+ break;
+ }
+ for (int32_t i = 0; i < 5; ++i) {
+ ret = ::ToUnicodeEx(
+ virtualKey, 0, kbdState, (LPWSTR)baseChars,
+ ArrayLength(baseChars), 0, mKeyboardLayout);
+ if (ret >= 0) {
+ break;
+ }
+ }
+ if (ret > 0 &&
+ AddDeadKeyEntry(baseChars[0], compositeChars[0],
+ aDeadKeyArray, entries)) {
+ entries++;
+ }
+ // Inactivate dead-key state for current virtual keycode.
+ EnsureDeadKeyActive(false, virtualKey, kbdState);
+ break;
+ }
+ default:
+ NS_WARNING("File a bug for this dead-key handling!");
+ deadKeyActive = false;
+ break;
+ }
+ }
+ MOZ_LOG(
+ sKeyboardLayoutLogger, LogLevel::Debug,
+ (" %s -> %s (%d): DeadKeyEntry(%s, %s) (ret=%d)",
+ kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki,
+ GetCharacterCodeNames(compositeChars, 1).get(),
+ ret <= 0
+ ? "''"
+ : GetCharacterCodeNames(baseChars, std::min(ret, 5)).get(),
+ ret));
+ break;
+ }
+ default:
+ // 1. Unexpected dead-key. Dead-key chaining is not supported.
+ // 2. More than one character generated. This is not a valid
+ // dead-key and base character combination.
+ deadKeyActive = false;
+ MOZ_LOG(
+ sKeyboardLayoutLogger, LogLevel::Verbose,
+ (" %s -> %s (%d): Unsupport dead key type(%s) (ret=%d)",
+ kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki,
+ ret <= 0
+ ? "''"
+ : GetCharacterCodeNames(compositeChars, std::min(ret, 5))
+ .get(),
+ ret));
+ break;
+ }
+ }
+ }
+ }
+
+ if (deadKeyActive) {
+ deadKeyActive = EnsureDeadKeyActive(false, aDeadKey, aDeadKeyKbdState);
+ }
+
+ NS_QuickSort(aDeadKeyArray, entries, sizeof(DeadKeyEntry),
+ CompareDeadKeyEntries, nullptr);
+ return entries;
+}
+
+uint32_t KeyboardLayout::ConvertNativeKeyCodeToDOMKeyCode(
+ UINT aNativeKeyCode) const {
+ // Alphabet or Numeric or Numpad or Function keys
+ if ((aNativeKeyCode >= 0x30 && aNativeKeyCode <= 0x39) ||
+ (aNativeKeyCode >= 0x41 && aNativeKeyCode <= 0x5A) ||
+ (aNativeKeyCode >= 0x60 && aNativeKeyCode <= 0x87)) {
+ return static_cast<uint32_t>(aNativeKeyCode);
+ }
+ switch (aNativeKeyCode) {
+ // Following keycodes are same as our DOM keycodes
+ case VK_CANCEL:
+ case VK_BACK:
+ case VK_TAB:
+ case VK_CLEAR:
+ case VK_RETURN:
+ case VK_SHIFT:
+ case VK_CONTROL:
+ case VK_MENU: // Alt
+ case VK_PAUSE:
+ case VK_CAPITAL: // CAPS LOCK
+ case VK_KANA: // same as VK_HANGUL
+ case VK_JUNJA:
+ case VK_FINAL:
+ case VK_HANJA: // same as VK_KANJI
+ case VK_ESCAPE:
+ case VK_CONVERT:
+ case VK_NONCONVERT:
+ case VK_ACCEPT:
+ case VK_MODECHANGE:
+ case VK_SPACE:
+ case VK_PRIOR: // PAGE UP
+ case VK_NEXT: // PAGE DOWN
+ case VK_END:
+ case VK_HOME:
+ case VK_LEFT:
+ case VK_UP:
+ case VK_RIGHT:
+ case VK_DOWN:
+ case VK_SELECT:
+ case VK_PRINT:
+ case VK_EXECUTE:
+ case VK_SNAPSHOT:
+ case VK_INSERT:
+ case VK_DELETE:
+ case VK_APPS: // Context Menu
+ case VK_SLEEP:
+ case VK_NUMLOCK:
+ case VK_SCROLL: // SCROLL LOCK
+ case VK_ATTN: // Attension key of IBM midrange computers, e.g., AS/400
+ case VK_CRSEL: // Cursor Selection
+ case VK_EXSEL: // Extend Selection
+ case VK_EREOF: // Erase EOF key of IBM 3270 keyboard layout
+ case VK_PLAY:
+ case VK_ZOOM:
+ case VK_PA1: // PA1 key of IBM 3270 keyboard layout
+ return uint32_t(aNativeKeyCode);
+
+ case VK_HELP:
+ return NS_VK_HELP;
+
+ // Windows key should be mapped to a Win keycode
+ // They should be able to be distinguished by DOM3 KeyboardEvent.location
+ case VK_LWIN:
+ case VK_RWIN:
+ return NS_VK_WIN;
+
+ case VK_VOLUME_MUTE:
+ return NS_VK_VOLUME_MUTE;
+ case VK_VOLUME_DOWN:
+ return NS_VK_VOLUME_DOWN;
+ case VK_VOLUME_UP:
+ return NS_VK_VOLUME_UP;
+
+ case VK_LSHIFT:
+ case VK_RSHIFT:
+ return NS_VK_SHIFT;
+
+ case VK_LCONTROL:
+ case VK_RCONTROL:
+ return NS_VK_CONTROL;
+
+ // Note that even if the key is AltGr, we should return NS_VK_ALT for
+ // compatibility with both older Gecko and the other browsers.
+ case VK_LMENU:
+ case VK_RMENU:
+ return NS_VK_ALT;
+
+ // Following keycodes are not defined in our DOM keycodes.
+ case VK_BROWSER_BACK:
+ case VK_BROWSER_FORWARD:
+ case VK_BROWSER_REFRESH:
+ case VK_BROWSER_STOP:
+ case VK_BROWSER_SEARCH:
+ case VK_BROWSER_FAVORITES:
+ case VK_BROWSER_HOME:
+ case VK_MEDIA_NEXT_TRACK:
+ case VK_MEDIA_PREV_TRACK:
+ case VK_MEDIA_STOP:
+ case VK_MEDIA_PLAY_PAUSE:
+ case VK_LAUNCH_MAIL:
+ case VK_LAUNCH_MEDIA_SELECT:
+ case VK_LAUNCH_APP1:
+ case VK_LAUNCH_APP2:
+ return 0;
+
+ // Following OEM specific virtual keycodes should pass through DOM keyCode
+ // for compatibility with the other browsers on Windows.
+
+ // Following OEM specific virtual keycodes are defined for Fujitsu/OASYS.
+ case VK_OEM_FJ_JISHO:
+ case VK_OEM_FJ_MASSHOU:
+ case VK_OEM_FJ_TOUROKU:
+ case VK_OEM_FJ_LOYA:
+ case VK_OEM_FJ_ROYA:
+ // Not sure what means "ICO".
+ case VK_ICO_HELP:
+ case VK_ICO_00:
+ case VK_ICO_CLEAR:
+ // Following OEM specific virtual keycodes are defined for Nokia/Ericsson.
+ case VK_OEM_RESET:
+ case VK_OEM_JUMP:
+ case VK_OEM_PA1:
+ case VK_OEM_PA2:
+ case VK_OEM_PA3:
+ case VK_OEM_WSCTRL:
+ case VK_OEM_CUSEL:
+ case VK_OEM_ATTN:
+ case VK_OEM_FINISH:
+ case VK_OEM_COPY:
+ case VK_OEM_AUTO:
+ case VK_OEM_ENLW:
+ case VK_OEM_BACKTAB:
+ // VK_OEM_CLEAR is defined as not OEM specific, but let's pass though
+ // DOM keyCode like other OEM specific virtual keycodes.
+ case VK_OEM_CLEAR:
+ return uint32_t(aNativeKeyCode);
+
+ // 0xE1 is an OEM specific virtual keycode. However, the value is already
+ // used in our DOM keyCode for AltGr on Linux. So, this virtual keycode
+ // cannot pass through DOM keyCode.
+ case 0xE1:
+ return 0;
+
+ // Following keycodes are OEM keys which are keycodes for non-alphabet and
+ // non-numeric keys, we should compute each keycode of them from unshifted
+ // character which is inputted by each key. But if the unshifted character
+ // is not an ASCII character but shifted character is an ASCII character,
+ // we should refer it.
+ case VK_OEM_1:
+ case VK_OEM_PLUS:
+ case VK_OEM_COMMA:
+ case VK_OEM_MINUS:
+ case VK_OEM_PERIOD:
+ case VK_OEM_2:
+ case VK_OEM_3:
+ case VK_OEM_4:
+ case VK_OEM_5:
+ case VK_OEM_6:
+ case VK_OEM_7:
+ case VK_OEM_8:
+ case VK_OEM_102:
+ case VK_ABNT_C1: {
+ NS_ASSERTION(IsPrintableCharKey(aNativeKeyCode),
+ "The key must be printable");
+ ModifierKeyState modKeyState(0);
+ UniCharsAndModifiers uniChars =
+ GetUniCharsAndModifiers(aNativeKeyCode, modKeyState);
+ if (uniChars.Length() != 1 || uniChars.CharAt(0) < ' ' ||
+ uniChars.CharAt(0) > 0x7F) {
+ modKeyState.Set(MODIFIER_SHIFT);
+ uniChars = GetUniCharsAndModifiers(aNativeKeyCode, modKeyState);
+ if (uniChars.Length() != 1 || uniChars.CharAt(0) < ' ' ||
+ uniChars.CharAt(0) > 0x7F) {
+ // In this case, we've returned 0 in this case for long time because
+ // we decided that we should avoid setting same keyCode value to 2 or
+ // more keys since active keyboard layout may have a key to input the
+ // punctuation with different key. However, setting keyCode to 0
+ // makes some web applications which are aware of neither
+ // KeyboardEvent.key nor KeyboardEvent.code not work with Firefox
+ // when user selects non-ASCII capable keyboard layout such as
+ // Russian and Thai layout. So, let's decide keyCode value with
+ // major keyboard layout's key which causes the OEM keycode.
+ // Actually, this maps same keyCode value to 2 keys on Russian
+ // keyboard layout. "Period" key causes VK_OEM_PERIOD but inputs
+ // Yu of Cyrillic and "Slash" key causes VK_OEM_2 (same as US
+ // keyboard layout) but inputs "." (period of ASCII). Therefore,
+ // we return DOM_VK_PERIOD which is same as VK_OEM_PERIOD for
+ // "Period" key. On the other hand, we use same keyCode value for
+ // "Slash" key too because it inputs ".".
+ CodeNameIndex code;
+ switch (aNativeKeyCode) {
+ case VK_OEM_1:
+ code = CODE_NAME_INDEX_Semicolon;
+ break;
+ case VK_OEM_PLUS:
+ code = CODE_NAME_INDEX_Equal;
+ break;
+ case VK_OEM_COMMA:
+ code = CODE_NAME_INDEX_Comma;
+ break;
+ case VK_OEM_MINUS:
+ code = CODE_NAME_INDEX_Minus;
+ break;
+ case VK_OEM_PERIOD:
+ code = CODE_NAME_INDEX_Period;
+ break;
+ case VK_OEM_2:
+ code = CODE_NAME_INDEX_Slash;
+ break;
+ case VK_OEM_3:
+ code = CODE_NAME_INDEX_Backquote;
+ break;
+ case VK_OEM_4:
+ code = CODE_NAME_INDEX_BracketLeft;
+ break;
+ case VK_OEM_5:
+ code = CODE_NAME_INDEX_Backslash;
+ break;
+ case VK_OEM_6:
+ code = CODE_NAME_INDEX_BracketRight;
+ break;
+ case VK_OEM_7:
+ code = CODE_NAME_INDEX_Quote;
+ break;
+ case VK_OEM_8:
+ // Use keyCode value for "Backquote" key on UK keyboard layout.
+ code = CODE_NAME_INDEX_Backquote;
+ break;
+ case VK_OEM_102:
+ // Use keyCode value for "IntlBackslash" key.
+ code = CODE_NAME_INDEX_IntlBackslash;
+ break;
+ case VK_ABNT_C1: // "/" of ABNT.
+ // Use keyCode value for "IntlBackslash" key on ABNT keyboard
+ // layout.
+ code = CODE_NAME_INDEX_IntlBackslash;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Handle all OEM keycode values");
+ return 0;
+ }
+ return WidgetKeyboardEvent::GetFallbackKeyCodeOfPunctuationKey(code);
+ }
+ }
+ return WidgetUtils::ComputeKeyCodeFromChar(uniChars.CharAt(0));
+ }
+
+ // IE sets 0xC2 to the DOM keyCode for VK_ABNT_C2. However, we're already
+ // using NS_VK_SEPARATOR for the separator key on Mac and Linux. Therefore,
+ // We should keep consistency between Gecko on all platforms rather than
+ // with other browsers since a lot of keyCode values are already different
+ // between browsers.
+ case VK_ABNT_C2:
+ return NS_VK_SEPARATOR;
+
+ // VK_PROCESSKEY means IME already consumed the key event.
+ case VK_PROCESSKEY:
+ return NS_VK_PROCESSKEY;
+ // VK_PACKET is generated by SendInput() API, we don't need to
+ // care this message as key event.
+ case VK_PACKET:
+ return 0;
+ // If a key is not mapped to a virtual keycode, 0xFF is used.
+ case 0xFF:
+ NS_WARNING("The key is failed to be converted to a virtual keycode");
+ return 0;
+ }
+#ifdef DEBUG
+ nsPrintfCString warning(
+ "Unknown virtual keycode (0x%08X), please check the "
+ "latest MSDN document, there may be some new "
+ "keycodes we've never known.",
+ aNativeKeyCode);
+ NS_WARNING(warning.get());
+#endif
+ return 0;
+}
+
+KeyNameIndex KeyboardLayout::ConvertNativeKeyCodeToKeyNameIndex(
+ uint8_t aVirtualKey) const {
+ if (IsPrintableCharKey(aVirtualKey) || aVirtualKey == VK_PACKET) {
+ return KEY_NAME_INDEX_USE_STRING;
+ }
+
+ // If the keyboard layout has AltGr and AltRight key is pressed,
+ // return AltGraph.
+ if (aVirtualKey == VK_RMENU && HasAltGr()) {
+ return KEY_NAME_INDEX_AltGraph;
+ }
+
+ switch (aVirtualKey) {
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: \
+ return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ break;
+ }
+
+ HKL layout = GetLayout();
+ WORD langID = LOWORD(static_cast<HKL>(layout));
+ WORD primaryLangID = PRIMARYLANGID(langID);
+
+ if (primaryLangID == LANG_JAPANESE) {
+ switch (aVirtualKey) {
+#undef NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+#define NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, \
+ aKeyNameIndex) \
+ case aNativeKey: \
+ return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ break;
+ }
+ } else if (primaryLangID == LANG_KOREAN) {
+ switch (aVirtualKey) {
+#undef NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+#define NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: \
+ return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ return KEY_NAME_INDEX_Unidentified;
+ }
+ }
+
+ switch (aVirtualKey) {
+#undef NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+#define NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: \
+ return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ return KEY_NAME_INDEX_Unidentified;
+ }
+}
+
+// static
+CodeNameIndex KeyboardLayout::ConvertScanCodeToCodeNameIndex(UINT aScanCode) {
+ switch (aScanCode) {
+#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
+ case aNativeKey: \
+ return aCodeNameIndex;
+
+#include "NativeKeyToDOMCodeName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
+
+ default:
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+}
+
+nsresult KeyboardLayout::SynthesizeNativeKeyEvent(
+ nsWindowBase* aWidget, int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode, uint32_t aModifierFlags,
+ const nsAString& aCharacters, const nsAString& aUnmodifiedCharacters) {
+ UINT keyboardLayoutListCount = ::GetKeyboardLayoutList(0, nullptr);
+ NS_ASSERTION(keyboardLayoutListCount > 0,
+ "One keyboard layout must be installed at least");
+ HKL keyboardLayoutListBuff[50];
+ HKL* keyboardLayoutList = keyboardLayoutListCount < 50
+ ? keyboardLayoutListBuff
+ : new HKL[keyboardLayoutListCount];
+ keyboardLayoutListCount =
+ ::GetKeyboardLayoutList(keyboardLayoutListCount, keyboardLayoutList);
+ NS_ASSERTION(keyboardLayoutListCount > 0,
+ "Failed to get all keyboard layouts installed on the system");
+
+ nsPrintfCString layoutName("%08x", aNativeKeyboardLayout);
+ HKL loadedLayout = LoadKeyboardLayoutA(layoutName.get(), KLF_NOTELLSHELL);
+ if (loadedLayout == nullptr) {
+ if (keyboardLayoutListBuff != keyboardLayoutList) {
+ delete[] keyboardLayoutList;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Setup clean key state and load desired layout
+ BYTE originalKbdState[256];
+ ::GetKeyboardState(originalKbdState);
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+ // This changes the state of the keyboard for the current thread only,
+ // and we'll restore it soon, so this should be OK.
+ ::SetKeyboardState(kbdState);
+
+ OverrideLayout(loadedLayout);
+
+ bool isAltGrKeyPress = false;
+ if (aModifierFlags & nsIWidget::ALTGRAPH) {
+ if (!HasAltGr()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ // AltGr emulates ControlLeft key press and AltRight key press.
+ // So, we should remove those flags from aModifierFlags before
+ // calling WinUtils::SetupKeyModifiersSequence() to create correct
+ // key sequence.
+ // FYI: We don't support both ControlLeft and AltRight (AltGr) are
+ // pressed at the same time unless synthesizing key is
+ // VK_LCONTROL.
+ aModifierFlags &= ~(nsIWidget::CTRL_L | nsIWidget::ALT_R);
+ }
+
+ uint8_t argumentKeySpecific = 0;
+ switch (aNativeKeyCode & 0xFF) {
+ case VK_SHIFT:
+ aModifierFlags &= ~(nsIWidget::SHIFT_L | nsIWidget::SHIFT_R);
+ argumentKeySpecific = VK_LSHIFT;
+ break;
+ case VK_LSHIFT:
+ aModifierFlags &= ~nsIWidget::SHIFT_L;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_SHIFT;
+ break;
+ case VK_RSHIFT:
+ aModifierFlags &= ~nsIWidget::SHIFT_R;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_SHIFT;
+ break;
+ case VK_CONTROL:
+ aModifierFlags &= ~(nsIWidget::CTRL_L | nsIWidget::CTRL_R);
+ argumentKeySpecific = VK_LCONTROL;
+ break;
+ case VK_LCONTROL:
+ aModifierFlags &= ~nsIWidget::CTRL_L;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_CONTROL;
+ break;
+ case VK_RCONTROL:
+ aModifierFlags &= ~nsIWidget::CTRL_R;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_CONTROL;
+ break;
+ case VK_MENU:
+ aModifierFlags &= ~(nsIWidget::ALT_L | nsIWidget::ALT_R);
+ argumentKeySpecific = VK_LMENU;
+ break;
+ case VK_LMENU:
+ aModifierFlags &= ~nsIWidget::ALT_L;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU;
+ break;
+ case VK_RMENU:
+ aModifierFlags &= ~(nsIWidget::ALT_R | nsIWidget::ALTGRAPH);
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU;
+ // If AltRight key is AltGr in the keyboard layout, let's use
+ // SetupKeyModifiersSequence() to emulate the native behavior
+ // since the same event order between keydown and keyup makes
+ // the following code complicated.
+ if (HasAltGr()) {
+ isAltGrKeyPress = true;
+ aModifierFlags &= ~nsIWidget::CTRL_L;
+ aModifierFlags |= nsIWidget::ALTGRAPH;
+ }
+ break;
+ case VK_CAPITAL:
+ aModifierFlags &= ~nsIWidget::CAPS_LOCK;
+ argumentKeySpecific = VK_CAPITAL;
+ break;
+ case VK_NUMLOCK:
+ aModifierFlags &= ~nsIWidget::NUM_LOCK;
+ argumentKeySpecific = VK_NUMLOCK;
+ break;
+ }
+
+ AutoTArray<KeyPair, 10> keySequence;
+ WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags, WM_KEYDOWN);
+ if (!isAltGrKeyPress) {
+ keySequence.AppendElement(KeyPair(aNativeKeyCode, argumentKeySpecific));
+ }
+
+ // Simulate the pressing of each modifier key and then the real key
+ // FYI: Each NativeKey instance here doesn't need to override keyboard layout
+ // since this method overrides and restores the keyboard layout.
+ for (uint32_t i = 0; i < keySequence.Length(); ++i) {
+ uint8_t key = keySequence[i].mGeneral;
+ uint8_t keySpecific = keySequence[i].mSpecific;
+ uint16_t scanCode = keySequence[i].mScanCode;
+ kbdState[key] = 0x81; // key is down and toggled on if appropriate
+ if (keySpecific) {
+ kbdState[keySpecific] = 0x81;
+ }
+ ::SetKeyboardState(kbdState);
+ ModifierKeyState modKeyState;
+ // If scan code isn't specified explicitly, let's compute it with current
+ // keyboard layout.
+ if (!scanCode) {
+ scanCode =
+ ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key);
+ }
+ LPARAM lParam = static_cast<LPARAM>(scanCode << 16);
+ // If the scan code is for an extended key, set extended key flag.
+ if ((scanCode & 0xFF00) == 0xE000) {
+ lParam |= 0x1000000;
+ }
+ // When AltGr key is pressed, both ControlLeft and AltRight cause
+ // WM_KEYDOWN messages.
+ bool makeSysKeyMsg =
+ !(aModifierFlags & nsIWidget::ALTGRAPH) && IsSysKey(key, modKeyState);
+ MSG keyDownMsg =
+ WinUtils::InitMSG(makeSysKeyMsg ? WM_SYSKEYDOWN : WM_KEYDOWN, key,
+ lParam, aWidget->GetWindowHandle());
+ if (i == keySequence.Length() - 1) {
+ bool makeDeadCharMsg =
+ (IsDeadKey(key, modKeyState) && aCharacters.IsEmpty());
+ nsAutoString chars(aCharacters);
+ if (makeDeadCharMsg) {
+ UniCharsAndModifiers deadChars =
+ GetUniCharsAndModifiers(key, modKeyState);
+ chars = deadChars.ToString();
+ NS_ASSERTION(chars.Length() == 1,
+ "Dead char must be only one character");
+ }
+ if (chars.IsEmpty()) {
+ NativeKey nativeKey(aWidget, keyDownMsg, modKeyState);
+ nativeKey.HandleKeyDownMessage();
+ } else {
+ AutoTArray<NativeKey::FakeCharMsg, 10> fakeCharMsgs;
+ for (uint32_t j = 0; j < chars.Length(); j++) {
+ NativeKey::FakeCharMsg* fakeCharMsg = fakeCharMsgs.AppendElement();
+ fakeCharMsg->mCharCode = chars.CharAt(j);
+ fakeCharMsg->mScanCode = scanCode;
+ fakeCharMsg->mIsSysKey = makeSysKeyMsg;
+ fakeCharMsg->mIsDeadKey = makeDeadCharMsg;
+ }
+ NativeKey nativeKey(aWidget, keyDownMsg, modKeyState, 0, &fakeCharMsgs);
+ bool dispatched;
+ nativeKey.HandleKeyDownMessage(&dispatched);
+ // If some char messages are not consumed, let's emulate the widget
+ // receiving the message directly.
+ for (uint32_t j = 1; j < fakeCharMsgs.Length(); j++) {
+ if (fakeCharMsgs[j].mConsumed) {
+ continue;
+ }
+ MSG charMsg = fakeCharMsgs[j].GetCharMsg(aWidget->GetWindowHandle());
+ NativeKey nativeKey(aWidget, charMsg, modKeyState);
+ nativeKey.HandleCharMessage(charMsg);
+ }
+ }
+ } else {
+ NativeKey nativeKey(aWidget, keyDownMsg, modKeyState);
+ nativeKey.HandleKeyDownMessage();
+ }
+ }
+
+ keySequence.Clear();
+ if (!isAltGrKeyPress) {
+ keySequence.AppendElement(KeyPair(aNativeKeyCode, argumentKeySpecific));
+ }
+ WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags, WM_KEYUP);
+ for (uint32_t i = 0; i < keySequence.Length(); ++i) {
+ uint8_t key = keySequence[i].mGeneral;
+ uint8_t keySpecific = keySequence[i].mSpecific;
+ uint16_t scanCode = keySequence[i].mScanCode;
+ kbdState[key] = 0; // key is up and toggled off if appropriate
+ if (keySpecific) {
+ kbdState[keySpecific] = 0;
+ }
+ ::SetKeyboardState(kbdState);
+ ModifierKeyState modKeyState;
+ // If scan code isn't specified explicitly, let's compute it with current
+ // keyboard layout.
+ if (!scanCode) {
+ scanCode =
+ ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key);
+ }
+ LPARAM lParam = static_cast<LPARAM>(scanCode << 16);
+ // If the scan code is for an extended key, set extended key flag.
+ if ((scanCode & 0xFF00) == 0xE000) {
+ lParam |= 0x1000000;
+ }
+ // Don't use WM_SYSKEYUP for Alt keyup.
+ // NOTE: When AltGr was pressed, ControlLeft causes WM_SYSKEYUP normally.
+ bool makeSysKeyMsg = IsSysKey(key, modKeyState) && key != VK_MENU;
+ MSG keyUpMsg = WinUtils::InitMSG(makeSysKeyMsg ? WM_SYSKEYUP : WM_KEYUP,
+ key, lParam, aWidget->GetWindowHandle());
+ NativeKey nativeKey(aWidget, keyUpMsg, modKeyState);
+ nativeKey.HandleKeyUpMessage();
+ }
+
+ // Restore old key state and layout
+ ::SetKeyboardState(originalKbdState);
+ RestoreLayout();
+
+ // Don't unload the layout if it's installed actually.
+ for (uint32_t i = 0; i < keyboardLayoutListCount; i++) {
+ if (keyboardLayoutList[i] == loadedLayout) {
+ loadedLayout = 0;
+ break;
+ }
+ }
+ if (keyboardLayoutListBuff != keyboardLayoutList) {
+ delete[] keyboardLayoutList;
+ }
+ if (loadedLayout) {
+ ::UnloadKeyboardLayout(loadedLayout);
+ }
+ return NS_OK;
+}
+
+/*****************************************************************************
+ * mozilla::widget::DeadKeyTable
+ *****************************************************************************/
+
+char16_t DeadKeyTable::GetCompositeChar(char16_t aBaseChar) const {
+ // Dead-key table is sorted by BaseChar in ascending order.
+ // Usually they are too small to use binary search.
+
+ for (uint32_t index = 0; index < mEntries; index++) {
+ if (mTable[index].BaseChar == aBaseChar) {
+ return mTable[index].CompositeChar;
+ }
+ if (mTable[index].BaseChar > aBaseChar) {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/*****************************************************************************
+ * mozilla::widget::RedirectedKeyDownMessage
+ *****************************************************************************/
+
+MSG RedirectedKeyDownMessageManager::sRedirectedKeyDownMsg;
+bool RedirectedKeyDownMessageManager::sDefaultPreventedOfRedirectedMsg = false;
+
+// static
+bool RedirectedKeyDownMessageManager::IsRedirectedMessage(const MSG& aMsg) {
+ return (aMsg.message == WM_KEYDOWN || aMsg.message == WM_SYSKEYDOWN) &&
+ (sRedirectedKeyDownMsg.message == aMsg.message &&
+ WinUtils::GetScanCode(sRedirectedKeyDownMsg.lParam) ==
+ WinUtils::GetScanCode(aMsg.lParam));
+}
+
+// static
+void RedirectedKeyDownMessageManager::RemoveNextCharMessage(HWND aWnd) {
+ MSG msg;
+ if (WinUtils::PeekMessage(&msg, aWnd, WM_KEYFIRST, WM_KEYLAST,
+ PM_NOREMOVE | PM_NOYIELD) &&
+ (msg.message == WM_CHAR || msg.message == WM_SYSCHAR)) {
+ WinUtils::PeekMessage(&msg, aWnd, msg.message, msg.message,
+ PM_REMOVE | PM_NOYIELD);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/KeyboardLayout.h b/widget/windows/KeyboardLayout.h
new file mode 100644
index 0000000000..5f2b9b6004
--- /dev/null
+++ b/widget/windows/KeyboardLayout.h
@@ -0,0 +1,1125 @@
+/* -*- 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 KeyboardLayout_h__
+#define KeyboardLayout_h__
+
+#include "mozilla/RefPtr.h"
+#include "nscore.h"
+#include "nsString.h"
+#include "nsWindowBase.h"
+#include "nsWindowDefs.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/widget/WinMessages.h"
+#include "mozilla/widget/WinModifierKeyState.h"
+#include <windows.h>
+
+#define NS_NUM_OF_KEYS 70
+
+#define VK_OEM_1 0xBA // ';:' for US
+#define VK_OEM_PLUS 0xBB // '+' any country
+#define VK_OEM_COMMA 0xBC
+#define VK_OEM_MINUS 0xBD // '-' any country
+#define VK_OEM_PERIOD 0xBE
+#define VK_OEM_2 0xBF
+#define VK_OEM_3 0xC0
+// '/?' for Brazilian (ABNT)
+#define VK_ABNT_C1 0xC1
+// Separator in Numpad for Brazilian (ABNT) or JIS keyboard for Mac.
+#define VK_ABNT_C2 0xC2
+#define VK_OEM_4 0xDB
+#define VK_OEM_5 0xDC
+#define VK_OEM_6 0xDD
+#define VK_OEM_7 0xDE
+#define VK_OEM_8 0xDF
+#define VK_OEM_102 0xE2
+#define VK_OEM_CLEAR 0xFE
+
+class nsIUserIdleServiceInternal;
+
+namespace mozilla {
+namespace widget {
+
+enum ScanCode : uint16_t {
+ eCapsLock = 0x003A,
+ eNumLock = 0xE045,
+ eShiftLeft = 0x002A,
+ eShiftRight = 0x0036,
+ eControlLeft = 0x001D,
+ eControlRight = 0xE01D,
+ eAltLeft = 0x0038,
+ eAltRight = 0xE038,
+};
+
+// 0: nsIWidget's native modifier flag
+// 1: Virtual keycode which does not distinguish whether left or right location.
+// 2: Virtual keycode which distinguishes whether left or right location.
+// 3: Scan code.
+static const uint32_t sModifierKeyMap[][4] = {
+ {nsIWidget::CAPS_LOCK, VK_CAPITAL, 0, ScanCode::eCapsLock},
+ {nsIWidget::NUM_LOCK, VK_NUMLOCK, 0, ScanCode::eNumLock},
+ {nsIWidget::SHIFT_L, VK_SHIFT, VK_LSHIFT, ScanCode::eShiftLeft},
+ {nsIWidget::SHIFT_R, VK_SHIFT, VK_RSHIFT, ScanCode::eShiftRight},
+ {nsIWidget::CTRL_L, VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft},
+ {nsIWidget::CTRL_R, VK_CONTROL, VK_RCONTROL, ScanCode::eControlRight},
+ {nsIWidget::ALT_L, VK_MENU, VK_LMENU, ScanCode::eAltLeft},
+ {nsIWidget::ALT_R, VK_MENU, VK_RMENU, ScanCode::eAltRight}};
+
+class KeyboardLayout;
+
+class MOZ_STACK_CLASS UniCharsAndModifiers final {
+ public:
+ UniCharsAndModifiers() {}
+ UniCharsAndModifiers operator+(const UniCharsAndModifiers& aOther) const;
+ UniCharsAndModifiers& operator+=(const UniCharsAndModifiers& aOther);
+
+ /**
+ * Append a pair of unicode character and the final modifier.
+ */
+ void Append(char16_t aUniChar, Modifiers aModifiers);
+ void Clear() {
+ mChars.Truncate();
+ mModifiers.Clear();
+ }
+ bool IsEmpty() const {
+ MOZ_ASSERT(mChars.Length() == mModifiers.Length());
+ return mChars.IsEmpty();
+ }
+
+ char16_t CharAt(size_t aIndex) const {
+ MOZ_ASSERT(aIndex < Length());
+ return mChars[aIndex];
+ }
+ Modifiers ModifiersAt(size_t aIndex) const {
+ MOZ_ASSERT(aIndex < Length());
+ return mModifiers[aIndex];
+ }
+ size_t Length() const {
+ MOZ_ASSERT(mChars.Length() == mModifiers.Length());
+ return mChars.Length();
+ }
+
+ bool IsProducingCharsWithAltGr() const {
+ return !IsEmpty() && (ModifiersAt(0) & MODIFIER_ALTGRAPH) != 0;
+ }
+
+ void FillModifiers(Modifiers aModifiers);
+ /**
+ * OverwriteModifiersIfBeginsWith() assigns mModifiers with aOther between
+ * [0] and [aOther.mLength - 1] only when mChars begins with aOther.mChars.
+ */
+ void OverwriteModifiersIfBeginsWith(const UniCharsAndModifiers& aOther);
+
+ bool UniCharsEqual(const UniCharsAndModifiers& aOther) const;
+ bool UniCharsCaseInsensitiveEqual(const UniCharsAndModifiers& aOther) const;
+ bool BeginsWith(const UniCharsAndModifiers& aOther) const;
+
+ const nsString& ToString() const { return mChars; }
+
+ private:
+ nsAutoString mChars;
+ // 5 is enough number for normal keyboard layout handling. On Windows,
+ // a dead key sequence may cause inputting up to 5 characters per key press.
+ CopyableAutoTArray<Modifiers, 5> mModifiers;
+};
+
+struct DeadKeyEntry {
+ char16_t BaseChar;
+ char16_t CompositeChar;
+};
+
+class DeadKeyTable {
+ friend class KeyboardLayout;
+
+ uint16_t mEntries;
+ // KeyboardLayout::AddDeadKeyTable() will allocate as many entries as
+ // required. It is the only way to create new DeadKeyTable instances.
+ DeadKeyEntry mTable[1];
+
+ void Init(const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) {
+ mEntries = aEntries;
+ memcpy(mTable, aDeadKeyArray, aEntries * sizeof(DeadKeyEntry));
+ }
+
+ static uint32_t SizeInBytes(uint32_t aEntries) {
+ return offsetof(DeadKeyTable, mTable) + aEntries * sizeof(DeadKeyEntry);
+ }
+
+ public:
+ uint32_t Entries() const { return mEntries; }
+
+ bool IsEqual(const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) const {
+ return (mEntries == aEntries &&
+ !memcmp(mTable, aDeadKeyArray, aEntries * sizeof(DeadKeyEntry)));
+ }
+
+ char16_t GetCompositeChar(char16_t aBaseChar) const;
+};
+
+class VirtualKey {
+ public:
+ enum ShiftStateIndex : uint8_t {
+ // 0 - Normal
+ eNormal = 0,
+ // 1 - Shift
+ eShift,
+ // 2 - Control
+ eControl,
+ // 3 - Control + Shift
+ eControlShift,
+ // 4 - Alt
+ eAlt,
+ // 5 - Alt + Shift
+ eAltShift,
+ // 6 - Alt + Control (AltGr)
+ eAltGr,
+ // 7 - Alt + Control + Shift (AltGr + Shift)
+ eAltGrShift,
+ // 8 - CapsLock
+ eWithCapsLock,
+ // 9 - CapsLock + Shift
+ eShiftWithCapsLock,
+ // 10 - CapsLock + Control
+ eControlWithCapsLock,
+ // 11 - CapsLock + Control + Shift
+ eControlShiftWithCapsLock,
+ // 12 - CapsLock + Alt
+ eAltWithCapsLock,
+ // 13 - CapsLock + Alt + Shift
+ eAltShiftWithCapsLock,
+ // 14 - CapsLock + Alt + Control (CapsLock + AltGr)
+ eAltGrWithCapsLock,
+ // 15 - CapsLock + Alt + Control + Shift (CapsLock + AltGr + Shift)
+ eAltGrShiftWithCapsLock,
+ };
+
+ enum ShiftStateFlag {
+ STATE_SHIFT = 0x01,
+ STATE_CONTROL = 0x02,
+ STATE_ALT = 0x04,
+ STATE_CAPSLOCK = 0x08,
+ // ShiftState needs to have AltGr state separately since this is necessary
+ // for lossless conversion with Modifiers.
+ STATE_ALTGRAPH = 0x80,
+ // Useful to remove or check Ctrl and Alt flags.
+ STATE_CONTROL_ALT = STATE_CONTROL | STATE_ALT,
+ };
+
+ typedef uint8_t ShiftState;
+
+ static ShiftState ModifiersToShiftState(Modifiers aModifiers);
+ static ShiftState ModifierKeyStateToShiftState(
+ const ModifierKeyState& aModKeyState) {
+ return ModifiersToShiftState(aModKeyState.GetModifiers());
+ }
+ static Modifiers ShiftStateToModifiers(ShiftState aShiftState);
+ static bool IsAltGrIndex(uint8_t aIndex) {
+ return (aIndex & STATE_CONTROL_ALT) == STATE_CONTROL_ALT;
+ }
+
+ private:
+ union KeyShiftState {
+ struct {
+ char16_t Chars[4];
+ } Normal;
+ struct {
+ const DeadKeyTable* Table;
+ char16_t DeadChar;
+ } DeadKey;
+ };
+
+ KeyShiftState mShiftStates[16];
+ uint16_t mIsDeadKey;
+
+ static uint8_t ToShiftStateIndex(ShiftState aShiftState) {
+ if (!(aShiftState & STATE_ALTGRAPH)) {
+ MOZ_ASSERT(aShiftState <= eAltGrShiftWithCapsLock);
+ return static_cast<uint8_t>(aShiftState);
+ }
+ uint8_t index = aShiftState & ~STATE_ALTGRAPH;
+ index |= (STATE_ALT | STATE_CONTROL);
+ MOZ_ASSERT(index <= eAltGrShiftWithCapsLock);
+ return index;
+ }
+
+ void SetDeadKey(ShiftState aShiftState, bool aIsDeadKey) {
+ if (aIsDeadKey) {
+ mIsDeadKey |= 1 << ToShiftStateIndex(aShiftState);
+ } else {
+ mIsDeadKey &= ~(1 << ToShiftStateIndex(aShiftState));
+ }
+ }
+
+ public:
+ static void FillKbdState(PBYTE aKbdState, const ShiftState aShiftState);
+
+ bool IsDeadKey(ShiftState aShiftState) const {
+ return (mIsDeadKey & (1 << ToShiftStateIndex(aShiftState))) != 0;
+ }
+
+ /**
+ * IsChangedByAltGr() is useful to check if a key with AltGr produces
+ * different character(s) from the key without AltGr.
+ * Note that this is designed for checking if a keyboard layout has AltGr
+ * key. So, this result may not exactly correct for the key since it's
+ * okay to fails in some edge cases when we check all keys which produce
+ * character(s) in a layout.
+ */
+ bool IsChangedByAltGr(ShiftState aShiftState) const {
+ MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState));
+ MOZ_ASSERT(IsAltGrIndex(aShiftState));
+ MOZ_ASSERT(IsDeadKey(aShiftState) ||
+ mShiftStates[aShiftState].Normal.Chars[0]);
+ const ShiftState kShiftStateWithoutAltGr =
+ aShiftState - ShiftStateIndex::eAltGr;
+ if (IsDeadKey(aShiftState) != IsDeadKey(kShiftStateWithoutAltGr)) {
+ return false;
+ }
+ if (IsDeadKey(aShiftState)) {
+ return mShiftStates[aShiftState].DeadKey.DeadChar !=
+ mShiftStates[kShiftStateWithoutAltGr].DeadKey.DeadChar;
+ }
+ for (size_t i = 0; i < 4; i++) {
+ if (mShiftStates[aShiftState].Normal.Chars[i] !=
+ mShiftStates[kShiftStateWithoutAltGr].Normal.Chars[i]) {
+ return true;
+ }
+ if (!mShiftStates[aShiftState].Normal.Chars[i] &&
+ !mShiftStates[kShiftStateWithoutAltGr].Normal.Chars[i]) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ void AttachDeadKeyTable(ShiftState aShiftState,
+ const DeadKeyTable* aDeadKeyTable) {
+ MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState));
+ mShiftStates[aShiftState].DeadKey.Table = aDeadKeyTable;
+ }
+
+ void SetNormalChars(ShiftState aShiftState, const char16_t* aChars,
+ uint32_t aNumOfChars);
+ void SetDeadChar(ShiftState aShiftState, char16_t aDeadChar);
+ const DeadKeyTable* MatchingDeadKeyTable(const DeadKeyEntry* aDeadKeyArray,
+ uint32_t aEntries) const;
+ inline char16_t GetCompositeChar(ShiftState aShiftState,
+ char16_t aBaseChar) const {
+ return mShiftStates[ToShiftStateIndex(aShiftState)]
+ .DeadKey.Table->GetCompositeChar(aBaseChar);
+ }
+
+ char16_t GetCompositeChar(const ModifierKeyState& aModKeyState,
+ char16_t aBaseChar) const {
+ return GetCompositeChar(ModifierKeyStateToShiftState(aModKeyState),
+ aBaseChar);
+ }
+
+ /**
+ * GetNativeUniChars() returns character(s) which is produced by the
+ * key with given modifiers. This does NOT return proper MODIFIER_ALTGRAPH
+ * state because this is raw accessor of the database of this key.
+ */
+ UniCharsAndModifiers GetNativeUniChars(ShiftState aShiftState) const;
+ UniCharsAndModifiers GetNativeUniChars(
+ const ModifierKeyState& aModKeyState) const {
+ return GetNativeUniChars(ModifierKeyStateToShiftState(aModKeyState));
+ }
+
+ /**
+ * GetUniChars() returns characters and modifiers which are not consumed
+ * to input the character.
+ * For example, if you specify Ctrl key but the key produces no character
+ * with Ctrl, this returns character(s) which is produced by the key
+ * without Ctrl. So, the result is useful to decide KeyboardEvent.key
+ * value.
+ * Another example is, if you specify Ctrl key and the key produces
+ * different character(s) from the case without Ctrl key, this returns
+ * the character(s) *without* MODIFIER_CONTROL. This modifier information
+ * is useful for eKeyPress since TextEditor does not treat eKeyPress events
+ * whose modifier includes MODIFIER_ALT and/or MODIFIER_CONTROL.
+ *
+ * @param aShiftState Modifiers which you want to retrieve
+ * KeyboardEvent.key value for the key with.
+ * If AltGr key is pressed, this should include
+ * STATE_ALTGRAPH and should NOT include
+ * STATE_ALT nor STATE_CONTROL.
+ * If both Alt and Ctrl are pressed to emulate
+ * AltGr, this should include both STATE_ALT and
+ * STATE_CONTROL but should NOT include
+ * MODIFIER_ALTGRAPH.
+ * Then, this returns proper modifiers when
+ * this key produces no character with AltGr.
+ */
+ UniCharsAndModifiers GetUniChars(ShiftState aShiftState) const;
+ UniCharsAndModifiers GetUniChars(const ModifierKeyState& aModKeyState) const {
+ return GetUniChars(ModifierKeyStateToShiftState(aModKeyState));
+ }
+};
+
+class MOZ_STACK_CLASS NativeKey final {
+ friend class KeyboardLayout;
+
+ public:
+ struct FakeCharMsg {
+ UINT mCharCode;
+ UINT mScanCode;
+ bool mIsSysKey;
+ bool mIsDeadKey;
+ bool mConsumed;
+
+ FakeCharMsg()
+ : mCharCode(0),
+ mScanCode(0),
+ mIsSysKey(false),
+ mIsDeadKey(false),
+ mConsumed(false) {}
+
+ MSG GetCharMsg(HWND aWnd) const {
+ MSG msg;
+ msg.hwnd = aWnd;
+ msg.message = mIsDeadKey && mIsSysKey ? WM_SYSDEADCHAR
+ : mIsDeadKey ? WM_DEADCHAR
+ : mIsSysKey ? WM_SYSCHAR
+ : WM_CHAR;
+ msg.wParam = static_cast<WPARAM>(mCharCode);
+ msg.lParam = static_cast<LPARAM>(mScanCode << 16);
+ msg.time = 0;
+ msg.pt.x = msg.pt.y = 0;
+ return msg;
+ }
+ };
+
+ NativeKey(nsWindowBase* aWidget, const MSG& aMessage,
+ const ModifierKeyState& aModKeyState,
+ HKL aOverrideKeyboardLayout = 0,
+ nsTArray<FakeCharMsg>* aFakeCharMsgs = nullptr);
+
+ ~NativeKey();
+
+ /**
+ * Handle WM_KEYDOWN message or WM_SYSKEYDOWN message. The instance must be
+ * initialized with WM_KEYDOWN or WM_SYSKEYDOWN.
+ * Returns true if dispatched keydown event or keypress event is consumed.
+ * Otherwise, false.
+ */
+ bool HandleKeyDownMessage(bool* aEventDispatched = nullptr) const;
+
+ /**
+ * Handles WM_CHAR message or WM_SYSCHAR message. The instance must be
+ * initialized with them.
+ * Returns true if dispatched keypress event is consumed. Otherwise, false.
+ */
+ bool HandleCharMessage(bool* aEventDispatched = nullptr) const;
+
+ /**
+ * Handles keyup message. Returns true if the event is consumed.
+ * Otherwise, false.
+ */
+ bool HandleKeyUpMessage(bool* aEventDispatched = nullptr) const;
+
+ /**
+ * Handles WM_APPCOMMAND message. Returns true if the event is consumed.
+ * Otherwise, false.
+ */
+ bool HandleAppCommandMessage() const;
+
+ /**
+ * Callback of TextEventDispatcherListener::WillDispatchKeyboardEvent().
+ * This method sets alternative char codes of aKeyboardEvent.
+ */
+ void WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndex);
+
+ /**
+ * Returns true if aChar is a control character which shouldn't be inputted
+ * into focused text editor.
+ */
+ static bool IsControlChar(char16_t aChar);
+
+ bool IsShift() const { return mModKeyState.IsShift(); }
+ bool IsControl() const { return mModKeyState.IsControl(); }
+ bool IsAlt() const { return mModKeyState.IsAlt(); }
+ bool MaybeEmulatingAltGraph() const;
+ Modifiers GetModifiers() const { return mModKeyState.GetModifiers(); }
+ const ModifierKeyState& ModifierKeyStateRef() const { return mModKeyState; }
+ VirtualKey::ShiftState GetShiftState() const {
+ return VirtualKey::ModifierKeyStateToShiftState(mModKeyState);
+ }
+
+ /**
+ * GenericVirtualKeyCode() returns virtual keycode which cannot distinguish
+ * position of modifier keys. E.g., VK_CONTROL for both ControlLeft and
+ * ControlRight.
+ */
+ uint8_t GenericVirtualKeyCode() const { return mOriginalVirtualKeyCode; }
+
+ /**
+ * SpecificVirtualKeyCode() returns virtual keycode which can distinguish
+ * position of modifier keys. E.g., returns VK_LCONTROL or VK_RCONTROL
+ * instead of VK_CONTROL. If the key message is synthesized with not
+ * enough information, this prefers left position's keycode.
+ */
+ uint8_t SpecificVirtualKeyCode() const { return mVirtualKeyCode; }
+
+ private:
+ NativeKey* mLastInstance;
+ // mRemovingMsg is set at removing a char message from
+ // GetFollowingCharMessage().
+ MSG mRemovingMsg;
+ // mReceivedMsg is set when another instance starts to handle the message
+ // unexpectedly.
+ MSG mReceivedMsg;
+ RefPtr<nsWindowBase> mWidget;
+ RefPtr<TextEventDispatcher> mDispatcher;
+ HKL mKeyboardLayout;
+ MSG mMsg;
+ // mFollowingCharMsgs stores WM_CHAR, WM_SYSCHAR, WM_DEADCHAR or
+ // WM_SYSDEADCHAR message which follows WM_KEYDOWN.
+ // Note that the stored messaged are already removed from the queue.
+ // FYI: 5 is enough number for usual keyboard layout handling. On Windows,
+ // a dead key sequence may cause inputting up to 5 characters per key press.
+ AutoTArray<MSG, 5> mFollowingCharMsgs;
+ // mRemovedOddCharMsgs stores WM_CHAR messages which are caused by ATOK or
+ // WXG (they are Japanese IME) when the user tries to do "Kakutei-undo"
+ // (it means "undo the last commit").
+ nsTArray<MSG> mRemovedOddCharMsgs;
+ // If dispatching eKeyDown or eKeyPress event causes focus change,
+ // the instance shouldn't handle remaning char messages. For checking it,
+ // this should store first focused window.
+ HWND mFocusedWndBeforeDispatch;
+
+ uint32_t mDOMKeyCode;
+ KeyNameIndex mKeyNameIndex;
+ CodeNameIndex mCodeNameIndex;
+
+ ModifierKeyState mModKeyState;
+
+ // mVirtualKeyCode distinguishes left key or right key of modifier key.
+ uint8_t mVirtualKeyCode;
+ // mOriginalVirtualKeyCode doesn't distinguish left key or right key of
+ // modifier key. However, if the given keycode is VK_PROCESS, it's resolved
+ // to a keycode before it's handled by IME.
+ uint8_t mOriginalVirtualKeyCode;
+
+ // mCommittedChars indicates the inputted characters which is committed by
+ // the key. If dead key fail to composite a character, mCommittedChars
+ // indicates both the dead characters and the base characters.
+ UniCharsAndModifiers mCommittedCharsAndModifiers;
+
+ // Following strings are computed by
+ // ComputeInputtingStringWithKeyboardLayout() which is typically called
+ // before dispatching keydown event.
+ // mInputtingStringAndModifiers's string is the string to be
+ // inputted into the focused editor and its modifier state is proper
+ // modifier state for inputting the string into the editor.
+ UniCharsAndModifiers mInputtingStringAndModifiers;
+ // mShiftedString is the string to be inputted into the editor with
+ // current modifier state with active shift state.
+ UniCharsAndModifiers mShiftedString;
+ // mUnshiftedString is the string to be inputted into the editor with
+ // current modifier state without shift state.
+ UniCharsAndModifiers mUnshiftedString;
+ // Following integers are computed by
+ // ComputeInputtingStringWithKeyboardLayout() which is typically called
+ // before dispatching keydown event. The meaning of these values is same
+ // as charCode.
+ uint32_t mShiftedLatinChar;
+ uint32_t mUnshiftedLatinChar;
+
+ WORD mScanCode;
+ bool mIsExtended;
+ // mIsRepeat is true if the key message is caused by the auto-repeat
+ // feature.
+ bool mIsRepeat;
+ bool mIsDeadKey;
+ // mIsPrintableKey is true if the key may be a printable key without
+ // any modifier keys. Otherwise, false.
+ // Please note that the event may not cause any text input even if this
+ // is true. E.g., it might be dead key state or Ctrl key may be pressed.
+ bool mIsPrintableKey;
+ // mIsSkippableInRemoteProcess is false if the key event shouldn't be
+ // skipped in the remote process even if it's too old event.
+ bool mIsSkippableInRemoteProcess;
+ // mCharMessageHasGone is true if the message is a keydown message and
+ // it's followed by at least one char message but it's gone at removing
+ // from the queue. This could occur if PeekMessage() or something is
+ // hooked by odd tool.
+ bool mCharMessageHasGone;
+ // mIsOverridingKeyboardLayout is true if the instance temporarily overriding
+ // keyboard layout with specified by the constructor.
+ bool mIsOverridingKeyboardLayout;
+ // mCanIgnoreModifierStateAtKeyPress is true if it's allowed to remove
+ // Ctrl or Alt modifier state at dispatching eKeyPress.
+ bool mCanIgnoreModifierStateAtKeyPress;
+
+ nsTArray<FakeCharMsg>* mFakeCharMsgs;
+
+ // When a keydown event is dispatched at handling WM_APPCOMMAND, the computed
+ // virtual keycode is set to this. Even if we consume WM_APPCOMMAND message,
+ // Windows may send WM_KEYDOWN and WM_KEYUP message for them.
+ // At that time, we should not dispatch key events for them.
+ static uint8_t sDispatchedKeyOfAppCommand;
+
+ NativeKey() {
+ MOZ_CRASH("The default constructor of NativeKey isn't available");
+ }
+
+ void InitWithAppCommand();
+ void InitWithKeyOrChar();
+
+ /**
+ * InitIsSkippableForKeyOrChar() initializes mIsSkippableInRemoteProcess with
+ * mIsRepeat and previous key message information. So, this must be called
+ * after mIsRepeat is initialized.
+ */
+ void InitIsSkippableForKeyOrChar(const MSG& aLastKeyMSG);
+
+ /**
+ * InitCommittedCharsAndModifiersWithFollowingCharMessages() initializes
+ * mCommittedCharsAndModifiers with mFollowingCharMsgs and mModKeyState.
+ * If mFollowingCharMsgs includes non-printable char messages, they are
+ * ignored (skipped).
+ */
+ void InitCommittedCharsAndModifiersWithFollowingCharMessages();
+
+ UINT GetScanCodeWithExtendedFlag() const;
+
+ // The result is one of eKeyLocation*.
+ uint32_t GetKeyLocation() const;
+
+ /**
+ * RemoveFollowingOddCharMessages() removes odd WM_CHAR messages from the
+ * queue when IsIMEDoingKakuteiUndo() returns true.
+ */
+ void RemoveFollowingOddCharMessages();
+
+ /**
+ * "Kakutei-Undo" of ATOK or WXG (both of them are Japanese IME) causes
+ * strange WM_KEYDOWN/WM_KEYUP/WM_CHAR message pattern. So, when this
+ * returns true, the caller needs to be careful for processing the messages.
+ */
+ bool IsIMEDoingKakuteiUndo() const;
+
+ /**
+ * This returns true if user types a number key in numpad with Alt key
+ * to input a Unicode character from its scalar value.
+ * Note that inputting Unicode scalar value is available without NumLock.
+ * Therefore, this returns true even if user presses a function key on
+ * numpad without NumLock, but that may be intended to perform a shortcut
+ * key like Alt + Home.
+ */
+ bool MaybeTypingUnicodeScalarValue() const {
+ return !mIsExtended && IsSysKeyDownOrKeyUpMessage() && IsAlt() &&
+ !IsControl() && !IsShift() &&
+ ((mScanCode >= 0x004F && mScanCode <= 0x0052) || // Numpad0-3
+ (mScanCode >= 0x004B && mScanCode <= 0x004D) || // Numpad4-6
+ (mScanCode >= 0x0047 && mScanCode <= 0x0049)); // Numpad7-9
+ }
+
+ bool IsKeyDownMessage() const {
+ return (mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN ||
+ mMsg.message == MOZ_WM_KEYDOWN);
+ }
+ bool IsKeyUpMessage() const {
+ return (mMsg.message == WM_KEYUP || mMsg.message == WM_SYSKEYUP ||
+ mMsg.message == MOZ_WM_KEYUP);
+ }
+ bool IsSysKeyDownOrKeyUpMessage() const {
+ return mMsg.message == WM_SYSKEYDOWN || mMsg.message == WM_SYSKEYUP;
+ }
+ bool IsCharOrSysCharMessage(const MSG& aMSG) const {
+ return IsCharOrSysCharMessage(aMSG.message);
+ }
+ bool IsCharOrSysCharMessage(UINT aMessage) const {
+ return (aMessage == WM_CHAR || aMessage == WM_SYSCHAR);
+ }
+ bool IsCharMessage(const MSG& aMSG) const {
+ return IsCharMessage(aMSG.message);
+ }
+ bool IsCharMessage(UINT aMessage) const {
+ return (IsCharOrSysCharMessage(aMessage) || IsDeadCharMessage(aMessage));
+ }
+ bool IsDeadCharMessage(const MSG& aMSG) const {
+ return IsDeadCharMessage(aMSG.message);
+ }
+ bool IsDeadCharMessage(UINT aMessage) const {
+ return (aMessage == WM_DEADCHAR || aMessage == WM_SYSDEADCHAR);
+ }
+ bool IsSysCharMessage(const MSG& aMSG) const {
+ return IsSysCharMessage(aMSG.message);
+ }
+ bool IsSysCharMessage(UINT aMessage) const {
+ return (aMessage == WM_SYSCHAR || aMessage == WM_SYSDEADCHAR);
+ }
+ bool MayBeSameCharMessage(const MSG& aCharMsg1, const MSG& aCharMsg2) const;
+ bool IsSamePhysicalKeyMessage(const MSG& aKeyOrCharMsg1,
+ const MSG& aKeyOrCharMsg2) const;
+ bool IsFollowedByPrintableCharMessage() const;
+ bool IsFollowedByPrintableCharOrSysCharMessage() const;
+ bool IsFollowedByDeadCharMessage() const;
+ bool IsKeyMessageOnPlugin() const {
+ return (mMsg.message == MOZ_WM_KEYDOWN || mMsg.message == MOZ_WM_KEYUP);
+ }
+ bool IsPrintableCharMessage(const MSG& aMSG) const {
+ return aMSG.message == WM_CHAR &&
+ !IsControlChar(static_cast<char16_t>(aMSG.wParam));
+ }
+ bool IsEnterKeyPressCharMessage(const MSG& aMSG) const {
+ return aMSG.message == WM_CHAR && aMSG.wParam == '\r';
+ }
+ bool IsPrintableCharOrSysCharMessage(const MSG& aMSG) const {
+ return IsCharOrSysCharMessage(aMSG) &&
+ !IsControlChar(static_cast<char16_t>(aMSG.wParam));
+ }
+ bool IsControlCharMessage(const MSG& aMSG) const {
+ return IsCharMessage(aMSG.message) &&
+ IsControlChar(static_cast<char16_t>(aMSG.wParam));
+ }
+
+ /**
+ * IsReservedBySystem() returns true if the key combination is reserved by
+ * the system. Even if it's consumed by web apps, the message should be
+ * sent to next wndproc.
+ */
+ bool IsReservedBySystem() const;
+
+ /**
+ * GetFollowingCharMessage() returns following char message of handling
+ * keydown event. If the message is found, this method returns true.
+ * Otherwise, returns false.
+ *
+ * WARNING: Even if this returns true, aCharMsg may be WM_NULL or its
+ * hwnd may be different window.
+ */
+ bool GetFollowingCharMessage(MSG& aCharMsg);
+
+ /**
+ * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK.
+ */
+ uint8_t ComputeVirtualKeyCodeFromScanCode() const;
+
+ /**
+ * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK_EX.
+ */
+ uint8_t ComputeVirtualKeyCodeFromScanCodeEx() const;
+
+ /**
+ * Wraps MapVirtualKeyEx() with MAPVK_VK_TO_VSC_EX or MAPVK_VK_TO_VSC.
+ */
+ uint16_t ComputeScanCodeExFromVirtualKeyCode(UINT aVirtualKeyCode) const;
+
+ /**
+ * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK and MAPVK_VK_TO_CHAR.
+ */
+ char16_t ComputeUnicharFromScanCode() const;
+
+ /**
+ * Initializes the aKeyEvent with the information stored in the instance.
+ */
+ nsEventStatus InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const ModifierKeyState& aModKeyState) const;
+ nsEventStatus InitKeyEvent(WidgetKeyboardEvent& aKeyEvent) const;
+
+ /**
+ * Dispatches a command event for aEventCommand.
+ * Returns true if the event is consumed. Otherwise, false.
+ */
+ bool DispatchCommandEvent(uint32_t aEventCommand) const;
+
+ /**
+ * DispatchKeyPressEventsWithRetrievedCharMessages() dispatches keypress
+ * event(s) with retrieved char messages.
+ */
+ bool DispatchKeyPressEventsWithRetrievedCharMessages() const;
+
+ /**
+ * DispatchKeyPressEventsWithoutCharMessage() dispatches keypress event(s)
+ * without char messages. So, this should be used only when there are no
+ * following char messages.
+ */
+ bool DispatchKeyPressEventsWithoutCharMessage() const;
+
+ /**
+ * Checkes whether the key event down message is handled without following
+ * WM_CHAR messages. For example, if following WM_CHAR message indicates
+ * control character input, the WM_CHAR message is unclear whether it's
+ * caused by a printable key with Ctrl or just a function key such as Enter
+ * or Backspace.
+ */
+ bool NeedsToHandleWithoutFollowingCharMessages() const;
+
+ /**
+ * ComputeInputtingStringWithKeyboardLayout() computes string to be inputted
+ * with the key and the modifier state, without shift state and with shift
+ * state.
+ */
+ void ComputeInputtingStringWithKeyboardLayout();
+
+ /**
+ * IsFocusedWindowChanged() returns true if focused window is changed
+ * after the instance is created.
+ */
+ bool IsFocusedWindowChanged() const {
+ return mFocusedWndBeforeDispatch != ::GetFocus();
+ }
+
+ /**
+ * Handles WM_CHAR message or WM_SYSCHAR message. The instance must be
+ * initialized with WM_KEYDOWN, WM_SYSKEYDOWN or them.
+ * Returns true if dispatched keypress event is consumed. Otherwise, false.
+ */
+ bool HandleCharMessage(const MSG& aCharMsg,
+ bool* aEventDispatched = nullptr) const;
+
+ // Calls of PeekMessage() from NativeKey might cause nested message handling
+ // due to (perhaps) odd API hook. NativeKey should do nothing if given
+ // message is tried to be retrieved by another instance.
+
+ /**
+ * sLatestInstacne is a pointer to the newest instance of NativeKey which is
+ * handling a key or char message(s).
+ */
+ static NativeKey* sLatestInstance;
+
+ static const MSG sEmptyMSG;
+
+ static MSG sLastKeyOrCharMSG;
+
+ static MSG sLastKeyMSG;
+
+ static bool IsEmptyMSG(const MSG& aMSG) {
+ return !memcmp(&aMSG, &sEmptyMSG, sizeof(MSG));
+ }
+
+ bool IsAnotherInstanceRemovingCharMessage() const {
+ return mLastInstance && !IsEmptyMSG(mLastInstance->mRemovingMsg);
+ }
+
+ public:
+ /**
+ * Returns last key or char MSG. If no MSG has been received yet, the result
+ * is empty MSG (i.e., .message is WM_NULL).
+ */
+ static const MSG& LastKeyOrCharMSG() { return sLastKeyOrCharMSG; }
+};
+
+class KeyboardLayout {
+ public:
+ static KeyboardLayout* GetInstance();
+ static void Shutdown();
+ static HKL GetActiveLayout();
+ static nsCString GetActiveLayoutName();
+ static void NotifyIdleServiceOfUserActivity();
+
+ static bool IsPrintableCharKey(uint8_t aVirtualKey);
+
+ /**
+ * HasAltGr() returns true if the keyboard layout's AltRight key is AltGr
+ * key.
+ */
+ bool HasAltGr() const { return mHasAltGr; }
+
+ /**
+ * IsDeadKey() returns true if aVirtualKey is a dead key with aModKeyState.
+ * This method isn't stateful.
+ */
+ bool IsDeadKey(uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const;
+ bool IsDeadKey(const NativeKey& aNativeKey) const {
+ return IsDeadKey(aNativeKey.GenericVirtualKeyCode(),
+ aNativeKey.ModifierKeyStateRef());
+ }
+
+ /**
+ * IsInDeadKeySequence() returns true when it's in a dead key sequence.
+ * It starts when a dead key is down and ends when another key down causes
+ * inactivating the dead key state.
+ */
+ bool IsInDeadKeySequence() const { return !mActiveDeadKeys.IsEmpty(); }
+
+ /**
+ * IsSysKey() returns true if aVirtualKey with aModKeyState causes WM_SYSKEY*
+ * or WM_SYS*CHAR messages.
+ */
+ bool IsSysKey(uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const;
+ bool IsSysKey(const NativeKey& aNativeKey) const {
+ return IsSysKey(aNativeKey.GenericVirtualKeyCode(),
+ aNativeKey.ModifierKeyStateRef());
+ }
+
+ /**
+ * GetUniCharsAndModifiers() returns characters which are inputted by
+ * aVirtualKey with aModKeyState. This method isn't stateful.
+ * Note that if the combination causes text input, the result's Ctrl and
+ * Alt key state are never active.
+ */
+ UniCharsAndModifiers GetUniCharsAndModifiers(
+ uint8_t aVirtualKey, const ModifierKeyState& aModKeyState) const {
+ VirtualKey::ShiftState shiftState =
+ VirtualKey::ModifierKeyStateToShiftState(aModKeyState);
+ return GetUniCharsAndModifiers(aVirtualKey, shiftState);
+ }
+ UniCharsAndModifiers GetUniCharsAndModifiers(
+ const NativeKey& aNativeKey) const {
+ return GetUniCharsAndModifiers(aNativeKey.GenericVirtualKeyCode(),
+ aNativeKey.GetShiftState());
+ }
+
+ /**
+ * OnLayoutChange() must be called before the first keydown message is
+ * received. LoadLayout() changes the keyboard state, that causes breaking
+ * dead key state. Therefore, we need to load the layout before the first
+ * keydown message.
+ */
+ void OnLayoutChange(HKL aKeyboardLayout) {
+ MOZ_ASSERT(!mIsOverridden);
+ LoadLayout(aKeyboardLayout);
+ }
+
+ /**
+ * OverrideLayout() loads the specified keyboard layout.
+ */
+ void OverrideLayout(HKL aLayout) {
+ mIsOverridden = true;
+ LoadLayout(aLayout);
+ }
+
+ /**
+ * RestoreLayout() loads the current keyboard layout of the thread.
+ */
+ void RestoreLayout() {
+ mIsOverridden = false;
+ mIsPendingToRestoreKeyboardLayout = true;
+ }
+
+ uint32_t ConvertNativeKeyCodeToDOMKeyCode(UINT aNativeKeyCode) const;
+
+ /**
+ * ConvertNativeKeyCodeToKeyNameIndex() returns KeyNameIndex value for
+ * non-printable keys (except some special keys like space key).
+ */
+ KeyNameIndex ConvertNativeKeyCodeToKeyNameIndex(uint8_t aVirtualKey) const;
+
+ /**
+ * ConvertScanCodeToCodeNameIndex() returns CodeNameIndex value for
+ * the given scan code. aScanCode can be over 0xE000 since this method
+ * doesn't use Windows API.
+ */
+ static CodeNameIndex ConvertScanCodeToCodeNameIndex(UINT aScanCode);
+
+ HKL GetLayout() const {
+ return mIsPendingToRestoreKeyboardLayout ? ::GetKeyboardLayout(0)
+ : mKeyboardLayout;
+ }
+
+ /**
+ * This wraps MapVirtualKeyEx() API with MAPVK_VK_TO_VSC.
+ */
+ WORD ComputeScanCodeForVirtualKeyCode(uint8_t aVirtualKeyCode) const;
+
+ /**
+ * Implementation of nsIWidget::SynthesizeNativeKeyEvent().
+ */
+ nsresult SynthesizeNativeKeyEvent(nsWindowBase* aWidget,
+ int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters);
+
+ private:
+ KeyboardLayout();
+ ~KeyboardLayout();
+
+ static KeyboardLayout* sInstance;
+ static nsIUserIdleServiceInternal* sIdleService;
+
+ struct DeadKeyTableListEntry {
+ DeadKeyTableListEntry* next;
+ uint8_t data[1];
+ };
+
+ HKL mKeyboardLayout;
+
+ VirtualKey mVirtualKeys[NS_NUM_OF_KEYS];
+ DeadKeyTableListEntry* mDeadKeyTableListHead;
+ // When mActiveDeadKeys is empty, it's not in dead key sequence.
+ // Otherwise, it contains virtual keycodes which are pressed in current
+ // dead key sequence.
+ nsTArray<uint8_t> mActiveDeadKeys;
+ // mDeadKeyShiftStates is always same length as mActiveDeadKeys.
+ // This stores shift states at pressing each dead key stored in
+ // mActiveDeadKeys.
+ nsTArray<VirtualKey::ShiftState> mDeadKeyShiftStates;
+
+ bool mIsOverridden;
+ bool mIsPendingToRestoreKeyboardLayout;
+ bool mHasAltGr;
+
+ static inline int32_t GetKeyIndex(uint8_t aVirtualKey);
+ static int CompareDeadKeyEntries(const void* aArg1, const void* aArg2,
+ void* aData);
+ static bool AddDeadKeyEntry(char16_t aBaseChar, char16_t aCompositeChar,
+ DeadKeyEntry* aDeadKeyArray, uint32_t aEntries);
+ bool EnsureDeadKeyActive(bool aIsActive, uint8_t aDeadKey,
+ const PBYTE aDeadKeyKbdState);
+ uint32_t GetDeadKeyCombinations(uint8_t aDeadKey,
+ const PBYTE aDeadKeyKbdState,
+ uint16_t aShiftStatesWithBaseChars,
+ DeadKeyEntry* aDeadKeyArray,
+ uint32_t aMaxEntries);
+ /**
+ * Activates or deactivates dead key state.
+ */
+ void ActivateDeadKeyState(const NativeKey& aNativeKey);
+ void DeactivateDeadKeyState();
+
+ const DeadKeyTable* AddDeadKeyTable(const DeadKeyEntry* aDeadKeyArray,
+ uint32_t aEntries);
+ void ReleaseDeadKeyTables();
+
+ /**
+ * Loads the specified keyboard layout. This method always clear the dead key
+ * state.
+ */
+ void LoadLayout(HKL aLayout);
+
+ /**
+ * Gets the keyboard layout name of aLayout. Be careful, this may be too
+ * slow to call at handling user input.
+ */
+ nsCString GetLayoutName(HKL aLayout) const;
+
+ /**
+ * InitNativeKey() must be called when actually widget receives WM_KEYDOWN or
+ * WM_KEYUP. This method is stateful. This saves current dead key state at
+ * WM_KEYDOWN. Additionally, computes current inputted character(s) and set
+ * them to the aNativeKey.
+ */
+ void InitNativeKey(NativeKey& aNativeKey);
+
+ /**
+ * MaybeInitNativeKeyAsDeadKey() initializes aNativeKey only when aNativeKey
+ * is a dead key's event.
+ * When it's not in a dead key sequence, this activates the dead key state.
+ * When it's in a dead key sequence, this initializes aNativeKey with a
+ * composite character or a preceding dead char and a dead char which should
+ * be caused by aNativeKey.
+ * Returns true when this initializes aNativeKey. Otherwise, false.
+ */
+ bool MaybeInitNativeKeyAsDeadKey(NativeKey& aNativeKey);
+
+ /**
+ * MaybeInitNativeKeyWithCompositeChar() may initialize aNativeKey with
+ * proper composite character when dead key produces a composite character.
+ * Otherwise, just returns false.
+ */
+ bool MaybeInitNativeKeyWithCompositeChar(NativeKey& aNativeKey);
+
+ /**
+ * See the comment of GetUniCharsAndModifiers() below.
+ */
+ UniCharsAndModifiers GetUniCharsAndModifiers(
+ uint8_t aVirtualKey, VirtualKey::ShiftState aShiftState) const;
+
+ /**
+ * GetDeadUniCharsAndModifiers() returns dead chars which are stored in
+ * current dead key sequence. So, this is stateful.
+ */
+ UniCharsAndModifiers GetDeadUniCharsAndModifiers() const;
+
+ /**
+ * GetCompositeChar() returns a composite character with dead character
+ * caused by mActiveDeadKeys, mDeadKeyShiftStates and a base character
+ * (aBaseChar).
+ * If the combination of the dead character and the base character doesn't
+ * cause a composite character, this returns 0.
+ */
+ char16_t GetCompositeChar(char16_t aBaseChar) const;
+
+ // NativeKey class should access InitNativeKey() directly, but it shouldn't
+ // be available outside of NativeKey. So, let's make NativeKey a friend
+ // class of this.
+ friend class NativeKey;
+};
+
+class RedirectedKeyDownMessageManager {
+ public:
+ /*
+ * If a window receives WM_KEYDOWN message or WM_SYSKEYDOWM message which is
+ * a redirected message, NativeKey::DispatchKeyDownAndKeyPressEvent()
+ * prevents to dispatch eKeyDown event because it has been dispatched
+ * before the message was redirected. However, in some cases, WM_*KEYDOWN
+ * message handler may not handle actually. Then, the message handler needs
+ * to forget the redirected message and remove WM_CHAR message or WM_SYSCHAR
+ * message for the redirected keydown message. AutoFlusher class is a helper
+ * class for doing it. This must be created in the stack.
+ */
+ class MOZ_STACK_CLASS AutoFlusher final {
+ public:
+ AutoFlusher(nsWindowBase* aWidget, const MSG& aMsg)
+ : mCancel(!RedirectedKeyDownMessageManager::IsRedirectedMessage(aMsg)),
+ mWidget(aWidget),
+ mMsg(aMsg) {}
+
+ ~AutoFlusher() {
+ if (mCancel) {
+ return;
+ }
+ // Prevent unnecessary keypress event
+ if (!mWidget->Destroyed()) {
+ RedirectedKeyDownMessageManager::RemoveNextCharMessage(mMsg.hwnd);
+ }
+ // Foreget the redirected message
+ RedirectedKeyDownMessageManager::Forget();
+ }
+
+ void Cancel() { mCancel = true; }
+
+ private:
+ bool mCancel;
+ RefPtr<nsWindowBase> mWidget;
+ const MSG& mMsg;
+ };
+
+ static void WillRedirect(const MSG& aMsg, bool aDefualtPrevented) {
+ sRedirectedKeyDownMsg = aMsg;
+ sDefaultPreventedOfRedirectedMsg = aDefualtPrevented;
+ }
+
+ static void Forget() { sRedirectedKeyDownMsg.message = WM_NULL; }
+
+ static void PreventDefault() { sDefaultPreventedOfRedirectedMsg = true; }
+ static bool DefaultPrevented() { return sDefaultPreventedOfRedirectedMsg; }
+
+ static bool IsRedirectedMessage(const MSG& aMsg);
+
+ /**
+ * RemoveNextCharMessage() should be called by WM_KEYDOWN or WM_SYSKEYDOWM
+ * message handler. If there is no WM_(SYS)CHAR message for it, this
+ * method does nothing.
+ * NOTE: WM_(SYS)CHAR message is posted by TranslateMessage() API which is
+ * called in message loop. So, WM_(SYS)KEYDOWN message should have
+ * WM_(SYS)CHAR message in the queue if the keydown event causes character
+ * input.
+ */
+ static void RemoveNextCharMessage(HWND aWnd);
+
+ private:
+ // sRedirectedKeyDownMsg is WM_KEYDOWN message or WM_SYSKEYDOWN message which
+ // is reirected with SendInput() API by
+ // widget::NativeKey::DispatchKeyDownAndKeyPressEvent()
+ static MSG sRedirectedKeyDownMsg;
+ static bool sDefaultPreventedOfRedirectedMsg;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/windows/LSPAnnotator.cpp b/widget/windows/LSPAnnotator.cpp
new file mode 100644
index 0000000000..d0db086cd8
--- /dev/null
+++ b/widget/windows/LSPAnnotator.cpp
@@ -0,0 +1,135 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * LSPs are evil little bits of code that are allowed to inject into our
+ * networking stack by Windows. Once they have wormed into our process
+ * they gnaw at our innards until we crash. Here we force the buggers
+ * into the light by recording them in our crash information.
+ * We do the enumeration on a thread because I'm concerned about startup perf
+ * on machines with several LSPs.
+ */
+
+#include "nsICrashReporter.h"
+#include "nsISupportsImpl.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsQueryObject.h"
+#include "nsWindowsHelpers.h"
+#include <windows.h>
+#include <rpc.h>
+#include <ws2spi.h>
+
+namespace mozilla {
+namespace crashreporter {
+
+class LSPAnnotationGatherer : public Runnable {
+ ~LSPAnnotationGatherer() {}
+
+ public:
+ LSPAnnotationGatherer() : Runnable("crashreporter::LSPAnnotationGatherer") {}
+ NS_DECL_NSIRUNNABLE
+
+ void Annotate();
+ nsCString mString;
+};
+
+void LSPAnnotationGatherer::Annotate() {
+ nsCOMPtr<nsICrashReporter> cr =
+ do_GetService("@mozilla.org/toolkit/crash-reporter;1");
+ bool enabled;
+ if (cr && NS_SUCCEEDED(cr->GetEnabled(&enabled)) && enabled) {
+ cr->AnnotateCrashReport("Winsock_LSP"_ns, mString);
+ }
+}
+
+NS_IMETHODIMP
+LSPAnnotationGatherer::Run() {
+ DWORD size = 0;
+ int err;
+ // Get the size of the buffer we need
+ if (SOCKET_ERROR != WSCEnumProtocols(nullptr, nullptr, &size, &err) ||
+ err != WSAENOBUFS) {
+ // Er, what?
+ MOZ_ASSERT_UNREACHABLE(
+ "WSCEnumProtocols succeeded when it should have "
+ "failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ auto byteArray = MakeUnique<char[]>(size);
+ WSAPROTOCOL_INFOW* providers =
+ reinterpret_cast<WSAPROTOCOL_INFOW*>(byteArray.get());
+
+ int n = WSCEnumProtocols(nullptr, providers, &size, &err);
+ if (n == SOCKET_ERROR) {
+ // Lame. We provided the right size buffer; we'll just give up now.
+ NS_WARNING("Could not get LSP list");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString str;
+ for (int i = 0; i < n; i++) {
+ AppendUTF16toUTF8(nsDependentString(providers[i].szProtocol), str);
+ str.AppendLiteral(" : ");
+ str.AppendInt(providers[i].iVersion);
+ str.AppendLiteral(" : ");
+ str.AppendInt(providers[i].iAddressFamily);
+ str.AppendLiteral(" : ");
+ str.AppendInt(providers[i].iSocketType);
+ str.AppendLiteral(" : ");
+ str.AppendInt(providers[i].iProtocol);
+ str.AppendLiteral(" : ");
+ str.AppendPrintf("0x%x", providers[i].dwServiceFlags1);
+ str.AppendLiteral(" : ");
+ str.AppendPrintf("0x%x", providers[i].dwProviderFlags);
+ str.AppendLiteral(" : ");
+
+ wchar_t path[MAX_PATH];
+ int pathLen = MAX_PATH;
+ if (!WSCGetProviderPath(&providers[i].ProviderId, path, &pathLen, &err)) {
+ AppendUTF16toUTF8(nsDependentString(path), str);
+ }
+
+ str.AppendLiteral(" : ");
+ // Call WSCGetProviderInfo to obtain the category flags for this provider.
+ // When present, these flags inform Windows as to which order to chain the
+ // providers.
+ DWORD categoryInfo;
+ size_t categoryInfoSize = sizeof(categoryInfo);
+ if (!WSCGetProviderInfo(&providers[i].ProviderId, ProviderInfoLspCategories,
+ (PBYTE)&categoryInfo, &categoryInfoSize, 0, &err)) {
+ str.AppendPrintf("0x%lx", categoryInfo);
+ }
+
+ str.AppendLiteral(" : ");
+ if (providers[i].ProtocolChain.ChainLen <= BASE_PROTOCOL) {
+ // If we're dealing with a catalog entry that identifies an individual
+ // base or layer provider, log its provider GUID.
+ RPC_CSTR provIdStr = nullptr;
+ if (UuidToStringA(&providers[i].ProviderId, &provIdStr) == RPC_S_OK) {
+ str.Append(reinterpret_cast<char*>(provIdStr));
+ RpcStringFreeA(&provIdStr);
+ }
+ }
+
+ if (i + 1 != n) {
+ str.AppendLiteral(" \n ");
+ }
+ }
+
+ mString = str;
+ NS_DispatchToMainThread(
+ NewRunnableMethod("crashreporter::LSPAnnotationGatherer::Annotate", this,
+ &LSPAnnotationGatherer::Annotate));
+ return NS_OK;
+}
+
+void LSPAnnotate() {
+ nsCOMPtr<nsIRunnable> runnable(new LSPAnnotationGatherer());
+ NS_DispatchBackgroundTask(runnable.forget());
+}
+
+} // namespace crashreporter
+} // namespace mozilla
diff --git a/widget/windows/MediaKeysEventSourceFactory.cpp b/widget/windows/MediaKeysEventSourceFactory.cpp
new file mode 100644
index 0000000000..525aab19c6
--- /dev/null
+++ b/widget/windows/MediaKeysEventSourceFactory.cpp
@@ -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 "MediaKeysEventSourceFactory.h"
+#include "WindowsSMTCProvider.h"
+
+namespace mozilla {
+namespace widget {
+
+mozilla::dom::MediaControlKeySource* CreateMediaControlKeySource() {
+#ifndef __MINGW32__
+ return new WindowsSMTCProvider();
+#else
+ return nullptr; // MinGW doesn't support the required Windows 8.1+ APIs
+#endif
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/OSKInputPaneManager.cpp b/widget/windows/OSKInputPaneManager.cpp
new file mode 100644
index 0000000000..293a84cd28
--- /dev/null
+++ b/widget/windows/OSKInputPaneManager.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+#define NTDDI_VERSION NTDDI_WIN10_RS1
+
+#include "OSKInputPaneManager.h"
+#include "nsDebug.h"
+
+#ifndef __MINGW32__
+# include <inputpaneinterop.h>
+# include <windows.ui.viewmanagement.h>
+# include <wrl.h>
+
+using namespace ABI::Windows::UI::ViewManagement;
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+#endif
+
+namespace mozilla {
+namespace widget {
+
+#ifndef __MINGW32__
+static ComPtr<IInputPane2> GetInputPane(HWND aHwnd) {
+ ComPtr<IInputPaneInterop> inputPaneInterop;
+ HRESULT hr = RoGetActivationFactory(
+ HStringReference(RuntimeClass_Windows_UI_ViewManagement_InputPane).Get(),
+ IID_PPV_ARGS(&inputPaneInterop));
+ if (NS_WARN_IF(FAILED(hr))) {
+ return nullptr;
+ }
+
+ ComPtr<IInputPane> inputPane;
+ hr = inputPaneInterop->GetForWindow(aHwnd, IID_PPV_ARGS(&inputPane));
+ if (NS_WARN_IF(FAILED(hr))) {
+ return nullptr;
+ }
+
+ ComPtr<IInputPane2> inputPane2;
+ inputPane.As(&inputPane2);
+ return inputPane2;
+}
+
+# ifdef DEBUG
+static bool IsInputPaneVisible(ComPtr<IInputPane2>& aInputPane2) {
+ ComPtr<IInputPaneControl> inputPaneControl;
+ aInputPane2.As(&inputPaneControl);
+ if (NS_WARN_IF(!inputPaneControl)) {
+ return false;
+ }
+ boolean visible = false;
+ inputPaneControl->get_Visible(&visible);
+ return visible;
+}
+
+static bool IsForegroundApp() {
+ HWND foregroundWnd = ::GetForegroundWindow();
+ if (!foregroundWnd) {
+ return false;
+ }
+ DWORD pid;
+ ::GetWindowThreadProcessId(foregroundWnd, &pid);
+ return pid == ::GetCurrentProcessId();
+}
+# endif
+#endif
+
+// static
+void OSKInputPaneManager::ShowOnScreenKeyboard(HWND aWnd) {
+#ifndef __MINGW32__
+ ComPtr<IInputPane2> inputPane2 = GetInputPane(aWnd);
+ if (!inputPane2) {
+ return;
+ }
+ boolean result;
+ inputPane2->TryShow(&result);
+ NS_WARNING_ASSERTION(
+ result || !IsForegroundApp() || IsInputPaneVisible(inputPane2),
+ "IInputPane2::TryShow is failure");
+#endif
+}
+
+// static
+void OSKInputPaneManager::DismissOnScreenKeyboard(HWND aWnd) {
+#ifndef __MINGW32__
+ ComPtr<IInputPane2> inputPane2 = GetInputPane(aWnd);
+ if (!inputPane2) {
+ return;
+ }
+ boolean result;
+ inputPane2->TryHide(&result);
+ NS_WARNING_ASSERTION(
+ result || !IsForegroundApp() || !IsInputPaneVisible(inputPane2),
+ "IInputPane2::TryHide is failure");
+#endif
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/OSKInputPaneManager.h b/widget/windows/OSKInputPaneManager.h
new file mode 100644
index 0000000000..421a5f1e02
--- /dev/null
+++ b/widget/windows/OSKInputPaneManager.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_OSKInputPaneManager_h
+#define mozilla_widget_OSKInputPaneManager_h
+
+#include <windows.h>
+
+namespace mozilla {
+namespace widget {
+
+class OSKInputPaneManager final {
+ public:
+ static void ShowOnScreenKeyboard(HWND aHwnd);
+ static void DismissOnScreenKeyboard(HWND aHwnd);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_OSKInputPaneManager_h
diff --git a/widget/windows/PCompositorWidget.ipdl b/widget/windows/PCompositorWidget.ipdl
new file mode 100644
index 0000000000..a2d8db5896
--- /dev/null
+++ b/widget/windows/PCompositorWidget.ipdl
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 protocol PCompositorBridge;
+
+include "mozilla/widget/WidgetMessageUtils.h";
+
+using mozilla::gfx::IntSize from "mozilla/gfx/Point.h";
+using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h";
+using nsTransparencyMode from "nsIWidget.h";
+
+namespace mozilla {
+namespace widget {
+
+struct RemoteBackbufferHandles {
+ WindowsHandle fileMapping;
+ WindowsHandle requestReadyEvent;
+ WindowsHandle responseReadyEvent;
+};
+
+sync protocol PCompositorWidget
+{
+ manager PCompositorBridge;
+
+parent:
+ sync Initialize(RemoteBackbufferHandles aRemoteHandles);
+
+ sync EnterPresentLock();
+ sync LeavePresentLock();
+ async UpdateTransparency(nsTransparencyMode aMode);
+ sync ClearTransparentWindow();
+ async __delete__();
+
+child:
+ async ObserveVsync();
+ async UnobserveVsync();
+ async UpdateCompositorWnd(WindowsHandle aCompositorWnd, WindowsHandle aParentWnd)
+ returns (bool success);
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/PlatformWidgetTypes.ipdlh b/widget/windows/PlatformWidgetTypes.ipdlh
new file mode 100644
index 0000000000..1b7dc66195
--- /dev/null
+++ b/widget/windows/PlatformWidgetTypes.ipdlh
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/TabMessageUtils.h";
+include "mozilla/widget/WidgetMessageUtils.h";
+
+include HeadlessWidgetTypes;
+
+using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h";
+using nsSizeMode from "nsIWidgetListener.h";
+using nsTransparencyMode from "nsIWidget.h";
+
+namespace mozilla {
+namespace widget {
+
+struct WinCompositorWidgetInitData
+{
+ WindowsHandle hWnd;
+ uintptr_t widgetKey;
+ nsTransparencyMode transparencyMode;
+ nsSizeMode sizeMode;
+};
+
+union CompositorWidgetInitData
+{
+ WinCompositorWidgetInitData;
+ HeadlessCompositorWidgetInitData;
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/RemoteBackbuffer.cpp b/widget/windows/RemoteBackbuffer.cpp
new file mode 100644
index 0000000000..08b420affb
--- /dev/null
+++ b/widget/windows/RemoteBackbuffer.cpp
@@ -0,0 +1,675 @@
+/* -*- 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 "RemoteBackbuffer.h"
+#include "mozilla/Span.h"
+#include <algorithm>
+#include <type_traits>
+
+namespace mozilla {
+namespace widget {
+namespace remote_backbuffer {
+
+// This number can be adjusted as a time-memory tradeoff
+constexpr uint8_t kMaxDirtyRects = 8;
+
+struct IpcSafeRect {
+ explicit IpcSafeRect(const gfx::IntRect& aRect)
+ : x(aRect.x), y(aRect.y), width(aRect.width), height(aRect.height) {}
+ int32_t x;
+ int32_t y;
+ int32_t width;
+ int32_t height;
+};
+
+enum class ResponseResult {
+ Unknown,
+ Error,
+ BorrowSuccess,
+ BorrowSameBuffer,
+ PresentSuccess
+};
+
+enum class SharedDataType {
+ BorrowRequest,
+ BorrowRequestAllowSameBuffer,
+ BorrowResponse,
+ PresentRequest,
+ PresentResponse
+};
+
+struct BorrowResponseData {
+ ResponseResult result;
+ int32_t width;
+ int32_t height;
+ HANDLE fileMapping;
+};
+
+struct PresentRequestData {
+ uint8_t lenDirtyRects;
+ IpcSafeRect dirtyRects[kMaxDirtyRects];
+};
+
+struct PresentResponseData {
+ ResponseResult result;
+};
+
+struct SharedData {
+ SharedDataType dataType;
+ union {
+ BorrowResponseData borrowResponse;
+ PresentRequestData presentRequest;
+ PresentResponseData presentResponse;
+ } data;
+};
+
+static_assert(std::is_trivially_copyable<SharedData>::value &&
+ std::is_standard_layout<SharedData>::value,
+ "SharedData must be safe to pass over IPC boundaries");
+
+class SharedImage {
+ public:
+ SharedImage()
+ : mWidth(0), mHeight(0), mFileMapping(nullptr), mPixelData(nullptr) {}
+
+ ~SharedImage() {
+ if (mPixelData) {
+ MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mPixelData));
+ }
+
+ if (mFileMapping) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping));
+ }
+ }
+
+ bool Initialize(int32_t aWidth, int32_t aHeight) {
+ MOZ_ASSERT(aWidth > 0);
+ MOZ_ASSERT(aHeight > 0);
+
+ mWidth = aWidth;
+ mHeight = aHeight;
+
+ DWORD bufferSize = static_cast<DWORD>(mHeight * GetStride());
+
+ mFileMapping = ::CreateFileMappingW(
+ INVALID_HANDLE_VALUE, nullptr /*secattr*/, PAGE_READWRITE,
+ 0 /*sizeHigh*/, bufferSize, nullptr /*name*/);
+ if (!mFileMapping) {
+ return false;
+ }
+
+ void* mappedFilePtr =
+ ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/,
+ 0 /*offsetLow*/, 0 /*bytesToMap*/);
+ if (!mappedFilePtr) {
+ return false;
+ }
+
+ mPixelData = reinterpret_cast<unsigned char*>(mappedFilePtr);
+
+ return true;
+ }
+
+ bool InitializeRemote(int32_t aWidth, int32_t aHeight, HANDLE aFileMapping) {
+ MOZ_ASSERT(aWidth > 0);
+ MOZ_ASSERT(aHeight > 0);
+ MOZ_ASSERT(aFileMapping);
+
+ mWidth = aWidth;
+ mHeight = aHeight;
+ mFileMapping = aFileMapping;
+
+ void* mappedFilePtr =
+ ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/,
+ 0 /*offsetLow*/, 0 /*bytesToMap*/);
+ if (!mappedFilePtr) {
+ return false;
+ }
+
+ mPixelData = reinterpret_cast<unsigned char*>(mappedFilePtr);
+
+ return true;
+ }
+
+ HBITMAP CreateDIBSection() {
+ BITMAPINFO bitmapInfo = {};
+ bitmapInfo.bmiHeader.biSize = sizeof(bitmapInfo.bmiHeader);
+ bitmapInfo.bmiHeader.biWidth = mWidth;
+ bitmapInfo.bmiHeader.biHeight = -mHeight;
+ bitmapInfo.bmiHeader.biPlanes = 1;
+ bitmapInfo.bmiHeader.biBitCount = 32;
+ bitmapInfo.bmiHeader.biCompression = BI_RGB;
+ void* dummy = nullptr;
+ return ::CreateDIBSection(nullptr /*paletteDC*/, &bitmapInfo,
+ DIB_RGB_COLORS, &dummy, mFileMapping,
+ 0 /*offset*/);
+ }
+
+ HANDLE CreateRemoteFileMapping(DWORD aTargetProcessId) {
+ MOZ_ASSERT(aTargetProcessId);
+
+ HANDLE fileMapping = nullptr;
+ if (!ipc::DuplicateHandle(mFileMapping, aTargetProcessId, &fileMapping,
+ 0 /*desiredAccess*/, DUPLICATE_SAME_ACCESS)) {
+ return nullptr;
+ }
+ return fileMapping;
+ }
+
+ already_AddRefed<gfx::DrawTarget> CreateDrawTarget() {
+ return gfx::Factory::CreateDrawTargetForData(
+ gfx::BackendType::CAIRO, mPixelData, IntSize(mWidth, mHeight),
+ GetStride(), gfx::SurfaceFormat::B8G8R8A8);
+ }
+
+ void CopyPixelsFrom(const SharedImage& other) {
+ const unsigned char* src = other.mPixelData;
+ unsigned char* dst = mPixelData;
+
+ int32_t width = std::min(mWidth, other.mWidth);
+ int32_t height = std::min(mHeight, other.mHeight);
+
+ for (int32_t row = 0; row < height; ++row) {
+ memcpy(dst, src, static_cast<uint32_t>(width * kBytesPerPixel));
+ src += other.GetStride();
+ dst += GetStride();
+ }
+ }
+
+ int32_t GetWidth() { return mWidth; }
+
+ int32_t GetHeight() { return mHeight; }
+
+ SharedImage(const SharedImage&) = delete;
+ SharedImage(SharedImage&&) = delete;
+ SharedImage& operator=(const SharedImage&) = delete;
+ SharedImage& operator=(SharedImage&&) = delete;
+
+ private:
+ static constexpr int32_t kBytesPerPixel = 4;
+
+ int32_t GetStride() const {
+ // DIB requires 32-bit row alignment
+ return (((mWidth * kBytesPerPixel) + 3) / 4) * 4;
+ }
+
+ int32_t mWidth;
+ int32_t mHeight;
+ HANDLE mFileMapping;
+ unsigned char* mPixelData;
+};
+
+class PresentableSharedImage {
+ public:
+ PresentableSharedImage()
+ : mSharedImage(),
+ mDeviceContext(nullptr),
+ mDIBSection(nullptr),
+ mSavedObject(nullptr) {}
+
+ ~PresentableSharedImage() {
+ if (mSavedObject) {
+ MOZ_ALWAYS_TRUE(::SelectObject(mDeviceContext, mSavedObject));
+ }
+
+ if (mDIBSection) {
+ MOZ_ALWAYS_TRUE(::DeleteObject(mDIBSection));
+ }
+
+ if (mDeviceContext) {
+ MOZ_ALWAYS_TRUE(::DeleteDC(mDeviceContext));
+ }
+ }
+
+ bool Initialize(int32_t aWidth, int32_t aHeight) {
+ if (!mSharedImage.Initialize(aWidth, aHeight)) {
+ return false;
+ }
+
+ mDeviceContext = ::CreateCompatibleDC(nullptr);
+ if (!mDeviceContext) {
+ return false;
+ }
+
+ mDIBSection = mSharedImage.CreateDIBSection();
+ if (!mDIBSection) {
+ return false;
+ }
+
+ mSavedObject = ::SelectObject(mDeviceContext, mDIBSection);
+ if (!mSavedObject) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool PresentToWindow(HWND aWindowHandle, nsTransparencyMode aTransparencyMode,
+ Span<const IpcSafeRect> aDirtyRects) {
+ if (aTransparencyMode == eTransparencyTransparent) {
+ // If our window is a child window or a child-of-a-child, the window
+ // that needs to be updated is the top level ancestor of the tree
+ HWND topLevelWindow = WinUtils::GetTopLevelHWND(aWindowHandle, true);
+ MOZ_ASSERT(::GetWindowLongPtr(topLevelWindow, GWL_EXSTYLE) &
+ WS_EX_LAYERED);
+
+ BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
+ SIZE winSize = {mSharedImage.GetWidth(), mSharedImage.GetHeight()};
+ POINT srcPos = {0, 0};
+ return !!::UpdateLayeredWindow(
+ topLevelWindow, nullptr /*paletteDC*/, nullptr /*newPos*/, &winSize,
+ mDeviceContext, &srcPos, 0 /*colorKey*/, &bf, ULW_ALPHA);
+ }
+
+ IntRect sharedImageRect{0, 0, mSharedImage.GetWidth(),
+ mSharedImage.GetHeight()};
+
+ bool result = true;
+
+ HDC windowDC = ::GetDC(aWindowHandle);
+ if (!windowDC) {
+ return false;
+ }
+
+ for (auto& ipcDirtyRect : aDirtyRects) {
+ IntRect dirtyRect{ipcDirtyRect.x, ipcDirtyRect.y, ipcDirtyRect.width,
+ ipcDirtyRect.height};
+ IntRect bltRect = dirtyRect.Intersect(sharedImageRect);
+
+ if (!::BitBlt(windowDC, bltRect.x /*dstX*/, bltRect.y /*dstY*/,
+ bltRect.width, bltRect.height, mDeviceContext,
+ bltRect.x /*srcX*/, bltRect.y /*srcY*/, SRCCOPY)) {
+ result = false;
+ break;
+ }
+ }
+
+ MOZ_ALWAYS_TRUE(::ReleaseDC(aWindowHandle, windowDC));
+
+ return result;
+ }
+
+ HANDLE CreateRemoteFileMapping(DWORD aTargetProcessId) {
+ return mSharedImage.CreateRemoteFileMapping(aTargetProcessId);
+ }
+
+ already_AddRefed<gfx::DrawTarget> CreateDrawTarget() {
+ return mSharedImage.CreateDrawTarget();
+ }
+
+ void CopyPixelsFrom(const PresentableSharedImage& other) {
+ mSharedImage.CopyPixelsFrom(other.mSharedImage);
+ }
+
+ int32_t GetWidth() { return mSharedImage.GetWidth(); }
+
+ int32_t GetHeight() { return mSharedImage.GetHeight(); }
+
+ PresentableSharedImage(const PresentableSharedImage&) = delete;
+ PresentableSharedImage(PresentableSharedImage&&) = delete;
+ PresentableSharedImage& operator=(const PresentableSharedImage&) = delete;
+ PresentableSharedImage& operator=(PresentableSharedImage&&) = delete;
+
+ private:
+ SharedImage mSharedImage;
+ HDC mDeviceContext;
+ HBITMAP mDIBSection;
+ HGDIOBJ mSavedObject;
+};
+
+Provider::Provider()
+ : mWindowHandle(nullptr),
+ mTargetProcessId(0),
+ mFileMapping(nullptr),
+ mRequestReadyEvent(nullptr),
+ mResponseReadyEvent(nullptr),
+ mSharedDataPtr(nullptr),
+ mStopServiceThread(false),
+ mServiceThread(),
+ mBackbuffer() {}
+
+Provider::~Provider() {
+ mBackbuffer.reset();
+
+ if (mServiceThread.joinable()) {
+ mStopServiceThread = true;
+ MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent));
+ mServiceThread.join();
+ }
+
+ if (mSharedDataPtr) {
+ MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mSharedDataPtr));
+ }
+
+ if (mResponseReadyEvent) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mResponseReadyEvent));
+ }
+
+ if (mRequestReadyEvent) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mRequestReadyEvent));
+ }
+
+ if (mFileMapping) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping));
+ }
+}
+
+bool Provider::Initialize(HWND aWindowHandle, DWORD aTargetProcessId,
+ nsTransparencyMode aTransparencyMode) {
+ MOZ_ASSERT(aWindowHandle);
+ MOZ_ASSERT(aTargetProcessId);
+
+ mWindowHandle = aWindowHandle;
+ mTargetProcessId = aTargetProcessId;
+
+ mFileMapping = ::CreateFileMappingW(
+ INVALID_HANDLE_VALUE, nullptr /*secattr*/, PAGE_READWRITE, 0 /*sizeHigh*/,
+ static_cast<DWORD>(sizeof(SharedData)), nullptr /*name*/);
+ if (!mFileMapping) {
+ return false;
+ }
+
+ mRequestReadyEvent =
+ ::CreateEventW(nullptr /*secattr*/, FALSE /*manualReset*/,
+ FALSE /*initialState*/, nullptr /*name*/);
+ if (!mRequestReadyEvent) {
+ return false;
+ }
+
+ mResponseReadyEvent =
+ ::CreateEventW(nullptr /*secattr*/, FALSE /*manualReset*/,
+ FALSE /*initialState*/, nullptr /*name*/);
+ if (!mResponseReadyEvent) {
+ return false;
+ }
+
+ void* mappedFilePtr =
+ ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/,
+ 0 /*offsetLow*/, 0 /*bytesToMap*/);
+ if (!mappedFilePtr) {
+ return false;
+ }
+
+ mSharedDataPtr = reinterpret_cast<SharedData*>(mappedFilePtr);
+
+ mStopServiceThread = false;
+
+ mServiceThread = std::thread([this] { this->ThreadMain(); });
+
+ mTransparencyMode = aTransparencyMode;
+
+ return true;
+}
+
+Maybe<RemoteBackbufferHandles> Provider::CreateRemoteHandles() {
+ HANDLE fileMapping = nullptr;
+ if (!ipc::DuplicateHandle(mFileMapping, mTargetProcessId, &fileMapping,
+ 0 /*desiredAccess*/, DUPLICATE_SAME_ACCESS)) {
+ return Nothing();
+ }
+
+ HANDLE requestReadyEvent = nullptr;
+ if (!ipc::DuplicateHandle(mRequestReadyEvent, mTargetProcessId,
+ &requestReadyEvent, 0 /*desiredAccess*/,
+ DUPLICATE_SAME_ACCESS)) {
+ return Nothing();
+ }
+
+ HANDLE responseReadyEvent = nullptr;
+ if (!ipc::DuplicateHandle(mResponseReadyEvent, mTargetProcessId,
+ &responseReadyEvent, 0 /*desiredAccess*/,
+ DUPLICATE_SAME_ACCESS)) {
+ return Nothing();
+ }
+
+ return Some(RemoteBackbufferHandles(
+ reinterpret_cast<WindowsHandle>(fileMapping),
+ reinterpret_cast<WindowsHandle>(requestReadyEvent),
+ reinterpret_cast<WindowsHandle>(responseReadyEvent)));
+}
+
+void Provider::UpdateTransparencyMode(nsTransparencyMode aTransparencyMode) {
+ mTransparencyMode = aTransparencyMode;
+}
+
+void Provider::ThreadMain() {
+ while (true) {
+ MOZ_ALWAYS_TRUE(::WaitForSingleObject(mRequestReadyEvent, INFINITE) ==
+ WAIT_OBJECT_0);
+
+ if (mStopServiceThread) {
+ break;
+ }
+
+ switch (mSharedDataPtr->dataType) {
+ case SharedDataType::BorrowRequest:
+ case SharedDataType::BorrowRequestAllowSameBuffer: {
+ BorrowResponseData responseData = {};
+
+ HandleBorrowRequest(&responseData,
+ mSharedDataPtr->dataType ==
+ SharedDataType::BorrowRequestAllowSameBuffer);
+
+ mSharedDataPtr->dataType = SharedDataType::BorrowResponse;
+ mSharedDataPtr->data.borrowResponse = responseData;
+
+ MOZ_ALWAYS_TRUE(::SetEvent(mResponseReadyEvent));
+
+ break;
+ }
+ case SharedDataType::PresentRequest: {
+ PresentRequestData requestData = mSharedDataPtr->data.presentRequest;
+ PresentResponseData responseData = {};
+
+ HandlePresentRequest(requestData, &responseData);
+
+ mSharedDataPtr->dataType = SharedDataType::PresentResponse;
+ mSharedDataPtr->data.presentResponse = responseData;
+
+ MOZ_ALWAYS_TRUE(::SetEvent(mResponseReadyEvent));
+
+ break;
+ }
+ default:
+ break;
+ };
+ }
+}
+
+void Provider::HandleBorrowRequest(BorrowResponseData* aResponseData,
+ bool aAllowSameBuffer) {
+ MOZ_ASSERT(aResponseData);
+
+ aResponseData->result = ResponseResult::Error;
+
+ RECT clientRect = {};
+ if (!::GetClientRect(mWindowHandle, &clientRect)) {
+ return;
+ }
+
+ MOZ_ASSERT(clientRect.left == 0);
+ MOZ_ASSERT(clientRect.top == 0);
+
+ int32_t width = clientRect.right ? clientRect.right : 1;
+ int32_t height = clientRect.bottom ? clientRect.bottom : 1;
+
+ bool needNewBackbuffer = !aAllowSameBuffer || !mBackbuffer ||
+ (mBackbuffer->GetWidth() != width) ||
+ (mBackbuffer->GetHeight() != height);
+
+ if (!needNewBackbuffer) {
+ aResponseData->result = ResponseResult::BorrowSameBuffer;
+ return;
+ }
+
+ auto newBackbuffer = std::make_unique<PresentableSharedImage>();
+ if (!newBackbuffer->Initialize(width, height)) {
+ return;
+ }
+
+ // Preserve the contents of the old backbuffer (if it exists)
+ if (mBackbuffer) {
+ newBackbuffer->CopyPixelsFrom(*mBackbuffer);
+ mBackbuffer.reset();
+ }
+
+ HANDLE remoteFileMapping =
+ newBackbuffer->CreateRemoteFileMapping(mTargetProcessId);
+ if (!remoteFileMapping) {
+ return;
+ }
+
+ aResponseData->result = ResponseResult::BorrowSuccess;
+ aResponseData->width = width;
+ aResponseData->height = height;
+ aResponseData->fileMapping = remoteFileMapping;
+
+ mBackbuffer = std::move(newBackbuffer);
+}
+
+void Provider::HandlePresentRequest(const PresentRequestData& aRequestData,
+ PresentResponseData* aResponseData) {
+ MOZ_ASSERT(aResponseData);
+
+ Span rectSpan(aRequestData.dirtyRects, kMaxDirtyRects);
+
+ aResponseData->result = ResponseResult::Error;
+
+ if (!mBackbuffer) {
+ return;
+ }
+
+ if (!mBackbuffer->PresentToWindow(
+ mWindowHandle, mTransparencyMode,
+ rectSpan.First(aRequestData.lenDirtyRects))) {
+ return;
+ }
+
+ aResponseData->result = ResponseResult::PresentSuccess;
+}
+
+Client::Client()
+ : mFileMapping(nullptr),
+ mRequestReadyEvent(nullptr),
+ mResponseReadyEvent(nullptr),
+ mSharedDataPtr(nullptr),
+ mBackbuffer() {}
+
+Client::~Client() {
+ mBackbuffer.reset();
+
+ if (mSharedDataPtr) {
+ MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mSharedDataPtr));
+ }
+
+ if (mResponseReadyEvent) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mResponseReadyEvent));
+ }
+
+ if (mRequestReadyEvent) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mRequestReadyEvent));
+ }
+
+ if (mFileMapping) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping));
+ }
+}
+
+bool Client::Initialize(const RemoteBackbufferHandles& aRemoteHandles) {
+ MOZ_ASSERT(aRemoteHandles.fileMapping());
+ MOZ_ASSERT(aRemoteHandles.requestReadyEvent());
+ MOZ_ASSERT(aRemoteHandles.responseReadyEvent());
+
+ mFileMapping = reinterpret_cast<HANDLE>(aRemoteHandles.fileMapping());
+ mRequestReadyEvent =
+ reinterpret_cast<HANDLE>(aRemoteHandles.requestReadyEvent());
+ mResponseReadyEvent =
+ reinterpret_cast<HANDLE>(aRemoteHandles.responseReadyEvent());
+
+ void* mappedFilePtr =
+ ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/,
+ 0 /*offsetLow*/, 0 /*bytesToMap*/);
+ if (!mappedFilePtr) {
+ return false;
+ }
+
+ mSharedDataPtr = reinterpret_cast<SharedData*>(mappedFilePtr);
+
+ return true;
+}
+
+already_AddRefed<gfx::DrawTarget> Client::BorrowDrawTarget() {
+ mSharedDataPtr->dataType = mBackbuffer
+ ? SharedDataType::BorrowRequestAllowSameBuffer
+ : SharedDataType::BorrowRequest;
+
+ MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent));
+ MOZ_ALWAYS_TRUE(::WaitForSingleObject(mResponseReadyEvent, INFINITE) ==
+ WAIT_OBJECT_0);
+
+ if (mSharedDataPtr->dataType != SharedDataType::BorrowResponse) {
+ return nullptr;
+ }
+
+ BorrowResponseData responseData = mSharedDataPtr->data.borrowResponse;
+
+ if ((responseData.result != ResponseResult::BorrowSameBuffer) &&
+ (responseData.result != ResponseResult::BorrowSuccess)) {
+ return nullptr;
+ }
+
+ if (responseData.result == ResponseResult::BorrowSuccess) {
+ mBackbuffer.reset();
+
+ auto newBackbuffer = std::make_unique<SharedImage>();
+ if (!newBackbuffer->InitializeRemote(responseData.width,
+ responseData.height,
+ responseData.fileMapping)) {
+ return nullptr;
+ }
+
+ mBackbuffer = std::move(newBackbuffer);
+ }
+
+ MOZ_ASSERT(mBackbuffer);
+
+ return mBackbuffer->CreateDrawTarget();
+}
+
+bool Client::PresentDrawTarget(gfx::IntRegion aDirtyRegion) {
+ mSharedDataPtr->dataType = SharedDataType::PresentRequest;
+
+ // Simplify the region until it has <= kMaxDirtyRects
+ aDirtyRegion.SimplifyOutward(kMaxDirtyRects);
+
+ Span rectSpan(mSharedDataPtr->data.presentRequest.dirtyRects, kMaxDirtyRects);
+
+ uint8_t rectIndex = 0;
+ for (auto iter = aDirtyRegion.RectIter(); !iter.Done(); iter.Next()) {
+ rectSpan[rectIndex] = IpcSafeRect(iter.Get());
+ ++rectIndex;
+ }
+
+ mSharedDataPtr->data.presentRequest.lenDirtyRects = rectIndex;
+
+ MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent));
+ MOZ_ALWAYS_TRUE(::WaitForSingleObject(mResponseReadyEvent, INFINITE) ==
+ WAIT_OBJECT_0);
+
+ if (mSharedDataPtr->dataType != SharedDataType::PresentResponse) {
+ return false;
+ }
+
+ if (mSharedDataPtr->data.presentResponse.result !=
+ ResponseResult::PresentSuccess) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace remote_backbuffer
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/RemoteBackbuffer.h b/widget/windows/RemoteBackbuffer.h
new file mode 100644
index 0000000000..038078d4cd
--- /dev/null
+++ b/widget/windows/RemoteBackbuffer.h
@@ -0,0 +1,92 @@
+/* -*- 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 widget_windows_RemoteBackbuffer_h
+#define widget_windows_RemoteBackbuffer_h
+
+#include "nsIWidget.h"
+#include "mozilla/widget/PCompositorWidgetParent.h"
+#include "mozilla/Maybe.h"
+#include <thread>
+#include <windows.h>
+
+namespace mozilla {
+namespace widget {
+namespace remote_backbuffer {
+
+struct IpcRect;
+struct SharedData;
+struct BorrowResponseData;
+struct PresentRequestData;
+struct PresentResponseData;
+class SharedImage;
+class PresentableSharedImage;
+
+class Provider {
+ public:
+ Provider();
+ ~Provider();
+
+ bool Initialize(HWND aWindowHandle, DWORD aTargetProcessId,
+ nsTransparencyMode aTransparencyMode);
+
+ Maybe<RemoteBackbufferHandles> CreateRemoteHandles();
+
+ void UpdateTransparencyMode(nsTransparencyMode aTransparencyMode);
+
+ Provider(const Provider&) = delete;
+ Provider(Provider&&) = delete;
+ Provider& operator=(const Provider&) = delete;
+ Provider& operator=(Provider&&) = delete;
+
+ private:
+ void ThreadMain();
+
+ void HandleBorrowRequest(BorrowResponseData* aResponseData,
+ bool aAllowSameBuffer);
+ void HandlePresentRequest(const PresentRequestData& aRequestData,
+ PresentResponseData* aResponseData);
+
+ HWND mWindowHandle;
+ DWORD mTargetProcessId;
+ HANDLE mFileMapping;
+ HANDLE mRequestReadyEvent;
+ HANDLE mResponseReadyEvent;
+ SharedData* mSharedDataPtr;
+ bool mStopServiceThread;
+ std::thread mServiceThread;
+ std::unique_ptr<PresentableSharedImage> mBackbuffer;
+ mozilla::Atomic<nsTransparencyMode, MemoryOrdering::Relaxed>
+ mTransparencyMode;
+};
+
+class Client {
+ public:
+ Client();
+ ~Client();
+
+ bool Initialize(const RemoteBackbufferHandles& aRemoteHandles);
+
+ already_AddRefed<gfx::DrawTarget> BorrowDrawTarget();
+ bool PresentDrawTarget(gfx::IntRegion aDirtyRegion);
+
+ Client(const Client&) = delete;
+ Client(Client&&) = delete;
+ Client& operator=(const Client&) = delete;
+ Client& operator=(Client&&) = delete;
+
+ private:
+ HANDLE mFileMapping;
+ HANDLE mRequestReadyEvent;
+ HANDLE mResponseReadyEvent;
+ SharedData* mSharedDataPtr;
+ std::unique_ptr<SharedImage> mBackbuffer;
+};
+
+} // namespace remote_backbuffer
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_RemoteBackbuffer_h
diff --git a/widget/windows/ScreenHelperWin.cpp b/widget/windows/ScreenHelperWin.cpp
new file mode 100644
index 0000000000..ff455b6c8c
--- /dev/null
+++ b/widget/windows/ScreenHelperWin.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "ScreenHelperWin.h"
+
+#include "mozilla/Logging.h"
+#include "nsTArray.h"
+#include "WinUtils.h"
+
+static mozilla::LazyLogModule sScreenLog("WidgetScreen");
+
+namespace mozilla {
+namespace widget {
+
+BOOL CALLBACK CollectMonitors(HMONITOR aMon, HDC, LPRECT, LPARAM ioParam) {
+ auto screens = reinterpret_cast<nsTArray<RefPtr<Screen>>*>(ioParam);
+ BOOL success = FALSE;
+ MONITORINFOEX info;
+ info.cbSize = sizeof(MONITORINFOEX);
+ success = ::GetMonitorInfoW(aMon, &info);
+ if (!success) {
+ MOZ_LOG(sScreenLog, LogLevel::Error, ("GetMonitorInfoW failed"));
+ return TRUE; // continue the enumeration
+ }
+
+ double scale = WinUtils::LogToPhysFactor(aMon);
+ DesktopToLayoutDeviceScale contentsScaleFactor;
+ if (WinUtils::IsPerMonitorDPIAware()) {
+ contentsScaleFactor.scale = 1.0;
+ } else {
+ contentsScaleFactor.scale = scale;
+ }
+ CSSToLayoutDeviceScale defaultCssScaleFactor(scale);
+ LayoutDeviceIntRect rect(info.rcMonitor.left, info.rcMonitor.top,
+ info.rcMonitor.right - info.rcMonitor.left,
+ info.rcMonitor.bottom - info.rcMonitor.top);
+ LayoutDeviceIntRect availRect(info.rcWork.left, info.rcWork.top,
+ info.rcWork.right - info.rcWork.left,
+ info.rcWork.bottom - info.rcWork.top);
+
+ HDC hDC = CreateDC(nullptr, info.szDevice, nullptr, nullptr);
+ if (!hDC) {
+ MOZ_LOG(sScreenLog, LogLevel::Error, ("CollectMonitors CreateDC failed"));
+ return TRUE;
+ }
+ uint32_t pixelDepth = ::GetDeviceCaps(hDC, BITSPIXEL);
+ DeleteDC(hDC);
+ if (pixelDepth == 32) {
+ // If a device uses 32 bits per pixel, it's still only using 8 bits
+ // per color component, which is what our callers want to know.
+ // (Some devices report 32 and some devices report 24.)
+ pixelDepth = 24;
+ }
+
+ float dpi = WinUtils::MonitorDPI(aMon);
+ MOZ_LOG(sScreenLog, LogLevel::Debug,
+ ("New screen [%d %d %d %d (%d %d %d %d) %d %f %f %f]", rect.X(),
+ rect.Y(), rect.Width(), rect.Height(), availRect.X(), availRect.Y(),
+ availRect.Width(), availRect.Height(), pixelDepth,
+ contentsScaleFactor.scale, defaultCssScaleFactor.scale, dpi));
+ auto screen = new Screen(rect, availRect, pixelDepth, pixelDepth,
+ contentsScaleFactor, defaultCssScaleFactor, dpi);
+ if (info.dwFlags & MONITORINFOF_PRIMARY) {
+ // The primary monitor must be the first element of the screen list.
+ screens->InsertElementAt(0, std::move(screen));
+ } else {
+ screens->AppendElement(std::move(screen));
+ }
+ return TRUE;
+}
+
+void ScreenHelperWin::RefreshScreens() {
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens"));
+
+ AutoTArray<RefPtr<Screen>, 4> screens;
+ BOOL result = ::EnumDisplayMonitors(
+ nullptr, nullptr, (MONITORENUMPROC)CollectMonitors, (LPARAM)&screens);
+ if (!result) {
+ NS_WARNING("Unable to EnumDisplayMonitors");
+ }
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ screenManager.Refresh(std::move(screens));
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/ScreenHelperWin.h b/widget/windows/ScreenHelperWin.h
new file mode 100644
index 0000000000..275014232a
--- /dev/null
+++ b/widget/windows/ScreenHelperWin.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_windows_ScreenHelperWin_h
+#define mozilla_widget_windows_ScreenHelperWin_h
+
+#include "mozilla/widget/ScreenManager.h"
+
+namespace mozilla {
+namespace widget {
+
+class ScreenHelperWin final : public ScreenManager::Helper {
+ public:
+ ScreenHelperWin(){};
+ ~ScreenHelperWin() override {}
+
+ static void RefreshScreens();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_windows_ScreenHelperWin_h
diff --git a/widget/windows/ScrollbarUtil.cpp b/widget/windows/ScrollbarUtil.cpp
new file mode 100644
index 0000000000..4c00dd1963
--- /dev/null
+++ b/widget/windows/ScrollbarUtil.cpp
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 40; 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 "ScrollbarUtil.h"
+
+#include "mozilla/RelativeLuminanceUtils.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "nsNativeTheme.h"
+
+/*static*/
+bool ScrollbarUtil::IsScrollbarWidthThin(ComputedStyle* aStyle) {
+ auto scrollbarWidth = aStyle->StyleUIReset()->mScrollbarWidth;
+ return scrollbarWidth == StyleScrollbarWidth::Thin;
+}
+
+/*static*/
+bool ScrollbarUtil::IsScrollbarWidthThin(nsIFrame* aFrame) {
+ ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
+ return IsScrollbarWidthThin(style);
+}
+
+/*static*/
+ComputedStyle* ScrollbarUtil::GetCustomScrollbarStyle(nsIFrame* aFrame,
+ bool* aDarkScrollbar) {
+ ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
+ if (style->StyleUI()->HasCustomScrollbars()) {
+ return style;
+ }
+ bool useDarkScrollbar = !StaticPrefs::widget_disable_dark_scrollbar() &&
+ nsNativeTheme::IsDarkBackground(aFrame);
+ if (useDarkScrollbar || IsScrollbarWidthThin(style)) {
+ if (aDarkScrollbar) {
+ *aDarkScrollbar = useDarkScrollbar;
+ }
+ return style;
+ }
+ return nullptr;
+}
+
+/*static*/
+nscolor ScrollbarUtil::GetScrollbarButtonColor(nscolor aTrackColor,
+ EventStates aStates) {
+ // See numbers in GetScrollbarArrowColor.
+ // This function is written based on ratios between values listed there.
+
+ bool isActive = aStates.HasState(NS_EVENT_STATE_ACTIVE);
+ bool isHover = aStates.HasState(NS_EVENT_STATE_HOVER);
+ if (!isActive && !isHover) {
+ return aTrackColor;
+ }
+ float luminance = RelativeLuminanceUtils::Compute(aTrackColor);
+ if (isActive) {
+ if (luminance >= 0.18f) {
+ luminance *= 0.134f;
+ } else {
+ luminance /= 0.134f;
+ luminance = std::min(luminance, 1.0f);
+ }
+ } else {
+ if (luminance >= 0.18f) {
+ luminance *= 0.805f;
+ } else {
+ luminance /= 0.805f;
+ }
+ }
+ return RelativeLuminanceUtils::Adjust(aTrackColor, luminance);
+}
+
+/*static*/
+nscolor ScrollbarUtil::GetScrollbarArrowColor(nscolor aButtonColor) {
+ // In Windows 10 scrollbar, there are several gray colors used:
+ //
+ // State | Background (lum) | Arrow | Contrast
+ // -------+------------------+---------+---------
+ // Normal | Gray 240 (87.1%) | Gray 96 | 5.5
+ // Hover | Gray 218 (70.1%) | Black | 15.0
+ // Active | Gray 96 (11.7%) | White | 6.3
+ //
+ // Contrast value is computed based on the definition in
+ // https://www.w3.org/TR/WCAG20/#contrast-ratiodef
+ //
+ // This function is written based on these values.
+
+ float luminance = RelativeLuminanceUtils::Compute(aButtonColor);
+ // Color with luminance larger than 0.72 has contrast ratio over 4.6
+ // to color with luminance of gray 96, so this value is chosen for
+ // this range. It is the luminance of gray 221.
+ if (luminance >= 0.72) {
+ // ComputeRelativeLuminanceFromComponents(96). That function cannot
+ // be constexpr because of std::pow.
+ const float GRAY96_LUMINANCE = 0.117f;
+ return RelativeLuminanceUtils::Adjust(aButtonColor, GRAY96_LUMINANCE);
+ }
+ // The contrast ratio of a color to black equals that to white when its
+ // luminance is around 0.18, with a contrast ratio ~4.6 to both sides,
+ // thus the value below. It's the lumanince of gray 118.
+ if (luminance >= 0.18) {
+ return NS_RGBA(0, 0, 0, NS_GET_A(aButtonColor));
+ }
+ return NS_RGBA(255, 255, 255, NS_GET_A(aButtonColor));
+}
+
+/*static*/
+nscolor ScrollbarUtil::AdjustScrollbarFaceColor(nscolor aFaceColor,
+ EventStates aStates) {
+ // In Windows 10, scrollbar thumb has the following colors:
+ //
+ // State | Color | Luminance
+ // -------+----------+----------
+ // Normal | Gray 205 | 61.0%
+ // Hover | Gray 166 | 38.1%
+ // Active | Gray 96 | 11.7%
+ //
+ // This function is written based on the ratios between the values.
+
+ bool isActive = aStates.HasState(NS_EVENT_STATE_ACTIVE);
+ bool isHover = aStates.HasState(NS_EVENT_STATE_HOVER);
+ if (!isActive && !isHover) {
+ return aFaceColor;
+ }
+ float luminance = RelativeLuminanceUtils::Compute(aFaceColor);
+ if (isActive) {
+ if (luminance >= 0.18f) {
+ luminance *= 0.192f;
+ } else {
+ luminance /= 0.192f;
+ }
+ } else {
+ if (luminance >= 0.18f) {
+ luminance *= 0.625f;
+ } else {
+ luminance /= 0.625f;
+ }
+ }
+ return RelativeLuminanceUtils::Adjust(aFaceColor, luminance);
+}
+
+/*static*/
+nscolor ScrollbarUtil::GetScrollbarTrackColor(nsIFrame* aFrame) {
+ bool darkScrollbar = false;
+ ComputedStyle* style = GetCustomScrollbarStyle(aFrame, &darkScrollbar);
+ if (style) {
+ const nsStyleUI* ui = style->StyleUI();
+ auto* customColors = ui->mScrollbarColor.IsAuto()
+ ? nullptr
+ : &ui->mScrollbarColor.AsColors();
+ if (customColors) {
+ return customColors->track.CalcColor(*style);
+ }
+ }
+ return darkScrollbar ? NS_RGBA(20, 20, 25, 77) : NS_RGB(240, 240, 240);
+}
+
+/*static*/
+nscolor ScrollbarUtil::GetScrollbarThumbColor(nsIFrame* aFrame,
+ EventStates aEventStates) {
+ bool darkScrollbar = false;
+ ComputedStyle* style = GetCustomScrollbarStyle(aFrame, &darkScrollbar);
+ if (style) {
+ const nsStyleUI* ui = style->StyleUI();
+ auto* customColors = ui->mScrollbarColor.IsAuto()
+ ? nullptr
+ : &ui->mScrollbarColor.AsColors();
+ if (customColors) {
+ nscolor faceColor = customColors->thumb.CalcColor(*style);
+ return AdjustScrollbarFaceColor(faceColor, aEventStates);
+ }
+ }
+ nscolor faceColor =
+ darkScrollbar ? NS_RGBA(249, 249, 250, 102) : NS_RGB(205, 205, 205);
+ return AdjustScrollbarFaceColor(faceColor, aEventStates);
+}
+
+/*static*/
+Maybe<nsITheme::Transparency> ScrollbarUtil::GetScrollbarPartTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ if (nsNativeTheme::IsWidgetScrollbarPart(aAppearance)) {
+ if (ComputedStyle* style = ScrollbarUtil::GetCustomScrollbarStyle(aFrame)) {
+ auto* ui = style->StyleUI();
+ if (ui->mScrollbarColor.IsAuto() ||
+ ui->mScrollbarColor.AsColors().track.MaybeTransparent()) {
+ return Some(nsITheme::eTransparent);
+ }
+ // These widgets may be thinner than the track, so we need to return
+ // transparent for them to make the track visible.
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ return Some(nsITheme::eTransparent);
+ default:
+ break;
+ }
+ }
+ }
+
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::Scrollcorner:
+ case StyleAppearance::Statusbar:
+ // Knowing that scrollbars and statusbars are opaque improves
+ // performance, because we create layers for them. This better be
+ // true across all Windows themes! If it's not true, we should
+ // paint an opaque background for them to make it true!
+ return Some(nsITheme::eOpaque);
+ default:
+ break;
+ }
+
+ return Nothing();
+}
diff --git a/widget/windows/ScrollbarUtil.h b/widget/windows/ScrollbarUtil.h
new file mode 100644
index 0000000000..9085653fb2
--- /dev/null
+++ b/widget/windows/ScrollbarUtil.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 40; 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 ScrollbarUtil_h
+#define ScrollbarUtil_h
+
+#include "nsLayoutUtils.h"
+#include "nsNativeTheme.h"
+
+class ScrollbarUtil {
+ public:
+ static bool IsScrollbarWidthThin(ComputedStyle* aStyle);
+ static bool IsScrollbarWidthThin(nsIFrame* aFrame);
+
+ // Returns the style for custom scrollbar if the scrollbar part frame should
+ // use the custom drawing path, nullptr otherwise.
+ //
+ // Optionally the caller can pass a pointer to aDarkScrollbar for whether
+ // custom scrollbar may be drawn due to dark background.
+ static ComputedStyle* GetCustomScrollbarStyle(nsIFrame* aFrame,
+ bool* aDarkScrollbar = nullptr);
+
+ static nscolor GetScrollbarButtonColor(nscolor aTrackColor,
+ EventStates aStates);
+ static nscolor GetScrollbarArrowColor(nscolor aButtonColor);
+ static nscolor AdjustScrollbarFaceColor(nscolor aFaceColor,
+ EventStates aStates);
+ static nscolor GetScrollbarTrackColor(nsIFrame* aFrame);
+ static nscolor GetScrollbarThumbColor(nsIFrame* aFrame,
+ EventStates aEventStates);
+ static Maybe<nsITheme::Transparency> GetScrollbarPartTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance);
+
+ protected:
+ ScrollbarUtil() = default;
+ virtual ~ScrollbarUtil() = default;
+};
+
+#endif
diff --git a/widget/windows/ShellHeaderOnlyUtils.h b/widget/windows/ShellHeaderOnlyUtils.h
new file mode 100644
index 0000000000..11849c99cc
--- /dev/null
+++ b/widget/windows/ShellHeaderOnlyUtils.h
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ShellHeaderOnlyUtils_h
+#define mozilla_ShellHeaderOnlyUtils_h
+
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+#include <objbase.h>
+
+#include <exdisp.h>
+#include <shldisp.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <shobjidl.h>
+#include <shtypes.h>
+// NB: include this after shldisp.h so its macros do not conflict with COM
+// interfaces defined by shldisp.h
+#include <shellapi.h>
+#include <type_traits>
+
+#include <comdef.h>
+#include <comutil.h>
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+/**
+ * Ask the current user's Desktop to ShellExecute on our behalf, thus causing
+ * the resulting launched process to inherit its security priviliges from
+ * Explorer instead of our process.
+ *
+ * This is useful in two scenarios, in particular:
+ * * We are running as an elevated user and we want to start something as the
+ * "normal" user;
+ * * We are starting a process that is incompatible with our process's
+ * process mitigation policies. By delegating to Explorer, the child process
+ * will not be affected by our process mitigations.
+ *
+ * Since this communication happens over DCOM, Explorer's COM DACL governs
+ * whether or not we can execute against it, thus avoiding privilege escalation.
+ */
+inline LauncherVoidResult ShellExecuteByExplorer(const _bstr_t& aPath,
+ const _variant_t& aArgs,
+ const _variant_t& aVerb,
+ const _variant_t& aWorkingDir,
+ const _variant_t& aShowCmd) {
+ // NB: Explorer may be a local server, not an inproc server
+ RefPtr<IShellWindows> shellWindows;
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_ShellWindows, nullptr, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
+ IID_IShellWindows, getter_AddRefs(shellWindows));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ // 1. Find the shell view for the desktop.
+ _variant_t loc(int(CSIDL_DESKTOP));
+ _variant_t empty;
+ long hwnd;
+ RefPtr<IDispatch> dispDesktop;
+ hr = shellWindows->FindWindowSW(&loc, &empty, SWC_DESKTOP, &hwnd,
+ SWFO_NEEDDISPATCH,
+ getter_AddRefs(dispDesktop));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ if (hr == S_FALSE) {
+ // The call succeeded but the window was not found.
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_NOT_FOUND);
+ }
+
+ RefPtr<IServiceProvider> servProv;
+ hr = dispDesktop->QueryInterface(IID_IServiceProvider,
+ getter_AddRefs(servProv));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ RefPtr<IShellBrowser> browser;
+ hr = servProv->QueryService(SID_STopLevelBrowser, IID_IShellBrowser,
+ getter_AddRefs(browser));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ RefPtr<IShellView> activeShellView;
+ hr = browser->QueryActiveShellView(getter_AddRefs(activeShellView));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ // 2. Get the automation object for the desktop.
+ RefPtr<IDispatch> dispView;
+ hr = activeShellView->GetItemObject(SVGIO_BACKGROUND, IID_IDispatch,
+ getter_AddRefs(dispView));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ RefPtr<IShellFolderViewDual> folderView;
+ hr = dispView->QueryInterface(IID_IShellFolderViewDual,
+ getter_AddRefs(folderView));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ // 3. Get the interface to IShellDispatch2
+ RefPtr<IDispatch> dispShell;
+ hr = folderView->get_Application(getter_AddRefs(dispShell));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ RefPtr<IShellDispatch2> shellDisp;
+ hr =
+ dispShell->QueryInterface(IID_IShellDispatch2, getter_AddRefs(shellDisp));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ // Passing the foreground privilege so that the shell can launch an
+ // application in the foreground. This fails with E_ACCESSDENIED if the
+ // current window is shown in the background. We keep a soft assert for
+ // the other failures to investigate how it happened.
+ hr = ::CoAllowSetForegroundWindow(shellDisp, nullptr);
+ MOZ_ASSERT(SUCCEEDED(hr) || hr == E_ACCESSDENIED);
+
+ // shellapi.h macros interfere with the correct naming of the method being
+ // called on IShellDispatch2. Temporarily remove that definition.
+#if defined(ShellExecute)
+# define MOZ_REDEFINE_SHELLEXECUTE
+# undef ShellExecute
+#endif // defined(ShellExecute)
+
+ // 4. Now call IShellDispatch2::ShellExecute to ask Explorer to execute.
+ hr = shellDisp->ShellExecute(aPath, aArgs, aWorkingDir, aVerb, aShowCmd);
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ // Restore the macro that was removed prior to IShellDispatch2::ShellExecute
+#if defined(MOZ_REDEFINE_SHELLEXECUTE)
+# if defined(UNICODE)
+# define ShellExecute ShellExecuteW
+# else
+# define ShellExecute ShellExecuteA
+# endif
+# undef MOZ_REDEFINE_SHELLEXECUTE
+#endif // defined(MOZ_REDEFINE_SHELLEXECUTE)
+
+ return Ok();
+}
+
+using UniqueAbsolutePidl =
+ UniquePtr<std::remove_pointer_t<PIDLIST_ABSOLUTE>, CoTaskMemFreeDeleter>;
+
+inline LauncherResult<UniqueAbsolutePidl> ShellParseDisplayName(
+ const wchar_t* aPath) {
+ PIDLIST_ABSOLUTE rawAbsPidl = nullptr;
+ SFGAOF sfgao;
+ HRESULT hr = ::SHParseDisplayName(aPath, nullptr, &rawAbsPidl, 0, &sfgao);
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ return UniqueAbsolutePidl(rawAbsPidl);
+}
+
+} // namespace mozilla
+
+#endif // mozilla_ShellHeaderOnlyUtils_h
diff --git a/widget/windows/SystemStatusBar.cpp b/widget/windows/SystemStatusBar.cpp
new file mode 100644
index 0000000000..8dc4ffed4b
--- /dev/null
+++ b/widget/windows/SystemStatusBar.cpp
@@ -0,0 +1,302 @@
+/* -*- Mode: C++; tab-width: 4; 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 <strsafe.h>
+#include "SystemStatusBar.h"
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/StaticPtr.h"
+#include "nsComputedDOMStyle.h"
+#include "nsIContentPolicy.h"
+#include "nsMenuFrame.h"
+#include "nsMenuPopupFrame.h"
+#include "nsXULPopupManager.h"
+#include "IconLoaderHelperWin.h"
+#include "nsIDocShell.h"
+#include "nsDocShell.h"
+
+namespace mozilla::widget {
+
+using mozilla::LinkedListElement;
+using mozilla::dom::Element;
+using mozilla::widget::IconLoaderListenerWin;
+
+class StatusBarEntry final : public LinkedListElement<RefPtr<StatusBarEntry>>,
+ public IconLoaderListenerWin {
+ public:
+ explicit StatusBarEntry(Element* aMenu);
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(StatusBarEntry)
+ nsresult Init();
+ LRESULT OnMessage(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp);
+ const Element* GetMenu() { return mMenu; };
+
+ nsresult OnComplete();
+
+ private:
+ ~StatusBarEntry();
+ RefPtr<mozilla::widget::IconLoader> mIconLoader;
+ RefPtr<mozilla::widget::IconLoaderHelperWin> mIconLoaderHelper;
+ RefPtr<Element> mMenu;
+ NOTIFYICONDATAW mIconData;
+ boolean mInitted;
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(StatusBarEntry)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StatusBarEntry)
+ tmp->OnComplete();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StatusBarEntry)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIconLoader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIconLoaderHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenu)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StatusBarEntry)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(StatusBarEntry)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(StatusBarEntry)
+
+StatusBarEntry::StatusBarEntry(Element* aMenu) : mMenu(aMenu), mInitted(false) {
+ mIconData = {/* cbSize */ sizeof(NOTIFYICONDATA),
+ /* hWnd */ 0,
+ /* uID */ 2,
+ /* uFlags */ NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP,
+ /* uCallbackMessage */ WM_USER,
+ /* hIcon */ 0,
+ /* szTip */ L"", // This is updated in Init()
+ /* dwState */ 0,
+ /* dwStateMask */ 0,
+ /* szInfo */ L"",
+ /* uVersion */ {NOTIFYICON_VERSION_4},
+ /* szInfoTitle */ L"",
+ /* dwInfoFlags */ 0};
+ MOZ_ASSERT(mMenu);
+}
+
+StatusBarEntry::~StatusBarEntry() {
+ if (!mInitted) {
+ return;
+ }
+ ::Shell_NotifyIconW(NIM_DELETE, &mIconData);
+ VERIFY(::DestroyWindow(mIconData.hWnd));
+}
+
+nsresult StatusBarEntry::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // First, look at the content node's "image" attribute.
+ nsAutoString imageURIString;
+ bool hasImageAttr =
+ mMenu->GetAttr(kNameSpaceID_None, nsGkAtoms::image, imageURIString);
+
+ nsresult rv;
+ RefPtr<ComputedStyle> sc;
+ nsCOMPtr<nsIURI> iconURI;
+ if (!hasImageAttr) {
+ // If the content node has no "image" attribute, get the
+ // "list-style-image" property from CSS.
+ RefPtr<mozilla::dom::Document> document = mMenu->GetComposedDoc();
+ if (!document) {
+ return NS_ERROR_FAILURE;
+ }
+
+ sc = nsComputedDOMStyle::GetComputedStyle(mMenu, nullptr);
+ if (!sc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ iconURI = sc->StyleList()->GetListStyleImageURI();
+ } else {
+ uint64_t dummy = 0;
+ nsContentPolicyType policyType;
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = mMenu->NodePrincipal();
+ nsContentUtils::GetContentPolicyTypeForUIImageLoading(
+ mMenu, getter_AddRefs(triggeringPrincipal), policyType, &dummy);
+ if (policyType != nsIContentPolicy::TYPE_INTERNAL_IMAGE) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // If this menu item shouldn't have an icon, the string will be empty,
+ // and NS_NewURI will fail.
+ rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ mIconLoaderHelper = new IconLoaderHelperWin(this);
+ nsIntRect rect;
+ mIconLoader = new IconLoader(mIconLoaderHelper, mMenu, rect);
+
+ if (iconURI) {
+ rv = mIconLoader->LoadIcon(iconURI);
+ }
+
+ HWND iconWindow;
+ NS_ENSURE_TRUE(iconWindow = ::CreateWindowExW(
+ /* extended style */ 0,
+ /* className */ L"IconWindowClass",
+ /* title */ 0,
+ /* style */ WS_CAPTION,
+ /* x, y, cx, cy */ 0, 0, 0, 0,
+ /* parent */ 0,
+ /* menu */ 0,
+ /* instance */ 0,
+ /* create struct */ 0),
+ NS_ERROR_FAILURE);
+ ::SetWindowLongPtr(iconWindow, GWLP_USERDATA, (LONG_PTR)this);
+
+ mIconData.hWnd = iconWindow;
+ mIconData.hIcon = mIconLoaderHelper->GetNativeIconImage();
+
+ nsAutoString labelAttr;
+ mMenu->GetAttr(kNameSpaceID_None, nsGkAtoms::label, labelAttr);
+ const nsString& label = PromiseFlatString(labelAttr);
+
+ size_t destLength = sizeof mIconData.szTip / (sizeof mIconData.szTip[0]);
+ wchar_t* tooltip = &(mIconData.szTip[0]);
+ ::StringCchCopyNW(tooltip, destLength, label.get(), label.Length());
+
+ ::Shell_NotifyIconW(NIM_ADD, &mIconData);
+ ::Shell_NotifyIconW(NIM_SETVERSION, &mIconData);
+
+ mInitted = true;
+ return NS_OK;
+}
+
+nsresult StatusBarEntry::OnComplete() {
+ RefPtr<StatusBarEntry> kungFuDeathGrip = this;
+ mIconData.hIcon = mIconLoaderHelper->GetNativeIconImage();
+
+ ::Shell_NotifyIconW(NIM_MODIFY, &mIconData);
+
+ // To simplify things, we won't react to CSS changes to update the icon
+ // with this implementation. We can get rid of the IconLoader and Helper
+ // at this point, which will also free the allocated HICON.
+ mIconLoaderHelper->Destroy();
+ mIconLoader->ReleaseJSObjects();
+ mIconLoader->Destroy();
+ mIconLoader = nullptr;
+ mIconLoaderHelper = nullptr;
+ return NS_OK;
+}
+
+LRESULT StatusBarEntry::OnMessage(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
+ if (msg == WM_USER &&
+ (LOWORD(lp) == WM_LBUTTONUP || LOWORD(lp) == WM_RBUTTONUP)) {
+ nsMenuFrame* menu = do_QueryFrame(mMenu->GetPrimaryFrame());
+ if (!menu) {
+ return TRUE;
+ }
+
+ nsMenuPopupFrame* popupFrame = menu->GetPopup();
+ if (!popupFrame) {
+ return TRUE;
+ }
+
+ nsIWidget* widget = popupFrame->GetNearestWidget();
+ if (!widget) {
+ return TRUE;
+ }
+
+ HWND win = static_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW));
+ if (!win) {
+ return TRUE;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = popupFrame->PresContext()->GetDocShell();
+ nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(docShell);
+ if (!baseWin) {
+ return TRUE;
+ }
+
+ double scale = 1.0;
+ baseWin->GetUnscaledDevicePixelsPerCSSPixel(&scale);
+ int32_t x = NSToIntRound(GET_X_LPARAM(wp) / scale);
+ int32_t y = NSToIntRound(GET_Y_LPARAM(wp) / scale);
+
+ // The menu that is being opened is a Gecko <xul:menu>, and the popup code
+ // that manages it expects that the window that the <xul:menu> belongs to
+ // will be in the foreground when it opens. If we don't do this, then if the
+ // icon is clicked when the window is _not_ in the foreground, then the
+ // opened menu will not be keyboard focusable, nor will it close on its own
+ // if the user clicks away from the menu (at least, not until the user
+ // focuses any window in the parent process).
+ ::SetForegroundWindow(win);
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ pm->ShowPopupAtScreen(popupFrame->GetContent(), x, y, false, nullptr);
+ }
+
+ return DefWindowProc(hWnd, msg, wp, lp);
+}
+
+NS_IMPL_ISUPPORTS(SystemStatusBar, nsISystemStatusBar)
+
+static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
+ StatusBarEntry* entry =
+ (StatusBarEntry*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
+ if (entry) {
+ return entry->OnMessage(hWnd, msg, wp, lp);
+ }
+ return TRUE;
+}
+
+static StaticRefPtr<SystemStatusBar> sSingleton;
+
+SystemStatusBar& SystemStatusBar::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new SystemStatusBar();
+ ClearOnShutdown(&sSingleton);
+ }
+ return *sSingleton;
+}
+
+already_AddRefed<SystemStatusBar> SystemStatusBar::GetAddRefedSingleton() {
+ RefPtr<SystemStatusBar> sm = &GetSingleton();
+ return sm.forget();
+}
+
+nsresult SystemStatusBar::Init() {
+ WNDCLASS classStruct = {/* style */ 0,
+ /* lpfnWndProc */ &WindowProc,
+ /* cbClsExtra */ 0,
+ /* cbWndExtra */ 0,
+ /* hInstance */ 0,
+ /* hIcon */ 0,
+ /* hCursor */ 0,
+ /* hbrBackground */ 0,
+ /* lpszMenuName */ 0,
+ /* lpszClassName */ L"IconWindowClass"};
+ NS_ENSURE_TRUE(::RegisterClass(&classStruct), NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SystemStatusBar::AddItem(Element* aElement) {
+ RefPtr<StatusBarEntry> entry = new StatusBarEntry(aElement);
+ nsresult rv = entry->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatusBarEntries.insertBack(entry);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SystemStatusBar::RemoveItem(Element* aElement) {
+ for (StatusBarEntry* entry : mStatusBarEntries) {
+ if (entry->GetMenu() == aElement) {
+ entry->removeFrom(mStatusBarEntries);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+} // namespace mozilla::widget
diff --git a/widget/windows/SystemStatusBar.h b/widget/windows/SystemStatusBar.h
new file mode 100644
index 0000000000..6c90fc0097
--- /dev/null
+++ b/widget/windows/SystemStatusBar.h
@@ -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/. */
+
+#ifndef widget_windows_SystemStatusBar_h
+#define widget_windows_SystemStatusBar_h
+
+#include "nsISystemStatusBar.h"
+
+namespace mozilla::widget {
+class StatusBarEntry;
+
+class SystemStatusBar final : public nsISystemStatusBar {
+ public:
+ explicit SystemStatusBar() = default;
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISYSTEMSTATUSBAR
+
+ static SystemStatusBar& GetSingleton();
+ static already_AddRefed<SystemStatusBar> GetAddRefedSingleton();
+
+ nsresult Init();
+
+ private:
+ ~SystemStatusBar() = default;
+ mozilla::LinkedList<RefPtr<StatusBarEntry>> mStatusBarEntries;
+};
+
+} // namespace mozilla::widget
+
+#endif // widget_windows_SystemStatusBar_h
diff --git a/widget/windows/TSFTextStore.cpp b/widget/windows/TSFTextStore.cpp
new file mode 100644
index 0000000000..e67975779f
--- /dev/null
+++ b/widget/windows/TSFTextStore.cpp
@@ -0,0 +1,7398 @@
+/* -*- 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/. */
+
+#define INPUTSCOPE_INIT_GUID
+#define TEXTATTRS_INIT_GUID
+#include "TSFTextStore.h"
+
+#include <olectl.h>
+#include <algorithm>
+#include "nscore.h"
+
+#include "IMMHandler.h"
+#include "KeyboardLayout.h"
+#include "WinIMEHandler.h"
+#include "WinUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_intl.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/ToString.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsWindow.h"
+#include "nsPrintfCString.h"
+
+// Workaround for mingw32
+#ifndef TS_SD_INPUTPANEMANUALDISPLAYENABLE
+# define TS_SD_INPUTPANEMANUALDISPLAYENABLE 0x40
+#endif
+
+namespace mozilla {
+namespace widget {
+
+static const char* kPrefNameEnableTSF = "intl.tsf.enable";
+
+/**
+ * TSF related code should log its behavior even on release build especially
+ * in the interface methods.
+ *
+ * In interface methods, use LogLevel::Info.
+ * In internal methods, use LogLevel::Debug for logging normal behavior.
+ * For logging error, use LogLevel::Error.
+ *
+ * When an instance method is called, start with following text:
+ * "0x%p TSFFoo::Bar(", the 0x%p should be the "this" of the nsFoo.
+ * after that, start with:
+ * "0x%p TSFFoo::Bar("
+ * In an internal method, start with following text:
+ * "0x%p TSFFoo::Bar("
+ * When a static method is called, start with following text:
+ * "TSFFoo::Bar("
+ */
+
+LazyLogModule sTextStoreLog("nsTextStoreWidgets");
+
+enum class TextInputProcessorID {
+ // Internal use only. This won't be returned by TSFStaticSink::ActiveTIP().
+ eNotComputed,
+
+ // Not a TIP. E.g., simple keyboard layout or IMM-IME.
+ eNone,
+
+ // Used for other TIPs, i.e., any TIPs which we don't support specifically.
+ eUnknown,
+
+ // TIP for Japanese.
+ eMicrosoftIMEForJapanese,
+ eMicrosoftOfficeIME2010ForJapanese,
+ eGoogleJapaneseInput,
+ eATOK2011,
+ eATOK2012,
+ eATOK2013,
+ eATOK2014,
+ eATOK2015,
+ eATOK2016,
+ eATOKUnknown,
+ eJapanist10,
+
+ // TIP for Traditional Chinese.
+ eMicrosoftBopomofo,
+ eMicrosoftChangJie,
+ eMicrosoftPhonetic,
+ eMicrosoftQuick,
+ eMicrosoftNewChangJie,
+ eMicrosoftNewPhonetic,
+ eMicrosoftNewQuick,
+ eFreeChangJie,
+
+ // TIP for Simplified Chinese.
+ eMicrosoftPinyin,
+ eMicrosoftPinyinNewExperienceInputStyle,
+ eMicrosoftWubi,
+
+ // TIP for Korean.
+ eMicrosoftIMEForKorean,
+ eMicrosoftOldHangul,
+
+ // Keyman Desktop, which can install various language keyboards.
+ eKeymanDesktop,
+};
+
+static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
+
+static void HandleSeparator(nsCString& aDesc) {
+ if (!aDesc.IsEmpty()) {
+ aDesc.AppendLiteral(" | ");
+ }
+}
+
+static const nsCString GetFindFlagName(DWORD aFindFlag) {
+ nsCString description;
+ if (!aFindFlag) {
+ description.AppendLiteral("no flags (0)");
+ return description;
+ }
+ if (aFindFlag & TS_ATTR_FIND_BACKWARDS) {
+ description.AppendLiteral("TS_ATTR_FIND_BACKWARDS");
+ }
+ if (aFindFlag & TS_ATTR_FIND_WANT_OFFSET) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_WANT_OFFSET");
+ }
+ if (aFindFlag & TS_ATTR_FIND_UPDATESTART) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_UPDATESTART");
+ }
+ if (aFindFlag & TS_ATTR_FIND_WANT_VALUE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_WANT_VALUE");
+ }
+ if (aFindFlag & TS_ATTR_FIND_WANT_END) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_WANT_END");
+ }
+ if (aFindFlag & TS_ATTR_FIND_HIDDEN) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_HIDDEN");
+ }
+ if (description.IsEmpty()) {
+ description.AppendLiteral("Unknown (");
+ description.AppendInt(static_cast<uint32_t>(aFindFlag));
+ description.Append(')');
+ }
+ return description;
+}
+
+class GetACPFromPointFlagName : public nsAutoCString {
+ public:
+ explicit GetACPFromPointFlagName(DWORD aFlags) {
+ if (!aFlags) {
+ AppendLiteral("no flags (0)");
+ return;
+ }
+ if (aFlags & GXFPF_ROUND_NEAREST) {
+ AppendLiteral("GXFPF_ROUND_NEAREST");
+ aFlags &= ~GXFPF_ROUND_NEAREST;
+ }
+ if (aFlags & GXFPF_NEAREST) {
+ HandleSeparator(*this);
+ AppendLiteral("GXFPF_NEAREST");
+ aFlags &= ~GXFPF_NEAREST;
+ }
+ if (aFlags) {
+ HandleSeparator(*this);
+ AppendLiteral("Unknown(");
+ AppendInt(static_cast<uint32_t>(aFlags));
+ Append(')');
+ }
+ }
+ virtual ~GetACPFromPointFlagName() {}
+};
+
+static const char* GetFocusChangeName(
+ InputContextAction::FocusChange aFocusChange) {
+ switch (aFocusChange) {
+ case InputContextAction::FOCUS_NOT_CHANGED:
+ return "FOCUS_NOT_CHANGED";
+ case InputContextAction::GOT_FOCUS:
+ return "GOT_FOCUS";
+ case InputContextAction::LOST_FOCUS:
+ return "LOST_FOCUS";
+ case InputContextAction::MENU_GOT_PSEUDO_FOCUS:
+ return "MENU_GOT_PSEUDO_FOCUS";
+ case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
+ return "MENU_LOST_PSEUDO_FOCUS";
+ case InputContextAction::WIDGET_CREATED:
+ return "WIDGET_CREATED";
+ default:
+ return "Unknown";
+ }
+}
+
+static nsCString GetCLSIDNameStr(REFCLSID aCLSID) {
+ LPOLESTR str = nullptr;
+ HRESULT hr = ::StringFromCLSID(aCLSID, &str);
+ if (FAILED(hr) || !str || !str[0]) {
+ return ""_ns;
+ }
+
+ nsCString result;
+ result = NS_ConvertUTF16toUTF8(str);
+ ::CoTaskMemFree(str);
+ return result;
+}
+
+static nsCString GetGUIDNameStr(REFGUID aGUID) {
+ OLECHAR str[40];
+ int len = ::StringFromGUID2(aGUID, str, ArrayLength(str));
+ if (!len || !str[0]) {
+ return ""_ns;
+ }
+
+ return NS_ConvertUTF16toUTF8(str);
+}
+
+static nsCString GetGUIDNameStrWithTable(REFGUID aGUID) {
+#define RETURN_GUID_NAME(aNamedGUID) \
+ if (IsEqualGUID(aGUID, aNamedGUID)) { \
+ return nsLiteralCString(#aNamedGUID); \
+ }
+
+ RETURN_GUID_NAME(GUID_PROP_INPUTSCOPE)
+ RETURN_GUID_NAME(TSATTRID_OTHERS)
+ RETURN_GUID_NAME(TSATTRID_Font)
+ RETURN_GUID_NAME(TSATTRID_Font_FaceName)
+ RETURN_GUID_NAME(TSATTRID_Font_SizePts)
+ RETURN_GUID_NAME(TSATTRID_Font_Style)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Bold)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Italic)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_SmallCaps)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Capitalize)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Uppercase)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Lowercase)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_LasVegasLights)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_BlinkingBackground)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_SparkleText)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingBlackAnts)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingRedAnts)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_Shimmer)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeDown)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeRight)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Emboss)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Engrave)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Hidden)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Kerning)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Outlined)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Position)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Protected)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Shadow)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Spacing)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Weight)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Height)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Underline)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Single)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Double)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Single)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Double)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Overline)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Single)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Double)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Blink)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Subscript)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Superscript)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Color)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_BackgroundColor)
+ RETURN_GUID_NAME(TSATTRID_Text)
+ RETURN_GUID_NAME(TSATTRID_Text_VerticalWriting)
+ RETURN_GUID_NAME(TSATTRID_Text_RightToLeft)
+ RETURN_GUID_NAME(TSATTRID_Text_Orientation)
+ RETURN_GUID_NAME(TSATTRID_Text_Language)
+ RETURN_GUID_NAME(TSATTRID_Text_ReadOnly)
+ RETURN_GUID_NAME(TSATTRID_Text_EmbeddedObject)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment_Left)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment_Right)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment_Center)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment_Justify)
+ RETURN_GUID_NAME(TSATTRID_Text_Link)
+ RETURN_GUID_NAME(TSATTRID_Text_Hyphenation)
+ RETURN_GUID_NAME(TSATTRID_Text_Para)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_FirstLineIndent)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LeftIndent)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_RightIndent)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceAfter)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceBefore)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Single)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_OnePtFive)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Double)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_AtLeast)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Exactly)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Multiple)
+ RETURN_GUID_NAME(TSATTRID_List)
+ RETURN_GUID_NAME(TSATTRID_List_LevelIndel)
+ RETURN_GUID_NAME(TSATTRID_List_Type)
+ RETURN_GUID_NAME(TSATTRID_List_Type_Bullet)
+ RETURN_GUID_NAME(TSATTRID_List_Type_Arabic)
+ RETURN_GUID_NAME(TSATTRID_List_Type_LowerLetter)
+ RETURN_GUID_NAME(TSATTRID_List_Type_UpperLetter)
+ RETURN_GUID_NAME(TSATTRID_List_Type_LowerRoman)
+ RETURN_GUID_NAME(TSATTRID_List_Type_UpperRoman)
+ RETURN_GUID_NAME(TSATTRID_App)
+ RETURN_GUID_NAME(TSATTRID_App_IncorrectSpelling)
+ RETURN_GUID_NAME(TSATTRID_App_IncorrectGrammar)
+
+#undef RETURN_GUID_NAME
+
+ return GetGUIDNameStr(aGUID);
+}
+
+static nsCString GetRIIDNameStr(REFIID aRIID) {
+ LPOLESTR str = nullptr;
+ HRESULT hr = ::StringFromIID(aRIID, &str);
+ if (FAILED(hr) || !str || !str[0]) {
+ return ""_ns;
+ }
+
+ nsAutoString key(L"Interface\\");
+ key += str;
+
+ nsCString result;
+ wchar_t buf[256];
+ if (WinUtils::GetRegistryKey(HKEY_CLASSES_ROOT, key.get(), nullptr, buf,
+ sizeof(buf))) {
+ result = NS_ConvertUTF16toUTF8(buf);
+ } else {
+ result = NS_ConvertUTF16toUTF8(str);
+ }
+
+ ::CoTaskMemFree(str);
+ return result;
+}
+
+static const char* GetCommonReturnValueName(HRESULT aResult) {
+ switch (aResult) {
+ case S_OK:
+ return "S_OK";
+ case E_ABORT:
+ return "E_ABORT";
+ case E_ACCESSDENIED:
+ return "E_ACCESSDENIED";
+ case E_FAIL:
+ return "E_FAIL";
+ case E_HANDLE:
+ return "E_HANDLE";
+ case E_INVALIDARG:
+ return "E_INVALIDARG";
+ case E_NOINTERFACE:
+ return "E_NOINTERFACE";
+ case E_NOTIMPL:
+ return "E_NOTIMPL";
+ case E_OUTOFMEMORY:
+ return "E_OUTOFMEMORY";
+ case E_POINTER:
+ return "E_POINTER";
+ case E_UNEXPECTED:
+ return "E_UNEXPECTED";
+ default:
+ return SUCCEEDED(aResult) ? "Succeeded" : "Failed";
+ }
+}
+
+static const char* GetTextStoreReturnValueName(HRESULT aResult) {
+ switch (aResult) {
+ case TS_E_FORMAT:
+ return "TS_E_FORMAT";
+ case TS_E_INVALIDPOINT:
+ return "TS_E_INVALIDPOINT";
+ case TS_E_INVALIDPOS:
+ return "TS_E_INVALIDPOS";
+ case TS_E_NOINTERFACE:
+ return "TS_E_NOINTERFACE";
+ case TS_E_NOLAYOUT:
+ return "TS_E_NOLAYOUT";
+ case TS_E_NOLOCK:
+ return "TS_E_NOLOCK";
+ case TS_E_NOOBJECT:
+ return "TS_E_NOOBJECT";
+ case TS_E_NOSELECTION:
+ return "TS_E_NOSELECTION";
+ case TS_E_NOSERVICE:
+ return "TS_E_NOSERVICE";
+ case TS_E_READONLY:
+ return "TS_E_READONLY";
+ case TS_E_SYNCHRONOUS:
+ return "TS_E_SYNCHRONOUS";
+ case TS_S_ASYNC:
+ return "TS_S_ASYNC";
+ default:
+ return GetCommonReturnValueName(aResult);
+ }
+}
+
+static const nsCString GetSinkMaskNameStr(DWORD aSinkMask) {
+ nsCString description;
+ if (aSinkMask & TS_AS_TEXT_CHANGE) {
+ description.AppendLiteral("TS_AS_TEXT_CHANGE");
+ }
+ if (aSinkMask & TS_AS_SEL_CHANGE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_AS_SEL_CHANGE");
+ }
+ if (aSinkMask & TS_AS_LAYOUT_CHANGE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_AS_LAYOUT_CHANGE");
+ }
+ if (aSinkMask & TS_AS_ATTR_CHANGE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_AS_ATTR_CHANGE");
+ }
+ if (aSinkMask & TS_AS_STATUS_CHANGE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_AS_STATUS_CHANGE");
+ }
+ if (description.IsEmpty()) {
+ description.AppendLiteral("not-specified");
+ }
+ return description;
+}
+
+static const nsCString GetLockFlagNameStr(DWORD aLockFlags) {
+ nsCString description;
+ if ((aLockFlags & TS_LF_READWRITE) == TS_LF_READWRITE) {
+ description.AppendLiteral("TS_LF_READWRITE");
+ } else if (aLockFlags & TS_LF_READ) {
+ description.AppendLiteral("TS_LF_READ");
+ }
+ if (aLockFlags & TS_LF_SYNC) {
+ if (!description.IsEmpty()) {
+ description.AppendLiteral(" | ");
+ }
+ description.AppendLiteral("TS_LF_SYNC");
+ }
+ if (description.IsEmpty()) {
+ description.AppendLiteral("not-specified");
+ }
+ return description;
+}
+
+static const char* GetTextRunTypeName(TsRunType aRunType) {
+ switch (aRunType) {
+ case TS_RT_PLAIN:
+ return "TS_RT_PLAIN";
+ case TS_RT_HIDDEN:
+ return "TS_RT_HIDDEN";
+ case TS_RT_OPAQUE:
+ return "TS_RT_OPAQUE";
+ default:
+ return "Unknown";
+ }
+}
+
+static nsCString GetColorName(const TF_DA_COLOR& aColor) {
+ switch (aColor.type) {
+ case TF_CT_NONE:
+ return "TF_CT_NONE"_ns;
+ case TF_CT_SYSCOLOR:
+ return nsPrintfCString("TF_CT_SYSCOLOR, nIndex:0x%08X",
+ static_cast<int32_t>(aColor.nIndex));
+ case TF_CT_COLORREF:
+ return nsPrintfCString("TF_CT_COLORREF, cr:0x%08X",
+ static_cast<int32_t>(aColor.cr));
+ break;
+ default:
+ return nsPrintfCString("Unknown(%08X)",
+ static_cast<int32_t>(aColor.type));
+ }
+}
+
+static nsCString GetLineStyleName(TF_DA_LINESTYLE aLineStyle) {
+ switch (aLineStyle) {
+ case TF_LS_NONE:
+ return "TF_LS_NONE"_ns;
+ case TF_LS_SOLID:
+ return "TF_LS_SOLID"_ns;
+ case TF_LS_DOT:
+ return "TF_LS_DOT"_ns;
+ case TF_LS_DASH:
+ return "TF_LS_DASH"_ns;
+ case TF_LS_SQUIGGLE:
+ return "TF_LS_SQUIGGLE"_ns;
+ default: {
+ return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aLineStyle));
+ }
+ }
+}
+
+static nsCString GetClauseAttrName(TF_DA_ATTR_INFO aAttr) {
+ switch (aAttr) {
+ case TF_ATTR_INPUT:
+ return "TF_ATTR_INPUT"_ns;
+ case TF_ATTR_TARGET_CONVERTED:
+ return "TF_ATTR_TARGET_CONVERTED"_ns;
+ case TF_ATTR_CONVERTED:
+ return "TF_ATTR_CONVERTED"_ns;
+ case TF_ATTR_TARGET_NOTCONVERTED:
+ return "TF_ATTR_TARGET_NOTCONVERTED"_ns;
+ case TF_ATTR_INPUT_ERROR:
+ return "TF_ATTR_INPUT_ERROR"_ns;
+ case TF_ATTR_FIXEDCONVERTED:
+ return "TF_ATTR_FIXEDCONVERTED"_ns;
+ case TF_ATTR_OTHER:
+ return "TF_ATTR_OTHER"_ns;
+ default: {
+ return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aAttr));
+ }
+ }
+}
+
+static nsCString GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE& aDispAttr) {
+ nsCString str;
+ str = "crText:{ ";
+ str += GetColorName(aDispAttr.crText);
+ str += " }, crBk:{ ";
+ str += GetColorName(aDispAttr.crBk);
+ str += " }, lsStyle: ";
+ str += GetLineStyleName(aDispAttr.lsStyle);
+ str += ", fBoldLine: ";
+ str += GetBoolName(aDispAttr.fBoldLine);
+ str += ", crLine:{ ";
+ str += GetColorName(aDispAttr.crLine);
+ str += " }, bAttr: ";
+ str += GetClauseAttrName(aDispAttr.bAttr);
+ return str;
+}
+
+static const char* GetMouseButtonName(int16_t aButton) {
+ switch (aButton) {
+ case MouseButton::ePrimary:
+ return "LeftButton";
+ case MouseButton::eMiddle:
+ return "MiddleButton";
+ case MouseButton::eSecondary:
+ return "RightButton";
+ default:
+ return "UnknownButton";
+ }
+}
+
+#define ADD_SEPARATOR_IF_NECESSARY(aStr) \
+ if (!aStr.IsEmpty()) { \
+ aStr.AppendLiteral(", "); \
+ }
+
+static nsCString GetMouseButtonsName(int16_t aButtons) {
+ if (!aButtons) {
+ return "no buttons"_ns;
+ }
+ nsCString names;
+ if (aButtons & MouseButtonsFlag::ePrimaryFlag) {
+ names = "LeftButton";
+ }
+ if (aButtons & MouseButtonsFlag::eSecondaryFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "RightButton";
+ }
+ if (aButtons & MouseButtonsFlag::eMiddleFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "MiddleButton";
+ }
+ if (aButtons & MouseButtonsFlag::e4thFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "4thButton";
+ }
+ if (aButtons & MouseButtonsFlag::e5thFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "5thButton";
+ }
+ return names;
+}
+
+static nsCString GetModifiersName(Modifiers aModifiers) {
+ if (aModifiers == MODIFIER_NONE) {
+ return "no modifiers"_ns;
+ }
+ nsCString names;
+ if (aModifiers & MODIFIER_ALT) {
+ names = NS_DOM_KEYNAME_ALT;
+ }
+ if (aModifiers & MODIFIER_ALTGRAPH) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_ALTGRAPH;
+ }
+ if (aModifiers & MODIFIER_CAPSLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_CAPSLOCK;
+ }
+ if (aModifiers & MODIFIER_CONTROL) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_CONTROL;
+ }
+ if (aModifiers & MODIFIER_FN) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_FN;
+ }
+ if (aModifiers & MODIFIER_FNLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_FNLOCK;
+ }
+ if (aModifiers & MODIFIER_META) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_META;
+ }
+ if (aModifiers & MODIFIER_NUMLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_NUMLOCK;
+ }
+ if (aModifiers & MODIFIER_SCROLLLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_SCROLLLOCK;
+ }
+ if (aModifiers & MODIFIER_SHIFT) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_SHIFT;
+ }
+ if (aModifiers & MODIFIER_SYMBOL) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_SYMBOL;
+ }
+ if (aModifiers & MODIFIER_SYMBOLLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_SYMBOLLOCK;
+ }
+ if (aModifiers & MODIFIER_OS) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_OS;
+ }
+ return names;
+}
+
+class GetWritingModeName : public nsAutoCString {
+ public:
+ explicit GetWritingModeName(const WritingMode& aWritingMode) {
+ if (!aWritingMode.IsVertical()) {
+ AssignLiteral("Horizontal");
+ return;
+ }
+ if (aWritingMode.IsVerticalLR()) {
+ AssignLiteral("Vertical (LR)");
+ return;
+ }
+ AssignLiteral("Vertical (RL)");
+ }
+ virtual ~GetWritingModeName() {}
+};
+
+class GetEscapedUTF8String final : public NS_ConvertUTF16toUTF8 {
+ public:
+ explicit GetEscapedUTF8String(const nsAString& aString)
+ : NS_ConvertUTF16toUTF8(aString) {
+ Escape();
+ }
+ explicit GetEscapedUTF8String(const char16ptr_t aString)
+ : NS_ConvertUTF16toUTF8(aString) {
+ Escape();
+ }
+ GetEscapedUTF8String(const char16ptr_t aString, uint32_t aLength)
+ : NS_ConvertUTF16toUTF8(aString, aLength) {
+ Escape();
+ }
+
+ private:
+ void Escape() {
+ ReplaceSubstring("\r", "\\r");
+ ReplaceSubstring("\n", "\\n");
+ ReplaceSubstring("\t", "\\t");
+ }
+};
+
+class GetInputScopeString : public nsAutoCString {
+ public:
+ explicit GetInputScopeString(const nsTArray<InputScope>& aList) {
+ for (InputScope inputScope : aList) {
+ if (!IsEmpty()) {
+ AppendLiteral(", ");
+ }
+ switch (inputScope) {
+ case IS_DEFAULT:
+ AppendLiteral("IS_DEFAULT");
+ break;
+ case IS_URL:
+ AppendLiteral("IS_URL");
+ break;
+ case IS_FILE_FULLFILEPATH:
+ AppendLiteral("IS_FILE_FULLFILEPATH");
+ break;
+ case IS_FILE_FILENAME:
+ AppendLiteral("IS_FILE_FILENAME");
+ break;
+ case IS_EMAIL_USERNAME:
+ AppendLiteral("IS_EMAIL_USERNAME");
+ break;
+ case IS_EMAIL_SMTPEMAILADDRESS:
+ AppendLiteral("IS_EMAIL_SMTPEMAILADDRESS");
+ break;
+ case IS_LOGINNAME:
+ AppendLiteral("IS_LOGINNAME");
+ break;
+ case IS_PERSONALNAME_FULLNAME:
+ AppendLiteral("IS_PERSONALNAME_FULLNAME");
+ break;
+ case IS_PERSONALNAME_PREFIX:
+ AppendLiteral("IS_PERSONALNAME_PREFIX");
+ break;
+ case IS_PERSONALNAME_GIVENNAME:
+ AppendLiteral("IS_PERSONALNAME_GIVENNAME");
+ break;
+ case IS_PERSONALNAME_MIDDLENAME:
+ AppendLiteral("IS_PERSONALNAME_MIDDLENAME");
+ break;
+ case IS_PERSONALNAME_SURNAME:
+ AppendLiteral("IS_PERSONALNAME_SURNAME");
+ break;
+ case IS_PERSONALNAME_SUFFIX:
+ AppendLiteral("IS_PERSONALNAME_SUFFIX");
+ break;
+ case IS_ADDRESS_FULLPOSTALADDRESS:
+ AppendLiteral("IS_ADDRESS_FULLPOSTALADDRESS");
+ break;
+ case IS_ADDRESS_POSTALCODE:
+ AppendLiteral("IS_ADDRESS_POSTALCODE");
+ break;
+ case IS_ADDRESS_STREET:
+ AppendLiteral("IS_ADDRESS_STREET");
+ break;
+ case IS_ADDRESS_STATEORPROVINCE:
+ AppendLiteral("IS_ADDRESS_STATEORPROVINCE");
+ break;
+ case IS_ADDRESS_CITY:
+ AppendLiteral("IS_ADDRESS_CITY");
+ break;
+ case IS_ADDRESS_COUNTRYNAME:
+ AppendLiteral("IS_ADDRESS_COUNTRYNAME");
+ break;
+ case IS_ADDRESS_COUNTRYSHORTNAME:
+ AppendLiteral("IS_ADDRESS_COUNTRYSHORTNAME");
+ break;
+ case IS_CURRENCY_AMOUNTANDSYMBOL:
+ AppendLiteral("IS_CURRENCY_AMOUNTANDSYMBOL");
+ break;
+ case IS_CURRENCY_AMOUNT:
+ AppendLiteral("IS_CURRENCY_AMOUNT");
+ break;
+ case IS_DATE_FULLDATE:
+ AppendLiteral("IS_DATE_FULLDATE");
+ break;
+ case IS_DATE_MONTH:
+ AppendLiteral("IS_DATE_MONTH");
+ break;
+ case IS_DATE_DAY:
+ AppendLiteral("IS_DATE_DAY");
+ break;
+ case IS_DATE_YEAR:
+ AppendLiteral("IS_DATE_YEAR");
+ break;
+ case IS_DATE_MONTHNAME:
+ AppendLiteral("IS_DATE_MONTHNAME");
+ break;
+ case IS_DATE_DAYNAME:
+ AppendLiteral("IS_DATE_DAYNAME");
+ break;
+ case IS_DIGITS:
+ AppendLiteral("IS_DIGITS");
+ break;
+ case IS_NUMBER:
+ AppendLiteral("IS_NUMBER");
+ break;
+ case IS_ONECHAR:
+ AppendLiteral("IS_ONECHAR");
+ break;
+ case IS_PASSWORD:
+ AppendLiteral("IS_PASSWORD");
+ break;
+ case IS_TELEPHONE_FULLTELEPHONENUMBER:
+ AppendLiteral("IS_TELEPHONE_FULLTELEPHONENUMBER");
+ break;
+ case IS_TELEPHONE_COUNTRYCODE:
+ AppendLiteral("IS_TELEPHONE_COUNTRYCODE");
+ break;
+ case IS_TELEPHONE_AREACODE:
+ AppendLiteral("IS_TELEPHONE_AREACODE");
+ break;
+ case IS_TELEPHONE_LOCALNUMBER:
+ AppendLiteral("IS_TELEPHONE_LOCALNUMBER");
+ break;
+ case IS_TIME_FULLTIME:
+ AppendLiteral("IS_TIME_FULLTIME");
+ break;
+ case IS_TIME_HOUR:
+ AppendLiteral("IS_TIME_HOUR");
+ break;
+ case IS_TIME_MINORSEC:
+ AppendLiteral("IS_TIME_MINORSEC");
+ break;
+ case IS_NUMBER_FULLWIDTH:
+ AppendLiteral("IS_NUMBER_FULLWIDTH");
+ break;
+ case IS_ALPHANUMERIC_HALFWIDTH:
+ AppendLiteral("IS_ALPHANUMERIC_HALFWIDTH");
+ break;
+ case IS_ALPHANUMERIC_FULLWIDTH:
+ AppendLiteral("IS_ALPHANUMERIC_FULLWIDTH");
+ break;
+ case IS_CURRENCY_CHINESE:
+ AppendLiteral("IS_CURRENCY_CHINESE");
+ break;
+ case IS_BOPOMOFO:
+ AppendLiteral("IS_BOPOMOFO");
+ break;
+ case IS_HIRAGANA:
+ AppendLiteral("IS_HIRAGANA");
+ break;
+ case IS_KATAKANA_HALFWIDTH:
+ AppendLiteral("IS_KATAKANA_HALFWIDTH");
+ break;
+ case IS_KATAKANA_FULLWIDTH:
+ AppendLiteral("IS_KATAKANA_FULLWIDTH");
+ break;
+ case IS_HANJA:
+ AppendLiteral("IS_HANJA");
+ break;
+ case IS_PHRASELIST:
+ AppendLiteral("IS_PHRASELIST");
+ break;
+ case IS_REGULAREXPRESSION:
+ AppendLiteral("IS_REGULAREXPRESSION");
+ break;
+ case IS_SRGS:
+ AppendLiteral("IS_SRGS");
+ break;
+ case IS_XML:
+ AppendLiteral("IS_XML");
+ break;
+ case IS_PRIVATE:
+ AppendLiteral("IS_PRIVATE");
+ break;
+ default:
+ AppendPrintf("Unknown Value(%d)", inputScope);
+ break;
+ }
+ }
+ }
+};
+
+/******************************************************************/
+/* InputScopeImpl */
+/******************************************************************/
+
+class InputScopeImpl final : public ITfInputScope {
+ ~InputScopeImpl() {}
+
+ public:
+ explicit InputScopeImpl(const nsTArray<InputScope>& aList)
+ : mInputScopes(aList.Clone()) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Info,
+ ("0x%p InputScopeImpl(%s)", this, GetInputScopeString(aList).get()));
+ }
+
+ NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(InputScopeImpl)
+
+ STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+ if ((IID_IUnknown == riid) || (IID_ITfInputScope == riid)) {
+ *ppv = static_cast<ITfInputScope*>(this);
+ }
+ if (*ppv) {
+ AddRef();
+ return S_OK;
+ }
+ return E_NOINTERFACE;
+ }
+
+ STDMETHODIMP GetInputScopes(InputScope** pprgInputScopes, UINT* pcCount) {
+ uint32_t count = (mInputScopes.IsEmpty() ? 1 : mInputScopes.Length());
+
+ InputScope* pScope =
+ (InputScope*)CoTaskMemAlloc(sizeof(InputScope) * count);
+ NS_ENSURE_TRUE(pScope, E_OUTOFMEMORY);
+
+ if (mInputScopes.IsEmpty()) {
+ *pScope = IS_DEFAULT;
+ *pcCount = 1;
+ *pprgInputScopes = pScope;
+ return S_OK;
+ }
+
+ *pcCount = 0;
+
+ for (uint32_t idx = 0; idx < count; idx++) {
+ *(pScope + idx) = mInputScopes[idx];
+ (*pcCount)++;
+ }
+
+ *pprgInputScopes = pScope;
+ return S_OK;
+ }
+
+ STDMETHODIMP GetPhrase(BSTR** ppbstrPhrases, UINT* pcCount) {
+ return E_NOTIMPL;
+ }
+ STDMETHODIMP GetRegularExpression(BSTR* pbstrRegExp) { return E_NOTIMPL; }
+ STDMETHODIMP GetSRGS(BSTR* pbstrSRGS) { return E_NOTIMPL; }
+ STDMETHODIMP GetXML(BSTR* pbstrXML) { return E_NOTIMPL; }
+
+ private:
+ nsTArray<InputScope> mInputScopes;
+};
+
+/******************************************************************/
+/* TSFStaticSink */
+/******************************************************************/
+
+class TSFStaticSink final : public ITfInputProcessorProfileActivationSink {
+ public:
+ static TSFStaticSink* GetInstance() {
+ if (!sInstance) {
+ RefPtr<ITfThreadMgr> threadMgr = TSFTextStore::GetThreadMgr();
+ if (NS_WARN_IF(!threadMgr)) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Error,
+ ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
+ "instance due to no ThreadMgr instance"));
+ return nullptr;
+ }
+ RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
+ TSFTextStore::GetInputProcessorProfiles();
+ if (NS_WARN_IF(!inputProcessorProfiles)) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Error,
+ ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
+ "instance due to no InputProcessorProfiles instance"));
+ return nullptr;
+ }
+ RefPtr<TSFStaticSink> staticSink = new TSFStaticSink();
+ if (NS_WARN_IF(!staticSink->Init(threadMgr, inputProcessorProfiles))) {
+ staticSink->Destroy();
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Error,
+ ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
+ "instance"));
+ return nullptr;
+ }
+ sInstance = staticSink.forget();
+ }
+ return sInstance;
+ }
+
+ static void Shutdown() {
+ if (sInstance) {
+ sInstance->Destroy();
+ sInstance = nullptr;
+ }
+ }
+
+ bool Init(ITfThreadMgr* aThreadMgr,
+ ITfInputProcessorProfiles* aInputProcessorProfiles);
+ STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+ if (IID_IUnknown == riid ||
+ IID_ITfInputProcessorProfileActivationSink == riid) {
+ *ppv = static_cast<ITfInputProcessorProfileActivationSink*>(this);
+ }
+ if (*ppv) {
+ AddRef();
+ return S_OK;
+ }
+ return E_NOINTERFACE;
+ }
+
+ NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFStaticSink)
+
+ const nsString& GetActiveTIPKeyboardDescription() const {
+ return mActiveTIPKeyboardDescription;
+ }
+
+ static bool IsIMM_IMEActive() {
+ // Use IMM API until TSFStaticSink starts to work.
+ if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
+ return IsIMM_IME(::GetKeyboardLayout(0));
+ }
+ return sInstance->mIsIMM_IME;
+ }
+
+ static bool IsIMM_IME(HKL aHKL) {
+ return (::ImmGetIMEFileNameW(aHKL, nullptr, 0) > 0);
+ }
+
+ static bool IsTraditionalChinese() {
+ EnsureInstance();
+ return sInstance && sInstance->IsTraditionalChineseInternal();
+ }
+ static bool IsSimplifiedChinese() {
+ EnsureInstance();
+ return sInstance && sInstance->IsSimplifiedChineseInternal();
+ }
+ static bool IsJapanese() {
+ EnsureInstance();
+ return sInstance && sInstance->IsJapaneseInternal();
+ }
+ static bool IsKorean() {
+ EnsureInstance();
+ return sInstance && sInstance->IsKoreanInternal();
+ }
+
+ /**
+ * ActiveTIP() returns an ID for currently active TIP.
+ * Please note that this method is expensive due to needs a lot of GUID
+ * comparations if active language ID is one of CJKT. If you need to
+ * check TIPs for a specific language, you should check current language
+ * first.
+ */
+ static TextInputProcessorID ActiveTIP() {
+ EnsureInstance();
+ if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
+ return TextInputProcessorID::eUnknown;
+ }
+ sInstance->ComputeActiveTextInputProcessor();
+ if (NS_WARN_IF(sInstance->mActiveTIP ==
+ TextInputProcessorID::eNotComputed)) {
+ return TextInputProcessorID::eUnknown;
+ }
+ return sInstance->mActiveTIP;
+ }
+
+ static bool IsMSChangJieOrMSQuickActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if the language
+ // for current TIP is Traditional Chinese.
+ if (!IsTraditionalChinese()) {
+ return false;
+ }
+ switch (ActiveTIP()) {
+ case TextInputProcessorID::eMicrosoftChangJie:
+ case TextInputProcessorID::eMicrosoftQuick:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ static bool IsMSPinyinOrMSWubiActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if the language
+ // for current TIP is Simplified Chinese.
+ if (!IsSimplifiedChinese()) {
+ return false;
+ }
+ switch (ActiveTIP()) {
+ case TextInputProcessorID::eMicrosoftPinyin:
+ case TextInputProcessorID::eMicrosoftWubi:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ static bool IsMSJapaneseIMEActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if the language
+ // for current TIP is Japanese.
+ if (!IsJapanese()) {
+ return false;
+ }
+ return ActiveTIP() == TextInputProcessorID::eMicrosoftIMEForJapanese;
+ }
+
+ static bool IsGoogleJapaneseInputActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if the language
+ // for current TIP is Japanese.
+ if (!IsJapanese()) {
+ return false;
+ }
+ return ActiveTIP() == TextInputProcessorID::eGoogleJapaneseInput;
+ }
+
+ static bool IsATOKActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if active TIP is
+ // ATOK first since it's cheaper.
+ return IsJapanese() && sInstance->IsATOKActiveInternal();
+ }
+
+ // Note that ATOK 2011 - 2016 refers native caret position for deciding its
+ // popup window position.
+ static bool IsATOKReferringNativeCaretActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if active TIP is
+ // ATOK first since it's cheaper.
+ if (!IsJapanese() || !sInstance->IsATOKActiveInternal()) {
+ return false;
+ }
+ switch (ActiveTIP()) {
+ case TextInputProcessorID::eATOK2011:
+ case TextInputProcessorID::eATOK2012:
+ case TextInputProcessorID::eATOK2013:
+ case TextInputProcessorID::eATOK2014:
+ case TextInputProcessorID::eATOK2015:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private:
+ static void EnsureInstance() {
+ if (!sInstance) {
+ RefPtr<TSFStaticSink> staticSink = GetInstance();
+ Unused << staticSink;
+ }
+ }
+
+ bool IsTraditionalChineseInternal() const { return mLangID == 0x0404; }
+ bool IsSimplifiedChineseInternal() const { return mLangID == 0x0804; }
+ bool IsJapaneseInternal() const { return mLangID == 0x0411; }
+ bool IsKoreanInternal() const { return mLangID == 0x0412; }
+
+ bool IsATOKActiveInternal() {
+ EnsureInitActiveTIPKeyboard();
+ // FYI: Name of packaged ATOK includes the release year like "ATOK 2015".
+ // Name of ATOK Passport (subscription) equals "ATOK".
+ return StringBeginsWith(mActiveTIPKeyboardDescription, u"ATOK "_ns) ||
+ mActiveTIPKeyboardDescription.EqualsLiteral("ATOK");
+ }
+
+ void ComputeActiveTextInputProcessor() {
+ if (mActiveTIP != TextInputProcessorID::eNotComputed) {
+ return;
+ }
+
+ if (mActiveTIPGUID == GUID_NULL) {
+ mActiveTIP = TextInputProcessorID::eNone;
+ return;
+ }
+
+ // Comparing GUID is slow. So, we should use language information to
+ // reduce the comparing cost for TIP which is not we do not support
+ // specifically since they are always compared with all supported TIPs.
+ switch (mLangID) {
+ case 0x0404:
+ mActiveTIP = ComputeActiveTIPAsTraditionalChinese();
+ break;
+ case 0x0411:
+ mActiveTIP = ComputeActiveTIPAsJapanese();
+ break;
+ case 0x0412:
+ mActiveTIP = ComputeActiveTIPAsKorean();
+ break;
+ case 0x0804:
+ mActiveTIP = ComputeActiveTIPAsSimplifiedChinese();
+ break;
+ default:
+ mActiveTIP = TextInputProcessorID::eUnknown;
+ break;
+ }
+ // Special case for Keyman Desktop, it is available for any languages.
+ // Therefore, we need to check it only if we don't know the active TIP.
+ if (mActiveTIP != TextInputProcessorID::eUnknown) {
+ return;
+ }
+
+ // Note that keyboard layouts for Keyman assign its GUID on install
+ // randomly, but CLSID is constant in any environments.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1670834#c7
+ // https://github.com/keymanapp/keyman/blob/318c73a9e1d571d942837ff9964590626e5bd5aa/windows/src/engine/kmtip/globals.cpp#L37
+ // {FE0420F1-38D1-4B4C-96BF-E7E20A74CFB7}
+ static constexpr CLSID kKeymanDesktop_CLSID = {
+ 0xFE0420F1,
+ 0x38D1,
+ 0x4B4C,
+ {0x96, 0xBF, 0xE7, 0xE2, 0x0A, 0x74, 0xCF, 0xB7}};
+ if (mActiveTIPCLSID == kKeymanDesktop_CLSID) {
+ mActiveTIP = TextInputProcessorID::eKeymanDesktop;
+ }
+ }
+
+ TextInputProcessorID ComputeActiveTIPAsJapanese() {
+ // {A76C93D9-5523-4E90-AAFA-4DB112F9AC76} (Win7, Win8.1, Win10)
+ static constexpr GUID kMicrosoftIMEForJapaneseGUID = {
+ 0xA76C93D9,
+ 0x5523,
+ 0x4E90,
+ {0xAA, 0xFA, 0x4D, 0xB1, 0x12, 0xF9, 0xAC, 0x76}};
+ if (mActiveTIPGUID == kMicrosoftIMEForJapaneseGUID) {
+ return TextInputProcessorID::eMicrosoftIMEForJapanese;
+ }
+ // {54EDCC94-1524-4BB1-9FB7-7BABE4F4CA64}
+ static constexpr GUID kMicrosoftOfficeIME2010ForJapaneseGUID = {
+ 0x54EDCC94,
+ 0x1524,
+ 0x4BB1,
+ {0x9F, 0xB7, 0x7B, 0xAB, 0xE4, 0xF4, 0xCA, 0x64}};
+ if (mActiveTIPGUID == kMicrosoftOfficeIME2010ForJapaneseGUID) {
+ return TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese;
+ }
+ // {773EB24E-CA1D-4B1B-B420-FA985BB0B80D}
+ static constexpr GUID kGoogleJapaneseInputGUID = {
+ 0x773EB24E,
+ 0xCA1D,
+ 0x4B1B,
+ {0xB4, 0x20, 0xFA, 0x98, 0x5B, 0xB0, 0xB8, 0x0D}};
+ if (mActiveTIPGUID == kGoogleJapaneseInputGUID) {
+ return TextInputProcessorID::eGoogleJapaneseInput;
+ }
+ // {F9C24A5C-8A53-499D-9572-93B2FF582115}
+ static const GUID kATOK2011GUID = {
+ 0xF9C24A5C,
+ 0x8A53,
+ 0x499D,
+ {0x95, 0x72, 0x93, 0xB2, 0xFF, 0x58, 0x21, 0x15}};
+ if (mActiveTIPGUID == kATOK2011GUID) {
+ return TextInputProcessorID::eATOK2011;
+ }
+ // {1DE01562-F445-401B-B6C3-E5B18DB79461}
+ static constexpr GUID kATOK2012GUID = {
+ 0x1DE01562,
+ 0xF445,
+ 0x401B,
+ {0xB6, 0xC3, 0xE5, 0xB1, 0x8D, 0xB7, 0x94, 0x61}};
+ if (mActiveTIPGUID == kATOK2012GUID) {
+ return TextInputProcessorID::eATOK2012;
+ }
+ // {3C4DB511-189A-4168-B6EA-BFD0B4C85615}
+ static constexpr GUID kATOK2013GUID = {
+ 0x3C4DB511,
+ 0x189A,
+ 0x4168,
+ {0xB6, 0xEA, 0xBF, 0xD0, 0xB4, 0xC8, 0x56, 0x15}};
+ if (mActiveTIPGUID == kATOK2013GUID) {
+ return TextInputProcessorID::eATOK2013;
+ }
+ // {4EF33B79-6AA9-4271-B4BF-9321C279381B}
+ static constexpr GUID kATOK2014GUID = {
+ 0x4EF33B79,
+ 0x6AA9,
+ 0x4271,
+ {0xB4, 0xBF, 0x93, 0x21, 0xC2, 0x79, 0x38, 0x1B}};
+ if (mActiveTIPGUID == kATOK2014GUID) {
+ return TextInputProcessorID::eATOK2014;
+ }
+ // {EAB4DC00-CE2E-483D-A86A-E6B99DA9599A}
+ static constexpr GUID kATOK2015GUID = {
+ 0xEAB4DC00,
+ 0xCE2E,
+ 0x483D,
+ {0xA8, 0x6A, 0xE6, 0xB9, 0x9D, 0xA9, 0x59, 0x9A}};
+ if (mActiveTIPGUID == kATOK2015GUID) {
+ return TextInputProcessorID::eATOK2015;
+ }
+ // {0B557B4C-5740-4110-A60A-1493FA10BF2B}
+ static constexpr GUID kATOK2016GUID = {
+ 0x0B557B4C,
+ 0x5740,
+ 0x4110,
+ {0xA6, 0x0A, 0x14, 0x93, 0xFA, 0x10, 0xBF, 0x2B}};
+ if (mActiveTIPGUID == kATOK2016GUID) {
+ return TextInputProcessorID::eATOK2016;
+ }
+
+ // * ATOK 2017
+ // - {6DBFD8F5-701D-11E6-920F-782BCBA6348F}
+ // * ATOK Passport (confirmed with version 31.1.2)
+ // - {A38F2FD9-7199-45E1-841C-BE0313D8052F}
+
+ if (IsATOKActiveInternal()) {
+ return TextInputProcessorID::eATOKUnknown;
+ }
+
+ // {E6D66705-1EDA-4373-8D01-1D0CB2D054C7}
+ static constexpr GUID kJapanist10GUID = {
+ 0xE6D66705,
+ 0x1EDA,
+ 0x4373,
+ {0x8D, 0x01, 0x1D, 0x0C, 0xB2, 0xD0, 0x54, 0xC7}};
+ if (mActiveTIPGUID == kJapanist10GUID) {
+ return TextInputProcessorID::eJapanist10;
+ }
+
+ return TextInputProcessorID::eUnknown;
+ }
+
+ TextInputProcessorID ComputeActiveTIPAsTraditionalChinese() {
+ // {B2F9C502-1742-11D4-9790-0080C882687E} (Win8.1, Win10)
+ static constexpr GUID kMicrosoftBopomofoGUID = {
+ 0xB2F9C502,
+ 0x1742,
+ 0x11D4,
+ {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftBopomofoGUID) {
+ return TextInputProcessorID::eMicrosoftBopomofo;
+ }
+ // {4BDF9F03-C7D3-11D4-B2AB-0080C882687E} (Win7, Win8.1, Win10)
+ static const GUID kMicrosoftChangJieGUID = {
+ 0x4BDF9F03,
+ 0xC7D3,
+ 0x11D4,
+ {0xB2, 0xAB, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftChangJieGUID) {
+ return TextInputProcessorID::eMicrosoftChangJie;
+ }
+ // {761309DE-317A-11D4-9B5D-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftPhoneticGUID = {
+ 0x761309DE,
+ 0x317A,
+ 0x11D4,
+ {0x9B, 0x5D, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftPhoneticGUID) {
+ return TextInputProcessorID::eMicrosoftPhonetic;
+ }
+ // {6024B45F-5C54-11D4-B921-0080C882687E} (Win7, Win8.1, Win10)
+ static constexpr GUID kMicrosoftQuickGUID = {
+ 0x6024B45F,
+ 0x5C54,
+ 0x11D4,
+ {0xB9, 0x21, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftQuickGUID) {
+ return TextInputProcessorID::eMicrosoftQuick;
+ }
+ // {F3BA907A-6C7E-11D4-97FA-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftNewChangJieGUID = {
+ 0xF3BA907A,
+ 0x6C7E,
+ 0x11D4,
+ {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftNewChangJieGUID) {
+ return TextInputProcessorID::eMicrosoftNewChangJie;
+ }
+ // {B2F9C502-1742-11D4-9790-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftNewPhoneticGUID = {
+ 0xB2F9C502,
+ 0x1742,
+ 0x11D4,
+ {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftNewPhoneticGUID) {
+ return TextInputProcessorID::eMicrosoftNewPhonetic;
+ }
+ // {0B883BA0-C1C7-11D4-87F9-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftNewQuickGUID = {
+ 0x0B883BA0,
+ 0xC1C7,
+ 0x11D4,
+ {0x87, 0xF9, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftNewQuickGUID) {
+ return TextInputProcessorID::eMicrosoftNewQuick;
+ }
+
+ // NOTE: There are some other Traditional Chinese TIPs installed in Windows:
+ // * Chinese Traditional Array (version 6.0)
+ // - {D38EFF65-AA46-4FD5-91A7-67845FB02F5B} (Win7, Win8.1)
+ // * Chinese Traditional DaYi (version 6.0)
+ // - {037B2C25-480C-4D7F-B027-D6CA6B69788A} (Win7, Win8.1)
+
+ // {B58630B5-0ED3-4335-BBC9-E77BBCB43CAD}
+ static const GUID kFreeChangJieGUID = {
+ 0xB58630B5,
+ 0x0ED3,
+ 0x4335,
+ {0xBB, 0xC9, 0xE7, 0x7B, 0xBC, 0xB4, 0x3C, 0xAD}};
+ if (mActiveTIPGUID == kFreeChangJieGUID) {
+ return TextInputProcessorID::eFreeChangJie;
+ }
+
+ return TextInputProcessorID::eUnknown;
+ }
+
+ TextInputProcessorID ComputeActiveTIPAsSimplifiedChinese() {
+ // FYI: This matches with neither "Microsoft Pinyin ABC Input Style" nor
+ // "Microsoft Pinyin New Experience Input Style" on Win7.
+ // {FA550B04-5AD7-411F-A5AC-CA038EC515D7} (Win8.1, Win10)
+ static constexpr GUID kMicrosoftPinyinGUID = {
+ 0xFA550B04,
+ 0x5AD7,
+ 0x411F,
+ {0xA5, 0xAC, 0xCA, 0x03, 0x8E, 0xC5, 0x15, 0xD7}};
+ if (mActiveTIPGUID == kMicrosoftPinyinGUID) {
+ return TextInputProcessorID::eMicrosoftPinyin;
+ }
+
+ // {F3BA9077-6C7E-11D4-97FA-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftPinyinNewExperienceInputStyleGUID = {
+ 0xF3BA9077,
+ 0x6C7E,
+ 0x11D4,
+ {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftPinyinNewExperienceInputStyleGUID) {
+ return TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle;
+ }
+ // {82590C13-F4DD-44F4-BA1D-8667246FDF8E} (Win8.1, Win10)
+ static constexpr GUID kMicrosoftWubiGUID = {
+ 0x82590C13,
+ 0xF4DD,
+ 0x44F4,
+ {0xBA, 0x1D, 0x86, 0x67, 0x24, 0x6F, 0xDF, 0x8E}};
+ if (mActiveTIPGUID == kMicrosoftWubiGUID) {
+ return TextInputProcessorID::eMicrosoftWubi;
+ }
+ // NOTE: There are some other Simplified Chinese TIPs installed in Windows:
+ // * Chinese Simplified QuanPin (version 6.0)
+ // - {54FC610E-6ABD-4685-9DDD-A130BDF1B170} (Win8.1)
+ // * Chinese Simplified ZhengMa (version 6.0)
+ // - {733B4D81-3BC3-4132-B91A-E9CDD5E2BFC9} (Win8.1)
+ // * Chinese Simplified ShuangPin (version 6.0)
+ // - {EF63706D-31C4-490E-9DBB-BD150ADC454B} (Win8.1)
+ // * Microsoft Pinyin ABC Input Style
+ // - {FCA121D2-8C6D-41FB-B2DE-A2AD110D4820} (Win7)
+ return TextInputProcessorID::eUnknown;
+ }
+
+ TextInputProcessorID ComputeActiveTIPAsKorean() {
+ // {B5FE1F02-D5F2-4445-9C03-C568F23C99A1} (Win7, Win8.1, Win10)
+ static constexpr GUID kMicrosoftIMEForKoreanGUID = {
+ 0xB5FE1F02,
+ 0xD5F2,
+ 0x4445,
+ {0x9C, 0x03, 0xC5, 0x68, 0xF2, 0x3C, 0x99, 0xA1}};
+ if (mActiveTIPGUID == kMicrosoftIMEForKoreanGUID) {
+ return TextInputProcessorID::eMicrosoftIMEForKorean;
+ }
+ // {B60AF051-257A-46BC-B9D3-84DAD819BAFB} (Win8.1, Win10)
+ static constexpr GUID kMicrosoftOldHangulGUID = {
+ 0xB60AF051,
+ 0x257A,
+ 0x46BC,
+ {0xB9, 0xD3, 0x84, 0xDA, 0xD8, 0x19, 0xBA, 0xFB}};
+ if (mActiveTIPGUID == kMicrosoftOldHangulGUID) {
+ return TextInputProcessorID::eMicrosoftOldHangul;
+ }
+
+ // NOTE: There is the other Korean TIP installed in Windows:
+ // * Microsoft IME 2010
+ // - {48878C45-93F9-4aaf-A6A1-272CD863C4F5} (Win7)
+
+ return TextInputProcessorID::eUnknown;
+ }
+
+ public: // ITfInputProcessorProfileActivationSink
+ STDMETHODIMP OnActivated(DWORD, LANGID, REFCLSID, REFGUID, REFGUID, HKL,
+ DWORD);
+
+ private:
+ TSFStaticSink();
+ virtual ~TSFStaticSink() {}
+
+ bool EnsureInitActiveTIPKeyboard();
+
+ void Destroy();
+
+ void GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile, nsAString& aDescription);
+ bool IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile);
+
+ TextInputProcessorID mActiveTIP;
+
+ // Cookie of installing ITfInputProcessorProfileActivationSink
+ DWORD mIPProfileCookie;
+
+ LANGID mLangID;
+
+ // True if current IME is implemented with IMM.
+ bool mIsIMM_IME;
+ // True if OnActivated() is already called
+ bool mOnActivatedCalled;
+
+ RefPtr<ITfThreadMgr> mThreadMgr;
+ RefPtr<ITfInputProcessorProfiles> mInputProcessorProfiles;
+
+ // Active TIP keyboard's description. If active language profile isn't TIP,
+ // i.e., IMM-IME or just a keyboard layout, this is empty.
+ nsString mActiveTIPKeyboardDescription;
+
+ // Active TIP's GUID and CLSID
+ GUID mActiveTIPGUID;
+ CLSID mActiveTIPCLSID;
+
+ static StaticRefPtr<TSFStaticSink> sInstance;
+};
+
+StaticRefPtr<TSFStaticSink> TSFStaticSink::sInstance;
+
+TSFStaticSink::TSFStaticSink()
+ : mActiveTIP(TextInputProcessorID::eNotComputed),
+ mIPProfileCookie(TF_INVALID_COOKIE),
+ mLangID(0),
+ mIsIMM_IME(false),
+ mOnActivatedCalled(false),
+ mActiveTIPGUID(GUID_NULL) {}
+
+bool TSFStaticSink::Init(ITfThreadMgr* aThreadMgr,
+ ITfInputProcessorProfiles* aInputProcessorProfiles) {
+ MOZ_ASSERT(!mThreadMgr && !mInputProcessorProfiles,
+ "TSFStaticSink::Init() must be called only once");
+
+ mThreadMgr = aThreadMgr;
+ mInputProcessorProfiles = aInputProcessorProfiles;
+
+ RefPtr<ITfSource> source;
+ HRESULT hr =
+ mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFStaticSink::Init() FAILED to get ITfSource "
+ "instance (0x%08X)",
+ this, hr));
+ return false;
+ }
+
+ // NOTE: On Vista or later, Windows let us know activate IME changed only
+ // with ITfInputProcessorProfileActivationSink.
+ hr = source->AdviseSink(
+ IID_ITfInputProcessorProfileActivationSink,
+ static_cast<ITfInputProcessorProfileActivationSink*>(this),
+ &mIPProfileCookie);
+ if (FAILED(hr) || mIPProfileCookie == TF_INVALID_COOKIE) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFStaticSink::Init() FAILED to install "
+ "ITfInputProcessorProfileActivationSink (0x%08X)",
+ this, hr));
+ return false;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFStaticSink::Init(), "
+ "mIPProfileCookie=0x%08X",
+ this, mIPProfileCookie));
+ return true;
+}
+
+void TSFStaticSink::Destroy() {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFStaticSink::Shutdown() "
+ "mIPProfileCookie=0x%08X",
+ this, mIPProfileCookie));
+
+ if (mIPProfileCookie != TF_INVALID_COOKIE) {
+ RefPtr<ITfSource> source;
+ HRESULT hr =
+ mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFStaticSink::Shutdown() FAILED to get "
+ "ITfSource instance (0x%08X)",
+ this, hr));
+ } else {
+ hr = source->UnadviseSink(mIPProfileCookie);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Shutdown() FAILED to uninstall "
+ "ITfInputProcessorProfileActivationSink (0x%08X)",
+ this, hr));
+ }
+ }
+ }
+
+ mThreadMgr = nullptr;
+ mInputProcessorProfiles = nullptr;
+}
+
+STDMETHODIMP
+TSFStaticSink::OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID rclsid,
+ REFGUID catid, REFGUID guidProfile, HKL hkl,
+ DWORD dwFlags) {
+ if ((dwFlags & TF_IPSINK_FLAG_ACTIVE) &&
+ (dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT ||
+ catid == GUID_TFCAT_TIP_KEYBOARD)) {
+ mOnActivatedCalled = true;
+ mActiveTIP = TextInputProcessorID::eNotComputed;
+ mActiveTIPGUID = guidProfile;
+ mActiveTIPCLSID = rclsid;
+ mLangID = langid & 0xFFFF;
+ mIsIMM_IME = IsIMM_IME(hkl);
+ GetTIPDescription(rclsid, langid, guidProfile,
+ mActiveTIPKeyboardDescription);
+ if (mActiveTIPGUID != GUID_NULL) {
+ // key should be "LocaleID|Description". Although GUID of the
+ // profile is unique key since description may be localized for system
+ // language, unfortunately, it's too long to record as key with its
+ // description. Therefore, we should record only the description with
+ // LocaleID because Microsoft IME may not include language information.
+ // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
+ nsAutoString key;
+ key.AppendPrintf("0x%04X|", mLangID);
+ nsAutoString description(mActiveTIPKeyboardDescription);
+ static const uint32_t kMaxDescriptionLength = 72 - key.Length();
+ if (description.Length() > kMaxDescriptionLength) {
+ if (NS_IS_LOW_SURROGATE(description[kMaxDescriptionLength - 1]) &&
+ NS_IS_HIGH_SURROGATE(description[kMaxDescriptionLength - 2])) {
+ description.Truncate(kMaxDescriptionLength - 2);
+ } else {
+ description.Truncate(kMaxDescriptionLength - 1);
+ }
+ // U+2026 is "..."
+ description.Append(char16_t(0x2026));
+ }
+ key.Append(description);
+ Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_WINDOWS, key,
+ true);
+ }
+ // Notify IMEHandler of changing active keyboard layout.
+ IMEHandler::OnKeyboardLayoutChanged();
+ }
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFStaticSink::OnActivated(dwProfileType=%s (0x%08X), "
+ "langid=0x%08X, rclsid=%s, catid=%s, guidProfile=%s, hkl=0x%08X, "
+ "dwFlags=0x%08X (TF_IPSINK_FLAG_ACTIVE: %s)), mIsIMM_IME=%s, "
+ "mActiveTIPDescription=\"%s\"",
+ this,
+ dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR
+ ? "TF_PROFILETYPE_INPUTPROCESSOR"
+ : dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT
+ ? "TF_PROFILETYPE_KEYBOARDLAYOUT"
+ : "Unknown",
+ dwProfileType, langid, GetCLSIDNameStr(rclsid).get(),
+ GetGUIDNameStr(catid).get(), GetGUIDNameStr(guidProfile).get(), hkl,
+ dwFlags, GetBoolName(dwFlags & TF_IPSINK_FLAG_ACTIVE),
+ GetBoolName(mIsIMM_IME),
+ NS_ConvertUTF16toUTF8(mActiveTIPKeyboardDescription).get()));
+ return S_OK;
+}
+
+bool TSFStaticSink::EnsureInitActiveTIPKeyboard() {
+ if (mOnActivatedCalled) {
+ return true;
+ }
+
+ RefPtr<ITfInputProcessorProfileMgr> profileMgr;
+ HRESULT hr = mInputProcessorProfiles->QueryInterface(
+ IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
+ if (FAILED(hr) || !profileMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
+ "to get input processor profile manager, hr=0x%08X",
+ this, hr));
+ return false;
+ }
+
+ TF_INPUTPROCESSORPROFILE profile;
+ hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
+ if (hr == S_FALSE) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
+ "to get active keyboard layout profile due to no active profile, "
+ "hr=0x%08X",
+ this, hr));
+ // XXX Should we call OnActivated() with arguments like non-TIP in this
+ // case?
+ return false;
+ }
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
+ "to get active TIP keyboard, hr=0x%08X",
+ this, hr));
+ return false;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), "
+ "calling OnActivated() manually...",
+ this));
+ OnActivated(profile.dwProfileType, profile.langid, profile.clsid,
+ profile.catid, profile.guidProfile, ::GetKeyboardLayout(0),
+ TF_IPSINK_FLAG_ACTIVE);
+ return true;
+}
+
+void TSFStaticSink::GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile,
+ nsAString& aDescription) {
+ aDescription.Truncate();
+
+ if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
+ return;
+ }
+
+ BSTR description = nullptr;
+ HRESULT hr = mInputProcessorProfiles->GetLanguageProfileDescription(
+ aTextService, aLangID, aProfile, &description);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFStaticSink::InitActiveTIPDescription() FAILED "
+ "due to GetLanguageProfileDescription() failure, hr=0x%08X",
+ this, hr));
+ return;
+ }
+
+ if (description && description[0]) {
+ aDescription.Assign(description);
+ }
+ ::SysFreeString(description);
+}
+
+bool TSFStaticSink::IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile) {
+ if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
+ return false;
+ }
+
+ RefPtr<IEnumTfLanguageProfiles> enumLangProfiles;
+ HRESULT hr = mInputProcessorProfiles->EnumLanguageProfiles(
+ aLangID, getter_AddRefs(enumLangProfiles));
+ if (FAILED(hr) || !enumLangProfiles) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFStaticSink::IsTIPCategoryKeyboard(), FAILED "
+ "to get language profiles enumerator, hr=0x%08X",
+ this, hr));
+ return false;
+ }
+
+ TF_LANGUAGEPROFILE profile;
+ ULONG fetch = 0;
+ while (SUCCEEDED(enumLangProfiles->Next(1, &profile, &fetch)) && fetch) {
+ // XXX We're not sure a profile is registered with two or more categories.
+ if (profile.clsid == aTextService && profile.guidProfile == aProfile &&
+ profile.catid == GUID_TFCAT_TIP_KEYBOARD) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/******************************************************************/
+/* TSFPreference */
+/******************************************************************/
+
+class TSFPrefs final {
+ public:
+#define DECL_AND_IMPL_BOOL_PREF(aPref, aName, aDefaultValue) \
+ static bool aName() { \
+ static bool s##aName##Value = Preferences::GetBool(aPref, aDefaultValue); \
+ return s##aName##Value; \
+ }
+
+ DECL_AND_IMPL_BOOL_PREF("intl.ime.hack.set_input_scope_of_url_bar_to_default",
+ ShouldSetInputScopeOfURLBarToDefault, true)
+ DECL_AND_IMPL_BOOL_PREF(
+ "intl.tsf.hack.allow_to_stop_hacking_on_build_17643_or_later",
+ AllowToStopHackingOnBuild17643OrLater, false)
+ DECL_AND_IMPL_BOOL_PREF("intl.tsf.hack.atok.create_native_caret",
+ NeedToCreateNativeCaretForLegacyATOK, true)
+ DECL_AND_IMPL_BOOL_PREF(
+ "intl.tsf.hack.atok.do_not_return_no_layout_error_of_composition_string",
+ DoNotReturnNoLayoutErrorToATOKOfCompositionString, true)
+ DECL_AND_IMPL_BOOL_PREF(
+ "intl.tsf.hack.japanist10."
+ "do_not_return_no_layout_error_of_composition_string",
+ DoNotReturnNoLayoutErrorToJapanist10OfCompositionString, true)
+ DECL_AND_IMPL_BOOL_PREF(
+ "intl.tsf.hack.ms_simplified_chinese.do_not_return_no_layout_error",
+ DoNotReturnNoLayoutErrorToMSSimplifiedTIP, true)
+ DECL_AND_IMPL_BOOL_PREF(
+ "intl.tsf.hack.ms_traditional_chinese.do_not_return_no_layout_error",
+ DoNotReturnNoLayoutErrorToMSTraditionalTIP, true)
+ DECL_AND_IMPL_BOOL_PREF(
+ "intl.tsf.hack.free_chang_jie.do_not_return_no_layout_error",
+ DoNotReturnNoLayoutErrorToFreeChangJie, true)
+ DECL_AND_IMPL_BOOL_PREF(
+ "intl.tsf.hack.ms_japanese_ime.do_not_return_no_layout_error_at_first_"
+ "char",
+ DoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar, true)
+ DECL_AND_IMPL_BOOL_PREF(
+ "intl.tsf.hack.ms_japanese_ime.do_not_return_no_layout_error_at_caret",
+ DoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret, true)
+ DECL_AND_IMPL_BOOL_PREF(
+ "intl.tsf.hack.ms_simplified_chinese.query_insert_result",
+ NeedToHackQueryInsertForMSSimplifiedTIP, true)
+ DECL_AND_IMPL_BOOL_PREF(
+ "intl.tsf.hack.ms_traditional_chinese.query_insert_result",
+ NeedToHackQueryInsertForMSTraditionalTIP, true)
+
+#undef DECL_AND_IMPL_BOOL_PREF
+};
+
+/******************************************************************/
+/* TSFTextStore */
+/******************************************************************/
+
+StaticRefPtr<ITfThreadMgr> TSFTextStore::sThreadMgr;
+StaticRefPtr<ITfMessagePump> TSFTextStore::sMessagePump;
+StaticRefPtr<ITfKeystrokeMgr> TSFTextStore::sKeystrokeMgr;
+StaticRefPtr<ITfDisplayAttributeMgr> TSFTextStore::sDisplayAttrMgr;
+StaticRefPtr<ITfCategoryMgr> TSFTextStore::sCategoryMgr;
+StaticRefPtr<ITfCompartment> TSFTextStore::sCompartmentForOpenClose;
+StaticRefPtr<ITfDocumentMgr> TSFTextStore::sDisabledDocumentMgr;
+StaticRefPtr<ITfContext> TSFTextStore::sDisabledContext;
+StaticRefPtr<ITfInputProcessorProfiles> TSFTextStore::sInputProcessorProfiles;
+StaticRefPtr<TSFTextStore> TSFTextStore::sEnabledTextStore;
+const MSG* TSFTextStore::sHandlingKeyMsg = nullptr;
+DWORD TSFTextStore::sClientId = 0;
+bool TSFTextStore::sIsKeyboardEventDispatched = false;
+
+#define TEXTSTORE_DEFAULT_VIEW (1)
+
+TSFTextStore::TSFTextStore()
+ : mEditCookie(0),
+ mSinkMask(0),
+ mLock(0),
+ mLockQueued(0),
+ mHandlingKeyMessage(0),
+ mRequestedAttrValues(false),
+ mIsRecordingActionsWithoutLock(false),
+ mHasReturnedNoLayoutError(false),
+ mWaitingQueryLayout(false),
+ mPendingDestroy(false),
+ mDeferClearingContentForTSF(false),
+ mDeferNotifyingTSF(false),
+ mDeferCommittingComposition(false),
+ mDeferCancellingComposition(false),
+ mDestroyed(false),
+ mBeingDestroyed(false) {
+ for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
+ mRequestedAttrs[i] = false;
+ }
+
+ // We hope that 5 or more actions don't occur at once.
+ mPendingActions.SetCapacity(5);
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::TSFTextStore() SUCCEEDED", this));
+}
+
+TSFTextStore::~TSFTextStore() {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore instance is destroyed", this));
+}
+
+bool TSFTextStore::Init(nsWindowBase* aWidget, const InputContext& aContext) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::Init(aWidget=0x%p)", this, aWidget));
+
+ if (NS_WARN_IF(!aWidget) || NS_WARN_IF(aWidget->Destroyed())) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED due to being initialized with "
+ "destroyed widget",
+ this));
+ return false;
+ }
+
+ if (mDocumentMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED due to already initialized",
+ this));
+ return false;
+ }
+
+ mWidget = aWidget;
+ if (NS_WARN_IF(!mWidget)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED "
+ "due to aWidget is nullptr ",
+ this));
+ return false;
+ }
+ mDispatcher = mWidget->GetTextEventDispatcher();
+ if (NS_WARN_IF(!mDispatcher)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED "
+ "due to aWidget->GetTextEventDispatcher() failure",
+ this));
+ return false;
+ }
+
+ SetInputScope(aContext.mHTMLInputType, aContext.mHTMLInputInputmode,
+ aContext.mInPrivateBrowsing);
+
+ // Create document manager
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ RefPtr<ITfDocumentMgr> documentMgr;
+ HRESULT hr = threadMgr->CreateDocumentMgr(getter_AddRefs(documentMgr));
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr "
+ "(0x%08X)",
+ this, hr));
+ return false;
+ }
+ if (NS_WARN_IF(mDestroyed)) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr due to "
+ "TextStore being destroyed during calling "
+ "ITfThreadMgr::CreateDocumentMgr()",
+ this));
+ return false;
+ }
+ // Create context and add it to document manager
+ RefPtr<ITfContext> context;
+ hr = documentMgr->CreateContext(sClientId, 0,
+ static_cast<ITextStoreACP*>(this),
+ getter_AddRefs(context), &mEditCookie);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create the context "
+ "(0x%08X)",
+ this, hr));
+ return false;
+ }
+ if (NS_WARN_IF(mDestroyed)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
+ "TextStore being destroyed during calling "
+ "ITfDocumentMgr::CreateContext()",
+ this));
+ return false;
+ }
+
+ hr = documentMgr->Push(context);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to push the context (0x%08X)",
+ this, hr));
+ return false;
+ }
+ if (NS_WARN_IF(mDestroyed)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
+ "TextStore being destroyed during calling ITfDocumentMgr::Push()",
+ this));
+ documentMgr->Pop(TF_POPF_ALL);
+ return false;
+ }
+
+ mDocumentMgr = documentMgr;
+ mContext = context;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::Init() succeeded: "
+ "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08X",
+ this, mDocumentMgr.get(), mContext.get(), mEditCookie));
+
+ return true;
+}
+
+void TSFTextStore::Destroy() {
+ if (mBeingDestroyed) {
+ return;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::Destroy(), mLock=%s, "
+ "mComposition=%s, mHandlingKeyMessage=%u",
+ this, GetLockFlagNameStr(mLock).get(),
+ ToString(mComposition).c_str(), mHandlingKeyMessage));
+
+ mDestroyed = true;
+
+ // Destroy native caret first because it's not directly related to TSF and
+ // there may be another textstore which gets focus. So, we should avoid
+ // to destroy caret after the new one recreates caret.
+ IMEHandler::MaybeDestroyNativeCaret();
+
+ if (mLock) {
+ mPendingDestroy = true;
+ return;
+ }
+
+ AutoRestore<bool> savedBeingDestroyed(mBeingDestroyed);
+ mBeingDestroyed = true;
+
+ // If there is composition, TSF keeps the composition even after the text
+ // store destroyed. So, we should clear the composition here.
+ if (mComposition.isSome()) {
+ CommitCompositionInternal(false);
+ }
+
+ if (mSink) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::Destroy(), calling "
+ "ITextStoreACPSink::OnLayoutChange(TS_LC_DESTROY)...",
+ this));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ sink->OnLayoutChange(TS_LC_DESTROY, TEXTSTORE_DEFAULT_VIEW);
+ }
+
+ // If this is called during handling a keydown or keyup message, we should
+ // put off to release TSF objects until it completely finishes since
+ // MS-IME for Japanese refers some objects without grabbing them.
+ if (!mHandlingKeyMessage) {
+ ReleaseTSFObjects();
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::Destroy() succeeded", this));
+}
+
+void TSFTextStore::ReleaseTSFObjects() {
+ MOZ_ASSERT(!mHandlingKeyMessage);
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::ReleaseTSFObjects()", this));
+
+ mContext = nullptr;
+ if (mDocumentMgr) {
+ RefPtr<ITfDocumentMgr> documentMgr = mDocumentMgr.forget();
+ documentMgr->Pop(TF_POPF_ALL);
+ }
+ mSink = nullptr;
+ mWidget = nullptr;
+ mDispatcher = nullptr;
+
+ if (!mMouseTrackers.IsEmpty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::ReleaseTSFObjects(), "
+ "removing a mouse tracker...",
+ this));
+ mMouseTrackers.Clear();
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::ReleaseTSFObjects() completed", this));
+}
+
+STDMETHODIMP
+TSFTextStore::QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+ if ((IID_IUnknown == riid) || (IID_ITextStoreACP == riid)) {
+ *ppv = static_cast<ITextStoreACP*>(this);
+ } else if (IID_ITfContextOwnerCompositionSink == riid) {
+ *ppv = static_cast<ITfContextOwnerCompositionSink*>(this);
+ } else if (IID_ITfMouseTrackerACP == riid) {
+ *ppv = static_cast<ITfMouseTrackerACP*>(this);
+ }
+ if (*ppv) {
+ AddRef();
+ return S_OK;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::QueryInterface() FAILED, riid=%s", this,
+ GetRIIDNameStr(riid).get()));
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP
+TSFTextStore::AdviseSink(REFIID riid, IUnknown* punk, DWORD dwMask) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::AdviseSink(riid=%s, punk=0x%p, dwMask=%s), "
+ "mSink=0x%p, mSinkMask=%s",
+ this, GetRIIDNameStr(riid).get(), punk, GetSinkMaskNameStr(dwMask).get(),
+ mSink.get(), GetSinkMaskNameStr(mSinkMask).get()));
+
+ if (!punk) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseSink() FAILED due to the null punk",
+ this));
+ return E_UNEXPECTED;
+ }
+
+ if (IID_ITextStoreACPSink != riid) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseSink() FAILED due to "
+ "unsupported interface",
+ this));
+ return E_INVALIDARG; // means unsupported interface.
+ }
+
+ if (!mSink) {
+ // Install sink
+ punk->QueryInterface(IID_ITextStoreACPSink, getter_AddRefs(mSink));
+ if (!mSink) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseSink() FAILED due to "
+ "punk not having the interface",
+ this));
+ return E_UNEXPECTED;
+ }
+ } else {
+ // If sink is already installed we check to see if they are the same
+ // Get IUnknown from both sides for comparison
+ RefPtr<IUnknown> comparison1, comparison2;
+ punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
+ mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
+ if (comparison1 != comparison2) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseSink() FAILED due to "
+ "the sink being different from the stored sink",
+ this));
+ return CONNECT_E_ADVISELIMIT;
+ }
+ }
+ // Update mask either for a new sink or an existing sink
+ mSinkMask = dwMask;
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::UnadviseSink(IUnknown* punk) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::UnadviseSink(punk=0x%p), mSink=0x%p", this, punk,
+ mSink.get()));
+
+ if (!punk) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseSink() FAILED due to the null punk",
+ this));
+ return E_INVALIDARG;
+ }
+ if (!mSink) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
+ "any sink not stored",
+ this));
+ return CONNECT_E_NOCONNECTION;
+ }
+ // Get IUnknown from both sides for comparison
+ RefPtr<IUnknown> comparison1, comparison2;
+ punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
+ mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
+ // Unadvise only if sinks are the same
+ if (comparison1 != comparison2) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
+ "the sink being different from the stored sink",
+ this));
+ return CONNECT_E_NOCONNECTION;
+ }
+ mSink = nullptr;
+ mSinkMask = 0;
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::RequestLock(DWORD dwLockFlags, HRESULT* phrSession) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestLock(dwLockFlags=%s, phrSession=0x%p), "
+ "mLock=%s, mDestroyed=%s",
+ this, GetLockFlagNameStr(dwLockFlags).get(), phrSession,
+ GetLockFlagNameStr(mLock).get(), GetBoolName(mDestroyed)));
+
+ if (!mSink) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RequestLock() FAILED due to "
+ "any sink not stored",
+ this));
+ return E_FAIL;
+ }
+ if (mDestroyed &&
+ (mContentForTSF.isNothing() || mSelectionForTSF.isNothing())) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RequestLock() FAILED due to "
+ "being destroyed and no information of the contents",
+ this));
+ return E_FAIL;
+ }
+ if (!phrSession) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RequestLock() FAILED due to "
+ "null phrSession",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!mLock) {
+ // put on lock
+ mLock = dwLockFlags & (~TS_LF_SYNC);
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Info,
+ ("0x%p Locking (%s) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
+ ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
+ this, GetLockFlagNameStr(mLock).get()));
+ // Don't release this instance during this lock because this is called by
+ // TSF but they don't grab us during this call.
+ RefPtr<TSFTextStore> kungFuDeathGrip(this);
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ *phrSession = sink->OnLockGranted(mLock);
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Info,
+ ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
+ "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
+ this, GetLockFlagNameStr(mLock).get()));
+ DidLockGranted();
+ while (mLockQueued) {
+ mLock = mLockQueued;
+ mLockQueued = 0;
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p Locking for the request in the queue (%s) >>>>>>>>>>>>>>"
+ ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
+ ">>>>>",
+ this, GetLockFlagNameStr(mLock).get()));
+ sink->OnLockGranted(mLock);
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
+ "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
+ "<<<<<",
+ this, GetLockFlagNameStr(mLock).get()));
+ DidLockGranted();
+ }
+
+ // The document is now completely unlocked.
+ mLock = 0;
+
+ MaybeFlushPendingNotifications();
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestLock() succeeded: *phrSession=%s",
+ this, GetTextStoreReturnValueName(*phrSession)));
+ return S_OK;
+ }
+
+ // only time when reentrant lock is allowed is when caller holds a
+ // read-only lock and is requesting an async write lock
+ if (IsReadLocked() && !IsReadWriteLocked() && IsReadWriteLock(dwLockFlags) &&
+ !(dwLockFlags & TS_LF_SYNC)) {
+ *phrSession = TS_S_ASYNC;
+ mLockQueued = dwLockFlags & (~TS_LF_SYNC);
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestLock() stores the request in the "
+ "queue, *phrSession=TS_S_ASYNC",
+ this));
+ return S_OK;
+ }
+
+ // no more locks allowed
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestLock() didn't allow to lock, "
+ "*phrSession=TS_E_SYNCHRONOUS",
+ this));
+ *phrSession = TS_E_SYNCHRONOUS;
+ return E_FAIL;
+}
+
+void TSFTextStore::DidLockGranted() {
+ if (IsReadWriteLocked()) {
+ // FreeCJ (TIP for Traditional Chinese) calls SetSelection() to set caret
+ // to the start of composition string and insert a full width space for
+ // a placeholder with a call of SetText(). After that, it calls
+ // OnUpdateComposition() without new range. Therefore, let's record the
+ // composition update information here.
+ CompleteLastActionIfStillIncomplete();
+
+ FlushPendingActions();
+ }
+
+ // If the widget has gone, we don't need to notify anything.
+ if (mDestroyed || !mWidget || mWidget->Destroyed()) {
+ mPendingSelectionChangeData.Clear();
+ mHasReturnedNoLayoutError = false;
+ }
+}
+
+void TSFTextStore::DispatchEvent(WidgetGUIEvent& aEvent) {
+ if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) {
+ return;
+ }
+ // If the event isn't a query content event, the event may be handled
+ // asynchronously. So, we should put off to answer from GetTextExt() etc.
+ if (!aEvent.AsQueryContentEvent()) {
+ mDeferNotifyingTSF = true;
+ }
+ mWidget->DispatchWindowEvent(&aEvent);
+}
+
+void TSFTextStore::FlushPendingActions() {
+ if (!mWidget || mWidget->Destroyed()) {
+ // Note that don't clear mContentForTSF because TIP may try to commit
+ // composition with a document lock. In such case, TSFTextStore needs to
+ // behave as expected by TIP.
+ mPendingActions.Clear();
+ mPendingSelectionChangeData.Clear();
+ mHasReturnedNoLayoutError = false;
+ return;
+ }
+
+ // Some TIP may request lock but does nothing during the lock. In such case,
+ // this should do nothing. For example, when MS-IME for Japanese is active
+ // and we're inactivating, this case occurs and causes different behavior
+ // from the other TIPs.
+ if (mPendingActions.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<nsWindowBase> widget(mWidget);
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED due to BeginNativeInputTransaction() failure",
+ this));
+ return;
+ }
+ for (uint32_t i = 0; i < mPendingActions.Length(); i++) {
+ PendingAction& action = mPendingActions[i];
+ switch (action.mType) {
+ case PendingAction::Type::eKeyboardEvent:
+ if (mDestroyed) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Warning,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "IGNORED pending KeyboardEvent(%s) due to already destroyed",
+ action.mKeyMsg.message == WM_KEYDOWN ? "eKeyDown" : "eKeyUp",
+ this));
+ }
+ MOZ_DIAGNOSTIC_ASSERT(action.mKeyMsg.message == WM_KEYDOWN ||
+ action.mKeyMsg.message == WM_KEYUP);
+ DispatchKeyboardEventAsProcessedByIME(action.mKeyMsg);
+ if (!widget || widget->Destroyed()) {
+ break;
+ }
+ break;
+ case PendingAction::Type::eCompositionStart: {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing Type::eCompositionStart={ mSelectionStart=%d, "
+ "mSelectionLength=%d }, mDestroyed=%s",
+ this, action.mSelectionStart, action.mSelectionLength,
+ GetBoolName(mDestroyed)));
+
+ if (mDestroyed) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Warning,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "IGNORED pending compositionstart due to already destroyed",
+ this));
+ break;
+ }
+
+ if (action.mAdjustSelection) {
+ // Select composition range so the new composition replaces the range
+ WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
+ widget->InitEvent(selectionSet);
+ selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
+ selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
+ selectionSet.mReversed = false;
+ selectionSet.mExpandToClusterBoundary =
+ TSFStaticSink::ActiveTIP() !=
+ TextInputProcessorID::eKeymanDesktop &&
+ StaticPrefs::
+ intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries();
+ DispatchEvent(selectionSet);
+ if (!selectionSet.mSucceeded) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED due to eSetSelection failure",
+ this));
+ break;
+ }
+ }
+
+ // eCompositionStart always causes
+ // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. Therefore, we should
+ // wait to clear mContentForTSF until it's notified.
+ mDeferClearingContentForTSF = true;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "dispatching compositionstart event...",
+ this));
+ WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = mDispatcher->StartComposition(status, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to dispatch compositionstart event, "
+ "IsHandlingCompositionInContent()=%s",
+ this, GetBoolName(IsHandlingCompositionInContent())));
+ // XXX Is this right? If there is a composition in content,
+ // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
+ mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
+ }
+ if (!widget || widget->Destroyed()) {
+ break;
+ }
+ break;
+ }
+ case PendingAction::Type::eCompositionUpdate: {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing Type::eCompositionUpdate={ mData=\"%s\", "
+ "mRanges=0x%p, mRanges->Length()=%d }",
+ this, GetEscapedUTF8String(action.mData).get(),
+ action.mRanges.get(),
+ action.mRanges ? action.mRanges->Length() : 0));
+
+ // eCompositionChange causes a DOM text event, the IME will be notified
+ // of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. In this case, we
+ // should not clear mContentForTSF until we notify the IME of the
+ // composition update.
+ mDeferClearingContentForTSF = true;
+
+ rv = mDispatcher->SetPendingComposition(action.mData, action.mRanges);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to setting pending composition... "
+ "IsHandlingCompositionInContent()=%s",
+ this, GetBoolName(IsHandlingCompositionInContent())));
+ // XXX Is this right? If there is a composition in content,
+ // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
+ mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
+ } else {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "dispatching compositionchange event...",
+ this));
+ WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = mDispatcher->FlushPendingComposition(status, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to dispatch compositionchange event, "
+ "IsHandlingCompositionInContent()=%s",
+ this, GetBoolName(IsHandlingCompositionInContent())));
+ // XXX Is this right? If there is a composition in content,
+ // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
+ mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
+ }
+ // Be aware, the mWidget might already have been destroyed.
+ }
+ break;
+ }
+ case PendingAction::Type::eCompositionEnd: {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing Type::eCompositionEnd={ mData=\"%s\" }",
+ this, GetEscapedUTF8String(action.mData).get()));
+
+ // Dispatching eCompositionCommit causes a DOM text event, then,
+ // the IME will be notified of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
+ // when focused content actually handles the event. For example,
+ // when focused content is in a remote process, it's sent when
+ // all dispatched composition events have been handled in the remote
+ // process. So, until then, we don't have newer content information.
+ // Therefore, we need to put off to clear mContentForTSF.
+ mDeferClearingContentForTSF = true;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions(), "
+ "dispatching compositioncommit event...",
+ this));
+ WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = mDispatcher->CommitComposition(status, &action.mData, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to dispatch compositioncommit event, "
+ "IsHandlingCompositionInContent()=%s",
+ this, GetBoolName(IsHandlingCompositionInContent())));
+ // XXX Is this right? If there is a composition in content,
+ // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
+ mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
+ }
+ break;
+ }
+ case PendingAction::Type::eSetSelection: {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing Type::eSetSelection={ mSelectionStart=%d, "
+ "mSelectionLength=%d, mSelectionReversed=%s }, "
+ "mDestroyed=%s",
+ this, action.mSelectionStart, action.mSelectionLength,
+ GetBoolName(action.mSelectionReversed), GetBoolName(mDestroyed)));
+
+ if (mDestroyed) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Warning,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "IGNORED pending selectionset due to already destroyed",
+ this));
+ break;
+ }
+
+ WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
+ selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
+ selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
+ selectionSet.mReversed = action.mSelectionReversed;
+ selectionSet.mExpandToClusterBoundary =
+ TSFStaticSink::ActiveTIP() !=
+ TextInputProcessorID::eKeymanDesktop &&
+ StaticPrefs::
+ intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries();
+ DispatchEvent(selectionSet);
+ if (!selectionSet.mSucceeded) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED due to eSetSelection failure",
+ this));
+ break;
+ }
+ break;
+ }
+ default:
+ MOZ_CRASH("unexpected action type");
+ }
+
+ if (widget && !widget->Destroyed()) {
+ continue;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::FlushPendingActions(), "
+ "qutting since the mWidget has gone",
+ this));
+ break;
+ }
+ mPendingActions.Clear();
+}
+
+void TSFTextStore::MaybeFlushPendingNotifications() {
+ if (IsReadLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "putting off flushing pending notifications due to being the "
+ "document locked...",
+ this));
+ return;
+ }
+
+ if (mDeferCommittingComposition) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::CommitCompositionInternal(false)...",
+ this));
+ mDeferCommittingComposition = mDeferCancellingComposition = false;
+ CommitCompositionInternal(false);
+ } else if (mDeferCancellingComposition) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::CommitCompositionInternal(true)...",
+ this));
+ mDeferCommittingComposition = mDeferCancellingComposition = false;
+ CommitCompositionInternal(true);
+ }
+
+ if (mDeferNotifyingTSF) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "putting off flushing pending notifications due to being "
+ "dispatching events...",
+ this));
+ return;
+ }
+
+ if (mPendingDestroy) {
+ Destroy();
+ return;
+ }
+
+ if (mDestroyed) {
+ // If it's already been destroyed completely, this shouldn't notify TSF of
+ // anything anymore.
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "does nothing because this has already destroyed completely...",
+ this));
+ return;
+ }
+
+ if (!mDeferClearingContentForTSF && mContentForTSF.isSome()) {
+ mContentForTSF.reset();
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "mContentForTSF is set to `Nothing`",
+ this));
+ }
+
+ // When there is no cached content, we can sync actual contents and TSF/TIP
+ // expecting contents.
+ RefPtr<TSFTextStore> kungFuDeathGrip = this;
+ Unused << kungFuDeathGrip;
+ if (mContentForTSF.isNothing()) {
+ if (mPendingTextChangeData.IsValid()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::NotifyTSFOfTextChange()...",
+ this));
+ NotifyTSFOfTextChange();
+ }
+ if (mPendingSelectionChangeData.IsValid()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::NotifyTSFOfSelectionChange()...",
+ this));
+ NotifyTSFOfSelectionChange();
+ }
+ }
+
+ if (mHasReturnedNoLayoutError) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::NotifyTSFOfLayoutChange()...",
+ this));
+ NotifyTSFOfLayoutChange();
+ }
+}
+
+void TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME() {
+ // If we've already been destroyed, we cannot do anything.
+ if (mDestroyed) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+ "does nothing because it's already been destroyed",
+ this));
+ return;
+ }
+
+ // If we're not handling key message or we've already dispatched a keyboard
+ // event for the handling key message, we should do nothing anymore.
+ if (!sHandlingKeyMsg || sIsKeyboardEventDispatched) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+ "does nothing because not necessary to dispatch keyboard event",
+ this));
+ return;
+ }
+
+ sIsKeyboardEventDispatched = true;
+ // If the document is locked, just adding the task to dispatching an event
+ // to the queue.
+ if (IsReadLocked()) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+ "adding to dispatch a keyboard event into the queue...",
+ this));
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::Type::eKeyboardEvent;
+ memcpy(&action->mKeyMsg, sHandlingKeyMsg, sizeof(MSG));
+ return;
+ }
+
+ // Otherwise, dispatch a keyboard event.
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+ "trying to dispatch a keyboard event...",
+ this));
+ DispatchKeyboardEventAsProcessedByIME(*sHandlingKeyMsg);
+}
+
+void TSFTextStore::DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg) {
+ MOZ_ASSERT(mWidget);
+ MOZ_ASSERT(!mWidget->Destroyed());
+ MOZ_ASSERT(!mDestroyed);
+
+ ModifierKeyState modKeyState;
+ MSG msg(aMsg);
+ msg.wParam = VK_PROCESSKEY;
+ NativeKey nativeKey(mWidget, msg, modKeyState);
+ switch (aMsg.message) {
+ case WM_KEYDOWN:
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
+ "dispatching an eKeyDown event...",
+ this));
+ nativeKey.HandleKeyDownMessage();
+ break;
+ case WM_KEYUP:
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
+ "dispatching an eKeyUp event...",
+ this));
+ nativeKey.HandleKeyUpMessage();
+ break;
+ default:
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
+ "ERROR, it doesn't handle the message",
+ this));
+ break;
+ }
+}
+
+STDMETHODIMP
+TSFTextStore::GetStatus(TS_STATUS* pdcs) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetStatus(pdcs=0x%p)", this, pdcs));
+
+ if (!pdcs) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetStatus() FAILED due to null pdcs", this));
+ return E_INVALIDARG;
+ }
+ // We manage on-screen keyboard by own.
+ pdcs->dwDynamicFlags = TS_SD_INPUTPANEMANUALDISPLAYENABLE;
+ // we use a "flat" text model for TSF support so no hidden text
+ pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT;
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::QueryInsert(LONG acpTestStart, LONG acpTestEnd, ULONG cch,
+ LONG* pacpResultStart, LONG* pacpResultEnd) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::QueryInsert(acpTestStart=%ld, "
+ "acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)",
+ this, acpTestStart, acpTestEnd, cch, acpTestStart, acpTestEnd));
+
+ if (!pacpResultStart || !pacpResultEnd) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::QueryInsert() FAILED due to "
+ "the null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (acpTestStart < 0 || acpTestStart > acpTestEnd) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::QueryInsert() FAILED due to "
+ "wrong argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ // XXX need to adjust to cluster boundary
+ // Assume we are given good offsets for now
+ if (IsWin8OrLater() && mComposition.isNothing() &&
+ ((TSFPrefs::NeedToHackQueryInsertForMSTraditionalTIP() &&
+ TSFStaticSink::IsMSChangJieOrMSQuickActive()) ||
+ (TSFPrefs::NeedToHackQueryInsertForMSSimplifiedTIP() &&
+ TSFStaticSink::IsMSPinyinOrMSWubiActive()))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Warning,
+ ("0x%p TSFTextStore::QueryInsert() WARNING using different "
+ "result for the TIP",
+ this));
+ // Chinese TIPs of Microsoft assume that QueryInsert() returns selected
+ // range which should be removed.
+ *pacpResultStart = acpTestStart;
+ *pacpResultEnd = acpTestEnd;
+ } else {
+ *pacpResultStart = acpTestStart;
+ *pacpResultEnd = acpTestStart + cch;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::QueryInsert() succeeded: "
+ "*pacpResultStart=%ld, *pacpResultEnd=%ld)",
+ this, *pacpResultStart, *pacpResultEnd));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetSelection(ULONG ulIndex, ULONG ulCount,
+ TS_SELECTION_ACP* pSelection, ULONG* pcFetched) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetSelection(ulIndex=%lu, ulCount=%lu, "
+ "pSelection=0x%p, pcFetched=0x%p)",
+ this, ulIndex, ulCount, pSelection, pcFetched));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to not locked", this));
+ return TS_E_NOLOCK;
+ }
+ if (!ulCount || !pSelection || !pcFetched) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ *pcFetched = 0;
+
+ if (ulIndex != static_cast<ULONG>(TS_DEFAULT_SELECTION) && ulIndex != 0) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to "
+ "unsupported selection",
+ this));
+ return TS_E_NOSELECTION;
+ }
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ if (DoNotReturnErrorFromGetSelection()) {
+ TS_SELECTION_ACP acp;
+ acp.acpStart = acp.acpEnd = 0;
+ acp.style.ase = TS_AE_START;
+ acp.style.fInterimChar = FALSE;
+ *pSelection = acp;
+ *pcFetched = 1;
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetSelection() returns fake selection range "
+ "for avoiding a crash in TSF, *pSelection=%s",
+ this, mozilla::ToString(*pSelection).c_str()));
+ return S_OK;
+ }
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ *pSelection = selectionForTSF->ACPRef();
+ *pcFetched = 1;
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetSelection() succeeded, *pSelection=%s",
+ this, mozilla::ToString(*pSelection).c_str()));
+ return S_OK;
+}
+
+// static
+bool TSFTextStore::DoNotReturnErrorFromGetSelection() {
+ // There is a crash bug of TSF if we return error from GetSelection().
+ // That was introduced in Anniversary Update (build 14393, see bug 1312302)
+ // TODO: We should avoid to run this hack on fixed builds. When we get
+ // exact build number, we should get back here.
+ static bool sTSFMayCrashIfGetSelectionReturnsError =
+ IsWindows10BuildOrLater(14393);
+ return sTSFMayCrashIfGetSelectionReturnsError;
+}
+
+Maybe<TSFTextStore::Content>& TSFTextStore::ContentForTSF() {
+ // This should be called when the document is locked or the content hasn't
+ // been abandoned yet.
+ if (NS_WARN_IF(!IsReadLocked() && mContentForTSF.isNothing())) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
+ "called wrong timing, IsReadLocked()=%s, mContentForTSF=Nothing",
+ this, GetBoolName(IsReadLocked())));
+ return mContentForTSF;
+ }
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
+ "SelectionForTSF() failure",
+ this));
+ mContentForTSF.reset();
+ return mContentForTSF;
+ }
+
+ if (mContentForTSF.isNothing()) {
+ nsString text; // Don't use auto string for avoiding to copy long string.
+ if (NS_WARN_IF(!GetCurrentText(text))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
+ "GetCurrentText() failure",
+ this));
+ return mContentForTSF;
+ }
+
+ mContentForTSF.emplace(*this, text);
+ // Basically, the cached content which is expected by TSF/TIP should be
+ // cleared after active composition is committed or the document lock is
+ // unlocked. However, in e10s mode, content will be modified
+ // asynchronously. In such case, mDeferClearingContentForTSF may be
+ // true until whole dispatched events are handled by the focused editor.
+ mDeferClearingContentForTSF = false;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::ContentForTSF(): mContentForTSF=%s", this,
+ mozilla::ToString(mContentForTSF).c_str()));
+
+ return mContentForTSF;
+}
+
+bool TSFTextStore::CanAccessActualContentDirectly() const {
+ if (mContentForTSF.isNothing() || mSelectionForTSF.isNothing()) {
+ return true;
+ }
+
+ // If the cached content has been changed by something except composition,
+ // the content cache may be different from actual content.
+ if (mPendingTextChangeData.IsValid() &&
+ !mPendingTextChangeData.mCausedOnlyByComposition) {
+ return false;
+ }
+
+ // If the cached selection isn't changed, cached content and actual content
+ // should be same.
+ if (!mPendingSelectionChangeData.IsValid()) {
+ return true;
+ }
+
+ return mSelectionForTSF->EqualsExceptDirection(mPendingSelectionChangeData);
+}
+
+bool TSFTextStore::GetCurrentText(nsAString& aTextContent) {
+ if (mContentForTSF.isSome()) {
+ aTextContent = mContentForTSF->TextRef();
+ return true;
+ }
+
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(mWidget && !mWidget->Destroyed());
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetCurrentText(): "
+ "retrieving text from the content...",
+ this));
+
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
+ mWidget);
+ queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
+ mWidget->InitEvent(queryTextContentEvent);
+ DispatchEvent(queryTextContentEvent);
+ if (NS_WARN_IF(queryTextContentEvent.Failed())) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetCurrentText(), FAILED, due to "
+ "eQueryTextContent failure",
+ this));
+ aTextContent.Truncate();
+ return false;
+ }
+
+ aTextContent = queryTextContentEvent.mReply->DataRef();
+ return true;
+}
+
+Maybe<TSFTextStore::Selection>& TSFTextStore::SelectionForTSF() {
+ if (mSelectionForTSF.isNothing()) {
+ MOZ_ASSERT(!mDestroyed);
+ // If the window has never been available, we should crash since working
+ // with broken values may make TIP confused.
+ if (!mWidget || mWidget->Destroyed()) {
+ MOZ_ASSERT_UNREACHABLE("There should be non-destroyed widget");
+ }
+
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ mWidget);
+ mWidget->InitEvent(querySelectedTextEvent);
+ DispatchEvent(querySelectedTextEvent);
+ if (NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection())) {
+ return mSelectionForTSF;
+ }
+ MOZ_ASSERT(querySelectedTextEvent.mReply->mOffsetAndData.isSome());
+ mSelectionForTSF =
+ Some(Selection(querySelectedTextEvent.mReply->StartOffset(),
+ querySelectedTextEvent.mReply->DataLength(),
+ querySelectedTextEvent.mReply->mReversed,
+ querySelectedTextEvent.mReply->WritingModeRef()));
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::SelectionForTSF() succeeded, "
+ "mSelectionForTSF=%s",
+ this, ToString(mSelectionForTSF).c_str()));
+
+ return mSelectionForTSF;
+}
+
+static HRESULT GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength) {
+ RefPtr<ITfRangeACP> rangeACP;
+ aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP));
+ NS_ENSURE_TRUE(rangeACP, E_FAIL);
+ return rangeACP->GetExtent(aStart, aLength);
+}
+
+static TextRangeType GetGeckoSelectionValue(TF_DISPLAYATTRIBUTE& aDisplayAttr) {
+ switch (aDisplayAttr.bAttr) {
+ case TF_ATTR_TARGET_CONVERTED:
+ return TextRangeType::eSelectedClause;
+ case TF_ATTR_CONVERTED:
+ return TextRangeType::eConvertedClause;
+ case TF_ATTR_TARGET_NOTCONVERTED:
+ return TextRangeType::eSelectedRawClause;
+ default:
+ return TextRangeType::eRawClause;
+ }
+}
+
+HRESULT
+TSFTextStore::GetDisplayAttribute(ITfProperty* aAttrProperty, ITfRange* aRange,
+ TF_DISPLAYATTRIBUTE* aResult) {
+ NS_ENSURE_TRUE(aAttrProperty, E_FAIL);
+ NS_ENSURE_TRUE(aRange, E_FAIL);
+ NS_ENSURE_TRUE(aResult, E_FAIL);
+
+ HRESULT hr;
+
+ if (MOZ_LOG_TEST(sTextStoreLog, LogLevel::Debug)) {
+ LONG start = 0, length = 0;
+ hr = GetRangeExtent(aRange, &start, &length);
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetDisplayAttribute(): "
+ "GetDisplayAttribute range=%ld-%ld (hr=%s)",
+ this, start - mComposition->StartOffset(),
+ start - mComposition->StartOffset() + length,
+ GetCommonReturnValueName(hr)));
+ }
+
+ VARIANT propValue;
+ ::VariantInit(&propValue);
+ hr = aAttrProperty->GetValue(TfEditCookie(mEditCookie), aRange, &propValue);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfProperty::GetValue() failed",
+ this));
+ return hr;
+ }
+ if (VT_I4 != propValue.vt) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfProperty::GetValue() returns non-VT_I4 value",
+ this));
+ ::VariantClear(&propValue);
+ return E_FAIL;
+ }
+
+ RefPtr<ITfCategoryMgr> categoryMgr = GetCategoryMgr();
+ if (NS_WARN_IF(!categoryMgr)) {
+ return E_FAIL;
+ }
+ GUID guid;
+ hr = categoryMgr->GetGUID(DWORD(propValue.lVal), &guid);
+ ::VariantClear(&propValue);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfCategoryMgr::GetGUID() failed",
+ this));
+ return hr;
+ }
+
+ RefPtr<ITfDisplayAttributeMgr> displayAttrMgr = GetDisplayAttributeMgr();
+ if (NS_WARN_IF(!displayAttrMgr)) {
+ return E_FAIL;
+ }
+ RefPtr<ITfDisplayAttributeInfo> info;
+ hr = displayAttrMgr->GetDisplayAttributeInfo(guid, getter_AddRefs(info),
+ nullptr);
+ if (FAILED(hr) || !info) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfDisplayAttributeMgr::GetDisplayAttributeInfo() failed",
+ this));
+ return hr;
+ }
+
+ hr = info->GetAttributeInfo(aResult);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfDisplayAttributeInfo::GetAttributeInfo() failed",
+ this));
+ return hr;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetDisplayAttribute() succeeded: "
+ "Result={ %s }",
+ this, GetDisplayAttrStr(*aResult).get()));
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::RestartCompositionIfNecessary(ITfRange* aRangeNew) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary("
+ "aRangeNew=0x%p), mComposition=%s",
+ this, aRangeNew, ToString(mComposition).c_str()));
+
+ if (mComposition.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
+ "due to no composition view",
+ this));
+ return E_FAIL;
+ }
+
+ HRESULT hr;
+ RefPtr<ITfCompositionView> pComposition(mComposition->GetView());
+ RefPtr<ITfRange> composingRange(aRangeNew);
+ if (!composingRange) {
+ hr = pComposition->GetRange(getter_AddRefs(composingRange));
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
+ "FAILED due to pComposition->GetRange() failure",
+ this));
+ return hr;
+ }
+ }
+
+ // Get starting offset of the composition
+ LONG compStart = 0, compLength = 0;
+ hr = GetRangeExtent(composingRange, &compStart, &compLength);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
+ "due to GetRangeExtent() failure",
+ this));
+ return hr;
+ }
+
+ if (mComposition->StartOffset() == compStart &&
+ mComposition->Length() == compLength) {
+ return S_OK;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary(), "
+ "restaring composition because of compostion range is changed "
+ "(range=%ld-%ld, mComposition=%s)",
+ this, compStart, compStart + compLength,
+ ToString(mComposition).c_str()));
+
+ // If the queried composition length is different from the length
+ // of our composition string, OnUpdateComposition is being called
+ // because a part of the original composition was committed.
+ hr = RestartComposition(*mComposition, pComposition, composingRange);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
+ "FAILED due to RestartComposition() failure",
+ this));
+ return hr;
+ }
+
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() succeeded", this));
+ return S_OK;
+}
+
+HRESULT TSFTextStore::RestartComposition(Composition& aCurrentComposition,
+ ITfCompositionView* aCompositionView,
+ ITfRange* aNewRange) {
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+
+ LONG newStart, newLength;
+ HRESULT hr = GetRangeExtent(aNewRange, &newStart, &newLength);
+ LONG newEnd = newStart + newLength;
+
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartComposition() FAILED "
+ "due to SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartComposition() FAILED "
+ "due to GetRangeExtent() failure",
+ this));
+ return hr;
+ }
+
+ // If the new range has no overlap with the crrent range, we just commit
+ // the composition and restart new composition with the new range but
+ // current selection range should be preserved.
+ if (newStart >= aCurrentComposition.EndOffset() ||
+ newEnd <= aCurrentComposition.StartOffset()) {
+ RecordCompositionEndAction();
+ RecordCompositionStartAction(aCompositionView, newStart, newLength, true);
+ return S_OK;
+ }
+
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartComposition(aCompositionView=0x%p, "
+ "aNewRange=0x%p { newStart=%d, newLength=%d }), aCurrentComposition=%s, "
+ "selectionForTSF=%s",
+ this, aCompositionView, aNewRange, newStart, newLength,
+ ToString(aCurrentComposition).c_str(),
+ ToString(selectionForTSF).c_str()));
+
+ // If the new range has an overlap with the current one, we should not commit
+ // the whole current range to avoid creating an odd undo transaction.
+ // I.e., the overlapped range which is being composed should not appear in
+ // undo transaction.
+
+ // Backup current composition data and selection data.
+ Composition oldComposition = aCurrentComposition;
+ Selection oldSelection = *selectionForTSF;
+
+ // Commit only the part of composition.
+ LONG keepComposingStartOffset =
+ std::max(oldComposition.StartOffset(), newStart);
+ LONG keepComposingEndOffset = std::min(oldComposition.EndOffset(), newEnd);
+ MOZ_ASSERT(
+ keepComposingStartOffset <= keepComposingEndOffset,
+ "Why keepComposingEndOffset is smaller than keepComposingStartOffset?");
+ LONG keepComposingLength = keepComposingEndOffset - keepComposingStartOffset;
+ // Remove the overlapped part from the commit string.
+ nsAutoString commitString(oldComposition.DataRef());
+ commitString.Cut(keepComposingStartOffset - oldComposition.StartOffset(),
+ keepComposingLength);
+ // Update the composition string.
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartComposition() FAILED "
+ "due to ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ contentForTSF->ReplaceTextWith(oldComposition.StartOffset(),
+ oldComposition.Length(), commitString);
+ MOZ_ASSERT(mComposition.isSome());
+ // Record a compositionupdate action for commit the part of composing string.
+ PendingAction* action = LastOrNewPendingCompositionUpdate();
+ if (mComposition.isSome()) {
+ action->mData = mComposition->DataRef();
+ }
+ action->mRanges->Clear();
+ // Note that we shouldn't append ranges when composition string
+ // is empty because it may cause TextComposition confused.
+ if (!action->mData.IsEmpty()) {
+ TextRange caretRange;
+ caretRange.mStartOffset = caretRange.mEndOffset = static_cast<uint32_t>(
+ oldComposition.StartOffset() + commitString.Length());
+ caretRange.mRangeType = TextRangeType::eCaret;
+ action->mRanges->AppendElement(caretRange);
+ }
+ action->mIncomplete = false;
+
+ // Record compositionend action.
+ RecordCompositionEndAction();
+
+ // Record compositionstart action only with the new start since this method
+ // hasn't restored composing string yet.
+ RecordCompositionStartAction(aCompositionView, newStart, 0, false);
+
+ // Restore the latest text content and selection.
+ contentForTSF->ReplaceSelectedTextWith(nsDependentSubstring(
+ oldComposition.DataRef(),
+ keepComposingStartOffset - oldComposition.StartOffset(),
+ keepComposingLength));
+ selectionForTSF = Some(oldSelection);
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartComposition() succeeded, "
+ "mComposition=%s, selectionForTSF=%s",
+ this, ToString(mComposition).c_str(),
+ ToString(selectionForTSF).c_str()));
+
+ return S_OK;
+}
+
+static bool GetColor(const TF_DA_COLOR& aTSFColor, nscolor& aResult) {
+ switch (aTSFColor.type) {
+ case TF_CT_SYSCOLOR: {
+ DWORD sysColor = ::GetSysColor(aTSFColor.nIndex);
+ aResult =
+ NS_RGB(GetRValue(sysColor), GetGValue(sysColor), GetBValue(sysColor));
+ return true;
+ }
+ case TF_CT_COLORREF:
+ aResult = NS_RGB(GetRValue(aTSFColor.cr), GetGValue(aTSFColor.cr),
+ GetBValue(aTSFColor.cr));
+ return true;
+ case TF_CT_NONE:
+ default:
+ return false;
+ }
+}
+
+static bool GetLineStyle(TF_DA_LINESTYLE aTSFLineStyle,
+ TextRangeStyle::LineStyle& aTextRangeLineStyle) {
+ switch (aTSFLineStyle) {
+ case TF_LS_NONE:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::None;
+ return true;
+ case TF_LS_SOLID:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::Solid;
+ return true;
+ case TF_LS_DOT:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::Dotted;
+ return true;
+ case TF_LS_DASH:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::Dashed;
+ return true;
+ case TF_LS_SQUIGGLE:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::Wavy;
+ return true;
+ default:
+ return false;
+ }
+}
+
+HRESULT
+TSFTextStore::RecordCompositionUpdateAction() {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction(), mComposition=%s",
+ this, ToString(mComposition).c_str()));
+
+ if (mComposition.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to no composition view",
+ this));
+ return E_FAIL;
+ }
+
+ // Getting display attributes is *really* complicated!
+ // We first get the context and the property objects to query for
+ // attributes, but since a big range can have a variety of values for
+ // the attribute, we have to find out all the ranges that have distinct
+ // attribute values. Then we query for what the value represents through
+ // the display attribute manager and translate that to TextRange to be
+ // sent in eCompositionChange
+
+ RefPtr<ITfProperty> attrPropetry;
+ HRESULT hr =
+ mContext->GetProperty(GUID_PROP_ATTRIBUTE, getter_AddRefs(attrPropetry));
+ if (FAILED(hr) || !attrPropetry) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to mContext->GetProperty() failure",
+ this));
+ return FAILED(hr) ? hr : E_FAIL;
+ }
+
+ RefPtr<ITfRange> composingRange;
+ hr = mComposition->GetView()->GetRange(getter_AddRefs(composingRange));
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "FAILED due to mComposition->GetView()->GetRange() failure",
+ this));
+ return hr;
+ }
+
+ RefPtr<IEnumTfRanges> enumRanges;
+ hr = attrPropetry->EnumRanges(TfEditCookie(mEditCookie),
+ getter_AddRefs(enumRanges), composingRange);
+ if (FAILED(hr) || !enumRanges) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to attrPropetry->EnumRanges() failure",
+ this));
+ return FAILED(hr) ? hr : E_FAIL;
+ }
+
+ // First, put the log of content and selection here.
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ PendingAction* action = LastOrNewPendingCompositionUpdate();
+ action->mData = mComposition->DataRef();
+ // The ranges might already have been initialized, however, if this is
+ // called again, that means we need to overwrite the ranges with current
+ // information.
+ action->mRanges->Clear();
+
+ // Note that we shouldn't append ranges when composition string
+ // is empty because it may cause TextComposition confused.
+ if (!action->mData.IsEmpty()) {
+ TextRange newRange;
+ // No matter if we have display attribute info or not,
+ // we always pass in at least one range to eCompositionChange
+ newRange.mStartOffset = 0;
+ newRange.mEndOffset = action->mData.Length();
+ newRange.mRangeType = TextRangeType::eRawClause;
+ action->mRanges->AppendElement(newRange);
+
+ RefPtr<ITfRange> range;
+ while (enumRanges->Next(1, getter_AddRefs(range), nullptr) == S_OK) {
+ if (NS_WARN_IF(!range)) {
+ break;
+ }
+
+ LONG rangeStart = 0, rangeLength = 0;
+ if (FAILED(GetRangeExtent(range, &rangeStart, &rangeLength))) {
+ continue;
+ }
+ // The range may include out of composition string. We should ignore
+ // outside of the composition string.
+ LONG start = std::min(std::max(rangeStart, mComposition->StartOffset()),
+ mComposition->EndOffset());
+ LONG end = std::max(
+ std::min(rangeStart + rangeLength, mComposition->EndOffset()),
+ mComposition->StartOffset());
+ LONG length = end - start;
+ if (length < 0) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "ignores invalid range (%d-%d)",
+ this, rangeStart - mComposition->StartOffset(),
+ rangeStart - mComposition->StartOffset() + rangeLength));
+ continue;
+ }
+ if (!length) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "ignores a range due to outside of the composition or empty "
+ "(%d-%d)",
+ this, rangeStart - mComposition->StartOffset(),
+ rangeStart - mComposition->StartOffset() + rangeLength));
+ continue;
+ }
+
+ TextRange newRange;
+ newRange.mStartOffset =
+ static_cast<uint32_t>(start - mComposition->StartOffset());
+ // The end of the last range in the array is
+ // always kept at the end of composition
+ newRange.mEndOffset = mComposition->Length();
+
+ TF_DISPLAYATTRIBUTE attr;
+ hr = GetDisplayAttribute(attrPropetry, range, &attr);
+ if (FAILED(hr)) {
+ newRange.mRangeType = TextRangeType::eRawClause;
+ } else {
+ newRange.mRangeType = GetGeckoSelectionValue(attr);
+ if (GetColor(attr.crText, newRange.mRangeStyle.mForegroundColor)) {
+ newRange.mRangeStyle.mDefinedStyles |=
+ TextRangeStyle::DEFINED_FOREGROUND_COLOR;
+ }
+ if (GetColor(attr.crBk, newRange.mRangeStyle.mBackgroundColor)) {
+ newRange.mRangeStyle.mDefinedStyles |=
+ TextRangeStyle::DEFINED_BACKGROUND_COLOR;
+ }
+ if (GetColor(attr.crLine, newRange.mRangeStyle.mUnderlineColor)) {
+ newRange.mRangeStyle.mDefinedStyles |=
+ TextRangeStyle::DEFINED_UNDERLINE_COLOR;
+ }
+ if (GetLineStyle(attr.lsStyle, newRange.mRangeStyle.mLineStyle)) {
+ newRange.mRangeStyle.mDefinedStyles |=
+ TextRangeStyle::DEFINED_LINESTYLE;
+ newRange.mRangeStyle.mIsBoldLine = attr.fBoldLine != 0;
+ }
+ }
+
+ TextRange& lastRange = action->mRanges->LastElement();
+ if (lastRange.mStartOffset == newRange.mStartOffset) {
+ // Replace range if last range is the same as this one
+ // So that ranges don't overlap and confuse the editor
+ lastRange = newRange;
+ } else {
+ lastRange.mEndOffset = newRange.mStartOffset;
+ action->mRanges->AppendElement(newRange);
+ }
+ }
+
+ // We need to hack for Korean Input System which is Korean standard TIP.
+ // It sets no change style to IME selection (the selection is always only
+ // one). So, the composition string looks like normal (or committed)
+ // string. At this time, current selection range is same as the
+ // composition string range. Other applications set a wide caret which
+ // covers the composition string, however, Gecko doesn't support the wide
+ // caret drawing now (Gecko doesn't support XOR drawing), unfortunately.
+ // For now, we should change the range style to undefined.
+ if (!selectionForTSF->Collapsed() && action->mRanges->Length() == 1) {
+ TextRange& range = action->mRanges->ElementAt(0);
+ LONG start = selectionForTSF->MinOffset();
+ LONG end = selectionForTSF->MaxOffset();
+ if (range.mStartOffset == start - mComposition->StartOffset() &&
+ range.mEndOffset == end - mComposition->StartOffset() &&
+ range.mRangeStyle.IsNoChangeStyle()) {
+ range.mRangeStyle.Clear();
+ // The looks of selected type is better than others.
+ range.mRangeType = TextRangeType::eSelectedRawClause;
+ }
+ }
+
+ // The caret position has to be collapsed.
+ uint32_t caretPosition = static_cast<uint32_t>(
+ selectionForTSF->MaxOffset() - mComposition->StartOffset());
+
+ // If caret is in the target clause and it doesn't have specific style,
+ // the target clause will be painted as normal selection range. Since
+ // caret shouldn't be in selection range on Windows, we shouldn't append
+ // caret range in such case.
+ const TextRange* targetClause = action->mRanges->GetTargetClause();
+ if (!targetClause || targetClause->mRangeStyle.IsDefined() ||
+ caretPosition < targetClause->mStartOffset ||
+ caretPosition > targetClause->mEndOffset) {
+ TextRange caretRange;
+ caretRange.mStartOffset = caretRange.mEndOffset = caretPosition;
+ caretRange.mRangeType = TextRangeType::eCaret;
+ action->mRanges->AppendElement(caretRange);
+ }
+ }
+
+ action->mIncomplete = false;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "succeeded",
+ this));
+
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection,
+ bool aDispatchCompositionChangeEvent) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::SetSelectionInternal(pSelection=%s, "
+ "aDispatchCompositionChangeEvent=%s), mComposition=%s",
+ this, pSelection ? mozilla::ToString(*pSelection).c_str() : "nullptr",
+ GetBoolName(aDispatchCompositionChangeEvent),
+ ToString(mComposition).c_str()));
+
+ MOZ_ASSERT(IsReadWriteLocked());
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return E_FAIL;
+ }
+
+ // If actually the range is not changing, we should do nothing.
+ // Perhaps, we can ignore the difference change because it must not be
+ // important for following edit.
+ if (selectionForTSF->EqualsExceptDirection(*pSelection)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Warning,
+ ("0x%p TSFTextStore::SetSelectionInternal() Succeeded but "
+ "did nothing because the selection range isn't changing",
+ this));
+ selectionForTSF->SetSelection(*pSelection);
+ return S_OK;
+ }
+
+ if (mComposition.isSome()) {
+ if (aDispatchCompositionChangeEvent) {
+ HRESULT hr = RestartCompositionIfNecessary();
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "RestartCompositionIfNecessary() failure",
+ this));
+ return hr;
+ }
+ }
+ if (pSelection->acpStart < mComposition->StartOffset() ||
+ pSelection->acpEnd > mComposition->EndOffset()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "the selection being out of the composition string",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+ // Emulate selection during compositions
+ selectionForTSF->SetSelection(*pSelection);
+ if (aDispatchCompositionChangeEvent) {
+ HRESULT hr = RecordCompositionUpdateAction();
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "RecordCompositionUpdateAction() failure",
+ this));
+ return hr;
+ }
+ }
+ return S_OK;
+ }
+
+ TS_SELECTION_ACP selectionInContent(*pSelection);
+
+ // If mContentForTSF caches old contents which is now different from
+ // actual contents, we need some complicated hack here...
+ // Note that this hack assumes that this is used for reconversion.
+ if (mContentForTSF.isSome() && mPendingTextChangeData.IsValid() &&
+ !mPendingTextChangeData.mCausedOnlyByComposition) {
+ uint32_t startOffset = static_cast<uint32_t>(selectionInContent.acpStart);
+ uint32_t endOffset = static_cast<uint32_t>(selectionInContent.acpEnd);
+ if (mPendingTextChangeData.mStartOffset >= endOffset) {
+ // Setting selection before any changed ranges is fine.
+ } else if (mPendingTextChangeData.mRemovedEndOffset <= startOffset) {
+ // Setting selection after removed range is fine with following
+ // adjustment.
+ selectionInContent.acpStart += mPendingTextChangeData.Difference();
+ selectionInContent.acpEnd += mPendingTextChangeData.Difference();
+ } else if (startOffset == endOffset) {
+ // Moving caret position may be fine in most cases even if the insertion
+ // point has already gone but in this case, composition will be inserted
+ // to unexpected position, though.
+ // It seems that moving caret into middle of the new text is odd.
+ // Perhaps, end of it is expected by users in most cases.
+ selectionInContent.acpStart = mPendingTextChangeData.mAddedEndOffset;
+ selectionInContent.acpEnd = selectionInContent.acpStart;
+ } else {
+ // Otherwise, i.e., setting range has already gone, we cannot set
+ // selection properly.
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "there is unknown content change",
+ this));
+ return E_FAIL;
+ }
+ }
+
+ CompleteLastActionIfStillIncomplete();
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::Type::eSetSelection;
+ action->mSelectionStart = selectionInContent.acpStart;
+ action->mSelectionLength =
+ selectionInContent.acpEnd - selectionInContent.acpStart;
+ action->mSelectionReversed = (selectionInContent.style.ase == TS_AE_START);
+
+ // Use TSF specified selection for updating mSelectionForTSF.
+ selectionForTSF->SetSelection(*pSelection);
+
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::SetSelection(ULONG ulCount, const TS_SELECTION_ACP* pSelection) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::SetSelection(ulCount=%lu, pSelection=%s }), "
+ "mComposition=%s",
+ this, ulCount,
+ pSelection ? mozilla::ToString(pSelection).c_str() : "nullptr",
+ ToString(mComposition).c_str()));
+
+ if (!IsReadWriteLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "not locked (read-write)",
+ this));
+ return TS_E_NOLOCK;
+ }
+ if (ulCount != 1) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "trying setting multiple selection",
+ this));
+ return E_INVALIDARG;
+ }
+ if (!pSelection) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ HRESULT hr = SetSelectionInternal(pSelection, true);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "SetSelectionInternal() failure",
+ this));
+ } else {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::SetSelection() succeeded", this));
+ }
+ return hr;
+}
+
+STDMETHODIMP
+TSFTextStore::GetText(LONG acpStart, LONG acpEnd, WCHAR* pchPlain,
+ ULONG cchPlainReq, ULONG* pcchPlainOut,
+ TS_RUNINFO* prgRunInfo, ULONG ulRunInfoReq,
+ ULONG* pulRunInfoOut, LONG* pacpNext) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetText(acpStart=%ld, acpEnd=%ld, pchPlain=0x%p, "
+ "cchPlainReq=%lu, pcchPlainOut=0x%p, prgRunInfo=0x%p, ulRunInfoReq=%lu, "
+ "pulRunInfoOut=0x%p, pacpNext=0x%p), mComposition=%s",
+ this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut, prgRunInfo,
+ ulRunInfoReq, pulRunInfoOut, pacpNext, ToString(mComposition).c_str()));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pcchPlainOut || (!pchPlain && !prgRunInfo) ||
+ !cchPlainReq != !pchPlain || !ulRunInfoReq != !prgRunInfo) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "invalid argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (acpStart < 0 || acpEnd < -1 || (acpEnd != -1 && acpStart > acpEnd)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "invalid position",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+
+ // Making sure to null-terminate string just to be on the safe side
+ *pcchPlainOut = 0;
+ if (pchPlain && cchPlainReq) *pchPlain = 0;
+ if (pulRunInfoOut) *pulRunInfoOut = 0;
+ if (pacpNext) *pacpNext = acpStart;
+ if (prgRunInfo && ulRunInfoReq) {
+ prgRunInfo->uCount = 0;
+ prgRunInfo->type = TS_RT_PLAIN;
+ }
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ if (contentForTSF->TextRef().Length() < static_cast<uint32_t>(acpStart)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "acpStart is larger offset than the actual text length",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+ if (acpEnd != -1 &&
+ contentForTSF->TextRef().Length() < static_cast<uint32_t>(acpEnd)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "acpEnd is larger offset than the actual text length",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+ uint32_t length = (acpEnd == -1) ? contentForTSF->TextRef().Length() -
+ static_cast<uint32_t>(acpStart)
+ : static_cast<uint32_t>(acpEnd - acpStart);
+ if (cchPlainReq && cchPlainReq - 1 < length) {
+ length = cchPlainReq - 1;
+ }
+ if (length) {
+ if (pchPlain && cchPlainReq) {
+ const char16_t* startChar =
+ contentForTSF->TextRef().BeginReading() + acpStart;
+ memcpy(pchPlain, startChar, length * sizeof(*pchPlain));
+ pchPlain[length] = 0;
+ *pcchPlainOut = length;
+ }
+ if (prgRunInfo && ulRunInfoReq) {
+ prgRunInfo->uCount = length;
+ prgRunInfo->type = TS_RT_PLAIN;
+ if (pulRunInfoOut) *pulRunInfoOut = 1;
+ }
+ if (pacpNext) *pacpNext = acpStart + length;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetText() succeeded: pcchPlainOut=0x%p, "
+ "*prgRunInfo={ uCount=%lu, type=%s }, *pulRunInfoOut=%lu, "
+ "*pacpNext=%ld)",
+ this, pcchPlainOut, prgRunInfo ? prgRunInfo->uCount : 0,
+ prgRunInfo ? GetTextRunTypeName(prgRunInfo->type) : "N/A",
+ pulRunInfoOut ? *pulRunInfoOut : 0, pacpNext ? *pacpNext : 0));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd,
+ const WCHAR* pchText, ULONG cch, TS_TEXTCHANGE* pChange) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::SetText(dwFlags=%s, acpStart=%ld, acpEnd=%ld, "
+ "pchText=0x%p \"%s\", cch=%lu, pChange=0x%p), mComposition=%s",
+ this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" : "not-specified",
+ acpStart, acpEnd, pchText,
+ pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "", cch,
+ pChange, ToString(mComposition).c_str()));
+
+ // Per SDK documentation, and since we don't have better
+ // ways to do this, this method acts as a helper to
+ // call SetSelection followed by InsertTextAtSelection
+ if (!IsReadWriteLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetText() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ TS_SELECTION_ACP selection;
+ selection.acpStart = acpStart;
+ selection.acpEnd = acpEnd;
+ selection.style.ase = TS_AE_END;
+ selection.style.fInterimChar = 0;
+ // Set selection to desired range
+ HRESULT hr = SetSelectionInternal(&selection);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetText() FAILED due to "
+ "SetSelectionInternal() failure",
+ this));
+ return hr;
+ }
+ // Replace just selected text
+ if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
+ pChange)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetText() FAILED due to "
+ "InsertTextAtSelectionInternal() failure",
+ this));
+ return E_FAIL;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::SetText() succeeded: pChange={ "
+ "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
+ this, pChange ? pChange->acpStart : 0,
+ pChange ? pChange->acpOldEnd : 0, pChange ? pChange->acpNewEnd : 0));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetFormattedText(LONG acpStart, LONG acpEnd,
+ IDataObject** ppDataObject) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetFormattedText() called "
+ "but not supported (E_NOTIMPL)",
+ this));
+
+ // no support for formatted text
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP
+TSFTextStore::GetEmbedded(LONG acpPos, REFGUID rguidService, REFIID riid,
+ IUnknown** ppunk) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetEmbedded() called "
+ "but not supported (E_NOTIMPL)",
+ this));
+
+ // embedded objects are not supported
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP
+TSFTextStore::QueryInsertEmbedded(const GUID* pguidService,
+ const FORMATETC* pFormatEtc,
+ BOOL* pfInsertable) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::QueryInsertEmbedded() called "
+ "but not supported, *pfInsertable=FALSE (S_OK)",
+ this));
+
+ // embedded objects are not supported
+ *pfInsertable = FALSE;
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::InsertEmbedded(DWORD dwFlags, LONG acpStart, LONG acpEnd,
+ IDataObject* pDataObject, TS_TEXTCHANGE* pChange) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::InsertEmbedded() called "
+ "but not supported (E_NOTIMPL)",
+ this));
+
+ // embedded objects are not supported
+ return E_NOTIMPL;
+}
+
+// static
+bool TSFTextStore::ShouldSetInputScopeOfURLBarToDefault() {
+ // FYI: Google Japanese Input may be an IMM-IME. If it's installed on
+ // Win7, it's always IMM-IME. Otherwise, basically, it's a TIP.
+ // However, if it's installed on Win7 and has not been updated yet
+ // after the OS is upgraded to Win8 or later, it's still an IMM-IME.
+ // Therefore, we also need to check with IMMHandler here.
+ if (!TSFPrefs::ShouldSetInputScopeOfURLBarToDefault()) {
+ return false;
+ }
+
+ if (IMMHandler::IsGoogleJapaneseInputActive()) {
+ return true;
+ }
+
+ switch (TSFStaticSink::ActiveTIP()) {
+ case TextInputProcessorID::eMicrosoftIMEForJapanese:
+ case TextInputProcessorID::eGoogleJapaneseInput:
+ case TextInputProcessorID::eMicrosoftBopomofo:
+ case TextInputProcessorID::eMicrosoftChangJie:
+ case TextInputProcessorID::eMicrosoftPhonetic:
+ case TextInputProcessorID::eMicrosoftQuick:
+ case TextInputProcessorID::eMicrosoftNewChangJie:
+ case TextInputProcessorID::eMicrosoftNewPhonetic:
+ case TextInputProcessorID::eMicrosoftNewQuick:
+ case TextInputProcessorID::eMicrosoftPinyin:
+ case TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle:
+ case TextInputProcessorID::eMicrosoftOldHangul:
+ case TextInputProcessorID::eMicrosoftWubi:
+ return true;
+ case TextInputProcessorID::eMicrosoftIMEForKorean:
+ return IsWin8OrLater();
+ default:
+ return false;
+ }
+}
+
+void TSFTextStore::SetInputScope(const nsString& aHTMLInputType,
+ const nsString& aHTMLInputInputMode,
+ bool aInPrivateBrowsing) {
+ mInputScopes.Clear();
+
+ // IME may refer only first input scope, but we will append inputmode's
+ // input scopes too like Chrome since IME may refer it.
+ IMEHandler::AppendInputScopeFromType(aHTMLInputType, mInputScopes);
+ IMEHandler::AppendInputScopeFromInputmode(aHTMLInputInputMode, mInputScopes);
+
+ if (aInPrivateBrowsing) {
+ mInputScopes.AppendElement(IS_PRIVATE);
+ }
+}
+
+int32_t TSFTextStore::GetRequestedAttrIndex(const TS_ATTRID& aAttrID) {
+ if (IsEqualGUID(aAttrID, GUID_PROP_INPUTSCOPE)) {
+ return eInputScope;
+ }
+ if (IsEqualGUID(aAttrID, TSATTRID_Text_VerticalWriting)) {
+ return eTextVerticalWriting;
+ }
+ if (IsEqualGUID(aAttrID, TSATTRID_Text_Orientation)) {
+ return eTextOrientation;
+ }
+ return eNotSupported;
+}
+
+TS_ATTRID
+TSFTextStore::GetAttrID(int32_t aIndex) {
+ switch (aIndex) {
+ case eInputScope:
+ return GUID_PROP_INPUTSCOPE;
+ case eTextVerticalWriting:
+ return TSATTRID_Text_VerticalWriting;
+ case eTextOrientation:
+ return TSATTRID_Text_Orientation;
+ default:
+ MOZ_CRASH("Invalid index? Or not implemented yet?");
+ return GUID_NULL;
+ }
+}
+
+HRESULT
+TSFTextStore::HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount,
+ const TS_ATTRID* aFilterAttrs) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::HandleRequestAttrs(aFlags=%s, "
+ "aFilterCount=%u)",
+ this, GetFindFlagName(aFlags).get(), aFilterCount));
+
+ // This is a little weird! RequestSupportedAttrs gives us advanced notice
+ // of a support query via RetrieveRequestedAttrs for a specific attribute.
+ // RetrieveRequestedAttrs needs to return valid data for all attributes we
+ // support, but the text service will only want the input scope object
+ // returned in RetrieveRequestedAttrs if the dwFlags passed in here contains
+ // TS_ATTR_FIND_WANT_VALUE.
+ for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
+ mRequestedAttrs[i] = false;
+ }
+ mRequestedAttrValues = !!(aFlags & TS_ATTR_FIND_WANT_VALUE);
+
+ for (uint32_t i = 0; i < aFilterCount; i++) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::HandleRequestAttrs(), "
+ "requested attr=%s",
+ this, GetGUIDNameStrWithTable(aFilterAttrs[i]).get()));
+ int32_t index = GetRequestedAttrIndex(aFilterAttrs[i]);
+ if (index != eNotSupported) {
+ mRequestedAttrs[index] = true;
+ }
+ }
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::RequestSupportedAttrs(DWORD dwFlags, ULONG cFilterAttrs,
+ const TS_ATTRID* paFilterAttrs) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestSupportedAttrs(dwFlags=%s, "
+ "cFilterAttrs=%lu)",
+ this, GetFindFlagName(dwFlags).get(), cFilterAttrs));
+
+ return HandleRequestAttrs(dwFlags, cFilterAttrs, paFilterAttrs);
+}
+
+STDMETHODIMP
+TSFTextStore::RequestAttrsAtPosition(LONG acpPos, ULONG cFilterAttrs,
+ const TS_ATTRID* paFilterAttrs,
+ DWORD dwFlags) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestAttrsAtPosition(acpPos=%ld, "
+ "cFilterAttrs=%lu, dwFlags=%s)",
+ this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
+
+ return HandleRequestAttrs(dwFlags | TS_ATTR_FIND_WANT_VALUE, cFilterAttrs,
+ paFilterAttrs);
+}
+
+STDMETHODIMP
+TSFTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos,
+ ULONG cFilterAttrs,
+ const TS_ATTRID* paFilterAttr,
+ DWORD dwFlags) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestAttrsTransitioningAtPosition("
+ "acpPos=%ld, cFilterAttrs=%lu, dwFlags=%s) called but not supported "
+ "(S_OK)",
+ this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
+
+ // no per character attributes defined
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::FindNextAttrTransition(LONG acpStart, LONG acpHalt,
+ ULONG cFilterAttrs,
+ const TS_ATTRID* paFilterAttrs,
+ DWORD dwFlags, LONG* pacpNext,
+ BOOL* pfFound, LONG* plFoundOffset) {
+ if (!pacpNext || !pfFound || !plFoundOffset) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" 0x%p TSFTextStore::FindNextAttrTransition() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::FindNextAttrTransition() called "
+ "but not supported (S_OK)",
+ this));
+
+ // no per character attributes defined
+ *pacpNext = *plFoundOffset = acpHalt;
+ *pfFound = FALSE;
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::RetrieveRequestedAttrs(ULONG ulCount, TS_ATTRVAL* paAttrVals,
+ ULONG* pcFetched) {
+ if (!pcFetched || !paAttrVals) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ ULONG expectedCount = 0;
+ for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
+ if (mRequestedAttrs[i]) {
+ expectedCount++;
+ }
+ }
+ if (ulCount < expectedCount) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
+ "not enough count ulCount=%u, expectedCount=%u",
+ this, ulCount, expectedCount));
+ return E_INVALIDARG;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
+ "ulCount=%d, mRequestedAttrValues=%s",
+ this, ulCount, GetBoolName(mRequestedAttrValues)));
+
+ int32_t count = 0;
+ for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
+ if (!mRequestedAttrs[i]) {
+ continue;
+ }
+ mRequestedAttrs[i] = false;
+
+ TS_ATTRID attrID = GetAttrID(i);
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() for %s", this,
+ GetGUIDNameStrWithTable(attrID).get()));
+
+ paAttrVals[count].idAttr = attrID;
+ paAttrVals[count].dwOverlapId = 0;
+
+ if (!mRequestedAttrValues) {
+ paAttrVals[count].varValue.vt = VT_EMPTY;
+ } else {
+ switch (i) {
+ case eInputScope: {
+ paAttrVals[count].varValue.vt = VT_UNKNOWN;
+ RefPtr<IUnknown> inputScope = new InputScopeImpl(mInputScopes);
+ paAttrVals[count].varValue.punkVal = inputScope.forget().take();
+ break;
+ }
+ case eTextVerticalWriting: {
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ paAttrVals[count].varValue.vt = VT_BOOL;
+ paAttrVals[count].varValue.boolVal =
+ selectionForTSF.isSome() &&
+ selectionForTSF->GetWritingMode().IsVertical()
+ ? VARIANT_TRUE
+ : VARIANT_FALSE;
+ break;
+ }
+ case eTextOrientation: {
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ paAttrVals[count].varValue.vt = VT_I4;
+ paAttrVals[count].varValue.lVal =
+ selectionForTSF.isSome() &&
+ selectionForTSF->GetWritingMode().IsVertical()
+ ? 2700
+ : 0;
+ break;
+ }
+ default:
+ MOZ_CRASH("Invalid index? Or not implemented yet?");
+ break;
+ }
+ }
+ count++;
+ }
+
+ mRequestedAttrValues = false;
+
+ if (count) {
+ *pcFetched = count;
+ return S_OK;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
+ "for unknown TS_ATTRVAL, *pcFetched=0 (S_OK)",
+ this));
+
+ paAttrVals->dwOverlapId = 0;
+ paAttrVals->varValue.vt = VT_EMPTY;
+ *pcFetched = 0;
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetEndACP(LONG* pacp) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetEndACP(pacp=0x%p)", this, pacp));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetEndACP() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pacp) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetEndACP() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetEndACP() FAILED due to "
+ "ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ *pacp = static_cast<LONG>(contentForTSF->TextRef().Length());
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetActiveView(TsViewCookie* pvcView) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetActiveView(pvcView=0x%p)", this, pvcView));
+
+ if (!pvcView) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetActiveView() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ *pvcView = TEXTSTORE_DEFAULT_VIEW;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetActiveView() succeeded: *pvcView=%ld", this,
+ *pvcView));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetACPFromPoint(TsViewCookie vcView, const POINT* pt,
+ DWORD dwFlags, LONG* pacp) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetACPFromPoint(pvcView=%d, pt=%p (x=%d, "
+ "y=%d), dwFlags=%s, pacp=%p, mDeferNotifyingTSF=%s, "
+ "mWaitingQueryLayout=%s",
+ this, vcView, pt, pt ? pt->x : 0, pt ? pt->y : 0,
+ GetACPFromPointFlagName(dwFlags).get(), pacp,
+ GetBoolName(mDeferNotifyingTSF), GetBoolName(mWaitingQueryLayout)));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "called with invalid view",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!pt) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "null pt",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!pacp) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "null pacp",
+ this));
+ return E_INVALIDARG;
+ }
+
+ mWaitingQueryLayout = false;
+
+ if (mDestroyed ||
+ (mContentForTSF.isSome() && mContentForTSF->IsLayoutChanged())) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() returned "
+ "TS_E_NOLAYOUT",
+ this));
+ mHasReturnedNoLayoutError = true;
+ return TS_E_NOLAYOUT;
+ }
+
+ LayoutDeviceIntPoint ourPt(pt->x, pt->y);
+ // Convert to widget relative coordinates from screen's.
+ ourPt -= mWidget->WidgetToScreenOffset();
+
+ // NOTE: Don't check if the point is in the widget since the point can be
+ // outside of the widget if focused editor is in a XUL <panel>.
+
+ WidgetQueryContentEvent queryCharAtPointEvent(true, eQueryCharacterAtPoint,
+ mWidget);
+ mWidget->InitEvent(queryCharAtPointEvent, &ourPt);
+
+ // FYI: WidgetQueryContentEvent may cause flushing pending layout and it
+ // may cause focus change or something.
+ RefPtr<TSFTextStore> kungFuDeathGrip(this);
+ DispatchEvent(queryCharAtPointEvent);
+ if (!mWidget || mWidget->Destroyed()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "mWidget was destroyed during eQueryCharacterAtPoint",
+ this));
+ return E_FAIL;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetACPFromPoint(), queryCharAtPointEvent={ "
+ "mReply=%s }",
+ this, ToString(queryCharAtPointEvent.mReply).c_str()));
+
+ if (NS_WARN_IF(queryCharAtPointEvent.Failed())) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "eQueryCharacterAtPoint failure",
+ this));
+ return E_FAIL;
+ }
+
+ // If dwFlags isn't set and the point isn't in any character's bounding box,
+ // we should return TS_E_INVALIDPOINT.
+ if (!(dwFlags & GXFPF_NEAREST) && queryCharAtPointEvent.DidNotFindChar()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to the "
+ "point contained by no bounding box",
+ this));
+ return TS_E_INVALIDPOINT;
+ }
+
+ // Although, we're not sure if mTentativeCaretOffset becomes NOT_FOUND,
+ // let's assume that there is no content in such case.
+ NS_WARNING_ASSERTION(queryCharAtPointEvent.DidNotFindTentativeCaretOffset(),
+ "Tentative caret offset was not found");
+
+ uint32_t offset;
+
+ // If dwFlags includes GXFPF_ROUND_NEAREST, we should return tentative
+ // caret offset (MSDN calls it "range position").
+ if (dwFlags & GXFPF_ROUND_NEAREST) {
+ offset = queryCharAtPointEvent.mReply->mTentativeCaretOffset.valueOr(0);
+ } else if (queryCharAtPointEvent.FoundChar()) {
+ // Otherwise, we should return character offset whose bounding box contains
+ // the point.
+ offset = queryCharAtPointEvent.mReply->StartOffset();
+ } else {
+ // If the point isn't in any character's bounding box but we need to return
+ // the nearest character from the point, we should *guess* the character
+ // offset since there is no inexpensive API to check it strictly.
+ // XXX If we retrieve 2 bounding boxes, one is before the offset and
+ // the other is after the offset, we could resolve the offset.
+ // However, dispatching 2 eQueryTextRect may be expensive.
+
+ // So, use tentative offset for now.
+ offset = queryCharAtPointEvent.mReply->mTentativeCaretOffset.valueOr(0);
+
+ // However, if it's after the last character, we need to decrement the
+ // offset.
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ if (contentForTSF->TextRef().Length() <= offset) {
+ // If the tentative caret is after the last character, let's return
+ // the last character's offset.
+ offset = contentForTSF->TextRef().Length() - 1;
+ }
+ }
+
+ if (NS_WARN_IF(offset > LONG_MAX)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to out of "
+ "range of the result",
+ this));
+ return TS_E_INVALIDPOINT;
+ }
+
+ *pacp = static_cast<LONG>(offset);
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetACPFromPoint() succeeded: *pacp=%d", this,
+ *pacp));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd,
+ RECT* prc, BOOL* pfClipped) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetTextExt(vcView=%ld, "
+ "acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p), "
+ "IsHandlingCompositionInParent()=%s, "
+ "IsHandlingCompositionInContent()=%s, mContentForTSF=%s, "
+ "mSelectionForTSF=%s, mComposition=%s, mDeferNotifyingTSF=%s, "
+ "mWaitingQueryLayout=%s, IMEHandler::IsA11yHandlingNativeCaret()=%s",
+ this, vcView, acpStart, acpEnd, prc, pfClipped,
+ GetBoolName(IsHandlingCompositionInParent()),
+ GetBoolName(IsHandlingCompositionInContent()),
+ mozilla::ToString(mContentForTSF).c_str(),
+ ToString(mSelectionForTSF).c_str(), ToString(mComposition).c_str(),
+ GetBoolName(mDeferNotifyingTSF), GetBoolName(mWaitingQueryLayout),
+ GetBoolName(IMEHandler::IsA11yHandlingNativeCaret())));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "called with invalid view",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!prc || !pfClipped) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ // According to MSDN, ITextStoreACP::GetTextExt() should return
+ // TS_E_INVALIDARG when acpStart and acpEnd are same (i.e., collapsed range).
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms538435(v=vs.85).aspx
+ // > TS_E_INVALIDARG: The specified start and end character positions are
+ // > equal.
+ // However, some TIPs (including Microsoft's Chinese TIPs!) call this with
+ // collapsed range and if we return TS_E_INVALIDARG, they stops showing their
+ // owning window or shows it but odd position. So, we should just return
+ // error only when acpStart and/or acpEnd are really odd.
+
+ if (acpStart < 0 || acpEnd < acpStart) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "invalid position",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+
+ mWaitingQueryLayout = false;
+
+ if (IsHandlingCompositionInContent() && mContentForTSF.isSome() &&
+ mContentForTSF->HasOrHadComposition() &&
+ mContentForTSF->IsLayoutChanged() &&
+ mContentForTSF->MinModifiedOffset().value() >
+ static_cast<uint32_t>(LONG_MAX)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt(), FAILED due to the text "
+ "is too big for TSF (cannot treat modified offset as LONG), "
+ "mContentForTSF=%s",
+ this, ToString(mContentForTSF).c_str()));
+ return E_FAIL;
+ }
+
+ // At Windows 10 build 17643 (an insider preview for RS5), Microsoft fixed
+ // the bug of TS_E_NOLAYOUT (even when we returned TS_E_NOLAYOUT, TSF
+ // returned E_FAIL to TIP). However, until we drop to support older Windows
+ // and all TIPs are aware of TS_E_NOLAYOUT result, we need to keep returning
+ // S_OK and available rectangle only for them.
+ if (!MaybeHackNoErrorLayoutBugs(acpStart, acpEnd) &&
+ mContentForTSF.isSome() && mContentForTSF->IsLayoutChangedAt(acpEnd)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
+ "(acpEnd=%d)",
+ this, acpEnd));
+ mHasReturnedNoLayoutError = true;
+ return TS_E_NOLAYOUT;
+ }
+
+ if (mDestroyed) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
+ "(acpEnd=%d) because this has already been destroyed",
+ this, acpEnd));
+ mHasReturnedNoLayoutError = true;
+ return TS_E_NOLAYOUT;
+ }
+
+ // use eQueryTextRect to get rect in system, screen coordinates
+ WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, mWidget);
+ mWidget->InitEvent(queryTextRectEvent);
+
+ WidgetQueryContentEvent::Options options;
+ int64_t startOffset = acpStart;
+ if (mComposition.isSome()) {
+ // If there is a composition, TSF must want character rects related to
+ // the composition. Therefore, we should use insertion point relative
+ // query because the composition might be at different position from
+ // the position where TSFTextStore believes it at.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mComposition->StartOffset();
+ } else if (IsHandlingCompositionInParent() && mContentForTSF.isSome() &&
+ mContentForTSF->HasOrHadComposition()) {
+ // If there was a composition and its commit event hasn't been dispatched
+ // yet, ContentCacheInParent is still open for relative offset query from
+ // the latest composition.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mContentForTSF->LatestCompositionRange()->StartOffset();
+ } else if (!CanAccessActualContentDirectly()) {
+ // If TSF/TIP cannot access actual content directly, there may be pending
+ // text and/or selection changes which have not been notified TSF yet.
+ // Therefore, we should use relative to insertion point query since
+ // TSF/TIP computes the offset from the cached selection.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mSelectionForTSF->StartOffset();
+ }
+ // ContentEventHandler and ContentCache return actual caret rect when
+ // the queried range is collapsed and selection is collapsed at the
+ // queried range. Then, its height (in horizontal layout, width in vertical
+ // layout) may be different from actual font height of the line. In such
+ // case, users see "dancing" of candidate or suggest window of TIP.
+ // For preventing it, we should query text rect with at least 1 length.
+ uint32_t length = std::max(static_cast<int32_t>(acpEnd - acpStart), 1);
+ queryTextRectEvent.InitForQueryTextRect(startOffset, length, options);
+
+ DispatchEvent(queryTextRectEvent);
+ if (NS_WARN_IF(queryTextRectEvent.Failed())) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "eQueryTextRect failure",
+ this));
+ return TS_E_INVALIDPOS; // but unexpected failure, maybe.
+ }
+
+ // IMEs don't like empty rects, fix here
+ if (queryTextRectEvent.mReply->mRect.Width() <= 0) {
+ queryTextRectEvent.mReply->mRect.SetWidth(1);
+ }
+ if (queryTextRectEvent.mReply->mRect.Height() <= 0) {
+ queryTextRectEvent.mReply->mRect.SetHeight(1);
+ }
+
+ // convert to unclipped screen rect
+ nsWindow* refWindow =
+ static_cast<nsWindow*>(!!queryTextRectEvent.mReply->mFocusedWidget
+ ? queryTextRectEvent.mReply->mFocusedWidget
+ : static_cast<nsIWidget*>(mWidget.get()));
+ // Result rect is in top level widget coordinates
+ refWindow = refWindow->GetTopLevelWindow(false);
+ if (!refWindow) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "no top level window",
+ this));
+ return E_FAIL;
+ }
+
+ queryTextRectEvent.mReply->mRect.MoveBy(refWindow->WidgetToScreenOffset());
+
+ // get bounding screen rect to test for clipping
+ if (!GetScreenExtInternal(*prc)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "GetScreenExtInternal() failure",
+ this));
+ return E_FAIL;
+ }
+
+ // clip text rect to bounding rect
+ RECT textRect;
+ ::SetRect(&textRect, queryTextRectEvent.mReply->mRect.X(),
+ queryTextRectEvent.mReply->mRect.Y(),
+ queryTextRectEvent.mReply->mRect.XMost(),
+ queryTextRectEvent.mReply->mRect.YMost());
+ if (!::IntersectRect(prc, prc, &textRect))
+ // Text is not visible
+ ::SetRectEmpty(prc);
+
+ // not equal if text rect was clipped
+ *pfClipped = !::EqualRect(prc, &textRect);
+
+ // ATOK 2011 - 2016 refers native caret position and size on windows whose
+ // class name is one of Mozilla's windows for deciding candidate window
+ // position. Additionally, ATOK 2015 and earlier behaves really odd when
+ // we don't create native caret. Therefore, we need to create native caret
+ // only when ATOK 2011 - 2015 is active (i.e., not necessary for ATOK 2016).
+ // However, if a11y module is handling native caret, we shouldn't touch it.
+ // Note that ATOK must require the latest information of the caret. So,
+ // even if we'll create native caret later, we need to creat it here with
+ // current information.
+ if (!IMEHandler::IsA11yHandlingNativeCaret() &&
+ TSFPrefs::NeedToCreateNativeCaretForLegacyATOK() &&
+ TSFStaticSink::IsATOKReferringNativeCaretActive() &&
+ mComposition.isSome() &&
+ mComposition->IsOffsetInRangeOrEndOffset(acpStart) &&
+ mComposition->IsOffsetInRangeOrEndOffset(acpEnd)) {
+ CreateNativeCaret();
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetTextExt() succeeded: "
+ "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }, *pfClipped=%s",
+ this, prc->left, prc->top, prc->right, prc->bottom,
+ GetBoolName(*pfClipped)));
+
+ return S_OK;
+}
+
+bool TSFTextStore::MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd) {
+ // When ITextStoreACP::GetTextExt() returns TS_E_NOLAYOUT, TSF returns E_FAIL
+ // to its caller (typically, active TIP). Then, most TIPs abort current job
+ // or treat such application as non-GUI apps. E.g., some of them give up
+ // showing candidate window, some others show candidate window at top-left of
+ // the screen. For avoiding this issue, when there is composition (until
+ // composition is actually committed in remote content), we should not
+ // return TS_E_NOLAYOUT error for TIPs whose some features are broken by
+ // this issue.
+ // Note that ideally, this issue should be avoided by each TIP since this
+ // won't be fixed at least on non-latest Windows. Actually, Google Japanese
+ // Input (based on Mozc) does it. When GetTextExt() returns E_FAIL, TIPs
+ // should try to check result of GetRangeFromPoint() because TSF returns
+ // TS_E_NOLAYOUT correctly in this case. See:
+ // https://github.com/google/mozc/blob/6b878e31fb6ac4347dc9dfd8ccc1080fe718479f/src/win32/tip/tip_range_util.cc#L237-L257
+
+ if (!IsHandlingCompositionInContent() || mContentForTSF.isNothing() ||
+ !mContentForTSF->HasOrHadComposition() ||
+ !mContentForTSF->IsLayoutChangedAt(aACPEnd)) {
+ return false;
+ }
+
+ MOZ_ASSERT(mComposition.isNothing() ||
+ mComposition->StartOffset() ==
+ mContentForTSF->LatestCompositionRange()->StartOffset());
+ MOZ_ASSERT(mComposition.isNothing() ||
+ mComposition->EndOffset() ==
+ mContentForTSF->LatestCompositionRange()->EndOffset());
+
+ // If TSF does not have the bug, we need to hack only with a few TIPs.
+ static const bool sAlllowToStopHackingIfFine =
+ IsWindows10BuildOrLater(17643) &&
+ TSFPrefs::AllowToStopHackingOnBuild17643OrLater();
+
+ // We need to compute active TIP now. This may take a couple of milliseconds,
+ // however, it'll be cached, so, must be faster than check active TIP every
+ // GetTextExt() calls.
+ const Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ switch (TSFStaticSink::ActiveTIP()) {
+ // MS IME for Japanese doesn't support asynchronous handling at deciding
+ // its suggest list window position. The feature was implemented
+ // starting from Windows 8. And also we may meet same trouble in e10s
+ // mode on Win7. So, we should never return TS_E_NOLAYOUT to MS IME for
+ // Japanese.
+ case TextInputProcessorID::eMicrosoftIMEForJapanese:
+ // Basically, MS-IME tries to retrieve whole composition string rect
+ // at deciding suggest window immediately after unlocking the document.
+ // However, in e10s mode, the content hasn't updated yet in most cases.
+ // Therefore, if the first character at the retrieving range rect is
+ // available, we should use it as the result.
+ // Note that according to bug 1609675, MS-IME for Japanese itself does
+ // not handle TS_E_NOLAYOUT correctly at least on Build 18363.657 (1909).
+ if (TSFPrefs::DoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar() &&
+ aACPStart < aACPEnd) {
+ aACPEnd = aACPStart;
+ break;
+ }
+ if (sAlllowToStopHackingIfFine) {
+ return false;
+ }
+ // Although, the condition is not clear, MS-IME sometimes retrieves the
+ // caret rect immediately after modifying the composition string but
+ // before unlocking the document. In such case, we should return the
+ // nearest character rect.
+ if (TSFPrefs::DoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret() &&
+ selectionForTSF.isSome() && aACPStart == aACPEnd &&
+ selectionForTSF->Collapsed() &&
+ selectionForTSF->EndOffset() == aACPEnd) {
+ int32_t minOffsetOfLayoutChanged =
+ static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
+ aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
+ } else {
+ return false;
+ }
+ break;
+ // The bug of Microsoft Office IME 2010 for Japanese is similar to
+ // MS-IME for Win 8.1 and Win 10. Newer version of MS Office IME is not
+ // released yet. So, we can hack it without prefs because there must be
+ // no developers who want to disable this hack for tests.
+ // XXX We have not tested with Microsoft Office IME 2010 since it's
+ // installable only with Win7 and Win8 (i.e., cannot install Win8.1
+ // and Win10), and requires upgrade to Win10.
+ case TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese:
+ // Basically, MS-IME tries to retrieve whole composition string rect
+ // at deciding suggest window immediately after unlocking the document.
+ // However, in e10s mode, the content hasn't updated yet in most cases.
+ // Therefore, if the first character at the retrieving range rect is
+ // available, we should use it as the result.
+ if (aACPStart < aACPEnd) {
+ aACPEnd = aACPStart;
+ }
+ // Although, the condition is not clear, MS-IME sometimes retrieves the
+ // caret rect immediately after modifying the composition string but
+ // before unlocking the document. In such case, we should return the
+ // nearest character rect.
+ else if (aACPStart == aACPEnd && selectionForTSF.isSome() &&
+ selectionForTSF->Collapsed() &&
+ selectionForTSF->EndOffset() == aACPEnd) {
+ int32_t minOffsetOfLayoutChanged =
+ static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
+ aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
+ } else {
+ return false;
+ }
+ break;
+ // ATOK fails to handle TS_E_NOLAYOUT only when it decides the position of
+ // suggest window. In such case, ATOK tries to query rect of whole or a
+ // part of composition string.
+ // FYI: ATOK changes their implementation around candidate window and
+ // suggest widget at ATOK 2016. Therefore, there are some differences
+ // ATOK 2015 (or older) and ATOK 2016 (or newer).
+ // FYI: ATOK 2017 stops referring our window class name. I.e., ATOK 2016
+ // and older may behave differently only on Gecko but this must be
+ // finished from ATOK 2017.
+ // FYI: For testing with legacy ATOK, we should hack it even if current ATOK
+ // refers native caret rect on windows whose window class is one of
+ // Mozilla window classes and we stop creating native caret for ATOK
+ // because creating native caret causes ATOK refers caret position
+ // when GetTextExt() returns TS_E_NOLAYOUT.
+ case TextInputProcessorID::eATOK2011:
+ case TextInputProcessorID::eATOK2012:
+ case TextInputProcessorID::eATOK2013:
+ case TextInputProcessorID::eATOK2014:
+ case TextInputProcessorID::eATOK2015:
+ // ATOK 2016 and later may temporarily show candidate window at odd
+ // position when you convert a word quickly (e.g., keep pressing
+ // space bar). So, on ATOK 2016 or later, we need to keep hacking the
+ // result of GetTextExt().
+ if (sAlllowToStopHackingIfFine) {
+ return false;
+ }
+ // If we'll create native caret where we paint our caret. Then, ATOK
+ // will refer native caret. So, we don't need to hack anything in
+ // this case.
+ if (TSFPrefs::NeedToCreateNativeCaretForLegacyATOK()) {
+ MOZ_ASSERT(TSFStaticSink::IsATOKReferringNativeCaretActive());
+ return false;
+ }
+ [[fallthrough]];
+ case TextInputProcessorID::eATOK2016:
+ case TextInputProcessorID::eATOKUnknown:
+ if (!TSFPrefs::DoNotReturnNoLayoutErrorToATOKOfCompositionString()) {
+ return false;
+ }
+ // If the range is in the composition string, we should return rectangle
+ // in it as far as possible.
+ if (!mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
+ aACPStart) ||
+ !mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
+ aACPEnd)) {
+ return false;
+ }
+ break;
+ // Japanist 10 fails to handle TS_E_NOLAYOUT when it decides the position
+ // of candidate window. In such case, Japanist shows candidate window at
+ // top-left of the screen. So, we should return the nearest caret rect
+ // where we know. This is Japanist's bug. So, even after build 17643,
+ // we need this hack.
+ case TextInputProcessorID::eJapanist10:
+ if (!TSFPrefs::
+ DoNotReturnNoLayoutErrorToJapanist10OfCompositionString()) {
+ return false;
+ }
+ if (!mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
+ aACPStart) ||
+ !mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
+ aACPEnd)) {
+ return false;
+ }
+ break;
+ // Free ChangJie 2010 doesn't handle ITfContextView::GetTextExt() properly.
+ // This must be caused by the bug of TSF since Free ChangJie works fine on
+ // build 17643 and later.
+ case TextInputProcessorID::eFreeChangJie:
+ if (sAlllowToStopHackingIfFine) {
+ return false;
+ }
+ if (!TSFPrefs::DoNotReturnNoLayoutErrorToFreeChangJie()) {
+ return false;
+ }
+ aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
+ aACPStart = std::min(aACPStart, aACPEnd);
+ break;
+ // Some Traditional Chinese TIPs of Microsoft don't show candidate window
+ // in e10s mode on Win8 or later.
+ case TextInputProcessorID::eMicrosoftQuick:
+ if (sAlllowToStopHackingIfFine) {
+ return false; // MS Quick works fine with Win10 build 17643.
+ }
+ [[fallthrough]];
+ case TextInputProcessorID::eMicrosoftChangJie:
+ if (!IsWin8OrLater() ||
+ !TSFPrefs::DoNotReturnNoLayoutErrorToMSTraditionalTIP()) {
+ return false;
+ }
+ aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
+ aACPStart = std::min(aACPStart, aACPEnd);
+ break;
+ // Some Simplified Chinese TIPs of Microsoft don't show candidate window
+ // in e10s mode on Win8 or later.
+ // FYI: Only Simplified Chinese TIPs of Microsoft still require this hack
+ // because they sometimes do not show candidate window when we return
+ // TS_E_NOLAYOUT for first query. Note that even when they show
+ // candidate window properly, we return TS_E_NOLAYOUT and following
+ // log looks same as when they don't show candidate window. Perhaps,
+ // there is stateful cause or race in them.
+ case TextInputProcessorID::eMicrosoftPinyin:
+ case TextInputProcessorID::eMicrosoftWubi:
+ if (!IsWin8OrLater() ||
+ !TSFPrefs::DoNotReturnNoLayoutErrorToMSSimplifiedTIP()) {
+ return false;
+ }
+ aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
+ aACPStart = std::min(aACPStart, aACPEnd);
+ break;
+ default:
+ return false;
+ }
+
+ // If we hack the queried range for active TIP, that means we should not
+ // return TS_E_NOLAYOUT even if hacked offset is still modified. So, as
+ // far as possible, we should adjust the offset.
+ MOZ_ASSERT(mContentForTSF->IsLayoutChanged());
+ bool collapsed = aACPStart == aACPEnd;
+ // Note that even if all characters in the editor or the composition
+ // string was modified, 0 or start offset of the composition string is
+ // useful because it may return caret rect or old character's rect which
+ // the user still see. That must be useful information for TIP.
+ int32_t firstModifiedOffset =
+ static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
+ LONG lastUnmodifiedOffset = std::max(firstModifiedOffset - 1, 0);
+ if (mContentForTSF->IsLayoutChangedAt(aACPStart)) {
+ if (aACPStart >= mContentForTSF->LatestCompositionRange()->StartOffset()) {
+ // If mContentForTSF has last composition string and current
+ // composition string, we can assume that ContentCacheInParent has
+ // cached rects of composition string at least length of current
+ // composition string. Otherwise, we can assume that rect for
+ // first character of composition string is stored since it was
+ // selection start or caret position.
+ LONG maxCachedOffset =
+ mContentForTSF->LatestCompositionRange()->EndOffset();
+ if (mContentForTSF->LastComposition().isSome()) {
+ maxCachedOffset = std::min(
+ maxCachedOffset, mContentForTSF->LastComposition()->EndOffset());
+ }
+ aACPStart = std::min(aACPStart, maxCachedOffset);
+ }
+ // Otherwise, we don't know which character rects are cached. So, we
+ // need to use first unmodified character's rect in this case. Even
+ // if there is no character, the query event will return caret rect
+ // instead.
+ else {
+ aACPStart = lastUnmodifiedOffset;
+ }
+ MOZ_ASSERT(aACPStart <= aACPEnd);
+ }
+
+ // If TIP requests caret rect with collapsed range, we should keep
+ // collapsing the range.
+ if (collapsed) {
+ aACPEnd = aACPStart;
+ }
+ // Let's set aACPEnd to larger offset of last unmodified offset or
+ // aACPStart which may be the first character offset of the composition
+ // string. However, some TIPs may want to know the right edge of the
+ // range. Therefore, if aACPEnd is in composition string and active TIP
+ // doesn't retrieve caret rect (i.e., the range isn't collapsed), we
+ // should keep using the original aACPEnd. Otherwise, we should set
+ // aACPEnd to larger value of aACPStart and lastUnmodifiedOffset.
+ else if (mContentForTSF->IsLayoutChangedAt(aACPEnd) &&
+ !mContentForTSF->LatestCompositionRange()
+ ->IsOffsetInRangeOrEndOffset(aACPEnd)) {
+ aACPEnd = std::max(aACPStart, lastUnmodifiedOffset);
+ }
+
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::HackNoErrorLayoutBugs() hacked the queried range "
+ "for not returning TS_E_NOLAYOUT, new values are: "
+ "aACPStart=%d, aACPEnd=%d",
+ this, aACPStart, aACPEnd));
+
+ return true;
+}
+
+STDMETHODIMP
+TSFTextStore::GetScreenExt(TsViewCookie vcView, RECT* prc) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetScreenExt(vcView=%ld, prc=0x%p)", this,
+ vcView, prc));
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "called with invalid view",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!prc) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (mDestroyed) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() returns empty rect "
+ "due to already destroyed",
+ this));
+ prc->left = prc->top = prc->right = prc->bottom = 0;
+ return S_OK;
+ }
+
+ if (!GetScreenExtInternal(*prc)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "GetScreenExtInternal() failure",
+ this));
+ return E_FAIL;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetScreenExt() succeeded: "
+ "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
+ this, prc->left, prc->top, prc->right, prc->bottom));
+ return S_OK;
+}
+
+bool TSFTextStore::GetScreenExtInternal(RECT& aScreenExt) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetScreenExtInternal()", this));
+
+ MOZ_ASSERT(!mDestroyed);
+
+ // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates
+ WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, mWidget);
+ mWidget->InitEvent(queryEditorRectEvent);
+ DispatchEvent(queryEditorRectEvent);
+ if (queryEditorRectEvent.Failed()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
+ "eQueryEditorRect failure",
+ this));
+ return false;
+ }
+
+ nsWindow* refWindow =
+ static_cast<nsWindow*>(!!queryEditorRectEvent.mReply->mFocusedWidget
+ ? queryEditorRectEvent.mReply->mFocusedWidget
+ : static_cast<nsIWidget*>(mWidget.get()));
+ // Result rect is in top level widget coordinates
+ refWindow = refWindow->GetTopLevelWindow(false);
+ if (!refWindow) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
+ "no top level window",
+ this));
+ return false;
+ }
+
+ LayoutDeviceIntRect boundRect = refWindow->GetClientBounds();
+ boundRect.MoveTo(0, 0);
+
+ // Clip frame rect to window rect
+ boundRect.IntersectRect(queryEditorRectEvent.mReply->mRect, boundRect);
+ if (!boundRect.IsEmpty()) {
+ boundRect.MoveBy(refWindow->WidgetToScreenOffset());
+ ::SetRect(&aScreenExt, boundRect.X(), boundRect.Y(), boundRect.XMost(),
+ boundRect.YMost());
+ } else {
+ ::SetRectEmpty(&aScreenExt);
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetScreenExtInternal() succeeded: "
+ "aScreenExt={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
+ this, aScreenExt.left, aScreenExt.top, aScreenExt.right,
+ aScreenExt.bottom));
+ return true;
+}
+
+STDMETHODIMP
+TSFTextStore::GetWnd(TsViewCookie vcView, HWND* phwnd) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetWnd(vcView=%ld, phwnd=0x%p), "
+ "mWidget=0x%p",
+ this, vcView, phwnd, mWidget.get()));
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetWnd() FAILED due to "
+ "called with invalid view",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!phwnd) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ *phwnd = mWidget ? mWidget->GetWindowHandle() : nullptr;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetWnd() succeeded: *phwnd=0x%p", this,
+ static_cast<void*>(*phwnd)));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::InsertTextAtSelection(DWORD dwFlags, const WCHAR* pchText,
+ ULONG cch, LONG* pacpStart, LONG* pacpEnd,
+ TS_TEXTCHANGE* pChange) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::InsertTextAtSelection(dwFlags=%s, "
+ "pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, "
+ "pChange=0x%p), mComposition=%s",
+ this,
+ dwFlags == 0 ? "0"
+ : dwFlags == TF_IAS_NOQUERY ? "TF_IAS_NOQUERY"
+ : dwFlags == TF_IAS_QUERYONLY ? "TF_IAS_QUERYONLY"
+ : "Unknown",
+ pchText, pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "",
+ cch, pacpStart, pacpEnd, pChange, ToString(mComposition).c_str()));
+
+ if (cch && !pchText) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null pchText",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (TS_IAS_QUERYONLY == dwFlags) {
+ if (!IsReadLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pacpStart || !pacpEnd) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ // Get selection first
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ // Simulate text insertion
+ *pacpStart = selectionForTSF->StartOffset();
+ *pacpEnd = selectionForTSF->EndOffset();
+ if (pChange) {
+ pChange->acpStart = selectionForTSF->StartOffset();
+ pChange->acpOldEnd = selectionForTSF->EndOffset();
+ pChange->acpNewEnd =
+ selectionForTSF->StartOffset() + static_cast<LONG>(cch);
+ }
+ } else {
+ if (!IsReadWriteLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "not locked (read-write)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pChange) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null pChange",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (TS_IAS_NOQUERY != dwFlags && (!pacpStart || !pacpEnd)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
+ pChange)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "InsertTextAtSelectionInternal() failure",
+ this));
+ return E_FAIL;
+ }
+
+ if (TS_IAS_NOQUERY != dwFlags) {
+ *pacpStart = pChange->acpStart;
+ *pacpEnd = pChange->acpNewEnd;
+ }
+ }
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::InsertTextAtSelection() succeeded: "
+ "*pacpStart=%ld, *pacpEnd=%ld, "
+ "*pChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld })",
+ this, pacpStart ? *pacpStart : 0, pacpEnd ? *pacpEnd : 0,
+ pChange ? pChange->acpStart : 0, pChange ? pChange->acpOldEnd : 0,
+ pChange ? pChange->acpNewEnd : 0));
+ return S_OK;
+}
+
+bool TSFTextStore::InsertTextAtSelectionInternal(const nsAString& aInsertStr,
+ TS_TEXTCHANGE* aTextChange) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal("
+ "aInsertStr=\"%s\", aTextChange=0x%p), mComposition=%s",
+ this, GetEscapedUTF8String(aInsertStr).get(), aTextChange,
+ ToString(mComposition).c_str()));
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() failed "
+ "due to ContentForTSF() failure()",
+ this));
+ return false;
+ }
+
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return false;
+ }
+
+ TS_SELECTION_ACP oldSelection = contentForTSF->Selection()->ACPRef();
+ if (mComposition.isNothing()) {
+ // Use a temporary composition to contain the text
+ PendingAction* compositionStart = mPendingActions.AppendElements(2);
+ PendingAction* compositionEnd = compositionStart + 1;
+
+ compositionStart->mType = PendingAction::Type::eCompositionStart;
+ compositionStart->mSelectionStart = oldSelection.acpStart;
+ compositionStart->mSelectionLength =
+ oldSelection.acpEnd - oldSelection.acpStart;
+ compositionStart->mAdjustSelection = false;
+
+ compositionEnd->mType = PendingAction::Type::eCompositionEnd;
+ compositionEnd->mData = aInsertStr;
+ compositionEnd->mSelectionStart = compositionStart->mSelectionStart;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
+ "appending pending compositionstart and compositionend... "
+ "PendingCompositionStart={ mSelectionStart=%d, "
+ "mSelectionLength=%d }, PendingCompositionEnd={ mData=\"%s\" "
+ "(Length()=%u), mSelectionStart=%d }",
+ this, compositionStart->mSelectionStart,
+ compositionStart->mSelectionLength,
+ GetEscapedUTF8String(compositionEnd->mData).get(),
+ compositionEnd->mData.Length(), compositionEnd->mSelectionStart));
+ }
+
+ contentForTSF->ReplaceSelectedTextWith(aInsertStr);
+
+ if (aTextChange) {
+ aTextChange->acpStart = oldSelection.acpStart;
+ aTextChange->acpOldEnd = oldSelection.acpEnd;
+ aTextChange->acpNewEnd = contentForTSF->Selection()->EndOffset();
+ }
+
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
+ "succeeded: mWidget=0x%p, mWidget->Destroyed()=%s, aTextChange={ "
+ "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
+ this, mWidget.get(), GetBoolName(mWidget ? mWidget->Destroyed() : true),
+ aTextChange ? aTextChange->acpStart : 0,
+ aTextChange ? aTextChange->acpOldEnd : 0,
+ aTextChange ? aTextChange->acpNewEnd : 0));
+ return true;
+}
+
+STDMETHODIMP
+TSFTextStore::InsertEmbeddedAtSelection(DWORD dwFlags, IDataObject* pDataObject,
+ LONG* pacpStart, LONG* pacpEnd,
+ TS_TEXTCHANGE* pChange) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::InsertEmbeddedAtSelection() called "
+ "but not supported (E_NOTIMPL)",
+ this));
+
+ // embedded objects are not supported
+ return E_NOTIMPL;
+}
+
+HRESULT TSFTextStore::RecordCompositionStartAction(
+ ITfCompositionView* aCompositionView, ITfRange* aRange,
+ bool aPreserveSelection) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionStartAction("
+ "aCompositionView=0x%p, aRange=0x%p, aPreserveSelection=%s), "
+ "mComposition=%s",
+ this, aCompositionView, aRange, GetBoolName(aPreserveSelection),
+ ToString(mComposition).c_str()));
+
+ LONG start = 0, length = 0;
+ HRESULT hr = GetRangeExtent(aRange, &start, &length);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
+ "due to GetRangeExtent() failure",
+ this));
+ return hr;
+ }
+
+ return RecordCompositionStartAction(aCompositionView, start, length,
+ aPreserveSelection);
+}
+
+HRESULT TSFTextStore::RecordCompositionStartAction(
+ ITfCompositionView* aCompositionView, LONG aStart, LONG aLength,
+ bool aPreserveSelection) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionStartAction("
+ "aCompositionView=0x%p, aStart=%d, aLength=%d, aPreserveSelection=%s), "
+ "mComposition=%s",
+ this, aCompositionView, aStart, aLength, GetBoolName(aPreserveSelection),
+ ToString(mComposition).c_str()));
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
+ "due to ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return false;
+ }
+
+ CompleteLastActionIfStillIncomplete();
+
+ // TIP may have inserted text at selection before calling
+ // OnStartComposition(). In this case, we've already created a pending
+ // compositionend. If new composition replaces all commit string of the
+ // pending compositionend, we should cancel the pending compositionend and
+ // keep the previous composition normally.
+ // On Windows 7, MS-IME for Korean, MS-IME 2010 for Korean and MS Old Hangul
+ // may start composition with calling InsertTextAtSelection() and
+ // OnStartComposition() with this order (bug 1208043).
+ // On Windows 10, MS Pinyin, MS Wubi, MS ChangJie and MS Quick commits
+ // last character and replace it with empty string with new composition
+ // when user removes last character of composition string with Backspace
+ // key (bug 1462257).
+ if (!aPreserveSelection &&
+ IsLastPendingActionCompositionEndAt(aStart, aLength)) {
+ const PendingAction& pendingCompositionEnd = mPendingActions.LastElement();
+ contentForTSF->RestoreCommittedComposition(aCompositionView,
+ pendingCompositionEnd);
+ mPendingActions.RemoveLastElement();
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() "
+ "succeeded: restoring the committed string as composing string, "
+ "mComposition=%s, mSelectionForTSF=%s",
+ this, ToString(mComposition).c_str(),
+ ToString(mSelectionForTSF).c_str()));
+ return S_OK;
+ }
+
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::Type::eCompositionStart;
+ action->mSelectionStart = aStart;
+ action->mSelectionLength = aLength;
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
+ "due to SelectionForTSF() failure",
+ this));
+ action->mAdjustSelection = true;
+ } else if (selectionForTSF->MinOffset() != aStart ||
+ selectionForTSF->MaxOffset() != aStart + aLength) {
+ // If new composition range is different from current selection range,
+ // we need to set selection before dispatching compositionstart event.
+ action->mAdjustSelection = true;
+ } else {
+ // We shouldn't dispatch selection set event before dispatching
+ // compositionstart event because it may cause put caret different
+ // position in HTML editor since generated flat text content and offset in
+ // it are lossy data of HTML contents.
+ action->mAdjustSelection = false;
+ }
+
+ contentForTSF->StartComposition(aCompositionView, *action,
+ aPreserveSelection);
+ MOZ_ASSERT(mComposition.isSome());
+ action->mData = mComposition->DataRef();
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() succeeded: "
+ "mComposition=%s, mSelectionForTSF=%s }",
+ this, ToString(mComposition).c_str(),
+ ToString(mSelectionForTSF).c_str()));
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::RecordCompositionEndAction() {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionEndAction(), "
+ "mComposition=%s",
+ this, ToString(mComposition).c_str()));
+
+ MOZ_ASSERT(mComposition.isSome());
+
+ if (mComposition.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
+ "no composition",
+ this));
+ return false;
+ }
+
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return false;
+ }
+
+ // If we're handling incomplete composition update or already handled
+ // composition update, we can forget them since composition end will send
+ // the latest composition string and it overwrites the composition string
+ // even if we dispatch eCompositionChange event before that. So, let's
+ // forget all composition updates now.
+ RemoveLastCompositionUpdateActions();
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::Type::eCompositionEnd;
+ action->mData = mComposition->DataRef();
+ action->mSelectionStart = mComposition->StartOffset();
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due "
+ "to ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ contentForTSF->EndComposition(*action);
+
+ // If this composition was restart but the composition doesn't modify
+ // anything, we should remove the pending composition for preventing to
+ // dispatch redundant composition events.
+ for (size_t i = mPendingActions.Length(), j = 1; i > 0; --i, ++j) {
+ PendingAction& pendingAction = mPendingActions[i - 1];
+ if (pendingAction.mType == PendingAction::Type::eCompositionStart) {
+ if (pendingAction.mData != action->mData) {
+ break;
+ }
+ // When only setting selection is necessary, we should append it.
+ if (pendingAction.mAdjustSelection) {
+ LONG selectionStart = pendingAction.mSelectionStart;
+ LONG selectionLength = pendingAction.mSelectionLength;
+
+ PendingAction* setSelection = mPendingActions.AppendElement();
+ setSelection->mType = PendingAction::Type::eSetSelection;
+ setSelection->mSelectionStart = selectionStart;
+ setSelection->mSelectionLength = selectionLength;
+ setSelection->mSelectionReversed = false;
+ }
+ // Remove the redundant pending composition.
+ mPendingActions.RemoveElementsAt(i - 1, j);
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionEndAction(), "
+ "succeeded, but the composition was canceled due to redundant",
+ this));
+ return S_OK;
+ }
+ }
+
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionEndAction(), succeeded", this));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnStartComposition(pComposition=0x%p, "
+ "pfOk=0x%p), mComposition=%s",
+ this, pComposition, pfOk, ToString(mComposition).c_str()));
+
+ AutoPendingActionAndContentFlusher flusher(this);
+
+ *pfOk = FALSE;
+
+ // Only one composition at a time
+ if (mComposition.isSome()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
+ "there is another composition already (but returns S_OK)",
+ this));
+ return S_OK;
+ }
+
+ RefPtr<ITfRange> range;
+ HRESULT hr = pComposition->GetRange(getter_AddRefs(range));
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
+ "pComposition->GetRange() failure",
+ this));
+ return hr;
+ }
+ hr = RecordCompositionStartAction(pComposition, range, false);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
+ "RecordCompositionStartAction() failure",
+ this));
+ return hr;
+ }
+
+ *pfOk = TRUE;
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnStartComposition() succeeded", this));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::OnUpdateComposition(ITfCompositionView* pComposition,
+ ITfRange* pRangeNew) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnUpdateComposition(pComposition=0x%p, "
+ "pRangeNew=0x%p), mComposition=%s",
+ this, pComposition, pRangeNew, ToString(mComposition).c_str()));
+
+ AutoPendingActionAndContentFlusher flusher(this);
+
+ if (!mDocumentMgr || !mContext) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "not ready for the composition",
+ this));
+ return E_UNEXPECTED;
+ }
+ if (mComposition.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "no active composition",
+ this));
+ return E_UNEXPECTED;
+ }
+ if (mComposition->GetView() != pComposition) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "different composition view specified",
+ this));
+ return E_UNEXPECTED;
+ }
+
+ // pRangeNew is null when the update is not complete
+ if (!pRangeNew) {
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return E_FAIL;
+ }
+ PendingAction* action = LastOrNewPendingCompositionUpdate();
+ action->mIncomplete = true;
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnUpdateComposition() succeeded but "
+ "not complete",
+ this));
+ return S_OK;
+ }
+
+ HRESULT hr = RestartCompositionIfNecessary(pRangeNew);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "RestartCompositionIfNecessary() failure",
+ this));
+ return hr;
+ }
+
+ hr = RecordCompositionUpdateAction();
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "RecordCompositionUpdateAction() failure",
+ this));
+ return hr;
+ }
+
+ if (MOZ_LOG_TEST(sTextStoreLog, LogLevel::Info)) {
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnUpdateComposition() succeeded: "
+ "mComposition=%s, SelectionForTSF()=%s",
+ this, ToString(mComposition).c_str(),
+ ToString(selectionForTSF).c_str()));
+ }
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::OnEndComposition(ITfCompositionView* pComposition) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnEndComposition(pComposition=0x%p), "
+ "mComposition=%s",
+ this, pComposition, ToString(mComposition).c_str()));
+
+ AutoPendingActionAndContentFlusher flusher(this);
+
+ if (mComposition.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
+ "no active composition",
+ this));
+ return E_UNEXPECTED;
+ }
+
+ if (mComposition->GetView() != pComposition) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
+ "different composition view specified",
+ this));
+ return E_UNEXPECTED;
+ }
+
+ HRESULT hr = RecordCompositionEndAction();
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
+ "RecordCompositionEndAction() failure",
+ this));
+ return hr;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnEndComposition(), succeeded", this));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::AdviseMouseSink(ITfRangeACP* range, ITfMouseSink* pSink,
+ DWORD* pdwCookie) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::AdviseMouseSink(range=0x%p, pSink=0x%p, "
+ "pdwCookie=0x%p)",
+ this, range, pSink, pdwCookie));
+
+ if (!pdwCookie) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
+ "pdwCookie is null",
+ this));
+ return E_INVALIDARG;
+ }
+ // Initialize the result with invalid cookie for safety.
+ *pdwCookie = MouseTracker::kInvalidCookie;
+
+ if (!range) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
+ "range is null",
+ this));
+ return E_INVALIDARG;
+ }
+ if (!pSink) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
+ "pSink is null",
+ this));
+ return E_INVALIDARG;
+ }
+
+ // Looking for an unusing tracker.
+ MouseTracker* tracker = nullptr;
+ for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
+ if (mMouseTrackers[i].IsUsing()) {
+ continue;
+ }
+ tracker = &mMouseTrackers[i];
+ }
+ // If there is no unusing tracker, create new one.
+ // XXX Should we make limitation of the number of installs?
+ if (!tracker) {
+ tracker = mMouseTrackers.AppendElement();
+ HRESULT hr = tracker->Init(this);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to "
+ "failure of MouseTracker::Init()",
+ this));
+ return hr;
+ }
+ }
+ HRESULT hr = tracker->AdviseSink(this, range, pSink);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to failure "
+ "of MouseTracker::Init()",
+ this));
+ return hr;
+ }
+ *pdwCookie = tracker->Cookie();
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::AdviseMouseSink(), succeeded, "
+ "*pdwCookie=%d",
+ this, *pdwCookie));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::UnadviseMouseSink(DWORD dwCookie) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::UnadviseMouseSink(dwCookie=%d)", this, dwCookie));
+ if (dwCookie == MouseTracker::kInvalidCookie) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
+ "the cookie is invalid value",
+ this));
+ return E_INVALIDARG;
+ }
+ // The cookie value must be an index of mMouseTrackers.
+ // We can use this shortcut for now.
+ if (static_cast<size_t>(dwCookie) >= mMouseTrackers.Length()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
+ "the cookie is too large value",
+ this));
+ return E_INVALIDARG;
+ }
+ MouseTracker& tracker = mMouseTrackers[dwCookie];
+ if (!tracker.IsUsing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
+ "the found tracker uninstalled already",
+ this));
+ return E_INVALIDARG;
+ }
+ tracker.UnadviseSink();
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::UnadviseMouseSink(), succeeded", this));
+ return S_OK;
+}
+
+// static
+nsresult TSFTextStore::OnFocusChange(bool aGotFocus,
+ nsWindowBase* aFocusedWidget,
+ const InputContext& aContext) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ (" TSFTextStore::OnFocusChange(aGotFocus=%s, "
+ "aFocusedWidget=0x%p, aContext=%s), "
+ "sThreadMgr=0x%p, sEnabledTextStore=0x%p",
+ GetBoolName(aGotFocus), aFocusedWidget,
+ mozilla::ToString(aContext).c_str(), sThreadMgr.get(),
+ sEnabledTextStore.get()));
+
+ if (NS_WARN_IF(!IsInTSFMode())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
+ bool hasFocus = ThinksHavingFocus();
+ RefPtr<TSFTextStore> oldTextStore = sEnabledTextStore.forget();
+
+ // If currently oldTextStore still has focus, notifies TSF of losing focus.
+ if (hasFocus) {
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ DebugOnly<HRESULT> hr = threadMgr->AssociateFocus(
+ oldTextStore->mWidget->GetWindowHandle(), nullptr,
+ getter_AddRefs(prevFocusedDocumentMgr));
+ NS_ASSERTION(SUCCEEDED(hr), "Disassociating focus failed");
+ NS_ASSERTION(prevFocusedDocumentMgr == oldTextStore->mDocumentMgr,
+ "different documentMgr has been associated with the window");
+ }
+
+ // Even if there was a focused TextStore, we won't use it with new focused
+ // editor. So, release it now.
+ if (oldTextStore) {
+ oldTextStore->Destroy();
+ }
+
+ if (NS_WARN_IF(!sThreadMgr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::OnFocusChange() FAILED, due to "
+ "sThreadMgr being destroyed during calling "
+ "ITfThreadMgr::AssociateFocus()"));
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_WARN_IF(sEnabledTextStore)) {
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::OnFocusChange() FAILED, due to "
+ "nested event handling has created another focused TextStore during "
+ "calling ITfThreadMgr::AssociateFocus()"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this is a notification of blur, move focus to the dummy document
+ // manager.
+ if (!aGotFocus || !aContext.mIMEState.IsEditable()) {
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ RefPtr<ITfDocumentMgr> disabledDocumentMgr = sDisabledDocumentMgr;
+ HRESULT hr = threadMgr->SetFocus(disabledDocumentMgr);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::OnFocusChange() FAILED due to "
+ "ITfThreadMgr::SetFocus() failure"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+ }
+
+ // If an editor is getting focus, create new TextStore and set focus.
+ if (NS_WARN_IF(!CreateAndSetFocus(aFocusedWidget, aContext))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::OnFocusChange() FAILED due to "
+ "ITfThreadMgr::CreateAndSetFocus() failure"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+// static
+void TSFTextStore::EnsureToDestroyAndReleaseEnabledTextStoreIf(
+ RefPtr<TSFTextStore>& aTextStore) {
+ aTextStore->Destroy();
+ if (sEnabledTextStore == aTextStore) {
+ sEnabledTextStore = nullptr;
+ }
+ aTextStore = nullptr;
+}
+
+// static
+bool TSFTextStore::CreateAndSetFocus(nsWindowBase* aFocusedWidget,
+ const InputContext& aContext) {
+ // TSF might do something which causes that we need to access static methods
+ // of TSFTextStore. At that time, sEnabledTextStore may be necessary.
+ // So, we should set sEnabledTextStore directly.
+ RefPtr<TSFTextStore> textStore = new TSFTextStore();
+ sEnabledTextStore = textStore;
+ if (NS_WARN_IF(!textStore->Init(aFocusedWidget, aContext))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "TSFTextStore::Init() failure"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ RefPtr<ITfDocumentMgr> newDocMgr = textStore->mDocumentMgr;
+ if (NS_WARN_IF(!newDocMgr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "invalid TSFTextStore::mDocumentMgr"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (aContext.mIMEState.mEnabled == IMEEnabled::Password) {
+ MarkContextAsKeyboardDisabled(textStore->mContext);
+ RefPtr<ITfContext> topContext;
+ newDocMgr->GetTop(getter_AddRefs(topContext));
+ if (topContext && topContext != textStore->mContext) {
+ MarkContextAsKeyboardDisabled(topContext);
+ }
+ }
+
+ HRESULT hr;
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ hr = threadMgr->SetFocus(newDocMgr);
+
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "ITfTheadMgr::SetFocus() failure"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (NS_WARN_IF(!sThreadMgr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "sThreadMgr being destroyed during calling "
+ "ITfTheadMgr::SetFocus()"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (NS_WARN_IF(sEnabledTextStore != textStore)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "creating TextStore has lost focus during calling "
+ "ITfThreadMgr::SetFocus()"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+
+ // Use AssociateFocus() for ensuring that any native focus event
+ // never steal focus from our documentMgr.
+ RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
+ hr = threadMgr->AssociateFocus(aFocusedWidget->GetWindowHandle(), newDocMgr,
+ getter_AddRefs(prevFocusedDocumentMgr));
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "ITfTheadMgr::AssociateFocus() failure"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (NS_WARN_IF(!sThreadMgr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "sThreadMgr being destroyed during calling "
+ "ITfTheadMgr::AssociateFocus()"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (NS_WARN_IF(sEnabledTextStore != textStore)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "creating TextStore has lost focus during calling "
+ "ITfTheadMgr::AssociateFocus()"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+
+ if (textStore->mSink) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ (" TSFTextStore::CreateAndSetFocus(), calling "
+ "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE) for 0x%p...",
+ textStore.get()));
+ RefPtr<ITextStoreACPSink> sink = textStore->mSink;
+ sink->OnLayoutChange(TS_LC_CREATE, TEXTSTORE_DEFAULT_VIEW);
+ if (NS_WARN_IF(sEnabledTextStore != textStore)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "creating TextStore has lost focus during calling "
+ "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE)"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ }
+ return true;
+}
+
+// static
+IMENotificationRequests TSFTextStore::GetIMENotificationRequests() {
+ if (!sEnabledTextStore || NS_WARN_IF(!sEnabledTextStore->mDocumentMgr)) {
+ // If there is no active text store, we don't need any notifications
+ // since there is no sink which needs notifications.
+ return IMENotificationRequests();
+ }
+
+ // Otherwise, requests all notifications since even if some of them may not
+ // be required by the sink of active TIP, active TIP may be changed and
+ // other TIPs may need all notifications.
+ // Note that Windows temporarily steal focus from active window if the main
+ // process which created the window becomes busy. In this case, we shouldn't
+ // commit composition since user may want to continue to compose the
+ // composition after becoming not busy. Therefore, we need notifications
+ // even during deactive.
+ // Be aware, we don't need to check actual focused text store. For example,
+ // MS-IME for Japanese handles focus messages by themselves and sets focused
+ // text store to nullptr when the process is being inactivated. However,
+ // we still need to reuse sEnabledTextStore if the process is activated and
+ // focused element isn't changed. Therefore, if sEnabledTextStore isn't
+ // nullptr, we need to keep notifying the sink even when it is not focused
+ // text store for the thread manager.
+ return IMENotificationRequests(
+ IMENotificationRequests::NOTIFY_TEXT_CHANGE |
+ IMENotificationRequests::NOTIFY_POSITION_CHANGE |
+ IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR |
+ IMENotificationRequests::NOTIFY_DURING_DEACTIVE);
+}
+
+nsresult TSFTextStore::OnTextChangeInternal(
+ const IMENotification& aIMENotification) {
+ const TextChangeDataBase& textChangeData = aIMENotification.mTextChangeData;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnTextChangeInternal(aIMENotification={ "
+ "mMessage=0x%08X, mTextChangeData=%s }), "
+ "mDestroyed=%s, mSink=0x%p, mSinkMask=%s, "
+ "mComposition=%s",
+ this, aIMENotification.mMessage,
+ mozilla::ToString(textChangeData).c_str(), GetBoolName(mDestroyed),
+ mSink.get(), GetSinkMaskNameStr(mSinkMask).get(),
+ ToString(mComposition).c_str()));
+
+ if (mDestroyed) {
+ // If this instance is already destroyed, we shouldn't notify TSF of any
+ // changes.
+ return NS_OK;
+ }
+
+ mDeferNotifyingTSF = false;
+
+ // Different from selection change, we don't modify anything with text
+ // change data. Therefore, if neither TSF not TIP wants text change
+ // notifications, we don't need to store the changes.
+ if (!mSink || !(mSinkMask & TS_AS_TEXT_CHANGE)) {
+ return NS_OK;
+ }
+
+ // Merge any text change data even if it's caused by composition.
+ mPendingTextChangeData.MergeWith(textChangeData);
+
+ MaybeFlushPendingNotifications();
+
+ return NS_OK;
+}
+
+void TSFTextStore::NotifyTSFOfTextChange() {
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(!IsReadLocked());
+ MOZ_ASSERT(mComposition.isNothing());
+ MOZ_ASSERT(mPendingTextChangeData.IsValid());
+
+ // If the text changes are caused only by composition, we don't need to
+ // notify TSF of the text changes.
+ if (mPendingTextChangeData.mCausedOnlyByComposition) {
+ mPendingTextChangeData.Clear();
+ return;
+ }
+
+ // First, forget cached selection.
+ mSelectionForTSF.reset();
+
+ // For making it safer, we should check if there is a valid sink to receive
+ // text change notification.
+ if (NS_WARN_IF(!mSink) || NS_WARN_IF(!(mSinkMask & TS_AS_TEXT_CHANGE))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
+ "mSink is not ready to call ITextStoreACPSink::OnTextChange()...",
+ this));
+ mPendingTextChangeData.Clear();
+ return;
+ }
+
+ if (NS_WARN_IF(!mPendingTextChangeData.IsInInt32Range())) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
+ "offset is too big for calling "
+ "ITextStoreACPSink::OnTextChange()...",
+ this));
+ mPendingTextChangeData.Clear();
+ return;
+ }
+
+ TS_TEXTCHANGE textChange;
+ textChange.acpStart = static_cast<LONG>(mPendingTextChangeData.mStartOffset);
+ textChange.acpOldEnd =
+ static_cast<LONG>(mPendingTextChangeData.mRemovedEndOffset);
+ textChange.acpNewEnd =
+ static_cast<LONG>(mPendingTextChangeData.mAddedEndOffset);
+ mPendingTextChangeData.Clear();
+
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfTextChange(), calling "
+ "ITextStoreACPSink::OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
+ "acpNewEnd=%ld })...",
+ this, textChange.acpStart, textChange.acpOldEnd, textChange.acpNewEnd));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ sink->OnTextChange(0, &textChange);
+}
+
+nsresult TSFTextStore::OnSelectionChangeInternal(
+ const IMENotification& aIMENotification) {
+ const SelectionChangeDataBase& selectionChangeData =
+ aIMENotification.mSelectionChangeData;
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnSelectionChangeInternal("
+ "aIMENotification={ mSelectionChangeData=%s }), mDestroyed=%s, "
+ "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, "
+ "mComposition=%s",
+ this, mozilla::ToString(selectionChangeData).c_str(),
+ GetBoolName(mDestroyed), mSink.get(),
+ GetSinkMaskNameStr(mSinkMask).get(),
+ GetBoolName(mIsRecordingActionsWithoutLock),
+ ToString(mComposition).c_str()));
+
+ if (mDestroyed) {
+ // If this instance is already destroyed, we shouldn't notify TSF of any
+ // changes.
+ return NS_OK;
+ }
+
+ mDeferNotifyingTSF = false;
+
+ // Assign the new selection change data to the pending selection change data
+ // because only the latest selection data is necessary.
+ // Note that this is necessary to update mSelectionForTSF. Therefore, even if
+ // neither TSF nor TIP wants selection change notifications, we need to
+ // store the selection information.
+ mPendingSelectionChangeData.Assign(selectionChangeData);
+
+ // Flush remaining pending notifications here if it's possible.
+ MaybeFlushPendingNotifications();
+
+ // If we're available, we should create native caret instead of IMEHandler
+ // because we may have some cache to do it.
+ // Note that if we have composition, we'll notified composition-updated
+ // later so that we don't need to create native caret in such case.
+ if (!IsHandlingCompositionInContent() &&
+ IMEHandler::NeedsToCreateNativeCaret()) {
+ CreateNativeCaret();
+ }
+
+ return NS_OK;
+}
+
+void TSFTextStore::NotifyTSFOfSelectionChange() {
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(!IsReadLocked());
+ MOZ_ASSERT(mComposition.isNothing());
+ MOZ_ASSERT(mPendingSelectionChangeData.IsValid());
+
+ // If selection range isn't actually changed, we don't need to notify TSF
+ // of this selection change.
+ if (mSelectionForTSF.isNothing()) {
+ mSelectionForTSF.emplace(mPendingSelectionChangeData.mOffset,
+ mPendingSelectionChangeData.Length(),
+ mPendingSelectionChangeData.mReversed,
+ mPendingSelectionChangeData.GetWritingMode());
+ } else if (!mSelectionForTSF->SetSelection(
+ mPendingSelectionChangeData.mOffset,
+ mPendingSelectionChangeData.Length(),
+ mPendingSelectionChangeData.mReversed,
+ mPendingSelectionChangeData.GetWritingMode())) {
+ mPendingSelectionChangeData.Clear();
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), "
+ "selection isn't actually changed.",
+ this));
+ return;
+ }
+
+ mPendingSelectionChangeData.Clear();
+
+ if (!mSink || !(mSinkMask & TS_AS_SEL_CHANGE)) {
+ return;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), calling "
+ "ITextStoreACPSink::OnSelectionChange()...",
+ this));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ sink->OnSelectionChange();
+}
+
+nsresult TSFTextStore::OnLayoutChangeInternal() {
+ if (mDestroyed) {
+ // If this instance is already destroyed, we shouldn't notify TSF of any
+ // changes.
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(mSink, NS_ERROR_FAILURE);
+
+ mDeferNotifyingTSF = false;
+
+ nsresult rv = NS_OK;
+
+ // We need to notify TSF of layout change even if the document is locked.
+ // So, don't use MaybeFlushPendingNotifications() for flushing pending
+ // layout change.
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
+ "NotifyTSFOfLayoutChange()...",
+ this));
+ if (NS_WARN_IF(!NotifyTSFOfLayoutChange())) {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
+ "MaybeFlushPendingNotifications()...",
+ this));
+ MaybeFlushPendingNotifications();
+
+ return rv;
+}
+
+bool TSFTextStore::NotifyTSFOfLayoutChange() {
+ MOZ_ASSERT(!mDestroyed);
+
+ // If we're waiting a query of layout information from TIP, it means that
+ // we've returned TS_E_NOLAYOUT error.
+ bool returnedNoLayoutError = mHasReturnedNoLayoutError || mWaitingQueryLayout;
+
+ // If we returned TS_E_NOLAYOUT, TIP should query the computed layout again.
+ mWaitingQueryLayout = returnedNoLayoutError;
+
+ // For avoiding to call this method again at unlocking the document during
+ // calls of OnLayoutChange(), reset mHasReturnedNoLayoutError.
+ mHasReturnedNoLayoutError = false;
+
+ // Now, layout has been computed. We should notify mContentForTSF for
+ // making GetTextExt() and GetACPFromPoint() not return TS_E_NOLAYOUT.
+ if (mContentForTSF.isSome()) {
+ mContentForTSF->OnLayoutChanged();
+ }
+
+ if (IMEHandler::NeedsToCreateNativeCaret()) {
+ // If we're available, we should create native caret instead of IMEHandler
+ // because we may have some cache to do it.
+ CreateNativeCaret();
+ } else {
+ // Now, the caret position is different from ours. Destroy the native caret
+ // if we've create it only for GetTextExt().
+ IMEHandler::MaybeDestroyNativeCaret();
+ }
+
+ // This method should return true if either way succeeds.
+ bool ret = true;
+
+ if (mSink) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "calling ITextStoreACPSink::OnLayoutChange()...",
+ this));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ HRESULT hr = sink->OnLayoutChange(TS_LC_CHANGE, TEXTSTORE_DEFAULT_VIEW);
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "called ITextStoreACPSink::OnLayoutChange()",
+ this));
+ ret = SUCCEEDED(hr);
+ }
+
+ // The layout change caused by composition string change should cause
+ // calling ITfContextOwnerServices::OnLayoutChange() too.
+ if (returnedNoLayoutError && mContext) {
+ RefPtr<ITfContextOwnerServices> service;
+ mContext->QueryInterface(IID_ITfContextOwnerServices,
+ getter_AddRefs(service));
+ if (service) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "calling ITfContextOwnerServices::OnLayoutChange()...",
+ this));
+ HRESULT hr = service->OnLayoutChange();
+ ret = ret && SUCCEEDED(hr);
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "called ITfContextOwnerServices::OnLayoutChange()",
+ this));
+ }
+ }
+
+ if (!mWidget || mWidget->Destroyed()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "the widget is destroyed during calling OnLayoutChange()",
+ this));
+ return ret;
+ }
+
+ if (mDestroyed) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "the TSFTextStore instance is destroyed during calling "
+ "OnLayoutChange()",
+ this));
+ return ret;
+ }
+
+ // If we returned TS_E_NOLAYOUT again, we need another call of
+ // OnLayoutChange() later. So, let's wait a query from TIP.
+ if (mHasReturnedNoLayoutError) {
+ mWaitingQueryLayout = true;
+ }
+
+ if (!mWaitingQueryLayout) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "succeeded notifying TIP of our layout change",
+ this));
+ return ret;
+ }
+
+ // If we believe that TIP needs to retry to retrieve our layout information
+ // later, we should call it with ::PostMessage() hack.
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "posing MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE for calling "
+ "OnLayoutChange() again...",
+ this));
+ ::PostMessage(mWidget->GetWindowHandle(), MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE,
+ reinterpret_cast<WPARAM>(this), 0);
+
+ return true;
+}
+
+void TSFTextStore::NotifyTSFOfLayoutChangeAgain() {
+ // Don't notify TSF of layout change after destroyed.
+ if (mDestroyed) {
+ mWaitingQueryLayout = false;
+ return;
+ }
+
+ // Before preforming this method, TIP has accessed our layout information by
+ // itself. In such case, we don't need to call OnLayoutChange() anymore.
+ if (!mWaitingQueryLayout) {
+ return;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
+ "calling NotifyTSFOfLayoutChange()...",
+ this));
+ NotifyTSFOfLayoutChange();
+
+ // If TIP didn't retrieved our layout information during a call of
+ // NotifyTSFOfLayoutChange(), it means that the TIP already gave up to
+ // retry to retrieve layout information or doesn't necessary it anymore.
+ // But don't forget that the call may have caused returning TS_E_NOLAYOUT
+ // error again. In such case we still need to call OnLayoutChange() later.
+ if (!mHasReturnedNoLayoutError && mWaitingQueryLayout) {
+ mWaitingQueryLayout = false;
+ MOZ_LOG(sTextStoreLog, LogLevel::Warning,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
+ "called NotifyTSFOfLayoutChange() but TIP didn't retry to "
+ "retrieve the layout information",
+ this));
+ } else {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
+ "called NotifyTSFOfLayoutChange()",
+ this));
+ }
+}
+
+nsresult TSFTextStore::OnUpdateCompositionInternal() {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnUpdateCompositionInternal(), "
+ "mDestroyed=%s, mDeferNotifyingTSF=%s",
+ this, GetBoolName(mDestroyed), GetBoolName(mDeferNotifyingTSF)));
+
+ // There are nothing to do after destroyed.
+ if (mDestroyed) {
+ return NS_OK;
+ }
+
+ // Update cached data now because all pending events have been handled now.
+ if (mContentForTSF.isSome()) {
+ mContentForTSF->OnCompositionEventsHandled();
+ }
+
+ // If composition is completely finished both in TSF/TIP and the focused
+ // editor which may be in a remote process, we can clear the cache and don't
+ // have it until starting next composition.
+ if (mComposition.isNothing() && !IsHandlingCompositionInContent()) {
+ mDeferClearingContentForTSF = false;
+ }
+ mDeferNotifyingTSF = false;
+ MaybeFlushPendingNotifications();
+
+ // If we're available, we should create native caret instead of IMEHandler
+ // because we may have some cache to do it.
+ if (IMEHandler::NeedsToCreateNativeCaret()) {
+ CreateNativeCaret();
+ }
+
+ return NS_OK;
+}
+
+nsresult TSFTextStore::OnMouseButtonEventInternal(
+ const IMENotification& aIMENotification) {
+ if (mDestroyed) {
+ // If this instance is already destroyed, we shouldn't notify TSF of any
+ // events.
+ return NS_OK;
+ }
+
+ if (mMouseTrackers.IsEmpty()) {
+ return NS_OK;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnMouseButtonEventInternal("
+ "aIMENotification={ mEventMessage=%s, mOffset=%u, mCursorPos={ "
+ "mX=%d, mY=%d }, mCharRect={ mX=%d, mY=%d, mWidth=%d, mHeight=%d }, "
+ "mButton=%s, mButtons=%s, mModifiers=%s })",
+ this, ToChar(aIMENotification.mMouseButtonEventData.mEventMessage),
+ aIMENotification.mMouseButtonEventData.mOffset,
+ aIMENotification.mMouseButtonEventData.mCursorPos.mX,
+ aIMENotification.mMouseButtonEventData.mCursorPos.mY,
+ aIMENotification.mMouseButtonEventData.mCharRect.mX,
+ aIMENotification.mMouseButtonEventData.mCharRect.mY,
+ aIMENotification.mMouseButtonEventData.mCharRect.mWidth,
+ aIMENotification.mMouseButtonEventData.mCharRect.mHeight,
+ GetMouseButtonName(aIMENotification.mMouseButtonEventData.mButton),
+ GetMouseButtonsName(aIMENotification.mMouseButtonEventData.mButtons)
+ .get(),
+ GetModifiersName(aIMENotification.mMouseButtonEventData.mModifiers)
+ .get()));
+
+ uint32_t offset = aIMENotification.mMouseButtonEventData.mOffset;
+ if (offset > static_cast<uint32_t>(LONG_MAX)) {
+ return NS_OK;
+ }
+ nsIntRect charRect =
+ aIMENotification.mMouseButtonEventData.mCharRect.AsIntRect();
+ nsIntPoint cursorPos =
+ aIMENotification.mMouseButtonEventData.mCursorPos.AsIntPoint();
+ ULONG quadrant = 1;
+ if (charRect.Width() > 0) {
+ int32_t cursorXInChar = cursorPos.x - charRect.X();
+ quadrant = cursorXInChar * 4 / charRect.Width();
+ quadrant = (quadrant + 2) % 4;
+ }
+ ULONG edge = quadrant < 2 ? offset + 1 : offset;
+ DWORD buttonStatus = 0;
+ bool isMouseUp =
+ aIMENotification.mMouseButtonEventData.mEventMessage == eMouseUp;
+ if (!isMouseUp) {
+ switch (aIMENotification.mMouseButtonEventData.mButton) {
+ case MouseButton::ePrimary:
+ buttonStatus = MK_LBUTTON;
+ break;
+ case MouseButton::eMiddle:
+ buttonStatus = MK_MBUTTON;
+ break;
+ case MouseButton::eSecondary:
+ buttonStatus = MK_RBUTTON;
+ break;
+ }
+ }
+ if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_CONTROL) {
+ buttonStatus |= MK_CONTROL;
+ }
+ if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_SHIFT) {
+ buttonStatus |= MK_SHIFT;
+ }
+ for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
+ MouseTracker& tracker = mMouseTrackers[i];
+ if (!tracker.IsUsing() || tracker.Range().isNothing() ||
+ !tracker.Range()->IsOffsetInRange(offset)) {
+ continue;
+ }
+ if (tracker.OnMouseButtonEvent(edge - tracker.Range()->StartOffset(),
+ quadrant, buttonStatus)) {
+ return NS_SUCCESS_EVENT_CONSUMED;
+ }
+ }
+ return NS_OK;
+}
+
+void TSFTextStore::CreateNativeCaret() {
+ MOZ_ASSERT(!IMEHandler::IsA11yHandlingNativeCaret());
+
+ IMEHandler::MaybeDestroyNativeCaret();
+
+ // Don't create native caret after destroyed.
+ if (mDestroyed) {
+ return;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CreateNativeCaret(), mComposition=%s", this,
+ ToString(mComposition).c_str()));
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return;
+ }
+
+ WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, mWidget);
+ mWidget->InitEvent(queryCaretRectEvent);
+
+ WidgetQueryContentEvent::Options options;
+ // XXX If this is called without composition and the selection isn't
+ // collapsed, is it OK?
+ int64_t caretOffset = selectionForTSF->MaxOffset();
+ if (mComposition.isSome()) {
+ // If there is a composition, use insertion point relative query for
+ // deciding caret position because composition might be at different
+ // position where TSFTextStore believes it at.
+ options.mRelativeToInsertionPoint = true;
+ caretOffset -= mComposition->StartOffset();
+ } else if (!CanAccessActualContentDirectly()) {
+ // If TSF/TIP cannot access actual content directly, there may be pending
+ // text and/or selection changes which have not been notified TSF yet.
+ // Therefore, we should use relative to insertion point query since
+ // TSF/TIP computes the offset from the cached selection.
+ options.mRelativeToInsertionPoint = true;
+ caretOffset -= selectionForTSF->StartOffset();
+ }
+ queryCaretRectEvent.InitForQueryCaretRect(caretOffset, options);
+
+ DispatchEvent(queryCaretRectEvent);
+ if (NS_WARN_IF(queryCaretRectEvent.Failed())) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "eQueryCaretRect failure (offset=%d)",
+ this, caretOffset));
+ return;
+ }
+
+ if (!IMEHandler::CreateNativeCaret(static_cast<nsWindow*>(mWidget.get()),
+ queryCaretRectEvent.mReply->mRect)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "IMEHandler::CreateNativeCaret() failure",
+ this));
+ return;
+ }
+}
+
+void TSFTextStore::CommitCompositionInternal(bool aDiscard) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(aDiscard=%s), "
+ "mSink=0x%p, mContext=0x%p, mComposition=%s",
+ this, GetBoolName(aDiscard), mSink.get(), mContext.get(),
+ ToString(mComposition).c_str()));
+
+ // If the document is locked, TSF will fail to commit composition since
+ // TSF needs another document lock. So, let's put off the request.
+ // Note that TextComposition will commit composition in the focused editor
+ // with the latest composition string for web apps and waits asynchronous
+ // committing messages. Therefore, we can and need to perform this
+ // asynchronously.
+ if (IsReadLocked()) {
+ if (mDeferCommittingComposition || mDeferCancellingComposition) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(), "
+ "does nothing because already called and waiting unlock...",
+ this));
+ return;
+ }
+ if (aDiscard) {
+ mDeferCancellingComposition = true;
+ } else {
+ mDeferCommittingComposition = true;
+ }
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(), "
+ "putting off to request to %s composition after unlocking the "
+ "document",
+ this, aDiscard ? "cancel" : "commit"));
+ return;
+ }
+
+ if (mComposition.isSome() && aDiscard) {
+ LONG endOffset = mComposition->EndOffset();
+ mComposition->SetData(EmptyString());
+ // Note that don't notify TSF of text change after this is destroyed.
+ if (mSink && !mDestroyed) {
+ TS_TEXTCHANGE textChange;
+ textChange.acpStart = mComposition->StartOffset();
+ textChange.acpOldEnd = endOffset;
+ textChange.acpNewEnd = mComposition->StartOffset();
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::CommitCompositionInternal(), calling"
+ "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
+ "acpNewEnd=%ld })...",
+ this, textChange.acpStart, textChange.acpOldEnd,
+ textChange.acpNewEnd));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ sink->OnTextChange(0, &textChange);
+ }
+ }
+ // Terminate two contexts, the base context (mContext) and the top
+ // if the top context is not the same as the base context
+ RefPtr<ITfContext> context = mContext;
+ do {
+ if (context) {
+ RefPtr<ITfContextOwnerCompositionServices> services;
+ context->QueryInterface(IID_ITfContextOwnerCompositionServices,
+ getter_AddRefs(services));
+ if (services) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(), "
+ "requesting TerminateComposition() for the context 0x%p...",
+ this, context.get()));
+ services->TerminateComposition(nullptr);
+ }
+ }
+ if (context != mContext) break;
+ if (mDocumentMgr) mDocumentMgr->GetTop(getter_AddRefs(context));
+ } while (context != mContext);
+}
+
+static bool GetCompartment(IUnknown* pUnk, const GUID& aID,
+ ITfCompartment** aCompartment) {
+ if (!pUnk) return false;
+
+ RefPtr<ITfCompartmentMgr> compMgr;
+ pUnk->QueryInterface(IID_ITfCompartmentMgr, getter_AddRefs(compMgr));
+ if (!compMgr) return false;
+
+ return SUCCEEDED(compMgr->GetCompartment(aID, aCompartment)) &&
+ (*aCompartment) != nullptr;
+}
+
+// static
+void TSFTextStore::SetIMEOpenState(bool aState) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("TSFTextStore::SetIMEOpenState(aState=%s)", GetBoolName(aState)));
+
+ if (!sThreadMgr) {
+ return;
+ }
+
+ RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
+ if (NS_WARN_IF(!comp)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ (" TSFTextStore::SetIMEOpenState() FAILED due to"
+ "no compartment available"));
+ return;
+ }
+
+ VARIANT variant;
+ variant.vt = VT_I4;
+ variant.lVal = aState;
+ HRESULT hr = comp->SetValue(sClientId, &variant);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::SetIMEOpenState() FAILED due to "
+ "ITfCompartment::SetValue() failure, hr=0x%08X",
+ hr));
+ return;
+ }
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ (" TSFTextStore::SetIMEOpenState(), setting "
+ "0x%04X to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...",
+ variant.lVal));
+}
+
+// static
+bool TSFTextStore::GetIMEOpenState() {
+ if (!sThreadMgr) {
+ return false;
+ }
+
+ RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
+ if (NS_WARN_IF(!comp)) {
+ return false;
+ }
+
+ VARIANT variant;
+ ::VariantInit(&variant);
+ HRESULT hr = comp->GetValue(&variant);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::GetIMEOpenState() FAILED due to "
+ "ITfCompartment::GetValue() failure, hr=0x%08X",
+ hr));
+ return false;
+ }
+ // Until IME is open in this process, the result may be empty.
+ if (variant.vt == VT_EMPTY) {
+ return false;
+ }
+ if (NS_WARN_IF(variant.vt != VT_I4)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::GetIMEOpenState() FAILED due to "
+ "invalid result of ITfCompartment::GetValue()"));
+ ::VariantClear(&variant);
+ return false;
+ }
+
+ return variant.lVal != 0;
+}
+
+// static
+void TSFTextStore::SetInputContext(nsWindowBase* aWidget,
+ const InputContext& aContext,
+ const InputContextAction& aAction) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("TSFTextStore::SetInputContext(aWidget=%p, "
+ "aContext=%s, aAction.mFocusChange=%s), "
+ "sEnabledTextStore(0x%p)={ mWidget=0x%p }, ThinksHavingFocus()=%s",
+ aWidget, mozilla::ToString(aContext).c_str(),
+ GetFocusChangeName(aAction.mFocusChange), sEnabledTextStore.get(),
+ sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr,
+ GetBoolName(ThinksHavingFocus())));
+
+ switch (aAction.mFocusChange) {
+ case InputContextAction::WIDGET_CREATED:
+ // If this is called when the widget is created, there is nothing to do.
+ return;
+ case InputContextAction::FOCUS_NOT_CHANGED:
+ case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
+ if (NS_WARN_IF(!IsInTSFMode())) {
+ return;
+ }
+ // In these cases, `NOTIFY_IME_OF_FOCUS` won't be sent. Therefore,
+ // we need to reset text store for new state right now.
+ break;
+ default:
+ NS_WARNING_ASSERTION(IsInTSFMode(),
+ "Why is this called when TSF is disabled?");
+ if (sEnabledTextStore) {
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ textStore->SetInputScope(aContext.mHTMLInputType,
+ aContext.mHTMLInputInputmode,
+ aContext.mInPrivateBrowsing);
+ }
+ return;
+ }
+
+ // If focus isn't actually changed but the enabled state is changed,
+ // emulate the focus move.
+ if (!ThinksHavingFocus() && aContext.mIMEState.IsEditable()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ (" TSFTextStore::SetInputContent() emulates focus for IME "
+ "state change"));
+ OnFocusChange(true, aWidget, aContext);
+ } else if (ThinksHavingFocus() && !aContext.mIMEState.IsEditable()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ (" TSFTextStore::SetInputContent() emulates blur for IME "
+ "state change"));
+ OnFocusChange(false, aWidget, aContext);
+ }
+}
+
+// static
+void TSFTextStore::MarkContextAsKeyboardDisabled(ITfContext* aContext) {
+ VARIANT variant_int4_value1;
+ variant_int4_value1.vt = VT_I4;
+ variant_int4_value1.lVal = 1;
+
+ RefPtr<ITfCompartment> comp;
+ if (!GetCompartment(aContext, GUID_COMPARTMENT_KEYBOARD_DISABLED,
+ getter_AddRefs(comp))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::MarkContextAsKeyboardDisabled() failed"
+ "aContext=0x%p...",
+ aContext));
+ return;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("TSFTextStore::MarkContextAsKeyboardDisabled(), setting "
+ "to disable context 0x%p...",
+ aContext));
+ comp->SetValue(sClientId, &variant_int4_value1);
+}
+
+// static
+void TSFTextStore::MarkContextAsEmpty(ITfContext* aContext) {
+ VARIANT variant_int4_value1;
+ variant_int4_value1.vt = VT_I4;
+ variant_int4_value1.lVal = 1;
+
+ RefPtr<ITfCompartment> comp;
+ if (!GetCompartment(aContext, GUID_COMPARTMENT_EMPTYCONTEXT,
+ getter_AddRefs(comp))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::MarkContextAsEmpty() failed"
+ "aContext=0x%p...",
+ aContext));
+ return;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("TSFTextStore::MarkContextAsEmpty(), setting "
+ "to mark empty context 0x%p...",
+ aContext));
+ comp->SetValue(sClientId, &variant_int4_value1);
+}
+
+// static
+void TSFTextStore::Initialize() {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("TSFTextStore::Initialize() is called..."));
+
+ if (sThreadMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED due to already initialized"));
+ return;
+ }
+
+ bool enableTsf = Preferences::GetBool(kPrefNameEnableTSF, false);
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ (" TSFTextStore::Initialize(), TSF is %s",
+ enableTsf ? "enabled" : "disabled"));
+ if (!enableTsf) {
+ return;
+ }
+
+ RefPtr<ITfThreadMgr> threadMgr;
+ HRESULT hr =
+ ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ITfThreadMgr, getter_AddRefs(threadMgr));
+ if (FAILED(hr) || !threadMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to "
+ "create the thread manager, hr=0x%08X",
+ hr));
+ return;
+ }
+
+ hr = threadMgr->Activate(&sClientId);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to activate, hr=0x%08X", hr));
+ return;
+ }
+
+ RefPtr<ITfDocumentMgr> disabledDocumentMgr;
+ hr = threadMgr->CreateDocumentMgr(getter_AddRefs(disabledDocumentMgr));
+ if (FAILED(hr) || !disabledDocumentMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to create "
+ "a document manager for disabled mode, hr=0x%08X",
+ hr));
+ return;
+ }
+
+ RefPtr<ITfContext> disabledContext;
+ DWORD editCookie = 0;
+ hr = disabledDocumentMgr->CreateContext(
+ sClientId, 0, nullptr, getter_AddRefs(disabledContext), &editCookie);
+ if (FAILED(hr) || !disabledContext) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to create "
+ "a context for disabled mode, hr=0x%08X",
+ hr));
+ return;
+ }
+
+ MarkContextAsKeyboardDisabled(disabledContext);
+ MarkContextAsEmpty(disabledContext);
+
+ sThreadMgr = threadMgr;
+ sDisabledDocumentMgr = disabledDocumentMgr;
+ sDisabledContext = disabledContext;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ (" TSFTextStore::Initialize(), sThreadMgr=0x%p, "
+ "sClientId=0x%08X, sDisabledDocumentMgr=0x%p, sDisabledContext=%p",
+ sThreadMgr.get(), sClientId, sDisabledDocumentMgr.get(),
+ sDisabledContext.get()));
+}
+
+// static
+already_AddRefed<ITfThreadMgr> TSFTextStore::GetThreadMgr() {
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ return threadMgr.forget();
+}
+
+// static
+already_AddRefed<ITfMessagePump> TSFTextStore::GetMessagePump() {
+ static bool sInitialized = false;
+ if (!sThreadMgr) {
+ return nullptr;
+ }
+ if (sMessagePump) {
+ RefPtr<ITfMessagePump> messagePump = sMessagePump;
+ return messagePump.forget();
+ }
+ // If it tried to retrieve ITfMessagePump from sThreadMgr but it failed,
+ // we shouldn't retry it at every message due to performance reason.
+ // Although this shouldn't occur actually.
+ if (sInitialized) {
+ return nullptr;
+ }
+ sInitialized = true;
+
+ RefPtr<ITfMessagePump> messagePump;
+ HRESULT hr = sThreadMgr->QueryInterface(IID_ITfMessagePump,
+ getter_AddRefs(messagePump));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!messagePump)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::GetMessagePump() FAILED to "
+ "QI message pump from the thread manager, hr=0x%08X",
+ hr));
+ return nullptr;
+ }
+ sMessagePump = messagePump;
+ return messagePump.forget();
+}
+
+// static
+already_AddRefed<ITfDisplayAttributeMgr>
+TSFTextStore::GetDisplayAttributeMgr() {
+ RefPtr<ITfDisplayAttributeMgr> displayAttributeMgr;
+ if (sDisplayAttrMgr) {
+ displayAttributeMgr = sDisplayAttrMgr;
+ return displayAttributeMgr.forget();
+ }
+
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_TF_DisplayAttributeMgr, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ITfDisplayAttributeMgr, getter_AddRefs(displayAttributeMgr));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!displayAttributeMgr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::GetDisplayAttributeMgr() FAILED to create "
+ "a display attribute manager instance, hr=0x%08X",
+ hr));
+ return nullptr;
+ }
+ sDisplayAttrMgr = displayAttributeMgr;
+ return displayAttributeMgr.forget();
+}
+
+// static
+already_AddRefed<ITfCategoryMgr> TSFTextStore::GetCategoryMgr() {
+ RefPtr<ITfCategoryMgr> categoryMgr;
+ if (sCategoryMgr) {
+ categoryMgr = sCategoryMgr;
+ return categoryMgr.forget();
+ }
+ HRESULT hr =
+ ::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ITfCategoryMgr, getter_AddRefs(categoryMgr));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!categoryMgr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::GetCategoryMgr() FAILED to create "
+ "a category manager instance, hr=0x%08X",
+ hr));
+ return nullptr;
+ }
+ sCategoryMgr = categoryMgr;
+ return categoryMgr.forget();
+}
+
+// static
+already_AddRefed<ITfCompartment> TSFTextStore::GetCompartmentForOpenClose() {
+ if (sCompartmentForOpenClose) {
+ RefPtr<ITfCompartment> compartment = sCompartmentForOpenClose;
+ return compartment.forget();
+ }
+
+ if (!sThreadMgr) {
+ return nullptr;
+ }
+
+ RefPtr<ITfCompartmentMgr> compartmentMgr;
+ HRESULT hr = sThreadMgr->QueryInterface(IID_ITfCompartmentMgr,
+ getter_AddRefs(compartmentMgr));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartmentMgr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
+ "sThreadMgr not having ITfCompartmentMgr, hr=0x%08X",
+ hr));
+ return nullptr;
+ }
+
+ RefPtr<ITfCompartment> compartment;
+ hr = compartmentMgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
+ getter_AddRefs(compartment));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartment)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
+ "ITfCompartmentMgr::GetCompartment() failuere, hr=0x%08X",
+ hr));
+ return nullptr;
+ }
+
+ sCompartmentForOpenClose = compartment;
+ return compartment.forget();
+}
+
+// static
+already_AddRefed<ITfInputProcessorProfiles>
+TSFTextStore::GetInputProcessorProfiles() {
+ RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles;
+ if (sInputProcessorProfiles) {
+ inputProcessorProfiles = sInputProcessorProfiles;
+ return inputProcessorProfiles.forget();
+ }
+ // XXX MSDN documents that ITfInputProcessorProfiles is available only on
+ // desktop apps. However, there is no known way to obtain
+ // ITfInputProcessorProfileMgr instance without ITfInputProcessorProfiles
+ // instance.
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_TF_InputProcessorProfiles, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ITfInputProcessorProfiles, getter_AddRefs(inputProcessorProfiles));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!inputProcessorProfiles)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::GetInputProcessorProfiles() FAILED to create input "
+ "processor profiles, hr=0x%08X",
+ hr));
+ return nullptr;
+ }
+ sInputProcessorProfiles = inputProcessorProfiles;
+ return inputProcessorProfiles.forget();
+}
+
+// static
+void TSFTextStore::Terminate() {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info, ("TSFTextStore::Terminate()"));
+
+ TSFStaticSink::Shutdown();
+
+ sDisplayAttrMgr = nullptr;
+ sCategoryMgr = nullptr;
+ sEnabledTextStore = nullptr;
+ sDisabledDocumentMgr = nullptr;
+ sDisabledContext = nullptr;
+ sCompartmentForOpenClose = nullptr;
+ sInputProcessorProfiles = nullptr;
+ sClientId = 0;
+ if (sThreadMgr) {
+ sThreadMgr->Deactivate();
+ sThreadMgr = nullptr;
+ sMessagePump = nullptr;
+ sKeystrokeMgr = nullptr;
+ }
+}
+
+// static
+bool TSFTextStore::ProcessRawKeyMessage(const MSG& aMsg) {
+ if (!sThreadMgr) {
+ return false; // not in TSF mode
+ }
+ static bool sInitialized = false;
+ if (!sKeystrokeMgr) {
+ // If it tried to retrieve ITfKeystrokeMgr from sThreadMgr but it failed,
+ // we shouldn't retry it at every keydown nor keyup due to performance
+ // reason. Although this shouldn't occur actually.
+ if (sInitialized) {
+ return false;
+ }
+ sInitialized = true;
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr;
+ HRESULT hr = sThreadMgr->QueryInterface(IID_ITfKeystrokeMgr,
+ getter_AddRefs(keystrokeMgr));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!keystrokeMgr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::ProcessRawKeyMessage() FAILED to "
+ "QI keystroke manager from the thread manager, hr=0x%08X",
+ hr));
+ return false;
+ }
+ sKeystrokeMgr = keystrokeMgr.forget();
+ }
+
+ if (aMsg.message == WM_KEYDOWN) {
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ if (textStore) {
+ textStore->OnStartToHandleKeyMessage();
+ if (NS_WARN_IF(textStore != sEnabledTextStore)) {
+ // Let's handle the key message with new focused TSFTextStore.
+ textStore = sEnabledTextStore;
+ }
+ }
+ AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
+ AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
+ sHandlingKeyMsg = &aMsg;
+ sIsKeyboardEventDispatched = false;
+ BOOL eaten;
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
+ HRESULT hr = keystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten);
+ if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
+ return false;
+ }
+ hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten);
+ if (textStore) {
+ textStore->OnEndHandlingKeyMessage(!!eaten);
+ }
+ return SUCCEEDED(hr) &&
+ (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
+ }
+ if (aMsg.message == WM_KEYUP) {
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ if (textStore) {
+ textStore->OnStartToHandleKeyMessage();
+ if (NS_WARN_IF(textStore != sEnabledTextStore)) {
+ // Let's handle the key message with new focused TSFTextStore.
+ textStore = sEnabledTextStore;
+ }
+ }
+ AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
+ AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
+ sHandlingKeyMsg = &aMsg;
+ sIsKeyboardEventDispatched = false;
+ BOOL eaten;
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
+ HRESULT hr = keystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten);
+ if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
+ return false;
+ }
+ hr = keystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten);
+ if (textStore) {
+ textStore->OnEndHandlingKeyMessage(!!eaten);
+ }
+ return SUCCEEDED(hr) &&
+ (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
+ }
+ return false;
+}
+
+// static
+void TSFTextStore::ProcessMessage(nsWindowBase* aWindow, UINT aMessage,
+ WPARAM& aWParam, LPARAM& aLParam,
+ MSGResult& aResult) {
+ switch (aMessage) {
+ case WM_IME_SETCONTEXT:
+ // If a windowless plugin had focus and IME was handled on it, composition
+ // window was set the position. After that, even in TSF mode, WinXP keeps
+ // to use composition window at the position if the active IME is not
+ // aware TSF. For avoiding this issue, we need to hide the composition
+ // window here.
+ if (aWParam) {
+ aLParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
+ }
+ break;
+ case WM_ENTERIDLE:
+ // When an modal dialog such as a file picker is open, composition
+ // should be committed because IME might be used on it.
+ if (!IsComposingOn(aWindow)) {
+ break;
+ }
+ CommitComposition(false);
+ break;
+ case MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE: {
+ TSFTextStore* maybeTextStore = reinterpret_cast<TSFTextStore*>(aWParam);
+ if (maybeTextStore == sEnabledTextStore) {
+ RefPtr<TSFTextStore> textStore(maybeTextStore);
+ textStore->NotifyTSFOfLayoutChangeAgain();
+ }
+ break;
+ }
+ }
+}
+
+// static
+bool TSFTextStore::IsIMM_IMEActive() {
+ return TSFStaticSink::IsIMM_IMEActive();
+}
+
+// static
+bool TSFTextStore::IsMSJapaneseIMEActive() {
+ return TSFStaticSink::IsMSJapaneseIMEActive();
+}
+
+// static
+bool TSFTextStore::IsGoogleJapaneseInputActive() {
+ return TSFStaticSink::IsGoogleJapaneseInputActive();
+}
+
+/******************************************************************************
+ * TSFTextStore::Content
+ *****************************************************************************/
+
+const nsDependentSubstring TSFTextStore::Content::GetSelectedText() const {
+ if (NS_WARN_IF(mSelection.isNothing())) {
+ return nsDependentSubstring();
+ }
+ return GetSubstring(static_cast<uint32_t>(mSelection->StartOffset()),
+ static_cast<uint32_t>(mSelection->Length()));
+}
+
+const nsDependentSubstring TSFTextStore::Content::GetSubstring(
+ uint32_t aStart, uint32_t aLength) const {
+ return nsDependentSubstring(mText, aStart, aLength);
+}
+
+void TSFTextStore::Content::ReplaceSelectedTextWith(const nsAString& aString) {
+ if (NS_WARN_IF(mSelection.isNothing())) {
+ return;
+ }
+ ReplaceTextWith(mSelection->StartOffset(), mSelection->Length(), aString);
+}
+
+inline uint32_t FirstDifferentCharOffset(const nsAString& aStr1,
+ const nsAString& aStr2) {
+ MOZ_ASSERT(aStr1 != aStr2);
+ uint32_t i = 0;
+ uint32_t minLength = std::min(aStr1.Length(), aStr2.Length());
+ for (; i < minLength && aStr1[i] == aStr2[i]; i++) {
+ /* nothing to do */
+ }
+ return i;
+}
+
+void TSFTextStore::Content::ReplaceTextWith(LONG aStart, LONG aLength,
+ const nsAString& aReplaceString) {
+ MOZ_ASSERT(aStart >= 0);
+ MOZ_ASSERT(aLength >= 0);
+ const nsDependentSubstring replacedString = GetSubstring(
+ static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength));
+ if (aReplaceString != replacedString) {
+ uint32_t firstDifferentOffset = mMinModifiedOffset.valueOr(UINT32_MAX);
+ if (mComposition.isSome()) {
+ // Emulate text insertion during compositions, because during a
+ // composition, editor expects the whole composition string to
+ // be sent in eCompositionChange, not just the inserted part.
+ // The actual eCompositionChange will be sent in SetSelection
+ // or OnUpdateComposition.
+ MOZ_ASSERT(aStart >= mComposition->StartOffset());
+ MOZ_ASSERT(aStart + aLength <= mComposition->EndOffset());
+ mComposition->ReplaceData(
+ static_cast<uint32_t>(aStart - mComposition->StartOffset()),
+ static_cast<uint32_t>(aLength), aReplaceString);
+ // TIP may set composition string twice or more times during a document
+ // lock. Therefore, we should compute the first difference offset with
+ // mLastComposition.
+ if (mLastComposition.isNothing()) {
+ firstDifferentOffset = mComposition->StartOffset();
+ } else if (mComposition->DataRef() != mLastComposition->DataRef()) {
+ firstDifferentOffset =
+ mComposition->StartOffset() +
+ FirstDifferentCharOffset(mComposition->DataRef(),
+ mLastComposition->DataRef());
+ // The previous change to the composition string is canceled.
+ if (mMinModifiedOffset.isSome() &&
+ mMinModifiedOffset.value() >=
+ static_cast<uint32_t>(mComposition->StartOffset()) &&
+ mMinModifiedOffset.value() < firstDifferentOffset) {
+ mMinModifiedOffset = Some(firstDifferentOffset);
+ }
+ } else if (mMinModifiedOffset.isSome() &&
+ mMinModifiedOffset.value() < static_cast<uint32_t>(LONG_MAX) &&
+ mComposition->IsOffsetInRange(
+ static_cast<long>(mMinModifiedOffset.value()))) {
+ // The previous change to the composition string is canceled.
+ firstDifferentOffset = mComposition->EndOffset();
+ mMinModifiedOffset = Some(firstDifferentOffset);
+ }
+ mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
+ MOZ_LOG(
+ sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::Content::ReplaceTextWith(aStart=%d, "
+ "aLength=%d, aReplaceString=\"%s\"), mComposition=%s, "
+ "mLastComposition=%s, mMinModifiedOffset=%s, "
+ "firstDifferentOffset=%u",
+ this, aStart, aLength, GetEscapedUTF8String(aReplaceString).get(),
+ ToString(mComposition).c_str(), ToString(mLastComposition).c_str(),
+ ToString(mMinModifiedOffset).c_str(), firstDifferentOffset));
+ } else {
+ firstDifferentOffset =
+ static_cast<uint32_t>(aStart) +
+ FirstDifferentCharOffset(aReplaceString, replacedString);
+ }
+ mMinModifiedOffset =
+ mMinModifiedOffset.isNothing()
+ ? Some(firstDifferentOffset)
+ : Some(std::min(mMinModifiedOffset.value(), firstDifferentOffset));
+ mText.Replace(static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength),
+ aReplaceString);
+ }
+ // Selection should be collapsed at the end of the inserted string.
+ mSelection = Some(TSFTextStore::Selection(static_cast<uint32_t>(aStart) +
+ aReplaceString.Length()));
+}
+
+void TSFTextStore::Content::StartComposition(
+ ITfCompositionView* aCompositionView, const PendingAction& aCompStart,
+ bool aPreserveSelection) {
+ MOZ_ASSERT(aCompositionView);
+ MOZ_ASSERT(mComposition.isNothing());
+ MOZ_ASSERT(aCompStart.mType == PendingAction::Type::eCompositionStart);
+
+ mComposition.reset(); // Avoid new crash in the beta and nightly channels.
+ mComposition.emplace(
+ aCompositionView, aCompStart.mSelectionStart,
+ GetSubstring(static_cast<uint32_t>(aCompStart.mSelectionStart),
+ static_cast<uint32_t>(aCompStart.mSelectionLength)));
+ mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
+ if (!aPreserveSelection) {
+ // XXX Do we need to set a new writing-mode here when setting a new
+ // selection? Currently, we just preserve the existing value.
+ WritingMode writingMode =
+ mSelection.isNothing() ? WritingMode() : mSelection->GetWritingMode();
+ mSelection = Some(TSFTextStore::Selection(mComposition->StartOffset(),
+ mComposition->Length(), false,
+ writingMode));
+ }
+}
+
+void TSFTextStore::Content::RestoreCommittedComposition(
+ ITfCompositionView* aCompositionView,
+ const PendingAction& aCanceledCompositionEnd) {
+ MOZ_ASSERT(aCompositionView);
+ MOZ_ASSERT(mComposition.isNothing());
+ MOZ_ASSERT(aCanceledCompositionEnd.mType ==
+ PendingAction::Type::eCompositionEnd);
+ MOZ_ASSERT(
+ GetSubstring(
+ static_cast<uint32_t>(aCanceledCompositionEnd.mSelectionStart),
+ static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) ==
+ aCanceledCompositionEnd.mData);
+
+ // Restore the committed string as composing string.
+ mComposition.reset(); // Avoid new crash in the beta and nightly channels.
+ mComposition.emplace(aCompositionView,
+ aCanceledCompositionEnd.mSelectionStart,
+ aCanceledCompositionEnd.mData);
+ mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
+}
+
+void TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd) {
+ MOZ_ASSERT(mComposition.isSome());
+ MOZ_ASSERT(aCompEnd.mType == PendingAction::Type::eCompositionEnd);
+
+ if (mComposition.isNothing()) {
+ return; // Avoid new crash in the beta and nightly channels.
+ }
+
+ mSelection = Some(TSFTextStore::Selection(mComposition->StartOffset() +
+ aCompEnd.mData.Length()));
+ mComposition.reset();
+}
+
+/******************************************************************************
+ * TSFTextStore::MouseTracker
+ *****************************************************************************/
+
+TSFTextStore::MouseTracker::MouseTracker() : mCookie(kInvalidCookie) {}
+
+HRESULT
+TSFTextStore::MouseTracker::Init(TSFTextStore* aTextStore) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::Init(aTextStore=0x%p), "
+ "aTextStore->mMouseTrackers.Length()=%d",
+ this, aTextStore->mMouseTrackers.Length()));
+
+ if (&aTextStore->mMouseTrackers.LastElement() != this) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
+ "this is not the last element of mMouseTrackers",
+ this));
+ return E_FAIL;
+ }
+ if (aTextStore->mMouseTrackers.Length() > kInvalidCookie) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
+ "no new cookie available",
+ this));
+ return E_FAIL;
+ }
+ MOZ_ASSERT(!aTextStore->mMouseTrackers.IsEmpty(),
+ "This instance must be in TSFTextStore::mMouseTrackers");
+ mCookie = static_cast<DWORD>(aTextStore->mMouseTrackers.Length() - 1);
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::MouseTracker::AdviseSink(TSFTextStore* aTextStore,
+ ITfRangeACP* aTextRange,
+ ITfMouseSink* aMouseSink) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::AdviseSink(aTextStore=0x%p, "
+ "aTextRange=0x%p, aMouseSink=0x%p), mCookie=%d, mSink=0x%p",
+ this, aTextStore, aTextRange, aMouseSink, mCookie, mSink.get()));
+ MOZ_ASSERT(mCookie != kInvalidCookie, "This hasn't been initalized?");
+
+ if (mSink) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to already being used",
+ this));
+ return E_FAIL;
+ }
+
+ MOZ_ASSERT(mRange.isNothing());
+
+ LONG start = 0, length = 0;
+ HRESULT hr = aTextRange->GetExtent(&start, &length);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to failure of ITfRangeACP::GetExtent()",
+ this));
+ return hr;
+ }
+
+ if (start < 0 || length <= 0 || start + length > LONG_MAX) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to odd result of ITfRangeACP::GetExtent(), "
+ "start=%d, length=%d",
+ this, start, length));
+ return E_INVALIDARG;
+ }
+
+ nsAutoString textContent;
+ if (NS_WARN_IF(!aTextStore->GetCurrentText(textContent))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to failure of TSFTextStore::GetCurrentText()",
+ this));
+ return E_FAIL;
+ }
+
+ if (textContent.Length() <= static_cast<uint32_t>(start) ||
+ textContent.Length() < static_cast<uint32_t>(start + length)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to out of range, start=%d, length=%d, "
+ "textContent.Length()=%d",
+ this, start, length, textContent.Length()));
+ return E_INVALIDARG;
+ }
+
+ mRange.emplace(start, start + length);
+
+ mSink = aMouseSink;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink(), "
+ "succeeded, mRange=%s, textContent.Length()=%d",
+ this, ToString(mRange).c_str(), textContent.Length()));
+ return S_OK;
+}
+
+void TSFTextStore::MouseTracker::UnadviseSink() {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::UnadviseSink(), "
+ "mCookie=%d, mSink=0x%p, mRange=%s",
+ this, mCookie, mSink.get(), ToString(mRange).c_str()));
+ mSink = nullptr;
+ mRange.reset();
+}
+
+bool TSFTextStore::MouseTracker::OnMouseButtonEvent(ULONG aEdge,
+ ULONG aQuadrant,
+ DWORD aButtonStatus) {
+ MOZ_ASSERT(IsUsing(), "The caller must check before calling OnMouseEvent()");
+
+ BOOL eaten = FALSE;
+ RefPtr<ITfMouseSink> sink = mSink;
+ HRESULT hr = sink->OnMouseEvent(aEdge, aQuadrant, aButtonStatus, &eaten);
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::OnMouseEvent(aEdge=%d, "
+ "aQuadrant=%d, aButtonStatus=0x%08X), hr=0x%08X, eaten=%s",
+ this, aEdge, aQuadrant, aButtonStatus, hr, GetBoolName(!!eaten)));
+
+ return SUCCEEDED(hr) && eaten;
+}
+
+#ifdef DEBUG
+// static
+bool TSFTextStore::CurrentKeyboardLayoutHasIME() {
+ RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
+ TSFTextStore::GetInputProcessorProfiles();
+ if (!inputProcessorProfiles) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED due to "
+ "there is no input processor profiles instance"));
+ return false;
+ }
+ RefPtr<ITfInputProcessorProfileMgr> profileMgr;
+ HRESULT hr = inputProcessorProfiles->QueryInterface(
+ IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
+ if (FAILED(hr) || !profileMgr) {
+ // On Windows Vista or later, ImmIsIME() API always returns true.
+ // If we failed to obtain the profile manager, we cannot know if current
+ // keyboard layout has IME.
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to query "
+ "ITfInputProcessorProfileMgr"));
+ return false;
+ }
+
+ TF_INPUTPROCESSORPROFILE profile;
+ hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
+ if (hr == S_FALSE) {
+ return false; // not found or not active
+ }
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to retreive "
+ "active profile"));
+ return false;
+ }
+ return (profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR);
+}
+#endif // #ifdef DEBUG
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/TSFTextStore.h b/widget/windows/TSFTextStore.h
new file mode 100644
index 0000000000..7be04df276
--- /dev/null
+++ b/widget/windows/TSFTextStore.h
@@ -0,0 +1,1053 @@
+/* -*- 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 TSFTextStore_h_
+#define TSFTextStore_h_
+
+#include "nsCOMPtr.h"
+#include "nsIWidget.h"
+#include "nsString.h"
+#include "nsWindowBase.h"
+
+#include "WinUtils.h"
+#include "WritingModes.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextRange.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/widget/IMEData.h"
+
+#include <msctf.h>
+#include <textstor.h>
+
+// GUID_PROP_INPUTSCOPE is declared in inputscope.h using INIT_GUID.
+// With initguid.h, we get its instance instead of extern declaration.
+#ifdef INPUTSCOPE_INIT_GUID
+# include <initguid.h>
+#endif
+#ifdef TEXTATTRS_INIT_GUID
+# include <tsattrs.h>
+#endif
+#include <inputscope.h>
+
+// TSF InputScope, for earlier SDK 8
+#define IS_SEARCH static_cast<InputScope>(50)
+
+struct ITfThreadMgr;
+struct ITfDocumentMgr;
+struct ITfDisplayAttributeMgr;
+struct ITfCategoryMgr;
+class nsWindow;
+
+inline std::ostream& operator<<(std::ostream& aStream,
+ const TS_SELECTIONSTYLE& aSelectionStyle) {
+ const char* ase = "Unknown";
+ switch (aSelectionStyle.ase) {
+ case TS_AE_START:
+ ase = "TS_AE_START";
+ break;
+ case TS_AE_END:
+ ase = "TS_AE_END";
+ break;
+ case TS_AE_NONE:
+ ase = "TS_AE_NONE";
+ break;
+ }
+ aStream << "{ ase=" << ase << ", fInterimChar="
+ << (aSelectionStyle.fInterimChar ? "TRUE" : "FALSE") << " }";
+ return aStream;
+}
+
+inline std::ostream& operator<<(std::ostream& aStream,
+ const TS_SELECTION_ACP& aACP) {
+ aStream << "{ acpStart=" << aACP.acpStart << ", acpEnd=" << aACP.acpEnd
+ << ", style=" << mozilla::ToString(aACP.style).c_str() << " }";
+ return aStream;
+}
+
+namespace mozilla {
+namespace widget {
+
+class TSFStaticSink;
+struct MSGResult;
+
+/*
+ * Text Services Framework text store
+ */
+
+class TSFTextStore final : public ITextStoreACP,
+ public ITfContextOwnerCompositionSink,
+ public ITfMouseTrackerACP {
+ friend class TSFStaticSink;
+
+ private:
+ typedef IMENotification::SelectionChangeDataBase SelectionChangeDataBase;
+ typedef IMENotification::SelectionChangeData SelectionChangeData;
+ typedef IMENotification::TextChangeDataBase TextChangeDataBase;
+ typedef IMENotification::TextChangeData TextChangeData;
+
+ public: /*IUnknown*/
+ STDMETHODIMP QueryInterface(REFIID, void**);
+
+ NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFTextStore)
+
+ public: /*ITextStoreACP*/
+ STDMETHODIMP AdviseSink(REFIID, IUnknown*, DWORD);
+ STDMETHODIMP UnadviseSink(IUnknown*);
+ STDMETHODIMP RequestLock(DWORD, HRESULT*);
+ STDMETHODIMP GetStatus(TS_STATUS*);
+ STDMETHODIMP QueryInsert(LONG, LONG, ULONG, LONG*, LONG*);
+ STDMETHODIMP GetSelection(ULONG, ULONG, TS_SELECTION_ACP*, ULONG*);
+ STDMETHODIMP SetSelection(ULONG, const TS_SELECTION_ACP*);
+ STDMETHODIMP GetText(LONG, LONG, WCHAR*, ULONG, ULONG*, TS_RUNINFO*, ULONG,
+ ULONG*, LONG*);
+ STDMETHODIMP SetText(DWORD, LONG, LONG, const WCHAR*, ULONG, TS_TEXTCHANGE*);
+ STDMETHODIMP GetFormattedText(LONG, LONG, IDataObject**);
+ STDMETHODIMP GetEmbedded(LONG, REFGUID, REFIID, IUnknown**);
+ STDMETHODIMP QueryInsertEmbedded(const GUID*, const FORMATETC*, BOOL*);
+ STDMETHODIMP InsertEmbedded(DWORD, LONG, LONG, IDataObject*, TS_TEXTCHANGE*);
+ STDMETHODIMP RequestSupportedAttrs(DWORD, ULONG, const TS_ATTRID*);
+ STDMETHODIMP RequestAttrsAtPosition(LONG, ULONG, const TS_ATTRID*, DWORD);
+ STDMETHODIMP RequestAttrsTransitioningAtPosition(LONG, ULONG,
+ const TS_ATTRID*, DWORD);
+ STDMETHODIMP FindNextAttrTransition(LONG, LONG, ULONG, const TS_ATTRID*,
+ DWORD, LONG*, BOOL*, LONG*);
+ STDMETHODIMP RetrieveRequestedAttrs(ULONG, TS_ATTRVAL*, ULONG*);
+ STDMETHODIMP GetEndACP(LONG*);
+ STDMETHODIMP GetActiveView(TsViewCookie*);
+ STDMETHODIMP GetACPFromPoint(TsViewCookie, const POINT*, DWORD, LONG*);
+ STDMETHODIMP GetTextExt(TsViewCookie, LONG, LONG, RECT*, BOOL*);
+ STDMETHODIMP GetScreenExt(TsViewCookie, RECT*);
+ STDMETHODIMP GetWnd(TsViewCookie, HWND*);
+ STDMETHODIMP InsertTextAtSelection(DWORD, const WCHAR*, ULONG, LONG*, LONG*,
+ TS_TEXTCHANGE*);
+ STDMETHODIMP InsertEmbeddedAtSelection(DWORD, IDataObject*, LONG*, LONG*,
+ TS_TEXTCHANGE*);
+
+ public: /*ITfContextOwnerCompositionSink*/
+ STDMETHODIMP OnStartComposition(ITfCompositionView*, BOOL*);
+ STDMETHODIMP OnUpdateComposition(ITfCompositionView*, ITfRange*);
+ STDMETHODIMP OnEndComposition(ITfCompositionView*);
+
+ public: /*ITfMouseTrackerACP*/
+ STDMETHODIMP AdviseMouseSink(ITfRangeACP*, ITfMouseSink*, DWORD*);
+ STDMETHODIMP UnadviseMouseSink(DWORD);
+
+ public:
+ static void Initialize(void);
+ static void Terminate(void);
+
+ static bool ProcessRawKeyMessage(const MSG& aMsg);
+ static void ProcessMessage(nsWindowBase* aWindow, UINT aMessage,
+ WPARAM& aWParam, LPARAM& aLParam,
+ MSGResult& aResult);
+
+ static void SetIMEOpenState(bool);
+ static bool GetIMEOpenState(void);
+
+ static void CommitComposition(bool aDiscard) {
+ NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
+ if (!sEnabledTextStore) {
+ return;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ textStore->CommitCompositionInternal(aDiscard);
+ }
+
+ static void SetInputContext(nsWindowBase* aWidget,
+ const InputContext& aContext,
+ const InputContextAction& aAction);
+
+ static nsresult OnFocusChange(bool aGotFocus, nsWindowBase* aFocusedWidget,
+ const InputContext& aContext);
+ static nsresult OnTextChange(const IMENotification& aIMENotification) {
+ NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
+ if (!sEnabledTextStore) {
+ return NS_OK;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ return textStore->OnTextChangeInternal(aIMENotification);
+ }
+
+ static nsresult OnSelectionChange(const IMENotification& aIMENotification) {
+ NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
+ if (!sEnabledTextStore) {
+ return NS_OK;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ return textStore->OnSelectionChangeInternal(aIMENotification);
+ }
+
+ static nsresult OnLayoutChange() {
+ NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
+ if (!sEnabledTextStore) {
+ return NS_OK;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ return textStore->OnLayoutChangeInternal();
+ }
+
+ static nsresult OnUpdateComposition() {
+ NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
+ if (!sEnabledTextStore) {
+ return NS_OK;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ return textStore->OnUpdateCompositionInternal();
+ }
+
+ static nsresult OnMouseButtonEvent(const IMENotification& aIMENotification) {
+ NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
+ if (!sEnabledTextStore) {
+ return NS_OK;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ return textStore->OnMouseButtonEventInternal(aIMENotification);
+ }
+
+ static IMENotificationRequests GetIMENotificationRequests();
+
+ // Returns the address of the pointer so that the TSF automatic test can
+ // replace the system object with a custom implementation for testing.
+ // XXX TSF doesn't work now. Should we remove it?
+ static void* GetNativeData(uint32_t aDataType) {
+ switch (aDataType) {
+ case NS_NATIVE_TSF_THREAD_MGR:
+ Initialize(); // Apply any previous changes
+ return static_cast<void*>(&sThreadMgr);
+ case NS_NATIVE_TSF_CATEGORY_MGR:
+ return static_cast<void*>(&sCategoryMgr);
+ case NS_NATIVE_TSF_DISPLAY_ATTR_MGR:
+ return static_cast<void*>(&sDisplayAttrMgr);
+ default:
+ return nullptr;
+ }
+ }
+
+ static void* GetThreadManager() { return static_cast<void*>(sThreadMgr); }
+
+ static bool ThinksHavingFocus() {
+ return (sEnabledTextStore && sEnabledTextStore->mContext);
+ }
+
+ static bool IsInTSFMode() { return sThreadMgr != nullptr; }
+
+ static bool IsComposing() {
+ return (sEnabledTextStore && sEnabledTextStore->mComposition.isSome());
+ }
+
+ static bool IsComposingOn(nsWindowBase* aWidget) {
+ return (IsComposing() && sEnabledTextStore->mWidget == aWidget);
+ }
+
+ static nsWindowBase* GetEnabledWindowBase() {
+ return sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr;
+ }
+
+ /**
+ * Returns true if active keyboard layout is a legacy IMM-IME.
+ */
+ static bool IsIMM_IMEActive();
+
+ /**
+ * Returns true if active TIP is MS-IME for Japanese.
+ */
+ static bool IsMSJapaneseIMEActive();
+
+ /**
+ * Returns true if active TIP is Google Japanese Input.
+ * Note that if Google Japanese Input is installed as an IMM-IME,
+ * this return false even if Google Japanese Input is active.
+ * So, you may need to check IMMHandler::IsGoogleJapaneseInputActive() too.
+ */
+ static bool IsGoogleJapaneseInputActive();
+
+ /**
+ * Returns true if active TIP or IME is a black listed one and we should
+ * set input scope of URL bar to IS_DEFAULT rather than IS_URL.
+ */
+ static bool ShouldSetInputScopeOfURLBarToDefault();
+
+ /**
+ * Returns true if TSF may crash if GetSelection() returns E_FAIL.
+ */
+ static bool DoNotReturnErrorFromGetSelection();
+
+#ifdef DEBUG
+ // Returns true when keyboard layout has IME (TIP).
+ static bool CurrentKeyboardLayoutHasIME();
+#endif // #ifdef DEBUG
+
+ protected:
+ TSFTextStore();
+ ~TSFTextStore();
+
+ static bool CreateAndSetFocus(nsWindowBase* aFocusedWidget,
+ const InputContext& aContext);
+ static void EnsureToDestroyAndReleaseEnabledTextStoreIf(
+ RefPtr<TSFTextStore>& aTextStore);
+ static void MarkContextAsKeyboardDisabled(ITfContext* aContext);
+ static void MarkContextAsEmpty(ITfContext* aContext);
+
+ bool Init(nsWindowBase* aWidget, const InputContext& aContext);
+ void Destroy();
+ void ReleaseTSFObjects();
+
+ bool IsReadLock(DWORD aLock) const {
+ return (TS_LF_READ == (aLock & TS_LF_READ));
+ }
+ bool IsReadWriteLock(DWORD aLock) const {
+ return (TS_LF_READWRITE == (aLock & TS_LF_READWRITE));
+ }
+ bool IsReadLocked() const { return IsReadLock(mLock); }
+ bool IsReadWriteLocked() const { return IsReadWriteLock(mLock); }
+
+ // This is called immediately after a call of OnLockGranted() of mSink.
+ // Note that mLock isn't cleared yet when this is called.
+ void DidLockGranted();
+
+ bool GetScreenExtInternal(RECT& aScreenExt);
+ // If aDispatchCompositionChangeEvent is true, this method will dispatch
+ // compositionchange event if this is called during IME composing.
+ // aDispatchCompositionChangeEvent should be true only when this is called
+ // from SetSelection. Because otherwise, the compositionchange event should
+ // not be sent from here.
+ HRESULT SetSelectionInternal(const TS_SELECTION_ACP*,
+ bool aDispatchCompositionChangeEvent = false);
+ bool InsertTextAtSelectionInternal(const nsAString& aInsertStr,
+ TS_TEXTCHANGE* aTextChange);
+ void CommitCompositionInternal(bool);
+ HRESULT GetDisplayAttribute(ITfProperty* aProperty, ITfRange* aRange,
+ TF_DISPLAYATTRIBUTE* aResult);
+ HRESULT RestartCompositionIfNecessary(ITfRange* pRangeNew = nullptr);
+ class Composition;
+ HRESULT RestartComposition(Composition& aCurrentComposition,
+ ITfCompositionView* aCompositionView,
+ ITfRange* aNewRange);
+
+ // Following methods record composing action(s) to mPendingActions.
+ // They will be flushed FlushPendingActions().
+ HRESULT RecordCompositionStartAction(ITfCompositionView* aCompositionView,
+ ITfRange* aRange,
+ bool aPreserveSelection);
+ HRESULT RecordCompositionStartAction(ITfCompositionView* aCompositionView,
+ LONG aStart, LONG aLength,
+ bool aPreserveSelection);
+ HRESULT RecordCompositionUpdateAction();
+ HRESULT RecordCompositionEndAction();
+
+ // DispatchEvent() dispatches the event and if it may not be handled
+ // synchronously, this makes the instance not notify TSF of pending
+ // notifications until next notification from content.
+ void DispatchEvent(WidgetGUIEvent& aEvent);
+ void OnLayoutInformationAvaliable();
+
+ // FlushPendingActions() performs pending actions recorded in mPendingActions
+ // and clear it.
+ void FlushPendingActions();
+ // MaybeFlushPendingNotifications() performs pending notifications to TSF.
+ void MaybeFlushPendingNotifications();
+
+ nsresult OnTextChangeInternal(const IMENotification& aIMENotification);
+ nsresult OnSelectionChangeInternal(const IMENotification& aIMENotification);
+ nsresult OnMouseButtonEventInternal(const IMENotification& aIMENotification);
+ nsresult OnLayoutChangeInternal();
+ nsresult OnUpdateCompositionInternal();
+
+ // mPendingSelectionChangeData stores selection change data until notifying
+ // TSF of selection change. If two or more selection changes occur, this
+ // stores the latest selection change data because only it is necessary.
+ SelectionChangeData mPendingSelectionChangeData;
+
+ // mPendingTextChangeData stores one or more text change data until notifying
+ // TSF of text change. If two or more text changes occur, this merges
+ // every text change data.
+ TextChangeData mPendingTextChangeData;
+
+ void NotifyTSFOfTextChange();
+ void NotifyTSFOfSelectionChange();
+ bool NotifyTSFOfLayoutChange();
+ void NotifyTSFOfLayoutChangeAgain();
+
+ HRESULT HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount,
+ const TS_ATTRID* aFilterAttrs);
+ void SetInputScope(const nsString& aHTMLInputType,
+ const nsString& aHTMLInputInputmode,
+ bool aInPrivateBrowsing);
+
+ // Creates native caret over our caret. This method only works on desktop
+ // application. Otherwise, this does nothing.
+ void CreateNativeCaret();
+ // Destroys native caret if there is.
+ void MaybeDestroyNativeCaret();
+
+ /**
+ * MaybeHackNoErrorLayoutBugs() is a helper method of GetTextExt(). In
+ * strictly speaking, TSF is aware of asynchronous layout computation like us.
+ * However, Windows 10 version 1803 and older (including Windows 8.1 and
+ * older) Windows has a bug which is that the caller of GetTextExt() of TSF
+ * does not return TS_E_NOLAYOUT to TIP as is. Additionally, even after
+ * fixing this bug, some TIPs are not work well when we return TS_E_NOLAYOUT.
+ * For avoiding this issue, this method checks current Windows version and
+ * active TIP, and if in case we cannot return TS_E_NOLAYOUT, this modifies
+ * aACPStart and aACPEnd to making sure that they are in range of unmodified
+ * characters.
+ *
+ * @param aACPStart Initial value should be acpStart of GetTextExt().
+ * If this method returns true, this may be modified
+ * to be in range of unmodified characters.
+ * @param aACPEnd Initial value should be acpEnd of GetTextExt().
+ * If this method returns true, this may be modified
+ * to be in range of unmodified characters.
+ * And also this may become same as aACPStart.
+ * @return true if the caller shouldn't return TS_E_NOLAYOUT.
+ * In this case, this method modifies aACPStart and/or
+ * aASCPEnd to compute rectangle of unmodified characters.
+ * false if the caller can return TS_E_NOLAYOUT or
+ * we cannot have proper unmodified characters.
+ */
+ bool MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd);
+
+ // Holds the pointer to our current win32 widget
+ RefPtr<nsWindowBase> mWidget;
+ // mDispatcher is a helper class to dispatch composition events.
+ RefPtr<TextEventDispatcher> mDispatcher;
+ // Document manager for the currently focused editor
+ RefPtr<ITfDocumentMgr> mDocumentMgr;
+ // Edit cookie associated with the current editing context
+ DWORD mEditCookie;
+ // Editing context at the bottom of mDocumentMgr's context stack
+ RefPtr<ITfContext> mContext;
+ // Currently installed notification sink
+ RefPtr<ITextStoreACPSink> mSink;
+ // TS_AS_* mask of what events to notify
+ DWORD mSinkMask;
+ // 0 if not locked, otherwise TS_LF_* indicating the current lock
+ DWORD mLock;
+ // 0 if no lock is queued, otherwise TS_LF_* indicating the queue lock
+ DWORD mLockQueued;
+
+ uint32_t mHandlingKeyMessage;
+ void OnStartToHandleKeyMessage() {
+ // If we're starting to handle another key message during handling a
+ // key message, let's assume that the handling key message is handled by
+ // TIP and it sends another key message for hacking something.
+ // Let's try to dispatch a keyboard event now.
+ // FYI: All callers of this method grab this instance with local variable.
+ // So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(),
+ // we're safe to access any members.
+ if (!mDestroyed && sHandlingKeyMsg && !sIsKeyboardEventDispatched) {
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ }
+ ++mHandlingKeyMessage;
+ }
+ void OnEndHandlingKeyMessage(bool aIsProcessedByTSF) {
+ // If sHandlingKeyMsg has been handled by TSF or TIP and we're still
+ // alive, but we haven't dispatch keyboard event for it, let's fire it now.
+ // FYI: All callers of this method grab this instance with local variable.
+ // So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(),
+ // we're safe to access any members.
+ if (!mDestroyed && sHandlingKeyMsg && aIsProcessedByTSF &&
+ !sIsKeyboardEventDispatched) {
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ }
+ MOZ_ASSERT(mHandlingKeyMessage);
+ if (--mHandlingKeyMessage) {
+ return;
+ }
+ // If TSFTextStore instance is destroyed during handling key message(s),
+ // release all TSF objects when all nested key messages have been handled.
+ if (mDestroyed) {
+ ReleaseTSFObjects();
+ }
+ }
+
+ /**
+ * MaybeDispatchKeyboardEventAsProcessedByIME() tries to dispatch eKeyDown
+ * event or eKeyUp event for sHandlingKeyMsg and marking the dispatching
+ * event as "processed by IME". Note that if the document is locked, this
+ * just adds a pending action into the queue and sets
+ * sIsKeyboardEventDispatched to true.
+ */
+ void MaybeDispatchKeyboardEventAsProcessedByIME();
+
+ /**
+ * DispatchKeyboardEventAsProcessedByIME() dispatches an eKeyDown or
+ * eKeyUp event with NativeKey class and aMsg.
+ */
+ void DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg);
+
+ // Composition class stores a copy of the active composition string. Only
+ // the data is updated during an InsertTextAtSelection call if we have a
+ // composition. The data acts as a buffer until OnUpdateComposition is
+ // called and the data is flushed to editor through eCompositionChange.
+ // This allows all changes to be updated in batches to avoid inconsistencies
+ // and artifacts.
+ class Composition final : public OffsetAndData<LONG> {
+ public:
+ explicit Composition(ITfCompositionView* aCompositionView,
+ LONG aCompositionStartOffset,
+ const nsAString& aCompositionString)
+ : OffsetAndData<LONG>(aCompositionStartOffset, aCompositionString),
+ mView(aCompositionView) {}
+
+ ITfCompositionView* GetView() const { return mView; }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const Composition& aComposition) {
+ aStream << "{ mView=0x" << aComposition.mView.get()
+ << ", OffsetAndData<LONG>="
+ << static_cast<const OffsetAndData<LONG>&>(aComposition) << " }";
+ return aStream;
+ }
+
+ private:
+ RefPtr<ITfCompositionView> const mView;
+ };
+ // While the document is locked, we cannot dispatch any events which cause
+ // DOM events since the DOM events' handlers may modify the locked document.
+ // However, even while the document is locked, TSF may queries us.
+ // For that, TSFTextStore modifies mComposition even while the document is
+ // locked. With mComposition, query methods can returns the text content
+ // information.
+ Maybe<Composition> mComposition;
+
+ /**
+ * IsHandlingCompositionInParent() returns true if eCompositionStart is
+ * dispatched, but eCompositionCommit(AsIs) is not dispatched. This means
+ * that if composition is handled in a content process, this status indicates
+ * whether ContentCacheInParent has composition or not. On the other hand,
+ * if it's handled in the chrome process, this is exactly same as
+ * IsHandlingCompositionInContent().
+ */
+ bool IsHandlingCompositionInParent() const {
+ return mDispatcher && mDispatcher->IsComposing();
+ }
+
+ /**
+ * IsHandlingCompositionInContent() returns true if there is a composition in
+ * the focused editor which may be in a content process.
+ */
+ bool IsHandlingCompositionInContent() const {
+ return mDispatcher && mDispatcher->IsHandlingComposition();
+ }
+
+ class Selection {
+ public:
+ const TS_SELECTION_ACP& ACPRef() const { return mACP; }
+
+ explicit Selection(const TS_SELECTION_ACP& aSelection) {
+ SetSelection(aSelection);
+ }
+
+ explicit Selection(uint32_t aOffsetToCollapse) {
+ Collapse(aOffsetToCollapse);
+ }
+
+ explicit Selection(uint32_t aStart, uint32_t aLength, bool aReversed,
+ const WritingMode& aWritingMode) {
+ SetSelection(aStart, aLength, aReversed, aWritingMode);
+ }
+
+ void SetSelection(const TS_SELECTION_ACP& aSelection) {
+ mACP = aSelection;
+ // Selection end must be active in our editor.
+ if (mACP.style.ase != TS_AE_START) {
+ mACP.style.ase = TS_AE_END;
+ }
+ // We're not support interim char selection for now.
+ // XXX Probably, this is necessary for supporting South Asian languages.
+ mACP.style.fInterimChar = FALSE;
+ }
+
+ bool SetSelection(uint32_t aStart, uint32_t aLength, bool aReversed,
+ const WritingMode& aWritingMode) {
+ bool changed = mACP.acpStart != static_cast<LONG>(aStart) ||
+ mACP.acpEnd != static_cast<LONG>(aStart + aLength);
+ mACP.acpStart = static_cast<LONG>(aStart);
+ mACP.acpEnd = static_cast<LONG>(aStart + aLength);
+ mACP.style.ase = aReversed ? TS_AE_START : TS_AE_END;
+ mACP.style.fInterimChar = FALSE;
+ mWritingMode = aWritingMode;
+
+ return changed;
+ }
+
+ bool Collapsed() const { return mACP.acpStart == mACP.acpEnd; }
+
+ void Collapse(uint32_t aOffset) {
+ // XXX This does not update the selection's mWritingMode.
+ // If it is ever used to "collapse" to an entirely new location,
+ // we may need to fix that.
+ mACP.acpStart = mACP.acpEnd = static_cast<LONG>(aOffset);
+ mACP.style.ase = TS_AE_END;
+ mACP.style.fInterimChar = FALSE;
+ }
+
+ LONG MinOffset() const {
+ LONG min = std::min(mACP.acpStart, mACP.acpEnd);
+ MOZ_ASSERT(min >= 0);
+ return min;
+ }
+
+ LONG MaxOffset() const {
+ LONG max = std::max(mACP.acpStart, mACP.acpEnd);
+ MOZ_ASSERT(max >= 0);
+ return max;
+ }
+
+ LONG StartOffset() const {
+ MOZ_ASSERT(mACP.acpStart >= 0);
+ return mACP.acpStart;
+ }
+
+ LONG EndOffset() const {
+ MOZ_ASSERT(mACP.acpEnd >= 0);
+ return mACP.acpEnd;
+ }
+
+ LONG Length() const {
+ MOZ_ASSERT(mACP.acpEnd >= mACP.acpStart);
+ return std::abs(mACP.acpEnd - mACP.acpStart);
+ }
+
+ bool IsReversed() const { return mACP.style.ase == TS_AE_START; }
+
+ TsActiveSelEnd ActiveSelEnd() const { return mACP.style.ase; }
+
+ bool IsInterimChar() const { return mACP.style.fInterimChar != FALSE; }
+
+ WritingMode GetWritingMode() const { return mWritingMode; }
+
+ bool EqualsExceptDirection(const TS_SELECTION_ACP& aACP) const {
+ if (mACP.style.ase == aACP.style.ase) {
+ return mACP.acpStart == aACP.acpStart && mACP.acpEnd == aACP.acpEnd;
+ }
+ return mACP.acpStart == aACP.acpEnd && mACP.acpEnd == aACP.acpStart;
+ }
+
+ bool EqualsExceptDirection(
+ const SelectionChangeDataBase& aChangedSelection) const {
+ MOZ_ASSERT(aChangedSelection.IsValid());
+ return aChangedSelection.Length() == static_cast<uint32_t>(Length()) &&
+ aChangedSelection.mOffset == static_cast<uint32_t>(StartOffset());
+ }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const Selection& aSelection) {
+ aStream << "{ mACP=" << ToString(aSelection.mACP).c_str()
+ << ", mWritingMode=" << ToString(aSelection.mWritingMode).c_str()
+ << ", Collapsed()="
+ << (aSelection.Collapsed() ? "true" : "false")
+ << ", Length=" << aSelection.Length() << " }";
+ return aStream;
+ }
+
+ private:
+ TS_SELECTION_ACP mACP;
+ WritingMode mWritingMode;
+ };
+ // Don't access mSelection directly. Instead, Use SelectionForTSFRef().
+ // This is modified immediately when TSF requests to set selection and not
+ // updated by selection change in content until mContentForTSF is cleared.
+ Maybe<Selection> mSelectionForTSF;
+
+ /**
+ * Get the selection expected by TSF. If mSelectionForTSF is already valid,
+ * this just return the reference to it. Otherwise, this initializes it
+ * with eQuerySelectedText. Please check if the result is valid before
+ * actually using it.
+ * Note that this is also called by ContentForTSF().
+ */
+ Maybe<Selection>& SelectionForTSF();
+
+ struct PendingAction final {
+ enum class Type : uint8_t {
+ eCompositionStart,
+ eCompositionUpdate,
+ eCompositionEnd,
+ eSetSelection,
+ eKeyboardEvent,
+ };
+ Type mType;
+ // For eCompositionStart, eCompositionEnd and eSetSelection
+ LONG mSelectionStart;
+ // For eCompositionStart and eSetSelection
+ LONG mSelectionLength;
+ // For eCompositionStart, eCompositionUpdate and eCompositionEnd
+ nsString mData;
+ // For eCompositionUpdate
+ RefPtr<TextRangeArray> mRanges;
+ // For eKeyboardEvent
+ MSG mKeyMsg;
+ // For eSetSelection
+ bool mSelectionReversed;
+ // For eCompositionUpdate
+ bool mIncomplete;
+ // For eCompositionStart
+ bool mAdjustSelection;
+ };
+ // Items of mPendingActions are appended when TSF tells us to need to dispatch
+ // DOM composition events. However, we cannot dispatch while the document is
+ // locked because it can cause modifying the locked document. So, the pending
+ // actions should be performed when document lock is unlocked.
+ nsTArray<PendingAction> mPendingActions;
+
+ PendingAction* LastOrNewPendingCompositionUpdate() {
+ if (!mPendingActions.IsEmpty()) {
+ PendingAction& lastAction = mPendingActions.LastElement();
+ if (lastAction.mType == PendingAction::Type::eCompositionUpdate) {
+ return &lastAction;
+ }
+ }
+ PendingAction* newAction = mPendingActions.AppendElement();
+ newAction->mType = PendingAction::Type::eCompositionUpdate;
+ newAction->mRanges = new TextRangeArray();
+ newAction->mIncomplete = true;
+ return newAction;
+ }
+
+ /**
+ * IsLastPendingActionCompositionEndAt() checks whether the previous pending
+ * action is committing composition whose range starts from aStart and its
+ * length is aLength. In other words, this checks whether new composition
+ * which will replace same range as previous pending commit can be merged
+ * with the previous composition.
+ *
+ * @param aStart The inserted offset you expected.
+ * @param aLength The inserted text length you expected.
+ * @return true if the last pending action is
+ * eCompositionEnd and it inserted the text
+ * between aStart and aStart + aLength.
+ */
+ bool IsLastPendingActionCompositionEndAt(LONG aStart, LONG aLength) const {
+ if (mPendingActions.IsEmpty()) {
+ return false;
+ }
+ const PendingAction& pendingLastAction = mPendingActions.LastElement();
+ return pendingLastAction.mType == PendingAction::Type::eCompositionEnd &&
+ pendingLastAction.mSelectionStart == aStart &&
+ pendingLastAction.mData.Length() == static_cast<ULONG>(aLength);
+ }
+
+ bool IsPendingCompositionUpdateIncomplete() const {
+ if (mPendingActions.IsEmpty()) {
+ return false;
+ }
+ const PendingAction& lastAction = mPendingActions.LastElement();
+ return lastAction.mType == PendingAction::Type::eCompositionUpdate &&
+ lastAction.mIncomplete;
+ }
+
+ void CompleteLastActionIfStillIncomplete() {
+ if (!IsPendingCompositionUpdateIncomplete()) {
+ return;
+ }
+ RecordCompositionUpdateAction();
+ }
+
+ void RemoveLastCompositionUpdateActions() {
+ while (!mPendingActions.IsEmpty()) {
+ const PendingAction& lastAction = mPendingActions.LastElement();
+ if (lastAction.mType != PendingAction::Type::eCompositionUpdate) {
+ break;
+ }
+ mPendingActions.RemoveLastElement();
+ }
+ }
+
+ // When On*Composition() is called without document lock, we need to flush
+ // the recorded actions at quitting the method.
+ // AutoPendingActionAndContentFlusher class is usedful for it.
+ class MOZ_STACK_CLASS AutoPendingActionAndContentFlusher final {
+ public:
+ explicit AutoPendingActionAndContentFlusher(TSFTextStore* aTextStore)
+ : mTextStore(aTextStore) {
+ MOZ_ASSERT(!mTextStore->mIsRecordingActionsWithoutLock);
+ if (!mTextStore->IsReadWriteLocked()) {
+ mTextStore->mIsRecordingActionsWithoutLock = true;
+ }
+ }
+
+ ~AutoPendingActionAndContentFlusher() {
+ if (!mTextStore->mIsRecordingActionsWithoutLock) {
+ return;
+ }
+ mTextStore->FlushPendingActions();
+ mTextStore->mIsRecordingActionsWithoutLock = false;
+ }
+
+ private:
+ AutoPendingActionAndContentFlusher() {}
+
+ RefPtr<TSFTextStore> mTextStore;
+ };
+
+ class Content final {
+ public:
+ Content(TSFTextStore& aTSFTextStore, const nsAString& aText)
+ : mText(aText),
+ mLastComposition(aTSFTextStore.mComposition),
+ mComposition(aTSFTextStore.mComposition),
+ mSelection(aTSFTextStore.mSelectionForTSF) {}
+
+ void OnLayoutChanged() { mMinModifiedOffset.reset(); }
+
+ // OnCompositionEventsHandled() is called when all pending composition
+ // events are handled in the focused content which may be in a remote
+ // process.
+ void OnCompositionEventsHandled() { mLastComposition = mComposition; }
+
+ const nsDependentSubstring GetSelectedText() const;
+ const nsDependentSubstring GetSubstring(uint32_t aStart,
+ uint32_t aLength) const;
+ void ReplaceSelectedTextWith(const nsAString& aString);
+ void ReplaceTextWith(LONG aStart, LONG aLength, const nsAString& aString);
+
+ void StartComposition(ITfCompositionView* aCompositionView,
+ const PendingAction& aCompStart,
+ bool aPreserveSelection);
+ /**
+ * RestoreCommittedComposition() restores the committed string as
+ * composing string. If InsertTextAtSelection() or something is called
+ * before a call of OnStartComposition() or previous composition is
+ * committed and new composition is restarted to clean up the commited
+ * string, there is a pending compositionend. In this case, we need to
+ * cancel the pending compositionend and continue the composition.
+ *
+ * @param aCompositionView The composition view.
+ * @param aCanceledCompositionEnd The pending compositionend which is
+ * canceled for restarting the composition.
+ */
+ void RestoreCommittedComposition(
+ ITfCompositionView* aCompositionView,
+ const PendingAction& aCanceledCompositionEnd);
+ void EndComposition(const PendingAction& aCompEnd);
+
+ const nsString& TextRef() const { return mText; }
+ const Maybe<OffsetAndData<LONG>>& LastComposition() const {
+ return mLastComposition;
+ }
+ const Maybe<uint32_t>& MinModifiedOffset() const {
+ return mMinModifiedOffset;
+ }
+ const Maybe<StartAndEndOffsets<LONG>>& LatestCompositionRange() const {
+ return mLatestCompositionRange;
+ }
+
+ // Returns true if layout of the character at the aOffset has not been
+ // calculated.
+ bool IsLayoutChangedAt(uint32_t aOffset) const {
+ return IsLayoutChanged() && (mMinModifiedOffset.value() <= aOffset);
+ }
+ // Returns true if layout of the content has been changed, i.e., the new
+ // layout has not been calculated.
+ bool IsLayoutChanged() const { return mMinModifiedOffset.isSome(); }
+ bool HasOrHadComposition() const {
+ return mLatestCompositionRange.isSome();
+ }
+
+ Maybe<TSFTextStore::Composition>& Composition() { return mComposition; }
+ Maybe<TSFTextStore::Selection>& Selection() { return mSelection; }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const Content& aContent) {
+ aStream << "{ mText="
+ << PrintStringDetail(aContent.mText,
+ PrintStringDetail::kMaxLengthForEditor)
+ .get()
+ << ", mLastComposition=" << aContent.mLastComposition
+ << ", mLatestCompositionRange="
+ << aContent.mLatestCompositionRange
+ << ", mMinModifiedOffset=" << aContent.mMinModifiedOffset << " }";
+ return aStream;
+ }
+
+ private:
+ nsString mText;
+
+ // mLastComposition may store the composition string and its start offset
+ // when the document is locked. This is necessary to compute
+ // mMinTextModifiedOffset.
+ Maybe<OffsetAndData<LONG>> mLastComposition;
+
+ Maybe<TSFTextStore::Composition>& mComposition;
+ Maybe<TSFTextStore::Selection>& mSelection;
+
+ // The latest composition's start and end offset.
+ Maybe<StartAndEndOffsets<LONG>> mLatestCompositionRange;
+
+ // The minimum offset of modified part of the text.
+ Maybe<uint32_t> mMinModifiedOffset;
+ };
+ // mContentForTSF is cache of content. The information is expected by TSF
+ // and TIP. Therefore, this is useful for answering the query from TSF or
+ // TIP.
+ // This is initialized by ContentForTSF() automatically (therefore, don't
+ // access this member directly except at calling Clear(), IsInitialized(),
+ // IsLayoutChangeAfter() or IsLayoutChanged()).
+ // This is cleared when:
+ // - When there is no composition, the document is unlocked.
+ // - When there is a composition, all dispatched events are handled by
+ // the focused editor which may be in a remote process.
+ // So, if two compositions are created very quickly, this cache may not be
+ // cleared between eCompositionCommit(AsIs) and eCompositionStart.
+ Maybe<Content> mContentForTSF;
+
+ Maybe<Content>& ContentForTSF();
+
+ // CanAccessActualContentDirectly() returns true when TSF/TIP can access
+ // actual content directly. In other words, mContentForTSF and/or
+ // mSelectionForTSF doesn't cache content or they matches with actual
+ // contents due to no pending text/selection change notifications.
+ bool CanAccessActualContentDirectly() const;
+
+ // While mContentForTSF is valid, this returns the text stored by it.
+ // Otherwise, return the current text content retrieved by eQueryTextContent.
+ bool GetCurrentText(nsAString& aTextContent);
+
+ class MouseTracker final {
+ public:
+ static const DWORD kInvalidCookie = static_cast<DWORD>(-1);
+
+ MouseTracker();
+
+ HRESULT Init(TSFTextStore* aTextStore);
+ HRESULT AdviseSink(TSFTextStore* aTextStore, ITfRangeACP* aTextRange,
+ ITfMouseSink* aMouseSink);
+ void UnadviseSink();
+
+ bool IsUsing() const { return mSink != nullptr; }
+ DWORD Cookie() const { return mCookie; }
+ bool OnMouseButtonEvent(ULONG aEdge, ULONG aQuadrant, DWORD aButtonStatus);
+ const Maybe<StartAndEndOffsets<LONG>> Range() const { return mRange; }
+
+ private:
+ RefPtr<ITfMouseSink> mSink;
+ Maybe<StartAndEndOffsets<LONG>> mRange;
+ DWORD mCookie;
+ };
+ // mMouseTrackers is an array to store each information of installed
+ // ITfMouseSink instance.
+ nsTArray<MouseTracker> mMouseTrackers;
+
+ // The input scopes for this context, defaults to IS_DEFAULT.
+ nsTArray<InputScope> mInputScopes;
+
+ // Support retrieving attributes.
+ // TODO: We should support RightToLeft, perhaps.
+ enum {
+ // Used for result of GetRequestedAttrIndex()
+ eNotSupported = -1,
+
+ // Supported attributes
+ eInputScope = 0,
+ eTextVerticalWriting,
+ eTextOrientation,
+
+ // Count of the supported attributes
+ NUM_OF_SUPPORTED_ATTRS
+ };
+ bool mRequestedAttrs[NUM_OF_SUPPORTED_ATTRS];
+
+ int32_t GetRequestedAttrIndex(const TS_ATTRID& aAttrID);
+ TS_ATTRID GetAttrID(int32_t aIndex);
+
+ bool mRequestedAttrValues;
+
+ // If edit actions are being recorded without document lock, this is true.
+ // Otherwise, false.
+ bool mIsRecordingActionsWithoutLock;
+ // If GetTextExt() or GetACPFromPoint() is called and the layout hasn't been
+ // calculated yet, these methods return TS_E_NOLAYOUT. At that time,
+ // mHasReturnedNoLayoutError is set to true.
+ bool mHasReturnedNoLayoutError;
+ // Before calling ITextStoreACPSink::OnLayoutChange() and
+ // ITfContextOwnerServices::OnLayoutChange(), mWaitingQueryLayout is set to
+ // true. This is set to false when GetTextExt() or GetACPFromPoint() is
+ // called.
+ bool mWaitingQueryLayout;
+ // During the documet is locked, we shouldn't destroy the instance.
+ // If this is true, the instance will be destroyed after unlocked.
+ bool mPendingDestroy;
+ // If this is false, MaybeFlushPendingNotifications() will clear the
+ // mContentForTSF.
+ bool mDeferClearingContentForTSF;
+ // While the instance is dispatching events, the event may not be handled
+ // synchronously in e10s mode. So, in such case, in strictly speaking,
+ // we shouldn't query layout information. However, TS_E_NOLAYOUT bugs of
+ // ITextStoreAPC::GetTextExt() blocks us to behave ideally.
+ // For preventing it to be called, we should put off notifying TSF of
+ // anything until layout information becomes available.
+ bool mDeferNotifyingTSF;
+ // While the document is locked, committing composition always fails since
+ // TSF needs another document lock for modifying the composition, selection
+ // and etc. So, committing composition should be performed after the
+ // document is unlocked.
+ bool mDeferCommittingComposition;
+ bool mDeferCancellingComposition;
+ // Immediately after a call of Destroy(), mDestroyed becomes true. If this
+ // is true, the instance shouldn't grant any requests from the TIP anymore.
+ bool mDestroyed;
+ // While the instance is being destroyed, this is set to true for avoiding
+ // recursive Destroy() calls.
+ bool mBeingDestroyed;
+
+ // TSF thread manager object for the current application
+ static StaticRefPtr<ITfThreadMgr> sThreadMgr;
+ static already_AddRefed<ITfThreadMgr> GetThreadMgr();
+ // sMessagePump is QI'ed from sThreadMgr
+ static StaticRefPtr<ITfMessagePump> sMessagePump;
+
+ public:
+ // Expose GetMessagePump() for WinUtils.
+ static already_AddRefed<ITfMessagePump> GetMessagePump();
+
+ private:
+ // sKeystrokeMgr is QI'ed from sThreadMgr
+ static StaticRefPtr<ITfKeystrokeMgr> sKeystrokeMgr;
+ // TSF display attribute manager
+ static StaticRefPtr<ITfDisplayAttributeMgr> sDisplayAttrMgr;
+ static already_AddRefed<ITfDisplayAttributeMgr> GetDisplayAttributeMgr();
+ // TSF category manager
+ static StaticRefPtr<ITfCategoryMgr> sCategoryMgr;
+ static already_AddRefed<ITfCategoryMgr> GetCategoryMgr();
+ // Compartment for (Get|Set)IMEOpenState()
+ static StaticRefPtr<ITfCompartment> sCompartmentForOpenClose;
+ static already_AddRefed<ITfCompartment> GetCompartmentForOpenClose();
+
+ // Current text store which is managing a keyboard enabled editor (i.e.,
+ // editable editor). Currently only ONE TSFTextStore instance is ever used,
+ // although Create is called when an editor is focused and Destroy called
+ // when the focused editor is blurred.
+ static StaticRefPtr<TSFTextStore> sEnabledTextStore;
+
+ // For IME (keyboard) disabled state:
+ static StaticRefPtr<ITfDocumentMgr> sDisabledDocumentMgr;
+ static StaticRefPtr<ITfContext> sDisabledContext;
+
+ static StaticRefPtr<ITfInputProcessorProfiles> sInputProcessorProfiles;
+ static already_AddRefed<ITfInputProcessorProfiles>
+ GetInputProcessorProfiles();
+
+ // Handling key message.
+ static const MSG* sHandlingKeyMsg;
+
+ // TSF client ID for the current application
+ static DWORD sClientId;
+
+ // true if an eKeyDown or eKeyUp event for sHandlingKeyMsg has already
+ // been dispatched.
+ static bool sIsKeyboardEventDispatched;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef TSFTextStore_h_
diff --git a/widget/windows/TaskbarPreview.cpp b/widget/windows/TaskbarPreview.cpp
new file mode 100644
index 0000000000..f31e187528
--- /dev/null
+++ b/widget/windows/TaskbarPreview.cpp
@@ -0,0 +1,409 @@
+/* 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/. */
+
+#include "TaskbarPreview.h"
+#include <nsITaskbarPreviewController.h>
+#include <windows.h>
+
+#include <nsError.h>
+#include <nsCOMPtr.h>
+#include <nsIWidget.h>
+#include <nsServiceManagerUtils.h>
+
+#include "nsUXThemeData.h"
+#include "nsWindow.h"
+#include "nsAppShell.h"
+#include "TaskbarPreviewButton.h"
+#include "WinUtils.h"
+
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/Telemetry.h"
+
+// Defined in dwmapi in a header that needs a higher numbered _WINNT #define
+#ifndef DWM_SIT_DISPLAYFRAME
+# define DWM_SIT_DISPLAYFRAME 0x1
+#endif
+
+namespace mozilla {
+namespace widget {
+
+///////////////////////////////////////////////////////////////////////////////
+// TaskbarPreview
+
+TaskbarPreview::TaskbarPreview(ITaskbarList4* aTaskbar,
+ nsITaskbarPreviewController* aController,
+ HWND aHWND, nsIDocShell* aShell)
+ : mTaskbar(aTaskbar),
+ mController(aController),
+ mWnd(aHWND),
+ mVisible(false),
+ mDocShell(do_GetWeakReference(aShell)) {}
+
+TaskbarPreview::~TaskbarPreview() {
+ // Avoid dangling pointer
+ if (sActivePreview == this) sActivePreview = nullptr;
+
+ // Our subclass should have invoked DetachFromNSWindow already.
+ NS_ASSERTION(
+ !mWnd,
+ "TaskbarPreview::DetachFromNSWindow was not called before destruction");
+
+ // Make sure to release before potentially uninitializing COM
+ mTaskbar = nullptr;
+
+ ::CoUninitialize();
+}
+
+nsresult TaskbarPreview::Init() {
+ // TaskbarPreview may outlive the WinTaskbar that created it
+ if (FAILED(::CoInitialize(nullptr))) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ WindowHook* hook = GetWindowHook();
+ if (!hook) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return hook->AddMonitor(WM_DESTROY, MainWindowHook, this);
+}
+
+NS_IMETHODIMP
+TaskbarPreview::SetController(nsITaskbarPreviewController* aController) {
+ NS_ENSURE_ARG(aController);
+
+ mController = aController;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::GetController(nsITaskbarPreviewController** aController) {
+ NS_ADDREF(*aController = mController);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::GetTooltip(nsAString& aTooltip) {
+ aTooltip = mTooltip;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::SetTooltip(const nsAString& aTooltip) {
+ mTooltip = aTooltip;
+ return CanMakeTaskbarCalls() ? UpdateTooltip() : NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::SetVisible(bool visible) {
+ if (mVisible == visible) return NS_OK;
+ mVisible = visible;
+
+ // If the nsWindow has already been destroyed but the caller is still trying
+ // to use it then just pretend that everything succeeded. The caller doesn't
+ // actually have a way to detect this since it's the same case as when we
+ // CanMakeTaskbarCalls returns false.
+ if (!IsWindowAvailable()) return NS_OK;
+
+ return visible ? Enable() : Disable();
+}
+
+NS_IMETHODIMP
+TaskbarPreview::GetVisible(bool* visible) {
+ *visible = mVisible;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::SetActive(bool active) {
+ if (active)
+ sActivePreview = this;
+ else if (sActivePreview == this)
+ sActivePreview = nullptr;
+
+ return CanMakeTaskbarCalls() ? ShowActive(active) : NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::GetActive(bool* active) {
+ *active = sActivePreview == this;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::Invalidate() {
+ if (!mVisible) return NS_OK;
+
+ // DWM Composition is required for previews
+ if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) return NS_OK;
+
+ HWND previewWindow = PreviewWindow();
+ return FAILED(DwmInvalidateIconicBitmaps(previewWindow)) ? NS_ERROR_FAILURE
+ : NS_OK;
+}
+
+nsresult TaskbarPreview::UpdateTaskbarProperties() {
+ nsresult rv = UpdateTooltip();
+
+ // If we are the active preview and our window is the active window, restore
+ // our active state - otherwise some other non-preview window is now active
+ // and should be displayed as so.
+ if (sActivePreview == this) {
+ if (mWnd == ::GetActiveWindow()) {
+ nsresult rvActive = ShowActive(true);
+ if (NS_FAILED(rvActive)) rv = rvActive;
+ } else {
+ sActivePreview = nullptr;
+ }
+ }
+ return rv;
+}
+
+nsresult TaskbarPreview::Enable() {
+ nsresult rv = NS_OK;
+ if (CanMakeTaskbarCalls()) {
+ rv = UpdateTaskbarProperties();
+ } else if (IsWindowAvailable()) {
+ WindowHook* hook = GetWindowHook();
+ MOZ_ASSERT(hook,
+ "IsWindowAvailable() should have eliminated the null case.");
+ hook->AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
+ MainWindowHook, this);
+ }
+ return rv;
+}
+
+nsresult TaskbarPreview::Disable() {
+ if (!IsWindowAvailable()) {
+ // Window is already destroyed
+ return NS_OK;
+ }
+
+ WindowHook* hook = GetWindowHook();
+ MOZ_ASSERT(hook, "IsWindowAvailable() should have eliminated the null case.");
+ (void)hook->RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
+ MainWindowHook, this);
+
+ return NS_OK;
+}
+
+bool TaskbarPreview::IsWindowAvailable() const {
+ if (mWnd) {
+ nsWindow* win = WinUtils::GetNSWindowPtr(mWnd);
+ if (win && !win->Destroyed()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void TaskbarPreview::DetachFromNSWindow() {
+ if (WindowHook* hook = GetWindowHook()) {
+ hook->RemoveMonitor(WM_DESTROY, MainWindowHook, this);
+ }
+ mWnd = nullptr;
+}
+
+LRESULT
+TaskbarPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
+ switch (nMsg) {
+ case WM_DWMSENDICONICTHUMBNAIL: {
+ uint32_t width = HIWORD(lParam);
+ uint32_t height = LOWORD(lParam);
+ float aspectRatio = width / float(height);
+
+ nsresult rv;
+ float preferredAspectRatio;
+ rv = mController->GetThumbnailAspectRatio(&preferredAspectRatio);
+ if (NS_FAILED(rv)) break;
+
+ uint32_t thumbnailWidth = width;
+ uint32_t thumbnailHeight = height;
+
+ if (aspectRatio > preferredAspectRatio) {
+ thumbnailWidth = uint32_t(thumbnailHeight * preferredAspectRatio);
+ } else {
+ thumbnailHeight = uint32_t(thumbnailWidth / preferredAspectRatio);
+ }
+
+ DrawBitmap(thumbnailWidth, thumbnailHeight, false);
+ } break;
+ case WM_DWMSENDICONICLIVEPREVIEWBITMAP: {
+ uint32_t width, height;
+ nsresult rv;
+ rv = mController->GetWidth(&width);
+ if (NS_FAILED(rv)) break;
+ rv = mController->GetHeight(&height);
+ if (NS_FAILED(rv)) break;
+
+ double scale = StaticPrefs::layout_css_devPixelsPerPx();
+ if (scale <= 0.0) {
+ scale = WinUtils::LogToPhysFactor(PreviewWindow());
+ }
+ DrawBitmap(NSToIntRound(scale * width), NSToIntRound(scale * height),
+ true);
+ } break;
+ }
+ return ::DefWindowProcW(PreviewWindow(), nMsg, wParam, lParam);
+}
+
+bool TaskbarPreview::CanMakeTaskbarCalls() {
+ // If the nsWindow has already been destroyed and we know it but our caller
+ // clearly doesn't so we can't make any calls.
+ if (!mWnd) return false;
+ // Certain functions like SetTabOrder seem to require a visible window. During
+ // window close, the window seems to be hidden before being destroyed.
+ if (!::IsWindowVisible(mWnd)) return false;
+ if (mVisible) {
+ nsWindow* window = WinUtils::GetNSWindowPtr(mWnd);
+ NS_ASSERTION(window, "Could not get nsWindow from HWND");
+ return window ? window->HasTaskbarIconBeenCreated() : false;
+ }
+ return false;
+}
+
+WindowHook* TaskbarPreview::GetWindowHook() {
+ nsWindow* window = WinUtils::GetNSWindowPtr(mWnd);
+ NS_ASSERTION(window, "Cannot use taskbar previews in an embedded context!");
+
+ return window ? &window->GetWindowHook() : nullptr;
+}
+
+void TaskbarPreview::EnableCustomDrawing(HWND aHWND, bool aEnable) {
+ BOOL enabled = aEnable;
+ DwmSetWindowAttribute(aHWND, DWMWA_FORCE_ICONIC_REPRESENTATION, &enabled,
+ sizeof(enabled));
+
+ DwmSetWindowAttribute(aHWND, DWMWA_HAS_ICONIC_BITMAP, &enabled,
+ sizeof(enabled));
+}
+
+nsresult TaskbarPreview::UpdateTooltip() {
+ NS_ASSERTION(CanMakeTaskbarCalls() && mVisible,
+ "UpdateTooltip called on invisible tab preview");
+
+ if (FAILED(mTaskbar->SetThumbnailTooltip(PreviewWindow(), mTooltip.get())))
+ return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+void TaskbarPreview::DrawBitmap(uint32_t width, uint32_t height,
+ bool isPreview) {
+ nsresult rv;
+ nsCOMPtr<nsITaskbarPreviewCallback> callback =
+ do_CreateInstance("@mozilla.org/widget/taskbar-preview-callback;1", &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ ((TaskbarPreviewCallback*)callback.get())->SetPreview(this);
+
+ if (isPreview) {
+ ((TaskbarPreviewCallback*)callback.get())->SetIsPreview();
+ mController->RequestPreview(callback);
+ } else {
+ mController->RequestThumbnail(callback, width, height);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TaskbarPreviewCallback
+
+NS_IMPL_ISUPPORTS(TaskbarPreviewCallback, nsITaskbarPreviewCallback)
+
+/* void done (in nsISupports aCanvas, in boolean aDrawBorder); */
+NS_IMETHODIMP
+TaskbarPreviewCallback::Done(nsISupports* aCanvas, bool aDrawBorder) {
+ // We create and destroy TaskbarTabPreviews from front end code in response
+ // to TabOpen and TabClose events. Each TaskbarTabPreview creates and owns a
+ // proxy HWND which it hands to Windows as a tab identifier. When a tab
+ // closes, TaskbarTabPreview Disable() method is called by front end, which
+ // destroys the proxy window and clears mProxyWindow which is the HWND
+ // returned from PreviewWindow(). So, since this is async, we should check to
+ // be sure the tab is still alive before doing all this gfx work and making
+ // dwm calls. To accomplish this we check the result of PreviewWindow().
+ if (!aCanvas || !mPreview || !mPreview->PreviewWindow() ||
+ !mPreview->IsWindowAvailable()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aCanvas));
+ auto canvas = dom::HTMLCanvasElement::FromNodeOrNull(content);
+ if (!canvas) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<gfx::SourceSurface> source = canvas->GetSurfaceSnapshot();
+ if (!source) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<gfxWindowsSurface> target = new gfxWindowsSurface(
+ source->GetSize(), gfx::SurfaceFormat::A8R8G8B8_UINT32);
+ if (!target) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<gfx::DataSourceSurface> srcSurface = source->GetDataSurface();
+ RefPtr<gfxImageSurface> imageSurface = target->GetAsImageSurface();
+ if (!srcSurface || !imageSurface) {
+ return NS_ERROR_FAILURE;
+ }
+
+ gfx::DataSourceSurface::MappedSurface sourceMap;
+ srcSurface->Map(gfx::DataSourceSurface::READ, &sourceMap);
+ mozilla::gfx::CopySurfaceDataToPackedArray(
+ sourceMap.mData, imageSurface->Data(), srcSurface->GetSize(),
+ sourceMap.mStride, BytesPerPixel(srcSurface->GetFormat()));
+ srcSurface->Unmap();
+
+ HDC hDC = target->GetDC();
+ HBITMAP hBitmap = (HBITMAP)GetCurrentObject(hDC, OBJ_BITMAP);
+
+ DWORD flags = aDrawBorder ? DWM_SIT_DISPLAYFRAME : 0;
+ POINT pptClient = {0, 0};
+ HRESULT hr;
+ if (!mIsThumbnail) {
+ hr = DwmSetIconicLivePreviewBitmap(mPreview->PreviewWindow(), hBitmap,
+ &pptClient, flags);
+ } else {
+ hr = DwmSetIconicThumbnail(mPreview->PreviewWindow(), hBitmap, flags);
+ }
+ MOZ_ASSERT(SUCCEEDED(hr));
+ return NS_OK;
+}
+
+/* static */
+bool TaskbarPreview::MainWindowHook(void* aContext, HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam,
+ LRESULT* aResult) {
+ NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage() ||
+ nMsg == WM_DESTROY,
+ "Window hook proc called with wrong message");
+ NS_ASSERTION(aContext, "Null context in MainWindowHook");
+ if (!aContext) return false;
+ TaskbarPreview* preview = reinterpret_cast<TaskbarPreview*>(aContext);
+ if (nMsg == WM_DESTROY) {
+ // nsWindow is being destroyed
+ // We can't really do anything at this point including removing hooks
+ return false;
+ } else {
+ nsWindow* window = WinUtils::GetNSWindowPtr(preview->mWnd);
+ if (window) {
+ window->SetHasTaskbarIconBeenCreated();
+
+ if (preview->mVisible) preview->UpdateTaskbarProperties();
+ }
+ }
+ return false;
+}
+
+TaskbarPreview* TaskbarPreview::sActivePreview = nullptr;
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/TaskbarPreview.h b/widget/windows/TaskbarPreview.h
new file mode 100644
index 0000000000..ca21e3eeb8
--- /dev/null
+++ b/widget/windows/TaskbarPreview.h
@@ -0,0 +1,132 @@
+/* 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_TaskbarPreview_h__
+#define __mozilla_widget_TaskbarPreview_h__
+
+#include <windows.h>
+#include <shobjidl.h>
+#undef LogSeverity // SetupAPI.h #defines this as DWORD
+
+#include "mozilla/RefPtr.h"
+#include <nsITaskbarPreview.h>
+#include <nsITaskbarPreviewController.h>
+#include <nsString.h>
+#include <nsIWeakReferenceUtils.h>
+#include <nsIDocShell.h>
+#include "WindowHook.h"
+
+namespace mozilla {
+namespace widget {
+
+class TaskbarPreviewCallback;
+
+class TaskbarPreview : public nsITaskbarPreview {
+ public:
+ TaskbarPreview(ITaskbarList4* aTaskbar,
+ nsITaskbarPreviewController* aController, HWND aHWND,
+ nsIDocShell* aShell);
+ virtual nsresult Init();
+
+ friend class TaskbarPreviewCallback;
+
+ NS_DECL_NSITASKBARPREVIEW
+
+ protected:
+ virtual ~TaskbarPreview();
+
+ // Called to update ITaskbarList4 dependent properties
+ virtual nsresult UpdateTaskbarProperties();
+
+ // Invoked when the preview is made visible
+ virtual nsresult Enable();
+ // Invoked when the preview is made invisible
+ virtual nsresult Disable();
+
+ // Detaches this preview from the nsWindow instance it's tied to
+ virtual void DetachFromNSWindow();
+
+ // Determines if the window is available and a destroy has not yet started
+ bool IsWindowAvailable() const;
+
+ // Marks this preview as being active
+ virtual nsresult ShowActive(bool active) = 0;
+ // Gets a reference to the window used to handle the preview messages
+ virtual HWND& PreviewWindow() = 0;
+
+ // Window procedure for the PreviewWindow (hooked for window previews)
+ virtual LRESULT WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam);
+
+ // Returns whether or not the taskbar icon has been created for mWnd The
+ // ITaskbarList4 API requires that we wait until the icon has been created
+ // before we can call its methods.
+ bool CanMakeTaskbarCalls();
+
+ // Gets the WindowHook for the nsWindow
+ WindowHook* GetWindowHook();
+
+ // Enables/disables custom drawing for the given window
+ static void EnableCustomDrawing(HWND aHWND, bool aEnable);
+
+ // MSCOM Taskbar interface
+ RefPtr<ITaskbarList4> mTaskbar;
+ // Controller for this preview
+ nsCOMPtr<nsITaskbarPreviewController> mController;
+ // The HWND to the nsWindow that this object previews
+ HWND mWnd;
+ // Whether or not this preview is visible
+ bool mVisible;
+
+ private:
+ // Called when the tooltip should be updated
+ nsresult UpdateTooltip();
+
+ // Requests the controller to draw into a canvas of the given width and
+ // height. The resulting bitmap is sent to the DWM to display.
+ void DrawBitmap(uint32_t width, uint32_t height, bool isPreview);
+
+ // WindowHook procedure for hooking mWnd
+ static bool MainWindowHook(void* aContext, HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam, LRESULT* aResult);
+
+ // Docshell corresponding to the <window> the nsWindow contains
+ nsWeakPtr mDocShell;
+ nsString mTooltip;
+
+ // The preview currently marked as active in the taskbar. nullptr if no
+ // preview is active (some other window is).
+ static TaskbarPreview* sActivePreview;
+};
+
+/*
+ * Callback object TaskbarPreview hands to preview controllers when we
+ * request async thumbnail or live preview images. Controllers invoke
+ * this interface once they have aquired the requested image.
+ */
+class TaskbarPreviewCallback : public nsITaskbarPreviewCallback {
+ public:
+ TaskbarPreviewCallback() : mIsThumbnail(true) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITASKBARPREVIEWCALLBACK
+
+ void SetPreview(TaskbarPreview* aPreview) { mPreview = aPreview; }
+
+ void SetIsPreview() { mIsThumbnail = false; }
+
+ protected:
+ virtual ~TaskbarPreviewCallback() {}
+
+ private:
+ RefPtr<TaskbarPreview> mPreview;
+ bool mIsThumbnail;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_TaskbarPreview_h__ */
diff --git a/widget/windows/TaskbarPreviewButton.cpp b/widget/windows/TaskbarPreviewButton.cpp
new file mode 100644
index 0000000000..abbb1069a9
--- /dev/null
+++ b/widget/windows/TaskbarPreviewButton.cpp
@@ -0,0 +1,137 @@
+/* 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/. */
+
+#include <windows.h>
+#include <strsafe.h>
+
+#include "TaskbarWindowPreview.h"
+#include "TaskbarPreviewButton.h"
+#include "nsWindowGfx.h"
+#include <imgIContainer.h>
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS(TaskbarPreviewButton, nsITaskbarPreviewButton,
+ nsISupportsWeakReference)
+
+TaskbarPreviewButton::TaskbarPreviewButton(TaskbarWindowPreview* preview,
+ uint32_t index)
+ : mPreview(preview), mIndex(index) {}
+
+TaskbarPreviewButton::~TaskbarPreviewButton() { SetVisible(false); }
+
+NS_IMETHODIMP
+TaskbarPreviewButton::GetTooltip(nsAString& aTooltip) {
+ aTooltip = mTooltip;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::SetTooltip(const nsAString& aTooltip) {
+ mTooltip = aTooltip;
+ size_t destLength = sizeof Button().szTip / (sizeof Button().szTip[0]);
+ wchar_t* tooltip = &(Button().szTip[0]);
+ StringCchCopyNW(tooltip, destLength, mTooltip.get(), mTooltip.Length());
+ return Update();
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::GetDismissOnClick(bool* dismiss) {
+ *dismiss = (Button().dwFlags & THBF_DISMISSONCLICK) == THBF_DISMISSONCLICK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::SetDismissOnClick(bool dismiss) {
+ if (dismiss)
+ Button().dwFlags |= THBF_DISMISSONCLICK;
+ else
+ Button().dwFlags &= ~THBF_DISMISSONCLICK;
+ return Update();
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::GetHasBorder(bool* hasBorder) {
+ *hasBorder = (Button().dwFlags & THBF_NOBACKGROUND) != THBF_NOBACKGROUND;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::SetHasBorder(bool hasBorder) {
+ if (hasBorder)
+ Button().dwFlags &= ~THBF_NOBACKGROUND;
+ else
+ Button().dwFlags |= THBF_NOBACKGROUND;
+ return Update();
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::GetDisabled(bool* disabled) {
+ *disabled = (Button().dwFlags & THBF_DISABLED) == THBF_DISABLED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::SetDisabled(bool disabled) {
+ if (disabled)
+ Button().dwFlags |= THBF_DISABLED;
+ else
+ Button().dwFlags &= ~THBF_DISABLED;
+ return Update();
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::GetImage(imgIContainer** img) {
+ if (mImage)
+ NS_ADDREF(*img = mImage);
+ else
+ *img = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::SetImage(imgIContainer* img) {
+ if (Button().hIcon) ::DestroyIcon(Button().hIcon);
+ if (img) {
+ nsresult rv;
+ rv = nsWindowGfx::CreateIcon(
+ img, false, LayoutDeviceIntPoint(),
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon),
+ &Button().hIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ Button().hIcon = nullptr;
+ }
+ return Update();
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::GetVisible(bool* visible) {
+ *visible = (Button().dwFlags & THBF_HIDDEN) != THBF_HIDDEN;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::SetVisible(bool visible) {
+ if (visible)
+ Button().dwFlags &= ~THBF_HIDDEN;
+ else
+ Button().dwFlags |= THBF_HIDDEN;
+ return Update();
+}
+
+THUMBBUTTON& TaskbarPreviewButton::Button() {
+ return mPreview->mThumbButtons[mIndex];
+}
+
+nsresult TaskbarPreviewButton::Update() {
+ return mPreview->UpdateButton(mIndex);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/TaskbarPreviewButton.h b/widget/windows/TaskbarPreviewButton.h
new file mode 100644
index 0000000000..e13b2d6771
--- /dev/null
+++ b/widget/windows/TaskbarPreviewButton.h
@@ -0,0 +1,47 @@
+/* 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_TaskbarPreviewButton_h__
+#define __mozilla_widget_TaskbarPreviewButton_h__
+
+#include <windows.h>
+#include <shobjidl.h>
+#undef LogSeverity // SetupAPI.h #defines this as DWORD
+
+#include "mozilla/RefPtr.h"
+#include <nsITaskbarPreviewButton.h>
+#include <nsString.h>
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace widget {
+
+class TaskbarWindowPreview;
+class TaskbarPreviewButton : public nsITaskbarPreviewButton,
+ public nsSupportsWeakReference {
+ virtual ~TaskbarPreviewButton();
+
+ public:
+ TaskbarPreviewButton(TaskbarWindowPreview* preview, uint32_t index);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITASKBARPREVIEWBUTTON
+
+ private:
+ THUMBBUTTON& Button();
+ nsresult Update();
+
+ RefPtr<TaskbarWindowPreview> mPreview;
+ uint32_t mIndex;
+ nsString mTooltip;
+ nsCOMPtr<imgIContainer> mImage;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_TaskbarPreviewButton_h__ */
diff --git a/widget/windows/TaskbarTabPreview.cpp b/widget/windows/TaskbarTabPreview.cpp
new file mode 100644
index 0000000000..a19a6bf12e
--- /dev/null
+++ b/widget/windows/TaskbarTabPreview.cpp
@@ -0,0 +1,345 @@
+/* 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/. */
+
+#include "TaskbarTabPreview.h"
+#include "nsWindowGfx.h"
+#include "nsUXThemeData.h"
+#include "WinUtils.h"
+#include <nsITaskbarPreviewController.h>
+
+#define TASKBARPREVIEW_HWNDID L"TaskbarTabPreviewHwnd"
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS(TaskbarTabPreview, nsITaskbarTabPreview)
+
+const wchar_t* const kWindowClass = L"MozillaTaskbarPreviewClass";
+
+TaskbarTabPreview::TaskbarTabPreview(ITaskbarList4* aTaskbar,
+ nsITaskbarPreviewController* aController,
+ HWND aHWND, nsIDocShell* aShell)
+ : TaskbarPreview(aTaskbar, aController, aHWND, aShell),
+ mProxyWindow(nullptr),
+ mIcon(nullptr),
+ mRegistered(false) {}
+
+TaskbarTabPreview::~TaskbarTabPreview() {
+ if (mIcon) {
+ ::DestroyIcon(mIcon);
+ mIcon = nullptr;
+ }
+
+ // We need to ensure that proxy window disappears or else Bad Things happen.
+ if (mProxyWindow) Disable();
+
+ NS_ASSERTION(!mProxyWindow, "Taskbar proxy window was not destroyed!");
+
+ if (IsWindowAvailable()) {
+ DetachFromNSWindow();
+ } else {
+ mWnd = nullptr;
+ }
+}
+
+nsresult TaskbarTabPreview::Init() {
+ nsresult rv = TaskbarPreview::Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ WindowHook* hook = GetWindowHook();
+ if (!hook) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return hook->AddMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this);
+}
+
+nsresult TaskbarTabPreview::ShowActive(bool active) {
+ NS_ASSERTION(mVisible && CanMakeTaskbarCalls(),
+ "ShowActive called on invisible window or before taskbar calls "
+ "can be made for this window");
+ return FAILED(
+ mTaskbar->SetTabActive(active ? mProxyWindow : nullptr, mWnd, 0))
+ ? NS_ERROR_FAILURE
+ : NS_OK;
+}
+
+HWND& TaskbarTabPreview::PreviewWindow() { return mProxyWindow; }
+
+nativeWindow TaskbarTabPreview::GetHWND() { return mProxyWindow; }
+
+void TaskbarTabPreview::EnsureRegistration() {
+ NS_ASSERTION(mVisible && CanMakeTaskbarCalls(),
+ "EnsureRegistration called when it is not safe to do so");
+
+ (void)UpdateTaskbarProperties();
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::GetTitle(nsAString& aTitle) {
+ aTitle = mTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::SetTitle(const nsAString& aTitle) {
+ mTitle = aTitle;
+ return mVisible ? UpdateTitle() : NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::SetIcon(imgIContainer* icon) {
+ HICON hIcon = nullptr;
+ if (icon) {
+ nsresult rv;
+ rv = nsWindowGfx::CreateIcon(
+ icon, false, LayoutDeviceIntPoint(),
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), &hIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mIcon) ::DestroyIcon(mIcon);
+ mIcon = hIcon;
+ mIconImage = icon;
+ return mVisible ? UpdateIcon() : NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::GetIcon(imgIContainer** icon) {
+ NS_IF_ADDREF(*icon = mIconImage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::Move(nsITaskbarTabPreview* aNext) {
+ if (aNext == this) return NS_ERROR_INVALID_ARG;
+ mNext = aNext;
+ return CanMakeTaskbarCalls() ? UpdateNext() : NS_OK;
+}
+
+nsresult TaskbarTabPreview::UpdateTaskbarProperties() {
+ if (mRegistered) return NS_OK;
+
+ if (FAILED(mTaskbar->RegisterTab(mProxyWindow, mWnd)))
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = UpdateNext();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = TaskbarPreview::UpdateTaskbarProperties();
+ mRegistered = true;
+ return rv;
+}
+
+LRESULT
+TaskbarTabPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
+ RefPtr<TaskbarTabPreview> kungFuDeathGrip(this);
+ switch (nMsg) {
+ case WM_CREATE:
+ TaskbarPreview::EnableCustomDrawing(mProxyWindow, true);
+ return 0;
+ case WM_CLOSE:
+ mController->OnClose();
+ return 0;
+ case WM_ACTIVATE:
+ if (LOWORD(wParam) == WA_ACTIVE) {
+ // Activate the tab the user selected then restore the main window,
+ // keeping normal/max window state intact.
+ bool activateWindow;
+ nsresult rv = mController->OnActivate(&activateWindow);
+ if (NS_SUCCEEDED(rv) && activateWindow) {
+ nsWindow* win = WinUtils::GetNSWindowPtr(mWnd);
+ if (win) {
+ nsWindow* parent = win->GetTopLevelWindow(true);
+ if (parent) {
+ parent->Show(true);
+ }
+ }
+ }
+ }
+ return 0;
+ case WM_GETICON:
+ return (LRESULT)mIcon;
+ case WM_SYSCOMMAND:
+ // Send activation events to the top level window and select the proper
+ // tab through the controller.
+ if (wParam == SC_RESTORE || wParam == SC_MAXIMIZE) {
+ bool activateWindow;
+ nsresult rv = mController->OnActivate(&activateWindow);
+ if (NS_SUCCEEDED(rv) && activateWindow) {
+ // Note, restoring an iconic, maximized window here will only
+ // activate the maximized window. This is not a bug, it's default
+ // windows behavior.
+ ::SendMessageW(mWnd, WM_SYSCOMMAND, wParam, lParam);
+ }
+ return 0;
+ }
+ // Forward everything else to the top level window. Do not forward
+ // close since that's intended for the tab. When the preview proxy
+ // closes, we'll close the tab above.
+ return wParam == SC_CLOSE
+ ? ::DefWindowProcW(mProxyWindow, WM_SYSCOMMAND, wParam, lParam)
+ : ::SendMessageW(mWnd, WM_SYSCOMMAND, wParam, lParam);
+ return 0;
+ }
+ return TaskbarPreview::WndProc(nMsg, wParam, lParam);
+}
+
+/* static */
+LRESULT CALLBACK TaskbarTabPreview::GlobalWndProc(HWND hWnd, UINT nMsg,
+ WPARAM wParam,
+ LPARAM lParam) {
+ TaskbarTabPreview* preview(nullptr);
+ if (nMsg == WM_CREATE) {
+ CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam);
+ preview = reinterpret_cast<TaskbarTabPreview*>(cs->lpCreateParams);
+ if (!::SetPropW(hWnd, TASKBARPREVIEW_HWNDID, preview))
+ NS_ERROR("Could not associate native window with tab preview");
+ preview->mProxyWindow = hWnd;
+ } else {
+ preview = reinterpret_cast<TaskbarTabPreview*>(
+ ::GetPropW(hWnd, TASKBARPREVIEW_HWNDID));
+ if (nMsg == WM_DESTROY) ::RemovePropW(hWnd, TASKBARPREVIEW_HWNDID);
+ }
+
+ if (preview) return preview->WndProc(nMsg, wParam, lParam);
+ return ::DefWindowProcW(hWnd, nMsg, wParam, lParam);
+}
+
+nsresult TaskbarTabPreview::Enable() {
+ WNDCLASSW wc;
+ HINSTANCE module = GetModuleHandle(nullptr);
+
+ if (!GetClassInfoW(module, kWindowClass, &wc)) {
+ wc.style = 0;
+ wc.lpfnWndProc = GlobalWndProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = module;
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = (HBRUSH) nullptr;
+ wc.lpszMenuName = (LPCWSTR) nullptr;
+ wc.lpszClassName = kWindowClass;
+ RegisterClassW(&wc);
+ }
+ ::CreateWindowW(kWindowClass, L"TaskbarPreviewWindow",
+ WS_CAPTION | WS_SYSMENU, 0, 0, 200, 60, nullptr, nullptr,
+ module, this);
+ // GlobalWndProc will set mProxyWindow so that WM_CREATE can have a valid HWND
+ if (!mProxyWindow) return NS_ERROR_INVALID_ARG;
+
+ UpdateProxyWindowStyle();
+
+ nsresult rv = TaskbarPreview::Enable();
+ nsresult rvUpdate;
+ rvUpdate = UpdateTitle();
+ if (NS_FAILED(rvUpdate)) rv = rvUpdate;
+
+ rvUpdate = UpdateIcon();
+ if (NS_FAILED(rvUpdate)) rv = rvUpdate;
+
+ return rv;
+}
+
+nsresult TaskbarTabPreview::Disable() {
+ // TaskbarPreview::Disable assumes that mWnd is valid but this method can be
+ // called when it is null iff the nsWindow has already been destroyed and we
+ // are still visible for some reason during object destruction.
+ if (mWnd) TaskbarPreview::Disable();
+
+ if (FAILED(mTaskbar->UnregisterTab(mProxyWindow))) return NS_ERROR_FAILURE;
+ mRegistered = false;
+
+ // TaskbarPreview::WndProc will set mProxyWindow to null
+ if (!DestroyWindow(mProxyWindow)) return NS_ERROR_FAILURE;
+ mProxyWindow = nullptr;
+ return NS_OK;
+}
+
+void TaskbarTabPreview::DetachFromNSWindow() {
+ (void)SetVisible(false);
+ if (WindowHook* hook = GetWindowHook()) {
+ hook->RemoveMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this);
+ }
+ TaskbarPreview::DetachFromNSWindow();
+}
+
+/* static */
+bool TaskbarTabPreview::MainWindowHook(void* aContext, HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam,
+ LRESULT* aResult) {
+ if (nMsg == WM_WINDOWPOSCHANGED) {
+ TaskbarTabPreview* preview = reinterpret_cast<TaskbarTabPreview*>(aContext);
+ WINDOWPOS* pos = reinterpret_cast<WINDOWPOS*>(lParam);
+ if (SWP_FRAMECHANGED == (pos->flags & SWP_FRAMECHANGED))
+ preview->UpdateProxyWindowStyle();
+ } else {
+ MOZ_ASSERT_UNREACHABLE(
+ "Style changed hook fired on non-style changed "
+ "message");
+ }
+ return false;
+}
+
+void TaskbarTabPreview::UpdateProxyWindowStyle() {
+ if (!mProxyWindow) return;
+
+ DWORD minMaxMask = WS_MINIMIZE | WS_MAXIMIZE;
+ DWORD windowStyle = GetWindowLongW(mWnd, GWL_STYLE);
+
+ DWORD proxyStyle = GetWindowLongW(mProxyWindow, GWL_STYLE);
+ proxyStyle &= ~minMaxMask;
+ proxyStyle |= windowStyle & minMaxMask;
+ SetWindowLongW(mProxyWindow, GWL_STYLE, proxyStyle);
+
+ DWORD exStyle =
+ (WS_MAXIMIZE == (windowStyle & WS_MAXIMIZE)) ? WS_EX_TOOLWINDOW : 0;
+ SetWindowLongW(mProxyWindow, GWL_EXSTYLE, exStyle);
+}
+
+nsresult TaskbarTabPreview::UpdateTitle() {
+ NS_ASSERTION(mVisible, "UpdateTitle called on invisible preview");
+
+ if (!::SetWindowTextW(mProxyWindow, mTitle.get())) return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+nsresult TaskbarTabPreview::UpdateIcon() {
+ NS_ASSERTION(mVisible, "UpdateIcon called on invisible preview");
+
+ ::SendMessageW(mProxyWindow, WM_SETICON, ICON_SMALL, (LPARAM)mIcon);
+
+ return NS_OK;
+}
+
+nsresult TaskbarTabPreview::UpdateNext() {
+ NS_ASSERTION(CanMakeTaskbarCalls() && mVisible,
+ "UpdateNext called on invisible tab preview");
+ HWND hNext = nullptr;
+ if (mNext) {
+ bool visible;
+ nsresult rv = mNext->GetVisible(&visible);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Can only move next to enabled previews
+ if (!visible) return NS_ERROR_FAILURE;
+
+ hNext = (HWND)mNext->GetHWND();
+
+ // hNext must be registered with the taskbar if the call is to succeed
+ mNext->EnsureRegistration();
+ }
+ if (FAILED(mTaskbar->SetTabOrder(mProxyWindow, hNext)))
+ return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/TaskbarTabPreview.h b/widget/windows/TaskbarTabPreview.h
new file mode 100644
index 0000000000..9a15760557
--- /dev/null
+++ b/widget/windows/TaskbarTabPreview.h
@@ -0,0 +1,70 @@
+/* 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_TaskbarTabPreview_h__
+#define __mozilla_widget_TaskbarTabPreview_h__
+
+#include "nsITaskbarTabPreview.h"
+#include "TaskbarPreview.h"
+
+namespace mozilla {
+namespace widget {
+
+class TaskbarTabPreview : public nsITaskbarTabPreview, public TaskbarPreview {
+ virtual ~TaskbarTabPreview();
+
+ public:
+ TaskbarTabPreview(ITaskbarList4* aTaskbar,
+ nsITaskbarPreviewController* aController, HWND aHWND,
+ nsIDocShell* aShell);
+ virtual nsresult Init() override;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITASKBARTABPREVIEW
+ NS_FORWARD_NSITASKBARPREVIEW(TaskbarPreview::)
+
+ private:
+ virtual nsresult ShowActive(bool active);
+ virtual HWND& PreviewWindow();
+ virtual LRESULT WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam);
+ static LRESULT CALLBACK GlobalWndProc(HWND hWnd, UINT nMsg, WPARAM wParam,
+ LPARAM lParam);
+
+ virtual nsresult UpdateTaskbarProperties();
+ virtual nsresult Enable();
+ virtual nsresult Disable();
+ virtual void DetachFromNSWindow();
+
+ // WindowHook procedure for hooking mWnd
+ static bool MainWindowHook(void* aContext, HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam, LRESULT* aResult);
+
+ // Bug 520807 - we need to update the proxy window style based on the main
+ // window's style to workaround a bug with the way the DWM displays the
+ // previews.
+ void UpdateProxyWindowStyle();
+
+ nsresult UpdateTitle();
+ nsresult UpdateIcon();
+ nsresult UpdateNext();
+
+ // Handle to the toplevel proxy window
+ HWND mProxyWindow;
+ nsString mTitle;
+ nsCOMPtr<imgIContainer> mIconImage;
+ // Cached Windows icon of mIconImage
+ HICON mIcon;
+ // Preview that follows this preview in the taskbar (left-to-right order)
+ nsCOMPtr<nsITaskbarTabPreview> mNext;
+ // True if this preview has been registered with the taskbar
+ bool mRegistered;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_TaskbarTabPreview_h__ */
diff --git a/widget/windows/TaskbarWindowPreview.cpp b/widget/windows/TaskbarWindowPreview.cpp
new file mode 100644
index 0000000000..670a27bbef
--- /dev/null
+++ b/widget/windows/TaskbarWindowPreview.cpp
@@ -0,0 +1,323 @@
+/* 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/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include <nsITaskbarPreviewController.h>
+#include "TaskbarWindowPreview.h"
+#include "WindowHook.h"
+#include "nsUXThemeData.h"
+#include "TaskbarPreviewButton.h"
+#include "nsWindow.h"
+#include "nsWindowGfx.h"
+
+namespace mozilla {
+namespace widget {
+
+namespace {
+bool WindowHookProc(void* aContext, HWND hWnd, UINT nMsg, WPARAM wParam,
+ LPARAM lParam, LRESULT* aResult) {
+ TaskbarWindowPreview* preview =
+ reinterpret_cast<TaskbarWindowPreview*>(aContext);
+ *aResult = preview->WndProc(nMsg, wParam, lParam);
+ return true;
+}
+} // namespace
+
+NS_IMPL_ISUPPORTS(TaskbarWindowPreview, nsITaskbarWindowPreview,
+ nsITaskbarProgress, nsITaskbarOverlayIconController,
+ nsISupportsWeakReference)
+
+/**
+ * These correspond directly to the states defined in nsITaskbarProgress.idl, so
+ * they should be kept in sync.
+ */
+static TBPFLAG sNativeStates[] = {TBPF_NOPROGRESS, TBPF_INDETERMINATE,
+ TBPF_NORMAL, TBPF_ERROR, TBPF_PAUSED};
+
+TaskbarWindowPreview::TaskbarWindowPreview(
+ ITaskbarList4* aTaskbar, nsITaskbarPreviewController* aController,
+ HWND aHWND, nsIDocShell* aShell)
+ : TaskbarPreview(aTaskbar, aController, aHWND, aShell),
+ mCustomDrawing(false),
+ mHaveButtons(false),
+ mState(TBPF_NOPROGRESS),
+ mCurrentValue(0),
+ mMaxValue(0),
+ mOverlayIcon(nullptr) {
+ // Window previews are visible by default
+ (void)SetVisible(true);
+
+ memset(mThumbButtons, 0, sizeof mThumbButtons);
+ for (int32_t i = 0; i < nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS; i++) {
+ mThumbButtons[i].dwMask = THB_FLAGS | THB_ICON | THB_TOOLTIP;
+ mThumbButtons[i].iId = i;
+ mThumbButtons[i].dwFlags = THBF_HIDDEN;
+ }
+}
+
+TaskbarWindowPreview::~TaskbarWindowPreview() {
+ if (mOverlayIcon) {
+ ::DestroyIcon(mOverlayIcon);
+ mOverlayIcon = nullptr;
+ }
+
+ if (IsWindowAvailable()) {
+ DetachFromNSWindow();
+ } else {
+ mWnd = nullptr;
+ }
+}
+
+nsresult TaskbarWindowPreview::Init() {
+ nsresult rv = TaskbarPreview::Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (CanMakeTaskbarCalls()) {
+ return NS_OK;
+ }
+
+ WindowHook* hook = GetWindowHook();
+ if (!hook) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return hook->AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
+ TaskbarWindowHook, this);
+}
+
+nsresult TaskbarWindowPreview::ShowActive(bool active) {
+ return FAILED(mTaskbar->ActivateTab(active ? mWnd : nullptr))
+ ? NS_ERROR_FAILURE
+ : NS_OK;
+}
+
+HWND& TaskbarWindowPreview::PreviewWindow() { return mWnd; }
+
+nsresult TaskbarWindowPreview::GetButton(uint32_t index,
+ nsITaskbarPreviewButton** _retVal) {
+ if (index >= nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS)
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsITaskbarPreviewButton> button(
+ do_QueryReferent(mWeakButtons[index]));
+
+ if (!button) {
+ // Lost reference
+ button = new TaskbarPreviewButton(this, index);
+ if (!button) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mWeakButtons[index] = do_GetWeakReference(button);
+ }
+
+ if (!mHaveButtons) {
+ mHaveButtons = true;
+
+ WindowHook* hook = GetWindowHook();
+ if (!hook) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ (void)hook->AddHook(WM_COMMAND, WindowHookProc, this);
+
+ if (mVisible && FAILED(mTaskbar->ThumbBarAddButtons(
+ mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS,
+ mThumbButtons))) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ button.forget(_retVal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarWindowPreview::SetEnableCustomDrawing(bool aEnable) {
+ if (aEnable == mCustomDrawing) return NS_OK;
+
+ WindowHook* hook = GetWindowHook();
+ if (!hook) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mCustomDrawing = aEnable;
+ TaskbarPreview::EnableCustomDrawing(mWnd, aEnable);
+
+ if (aEnable) {
+ (void)hook->AddHook(WM_DWMSENDICONICTHUMBNAIL, WindowHookProc, this);
+ (void)hook->AddHook(WM_DWMSENDICONICLIVEPREVIEWBITMAP, WindowHookProc,
+ this);
+ } else {
+ (void)hook->RemoveHook(WM_DWMSENDICONICLIVEPREVIEWBITMAP, WindowHookProc,
+ this);
+ (void)hook->RemoveHook(WM_DWMSENDICONICTHUMBNAIL, WindowHookProc, this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarWindowPreview::GetEnableCustomDrawing(bool* aEnable) {
+ *aEnable = mCustomDrawing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarWindowPreview::SetProgressState(nsTaskbarProgressState aState,
+ uint64_t aCurrentValue,
+ uint64_t aMaxValue) {
+ NS_ENSURE_ARG_RANGE(aState, nsTaskbarProgressState(0),
+ nsTaskbarProgressState(ArrayLength(sNativeStates) - 1));
+
+ TBPFLAG nativeState = sNativeStates[aState];
+ if (nativeState == TBPF_NOPROGRESS || nativeState == TBPF_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;
+
+ mState = nativeState;
+ mCurrentValue = aCurrentValue;
+ mMaxValue = aMaxValue;
+
+ // Only update if we can
+ return CanMakeTaskbarCalls() ? UpdateTaskbarProgress() : NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarWindowPreview::SetOverlayIcon(imgIContainer* aStatusIcon,
+ const nsAString& aStatusDescription) {
+ nsresult rv;
+ if (aStatusIcon) {
+ // The image shouldn't be animated
+ bool isAnimated;
+ rv = aStatusIcon->GetAnimated(&isAnimated);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_FALSE(isAnimated, NS_ERROR_INVALID_ARG);
+ }
+
+ HICON hIcon = nullptr;
+ if (aStatusIcon) {
+ rv = nsWindowGfx::CreateIcon(
+ aStatusIcon, false, LayoutDeviceIntPoint(),
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), &hIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mOverlayIcon) ::DestroyIcon(mOverlayIcon);
+ mOverlayIcon = hIcon;
+ mIconDescription = aStatusDescription;
+
+ // Only update if we can
+ return CanMakeTaskbarCalls() ? UpdateOverlayIcon() : NS_OK;
+}
+
+nsresult TaskbarWindowPreview::UpdateTaskbarProperties() {
+ if (mHaveButtons) {
+ if (FAILED(mTaskbar->ThumbBarAddButtons(
+ mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS, mThumbButtons)))
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = UpdateTaskbarProgress();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = UpdateOverlayIcon();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return TaskbarPreview::UpdateTaskbarProperties();
+}
+
+nsresult TaskbarWindowPreview::UpdateTaskbarProgress() {
+ HRESULT hr = mTaskbar->SetProgressState(mWnd, mState);
+ if (SUCCEEDED(hr) && mState != TBPF_NOPROGRESS &&
+ mState != TBPF_INDETERMINATE)
+ hr = mTaskbar->SetProgressValue(mWnd, mCurrentValue, mMaxValue);
+
+ return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult TaskbarWindowPreview::UpdateOverlayIcon() {
+ HRESULT hr =
+ mTaskbar->SetOverlayIcon(mWnd, mOverlayIcon, mIconDescription.get());
+ return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+LRESULT
+TaskbarWindowPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
+ RefPtr<TaskbarWindowPreview> kungFuDeathGrip(this);
+ switch (nMsg) {
+ case WM_COMMAND: {
+ uint32_t id = LOWORD(wParam);
+ uint32_t index = id;
+ nsCOMPtr<nsITaskbarPreviewButton> button;
+ nsresult rv = GetButton(index, getter_AddRefs(button));
+ if (NS_SUCCEEDED(rv)) mController->OnClick(button);
+ }
+ return 0;
+ }
+ return TaskbarPreview::WndProc(nMsg, wParam, lParam);
+}
+
+/* static */
+bool TaskbarWindowPreview::TaskbarWindowHook(void* aContext, HWND hWnd,
+ UINT nMsg, WPARAM wParam,
+ LPARAM lParam, LRESULT* aResult) {
+ NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage(),
+ "Window hook proc called with wrong message");
+ TaskbarWindowPreview* preview =
+ reinterpret_cast<TaskbarWindowPreview*>(aContext);
+ // Now we can make all the calls to mTaskbar
+ preview->UpdateTaskbarProperties();
+ return false;
+}
+
+nsresult TaskbarWindowPreview::Enable() {
+ nsresult rv = TaskbarPreview::Enable();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return FAILED(mTaskbar->AddTab(mWnd)) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult TaskbarWindowPreview::Disable() {
+ nsresult rv = TaskbarPreview::Disable();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return FAILED(mTaskbar->DeleteTab(mWnd)) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+void TaskbarWindowPreview::DetachFromNSWindow() {
+ // Remove the hooks we have for drawing
+ SetEnableCustomDrawing(false);
+
+ if (WindowHook* hook = GetWindowHook()) {
+ (void)hook->RemoveHook(WM_COMMAND, WindowHookProc, this);
+ (void)hook->RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
+ TaskbarWindowHook, this);
+ }
+ TaskbarPreview::DetachFromNSWindow();
+}
+
+nsresult TaskbarWindowPreview::UpdateButtons() {
+ NS_ASSERTION(mVisible, "UpdateButtons called on invisible preview");
+
+ if (FAILED(mTaskbar->ThumbBarUpdateButtons(
+ mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS, mThumbButtons)))
+ return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+nsresult TaskbarWindowPreview::UpdateButton(uint32_t index) {
+ if (index >= nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS)
+ return NS_ERROR_INVALID_ARG;
+ if (mVisible) {
+ if (FAILED(mTaskbar->ThumbBarUpdateButtons(mWnd, 1, &mThumbButtons[index])))
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/TaskbarWindowPreview.h b/widget/windows/TaskbarWindowPreview.h
new file mode 100644
index 0000000000..97c074197c
--- /dev/null
+++ b/widget/windows/TaskbarWindowPreview.h
@@ -0,0 +1,85 @@
+/* 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_TaskbarWindowPreview_h__
+#define __mozilla_widget_TaskbarWindowPreview_h__
+
+#include "nsITaskbarWindowPreview.h"
+#include "nsITaskbarProgress.h"
+#include "nsITaskbarOverlayIconController.h"
+#include "TaskbarPreview.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace widget {
+
+class TaskbarPreviewButton;
+class TaskbarWindowPreview : public TaskbarPreview,
+ public nsITaskbarWindowPreview,
+ public nsITaskbarProgress,
+ public nsITaskbarOverlayIconController,
+ public nsSupportsWeakReference {
+ virtual ~TaskbarWindowPreview();
+
+ public:
+ TaskbarWindowPreview(ITaskbarList4* aTaskbar,
+ nsITaskbarPreviewController* aController, HWND aHWND,
+ nsIDocShell* aShell);
+ virtual nsresult Init() override;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITASKBARWINDOWPREVIEW
+ NS_DECL_NSITASKBARPROGRESS
+ NS_DECL_NSITASKBAROVERLAYICONCONTROLLER
+ NS_FORWARD_NSITASKBARPREVIEW(TaskbarPreview::)
+
+ virtual LRESULT WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) override;
+
+ private:
+ virtual nsresult ShowActive(bool active) override;
+ virtual HWND& PreviewWindow() override;
+
+ virtual nsresult UpdateTaskbarProperties() override;
+ virtual nsresult Enable() override;
+ virtual nsresult Disable() override;
+ virtual void DetachFromNSWindow() override;
+ nsresult UpdateButton(uint32_t index);
+ nsresult UpdateButtons();
+
+ // Is custom drawing enabled?
+ bool mCustomDrawing;
+ // Have we made any buttons?
+ bool mHaveButtons;
+ // Windows button format
+ THUMBBUTTON mThumbButtons[nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS];
+ // Pointers to our button class (cached instances)
+ nsWeakPtr mWeakButtons[nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS];
+
+ // Called to update ITaskbarList4 dependent properties
+ nsresult UpdateTaskbarProgress();
+ nsresult UpdateOverlayIcon();
+
+ // The taskbar progress
+ TBPFLAG mState;
+ ULONGLONG mCurrentValue;
+ ULONGLONG mMaxValue;
+
+ // Taskbar overlay icon
+ HICON mOverlayIcon;
+ nsString mIconDescription;
+
+ // WindowHook procedure for hooking mWnd for taskbar progress and icon stuff
+ static bool TaskbarWindowHook(void* aContext, HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam, LRESULT* aResult);
+
+ friend class TaskbarPreviewButton;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_TaskbarWindowPreview_h__ */
diff --git a/widget/windows/ToastNotification.cpp b/widget/windows/ToastNotification.cpp
new file mode 100644
index 0000000000..916f770590
--- /dev/null
+++ b/widget/windows/ToastNotification.cpp
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 4; 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 "ToastNotification.h"
+
+#include "mozilla/WindowsVersion.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIObserverService.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "ToastNotificationHandler.h"
+#include "WinTaskbar.h"
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS(ToastNotification, nsIAlertsService, nsIObserver,
+ nsISupportsWeakReference)
+
+ToastNotification::ToastNotification() = default;
+
+ToastNotification::~ToastNotification() = default;
+
+nsresult ToastNotification::Init() {
+ if (!IsWin8OrLater()) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsAutoString uid;
+ if (NS_WARN_IF(!WinTaskbar::GetAppUserModelID(uid))) {
+ // Windows Toast Notification requires AppId
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsresult rv =
+ NS_NewNamedThread("ToastBgThread", getter_AddRefs(mBackgroundThread));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIObserverService> obsServ =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (obsServ) {
+ obsServ->AddObserver(this, "quit-application", true);
+ }
+
+ return NS_OK;
+}
+
+nsresult ToastNotification::BackgroundDispatch(nsIRunnable* runnable) {
+ return mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+ToastNotification::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ // Got quit-application
+ // The handlers destructors will do the right thing (de-register with
+ // Windows).
+ for (auto iter = mActiveHandlers.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ToastNotificationHandler> handler = iter.UserData();
+ iter.Remove();
+
+ // Break the cycle between the handler and the MSCOM notification so the
+ // handler's destructor will be called.
+ handler->UnregisterHandler();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ToastNotification::ShowAlertNotification(
+ const nsAString& aImageUrl, const nsAString& aAlertTitle,
+ const nsAString& aAlertText, bool aAlertTextClickable,
+ const nsAString& aAlertCookie, nsIObserver* aAlertListener,
+ const nsAString& aAlertName, const nsAString& aBidi, const nsAString& aLang,
+ const nsAString& aData, nsIPrincipal* aPrincipal, bool aInPrivateBrowsing,
+ bool aRequireInteraction) {
+ nsCOMPtr<nsIAlertNotification> alert =
+ do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
+ if (NS_WARN_IF(!alert)) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv =
+ alert->Init(aAlertName, aImageUrl, aAlertTitle, aAlertText,
+ aAlertTextClickable, aAlertCookie, aBidi, aLang, aData,
+ aPrincipal, aInPrivateBrowsing, aRequireInteraction);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return ShowAlert(alert, aAlertListener);
+}
+
+NS_IMETHODIMP
+ToastNotification::ShowPersistentNotification(const nsAString& aPersistentData,
+ nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener) {
+ return ShowAlert(aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP
+ToastNotification::ShowAlert(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener) {
+ if (NS_WARN_IF(!aAlert)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoString cookie;
+ nsresult rv = aAlert->GetCookie(cookie);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString name;
+ rv = aAlert->GetName(name);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString title;
+ rv = aAlert->GetTitle(title);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString text;
+ rv = aAlert->GetText(text);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool textClickable;
+ rv = aAlert->GetTextClickable(&textClickable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString hostPort;
+ rv = aAlert->GetSource(hostPort);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<ToastNotificationHandler> oldHandler = mActiveHandlers.Get(name);
+
+ RefPtr<ToastNotificationHandler> handler = new ToastNotificationHandler(
+ this, aAlertListener, name, cookie, title, text, hostPort, textClickable);
+ mActiveHandlers.Put(name, RefPtr{handler});
+
+ rv = handler->InitAlertAsync(aAlert);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mActiveHandlers.Remove(name);
+ handler->UnregisterHandler();
+ return rv;
+ }
+
+ // If there was a previous handler with the same name then unregister it.
+ if (oldHandler) {
+ oldHandler->UnregisterHandler();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ToastNotification::CloseAlert(const nsAString& aAlertName) {
+ RefPtr<ToastNotificationHandler> handler;
+ if (NS_WARN_IF(!mActiveHandlers.Get(aAlertName, getter_AddRefs(handler)))) {
+ return NS_OK;
+ }
+ mActiveHandlers.Remove(aAlertName);
+ handler->UnregisterHandler();
+ return NS_OK;
+}
+
+bool ToastNotification::IsActiveHandler(const nsAString& aAlertName,
+ ToastNotificationHandler* aHandler) {
+ RefPtr<ToastNotificationHandler> handler;
+ if (NS_WARN_IF(!mActiveHandlers.Get(aAlertName, getter_AddRefs(handler)))) {
+ return false;
+ }
+ return handler == aHandler;
+}
+
+void ToastNotification::RemoveHandler(const nsAString& aAlertName,
+ ToastNotificationHandler* aHandler) {
+ // The alert may have been replaced; only remove it from the active
+ // handlers map if it's the same.
+ if (IsActiveHandler(aAlertName, aHandler)) {
+ // Terrible things happen if the destructor of a handler is called inside
+ // the hashtable .Remove() method. Wait until we have returned from there.
+ RefPtr<ToastNotificationHandler> kungFuDeathGrip(aHandler);
+ mActiveHandlers.Remove(aAlertName);
+ aHandler->UnregisterHandler();
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/ToastNotification.h b/widget/windows/ToastNotification.h
new file mode 100644
index 0000000000..31d2e548dd
--- /dev/null
+++ b/widget/windows/ToastNotification.h
@@ -0,0 +1,50 @@
+/* -*- 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 ToastNotification_h__
+#define ToastNotification_h__
+
+#include "nsIAlertsService.h"
+#include "nsIObserver.h"
+#include "nsIThread.h"
+#include "nsRefPtrHashtable.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace widget {
+
+class ToastNotificationHandler;
+
+class ToastNotification final : public nsIAlertsService,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_NSIALERTSSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_ISUPPORTS
+
+ ToastNotification();
+
+ nsresult Init();
+
+ bool IsActiveHandler(const nsAString& aAlertName,
+ ToastNotificationHandler* aHandler);
+ void RemoveHandler(const nsAString& aAlertName,
+ ToastNotificationHandler* aHandler);
+
+ nsresult BackgroundDispatch(nsIRunnable* runnable);
+
+ protected:
+ virtual ~ToastNotification();
+
+ nsRefPtrHashtable<nsStringHashKey, ToastNotificationHandler> mActiveHandlers;
+
+ nsCOMPtr<nsIThread> mBackgroundThread;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/windows/ToastNotificationHandler.cpp b/widget/windows/ToastNotificationHandler.cpp
new file mode 100644
index 0000000000..923b6da38e
--- /dev/null
+++ b/widget/windows/ToastNotificationHandler.cpp
@@ -0,0 +1,622 @@
+/* -*- Mode: C++; tab-width: 4; 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 "ToastNotificationHandler.h"
+
+#include "WidgetUtils.h"
+#include "WinTaskbar.h"
+#include "WinUtils.h"
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/gfx/2D.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIStringBundle.h"
+#include "nsIURI.h"
+#include "nsIUUIDGenerator.h"
+#include "nsIWidget.h"
+#include "nsIWindowMediator.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsProxyRelease.h"
+
+#include "ToastNotification.h"
+
+namespace mozilla {
+namespace widget {
+
+typedef ABI::Windows::Foundation::ITypedEventHandler<
+ ABI::Windows::UI::Notifications::ToastNotification*, IInspectable*>
+ ToastActivationHandler;
+typedef ABI::Windows::Foundation::ITypedEventHandler<
+ ABI::Windows::UI::Notifications::ToastNotification*,
+ ABI::Windows::UI::Notifications::ToastDismissedEventArgs*>
+ ToastDismissedHandler;
+typedef ABI::Windows::Foundation::ITypedEventHandler<
+ ABI::Windows::UI::Notifications::ToastNotification*,
+ ABI::Windows::UI::Notifications::ToastFailedEventArgs*>
+ ToastFailedHandler;
+
+using namespace ABI::Windows::Data::Xml::Dom;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::UI::Notifications;
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(ToastNotificationHandler, nsIAlertNotificationImageListener)
+
+static bool SetNodeValueString(const nsString& aString, IXmlNode* node,
+ IXmlDocument* xml) {
+ ComPtr<IXmlText> inputText;
+ if (NS_WARN_IF(FAILED(xml->CreateTextNode(
+ HStringReference(static_cast<const wchar_t*>(aString.get())).Get(),
+ &inputText)))) {
+ return false;
+ }
+ ComPtr<IXmlNode> inputTextNode;
+ if (NS_WARN_IF(FAILED(inputText.As(&inputTextNode)))) {
+ return false;
+ }
+ ComPtr<IXmlNode> appendedChild;
+ if (NS_WARN_IF(
+ FAILED(node->AppendChild(inputTextNode.Get(), &appendedChild)))) {
+ return false;
+ }
+ return true;
+}
+
+static bool SetAttribute(IXmlElement* element, const HSTRING name,
+ const nsAString& value) {
+ HSTRING valueStr = HStringReference(static_cast<const wchar_t*>(
+ PromiseFlatString(value).get()))
+ .Get();
+ if (NS_WARN_IF(FAILED(element->SetAttribute(name, valueStr)))) {
+ return false;
+ }
+ return true;
+}
+
+static bool AddActionNode(IXmlDocument* toastXml, IXmlNode* actionsNode,
+ const nsAString& actionTitle,
+ const nsAString& actionArgs) {
+ ComPtr<IXmlElement> action;
+ HRESULT hr =
+ toastXml->CreateElement(HStringReference(L"action").Get(), &action);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ if (NS_WARN_IF(!SetAttribute(action.Get(), HStringReference(L"content").Get(),
+ actionTitle))) {
+ return false;
+ }
+
+ if (NS_WARN_IF(!SetAttribute(
+ action.Get(), HStringReference(L"arguments").Get(), actionArgs))) {
+ return false;
+ }
+ if (NS_WARN_IF(!SetAttribute(action.Get(),
+ HStringReference(L"placement").Get(),
+ u"contextmenu"_ns))) {
+ return false;
+ }
+
+ // Add <action> to <actions>
+ ComPtr<IXmlNode> actionNode;
+ hr = action.As(&actionNode);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ ComPtr<IXmlNode> appendedChild;
+ hr = actionsNode->AppendChild(actionNode.Get(), &appendedChild);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ return true;
+}
+
+static ComPtr<IToastNotificationManagerStatics>
+GetToastNotificationManagerStatics() {
+ ComPtr<IToastNotificationManagerStatics> toastNotificationManagerStatics;
+ if (NS_WARN_IF(FAILED(GetActivationFactory(
+ HStringReference(
+ RuntimeClass_Windows_UI_Notifications_ToastNotificationManager)
+ .Get(),
+ &toastNotificationManagerStatics)))) {
+ return nullptr;
+ }
+
+ return toastNotificationManagerStatics;
+}
+
+ToastNotificationHandler::~ToastNotificationHandler() {
+ if (mImageRequest) {
+ mImageRequest->Cancel(NS_BINDING_ABORTED);
+ mImageRequest = nullptr;
+ }
+
+ if (mHasImage) {
+ DebugOnly<nsresult> rv = mImageFile->Remove(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Cannot remove temporary image file");
+ }
+
+ UnregisterHandler();
+}
+
+void ToastNotificationHandler::UnregisterHandler() {
+ if (mNotification && mNotifier) {
+ mNotification->remove_Dismissed(mDismissedToken);
+ mNotification->remove_Activated(mActivatedToken);
+ mNotification->remove_Failed(mFailedToken);
+ mNotifier->Hide(mNotification.Get());
+ }
+
+ mNotification = nullptr;
+ mNotifier = nullptr;
+
+ SendFinished();
+}
+
+ComPtr<IXmlDocument> ToastNotificationHandler::InitializeXmlForTemplate(
+ ToastTemplateType templateType) {
+ ComPtr<IToastNotificationManagerStatics> toastNotificationManagerStatics =
+ GetToastNotificationManagerStatics();
+
+ ComPtr<IXmlDocument> toastXml;
+ toastNotificationManagerStatics->GetTemplateContent(templateType, &toastXml);
+
+ return toastXml;
+}
+
+nsresult ToastNotificationHandler::InitAlertAsync(
+ nsIAlertNotification* aAlert) {
+ return aAlert->LoadImage(/* aTimeout = */ 0, this, /* aUserData = */ nullptr,
+ getter_AddRefs(mImageRequest));
+}
+
+bool ToastNotificationHandler::ShowAlert() {
+ if (!mBackend->IsActiveHandler(mName, this)) {
+ return true;
+ }
+
+ ToastTemplateType toastTemplate;
+ if (mHostPort.IsEmpty()) {
+ toastTemplate =
+ mHasImage ? ToastTemplateType::ToastTemplateType_ToastImageAndText03
+ : ToastTemplateType::ToastTemplateType_ToastText03;
+ } else {
+ toastTemplate =
+ mHasImage ? ToastTemplateType::ToastTemplateType_ToastImageAndText04
+ : ToastTemplateType::ToastTemplateType_ToastText04;
+ }
+
+ ComPtr<IXmlDocument> toastXml = InitializeXmlForTemplate(toastTemplate);
+ if (!toastXml) {
+ return false;
+ }
+
+ HRESULT hr;
+
+ if (mHasImage) {
+ ComPtr<IXmlNodeList> toastImageElements;
+ hr = toastXml->GetElementsByTagName(HStringReference(L"image").Get(),
+ &toastImageElements);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+ ComPtr<IXmlNode> imageNode;
+ hr = toastImageElements->Item(0, &imageNode);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+ ComPtr<IXmlElement> image;
+ hr = imageNode.As(&image);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+ if (NS_WARN_IF(!SetAttribute(image.Get(), HStringReference(L"src").Get(),
+ mImageUri))) {
+ return false;
+ }
+ }
+
+ ComPtr<IXmlNodeList> toastTextElements;
+ hr = toastXml->GetElementsByTagName(HStringReference(L"text").Get(),
+ &toastTextElements);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ ComPtr<IXmlNode> titleTextNodeRoot;
+ hr = toastTextElements->Item(0, &titleTextNodeRoot);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+ ComPtr<IXmlNode> msgTextNodeRoot;
+ hr = toastTextElements->Item(1, &msgTextNodeRoot);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ if (NS_WARN_IF(!SetNodeValueString(mTitle, titleTextNodeRoot.Get(),
+ toastXml.Get()))) {
+ return false;
+ }
+ if (NS_WARN_IF(
+ !SetNodeValueString(mMsg, msgTextNodeRoot.Get(), toastXml.Get()))) {
+ return false;
+ }
+
+ ComPtr<IXmlNodeList> toastElements;
+ hr = toastXml->GetElementsByTagName(HStringReference(L"toast").Get(),
+ &toastElements);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ ComPtr<IXmlNode> toastNodeRoot;
+ hr = toastElements->Item(0, &toastNodeRoot);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ ComPtr<IXmlElement> actions;
+ hr = toastXml->CreateElement(HStringReference(L"actions").Get(), &actions);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ ComPtr<IXmlNode> actionsNode;
+ hr = actions.As(&actionsNode);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ nsCOMPtr<nsIStringBundleService> sbs =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ if (NS_WARN_IF(!sbs)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ sbs->CreateBundle("chrome://alerts/locale/alert.properties",
+ getter_AddRefs(bundle));
+ if (NS_WARN_IF(!bundle)) {
+ return false;
+ }
+
+ if (!mHostPort.IsEmpty()) {
+ AutoTArray<nsString, 1> formatStrings = {mHostPort};
+
+ ComPtr<IXmlNode> urlTextNodeRoot;
+ hr = toastTextElements->Item(2, &urlTextNodeRoot);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ nsAutoString urlReference;
+ bundle->FormatStringFromName("source.label", formatStrings, urlReference);
+
+ if (NS_WARN_IF(!SetNodeValueString(urlReference, urlTextNodeRoot.Get(),
+ toastXml.Get()))) {
+ return false;
+ }
+
+ if (IsWin10AnniversaryUpdateOrLater()) {
+ ComPtr<IXmlElement> placementText;
+ hr = urlTextNodeRoot.As(&placementText);
+ if (SUCCEEDED(hr)) {
+ // placement is supported on Windows 10 Anniversary Update or later
+ SetAttribute(placementText.Get(), HStringReference(L"placement").Get(),
+ u"attribution"_ns);
+ }
+ }
+
+ nsAutoString disableButtonTitle;
+ bundle->FormatStringFromName("webActions.disableForOrigin.label",
+ formatStrings, disableButtonTitle);
+
+ AddActionNode(toastXml.Get(), actionsNode.Get(), disableButtonTitle,
+ u"snooze"_ns);
+ }
+
+ nsAutoString settingsButtonTitle;
+ bundle->GetStringFromName("webActions.settings.label", settingsButtonTitle);
+ AddActionNode(toastXml.Get(), actionsNode.Get(), settingsButtonTitle,
+ u"settings"_ns);
+
+ ComPtr<IXmlNode> appendedChild;
+ hr = toastNodeRoot->AppendChild(actionsNode.Get(), &appendedChild);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ return CreateWindowsNotificationFromXml(toastXml.Get());
+}
+
+bool ToastNotificationHandler::CreateWindowsNotificationFromXml(
+ IXmlDocument* aXml) {
+ ComPtr<IToastNotificationFactory> factory;
+ HRESULT hr = GetActivationFactory(
+ HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotification)
+ .Get(),
+ &factory);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ hr = factory->CreateToastNotification(aXml, &mNotification);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ RefPtr<ToastNotificationHandler> self = this;
+
+ hr = mNotification->add_Activated(
+ Callback<ToastActivationHandler>([self](IToastNotification* aNotification,
+ IInspectable* aInspectable) {
+ return self->OnActivate(aNotification, aInspectable);
+ }).Get(),
+ &mActivatedToken);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ hr = mNotification->add_Dismissed(
+ Callback<ToastDismissedHandler>([self](IToastNotification* aNotification,
+ IToastDismissedEventArgs* aArgs) {
+ return self->OnDismiss(aNotification, aArgs);
+ }).Get(),
+ &mDismissedToken);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ hr = mNotification->add_Failed(
+ Callback<ToastFailedHandler>([self](IToastNotification* aNotification,
+ IToastFailedEventArgs* aArgs) {
+ return self->OnFail(aNotification, aArgs);
+ }).Get(),
+ &mFailedToken);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ ComPtr<IToastNotificationManagerStatics> toastNotificationManagerStatics =
+ GetToastNotificationManagerStatics();
+ if (NS_WARN_IF(!toastNotificationManagerStatics)) {
+ return false;
+ }
+
+ nsAutoString uid;
+ if (NS_WARN_IF(!WinTaskbar::GetAppUserModelID(uid))) {
+ return false;
+ }
+
+ HSTRING uidStr =
+ HStringReference(static_cast<const wchar_t*>(uid.get())).Get();
+ hr = toastNotificationManagerStatics->CreateToastNotifierWithId(uidStr,
+ &mNotifier);
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ hr = mNotifier->Show(mNotification.Get());
+ if (NS_WARN_IF(FAILED(hr))) {
+ return false;
+ }
+
+ if (mAlertListener) {
+ mAlertListener->Observe(nullptr, "alertshow", mCookie.get());
+ }
+
+ return true;
+}
+
+void ToastNotificationHandler::SendFinished() {
+ if (!mSentFinished && mAlertListener) {
+ mAlertListener->Observe(nullptr, "alertfinished", mCookie.get());
+ }
+
+ mSentFinished = true;
+}
+
+HRESULT
+ToastNotificationHandler::OnActivate(IToastNotification* notification,
+ IInspectable* inspectable) {
+ if (mAlertListener) {
+ nsAutoString argString;
+ if (inspectable) {
+ ComPtr<IToastActivatedEventArgs> eventArgs;
+ HRESULT hr = inspectable->QueryInterface(
+ __uuidof(IToastActivatedEventArgs), (void**)&eventArgs);
+ if (SUCCEEDED(hr)) {
+ HSTRING arguments;
+ hr = eventArgs->get_Arguments(&arguments);
+ if (SUCCEEDED(hr)) {
+ uint32_t len = 0;
+ const wchar_t* buffer = WindowsGetStringRawBuffer(arguments, &len);
+ if (buffer) {
+ argString.Assign(buffer, len);
+ }
+ }
+ }
+ }
+
+ if (argString.EqualsLiteral("settings")) {
+ mAlertListener->Observe(nullptr, "alertsettingscallback", mCookie.get());
+ } else if (argString.EqualsLiteral("snooze")) {
+ mAlertListener->Observe(nullptr, "alertdisablecallback", mCookie.get());
+ } else if (mClickable) {
+ // When clicking toast, focus moves to another process, but we want to set
+ // focus on Firefox process.
+ nsCOMPtr<nsIWindowMediator> winMediator(
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (winMediator) {
+ nsCOMPtr<mozIDOMWindowProxy> navWin;
+ winMediator->GetMostRecentWindow(u"navigator:browser",
+ getter_AddRefs(navWin));
+ if (navWin) {
+ nsCOMPtr<nsIWidget> widget =
+ WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(navWin));
+ if (widget) {
+ SetForegroundWindow(
+ static_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW)));
+ }
+ }
+ }
+ mAlertListener->Observe(nullptr, "alertclickcallback", mCookie.get());
+ }
+ }
+ mBackend->RemoveHandler(mName, this);
+ return S_OK;
+}
+
+HRESULT
+ToastNotificationHandler::OnDismiss(IToastNotification* notification,
+ IToastDismissedEventArgs* aArgs) {
+ SendFinished();
+ mBackend->RemoveHandler(mName, this);
+ return S_OK;
+}
+
+HRESULT
+ToastNotificationHandler::OnFail(IToastNotification* notification,
+ IToastFailedEventArgs* aArgs) {
+ SendFinished();
+ mBackend->RemoveHandler(mName, this);
+ return S_OK;
+}
+
+nsresult ToastNotificationHandler::TryShowAlert() {
+ if (NS_WARN_IF(!ShowAlert())) {
+ mBackend->RemoveHandler(mName, this);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ToastNotificationHandler::OnImageMissing(nsISupports*) {
+ return TryShowAlert();
+}
+
+NS_IMETHODIMP
+ToastNotificationHandler::OnImageReady(nsISupports*, imgIRequest* aRequest) {
+ nsresult rv = AsyncSaveImage(aRequest);
+ if (NS_FAILED(rv)) {
+ return TryShowAlert();
+ }
+ return rv;
+}
+
+nsresult ToastNotificationHandler::AsyncSaveImage(imgIRequest* aRequest) {
+ nsresult rv =
+ NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mImageFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mImageFile->Append(u"notificationimages"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mImageFile->Create(nsIFile::DIRECTORY_TYPE, 0500);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIUUIDGenerator> idGen =
+ do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsID uuid;
+ rv = idGen->GenerateUUIDInPlace(&uuid);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ char uuidChars[NSID_LENGTH];
+ uuid.ToProvidedString(uuidChars);
+ // Remove the brackets at the beginning and ending of the generated UUID.
+ nsAutoCString uuidStr(Substring(uuidChars + 1, uuidChars + NSID_LENGTH - 2));
+ uuidStr.AppendLiteral(".bmp");
+ mImageFile->AppendNative(uuidStr);
+
+ nsCOMPtr<imgIContainer> imgContainer;
+ rv = aRequest->GetImage(getter_AddRefs(imgContainer));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsMainThreadPtrHandle<ToastNotificationHandler> self(
+ new nsMainThreadPtrHolder<ToastNotificationHandler>(
+ "ToastNotificationHandler", this));
+
+ nsCOMPtr<nsIFile> imageFile(mImageFile);
+ RefPtr<mozilla::gfx::SourceSurface> surface = imgContainer->GetFrame(
+ imgIContainer::FRAME_FIRST,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "ToastNotificationHandler::AsyncWriteBitmap",
+ [self, imageFile, surface]() -> void {
+ nsresult rv;
+ if (!surface) {
+ rv = NS_ERROR_FAILURE;
+ } else {
+ rv = WinUtils::WriteBitmap(imageFile, surface);
+ }
+
+ nsCOMPtr<nsIRunnable> cbRunnable = NS_NewRunnableFunction(
+ "ToastNotificationHandler::AsyncWriteBitmapCb",
+ [self, rv]() -> void {
+ auto handler = const_cast<ToastNotificationHandler*>(self.get());
+ handler->OnWriteBitmapFinished(rv);
+ });
+
+ NS_DispatchToMainThread(cbRunnable);
+ });
+
+ return mBackend->BackgroundDispatch(r);
+}
+
+void ToastNotificationHandler::OnWriteBitmapFinished(nsresult rv) {
+ if (NS_SUCCEEDED(rv)) {
+ OnWriteBitmapSuccess();
+ }
+ TryShowAlert();
+}
+
+nsresult ToastNotificationHandler::OnWriteBitmapSuccess() {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> fileURI;
+ rv = NS_NewFileURI(getter_AddRefs(fileURI), mImageFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString uriStr;
+ rv = fileURI->GetSpec(uriStr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ AppendUTF8toUTF16(uriStr, mImageUri);
+
+ mHasImage = true;
+
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/ToastNotificationHandler.h b/widget/windows/ToastNotificationHandler.h
new file mode 100644
index 0000000000..106756d9ce
--- /dev/null
+++ b/widget/windows/ToastNotificationHandler.h
@@ -0,0 +1,108 @@
+/* -*- 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 ToastNotificationHandler_h__
+#define ToastNotificationHandler_h__
+
+#include <windows.ui.notifications.h>
+#include <windows.data.xml.dom.h>
+#include <wrl.h>
+#include "nsCOMPtr.h"
+#include "nsIAlertsService.h"
+#include "nsICancelable.h"
+#include "nsIFile.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace widget {
+
+class ToastNotification;
+
+class ToastNotificationHandler final
+ : public nsIAlertNotificationImageListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIALERTNOTIFICATIONIMAGELISTENER
+
+ ToastNotificationHandler(ToastNotification* backend,
+ nsIObserver* aAlertListener, const nsAString& aName,
+ const nsAString& aCookie, const nsAString& aTitle,
+ const nsAString& aMsg, const nsAString& aHostPort,
+ bool aClickable)
+ : mBackend(backend),
+ mHasImage(false),
+ mAlertListener(aAlertListener),
+ mName(aName),
+ mCookie(aCookie),
+ mTitle(aTitle),
+ mMsg(aMsg),
+ mHostPort(aHostPort),
+ mClickable(aClickable),
+ mSentFinished(!aAlertListener) {}
+
+ nsresult InitAlertAsync(nsIAlertNotification* aAlert);
+
+ void OnWriteBitmapFinished(nsresult rv);
+
+ void UnregisterHandler();
+
+ protected:
+ virtual ~ToastNotificationHandler();
+
+ typedef ABI::Windows::Data::Xml::Dom::IXmlDocument IXmlDocument;
+ typedef ABI::Windows::UI::Notifications::IToastNotifier IToastNotifier;
+ typedef ABI::Windows::UI::Notifications::IToastNotification
+ IToastNotification;
+ typedef ABI::Windows::UI::Notifications::IToastDismissedEventArgs
+ IToastDismissedEventArgs;
+ typedef ABI::Windows::UI::Notifications::IToastFailedEventArgs
+ IToastFailedEventArgs;
+ typedef ABI::Windows::UI::Notifications::ToastTemplateType ToastTemplateType;
+
+ Microsoft::WRL::ComPtr<IToastNotification> mNotification;
+ Microsoft::WRL::ComPtr<IToastNotifier> mNotifier;
+
+ RefPtr<ToastNotification> mBackend;
+
+ nsCOMPtr<nsICancelable> mImageRequest;
+ nsCOMPtr<nsIFile> mImageFile;
+ nsString mImageUri;
+ bool mHasImage;
+
+ EventRegistrationToken mActivatedToken;
+ EventRegistrationToken mDismissedToken;
+ EventRegistrationToken mFailedToken;
+
+ nsCOMPtr<nsIObserver> mAlertListener;
+ nsString mName;
+ nsString mCookie;
+ nsString mTitle;
+ nsString mMsg;
+ nsString mHostPort;
+ bool mClickable;
+ bool mSentFinished;
+
+ nsresult TryShowAlert();
+ bool ShowAlert();
+ nsresult AsyncSaveImage(imgIRequest* aRequest);
+ nsresult OnWriteBitmapSuccess();
+ void SendFinished();
+
+ bool CreateWindowsNotificationFromXml(IXmlDocument* aToastXml);
+ Microsoft::WRL::ComPtr<IXmlDocument> InitializeXmlForTemplate(
+ ToastTemplateType templateType);
+
+ HRESULT OnActivate(IToastNotification* notification,
+ IInspectable* inspectable);
+ HRESULT OnDismiss(IToastNotification* notification,
+ IToastDismissedEventArgs* aArgs);
+ HRESULT OnFail(IToastNotification* notification,
+ IToastFailedEventArgs* aArgs);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/windows/UrlmonHeaderOnlyUtils.h b/widget/windows/UrlmonHeaderOnlyUtils.h
new file mode 100644
index 0000000000..dd9209f78f
--- /dev/null
+++ b/widget/windows/UrlmonHeaderOnlyUtils.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_UrlmonHeaderOnlyUtils_h
+#define mozilla_UrlmonHeaderOnlyUtils_h
+
+#include "mozilla/ShellHeaderOnlyUtils.h"
+
+namespace mozilla {
+
+/**
+ * We used to validate a uri with SHParseDisplayName to mitigate the Windows
+ * bug (Bug 394974). However, Bug 1573051 revealed an issue that a fragment,
+ * a string following a hash mark (#), is dropped when we extract a string
+ * from PIDL. This is the intended behavior of Windows.
+ *
+ * To deal with the fragment issue as well as keeping our mitigation, we
+ * decided to use CreateUri to validate a uri string, but we also keep using
+ * SHParseDisplayName as a pre-check. This is because there are several
+ * cases where CreateUri succeeds while SHParseDisplayName fails such as
+ * a non-existent file: uri.
+ *
+ * To minimize the impact of introducing CreateUri into the validation logic,
+ * we try to mimic the logic of windows_storage!IUriToPidl (ieframe!IUriToPidl
+ * in Win7) which is executed behind SHParseDisplayName.
+ * What IUriToPidl does is:
+ * 1) If a given uri has a fragment, removes a fragment.
+ * 2) Takes an absolute uri if it's available in the given uri, otherwise
+ * takes a raw uri.
+ *
+ * As we need to get a full uri including a fragment, this function does 2).
+ */
+inline LauncherResult<_bstr_t> UrlmonValidateUri(const wchar_t* aUri) {
+ LauncherResult<UniqueAbsolutePidl> pidlResult = ShellParseDisplayName(aUri);
+ if (pidlResult.isErr()) {
+ return pidlResult.propagateErr();
+ }
+
+ // The value of |flags| is the same value as used in ieframe!_EnsureIUri in
+ // Win7, which is called behind SHParseDisplayName. In Win10, on the other
+ // hand, an flag 0x03000000 is also passed to CreateUri, but we don't
+ // specify it because it's undocumented and unknown.
+ constexpr DWORD flags =
+ Uri_CREATE_NO_DECODE_EXTRA_INFO | Uri_CREATE_CANONICALIZE |
+ Uri_CREATE_CRACK_UNKNOWN_SCHEMES | Uri_CREATE_PRE_PROCESS_HTML_URI |
+ Uri_CREATE_IE_SETTINGS;
+ RefPtr<IUri> uri;
+ HRESULT hr;
+ SAFECALL_URLMON_FUNC(CreateUri, aUri, flags, 0, getter_AddRefs(uri));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ _bstr_t bstrUri;
+
+ hr = uri->GetAbsoluteUri(bstrUri.GetAddress());
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ if (hr == S_FALSE) {
+ hr = uri->GetRawUri(bstrUri.GetAddress());
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+ }
+
+ return bstrUri;
+}
+
+} // namespace mozilla
+
+#endif // mozilla_UrlmonHeaderOnlyUtils_h
diff --git a/widget/windows/WidgetTraceEvent.cpp b/widget/windows/WidgetTraceEvent.cpp
new file mode 100644
index 0000000000..4509791d59
--- /dev/null
+++ b/widget/windows/WidgetTraceEvent.cpp
@@ -0,0 +1,119 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Windows widget support for event loop instrumentation.
+ * See toolkit/xre/EventTracer.cpp for more details.
+ */
+
+#include <stdio.h>
+#include <windows.h>
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/WidgetTraceEvent.h"
+#include "nsAppShellCID.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIAppShellService.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsISupportsImpl.h"
+#include "nsIWidget.h"
+#include "nsIAppWindow.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsWindowDefs.h"
+
+namespace {
+
+// Used for signaling the background thread from the main thread.
+HANDLE sEventHandle = nullptr;
+
+// We need a runnable in order to find the hidden window on the main
+// thread.
+class HWNDGetter : public mozilla::Runnable {
+ public:
+ HWNDGetter() : Runnable("HWNDGetter"), hidden_window_hwnd(nullptr) {}
+
+ HWND hidden_window_hwnd;
+
+ NS_IMETHOD Run() override {
+ // Jump through some hoops to locate the hidden window.
+ nsCOMPtr<nsIAppShellService> appShell(
+ do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ nsCOMPtr<nsIAppWindow> hiddenWindow;
+
+ nsresult rv = appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell;
+ rv = hiddenWindow->GetDocShell(getter_AddRefs(docShell));
+ if (NS_FAILED(rv) || !docShell) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(docShell));
+
+ if (!baseWindow) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIWidget> widget;
+ baseWindow->GetMainWidget(getter_AddRefs(widget));
+
+ if (!widget) return NS_ERROR_FAILURE;
+
+ hidden_window_hwnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW);
+
+ return NS_OK;
+ }
+};
+
+HWND GetHiddenWindowHWND() {
+ // Need to dispatch this to the main thread because plenty of
+ // the things it wants to access are main-thread-only.
+ RefPtr<HWNDGetter> getter = new HWNDGetter();
+ NS_DispatchToMainThread(getter, NS_DISPATCH_SYNC);
+ return getter->hidden_window_hwnd;
+}
+
+} // namespace
+
+namespace mozilla {
+
+bool InitWidgetTracing() {
+ sEventHandle = CreateEventW(nullptr, FALSE, FALSE, nullptr);
+ return sEventHandle != nullptr;
+}
+
+void CleanUpWidgetTracing() {
+ CloseHandle(sEventHandle);
+ sEventHandle = nullptr;
+}
+
+// This function is called from the main (UI) thread.
+void SignalTracerThread() {
+ if (sEventHandle != nullptr) SetEvent(sEventHandle);
+}
+
+// This function is called from the background tracer thread.
+bool FireAndWaitForTracerEvent() {
+ MOZ_ASSERT(sEventHandle, "Tracing not initialized!");
+
+ // First, try to find the hidden window.
+ static HWND hidden_window = nullptr;
+ if (hidden_window == nullptr) {
+ hidden_window = GetHiddenWindowHWND();
+ }
+
+ if (hidden_window == nullptr) return false;
+
+ // Post the tracer message into the hidden window's message queue,
+ // and then block until it's processed.
+ PostMessage(hidden_window, MOZ_WM_TRACE, 0, 0);
+ WaitForSingleObject(sEventHandle, INFINITE);
+ return true;
+}
+
+} // namespace mozilla
diff --git a/widget/windows/WinCompositorWidget.cpp b/widget/windows/WinCompositorWidget.cpp
new file mode 100644
index 0000000000..3f0dc92a5b
--- /dev/null
+++ b/widget/windows/WinCompositorWidget.cpp
@@ -0,0 +1,105 @@
+/* -*- 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 "WinCompositorWidget.h"
+
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/layers/Compositor.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsWindow.h"
+#include "VsyncDispatcher.h"
+#include "WinCompositorWindowThread.h"
+#include "VRShMem.h"
+
+#include <ddraw.h>
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::gfx;
+using namespace mozilla;
+
+WinCompositorWidget::WinCompositorWidget(
+ const WinCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions)
+ : CompositorWidget(aOptions),
+ mSetParentCompleted(false),
+ mWidgetKey(aInitData.widgetKey()),
+ mWnd(reinterpret_cast<HWND>(aInitData.hWnd())),
+ mCompositorWnds(nullptr, nullptr) {
+ MOZ_ASSERT(mWnd && ::IsWindow(mWnd));
+}
+
+WinCompositorWidget::~WinCompositorWidget() { DestroyCompositorWindow(); }
+
+uintptr_t WinCompositorWidget::GetWidgetKey() { return mWidgetKey; }
+
+void WinCompositorWidget::EnsureCompositorWindow() {
+ if (mCompositorWnds.mCompositorWnd || mCompositorWnds.mInitialParentWnd) {
+ return;
+ }
+
+ mCompositorWnds = WinCompositorWindowThread::CreateCompositorWindow();
+ UpdateCompositorWnd(mCompositorWnds.mCompositorWnd, mWnd);
+
+ MOZ_ASSERT(mCompositorWnds.mCompositorWnd);
+ MOZ_ASSERT(mCompositorWnds.mInitialParentWnd);
+}
+
+void WinCompositorWidget::DestroyCompositorWindow() {
+ if (!mCompositorWnds.mCompositorWnd && !mCompositorWnds.mInitialParentWnd) {
+ return;
+ }
+ WinCompositorWindowThread::DestroyCompositorWindow(mCompositorWnds);
+ mCompositorWnds = WinCompositorWnds(nullptr, nullptr);
+}
+
+void WinCompositorWidget::UpdateCompositorWndSizeIfNecessary() {
+ if (!mCompositorWnds.mCompositorWnd) {
+ return;
+ }
+
+ LayoutDeviceIntSize size = GetClientSize();
+ if (mLastCompositorWndSize == size) {
+ return;
+ }
+
+ // This code is racing with the compositor, which needs to reparent
+ // the compositor surface to the actual window (mWnd). To avoid racing
+ // mutations, we refuse to proceed until ::SetParent() is called in parent
+ // process. After the ::SetParent() call, composition is scheduled in
+ // CompositorWidgetParent::UpdateCompositorWnd().
+ if (!mSetParentCompleted) {
+ // ::SetParent() is not completed yet.
+ return;
+ }
+
+ MOZ_ASSERT(mWnd == ::GetParent(mCompositorWnds.mCompositorWnd));
+
+ // Force a resize and redraw (but not a move, activate, etc.).
+ if (!::SetWindowPos(
+ mCompositorWnds.mCompositorWnd, nullptr, 0, 0, size.width,
+ size.height,
+ SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOOWNERZORDER | SWP_NOZORDER)) {
+ return;
+ }
+
+ mLastCompositorWndSize = size;
+}
+
+// Creates a new instance of FxROutputHandler so that this compositor widget
+// can send its output to Firefox Reality for Desktop.
+void WinCompositorWidget::RequestFxrOutput() {
+ MOZ_ASSERT(mFxrHandler == nullptr);
+
+ mFxrHandler.reset(new FxROutputHandler());
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinCompositorWidget.h b/widget/windows/WinCompositorWidget.h
new file mode 100644
index 0000000000..440897be09
--- /dev/null
+++ b/widget/windows/WinCompositorWidget.h
@@ -0,0 +1,99 @@
+/* -*- 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 widget_windows_WinCompositorWidget_h
+#define widget_windows_WinCompositorWidget_h
+
+#include "CompositorWidget.h"
+#include "gfxASurface.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/gfx/CriticalSection.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/widget/WinCompositorWindowThread.h"
+#include "FxROutputHandler.h"
+#include "nsIWidget.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class PlatformCompositorWidgetDelegate : public CompositorWidgetDelegate {
+ public:
+ // Callbacks for nsWindow.
+ virtual void EnterPresentLock() = 0;
+ virtual void LeavePresentLock() = 0;
+ virtual void OnDestroyWindow() = 0;
+ virtual bool OnWindowResize(const LayoutDeviceIntSize& aSize) = 0;
+ virtual void OnWindowModeChange(nsSizeMode aSizeMode) = 0;
+
+ // Transparency handling.
+ virtual void UpdateTransparency(nsTransparencyMode aMode) = 0;
+ virtual void ClearTransparentWindow() = 0;
+
+ // CompositorWidgetDelegate Overrides
+
+ PlatformCompositorWidgetDelegate* AsPlatformSpecificDelegate() override {
+ return this;
+ }
+};
+
+class WinCompositorWidgetInitData;
+
+// This is the Windows-specific implementation of CompositorWidget. For
+// the most part it only requires an HWND, however it maintains extra state
+// for transparent windows, as well as for synchronizing WM_SETTEXT messages
+// with the compositor.
+class WinCompositorWidget : public CompositorWidget {
+ public:
+ WinCompositorWidget(const WinCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions);
+ ~WinCompositorWidget() override;
+
+ // CompositorWidget Overrides
+
+ uintptr_t GetWidgetKey() override;
+ WinCompositorWidget* AsWindows() override { return this; }
+
+ HWND GetHwnd() const {
+ return mCompositorWnds.mCompositorWnd ? mCompositorWnds.mCompositorWnd
+ : mWnd;
+ }
+
+ HWND GetCompositorHwnd() const { return mCompositorWnds.mCompositorWnd; }
+
+ void EnsureCompositorWindow();
+ void DestroyCompositorWindow();
+ void UpdateCompositorWndSizeIfNecessary();
+
+ void RequestFxrOutput();
+ bool HasFxrOutputHandler() const { return mFxrHandler != nullptr; }
+ FxROutputHandler* GetFxrOutputHandler() const { return mFxrHandler.get(); }
+
+ virtual bool HasGlass() const = 0;
+
+ virtual void UpdateCompositorWnd(const HWND aCompositorWnd,
+ const HWND aParentWnd) = 0;
+ virtual void SetRootLayerTreeID(const layers::LayersId& aRootLayerTreeId) = 0;
+
+ protected:
+ bool mSetParentCompleted;
+
+ private:
+ uintptr_t mWidgetKey;
+ HWND mWnd;
+
+ WinCompositorWnds mCompositorWnds;
+ LayoutDeviceIntSize mLastCompositorWndSize;
+
+ UniquePtr<FxROutputHandler> mFxrHandler;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_WinCompositorWidget_h
diff --git a/widget/windows/WinCompositorWindowThread.cpp b/widget/windows/WinCompositorWindowThread.cpp
new file mode 100644
index 0000000000..f712522bc7
--- /dev/null
+++ b/widget/windows/WinCompositorWindowThread.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "base/platform_thread.h"
+#include "WinCompositorWindowThread.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/layers/SynchronousTask.h"
+#include "mozilla/StaticPtr.h"
+#include "transport/runnable_utils.h"
+#include "mozilla/StaticPrefs_apz.h"
+
+#if WINVER < 0x0602
+# define WS_EX_NOREDIRECTIONBITMAP 0x00200000L
+#endif
+
+namespace mozilla {
+namespace widget {
+
+static StaticRefPtr<WinCompositorWindowThread> sWinCompositorWindowThread;
+
+WinCompositorWindowThread::WinCompositorWindowThread(base::Thread* aThread)
+ : mThread(aThread) {}
+
+WinCompositorWindowThread::~WinCompositorWindowThread() { delete mThread; }
+
+/* static */
+WinCompositorWindowThread* WinCompositorWindowThread::Get() {
+ return sWinCompositorWindowThread;
+}
+
+/* static */
+void WinCompositorWindowThread::Start() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!sWinCompositorWindowThread);
+
+ base::Thread* thread = new base::Thread("WinCompositor");
+
+ base::Thread::Options options;
+ // HWND requests ui thread.
+ options.message_loop_type = MessageLoop::TYPE_UI;
+
+ if (!thread->StartWithOptions(options)) {
+ delete thread;
+ return;
+ }
+
+ sWinCompositorWindowThread = new WinCompositorWindowThread(thread);
+}
+
+/* static */
+void WinCompositorWindowThread::ShutDown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(sWinCompositorWindowThread);
+
+ layers::SynchronousTask task("WinCompositorWindowThread");
+ RefPtr<Runnable> runnable = WrapRunnable(
+ RefPtr<WinCompositorWindowThread>(sWinCompositorWindowThread.get()),
+ &WinCompositorWindowThread::ShutDownTask, &task);
+ sWinCompositorWindowThread->Loop()->PostTask(runnable.forget());
+ task.Wait();
+
+ sWinCompositorWindowThread = nullptr;
+}
+
+void WinCompositorWindowThread::ShutDownTask(layers::SynchronousTask* aTask) {
+ layers::AutoCompleteTask complete(aTask);
+ MOZ_ASSERT(IsInCompositorWindowThread());
+}
+
+/* static */
+MessageLoop* WinCompositorWindowThread::Loop() {
+ return sWinCompositorWindowThread
+ ? sWinCompositorWindowThread->mThread->message_loop()
+ : nullptr;
+}
+
+/* static */
+bool WinCompositorWindowThread::IsInCompositorWindowThread() {
+ return sWinCompositorWindowThread &&
+ sWinCompositorWindowThread->mThread->thread_id() ==
+ PlatformThread::CurrentId();
+}
+
+const wchar_t kClassNameCompositorInitalParent[] =
+ L"MozillaCompositorInitialParentClass";
+const wchar_t kClassNameCompositor[] = L"MozillaCompositorWindowClass";
+
+ATOM g_compositor_inital_parent_window_class;
+ATOM g_compositor_window_class;
+
+// This runs on the window owner thread.
+void InitializeInitialParentWindowClass() {
+ if (g_compositor_inital_parent_window_class) {
+ return;
+ }
+
+ WNDCLASSW wc;
+ wc.style = 0;
+ wc.lpfnWndProc = ::DefWindowProcW;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = GetModuleHandle(nullptr);
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+ wc.lpszClassName = kClassNameCompositorInitalParent;
+ g_compositor_inital_parent_window_class = ::RegisterClassW(&wc);
+}
+
+// This runs on the window owner thread.
+void InitializeWindowClass() {
+ if (g_compositor_window_class) {
+ return;
+ }
+
+ WNDCLASSW wc;
+ wc.style = CS_OWNDC;
+ wc.lpfnWndProc = ::DefWindowProcW;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = GetModuleHandle(nullptr);
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+ wc.lpszClassName = kClassNameCompositor;
+ g_compositor_window_class = ::RegisterClassW(&wc);
+}
+
+/* static */
+WinCompositorWnds WinCompositorWindowThread::CreateCompositorWindow() {
+ MOZ_ASSERT(Loop());
+
+ if (!Loop()) {
+ return WinCompositorWnds(nullptr, nullptr);
+ }
+
+ layers::SynchronousTask task("Create compositor window");
+
+ HWND initialParentWnd = nullptr;
+ HWND compositorWnd = nullptr;
+
+ RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+ "WinCompositorWindowThread::CreateCompositorWindow::Runnable", [&]() {
+ layers::AutoCompleteTask complete(&task);
+
+ InitializeInitialParentWindowClass();
+ InitializeWindowClass();
+
+ // Create initial parent window.
+ // We could not directly create a compositor window with a main window
+ // as parent window, so instead create it with a temporary placeholder
+ // parent. Its parent is set as main window in UI process.
+ initialParentWnd =
+ ::CreateWindowEx(WS_EX_TOOLWINDOW, kClassNameCompositorInitalParent,
+ nullptr, WS_POPUP | WS_DISABLED, 0, 0, 1, 1,
+ nullptr, 0, GetModuleHandle(nullptr), 0);
+ if (!initialParentWnd) {
+ gfxCriticalNoteOnce << "Inital parent window failed "
+ << ::GetLastError();
+ return;
+ }
+
+ DWORD extendedStyle = WS_EX_NOPARENTNOTIFY | WS_EX_NOREDIRECTIONBITMAP;
+
+ if (!StaticPrefs::apz_windows_force_disable_direct_manipulation()) {
+ extendedStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT;
+ }
+
+ compositorWnd = ::CreateWindowEx(
+ extendedStyle, kClassNameCompositor, nullptr,
+ WS_CHILDWINDOW | WS_DISABLED | WS_VISIBLE, 0, 0, 1, 1,
+ initialParentWnd, 0, GetModuleHandle(nullptr), 0);
+ if (!compositorWnd) {
+ gfxCriticalNoteOnce << "Compositor window failed "
+ << ::GetLastError();
+ }
+ });
+
+ Loop()->PostTask(runnable.forget());
+
+ task.Wait();
+
+ return WinCompositorWnds(compositorWnd, initialParentWnd);
+}
+
+/* static */
+void WinCompositorWindowThread::DestroyCompositorWindow(
+ WinCompositorWnds aWnds) {
+ MOZ_ASSERT(aWnds.mCompositorWnd);
+ MOZ_ASSERT(aWnds.mInitialParentWnd);
+ MOZ_ASSERT(Loop());
+
+ if (!Loop()) {
+ return;
+ }
+
+ RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+ "WinCompositorWidget::CreateNativeWindow::Runnable", [aWnds]() {
+ ::DestroyWindow(aWnds.mCompositorWnd);
+ ::DestroyWindow(aWnds.mInitialParentWnd);
+ });
+
+ Loop()->PostTask(runnable.forget());
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinCompositorWindowThread.h b/widget/windows/WinCompositorWindowThread.h
new file mode 100644
index 0000000000..c1462711ce
--- /dev/null
+++ b/widget/windows/WinCompositorWindowThread.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 widget_windows_WinCompositorWindowThread_h
+#define widget_windows_WinCompositorWindowThread_h
+
+#include "base/thread.h"
+#include "base/message_loop.h"
+
+namespace mozilla {
+
+namespace layers {
+class SynchronousTask;
+}
+
+namespace widget {
+
+struct WinCompositorWnds {
+ HWND mCompositorWnd;
+ HWND mInitialParentWnd;
+ WinCompositorWnds(HWND aCompositorWnd, HWND aInitialParentWnd)
+ : mCompositorWnd(aCompositorWnd), mInitialParentWnd(aInitialParentWnd) {}
+};
+
+class WinCompositorWindowThread final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD(
+ WinCompositorWindowThread)
+
+ public:
+ /// Can be called from any thread.
+ static WinCompositorWindowThread* Get();
+
+ /// Can only be called from the main thread.
+ static void Start();
+
+ /// Can only be called from the main thread.
+ static void ShutDown();
+
+ /// Can be called from any thread.
+ static MessageLoop* Loop();
+
+ /// Can be called from any thread.
+ static bool IsInCompositorWindowThread();
+
+ /// Can be called from any thread.
+ static WinCompositorWnds CreateCompositorWindow();
+
+ /// Can be called from any thread.
+ static void DestroyCompositorWindow(WinCompositorWnds aWnds);
+
+ private:
+ explicit WinCompositorWindowThread(base::Thread* aThread);
+ ~WinCompositorWindowThread();
+
+ void ShutDownTask(layers::SynchronousTask* aTask);
+
+ base::Thread* const mThread;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_WinCompositorWindowThread_h
diff --git a/widget/windows/WinContentSystemParameters.cpp b/widget/windows/WinContentSystemParameters.cpp
new file mode 100644
index 0000000000..f15e9aa0cc
--- /dev/null
+++ b/widget/windows/WinContentSystemParameters.cpp
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "WinContentSystemParameters.h"
+#include "WinUtils.h"
+#include "nsUXThemeData.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+
+namespace mozilla {
+namespace widget {
+using dom::SystemParameterKVPair;
+
+static_assert(uint8_t(SystemParameterId::Count) < 32,
+ "Too many SystemParameterId for "
+ "WinContentSystemParameters::Detail::validCachedValueBitfield");
+
+struct WinContentSystemParameters::Detail {
+ OffTheBooksMutex mutex{"WinContentSystemParameters::Detail::mutex"};
+ // A bitfield indicating which cached ids are valid
+ uint32_t validCachedValueBitfield{0};
+ bool cachedIsPerMonitorDPIAware{false};
+ float cachedSystemDPI{0.0f};
+ bool cachedFlatMenusEnabled{false};
+
+ // Almost-always true in Windows 7, always true starting in Windows 8
+ bool cachedIsAppThemed{true};
+};
+
+// static
+WinContentSystemParameters* WinContentSystemParameters::GetSingleton() {
+ static WinContentSystemParameters sInstance{};
+ return &sInstance;
+}
+
+WinContentSystemParameters::WinContentSystemParameters()
+ : mDetail(std::make_unique<Detail>()) {}
+
+WinContentSystemParameters::~WinContentSystemParameters() {}
+
+bool WinContentSystemParameters::IsPerMonitorDPIAware() {
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ OffTheBooksMutexAutoLock lock(mDetail->mutex);
+ MOZ_RELEASE_ASSERT(
+ IsCachedValueValid(SystemParameterId::IsPerMonitorDPIAware));
+ return mDetail->cachedIsPerMonitorDPIAware;
+}
+
+float WinContentSystemParameters::SystemDPI() {
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ OffTheBooksMutexAutoLock lock(mDetail->mutex);
+ MOZ_RELEASE_ASSERT(IsCachedValueValid(SystemParameterId::SystemDPI));
+ return mDetail->cachedSystemDPI;
+}
+
+bool WinContentSystemParameters::AreFlatMenusEnabled() {
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ OffTheBooksMutexAutoLock lock(mDetail->mutex);
+ MOZ_RELEASE_ASSERT(IsCachedValueValid(SystemParameterId::FlatMenusEnabled));
+ return mDetail->cachedFlatMenusEnabled;
+}
+
+bool WinContentSystemParameters::IsAppThemed() {
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ OffTheBooksMutexAutoLock lock(mDetail->mutex);
+ MOZ_RELEASE_ASSERT(IsCachedValueValid(SystemParameterId::IsAppThemed));
+ return mDetail->cachedIsAppThemed;
+}
+
+void WinContentSystemParameters::SetContentValueInternal(
+ const SystemParameterKVPair& aKVPair) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_RELEASE_ASSERT(aKVPair.id() < uint8_t(SystemParameterId::Count));
+
+ SetCachedValueValid(SystemParameterId(aKVPair.id()), true);
+
+ mDetail->mutex.AssertCurrentThreadOwns();
+
+ switch (SystemParameterId(aKVPair.id())) {
+ case SystemParameterId::IsPerMonitorDPIAware:
+ mDetail->cachedIsPerMonitorDPIAware = aKVPair.value();
+ return;
+
+ case SystemParameterId::SystemDPI:
+ mDetail->cachedSystemDPI = aKVPair.value();
+ return;
+
+ case SystemParameterId::FlatMenusEnabled:
+ mDetail->cachedFlatMenusEnabled = aKVPair.value();
+ return;
+
+ case SystemParameterId::IsAppThemed:
+ mDetail->cachedIsAppThemed = aKVPair.value();
+ return;
+
+ case SystemParameterId::Count:
+ MOZ_CRASH("Invalid SystemParameterId");
+ }
+ MOZ_CRASH("Unhandled SystemParameterId");
+}
+
+void WinContentSystemParameters::SetContentValues(
+ const nsTArray<dom::SystemParameterKVPair>& values) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ OffTheBooksMutexAutoLock lock(mDetail->mutex);
+ for (auto& kvPair : values) {
+ SetContentValueInternal(kvPair);
+ }
+}
+
+bool WinContentSystemParameters::GetParentValueInternal(
+ SystemParameterId aId, SystemParameterKVPair* aKVPair) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(aKVPair);
+
+ aKVPair->id() = uint8_t(aId);
+
+ switch (aId) {
+ case SystemParameterId::IsPerMonitorDPIAware:
+ aKVPair->value() = WinUtils::IsPerMonitorDPIAware();
+ return true;
+
+ case SystemParameterId::SystemDPI:
+ aKVPair->value() = WinUtils::SystemDPI();
+ return true;
+
+ case SystemParameterId::FlatMenusEnabled:
+ aKVPair->value() = nsUXThemeData::AreFlatMenusEnabled();
+ return true;
+
+ case SystemParameterId::IsAppThemed:
+ aKVPair->value() = nsUXThemeData::IsAppThemed();
+ return true;
+
+ case SystemParameterId::Count:
+ MOZ_CRASH("Invalid SystemParameterId");
+ }
+ MOZ_CRASH("Unhandled SystemParameterId");
+}
+
+nsTArray<dom::SystemParameterKVPair>
+WinContentSystemParameters::GetParentValues() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsTArray<dom::SystemParameterKVPair> results;
+ for (uint8_t i = 0; i < uint8_t(SystemParameterId::Count); ++i) {
+ dom::SystemParameterKVPair kvPair{};
+ GetParentValueInternal(SystemParameterId(i), &kvPair);
+ results.AppendElement(std::move(kvPair));
+ }
+ return results;
+}
+
+void WinContentSystemParameters::OnThemeChanged() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsTArray<dom::SystemParameterKVPair> updates;
+
+ {
+ dom::SystemParameterKVPair kvPair{};
+ GetParentValueInternal(SystemParameterId::FlatMenusEnabled, &kvPair);
+ updates.AppendElement(std::move(kvPair));
+ }
+
+ {
+ dom::SystemParameterKVPair kvPair{};
+ GetParentValueInternal(SystemParameterId::IsAppThemed, &kvPair);
+ updates.AppendElement(std::move(kvPair));
+ }
+
+ nsTArray<dom::ContentParent*> contentProcesses{};
+ dom::ContentParent::GetAll(contentProcesses);
+ for (auto contentProcess : contentProcesses) {
+ Unused << contentProcess->SendUpdateSystemParameters(updates);
+ }
+}
+
+bool WinContentSystemParameters::IsCachedValueValid(SystemParameterId aId) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ MOZ_ASSERT(uint8_t(aId) < uint8_t(SystemParameterId::Count));
+ mDetail->mutex.AssertCurrentThreadOwns();
+ uint32_t mask = uint32_t(1) << uint8_t(aId);
+ return (mDetail->validCachedValueBitfield & mask) != 0;
+}
+
+void WinContentSystemParameters::SetCachedValueValid(SystemParameterId aId,
+ bool aIsValid) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(uint8_t(aId) < uint8_t(SystemParameterId::Count));
+ mDetail->mutex.AssertCurrentThreadOwns();
+ uint32_t mask = uint32_t(1) << uint8_t(aId);
+
+ if (aIsValid) {
+ mDetail->validCachedValueBitfield |= mask;
+ } else {
+ mDetail->validCachedValueBitfield &= ~mask;
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinContentSystemParameters.h b/widget/windows/WinContentSystemParameters.h
new file mode 100644
index 0000000000..daa7db2115
--- /dev/null
+++ b/widget/windows/WinContentSystemParameters.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_WinContentSystemParameters_h
+#define mozilla_widget_WinContentSystemParameters_h
+#include <memory>
+#include <cinttypes>
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace dom {
+
+class SystemParameterKVPair;
+
+}
+
+namespace widget {
+
+enum class SystemParameterId : uint8_t {
+ IsPerMonitorDPIAware = 0,
+ SystemDPI,
+ FlatMenusEnabled,
+ IsAppThemed,
+ Count,
+};
+
+class WinContentSystemParameters {
+ public:
+ static WinContentSystemParameters* GetSingleton();
+
+ bool IsPerMonitorDPIAware();
+
+ float SystemDPI();
+
+ bool AreFlatMenusEnabled();
+
+ bool IsAppThemed();
+
+ void SetContentValues(const nsTArray<dom::SystemParameterKVPair>& values);
+
+ nsTArray<dom::SystemParameterKVPair> GetParentValues();
+
+ void OnThemeChanged();
+
+ WinContentSystemParameters(const WinContentSystemParameters&) = delete;
+ WinContentSystemParameters(WinContentSystemParameters&&) = delete;
+ WinContentSystemParameters& operator=(const WinContentSystemParameters&) =
+ delete;
+ WinContentSystemParameters& operator=(WinContentSystemParameters&&) = delete;
+
+ private:
+ WinContentSystemParameters();
+ ~WinContentSystemParameters();
+
+ void SetContentValueInternal(const dom::SystemParameterKVPair& aKVPair);
+
+ bool GetParentValueInternal(SystemParameterId aId,
+ dom::SystemParameterKVPair* aKVPair);
+
+ bool IsCachedValueValid(SystemParameterId aId);
+ void SetCachedValueValid(SystemParameterId aId, bool aIsValid);
+
+ struct Detail;
+ std::unique_ptr<Detail> mDetail;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_WinContentSystemParameters_h
diff --git a/widget/windows/WinHeaderOnlyUtils.h b/widget/windows/WinHeaderOnlyUtils.h
new file mode 100644
index 0000000000..2bab787869
--- /dev/null
+++ b/widget/windows/WinHeaderOnlyUtils.h
@@ -0,0 +1,742 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_WinHeaderOnlyUtils_h
+#define mozilla_WinHeaderOnlyUtils_h
+
+#include <windows.h>
+#include <winerror.h>
+#include <winnt.h>
+#include <winternl.h>
+#include <objbase.h>
+
+#include <stdlib.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsWindowsHelpers.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+# include "nsIFile.h"
+# include "nsString.h"
+#endif // defined(MOZILLA_INTERNAL_API)
+
+/**
+ * This header is intended for self-contained, header-only, utility code for
+ * Win32. It may be used outside of xul.dll, in places such as firefox.exe or
+ * mozglue.dll. If your code creates dependencies on Mozilla libraries, you
+ * should put it elsewhere.
+ */
+
+#if _WIN32_WINNT < _WIN32_WINNT_WIN8
+typedef struct _FILE_ID_INFO {
+ ULONGLONG VolumeSerialNumber;
+ FILE_ID_128 FileId;
+} FILE_ID_INFO;
+
+# define FileIdInfo ((FILE_INFO_BY_HANDLE_CLASS)18)
+
+#endif // _WIN32_WINNT < _WIN32_WINNT_WIN8
+
+#if !defined(STATUS_SUCCESS)
+# define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
+#endif // !defined(STATUS_SUCCESS)
+
+// Our data indicates a few users of Win7 x86 hit failure to load urlmon.dll
+// for unknown reasons. Since we don't always require urlmon.dll on Win7,
+// we delay-load it, which causes a crash if loading urlmon.dll fails. This
+// macro is to safely load and call urlmon's API graciously without crash.
+#if defined(_X86_)
+# define SAFECALL_URLMON_FUNC(FuncName, ...) \
+ do { \
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<decltype( \
+ &::FuncName)> \
+ func(L"urlmon.dll", #FuncName); \
+ hr = \
+ func ? func(__VA_ARGS__) : HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND); \
+ } while (0)
+#else
+# define SAFECALL_URLMON_FUNC(FuncName, ...) hr = ::FuncName(__VA_ARGS__)
+#endif
+
+namespace mozilla {
+
+class WindowsError final {
+ private:
+ // HRESULT and NTSTATUS are both typedefs of LONG, so we cannot use
+ // overloading to properly differentiate between the two. Instead we'll use
+ // static functions to convert the various error types to HRESULTs before
+ // instantiating.
+ explicit constexpr WindowsError(HRESULT aHResult) : mHResult(aHResult) {}
+
+ public:
+ using UniqueString = UniquePtr<WCHAR[], LocalFreeDeleter>;
+
+ static constexpr WindowsError FromNtStatus(NTSTATUS aNtStatus) {
+ if (aNtStatus == STATUS_SUCCESS) {
+ // Special case: we don't want to set FACILITY_NT_BIT
+ // (HRESULT_FROM_NT does not handle this case, unlike HRESULT_FROM_WIN32)
+ return WindowsError(S_OK);
+ }
+
+ return WindowsError(HRESULT_FROM_NT(aNtStatus));
+ }
+
+ static constexpr WindowsError FromHResult(HRESULT aHResult) {
+ return WindowsError(aHResult);
+ }
+
+ static constexpr WindowsError FromWin32Error(DWORD aWin32Err) {
+ return WindowsError(HRESULT_FROM_WIN32(aWin32Err));
+ }
+
+ static WindowsError FromLastError() {
+ return FromWin32Error(::GetLastError());
+ }
+
+ static WindowsError CreateSuccess() { return WindowsError(S_OK); }
+
+ static WindowsError CreateGeneric() {
+ return FromWin32Error(ERROR_UNIDENTIFIED_ERROR);
+ }
+
+ bool IsSuccess() const { return SUCCEEDED(mHResult); }
+
+ bool IsFailure() const { return FAILED(mHResult); }
+
+ bool IsAvailableAsWin32Error() const {
+ return IsAvailableAsNtStatus() ||
+ HRESULT_FACILITY(mHResult) == FACILITY_WIN32;
+ }
+
+ bool IsAvailableAsNtStatus() const {
+ return mHResult == S_OK || (mHResult & FACILITY_NT_BIT);
+ }
+
+ bool IsAvailableAsHResult() const { return true; }
+
+ UniqueString AsString() const {
+ LPWSTR rawMsgBuf = nullptr;
+ constexpr DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS;
+ DWORD result =
+ ::FormatMessageW(flags, nullptr, mHResult, 0,
+ reinterpret_cast<LPWSTR>(&rawMsgBuf), 0, nullptr);
+ if (!result) {
+ return nullptr;
+ }
+
+ return UniqueString(rawMsgBuf);
+ }
+
+ HRESULT AsHResult() const { return mHResult; }
+
+ // Not all HRESULTs are convertible to Win32 Errors, so we use Maybe
+ Maybe<DWORD> AsWin32Error() const {
+ if (mHResult == S_OK) {
+ return Some(static_cast<DWORD>(ERROR_SUCCESS));
+ }
+
+ if (HRESULT_FACILITY(mHResult) == FACILITY_WIN32) {
+ // This is the inverse of HRESULT_FROM_WIN32
+ return Some(static_cast<DWORD>(HRESULT_CODE(mHResult)));
+ }
+
+ // The NTSTATUS facility is a special case and thus does not utilize the
+ // HRESULT_FACILITY and HRESULT_CODE macros.
+ if (mHResult & FACILITY_NT_BIT) {
+ return Some(NtStatusToWin32Error(
+ static_cast<NTSTATUS>(mHResult & ~FACILITY_NT_BIT)));
+ }
+
+ return Nothing();
+ }
+
+ // Not all HRESULTs are convertible to NTSTATUS, so we use Maybe
+ Maybe<NTSTATUS> AsNtStatus() const {
+ if (mHResult == S_OK) {
+ return Some(STATUS_SUCCESS);
+ }
+
+ // The NTSTATUS facility is a special case and thus does not utilize the
+ // HRESULT_FACILITY and HRESULT_CODE macros.
+ if (mHResult & FACILITY_NT_BIT) {
+ return Some(static_cast<NTSTATUS>(mHResult & ~FACILITY_NT_BIT));
+ }
+
+ return Nothing();
+ }
+
+ constexpr bool operator==(const WindowsError& aOther) const {
+ return mHResult == aOther.mHResult;
+ }
+
+ constexpr bool operator!=(const WindowsError& aOther) const {
+ return mHResult != aOther.mHResult;
+ }
+
+ static DWORD NtStatusToWin32Error(NTSTATUS aNtStatus) {
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(
+ &RtlNtStatusToDosError)>
+ pRtlNtStatusToDosError(L"ntdll.dll", "RtlNtStatusToDosError");
+
+ MOZ_ASSERT(!!pRtlNtStatusToDosError);
+ if (!pRtlNtStatusToDosError) {
+ return ERROR_UNIDENTIFIED_ERROR;
+ }
+
+ return pRtlNtStatusToDosError(aNtStatus);
+ }
+
+ private:
+ // We store the error code as an HRESULT because they can encode both Win32
+ // error codes and NTSTATUS codes.
+ HRESULT mHResult;
+};
+
+namespace detail {
+template <>
+struct UnusedZero<WindowsError> {
+ using StorageType = WindowsError;
+
+ static constexpr bool value = true;
+ static constexpr StorageType nullValue = WindowsError::FromHResult(S_OK);
+
+ static constexpr void AssertValid(StorageType aValue) {}
+ static constexpr const WindowsError& Inspect(const StorageType& aValue) {
+ return aValue;
+ }
+ static constexpr WindowsError Unwrap(StorageType aValue) { return aValue; }
+ static constexpr StorageType Store(WindowsError aValue) { return aValue; }
+};
+} // namespace detail
+
+enum DetourResultCode : uint32_t {
+ RESULT_OK = 0,
+ INTERCEPTOR_MOD_NULL,
+ INTERCEPTOR_MOD_INACCESSIBLE,
+ INTERCEPTOR_PROC_NULL,
+ INTERCEPTOR_PROC_INACCESSIBLE,
+ DETOUR_PATCHER_RESERVE_FOR_MODULE_PE_ERROR,
+ DETOUR_PATCHER_RESERVE_FOR_MODULE_TEXT_ERROR,
+ DETOUR_PATCHER_RESERVE_FOR_MODULE_RESERVE_ERROR,
+ DETOUR_PATCHER_DO_RESERVE_ERROR,
+ DETOUR_PATCHER_NEXT_TRAMPOLINE_ERROR,
+ DETOUR_PATCHER_INVALID_TRAMPOLINE,
+ DETOUR_PATCHER_WRITE_POINTER_ERROR,
+ DETOUR_PATCHER_CREATE_TRAMPOLINE_ERROR,
+ FUNCHOOKCROSSPROCESS_COPYSTUB_ERROR,
+ MMPOLICY_RESERVE_INVALIDARG,
+ MMPOLICY_RESERVE_ZERO_RESERVATIONSIZE,
+ MMPOLICY_RESERVE_CREATEFILEMAPPING,
+ MMPOLICY_RESERVE_MAPVIEWOFFILE,
+ MMPOLICY_RESERVE_NOBOUND_RESERVE_ERROR,
+ MMPOLICY_RESERVE_FINDREGION_INVALIDLEN,
+ MMPOLICY_RESERVE_FINDREGION_INVALIDRANGE,
+ MMPOLICY_RESERVE_FINDREGION_VIRTUALQUERY_ERROR,
+ MMPOLICY_RESERVE_FINDREGION_NO_FREE_REGION,
+ MMPOLICY_RESERVE_FINAL_RESERVE_ERROR,
+};
+
+#if defined(NIGHTLY_BUILD)
+struct DetourError {
+ // We have a 16-bytes buffer, but only minimum bytes to detour per
+ // architecture are copied. See CreateTrampoline in PatcherDetour.h.
+ DetourResultCode mErrorCode;
+ uint8_t mOrigBytes[16];
+ explicit DetourError(DetourResultCode aError)
+ : mErrorCode(aError), mOrigBytes{} {}
+ DetourError(DetourResultCode aError, DWORD aWin32Error)
+ : mErrorCode(aError), mOrigBytes{} {
+ static_assert(sizeof(mOrigBytes) >= sizeof(aWin32Error),
+ "Can't fit a DWORD in mOrigBytes");
+ *reinterpret_cast<DWORD*>(mOrigBytes) = aWin32Error;
+ }
+ operator WindowsError() const {
+ return WindowsError::FromHResult(mErrorCode);
+ }
+};
+#endif // defined(NIGHTLY_BUILD)
+
+template <typename T>
+using WindowsErrorResult = Result<T, WindowsError>;
+
+struct LauncherError {
+ LauncherError(const char* aFile, int aLine, WindowsError aWin32Error)
+ : mFile(aFile), mLine(aLine), mError(aWin32Error) {}
+
+#if defined(NIGHTLY_BUILD)
+ LauncherError(const char* aFile, int aLine,
+ const Maybe<DetourError>& aDetourError)
+ : mFile(aFile),
+ mLine(aLine),
+ mError(aDetourError.isSome() ? aDetourError.value()
+ : WindowsError::CreateGeneric()),
+ mDetourError(aDetourError) {}
+#endif // defined(NIGHTLY_BUILD)
+
+ const char* mFile;
+ int mLine;
+ WindowsError mError;
+#if defined(NIGHTLY_BUILD)
+ Maybe<DetourError> mDetourError;
+#endif // defined(NIGHTLY_BUILD)
+
+ bool operator==(const LauncherError& aOther) const {
+ return mError == aOther.mError;
+ }
+
+ bool operator!=(const LauncherError& aOther) const {
+ return mError != aOther.mError;
+ }
+
+ bool operator==(const WindowsError& aOther) const { return mError == aOther; }
+
+ bool operator!=(const WindowsError& aOther) const { return mError != aOther; }
+};
+
+#if defined(MOZ_USE_LAUNCHER_ERROR)
+
+template <typename T>
+using LauncherResult = Result<T, LauncherError>;
+
+template <typename T>
+using LauncherResultWithLineInfo = LauncherResult<T>;
+
+using WindowsErrorType = LauncherError;
+
+#else
+
+template <typename T>
+using LauncherResult = WindowsErrorResult<T>;
+
+template <typename T>
+using LauncherResultWithLineInfo = Result<T, LauncherError>;
+
+using WindowsErrorType = WindowsError;
+
+#endif // defined(MOZ_USE_LAUNCHER_ERROR)
+
+using LauncherVoidResult = LauncherResult<Ok>;
+
+using LauncherVoidResultWithLineInfo = LauncherResultWithLineInfo<Ok>;
+
+#if defined(MOZ_USE_LAUNCHER_ERROR)
+
+# define LAUNCHER_ERROR_GENERIC() \
+ ::mozilla::Err(::mozilla::LauncherError( \
+ __FILE__, __LINE__, ::mozilla::WindowsError::CreateGeneric()))
+
+# if defined(NIGHTLY_BUILD)
+# define LAUNCHER_ERROR_FROM_DETOUR_ERROR(err) \
+ ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, err))
+# else
+# define LAUNCHER_ERROR_FROM_DETOUR_ERROR(err) LAUNCHER_ERROR_GENERIC()
+# endif // defined(NIGHTLY_BUILD)
+
+# define LAUNCHER_ERROR_FROM_WIN32(err) \
+ ::mozilla::Err(::mozilla::LauncherError( \
+ __FILE__, __LINE__, ::mozilla::WindowsError::FromWin32Error(err)))
+
+# define LAUNCHER_ERROR_FROM_LAST() \
+ ::mozilla::Err(::mozilla::LauncherError( \
+ __FILE__, __LINE__, ::mozilla::WindowsError::FromLastError()))
+
+# define LAUNCHER_ERROR_FROM_NTSTATUS(ntstatus) \
+ ::mozilla::Err(::mozilla::LauncherError( \
+ __FILE__, __LINE__, ::mozilla::WindowsError::FromNtStatus(ntstatus)))
+
+# define LAUNCHER_ERROR_FROM_HRESULT(hresult) \
+ ::mozilla::Err(::mozilla::LauncherError( \
+ __FILE__, __LINE__, ::mozilla::WindowsError::FromHResult(hresult)))
+
+// This macro wraps the supplied WindowsError with a LauncherError
+# define LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(err) \
+ ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, err))
+
+#else
+
+# define LAUNCHER_ERROR_GENERIC() \
+ ::mozilla::Err(::mozilla::WindowsError::CreateGeneric())
+
+# define LAUNCHER_ERROR_FROM_DETOUR_ERROR(err) LAUNCHER_ERROR_GENERIC()
+
+# define LAUNCHER_ERROR_FROM_WIN32(err) \
+ ::mozilla::Err(::mozilla::WindowsError::FromWin32Error(err))
+
+# define LAUNCHER_ERROR_FROM_LAST() \
+ ::mozilla::Err(::mozilla::WindowsError::FromLastError())
+
+# define LAUNCHER_ERROR_FROM_NTSTATUS(ntstatus) \
+ ::mozilla::Err(::mozilla::WindowsError::FromNtStatus(ntstatus))
+
+# define LAUNCHER_ERROR_FROM_HRESULT(hresult) \
+ ::mozilla::Err(::mozilla::WindowsError::FromHResult(hresult))
+
+# define LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(err) ::mozilla::Err(err)
+
+#endif // defined(MOZ_USE_LAUNCHER_ERROR)
+
+// How long to wait for a created process to become available for input,
+// to prevent that process's windows being forced to the background.
+// This is used across update, restart, and the launcher.
+const DWORD kWaitForInputIdleTimeoutMS = 10 * 1000;
+
+/**
+ * Wait for a child GUI process to become "idle." Idle means that the process
+ * has created its message queue and has begun waiting for user input.
+ *
+ * Note that this must only be used when the child process is going to display
+ * GUI! Otherwise you're going to be waiting for a very long time ;-)
+ *
+ * @return true if we successfully waited for input idle;
+ * false if we timed out or failed to wait.
+ */
+inline bool WaitForInputIdle(HANDLE aProcess,
+ DWORD aTimeoutMs = kWaitForInputIdleTimeoutMS) {
+ const DWORD kSleepTimeMs = 10;
+ const DWORD waitStart = aTimeoutMs == INFINITE ? 0 : ::GetTickCount();
+ DWORD elapsed = 0;
+
+ while (true) {
+ if (aTimeoutMs != INFINITE) {
+ elapsed = ::GetTickCount() - waitStart;
+ }
+
+ if (elapsed >= aTimeoutMs) {
+ return false;
+ }
+
+ // ::WaitForInputIdle() doesn't always set the last-error code on failure
+ ::SetLastError(ERROR_SUCCESS);
+
+ DWORD waitResult = ::WaitForInputIdle(aProcess, aTimeoutMs - elapsed);
+ if (!waitResult) {
+ return true;
+ }
+
+ if (waitResult == WAIT_FAILED &&
+ ::GetLastError() == ERROR_NOT_GUI_PROCESS) {
+ ::Sleep(kSleepTimeMs);
+ continue;
+ }
+
+ return false;
+ }
+}
+
+enum class PathType {
+ eNtPath,
+ eDosPath,
+};
+
+class FileUniqueId final {
+ public:
+ explicit FileUniqueId(const wchar_t* aPath, PathType aPathType)
+ : mId(FILE_ID_INFO()) {
+ if (!aPath) {
+ mId = LAUNCHER_ERROR_FROM_HRESULT(E_INVALIDARG);
+ return;
+ }
+
+ nsAutoHandle file;
+
+ switch (aPathType) {
+ default:
+ mId = LAUNCHER_ERROR_FROM_HRESULT(E_INVALIDARG);
+ MOZ_ASSERT_UNREACHABLE("Unhandled PathType");
+ return;
+
+ case PathType::eNtPath: {
+ UNICODE_STRING unicodeString;
+ ::RtlInitUnicodeString(&unicodeString, aPath);
+ OBJECT_ATTRIBUTES objectAttributes;
+ InitializeObjectAttributes(&objectAttributes, &unicodeString,
+ OBJ_CASE_INSENSITIVE, nullptr, nullptr);
+ IO_STATUS_BLOCK ioStatus = {};
+ HANDLE ntHandle;
+ NTSTATUS status = ::NtOpenFile(
+ &ntHandle, SYNCHRONIZE | FILE_READ_ATTRIBUTES, &objectAttributes,
+ &ioStatus, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT);
+ // We don't need to check |ntHandle| for INVALID_HANDLE_VALUE here,
+ // as that value is set by the Win32 layer.
+ if (!NT_SUCCESS(status)) {
+ mId = LAUNCHER_ERROR_FROM_NTSTATUS(status);
+ return;
+ }
+
+ file.own(ntHandle);
+ break;
+ }
+
+ case PathType::eDosPath: {
+ file.own(::CreateFileW(
+ aPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
+ if (file == INVALID_HANDLE_VALUE) {
+ mId = LAUNCHER_ERROR_FROM_LAST();
+ return;
+ }
+
+ break;
+ }
+ }
+
+ GetId(file);
+ }
+
+ explicit FileUniqueId(const nsAutoHandle& aFile) : mId(FILE_ID_INFO()) {
+ GetId(aFile);
+ }
+
+ ~FileUniqueId() = default;
+
+ bool IsError() const { return mId.isErr(); }
+
+ const WindowsErrorType& GetError() const { return mId.inspectErr(); }
+
+ FileUniqueId(FileUniqueId&& aOther) = default;
+ FileUniqueId& operator=(FileUniqueId&& aOther) = delete;
+
+ bool operator==(const FileUniqueId& aOther) const {
+ return mId.isOk() && aOther.mId.isOk() &&
+ !memcmp(&mId.inspect(), &aOther.mId.inspect(), sizeof(FILE_ID_INFO));
+ }
+
+ bool operator!=(const FileUniqueId& aOther) const {
+ return !((*this) == aOther);
+ }
+
+ private:
+ void GetId(const nsAutoHandle& aFile) {
+ FILE_ID_INFO fileIdInfo = {};
+ if (IsWin8OrLater()) {
+ if (::GetFileInformationByHandleEx(aFile.get(), FileIdInfo, &fileIdInfo,
+ sizeof(fileIdInfo))) {
+ mId = fileIdInfo;
+ return;
+ }
+ // Only NTFS and ReFS support FileIdInfo. So we have to fallback if
+ // GetFileInformationByHandleEx failed.
+ }
+
+ BY_HANDLE_FILE_INFORMATION info = {};
+ if (!::GetFileInformationByHandle(aFile.get(), &info)) {
+ mId = LAUNCHER_ERROR_FROM_LAST();
+ return;
+ }
+
+ fileIdInfo.VolumeSerialNumber = info.dwVolumeSerialNumber;
+ memcpy(&fileIdInfo.FileId.Identifier[0], &info.nFileIndexLow,
+ sizeof(DWORD));
+ memcpy(&fileIdInfo.FileId.Identifier[sizeof(DWORD)], &info.nFileIndexHigh,
+ sizeof(DWORD));
+ mId = fileIdInfo;
+ }
+
+ private:
+ LauncherResult<FILE_ID_INFO> mId;
+};
+
+class MOZ_RAII AutoVirtualProtect final {
+ public:
+ AutoVirtualProtect(void* aAddress, size_t aLength, DWORD aProtFlags,
+ HANDLE aTargetProcess = ::GetCurrentProcess())
+ : mAddress(aAddress),
+ mLength(aLength),
+ mTargetProcess(aTargetProcess),
+ mPrevProt(0),
+ mError(WindowsError::CreateSuccess()) {
+ if (!::VirtualProtectEx(aTargetProcess, aAddress, aLength, aProtFlags,
+ &mPrevProt)) {
+ mError = WindowsError::FromLastError();
+ }
+ }
+
+ ~AutoVirtualProtect() {
+ if (mError.IsFailure()) {
+ return;
+ }
+
+ ::VirtualProtectEx(mTargetProcess, mAddress, mLength, mPrevProt,
+ &mPrevProt);
+ }
+
+ explicit operator bool() const { return mError.IsSuccess(); }
+
+ WindowsError GetError() const { return mError; }
+
+ DWORD PrevProt() const { return mPrevProt; }
+
+ AutoVirtualProtect(const AutoVirtualProtect&) = delete;
+ AutoVirtualProtect(AutoVirtualProtect&&) = delete;
+ AutoVirtualProtect& operator=(const AutoVirtualProtect&) = delete;
+ AutoVirtualProtect& operator=(AutoVirtualProtect&&) = delete;
+
+ private:
+ void* mAddress;
+ size_t mLength;
+ HANDLE mTargetProcess;
+ DWORD mPrevProt;
+ WindowsError mError;
+};
+
+inline UniquePtr<wchar_t[]> GetFullModulePath(HMODULE aModule) {
+ DWORD bufLen = MAX_PATH;
+ mozilla::UniquePtr<wchar_t[]> buf;
+ DWORD retLen;
+
+ while (true) {
+ buf = mozilla::MakeUnique<wchar_t[]>(bufLen);
+ retLen = ::GetModuleFileNameW(aModule, buf.get(), bufLen);
+ if (!retLen) {
+ return nullptr;
+ }
+
+ if (retLen == bufLen && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ bufLen *= 2;
+ continue;
+ }
+
+ break;
+ }
+
+ // Upon success, retLen *excludes* the null character
+ ++retLen;
+
+ // Since we're likely to have a bunch of unused space in buf, let's
+ // reallocate a string to the actual size of the file name.
+ auto result = mozilla::MakeUnique<wchar_t[]>(retLen);
+ if (wcscpy_s(result.get(), retLen, buf.get())) {
+ return nullptr;
+ }
+
+ return result;
+}
+
+inline UniquePtr<wchar_t[]> GetFullBinaryPath() {
+ return GetFullModulePath(nullptr);
+}
+
+class ModuleVersion final {
+ public:
+ constexpr ModuleVersion() : mVersion(0ULL) {}
+
+ explicit ModuleVersion(const VS_FIXEDFILEINFO& aFixedInfo)
+ : mVersion((static_cast<uint64_t>(aFixedInfo.dwFileVersionMS) << 32) |
+ static_cast<uint64_t>(aFixedInfo.dwFileVersionLS)) {}
+
+ explicit ModuleVersion(const uint64_t aVersion) : mVersion(aVersion) {}
+
+ ModuleVersion(const ModuleVersion& aOther) : mVersion(aOther.mVersion) {}
+
+ uint64_t AsInteger() const { return mVersion; }
+
+ operator uint64_t() const { return AsInteger(); }
+
+ Tuple<uint16_t, uint16_t, uint16_t, uint16_t> AsTuple() const {
+ uint16_t major = static_cast<uint16_t>((mVersion >> 48) & 0xFFFFU);
+ uint16_t minor = static_cast<uint16_t>((mVersion >> 32) & 0xFFFFU);
+ uint16_t patch = static_cast<uint16_t>((mVersion >> 16) & 0xFFFFU);
+ uint16_t build = static_cast<uint16_t>(mVersion & 0xFFFFU);
+
+ return MakeTuple(major, minor, patch, build);
+ }
+
+ explicit operator bool() const { return !!mVersion; }
+
+ bool operator<(const ModuleVersion& aOther) const {
+ return mVersion < aOther.mVersion;
+ }
+
+ bool operator<(const uint64_t& aOther) const { return mVersion < aOther; }
+
+ ModuleVersion& operator=(const uint64_t aIntVersion) {
+ mVersion = aIntVersion;
+ return *this;
+ }
+
+ private:
+ uint64_t mVersion;
+};
+
+inline LauncherResult<ModuleVersion> GetModuleVersion(
+ const wchar_t* aModuleFullPath) {
+ DWORD verInfoLen = ::GetFileVersionInfoSizeW(aModuleFullPath, nullptr);
+ if (!verInfoLen) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ auto verInfoBuf = MakeUnique<BYTE[]>(verInfoLen);
+ if (!::GetFileVersionInfoW(aModuleFullPath, 0, verInfoLen,
+ verInfoBuf.get())) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ UINT fixedInfoLen;
+ VS_FIXEDFILEINFO* fixedInfo = nullptr;
+ if (!::VerQueryValueW(verInfoBuf.get(), L"\\",
+ reinterpret_cast<LPVOID*>(&fixedInfo), &fixedInfoLen)) {
+ // VerQueryValue may fail if the resource does not exist. This is not an
+ // error; we'll return 0 in this case.
+ return ModuleVersion(0ULL);
+ }
+
+ return ModuleVersion(*fixedInfo);
+}
+
+inline LauncherResult<ModuleVersion> GetModuleVersion(HMODULE aModule) {
+ UniquePtr<wchar_t[]> fullPath(GetFullModulePath(aModule));
+ if (!fullPath) {
+ return LAUNCHER_ERROR_GENERIC();
+ }
+
+ return GetModuleVersion(fullPath.get());
+}
+
+#if defined(MOZILLA_INTERNAL_API)
+inline LauncherResult<ModuleVersion> GetModuleVersion(nsIFile* aFile) {
+ if (!aFile) {
+ return LAUNCHER_ERROR_FROM_HRESULT(E_INVALIDARG);
+ }
+
+ nsAutoString fullPath;
+ nsresult rv = aFile->GetPath(fullPath);
+ if (NS_FAILED(rv)) {
+ return LAUNCHER_ERROR_GENERIC();
+ }
+
+ return GetModuleVersion(fullPath.get());
+}
+#endif // defined(MOZILLA_INTERNAL_API)
+
+struct CoTaskMemFreeDeleter {
+ void operator()(void* aPtr) { ::CoTaskMemFree(aPtr); }
+};
+
+inline LauncherResult<TOKEN_ELEVATION_TYPE> GetElevationType(
+ const nsAutoHandle& aToken) {
+ DWORD retLen;
+ TOKEN_ELEVATION_TYPE elevationType;
+ if (!::GetTokenInformation(aToken.get(), TokenElevationType, &elevationType,
+ sizeof(elevationType), &retLen)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ return elevationType;
+}
+
+} // namespace mozilla
+
+#endif // mozilla_WinHeaderOnlyUtils_h
diff --git a/widget/windows/WinIMEHandler.cpp b/widget/windows/WinIMEHandler.cpp
new file mode 100644
index 0000000000..e5733cc8d6
--- /dev/null
+++ b/widget/windows/WinIMEHandler.cpp
@@ -0,0 +1,1180 @@
+/* -*- 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 "WinIMEHandler.h"
+
+#include "IMMHandler.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsWindowDefs.h"
+#include "WinTextEventDispatcherListener.h"
+
+#include "TSFTextStore.h"
+
+#include "OSKInputPaneManager.h"
+#include "nsLookAndFeel.h"
+#include "nsWindow.h"
+#include "WinUtils.h"
+#include "nsIWindowsRegKey.h"
+#include "nsIWindowsUIUtils.h"
+
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif // #ifdef ACCESSIBILITY
+
+#include "shellapi.h"
+#include "shlobj.h"
+#include "powrprof.h"
+#include "setupapi.h"
+#include "cfgmgr32.h"
+
+#include "FxRWindowManager.h"
+#include "VRShMem.h"
+#include "moz_external_vr.h"
+
+const char* kOskPathPrefName = "ui.osk.on_screen_keyboard_path";
+const char* kOskEnabled = "ui.osk.enabled";
+const char* kOskDetectPhysicalKeyboard = "ui.osk.detect_physical_keyboard";
+const char* kOskRequireWin10 = "ui.osk.require_win10";
+const char* kOskDebugReason = "ui.osk.debug.keyboardDisplayReason";
+
+namespace mozilla {
+namespace widget {
+
+/******************************************************************************
+ * IMEHandler
+ ******************************************************************************/
+
+nsWindow* IMEHandler::sFocusedWindow = nullptr;
+InputContextAction::Cause IMEHandler::sLastContextActionCause =
+ InputContextAction::CAUSE_UNKNOWN;
+bool IMEHandler::sMaybeEditable = false;
+bool IMEHandler::sForceDisableCurrentIMM_IME = false;
+bool IMEHandler::sNativeCaretIsCreated = false;
+bool IMEHandler::sHasNativeCaretBeenRequested = false;
+
+bool IMEHandler::sIsInTSFMode = false;
+bool IMEHandler::sIsIMMEnabled = true;
+bool IMEHandler::sAssociateIMCOnlyWhenIMM_IMEActive = false;
+decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr;
+
+static POWER_PLATFORM_ROLE sPowerPlatformRole = PlatformRoleUnspecified;
+static bool sDeterminedPowerPlatformRole = false;
+
+// static
+void IMEHandler::Initialize() {
+ TSFTextStore::Initialize();
+ sIsInTSFMode = TSFTextStore::IsInTSFMode();
+ sIsIMMEnabled =
+ !sIsInTSFMode || Preferences::GetBool("intl.tsf.support_imm", true);
+ sAssociateIMCOnlyWhenIMM_IMEActive =
+ sIsIMMEnabled &&
+ Preferences::GetBool("intl.tsf.associate_imc_only_when_imm_ime_is_active",
+ false);
+ if (!sIsInTSFMode) {
+ // When full TSFTextStore is not available, try to use SetInputScopes API
+ // to enable at least InputScope. Use GET_MODULE_HANDLE_EX_FLAG_PIN to
+ // ensure that msctf.dll will not be unloaded.
+ HMODULE module = nullptr;
+ if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, L"msctf.dll",
+ &module)) {
+ sSetInputScopes = reinterpret_cast<decltype(SetInputScopes)*>(
+ GetProcAddress(module, "SetInputScopes"));
+ }
+ }
+
+ IMMHandler::Initialize();
+
+ sForceDisableCurrentIMM_IME = IMMHandler::IsActiveIMEInBlockList();
+}
+
+// static
+void IMEHandler::Terminate() {
+ if (sIsInTSFMode) {
+ TSFTextStore::Terminate();
+ sIsInTSFMode = false;
+ }
+
+ IMMHandler::Terminate();
+ WinTextEventDispatcherListener::Shutdown();
+}
+
+// static
+void* IMEHandler::GetNativeData(nsWindow* aWindow, uint32_t aDataType) {
+ if (aDataType == NS_RAW_NATIVE_IME_CONTEXT) {
+ if (IsTSFAvailable()) {
+ return TSFTextStore::GetThreadManager();
+ }
+ IMEContext context(aWindow);
+ if (context.IsValid()) {
+ return context.get();
+ }
+ // If IMC isn't associated with the window, IME is disabled on the window
+ // now. In such case, we should return default IMC instead.
+ const IMEContext& defaultIMC = aWindow->DefaultIMC();
+ if (defaultIMC.IsValid()) {
+ return defaultIMC.get();
+ }
+ // If there is no default IMC, we should return the pointer to the window
+ // since if we return nullptr, IMEStateManager cannot manage composition
+ // with TextComposition instance. This is possible if no IME is installed,
+ // but composition may occur with dead key sequence.
+ return aWindow;
+ }
+
+ void* result = TSFTextStore::GetNativeData(aDataType);
+ if (!result || !(*(static_cast<void**>(result)))) {
+ return nullptr;
+ }
+ // XXX During the TSF module test, sIsInTSFMode must be true. After that,
+ // the value should be restored but currently, there is no way for that.
+ // When the TSF test is enabled again, we need to fix this. Perhaps,
+ // sending a message can fix this.
+ sIsInTSFMode = true;
+ return result;
+}
+
+// static
+bool IMEHandler::ProcessRawKeyMessage(const MSG& aMsg) {
+ if (IsTSFAvailable()) {
+ return TSFTextStore::ProcessRawKeyMessage(aMsg);
+ }
+ return false; // noting to do in IMM mode.
+}
+
+// static
+bool IMEHandler::ProcessMessage(nsWindow* aWindow, UINT aMessage,
+ WPARAM& aWParam, LPARAM& aLParam,
+ MSGResult& aResult) {
+ // If we're putting native caret over our caret, Windows dispatches
+ // EVENT_OBJECT_LOCATIONCHANGE event on other applications which hook
+ // the event with ::SetWinEventHook() and handles WM_GETOBJECT for
+ // OBJID_CARET (this is request of caret from such applications) instead
+ // of us. If a11y module is active, it observes every our caret change
+ // and put native caret over it automatically. However, if other
+ // applications require only caret information, activating a11y module is
+ // overwork and such applications may requires carets only in editors.
+ // Therefore, if it'd be possible, IMEHandler should put native caret over
+ // our caret, but there is a problem. Some versions of ATOK (Japanese TIP)
+ // refer native caret and if there is, the behavior is worse than the
+ // behavior without native caret. Therefore, we shouldn't put native caret
+ // as far as possible.
+ if (!sHasNativeCaretBeenRequested && aMessage == WM_GETOBJECT &&
+ static_cast<DWORD>(aLParam) == OBJID_CARET) {
+ // So, when we receive first WM_GETOBJECT for OBJID_CARET, let's start to
+ // create native caret for such applications.
+ sHasNativeCaretBeenRequested = true;
+ // If an editable element has focus, we can put native caret now.
+ // XXX Should we avoid doing this if there is composition?
+ MaybeCreateNativeCaret(aWindow);
+ }
+
+ if (IsTSFAvailable()) {
+ TSFTextStore::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult);
+ if (aResult.mConsumed) {
+ return true;
+ }
+ // If we don't support IMM in TSF mode, we don't use IMMHandler.
+ if (!sIsIMMEnabled) {
+ return false;
+ }
+ // IME isn't implemented with IMM, IMMHandler shouldn't handle any
+ // messages.
+ if (!IsIMMActive()) {
+ return false;
+ }
+ }
+
+ bool keepGoing =
+ IMMHandler::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult);
+
+ // If user changes active IME to an IME which is listed in our block list,
+ // we should disassociate IMC from the window for preventing the IME to work
+ // and crash.
+ if (aMessage == WM_INPUTLANGCHANGE) {
+ bool disableIME = IMMHandler::IsActiveIMEInBlockList();
+ if (disableIME != sForceDisableCurrentIMM_IME) {
+ bool enable =
+ !disableIME && WinUtils::IsIMEEnabled(aWindow->InputContextRef());
+ AssociateIMEContext(aWindow, enable);
+ sForceDisableCurrentIMM_IME = disableIME;
+ }
+ }
+
+ return keepGoing;
+}
+
+// static
+bool IMEHandler::IsA11yHandlingNativeCaret() {
+#ifndef ACCESSIBILITY
+ return false;
+#else // #ifndef ACCESSIBILITY
+ // Let's assume that when there is the service, it handles native caret.
+ return GetAccService() != nullptr;
+#endif // #ifndef ACCESSIBILITY #else
+}
+
+// static
+bool IMEHandler::IsIMMActive() { return TSFTextStore::IsIMM_IMEActive(); }
+
+// static
+bool IMEHandler::IsComposing() {
+ if (IsTSFAvailable()) {
+ return TSFTextStore::IsComposing() || IMMHandler::IsComposing();
+ }
+
+ return IMMHandler::IsComposing();
+}
+
+// static
+bool IMEHandler::IsComposingOn(nsWindow* aWindow) {
+ if (IsTSFAvailable()) {
+ return TSFTextStore::IsComposingOn(aWindow) ||
+ IMMHandler::IsComposingOn(aWindow);
+ }
+
+ return IMMHandler::IsComposingOn(aWindow);
+}
+
+// static
+nsresult IMEHandler::NotifyIME(nsWindow* aWindow,
+ const IMENotification& aIMENotification) {
+ if (IsTSFAvailable()) {
+ switch (aIMENotification.mMessage) {
+ case NOTIFY_IME_OF_SELECTION_CHANGE: {
+ nsresult rv = TSFTextStore::OnSelectionChange(aIMENotification);
+ // If IMM IME is active, we need to notify IMMHandler of updating
+ // composition change. It will adjust candidate window position or
+ // composition window position.
+ bool isIMMActive = IsIMMActive();
+ if (isIMMActive) {
+ IMMHandler::OnUpdateComposition(aWindow);
+ }
+ IMMHandler::OnSelectionChange(aWindow, aIMENotification, isIMMActive);
+ return rv;
+ }
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
+ // If IMM IME is active, we need to notify IMMHandler of updating
+ // composition change. It will adjust candidate window position or
+ // composition window position.
+ if (IsIMMActive()) {
+ IMMHandler::OnUpdateComposition(aWindow);
+ } else {
+ TSFTextStore::OnUpdateComposition();
+ }
+ return NS_OK;
+ case NOTIFY_IME_OF_TEXT_CHANGE:
+ return TSFTextStore::OnTextChange(aIMENotification);
+ case NOTIFY_IME_OF_FOCUS: {
+ sFocusedWindow = aWindow;
+ IMMHandler::OnFocusChange(true, aWindow);
+ nsresult rv = TSFTextStore::OnFocusChange(true, aWindow,
+ aWindow->GetInputContext());
+ MaybeCreateNativeCaret(aWindow);
+ IMEHandler::MaybeShowOnScreenKeyboard(aWindow,
+ aWindow->GetInputContext());
+ return rv;
+ }
+ case NOTIFY_IME_OF_BLUR:
+ sFocusedWindow = nullptr;
+ IMEHandler::MaybeDismissOnScreenKeyboard(aWindow);
+ IMMHandler::OnFocusChange(false, aWindow);
+ return TSFTextStore::OnFocusChange(false, aWindow,
+ aWindow->GetInputContext());
+ case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
+ // If IMM IME is active, we should send a mouse button event via IMM.
+ if (IsIMMActive()) {
+ return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification);
+ }
+ return TSFTextStore::OnMouseButtonEvent(aIMENotification);
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ if (TSFTextStore::IsComposingOn(aWindow)) {
+ TSFTextStore::CommitComposition(false);
+ } else if (IsIMMActive()) {
+ IMMHandler::CommitComposition(aWindow);
+ }
+ return NS_OK;
+ case REQUEST_TO_CANCEL_COMPOSITION:
+ if (TSFTextStore::IsComposingOn(aWindow)) {
+ TSFTextStore::CommitComposition(true);
+ } else if (IsIMMActive()) {
+ IMMHandler::CancelComposition(aWindow);
+ }
+ return NS_OK;
+ case NOTIFY_IME_OF_POSITION_CHANGE:
+ return TSFTextStore::OnLayoutChange();
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ }
+
+ switch (aIMENotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ IMMHandler::CommitComposition(aWindow);
+ return NS_OK;
+ case REQUEST_TO_CANCEL_COMPOSITION:
+ IMMHandler::CancelComposition(aWindow);
+ return NS_OK;
+ case NOTIFY_IME_OF_POSITION_CHANGE:
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
+ IMMHandler::OnUpdateComposition(aWindow);
+ return NS_OK;
+ case NOTIFY_IME_OF_SELECTION_CHANGE:
+ IMMHandler::OnSelectionChange(aWindow, aIMENotification, true);
+ // IMMHandler::OnSelectionChange() cannot work without its singleton
+ // instance. Therefore, IMEHandler needs to create native caret instead
+ // if it's necessary.
+ MaybeCreateNativeCaret(aWindow);
+ return NS_OK;
+ case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
+ return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification);
+ case NOTIFY_IME_OF_FOCUS:
+ sFocusedWindow = aWindow;
+ IMMHandler::OnFocusChange(true, aWindow);
+ IMEHandler::MaybeShowOnScreenKeyboard(aWindow,
+ aWindow->GetInputContext());
+ MaybeCreateNativeCaret(aWindow);
+ return NS_OK;
+ case NOTIFY_IME_OF_BLUR:
+ sFocusedWindow = nullptr;
+ IMEHandler::MaybeDismissOnScreenKeyboard(aWindow);
+ IMMHandler::OnFocusChange(false, aWindow);
+ // If a plugin gets focus while TSF has focus, we need to notify TSF of
+ // the blur.
+ if (TSFTextStore::ThinksHavingFocus()) {
+ return TSFTextStore::OnFocusChange(false, aWindow,
+ aWindow->GetInputContext());
+ }
+ return NS_OK;
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+}
+
+// static
+IMENotificationRequests IMEHandler::GetIMENotificationRequests() {
+ if (IsTSFAvailable()) {
+ if (!sIsIMMEnabled) {
+ return TSFTextStore::GetIMENotificationRequests();
+ }
+ // Even if TSF is available, the active IME may be an IMM-IME.
+ // Unfortunately, changing the result of GetIMENotificationRequests() while
+ // an editor has focus isn't supported by IMEContentObserver nor
+ // ContentCacheInParent. Therefore, we need to request whole notifications
+ // which are necessary either IMMHandler or TSFTextStore.
+ return IMMHandler::GetIMENotificationRequests() |
+ TSFTextStore::GetIMENotificationRequests();
+ }
+
+ return IMMHandler::GetIMENotificationRequests();
+}
+
+// static
+TextEventDispatcherListener*
+IMEHandler::GetNativeTextEventDispatcherListener() {
+ return WinTextEventDispatcherListener::GetInstance();
+}
+
+// static
+bool IMEHandler::GetOpenState(nsWindow* aWindow) {
+ if (IsTSFAvailable() && !IsIMMActive()) {
+ return TSFTextStore::GetIMEOpenState();
+ }
+
+ IMEContext context(aWindow);
+ return context.GetOpenState();
+}
+
+// static
+void IMEHandler::OnDestroyWindow(nsWindow* aWindow) {
+ // When focus is in remote process, but the window is being destroyed, we
+ // need to clean up TSFTextStore here since NOTIFY_IME_OF_BLUR won't reach
+ // here because BrowserParent already lost the reference to the nsWindow when
+ // it receives from the remote process.
+ if (sFocusedWindow == aWindow) {
+ MOZ_ASSERT(aWindow->GetInputContext().IsOriginContentProcess(),
+ "input context of focused widget should've been set by a remote "
+ "process "
+ "if IME focus isn't cleared before destroying the widget");
+ NotifyIME(aWindow, IMENotification(NOTIFY_IME_OF_BLUR));
+ }
+
+ // We need to do nothing here for TSF. Just restore the default context
+ // if it's been disassociated.
+ if (!sIsInTSFMode) {
+ // MSDN says we need to set IS_DEFAULT to avoid memory leak when we use
+ // SetInputScopes API. Use an empty string to do this.
+ SetInputScopeForIMM32(aWindow, u""_ns, u""_ns, false);
+ }
+ AssociateIMEContext(aWindow, true);
+}
+
+// static
+bool IMEHandler::NeedsToAssociateIMC() {
+ return !sForceDisableCurrentIMM_IME &&
+ (!sAssociateIMCOnlyWhenIMM_IMEActive || !IsIMMActive());
+}
+
+// static
+void IMEHandler::SetInputContext(nsWindow* aWindow, InputContext& aInputContext,
+ const InputContextAction& aAction) {
+ sLastContextActionCause = aAction.mCause;
+ // FYI: If there is no composition, this call will do nothing.
+ NotifyIME(aWindow, IMENotification(REQUEST_TO_COMMIT_COMPOSITION));
+
+ if (aInputContext.mHTMLInputInputmode.EqualsLiteral("none")) {
+ IMEHandler::MaybeDismissOnScreenKeyboard(aWindow, Sync::Yes);
+ } else if (aAction.UserMightRequestOpenVKB()) {
+ IMEHandler::MaybeShowOnScreenKeyboard(aWindow, aInputContext);
+ }
+
+ bool enable = WinUtils::IsIMEEnabled(aInputContext);
+ bool adjustOpenState = (enable && aInputContext.mIMEState.mOpen !=
+ IMEState::DONT_CHANGE_OPEN_STATE);
+ bool open =
+ (adjustOpenState && aInputContext.mIMEState.mOpen == IMEState::OPEN);
+
+ // Note that even while a plugin has focus, we need to notify TSF of that.
+ if (sIsInTSFMode) {
+ TSFTextStore::SetInputContext(aWindow, aInputContext, aAction);
+ if (IsTSFAvailable()) {
+ if (sIsIMMEnabled) {
+ // Associate IMC with aWindow only when it's necessary.
+ AssociateIMEContext(aWindow, enable && NeedsToAssociateIMC());
+ }
+ if (adjustOpenState) {
+ TSFTextStore::SetIMEOpenState(open);
+ }
+ return;
+ }
+ } else {
+ // Set at least InputScope even when TextStore is not available.
+ SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType,
+ aInputContext.mHTMLInputInputmode,
+ aInputContext.mInPrivateBrowsing);
+ }
+
+ AssociateIMEContext(aWindow, enable);
+
+ IMEContext context(aWindow);
+ if (adjustOpenState) {
+ context.SetOpenState(open);
+ }
+}
+
+// static
+void IMEHandler::AssociateIMEContext(nsWindowBase* aWindowBase, bool aEnable) {
+ IMEContext context(aWindowBase);
+ if (aEnable) {
+ context.AssociateDefaultContext();
+ return;
+ }
+ // Don't disassociate the context after the window is destroyed.
+ if (aWindowBase->Destroyed()) {
+ return;
+ }
+ context.Disassociate();
+}
+
+// static
+void IMEHandler::InitInputContext(nsWindow* aWindow,
+ InputContext& aInputContext) {
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->GetWindowHandle(),
+ "IMEHandler::SetInputContext() requires non-nullptr HWND");
+
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ sInitialized = true;
+ // Some TIPs like QQ Input (Simplified Chinese) may need normal window
+ // (i.e., windows except message window) when initializing themselves.
+ // Therefore, we need to initialize TSF/IMM modules after first normal
+ // window is created. InitInputContext() should be called immediately
+ // after creating each normal window, so, here is a good place to
+ // initialize these modules.
+ Initialize();
+ }
+
+ // For a11y, the default enabled state should be 'enabled'.
+ aInputContext.mIMEState.mEnabled = IMEEnabled::Enabled;
+
+ if (sIsInTSFMode) {
+ TSFTextStore::SetInputContext(
+ aWindow, aInputContext,
+ InputContextAction(InputContextAction::CAUSE_UNKNOWN,
+ InputContextAction::WIDGET_CREATED));
+ // IME context isn't necessary in pure TSF mode.
+ if (!sIsIMMEnabled) {
+ AssociateIMEContext(aWindow, false);
+ }
+ return;
+ }
+
+#ifdef DEBUG
+ // NOTE: IMC may be null if IMM module isn't installed.
+ IMEContext context(aWindow);
+ MOZ_ASSERT(context.IsValid() || !CurrentKeyboardLayoutHasIME());
+#endif // #ifdef DEBUG
+}
+
+#ifdef DEBUG
+// static
+bool IMEHandler::CurrentKeyboardLayoutHasIME() {
+ if (sIsInTSFMode) {
+ return TSFTextStore::CurrentKeyboardLayoutHasIME();
+ }
+
+ return IMMHandler::IsIMEAvailable();
+}
+#endif // #ifdef DEBUG
+
+// static
+void IMEHandler::OnKeyboardLayoutChanged() {
+ // Be aware, this method won't be called until TSFStaticSink starts to
+ // observe active TIP change. If you need to be notified of this, you
+ // need to create TSFStaticSink::Observe() or something and call it
+ // TSFStaticSink::EnsureInitActiveTIPKeyboard() forcibly.
+
+ if (!sIsIMMEnabled || !IsTSFAvailable()) {
+ return;
+ }
+
+ // We don't need to do anything when sAssociateIMCOnlyWhenIMM_IMEActive is
+ // false because IMContext won't be associated/disassociated when changing
+ // active keyboard layout/IME.
+ if (!sAssociateIMCOnlyWhenIMM_IMEActive) {
+ return;
+ }
+
+ // If there is no TSFTextStore which has focus, i.e., no editor has focus,
+ // nothing to do here.
+ nsWindowBase* windowBase = TSFTextStore::GetEnabledWindowBase();
+ if (!windowBase) {
+ return;
+ }
+
+ // If IME isn't available, nothing to do here.
+ InputContext inputContext = windowBase->GetInputContext();
+ if (!WinUtils::IsIMEEnabled(inputContext)) {
+ return;
+ }
+
+ // Associate or Disassociate IMC if it's necessary.
+ // Note that this does nothing if the window has already associated with or
+ // disassociated from the window.
+ AssociateIMEContext(windowBase, NeedsToAssociateIMC());
+}
+
+// static
+void IMEHandler::SetInputScopeForIMM32(nsWindow* aWindow,
+ const nsAString& aHTMLInputType,
+ const nsAString& aHTMLInputInputmode,
+ bool aInPrivateBrowsing) {
+ if (sIsInTSFMode || !sSetInputScopes || aWindow->Destroyed()) {
+ return;
+ }
+ AutoTArray<InputScope, 3> scopes;
+
+ // IME may refer only first input scope, but we will append inputmode's
+ // input scopes since IME may refer it like Chrome.
+ AppendInputScopeFromType(aHTMLInputType, scopes);
+ AppendInputScopeFromInputmode(aHTMLInputInputmode, scopes);
+
+ if (aInPrivateBrowsing) {
+ scopes.AppendElement(IS_PRIVATE);
+ }
+
+ if (scopes.IsEmpty()) {
+ // At least, 1 item is necessary.
+ scopes.AppendElement(IS_DEFAULT);
+ }
+
+ sSetInputScopes(aWindow->GetWindowHandle(), scopes.Elements(),
+ scopes.Length(), nullptr, 0, nullptr, nullptr);
+}
+
+// static
+void IMEHandler::AppendInputScopeFromInputmode(const nsAString& aInputmode,
+ nsTArray<InputScope>& aScopes) {
+ if (aInputmode.EqualsLiteral("mozAwesomebar")) {
+ // Even if Awesomebar has focus, user may not input URL directly.
+ // However, on-screen keyboard for URL should be shown because it has
+ // some useful additional keys like ".com" and they are not hindrances
+ // even when inputting non-URL text, e.g., words to search something in
+ // the web. On the other hand, a lot of Microsoft's IMEs and Google
+ // Japanese Input make their open state "closed" automatically if we
+ // notify them of URL as the input scope. However, this is very annoying
+ // for the users when they try to input some words to search the web or
+ // bookmark/history items. Therefore, if they are active, we need to
+ // notify them of the default input scope for avoiding this issue.
+ // FYI: We cannot check active TIP without TSF. Therefore, if it's
+ // not in TSF mode, this will check only if active IMM-IME is Google
+ // Japanese Input. Google Japanese Input is a TIP of TSF basically.
+ // However, if the OS is Win7 or it's installed on Win7 but has not
+ // been updated yet even after the OS is upgraded to Win8 or later,
+ // it's installed as IMM-IME.
+ if (TSFTextStore::ShouldSetInputScopeOfURLBarToDefault()) {
+ return;
+ }
+ // Don't append IS_SEARCH here for showing on-screen keyboard for URL.
+ if (!aScopes.Contains(IS_URL)) {
+ aScopes.AppendElement(IS_URL);
+ }
+ return;
+ }
+
+ // https://html.spec.whatwg.org/dev/interaction.html#attr-inputmode
+ if (aInputmode.EqualsLiteral("url")) {
+ if (!aScopes.Contains(IS_SEARCH)) {
+ aScopes.AppendElement(IS_URL);
+ }
+ return;
+ }
+ if (aInputmode.EqualsLiteral("email")) {
+ if (!aScopes.Contains(IS_EMAIL_SMTPEMAILADDRESS)) {
+ aScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS);
+ }
+ return;
+ }
+ if (aInputmode.EqualsLiteral("tel")) {
+ if (!aScopes.Contains(IS_TELEPHONE_FULLTELEPHONENUMBER)) {
+ aScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER);
+ }
+ if (!aScopes.Contains(IS_TELEPHONE_LOCALNUMBER)) {
+ aScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER);
+ }
+ return;
+ }
+ if (aInputmode.EqualsLiteral("numeric")) {
+ if (!aScopes.Contains(IS_DIGITS)) {
+ aScopes.AppendElement(IS_DIGITS);
+ }
+ return;
+ }
+ if (aInputmode.EqualsLiteral("decimal")) {
+ if (!aScopes.Contains(IS_NUMBER)) {
+ aScopes.AppendElement(IS_NUMBER);
+ }
+ return;
+ }
+ if (aInputmode.EqualsLiteral("search")) {
+ if (!aScopes.Contains(IS_SEARCH)) {
+ aScopes.AppendElement(IS_SEARCH);
+ }
+ return;
+ }
+}
+
+// static
+void IMEHandler::AppendInputScopeFromType(const nsAString& aHTMLInputType,
+ nsTArray<InputScope>& aScopes) {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html
+ if (aHTMLInputType.EqualsLiteral("url")) {
+ aScopes.AppendElement(IS_URL);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("search")) {
+ aScopes.AppendElement(IS_SEARCH);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("email")) {
+ aScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("password")) {
+ aScopes.AppendElement(IS_PASSWORD);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("datetime") ||
+ aHTMLInputType.EqualsLiteral("datetime-local")) {
+ aScopes.AppendElement(IS_DATE_FULLDATE);
+ aScopes.AppendElement(IS_TIME_FULLTIME);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("date") ||
+ aHTMLInputType.EqualsLiteral("month") ||
+ aHTMLInputType.EqualsLiteral("week")) {
+ aScopes.AppendElement(IS_DATE_FULLDATE);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("time")) {
+ aScopes.AppendElement(IS_TIME_FULLTIME);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("tel")) {
+ aScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER);
+ aScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("number")) {
+ aScopes.AppendElement(IS_NUMBER);
+ return;
+ }
+}
+
+// static
+void IMEHandler::MaybeShowOnScreenKeyboard(nsWindow* aWindow,
+ const InputContext& aInputContext) {
+ if (aInputContext.mHTMLInputInputmode.EqualsLiteral("none")) {
+ return;
+ }
+#ifdef NIGHTLY_BUILD
+ if (FxRWindowManager::GetInstance()->IsFxRWindow(sFocusedWindow)) {
+ mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
+ shmem.SendIMEState(FxRWindowManager::GetInstance()->GetWindowID(),
+ mozilla::gfx::VRFxEventState::FOCUS);
+ return;
+ }
+#endif // NIGHTLY_BUILD
+ if (!IsWin8OrLater() || !Preferences::GetBool(kOskEnabled, true) ||
+ GetOnScreenKeyboardWindow() || !IMEHandler::NeedOnScreenKeyboard()) {
+ return;
+ }
+
+ // On Windows 10 we require tablet mode, unless the user has set the relevant
+ // Windows setting to enable the on-screen keyboard in desktop mode.
+ // We might be disabled specifically on Win8(.1), so we check that afterwards.
+ if (IsWin10OrLater()) {
+ if (!IsInTabletMode() && !AutoInvokeOnScreenKeyboardInDesktopMode()) {
+ return;
+ }
+ } else if (Preferences::GetBool(kOskRequireWin10, true)) {
+ return;
+ }
+
+ IMEHandler::ShowOnScreenKeyboard(aWindow);
+}
+
+// static
+void IMEHandler::MaybeDismissOnScreenKeyboard(nsWindow* aWindow, Sync aSync) {
+#ifdef NIGHTLY_BUILD
+ if (FxRWindowManager::GetInstance()->IsFxRWindow(aWindow)) {
+ mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
+ shmem.SendIMEState(FxRWindowManager::GetInstance()->GetWindowID(),
+ mozilla::gfx::VRFxEventState::BLUR);
+ }
+#endif // NIGHTLY_BUILD
+ if (!IsWin8OrLater()) {
+ return;
+ }
+
+ if (aSync == Sync::Yes) {
+ DismissOnScreenKeyboard(aWindow);
+ return;
+ }
+
+ RefPtr<nsWindow> window(aWindow);
+ NS_DispatchToCurrentThreadQueue(
+ NS_NewRunnableFunction("IMEHandler::MaybeDismissOnScreenKeyboard",
+ [window]() {
+ if (window->Destroyed()) {
+ return;
+ }
+ if (!sFocusedWindow) {
+ DismissOnScreenKeyboard(window);
+ }
+ }),
+ EventQueuePriority::Idle);
+}
+
+// static
+bool IMEHandler::WStringStartsWithCaseInsensitive(const std::wstring& aHaystack,
+ const std::wstring& aNeedle) {
+ std::wstring lowerCaseHaystack(aHaystack);
+ std::wstring lowerCaseNeedle(aNeedle);
+ std::transform(lowerCaseHaystack.begin(), lowerCaseHaystack.end(),
+ lowerCaseHaystack.begin(), ::tolower);
+ std::transform(lowerCaseNeedle.begin(), lowerCaseNeedle.end(),
+ lowerCaseNeedle.begin(), ::tolower);
+ return wcsstr(lowerCaseHaystack.c_str(), lowerCaseNeedle.c_str()) ==
+ lowerCaseHaystack.c_str();
+}
+
+// Returns false if a physical keyboard is detected on Windows 8 and up,
+// or there is some other reason why an onscreen keyboard is not necessary.
+// Returns true if no keyboard is found and this device looks like it needs
+// an on-screen keyboard for text input.
+// static
+bool IMEHandler::NeedOnScreenKeyboard() {
+ // This function is only supported for Windows 8 and up.
+ if (!IsWin8OrLater()) {
+ Preferences::SetString(kOskDebugReason, L"IKPOS: Requires Win8+.");
+ return false;
+ }
+
+ if (!Preferences::GetBool(kOskDetectPhysicalKeyboard, true)) {
+ Preferences::SetString(kOskDebugReason, L"IKPOS: Detection disabled.");
+ return true;
+ }
+
+ // If the last focus cause was not user-initiated (ie a result of code
+ // setting focus to an element) then don't auto-show a keyboard. This
+ // avoids cases where the keyboard would pop up "just" because e.g. a
+ // web page chooses to focus a search field on the page, even when that
+ // really isn't what the user is trying to do at that moment.
+ if (!InputContextAction::IsHandlingUserInput(sLastContextActionCause)) {
+ return false;
+ }
+
+ // This function should be only invoked for machines with touch screens.
+ if ((::GetSystemMetrics(SM_DIGITIZER) & NID_INTEGRATED_TOUCH) !=
+ NID_INTEGRATED_TOUCH) {
+ Preferences::SetString(kOskDebugReason, L"IKPOS: Touch screen not found.");
+ return false;
+ }
+
+ // If the device is docked, the user is treating the device as a PC.
+ if (::GetSystemMetrics(SM_SYSTEMDOCKED) != 0) {
+ Preferences::SetString(kOskDebugReason, L"IKPOS: System docked.");
+ return false;
+ }
+
+ // To determine whether a keyboard is present on the device, we do the
+ // following:-
+ // 1. If the platform role is that of a mobile or slate device, check the
+ // system metric SM_CONVERTIBLESLATEMODE to see if it is being used
+ // in slate mode. If it is, also check that the last input was a touch.
+ // If all of this is true, then we should show the on-screen keyboard.
+
+ // 2. If step 1 didn't determine we should show the keyboard, we check if
+ // this device has keyboards attached to it.
+
+ // Check if the device is being used as a laptop or a tablet. This can be
+ // checked by first checking the role of the device and then the
+ // corresponding system metric (SM_CONVERTIBLESLATEMODE). If it is being
+ // used as a tablet then we want the OSK to show up.
+ if (!sDeterminedPowerPlatformRole) {
+ sDeterminedPowerPlatformRole = true;
+ sPowerPlatformRole = WinUtils::GetPowerPlatformRole();
+ }
+
+ // If this a mobile or slate (tablet) device, check if it is in slate mode.
+ // If the last input was touch, ignore whether or not a keyboard is present.
+ if ((sPowerPlatformRole == PlatformRoleMobile ||
+ sPowerPlatformRole == PlatformRoleSlate) &&
+ ::GetSystemMetrics(SM_CONVERTIBLESLATEMODE) == 0 &&
+ sLastContextActionCause == InputContextAction::CAUSE_TOUCH) {
+ Preferences::SetString(
+ kOskDebugReason,
+ L"IKPOS: Mobile/Slate Platform role, in slate mode with touch event.");
+ return true;
+ }
+
+ return !IMEHandler::IsKeyboardPresentOnSlate();
+}
+
+// Uses the Setup APIs to enumerate the attached keyboards and returns true
+// if the keyboard count is 1 or more. While this will work in most cases
+// it won't work if there are devices which expose keyboard interfaces which
+// are attached to the machine.
+// Based on IsKeyboardPresentOnSlate() in Chromium's base/win/win_util.cc.
+// static
+bool IMEHandler::IsKeyboardPresentOnSlate() {
+ const GUID KEYBOARD_CLASS_GUID = {
+ 0x4D36E96B,
+ 0xE325,
+ 0x11CE,
+ {0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18}};
+
+ // Query for all the keyboard devices.
+ HDEVINFO device_info = ::SetupDiGetClassDevs(&KEYBOARD_CLASS_GUID, nullptr,
+ nullptr, DIGCF_PRESENT);
+ if (device_info == INVALID_HANDLE_VALUE) {
+ Preferences::SetString(kOskDebugReason, L"IKPOS: No keyboard info.");
+ return false;
+ }
+
+ // Enumerate all keyboards and look for ACPI\PNP and HID\VID devices. If
+ // the count is more than 1 we assume that a keyboard is present. This is
+ // under the assumption that there will always be one keyboard device.
+ for (DWORD i = 0;; ++i) {
+ SP_DEVINFO_DATA device_info_data = {0};
+ device_info_data.cbSize = sizeof(device_info_data);
+ if (!::SetupDiEnumDeviceInfo(device_info, i, &device_info_data)) {
+ break;
+ }
+
+ // Get the device ID.
+ wchar_t device_id[MAX_DEVICE_ID_LEN];
+ CONFIGRET status = ::CM_Get_Device_ID(device_info_data.DevInst, device_id,
+ MAX_DEVICE_ID_LEN, 0);
+ if (status == CR_SUCCESS) {
+ static const std::wstring BT_HID_DEVICE = L"HID\\{00001124";
+ static const std::wstring BT_HOGP_DEVICE = L"HID\\{00001812";
+ // To reduce the scope of the hack we only look for ACPI and HID\\VID
+ // prefixes in the keyboard device ids.
+ if (IMEHandler::WStringStartsWithCaseInsensitive(device_id, L"ACPI") ||
+ IMEHandler::WStringStartsWithCaseInsensitive(device_id,
+ L"HID\\VID") ||
+ IMEHandler::WStringStartsWithCaseInsensitive(device_id,
+ BT_HID_DEVICE) ||
+ IMEHandler::WStringStartsWithCaseInsensitive(device_id,
+ BT_HOGP_DEVICE)) {
+ // The heuristic we are using is to check the count of keyboards and
+ // return true if the API's report one or more keyboards. Please note
+ // that this will break for non keyboard devices which expose a
+ // keyboard PDO.
+ Preferences::SetString(kOskDebugReason,
+ L"IKPOS: Keyboard presence confirmed.");
+ return true;
+ }
+ }
+ }
+ Preferences::SetString(kOskDebugReason,
+ L"IKPOS: Lack of keyboard confirmed.");
+ return false;
+}
+
+// static
+bool IMEHandler::IsInTabletMode() {
+ nsCOMPtr<nsIWindowsUIUtils> uiUtils(
+ do_GetService("@mozilla.org/windows-ui-utils;1"));
+ if (NS_WARN_IF(!uiUtils)) {
+ Preferences::SetString(kOskDebugReason,
+ L"IITM: nsIWindowsUIUtils not available.");
+ return false;
+ }
+ bool isInTabletMode = false;
+ uiUtils->GetInTabletMode(&isInTabletMode);
+ if (isInTabletMode) {
+ Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=true.");
+ } else {
+ Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=false.");
+ }
+ return isInTabletMode;
+}
+
+static bool ReadEnableDesktopModeAutoInvoke(uint32_t aRoot,
+ nsIWindowsRegKey* aRegKey,
+ uint32_t& aValue) {
+ nsresult rv;
+ rv = aRegKey->Open(aRoot, u"SOFTWARE\\Microsoft\\TabletTip\\1.7"_ns,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv)) {
+ Preferences::SetString(kOskDebugReason,
+ L"AIOSKIDM: failed opening regkey.");
+ return false;
+ }
+ // EnableDesktopModeAutoInvoke is an opt-in option from the Windows
+ // Settings to "Automatically show the touch keyboard in windowed apps
+ // when there's no keyboard attached to your device." If the user has
+ // opted-in to this behavior, the tablet-mode requirement is skipped.
+ rv = aRegKey->ReadIntValue(u"EnableDesktopModeAutoInvoke"_ns, &aValue);
+ if (NS_FAILED(rv)) {
+ Preferences::SetString(kOskDebugReason,
+ L"AIOSKIDM: failed reading value of regkey.");
+ return false;
+ }
+ return true;
+}
+
+// static
+bool IMEHandler::AutoInvokeOnScreenKeyboardInDesktopMode() {
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> regKey(
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Preferences::SetString(kOskDebugReason,
+ L"AIOSKIDM: "
+ L"nsIWindowsRegKey not available");
+ return false;
+ }
+
+ uint32_t value;
+ if (!ReadEnableDesktopModeAutoInvoke(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ regKey, value) &&
+ !ReadEnableDesktopModeAutoInvoke(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
+ regKey, value)) {
+ return false;
+ }
+ if (!!value) {
+ Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=true.");
+ } else {
+ Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=false.");
+ }
+ return !!value;
+}
+
+// Based on DisplayVirtualKeyboard() in Chromium's base/win/win_util.cc.
+// static
+void IMEHandler::ShowOnScreenKeyboard(nsWindow* aWindow) {
+ if (IsWin10AnniversaryUpdateOrLater()) {
+ OSKInputPaneManager::ShowOnScreenKeyboard(aWindow->GetWindowHandle());
+ return;
+ }
+
+ nsAutoString cachedPath;
+ nsresult result = Preferences::GetString(kOskPathPrefName, cachedPath);
+ if (NS_FAILED(result) || cachedPath.IsEmpty()) {
+ wchar_t path[MAX_PATH];
+ // The path to TabTip.exe is defined at the following registry key.
+ // This is pulled out of the 64-bit registry hive directly.
+ const wchar_t kRegKeyName[] =
+ L"Software\\Classes\\CLSID\\"
+ L"{054AAE20-4BEA-4347-8A35-64A533254A9D}\\LocalServer32";
+ if (!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, kRegKeyName, nullptr,
+ path, sizeof path)) {
+ return;
+ }
+
+ std::wstring wstrpath(path);
+ // The path provided by the registry will often contain
+ // %CommonProgramFiles%, which will need to be replaced if it is present.
+ size_t commonProgramFilesOffset = wstrpath.find(L"%CommonProgramFiles%");
+ if (commonProgramFilesOffset != std::wstring::npos) {
+ // The path read from the registry contains the %CommonProgramFiles%
+ // environment variable prefix. On 64 bit Windows the
+ // SHGetKnownFolderPath function returns the common program files path
+ // with the X86 suffix for the FOLDERID_ProgramFilesCommon value.
+ // To get the correct path to TabTip.exe we first read the environment
+ // variable CommonProgramW6432 which points to the desired common
+ // files path. Failing that we fallback to the SHGetKnownFolderPath API.
+
+ // We then replace the %CommonProgramFiles% value with the actual common
+ // files path found in the process.
+ std::wstring commonProgramFilesPath;
+ std::vector<wchar_t> commonProgramFilesPathW6432;
+ DWORD bufferSize =
+ ::GetEnvironmentVariableW(L"CommonProgramW6432", nullptr, 0);
+ if (bufferSize) {
+ commonProgramFilesPathW6432.resize(bufferSize);
+ ::GetEnvironmentVariableW(L"CommonProgramW6432",
+ commonProgramFilesPathW6432.data(),
+ bufferSize);
+ commonProgramFilesPath =
+ std::wstring(commonProgramFilesPathW6432.data());
+ } else {
+ PWSTR path = nullptr;
+ HRESULT hres = SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0,
+ nullptr, &path);
+ if (FAILED(hres) || !path) {
+ return;
+ }
+ commonProgramFilesPath =
+ static_cast<const wchar_t*>(nsDependentString(path).get());
+ ::CoTaskMemFree(path);
+ }
+ wstrpath.replace(commonProgramFilesOffset,
+ wcslen(L"%CommonProgramFiles%"), commonProgramFilesPath);
+ }
+
+ cachedPath.Assign(wstrpath.data());
+ Preferences::SetString(kOskPathPrefName, cachedPath);
+ }
+
+ const char16_t* cachedPathPtr;
+ cachedPath.GetData(&cachedPathPtr);
+ ShellExecuteW(nullptr, L"", char16ptr_t(cachedPathPtr), nullptr, nullptr,
+ SW_SHOW);
+}
+
+// Based on DismissVirtualKeyboard() in Chromium's base/win/win_util.cc.
+// static
+void IMEHandler::DismissOnScreenKeyboard(nsWindow* aWindow) {
+ // Dismiss the virtual keyboard if it's open
+ if (IsWin10AnniversaryUpdateOrLater()) {
+ OSKInputPaneManager::DismissOnScreenKeyboard(aWindow->GetWindowHandle());
+ return;
+ }
+
+ HWND osk = GetOnScreenKeyboardWindow();
+ if (osk) {
+ ::PostMessage(osk, WM_SYSCOMMAND, SC_CLOSE, 0);
+ }
+}
+
+// static
+HWND IMEHandler::GetOnScreenKeyboardWindow() {
+ const wchar_t kOSKClassName[] = L"IPTip_Main_Window";
+ HWND osk = ::FindWindowW(kOSKClassName, nullptr);
+ if (::IsWindow(osk) && ::IsWindowEnabled(osk) && ::IsWindowVisible(osk)) {
+ return osk;
+ }
+ return nullptr;
+}
+
+bool IMEHandler::MaybeCreateNativeCaret(nsWindow* aWindow) {
+ MOZ_ASSERT(aWindow);
+
+ if (IsA11yHandlingNativeCaret()) {
+ return false;
+ }
+
+ if (!sHasNativeCaretBeenRequested) {
+ // If we have not received WM_GETOBJECT for OBJID_CARET, there may be new
+ // application which requires our caret information. For kicking its
+ // window event proc, we should fire a window event here.
+ // (If there is such application, sHasNativeCaretBeenRequested will be set
+ // to true later.)
+ // FYI: If we create native caret and move its position, native caret
+ // causes EVENT_OBJECT_LOCATIONCHANGE event with OBJID_CARET and
+ // OBJID_CLIENT.
+ ::NotifyWinEvent(EVENT_OBJECT_LOCATIONCHANGE, aWindow->GetWindowHandle(),
+ OBJID_CARET, OBJID_CLIENT);
+ return false;
+ }
+
+ MaybeDestroyNativeCaret();
+
+ // If focused content is not text editable, we don't support caret
+ // caret information without a11y module.
+ if (!aWindow->GetInputContext().mIMEState.IsEditable()) {
+ return false;
+ }
+
+ WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, aWindow);
+ aWindow->InitEvent(queryCaretRectEvent);
+
+ WidgetQueryContentEvent::Options options;
+ options.mRelativeToInsertionPoint = true;
+ queryCaretRectEvent.InitForQueryCaretRect(0, options);
+
+ aWindow->DispatchWindowEvent(&queryCaretRectEvent);
+ if (NS_WARN_IF(queryCaretRectEvent.Failed())) {
+ return false;
+ }
+
+ return CreateNativeCaret(aWindow, queryCaretRectEvent.mReply->mRect);
+}
+
+bool IMEHandler::CreateNativeCaret(nsWindow* aWindow,
+ const LayoutDeviceIntRect& aCaretRect) {
+ MOZ_ASSERT(aWindow);
+
+ MOZ_ASSERT(!IsA11yHandlingNativeCaret());
+
+ sNativeCaretIsCreated =
+ ::CreateCaret(aWindow->GetWindowHandle(), nullptr, aCaretRect.Width(),
+ aCaretRect.Height());
+ if (!sNativeCaretIsCreated) {
+ return false;
+ }
+ nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
+ if (NS_WARN_IF(!toplevelWindow)) {
+ MaybeDestroyNativeCaret();
+ return false;
+ }
+
+ LayoutDeviceIntPoint caretPosition(aCaretRect.TopLeft());
+ if (toplevelWindow != aWindow) {
+ caretPosition += toplevelWindow->WidgetToScreenOffset();
+ caretPosition -= aWindow->WidgetToScreenOffset();
+ }
+
+ ::SetCaretPos(caretPosition.x, caretPosition.y);
+ return true;
+}
+
+void IMEHandler::MaybeDestroyNativeCaret() {
+ if (!sNativeCaretIsCreated) {
+ return;
+ }
+ ::DestroyCaret();
+ sNativeCaretIsCreated = false;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinIMEHandler.h b/widget/windows/WinIMEHandler.h
new file mode 100644
index 0000000000..5ccb62a7c7
--- /dev/null
+++ b/widget/windows/WinIMEHandler.h
@@ -0,0 +1,245 @@
+/* -*- 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 WinIMEHandler_h_
+#define WinIMEHandler_h_
+
+#include "nscore.h"
+#include "nsWindowBase.h"
+#include "npapi.h"
+#include <windows.h>
+#include <inputscope.h>
+
+#define NS_WM_IMEFIRST WM_IME_SETCONTEXT
+#define NS_WM_IMELAST WM_IME_KEYUP
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+struct MSGResult;
+
+/**
+ * IMEHandler class is a mediator class. On Windows, there are two IME API
+ * sets: One is IMM which is legacy API set. The other is TSF which is modern
+ * API set. By using this class, non-IME handler classes don't need to worry
+ * that we're in which mode.
+ */
+class IMEHandler final {
+ private:
+ /**
+ * Initialize() initializes both TSF modules and IMM modules. Some TIPs
+ * may require a normal window (i.e., not message window) belonging to
+ * this process. Therefore, this is called immediately after first normal
+ * window is created.
+ */
+ static void Initialize();
+
+ public:
+ static void Terminate();
+
+ /**
+ * Returns TSF related native data or native IME context.
+ */
+ static void* GetNativeData(nsWindow* aWindow, uint32_t aDataType);
+
+ /**
+ * ProcessRawKeyMessage() message is called before calling TranslateMessage()
+ * and DispatchMessage(). If this returns true, the message is consumed.
+ * Then, caller must not perform TranslateMessage() nor DispatchMessage().
+ */
+ static bool ProcessRawKeyMessage(const MSG& aMsg);
+
+ /**
+ * When the message is not needed to handle anymore by the caller, this
+ * returns true. Otherwise, false.
+ */
+ static bool ProcessMessage(nsWindow* aWindow, UINT aMessage, WPARAM& aWParam,
+ LPARAM& aLParam, MSGResult& aResult);
+
+ /**
+ * IsA11yHandlingNativeCaret() returns true if a11y is handling
+ * native caret. In such case, IME modules shouldn't touch native caret.
+ **/
+ static bool IsA11yHandlingNativeCaret();
+
+ /**
+ * NeedsToCreateNativeCaret() returns true if IME handler needs to create
+ * native caret for other applications which requests OBJID_CARET with
+ * WM_GETOBJECT and a11y module isn't active (if a11y module is active,
+ * it always creates native caret, i.e., even if no editor has focus).
+ */
+ static bool NeedsToCreateNativeCaret() {
+ return sHasNativeCaretBeenRequested && !IsA11yHandlingNativeCaret();
+ }
+
+ /**
+ * CreateNativeCaret() create native caret if this has been created it.
+ *
+ * @param aWindow The window which owns the caret.
+ * @param aCaretRect The caret rect relative to aWindow.
+ */
+ static bool CreateNativeCaret(nsWindow* aWindow,
+ const LayoutDeviceIntRect& aCaretRect);
+
+ /**
+ * MaybeDestroyNativeCaret() destroies native caret if it has been created
+ * by IMEHandler.
+ */
+ static void MaybeDestroyNativeCaret();
+
+ /**
+ * HasNativeCaret() returns true if there is native caret and it was created
+ * by IMEHandler.
+ */
+ static bool HasNativeCaret() { return sNativeCaretIsCreated; }
+
+ /**
+ * When there is a composition, returns true. Otherwise, false.
+ */
+ static bool IsComposing();
+
+ /**
+ * When there is a composition and it's in the window, returns true.
+ * Otherwise, false.
+ */
+ static bool IsComposingOn(nsWindow* aWindow);
+
+ /**
+ * Notifies IME of the notification (a request or an event).
+ */
+ static nsresult NotifyIME(nsWindow* aWindow,
+ const IMENotification& aIMENotification);
+
+ /**
+ * Returns notification requests of IME.
+ */
+ static IMENotificationRequests GetIMENotificationRequests();
+
+ /**
+ * Returns native text event dispatcher listener.
+ */
+ static TextEventDispatcherListener* GetNativeTextEventDispatcherListener();
+
+ /**
+ * Returns IME open state on the window.
+ */
+ static bool GetOpenState(nsWindow* aWindow);
+
+ /**
+ * Called when the window is destroying.
+ */
+ static void OnDestroyWindow(nsWindow* aWindow);
+
+ /**
+ * Called when nsIWidget::SetInputContext() is called before the window's
+ * InputContext is modified actually.
+ */
+ static void SetInputContext(nsWindow* aWindow, InputContext& aInputContext,
+ const InputContextAction& aAction);
+
+ /**
+ * Associate or disassociate IME context to/from the aWindowBase.
+ */
+ static void AssociateIMEContext(nsWindowBase* aWindowBase, bool aEnable);
+
+ /**
+ * Called when the window is created.
+ */
+ static void InitInputContext(nsWindow* aWindow, InputContext& aInputContext);
+
+ /**
+ * This is called by TSFStaticSink when active IME is changed.
+ */
+ static void OnKeyboardLayoutChanged();
+
+#ifdef DEBUG
+ /**
+ * Returns true when current keyboard layout has IME. Otherwise, false.
+ */
+ static bool CurrentKeyboardLayoutHasIME();
+#endif // #ifdef DEBUG
+
+ /**
+ * Append InputScope values from inputmode string.
+ */
+ static void AppendInputScopeFromInputmode(const nsAString& aInputmode,
+ nsTArray<InputScope>& aScopes);
+
+ /**
+ * Append InputScope values from type attreibute string of input element
+ */
+ static void AppendInputScopeFromType(const nsAString& aInputType,
+ nsTArray<InputScope>& aScopes);
+
+ private:
+ static nsWindow* sFocusedWindow;
+ static InputContextAction::Cause sLastContextActionCause;
+
+ static bool sMaybeEditable;
+ static bool sForceDisableCurrentIMM_IME;
+ static bool sNativeCaretIsCreated;
+ static bool sHasNativeCaretBeenRequested;
+
+ /**
+ * MaybeCreateNativeCaret() may create native caret over our caret if
+ * focused content is text editable and we need to create native caret
+ * for other applications.
+ *
+ * @param aWindow The window which owns the native caret.
+ */
+ static bool MaybeCreateNativeCaret(nsWindow* aWindow);
+
+ static decltype(SetInputScopes)* sSetInputScopes;
+ static void SetInputScopeForIMM32(nsWindow* aWindow,
+ const nsAString& aHTMLInputType,
+ const nsAString& aHTMLInputInputmode,
+ bool aInPrivateBrowsing);
+ static bool sIsInTSFMode;
+ // If sIMMEnabled is false, any IME messages are not handled in TSF mode.
+ // Additionally, IME context is always disassociated from focused window.
+ static bool sIsIMMEnabled;
+ static bool sAssociateIMCOnlyWhenIMM_IMEActive;
+
+ static bool IsTSFAvailable() { return sIsInTSFMode; }
+ static bool IsIMMActive();
+
+ static void MaybeShowOnScreenKeyboard(nsWindow* aWindow,
+ const InputContext& aInputContext);
+ enum class Sync { Yes, No };
+ static void MaybeDismissOnScreenKeyboard(nsWindow* aWindow,
+ Sync aSync = Sync::No);
+ static bool WStringStartsWithCaseInsensitive(const std::wstring& aHaystack,
+ const std::wstring& aNeedle);
+ static bool NeedOnScreenKeyboard();
+ static bool IsKeyboardPresentOnSlate();
+ static bool IsInTabletMode();
+ static bool AutoInvokeOnScreenKeyboardInDesktopMode();
+ static bool NeedsToAssociateIMC();
+
+ /**
+ * Show the Windows on-screen keyboard. Only allowed for
+ * chrome documents and Windows 8 and higher.
+ */
+ static void ShowOnScreenKeyboard(nsWindow* aWindow);
+
+ /**
+ * Dismiss the Windows on-screen keyboard. Only allowed for
+ * Windows 8 and higher.
+ */
+ static void DismissOnScreenKeyboard(nsWindow* aWindow);
+
+ /**
+ * Get the HWND for the on-screen keyboard, if it's up. Only
+ * allowed for Windows 8 and higher.
+ */
+ static HWND GetOnScreenKeyboardWindow();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef WinIMEHandler_h_
diff --git a/widget/windows/WinMessages.h b/widget/windows/WinMessages.h
new file mode 100644
index 0000000000..3eec16397a
--- /dev/null
+++ b/widget/windows/WinMessages.h
@@ -0,0 +1,104 @@
+/* -*- 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_WinMessages_h_
+#define mozilla_widget_WinMessages_h_
+
+/*****************************************************************************
+ * MOZ_WM_* messages
+ ****************************************************************************/
+
+// A magic APP message that can be sent to quit, sort of like a
+// QUERYENDSESSION/ENDSESSION, but without the query.
+#define MOZ_WM_APP_QUIT (WM_APP + 0x0300)
+// Used as a "tracer" event to probe event loop latency.
+#define MOZ_WM_TRACE (WM_APP + 0x0301)
+// accessibility priming
+#define MOZ_WM_STARTA11Y (WM_APP + 0x0302)
+// Our internal message for WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL and
+// WM_HSCROLL
+#define MOZ_WM_MOUSEVWHEEL (WM_APP + 0x0310)
+#define MOZ_WM_MOUSEHWHEEL (WM_APP + 0x0311)
+#define MOZ_WM_VSCROLL (WM_APP + 0x0312)
+#define MOZ_WM_HSCROLL (WM_APP + 0x0313)
+#define MOZ_WM_MOUSEWHEEL_FIRST MOZ_WM_MOUSEVWHEEL
+#define MOZ_WM_MOUSEWHEEL_LAST MOZ_WM_HSCROLL
+// If a popup window is being activated, we try to reactivate the previous
+// window with this message.
+#define MOZ_WM_REACTIVATE (WM_APP + 0x0314)
+// If TSFTextStore needs to notify TSF/TIP of layout change later, this
+// message is posted.
+#define MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE (WM_APP + 0x0315)
+// Internal message used in correcting backwards clock skew
+#define MOZ_WM_SKEWFIX (WM_APP + 0x0316)
+
+// Internal message used for rolling up popups for dmanip events
+#define MOZ_WM_DMANIP (WM_APP + 0x0317)
+
+// Following MOZ_WM_*KEY* messages are used by PluginInstanceChild and
+// NativeKey internally. (never posted to the queue)
+#define MOZ_WM_KEYDOWN (WM_APP + 0x0318)
+#define MOZ_WM_KEYUP (WM_APP + 0x0319)
+#define MOZ_WM_SYSKEYDOWN (WM_APP + 0x031A)
+#define MOZ_WM_SYSKEYUP (WM_APP + 0x031B)
+#define MOZ_WM_CHAR (WM_APP + 0x031C)
+#define MOZ_WM_SYSCHAR (WM_APP + 0x031D)
+#define MOZ_WM_DEADCHAR (WM_APP + 0x031E)
+#define MOZ_WM_SYSDEADCHAR (WM_APP + 0x031F)
+
+// XXX Should rename them to MOZ_WM_* and use safer values!
+// Messages for fullscreen transition window
+#define WM_FULLSCREEN_TRANSITION_BEFORE (WM_USER + 0)
+#define WM_FULLSCREEN_TRANSITION_AFTER (WM_USER + 1)
+
+// Drop shadow window style
+#define CS_XP_DROPSHADOW 0x00020000
+
+#ifndef APPCOMMAND_BROWSER_BACKWARD
+# define APPCOMMAND_BROWSER_BACKWARD 1
+# define APPCOMMAND_BROWSER_FORWARD 2
+# define APPCOMMAND_BROWSER_REFRESH 3
+# define APPCOMMAND_BROWSER_STOP 4
+# define APPCOMMAND_BROWSER_SEARCH 5
+# define APPCOMMAND_BROWSER_FAVORITES 6
+# define APPCOMMAND_BROWSER_HOME 7
+
+# define APPCOMMAND_MEDIA_NEXTTRACK 11
+# define APPCOMMAND_MEDIA_PREVIOUSTRACK 12
+# define APPCOMMAND_MEDIA_STOP 13
+# define APPCOMMAND_MEDIA_PLAY_PAUSE 14
+
+/*
+ * Additional commands currently not in use.
+ *
+ *#define APPCOMMAND_VOLUME_MUTE 8
+ *#define APPCOMMAND_VOLUME_DOWN 9
+ *#define APPCOMMAND_VOLUME_UP 10
+ *#define APPCOMMAND_LAUNCH_MAIL 15
+ *#define APPCOMMAND_LAUNCH_MEDIA_SELECT 16
+ *#define APPCOMMAND_LAUNCH_APP1 17
+ *#define APPCOMMAND_LAUNCH_APP2 18
+ *#define APPCOMMAND_BASS_DOWN 19
+ *#define APPCOMMAND_BASS_BOOST 20
+ *#define APPCOMMAND_BASS_UP 21
+ *#define APPCOMMAND_TREBLE_DOWN 22
+ *#define APPCOMMAND_TREBLE_UP 23
+ *#define FAPPCOMMAND_MOUSE 0x8000
+ *#define FAPPCOMMAND_KEY 0
+ *#define FAPPCOMMAND_OEM 0x1000
+ */
+
+# define GET_APPCOMMAND_LPARAM(lParam) \
+ ((short)(HIWORD(lParam) & ~FAPPCOMMAND_MASK))
+
+/*
+ *#define GET_DEVICE_LPARAM(lParam) ((WORD)(HIWORD(lParam) &
+ *FAPPCOMMAND_MASK)) #define GET_MOUSEORKEY_LPARAM GET_DEVICE_LPARAM
+ *#define GET_FLAGS_LPARAM(lParam) (LOWORD(lParam))
+ *#define GET_KEYSTATE_LPARAM(lParam) GET_FLAGS_LPARAM(lParam)
+ */
+#endif // #ifndef APPCOMMAND_BROWSER_BACKWARD
+
+#endif // #ifndef mozilla_widget_WinMessages_h_
diff --git a/widget/windows/WinModifierKeyState.h b/widget/windows/WinModifierKeyState.h
new file mode 100644
index 0000000000..0788c25939
--- /dev/null
+++ b/widget/windows/WinModifierKeyState.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_WinModifierKeyState_h_
+#define mozilla_widget_WinModifierKeyState_h_
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/EventForwards.h"
+#include "nsStringFwd.h"
+#include <windows.h>
+
+namespace mozilla {
+namespace widget {
+
+class MOZ_STACK_CLASS ModifierKeyState final {
+ public:
+ ModifierKeyState();
+ explicit ModifierKeyState(Modifiers aModifiers);
+
+ void Update();
+
+ void Unset(Modifiers aRemovingModifiers);
+ void Set(Modifiers aAddingModifiers);
+
+ void InitInputEvent(WidgetInputEvent& aInputEvent) const;
+
+ // Do not create IsAltGr() because it's unclear whether:
+ // - AltGr key is actually pressed.
+ // - Both Ctrl and Alt keys are pressed when a keyboard layout which
+ // has AltGr key.
+ // - Both Ctrl and Alt keys are pressed when a keyboard layout which
+ // does not have AltGr key.
+ bool IsShift() const;
+ bool IsControl() const;
+ bool IsAlt() const;
+ bool IsWin() const;
+
+ bool MaybeMatchShortcutKey() const;
+
+ bool IsCapsLocked() const;
+ bool IsNumLocked() const;
+ bool IsScrollLocked() const;
+
+ MOZ_ALWAYS_INLINE Modifiers GetModifiers() const { return mModifiers; }
+
+ private:
+ Modifiers mModifiers;
+
+ void InitMouseEvent(WidgetInputEvent& aMouseEvent) const;
+};
+
+const nsCString ToString(const ModifierKeyState& aModifierKeyState);
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef mozilla_widget_WinModifierKeyState_h_
diff --git a/widget/windows/WinMouseScrollHandler.cpp b/widget/windows/WinMouseScrollHandler.cpp
new file mode 100644
index 0000000000..1da2ba624e
--- /dev/null
+++ b/widget/windows/WinMouseScrollHandler.cpp
@@ -0,0 +1,1702 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/DebugOnly.h"
+
+#include "mozilla/Logging.h"
+
+#include "WinMouseScrollHandler.h"
+#include "nsWindow.h"
+#include "nsWindowDefs.h"
+#include "KeyboardLayout.h"
+#include "WinUtils.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMWindowUtils.h"
+
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/WheelEventBinding.h"
+#include "mozilla/StaticPrefs_mousewheel.h"
+
+#include <psapi.h>
+
+namespace mozilla {
+namespace widget {
+
+LazyLogModule gMouseScrollLog("MouseScrollHandlerWidgets");
+
+static const char* GetBoolName(bool aBool) { return aBool ? "TRUE" : "FALSE"; }
+
+MouseScrollHandler* MouseScrollHandler::sInstance = nullptr;
+
+bool MouseScrollHandler::Device::sFakeScrollableWindowNeeded = false;
+
+bool MouseScrollHandler::Device::SynTP::sInitialized = false;
+int32_t MouseScrollHandler::Device::SynTP::sMajorVersion = 0;
+int32_t MouseScrollHandler::Device::SynTP::sMinorVersion = -1;
+
+bool MouseScrollHandler::Device::Elantech::sUseSwipeHack = false;
+bool MouseScrollHandler::Device::Elantech::sUsePinchHack = false;
+DWORD MouseScrollHandler::Device::Elantech::sZoomUntil = 0;
+
+bool MouseScrollHandler::Device::Apoint::sInitialized = false;
+int32_t MouseScrollHandler::Device::Apoint::sMajorVersion = 0;
+int32_t MouseScrollHandler::Device::Apoint::sMinorVersion = -1;
+
+bool MouseScrollHandler::Device::SetPoint::sMightBeUsing = false;
+
+// The duration until timeout of events transaction. The value is 1.5 sec,
+// it's just a magic number, it was suggested by Logitech's engineer, see
+// bug 605648 comment 90.
+#define DEFAULT_TIMEOUT_DURATION 1500
+
+/******************************************************************************
+ *
+ * MouseScrollHandler
+ *
+ ******************************************************************************/
+
+/* static */
+POINTS
+MouseScrollHandler::GetCurrentMessagePos() {
+ if (SynthesizingEvent::IsSynthesizing()) {
+ return sInstance->mSynthesizingEvent->GetCursorPoint();
+ }
+ DWORD pos = ::GetMessagePos();
+ return MAKEPOINTS(pos);
+}
+
+// Get rid of the GetMessagePos() API.
+#define GetMessagePos()
+
+/* static */
+void MouseScrollHandler::Initialize() { Device::Init(); }
+
+/* static */
+void MouseScrollHandler::Shutdown() {
+ delete sInstance;
+ sInstance = nullptr;
+}
+
+/* static */
+MouseScrollHandler* MouseScrollHandler::GetInstance() {
+ if (!sInstance) {
+ sInstance = new MouseScrollHandler();
+ }
+ return sInstance;
+}
+
+MouseScrollHandler::MouseScrollHandler()
+ : mIsWaitingInternalMessage(false), mSynthesizingEvent(nullptr) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll: Creating an instance, this=%p, sInstance=%p", this,
+ sInstance));
+}
+
+MouseScrollHandler::~MouseScrollHandler() {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll: Destroying an instance, this=%p, sInstance=%p", this,
+ sInstance));
+
+ delete mSynthesizingEvent;
+}
+
+/* static */
+void MouseScrollHandler::MaybeLogKeyState() {
+ if (!MOZ_LOG_TEST(gMouseScrollLog, LogLevel::Debug)) {
+ return;
+ }
+ BYTE keyboardState[256];
+ if (::GetKeyboardState(keyboardState)) {
+ for (size_t i = 0; i < ArrayLength(keyboardState); i++) {
+ if (keyboardState[i]) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Debug,
+ (" Current key state: keyboardState[0x%02X]=0x%02X (%s)", i,
+ keyboardState[i],
+ ((keyboardState[i] & 0x81) == 0x81) ? "Pressed and Toggled"
+ : (keyboardState[i] & 0x80) ? "Pressed"
+ : (keyboardState[i] & 0x01) ? "Toggled"
+ : "Unknown"));
+ }
+ }
+ } else {
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Debug,
+ ("MouseScroll::MaybeLogKeyState(): Failed to print current keyboard "
+ "state"));
+ }
+}
+
+/* static */
+bool MouseScrollHandler::NeedsMessage(UINT aMsg) {
+ switch (aMsg) {
+ case WM_SETTINGCHANGE:
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL:
+ case WM_HSCROLL:
+ case WM_VSCROLL:
+ case MOZ_WM_MOUSEVWHEEL:
+ case MOZ_WM_MOUSEHWHEEL:
+ case MOZ_WM_HSCROLL:
+ case MOZ_WM_VSCROLL:
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ return true;
+ }
+ return false;
+}
+
+/* static */
+bool MouseScrollHandler::ProcessMessage(nsWindowBase* aWidget, UINT msg,
+ WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ Device::Elantech::UpdateZoomUntil();
+
+ switch (msg) {
+ case WM_SETTINGCHANGE:
+ if (!sInstance) {
+ return false;
+ }
+ if (wParam == SPI_SETWHEELSCROLLLINES ||
+ wParam == SPI_SETWHEELSCROLLCHARS) {
+ sInstance->mSystemSettings.MarkDirty();
+ }
+ return false;
+
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL:
+ GetInstance()->ProcessNativeMouseWheelMessage(aWidget, msg, wParam,
+ lParam);
+ sInstance->mSynthesizingEvent->NotifyNativeMessageHandlingFinished();
+ // We don't need to call next wndproc for WM_MOUSEWHEEL and
+ // WM_MOUSEHWHEEL. We should consume them always. If the messages
+ // would be handled by our window again, it caused making infinite
+ // message loop.
+ aResult.mConsumed = true;
+ aResult.mResult = (msg != WM_MOUSEHWHEEL);
+ return true;
+
+ case WM_HSCROLL:
+ case WM_VSCROLL:
+ aResult.mConsumed = GetInstance()->ProcessNativeScrollMessage(
+ aWidget, msg, wParam, lParam);
+ sInstance->mSynthesizingEvent->NotifyNativeMessageHandlingFinished();
+ aResult.mResult = 0;
+ return true;
+
+ case MOZ_WM_MOUSEVWHEEL:
+ case MOZ_WM_MOUSEHWHEEL:
+ GetInstance()->HandleMouseWheelMessage(aWidget, msg, wParam, lParam);
+ sInstance->mSynthesizingEvent->NotifyInternalMessageHandlingFinished();
+ // Doesn't need to call next wndproc for internal wheel message.
+ aResult.mConsumed = true;
+ return true;
+
+ case MOZ_WM_HSCROLL:
+ case MOZ_WM_VSCROLL:
+ GetInstance()->HandleScrollMessageAsMouseWheelMessage(aWidget, msg,
+ wParam, lParam);
+ sInstance->mSynthesizingEvent->NotifyInternalMessageHandlingFinished();
+ // Doesn't need to call next wndproc for internal scroll message.
+ aResult.mConsumed = true;
+ return true;
+
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessMessage(): aWidget=%p, "
+ "msg=%s(0x%04X), wParam=0x%02X, ::GetMessageTime()=%d",
+ aWidget,
+ msg == WM_KEYDOWN ? "WM_KEYDOWN"
+ : msg == WM_KEYUP ? "WM_KEYUP"
+ : "Unknown",
+ msg, wParam, ::GetMessageTime()));
+ MaybeLogKeyState();
+ if (Device::Elantech::HandleKeyMessage(aWidget, msg, wParam, lParam)) {
+ aResult.mResult = 0;
+ aResult.mConsumed = true;
+ return true;
+ }
+ return false;
+
+ default:
+ return false;
+ }
+}
+
+/* static */
+nsresult MouseScrollHandler::SynthesizeNativeMouseScrollEvent(
+ nsWindowBase* aWidget, const LayoutDeviceIntPoint& aPoint,
+ uint32_t aNativeMessage, int32_t aDelta, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags) {
+ bool useFocusedWindow = !(
+ aAdditionalFlags & nsIDOMWindowUtils::MOUSESCROLL_PREFER_WIDGET_AT_POINT);
+
+ POINT pt;
+ pt.x = aPoint.x;
+ pt.y = aPoint.y;
+
+ HWND target = useFocusedWindow ? ::WindowFromPoint(pt) : ::GetFocus();
+ NS_ENSURE_TRUE(target, NS_ERROR_FAILURE);
+
+ WPARAM wParam = 0;
+ LPARAM lParam = 0;
+ switch (aNativeMessage) {
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL: {
+ lParam = MAKELPARAM(pt.x, pt.y);
+ WORD mod = 0;
+ if (aModifierFlags & (nsIWidget::CTRL_L | nsIWidget::CTRL_R)) {
+ mod |= MK_CONTROL;
+ }
+ if (aModifierFlags & (nsIWidget::SHIFT_L | nsIWidget::SHIFT_R)) {
+ mod |= MK_SHIFT;
+ }
+ wParam = MAKEWPARAM(mod, aDelta);
+ break;
+ }
+ case WM_VSCROLL:
+ case WM_HSCROLL:
+ lParam = (aAdditionalFlags &
+ nsIDOMWindowUtils::MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL)
+ ? reinterpret_cast<LPARAM>(target)
+ : 0;
+ wParam = aDelta;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Ensure to make the instance.
+ GetInstance();
+
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+
+ AutoTArray<KeyPair, 10> keySequence;
+ WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags,
+ aNativeMessage);
+
+ for (uint32_t i = 0; i < keySequence.Length(); ++i) {
+ uint8_t key = keySequence[i].mGeneral;
+ uint8_t keySpecific = keySequence[i].mSpecific;
+ kbdState[key] = 0x81; // key is down and toggled on if appropriate
+ if (keySpecific) {
+ kbdState[keySpecific] = 0x81;
+ }
+ }
+
+ if (!sInstance->mSynthesizingEvent) {
+ sInstance->mSynthesizingEvent = new SynthesizingEvent();
+ }
+
+ POINTS pts;
+ pts.x = static_cast<SHORT>(pt.x);
+ pts.y = static_cast<SHORT>(pt.y);
+ return sInstance->mSynthesizingEvent->Synthesize(pts, target, aNativeMessage,
+ wParam, lParam, kbdState);
+}
+
+/* static */
+void MouseScrollHandler::InitEvent(nsWindowBase* aWidget,
+ WidgetGUIEvent& aEvent, LPARAM* aPoint) {
+ NS_ENSURE_TRUE_VOID(aWidget);
+
+ // If a point is provided, use it; otherwise, get current message point or
+ // synthetic point
+ POINTS pointOnScreen;
+ if (aPoint != nullptr) {
+ pointOnScreen = MAKEPOINTS(*aPoint);
+ } else {
+ pointOnScreen = GetCurrentMessagePos();
+ }
+
+ // InitEvent expects the point to be in window coordinates, so translate the
+ // point from screen coordinates.
+ POINT pointOnWindow;
+ POINTSTOPOINT(pointOnWindow, pointOnScreen);
+ ::ScreenToClient(aWidget->GetWindowHandle(), &pointOnWindow);
+
+ LayoutDeviceIntPoint point;
+ point.x = pointOnWindow.x;
+ point.y = pointOnWindow.y;
+
+ aWidget->InitEvent(aEvent, &point);
+}
+
+/* static */
+ModifierKeyState MouseScrollHandler::GetModifierKeyState(UINT aMessage) {
+ ModifierKeyState result;
+ // Assume the Control key is down if the Elantech touchpad has sent the
+ // mis-ordered WM_KEYDOWN/WM_MOUSEWHEEL messages. (See the comment in
+ // MouseScrollHandler::Device::Elantech::HandleKeyMessage().)
+ if ((aMessage == MOZ_WM_MOUSEVWHEEL || aMessage == WM_MOUSEWHEEL) &&
+ !result.IsControl() && Device::Elantech::IsZooming()) {
+ // XXX Do we need to unset MODIFIER_SHIFT, MODIFIER_ALT, MODIFIER_OS too?
+ // If one of them are true, the default action becomes not zooming.
+ result.Unset(MODIFIER_ALTGRAPH);
+ result.Set(MODIFIER_CONTROL);
+ }
+ return result;
+}
+
+POINT
+MouseScrollHandler::ComputeMessagePos(UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam) {
+ POINT point;
+ if (Device::SetPoint::IsGetMessagePosResponseValid(aMessage, aWParam,
+ aLParam)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ComputeMessagePos: Using ::GetCursorPos()"));
+ ::GetCursorPos(&point);
+ } else {
+ POINTS pts = GetCurrentMessagePos();
+ point.x = pts.x;
+ point.y = pts.y;
+ }
+ return point;
+}
+
+void MouseScrollHandler::ProcessNativeMouseWheelMessage(nsWindowBase* aWidget,
+ UINT aMessage,
+ WPARAM aWParam,
+ LPARAM aLParam) {
+ if (SynthesizingEvent::IsSynthesizing()) {
+ mSynthesizingEvent->NativeMessageReceived(aWidget, aMessage, aWParam,
+ aLParam);
+ }
+
+ POINT point = ComputeMessagePos(aMessage, aWParam, aLParam);
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: aWidget=%p, "
+ "aMessage=%s, wParam=0x%08X, lParam=0x%08X, point: { x=%d, y=%d }",
+ aWidget,
+ aMessage == WM_MOUSEWHEEL ? "WM_MOUSEWHEEL"
+ : aMessage == WM_MOUSEHWHEEL ? "WM_MOUSEHWHEEL"
+ : aMessage == WM_VSCROLL ? "WM_VSCROLL"
+ : "WM_HSCROLL",
+ aWParam, aLParam, point.x, point.y));
+ MaybeLogKeyState();
+
+ HWND underCursorWnd = ::WindowFromPoint(point);
+ if (!underCursorWnd) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: "
+ "No window is not found under the cursor"));
+ return;
+ }
+
+ if (Device::Elantech::IsPinchHackNeeded() &&
+ Device::Elantech::IsHelperWindow(underCursorWnd)) {
+ // The Elantech driver places a window right underneath the cursor
+ // when sending a WM_MOUSEWHEEL event to us as part of a pinch-to-zoom
+ // gesture. We detect that here, and search for our window that would
+ // be beneath the cursor if that window wasn't there.
+ underCursorWnd = WinUtils::FindOurWindowAtPoint(point);
+ if (!underCursorWnd) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: "
+ "Our window is not found under the Elantech helper window"));
+ return;
+ }
+ }
+
+ // Handle most cases first. If the window under mouse cursor is our window
+ // except plugin window (MozillaWindowClass), we should handle the message
+ // on the window.
+ if (WinUtils::IsOurProcessWindow(underCursorWnd)) {
+ nsWindowBase* destWindow = WinUtils::GetNSWindowBasePtr(underCursorWnd);
+ if (!destWindow) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: "
+ "Found window under the cursor isn't managed by nsWindow..."));
+ HWND wnd = ::GetParent(underCursorWnd);
+ for (; wnd; wnd = ::GetParent(wnd)) {
+ destWindow = WinUtils::GetNSWindowBasePtr(wnd);
+ if (destWindow) {
+ break;
+ }
+ }
+ if (!wnd) {
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: Our window which is "
+ "managed by nsWindow is not found under the cursor"));
+ return;
+ }
+ }
+
+ MOZ_ASSERT(destWindow, "destWindow must not be NULL");
+
+ // Some odd touchpad utils sets focus to window under the mouse cursor.
+ // this emulates the odd behavior for debug.
+ if (mUserPrefs.ShouldEmulateToMakeWindowUnderCursorForeground() &&
+ (aMessage == WM_MOUSEWHEEL || aMessage == WM_MOUSEHWHEEL) &&
+ ::GetForegroundWindow() != destWindow->GetWindowHandle()) {
+ ::SetForegroundWindow(destWindow->GetWindowHandle());
+ }
+
+ // If the found window is our plugin window, it means that the message
+ // has been handled by the plugin but not consumed. We should handle the
+ // message on its parent window. However, note that the DOM event may
+ // cause accessing the plugin. Therefore, we should unlock the plugin
+ // process by using PostMessage().
+ if (destWindow->IsPlugin()) {
+ destWindow = destWindow->GetParentWindowBase(false);
+ if (!destWindow) {
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: "
+ "Our window which is a parent of a plugin window is not found"));
+ return;
+ }
+ }
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, "
+ "Posting internal message to an nsWindow (%p)...",
+ destWindow));
+ mIsWaitingInternalMessage = true;
+ UINT internalMessage = WinUtils::GetInternalMessage(aMessage);
+ ::PostMessage(destWindow->GetWindowHandle(), internalMessage, aWParam,
+ aLParam);
+ return;
+ }
+
+ // If the window under cursor is not in our process, it means:
+ // 1. The window may be a plugin window (GeckoPluginWindow or its descendant).
+ // 2. The window may be another application's window.
+ HWND pluginWnd = WinUtils::FindOurProcessWindow(underCursorWnd);
+ if (!pluginWnd) {
+ // If there is no plugin window in ancestors of the window under cursor,
+ // the window is for another applications (case 2).
+ // We don't need to handle this message.
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: "
+ "Our window is not found under the cursor"));
+ return;
+ }
+
+ // If we're a plugin window (MozillaWindowClass) and cursor in this window,
+ // the message shouldn't go to plugin's wndproc again. So, we should handle
+ // it on parent window. However, note that the DOM event may cause accessing
+ // the plugin. Therefore, we should unlock the plugin process by using
+ // PostMessage().
+ if (aWidget->IsPlugin() && aWidget->GetWindowHandle() == pluginWnd) {
+ nsWindowBase* destWindow = aWidget->GetParentWindowBase(false);
+ if (!destWindow) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: Our normal window "
+ "which "
+ "is a parent of this plugin window is not found"));
+ return;
+ }
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, "
+ "Posting internal message to an nsWindow (%p) which is parent of this "
+ "plugin window...",
+ destWindow));
+ mIsWaitingInternalMessage = true;
+ UINT internalMessage = WinUtils::GetInternalMessage(aMessage);
+ ::PostMessage(destWindow->GetWindowHandle(), internalMessage, aWParam,
+ aLParam);
+ return;
+ }
+
+ // If the window is a part of plugin, we should post the message to it.
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, "
+ "Redirecting the message to a window which is a plugin child window"));
+ ::PostMessage(underCursorWnd, aMessage, aWParam, aLParam);
+}
+
+bool MouseScrollHandler::ProcessNativeScrollMessage(nsWindowBase* aWidget,
+ UINT aMessage,
+ WPARAM aWParam,
+ LPARAM aLParam) {
+ if (aLParam || mUserPrefs.IsScrollMessageHandledAsWheelMessage()) {
+ // Scroll message generated by Thinkpad Trackpoint Driver or similar
+ // Treat as a mousewheel message and scroll appropriately
+ ProcessNativeMouseWheelMessage(aWidget, aMessage, aWParam, aLParam);
+ // Always consume the scroll message if we try to emulate mouse wheel
+ // action.
+ return true;
+ }
+
+ if (SynthesizingEvent::IsSynthesizing()) {
+ mSynthesizingEvent->NativeMessageReceived(aWidget, aMessage, aWParam,
+ aLParam);
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeScrollMessage: aWidget=%p, "
+ "aMessage=%s, wParam=0x%08X, lParam=0x%08X",
+ aWidget, aMessage == WM_VSCROLL ? "WM_VSCROLL" : "WM_HSCROLL",
+ aWParam, aLParam));
+
+ // Scroll message generated by external application
+ WidgetContentCommandEvent commandEvent(true, eContentCommandScroll, aWidget);
+ commandEvent.mScroll.mIsHorizontal = (aMessage == WM_HSCROLL);
+
+ switch (LOWORD(aWParam)) {
+ case SB_LINEUP: // SB_LINELEFT
+ commandEvent.mScroll.mUnit =
+ WidgetContentCommandEvent::eCmdScrollUnit_Line;
+ commandEvent.mScroll.mAmount = -1;
+ break;
+ case SB_LINEDOWN: // SB_LINERIGHT
+ commandEvent.mScroll.mUnit =
+ WidgetContentCommandEvent::eCmdScrollUnit_Line;
+ commandEvent.mScroll.mAmount = 1;
+ break;
+ case SB_PAGEUP: // SB_PAGELEFT
+ commandEvent.mScroll.mUnit =
+ WidgetContentCommandEvent::eCmdScrollUnit_Page;
+ commandEvent.mScroll.mAmount = -1;
+ break;
+ case SB_PAGEDOWN: // SB_PAGERIGHT
+ commandEvent.mScroll.mUnit =
+ WidgetContentCommandEvent::eCmdScrollUnit_Page;
+ commandEvent.mScroll.mAmount = 1;
+ break;
+ case SB_TOP: // SB_LEFT
+ commandEvent.mScroll.mUnit =
+ WidgetContentCommandEvent::eCmdScrollUnit_Whole;
+ commandEvent.mScroll.mAmount = -1;
+ break;
+ case SB_BOTTOM: // SB_RIGHT
+ commandEvent.mScroll.mUnit =
+ WidgetContentCommandEvent::eCmdScrollUnit_Whole;
+ commandEvent.mScroll.mAmount = 1;
+ break;
+ default:
+ return false;
+ }
+ // XXX If this is a plugin window, we should dispatch the event from
+ // parent window.
+ aWidget->DispatchContentCommandEvent(&commandEvent);
+ return true;
+}
+
+void MouseScrollHandler::HandleMouseWheelMessage(nsWindowBase* aWidget,
+ UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam) {
+ MOZ_ASSERT((aMessage == MOZ_WM_MOUSEVWHEEL || aMessage == MOZ_WM_MOUSEHWHEEL),
+ "HandleMouseWheelMessage must be called with "
+ "MOZ_WM_MOUSEVWHEEL or MOZ_WM_MOUSEHWHEEL");
+
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::HandleMouseWheelMessage: aWidget=%p, "
+ "aMessage=MOZ_WM_MOUSE%sWHEEL, aWParam=0x%08X, aLParam=0x%08X",
+ aWidget, aMessage == MOZ_WM_MOUSEVWHEEL ? "V" : "H", aWParam, aLParam));
+
+ mIsWaitingInternalMessage = false;
+
+ // If it's not allowed to cache system settings, we need to reset the cache
+ // before handling the mouse wheel message.
+ mSystemSettings.TrustedScrollSettingsDriver();
+
+ EventInfo eventInfo(aWidget, WinUtils::GetNativeMessage(aMessage), aWParam,
+ aLParam);
+ if (!eventInfo.CanDispatchWheelEvent()) {
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::HandleMouseWheelMessage: Cannot dispatch the events"));
+ mLastEventInfo.ResetTransaction();
+ return;
+ }
+
+ // Discard the remaining delta if current wheel message and last one are
+ // received by different window or to scroll different direction or
+ // different unit scroll. Furthermore, if the last event was too old.
+ if (!mLastEventInfo.CanContinueTransaction(eventInfo)) {
+ mLastEventInfo.ResetTransaction();
+ }
+
+ mLastEventInfo.RecordEvent(eventInfo);
+
+ ModifierKeyState modKeyState = GetModifierKeyState(aMessage);
+
+ // Grab the widget, it might be destroyed by a DOM event handler.
+ RefPtr<nsWindowBase> kungFuDethGrip(aWidget);
+
+ WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
+ if (mLastEventInfo.InitWheelEvent(aWidget, wheelEvent, modKeyState,
+ aLParam)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::HandleMouseWheelMessage: dispatching "
+ "eWheel event"));
+ aWidget->DispatchWheelEvent(&wheelEvent);
+ if (aWidget->Destroyed()) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::HandleMouseWheelMessage: The window was destroyed "
+ "by eWheel event"));
+ mLastEventInfo.ResetTransaction();
+ return;
+ }
+ } else {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::HandleMouseWheelMessage: eWheel event is not "
+ "dispatched"));
+ }
+}
+
+void MouseScrollHandler::HandleScrollMessageAsMouseWheelMessage(
+ nsWindowBase* aWidget, UINT aMessage, WPARAM aWParam, LPARAM aLParam) {
+ MOZ_ASSERT((aMessage == MOZ_WM_VSCROLL || aMessage == MOZ_WM_HSCROLL),
+ "HandleScrollMessageAsMouseWheelMessage must be called with "
+ "MOZ_WM_VSCROLL or MOZ_WM_HSCROLL");
+
+ mIsWaitingInternalMessage = false;
+
+ ModifierKeyState modKeyState = GetModifierKeyState(aMessage);
+
+ WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
+ double& delta =
+ (aMessage == MOZ_WM_VSCROLL) ? wheelEvent.mDeltaY : wheelEvent.mDeltaX;
+ int32_t& lineOrPageDelta = (aMessage == MOZ_WM_VSCROLL)
+ ? wheelEvent.mLineOrPageDeltaY
+ : wheelEvent.mLineOrPageDeltaX;
+
+ delta = 1.0;
+ lineOrPageDelta = 1;
+
+ switch (LOWORD(aWParam)) {
+ case SB_PAGEUP:
+ delta = -1.0;
+ lineOrPageDelta = -1;
+ case SB_PAGEDOWN:
+ wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_PAGE;
+ break;
+
+ case SB_LINEUP:
+ delta = -1.0;
+ lineOrPageDelta = -1;
+ case SB_LINEDOWN:
+ wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_LINE;
+ break;
+
+ default:
+ return;
+ }
+ modKeyState.InitInputEvent(wheelEvent);
+
+ // Current mouse position may not be same as when the original message
+ // is received. However, this data is not available with the original
+ // message, which is why nullptr is passed in. We need to know the actual
+ // mouse cursor position when the original message was received.
+ InitEvent(aWidget, wheelEvent, nullptr);
+
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::HandleScrollMessageAsMouseWheelMessage: aWidget=%p, "
+ "aMessage=MOZ_WM_%sSCROLL, aWParam=0x%08X, aLParam=0x%08X, "
+ "wheelEvent { mRefPoint: { x: %d, y: %d }, mDeltaX: %f, mDeltaY: %f, "
+ "mLineOrPageDeltaX: %d, mLineOrPageDeltaY: %d, "
+ "isShift: %s, isControl: %s, isAlt: %s, isMeta: %s }",
+ aWidget, (aMessage == MOZ_WM_VSCROLL) ? "V" : "H", aWParam, aLParam,
+ wheelEvent.mRefPoint.x, wheelEvent.mRefPoint.y, wheelEvent.mDeltaX,
+ wheelEvent.mDeltaY, wheelEvent.mLineOrPageDeltaX,
+ wheelEvent.mLineOrPageDeltaY, GetBoolName(wheelEvent.IsShift()),
+ GetBoolName(wheelEvent.IsControl()), GetBoolName(wheelEvent.IsAlt()),
+ GetBoolName(wheelEvent.IsMeta())));
+
+ aWidget->DispatchWheelEvent(&wheelEvent);
+}
+
+/******************************************************************************
+ *
+ * EventInfo
+ *
+ ******************************************************************************/
+
+MouseScrollHandler::EventInfo::EventInfo(nsWindowBase* aWidget, UINT aMessage,
+ WPARAM aWParam, LPARAM aLParam) {
+ MOZ_ASSERT(
+ aMessage == WM_MOUSEWHEEL || aMessage == WM_MOUSEHWHEEL,
+ "EventInfo must be initialized with WM_MOUSEWHEEL or WM_MOUSEHWHEEL");
+
+ MouseScrollHandler::GetInstance()->mSystemSettings.Init();
+
+ mIsVertical = (aMessage == WM_MOUSEWHEEL);
+ mIsPage =
+ MouseScrollHandler::sInstance->mSystemSettings.IsPageScroll(mIsVertical);
+ mDelta = (short)HIWORD(aWParam);
+ mWnd = aWidget->GetWindowHandle();
+ mTimeStamp = TimeStamp::Now();
+}
+
+bool MouseScrollHandler::EventInfo::CanDispatchWheelEvent() const {
+ if (!GetScrollAmount()) {
+ // XXX I think that we should dispatch mouse wheel events even if the
+ // operation will not scroll because the wheel operation really happened
+ // and web application may want to handle the event for non-scroll action.
+ return false;
+ }
+
+ return (mDelta != 0);
+}
+
+int32_t MouseScrollHandler::EventInfo::GetScrollAmount() const {
+ if (mIsPage) {
+ return 1;
+ }
+ return MouseScrollHandler::sInstance->mSystemSettings.GetScrollAmount(
+ mIsVertical);
+}
+
+/******************************************************************************
+ *
+ * LastEventInfo
+ *
+ ******************************************************************************/
+
+bool MouseScrollHandler::LastEventInfo::CanContinueTransaction(
+ const EventInfo& aNewEvent) {
+ int32_t timeout = MouseScrollHandler::sInstance->mUserPrefs
+ .GetMouseScrollTransactionTimeout();
+ return !mWnd ||
+ (mWnd == aNewEvent.GetWindowHandle() &&
+ IsPositive() == aNewEvent.IsPositive() &&
+ mIsVertical == aNewEvent.IsVertical() &&
+ mIsPage == aNewEvent.IsPage() &&
+ (timeout < 0 || TimeStamp::Now() - mTimeStamp <=
+ TimeDuration::FromMilliseconds(timeout)));
+}
+
+void MouseScrollHandler::LastEventInfo::ResetTransaction() {
+ if (!mWnd) {
+ return;
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::LastEventInfo::ResetTransaction()"));
+
+ mWnd = nullptr;
+ mAccumulatedDelta = 0;
+}
+
+void MouseScrollHandler::LastEventInfo::RecordEvent(const EventInfo& aEvent) {
+ mWnd = aEvent.GetWindowHandle();
+ mDelta = aEvent.GetNativeDelta();
+ mIsVertical = aEvent.IsVertical();
+ mIsPage = aEvent.IsPage();
+ mTimeStamp = TimeStamp::Now();
+}
+
+/* static */
+int32_t MouseScrollHandler::LastEventInfo::RoundDelta(double aDelta) {
+ return (aDelta >= 0) ? (int32_t)floor(aDelta) : (int32_t)ceil(aDelta);
+}
+
+bool MouseScrollHandler::LastEventInfo::InitWheelEvent(
+ nsWindowBase* aWidget, WidgetWheelEvent& aWheelEvent,
+ const ModifierKeyState& aModKeyState, LPARAM aLParam) {
+ MOZ_ASSERT(aWheelEvent.mMessage == eWheel);
+
+ if (StaticPrefs::mousewheel_ignore_cursor_position_in_lparam()) {
+ InitEvent(aWidget, aWheelEvent, nullptr);
+ } else {
+ InitEvent(aWidget, aWheelEvent, &aLParam);
+ }
+
+ aModKeyState.InitInputEvent(aWheelEvent);
+
+ // Our positive delta value means to bottom or right.
+ // But positive native delta value means to top or right.
+ // Use orienter for computing our delta value with native delta value.
+ int32_t orienter = mIsVertical ? -1 : 1;
+
+ aWheelEvent.mDeltaMode = mIsPage ? dom::WheelEvent_Binding::DOM_DELTA_PAGE
+ : dom::WheelEvent_Binding::DOM_DELTA_LINE;
+
+ double& delta = mIsVertical ? aWheelEvent.mDeltaY : aWheelEvent.mDeltaX;
+ int32_t& lineOrPageDelta = mIsVertical ? aWheelEvent.mLineOrPageDeltaY
+ : aWheelEvent.mLineOrPageDeltaX;
+
+ double nativeDeltaPerUnit =
+ mIsPage ? static_cast<double>(WHEEL_DELTA)
+ : static_cast<double>(WHEEL_DELTA) / GetScrollAmount();
+
+ delta = static_cast<double>(mDelta) * orienter / nativeDeltaPerUnit;
+ mAccumulatedDelta += mDelta;
+ lineOrPageDelta =
+ mAccumulatedDelta * orienter / RoundDelta(nativeDeltaPerUnit);
+ mAccumulatedDelta -=
+ lineOrPageDelta * orienter * RoundDelta(nativeDeltaPerUnit);
+
+ if (aWheelEvent.mDeltaMode != dom::WheelEvent_Binding::DOM_DELTA_LINE) {
+ // If the scroll delta mode isn't per line scroll, we shouldn't allow to
+ // override the system scroll speed setting.
+ aWheelEvent.mAllowToOverrideSystemScrollSpeed = false;
+ } else if (!MouseScrollHandler::sInstance->mSystemSettings
+ .IsOverridingSystemScrollSpeedAllowed()) {
+ // If the system settings are customized by either the user or
+ // the mouse utility, we shouldn't allow to override the system scroll
+ // speed setting.
+ aWheelEvent.mAllowToOverrideSystemScrollSpeed = false;
+ } else {
+ // For suppressing too fast scroll, we should ensure that the maximum
+ // overridden delta value should be less than overridden scroll speed
+ // with default scroll amount.
+ double defaultScrollAmount = mIsVertical
+ ? SystemSettings::DefaultScrollLines()
+ : SystemSettings::DefaultScrollChars();
+ double maxDelta = WidgetWheelEvent::ComputeOverriddenDelta(
+ defaultScrollAmount, mIsVertical);
+ if (maxDelta != defaultScrollAmount) {
+ double overriddenDelta =
+ WidgetWheelEvent::ComputeOverriddenDelta(Abs(delta), mIsVertical);
+ if (overriddenDelta > maxDelta) {
+ // Suppress to fast scroll since overriding system scroll speed with
+ // current delta value causes too big delta value.
+ aWheelEvent.mAllowToOverrideSystemScrollSpeed = false;
+ }
+ }
+ }
+
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::LastEventInfo::InitWheelEvent: aWidget=%p, "
+ "aWheelEvent { mRefPoint: { x: %d, y: %d }, mDeltaX: %f, mDeltaY: %f, "
+ "mLineOrPageDeltaX: %d, mLineOrPageDeltaY: %d, "
+ "isShift: %s, isControl: %s, isAlt: %s, isMeta: %s, "
+ "mAllowToOverrideSystemScrollSpeed: %s }, "
+ "mAccumulatedDelta: %d",
+ aWidget, aWheelEvent.mRefPoint.x, aWheelEvent.mRefPoint.y,
+ aWheelEvent.mDeltaX, aWheelEvent.mDeltaY, aWheelEvent.mLineOrPageDeltaX,
+ aWheelEvent.mLineOrPageDeltaY, GetBoolName(aWheelEvent.IsShift()),
+ GetBoolName(aWheelEvent.IsControl()), GetBoolName(aWheelEvent.IsAlt()),
+ GetBoolName(aWheelEvent.IsMeta()),
+ GetBoolName(aWheelEvent.mAllowToOverrideSystemScrollSpeed),
+ mAccumulatedDelta));
+
+ return (delta != 0);
+}
+
+/******************************************************************************
+ *
+ * SystemSettings
+ *
+ ******************************************************************************/
+
+void MouseScrollHandler::SystemSettings::Init() {
+ if (mInitialized) {
+ return;
+ }
+
+ InitScrollLines();
+ InitScrollChars();
+
+ mInitialized = true;
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::SystemSettings::Init(): initialized, "
+ "mScrollLines=%d, mScrollChars=%d",
+ mScrollLines, mScrollChars));
+}
+
+bool MouseScrollHandler::SystemSettings::InitScrollLines() {
+ int32_t oldValue = mInitialized ? mScrollLines : 0;
+ mIsReliableScrollLines = false;
+ mScrollLines = MouseScrollHandler::sInstance->mUserPrefs
+ .GetOverriddenVerticalScrollAmout();
+ if (mScrollLines >= 0) {
+ // overridden by the pref.
+ mIsReliableScrollLines = true;
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::SystemSettings::InitScrollLines(): mScrollLines is "
+ "overridden by the pref: %d",
+ mScrollLines));
+ } else if (!::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &mScrollLines,
+ 0)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::SystemSettings::InitScrollLines(): "
+ "::SystemParametersInfo("
+ "SPI_GETWHEELSCROLLLINES) failed"));
+ mScrollLines = DefaultScrollLines();
+ }
+
+ if (mScrollLines > WHEEL_DELTA) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::SystemSettings::InitScrollLines(): the result of "
+ "::SystemParametersInfo(SPI_GETWHEELSCROLLLINES) is too large: %d",
+ mScrollLines));
+ // sScrollLines usually equals 3 or 0 (for no scrolling)
+ // However, if sScrollLines > WHEEL_DELTA, we assume that
+ // the mouse driver wants a page scroll. The docs state that
+ // sScrollLines should explicitly equal WHEEL_PAGESCROLL, but
+ // since some mouse drivers use an arbitrary large number instead,
+ // we have to handle that as well.
+ mScrollLines = WHEEL_PAGESCROLL;
+ }
+
+ return oldValue != mScrollLines;
+}
+
+bool MouseScrollHandler::SystemSettings::InitScrollChars() {
+ int32_t oldValue = mInitialized ? mScrollChars : 0;
+ mIsReliableScrollChars = false;
+ mScrollChars = MouseScrollHandler::sInstance->mUserPrefs
+ .GetOverriddenHorizontalScrollAmout();
+ if (mScrollChars >= 0) {
+ // overridden by the pref.
+ mIsReliableScrollChars = true;
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::SystemSettings::InitScrollChars(): mScrollChars is "
+ "overridden by the pref: %d",
+ mScrollChars));
+ } else if (!::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &mScrollChars,
+ 0)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::SystemSettings::InitScrollChars(): "
+ "::SystemParametersInfo("
+ "SPI_GETWHEELSCROLLCHARS) failed, this is unexpected on Vista or "
+ "later"));
+ // XXX Should we use DefaultScrollChars()?
+ mScrollChars = 1;
+ }
+
+ if (mScrollChars > WHEEL_DELTA) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::SystemSettings::InitScrollChars(): the result of "
+ "::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS) is too large: %d",
+ mScrollChars));
+ // See the comments for the case mScrollLines > WHEEL_DELTA.
+ mScrollChars = WHEEL_PAGESCROLL;
+ }
+
+ return oldValue != mScrollChars;
+}
+
+void MouseScrollHandler::SystemSettings::MarkDirty() {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::SystemSettings::MarkDirty(): "
+ "Marking SystemSettings dirty"));
+ mInitialized = false;
+ // When system settings are changed, we should reset current transaction.
+ MOZ_ASSERT(sInstance,
+ "Must not be called at initializing MouseScrollHandler");
+ MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction();
+}
+
+void MouseScrollHandler::SystemSettings::RefreshCache() {
+ bool isChanged = InitScrollLines();
+ isChanged = InitScrollChars() || isChanged;
+ if (!isChanged) {
+ return;
+ }
+ // If the scroll amount is changed, we should reset current transaction.
+ MOZ_ASSERT(sInstance,
+ "Must not be called at initializing MouseScrollHandler");
+ MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction();
+}
+
+void MouseScrollHandler::SystemSettings::TrustedScrollSettingsDriver() {
+ if (!mInitialized) {
+ return;
+ }
+
+ // if the cache is initialized with prefs, we don't need to refresh it.
+ if (mIsReliableScrollLines && mIsReliableScrollChars) {
+ return;
+ }
+
+ MouseScrollHandler::UserPrefs& userPrefs =
+ MouseScrollHandler::sInstance->mUserPrefs;
+
+ // If system settings cache is disabled, we should always refresh them.
+ if (!userPrefs.IsSystemSettingCacheEnabled()) {
+ RefreshCache();
+ return;
+ }
+
+ // If pref is set to as "always trust the cache", we shouldn't refresh them
+ // in any environments.
+ if (userPrefs.IsSystemSettingCacheForciblyEnabled()) {
+ return;
+ }
+
+ // If SynTP of Synaptics or Apoint of Alps is installed, it may hook
+ // ::SystemParametersInfo() and returns different value from system settings.
+ if (Device::SynTP::IsDriverInstalled() ||
+ Device::Apoint::IsDriverInstalled()) {
+ RefreshCache();
+ return;
+ }
+
+ // XXX We're not sure about other touchpad drivers...
+}
+
+bool MouseScrollHandler::SystemSettings::
+ IsOverridingSystemScrollSpeedAllowed() {
+ return mScrollLines == DefaultScrollLines() &&
+ mScrollChars == DefaultScrollChars();
+}
+
+/******************************************************************************
+ *
+ * UserPrefs
+ *
+ ******************************************************************************/
+
+MouseScrollHandler::UserPrefs::UserPrefs() : mInitialized(false) {
+ // We need to reset mouse wheel transaction when all of mousewheel related
+ // prefs are changed.
+ DebugOnly<nsresult> rv =
+ Preferences::RegisterPrefixCallback(OnChange, "mousewheel.", this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to register callback for mousewheel.");
+}
+
+MouseScrollHandler::UserPrefs::~UserPrefs() {
+ DebugOnly<nsresult> rv =
+ Preferences::UnregisterPrefixCallback(OnChange, "mousewheel.", this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to unregister callback for mousewheel.");
+}
+
+void MouseScrollHandler::UserPrefs::Init() {
+ if (mInitialized) {
+ return;
+ }
+
+ mInitialized = true;
+
+ mScrollMessageHandledAsWheelMessage =
+ Preferences::GetBool("mousewheel.emulate_at_wm_scroll", false);
+ mEnableSystemSettingCache =
+ Preferences::GetBool("mousewheel.system_settings_cache.enabled", true);
+ mForceEnableSystemSettingCache = Preferences::GetBool(
+ "mousewheel.system_settings_cache.force_enabled", false);
+ mEmulateToMakeWindowUnderCursorForeground = Preferences::GetBool(
+ "mousewheel.debug.make_window_under_cursor_foreground", false);
+ mOverriddenVerticalScrollAmount =
+ Preferences::GetInt("mousewheel.windows.vertical_amount_override", -1);
+ mOverriddenHorizontalScrollAmount =
+ Preferences::GetInt("mousewheel.windows.horizontal_amount_override", -1);
+ mMouseScrollTransactionTimeout = Preferences::GetInt(
+ "mousewheel.windows.transaction.timeout", DEFAULT_TIMEOUT_DURATION);
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::UserPrefs::Init(): initialized, "
+ "mScrollMessageHandledAsWheelMessage=%s, "
+ "mEnableSystemSettingCache=%s, "
+ "mForceEnableSystemSettingCache=%s, "
+ "mEmulateToMakeWindowUnderCursorForeground=%s, "
+ "mOverriddenVerticalScrollAmount=%d, "
+ "mOverriddenHorizontalScrollAmount=%d, "
+ "mMouseScrollTransactionTimeout=%d",
+ GetBoolName(mScrollMessageHandledAsWheelMessage),
+ GetBoolName(mEnableSystemSettingCache),
+ GetBoolName(mForceEnableSystemSettingCache),
+ GetBoolName(mEmulateToMakeWindowUnderCursorForeground),
+ mOverriddenVerticalScrollAmount, mOverriddenHorizontalScrollAmount,
+ mMouseScrollTransactionTimeout));
+}
+
+void MouseScrollHandler::UserPrefs::MarkDirty() {
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::UserPrefs::MarkDirty(): Marking UserPrefs dirty"));
+ mInitialized = false;
+ // Some prefs might override system settings, so, we should mark them dirty.
+ MouseScrollHandler::sInstance->mSystemSettings.MarkDirty();
+ // When user prefs for mousewheel are changed, we should reset current
+ // transaction.
+ MOZ_ASSERT(sInstance,
+ "Must not be called at initializing MouseScrollHandler");
+ MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction();
+}
+
+/******************************************************************************
+ *
+ * Device
+ *
+ ******************************************************************************/
+
+/* static */
+bool MouseScrollHandler::Device::GetWorkaroundPref(const char* aPrefName,
+ bool aValueIfAutomatic) {
+ if (!aPrefName) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::GetWorkaroundPref(): Failed, aPrefName is "
+ "NULL"));
+ return aValueIfAutomatic;
+ }
+
+ int32_t lHackValue = 0;
+ if (NS_FAILED(Preferences::GetInt(aPrefName, &lHackValue))) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::GetWorkaroundPref(): Preferences::GetInt() "
+ "failed,"
+ " aPrefName=\"%s\", aValueIfAutomatic=%s",
+ aPrefName, GetBoolName(aValueIfAutomatic)));
+ return aValueIfAutomatic;
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::GetWorkaroundPref(): Succeeded, "
+ "aPrefName=\"%s\", aValueIfAutomatic=%s, lHackValue=%d",
+ aPrefName, GetBoolName(aValueIfAutomatic), lHackValue));
+
+ switch (lHackValue) {
+ case 0: // disabled
+ return false;
+ case 1: // enabled
+ return true;
+ default: // -1: autodetect
+ return aValueIfAutomatic;
+ }
+}
+
+/* static */
+void MouseScrollHandler::Device::Init() {
+ // FYI: Thinkpad's TrackPoint is Apoint of Alps and UltraNav is SynTP of
+ // Synaptics. So, those drivers' information should be initialized
+ // before calling methods of TrackPoint and UltraNav.
+ SynTP::Init();
+ Elantech::Init();
+ Apoint::Init();
+
+ sFakeScrollableWindowNeeded = GetWorkaroundPref(
+ "ui.trackpoint_hack.enabled", (TrackPoint::IsDriverInstalled() ||
+ UltraNav::IsObsoleteDriverInstalled()));
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Init(): sFakeScrollableWindowNeeded=%s",
+ GetBoolName(sFakeScrollableWindowNeeded)));
+}
+
+/******************************************************************************
+ *
+ * Device::SynTP
+ *
+ ******************************************************************************/
+
+/* static */
+void MouseScrollHandler::Device::SynTP::Init() {
+ if (sInitialized) {
+ return;
+ }
+
+ sInitialized = true;
+ sMajorVersion = 0;
+ sMinorVersion = -1;
+
+ wchar_t buf[40];
+ bool foundKey = WinUtils::GetRegistryKey(
+ HKEY_LOCAL_MACHINE, L"Software\\Synaptics\\SynTP\\Install",
+ L"DriverVersion", buf, sizeof buf);
+ if (!foundKey) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::SynTP::Init(): "
+ "SynTP driver is not found"));
+ return;
+ }
+
+ sMajorVersion = wcstol(buf, nullptr, 10);
+ sMinorVersion = 0;
+ wchar_t* p = wcschr(buf, L'.');
+ if (p) {
+ sMinorVersion = wcstol(p + 1, nullptr, 10);
+ }
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::SynTP::Init(): "
+ "found driver version = %d.%d",
+ sMajorVersion, sMinorVersion));
+}
+
+/******************************************************************************
+ *
+ * Device::Elantech
+ *
+ ******************************************************************************/
+
+/* static */
+void MouseScrollHandler::Device::Elantech::Init() {
+ int32_t version = GetDriverMajorVersion();
+ bool needsHack = Device::GetWorkaroundPref(
+ "ui.elantech_gesture_hacks.enabled", version != 0);
+ sUseSwipeHack = needsHack && version <= 7;
+ sUsePinchHack = needsHack && version <= 8;
+
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Elantech::Init(): version=%d, sUseSwipeHack=%s, "
+ "sUsePinchHack=%s",
+ version, GetBoolName(sUseSwipeHack), GetBoolName(sUsePinchHack)));
+}
+
+/* static */
+int32_t MouseScrollHandler::Device::Elantech::GetDriverMajorVersion() {
+ wchar_t buf[40];
+ // The driver version is found in one of these two registry keys.
+ bool foundKey = WinUtils::GetRegistryKey(HKEY_CURRENT_USER,
+ L"Software\\Elantech\\MainOption",
+ L"DriverVersion", buf, sizeof buf);
+ if (!foundKey) {
+ foundKey =
+ WinUtils::GetRegistryKey(HKEY_CURRENT_USER, L"Software\\Elantech",
+ L"DriverVersion", buf, sizeof buf);
+ }
+
+ if (!foundKey) {
+ return 0;
+ }
+
+ // Assume that the major version number can be found just after a space
+ // or at the start of the string.
+ for (wchar_t* p = buf; *p; p++) {
+ if (*p >= L'0' && *p <= L'9' && (p == buf || *(p - 1) == L' ')) {
+ return wcstol(p, nullptr, 10);
+ }
+ }
+
+ return 0;
+}
+
+/* static */
+bool MouseScrollHandler::Device::Elantech::IsHelperWindow(HWND aWnd) {
+ // The helper window cannot be distinguished based on its window class, so we
+ // need to check if it is owned by the helper process, ETDCtrl.exe.
+
+ const wchar_t* filenameSuffix = L"\\etdctrl.exe";
+ const int filenameSuffixLength = 12;
+
+ DWORD pid;
+ ::GetWindowThreadProcessId(aWnd, &pid);
+
+ HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
+ if (!hProcess) {
+ return false;
+ }
+
+ bool result = false;
+ wchar_t path[256] = {L'\0'};
+ if (::GetProcessImageFileNameW(hProcess, path, ArrayLength(path))) {
+ int pathLength = lstrlenW(path);
+ if (pathLength >= filenameSuffixLength) {
+ if (lstrcmpiW(path + pathLength - filenameSuffixLength, filenameSuffix) ==
+ 0) {
+ result = true;
+ }
+ }
+ }
+ ::CloseHandle(hProcess);
+
+ return result;
+}
+
+/* static */
+bool MouseScrollHandler::Device::Elantech::HandleKeyMessage(
+ nsWindowBase* aWidget, UINT aMsg, WPARAM aWParam, LPARAM aLParam) {
+ // The Elantech touchpad driver understands three-finger swipe left and
+ // right gestures, and translates them into Page Up and Page Down key
+ // events for most applications. For Firefox 3.6, it instead sends
+ // Alt+Left and Alt+Right to trigger browser back/forward actions. As
+ // with the Thinkpad Driver hack in nsWindow::Create, the change in
+ // HWND structure makes Firefox not trigger the driver's heuristics
+ // any longer.
+ //
+ // The Elantech driver actually sends these messages for a three-finger
+ // swipe right:
+ //
+ // WM_KEYDOWN virtual_key = 0xCC or 0xFF ScanCode = 00
+ // WM_KEYDOWN virtual_key = VK_NEXT ScanCode = 00
+ // WM_KEYUP virtual_key = VK_NEXT ScanCode = 00
+ // WM_KEYUP virtual_key = 0xCC or 0xFF ScanCode = 00
+ //
+ // Whether 0xCC or 0xFF is sent is suspected to depend on the driver
+ // version. 7.0.4.12_14Jul09_WHQL, 7.0.5.10, and 7.0.6.0 generate 0xCC.
+ // 7.0.4.3 from Asus on EeePC generates 0xFF.
+ //
+ // On some hardware, IS_VK_DOWN(0xFF) returns true even when Elantech
+ // messages are not involved, meaning that alone is not enough to
+ // distinguish the gesture from a regular Page Up or Page Down key press.
+ // The ScanCode is therefore also tested to detect the gesture.
+ // We then pretend that we should dispatch "Go Forward" command. Similarly
+ // for VK_PRIOR and "Go Back" command.
+ if (sUseSwipeHack && (aWParam == VK_NEXT || aWParam == VK_PRIOR) &&
+ WinUtils::GetScanCode(aLParam) == 0 &&
+ (IS_VK_DOWN(0xFF) || IS_VK_DOWN(0xCC))) {
+ if (aMsg == WM_KEYDOWN) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Elantech::HandleKeyMessage(): Dispatching "
+ "%s command event",
+ aWParam == VK_NEXT ? "Forward" : "Back"));
+
+ WidgetCommandEvent appCommandEvent(
+ true, (aWParam == VK_NEXT) ? nsGkAtoms::Forward : nsGkAtoms::Back,
+ aWidget);
+
+ // In this scenario, the coordinate of the event isn't supplied, so pass
+ // nullptr as an argument to indicate using the coordinate from the last
+ // available window message.
+ InitEvent(aWidget, appCommandEvent, nullptr);
+ aWidget->DispatchWindowEvent(&appCommandEvent);
+ } else {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Elantech::HandleKeyMessage(): Consumed"));
+ }
+ return true; // consume the message (doesn't need to dispatch key events)
+ }
+
+ // Version 8 of the Elantech touchpad driver sends these messages for
+ // zoom gestures:
+ //
+ // WM_KEYDOWN virtual_key = 0xCC time = 10
+ // WM_KEYDOWN virtual_key = VK_CONTROL time = 10
+ // WM_MOUSEWHEEL time = ::GetTickCount()
+ // WM_KEYUP virtual_key = VK_CONTROL time = 10
+ // WM_KEYUP virtual_key = 0xCC time = 10
+ //
+ // The result of this is that we process all of the WM_KEYDOWN/WM_KEYUP
+ // messages first because their timestamps make them appear to have
+ // been sent before the WM_MOUSEWHEEL message. To work around this,
+ // we store the current time when we process the WM_KEYUP message and
+ // assume that any WM_MOUSEWHEEL message with a timestamp before that
+ // time is one that should be processed as if the Control key was down.
+ if (sUsePinchHack && aMsg == WM_KEYUP && aWParam == VK_CONTROL &&
+ ::GetMessageTime() == 10) {
+ // We look only at the bottom 31 bits of the system tick count since
+ // GetMessageTime returns a LONG, which is signed, so we want values
+ // that are more easily comparable.
+ sZoomUntil = ::GetTickCount() & 0x7FFFFFFF;
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Elantech::HandleKeyMessage(): sZoomUntil=%d",
+ sZoomUntil));
+ }
+
+ return false;
+}
+
+/* static */
+void MouseScrollHandler::Device::Elantech::UpdateZoomUntil() {
+ if (!sZoomUntil) {
+ return;
+ }
+
+ // For the Elantech Touchpad Zoom Gesture Hack, we should check that the
+ // system time (32-bit milliseconds) hasn't wrapped around. Otherwise we
+ // might get into the situation where wheel events for the next 50 days of
+ // system uptime are assumed to be Ctrl+Wheel events. (It is unlikely that
+ // we would get into that state, because the system would already need to be
+ // up for 50 days and the Control key message would need to be processed just
+ // before the system time overflow and the wheel message just after.)
+ //
+ // We also take the chance to reset sZoomUntil if we simply have passed that
+ // time.
+ LONG msgTime = ::GetMessageTime();
+ if ((sZoomUntil >= 0x3fffffffu && DWORD(msgTime) < 0x40000000u) ||
+ (sZoomUntil < DWORD(msgTime))) {
+ sZoomUntil = 0;
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Elantech::UpdateZoomUntil(): "
+ "sZoomUntil was reset"));
+ }
+}
+
+/* static */
+bool MouseScrollHandler::Device::Elantech::IsZooming() {
+ // Assume the Control key is down if the Elantech touchpad has sent the
+ // mis-ordered WM_KEYDOWN/WM_MOUSEWHEEL messages. (See the comment in
+ // OnKeyUp.)
+ return (sZoomUntil && static_cast<DWORD>(::GetMessageTime()) < sZoomUntil);
+}
+
+/******************************************************************************
+ *
+ * Device::Apoint
+ *
+ ******************************************************************************/
+
+/* static */
+void MouseScrollHandler::Device::Apoint::Init() {
+ if (sInitialized) {
+ return;
+ }
+
+ sInitialized = true;
+ sMajorVersion = 0;
+ sMinorVersion = -1;
+
+ wchar_t buf[40];
+ bool foundKey =
+ WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, L"Software\\Alps\\Apoint",
+ L"ProductVer", buf, sizeof buf);
+ if (!foundKey) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Apoint::Init(): "
+ "Apoint driver is not found"));
+ return;
+ }
+
+ sMajorVersion = wcstol(buf, nullptr, 10);
+ sMinorVersion = 0;
+ wchar_t* p = wcschr(buf, L'.');
+ if (p) {
+ sMinorVersion = wcstol(p + 1, nullptr, 10);
+ }
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Apoint::Init(): "
+ "found driver version = %d.%d",
+ sMajorVersion, sMinorVersion));
+}
+
+/******************************************************************************
+ *
+ * Device::TrackPoint
+ *
+ ******************************************************************************/
+
+/* static */
+bool MouseScrollHandler::Device::TrackPoint::IsDriverInstalled() {
+ if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER,
+ L"Software\\Lenovo\\TrackPoint")) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::TrackPoint::IsDriverInstalled(): "
+ "Lenovo's TrackPoint driver is found"));
+ return true;
+ }
+
+ if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER,
+ L"Software\\Alps\\Apoint\\TrackPoint")) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::TrackPoint::IsDriverInstalled(): "
+ "Alps's TrackPoint driver is found"));
+ }
+
+ return false;
+}
+
+/******************************************************************************
+ *
+ * Device::UltraNav
+ *
+ ******************************************************************************/
+
+/* static */
+bool MouseScrollHandler::Device::UltraNav::IsObsoleteDriverInstalled() {
+ if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER,
+ L"Software\\Lenovo\\UltraNav")) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): "
+ "Lenovo's UltraNav driver is found"));
+ return true;
+ }
+
+ bool installed = false;
+ if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER,
+ L"Software\\Synaptics\\SynTPEnh\\UltraNavUSB")) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): "
+ "Synaptics's UltraNav (USB) driver is found"));
+ installed = true;
+ } else if (WinUtils::HasRegistryKey(
+ HKEY_CURRENT_USER,
+ L"Software\\Synaptics\\SynTPEnh\\UltraNavPS2")) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): "
+ "Synaptics's UltraNav (PS/2) driver is found"));
+ installed = true;
+ }
+
+ if (!installed) {
+ return false;
+ }
+
+ int32_t majorVersion = Device::SynTP::GetDriverMajorVersion();
+ if (!majorVersion) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): "
+ "Failed to get UltraNav driver version"));
+ return false;
+ }
+ int32_t minorVersion = Device::SynTP::GetDriverMinorVersion();
+ return majorVersion < 15 || (majorVersion == 15 && minorVersion == 0);
+}
+
+/******************************************************************************
+ *
+ * Device::SetPoint
+ *
+ ******************************************************************************/
+
+/* static */
+bool MouseScrollHandler::Device::SetPoint::IsGetMessagePosResponseValid(
+ UINT aMessage, WPARAM aWParam, LPARAM aLParam) {
+ if (aMessage != WM_MOUSEHWHEEL) {
+ return false;
+ }
+
+ POINTS pts = MouseScrollHandler::GetCurrentMessagePos();
+ LPARAM messagePos = MAKELPARAM(pts.x, pts.y);
+
+ // XXX We should check whether SetPoint is installed or not by registry.
+
+ // SetPoint, Logitech (Logicool) mouse driver, (confirmed with 4.82.11 and
+ // MX-1100) always sets 0 to the lParam of WM_MOUSEHWHEEL. The driver SENDs
+ // one message at first time, this time, ::GetMessagePos() works fine.
+ // Then, we will return 0 (0 means we process it) to the message. Then, the
+ // driver will POST the same messages continuously during the wheel tilted.
+ // But ::GetMessagePos() API always returns (0, 0) for them, even if the
+ // actual mouse cursor isn't 0,0. Therefore, we cannot trust the result of
+ // ::GetMessagePos API if the sender is SetPoint.
+ if (!sMightBeUsing && !aLParam && aLParam != messagePos &&
+ ::InSendMessage()) {
+ sMightBeUsing = true;
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::SetPoint::IsGetMessagePosResponseValid(): "
+ "Might using SetPoint"));
+ } else if (sMightBeUsing && aLParam != 0 && ::InSendMessage()) {
+ // The user has changed the mouse from Logitech's to another one (e.g.,
+ // the user has changed to the touchpad of the notebook.
+ sMightBeUsing = false;
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::SetPoint::IsGetMessagePosResponseValid(): "
+ "Might stop using SetPoint"));
+ }
+ return (sMightBeUsing && !aLParam && !messagePos);
+}
+
+/******************************************************************************
+ *
+ * SynthesizingEvent
+ *
+ ******************************************************************************/
+
+/* static */
+bool MouseScrollHandler::SynthesizingEvent::IsSynthesizing() {
+ return MouseScrollHandler::sInstance &&
+ MouseScrollHandler::sInstance->mSynthesizingEvent &&
+ MouseScrollHandler::sInstance->mSynthesizingEvent->mStatus !=
+ NOT_SYNTHESIZING;
+}
+
+nsresult MouseScrollHandler::SynthesizingEvent::Synthesize(
+ const POINTS& aCursorPoint, HWND aWnd, UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam, const BYTE (&aKeyStates)[256]) {
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::SynthesizingEvent::Synthesize(): aCursorPoint: { "
+ "x: %d, y: %d }, aWnd=0x%X, aMessage=0x%04X, aWParam=0x%08X, "
+ "aLParam=0x%08X, IsSynthesized()=%s, mStatus=%s",
+ aCursorPoint.x, aCursorPoint.y, aWnd, aMessage, aWParam, aLParam,
+ GetBoolName(IsSynthesizing()), GetStatusName()));
+
+ if (IsSynthesizing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ ::GetKeyboardState(mOriginalKeyState);
+
+ // Note that we cannot use ::SetCursorPos() because it works asynchronously.
+ // We should SEND the message for reducing the possibility of receiving
+ // unexpected message which were not sent from here.
+ mCursorPoint = aCursorPoint;
+
+ mWnd = aWnd;
+ mMessage = aMessage;
+ mWParam = aWParam;
+ mLParam = aLParam;
+
+ memcpy(mKeyState, aKeyStates, sizeof(mKeyState));
+ ::SetKeyboardState(mKeyState);
+
+ mStatus = SENDING_MESSAGE;
+
+ // Don't assume that aWnd is always managed by nsWindow. It might be
+ // a plugin window.
+ ::SendMessage(aWnd, aMessage, aWParam, aLParam);
+
+ return NS_OK;
+}
+
+void MouseScrollHandler::SynthesizingEvent::NativeMessageReceived(
+ nsWindowBase* aWidget, UINT aMessage, WPARAM aWParam, LPARAM aLParam) {
+ if (mStatus == SENDING_MESSAGE && mMessage == aMessage &&
+ mWParam == aWParam && mLParam == aLParam) {
+ mStatus = NATIVE_MESSAGE_RECEIVED;
+ if (aWidget && aWidget->GetWindowHandle() == mWnd) {
+ return;
+ }
+ // If the target window is not ours and received window is our plugin
+ // window, it comes from child window of the plugin.
+ if (aWidget && aWidget->IsPlugin() && !WinUtils::GetNSWindowBasePtr(mWnd)) {
+ return;
+ }
+ // Otherwise, the message may not be sent by us.
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::SynthesizingEvent::NativeMessageReceived(): "
+ "aWidget=%p, aWidget->GetWindowHandle()=0x%X, mWnd=0x%X, "
+ "aMessage=0x%04X, aWParam=0x%08X, aLParam=0x%08X, mStatus=%s",
+ aWidget, aWidget ? aWidget->GetWindowHandle() : 0, mWnd, aMessage,
+ aWParam, aLParam, GetStatusName()));
+
+ // We failed to receive our sent message, we failed to do the job.
+ Finish();
+
+ return;
+}
+
+void MouseScrollHandler::SynthesizingEvent::
+ NotifyNativeMessageHandlingFinished() {
+ if (!IsSynthesizing()) {
+ return;
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::SynthesizingEvent::"
+ "NotifyNativeMessageHandlingFinished(): IsWaitingInternalMessage=%s",
+ GetBoolName(MouseScrollHandler::IsWaitingInternalMessage())));
+
+ if (MouseScrollHandler::IsWaitingInternalMessage()) {
+ mStatus = INTERNAL_MESSAGE_POSTED;
+ return;
+ }
+
+ // If the native message handler didn't post our internal message,
+ // we our job is finished.
+ // TODO: When we post the message to plugin window, there is remaning job.
+ Finish();
+}
+
+void MouseScrollHandler::SynthesizingEvent::
+ NotifyInternalMessageHandlingFinished() {
+ if (!IsSynthesizing()) {
+ return;
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::SynthesizingEvent::"
+ "NotifyInternalMessageHandlingFinished()"));
+
+ Finish();
+}
+
+void MouseScrollHandler::SynthesizingEvent::Finish() {
+ if (!IsSynthesizing()) {
+ return;
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::SynthesizingEvent::Finish()"));
+
+ // Restore the original key state.
+ ::SetKeyboardState(mOriginalKeyState);
+
+ mStatus = NOT_SYNTHESIZING;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinMouseScrollHandler.h b/widget/windows/WinMouseScrollHandler.h
new file mode 100644
index 0000000000..5e915d630d
--- /dev/null
+++ b/widget/windows/WinMouseScrollHandler.h
@@ -0,0 +1,576 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_WinMouseScrollHandler_h__
+#define mozilla_widget_WinMouseScrollHandler_h__
+
+#include "nscore.h"
+#include "nsDebug.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TimeStamp.h"
+#include "Units.h"
+#include <windows.h>
+#include "nsPoint.h"
+
+class nsWindowBase;
+
+namespace mozilla {
+namespace widget {
+
+class ModifierKeyState;
+
+struct MSGResult;
+
+class MouseScrollHandler {
+ public:
+ static MouseScrollHandler* GetInstance();
+
+ static void Initialize();
+ static void Shutdown();
+
+ static bool NeedsMessage(UINT aMsg);
+ static bool ProcessMessage(nsWindowBase* aWidget, UINT msg, WPARAM wParam,
+ LPARAM lParam, MSGResult& aResult);
+
+ /**
+ * See nsIWidget::SynthesizeNativeMouseScrollEvent() for the detail about
+ * this method.
+ */
+ static nsresult SynthesizeNativeMouseScrollEvent(
+ nsWindowBase* aWidget, const LayoutDeviceIntPoint& aPoint,
+ uint32_t aNativeMessage, int32_t aDelta, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags);
+
+ /**
+ * IsWaitingInternalMessage() returns true if MouseScrollHandler posted
+ * an internal message for a native mouse wheel message and has not
+ * received it. Otherwise, false.
+ */
+ static bool IsWaitingInternalMessage() {
+ return sInstance && sInstance->mIsWaitingInternalMessage;
+ }
+
+ private:
+ MouseScrollHandler();
+ ~MouseScrollHandler();
+
+ bool mIsWaitingInternalMessage;
+
+ static void MaybeLogKeyState();
+
+ static MouseScrollHandler* sInstance;
+
+ /**
+ * InitEvent() initializes the aEvent. If aPoint is null, the result of
+ * GetCurrentMessagePos() will be used.
+ */
+ static void InitEvent(nsWindowBase* aWidget, WidgetGUIEvent& aEvent,
+ LPARAM* aPoint);
+
+ /**
+ * GetModifierKeyState() returns current modifier key state.
+ * Note that some devices need some hack for the modifier key state.
+ * This method does it automatically.
+ *
+ * @param aMessage Handling message.
+ */
+ static ModifierKeyState GetModifierKeyState(UINT aMessage);
+
+ /**
+ * MozGetMessagePos() returns the mouse cursor position when GetMessage()
+ * was called last time. However, if we're sending a native message,
+ * this returns the specified cursor position by
+ * SynthesizeNativeMouseScrollEvent().
+ */
+ static POINTS GetCurrentMessagePos();
+
+ /**
+ * ProcessNativeMouseWheelMessage() processes WM_MOUSEWHEEL and
+ * WM_MOUSEHWHEEL. Additionally, processes WM_VSCROLL and WM_HSCROLL if they
+ * should be processed as mouse wheel message.
+ * This method posts MOZ_WM_MOUSEVWHEEL, MOZ_WM_MOUSEHWHEEL,
+ * MOZ_WM_VSCROLL or MOZ_WM_HSCROLL if we need to dispatch mouse scroll
+ * events. That avoids deadlock with plugin process.
+ *
+ * @param aWidget A window which receives the message.
+ * @param aMessage WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL or
+ * WM_HSCROLL.
+ * @param aWParam The wParam value of the message.
+ * @param aLParam The lParam value of the message.
+ */
+ void ProcessNativeMouseWheelMessage(nsWindowBase* aWidget, UINT aMessage,
+ WPARAM aWParam, LPARAM aLParam);
+
+ /**
+ * ProcessNativeScrollMessage() processes WM_VSCROLL and WM_HSCROLL.
+ * This method just call ProcessMouseWheelMessage() if the message should be
+ * processed as mouse wheel message. Otherwise, dispatches a content
+ * command event.
+ *
+ * @param aWidget A window which receives the message.
+ * @param aMessage WM_VSCROLL or WM_HSCROLL.
+ * @param aWParam The wParam value of the message.
+ * @param aLParam The lParam value of the message.
+ * @return TRUE if the message is processed. Otherwise, FALSE.
+ */
+ bool ProcessNativeScrollMessage(nsWindowBase* aWidget, UINT aMessage,
+ WPARAM aWParam, LPARAM aLParam);
+
+ /**
+ * HandleMouseWheelMessage() processes MOZ_WM_MOUSEVWHEEL and
+ * MOZ_WM_MOUSEHWHEEL which are posted when one of our windows received
+ * WM_MOUSEWHEEL or WM_MOUSEHWHEEL for avoiding deadlock with OOPP.
+ *
+ * @param aWidget A window which receives the wheel message.
+ * @param aMessage MOZ_WM_MOUSEWHEEL or MOZ_WM_MOUSEHWHEEL.
+ * @param aWParam The wParam value of the original message.
+ * @param aLParam The lParam value of the original message.
+ */
+ void HandleMouseWheelMessage(nsWindowBase* aWidget, UINT aMessage,
+ WPARAM aWParam, LPARAM aLParam);
+
+ /**
+ * HandleScrollMessageAsMouseWheelMessage() processes the MOZ_WM_VSCROLL and
+ * MOZ_WM_HSCROLL which are posted when one of mouse windows received
+ * WM_VSCROLL or WM_HSCROLL and user wants them to emulate mouse wheel
+ * message's behavior.
+ *
+ * @param aWidget A window which receives the scroll message.
+ * @param aMessage MOZ_WM_VSCROLL or MOZ_WM_HSCROLL.
+ * @param aWParam The wParam value of the original message.
+ * @param aLParam The lParam value of the original message.
+ */
+ void HandleScrollMessageAsMouseWheelMessage(nsWindowBase* aWidget,
+ UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam);
+
+ /**
+ * ComputeMessagePos() computes the cursor position when the message was
+ * added to the queue.
+ *
+ * @param aMessage Handling message.
+ * @param aWParam Handling message's wParam.
+ * @param aLParam Handling message's lParam.
+ * @return Mouse cursor position when the message is added to
+ * the queue or current cursor position if the result of
+ * ::GetMessagePos() is broken.
+ */
+ POINT ComputeMessagePos(UINT aMessage, WPARAM aWParam, LPARAM aLParam);
+
+ class EventInfo {
+ public:
+ /**
+ * @param aWidget An nsWindow which is handling the event.
+ * @param aMessage Must be WM_MOUSEWHEEL or WM_MOUSEHWHEEL.
+ */
+ EventInfo(nsWindowBase* aWidget, UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam);
+
+ bool CanDispatchWheelEvent() const;
+
+ int32_t GetNativeDelta() const { return mDelta; }
+ HWND GetWindowHandle() const { return mWnd; }
+ const TimeStamp& GetTimeStamp() const { return mTimeStamp; }
+ bool IsVertical() const { return mIsVertical; }
+ bool IsPositive() const { return (mDelta > 0); }
+ bool IsPage() const { return mIsPage; }
+
+ /**
+ * @return Number of lines or pages scrolled per WHEEL_DELTA.
+ */
+ int32_t GetScrollAmount() const;
+
+ protected:
+ EventInfo()
+ : mIsVertical(false), mIsPage(false), mDelta(0), mWnd(nullptr) {}
+
+ // TRUE if event is for vertical scroll. Otherwise, FALSE.
+ bool mIsVertical;
+ // TRUE if event scrolls per page, otherwise, FALSE.
+ bool mIsPage;
+ // The native delta value.
+ int32_t mDelta;
+ // The window handle which is handling the event.
+ HWND mWnd;
+ // Timestamp of the event.
+ TimeStamp mTimeStamp;
+ };
+
+ class LastEventInfo : public EventInfo {
+ public:
+ LastEventInfo() : EventInfo(), mAccumulatedDelta(0) {}
+
+ /**
+ * CanContinueTransaction() checks whether the new event can continue the
+ * last transaction or not. Note that if there is no transaction, this
+ * returns true.
+ */
+ bool CanContinueTransaction(const EventInfo& aNewEvent);
+
+ /**
+ * ResetTransaction() resets the transaction, i.e., the instance forgets
+ * the last event information.
+ */
+ void ResetTransaction();
+
+ /**
+ * RecordEvent() saves the information of new event.
+ */
+ void RecordEvent(const EventInfo& aEvent);
+
+ /**
+ * InitWheelEvent() initializes NS_WHEEL_WHEEL event and
+ * recomputes the remaning detla for the event.
+ * This must be called only once during handling a message and after
+ * RecordEvent() is called.
+ *
+ * @param aWidget A window which will dispatch the event.
+ * @param aWheelEvent An NS_WHEEL_WHEEL event, this will be
+ * initialized.
+ * @param aModKeyState Current modifier key state.
+ * @return TRUE if the event is ready to dispatch.
+ * Otherwise, FALSE.
+ */
+ bool InitWheelEvent(nsWindowBase* aWidget, WidgetWheelEvent& aWheelEvent,
+ const ModifierKeyState& aModKeyState, LPARAM aLParam);
+
+ private:
+ static int32_t RoundDelta(double aDelta);
+
+ int32_t mAccumulatedDelta;
+ };
+
+ LastEventInfo mLastEventInfo;
+
+ class SystemSettings {
+ public:
+ SystemSettings() : mInitialized(false) {}
+
+ void Init();
+ void MarkDirty();
+ void NotifyUserPrefsMayOverrideSystemSettings();
+
+ // On some environments, SystemParametersInfo() may be hooked by touchpad
+ // utility or something. In such case, when user changes active pointing
+ // device to another one, the result of SystemParametersInfo() may be
+ // changed without WM_SETTINGCHANGE message. For avoiding this trouble,
+ // we need to modify cache of system settings at every wheel message
+ // handling if we meet known device whose utility may hook the API.
+ void TrustedScrollSettingsDriver();
+
+ // Returns true if the system scroll may be overridden for faster scroll.
+ // Otherwise, false. For example, if the user maybe uses an expensive
+ // mouse which supports acceleration of scroll speed, faster scroll makes
+ // the user inconvenient.
+ bool IsOverridingSystemScrollSpeedAllowed();
+
+ int32_t GetScrollAmount(bool aForVertical) const {
+ MOZ_ASSERT(mInitialized, "SystemSettings must be initialized");
+ return aForVertical ? mScrollLines : mScrollChars;
+ }
+
+ bool IsPageScroll(bool aForVertical) const {
+ MOZ_ASSERT(mInitialized, "SystemSettings must be initialized");
+ return aForVertical ? (uint32_t(mScrollLines) == WHEEL_PAGESCROLL)
+ : (uint32_t(mScrollChars) == WHEEL_PAGESCROLL);
+ }
+
+ // The default vertical and horizontal scrolling speed is 3, this is defined
+ // on the document of SystemParametersInfo in MSDN.
+ static int32_t DefaultScrollLines() { return 3; }
+ static int32_t DefaultScrollChars() { return 3; }
+
+ private:
+ bool mInitialized;
+ // The result of SystemParametersInfo() may not be reliable since it may
+ // be hooked. So, if the values are initialized with prefs, we can trust
+ // the value. Following mIsReliableScroll* are set true when mScroll* are
+ // initialized with prefs.
+ bool mIsReliableScrollLines;
+ bool mIsReliableScrollChars;
+
+ int32_t mScrollLines;
+ int32_t mScrollChars;
+
+ // Returns true if cached value is changed.
+ bool InitScrollLines();
+ bool InitScrollChars();
+
+ void RefreshCache();
+ };
+
+ SystemSettings mSystemSettings;
+
+ class UserPrefs {
+ public:
+ UserPrefs();
+ ~UserPrefs();
+
+ void MarkDirty();
+
+ bool IsScrollMessageHandledAsWheelMessage() {
+ Init();
+ return mScrollMessageHandledAsWheelMessage;
+ }
+
+ bool IsSystemSettingCacheEnabled() {
+ Init();
+ return mEnableSystemSettingCache;
+ }
+
+ bool IsSystemSettingCacheForciblyEnabled() {
+ Init();
+ return mForceEnableSystemSettingCache;
+ }
+
+ bool ShouldEmulateToMakeWindowUnderCursorForeground() {
+ Init();
+ return mEmulateToMakeWindowUnderCursorForeground;
+ }
+
+ int32_t GetOverriddenVerticalScrollAmout() {
+ Init();
+ return mOverriddenVerticalScrollAmount;
+ }
+
+ int32_t GetOverriddenHorizontalScrollAmout() {
+ Init();
+ return mOverriddenHorizontalScrollAmount;
+ }
+
+ int32_t GetMouseScrollTransactionTimeout() {
+ Init();
+ return mMouseScrollTransactionTimeout;
+ }
+
+ private:
+ void Init();
+
+ static void OnChange(const char* aPrefName, void* aSelf) {
+ static_cast<UserPrefs*>(aSelf)->MarkDirty();
+ }
+
+ bool mInitialized;
+ bool mScrollMessageHandledAsWheelMessage;
+ bool mEnableSystemSettingCache;
+ bool mForceEnableSystemSettingCache;
+ bool mEmulateToMakeWindowUnderCursorForeground;
+ int32_t mOverriddenVerticalScrollAmount;
+ int32_t mOverriddenHorizontalScrollAmount;
+ int32_t mMouseScrollTransactionTimeout;
+ };
+
+ UserPrefs mUserPrefs;
+
+ class SynthesizingEvent {
+ public:
+ SynthesizingEvent()
+ : mWnd(nullptr),
+ mMessage(0),
+ mWParam(0),
+ mLParam(0),
+ mStatus(NOT_SYNTHESIZING) {}
+
+ ~SynthesizingEvent() {}
+
+ static bool IsSynthesizing();
+
+ nsresult Synthesize(const POINTS& aCursorPoint, HWND aWnd, UINT aMessage,
+ WPARAM aWParam, LPARAM aLParam,
+ const BYTE (&aKeyStates)[256]);
+
+ void NativeMessageReceived(nsWindowBase* aWidget, UINT aMessage,
+ WPARAM aWParam, LPARAM aLParam);
+
+ void NotifyNativeMessageHandlingFinished();
+ void NotifyInternalMessageHandlingFinished();
+
+ const POINTS& GetCursorPoint() const { return mCursorPoint; }
+
+ private:
+ POINTS mCursorPoint;
+ HWND mWnd;
+ UINT mMessage;
+ WPARAM mWParam;
+ LPARAM mLParam;
+ BYTE mKeyState[256];
+ BYTE mOriginalKeyState[256];
+
+ enum Status {
+ NOT_SYNTHESIZING,
+ SENDING_MESSAGE,
+ NATIVE_MESSAGE_RECEIVED,
+ INTERNAL_MESSAGE_POSTED,
+ };
+ Status mStatus;
+
+ const char* GetStatusName() {
+ switch (mStatus) {
+ case NOT_SYNTHESIZING:
+ return "NOT_SYNTHESIZING";
+ case SENDING_MESSAGE:
+ return "SENDING_MESSAGE";
+ case NATIVE_MESSAGE_RECEIVED:
+ return "NATIVE_MESSAGE_RECEIVED";
+ case INTERNAL_MESSAGE_POSTED:
+ return "INTERNAL_MESSAGE_POSTED";
+ default:
+ return "Unknown";
+ }
+ }
+
+ void Finish();
+ }; // SynthesizingEvent
+
+ SynthesizingEvent* mSynthesizingEvent;
+
+ public:
+ class Device {
+ public:
+ // SynTP is a touchpad driver of Synaptics.
+ class SynTP {
+ public:
+ static bool IsDriverInstalled() { return sMajorVersion != 0; }
+ /**
+ * GetDriverMajorVersion() returns the installed driver's major version.
+ * If SynTP driver isn't installed, this returns 0.
+ */
+ static int32_t GetDriverMajorVersion() { return sMajorVersion; }
+ /**
+ * GetDriverMinorVersion() returns the installed driver's minor version.
+ * If SynTP driver isn't installed, this returns -1.
+ */
+ static int32_t GetDriverMinorVersion() { return sMinorVersion; }
+
+ static void Init();
+
+ private:
+ static bool sInitialized;
+ static int32_t sMajorVersion;
+ static int32_t sMinorVersion;
+ };
+
+ class Elantech {
+ public:
+ /**
+ * GetDriverMajorVersion() returns the installed driver's major version.
+ * If Elantech's driver was installed, returns 0.
+ */
+ static int32_t GetDriverMajorVersion();
+
+ /**
+ * IsHelperWindow() checks whether aWnd is a helper window of Elantech's
+ * touchpad. Returns TRUE if so. Otherwise, FALSE.
+ */
+ static bool IsHelperWindow(HWND aWnd);
+
+ /**
+ * Key message handler for Elantech's hack. Returns TRUE if the message
+ * is consumed by this handler. Otherwise, FALSE.
+ */
+ static bool HandleKeyMessage(nsWindowBase* aWidget, UINT aMsg,
+ WPARAM aWParam, LPARAM aLParam);
+
+ static void UpdateZoomUntil();
+ static bool IsZooming();
+
+ static void Init();
+
+ static bool IsPinchHackNeeded() { return sUsePinchHack; }
+
+ private:
+ // Whether to enable the Elantech swipe gesture hack.
+ static bool sUseSwipeHack;
+ // Whether to enable the Elantech pinch-to-zoom gesture hack.
+ static bool sUsePinchHack;
+ static DWORD sZoomUntil;
+ }; // class Elantech
+
+ // Apoint is a touchpad driver of Alps.
+ class Apoint {
+ public:
+ static bool IsDriverInstalled() { return sMajorVersion != 0; }
+ /**
+ * GetDriverMajorVersion() returns the installed driver's major version.
+ * If Apoint driver isn't installed, this returns 0.
+ */
+ static int32_t GetDriverMajorVersion() { return sMajorVersion; }
+ /**
+ * GetDriverMinorVersion() returns the installed driver's minor version.
+ * If Apoint driver isn't installed, this returns -1.
+ */
+ static int32_t GetDriverMinorVersion() { return sMinorVersion; }
+
+ static void Init();
+
+ private:
+ static bool sInitialized;
+ static int32_t sMajorVersion;
+ static int32_t sMinorVersion;
+ };
+
+ class TrackPoint {
+ public:
+ /**
+ * IsDriverInstalled() returns TRUE if TrackPoint's driver is installed.
+ * Otherwise, returns FALSE.
+ */
+ static bool IsDriverInstalled();
+ }; // class TrackPoint
+
+ class UltraNav {
+ public:
+ /**
+ * IsObsoleteDriverInstalled() checks whether obsoleted UltraNav
+ * is installed on the environment.
+ * Returns TRUE if it was installed. Otherwise, FALSE.
+ */
+ static bool IsObsoleteDriverInstalled();
+ }; // class UltraNav
+
+ class SetPoint {
+ public:
+ /**
+ * SetPoint, Logitech's mouse driver, may report wrong cursor position
+ * for WM_MOUSEHWHEEL message. See comment in the implementation for
+ * the detail.
+ */
+ static bool IsGetMessagePosResponseValid(UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam);
+
+ private:
+ static bool sMightBeUsing;
+ };
+
+ static void Init();
+
+ static bool IsFakeScrollableWindowNeeded() {
+ return sFakeScrollableWindowNeeded;
+ }
+
+ private:
+ /**
+ * Gets the bool value of aPrefName used to enable or disable an input
+ * workaround (like the Trackpoint hack). The pref can take values 0 (for
+ * disabled), 1 (for enabled) or -1 (to automatically detect whether to
+ * enable the workaround).
+ *
+ * @param aPrefName The name of the pref.
+ * @param aValueIfAutomatic Whether the given input workaround should be
+ * enabled by default.
+ */
+ static bool GetWorkaroundPref(const char* aPrefName,
+ bool aValueIfAutomatic);
+
+ static bool sFakeScrollableWindowNeeded;
+ }; // class Device
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_WinMouseScrollHandler_h__
diff --git a/widget/windows/WinNativeEventData.h b/widget/windows/WinNativeEventData.h
new file mode 100644
index 0000000000..b6b1de225a
--- /dev/null
+++ b/widget/windows/WinNativeEventData.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 mozilla_widget_WinNativeEventData_h_
+#define mozilla_widget_WinNativeEventData_h_
+
+#include <windows.h>
+
+#include "mozilla/EventForwards.h"
+#include "mozilla/widget/WinModifierKeyState.h"
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * WinNativeKeyEventData is used by nsIWidget::OnWindowedPluginKeyEvent() and
+ * related IPC methods. This class cannot hold any pointers and references
+ * since which are not available in different process.
+ */
+
+class WinNativeKeyEventData final {
+ public:
+ UINT mMessage;
+ WPARAM mWParam;
+ LPARAM mLParam;
+ Modifiers mModifiers;
+
+ private:
+ uintptr_t mKeyboardLayout;
+
+ public:
+ WinNativeKeyEventData(UINT aMessage, WPARAM aWParam, LPARAM aLParam,
+ const ModifierKeyState& aModifierKeyState)
+ : mMessage(aMessage),
+ mWParam(aWParam),
+ mLParam(aLParam),
+ mModifiers(aModifierKeyState.GetModifiers()),
+ mKeyboardLayout(reinterpret_cast<uintptr_t>(::GetKeyboardLayout(0))) {}
+
+ HKL GetKeyboardLayout() const {
+ return reinterpret_cast<HKL>(mKeyboardLayout);
+ }
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef mozilla_widget_WinNativeEventData_h_
diff --git a/widget/windows/WinPointerEvents.cpp b/widget/windows/WinPointerEvents.cpp
new file mode 100644
index 0000000000..62d2aa0515
--- /dev/null
+++ b/widget/windows/WinPointerEvents.cpp
@@ -0,0 +1,190 @@
+/* -*- 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/. */
+
+/*
+ * WinPointerEvents - Helper functions to retrieve PointerEvent's attributes
+ */
+
+#include "nscore.h"
+#include "WinPointerEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/dom/MouseEventBinding.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+const wchar_t WinPointerEvents::kPointerLibraryName[] = L"user32.dll";
+HMODULE WinPointerEvents::sLibraryHandle = nullptr;
+WinPointerEvents::GetPointerTypePtr WinPointerEvents::getPointerType = nullptr;
+WinPointerEvents::GetPointerInfoPtr WinPointerEvents::getPointerInfo = nullptr;
+WinPointerEvents::GetPointerPenInfoPtr WinPointerEvents::getPointerPenInfo =
+ nullptr;
+
+WinPointerEvents::WinPointerEvents() { InitLibrary(); }
+
+/* Load and shutdown */
+void WinPointerEvents::InitLibrary() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!IsWin8OrLater()) {
+ // Only Win8 or later supports WM_POINTER*
+ return;
+ }
+ if (getPointerType) {
+ // Return if we already initialized the PointerEvent related interfaces
+ return;
+ }
+ sLibraryHandle = ::LoadLibraryW(kPointerLibraryName);
+ MOZ_ASSERT(sLibraryHandle, "cannot load pointer library");
+ if (sLibraryHandle) {
+ getPointerType =
+ (GetPointerTypePtr)GetProcAddress(sLibraryHandle, "GetPointerType");
+ getPointerInfo =
+ (GetPointerInfoPtr)GetProcAddress(sLibraryHandle, "GetPointerInfo");
+ getPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(
+ sLibraryHandle, "GetPointerPenInfo");
+ }
+
+ if (!getPointerType || !getPointerInfo || !getPointerPenInfo) {
+ MOZ_ASSERT(false, "get PointerEvent interfaces failed");
+ getPointerType = nullptr;
+ getPointerInfo = nullptr;
+ getPointerPenInfo = nullptr;
+ return;
+ }
+}
+
+bool WinPointerEvents::ShouldHandleWinPointerMessages(UINT aMsg,
+ WPARAM aWParam) {
+ MOZ_ASSERT(aMsg == WM_POINTERDOWN || aMsg == WM_POINTERUP ||
+ aMsg == WM_POINTERUPDATE || aMsg == WM_POINTERLEAVE);
+ if (!sLibraryHandle || !StaticPrefs::dom_w3c_pointer_events_enabled()) {
+ return false;
+ }
+
+ // We only handle WM_POINTER* when the input source is pen. This is because
+ // we need some information (e.g. tiltX, tiltY) which can't be retrieved by
+ // WM_*BUTTONDOWN.
+ uint32_t pointerId = GetPointerId(aWParam);
+ POINTER_INPUT_TYPE pointerType = PT_POINTER;
+ if (!GetPointerType(pointerId, &pointerType)) {
+ MOZ_ASSERT(false, "cannot find PointerType");
+ return false;
+ }
+ return (pointerType == PT_PEN);
+}
+
+bool WinPointerEvents::GetPointerType(uint32_t aPointerId,
+ POINTER_INPUT_TYPE* aPointerType) {
+ if (!getPointerType) {
+ return false;
+ }
+ return getPointerType(aPointerId, aPointerType);
+}
+
+POINTER_INPUT_TYPE
+WinPointerEvents::GetPointerType(uint32_t aPointerId) {
+ POINTER_INPUT_TYPE pointerType = PT_POINTER;
+ Unused << GetPointerType(aPointerId, &pointerType);
+ return pointerType;
+}
+
+bool WinPointerEvents::GetPointerInfo(uint32_t aPointerId,
+ POINTER_INFO* aPointerInfo) {
+ if (!getPointerInfo) {
+ return false;
+ }
+ return getPointerInfo(aPointerId, aPointerInfo);
+}
+
+bool WinPointerEvents::GetPointerPenInfo(uint32_t aPointerId,
+ POINTER_PEN_INFO* aPenInfo) {
+ if (!getPointerPenInfo) {
+ return false;
+ }
+ return getPointerPenInfo(aPointerId, aPenInfo);
+}
+
+bool WinPointerEvents::ShouldEnableInkCollector() {
+ // We need InkCollector on Win7. For Win8 or later, we handle WM_POINTER* for
+ // pen.
+ return !IsWin8OrLater();
+}
+
+bool WinPointerEvents::ShouldRollupOnPointerEvent(UINT aMsg, WPARAM aWParam) {
+ MOZ_ASSERT(aMsg == WM_POINTERDOWN);
+ // Only roll up popups when we handling WM_POINTER* to fire Gecko
+ // WidgetMouseEvent and suppress Windows WM_*BUTTONDOWN.
+ return ShouldHandleWinPointerMessages(aMsg, aWParam) &&
+ ShouldFirePointerEventByWinPointerMessages();
+}
+
+bool WinPointerEvents::ShouldFirePointerEventByWinPointerMessages() {
+ MOZ_ASSERT(sLibraryHandle && StaticPrefs::dom_w3c_pointer_events_enabled());
+ return StaticPrefs::dom_w3c_pointer_events_dispatch_by_pointer_messages();
+}
+
+WinPointerInfo* WinPointerEvents::GetCachedPointerInfo(UINT aMsg,
+ WPARAM aWParam) {
+ if (!sLibraryHandle || !StaticPrefs::dom_w3c_pointer_events_enabled() ||
+ MOUSE_INPUT_SOURCE() != dom::MouseEvent_Binding::MOZ_SOURCE_PEN ||
+ ShouldFirePointerEventByWinPointerMessages()) {
+ return nullptr;
+ }
+ switch (aMsg) {
+ case WM_LBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ return &mPenPointerDownInfo;
+ case WM_LBUTTONUP:
+ case WM_MBUTTONUP:
+ case WM_RBUTTONUP:
+ return &mPenPointerDownInfo;
+ case WM_MOUSEMOVE:
+ return &mPenPointerUpdateInfo;
+ default:
+ MOZ_ASSERT(false);
+ }
+ return nullptr;
+}
+
+void WinPointerEvents::ConvertAndCachePointerInfo(UINT aMsg, WPARAM aWParam) {
+ MOZ_ASSERT(
+ !StaticPrefs::dom_w3c_pointer_events_dispatch_by_pointer_messages());
+ // Windows doesn't support chorded buttons for pen, so we can simply keep the
+ // latest information from pen generated pointer messages and use them when
+ // handling mouse messages. Used different pointer info for pointerdown,
+ // pointerupdate, and pointerup because Windows doesn't always interleave
+ // pointer messages and mouse messages.
+ switch (aMsg) {
+ case WM_POINTERDOWN:
+ ConvertAndCachePointerInfo(aWParam, &mPenPointerDownInfo);
+ break;
+ case WM_POINTERUP:
+ ConvertAndCachePointerInfo(aWParam, &mPenPointerUpInfo);
+ break;
+ case WM_POINTERUPDATE:
+ ConvertAndCachePointerInfo(aWParam, &mPenPointerUpdateInfo);
+ break;
+ default:
+ break;
+ }
+}
+
+void WinPointerEvents::ConvertAndCachePointerInfo(WPARAM aWParam,
+ WinPointerInfo* aInfo) {
+ MOZ_ASSERT(
+ !StaticPrefs::dom_w3c_pointer_events_dispatch_by_pointer_messages());
+ aInfo->pointerId = GetPointerId(aWParam);
+ MOZ_ASSERT(GetPointerType(aInfo->pointerId) == PT_PEN);
+ POINTER_PEN_INFO penInfo;
+ GetPointerPenInfo(aInfo->pointerId, &penInfo);
+ aInfo->tiltX = penInfo.tiltX;
+ aInfo->tiltY = penInfo.tiltY;
+ // Windows defines the pen pressure is normalized to a range between 0 and
+ // 1024. Convert it to float.
+ aInfo->mPressure = penInfo.pressure ? (float)penInfo.pressure / 1024 : 0;
+}
diff --git a/widget/windows/WinPointerEvents.h b/widget/windows/WinPointerEvents.h
new file mode 100644
index 0000000000..6bded07d06
--- /dev/null
+++ b/widget/windows/WinPointerEvents.h
@@ -0,0 +1,174 @@
+/* -*- 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 WinPointerEvents_h__
+#define WinPointerEvents_h__
+
+#include "mozilla/MouseEvents.h"
+#include "nsWindowBase.h"
+
+// Define PointerEvent related macros and structures when building code on
+// Windows version before Win8.
+#if WINVER < 0x0602
+
+// These definitions are copied from WinUser.h. Some of them are not used but
+// keep them here for future usage.
+# define WM_NCPOINTERUPDATE 0x0241
+# define WM_NCPOINTERDOWN 0x0242
+# define WM_NCPOINTERUP 0x0243
+# define WM_POINTERUPDATE 0x0245
+# define WM_POINTERDOWN 0x0246
+# define WM_POINTERUP 0x0247
+# define WM_POINTERENTER 0x0249
+# define WM_POINTERLEAVE 0x024A
+# define WM_POINTERACTIVATE 0x024B
+# define WM_POINTERCAPTURECHANGED 0x024C
+# define WM_TOUCHHITTESTING 0x024D
+# define WM_POINTERWHEEL 0x024E
+# define WM_POINTERHWHEEL 0x024F
+# define DM_POINTERHITTEST 0x0250
+
+typedef UINT32 PEN_FLAGS;
+# define PEN_FLAG_NONE 0x00000000 // Default
+# define PEN_FLAG_BARREL 0x00000001 // The barrel button is pressed
+# define PEN_FLAG_INVERTED 0x00000002 // The pen is inverted
+# define PEN_FLAG_ERASER 0x00000004 // The eraser button is pressed
+
+typedef UINT32 PEN_MASK;
+# define PEN_MASK_NONE \
+ 0x00000000 // Default - none of the optional fields are valid
+# define PEN_MASK_PRESSURE 0x00000001 // The pressure field is valid
+# define PEN_MASK_ROTATION 0x00000002 // The rotation field is valid
+# define PEN_MASK_TILT_X 0x00000004 // The tiltX field is valid
+# define PEN_MASK_TILT_Y 0x00000008 // The tiltY field is valid
+
+typedef struct tagPOINTER_PEN_INFO {
+ POINTER_INFO pointerInfo;
+ PEN_FLAGS penFlags;
+ PEN_MASK penMask;
+ UINT32 pressure;
+ UINT32 rotation;
+ INT32 tiltX;
+ INT32 tiltY;
+} POINTER_PEN_INFO;
+
+/*
+ * Flags that appear in pointer input message parameters
+ */
+# define POINTER_MESSAGE_FLAG_NEW 0x00000001 // New pointer
+# define POINTER_MESSAGE_FLAG_INRANGE 0x00000002 // Pointer has not departed
+# define POINTER_MESSAGE_FLAG_INCONTACT 0x00000004 // Pointer is in contact
+# define POINTER_MESSAGE_FLAG_FIRSTBUTTON 0x00000010 // Primary action
+# define POINTER_MESSAGE_FLAG_SECONDBUTTON 0x00000020 // Secondary action
+# define POINTER_MESSAGE_FLAG_THIRDBUTTON 0x00000040 // Third button
+# define POINTER_MESSAGE_FLAG_FOURTHBUTTON 0x00000080 // Fourth button
+# define POINTER_MESSAGE_FLAG_FIFTHBUTTON 0x00000100 // Fifth button
+# define POINTER_MESSAGE_FLAG_PRIMARY 0x00002000 // Pointer is primary
+# define POINTER_MESSAGE_FLAG_CONFIDENCE \
+ 0x00004000 // Pointer is considered unlikely to be accidental
+# define POINTER_MESSAGE_FLAG_CANCELED \
+ 0x00008000 // Pointer is departing in an abnormal manner
+
+/*
+ * Macros to retrieve information from pointer input message parameters
+ */
+# define GET_POINTERID_WPARAM(wParam) (LOWORD(wParam))
+# define IS_POINTER_FLAG_SET_WPARAM(wParam, flag) \
+ (((DWORD)HIWORD(wParam) & (flag)) == (flag))
+# define IS_POINTER_NEW_WPARAM(wParam) \
+ IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_NEW)
+# define IS_POINTER_INRANGE_WPARAM(wParam) \
+ IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_INRANGE)
+# define IS_POINTER_INCONTACT_WPARAM(wParam) \
+ IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_INCONTACT)
+# define IS_POINTER_FIRSTBUTTON_WPARAM(wParam) \
+ IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIRSTBUTTON)
+# define IS_POINTER_SECONDBUTTON_WPARAM(wParam) \
+ IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_SECONDBUTTON)
+# define IS_POINTER_THIRDBUTTON_WPARAM(wParam) \
+ IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_THIRDBUTTON)
+# define IS_POINTER_FOURTHBUTTON_WPARAM(wParam) \
+ IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FOURTHBUTTON)
+# define IS_POINTER_FIFTHBUTTON_WPARAM(wParam) \
+ IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIFTHBUTTON)
+# define IS_POINTER_PRIMARY_WPARAM(wParam) \
+ IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_PRIMARY)
+# define HAS_POINTER_CONFIDENCE_WPARAM(wParam) \
+ IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_CONFIDENCE)
+# define IS_POINTER_CANCELED_WPARAM(wParam) \
+ IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_CANCELED)
+
+/*
+ * WM_POINTERACTIVATE return codes
+ */
+# define PA_ACTIVATE MA_ACTIVATE
+# define PA_NOACTIVATE MA_NOACTIVATE
+
+#endif // WINVER < 0x0602
+
+/******************************************************************************
+ * WinPointerInfo
+ *
+ * This is a helper class to handle WM_POINTER*. It only supports Win8 or later.
+ *
+ ******************************************************************************/
+class WinPointerInfo final : public mozilla::WidgetPointerHelper {
+ public:
+ WinPointerInfo() : WidgetPointerHelper(), mPressure(0), mButtons(0) {}
+
+ WinPointerInfo(uint32_t aPointerId, uint32_t aTiltX, uint32_t aTiltY,
+ float aPressure, int16_t aButtons)
+ : WidgetPointerHelper(aPointerId, aTiltX, aTiltY),
+ mPressure(aPressure),
+ mButtons(aButtons) {}
+
+ float mPressure;
+ int16_t mButtons;
+};
+
+class WinPointerEvents final {
+ public:
+ explicit WinPointerEvents();
+
+ public:
+ bool ShouldHandleWinPointerMessages(UINT aMsg, WPARAM aWParam);
+
+ uint32_t GetPointerId(WPARAM aWParam) {
+ return GET_POINTERID_WPARAM(aWParam);
+ }
+ bool GetPointerType(uint32_t aPointerId, POINTER_INPUT_TYPE* aPointerType);
+ POINTER_INPUT_TYPE GetPointerType(uint32_t aPointerId);
+ bool GetPointerInfo(uint32_t aPointerId, POINTER_INFO* aPointerInfo);
+ bool GetPointerPenInfo(uint32_t aPointerId, POINTER_PEN_INFO* aPenInfo);
+ bool ShouldEnableInkCollector();
+ bool ShouldRollupOnPointerEvent(UINT aMsg, WPARAM aWParam);
+ bool ShouldFirePointerEventByWinPointerMessages();
+ WinPointerInfo* GetCachedPointerInfo(UINT aMsg, WPARAM aWParam);
+ void ConvertAndCachePointerInfo(UINT aMsg, WPARAM aWParam);
+ void ConvertAndCachePointerInfo(WPARAM aWParam, WinPointerInfo* aInfo);
+
+ private:
+ // Function prototypes
+ typedef BOOL(WINAPI* GetPointerTypePtr)(uint32_t aPointerId,
+ POINTER_INPUT_TYPE* aPointerType);
+ typedef BOOL(WINAPI* GetPointerInfoPtr)(uint32_t aPointerId,
+ POINTER_INFO* aPointerInfo);
+ typedef BOOL(WINAPI* GetPointerPenInfoPtr)(uint32_t aPointerId,
+ POINTER_PEN_INFO* aPenInfo);
+
+ void InitLibrary();
+
+ static HMODULE sLibraryHandle;
+ static const wchar_t kPointerLibraryName[];
+ // Static function pointers
+ static GetPointerTypePtr getPointerType;
+ static GetPointerInfoPtr getPointerInfo;
+ static GetPointerPenInfoPtr getPointerPenInfo;
+ WinPointerInfo mPenPointerDownInfo;
+ WinPointerInfo mPenPointerUpInfo;
+ WinPointerInfo mPenPointerUpdateInfo;
+};
+
+#endif // #ifndef WinPointerEvents_h__
diff --git a/widget/windows/WinTaskbar.cpp b/widget/windows/WinTaskbar.cpp
new file mode 100644
index 0000000000..d751cb2895
--- /dev/null
+++ b/widget/windows/WinTaskbar.cpp
@@ -0,0 +1,437 @@
+/* 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/. */
+
+#include "WinTaskbar.h"
+#include "TaskbarPreview.h"
+#include <nsITaskbarPreviewController.h>
+
+#include "mozilla/RefPtr.h"
+#include <nsError.h>
+#include <nsCOMPtr.h>
+#include <nsIWidget.h>
+#include <nsIBaseWindow.h>
+#include <nsServiceManagerUtils.h>
+#include "nsIXULAppInfo.h"
+#include "nsIJumpListBuilder.h"
+#include "nsUXThemeData.h"
+#include "nsWindow.h"
+#include "WinUtils.h"
+#include "TaskbarTabPreview.h"
+#include "TaskbarWindowPreview.h"
+#include "JumpListBuilder.h"
+#include "nsWidgetsCID.h"
+#include "nsPIDOMWindow.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "mozilla/Preferences.h"
+#include "nsAppRunner.h"
+#include "nsXREDirProvider.h"
+#include <io.h>
+#include <propvarutil.h>
+#include <propkey.h>
+#include <shellapi.h>
+
+static NS_DEFINE_CID(kJumpListBuilderCID, NS_WIN_JUMPLISTBUILDER_CID);
+
+namespace {
+
+HWND GetHWNDFromDocShell(nsIDocShell* aShell) {
+ nsCOMPtr<nsIBaseWindow> baseWindow(
+ do_QueryInterface(reinterpret_cast<nsISupports*>(aShell)));
+
+ if (!baseWindow) return nullptr;
+
+ nsCOMPtr<nsIWidget> widget;
+ baseWindow->GetMainWidget(getter_AddRefs(widget));
+
+ return widget ? (HWND)widget->GetNativeData(NS_NATIVE_WINDOW) : nullptr;
+}
+
+HWND GetHWNDFromDOMWindow(mozIDOMWindow* dw) {
+ nsCOMPtr<nsIWidget> widget;
+
+ if (!dw) return nullptr;
+
+ nsCOMPtr<nsPIDOMWindowInner> window = nsPIDOMWindowInner::From(dw);
+ return GetHWNDFromDocShell(window->GetDocShell());
+}
+
+nsresult SetWindowAppUserModelProp(mozIDOMWindow* aParent,
+ const nsString& aIdentifier) {
+ NS_ENSURE_ARG_POINTER(aParent);
+
+ if (aIdentifier.IsEmpty()) return NS_ERROR_INVALID_ARG;
+
+ HWND toplevelHWND = ::GetAncestor(GetHWNDFromDOMWindow(aParent), GA_ROOT);
+
+ if (!toplevelHWND) return NS_ERROR_INVALID_ARG;
+
+ RefPtr<IPropertyStore> pPropStore;
+ if (FAILED(SHGetPropertyStoreForWindow(toplevelHWND, IID_IPropertyStore,
+ getter_AddRefs(pPropStore)))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PROPVARIANT pv;
+ if (FAILED(InitPropVariantFromString(aIdentifier.get(), &pv))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+ if (FAILED(pPropStore->SetValue(PKEY_AppUserModel_ID, pv)) ||
+ FAILED(pPropStore->Commit())) {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ PropVariantClear(&pv);
+
+ return rv;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// default nsITaskbarPreviewController
+
+class DefaultController final : public nsITaskbarPreviewController {
+ ~DefaultController() {}
+ HWND mWnd;
+
+ public:
+ explicit DefaultController(HWND hWnd) : mWnd(hWnd) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITASKBARPREVIEWCONTROLLER
+};
+
+NS_IMETHODIMP
+DefaultController::GetWidth(uint32_t* aWidth) {
+ RECT r;
+ ::GetClientRect(mWnd, &r);
+ *aWidth = r.right;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::GetHeight(uint32_t* aHeight) {
+ RECT r;
+ ::GetClientRect(mWnd, &r);
+ *aHeight = r.bottom;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::GetThumbnailAspectRatio(float* aThumbnailAspectRatio) {
+ uint32_t width, height;
+ GetWidth(&width);
+ GetHeight(&height);
+ if (!height) height = 1;
+
+ *aThumbnailAspectRatio = width / float(height);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::RequestThumbnail(nsITaskbarPreviewCallback* aCallback,
+ uint32_t width, uint32_t height) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::RequestPreview(nsITaskbarPreviewCallback* aCallback) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::OnClose(void) {
+ MOZ_ASSERT_UNREACHABLE(
+ "OnClose should not be called for "
+ "TaskbarWindowPreviews");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::OnActivate(bool* rAcceptActivation) {
+ *rAcceptActivation = true;
+ MOZ_ASSERT_UNREACHABLE(
+ "OnActivate should not be called for "
+ "TaskbarWindowPreviews");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::OnClick(nsITaskbarPreviewButton* button) { return NS_OK; }
+
+NS_IMPL_ISUPPORTS(DefaultController, nsITaskbarPreviewController)
+} // namespace
+
+namespace mozilla {
+namespace widget {
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIWinTaskbar
+
+NS_IMPL_ISUPPORTS(WinTaskbar, nsIWinTaskbar)
+
+bool WinTaskbar::Initialize() {
+ if (mTaskbar) return true;
+
+ ::CoInitialize(nullptr);
+ HRESULT hr =
+ ::CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ITaskbarList4, (void**)&mTaskbar);
+ if (FAILED(hr)) return false;
+
+ hr = mTaskbar->HrInit();
+ if (FAILED(hr)) {
+ // This may fail with shell extensions like blackbox installed.
+ NS_WARNING("Unable to initialize taskbar");
+ NS_RELEASE(mTaskbar);
+ return false;
+ }
+ return true;
+}
+
+WinTaskbar::WinTaskbar() : mTaskbar(nullptr) {}
+
+WinTaskbar::~WinTaskbar() {
+ if (mTaskbar) { // match successful Initialize() call
+ NS_RELEASE(mTaskbar);
+ ::CoUninitialize();
+ }
+}
+
+// static
+bool WinTaskbar::GetAppUserModelID(nsAString& aDefaultGroupId) {
+ // If an ID has already been set then use that.
+ PWSTR id;
+ if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(&id))) {
+ aDefaultGroupId.Assign(id);
+ CoTaskMemFree(id);
+ }
+
+ // If marked as such in prefs, use a hash of the profile path for the id
+ // instead of the install path hash setup by the installer.
+ bool useProfile = Preferences::GetBool("taskbar.grouping.useprofile", false);
+ if (useProfile) {
+ nsCOMPtr<nsIFile> profileDir;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profileDir));
+ bool exists = false;
+ if (profileDir && NS_SUCCEEDED(profileDir->Exists(&exists)) && exists) {
+ nsAutoCString path;
+ if (NS_SUCCEEDED(profileDir->GetPersistentDescriptor(path))) {
+ nsAutoString id;
+ id.AppendInt(HashString(path));
+ if (!id.IsEmpty()) {
+ aDefaultGroupId.Assign(id);
+ return true;
+ }
+ }
+ }
+ }
+
+ // The default value is set by the installer and is stored in the registry
+ // under (HKLM||HKCU)/Software/Mozilla/Firefox/TaskBarIDs. If for any reason
+ // hash generation operation fails, the installer will not store a value in
+ // the registry or set ids on shortcuts. A lack of an id can also occur for
+ // zipped builds.
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1");
+ nsCString appName;
+ if (appInfo && NS_SUCCEEDED(appInfo->GetName(appName))) {
+ nsAutoString regKey;
+ regKey.AssignLiteral("Software\\Mozilla\\");
+ AppendASCIItoUTF16(appName, regKey);
+ regKey.AppendLiteral("\\TaskBarIDs");
+
+ WCHAR path[MAX_PATH];
+ if (GetModuleFileNameW(nullptr, path, MAX_PATH)) {
+ wchar_t* slash = wcsrchr(path, '\\');
+ if (!slash) return false;
+ *slash = '\0'; // no trailing slash
+
+ // The hash is short, but users may customize this, so use a respectable
+ // string buffer.
+ wchar_t buf[256];
+ if (WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, regKey.get(), path, buf,
+ sizeof buf)) {
+ aDefaultGroupId.Assign(buf);
+ } else if (WinUtils::GetRegistryKey(HKEY_CURRENT_USER, regKey.get(), path,
+ buf, sizeof buf)) {
+ aDefaultGroupId.Assign(buf);
+ }
+ }
+ }
+
+ // If we haven't found an ID yet then use the install hash. In xpcshell tests
+ // the directory provider may not have been initialized so bypass in this
+ // case.
+ if (aDefaultGroupId.IsEmpty() && gDirServiceProvider) {
+ gDirServiceProvider->GetInstallHash(aDefaultGroupId);
+ }
+
+ return !aDefaultGroupId.IsEmpty();
+}
+
+NS_IMETHODIMP
+WinTaskbar::GetDefaultGroupId(nsAString& aDefaultGroupId) {
+ if (!GetAppUserModelID(aDefaultGroupId)) return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+// (static) Called from AppShell
+bool WinTaskbar::RegisterAppUserModelID() {
+ nsAutoString uid;
+ if (!GetAppUserModelID(uid)) return false;
+
+ return SUCCEEDED(SetCurrentProcessExplicitAppUserModelID(uid.get()));
+}
+
+NS_IMETHODIMP
+WinTaskbar::GetAvailable(bool* aAvailable) {
+ // ITaskbarList4::HrInit() may fail with shell extensions like blackbox
+ // installed. Initialize early to return available=false in those cases.
+ *aAvailable = Initialize();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinTaskbar::CreateTaskbarTabPreview(nsIDocShell* shell,
+ nsITaskbarPreviewController* controller,
+ nsITaskbarTabPreview** _retval) {
+ if (!Initialize()) return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ENSURE_ARG_POINTER(shell);
+ NS_ENSURE_ARG_POINTER(controller);
+
+ HWND toplevelHWND = ::GetAncestor(GetHWNDFromDocShell(shell), GA_ROOT);
+
+ if (!toplevelHWND) return NS_ERROR_INVALID_ARG;
+
+ RefPtr<TaskbarTabPreview> preview(
+ new TaskbarTabPreview(mTaskbar, controller, toplevelHWND, shell));
+ if (!preview) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = preview->Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ preview.forget(_retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinTaskbar::GetTaskbarWindowPreview(nsIDocShell* shell,
+ nsITaskbarWindowPreview** _retval) {
+ if (!Initialize()) return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ENSURE_ARG_POINTER(shell);
+
+ HWND toplevelHWND = ::GetAncestor(GetHWNDFromDocShell(shell), GA_ROOT);
+
+ if (!toplevelHWND) return NS_ERROR_INVALID_ARG;
+
+ nsWindow* window = WinUtils::GetNSWindowPtr(toplevelHWND);
+
+ if (!window) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsITaskbarWindowPreview> preview = window->GetTaskbarPreview();
+ if (!preview) {
+ RefPtr<DefaultController> defaultController =
+ new DefaultController(toplevelHWND);
+
+ TaskbarWindowPreview* previewRaw = new TaskbarWindowPreview(
+ mTaskbar, defaultController, toplevelHWND, shell);
+ if (!previewRaw) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ preview = previewRaw;
+
+ nsresult rv = previewRaw->Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ window->SetTaskbarPreview(preview);
+ }
+
+ preview.forget(_retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinTaskbar::GetTaskbarProgress(nsIDocShell* shell,
+ nsITaskbarProgress** _retval) {
+ nsCOMPtr<nsITaskbarWindowPreview> preview;
+ nsresult rv = GetTaskbarWindowPreview(shell, getter_AddRefs(preview));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(preview, _retval);
+}
+
+NS_IMETHODIMP
+WinTaskbar::GetOverlayIconController(
+ nsIDocShell* shell, nsITaskbarOverlayIconController** _retval) {
+ nsCOMPtr<nsITaskbarWindowPreview> preview;
+ nsresult rv = GetTaskbarWindowPreview(shell, getter_AddRefs(preview));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(preview, _retval);
+}
+
+NS_IMETHODIMP
+WinTaskbar::CreateJumpListBuilder(nsIJumpListBuilder** aJumpListBuilder) {
+ nsresult rv;
+
+ if (JumpListBuilder::sBuildingList) return NS_ERROR_ALREADY_INITIALIZED;
+
+ nsCOMPtr<nsIJumpListBuilder> builder =
+ do_CreateInstance(kJumpListBuilderCID, &rv);
+ if (NS_FAILED(rv)) return NS_ERROR_UNEXPECTED;
+
+ NS_IF_ADDREF(*aJumpListBuilder = builder);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinTaskbar::SetGroupIdForWindow(mozIDOMWindow* aParent,
+ const nsAString& aIdentifier) {
+ return SetWindowAppUserModelProp(aParent, nsString(aIdentifier));
+}
+
+NS_IMETHODIMP
+WinTaskbar::PrepareFullScreen(mozIDOMWindow* aWindow, bool aFullScreen) {
+ NS_ENSURE_ARG_POINTER(aWindow);
+
+ HWND toplevelHWND = ::GetAncestor(GetHWNDFromDOMWindow(aWindow), GA_ROOT);
+ if (!toplevelHWND) return NS_ERROR_INVALID_ARG;
+
+ return PrepareFullScreenHWND(toplevelHWND, aFullScreen);
+}
+
+NS_IMETHODIMP
+WinTaskbar::PrepareFullScreenHWND(void* aHWND, bool aFullScreen) {
+ if (!Initialize()) return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ENSURE_ARG_POINTER(aHWND);
+
+ if (!::IsWindow((HWND)aHWND)) return NS_ERROR_INVALID_ARG;
+
+ HRESULT hr = mTaskbar->MarkFullscreenWindow((HWND)aHWND, aFullScreen);
+ if (FAILED(hr)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinTaskbar.h b/widget/windows/WinTaskbar.h
new file mode 100644
index 0000000000..44102f8b39
--- /dev/null
+++ b/widget/windows/WinTaskbar.h
@@ -0,0 +1,45 @@
+/* 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 __WinTaskbar_h__
+#define __WinTaskbar_h__
+
+#include <windows.h>
+#include <shobjidl.h>
+#undef LogSeverity // SetupAPI.h #defines this as DWORD
+#include "nsIWinTaskbar.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace widget {
+
+class WinTaskbar final : public nsIWinTaskbar {
+ ~WinTaskbar();
+
+ public:
+ WinTaskbar();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWINTASKBAR
+
+ // Registers the global app user model id for the instance.
+ // See comments in WinTaskbar.cpp for more information.
+ static bool RegisterAppUserModelID();
+ static bool GetAppUserModelID(nsAString& aDefaultGroupId);
+
+ private:
+ bool Initialize();
+
+ typedef HRESULT(WINAPI* SetCurrentProcessExplicitAppUserModelIDPtr)(
+ PCWSTR AppID);
+ ITaskbarList4* mTaskbar;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __WinTaskbar_h__ */
diff --git a/widget/windows/WinTextEventDispatcherListener.cpp b/widget/windows/WinTextEventDispatcherListener.cpp
new file mode 100644
index 0000000000..aeed8be01b
--- /dev/null
+++ b/widget/windows/WinTextEventDispatcherListener.cpp
@@ -0,0 +1,68 @@
+/* -*- 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 "KeyboardLayout.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/widget/IMEData.h"
+#include "nsWindow.h"
+#include "WinIMEHandler.h"
+#include "WinTextEventDispatcherListener.h"
+
+namespace mozilla {
+namespace widget {
+
+StaticRefPtr<WinTextEventDispatcherListener>
+ WinTextEventDispatcherListener::sInstance;
+
+// static
+WinTextEventDispatcherListener* WinTextEventDispatcherListener::GetInstance() {
+ if (!sInstance) {
+ sInstance = new WinTextEventDispatcherListener();
+ }
+ return sInstance.get();
+}
+
+void WinTextEventDispatcherListener::Shutdown() { sInstance = nullptr; }
+
+NS_IMPL_ISUPPORTS(WinTextEventDispatcherListener, TextEventDispatcherListener,
+ nsISupportsWeakReference)
+
+WinTextEventDispatcherListener::WinTextEventDispatcherListener() {}
+
+WinTextEventDispatcherListener::~WinTextEventDispatcherListener() {}
+
+NS_IMETHODIMP
+WinTextEventDispatcherListener::NotifyIME(
+ TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) {
+ nsWindow* window = static_cast<nsWindow*>(aTextEventDispatcher->GetWidget());
+ if (NS_WARN_IF(!window)) {
+ return NS_ERROR_FAILURE;
+ }
+ return IMEHandler::NotifyIME(window, aNotification);
+}
+
+NS_IMETHODIMP_(IMENotificationRequests)
+WinTextEventDispatcherListener::GetIMENotificationRequests() {
+ return IMEHandler::GetIMENotificationRequests();
+}
+
+NS_IMETHODIMP_(void)
+WinTextEventDispatcherListener::OnRemovedFrom(
+ TextEventDispatcher* aTextEventDispatcher) {
+ // XXX When input transaction is being stolen by add-on, what should we do?
+}
+
+NS_IMETHODIMP_(void)
+WinTextEventDispatcherListener::WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
+ void* aData) {
+ static_cast<NativeKey*>(aData)->WillDispatchKeyboardEvent(aKeyboardEvent,
+ aIndexOfKeypress);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinTextEventDispatcherListener.h b/widget/windows/WinTextEventDispatcherListener.h
new file mode 100644
index 0000000000..d799d0b280
--- /dev/null
+++ b/widget/windows/WinTextEventDispatcherListener.h
@@ -0,0 +1,50 @@
+/* -*- 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 WinTextEventDispatcherListener_h_
+#define WinTextEventDispatcherListener_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TextEventDispatcherListener.h"
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * On Windows, it's enough TextEventDispatcherListener to be a singleton
+ * because we have only one input context per process (IMM can create
+ * multiple IM context but we don't support such behavior).
+ */
+
+class WinTextEventDispatcherListener final
+ : public TextEventDispatcherListener {
+ public:
+ static WinTextEventDispatcherListener* GetInstance();
+ static void Shutdown();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+ NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;
+ NS_IMETHOD_(void)
+ OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override;
+ NS_IMETHOD_(void)
+ WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress, void* aData) override;
+
+ private:
+ WinTextEventDispatcherListener();
+ virtual ~WinTextEventDispatcherListener();
+
+ static StaticRefPtr<WinTextEventDispatcherListener> sInstance;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef WinTextEventDispatcherListener_h_
diff --git a/widget/windows/WinUtils.cpp b/widget/windows/WinUtils.cpp
new file mode 100644
index 0000000000..14706d86a4
--- /dev/null
+++ b/widget/windows/WinUtils.cpp
@@ -0,0 +1,2272 @@
+/* -*- 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 "WinUtils.h"
+
+#include <knownfolders.h>
+#include <winioctl.h>
+
+#include "GeckoProfiler.h"
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "nsWindow.h"
+#include "nsWindowDefs.h"
+#include "InputDeviceUtils.h"
+#include "KeyboardLayout.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/Unused.h"
+#include "nsIContentPolicy.h"
+#include "nsIWindowsUIUtils.h"
+#include "nsContentUtils.h"
+
+#include "mozilla/Logging.h"
+
+#include "nsString.h"
+#include "nsDirectoryServiceUtils.h"
+#include "imgIContainer.h"
+#include "imgITools.h"
+#include "nsNetUtil.h"
+#include "nsIOutputStream.h"
+#include "nsNetCID.h"
+#include "prtime.h"
+#ifdef MOZ_PLACES
+# include "nsIFaviconService.h"
+#endif
+#include "nsIDownloader.h"
+#include "nsIChannel.h"
+#include "nsIThread.h"
+#include "MainThreadUtils.h"
+#include "nsLookAndFeel.h"
+#include "nsUnicharUtils.h"
+#include "nsWindowsHelpers.h"
+#include "WinContentSystemParameters.h"
+
+#include <textstor.h>
+#include "TSFTextStore.h"
+
+#include <shlobj.h>
+#include <shlwapi.h>
+
+mozilla::LazyLogModule gWindowsLog("Widget");
+
+#define LOG_E(...) MOZ_LOG(gWindowsLog, LogLevel::Error, (__VA_ARGS__))
+#define LOG_D(...) MOZ_LOG(gWindowsLog, LogLevel::Debug, (__VA_ARGS__))
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace widget {
+
+#define ENTRY(_msg) \
+ { #_msg, _msg }
+EventMsgInfo gAllEvents[] = {ENTRY(WM_NULL),
+ ENTRY(WM_CREATE),
+ ENTRY(WM_DESTROY),
+ ENTRY(WM_MOVE),
+ ENTRY(WM_SIZE),
+ ENTRY(WM_ACTIVATE),
+ ENTRY(WM_SETFOCUS),
+ ENTRY(WM_KILLFOCUS),
+ ENTRY(WM_ENABLE),
+ ENTRY(WM_SETREDRAW),
+ ENTRY(WM_SETTEXT),
+ ENTRY(WM_GETTEXT),
+ ENTRY(WM_GETTEXTLENGTH),
+ ENTRY(WM_PAINT),
+ ENTRY(WM_CLOSE),
+ ENTRY(WM_QUERYENDSESSION),
+ ENTRY(WM_QUIT),
+ ENTRY(WM_QUERYOPEN),
+ ENTRY(WM_ERASEBKGND),
+ ENTRY(WM_SYSCOLORCHANGE),
+ ENTRY(WM_ENDSESSION),
+ ENTRY(WM_SHOWWINDOW),
+ ENTRY(WM_SETTINGCHANGE),
+ ENTRY(WM_DEVMODECHANGE),
+ ENTRY(WM_ACTIVATEAPP),
+ ENTRY(WM_FONTCHANGE),
+ ENTRY(WM_TIMECHANGE),
+ ENTRY(WM_CANCELMODE),
+ ENTRY(WM_SETCURSOR),
+ ENTRY(WM_MOUSEACTIVATE),
+ ENTRY(WM_CHILDACTIVATE),
+ ENTRY(WM_QUEUESYNC),
+ ENTRY(WM_GETMINMAXINFO),
+ ENTRY(WM_PAINTICON),
+ ENTRY(WM_ICONERASEBKGND),
+ ENTRY(WM_NEXTDLGCTL),
+ ENTRY(WM_SPOOLERSTATUS),
+ ENTRY(WM_DRAWITEM),
+ ENTRY(WM_MEASUREITEM),
+ ENTRY(WM_DELETEITEM),
+ ENTRY(WM_VKEYTOITEM),
+ ENTRY(WM_CHARTOITEM),
+ ENTRY(WM_SETFONT),
+ ENTRY(WM_GETFONT),
+ ENTRY(WM_SETHOTKEY),
+ ENTRY(WM_GETHOTKEY),
+ ENTRY(WM_QUERYDRAGICON),
+ ENTRY(WM_COMPAREITEM),
+ ENTRY(WM_GETOBJECT),
+ ENTRY(WM_COMPACTING),
+ ENTRY(WM_COMMNOTIFY),
+ ENTRY(WM_WINDOWPOSCHANGING),
+ ENTRY(WM_WINDOWPOSCHANGED),
+ ENTRY(WM_POWER),
+ ENTRY(WM_COPYDATA),
+ ENTRY(WM_CANCELJOURNAL),
+ ENTRY(WM_NOTIFY),
+ ENTRY(WM_INPUTLANGCHANGEREQUEST),
+ ENTRY(WM_INPUTLANGCHANGE),
+ ENTRY(WM_TCARD),
+ ENTRY(WM_HELP),
+ ENTRY(WM_USERCHANGED),
+ ENTRY(WM_NOTIFYFORMAT),
+ ENTRY(WM_CONTEXTMENU),
+ ENTRY(WM_STYLECHANGING),
+ ENTRY(WM_STYLECHANGED),
+ ENTRY(WM_DISPLAYCHANGE),
+ ENTRY(WM_GETICON),
+ ENTRY(WM_SETICON),
+ ENTRY(WM_NCCREATE),
+ ENTRY(WM_NCDESTROY),
+ ENTRY(WM_NCCALCSIZE),
+ ENTRY(WM_NCHITTEST),
+ ENTRY(WM_NCPAINT),
+ ENTRY(WM_NCACTIVATE),
+ ENTRY(WM_GETDLGCODE),
+ ENTRY(WM_SYNCPAINT),
+ ENTRY(WM_NCMOUSEMOVE),
+ ENTRY(WM_NCLBUTTONDOWN),
+ ENTRY(WM_NCLBUTTONUP),
+ ENTRY(WM_NCLBUTTONDBLCLK),
+ ENTRY(WM_NCRBUTTONDOWN),
+ ENTRY(WM_NCRBUTTONUP),
+ ENTRY(WM_NCRBUTTONDBLCLK),
+ ENTRY(WM_NCMBUTTONDOWN),
+ ENTRY(WM_NCMBUTTONUP),
+ ENTRY(WM_NCMBUTTONDBLCLK),
+ ENTRY(EM_GETSEL),
+ ENTRY(EM_SETSEL),
+ ENTRY(EM_GETRECT),
+ ENTRY(EM_SETRECT),
+ ENTRY(EM_SETRECTNP),
+ ENTRY(EM_SCROLL),
+ ENTRY(EM_LINESCROLL),
+ ENTRY(EM_SCROLLCARET),
+ ENTRY(EM_GETMODIFY),
+ ENTRY(EM_SETMODIFY),
+ ENTRY(EM_GETLINECOUNT),
+ ENTRY(EM_LINEINDEX),
+ ENTRY(EM_SETHANDLE),
+ ENTRY(EM_GETHANDLE),
+ ENTRY(EM_GETTHUMB),
+ ENTRY(EM_LINELENGTH),
+ ENTRY(EM_REPLACESEL),
+ ENTRY(EM_GETLINE),
+ ENTRY(EM_LIMITTEXT),
+ ENTRY(EM_CANUNDO),
+ ENTRY(EM_UNDO),
+ ENTRY(EM_FMTLINES),
+ ENTRY(EM_LINEFROMCHAR),
+ ENTRY(EM_SETTABSTOPS),
+ ENTRY(EM_SETPASSWORDCHAR),
+ ENTRY(EM_EMPTYUNDOBUFFER),
+ ENTRY(EM_GETFIRSTVISIBLELINE),
+ ENTRY(EM_SETREADONLY),
+ ENTRY(EM_SETWORDBREAKPROC),
+ ENTRY(EM_GETWORDBREAKPROC),
+ ENTRY(EM_GETPASSWORDCHAR),
+ ENTRY(EM_SETMARGINS),
+ ENTRY(EM_GETMARGINS),
+ ENTRY(EM_GETLIMITTEXT),
+ ENTRY(EM_POSFROMCHAR),
+ ENTRY(EM_CHARFROMPOS),
+ ENTRY(EM_SETIMESTATUS),
+ ENTRY(EM_GETIMESTATUS),
+ ENTRY(SBM_SETPOS),
+ ENTRY(SBM_GETPOS),
+ ENTRY(SBM_SETRANGE),
+ ENTRY(SBM_SETRANGEREDRAW),
+ ENTRY(SBM_GETRANGE),
+ ENTRY(SBM_ENABLE_ARROWS),
+ ENTRY(SBM_SETSCROLLINFO),
+ ENTRY(SBM_GETSCROLLINFO),
+ ENTRY(WM_KEYDOWN),
+ ENTRY(WM_KEYUP),
+ ENTRY(WM_CHAR),
+ ENTRY(WM_DEADCHAR),
+ ENTRY(WM_SYSKEYDOWN),
+ ENTRY(WM_SYSKEYUP),
+ ENTRY(WM_SYSCHAR),
+ ENTRY(WM_SYSDEADCHAR),
+ ENTRY(WM_KEYLAST),
+ ENTRY(WM_IME_STARTCOMPOSITION),
+ ENTRY(WM_IME_ENDCOMPOSITION),
+ ENTRY(WM_IME_COMPOSITION),
+ ENTRY(WM_INITDIALOG),
+ ENTRY(WM_COMMAND),
+ ENTRY(WM_SYSCOMMAND),
+ ENTRY(WM_TIMER),
+ ENTRY(WM_HSCROLL),
+ ENTRY(WM_VSCROLL),
+ ENTRY(WM_INITMENU),
+ ENTRY(WM_INITMENUPOPUP),
+ ENTRY(WM_MENUSELECT),
+ ENTRY(WM_MENUCHAR),
+ ENTRY(WM_ENTERIDLE),
+ ENTRY(WM_MENURBUTTONUP),
+ ENTRY(WM_MENUDRAG),
+ ENTRY(WM_MENUGETOBJECT),
+ ENTRY(WM_UNINITMENUPOPUP),
+ ENTRY(WM_MENUCOMMAND),
+ ENTRY(WM_CHANGEUISTATE),
+ ENTRY(WM_UPDATEUISTATE),
+ ENTRY(WM_CTLCOLORMSGBOX),
+ ENTRY(WM_CTLCOLOREDIT),
+ ENTRY(WM_CTLCOLORLISTBOX),
+ ENTRY(WM_CTLCOLORBTN),
+ ENTRY(WM_CTLCOLORDLG),
+ ENTRY(WM_CTLCOLORSCROLLBAR),
+ ENTRY(WM_CTLCOLORSTATIC),
+ ENTRY(CB_GETEDITSEL),
+ ENTRY(CB_LIMITTEXT),
+ ENTRY(CB_SETEDITSEL),
+ ENTRY(CB_ADDSTRING),
+ ENTRY(CB_DELETESTRING),
+ ENTRY(CB_DIR),
+ ENTRY(CB_GETCOUNT),
+ ENTRY(CB_GETCURSEL),
+ ENTRY(CB_GETLBTEXT),
+ ENTRY(CB_GETLBTEXTLEN),
+ ENTRY(CB_INSERTSTRING),
+ ENTRY(CB_RESETCONTENT),
+ ENTRY(CB_FINDSTRING),
+ ENTRY(CB_SELECTSTRING),
+ ENTRY(CB_SETCURSEL),
+ ENTRY(CB_SHOWDROPDOWN),
+ ENTRY(CB_GETITEMDATA),
+ ENTRY(CB_SETITEMDATA),
+ ENTRY(CB_GETDROPPEDCONTROLRECT),
+ ENTRY(CB_SETITEMHEIGHT),
+ ENTRY(CB_GETITEMHEIGHT),
+ ENTRY(CB_SETEXTENDEDUI),
+ ENTRY(CB_GETEXTENDEDUI),
+ ENTRY(CB_GETDROPPEDSTATE),
+ ENTRY(CB_FINDSTRINGEXACT),
+ ENTRY(CB_SETLOCALE),
+ ENTRY(CB_GETLOCALE),
+ ENTRY(CB_GETTOPINDEX),
+ ENTRY(CB_SETTOPINDEX),
+ ENTRY(CB_GETHORIZONTALEXTENT),
+ ENTRY(CB_SETHORIZONTALEXTENT),
+ ENTRY(CB_GETDROPPEDWIDTH),
+ ENTRY(CB_SETDROPPEDWIDTH),
+ ENTRY(CB_INITSTORAGE),
+ ENTRY(CB_MSGMAX),
+ ENTRY(LB_ADDSTRING),
+ ENTRY(LB_INSERTSTRING),
+ ENTRY(LB_DELETESTRING),
+ ENTRY(LB_SELITEMRANGEEX),
+ ENTRY(LB_RESETCONTENT),
+ ENTRY(LB_SETSEL),
+ ENTRY(LB_SETCURSEL),
+ ENTRY(LB_GETSEL),
+ ENTRY(LB_GETCURSEL),
+ ENTRY(LB_GETTEXT),
+ ENTRY(LB_GETTEXTLEN),
+ ENTRY(LB_GETCOUNT),
+ ENTRY(LB_SELECTSTRING),
+ ENTRY(LB_DIR),
+ ENTRY(LB_GETTOPINDEX),
+ ENTRY(LB_FINDSTRING),
+ ENTRY(LB_GETSELCOUNT),
+ ENTRY(LB_GETSELITEMS),
+ ENTRY(LB_SETTABSTOPS),
+ ENTRY(LB_GETHORIZONTALEXTENT),
+ ENTRY(LB_SETHORIZONTALEXTENT),
+ ENTRY(LB_SETCOLUMNWIDTH),
+ ENTRY(LB_ADDFILE),
+ ENTRY(LB_SETTOPINDEX),
+ ENTRY(LB_GETITEMRECT),
+ ENTRY(LB_GETITEMDATA),
+ ENTRY(LB_SETITEMDATA),
+ ENTRY(LB_SELITEMRANGE),
+ ENTRY(LB_SETANCHORINDEX),
+ ENTRY(LB_GETANCHORINDEX),
+ ENTRY(LB_SETCARETINDEX),
+ ENTRY(LB_GETCARETINDEX),
+ ENTRY(LB_SETITEMHEIGHT),
+ ENTRY(LB_GETITEMHEIGHT),
+ ENTRY(LB_FINDSTRINGEXACT),
+ ENTRY(LB_SETLOCALE),
+ ENTRY(LB_GETLOCALE),
+ ENTRY(LB_SETCOUNT),
+ ENTRY(LB_INITSTORAGE),
+ ENTRY(LB_ITEMFROMPOINT),
+ ENTRY(LB_MSGMAX),
+ ENTRY(WM_MOUSEMOVE),
+ ENTRY(WM_LBUTTONDOWN),
+ ENTRY(WM_LBUTTONUP),
+ ENTRY(WM_LBUTTONDBLCLK),
+ ENTRY(WM_RBUTTONDOWN),
+ ENTRY(WM_RBUTTONUP),
+ ENTRY(WM_RBUTTONDBLCLK),
+ ENTRY(WM_MBUTTONDOWN),
+ ENTRY(WM_MBUTTONUP),
+ ENTRY(WM_MBUTTONDBLCLK),
+ ENTRY(WM_MOUSEWHEEL),
+ ENTRY(WM_MOUSEHWHEEL),
+ ENTRY(WM_PARENTNOTIFY),
+ ENTRY(WM_ENTERMENULOOP),
+ ENTRY(WM_EXITMENULOOP),
+ ENTRY(WM_NEXTMENU),
+ ENTRY(WM_SIZING),
+ ENTRY(WM_CAPTURECHANGED),
+ ENTRY(WM_MOVING),
+ ENTRY(WM_POWERBROADCAST),
+ ENTRY(WM_DEVICECHANGE),
+ ENTRY(WM_MDICREATE),
+ ENTRY(WM_MDIDESTROY),
+ ENTRY(WM_MDIACTIVATE),
+ ENTRY(WM_MDIRESTORE),
+ ENTRY(WM_MDINEXT),
+ ENTRY(WM_MDIMAXIMIZE),
+ ENTRY(WM_MDITILE),
+ ENTRY(WM_MDICASCADE),
+ ENTRY(WM_MDIICONARRANGE),
+ ENTRY(WM_MDIGETACTIVE),
+ ENTRY(WM_MDISETMENU),
+ ENTRY(WM_ENTERSIZEMOVE),
+ ENTRY(WM_EXITSIZEMOVE),
+ ENTRY(WM_DROPFILES),
+ ENTRY(WM_MDIREFRESHMENU),
+ ENTRY(WM_IME_SETCONTEXT),
+ ENTRY(WM_IME_NOTIFY),
+ ENTRY(WM_IME_CONTROL),
+ ENTRY(WM_IME_COMPOSITIONFULL),
+ ENTRY(WM_IME_SELECT),
+ ENTRY(WM_IME_CHAR),
+ ENTRY(WM_IME_REQUEST),
+ ENTRY(WM_IME_KEYDOWN),
+ ENTRY(WM_IME_KEYUP),
+ ENTRY(WM_NCMOUSEHOVER),
+ ENTRY(WM_MOUSEHOVER),
+ ENTRY(WM_MOUSELEAVE),
+ ENTRY(WM_CUT),
+ ENTRY(WM_COPY),
+ ENTRY(WM_PASTE),
+ ENTRY(WM_CLEAR),
+ ENTRY(WM_UNDO),
+ ENTRY(WM_RENDERFORMAT),
+ ENTRY(WM_RENDERALLFORMATS),
+ ENTRY(WM_DESTROYCLIPBOARD),
+ ENTRY(WM_DRAWCLIPBOARD),
+ ENTRY(WM_PAINTCLIPBOARD),
+ ENTRY(WM_VSCROLLCLIPBOARD),
+ ENTRY(WM_SIZECLIPBOARD),
+ ENTRY(WM_ASKCBFORMATNAME),
+ ENTRY(WM_CHANGECBCHAIN),
+ ENTRY(WM_HSCROLLCLIPBOARD),
+ ENTRY(WM_QUERYNEWPALETTE),
+ ENTRY(WM_PALETTEISCHANGING),
+ ENTRY(WM_PALETTECHANGED),
+ ENTRY(WM_HOTKEY),
+ ENTRY(WM_PRINT),
+ ENTRY(WM_PRINTCLIENT),
+ ENTRY(WM_THEMECHANGED),
+ ENTRY(WM_HANDHELDFIRST),
+ ENTRY(WM_HANDHELDLAST),
+ ENTRY(WM_AFXFIRST),
+ ENTRY(WM_AFXLAST),
+ ENTRY(WM_PENWINFIRST),
+ ENTRY(WM_PENWINLAST),
+ ENTRY(WM_APP),
+ ENTRY(WM_DWMCOMPOSITIONCHANGED),
+ ENTRY(WM_DWMNCRENDERINGCHANGED),
+ ENTRY(WM_DWMCOLORIZATIONCOLORCHANGED),
+ ENTRY(WM_DWMWINDOWMAXIMIZEDCHANGE),
+ ENTRY(WM_DWMSENDICONICTHUMBNAIL),
+ ENTRY(WM_DWMSENDICONICLIVEPREVIEWBITMAP),
+ ENTRY(WM_TABLET_QUERYSYSTEMGESTURESTATUS),
+ ENTRY(WM_GESTURE),
+ ENTRY(WM_GESTURENOTIFY),
+ ENTRY(WM_GETTITLEBARINFOEX),
+ {nullptr, 0x0}};
+#undef ENTRY
+
+#ifdef MOZ_PLACES
+NS_IMPL_ISUPPORTS(myDownloadObserver, nsIDownloadObserver)
+NS_IMPL_ISUPPORTS(AsyncFaviconDataReady, nsIFaviconDataCallback)
+#endif
+NS_IMPL_ISUPPORTS(AsyncEncodeAndWriteIcon, nsIRunnable)
+NS_IMPL_ISUPPORTS(AsyncDeleteAllFaviconsFromDisk, nsIRunnable)
+
+const char FaviconHelper::kJumpListCacheDir[] = "jumpListCache";
+const char FaviconHelper::kShortcutCacheDir[] = "shortcutCache";
+
+// Prefix for path used by NT calls.
+const wchar_t kNTPrefix[] = L"\\??\\";
+const size_t kNTPrefixLen = ArrayLength(kNTPrefix) - 1;
+
+struct CoTaskMemFreePolicy {
+ void operator()(void* aPtr) { ::CoTaskMemFree(aPtr); }
+};
+
+SetThreadDpiAwarenessContextProc WinUtils::sSetThreadDpiAwarenessContext = NULL;
+EnableNonClientDpiScalingProc WinUtils::sEnableNonClientDpiScaling = NULL;
+GetSystemMetricsForDpiProc WinUtils::sGetSystemMetricsForDpi = NULL;
+
+/* static */
+void WinUtils::Initialize() {
+ if (IsWin10OrLater()) {
+ HMODULE user32Dll = ::GetModuleHandleW(L"user32");
+ if (user32Dll) {
+ auto getThreadDpiAwarenessContext =
+ (decltype(GetThreadDpiAwarenessContext)*)::GetProcAddress(
+ user32Dll, "GetThreadDpiAwarenessContext");
+ auto areDpiAwarenessContextsEqual =
+ (decltype(AreDpiAwarenessContextsEqual)*)::GetProcAddress(
+ user32Dll, "AreDpiAwarenessContextsEqual");
+ if (getThreadDpiAwarenessContext && areDpiAwarenessContextsEqual &&
+ areDpiAwarenessContextsEqual(
+ getThreadDpiAwarenessContext(),
+ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) {
+ // Only per-monitor v1 requires these workarounds.
+ sEnableNonClientDpiScaling =
+ (EnableNonClientDpiScalingProc)::GetProcAddress(
+ user32Dll, "EnableNonClientDpiScaling");
+ sSetThreadDpiAwarenessContext =
+ (SetThreadDpiAwarenessContextProc)::GetProcAddress(
+ user32Dll, "SetThreadDpiAwarenessContext");
+ }
+
+ sGetSystemMetricsForDpi = (GetSystemMetricsForDpiProc)::GetProcAddress(
+ user32Dll, "GetSystemMetricsForDpi");
+ }
+ }
+}
+
+// static
+LRESULT WINAPI WinUtils::NonClientDpiScalingDefWindowProcW(HWND hWnd, UINT msg,
+ WPARAM wParam,
+ LPARAM lParam) {
+ // NOTE: this function was copied out into the body of the pre-XUL skeleton
+ // UI window proc (PreXULSkeletonUI.cpp). If this function changes at any
+ // point, we should probably factor this out and use it from both locations.
+ if (msg == WM_NCCREATE && sEnableNonClientDpiScaling) {
+ sEnableNonClientDpiScaling(hWnd);
+ }
+ return ::DefWindowProcW(hWnd, msg, wParam, lParam);
+}
+
+// static
+void WinUtils::LogW(const wchar_t* fmt, ...) {
+ va_list args = nullptr;
+ if (!lstrlenW(fmt)) {
+ return;
+ }
+ va_start(args, fmt);
+ int buflen = _vscwprintf(fmt, args);
+ wchar_t* buffer = new wchar_t[buflen + 1];
+ if (!buffer) {
+ va_end(args);
+ return;
+ }
+ vswprintf(buffer, buflen, fmt, args);
+ va_end(args);
+
+ // MSVC, including remote debug sessions
+ OutputDebugStringW(buffer);
+ OutputDebugStringW(L"\n");
+
+ int len =
+ WideCharToMultiByte(CP_ACP, 0, buffer, -1, nullptr, 0, nullptr, nullptr);
+ if (len) {
+ char* utf8 = new char[len];
+ if (WideCharToMultiByte(CP_ACP, 0, buffer, -1, utf8, len, nullptr,
+ nullptr) > 0) {
+ // desktop console
+ printf("%s\n", utf8);
+ NS_ASSERTION(gWindowsLog,
+ "Called WinUtils Log() but Widget "
+ "log module doesn't exist!");
+ MOZ_LOG(gWindowsLog, LogLevel::Error, (utf8));
+ }
+ delete[] utf8;
+ }
+ delete[] buffer;
+}
+
+// static
+void WinUtils::Log(const char* fmt, ...) {
+ va_list args = nullptr;
+ if (!strlen(fmt)) {
+ return;
+ }
+ va_start(args, fmt);
+ int buflen = _vscprintf(fmt, args);
+ char* buffer = new char[buflen + 1];
+ if (!buffer) {
+ va_end(args);
+ return;
+ }
+ vsprintf(buffer, fmt, args);
+ va_end(args);
+
+ // MSVC, including remote debug sessions
+ OutputDebugStringA(buffer);
+ OutputDebugStringW(L"\n");
+
+ // desktop console
+ printf("%s\n", buffer);
+
+ NS_ASSERTION(gWindowsLog,
+ "Called WinUtils Log() but Widget "
+ "log module doesn't exist!");
+ MOZ_LOG(gWindowsLog, LogLevel::Error, (buffer));
+ delete[] buffer;
+}
+
+// static
+float WinUtils::SystemDPI() {
+ if (XRE_IsContentProcess()) {
+ return WinContentSystemParameters::GetSingleton()->SystemDPI();
+ }
+ // The result of GetDeviceCaps won't change dynamically, as it predates
+ // per-monitor DPI and support for on-the-fly resolution changes.
+ // Therefore, we only need to look it up once.
+ static float dpi = 0;
+ if (dpi <= 0) {
+ HDC screenDC = GetDC(nullptr);
+ dpi = GetDeviceCaps(screenDC, LOGPIXELSY);
+ ReleaseDC(nullptr, screenDC);
+ }
+
+ // Bug 1012487 - dpi can be 0 when the Screen DC is used off the
+ // main thread on windows. For now just assume a 100% DPI for this
+ // drawing call.
+ // XXX - fixme!
+ return dpi > 0 ? dpi : 96;
+}
+
+// static
+double WinUtils::SystemScaleFactor() { return SystemDPI() / 96.0; }
+
+#if WINVER < 0x603
+typedef enum {
+ MDT_EFFECTIVE_DPI = 0,
+ MDT_ANGULAR_DPI = 1,
+ MDT_RAW_DPI = 2,
+ MDT_DEFAULT = MDT_EFFECTIVE_DPI
+} MONITOR_DPI_TYPE;
+
+typedef enum {
+ PROCESS_DPI_UNAWARE = 0,
+ PROCESS_SYSTEM_DPI_AWARE = 1,
+ PROCESS_PER_MONITOR_DPI_AWARE = 2
+} PROCESS_DPI_AWARENESS;
+#endif
+
+typedef HRESULT(WINAPI* GETDPIFORMONITORPROC)(HMONITOR, MONITOR_DPI_TYPE, UINT*,
+ UINT*);
+
+typedef HRESULT(WINAPI* GETPROCESSDPIAWARENESSPROC)(HANDLE,
+ PROCESS_DPI_AWARENESS*);
+
+GETDPIFORMONITORPROC sGetDpiForMonitor;
+GETPROCESSDPIAWARENESSPROC sGetProcessDpiAwareness;
+
+static bool SlowIsPerMonitorDPIAware() {
+ // Intentionally leak the handle.
+ HMODULE shcore = LoadLibraryEx(L"shcore", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (shcore) {
+ sGetDpiForMonitor =
+ (GETDPIFORMONITORPROC)GetProcAddress(shcore, "GetDpiForMonitor");
+ sGetProcessDpiAwareness = (GETPROCESSDPIAWARENESSPROC)GetProcAddress(
+ shcore, "GetProcessDpiAwareness");
+ }
+ PROCESS_DPI_AWARENESS dpiAwareness;
+ return sGetDpiForMonitor && sGetProcessDpiAwareness &&
+ SUCCEEDED(
+ sGetProcessDpiAwareness(GetCurrentProcess(), &dpiAwareness)) &&
+ dpiAwareness == PROCESS_PER_MONITOR_DPI_AWARE;
+}
+
+/* static */
+bool WinUtils::IsPerMonitorDPIAware() {
+ if (XRE_IsContentProcess()) {
+ return WinContentSystemParameters::GetSingleton()->IsPerMonitorDPIAware();
+ }
+ static bool perMonitorDPIAware = SlowIsPerMonitorDPIAware();
+ return perMonitorDPIAware;
+}
+
+/* static */
+float WinUtils::MonitorDPI(HMONITOR aMonitor) {
+ if (IsPerMonitorDPIAware()) {
+ UINT dpiX, dpiY = 96;
+ sGetDpiForMonitor(aMonitor ? aMonitor : GetPrimaryMonitor(),
+ MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
+ return dpiY;
+ }
+
+ // We're not per-monitor aware, use system DPI instead.
+ return SystemDPI();
+}
+
+/* static */
+double WinUtils::LogToPhysFactor(HMONITOR aMonitor) {
+ return MonitorDPI(aMonitor) / 96.0;
+}
+
+/* static */
+int32_t WinUtils::LogToPhys(HMONITOR aMonitor, double aValue) {
+ return int32_t(NS_round(aValue * LogToPhysFactor(aMonitor)));
+}
+
+/* static */
+HMONITOR
+WinUtils::GetPrimaryMonitor() {
+ const POINT pt = {0, 0};
+ return ::MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY);
+}
+
+/* static */
+HMONITOR
+WinUtils::MonitorFromRect(const gfx::Rect& rect) {
+ // convert coordinates from desktop to device pixels for MonitorFromRect
+ double dpiScale =
+ IsPerMonitorDPIAware() ? 1.0 : LogToPhysFactor(GetPrimaryMonitor());
+
+ RECT globalWindowBounds = {NSToIntRound(dpiScale * rect.X()),
+ NSToIntRound(dpiScale * rect.Y()),
+ NSToIntRound(dpiScale * (rect.XMost())),
+ NSToIntRound(dpiScale * (rect.YMost()))};
+
+ return ::MonitorFromRect(&globalWindowBounds, MONITOR_DEFAULTTONEAREST);
+}
+
+/* static */
+bool WinUtils::HasSystemMetricsForDpi() {
+ return (sGetSystemMetricsForDpi != NULL);
+}
+
+/* static */
+int WinUtils::GetSystemMetricsForDpi(int nIndex, UINT dpi) {
+ if (HasSystemMetricsForDpi()) {
+ return sGetSystemMetricsForDpi(nIndex, dpi);
+ } else {
+ double scale = IsPerMonitorDPIAware() ? dpi / SystemDPI() : 1.0;
+ return NSToIntRound(::GetSystemMetrics(nIndex) * scale);
+ }
+}
+
+/* static */
+gfx::MarginDouble WinUtils::GetUnwriteableMarginsForDeviceInInches(HDC aHdc) {
+ if (!aHdc) {
+ return gfx::MarginDouble();
+ }
+
+ int pixelsPerInchY = ::GetDeviceCaps(aHdc, LOGPIXELSY);
+ int marginTop = ::GetDeviceCaps(aHdc, PHYSICALOFFSETY);
+ int printableAreaHeight = ::GetDeviceCaps(aHdc, VERTRES);
+ int physicalHeight = ::GetDeviceCaps(aHdc, PHYSICALHEIGHT);
+
+ double marginTopInch = double(marginTop) / pixelsPerInchY;
+
+ double printableAreaHeightInch = double(printableAreaHeight) / pixelsPerInchY;
+ double physicalHeightInch = double(physicalHeight) / pixelsPerInchY;
+ double marginBottomInch =
+ physicalHeightInch - printableAreaHeightInch - marginTopInch;
+
+ int pixelsPerInchX = ::GetDeviceCaps(aHdc, LOGPIXELSX);
+ int marginLeft = ::GetDeviceCaps(aHdc, PHYSICALOFFSETX);
+ int printableAreaWidth = ::GetDeviceCaps(aHdc, HORZRES);
+ int physicalWidth = ::GetDeviceCaps(aHdc, PHYSICALWIDTH);
+
+ double marginLeftInch = double(marginLeft) / pixelsPerInchX;
+
+ double printableAreaWidthInch = double(printableAreaWidth) / pixelsPerInchX;
+ double physicalWidthInch = double(physicalWidth) / pixelsPerInchX;
+ double marginRightInch =
+ physicalWidthInch - printableAreaWidthInch - marginLeftInch;
+
+ return gfx::MarginDouble(marginTopInch, marginRightInch, marginBottomInch,
+ marginLeftInch);
+}
+
+#ifdef ACCESSIBILITY
+/* static */
+a11y::Accessible* WinUtils::GetRootAccessibleForHWND(HWND aHwnd) {
+ nsWindow* window = GetNSWindowPtr(aHwnd);
+ if (!window) {
+ return nullptr;
+ }
+
+ return window->GetAccessible();
+}
+#endif // ACCESSIBILITY
+
+/* static */
+bool WinUtils::PeekMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
+ UINT aLastMessage, UINT aOption) {
+ RefPtr<ITfMessagePump> msgPump = TSFTextStore::GetMessagePump();
+ if (msgPump) {
+ BOOL ret = FALSE;
+ HRESULT hr = msgPump->PeekMessageW(aMsg, aWnd, aFirstMessage, aLastMessage,
+ aOption, &ret);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+ return ret;
+ }
+ return ::PeekMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, aOption);
+}
+
+/* static */
+bool WinUtils::GetMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
+ UINT aLastMessage) {
+ RefPtr<ITfMessagePump> msgPump = TSFTextStore::GetMessagePump();
+ if (msgPump) {
+ BOOL ret = FALSE;
+ HRESULT hr =
+ msgPump->GetMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, &ret);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+ return ret;
+ }
+ return ::GetMessageW(aMsg, aWnd, aFirstMessage, aLastMessage);
+}
+
+#if defined(ACCESSIBILITY)
+static DWORD GetWaitFlags() {
+ DWORD result = MWMO_INPUTAVAILABLE;
+ if (XRE_IsContentProcess()) {
+ result |= MWMO_ALERTABLE;
+ }
+ return result;
+}
+#endif
+
+/* static */
+void WinUtils::WaitForMessage(DWORD aTimeoutMs) {
+#if defined(ACCESSIBILITY)
+ static const DWORD waitFlags = GetWaitFlags();
+#else
+ const DWORD waitFlags = MWMO_INPUTAVAILABLE;
+#endif
+
+ const DWORD waitStart = ::GetTickCount();
+ DWORD elapsed = 0;
+ while (true) {
+ if (aTimeoutMs != INFINITE) {
+ elapsed = ::GetTickCount() - waitStart;
+ }
+ if (elapsed >= aTimeoutMs) {
+ break;
+ }
+ DWORD result;
+ {
+ AUTO_PROFILER_THREAD_SLEEP;
+ result = ::MsgWaitForMultipleObjectsEx(0, NULL, aTimeoutMs - elapsed,
+ MOZ_QS_ALLEVENT, waitFlags);
+ }
+ NS_WARNING_ASSERTION(result != WAIT_FAILED, "Wait failed");
+ if (result == WAIT_TIMEOUT) {
+ break;
+ }
+#if defined(ACCESSIBILITY)
+ if (result == WAIT_IO_COMPLETION) {
+ if (NS_IsMainThread()) {
+ // We executed an APC that would have woken up the hang monitor. Since
+ // there are no more APCs pending and we are now going to sleep again,
+ // we should notify the hang monitor.
+ mozilla::BackgroundHangMonitor().NotifyWait();
+ }
+ continue;
+ }
+#endif // defined(ACCESSIBILITY)
+
+ // Sent messages (via SendMessage and friends) are processed differently
+ // than queued messages (via PostMessage); the destination window procedure
+ // of the sent message is called during (Get|Peek)Message. Since PeekMessage
+ // does not tell us whether it processed any sent messages, we need to query
+ // this ahead of time.
+ bool haveSentMessagesPending =
+ (HIWORD(::GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
+
+ MSG msg = {0};
+ if (haveSentMessagesPending ||
+ ::PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE)) {
+ break;
+ }
+ // The message is intended for another thread that has been synchronized
+ // with our input queue; yield to give other threads an opportunity to
+ // process the message. This should prevent busy waiting if resumed due
+ // to another thread's message.
+ ::SwitchToThread();
+ }
+}
+
+/* static */
+bool WinUtils::GetRegistryKey(HKEY aRoot, char16ptr_t aKeyName,
+ char16ptr_t aValueName, wchar_t* aBuffer,
+ DWORD aBufferLength) {
+ MOZ_ASSERT(aKeyName, "The key name is NULL");
+
+ HKEY key;
+ LONG result =
+ ::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_32KEY, &key);
+ if (result != ERROR_SUCCESS) {
+ result =
+ ::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_64KEY, &key);
+ if (result != ERROR_SUCCESS) {
+ return false;
+ }
+ }
+
+ DWORD type;
+ result = ::RegQueryValueExW(key, aValueName, nullptr, &type, (BYTE*)aBuffer,
+ &aBufferLength);
+ ::RegCloseKey(key);
+ if (result != ERROR_SUCCESS || (type != REG_SZ && type != REG_EXPAND_SZ)) {
+ return false;
+ }
+ if (aBuffer) {
+ aBuffer[aBufferLength / sizeof(*aBuffer) - 1] = 0;
+ }
+ return true;
+}
+
+/* static */
+bool WinUtils::HasRegistryKey(HKEY aRoot, char16ptr_t aKeyName) {
+ MOZ_ASSERT(aRoot, "aRoot must not be NULL");
+ MOZ_ASSERT(aKeyName, "aKeyName must not be NULL");
+ HKEY key;
+ LONG result =
+ ::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_32KEY, &key);
+ if (result != ERROR_SUCCESS) {
+ result =
+ ::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_64KEY, &key);
+ if (result != ERROR_SUCCESS) {
+ return false;
+ }
+ }
+ ::RegCloseKey(key);
+ return true;
+}
+
+/* static */
+HWND WinUtils::GetTopLevelHWND(HWND aWnd, bool aStopIfNotChild,
+ bool aStopIfNotPopup) {
+ HWND curWnd = aWnd;
+ HWND topWnd = nullptr;
+
+ while (curWnd) {
+ topWnd = curWnd;
+
+ if (aStopIfNotChild) {
+ DWORD_PTR style = ::GetWindowLongPtrW(curWnd, GWL_STYLE);
+
+ VERIFY_WINDOW_STYLE(style);
+
+ if (!(style & WS_CHILD)) // first top-level window
+ break;
+ }
+
+ HWND upWnd = ::GetParent(curWnd); // Parent or owner (if has no parent)
+
+ // GetParent will only return the owner if the passed in window
+ // has the WS_POPUP style.
+ if (!upWnd && !aStopIfNotPopup) {
+ upWnd = ::GetWindow(curWnd, GW_OWNER);
+ }
+ curWnd = upWnd;
+ }
+
+ return topWnd;
+}
+
+static const wchar_t* GetNSWindowPropName() {
+ static wchar_t sPropName[40] = L"";
+ if (!*sPropName) {
+ _snwprintf(sPropName, 39, L"MozillansIWidgetPtr%u",
+ ::GetCurrentProcessId());
+ sPropName[39] = '\0';
+ }
+ return sPropName;
+}
+
+/* static */
+bool WinUtils::SetNSWindowBasePtr(HWND aWnd, nsWindowBase* aWidget) {
+ if (!aWidget) {
+ ::RemovePropW(aWnd, GetNSWindowPropName());
+ return true;
+ }
+ return ::SetPropW(aWnd, GetNSWindowPropName(), (HANDLE)aWidget);
+}
+
+/* static */
+nsWindowBase* WinUtils::GetNSWindowBasePtr(HWND aWnd) {
+ return static_cast<nsWindowBase*>(::GetPropW(aWnd, GetNSWindowPropName()));
+}
+
+/* static */
+nsWindow* WinUtils::GetNSWindowPtr(HWND aWnd) {
+ return static_cast<nsWindow*>(::GetPropW(aWnd, GetNSWindowPropName()));
+}
+
+static BOOL CALLBACK AddMonitor(HMONITOR, HDC, LPRECT, LPARAM aParam) {
+ (*(int32_t*)aParam)++;
+ return TRUE;
+}
+
+/* static */
+int32_t WinUtils::GetMonitorCount() {
+ int32_t monitorCount = 0;
+ EnumDisplayMonitors(nullptr, nullptr, AddMonitor, (LPARAM)&monitorCount);
+ return monitorCount;
+}
+
+/* static */
+bool WinUtils::IsOurProcessWindow(HWND aWnd) {
+ if (!aWnd) {
+ return false;
+ }
+ DWORD processId = 0;
+ ::GetWindowThreadProcessId(aWnd, &processId);
+ return (processId == ::GetCurrentProcessId());
+}
+
+/* static */
+HWND WinUtils::FindOurProcessWindow(HWND aWnd) {
+ for (HWND wnd = ::GetParent(aWnd); wnd; wnd = ::GetParent(wnd)) {
+ if (IsOurProcessWindow(wnd)) {
+ return wnd;
+ }
+ }
+ return nullptr;
+}
+
+static bool IsPointInWindow(HWND aWnd, const POINT& aPointInScreen) {
+ RECT bounds;
+ if (!::GetWindowRect(aWnd, &bounds)) {
+ return false;
+ }
+
+ return (aPointInScreen.x >= bounds.left && aPointInScreen.x < bounds.right &&
+ aPointInScreen.y >= bounds.top && aPointInScreen.y < bounds.bottom);
+}
+
+/**
+ * FindTopmostWindowAtPoint() returns the topmost child window (topmost means
+ * forground in this context) of aWnd.
+ */
+
+static HWND FindTopmostWindowAtPoint(HWND aWnd, const POINT& aPointInScreen) {
+ if (!::IsWindowVisible(aWnd) || !IsPointInWindow(aWnd, aPointInScreen)) {
+ return nullptr;
+ }
+
+ HWND childWnd = ::GetTopWindow(aWnd);
+ while (childWnd) {
+ HWND topmostWnd = FindTopmostWindowAtPoint(childWnd, aPointInScreen);
+ if (topmostWnd) {
+ return topmostWnd;
+ }
+ childWnd = ::GetNextWindow(childWnd, GW_HWNDNEXT);
+ }
+
+ return aWnd;
+}
+
+struct FindOurWindowAtPointInfo {
+ POINT mInPointInScreen;
+ HWND mOutWnd;
+};
+
+static BOOL CALLBACK FindOurWindowAtPointCallback(HWND aWnd, LPARAM aLPARAM) {
+ if (!WinUtils::IsOurProcessWindow(aWnd)) {
+ // This isn't one of our top-level windows; continue enumerating.
+ return TRUE;
+ }
+
+ // Get the top-most child window under the point. If there's no child
+ // window, and the point is within the top-level window, then the top-level
+ // window will be returned. (This is the usual case. A child window
+ // would be returned for plugins.)
+ FindOurWindowAtPointInfo* info =
+ reinterpret_cast<FindOurWindowAtPointInfo*>(aLPARAM);
+ HWND childWnd = FindTopmostWindowAtPoint(aWnd, info->mInPointInScreen);
+ if (!childWnd) {
+ // This window doesn't contain the point; continue enumerating.
+ return TRUE;
+ }
+
+ // Return the HWND and stop enumerating.
+ info->mOutWnd = childWnd;
+ return FALSE;
+}
+
+/* static */
+HWND WinUtils::FindOurWindowAtPoint(const POINT& aPointInScreen) {
+ FindOurWindowAtPointInfo info;
+ info.mInPointInScreen = aPointInScreen;
+ info.mOutWnd = nullptr;
+
+ // This will enumerate all top-level windows in order from top to bottom.
+ EnumWindows(FindOurWindowAtPointCallback, reinterpret_cast<LPARAM>(&info));
+ return info.mOutWnd;
+}
+
+/* static */
+UINT WinUtils::GetInternalMessage(UINT aNativeMessage) {
+ switch (aNativeMessage) {
+ case WM_MOUSEWHEEL:
+ return MOZ_WM_MOUSEVWHEEL;
+ case WM_MOUSEHWHEEL:
+ return MOZ_WM_MOUSEHWHEEL;
+ case WM_VSCROLL:
+ return MOZ_WM_VSCROLL;
+ case WM_HSCROLL:
+ return MOZ_WM_HSCROLL;
+ default:
+ return aNativeMessage;
+ }
+}
+
+/* static */
+UINT WinUtils::GetNativeMessage(UINT aInternalMessage) {
+ switch (aInternalMessage) {
+ case MOZ_WM_MOUSEVWHEEL:
+ return WM_MOUSEWHEEL;
+ case MOZ_WM_MOUSEHWHEEL:
+ return WM_MOUSEHWHEEL;
+ case MOZ_WM_VSCROLL:
+ return WM_VSCROLL;
+ case MOZ_WM_HSCROLL:
+ return WM_HSCROLL;
+ default:
+ return aInternalMessage;
+ }
+}
+
+/* static */
+uint16_t WinUtils::GetMouseInputSource() {
+ int32_t inputSource = dom::MouseEvent_Binding::MOZ_SOURCE_MOUSE;
+ LPARAM lParamExtraInfo = ::GetMessageExtraInfo();
+ if ((lParamExtraInfo & TABLET_INK_SIGNATURE) == TABLET_INK_CHECK) {
+ inputSource = (lParamExtraInfo & TABLET_INK_TOUCH)
+ ? dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH
+ : dom::MouseEvent_Binding::MOZ_SOURCE_PEN;
+ }
+ return static_cast<uint16_t>(inputSource);
+}
+
+/* static */
+uint16_t WinUtils::GetMousePointerID() {
+ LPARAM lParamExtraInfo = ::GetMessageExtraInfo();
+ return lParamExtraInfo & TABLET_INK_ID_MASK;
+}
+
+/* static */
+bool WinUtils::GetIsMouseFromTouch(EventMessage aEventMessage) {
+ const uint32_t MOZ_T_I_SIGNATURE = TABLET_INK_TOUCH | TABLET_INK_SIGNATURE;
+ const uint32_t MOZ_T_I_CHECK_TCH = TABLET_INK_TOUCH | TABLET_INK_CHECK;
+ return ((aEventMessage == eMouseMove || aEventMessage == eMouseDown ||
+ aEventMessage == eMouseUp || aEventMessage == eMouseAuxClick ||
+ aEventMessage == eMouseDoubleClick) &&
+ (GetMessageExtraInfo() & MOZ_T_I_SIGNATURE) == MOZ_T_I_CHECK_TCH);
+}
+
+/* static */
+MSG WinUtils::InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam, HWND aWnd) {
+ MSG msg;
+ msg.message = aMessage;
+ msg.wParam = wParam;
+ msg.lParam = lParam;
+ msg.hwnd = aWnd;
+ return msg;
+}
+
+static BOOL WINAPI EnumFirstChild(HWND hwnd, LPARAM lParam) {
+ *((HWND*)lParam) = hwnd;
+ return FALSE;
+}
+
+/* static */
+void WinUtils::InvalidatePluginAsWorkaround(nsIWidget* aWidget,
+ const LayoutDeviceIntRect& aRect) {
+ aWidget->Invalidate(aRect);
+
+ // XXX - Even more evil workaround!! See bug 762948, flash's bottom
+ // level sandboxed window doesn't seem to get our invalidate. We send
+ // an invalidate to it manually. This is totally specialized for this
+ // bug, for other child window structures this will just be a more or
+ // less bogus invalidate but since that should not have any bad
+ // side-effects this will have to do for now.
+ HWND current = (HWND)aWidget->GetNativeData(NS_NATIVE_WINDOW);
+
+ RECT windowRect;
+ RECT parentRect;
+
+ ::GetWindowRect(current, &parentRect);
+
+ HWND next = current;
+ do {
+ current = next;
+ ::EnumChildWindows(current, &EnumFirstChild, (LPARAM)&next);
+ ::GetWindowRect(next, &windowRect);
+ // This is relative to the screen, adjust it to be relative to the
+ // window we're reconfiguring.
+ windowRect.left -= parentRect.left;
+ windowRect.top -= parentRect.top;
+ } while (next != current && windowRect.top == 0 && windowRect.left == 0);
+
+ if (windowRect.top == 0 && windowRect.left == 0) {
+ RECT rect;
+ rect.left = aRect.X();
+ rect.top = aRect.Y();
+ rect.right = aRect.XMost();
+ rect.bottom = aRect.YMost();
+
+ ::InvalidateRect(next, &rect, FALSE);
+ }
+}
+
+#ifdef MOZ_PLACES
+/************************************************************************
+ * Constructs as AsyncFaviconDataReady Object
+ * @param aIOThread : the thread which performs the action
+ * @param aURLShortcut : Differentiates between (false)Jumplistcache and
+ * (true)Shortcutcache
+ * @param aRunnable : Executed in the aIOThread when the favicon cache is
+ * avaiable
+ ************************************************************************/
+
+AsyncFaviconDataReady::AsyncFaviconDataReady(
+ nsIURI* aNewURI, nsCOMPtr<nsIThread>& aIOThread, const bool aURLShortcut,
+ already_AddRefed<nsIRunnable> aRunnable)
+ : mNewURI(aNewURI),
+ mIOThread(aIOThread),
+ mRunnable(aRunnable),
+ mURLShortcut(aURLShortcut) {}
+
+NS_IMETHODIMP
+myDownloadObserver::OnDownloadComplete(nsIDownloader* downloader,
+ nsIRequest* request, nsISupports* ctxt,
+ nsresult status, nsIFile* result) {
+ return NS_OK;
+}
+
+nsresult AsyncFaviconDataReady::OnFaviconDataNotAvailable(void) {
+ if (!mURLShortcut) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> icoFile;
+ nsresult rv =
+ FaviconHelper::GetOutputIconPath(mNewURI, icoFile, mURLShortcut);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> mozIconURI;
+ rv = NS_NewURI(getter_AddRefs(mozIconURI), "moz-icon://.html?size=32");
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), mozIconURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDownloadObserver> downloadObserver = new myDownloadObserver;
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = NS_NewDownloader(getter_AddRefs(listener), downloadObserver, icoFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return channel->AsyncOpen(listener);
+}
+
+NS_IMETHODIMP
+AsyncFaviconDataReady::OnComplete(nsIURI* aFaviconURI, uint32_t aDataLen,
+ const uint8_t* aData,
+ const nsACString& aMimeType,
+ uint16_t aWidth) {
+ if (!aDataLen || !aData) {
+ if (mURLShortcut) {
+ OnFaviconDataNotAvailable();
+ }
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> icoFile;
+ nsresult rv =
+ FaviconHelper::GetOutputIconPath(mNewURI, icoFile, mURLShortcut);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString path;
+ rv = icoFile->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Decode the image from the format it was returned to us in (probably PNG)
+ nsCOMPtr<imgIContainer> container;
+ nsCOMPtr<imgITools> imgtool = do_CreateInstance("@mozilla.org/image/tools;1");
+ rv = imgtool->DecodeImageFromBuffer(reinterpret_cast<const char*>(aData),
+ aDataLen, aMimeType,
+ getter_AddRefs(container));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<SourceSurface> surface = container->GetFrame(
+ imgIContainer::FRAME_FIRST,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
+
+ RefPtr<DataSourceSurface> dataSurface;
+ IntSize size;
+
+ if (mURLShortcut &&
+ (surface->GetSize().width < 48 || surface->GetSize().height < 48)) {
+ // Create a 48x48 surface and paint the icon into the central rect.
+ size.width = std::max(surface->GetSize().width, 48);
+ size.height = std::max(surface->GetSize().height, 48);
+ dataSurface =
+ Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
+ BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride,
+ dataSurface->GetFormat());
+ if (!dt) {
+ gfxWarning() << "AsyncFaviconDataReady::OnComplete failed in "
+ "CreateDrawTargetForData";
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ dt->FillRect(Rect(0, 0, size.width, size.height),
+ ColorPattern(ToDeviceColor(sRGBColor::OpaqueWhite())));
+ IntPoint point;
+ point.x = (size.width - surface->GetSize().width) / 2;
+ point.y = (size.height - surface->GetSize().height) / 2;
+ dt->DrawSurface(surface,
+ Rect(point.x, point.y, surface->GetSize().width,
+ surface->GetSize().height),
+ Rect(Point(0, 0), Size(surface->GetSize().width,
+ surface->GetSize().height)));
+
+ dataSurface->Unmap();
+ } else {
+ // By using the input image surface's size, we may end up encoding
+ // to a different size than a 16x16 (or bigger for higher DPI) ICO, but
+ // Windows will resize appropriately for us. If we want to encode ourselves
+ // one day because we like our resizing better, we'd have to manually
+ // resize the image here and use GetSystemMetrics w/ SM_CXSMICON and
+ // SM_CYSMICON. We don't support resizing images asynchronously at the
+ // moment anyway so getting the DPI aware icon size won't help.
+ size.width = surface->GetSize().width;
+ size.height = surface->GetSize().height;
+ dataSurface = surface->GetDataSurface();
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+ }
+
+ // Allocate a new buffer that we own and can use out of line in
+ // another thread.
+ UniquePtr<uint8_t[]> data = SurfaceToPackedBGRA(dataSurface);
+ if (!data) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ int32_t stride = 4 * size.width;
+
+ // AsyncEncodeAndWriteIcon takes ownership of the heap allocated buffer
+ nsCOMPtr<nsIRunnable> event =
+ new AsyncEncodeAndWriteIcon(path, std::move(data), stride, size.width,
+ size.height, mRunnable.forget());
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+#endif
+
+// Warning: AsyncEncodeAndWriteIcon assumes ownership of the aData buffer passed
+// in
+AsyncEncodeAndWriteIcon::AsyncEncodeAndWriteIcon(
+ const nsAString& aIconPath, UniquePtr<uint8_t[]> aBuffer, uint32_t aStride,
+ uint32_t aWidth, uint32_t aHeight, already_AddRefed<nsIRunnable> aRunnable)
+ : mIconPath(aIconPath),
+ mBuffer(std::move(aBuffer)),
+ mRunnable(aRunnable),
+ mStride(aStride),
+ mWidth(aWidth),
+ mHeight(aHeight) {}
+
+NS_IMETHODIMP AsyncEncodeAndWriteIcon::Run() {
+ MOZ_ASSERT(!NS_IsMainThread(), "Should not be called on the main thread.");
+
+ // Note that since we're off the main thread we can't use
+ // gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()
+ RefPtr<DataSourceSurface> surface = Factory::CreateWrappingDataSourceSurface(
+ mBuffer.get(), mStride, IntSize(mWidth, mHeight),
+ SurfaceFormat::B8G8R8A8);
+
+ FILE* file = _wfopen(mIconPath.get(), L"wb");
+ if (!file) {
+ // Maybe the directory doesn't exist; try creating it, then fopen again.
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCOMPtr<nsIFile> comFile = do_CreateInstance("@mozilla.org/file/local;1");
+ if (comFile) {
+ rv = comFile->InitWithPath(mIconPath);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> dirPath;
+ comFile->GetParent(getter_AddRefs(dirPath));
+ if (dirPath) {
+ rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777);
+ if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) {
+ file = _wfopen(mIconPath.get(), L"wb");
+ if (!file) {
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+ }
+ if (!file) {
+ return rv;
+ }
+ }
+ nsresult rv = gfxUtils::EncodeSourceSurface(surface, ImageType::ICO, u""_ns,
+ gfxUtils::eBinaryEncode, file);
+ fclose(file);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mRunnable) {
+ mRunnable->Run();
+ }
+ return rv;
+}
+
+AsyncEncodeAndWriteIcon::~AsyncEncodeAndWriteIcon() {}
+
+AsyncDeleteAllFaviconsFromDisk::AsyncDeleteAllFaviconsFromDisk(
+ bool aIgnoreRecent)
+ : mIgnoreRecent(aIgnoreRecent) {
+ // We can't call FaviconHelper::GetICOCacheSecondsTimeout() on non-main
+ // threads, as it reads a pref, so cache its value here.
+ mIcoNoDeleteSeconds = FaviconHelper::GetICOCacheSecondsTimeout() + 600;
+
+ // Prepare the profile directory cache on the main thread, to ensure we wont
+ // do this on non-main threads.
+ Unused << NS_GetSpecialDirectory("ProfLDS",
+ getter_AddRefs(mJumpListCacheDir));
+}
+
+NS_IMETHODIMP AsyncDeleteAllFaviconsFromDisk::Run() {
+ if (!mJumpListCacheDir) {
+ return NS_ERROR_FAILURE;
+ }
+ // Construct the path of our jump list cache
+ nsresult rv = mJumpListCacheDir->AppendNative(
+ nsDependentCString(FaviconHelper::kJumpListCacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDirectoryEnumerator> entries;
+ rv = mJumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Loop through each directory entry and remove all ICO files found
+ do {
+ nsCOMPtr<nsIFile> currFile;
+ if (NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile))) || !currFile)
+ break;
+
+ nsAutoString path;
+ if (NS_FAILED(currFile->GetPath(path))) continue;
+
+ if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) {
+ // Check if the cached ICO file exists
+ bool exists;
+ if (NS_FAILED(currFile->Exists(&exists)) || !exists) continue;
+
+ if (mIgnoreRecent) {
+ // Check to make sure the icon wasn't just recently created.
+ // If it was created recently, don't delete it yet.
+ int64_t fileModTime = 0;
+ rv = currFile->GetLastModifiedTime(&fileModTime);
+ fileModTime /= PR_MSEC_PER_SEC;
+ // If the icon is older than the regeneration time (+ 10 min to be
+ // safe), then it's old and we can get rid of it.
+ // This code is only hit directly after a regeneration.
+ int64_t nowTime = PR_Now() / int64_t(PR_USEC_PER_SEC);
+ if (NS_FAILED(rv) || (nowTime - fileModTime) < mIcoNoDeleteSeconds) {
+ continue;
+ }
+ }
+
+ // We found an ICO file that exists, so we should remove it
+ currFile->Remove(false);
+ }
+ } while (true);
+
+ return NS_OK;
+}
+
+AsyncDeleteAllFaviconsFromDisk::~AsyncDeleteAllFaviconsFromDisk() {}
+
+/*
+ * (static) If the data is available, will return the path on disk where
+ * the favicon for page aFaviconPageURI is stored. If the favicon does not
+ * exist, or its cache is expired, this method will kick off an async request
+ * for the icon so that next time the method is called it will be available.
+ * @param aFaviconPageURI The URI of the page to obtain
+ * @param aICOFilePath The path of the icon file
+ * @param aIOThread The thread to perform the Fetch on
+ * @param aURLShortcut to distinguish between jumplistcache(false) and
+ * shortcutcache(true)
+ * @param aRunnable Executed in the aIOThread when the favicon cache is
+ * avaiable
+ */
+nsresult FaviconHelper::ObtainCachedIconFile(
+ nsCOMPtr<nsIURI> aFaviconPageURI, nsString& aICOFilePath,
+ nsCOMPtr<nsIThread>& aIOThread, bool aURLShortcut,
+ already_AddRefed<nsIRunnable> aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable = aRunnable;
+ // Obtain the ICO file path
+ nsCOMPtr<nsIFile> icoFile;
+ nsresult rv = GetOutputIconPath(aFaviconPageURI, icoFile, aURLShortcut);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if the cached ICO file already exists
+ bool exists;
+ rv = icoFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ // Obtain the file's last modification date in seconds
+ int64_t fileModTime = 0;
+ rv = icoFile->GetLastModifiedTime(&fileModTime);
+ fileModTime /= PR_MSEC_PER_SEC;
+ int32_t icoReCacheSecondsTimeout = GetICOCacheSecondsTimeout();
+ int64_t nowTime = PR_Now() / int64_t(PR_USEC_PER_SEC);
+
+ // If the last mod call failed or the icon is old then re-cache it
+ // This check is in case the favicon of a page changes
+ // the next time we try to build the jump list, the data will be available.
+ if (NS_FAILED(rv) || (nowTime - fileModTime) > icoReCacheSecondsTimeout) {
+ CacheIconFileFromFaviconURIAsync(aFaviconPageURI, icoFile, aIOThread,
+ aURLShortcut, runnable.forget());
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ } else {
+ // The file does not exist yet, obtain it async from the favicon service so
+ // that the next time we try to build the jump list it'll be available.
+ CacheIconFileFromFaviconURIAsync(aFaviconPageURI, icoFile, aIOThread,
+ aURLShortcut, runnable.forget());
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // The icoFile is filled with a path that exists, get its path
+ rv = icoFile->GetPath(aICOFilePath);
+ return rv;
+}
+
+nsresult FaviconHelper::HashURI(nsCOMPtr<nsICryptoHash>& aCryptoHash,
+ nsIURI* aUri, nsACString& aUriHash) {
+ if (!aUri) return NS_ERROR_INVALID_ARG;
+
+ nsAutoCString spec;
+ nsresult rv = aUri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aCryptoHash) {
+ aCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = aCryptoHash->Init(nsICryptoHash::MD5);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aCryptoHash->Update(
+ reinterpret_cast<const uint8_t*>(spec.BeginReading()), spec.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aCryptoHash->Finish(true, aUriHash);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+// (static) Obtains the ICO file for the favicon at page aFaviconPageURI
+// If successful, the file path on disk is in the format:
+// <ProfLDS>\jumpListCache\<hash(aFaviconPageURI)>.ico
+nsresult FaviconHelper::GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsCOMPtr<nsIFile>& aICOFile,
+ bool aURLShortcut) {
+ // Hash the input URI and replace any / with _
+ nsAutoCString inputURIHash;
+ nsCOMPtr<nsICryptoHash> cryptoHash;
+ nsresult rv = HashURI(cryptoHash, aFaviconPageURI, inputURIHash);
+ NS_ENSURE_SUCCESS(rv, rv);
+ char* cur = inputURIHash.BeginWriting();
+ char* end = inputURIHash.EndWriting();
+ for (; cur < end; ++cur) {
+ if ('/' == *cur) {
+ *cur = '_';
+ }
+ }
+
+ // Obtain the local profile directory and construct the output icon file path
+ rv = NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(aICOFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!aURLShortcut)
+ rv = aICOFile->AppendNative(nsDependentCString(kJumpListCacheDir));
+ else
+ rv = aICOFile->AppendNative(nsDependentCString(kShortcutCacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Append the icon extension
+ inputURIHash.AppendLiteral(".ico");
+ rv = aICOFile->AppendNative(inputURIHash);
+
+ return rv;
+}
+
+// (static) Asynchronously creates a cached ICO file on disk for the favicon of
+// page aFaviconPageURI and stores it to disk at the path of aICOFile.
+nsresult FaviconHelper::CacheIconFileFromFaviconURIAsync(
+ nsCOMPtr<nsIURI> aFaviconPageURI, nsCOMPtr<nsIFile> aICOFile,
+ nsCOMPtr<nsIThread>& aIOThread, bool aURLShortcut,
+ already_AddRefed<nsIRunnable> aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable = aRunnable;
+#ifdef MOZ_PLACES
+ // Obtain the favicon service and get the favicon for the specified page
+ nsCOMPtr<nsIFaviconService> favIconSvc(
+ do_GetService("@mozilla.org/browser/favicon-service;1"));
+ NS_ENSURE_TRUE(favIconSvc, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIFaviconDataCallback> callback =
+ new mozilla::widget::AsyncFaviconDataReady(
+ aFaviconPageURI, aIOThread, aURLShortcut, runnable.forget());
+
+ favIconSvc->GetFaviconDataForPage(aFaviconPageURI, callback, 0);
+#endif
+ return NS_OK;
+}
+
+// Obtains the jump list 'ICO cache timeout in seconds' pref
+int32_t FaviconHelper::GetICOCacheSecondsTimeout() {
+ // Only obtain the setting at most once from the pref service.
+ // In the rare case that 2 threads call this at the same
+ // time it is no harm and we will simply obtain the pref twice.
+ // None of the taskbar list prefs are currently updated via a
+ // pref observer so I think this should suffice.
+ const int32_t kSecondsPerDay = 86400;
+ static bool alreadyObtained = false;
+ static int32_t icoReCacheSecondsTimeout = kSecondsPerDay;
+ if (alreadyObtained) {
+ return icoReCacheSecondsTimeout;
+ }
+
+ // Obtain the pref
+ const char PREF_ICOTIMEOUT[] = "browser.taskbar.lists.icoTimeoutInSeconds";
+ icoReCacheSecondsTimeout =
+ Preferences::GetInt(PREF_ICOTIMEOUT, kSecondsPerDay);
+ alreadyObtained = true;
+ return icoReCacheSecondsTimeout;
+}
+
+/* static */
+bool WinUtils::GetShellItemPath(IShellItem* aItem, nsString& aResultString) {
+ NS_ENSURE_TRUE(aItem, false);
+ LPWSTR str = nullptr;
+ if (FAILED(aItem->GetDisplayName(SIGDN_FILESYSPATH, &str))) return false;
+ aResultString.Assign(str);
+ CoTaskMemFree(str);
+ return !aResultString.IsEmpty();
+}
+
+/* static */
+LayoutDeviceIntRegion WinUtils::ConvertHRGNToRegion(HRGN aRgn) {
+ NS_ASSERTION(aRgn, "Don't pass NULL region here");
+
+ LayoutDeviceIntRegion rgn;
+
+ DWORD size = ::GetRegionData(aRgn, 0, nullptr);
+ AutoTArray<uint8_t, 100> buffer;
+ buffer.SetLength(size);
+
+ RGNDATA* data = reinterpret_cast<RGNDATA*>(buffer.Elements());
+ if (!::GetRegionData(aRgn, size, data)) return rgn;
+
+ if (data->rdh.nCount > MAX_RECTS_IN_REGION) {
+ rgn = ToIntRect(data->rdh.rcBound);
+ return rgn;
+ }
+
+ RECT* rects = reinterpret_cast<RECT*>(data->Buffer);
+ for (uint32_t i = 0; i < data->rdh.nCount; ++i) {
+ RECT* r = rects + i;
+ rgn.Or(rgn, ToIntRect(*r));
+ }
+
+ return rgn;
+}
+
+LayoutDeviceIntRect WinUtils::ToIntRect(const RECT& aRect) {
+ return LayoutDeviceIntRect(aRect.left, aRect.top, aRect.right - aRect.left,
+ aRect.bottom - aRect.top);
+}
+
+/* static */
+bool WinUtils::IsIMEEnabled(const InputContext& aInputContext) {
+ return IsIMEEnabled(aInputContext.mIMEState.mEnabled);
+}
+
+/* static */
+bool WinUtils::IsIMEEnabled(IMEEnabled aIMEState) {
+ return aIMEState == IMEEnabled::Enabled;
+}
+
+/* static */
+void WinUtils::SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray,
+ uint32_t aModifiers, UINT aMessage) {
+ MOZ_ASSERT(!(aModifiers & nsIWidget::ALTGRAPH) ||
+ !(aModifiers & (nsIWidget::CTRL_L | nsIWidget::ALT_R)));
+ if (aMessage == WM_KEYUP) {
+ // If AltGr is released, ControlLeft key is released first, then,
+ // AltRight key is released.
+ if (aModifiers & nsIWidget::ALTGRAPH) {
+ aArray->AppendElement(
+ KeyPair(VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft));
+ aArray->AppendElement(KeyPair(VK_MENU, VK_RMENU, ScanCode::eAltRight));
+ }
+ for (uint32_t i = ArrayLength(sModifierKeyMap); i; --i) {
+ const uint32_t* map = sModifierKeyMap[i - 1];
+ if (aModifiers & map[0]) {
+ aArray->AppendElement(KeyPair(map[1], map[2], map[3]));
+ }
+ }
+ } else {
+ for (uint32_t i = 0; i < ArrayLength(sModifierKeyMap); ++i) {
+ const uint32_t* map = sModifierKeyMap[i];
+ if (aModifiers & map[0]) {
+ aArray->AppendElement(KeyPair(map[1], map[2], map[3]));
+ }
+ }
+ // If AltGr is pressed, ControlLeft key is pressed first, then,
+ // AltRight key is pressed.
+ if (aModifiers & nsIWidget::ALTGRAPH) {
+ aArray->AppendElement(
+ KeyPair(VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft));
+ aArray->AppendElement(KeyPair(VK_MENU, VK_RMENU, ScanCode::eAltRight));
+ }
+ }
+}
+
+/* static */
+nsresult WinUtils::WriteBitmap(nsIFile* aFile, imgIContainer* aImage) {
+ RefPtr<SourceSurface> surface = aImage->GetFrame(
+ imgIContainer::FRAME_FIRST,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
+
+ return WriteBitmap(aFile, surface);
+}
+
+/* static */
+nsresult WinUtils::WriteBitmap(nsIFile* aFile, SourceSurface* surface) {
+ nsresult rv;
+
+ // For either of the following formats we want to set the biBitCount member
+ // of the BITMAPINFOHEADER struct to 32, below. For that value the bitmap
+ // format defines that the A8/X8 WORDs in the bitmap byte stream be ignored
+ // for the BI_RGB value we use for the biCompression member.
+ MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+ surface->GetFormat() == SurfaceFormat::B8G8R8X8);
+
+ RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ int32_t width = dataSurface->GetSize().width;
+ int32_t height = dataSurface->GetSize().height;
+ int32_t bytesPerPixel = 4 * sizeof(uint8_t);
+ uint32_t bytesPerRow = bytesPerPixel * width;
+ bool hasAlpha = surface->GetFormat() == SurfaceFormat::B8G8R8A8;
+
+ // initialize these bitmap structs which we will later
+ // serialize directly to the head of the bitmap file
+ BITMAPV4HEADER bmi;
+ memset(&bmi, 0, sizeof(BITMAPV4HEADER));
+ bmi.bV4Size = sizeof(BITMAPV4HEADER);
+ bmi.bV4Width = width;
+ bmi.bV4Height = height;
+ bmi.bV4Planes = 1;
+ bmi.bV4BitCount = (WORD)bytesPerPixel * 8;
+ bmi.bV4V4Compression = hasAlpha ? BI_BITFIELDS : BI_RGB;
+ bmi.bV4SizeImage = bytesPerRow * height;
+ bmi.bV4CSType = LCS_sRGB;
+ if (hasAlpha) {
+ bmi.bV4RedMask = 0x00FF0000;
+ bmi.bV4GreenMask = 0x0000FF00;
+ bmi.bV4BlueMask = 0x000000FF;
+ bmi.bV4AlphaMask = 0xFF000000;
+ }
+
+ BITMAPFILEHEADER bf;
+ DWORD colormask[3];
+ bf.bfType = 0x4D42; // 'BM'
+ bf.bfReserved1 = 0;
+ bf.bfReserved2 = 0;
+ bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPV4HEADER) +
+ (hasAlpha ? sizeof(colormask) : 0);
+ bf.bfSize = bf.bfOffBits + bmi.bV4SizeImage;
+
+ // get a file output stream
+ nsCOMPtr<nsIOutputStream> stream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // write the bitmap headers and rgb pixel data to the file
+ rv = NS_ERROR_FAILURE;
+ if (stream) {
+ uint32_t written;
+ stream->Write((const char*)&bf, sizeof(BITMAPFILEHEADER), &written);
+ if (written == sizeof(BITMAPFILEHEADER)) {
+ stream->Write((const char*)&bmi, sizeof(BITMAPV4HEADER), &written);
+ if (written == sizeof(BITMAPV4HEADER)) {
+ if (hasAlpha) {
+ // color mask
+ colormask[0] = 0x00FF0000;
+ colormask[1] = 0x0000FF00;
+ colormask[2] = 0x000000FF;
+
+ stream->Write((const char*)colormask, sizeof(colormask), &written);
+ }
+ if (!hasAlpha || written == sizeof(colormask)) {
+ // write out the image data backwards because the desktop won't
+ // show bitmaps with negative heights for top-to-bottom
+ uint32_t i = map.mStride * height;
+ do {
+ i -= map.mStride;
+ stream->Write(((const char*)map.mData) + i, bytesPerRow, &written);
+ if (written == bytesPerRow) {
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ } while (i != 0);
+ }
+ }
+ }
+
+ stream->Close();
+ }
+
+ dataSurface->Unmap();
+
+ return rv;
+}
+
+// This is in use here and in dom/events/TouchEvent.cpp
+/* static */
+uint32_t WinUtils::IsTouchDeviceSupportPresent() {
+ int32_t touchCapabilities = ::GetSystemMetrics(SM_DIGITIZER);
+ return (touchCapabilities & NID_READY) &&
+ (touchCapabilities & (NID_EXTERNAL_TOUCH | NID_INTEGRATED_TOUCH));
+}
+
+/* static */
+uint32_t WinUtils::GetMaxTouchPoints() {
+ if (IsTouchDeviceSupportPresent()) {
+ return GetSystemMetrics(SM_MAXIMUMTOUCHES);
+ }
+ return 0;
+}
+
+/* static */
+POWER_PLATFORM_ROLE
+WinUtils::GetPowerPlatformRole() {
+ typedef POWER_PLATFORM_ROLE(WINAPI *
+ PowerDeterminePlatformRoleEx)(ULONG Version);
+ static PowerDeterminePlatformRoleEx power_determine_platform_role =
+ reinterpret_cast<PowerDeterminePlatformRoleEx>(::GetProcAddress(
+ ::LoadLibraryW(L"PowrProf.dll"), "PowerDeterminePlatformRoleEx"));
+
+ POWER_PLATFORM_ROLE powerPlatformRole = PlatformRoleUnspecified;
+ if (!power_determine_platform_role) {
+ return powerPlatformRole;
+ }
+
+ return power_determine_platform_role(POWER_PLATFORM_ROLE_V2);
+}
+
+static bool IsWindows10TabletMode() {
+ nsCOMPtr<nsIWindowsUIUtils> uiUtils(
+ do_GetService("@mozilla.org/windows-ui-utils;1"));
+ if (NS_WARN_IF(!uiUtils)) {
+ return false;
+ }
+ bool isInTabletMode = false;
+ uiUtils->GetInTabletMode(&isInTabletMode);
+ return isInTabletMode;
+}
+
+static bool CallGetAutoRotationState(AR_STATE* aRotationState) {
+ typedef BOOL(WINAPI * GetAutoRotationStateFunc)(PAR_STATE pState);
+ static GetAutoRotationStateFunc get_auto_rotation_state_func =
+ reinterpret_cast<GetAutoRotationStateFunc>(::GetProcAddress(
+ GetModuleHandleW(L"user32.dll"), "GetAutoRotationState"));
+ if (get_auto_rotation_state_func) {
+ ZeroMemory(aRotationState, sizeof(AR_STATE));
+ return get_auto_rotation_state_func(aRotationState);
+ }
+ return false;
+}
+
+static bool IsTabletDevice() {
+ // Guarantees that:
+ // - The device has a touch screen.
+ // - It is used as a tablet which means that it has no keyboard connected.
+ // On Windows 10 it means that it is verifying with ConvertibleSlateMode.
+
+ if (!IsWin8OrLater()) {
+ return false;
+ }
+
+ if (IsWindows10TabletMode()) {
+ return true;
+ }
+
+ if (!GetSystemMetrics(SM_MAXIMUMTOUCHES)) {
+ return false;
+ }
+
+ // If the device is docked, the user is treating the device as a PC.
+ if (GetSystemMetrics(SM_SYSTEMDOCKED)) {
+ return false;
+ }
+
+ // If the device is not supporting rotation, it's unlikely to be a tablet,
+ // a convertible or a detachable. See:
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dn629263(v=vs.85).aspx
+ AR_STATE rotation_state;
+ if (CallGetAutoRotationState(&rotation_state) &&
+ (rotation_state & (AR_NOT_SUPPORTED | AR_LAPTOP | AR_NOSENSOR))) {
+ return false;
+ }
+
+ // PlatformRoleSlate was added in Windows 8+.
+ POWER_PLATFORM_ROLE role = WinUtils::GetPowerPlatformRole();
+ if (role == PlatformRoleMobile || role == PlatformRoleSlate) {
+ return !GetSystemMetrics(SM_CONVERTIBLESLATEMODE);
+ }
+ return false;
+}
+
+static bool IsMousePresent() {
+ if (!::GetSystemMetrics(SM_MOUSEPRESENT)) {
+ return false;
+ }
+
+ DWORD count = InputDeviceUtils::CountMouseDevices();
+ if (!count) {
+ return false;
+ }
+
+ // If there is a mouse device and if this machine is a tablet or has a
+ // digitizer, that's counted as the mouse device.
+ // FIXME: Bug 1495938: We should drop this heuristic way once we find out a
+ // reliable way to tell there is no mouse or not.
+ if (count == 1 &&
+ (WinUtils::IsTouchDeviceSupportPresent() || IsTabletDevice())) {
+ return false;
+ }
+
+ return true;
+}
+
+/* static */
+PointerCapabilities WinUtils::GetPrimaryPointerCapabilities() {
+ if (IsTabletDevice()) {
+ return PointerCapabilities::Coarse;
+ }
+
+ if (IsMousePresent()) {
+ return PointerCapabilities::Fine | PointerCapabilities::Hover;
+ }
+
+ if (IsTouchDeviceSupportPresent()) {
+ return PointerCapabilities::Coarse;
+ }
+
+ return PointerCapabilities::None;
+}
+
+/* static */
+PointerCapabilities WinUtils::GetAllPointerCapabilities() {
+ PointerCapabilities result = PointerCapabilities::None;
+
+ if (IsTabletDevice() || IsTouchDeviceSupportPresent()) {
+ result |= PointerCapabilities::Coarse;
+ }
+
+ if (IsMousePresent()) {
+ result |= PointerCapabilities::Fine | PointerCapabilities::Hover;
+ }
+
+ return result;
+}
+
+/* static */
+bool WinUtils::ResolveJunctionPointsAndSymLinks(std::wstring& aPath) {
+ LOG_D("ResolveJunctionPointsAndSymLinks: Resolving path: %S", aPath.c_str());
+
+ wchar_t path[MAX_PATH] = {0};
+
+ nsAutoHandle handle(::CreateFileW(
+ aPath.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ LOG_E("Failed to open file handle to resolve path. GetLastError=%d",
+ GetLastError());
+ return false;
+ }
+
+ DWORD pathLen = GetFinalPathNameByHandleW(
+ handle, path, MAX_PATH, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
+ if (pathLen == 0 || pathLen >= MAX_PATH) {
+ LOG_E("GetFinalPathNameByHandleW failed. GetLastError=%d", GetLastError());
+ return false;
+ }
+ aPath = path;
+
+ // GetFinalPathNameByHandle sticks a '\\?\' in front of the path,
+ // but that confuses some APIs so strip it off. It will also put
+ // '\\?\UNC\' in front of network paths, we convert that to '\\'.
+ if (aPath.compare(0, 7, L"\\\\?\\UNC") == 0) {
+ aPath.erase(2, 6);
+ } else if (aPath.compare(0, 4, L"\\\\?\\") == 0) {
+ aPath.erase(0, 4);
+ }
+
+ LOG_D("ResolveJunctionPointsAndSymLinks: Resolved path to: %S",
+ aPath.c_str());
+ return true;
+}
+
+/* static */
+bool WinUtils::ResolveJunctionPointsAndSymLinks(nsIFile* aPath) {
+ MOZ_ASSERT(aPath);
+
+ nsAutoString filePath;
+ nsresult rv = aPath->GetPath(filePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ std::wstring resolvedPath(filePath.get());
+ if (!ResolveJunctionPointsAndSymLinks(resolvedPath)) {
+ return false;
+ }
+
+ rv = aPath->InitWithPath(nsDependentString(resolvedPath.c_str()));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return true;
+}
+
+/* static */
+bool WinUtils::RunningFromANetworkDrive() {
+ wchar_t exePath[MAX_PATH];
+ if (!::GetModuleFileNameW(nullptr, exePath, MAX_PATH)) {
+ return false;
+ }
+
+ std::wstring exeString(exePath);
+ if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(exeString)) {
+ return false;
+ }
+
+ wchar_t volPath[MAX_PATH];
+ if (!::GetVolumePathNameW(exeString.c_str(), volPath, MAX_PATH)) {
+ return false;
+ }
+
+ return (::GetDriveTypeW(volPath) == DRIVE_REMOTE);
+}
+
+/* static */
+bool WinUtils::GetModuleFullPath(HMODULE aModuleHandle, nsAString& aPath) {
+ size_t bufferSize = MAX_PATH;
+ size_t len = 0;
+ while (true) {
+ aPath.SetLength(bufferSize);
+ len = (size_t)::GetModuleFileNameW(
+ aModuleHandle, (char16ptr_t)aPath.BeginWriting(), bufferSize);
+ if (!len) {
+ return false;
+ }
+ if (len == bufferSize && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ bufferSize *= 2;
+ continue;
+ }
+ aPath.Truncate(len);
+ break;
+ }
+ return true;
+}
+
+/* static */
+bool WinUtils::CanonicalizePath(nsAString& aPath) {
+ wchar_t tempPath[MAX_PATH + 1];
+ if (!PathCanonicalizeW(tempPath,
+ (char16ptr_t)PromiseFlatString(aPath).get())) {
+ return false;
+ }
+ aPath = tempPath;
+ MOZ_ASSERT(aPath.Length() <= MAX_PATH);
+ return true;
+}
+
+/* static */
+bool WinUtils::MakeLongPath(nsAString& aPath) {
+ wchar_t tempPath[MAX_PATH + 1];
+ DWORD longResult =
+ GetLongPathNameW((char16ptr_t)PromiseFlatString(aPath).get(), tempPath,
+ ArrayLength(tempPath));
+ if (longResult > ArrayLength(tempPath)) {
+ // Our buffer is too short, and we're guaranteeing <= MAX_PATH results.
+ return false;
+ } else if (longResult) {
+ // Success.
+ aPath = tempPath;
+ MOZ_ASSERT(aPath.Length() <= MAX_PATH);
+ }
+ // GetLongPathNameW returns 0 if the path is not found or is not rooted,
+ // but we shouldn't consider that a failure condition.
+ return true;
+}
+
+/* static */
+bool WinUtils::UnexpandEnvVars(nsAString& aPath) {
+ wchar_t tempPath[MAX_PATH + 1];
+ // PathUnExpandEnvStringsW returns false if it doesn't make any
+ // substitutions. Silently continue using the unaltered path.
+ if (PathUnExpandEnvStringsW((char16ptr_t)PromiseFlatString(aPath).get(),
+ tempPath, ArrayLength(tempPath))) {
+ aPath = tempPath;
+ MOZ_ASSERT(aPath.Length() <= MAX_PATH);
+ }
+ return true;
+}
+
+/* static */
+WinUtils::WhitelistVec WinUtils::BuildWhitelist() {
+ WhitelistVec result;
+
+ Unused << result.emplaceBack(
+ std::make_pair(nsString(u"%ProgramFiles%"_ns), nsDependentString()));
+
+ // When no substitution is required, set the void flag
+ result.back().second.SetIsVoid(true);
+
+ Unused << result.emplaceBack(
+ std::make_pair(nsString(u"%SystemRoot%"_ns), nsDependentString()));
+ result.back().second.SetIsVoid(true);
+
+ wchar_t tmpPath[MAX_PATH + 1] = {};
+ if (GetTempPath(MAX_PATH, tmpPath)) {
+ // GetTempPath's result always ends with a backslash, which we don't want
+ uint32_t tmpPathLen = wcslen(tmpPath);
+ if (tmpPathLen) {
+ tmpPath[tmpPathLen - 1] = 0;
+ }
+
+ nsAutoString cleanTmpPath(tmpPath);
+ if (UnexpandEnvVars(cleanTmpPath)) {
+ constexpr auto tempVar = u"%TEMP%"_ns;
+ Unused << result.emplaceBack(std::make_pair(
+ nsString(cleanTmpPath), nsDependentString(tempVar, 0)));
+ }
+ }
+
+ // If we add more items to the whitelist, ensure we still don't invoke an
+ // unnecessary heap allocation.
+ MOZ_ASSERT(result.length() <= kMaxWhitelistedItems);
+
+ return result;
+}
+
+/**
+ * This function provides an array of (system path, substitution) pairs that are
+ * considered to be acceptable with respect to privacy, for the purposes of
+ * submitting within telemetry or crash reports.
+ *
+ * The substitution string's void flag may be set. If it is, no subsitution is
+ * necessary. Otherwise, the consumer should replace the system path with the
+ * substitution.
+ *
+ * @see PreparePathForTelemetry for an example of its usage.
+ */
+/* static */
+const WinUtils::WhitelistVec& WinUtils::GetWhitelistedPaths() {
+ static WhitelistVec sWhitelist([]() -> WhitelistVec {
+ auto setClearFn = [ptr = &sWhitelist]() -> void {
+ RunOnShutdown([ptr]() -> void { ptr->clear(); },
+ ShutdownPhase::ShutdownFinal);
+ };
+
+ if (NS_IsMainThread()) {
+ setClearFn();
+ } else {
+ SchedulerGroup::Dispatch(
+ TaskCategory::Other,
+ NS_NewRunnableFunction("WinUtils::GetWhitelistedPaths",
+ std::move(setClearFn)));
+ }
+
+ return BuildWhitelist();
+ }());
+ return sWhitelist;
+}
+
+/**
+ * This function is located here (as opposed to nsSystemInfo or elsewhere)
+ * because we need to gather this information as early as possible during
+ * startup.
+ */
+/* static */
+bool WinUtils::GetAppInitDLLs(nsAString& aOutput) {
+ aOutput.Truncate();
+ HKEY hkey = NULL;
+ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows",
+ 0, KEY_QUERY_VALUE, &hkey)) {
+ return false;
+ }
+ nsAutoRegKey key(hkey);
+ LONG status;
+ const wchar_t kLoadAppInitDLLs[] = L"LoadAppInit_DLLs";
+ DWORD loadAppInitDLLs = 0;
+ DWORD loadAppInitDLLsLen = sizeof(loadAppInitDLLs);
+ status = RegQueryValueExW(hkey, kLoadAppInitDLLs, nullptr, nullptr,
+ (LPBYTE)&loadAppInitDLLs, &loadAppInitDLLsLen);
+ if (status != ERROR_SUCCESS) {
+ return false;
+ }
+ if (!loadAppInitDLLs) {
+ // If loadAppInitDLLs is zero then AppInit_DLLs is disabled.
+ // In this case we'll return true along with an empty output string.
+ return true;
+ }
+ DWORD numBytes = 0;
+ const wchar_t kAppInitDLLs[] = L"AppInit_DLLs";
+ // Query for required buffer size
+ status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr, nullptr,
+ &numBytes);
+ if (status != ERROR_SUCCESS) {
+ return false;
+ }
+ // Allocate the buffer and query for the actual data
+ mozilla::UniquePtr<wchar_t[]> data =
+ mozilla::MakeUnique<wchar_t[]>(numBytes / sizeof(wchar_t));
+ status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr,
+ (LPBYTE)data.get(), &numBytes);
+ if (status != ERROR_SUCCESS) {
+ return false;
+ }
+ // For each token, split up the filename components and then check the
+ // name of the file.
+ const wchar_t kDelimiters[] = L", ";
+ wchar_t* tokenContext = nullptr;
+ wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext);
+ while (token) {
+ nsAutoString cleanPath(token);
+ // Since these paths are short paths originating from the registry, we need
+ // to canonicalize them, lengthen them, and sanitize them before we can
+ // check them against the whitelist
+ if (PreparePathForTelemetry(cleanPath)) {
+ if (!aOutput.IsEmpty()) {
+ aOutput += L";";
+ }
+ aOutput += cleanPath;
+ }
+ token = wcstok_s(nullptr, kDelimiters, &tokenContext);
+ }
+ return true;
+}
+
+/* static */
+bool WinUtils::PreparePathForTelemetry(nsAString& aPath,
+ PathTransformFlags aFlags) {
+ if (aFlags & PathTransformFlags::Canonicalize) {
+ if (!CanonicalizePath(aPath)) {
+ return false;
+ }
+ }
+ if (aFlags & PathTransformFlags::Lengthen) {
+ if (!MakeLongPath(aPath)) {
+ return false;
+ }
+ }
+ if (aFlags & PathTransformFlags::UnexpandEnvVars) {
+ if (!UnexpandEnvVars(aPath)) {
+ return false;
+ }
+ }
+
+ const WhitelistVec& whitelistedPaths = GetWhitelistedPaths();
+
+ for (uint32_t i = 0; i < whitelistedPaths.length(); ++i) {
+ const nsString& testPath = whitelistedPaths[i].first;
+ const nsDependentString& substitution = whitelistedPaths[i].second;
+ if (StringBeginsWith(aPath, testPath, nsCaseInsensitiveStringComparator)) {
+ if (!substitution.IsVoid()) {
+ aPath.Replace(0, testPath.Length(), substitution);
+ }
+ return true;
+ }
+ }
+
+ // For non-whitelisted paths, we strip the path component and just leave
+ // the filename. We can't use nsLocalFile to do this because these paths may
+ // begin with environment variables, and nsLocalFile doesn't like
+ // non-absolute paths.
+ const nsString& flatPath = PromiseFlatString(aPath);
+ LPCWSTR leafStart = ::PathFindFileNameW(flatPath.get());
+ ptrdiff_t cutLen = leafStart - flatPath.get();
+ if (cutLen) {
+ aPath.Cut(0, cutLen);
+ } else if (aFlags & PathTransformFlags::RequireFilePath) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinUtils.h b/widget/windows/WinUtils.h
new file mode 100644
index 0000000000..c23c0d243f
--- /dev/null
+++ b/widget/windows/WinUtils.h
@@ -0,0 +1,657 @@
+/* -*- 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_WinUtils_h__
+#define mozilla_widget_WinUtils_h__
+
+#include "nscore.h"
+#include <windows.h>
+#include <shobjidl.h>
+#include <uxtheme.h>
+#include <dwmapi.h>
+
+// Undo the windows.h damage
+#undef GetMessage
+#undef CreateEvent
+#undef GetClassName
+#undef GetBinaryType
+#undef RemoveDirectory
+
+#include "nsString.h"
+#include "nsRegion.h"
+#include "nsRect.h"
+
+#include "nsIRunnable.h"
+#include "nsICryptoHash.h"
+#ifdef MOZ_PLACES
+# include "nsIFaviconService.h"
+#endif
+#include "nsIDownloader.h"
+#include "nsIURI.h"
+#include "nsIWidget.h"
+#include "nsIThread.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+#include "mozilla/WindowsDpiAwareness.h"
+#include "mozilla/gfx/2D.h"
+
+/**
+ * NS_INLINE_DECL_IUNKNOWN_REFCOUNTING should be used for defining and
+ * implementing AddRef() and Release() of IUnknown interface.
+ * This depends on xpcom/base/nsISupportsImpl.h.
+ */
+
+#define NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(_class) \
+ public: \
+ STDMETHODIMP_(ULONG) AddRef() { \
+ MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ ++mRefCnt; \
+ NS_LOG_ADDREF(this, mRefCnt, #_class, sizeof(*this)); \
+ return static_cast<ULONG>(mRefCnt.get()); \
+ } \
+ STDMETHODIMP_(ULONG) Release() { \
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, \
+ "Release called on object that has already been released!"); \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ --mRefCnt; \
+ NS_LOG_RELEASE(this, mRefCnt, #_class); \
+ if (mRefCnt == 0) { \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ mRefCnt = 1; /* stabilize */ \
+ delete this; \
+ return 0; \
+ } \
+ return static_cast<ULONG>(mRefCnt.get()); \
+ } \
+ \
+ protected: \
+ nsAutoRefCnt mRefCnt; \
+ NS_DECL_OWNINGTHREAD \
+ public:
+
+class nsWindow;
+class nsWindowBase;
+struct KeyPair;
+
+namespace mozilla {
+enum class PointerCapabilities : uint8_t;
+#if defined(ACCESSIBILITY)
+namespace a11y {
+class Accessible;
+} // namespace a11y
+#endif // defined(ACCESSIBILITY)
+
+namespace widget {
+
+// Windows message debugging data
+typedef struct {
+ const char* mStr;
+ UINT mId;
+} EventMsgInfo;
+extern EventMsgInfo gAllEvents[];
+
+// More complete QS definitions for MsgWaitForMultipleObjects() and
+// GetQueueStatus() that include newer win8 specific defines.
+
+#ifndef QS_RAWINPUT
+# define QS_RAWINPUT 0x0400
+#endif
+
+#ifndef QS_TOUCH
+# define QS_TOUCH 0x0800
+# define QS_POINTER 0x1000
+#endif
+
+#define MOZ_QS_ALLEVENT \
+ (QS_KEY | QS_MOUSEMOVE | QS_MOUSEBUTTON | QS_POSTMESSAGE | QS_TIMER | \
+ QS_PAINT | QS_SENDMESSAGE | QS_HOTKEY | QS_ALLPOSTMESSAGE | QS_RAWINPUT | \
+ QS_TOUCH | QS_POINTER)
+
+// Logging macros
+#define LogFunction() mozilla::widget::WinUtils::Log(__FUNCTION__)
+#define LogThread() \
+ mozilla::widget::WinUtils::Log("%s: IsMainThread:%d ThreadId:%X", \
+ __FUNCTION__, NS_IsMainThread(), \
+ GetCurrentThreadId())
+#define LogThis() mozilla::widget::WinUtils::Log("[%X] %s", this, __FUNCTION__)
+#define LogException(e) \
+ mozilla::widget::WinUtils::Log("%s Exception:%s", __FUNCTION__, \
+ e->ToString()->Data())
+#define LogHRESULT(hr) \
+ mozilla::widget::WinUtils::Log("%s hr=%X", __FUNCTION__, hr)
+
+#ifdef MOZ_PLACES
+class myDownloadObserver final : public nsIDownloadObserver {
+ ~myDownloadObserver() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOWNLOADOBSERVER
+};
+#endif
+
+class WinUtils {
+ // Function pointers for APIs that may not be available depending on
+ // the Win10 update version -- will be set up in Initialize().
+ static SetThreadDpiAwarenessContextProc sSetThreadDpiAwarenessContext;
+ static EnableNonClientDpiScalingProc sEnableNonClientDpiScaling;
+ static GetSystemMetricsForDpiProc sGetSystemMetricsForDpi;
+
+ public:
+ class AutoSystemDpiAware {
+ public:
+ AutoSystemDpiAware() {
+ if (sSetThreadDpiAwarenessContext) {
+ mPrevContext =
+ sSetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
+ }
+ }
+
+ ~AutoSystemDpiAware() {
+ if (sSetThreadDpiAwarenessContext) {
+ sSetThreadDpiAwarenessContext(mPrevContext);
+ }
+ }
+
+ private:
+ DPI_AWARENESS_CONTEXT mPrevContext;
+ };
+
+ // Wrapper for DefWindowProc that will enable non-client dpi scaling on the
+ // window during creation.
+ static LRESULT WINAPI NonClientDpiScalingDefWindowProcW(HWND hWnd, UINT msg,
+ WPARAM wParam,
+ LPARAM lParam);
+
+ /**
+ * Get the system's default logical-to-physical DPI scaling factor,
+ * which is based on the primary display. Note however that unlike
+ * LogToPhysFactor(GetPrimaryMonitor()), this will not change during
+ * a session even if the displays are reconfigured. This scale factor
+ * is used by Windows theme metrics etc, which do not fully support
+ * dynamic resolution changes but are only updated on logout.
+ */
+ static double SystemScaleFactor();
+
+ static bool IsPerMonitorDPIAware();
+ /**
+ * Get the DPI of the given monitor if it's per-monitor DPI aware, otherwise
+ * return the system DPI.
+ */
+ static float MonitorDPI(HMONITOR aMonitor);
+ static float SystemDPI();
+ /**
+ * Functions to convert between logical pixels as used by most Windows APIs
+ * and physical (device) pixels.
+ */
+ static double LogToPhysFactor(HMONITOR aMonitor);
+ static double LogToPhysFactor(HWND aWnd) {
+ // if there's an ancestor window, we want to share its DPI setting
+ HWND ancestor = ::GetAncestor(aWnd, GA_ROOTOWNER);
+ return LogToPhysFactor(::MonitorFromWindow(ancestor ? ancestor : aWnd,
+ MONITOR_DEFAULTTOPRIMARY));
+ }
+ static double LogToPhysFactor(HDC aDC) {
+ return LogToPhysFactor(::WindowFromDC(aDC));
+ }
+ static int32_t LogToPhys(HMONITOR aMonitor, double aValue);
+ static HMONITOR GetPrimaryMonitor();
+ static HMONITOR MonitorFromRect(const gfx::Rect& rect);
+
+ static bool HasSystemMetricsForDpi();
+ static int GetSystemMetricsForDpi(int nIndex, UINT dpi);
+
+ /**
+ * @param aHdc HDC for printer
+ * @return unwritable margins for currently set page on aHdc or empty margins
+ * if aHdc is null
+ */
+ static gfx::MarginDouble GetUnwriteableMarginsForDeviceInInches(HDC aHdc);
+
+ /**
+ * Logging helpers that dump output to prlog module 'Widget', console, and
+ * OutputDebugString. Note these output in both debug and release builds.
+ */
+ static void Log(const char* fmt, ...);
+ static void LogW(const wchar_t* fmt, ...);
+
+ /**
+ * PeekMessage() and GetMessage() are wrapper methods for PeekMessageW(),
+ * GetMessageW(), ITfMessageMgr::PeekMessageW() and
+ * ITfMessageMgr::GetMessageW().
+ * Don't call the native APIs directly. You MUST use these methods instead.
+ */
+ static bool PeekMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
+ UINT aLastMessage, UINT aOption);
+ static bool GetMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
+ UINT aLastMessage);
+
+ /**
+ * Wait until a message is ready to be processed.
+ * Prefer using this method to directly calling ::WaitMessage since
+ * ::WaitMessage will wait if there is an unread message in the queue.
+ * That can cause freezes until another message enters the queue if the
+ * message is marked read by a call to PeekMessage which the caller is
+ * not aware of (e.g., from a different thread).
+ * Note that this method may cause sync dispatch of sent (as opposed to
+ * posted) messages.
+ * @param aTimeoutMs Timeout for waiting in ms, defaults to INFINITE
+ */
+ static void WaitForMessage(DWORD aTimeoutMs = INFINITE);
+
+ /**
+ * Gets the value of a string-typed registry value.
+ *
+ * @param aRoot The registry root to search in.
+ * @param aKeyName The name of the registry key to open.
+ * @param aValueName The name of the registry value in the specified key whose
+ * value is to be retrieved. Can be null, to retrieve the key's unnamed/
+ * default value.
+ * @param aBuffer The buffer into which to store the string value. Can be
+ * null, in which case the return value indicates just whether the value
+ * exists.
+ * @param aBufferLength The size of aBuffer, in bytes.
+ * @return Whether the value exists and is a string.
+ */
+ static bool GetRegistryKey(HKEY aRoot, char16ptr_t aKeyName,
+ char16ptr_t aValueName, wchar_t* aBuffer,
+ DWORD aBufferLength);
+
+ /**
+ * Checks whether the registry key exists in either 32bit or 64bit branch on
+ * the environment.
+ *
+ * @param aRoot The registry root of aName.
+ * @param aKeyName The name of the registry key to check.
+ * @return TRUE if it exists and is readable. Otherwise, FALSE.
+ */
+ static bool HasRegistryKey(HKEY aRoot, char16ptr_t aKeyName);
+
+ /**
+ * GetTopLevelHWND() returns a window handle of the top level window which
+ * aWnd belongs to. Note that the result may not be our window, i.e., it
+ * may not be managed by nsWindow.
+ *
+ * See follwing table for the detail of the result window type.
+ *
+ * +-------------------------+-----------------------------------------------+
+ * | | aStopIfNotPopup |
+ * +-------------------------+-----------------------+-----------------------+
+ * | | TRUE | FALSE |
+ + +-----------------+-------+-----------------------+-----------------------+
+ * | | | * an independent top level window |
+ * | | TRUE | * a pupup window (WS_POPUP) |
+ * | | | * an owned top level window (like dialog) |
+ * | aStopIfNotChild +-------+-----------------------+-----------------------+
+ * | | | * independent window | * only an independent |
+ * | | FALSE | * non-popup-owned- | top level window |
+ * | | | window like dialog | |
+ * +-----------------+-------+-----------------------+-----------------------+
+ */
+ static HWND GetTopLevelHWND(HWND aWnd, bool aStopIfNotChild = false,
+ bool aStopIfNotPopup = true);
+
+ /**
+ * SetNSWindowBasePtr() associates an nsWindowBase to aWnd. If aWidget is
+ * nullptr, it dissociate any nsBaseWidget pointer from aWnd.
+ * GetNSWindowBasePtr() returns an nsWindowBase pointer which was associated
+ * by SetNSWindowBasePtr(). GetNSWindowPtr() is a legacy api for win32
+ * nsWindow and should be avoided outside of nsWindow src.
+ */
+ static bool SetNSWindowBasePtr(HWND aWnd, nsWindowBase* aWidget);
+ static nsWindowBase* GetNSWindowBasePtr(HWND aWnd);
+ static nsWindow* GetNSWindowPtr(HWND aWnd);
+
+ /**
+ * GetMonitorCount() returns count of monitors on the environment.
+ */
+ static int32_t GetMonitorCount();
+
+ /**
+ * IsOurProcessWindow() returns TRUE if aWnd belongs our process.
+ * Otherwise, FALSE.
+ */
+ static bool IsOurProcessWindow(HWND aWnd);
+
+ /**
+ * FindOurProcessWindow() returns the nearest ancestor window which
+ * belongs to our process. If it fails to find our process's window by the
+ * top level window, returns nullptr. And note that this is using
+ * ::GetParent() for climbing the window hierarchy, therefore, it gives
+ * up at an owned top level window except popup window (e.g., dialog).
+ */
+ static HWND FindOurProcessWindow(HWND aWnd);
+
+ /**
+ * FindOurWindowAtPoint() returns the topmost child window which belongs to
+ * our process's top level window.
+ *
+ * NOTE: the topmost child window may NOT be our process's window like a
+ * plugin's window.
+ */
+ static HWND FindOurWindowAtPoint(const POINT& aPointInScreen);
+
+ /**
+ * InitMSG() returns an MSG struct which was initialized by the params.
+ * Don't trust the other members in the result.
+ */
+ static MSG InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam, HWND aWnd);
+
+ /**
+ * GetScanCode() returns a scan code for the LPARAM of WM_KEYDOWN, WM_KEYUP,
+ * WM_CHAR and WM_UNICHAR.
+ *
+ */
+ static WORD GetScanCode(LPARAM aLParam) { return (aLParam >> 16) & 0xFF; }
+
+ /**
+ * IsExtendedScanCode() returns TRUE if the LPARAM indicates the key message
+ * is an extended key event.
+ */
+ static bool IsExtendedScanCode(LPARAM aLParam) {
+ return (aLParam & 0x1000000) != 0;
+ }
+
+ /**
+ * GetInternalMessage() converts a native message to an internal message.
+ * If there is no internal message for the given native message, returns
+ * the native message itself.
+ */
+ static UINT GetInternalMessage(UINT aNativeMessage);
+
+ /**
+ * GetNativeMessage() converts an internal message to a native message.
+ * If aInternalMessage is a native message, returns the native message itself.
+ */
+ static UINT GetNativeMessage(UINT aInternalMessage);
+
+ /**
+ * GetMouseInputSource() returns a pointing device information. The value is
+ * one of MouseEvent_Binding::MOZ_SOURCE_*. This method MUST be called during
+ * mouse message handling.
+ */
+ static uint16_t GetMouseInputSource();
+
+ /**
+ * Windows also fires mouse window messages for pens and touches, so we should
+ * retrieve their pointer ID on receiving mouse events as well. Please refer
+ * to
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx
+ */
+ static uint16_t GetMousePointerID();
+
+ static bool GetIsMouseFromTouch(EventMessage aEventType);
+
+ /**
+ * GetShellItemPath return the file or directory path of a shell item.
+ * Internally calls IShellItem's GetDisplayName.
+ *
+ * aItem the shell item containing the path.
+ * aResultString the resulting string path.
+ * returns true if a path was retreived.
+ */
+ static bool GetShellItemPath(IShellItem* aItem, nsString& aResultString);
+
+ /**
+ * ConvertHRGNToRegion converts a Windows HRGN to an LayoutDeviceIntRegion.
+ *
+ * aRgn the HRGN to convert.
+ * returns the LayoutDeviceIntRegion.
+ */
+ static LayoutDeviceIntRegion ConvertHRGNToRegion(HRGN aRgn);
+
+ /**
+ * ToIntRect converts a Windows RECT to a LayoutDeviceIntRect.
+ *
+ * aRect the RECT to convert.
+ * returns the LayoutDeviceIntRect.
+ */
+ static LayoutDeviceIntRect ToIntRect(const RECT& aRect);
+
+ /**
+ * Helper used in invalidating flash plugin windows owned
+ * by low rights flash containers.
+ */
+ static void InvalidatePluginAsWorkaround(nsIWidget* aWidget,
+ const LayoutDeviceIntRect& aRect);
+
+ /**
+ * Returns true if the context or IME state is enabled. Otherwise, false.
+ */
+ static bool IsIMEEnabled(const InputContext& aInputContext);
+ static bool IsIMEEnabled(IMEEnabled aIMEState);
+
+ /**
+ * Returns modifier key array for aModifiers. This is for
+ * nsIWidget::SynthethizeNative*Event().
+ */
+ static void SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray,
+ uint32_t aModifiers, UINT aMessage);
+
+ /**
+ * Does device have touch support
+ */
+ static uint32_t IsTouchDeviceSupportPresent();
+
+ /**
+ * The maximum number of simultaneous touch contacts supported by the device.
+ * In the case of devices with multiple digitizers (e.g. multiple touch
+ * screens), the value will be the maximum of the set of maximum supported
+ * contacts by each individual digitizer.
+ */
+ static uint32_t GetMaxTouchPoints();
+
+ /**
+ * Returns the windows power platform role, which is useful for detecting
+ * tablets.
+ */
+ static POWER_PLATFORM_ROLE GetPowerPlatformRole();
+
+ // For pointer and hover media queries features.
+ static PointerCapabilities GetPrimaryPointerCapabilities();
+ // For any-pointer and any-hover media queries features.
+ static PointerCapabilities GetAllPointerCapabilities();
+
+ /**
+ * Fully resolves a path to its final path name. So if path contains
+ * junction points or symlinks to other folders, we'll resolve the path
+ * fully to the actual path that the links target.
+ *
+ * @param aPath path to be resolved.
+ * @return true if successful, including if nothing needs to be changed.
+ * false if something failed or aPath does not exist, aPath will
+ * remain unchanged.
+ */
+ static bool ResolveJunctionPointsAndSymLinks(std::wstring& aPath);
+ static bool ResolveJunctionPointsAndSymLinks(nsIFile* aPath);
+
+ /**
+ * Returns true if executable's path is on a network drive.
+ */
+ static bool RunningFromANetworkDrive();
+
+ static void Initialize();
+
+ static nsresult WriteBitmap(nsIFile* aFile,
+ mozilla::gfx::SourceSurface* surface);
+ // This function is a helper, but it cannot be called from the main thread.
+ // Use the one above!
+ static nsresult WriteBitmap(nsIFile* aFile, imgIContainer* aImage);
+
+ /**
+ * Wrapper for ::GetModuleFileNameW().
+ * @param aModuleHandle [in] The handle of a loaded module
+ * @param aPath [out] receives the full path of the module specified
+ * by aModuleBase.
+ * @return true if aPath was successfully populated.
+ */
+ static bool GetModuleFullPath(HMODULE aModuleHandle, nsAString& aPath);
+
+ /**
+ * Wrapper for PathCanonicalize().
+ * Upon success, the resulting output string length is <= MAX_PATH.
+ * @param aPath [in,out] The path to transform.
+ * @return true on success, false on failure.
+ */
+ static bool CanonicalizePath(nsAString& aPath);
+
+ /**
+ * Converts short paths (e.g. "C:\\PROGRA~1\\XYZ") to full paths.
+ * Upon success, the resulting output string length is <= MAX_PATH.
+ * @param aPath [in,out] The path to transform.
+ * @return true on success, false on failure.
+ */
+ static bool MakeLongPath(nsAString& aPath);
+
+ /**
+ * Wrapper for PathUnExpandEnvStringsW().
+ * Upon success, the resulting output string length is <= MAX_PATH.
+ * @param aPath [in,out] The path to transform.
+ * @return true on success, false on failure.
+ */
+ static bool UnexpandEnvVars(nsAString& aPath);
+
+ /**
+ * Retrieve a semicolon-delimited list of DLL files derived from AppInit_DLLs
+ */
+ static bool GetAppInitDLLs(nsAString& aOutput);
+
+ enum class PathTransformFlags : uint32_t {
+ Canonicalize = 1,
+ Lengthen = 2,
+ UnexpandEnvVars = 4,
+ RequireFilePath = 8,
+
+ Default = 7, // Default omits RequireFilePath
+ };
+
+ /**
+ * Given a path, transforms it in preparation to be reported via telemetry.
+ * That can include canonicalization, converting short to long paths,
+ * unexpanding environment strings, and removing potentially sensitive data
+ * from the path.
+ *
+ * @param aPath [in,out] The path to transform.
+ * @param aFlags [in] Specifies which transformations to perform, allowing
+ * the caller to skip operations they know have already been
+ * performed.
+ * @return true on success, false on failure.
+ */
+ static bool PreparePathForTelemetry(
+ nsAString& aPath,
+ PathTransformFlags aFlags = PathTransformFlags::Default);
+
+ static const size_t kMaxWhitelistedItems = 3;
+ using WhitelistVec =
+ Vector<std::pair<nsString, nsDependentString>, kMaxWhitelistedItems>;
+
+ static const WhitelistVec& GetWhitelistedPaths();
+
+ private:
+ static WhitelistVec BuildWhitelist();
+
+ public:
+#ifdef ACCESSIBILITY
+ static a11y::Accessible* GetRootAccessibleForHWND(HWND aHwnd);
+#endif
+};
+
+#ifdef MOZ_PLACES
+class AsyncFaviconDataReady final : public nsIFaviconDataCallback {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFAVICONDATACALLBACK
+
+ AsyncFaviconDataReady(nsIURI* aNewURI, nsCOMPtr<nsIThread>& aIOThread,
+ const bool aURLShortcut,
+ already_AddRefed<nsIRunnable> aRunnable);
+ nsresult OnFaviconDataNotAvailable(void);
+
+ private:
+ ~AsyncFaviconDataReady() {}
+
+ nsCOMPtr<nsIURI> mNewURI;
+ nsCOMPtr<nsIThread> mIOThread;
+ nsCOMPtr<nsIRunnable> mRunnable;
+ const bool mURLShortcut;
+};
+#endif
+
+/**
+ * Asynchronously tries add the list to the build
+ */
+class AsyncEncodeAndWriteIcon : public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ // Warning: AsyncEncodeAndWriteIcon assumes ownership of the aData buffer
+ // passed in
+ AsyncEncodeAndWriteIcon(const nsAString& aIconPath,
+ UniquePtr<uint8_t[]> aData, uint32_t aStride,
+ uint32_t aWidth, uint32_t aHeight,
+ already_AddRefed<nsIRunnable> aRunnable);
+
+ private:
+ virtual ~AsyncEncodeAndWriteIcon();
+
+ nsAutoString mIconPath;
+ UniquePtr<uint8_t[]> mBuffer;
+ nsCOMPtr<nsIRunnable> mRunnable;
+ uint32_t mStride;
+ uint32_t mWidth;
+ uint32_t mHeight;
+};
+
+class AsyncDeleteAllFaviconsFromDisk : public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ explicit AsyncDeleteAllFaviconsFromDisk(bool aIgnoreRecent = false);
+
+ private:
+ virtual ~AsyncDeleteAllFaviconsFromDisk();
+
+ int32_t mIcoNoDeleteSeconds;
+ bool mIgnoreRecent;
+ nsCOMPtr<nsIFile> mJumpListCacheDir;
+};
+
+class FaviconHelper {
+ public:
+ static const char kJumpListCacheDir[];
+ static const char kShortcutCacheDir[];
+ static nsresult ObtainCachedIconFile(
+ nsCOMPtr<nsIURI> aFaviconPageURI, nsString& aICOFilePath,
+ nsCOMPtr<nsIThread>& aIOThread, bool aURLShortcut,
+ already_AddRefed<nsIRunnable> aRunnable = nullptr);
+
+ static nsresult HashURI(nsCOMPtr<nsICryptoHash>& aCryptoHash, nsIURI* aUri,
+ nsACString& aUriHash);
+
+ static nsresult GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsCOMPtr<nsIFile>& aICOFile,
+ bool aURLShortcut);
+
+ static nsresult CacheIconFileFromFaviconURIAsync(
+ nsCOMPtr<nsIURI> aFaviconPageURI, nsCOMPtr<nsIFile> aICOFile,
+ nsCOMPtr<nsIThread>& aIOThread, bool aURLShortcut,
+ already_AddRefed<nsIRunnable> aRunnable);
+
+ static int32_t GetICOCacheSecondsTimeout();
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(WinUtils::PathTransformFlags);
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_WinUtils_h__
diff --git a/widget/windows/WindowHook.cpp b/widget/windows/WindowHook.cpp
new file mode 100644
index 0000000000..06773f21b7
--- /dev/null
+++ b/widget/windows/WindowHook.cpp
@@ -0,0 +1,113 @@
+/* -*- 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 "WindowHook.h"
+#include "nsWindow.h"
+#include "nsWindowDefs.h"
+
+namespace mozilla {
+namespace widget {
+
+nsresult WindowHook::AddHook(UINT nMsg, Callback callback, void* context) {
+ MessageData* data = LookupOrCreate(nMsg);
+
+ if (!data) return NS_ERROR_OUT_OF_MEMORY;
+
+ // Ensure we don't overwrite another hook
+ NS_ENSURE_TRUE(nullptr == data->hook.cb, NS_ERROR_UNEXPECTED);
+
+ data->hook = CallbackData(callback, context);
+
+ return NS_OK;
+}
+
+nsresult WindowHook::RemoveHook(UINT nMsg, Callback callback, void* context) {
+ CallbackData cbdata(callback, context);
+ MessageData* data = Lookup(nMsg);
+ if (!data) return NS_ERROR_UNEXPECTED;
+ if (data->hook != cbdata) return NS_ERROR_UNEXPECTED;
+ data->hook = CallbackData();
+
+ DeleteIfEmpty(data);
+ return NS_OK;
+}
+
+nsresult WindowHook::AddMonitor(UINT nMsg, Callback callback, void* context) {
+ MessageData* data = LookupOrCreate(nMsg);
+ return (data && data->monitors.AppendElement(CallbackData(callback, context),
+ fallible))
+ ? NS_OK
+ : NS_ERROR_OUT_OF_MEMORY;
+}
+
+nsresult WindowHook::RemoveMonitor(UINT nMsg, Callback callback,
+ void* context) {
+ CallbackData cbdata(callback, context);
+ MessageData* data = Lookup(nMsg);
+ if (!data) return NS_ERROR_UNEXPECTED;
+ CallbackDataArray::index_type idx = data->monitors.IndexOf(cbdata);
+ if (idx == CallbackDataArray::NoIndex) return NS_ERROR_UNEXPECTED;
+ data->monitors.RemoveElementAt(idx);
+ DeleteIfEmpty(data);
+ return NS_OK;
+}
+
+WindowHook::MessageData* WindowHook::Lookup(UINT nMsg) {
+ MessageDataArray::index_type idx;
+ for (idx = 0; idx < mMessageData.Length(); idx++) {
+ MessageData& data = mMessageData[idx];
+ if (data.nMsg == nMsg) return &data;
+ }
+ return nullptr;
+}
+
+WindowHook::MessageData* WindowHook::LookupOrCreate(UINT nMsg) {
+ MessageData* data = Lookup(nMsg);
+ if (!data) {
+ data = mMessageData.AppendElement();
+
+ if (!data) return nullptr;
+
+ data->nMsg = nMsg;
+ }
+ return data;
+}
+
+void WindowHook::DeleteIfEmpty(MessageData* data) {
+ // Never remove a MessageData that has still a hook or monitor entries.
+ if (data->hook || !data->monitors.IsEmpty()) return;
+
+ MessageDataArray::index_type idx;
+ idx = data - mMessageData.Elements();
+ NS_ASSERTION(
+ idx < mMessageData.Length(),
+ "Attempted to delete MessageData that doesn't belong to this array!");
+ mMessageData.RemoveElementAt(idx);
+}
+
+bool WindowHook::Notify(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ MessageData* data = Lookup(nMsg);
+ if (!data) return false;
+
+ uint32_t length = data->monitors.Length();
+ for (uint32_t midx = 0; midx < length; midx++) {
+ data->monitors[midx].Invoke(hWnd, nMsg, wParam, lParam, &aResult.mResult);
+ }
+
+ aResult.mConsumed =
+ data->hook.Invoke(hWnd, nMsg, wParam, lParam, &aResult.mResult);
+ return aResult.mConsumed;
+}
+
+bool WindowHook::CallbackData::Invoke(HWND hWnd, UINT msg, WPARAM wParam,
+ LPARAM lParam, LRESULT* aResult) {
+ if (!cb) return false;
+ return cb(context, hWnd, msg, wParam, lParam, aResult);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WindowHook.h b/widget/windows/WindowHook.h
new file mode 100644
index 0000000000..1d1f4b02da
--- /dev/null
+++ b/widget/windows/WindowHook.h
@@ -0,0 +1,76 @@
+/* -*- 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_WindowHook_h__
+#define __mozilla_WindowHook_h__
+
+#include <windows.h>
+
+#include <nsHashKeys.h>
+#include <nsClassHashtable.h>
+#include <nsTArray.h>
+
+#include "nsAppShell.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+struct MSGResult;
+
+class WindowHook {
+ public:
+ // It is expected that most callbacks will return false
+ typedef bool (*Callback)(void* aContext, HWND hWnd, UINT nMsg, WPARAM wParam,
+ LPARAM lParam, LRESULT* aResult);
+
+ nsresult AddHook(UINT nMsg, Callback callback, void* context);
+ nsresult RemoveHook(UINT nMsg, Callback callback, void* context);
+ nsresult AddMonitor(UINT nMsg, Callback callback, void* context);
+ nsresult RemoveMonitor(UINT nMsg, Callback callback, void* context);
+
+ private:
+ struct CallbackData {
+ Callback cb;
+ void* context;
+
+ CallbackData() : cb(nullptr), context(nullptr) {}
+ CallbackData(Callback cb, void* ctx) : cb(cb), context(ctx) {}
+ bool Invoke(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam,
+ LRESULT* aResult);
+ bool operator==(const CallbackData& rhs) const {
+ return cb == rhs.cb && context == rhs.context;
+ }
+ bool operator!=(const CallbackData& rhs) const { return !(*this == rhs); }
+ explicit operator bool() const { return !!cb; }
+ };
+
+ typedef nsTArray<CallbackData> CallbackDataArray;
+ struct MessageData {
+ UINT nMsg;
+ CallbackData hook;
+ CallbackDataArray monitors;
+ };
+
+ bool Notify(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+
+ MessageData* Lookup(UINT nMsg);
+ MessageData* LookupOrCreate(UINT nMsg);
+ void DeleteIfEmpty(MessageData* data);
+
+ typedef nsTArray<MessageData> MessageDataArray;
+ MessageDataArray mMessageData;
+
+ // For Notify
+ friend class ::nsWindow;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // __mozilla_WindowHook_h__
diff --git a/widget/windows/WindowsConsole.cpp b/widget/windows/WindowsConsole.cpp
new file mode 100644
index 0000000000..16005a642b
--- /dev/null
+++ b/widget/windows/WindowsConsole.cpp
@@ -0,0 +1,51 @@
+/* -*- 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 "WindowsConsole.h"
+
+#include <windows.h>
+#include <fcntl.h>
+
+namespace mozilla {
+
+static void AssignStdHandle(const char* aPath, const char* aMode, FILE* aStream,
+ DWORD aStdHandle) {
+ // Visual Studio's _fileno() returns -2 for the standard
+ // streams if they aren't associated with an output stream.
+ const int fd = _fileno(aStream);
+ if (fd == -2) {
+ freopen(aPath, aMode, aStream);
+ return;
+ }
+ if (fd < 0) {
+ return;
+ }
+
+ const HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
+ if (handle == INVALID_HANDLE_VALUE) {
+ return;
+ }
+
+ const HANDLE oldHandle = GetStdHandle(aStdHandle);
+ if (handle == oldHandle) {
+ return;
+ }
+
+ SetStdHandle(aStdHandle, handle);
+}
+
+// This code attaches the process to the appropriate console.
+void UseParentConsole() {
+ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
+ // Redirect the standard streams to the existing console, but
+ // only if they haven't been redirected to a valid file.
+ AssignStdHandle("CONOUT$", "w", stdout, STD_OUTPUT_HANDLE);
+ // There is no CONERR$, so use CONOUT$ for stderr as well.
+ AssignStdHandle("CONOUT$", "w", stderr, STD_ERROR_HANDLE);
+ AssignStdHandle("CONIN$", "r", stdin, STD_INPUT_HANDLE);
+ }
+}
+
+} // namespace mozilla
diff --git a/widget/windows/WindowsConsole.h b/widget/windows/WindowsConsole.h
new file mode 100644
index 0000000000..4b8ecf1823
--- /dev/null
+++ b/widget/windows/WindowsConsole.h
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_WindowsConsole_h
+#define mozilla_WindowsConsole_h
+
+namespace mozilla {
+
+// This code attaches the process to the appropriate console.
+void UseParentConsole();
+
+} // namespace mozilla
+
+#endif // mozilla_WindowsConsole_h
diff --git a/widget/windows/WindowsEMF.cpp b/widget/windows/WindowsEMF.cpp
new file mode 100644
index 0000000000..71e3631bea
--- /dev/null
+++ b/widget/windows/WindowsEMF.cpp
@@ -0,0 +1,94 @@
+/* -*- 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 "WindowsEMF.h"
+
+namespace mozilla {
+namespace widget {
+
+WindowsEMF::WindowsEMF() : mEmf(nullptr), mDC(nullptr) {}
+
+WindowsEMF::~WindowsEMF() { ReleaseAllResource(); }
+
+bool WindowsEMF::InitForDrawing(const wchar_t* aMetafilePath /* = nullptr */) {
+ ReleaseAllResource();
+
+ mDC = ::CreateEnhMetaFile(nullptr, aMetafilePath, nullptr, nullptr);
+ return !!mDC;
+}
+
+bool WindowsEMF::InitFromFileContents(const wchar_t* aMetafilePath) {
+ MOZ_ASSERT(aMetafilePath);
+ ReleaseAllResource();
+
+ mEmf = ::GetEnhMetaFileW(aMetafilePath);
+ return !!mEmf;
+}
+
+bool WindowsEMF::InitFromFileContents(LPBYTE aBytes, UINT aSize) {
+ MOZ_ASSERT(aBytes && aSize != 0);
+ ReleaseAllResource();
+
+ mEmf = SetEnhMetaFileBits(aSize, aBytes);
+
+ return !!mEmf;
+}
+
+bool WindowsEMF::FinishDocument() {
+ if (mDC) {
+ mEmf = ::CloseEnhMetaFile(mDC);
+ mDC = nullptr;
+ }
+ return !!mEmf;
+}
+
+void WindowsEMF::ReleaseEMFHandle() {
+ if (mEmf) {
+ ::DeleteEnhMetaFile(mEmf);
+ mEmf = nullptr;
+ }
+}
+
+void WindowsEMF::ReleaseAllResource() {
+ FinishDocument();
+ ReleaseEMFHandle();
+}
+
+bool WindowsEMF::Playback(HDC aDeviceContext, const RECT& aRect) {
+ DebugOnly<bool> result = FinishDocument();
+ MOZ_ASSERT(result, "This function should be used after InitXXX.");
+
+ return ::PlayEnhMetaFile(aDeviceContext, mEmf, &aRect) != 0;
+}
+
+bool WindowsEMF::SaveToFile() {
+ DebugOnly<bool> result = FinishDocument();
+ MOZ_ASSERT(result, "This function should be used after InitXXX.");
+
+ ReleaseEMFHandle();
+ return true;
+}
+
+UINT WindowsEMF::GetEMFContentSize() {
+ DebugOnly<bool> result = FinishDocument();
+ MOZ_ASSERT(result, "This function should be used after InitXXX.");
+
+ return GetEnhMetaFileBits(mEmf, 0, NULL);
+}
+
+bool WindowsEMF::GetEMFContentBits(LPBYTE aBytes) {
+ DebugOnly<bool> result = FinishDocument();
+ MOZ_ASSERT(result, "This function should be used after InitXXX.");
+
+ UINT emfSize = GetEMFContentSize();
+ if (GetEnhMetaFileBits(mEmf, emfSize, aBytes) != emfSize) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WindowsEMF.h b/widget/windows/WindowsEMF.h
new file mode 100644
index 0000000000..3a7a20173c
--- /dev/null
+++ b/widget/windows/WindowsEMF.h
@@ -0,0 +1,106 @@
+/* -*- 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_WINDOWSEMF_H
+#define MOZILLA_WIDGET_WINDOWSEMF_H
+
+/* include windows.h for the HDC definitions that we need. */
+#include <windows.h>
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * Windows Enhance Metafile: https://en.wikipedia.org/wiki/Windows_Metafile
+ * A metafile, also called a vector image, is an image that is stored as a
+ * sequence of drawing commands and settings. The commands and settings
+ * recorded in a Metafile object can be stored in memory or saved to a file.
+ *
+ * The metafile device context is used for all drawing operations required to
+ * create the picture. When the system processes a GDI function associated with
+ * a metafile DC, it converts the function into the appropriate data and stores
+ * this data in a record appended to the metafile.
+ */
+class WindowsEMF {
+ public:
+ WindowsEMF();
+ ~WindowsEMF();
+
+ /**
+ * Initializes the object with the path of a file where the EMF data stream
+ * should be stored. Callers are then expected to call GetDC() to draw output
+ * before going on to call Playback() or SaveToFile() to generate the EMF
+ * output.
+ */
+ bool InitForDrawing(const wchar_t* aMetafilePath = nullptr);
+
+ /**
+ * Initializes the object with an existing EMF file. Consumers cannot use
+ * GetDC() to obtain an HDC to modify the file. They can only use Playback().
+ */
+ bool InitFromFileContents(const wchar_t* aMetafilePath);
+
+ /**
+ * Creates the EMF from the specified data
+ *
+ * @param aByte Pointer to a buffer that contains EMF data.
+ * @param aSize Specifies the size, in bytes, of aByte.
+ */
+ bool InitFromFileContents(PBYTE aBytes, UINT aSize);
+
+ /**
+ * If this object was initiaziled using InitForDrawing() then this function
+ * returns an HDC that can be drawn to generate the EMF output. Otherwise it
+ * returns null. After finishing with the HDC, consumers could call Playback()
+ * to draw EMF onto the given DC or call SaveToFile() to finish writing the
+ * EMF file.
+ */
+ HDC GetDC() const {
+ MOZ_ASSERT(mDC,
+ "GetDC can be used only after "
+ "InitForDrawing/ InitFromFileContents and before"
+ "Playback/ SaveToFile");
+ return mDC;
+ }
+
+ /**
+ * Play the EMF's drawing commands onto the given DC.
+ */
+ bool Playback(HDC aDeviceContext, const RECT& aRect);
+
+ /**
+ * Called to generate the EMF file once a consumer has finished drawing to
+ * the HDC returned by GetDC(), if initializes the object with the path of a
+ * file.
+ */
+ bool SaveToFile();
+
+ /**
+ * Return the size of the enhanced metafile, in bytes.
+ */
+ UINT GetEMFContentSize();
+
+ /**
+ * Retrieves the contents of the EMF and copies them into a buffer.
+ *
+ * @param aByte the buffer to receive the data.
+ */
+ bool GetEMFContentBits(PBYTE aBytes);
+
+ private:
+ WindowsEMF(const WindowsEMF& aEMF) = delete;
+ bool FinishDocument();
+ void ReleaseEMFHandle();
+ void ReleaseAllResource();
+
+ /* Compiled EMF data handle. */
+ HENHMETAFILE mEmf;
+ HDC mDC;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* MOZILLA_WIDGET_WINDOWSEMF_H */
diff --git a/widget/windows/WindowsSMTCProvider.cpp b/widget/windows/WindowsSMTCProvider.cpp
new file mode 100644
index 0000000000..a20e81ed27
--- /dev/null
+++ b/widget/windows/WindowsSMTCProvider.cpp
@@ -0,0 +1,731 @@
+/* -*- 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/. */
+
+/* mingw currently doesn't support windows.media.h, so we disable
+ * the whole related class until this is fixed.
+ * @TODO: Maybe contact MinGW Team for inclusion?*/
+#ifndef __MINGW32__
+
+# include "WindowsSMTCProvider.h"
+
+# include <windows.h>
+# include <windows.media.h>
+# include <winsdkver.h>
+# include <wrl.h>
+
+# include "nsMimeTypes.h"
+# include "mozilla/Assertions.h"
+# include "mozilla/Logging.h"
+# include "mozilla/Maybe.h"
+# include "mozilla/WidgetUtils.h"
+# include "mozilla/WindowsVersion.h"
+# include "mozilla/ScopeExit.h"
+# include "mozilla/dom/MediaControlUtils.h"
+# include "mozilla/media/MediaUtils.h"
+# include "nsThreadUtils.h"
+
+# pragma comment(lib, "runtimeobject.lib")
+
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::Media;
+using namespace ABI::Windows::Storage::Streams;
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace mozilla;
+
+# ifndef RuntimeClass_Windows_Media_SystemMediaTransportControls
+# define RuntimeClass_Windows_Media_SystemMediaTransportControls \
+ L"Windows.Media.SystemMediaTransportControls"
+# endif
+
+# ifndef RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference
+# define RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference \
+ L"Windows.Storage.Streams.RandomAccessStreamReference"
+# endif
+
+# ifndef ISystemMediaTransportControlsInterop
+EXTERN_C const IID IID_ISystemMediaTransportControlsInterop;
+MIDL_INTERFACE("ddb0472d-c911-4a1f-86d9-dc3d71a95f5a")
+ISystemMediaTransportControlsInterop : public IInspectable {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE GetForWindow(
+ /* [in] */ __RPC__in HWND appWindow,
+ /* [in] */ __RPC__in REFIID riid,
+ /* [iid_is][retval][out] */
+ __RPC__deref_out_opt void** mediaTransportControl) = 0;
+};
+# endif /* __ISystemMediaTransportControlsInterop_INTERFACE_DEFINED__ */
+
+extern mozilla::LazyLogModule gMediaControlLog;
+
+# undef LOG
+# define LOG(msg, ...) \
+ MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
+ ("WindowSMTCProvider=%p, " msg, this, ##__VA_ARGS__))
+
+static inline Maybe<mozilla::dom::MediaControlKey> TranslateKeycode(
+ SystemMediaTransportControlsButton keycode) {
+ switch (keycode) {
+ case SystemMediaTransportControlsButton_Play:
+ return Some(mozilla::dom::MediaControlKey::Play);
+ case SystemMediaTransportControlsButton_Pause:
+ return Some(mozilla::dom::MediaControlKey::Pause);
+ case SystemMediaTransportControlsButton_Next:
+ return Some(mozilla::dom::MediaControlKey::Nexttrack);
+ case SystemMediaTransportControlsButton_Previous:
+ return Some(mozilla::dom::MediaControlKey::Previoustrack);
+ case SystemMediaTransportControlsButton_Stop:
+ return Some(mozilla::dom::MediaControlKey::Stop);
+ case SystemMediaTransportControlsButton_FastForward:
+ return Some(mozilla::dom::MediaControlKey::Seekforward);
+ case SystemMediaTransportControlsButton_Rewind:
+ return Some(mozilla::dom::MediaControlKey::Seekbackward);
+ default:
+ return Nothing(); // Not supported Button
+ }
+}
+
+static IAsyncInfo* GetIAsyncInfo(IAsyncOperation<unsigned int>* aAsyncOp) {
+ MOZ_ASSERT(aAsyncOp);
+ IAsyncInfo* asyncInfo;
+ HRESULT hr = aAsyncOp->QueryInterface(IID_IAsyncInfo,
+ reinterpret_cast<void**>(&asyncInfo));
+ // The assertion always works since IAsyncOperation implements IAsyncInfo
+ MOZ_ASSERT(SUCCEEDED(hr));
+ Unused << hr;
+ MOZ_ASSERT(asyncInfo);
+ return asyncInfo;
+}
+
+WindowsSMTCProvider::WindowsSMTCProvider() {
+ LOG("Creating an empty and invisible window");
+
+ // In order to create a SMTC-Provider, we need a hWnd, which shall be created
+ // dynamically from an invisible window. This leads to the following
+ // boilerplate code.
+ WNDCLASS wnd{};
+ wnd.lpszClassName = L"Firefox-MediaKeys";
+ wnd.hInstance = nullptr;
+ wnd.lpfnWndProc = DefWindowProc;
+ GetLastError(); // Clear the error
+ RegisterClass(&wnd);
+ MOZ_ASSERT(!GetLastError());
+
+ mWindow = CreateWindowExW(0, L"Firefox-MediaKeys", L"Firefox Media Keys", 0,
+ CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, nullptr,
+ nullptr, nullptr, nullptr);
+ MOZ_ASSERT(mWindow);
+ MOZ_ASSERT(!GetLastError());
+}
+
+WindowsSMTCProvider::~WindowsSMTCProvider() {
+ // Dispose the window
+ MOZ_ASSERT(mWindow);
+ if (!DestroyWindow(mWindow)) {
+ LOG("Failed to destroy the hidden window. Error Code: %d", GetLastError());
+ }
+ if (!UnregisterClass(L"Firefox-MediaKeys", nullptr)) {
+ // Note that this is logged when the class wasn't even registered.
+ LOG("Failed to unregister the class. Error Code: %d", GetLastError());
+ }
+}
+
+bool WindowsSMTCProvider::IsOpened() const { return mInitialized; }
+
+bool WindowsSMTCProvider::Open() {
+ LOG("Opening Source");
+ MOZ_ASSERT(!mInitialized);
+
+ if (!IsWin8Point1OrLater()) {
+ LOG("Windows 8.1 or later is required for Media Key Support");
+ return false;
+ }
+
+ if (!InitDisplayAndControls()) {
+ LOG("Failed to initialize the SMTC and its display");
+ return false;
+ }
+
+ if (!UpdateButtons()) {
+ LOG("Failed to initialize the buttons");
+ return false;
+ }
+
+ if (!RegisterEvents()) {
+ LOG("Failed to register SMTC key-event listener");
+ return false;
+ }
+
+ if (!EnableControl(true)) {
+ LOG("Failed to enable SMTC control");
+ return false;
+ }
+
+ mInitialized = true;
+ SetPlaybackState(mozilla::dom::MediaSessionPlaybackState::None);
+ return mInitialized;
+}
+
+void WindowsSMTCProvider::Close() {
+ MediaControlKeySource::Close();
+ // Prevent calling Set methods when init failed
+ if (mInitialized) {
+ SetPlaybackState(mozilla::dom::MediaSessionPlaybackState::None);
+ UnregisterEvents();
+ ClearMetadata();
+ // We have observed an Windows issue, if we modify `mControls` , (such as
+ // setting metadata, disable buttons) before disabling control, and those
+ // operations are not done sequentially within a same main thread task,
+ // then it would cause a problem where the SMTC wasn't clean up completely
+ // and show the executable name.
+ EnableControl(false);
+ mInitialized = false;
+ }
+}
+
+void WindowsSMTCProvider::SetPlaybackState(
+ mozilla::dom::MediaSessionPlaybackState aState) {
+ MOZ_ASSERT(mInitialized);
+ MediaControlKeySource::SetPlaybackState(aState);
+
+ HRESULT hr;
+
+ // Note: we can't return the status of put_PlaybackStatus, but we can at least
+ // assert it.
+ switch (aState) {
+ case mozilla::dom::MediaSessionPlaybackState::Paused:
+ hr = mControls->put_PlaybackStatus(
+ ABI::Windows::Media::MediaPlaybackStatus_Paused);
+ break;
+ case mozilla::dom::MediaSessionPlaybackState::Playing:
+ hr = mControls->put_PlaybackStatus(
+ ABI::Windows::Media::MediaPlaybackStatus_Playing);
+ break;
+ case mozilla::dom::MediaSessionPlaybackState::None:
+ hr = mControls->put_PlaybackStatus(
+ ABI::Windows::Media::MediaPlaybackStatus_Stopped);
+ break;
+ // MediaPlaybackStatus still supports Closed and Changing, which we don't
+ // use (yet)
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "Enum Inconsitency between PlaybackState and WindowsSMTCProvider");
+ break;
+ }
+
+ MOZ_ASSERT(SUCCEEDED(hr));
+ Unused << hr;
+}
+
+void WindowsSMTCProvider::SetMediaMetadata(
+ const mozilla::dom::MediaMetadataBase& aMetadata) {
+ MOZ_ASSERT(mInitialized);
+ SetMusicMetadata(aMetadata.mArtist.get(), aMetadata.mTitle.get(),
+ aMetadata.mAlbum.get());
+ LoadThumbnail(aMetadata.mArtwork);
+}
+
+void WindowsSMTCProvider::ClearMetadata() {
+ MOZ_ASSERT(mDisplay);
+ if (FAILED(mDisplay->ClearAll())) {
+ LOG("Failed to clear SMTC display");
+ }
+ mImageFetchRequest.DisconnectIfExists();
+ CancelPendingStoreAsyncOperation();
+ mThumbnailUrl.Truncate();
+ mProcessingUrl.Truncate();
+ mNextImageIndex = 0;
+ mSupportedKeys = 0;
+}
+
+void WindowsSMTCProvider::SetSupportedMediaKeys(
+ const MediaKeysArray& aSupportedKeys) {
+ MOZ_ASSERT(mInitialized);
+
+ uint32_t supportedKeys = 0;
+ for (const mozilla::dom::MediaControlKey& key : aSupportedKeys) {
+ supportedKeys |= GetMediaKeyMask(key);
+ }
+
+ if (supportedKeys == mSupportedKeys) {
+ LOG("Supported keys stay the same");
+ return;
+ }
+
+ LOG("Update supported keys");
+ mSupportedKeys = supportedKeys;
+ UpdateButtons();
+}
+
+void WindowsSMTCProvider::UnregisterEvents() {
+ if (mControls && mButtonPressedToken.value != 0) {
+ mControls->remove_ButtonPressed(mButtonPressedToken);
+ }
+}
+
+bool WindowsSMTCProvider::RegisterEvents() {
+ MOZ_ASSERT(mControls);
+ auto self = RefPtr<WindowsSMTCProvider>(this);
+ auto callbackbtnPressed = Callback<
+ ITypedEventHandler<SystemMediaTransportControls*,
+ SystemMediaTransportControlsButtonPressedEventArgs*>>(
+ [this, self](ISystemMediaTransportControls*,
+ ISystemMediaTransportControlsButtonPressedEventArgs* pArgs)
+ -> HRESULT {
+ MOZ_ASSERT(pArgs);
+ SystemMediaTransportControlsButton btn;
+
+ if (FAILED(pArgs->get_Button(&btn))) {
+ LOG("SystemMediaTransportControls: ButtonPressedEvent - Could "
+ "not get Button.");
+ return S_OK; // Propagating the error probably wouldn't help.
+ }
+
+ Maybe<mozilla::dom::MediaControlKey> keyCode = TranslateKeycode(btn);
+ if (keyCode.isSome() && IsOpened()) {
+ OnButtonPressed(keyCode.value());
+ }
+ return S_OK;
+ });
+
+ if (FAILED(mControls->add_ButtonPressed(callbackbtnPressed.Get(),
+ &mButtonPressedToken))) {
+ LOG("SystemMediaTransportControls: Failed at "
+ "registerEvents().add_ButtonPressed()");
+ return false;
+ }
+
+ return true;
+}
+
+void WindowsSMTCProvider::OnButtonPressed(
+ mozilla::dom::MediaControlKey aKey) const {
+ if (!IsKeySupported(aKey)) {
+ LOG("key: %s is not supported", ToMediaControlKeyStr(aKey));
+ return;
+ }
+
+ for (auto& listener : mListeners) {
+ listener->OnActionPerformed(mozilla::dom::MediaControlAction(aKey));
+ }
+}
+
+bool WindowsSMTCProvider::EnableControl(bool aEnabled) const {
+ MOZ_ASSERT(mControls);
+ return SUCCEEDED(mControls->put_IsEnabled(aEnabled));
+}
+
+bool WindowsSMTCProvider::UpdateButtons() const {
+ static const mozilla::dom::MediaControlKey kKeys[] = {
+ mozilla::dom::MediaControlKey::Play, mozilla::dom::MediaControlKey::Pause,
+ mozilla::dom::MediaControlKey::Previoustrack,
+ mozilla::dom::MediaControlKey::Nexttrack};
+
+ bool success = true;
+ for (const mozilla::dom::MediaControlKey& key : kKeys) {
+ if (!EnableKey(key, IsKeySupported(key))) {
+ success = false;
+ LOG("Failed to set %s=%s", ToMediaControlKeyStr(key),
+ IsKeySupported(key) ? "true" : "false");
+ }
+ }
+
+ return success;
+}
+
+bool WindowsSMTCProvider::IsKeySupported(
+ mozilla::dom::MediaControlKey aKey) const {
+ return mSupportedKeys & GetMediaKeyMask(aKey);
+}
+
+bool WindowsSMTCProvider::EnableKey(mozilla::dom::MediaControlKey aKey,
+ bool aEnable) const {
+ MOZ_ASSERT(mControls);
+ switch (aKey) {
+ case mozilla::dom::MediaControlKey::Play:
+ return SUCCEEDED(mControls->put_IsPlayEnabled(aEnable));
+ case mozilla::dom::MediaControlKey::Pause:
+ return SUCCEEDED(mControls->put_IsPauseEnabled(aEnable));
+ case mozilla::dom::MediaControlKey::Previoustrack:
+ return SUCCEEDED(mControls->put_IsPreviousEnabled(aEnable));
+ case mozilla::dom::MediaControlKey::Nexttrack:
+ return SUCCEEDED(mControls->put_IsNextEnabled(aEnable));
+ default:
+ LOG("No button for %s", ToMediaControlKeyStr(aKey));
+ return false;
+ }
+}
+
+bool WindowsSMTCProvider::InitDisplayAndControls() {
+ // As Open() might be called multiple times, "cache" the results of the COM
+ // API
+ if (mControls && mDisplay) {
+ return true;
+ }
+ ComPtr<ISystemMediaTransportControlsInterop> interop;
+ HRESULT hr = GetActivationFactory(
+ HStringReference(RuntimeClass_Windows_Media_SystemMediaTransportControls)
+ .Get(),
+ interop.GetAddressOf());
+ if (FAILED(hr)) {
+ LOG("SystemMediaTransportControls: Failed at instantiating the "
+ "Interop object");
+ return false;
+ }
+ MOZ_ASSERT(interop);
+
+ if (!mControls && FAILED(interop->GetForWindow(
+ mWindow, IID_PPV_ARGS(mControls.GetAddressOf())))) {
+ LOG("SystemMediaTransportControls: Failed at GetForWindow()");
+ return false;
+ }
+ MOZ_ASSERT(mControls);
+
+ if (!mDisplay &&
+ FAILED(mControls->get_DisplayUpdater(mDisplay.GetAddressOf()))) {
+ LOG("SystemMediaTransportControls: Failed at get_DisplayUpdater()");
+ }
+
+ MOZ_ASSERT(mDisplay);
+ return true;
+}
+
+bool WindowsSMTCProvider::SetMusicMetadata(const wchar_t* aArtist,
+ const wchar_t* aTitle,
+ const wchar_t* aAlbumArtist) {
+ MOZ_ASSERT(mDisplay);
+ MOZ_ASSERT(aArtist);
+ MOZ_ASSERT(aTitle);
+ MOZ_ASSERT(aAlbumArtist);
+ ComPtr<IMusicDisplayProperties> musicProps;
+
+ HRESULT hr = mDisplay->put_Type(MediaPlaybackType::MediaPlaybackType_Music);
+ MOZ_ASSERT(SUCCEEDED(hr));
+ Unused << hr;
+ hr = mDisplay->get_MusicProperties(musicProps.GetAddressOf());
+ if (FAILED(hr)) {
+ LOG("Failed to get music properties");
+ return false;
+ }
+
+ hr = musicProps->put_Artist(HStringReference(aArtist).Get());
+ if (FAILED(hr)) {
+ LOG("Failed to set the music's artist");
+ return false;
+ }
+
+ hr = musicProps->put_Title(HStringReference(aTitle).Get());
+ if (FAILED(hr)) {
+ LOG("Failed to set the music's title");
+ return false;
+ }
+
+ hr = musicProps->put_AlbumArtist(HStringReference(aAlbumArtist).Get());
+ if (FAILED(hr)) {
+ LOG("Failed to set the music's album");
+ return false;
+ }
+
+ hr = mDisplay->Update();
+ if (FAILED(hr)) {
+ LOG("Failed to refresh the display");
+ return false;
+ }
+
+ return true;
+}
+
+void WindowsSMTCProvider::LoadThumbnail(
+ const nsTArray<mozilla::dom::MediaImage>& aArtwork) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // TODO: Sort the images by the preferred size or format.
+ mArtwork = aArtwork;
+ mNextImageIndex = 0;
+
+ // Abort the loading if
+ // 1) thumbnail is being updated, and one in processing is in the artwork
+ // 2) thumbnail is not being updated, and one in use is in the artwork
+ if (!mProcessingUrl.IsEmpty()) {
+ LOG("Load thumbnail while image: %s is being processed",
+ NS_ConvertUTF16toUTF8(mProcessingUrl).get());
+ if (mozilla::dom::IsImageIn(mArtwork, mProcessingUrl)) {
+ LOG("No need to load thumbnail. The one being processed is in the "
+ "artwork");
+ return;
+ }
+ } else if (!mThumbnailUrl.IsEmpty()) {
+ if (mozilla::dom::IsImageIn(mArtwork, mThumbnailUrl)) {
+ LOG("No need to load thumbnail. The one in use is in the artwork");
+ return;
+ }
+ }
+
+ // If there is a pending image store operation, that image must be different
+ // from the new image will be loaded below, so the pending one should be
+ // cancelled.
+ CancelPendingStoreAsyncOperation();
+ // Remove the current thumbnail on the interface
+ ClearThumbnail();
+ // Then load the new thumbnail asynchronously
+ LoadImageAtIndex(mNextImageIndex++);
+}
+
+void WindowsSMTCProvider::LoadImageAtIndex(const size_t aIndex) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aIndex >= mArtwork.Length()) {
+ LOG("Stop loading thumbnail. No more available images");
+ mImageFetchRequest.DisconnectIfExists();
+ mProcessingUrl.Truncate();
+ return;
+ }
+
+ const mozilla::dom::MediaImage& image = mArtwork[aIndex];
+
+ // TODO: No need to fetch the default image and do image processing since the
+ // the default image is local file and it's trustworthy. For the default
+ // image, we can use `CreateFromFile` to create the IRandomAccessStream. We
+ // should probably cache it since it could be used very often (Bug 1643102)
+
+ if (!mozilla::dom::IsValidImageUrl(image.mSrc)) {
+ LOG("Skip the image with invalid URL. Try next image");
+ mImageFetchRequest.DisconnectIfExists();
+ LoadImageAtIndex(mNextImageIndex++);
+ return;
+ }
+
+ mImageFetchRequest.DisconnectIfExists();
+ mProcessingUrl = image.mSrc;
+
+ mImageFetcher = mozilla::MakeUnique<mozilla::dom::FetchImageHelper>(image);
+ RefPtr<WindowsSMTCProvider> self = this;
+ mImageFetcher->FetchImage()
+ ->Then(
+ AbstractThread::MainThread(), __func__,
+ [this, self](const nsCOMPtr<imgIContainer>& aImage) {
+ LOG("The image is fetched successfully");
+ mImageFetchRequest.Complete();
+
+ // Although IMAGE_JPEG or IMAGE_BMP are valid types as well, but a
+ // png image with transparent background will be converted into a
+ // jpeg/bmp file with a colored background. IMAGE_PNG format seems
+ // to be the best choice for now.
+ uint32_t size = 0;
+ char* src = nullptr;
+ // Only used to hold the image data
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = mozilla::dom::GetEncodedImageBuffer(
+ aImage, nsLiteralCString(IMAGE_PNG),
+ getter_AddRefs(inputStream), &size, &src);
+ if (NS_FAILED(rv) || !inputStream || size == 0 || !src) {
+ LOG("Failed to get the image buffer info. Try next image");
+ LoadImageAtIndex(mNextImageIndex++);
+ return;
+ }
+
+ LoadImage(src, size);
+ },
+ [this, self](bool) {
+ LOG("Failed to fetch image. Try next image");
+ mImageFetchRequest.Complete();
+ LoadImageAtIndex(mNextImageIndex++);
+ })
+ ->Track(mImageFetchRequest);
+}
+
+void WindowsSMTCProvider::LoadImage(const char* aImageData,
+ uint32_t aDataSize) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // 1. Use mImageDataWriter to write the binary data of image into mImageStream
+ // 2. Refer the image by mImageStreamReference and then set it to the SMTC
+ // In case of the race condition between they are being destroyed and the
+ // async operation for image loading, mImageDataWriter, mImageStream, and
+ // mImageStreamReference are member variables
+
+ HRESULT hr = ActivateInstance(
+ HStringReference(
+ RuntimeClass_Windows_Storage_Streams_InMemoryRandomAccessStream)
+ .Get(),
+ mImageStream.GetAddressOf());
+ if (FAILED(hr)) {
+ LOG("Failed to make mImageStream refer to an instance of "
+ "InMemoryRandomAccessStream");
+ return;
+ }
+
+ ComPtr<IOutputStream> outputStream;
+ hr = mImageStream.As(&outputStream);
+ if (FAILED(hr)) {
+ LOG("Failed when query IOutputStream interface from mImageStream");
+ return;
+ }
+
+ ComPtr<IDataWriterFactory> dataWriterFactory;
+ hr = GetActivationFactory(
+ HStringReference(RuntimeClass_Windows_Storage_Streams_DataWriter).Get(),
+ dataWriterFactory.GetAddressOf());
+ if (FAILED(hr)) {
+ LOG("Failed to get an activation factory for IDataWriterFactory");
+ return;
+ }
+
+ hr = dataWriterFactory->CreateDataWriter(outputStream.Get(),
+ mImageDataWriter.GetAddressOf());
+ if (FAILED(hr)) {
+ LOG("Failed to create mImageDataWriter that writes data to mImageStream");
+ return;
+ }
+
+ hr = mImageDataWriter->WriteBytes(
+ aDataSize, reinterpret_cast<BYTE*>(const_cast<char*>(aImageData)));
+ if (FAILED(hr)) {
+ LOG("Failed to write data to mImageStream");
+ return;
+ }
+
+ hr = mImageDataWriter->StoreAsync(&mStoreAsyncOperation);
+ if (FAILED(hr)) {
+ LOG("Failed to create a DataWriterStoreOperation for mStoreAsyncOperation");
+ return;
+ }
+
+ // Upon the image is stored in mImageStream, set the image to the SMTC
+ // interface
+ auto onStoreCompleted = Callback<
+ IAsyncOperationCompletedHandler<unsigned int>>(
+ [this, self = RefPtr<WindowsSMTCProvider>(this),
+ aImageUrl = nsString(mProcessingUrl)](
+ IAsyncOperation<unsigned int>* aAsyncOp, AsyncStatus aStatus) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aStatus != AsyncStatus::Completed) {
+ LOG("Asynchronous operation is not completed");
+ return E_ABORT;
+ }
+
+ HRESULT hr = S_OK;
+ IAsyncInfo* asyncInfo = GetIAsyncInfo(aAsyncOp);
+ asyncInfo->get_ErrorCode(&hr);
+ if (FAILED(hr)) {
+ LOG("Failed to get termination status of the asynchronous operation");
+ return hr;
+ }
+
+ if (!UpdateThumbnail(aImageUrl)) {
+ LOG("Failed to update thumbnail");
+ }
+
+ // If an error occurs above:
+ // - If aImageUrl is not mProcessingUrl. It's fine.
+ // - If aImageUrl is mProcessingUrl, then mProcessingUrl won't be reset.
+ // Therefore the thumbnail will remain empty until a new image whose
+ // url is different from mProcessingUrl is loaded.
+
+ return S_OK;
+ });
+
+ hr = mStoreAsyncOperation->put_Completed(onStoreCompleted.Get());
+ if (FAILED(hr)) {
+ LOG("Failed to set callback on completeing the asynchronous operation");
+ }
+}
+
+bool WindowsSMTCProvider::SetThumbnail(const nsAString& aUrl) {
+ MOZ_ASSERT(mDisplay);
+ MOZ_ASSERT(mImageStream);
+ MOZ_ASSERT(!aUrl.IsEmpty());
+
+ ComPtr<IRandomAccessStreamReferenceStatics> streamRefFactory;
+
+ HRESULT hr = GetActivationFactory(
+ HStringReference(
+ RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference)
+ .Get(),
+ streamRefFactory.GetAddressOf());
+ auto cleanup =
+ MakeScopeExit([this, self = RefPtr<WindowsSMTCProvider>(this)] {
+ LOG("Clean mThumbnailUrl");
+ mThumbnailUrl.Truncate();
+ });
+
+ if (FAILED(hr)) {
+ LOG("Failed to get an activation factory for "
+ "IRandomAccessStreamReferenceStatics type");
+ return false;
+ }
+
+ hr = streamRefFactory->CreateFromStream(mImageStream.Get(),
+ mImageStreamReference.GetAddressOf());
+ if (FAILED(hr)) {
+ LOG("Failed to create mImageStreamReference from mImageStream");
+ return false;
+ }
+
+ hr = mDisplay->put_Thumbnail(mImageStreamReference.Get());
+ if (FAILED(hr)) {
+ LOG("Failed to update thumbnail");
+ return false;
+ }
+
+ hr = mDisplay->Update();
+ if (FAILED(hr)) {
+ LOG("Failed to refresh display");
+ return false;
+ }
+
+ // No need to clean mThumbnailUrl since thumbnail is set successfully
+ cleanup.release();
+ mThumbnailUrl = aUrl;
+
+ return true;
+}
+
+void WindowsSMTCProvider::ClearThumbnail() {
+ MOZ_ASSERT(mDisplay);
+ HRESULT hr = mDisplay->put_Thumbnail(nullptr);
+ MOZ_ASSERT(SUCCEEDED(hr));
+ hr = mDisplay->Update();
+ MOZ_ASSERT(SUCCEEDED(hr));
+ Unused << hr;
+ mThumbnailUrl.Truncate();
+}
+
+bool WindowsSMTCProvider::UpdateThumbnail(const nsAString& aUrl) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsOpened()) {
+ LOG("Abort the thumbnail update: SMTC is closed");
+ return false;
+ }
+
+ if (aUrl != mProcessingUrl) {
+ LOG("Abort the thumbnail update: The image from %s is out of date",
+ NS_ConvertUTF16toUTF8(aUrl).get());
+ return false;
+ }
+
+ mProcessingUrl.Truncate();
+
+ if (!SetThumbnail(aUrl)) {
+ LOG("Failed to update thumbnail");
+ return false;
+ }
+
+ MOZ_ASSERT(mThumbnailUrl == aUrl);
+ LOG("The thumbnail is updated to the image from: %s",
+ NS_ConvertUTF16toUTF8(mThumbnailUrl).get());
+ return true;
+}
+
+void WindowsSMTCProvider::CancelPendingStoreAsyncOperation() const {
+ if (mStoreAsyncOperation) {
+ IAsyncInfo* asyncInfo = GetIAsyncInfo(mStoreAsyncOperation.Get());
+ asyncInfo->Cancel();
+ }
+}
+
+#endif // __MINGW32__
diff --git a/widget/windows/WindowsSMTCProvider.h b/widget/windows/WindowsSMTCProvider.h
new file mode 100644
index 0000000000..c46434e03e
--- /dev/null
+++ b/widget/windows/WindowsSMTCProvider.h
@@ -0,0 +1,129 @@
+/* -*- 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 WIDGET_WINDOWS_WINDOWSSTMCPROVIDER_H_
+#define WIDGET_WINDOWS_WINDOWSSTMCPROVIDER_H_
+
+#ifndef __MINGW32__
+
+# include <functional>
+# include <Windows.Media.h>
+# include <wrl.h>
+
+# include "mozilla/dom/FetchImageHelper.h"
+# include "mozilla/dom/MediaController.h"
+# include "mozilla/dom/MediaControlKeySource.h"
+# include "mozilla/UniquePtr.h"
+
+using ISMTC = ABI::Windows::Media::ISystemMediaTransportControls;
+using SMTCProperty = ABI::Windows::Media::SystemMediaTransportControlsProperty;
+using ISMTCDisplayUpdater =
+ ABI::Windows::Media::ISystemMediaTransportControlsDisplayUpdater;
+
+using ABI::Windows::Foundation::IAsyncOperation;
+using ABI::Windows::Storage::Streams::IDataWriter;
+using ABI::Windows::Storage::Streams::IRandomAccessStream;
+using ABI::Windows::Storage::Streams::IRandomAccessStreamReference;
+using Microsoft::WRL::ComPtr;
+
+class WindowsSMTCProvider final : public mozilla::dom::MediaControlKeySource {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WindowsSMTCProvider, override)
+
+ public:
+ WindowsSMTCProvider();
+
+ bool IsOpened() const override;
+ bool Open() override;
+ void Close() override;
+
+ void SetPlaybackState(
+ mozilla::dom::MediaSessionPlaybackState aState) override;
+
+ void SetMediaMetadata(
+ const mozilla::dom::MediaMetadataBase& aMetadata) override;
+
+ void SetSupportedMediaKeys(const MediaKeysArray& aSupportedKeys) override;
+
+ private:
+ ~WindowsSMTCProvider();
+ void UnregisterEvents();
+ bool RegisterEvents();
+
+ void OnButtonPressed(mozilla::dom::MediaControlKey aKey) const;
+ // Enable the SMTC interface
+ bool EnableControl(bool aEnabled) const;
+ // Sets the play, pause, next, previous buttons on the SMTC interface by
+ // mSupportedKeys
+ bool UpdateButtons() const;
+ bool IsKeySupported(mozilla::dom::MediaControlKey aKey) const;
+ bool EnableKey(mozilla::dom::MediaControlKey aKey, bool aEnable) const;
+
+ bool InitDisplayAndControls();
+
+ // Sets the Metadata for the currently playing media and sets the playback
+ // type to "MUSIC"
+ bool SetMusicMetadata(const wchar_t* aArtist, const wchar_t* aTitle,
+ const wchar_t* aAlbumArtist);
+
+ // Sets one of the artwork to the SMTC interface asynchronously
+ void LoadThumbnail(const nsTArray<mozilla::dom::MediaImage>& aArtwork);
+ // Stores the image at index aIndex of the mArtwork to the Thumbnail
+ // asynchronously
+ void LoadImageAtIndex(const size_t aIndex);
+ // Stores the raw binary data of an image to mImageStream and set it to the
+ // Thumbnail asynchronously
+ void LoadImage(const char* aImageData, uint32_t aDataSize);
+ // Sets the Thumbnail to the image stored in mImageStream
+ bool SetThumbnail(const nsAString& aUrl);
+ void ClearThumbnail();
+
+ bool UpdateThumbnail(const nsAString& aUrl);
+ void CancelPendingStoreAsyncOperation() const;
+
+ void ClearMetadata();
+
+ bool mInitialized = false;
+
+ // A bit table indicating what keys are enabled
+ uint32_t mSupportedKeys = 0;
+
+ ComPtr<ISMTC> mControls;
+ ComPtr<ISMTCDisplayUpdater> mDisplay;
+
+ // Use mImageDataWriter to write the binary data of image into mImageStream
+ // and refer the image by mImageStreamReference and then set it to the SMTC
+ // interface
+ ComPtr<IDataWriter> mImageDataWriter;
+ ComPtr<IRandomAccessStream> mImageStream;
+ ComPtr<IRandomAccessStreamReference> mImageStreamReference;
+ ComPtr<IAsyncOperation<unsigned int>> mStoreAsyncOperation;
+
+ // mThumbnailUrl is the url of the current Thumbnail
+ // mProcessingUrl is the url that is being processed. The process starts from
+ // fetching an image from the url and then storing the fetched image to the
+ // mImageStream. If mProcessingUrl is not empty, it means there is an image is
+ // in processing
+ // mThumbnailUrl and mProcessingUrl won't be set at the same time and they can
+ // only be touched on main thread
+ nsString mThumbnailUrl;
+ nsString mProcessingUrl;
+
+ // mArtwork can only be used in main thread in case of data racing
+ CopyableTArray<mozilla::dom::MediaImage> mArtwork;
+ size_t mNextImageIndex;
+
+ mozilla::UniquePtr<mozilla::dom::FetchImageHelper> mImageFetcher;
+ mozilla::MozPromiseRequestHolder<mozilla::dom::ImagePromise>
+ mImageFetchRequest;
+
+ HWND mWindow; // handle to the invisible window
+
+ // EventRegistrationTokens are used to have a handle on a callback (to remove
+ // it again)
+ EventRegistrationToken mButtonPressedToken;
+};
+
+#endif // __MINGW32__
+#endif // WIDGET_WINDOWS_WINDOWSSTMCPROVIDER_H_
diff --git a/widget/windows/WindowsUIUtils.cpp b/widget/windows/WindowsUIUtils.cpp
new file mode 100644
index 0000000000..a61dc245de
--- /dev/null
+++ b/widget/windows/WindowsUIUtils.cpp
@@ -0,0 +1,461 @@
+/* -*- 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 <windows.h>
+#include <winsdkver.h>
+#include <wrl.h>
+
+#include "nsServiceManagerUtils.h"
+
+#include "WindowsUIUtils.h"
+
+#include "nsIObserverService.h"
+#include "nsIAppShellService.h"
+#include "nsAppShellCID.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/Services.h"
+#include "mozilla/WidgetUtils.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/media/MediaUtils.h"
+#include "nsString.h"
+#include "nsIWidget.h"
+#include "nsIWindowMediator.h"
+#include "nsPIDOMWindow.h"
+#include "nsWindowGfx.h"
+#include "Units.h"
+
+/* mingw currently doesn't support windows.ui.viewmanagement.h, so we disable it
+ * until it's fixed. */
+#ifndef __MINGW32__
+
+# include <windows.ui.viewmanagement.h>
+
+# pragma comment(lib, "runtimeobject.lib")
+
+using namespace ABI::Windows::UI;
+using namespace ABI::Windows::UI::ViewManagement;
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::ApplicationModel::DataTransfer;
+
+/* All of this is win10 stuff and we're compiling against win81 headers
+ * for now, so we may need to do some legwork: */
+# if WINVER_MAXVER < 0x0A00
+namespace ABI {
+namespace Windows {
+namespace UI {
+namespace ViewManagement {
+enum UserInteractionMode {
+ UserInteractionMode_Mouse = 0,
+ UserInteractionMode_Touch = 1
+};
+}
+} // namespace UI
+} // namespace Windows
+} // namespace ABI
+
+# endif
+
+# ifndef RuntimeClass_Windows_UI_ViewManagement_UIViewSettings
+# define RuntimeClass_Windows_UI_ViewManagement_UIViewSettings \
+ L"Windows.UI.ViewManagement.UIViewSettings"
+# endif
+
+# if WINVER_MAXVER < 0x0A00
+namespace ABI {
+namespace Windows {
+namespace UI {
+namespace ViewManagement {
+interface IUIViewSettings;
+MIDL_INTERFACE("C63657F6-8850-470D-88F8-455E16EA2C26")
+IUIViewSettings : public IInspectable {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE get_UserInteractionMode(
+ UserInteractionMode * value) = 0;
+};
+
+extern const __declspec(selectany) IID& IID_IUIViewSettings =
+ __uuidof(IUIViewSettings);
+} // namespace ViewManagement
+} // namespace UI
+} // namespace Windows
+} // namespace ABI
+# endif
+
+# ifndef IUIViewSettingsInterop
+
+typedef interface IUIViewSettingsInterop IUIViewSettingsInterop;
+
+MIDL_INTERFACE("3694dbf9-8f68-44be-8ff5-195c98ede8a6")
+IUIViewSettingsInterop : public IInspectable {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE GetForWindow(HWND hwnd, REFIID riid,
+ void** ppv) = 0;
+};
+# endif
+
+# ifndef __IDataTransferManagerInterop_INTERFACE_DEFINED__
+# define __IDataTransferManagerInterop_INTERFACE_DEFINED__
+
+typedef interface IDataTransferManagerInterop IDataTransferManagerInterop;
+
+MIDL_INTERFACE("3A3DCD6C-3EAB-43DC-BCDE-45671CE800C8")
+IDataTransferManagerInterop : public IUnknown {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE GetForWindow(
+ HWND appWindow, REFIID riid, void** dataTransferManager) = 0;
+ virtual HRESULT STDMETHODCALLTYPE ShowShareUIForWindow(HWND appWindow) = 0;
+};
+
+# endif
+
+# if !defined( \
+ ____x_ABI_CWindows_CApplicationModel_CDataTransfer_CIDataPackage4_INTERFACE_DEFINED__)
+# define ____x_ABI_CWindows_CApplicationModel_CDataTransfer_CIDataPackage4_INTERFACE_DEFINED__
+
+MIDL_INTERFACE("13a24ec8-9382-536f-852a-3045e1b29a3b")
+IDataPackage4 : public IInspectable {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE add_ShareCanceled(
+ __FITypedEventHandler_2_Windows__CApplicationModel__CDataTransfer__CDataPackage_IInspectable *
+ handler,
+ EventRegistrationToken * token) = 0;
+ virtual HRESULT STDMETHODCALLTYPE remove_ShareCanceled(
+ EventRegistrationToken token) = 0;
+};
+
+# endif
+
+#endif
+
+using namespace mozilla;
+
+WindowsUIUtils::WindowsUIUtils() : mInTabletMode(eTabletModeUnknown) {}
+
+WindowsUIUtils::~WindowsUIUtils() {}
+
+/*
+ * Implement the nsISupports methods...
+ */
+NS_IMPL_ISUPPORTS(WindowsUIUtils, nsIWindowsUIUtils)
+
+NS_IMETHODIMP
+WindowsUIUtils::GetSystemSmallIconSize(int32_t* aSize) {
+ NS_ENSURE_ARG(aSize);
+
+ mozilla::LayoutDeviceIntSize size =
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon);
+ *aSize = std::max(size.width, size.height);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WindowsUIUtils::GetSystemLargeIconSize(int32_t* aSize) {
+ NS_ENSURE_ARG(aSize);
+
+ mozilla::LayoutDeviceIntSize size =
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon);
+ *aSize = std::max(size.width, size.height);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WindowsUIUtils::SetWindowIcon(mozIDOMWindowProxy* aWindow,
+ imgIContainer* aSmallIcon,
+ imgIContainer* aBigIcon) {
+ NS_ENSURE_ARG(aWindow);
+
+ nsCOMPtr<nsIWidget> widget =
+ nsGlobalWindowOuter::Cast(aWindow)->GetMainWidget();
+ nsWindow* window = static_cast<nsWindow*>(widget.get());
+
+ nsresult rv;
+
+ if (aSmallIcon) {
+ HICON hIcon = nullptr;
+ rv = nsWindowGfx::CreateIcon(
+ aSmallIcon, false, mozilla::LayoutDeviceIntPoint(),
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), &hIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ window->SetSmallIcon(hIcon);
+ }
+
+ if (aBigIcon) {
+ HICON hIcon = nullptr;
+ rv = nsWindowGfx::CreateIcon(
+ aBigIcon, false, mozilla::LayoutDeviceIntPoint(),
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon), &hIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ window->SetBigIcon(hIcon);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WindowsUIUtils::GetInTabletMode(bool* aResult) {
+ if (mInTabletMode == eTabletModeUnknown) {
+ UpdateTabletModeState();
+ }
+ *aResult = mInTabletMode == eTabletModeOn;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WindowsUIUtils::UpdateTabletModeState() {
+#ifndef __MINGW32__
+ if (!IsWin10OrLater()) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIWindowMediator> winMediator(
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIWidget> widget;
+ nsCOMPtr<mozIDOMWindowProxy> navWin;
+
+ rv = winMediator->GetMostRecentWindow(u"navigator:browser",
+ getter_AddRefs(navWin));
+ if (NS_FAILED(rv) || !navWin) {
+ // Fall back to the hidden window
+ nsCOMPtr<nsIAppShellService> appShell(
+ do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+
+ rv = appShell->GetHiddenDOMWindow(getter_AddRefs(navWin));
+ if (NS_FAILED(rv) || !navWin) {
+ return rv;
+ }
+ }
+
+ nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin);
+ widget = widget::WidgetUtils::DOMWindowToWidget(win);
+
+ if (!widget) return NS_ERROR_FAILURE;
+
+ HWND winPtr = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW);
+ ComPtr<IUIViewSettingsInterop> uiViewSettingsInterop;
+
+ HRESULT hr = GetActivationFactory(
+ HStringReference(RuntimeClass_Windows_UI_ViewManagement_UIViewSettings)
+ .Get(),
+ &uiViewSettingsInterop);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IUIViewSettings> uiViewSettings;
+ hr = uiViewSettingsInterop->GetForWindow(winPtr,
+ IID_PPV_ARGS(&uiViewSettings));
+ if (SUCCEEDED(hr)) {
+ UserInteractionMode mode;
+ hr = uiViewSettings->get_UserInteractionMode(&mode);
+ if (SUCCEEDED(hr)) {
+ TabletModeState oldTabletModeState = mInTabletMode;
+ mInTabletMode = (mode == UserInteractionMode_Touch) ? eTabletModeOn
+ : eTabletModeOff;
+ if (mInTabletMode != oldTabletModeState) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ observerService->NotifyObservers(
+ nullptr, "tablet-mode-change",
+ ((mInTabletMode == eTabletModeOn) ? u"tablet-mode"
+ : u"normal-mode"));
+ }
+ }
+ }
+ }
+#endif
+
+ return NS_OK;
+}
+
+#ifndef __MINGW32__
+struct HStringDeleter {
+ typedef HSTRING pointer;
+ void operator()(pointer aString) { WindowsDeleteString(aString); }
+};
+
+typedef UniquePtr<HSTRING, HStringDeleter> HStringUniquePtr;
+
+Result<HStringUniquePtr, HRESULT> ConvertToWindowsString(
+ const nsAString& aStr) {
+ HSTRING rawStr;
+ HRESULT hr = WindowsCreateString(PromiseFlatString(aStr).get(), aStr.Length(),
+ &rawStr);
+ if (FAILED(hr)) {
+ return Err(hr);
+ }
+ return HStringUniquePtr(rawStr);
+}
+
+Result<Ok, nsresult> RequestShare(
+ const std::function<HRESULT(IDataRequestedEventArgs* pArgs)>& aCallback) {
+ if (!IsWin10OrLater()) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ HWND hwnd = GetForegroundWindow();
+ if (!hwnd) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ ComPtr<IDataTransferManagerInterop> dtmInterop;
+ ComPtr<IDataTransferManager> dtm;
+
+ HRESULT hr = RoGetActivationFactory(
+ HStringReference(
+ RuntimeClass_Windows_ApplicationModel_DataTransfer_DataTransferManager)
+ .Get(),
+ IID_PPV_ARGS(&dtmInterop));
+ if (FAILED(hr) ||
+ FAILED(dtmInterop->GetForWindow(hwnd, IID_PPV_ARGS(&dtm)))) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ auto callback = Callback<
+ ITypedEventHandler<DataTransferManager*, DataRequestedEventArgs*>>(
+ [aCallback](IDataTransferManager*,
+ IDataRequestedEventArgs* pArgs) -> HRESULT {
+ return aCallback(pArgs);
+ });
+
+ EventRegistrationToken dataRequestedToken;
+ if (FAILED(dtm->add_DataRequested(callback.Get(), &dataRequestedToken)) ||
+ FAILED(dtmInterop->ShowShareUIForWindow(hwnd))) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ return Ok();
+}
+#endif
+
+RefPtr<SharePromise> WindowsUIUtils::Share(nsAutoString aTitle,
+ nsAutoString aText,
+ nsAutoString aUrl) {
+ auto promiseHolder = MakeRefPtr<
+ mozilla::media::Refcountable<MozPromiseHolder<SharePromise>>>();
+ RefPtr<SharePromise> promise = promiseHolder->Ensure(__func__);
+
+#ifndef __MINGW32__
+ auto result = RequestShare([promiseHolder, title = std::move(aTitle),
+ text = std::move(aText), url = std::move(aUrl)](
+ IDataRequestedEventArgs* pArgs) {
+ ComPtr<IDataRequest> spDataRequest;
+ ComPtr<IDataPackage> spDataPackage;
+ ComPtr<IDataPackage2> spDataPackage2;
+ ComPtr<IDataPackage3> spDataPackage3;
+ ComPtr<IDataPackagePropertySet> spDataPackageProperties;
+
+ if (FAILED(pArgs->get_Request(&spDataRequest)) ||
+ FAILED(spDataRequest->get_Data(&spDataPackage)) ||
+ FAILED(spDataPackage.As(&spDataPackage2)) ||
+ FAILED(spDataPackage.As(&spDataPackage3)) ||
+ FAILED(spDataPackage->get_Properties(&spDataPackageProperties))) {
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+ return E_FAIL;
+ }
+
+ /*
+ * Windows always requires a title, and an empty string does not work.
+ * Thus we trick the API by passing a whitespace when we have no title.
+ * https://docs.microsoft.com/en-us/windows/uwp/app-to-app/share-data
+ */
+ auto wTitle = ConvertToWindowsString((title.IsVoid() || title.Length() == 0)
+ ? nsAutoString(u" "_ns)
+ : title);
+ if (wTitle.isErr() ||
+ FAILED(spDataPackageProperties->put_Title(wTitle.unwrap().get()))) {
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+ return E_FAIL;
+ }
+
+ // Assign even if empty, as Windows requires some data to share
+ auto wText = ConvertToWindowsString(text);
+ if (wText.isErr() || FAILED(spDataPackage->SetText(wText.unwrap().get()))) {
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+ return E_FAIL;
+ }
+
+ if (!url.IsVoid()) {
+ auto wUrl = ConvertToWindowsString(url);
+ if (wUrl.isErr()) {
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+ return wUrl.unwrapErr();
+ }
+
+ ComPtr<IUriRuntimeClassFactory> uriFactory;
+ ComPtr<IUriRuntimeClass> uri;
+
+ auto hr = GetActivationFactory(
+ HStringReference(RuntimeClass_Windows_Foundation_Uri).Get(),
+ &uriFactory);
+
+ if (FAILED(hr) ||
+ FAILED(uriFactory->CreateUri(wUrl.unwrap().get(), &uri)) ||
+ FAILED(spDataPackage2->SetWebLink(uri.Get()))) {
+ promiseHolder->RejectIfExists(NS_ERROR_FAILURE, __func__);
+ return E_FAIL;
+ }
+ }
+
+ auto completedCallback =
+ Callback<ITypedEventHandler<DataPackage*, ShareCompletedEventArgs*>>(
+ [promiseHolder](IDataPackage*,
+ IShareCompletedEventArgs*) -> HRESULT {
+ promiseHolder->ResolveIfExists(true, __func__);
+ return S_OK;
+ });
+
+ EventRegistrationToken dataRequestedToken;
+ if (FAILED(spDataPackage3->add_ShareCompleted(completedCallback.Get(),
+ &dataRequestedToken))) {
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+ return E_FAIL;
+ }
+
+ ComPtr<IDataPackage4> spDataPackage4;
+ if (SUCCEEDED(spDataPackage.As(&spDataPackage4))) {
+ // Use SharedCanceled API only on supported versions of Windows
+ // So that the older ones can still use ShareUrl()
+
+ auto canceledCallback =
+ Callback<ITypedEventHandler<DataPackage*, IInspectable*>>(
+ [promiseHolder](IDataPackage*, IInspectable*) -> HRESULT {
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+ return S_OK;
+ });
+
+ if (FAILED(spDataPackage4->add_ShareCanceled(canceledCallback.Get(),
+ &dataRequestedToken))) {
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+ return E_FAIL;
+ }
+ }
+
+ return S_OK;
+ });
+ if (result.isErr()) {
+ promiseHolder->Reject(result.unwrapErr(), __func__);
+ }
+#else
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+#endif
+
+ return promise;
+}
+
+NS_IMETHODIMP
+WindowsUIUtils::ShareUrl(const nsAString& aUrlToShare,
+ const nsAString& aShareTitle) {
+ nsAutoString text;
+ text.SetIsVoid(true);
+ WindowsUIUtils::Share(nsAutoString(aShareTitle), text,
+ nsAutoString(aUrlToShare));
+ return NS_OK;
+}
diff --git a/widget/windows/WindowsUIUtils.h b/widget/windows/WindowsUIUtils.h
new file mode 100644
index 0000000000..b8743c5538
--- /dev/null
+++ b/widget/windows/WindowsUIUtils.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/. */
+
+#ifndef mozilla_widget_WindowsUIUtils_h__
+#define mozilla_widget_WindowsUIUtils_h__
+
+#include "nsIWindowsUIUtils.h"
+#include "nsString.h"
+#include "mozilla/MozPromise.h"
+
+typedef mozilla::MozPromise<bool, nsresult, /* IsExclusive */ true>
+ SharePromise;
+
+enum TabletModeState { eTabletModeUnknown = 0, eTabletModeOff, eTabletModeOn };
+
+class WindowsUIUtils final : public nsIWindowsUIUtils {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWINDOWSUIUTILS
+
+ WindowsUIUtils();
+
+ static RefPtr<SharePromise> Share(nsAutoString aTitle, nsAutoString aText,
+ nsAutoString aUrl);
+
+ protected:
+ ~WindowsUIUtils();
+
+ TabletModeState mInTabletMode;
+};
+
+#endif // mozilla_widget_WindowsUIUtils_h__
diff --git a/widget/windows/components.conf b/widget/windows/components.conf
new file mode 100644
index 0000000000..c37637445b
--- /dev/null
+++ b/widget/windows/components.conf
@@ -0,0 +1,219 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Headers = [
+ '/widget/windows/nsWidgetFactory.h',
+]
+
+InitFunc = 'nsWidgetWindowsModuleCtor'
+UnloadFunc = 'nsWidgetWindowsModuleDtor'
+
+Classes = [
+ {
+ 'cid': '{c401eb80-f9ea-11d3-bb6f-e732b73ebe7c}',
+ 'contract_ids': ['@mozilla.org/gfx/screenmanager;1'],
+ 'singleton': True,
+ 'type': 'mozilla::widget::ScreenManager',
+ 'constructor': 'mozilla::widget::ScreenManager::GetAddRefedSingleton',
+ 'headers': ['/widget/ScreenManager.h'],
+ 'processes': ProcessSelector.ALLOW_IN_GPU_AND_MAIN_PROCESS,
+ },
+ {
+ 'cid': '{2d96b3df-c051-11d1-a827-0040959a28c9}',
+ 'contract_ids': ['@mozilla.org/widget/appshell/win;1'],
+ 'headers': ['/widget/windows/nsWidgetFactory.h'],
+ 'legacy_constructor': 'nsAppShellConstructor',
+ 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{6987230e-0098-4e78-bc5f-1493ee7519fa}',
+ 'contract_ids': ['@mozilla.org/widget/useridleservice;1'],
+ 'singleton': True,
+ 'type': 'nsUserIdleServiceWin',
+ 'constructor': 'nsUserIdleServiceWin::GetInstance',
+ 'headers': ['/widget/windows/nsUserIdleServiceWin.h', 'nsUserIdleService.h'],
+ },
+ {
+ 'cid': '{b148eed2-236d-11d3-b35c-00a0cc3c1cde}',
+ 'contract_ids': ['@mozilla.org/sound;1'],
+ 'singleton': True,
+ 'type': 'nsISound',
+ 'constructor': 'nsSound::GetInstance',
+ 'headers': ['/widget/windows/nsSound.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{77221d5a-1dd2-11b2-8c69-c710f15d2ed5}',
+ 'contract_ids': ['@mozilla.org/widget/clipboardhelper;1'],
+ 'type': 'nsClipboardHelper',
+ 'headers': ['/widget/nsClipboardHelper.h'],
+ },
+ {
+ 'cid': '{b8e5bc54-a22f-4eb2-b061-24cb6d19c15f}',
+ 'contract_ids': ['@mozilla.org/windows-taskbar;1'],
+ 'type': 'mozilla::widget::WinTaskbar',
+ 'headers': ['/widget/windows/WinTaskbar.h'],
+ },
+ {
+ 'cid': '{73a5946f-608d-454f-9d33-0b8f8c7294b6}',
+ 'contract_ids': ['@mozilla.org/windows-jumplistbuilder;1'],
+ 'type': 'mozilla::widget::JumpListBuilder',
+ 'headers': ['/widget/windows/JumpListBuilder.h'],
+ },
+ {
+ 'cid': '{2b9a1f2c-27ce-45b6-8d4e-755d0e34f8db}',
+ 'contract_ids': ['@mozilla.org/windows-jumplistitem;1'],
+ 'type': 'mozilla::widget::JumpListItem',
+ 'headers': ['/widget/windows/JumpListItem.h'],
+ },
+ {
+ 'cid': '{21f1f13b-f75a-42ad-867a-d91ad694447e}',
+ 'contract_ids': ['@mozilla.org/windows-jumplistseparator;1'],
+ 'type': 'mozilla::widget::JumpListSeparator',
+ 'headers': ['/widget/windows/JumpListItem.h'],
+ },
+ {
+ 'cid': '{f72c5dc4-5a12-47be-be28-ab105f33b08f}',
+ 'contract_ids': ['@mozilla.org/windows-jumplistlink;1'],
+ 'type': 'mozilla::widget::JumpListLink',
+ 'headers': ['/widget/windows/JumpListItem.h'],
+ },
+ {
+ 'cid': '{b16656b2-5187-498f-abf4-56346126bfdb}',
+ 'contract_ids': ['@mozilla.org/windows-jumplistshortcut;1'],
+ 'type': 'mozilla::widget::JumpListShortcut',
+ 'headers': ['/widget/windows/JumpListItem.h'],
+ },
+ {
+ 'cid': '{e04a55e8-fee3-4ea2-a98b-41d2621adc3c}',
+ 'contract_ids': ['@mozilla.org/windows-ui-utils;1'],
+ 'type': 'WindowsUIUtils',
+ 'headers': ['/widget/windows/WindowsUIUtils.h'],
+ },
+ {
+ 'cid': '{8b5314bc-db01-11d2-96ce-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/transferable;1'],
+ 'type': 'nsTransferable',
+ 'headers': ['/widget/nsTransferable.h'],
+ },
+ {
+ 'cid': '{948a0023-e3a7-11d2-96cf-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/htmlformatconverter;1'],
+ 'type': 'nsHTMLFormatConverter',
+ 'headers': ['/widget/nsHTMLFormatConverter.h'],
+ },
+ {
+ 'cid': '{8b5314bb-db01-11d2-96ce-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/dragservice;1'],
+ 'type': 'nsDragService',
+ 'headers': ['/widget/windows/nsDragService.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{9a0cb62b-d638-4faf-9588-ae96f5e29093}',
+ 'contract_ids': ['@mozilla.org/widget/taskbar-preview-callback;1'],
+ 'type': 'mozilla::widget::TaskbarPreviewCallback',
+ 'headers': ['/widget/windows/TaskbarPreview.h'],
+ },
+ {
+ 'cid': '{d755a760-9f27-11df-0800-200c9a664242}',
+ 'contract_ids': ['@mozilla.org/gfx/info;1'],
+ 'type': 'mozilla::widget::GfxInfo',
+ 'headers': ['/widget/windows/GfxInfo.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{bd57cee8-1dd1-11b2-9fe7-95cf4709aea3}',
+ 'contract_ids': ['@mozilla.org/filepicker;1'],
+ 'type': 'nsFilePicker',
+ 'headers': ['/widget/windows/nsFilePicker.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{0f872c8c-3ee6-46bd-92a2-69652c6b474e}',
+ 'contract_ids': ['@mozilla.org/colorpicker;1'],
+ 'type': 'nsColorPicker',
+ 'headers': ['/widget/windows/nsColorPicker.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{1201d357-8417-4926-a694-e6408fbedcf8}',
+ 'contract_ids': ['@mozilla.org/sharepicker;1'],
+ 'type': 'nsSharePicker',
+ 'headers': ['/widget/windows/nsSharePicker.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'js_name': 'clipboard',
+ 'cid': '{8b5314ba-db01-11d2-96ce-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/clipboard;1'],
+ 'interfaces': ['nsIClipboard'],
+ 'type': 'nsIClipboard',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ 'overridable': True,
+ },
+ {
+ 'cid': '{b6e1a890-b2b8-4883-a65f-9476f6185313}',
+ 'contract_ids': ['@mozilla.org/widget/systemstatusbar;1'],
+ 'singleton': True,
+ 'init_method': 'Init',
+ 'type': 'mozilla::widget::SystemStatusBar',
+ 'constructor': 'mozilla::widget::SystemStatusBar::GetAddRefedSingleton',
+ 'headers': ['/widget/windows/SystemStatusBar.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+]
+
+if buildconfig.substs['CC_TYPE'] in ('msvc', 'clang-cl'):
+ Classes += [
+ {
+ 'cid': '{84e11f80-ca55-11dd-ad8b-0800200c9a66}',
+ 'contract_ids': ['@mozilla.org/system-alerts-service;1'],
+ 'type': 'mozilla::widget::ToastNotification',
+ 'headers': ['/widget/windows/ToastNotification.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ ]
+
+if defined('NS_PRINTING'):
+ Classes += [
+ {
+ 'cid': '{d3f69889-e13a-4321-980c-a39332e21f34}',
+ 'contract_ids': ['@mozilla.org/gfx/devicecontextspec;1'],
+ 'type': 'nsDeviceContextSpecWin',
+ 'headers': ['/widget/windows/nsDeviceContextSpecWin.h'],
+ },
+ {
+ 'cid': '{06beec76-a183-4d9f-85dd-085f26da565a}',
+ 'contract_ids': ['@mozilla.org/widget/printdialog-service;1'],
+ 'type': 'nsPrintDialogServiceWin',
+ 'headers': ['/widget/windows/nsPrintDialogWin.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{841387c8-72e6-484b-9296-bf6eea80d58a}',
+ 'contract_ids': ['@mozilla.org/gfx/printsettings-service;1'],
+ 'type': 'nsPrintSettingsServiceWin',
+ 'headers': ['/widget/windows/nsPrintSettingsServiceWin.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{a6cf9129-15b3-11d2-932e-00805f8add32}',
+ 'contract_ids': ['@mozilla.org/gfx/printerlist;1'],
+ 'type': 'nsPrinterListWin',
+ 'headers': ['/widget/windows/nsDeviceContextSpecWin.h'],
+ },
+ {
+ 'cid': '{2f977d53-5485-11d4-87e2-0010a4e75ef2}',
+ 'contract_ids': ['@mozilla.org/gfx/printsession;1'],
+ 'type': 'nsPrintSession',
+ 'headers': ['/widget/nsPrintSession.h'],
+ 'init_method': 'Init',
+ },
+ ]
diff --git a/widget/windows/moz.build b/widget/windows/moz.build
new file mode 100644
index 0000000000..4b1f0102b0
--- /dev/null
+++ b/widget/windows/moz.build
@@ -0,0 +1,192 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+ SCHEDULES.exclusive = ["windows"]
+
+with Files("*CompositorWidget*"):
+ BUG_COMPONENT = ("Core", "Graphics")
+
+with Files("*IMEHandler*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*IMMHandler*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*KeyboardLayout*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("OSK*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*TSFTextStore*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+TEST_DIRS += ["tests"]
+
+EXPORTS += [
+ "nsdefs.h",
+ "WindowHook.h",
+ "WinUtils.h",
+]
+
+EXPORTS.mozilla += [
+ "ShellHeaderOnlyUtils.h",
+ "UrlmonHeaderOnlyUtils.h",
+ "WindowsConsole.h",
+ "WinHeaderOnlyUtils.h",
+]
+
+EXPORTS.mozilla.widget += [
+ "AudioSession.h",
+ "CompositorWidgetChild.h",
+ "CompositorWidgetParent.h",
+ "InProcessWinCompositorWidget.h",
+ "WinCompositorWidget.h",
+ "WinCompositorWindowThread.h",
+ "WinContentSystemParameters.h",
+ "WindowsEMF.h",
+ "WindowsSMTCProvider.h",
+ "WinMessages.h",
+ "WinModifierKeyState.h",
+ "WinNativeEventData.h",
+]
+
+UNIFIED_SOURCES += [
+ "AudioSession.cpp",
+ "CompositorWidgetChild.cpp",
+ "GfxInfo.cpp",
+ "IconLoaderHelperWin.cpp",
+ "IEnumFE.cpp",
+ "IMMHandler.cpp",
+ "InkCollector.cpp",
+ "JumpListItem.cpp",
+ "KeyboardLayout.cpp",
+ "LSPAnnotator.cpp",
+ "nsAppShell.cpp",
+ "nsClipboard.cpp",
+ "nsColorPicker.cpp",
+ "nsDataObj.cpp",
+ "nsDataObjCollection.cpp",
+ "nsDragService.cpp",
+ "nsLookAndFeel.cpp",
+ "nsNativeBasicThemeWin.cpp",
+ "nsNativeDragSource.cpp",
+ "nsNativeDragTarget.cpp",
+ "nsNativeThemeWin.cpp",
+ "nsSound.cpp",
+ "nsToolkit.cpp",
+ "nsUserIdleServiceWin.cpp",
+ "nsUXThemeData.cpp",
+ "nsWindow.cpp",
+ "nsWindowBase.cpp",
+ "nsWindowDbg.cpp",
+ "nsWindowGfx.cpp",
+ "nsWinGesture.cpp",
+ "RemoteBackbuffer.cpp",
+ "ScreenHelperWin.cpp",
+ "ScrollbarUtil.cpp",
+ "SystemStatusBar.cpp",
+ "TaskbarPreview.cpp",
+ "TaskbarPreviewButton.cpp",
+ "TaskbarTabPreview.cpp",
+ "TaskbarWindowPreview.cpp",
+ "WidgetTraceEvent.cpp",
+ "WinCompositorWindowThread.cpp",
+ "WindowHook.cpp",
+ "WindowsConsole.cpp",
+ "WinIMEHandler.cpp",
+ "WinPointerEvents.cpp",
+ "WinTaskbar.cpp",
+ "WinTextEventDispatcherListener.cpp",
+ "WinUtils.cpp",
+]
+
+# The following files cannot be built in unified mode because of name clashes.
+SOURCES += [
+ "CompositorWidgetParent.cpp",
+ "InProcessWinCompositorWidget.cpp",
+ "JumpListBuilder.cpp",
+ "MediaKeysEventSourceFactory.cpp",
+ "nsBidiKeyboard.cpp",
+ "nsFilePicker.cpp",
+ "nsSharePicker.cpp",
+ "nsWidgetFactory.cpp",
+ "OSKInputPaneManager.cpp",
+ "WinCompositorWidget.cpp",
+ "WinContentSystemParameters.cpp",
+ "WindowsSMTCProvider.cpp",
+ "WindowsUIUtils.cpp",
+ "WinMouseScrollHandler.cpp",
+]
+
+# These files redefine the winsdk api version macro and we don't want it to leak to other files.
+SOURCES += [
+ "DirectManipulationOwner.cpp",
+]
+
+# Needs INITGUID and we don't allow INITGUID in unified sources since bug 970429.
+SOURCES += [
+ "InputDeviceUtils.cpp",
+ "TSFTextStore.cpp",
+]
+
+if CONFIG["NS_PRINTING"]:
+ UNIFIED_SOURCES += [
+ "nsDeviceContextSpecWin.cpp",
+ "nsPrintDialogUtil.cpp",
+ "nsPrintDialogWin.cpp",
+ "nsPrinterWin.cpp",
+ "nsPrintSettingsServiceWin.cpp",
+ "nsPrintSettingsWin.cpp",
+ ]
+
+if CONFIG["MOZ_ENABLE_SKIA_PDF"]:
+ UNIFIED_SOURCES += [
+ "WindowsEMF.cpp",
+ ]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["MOZ_ENABLE_SKIA_PDF"]:
+ LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"]
+
+LOCAL_INCLUDES += [
+ "/layout/forms",
+ "/layout/generic",
+ "/layout/style",
+ "/layout/xul",
+ "/toolkit/xre",
+ "/widget",
+ "/widget/headless",
+ "/xpcom/base",
+]
+
+DEFINES["MOZ_UNICODE"] = True
+
+for var in "MOZ_ENABLE_D3D10_LAYER":
+ if CONFIG[var]:
+ DEFINES[var] = True
+
+CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"]
+
+OS_LIBS += [
+ "rpcrt4",
+ "urlmon",
+]
+
+if CONFIG["CC_TYPE"] == "clang-cl":
+ SOURCES += [
+ "ToastNotification.cpp",
+ "ToastNotificationHandler.cpp",
+ ]
diff --git a/widget/windows/nsAppShell.cpp b/widget/windows/nsAppShell.cpp
new file mode 100644
index 0000000000..d1d654d96f
--- /dev/null
+++ b/widget/windows/nsAppShell.cpp
@@ -0,0 +1,781 @@
+/* -*- 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 "mozilla/ipc/MessageChannel.h"
+#include "mozilla/ipc/WindowsMessageLoop.h"
+#include "nsAppShell.h"
+#include "nsToolkit.h"
+#include "nsThreadUtils.h"
+#include "WinUtils.h"
+#include "WinTaskbar.h"
+#include "WinMouseScrollHandler.h"
+#include "nsWindowDefs.h"
+#include "nsWindow.h"
+#include "nsString.h"
+#include "WinIMEHandler.h"
+#include "mozilla/widget/AudioSession.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/Hal.h"
+#include "nsIDOMWakeLockListener.h"
+#include "nsIPowerManagerService.h"
+#include "mozilla/StaticPtr.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "GeckoProfiler.h"
+#include "nsComponentManagerUtils.h"
+#include "ScreenHelperWin.h"
+#include "HeadlessScreenHelper.h"
+#include "mozilla/widget/ScreenManager.h"
+#include "mozilla/Atomics.h"
+
+#if defined(ACCESSIBILITY)
+# include "mozilla/a11y/Compatibility.h"
+# include "mozilla/a11y/Platform.h"
+#endif // defined(ACCESSIBILITY)
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#define WAKE_LOCK_LOG(...) \
+ MOZ_LOG(gWinWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+static mozilla::LazyLogModule gWinWakeLockLog("WinWakeLock");
+
+// This wakelock listener is used for Window7 and above.
+class WinWakeLockListener final : public nsIDOMMozWakeLockListener {
+ public:
+ NS_DECL_ISUPPORTS
+ WinWakeLockListener() { MOZ_ASSERT(XRE_IsParentProcess()); }
+
+ private:
+ ~WinWakeLockListener() {
+ ReleaseWakelockIfNeeded(PowerRequestDisplayRequired);
+ ReleaseWakelockIfNeeded(PowerRequestExecutionRequired);
+ }
+
+ void SetHandle(HANDLE aHandle, POWER_REQUEST_TYPE aType) {
+ switch (aType) {
+ case PowerRequestDisplayRequired: {
+ if (!aHandle && mDisplayHandle) {
+ CloseHandle(mDisplayHandle);
+ }
+ mDisplayHandle = aHandle;
+ return;
+ }
+ case PowerRequestExecutionRequired: {
+ if (!aHandle && mNonDisplayHandle) {
+ CloseHandle(mNonDisplayHandle);
+ }
+ mNonDisplayHandle = aHandle;
+ return;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid request type");
+ return;
+ }
+ }
+
+ HANDLE GetHandle(POWER_REQUEST_TYPE aType) const {
+ switch (aType) {
+ case PowerRequestDisplayRequired:
+ return mDisplayHandle;
+ case PowerRequestExecutionRequired:
+ return mNonDisplayHandle;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid request type");
+ return nullptr;
+ }
+ }
+
+ HANDLE CreateHandle(POWER_REQUEST_TYPE aType) {
+ MOZ_ASSERT(!GetHandle(aType));
+ REASON_CONTEXT context = {0};
+ context.Version = POWER_REQUEST_CONTEXT_VERSION;
+ context.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING;
+ context.Reason.SimpleReasonString = RequestTypeLPWSTR(aType);
+ HANDLE handle = PowerCreateRequest(&context);
+ if (!handle) {
+ WAKE_LOCK_LOG("Failed to create handle for %s, error=%d",
+ RequestTypeStr(aType), GetLastError());
+ return nullptr;
+ }
+ SetHandle(handle, aType);
+ return handle;
+ }
+
+ LPWSTR RequestTypeLPWSTR(POWER_REQUEST_TYPE aType) const {
+ switch (aType) {
+ case PowerRequestDisplayRequired:
+ return const_cast<LPWSTR>(L"display request"); // -Wwritable-strings
+ case PowerRequestExecutionRequired:
+ return const_cast<LPWSTR>(
+ L"non-display request"); // -Wwritable-strings
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid request type");
+ return const_cast<LPWSTR>(L"unknown"); // -Wwritable-strings
+ }
+ }
+
+ const char* RequestTypeStr(POWER_REQUEST_TYPE aType) const {
+ switch (aType) {
+ case PowerRequestDisplayRequired:
+ return "display request";
+ case PowerRequestExecutionRequired:
+ return "non-display request";
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid request type");
+ return "unknown";
+ }
+ }
+
+ void RequestWakelockIfNeeded(POWER_REQUEST_TYPE aType) {
+ if (GetHandle(aType)) {
+ WAKE_LOCK_LOG("Already requested lock for %s", RequestTypeStr(aType));
+ return;
+ }
+
+ WAKE_LOCK_LOG("Prepare a wakelock for %s", RequestTypeStr(aType));
+ HANDLE handle = CreateHandle(aType);
+ if (!handle) {
+ WAKE_LOCK_LOG("Failed due to no handle for %s", RequestTypeStr(aType));
+ return;
+ }
+
+ if (PowerSetRequest(handle, aType)) {
+ WAKE_LOCK_LOG("Requested %s lock", RequestTypeStr(aType));
+ } else {
+ WAKE_LOCK_LOG("Failed to request %s lock, error=%d",
+ RequestTypeStr(aType), GetLastError());
+ SetHandle(nullptr, aType);
+ }
+ }
+
+ void ReleaseWakelockIfNeeded(POWER_REQUEST_TYPE aType) {
+ if (!GetHandle(aType)) {
+ WAKE_LOCK_LOG("Already released lock for %s", RequestTypeStr(aType));
+ return;
+ }
+
+ WAKE_LOCK_LOG("Prepare to release wakelock for %s", RequestTypeStr(aType));
+ if (!PowerClearRequest(GetHandle(aType), aType)) {
+ WAKE_LOCK_LOG("Failed to release %s lock, error=%d",
+ RequestTypeStr(aType), GetLastError());
+ return;
+ }
+ SetHandle(nullptr, aType);
+ WAKE_LOCK_LOG("Released wakelock for %s", RequestTypeStr(aType));
+ }
+
+ NS_IMETHOD Callback(const nsAString& aTopic,
+ const nsAString& aState) override {
+ WAKE_LOCK_LOG("topic=%s, state=%s", NS_ConvertUTF16toUTF8(aTopic).get(),
+ NS_ConvertUTF16toUTF8(aState).get());
+ if (!aTopic.EqualsASCII("screen") && !aTopic.EqualsASCII("audio-playing") &&
+ !aTopic.EqualsASCII("video-playing")) {
+ return NS_OK;
+ }
+
+ const bool isNonDisplayLock = aTopic.EqualsASCII("audio-playing");
+ bool requestLock = false;
+ if (isNonDisplayLock) {
+ requestLock = aState.EqualsASCII("locked-foreground") ||
+ aState.EqualsASCII("locked-background");
+ } else {
+ requestLock = aState.EqualsASCII("locked-foreground");
+ }
+
+ if (isNonDisplayLock) {
+ if (requestLock) {
+ RequestWakelockIfNeeded(PowerRequestExecutionRequired);
+ } else {
+ ReleaseWakelockIfNeeded(PowerRequestExecutionRequired);
+ }
+ } else {
+ if (requestLock) {
+ RequestWakelockIfNeeded(PowerRequestDisplayRequired);
+ } else {
+ ReleaseWakelockIfNeeded(PowerRequestDisplayRequired);
+ }
+ }
+ return NS_OK;
+ }
+
+ // Handle would only exist when we request wakelock successfully.
+ HANDLE mDisplayHandle = nullptr;
+ HANDLE mNonDisplayHandle = nullptr;
+};
+NS_IMPL_ISUPPORTS(WinWakeLockListener, nsIDOMMozWakeLockListener)
+
+// This wakelock is used for the version older than Windows7.
+class LegacyWinWakeLockListener final : public nsIDOMMozWakeLockListener {
+ public:
+ NS_DECL_ISUPPORTS
+ LegacyWinWakeLockListener() { MOZ_ASSERT(XRE_IsParentProcess()); }
+
+ private:
+ ~LegacyWinWakeLockListener() {}
+
+ NS_IMETHOD Callback(const nsAString& aTopic,
+ const nsAString& aState) override {
+ WAKE_LOCK_LOG("WinWakeLock: topic=%s, state=%s",
+ NS_ConvertUTF16toUTF8(aTopic).get(),
+ NS_ConvertUTF16toUTF8(aState).get());
+ if (!aTopic.EqualsASCII("screen") && !aTopic.EqualsASCII("audio-playing") &&
+ !aTopic.EqualsASCII("video-playing")) {
+ return NS_OK;
+ }
+
+ // Check what kind of lock we will require, if both display lock and non
+ // display lock are needed, we would require display lock because it has
+ // higher priority.
+ if (aTopic.EqualsASCII("audio-playing")) {
+ mRequireForNonDisplayLock = aState.EqualsASCII("locked-foreground") ||
+ aState.EqualsASCII("locked-background");
+ } else if (aTopic.EqualsASCII("screen") ||
+ aTopic.EqualsASCII("video-playing")) {
+ mRequireForDisplayLock = aState.EqualsASCII("locked-foreground");
+ }
+
+ if (mRequireForDisplayLock) {
+ WAKE_LOCK_LOG("WinWakeLock: Request display lock");
+ SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS);
+ } else if (mRequireForNonDisplayLock) {
+ WAKE_LOCK_LOG("WinWakeLock: Request non-display lock");
+ SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_CONTINUOUS);
+ } else {
+ WAKE_LOCK_LOG("WinWakeLock: reset lock");
+ SetThreadExecutionState(ES_CONTINUOUS);
+ }
+ return NS_OK;
+ }
+
+ bool mRequireForDisplayLock = false;
+ bool mRequireForNonDisplayLock = false;
+};
+
+NS_IMPL_ISUPPORTS(LegacyWinWakeLockListener, nsIDOMMozWakeLockListener)
+StaticRefPtr<nsIDOMMozWakeLockListener> sWakeLockListener;
+
+static void AddScreenWakeLockListener() {
+ nsCOMPtr<nsIPowerManagerService> sPowerManagerService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (sPowerManagerService) {
+ if (IsWin7SP1OrLater()) {
+ sWakeLockListener = new WinWakeLockListener();
+ } else {
+ sWakeLockListener = new LegacyWinWakeLockListener();
+ }
+ sPowerManagerService->AddWakeLockListener(sWakeLockListener);
+ } else {
+ NS_WARNING(
+ "Failed to retrieve PowerManagerService, wakelocks will be broken!");
+ }
+}
+
+static void RemoveScreenWakeLockListener() {
+ nsCOMPtr<nsIPowerManagerService> sPowerManagerService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (sPowerManagerService) {
+ sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
+ sPowerManagerService = nullptr;
+ sWakeLockListener = nullptr;
+ }
+}
+
+class SingleNativeEventPump final : public nsIThreadObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITHREADOBSERVER
+
+ SingleNativeEventPump() {
+ MOZ_ASSERT(!XRE_UseNativeEventProcessing(),
+ "Should only be used when not properly processing events.");
+ }
+
+ private:
+ ~SingleNativeEventPump() {}
+};
+
+NS_IMPL_ISUPPORTS(SingleNativeEventPump, nsIThreadObserver)
+
+NS_IMETHODIMP
+SingleNativeEventPump::OnDispatchedEvent() { return NS_OK; }
+
+NS_IMETHODIMP
+SingleNativeEventPump::OnProcessNextEvent(nsIThreadInternal* aThread,
+ bool aMayWait) {
+ MSG msg;
+ bool gotMessage = WinUtils::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE);
+ if (gotMessage) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SingleNativeEventPump::AfterProcessNextEvent(nsIThreadInternal* aThread,
+ bool aMayWait) {
+ return NS_OK;
+}
+
+// RegisterWindowMessage values
+// Native event callback message
+const wchar_t* kAppShellGeckoEventId = L"nsAppShell:EventID";
+UINT sAppShellGeckoMsgId;
+// Taskbar button creation message
+const wchar_t* kTaskbarButtonEventId = L"TaskbarButtonCreated";
+UINT sTaskbarButtonCreatedMsg;
+
+/* static */
+UINT nsAppShell::GetTaskbarButtonCreatedMessage() {
+ return sTaskbarButtonCreatedMsg;
+}
+
+namespace mozilla {
+namespace crashreporter {
+void LSPAnnotate();
+} // namespace crashreporter
+} // namespace mozilla
+
+using mozilla::crashreporter::LSPAnnotate;
+
+//-------------------------------------------------------------------------
+
+// Note that since we're on x86-ish processors here, ReleaseAcquire is the
+// semantics that normal loads and stores would use anyway.
+static Atomic<size_t, ReleaseAcquire> sOutstandingNativeEventCallbacks;
+
+/*static*/ LRESULT CALLBACK nsAppShell::EventWindowProc(HWND hwnd, UINT uMsg,
+ WPARAM wParam,
+ LPARAM lParam) {
+ if (uMsg == sAppShellGeckoMsgId) {
+ // The app shell might have been destroyed between this message being
+ // posted and being executed, so be extra careful.
+ if (!sOutstandingNativeEventCallbacks) {
+ return TRUE;
+ }
+
+ nsAppShell* as = reinterpret_cast<nsAppShell*>(lParam);
+ as->NativeEventCallback();
+ --sOutstandingNativeEventCallbacks;
+ return TRUE;
+ }
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+nsAppShell::~nsAppShell() {
+ hal::Shutdown();
+
+ if (mEventWnd) {
+ // DestroyWindow doesn't do anything when called from a non UI thread.
+ // Since mEventWnd was created on the UI thread, it must be destroyed on
+ // the UI thread.
+ SendMessage(mEventWnd, WM_CLOSE, 0, 0);
+ }
+
+ // Cancel any outstanding native event callbacks.
+ sOutstandingNativeEventCallbacks = 0;
+}
+
+#if defined(ACCESSIBILITY)
+
+static ULONG gUiaMsg;
+static HHOOK gUiaHook;
+static uint32_t gUiaAttempts;
+static const uint32_t kMaxUiaAttempts = 5;
+
+static void InitUIADetection();
+
+static LRESULT CALLBACK UiaHookProc(int aCode, WPARAM aWParam, LPARAM aLParam) {
+ if (aCode < 0) {
+ return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
+ }
+
+ auto cwp = reinterpret_cast<CWPSTRUCT*>(aLParam);
+ if (gUiaMsg && cwp->message == gUiaMsg) {
+ if (gUiaAttempts < kMaxUiaAttempts) {
+ ++gUiaAttempts;
+
+ Maybe<bool> shouldCallNextHook =
+ a11y::Compatibility::OnUIAMessage(cwp->wParam, cwp->lParam);
+ if (shouldCallNextHook.isSome()) {
+ // We've got an instantiator.
+ if (!shouldCallNextHook.value()) {
+ // We're blocking this instantiation. We need to keep this hook set
+ // so that we can catch any future instantiation attempts.
+ return 0;
+ }
+
+ // We're allowing the instantiator to proceed, so this hook is no longer
+ // needed.
+ if (::UnhookWindowsHookEx(gUiaHook)) {
+ gUiaHook = nullptr;
+ }
+ } else {
+ // Our hook might be firing after UIA; let's try reinstalling ourselves.
+ InitUIADetection();
+ }
+ } else {
+ // We've maxed out our attempts. Let's unhook.
+ if (::UnhookWindowsHookEx(gUiaHook)) {
+ gUiaHook = nullptr;
+ }
+ }
+ }
+
+ return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
+}
+
+static void InitUIADetection() {
+ if (gUiaHook) {
+ // In this case we want to re-hook so that the hook is always called ahead
+ // of UIA's hook.
+ if (::UnhookWindowsHookEx(gUiaHook)) {
+ gUiaHook = nullptr;
+ }
+ }
+
+ if (!gUiaMsg) {
+ // This is the message that UIA sends to trigger a command. UIA's
+ // CallWndProc looks for this message and then handles the request.
+ // Our hook gets in front of UIA's hook and examines the message first.
+ gUiaMsg = ::RegisterWindowMessageW(L"HOOKUTIL_MSG");
+ }
+
+ if (!gUiaHook) {
+ gUiaHook = ::SetWindowsHookEx(WH_CALLWNDPROC, &UiaHookProc, nullptr,
+ ::GetCurrentThreadId());
+ }
+}
+
+#endif // defined(ACCESSIBILITY)
+
+NS_IMETHODIMP
+nsAppShell::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIObserverService> obsServ(
+ mozilla::services::GetObserverService());
+
+#if defined(ACCESSIBILITY)
+ if (!strcmp(aTopic, "dll-loaded-main-thread")) {
+ if (a11y::PlatformDisabledState() != a11y::ePlatformIsDisabled &&
+ !gUiaHook) {
+ nsDependentString dllName(aData);
+
+ if (StringEndsWith(dllName, u"uiautomationcore.dll"_ns,
+ nsCaseInsensitiveStringComparator)) {
+ InitUIADetection();
+
+ // Now that we've handled the observer notification, we can remove it
+ obsServ->RemoveObserver(this, "dll-loaded-main-thread");
+ }
+ }
+
+ return NS_OK;
+ }
+#endif // defined(ACCESSIBILITY)
+
+ if (!strcmp(aTopic, "sessionstore-restoring-on-startup")) {
+ nsWindow::SetIsRestoringSession(true);
+ // Now that we've handled the observer notification, we can remove it
+ obsServ->RemoveObserver(this, "sessionstore-restoring-on-startup");
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "sessionstore-windows-restored")) {
+ nsWindow::SetIsRestoringSession(false);
+ // Now that we've handled the observer notification, we can remove it
+ obsServ->RemoveObserver(this, "sessionstore-windows-restored");
+ return NS_OK;
+ }
+ }
+
+ return nsBaseAppShell::Observe(aSubject, aTopic, aData);
+}
+
+nsresult nsAppShell::Init() {
+ LSPAnnotate();
+
+ hal::Init();
+
+ if (XRE_IsParentProcess()) {
+ sTaskbarButtonCreatedMsg = ::RegisterWindowMessageW(kTaskbarButtonEventId);
+ NS_ASSERTION(sTaskbarButtonCreatedMsg,
+ "Could not register taskbar button creation message");
+ }
+
+ // The hidden message window is used for interrupting the processing of native
+ // events, so that we can process gecko events. Therefore, we only need it if
+ // we are processing native events. Disabling this is required for win32k
+ // syscall lockdown.
+ if (XRE_UseNativeEventProcessing()) {
+ sAppShellGeckoMsgId = ::RegisterWindowMessageW(kAppShellGeckoEventId);
+ NS_ASSERTION(sAppShellGeckoMsgId,
+ "Could not register hidden window event message!");
+
+ mLastNativeEventScheduled = TimeStamp::NowLoRes();
+
+ WNDCLASSW wc;
+ HINSTANCE module = GetModuleHandle(nullptr);
+
+ const wchar_t* const kWindowClass = L"nsAppShell:EventWindowClass";
+ if (!GetClassInfoW(module, kWindowClass, &wc)) {
+ wc.style = 0;
+ wc.lpfnWndProc = EventWindowProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = module;
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = (HBRUSH) nullptr;
+ wc.lpszMenuName = (LPCWSTR) nullptr;
+ wc.lpszClassName = kWindowClass;
+ RegisterClassW(&wc);
+ }
+
+ mEventWnd = CreateWindowW(kWindowClass, L"nsAppShell:EventWindow", 0, 0, 0,
+ 10, 10, HWND_MESSAGE, nullptr, module, nullptr);
+ NS_ENSURE_STATE(mEventWnd);
+ } else if (XRE_IsContentProcess()) {
+ // We're not generally processing native events, but still using GDI and we
+ // still have some internal windows, e.g. from calling CoInitializeEx.
+ // So we use a class that will do a single event pump where previously we
+ // might have processed multiple events to make sure any occasional messages
+ // to these windows are processed. This also allows any internal Windows
+ // messages to be processed to ensure the GDI data remains fresh.
+ nsCOMPtr<nsIThreadInternal> threadInt =
+ do_QueryInterface(NS_GetCurrentThread());
+ if (threadInt) {
+ threadInt->SetObserver(new SingleNativeEventPump());
+ }
+ }
+
+ if (XRE_IsParentProcess()) {
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ if (gfxPlatform::IsHeadless()) {
+ screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
+ } else {
+ screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperWin>());
+ ScreenHelperWin::RefreshScreens();
+ }
+
+ nsCOMPtr<nsIObserverService> obsServ(
+ mozilla::services::GetObserverService());
+
+ obsServ->AddObserver(this, "sessionstore-restoring-on-startup", false);
+ obsServ->AddObserver(this, "sessionstore-windows-restored", false);
+
+#if defined(ACCESSIBILITY)
+ if (::GetModuleHandleW(L"uiautomationcore.dll")) {
+ InitUIADetection();
+ } else {
+ obsServ->AddObserver(this, "dll-loaded-main-thread", false);
+ }
+#endif // defined(ACCESSIBILITY)
+ }
+
+ return nsBaseAppShell::Init();
+}
+
+NS_IMETHODIMP
+nsAppShell::Run(void) {
+ // Content processes initialize audio later through PContent using audio
+ // tray id information pulled from the browser process AudioSession. This
+ // way the two share a single volume control.
+ // Note StopAudioSession() is called from nsAppRunner.cpp after xpcom is torn
+ // down to insure the browser shuts down after child processes.
+ if (XRE_IsParentProcess()) {
+ mozilla::widget::StartAudioSession();
+ }
+
+ // Add an observer that disables the screen saver when requested by Gecko.
+ // For example when we're playing video in the foreground tab. Whole firefox
+ // only needs one wakelock instance, so we would only create one listener in
+ // chrome process to prevent requesting unnecessary wakelock.
+ if (XRE_IsParentProcess()) {
+ AddScreenWakeLockListener();
+ }
+
+ nsresult rv = nsBaseAppShell::Run();
+
+ if (XRE_IsParentProcess()) {
+ RemoveScreenWakeLockListener();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAppShell::Exit(void) {
+#if defined(ACCESSIBILITY)
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIObserverService> obsServ(
+ mozilla::services::GetObserverService());
+ obsServ->RemoveObserver(this, "dll-loaded-main-thread");
+
+ if (gUiaHook && ::UnhookWindowsHookEx(gUiaHook)) {
+ gUiaHook = nullptr;
+ }
+ }
+#endif // defined(ACCESSIBILITY)
+
+ return nsBaseAppShell::Exit();
+}
+
+void nsAppShell::DoProcessMoreGeckoEvents() {
+ // Called by nsBaseAppShell's NativeEventCallback() after it has finished
+ // processing pending gecko events and there are still gecko events pending
+ // for the thread. (This can happen if NS_ProcessPendingEvents reached it's
+ // starvation timeout limit.) The default behavior in nsBaseAppShell is to
+ // call ScheduleNativeEventCallback to post a follow up native event callback
+ // message. This triggers an additional call to NativeEventCallback for more
+ // gecko event processing.
+
+ // There's a deadlock risk here with certain internal Windows modal loops. In
+ // our dispatch code, we prioritize messages so that input is handled first.
+ // However Windows modal dispatch loops often prioritize posted messages. If
+ // we find ourselves in a tight gecko timer loop where NS_ProcessPendingEvents
+ // takes longer than the timer duration, NS_HasPendingEvents(thread) will
+ // always be true. ScheduleNativeEventCallback will be called on every
+ // NativeEventCallback callback, and in a Windows modal dispatch loop, the
+ // callback message will be processed first -> input gets starved, dead lock.
+
+ // To avoid, don't post native callback messages from NativeEventCallback
+ // when we're in a modal loop. This gets us back into the Windows modal
+ // dispatch loop dispatching input messages. Once we drop out of the modal
+ // loop, we use mNativeCallbackPending to fire off a final NativeEventCallback
+ // if we need it, which insures NS_ProcessPendingEvents gets called and all
+ // gecko events get processed.
+ if (mEventloopNestingLevel < 2) {
+ OnDispatchedEvent();
+ mNativeCallbackPending = false;
+ } else {
+ mNativeCallbackPending = true;
+ }
+}
+
+void nsAppShell::ScheduleNativeEventCallback() {
+ MOZ_ASSERT(mEventWnd,
+ "We should have created mEventWnd in Init, if this is called.");
+
+ // Post a message to the hidden message window
+ ++sOutstandingNativeEventCallbacks;
+ {
+ MutexAutoLock lock(mLastNativeEventScheduledMutex);
+ // Time stamp this event so we can detect cases where the event gets
+ // dropping in sub classes / modal loops we do not control.
+ mLastNativeEventScheduled = TimeStamp::NowLoRes();
+ }
+ ::PostMessage(mEventWnd, sAppShellGeckoMsgId, 0,
+ reinterpret_cast<LPARAM>(this));
+}
+
+bool nsAppShell::ProcessNextNativeEvent(bool mayWait) {
+ // Notify ipc we are spinning a (possibly nested) gecko event loop.
+ mozilla::ipc::MessageChannel::NotifyGeckoEventDispatch();
+
+ bool gotMessage = false;
+
+ do {
+ MSG msg;
+ bool uiMessage = false;
+
+ // For avoiding deadlock between our process and plugin process by
+ // mouse wheel messages, we're handling actually when we receive one of
+ // following internal messages which is posted by native mouse wheel
+ // message handler. Any other events, especially native modifier key
+ // events, should not be handled between native message and posted
+ // internal message because it may make different modifier key state or
+ // mouse cursor position between them.
+ if (mozilla::widget::MouseScrollHandler::IsWaitingInternalMessage()) {
+ gotMessage = WinUtils::PeekMessage(&msg, nullptr, MOZ_WM_MOUSEWHEEL_FIRST,
+ MOZ_WM_MOUSEWHEEL_LAST, PM_REMOVE);
+ NS_ASSERTION(gotMessage,
+ "waiting internal wheel message, but it has not come");
+ uiMessage = gotMessage;
+ }
+
+ if (!gotMessage) {
+ gotMessage = WinUtils::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE);
+ uiMessage =
+ (msg.message >= WM_KEYFIRST && msg.message <= WM_IME_KEYLAST) ||
+ (msg.message >= NS_WM_IMEFIRST && msg.message <= NS_WM_IMELAST) ||
+ (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST);
+ }
+
+ if (gotMessage) {
+ if (msg.message == WM_QUIT) {
+ ::PostQuitMessage(msg.wParam);
+ Exit();
+ } else {
+ // If we had UI activity we would be processing it now so we know we
+ // have either kUIActivity or kActivityNoUIAVail.
+ mozilla::BackgroundHangMonitor().NotifyActivity();
+
+ if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST &&
+ IMEHandler::ProcessRawKeyMessage(msg)) {
+ continue; // the message is consumed.
+ }
+
+#if defined(_X86_)
+ // Store Printer dialog messages for reposting on x86, because on x86
+ // Windows 7 they are not processed by a window procedure, but are
+ // explicitly waited for in the winspool.drv code that will be further
+ // up the stack (winspool!WaitForCompletionMessage). These are
+ // undocumented Windows Message identifiers found in winspool.drv.
+ if (msg.message == 0x5b7a || msg.message == 0x5b7f ||
+ msg.message == 0x5b80 || msg.message == 0x5b81) {
+ mMsgsToRepost.push_back(msg);
+ continue;
+ }
+#endif
+
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ } else if (mayWait) {
+ // Block and wait for any posted application message
+ mozilla::BackgroundHangMonitor().NotifyWait();
+ {
+ AUTO_PROFILER_LABEL("nsAppShell::ProcessNextNativeEvent::Wait", IDLE);
+ WinUtils::WaitForMessage();
+ }
+ }
+ } while (!gotMessage && mayWait);
+
+ // See DoProcessNextNativeEvent, mEventloopNestingLevel will be
+ // one when a modal loop unwinds.
+ if (mNativeCallbackPending && mEventloopNestingLevel == 1)
+ DoProcessMoreGeckoEvents();
+
+ // Check for starved native callbacks. If we haven't processed one
+ // of these events in NATIVE_EVENT_STARVATION_LIMIT, fire one off.
+ static const mozilla::TimeDuration nativeEventStarvationLimit =
+ mozilla::TimeDuration::FromSeconds(NATIVE_EVENT_STARVATION_LIMIT);
+
+ TimeDuration timeSinceLastNativeEventScheduled;
+ {
+ MutexAutoLock lock(mLastNativeEventScheduledMutex);
+ timeSinceLastNativeEventScheduled =
+ TimeStamp::NowLoRes() - mLastNativeEventScheduled;
+ }
+ if (timeSinceLastNativeEventScheduled > nativeEventStarvationLimit) {
+ ScheduleNativeEventCallback();
+ }
+
+ return gotMessage;
+}
+
+nsresult nsAppShell::AfterProcessNextEvent(nsIThreadInternal* /* unused */,
+ bool /* unused */) {
+ if (!mMsgsToRepost.empty()) {
+ for (MSG msg : mMsgsToRepost) {
+ ::PostMessageW(msg.hwnd, msg.message, msg.wParam, msg.lParam);
+ }
+ mMsgsToRepost.clear();
+ }
+ return NS_OK;
+}
diff --git a/widget/windows/nsAppShell.h b/widget/windows/nsAppShell.h
new file mode 100644
index 0000000000..0c68d6b0a1
--- /dev/null
+++ b/widget/windows/nsAppShell.h
@@ -0,0 +1,61 @@
+/* -*- 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 nsAppShell_h__
+#define nsAppShell_h__
+
+#include "nsBaseAppShell.h"
+#include <windows.h>
+#include <vector>
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Mutex.h"
+
+// The maximum time we allow before forcing a native event callback.
+// In seconds.
+#define NATIVE_EVENT_STARVATION_LIMIT 1
+
+/**
+ * Native Win32 Application shell wrapper
+ */
+class nsAppShell : public nsBaseAppShell {
+ public:
+ nsAppShell()
+ : mEventWnd(nullptr),
+ mNativeCallbackPending(false),
+ mLastNativeEventScheduledMutex(
+ "nsAppShell::mLastNativeEventScheduledMutex") {}
+ typedef mozilla::TimeStamp TimeStamp;
+ typedef mozilla::Mutex Mutex;
+
+ nsresult Init();
+ void DoProcessMoreGeckoEvents();
+
+ static UINT GetTaskbarButtonCreatedMessage();
+
+ NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal* thread,
+ bool eventWasProcessed) final;
+
+ protected:
+ NS_IMETHOD Run() override;
+ NS_IMETHOD Exit() override;
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override;
+
+ virtual void ScheduleNativeEventCallback();
+ virtual bool ProcessNextNativeEvent(bool mayWait);
+ virtual ~nsAppShell();
+
+ static LRESULT CALLBACK EventWindowProc(HWND, UINT, WPARAM, LPARAM);
+
+ protected:
+ HWND mEventWnd;
+ bool mNativeCallbackPending;
+
+ Mutex mLastNativeEventScheduledMutex;
+ TimeStamp mLastNativeEventScheduled;
+ std::vector<MSG> mMsgsToRepost;
+};
+
+#endif // nsAppShell_h__
diff --git a/widget/windows/nsBidiKeyboard.cpp b/widget/windows/nsBidiKeyboard.cpp
new file mode 100644
index 0000000000..87d81d458e
--- /dev/null
+++ b/widget/windows/nsBidiKeyboard.cpp
@@ -0,0 +1,169 @@
+/* -*- 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 <stdio.h>
+#include "nsBidiKeyboard.h"
+#include "WidgetUtils.h"
+#include "nsIWidget.h"
+#include <tchar.h>
+
+NS_IMPL_ISUPPORTS(nsBidiKeyboard, nsIBidiKeyboard)
+
+nsBidiKeyboard::nsBidiKeyboard() : nsIBidiKeyboard() { Reset(); }
+
+nsBidiKeyboard::~nsBidiKeyboard() {}
+
+NS_IMETHODIMP nsBidiKeyboard::Reset() {
+ mInitialized = false;
+ mHaveBidiKeyboards = false;
+ mLTRKeyboard[0] = '\0';
+ mRTLKeyboard[0] = '\0';
+ mCurrentLocaleName[0] = '\0';
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBidiKeyboard::IsLangRTL(bool* aIsRTL) {
+ *aIsRTL = false;
+
+ nsresult result = SetupBidiKeyboards();
+ if (NS_FAILED(result)) return result;
+
+ HKL currentLocale;
+
+ currentLocale = ::GetKeyboardLayout(0);
+ *aIsRTL = IsRTLLanguage(currentLocale);
+
+ if (!::GetKeyboardLayoutNameW(mCurrentLocaleName)) return NS_ERROR_FAILURE;
+
+ NS_ASSERTION(*mCurrentLocaleName,
+ "GetKeyboardLayoutName return string length == 0");
+ NS_ASSERTION((wcslen(mCurrentLocaleName) < KL_NAMELENGTH),
+ "GetKeyboardLayoutName return string length >= KL_NAMELENGTH");
+
+ // The language set by the user overrides the default language for that
+ // direction
+ if (*aIsRTL) {
+ wcsncpy(mRTLKeyboard, mCurrentLocaleName, KL_NAMELENGTH);
+ mRTLKeyboard[KL_NAMELENGTH - 1] = '\0'; // null terminate
+ } else {
+ wcsncpy(mLTRKeyboard, mCurrentLocaleName, KL_NAMELENGTH);
+ mLTRKeyboard[KL_NAMELENGTH - 1] = '\0'; // null terminate
+ }
+
+ NS_ASSERTION((wcslen(mRTLKeyboard) < KL_NAMELENGTH),
+ "mLTRKeyboard has string length >= KL_NAMELENGTH");
+ NS_ASSERTION((wcslen(mLTRKeyboard) < KL_NAMELENGTH),
+ "mRTLKeyboard has string length >= KL_NAMELENGTH");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult result = SetupBidiKeyboards();
+ if (NS_FAILED(result)) return result;
+
+ *aResult = mHaveBidiKeyboards;
+ return NS_OK;
+}
+
+// Get the list of keyboard layouts available in the system
+// Set mLTRKeyboard to the first LTR keyboard in the list and mRTLKeyboard to
+// the first RTL keyboard in the list These defaults will be used unless the
+// user explicitly sets something else.
+nsresult nsBidiKeyboard::SetupBidiKeyboards() {
+ if (mInitialized) return mHaveBidiKeyboards ? NS_OK : NS_ERROR_FAILURE;
+
+ int keyboards;
+ HKL far* buf;
+ HKL locale;
+ wchar_t localeName[KL_NAMELENGTH];
+ bool isLTRKeyboardSet = false;
+ bool isRTLKeyboardSet = false;
+
+ // GetKeyboardLayoutList with 0 as first parameter returns the number of
+ // keyboard layouts available
+ keyboards = ::GetKeyboardLayoutList(0, nullptr);
+ if (!keyboards) return NS_ERROR_FAILURE;
+
+ // allocate a buffer to hold the list
+ buf = (HKL far*)malloc(keyboards * sizeof(HKL));
+ if (!buf) return NS_ERROR_OUT_OF_MEMORY;
+
+ // Call again to fill the buffer
+ if (::GetKeyboardLayoutList(keyboards, buf) != keyboards) {
+ free(buf);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Go through the list and pick a default LTR and RTL keyboard layout
+ while (keyboards--) {
+ locale = buf[keyboards];
+ if (IsRTLLanguage(locale)) {
+ _snwprintf(mRTLKeyboard, KL_NAMELENGTH, L"%.*x", KL_NAMELENGTH - 1,
+ LANGIDFROMLCID((DWORD_PTR)locale));
+ isRTLKeyboardSet = true;
+ } else {
+ _snwprintf(mLTRKeyboard, KL_NAMELENGTH, L"%.*x", KL_NAMELENGTH - 1,
+ LANGIDFROMLCID((DWORD_PTR)locale));
+ isLTRKeyboardSet = true;
+ }
+ }
+ free(buf);
+ mInitialized = true;
+
+ // If there is not at least one keyboard of each directionality, Bidi
+ // keyboard functionality will be disabled.
+ mHaveBidiKeyboards = (isRTLKeyboardSet && isLTRKeyboardSet);
+ if (!mHaveBidiKeyboards) return NS_ERROR_FAILURE;
+
+ // Get the current keyboard layout and use it for either mRTLKeyboard or
+ // mLTRKeyboard as appropriate. If the user has many keyboard layouts
+ // installed this prevents us from arbitrarily resetting the current
+ // layout (bug 80274)
+ locale = ::GetKeyboardLayout(0);
+ if (!::GetKeyboardLayoutNameW(localeName)) return NS_ERROR_FAILURE;
+
+ NS_ASSERTION(*localeName, "GetKeyboardLayoutName return string length == 0");
+ NS_ASSERTION((wcslen(localeName) < KL_NAMELENGTH),
+ "GetKeyboardLayout return string length >= KL_NAMELENGTH");
+
+ if (IsRTLLanguage(locale)) {
+ wcsncpy(mRTLKeyboard, localeName, KL_NAMELENGTH);
+ mRTLKeyboard[KL_NAMELENGTH - 1] = '\0'; // null terminate
+ } else {
+ wcsncpy(mLTRKeyboard, localeName, KL_NAMELENGTH);
+ mLTRKeyboard[KL_NAMELENGTH - 1] = '\0'; // null terminate
+ }
+
+ NS_ASSERTION(*mRTLKeyboard, "mLTRKeyboard has string length == 0");
+ NS_ASSERTION(*mLTRKeyboard, "mLTRKeyboard has string length == 0");
+
+ return NS_OK;
+}
+
+// Test whether the language represented by this locale identifier is a
+// right-to-left language, using bit 123 of the Unicode subset bitfield in
+// the LOCALESIGNATURE
+// See
+// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/intl/unicode_63ub.asp
+bool nsBidiKeyboard::IsRTLLanguage(HKL aLocale) {
+ LOCALESIGNATURE localesig;
+ return (::GetLocaleInfoW(PRIMARYLANGID((DWORD_PTR)aLocale),
+ LOCALE_FONTSIGNATURE, (LPWSTR)&localesig,
+ (sizeof(localesig) / sizeof(WCHAR))) &&
+ (localesig.lsUsb[3] & 0x08000000));
+}
+
+// static
+void nsBidiKeyboard::OnLayoutChange() {
+ mozilla::widget::WidgetUtils::SendBidiKeyboardInfoToContent();
+}
+
+// static
+already_AddRefed<nsIBidiKeyboard> nsIWidget::CreateBidiKeyboardInner() {
+ return do_AddRef(new nsBidiKeyboard());
+}
diff --git a/widget/windows/nsBidiKeyboard.h b/widget/windows/nsBidiKeyboard.h
new file mode 100644
index 0000000000..584d4d2dee
--- /dev/null
+++ b/widget/windows/nsBidiKeyboard.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/. */
+
+#ifndef __nsBidiKeyboard
+#define __nsBidiKeyboard
+#include "nsIBidiKeyboard.h"
+#include <windows.h>
+
+class nsBidiKeyboard : public nsIBidiKeyboard {
+ virtual ~nsBidiKeyboard();
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIBIDIKEYBOARD
+
+ nsBidiKeyboard();
+
+ static void OnLayoutChange();
+
+ protected:
+ nsresult SetupBidiKeyboards();
+ bool IsRTLLanguage(HKL aLocale);
+
+ bool mInitialized;
+ bool mHaveBidiKeyboards;
+ wchar_t mLTRKeyboard[KL_NAMELENGTH];
+ wchar_t mRTLKeyboard[KL_NAMELENGTH];
+ wchar_t mCurrentLocaleName[KL_NAMELENGTH];
+};
+
+#endif // __nsBidiKeyboard
diff --git a/widget/windows/nsClipboard.cpp b/widget/windows/nsClipboard.cpp
new file mode 100644
index 0000000000..e274adba46
--- /dev/null
+++ b/widget/windows/nsClipboard.cpp
@@ -0,0 +1,1071 @@
+/* -*- 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 "nsClipboard.h"
+#include <ole2.h>
+#include <shlobj.h>
+#include <intshcut.h>
+
+// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
+#include <shellapi.h>
+
+#include "nsArrayUtils.h"
+#include "nsCOMPtr.h"
+#include "nsDataObj.h"
+#include "nsString.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsITransferable.h"
+#include "nsCOMPtr.h"
+#include "nsXPCOM.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsIWidget.h"
+#include "nsWidgetsCID.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsIFileProtocolHandler.h"
+#include "nsEscape.h"
+#include "nsIObserverService.h"
+#include "nsMimeTypes.h"
+#include "imgITools.h"
+
+using mozilla::LogLevel;
+
+static mozilla::LazyLogModule gWin32ClipboardLog("nsClipboard");
+
+// oddly, this isn't in the MSVC headers anywhere.
+UINT nsClipboard::CF_HTML = ::RegisterClipboardFormatW(L"HTML Format");
+UINT nsClipboard::CF_CUSTOMTYPES =
+ ::RegisterClipboardFormatW(L"application/x-moz-custom-clipdata");
+
+//-------------------------------------------------------------------------
+//
+// nsClipboard constructor
+//
+//-------------------------------------------------------------------------
+nsClipboard::nsClipboard() : nsBaseClipboard() {
+ mIgnoreEmptyNotification = false;
+ mWindow = nullptr;
+
+ // Register for a shutdown notification so that we can flush data
+ // to the OS clipboard.
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (observerService) {
+ observerService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
+ PR_FALSE);
+ }
+}
+
+//-------------------------------------------------------------------------
+// nsClipboard destructor
+//-------------------------------------------------------------------------
+nsClipboard::~nsClipboard() {}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsClipboard, nsBaseClipboard, nsIObserver)
+
+NS_IMETHODIMP
+nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ // This will be called on shutdown.
+ ::OleFlushClipboard();
+ ::CloseClipboard();
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+UINT nsClipboard::GetFormat(const char* aMimeStr, bool aMapHTMLMime) {
+ UINT format;
+
+ if (strcmp(aMimeStr, kTextMime) == 0) {
+ format = CF_TEXT;
+ } else if (strcmp(aMimeStr, kUnicodeMime) == 0) {
+ format = CF_UNICODETEXT;
+ } else if (strcmp(aMimeStr, kRTFMime) == 0) {
+ format = ::RegisterClipboardFormat(L"Rich Text Format");
+ } else if (strcmp(aMimeStr, kJPEGImageMime) == 0 ||
+ strcmp(aMimeStr, kJPGImageMime) == 0 ||
+ strcmp(aMimeStr, kPNGImageMime) == 0) {
+ format = CF_DIBV5;
+ } else if (strcmp(aMimeStr, kFileMime) == 0 ||
+ strcmp(aMimeStr, kFilePromiseMime) == 0) {
+ format = CF_HDROP;
+ } else if ((strcmp(aMimeStr, kNativeHTMLMime) == 0) ||
+ (aMapHTMLMime && strcmp(aMimeStr, kHTMLMime) == 0)) {
+ format = CF_HTML;
+ } else if (strcmp(aMimeStr, kCustomTypesMime) == 0) {
+ format = CF_CUSTOMTYPES;
+ } else {
+ format = ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr).get());
+ }
+
+ return format;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::CreateNativeDataObject(nsITransferable* aTransferable,
+ IDataObject** aDataObj,
+ nsIURI* uri) {
+ if (nullptr == aTransferable) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Create our native DataObject that implements
+ // the OLE IDataObject interface
+ nsDataObj* dataObj = new nsDataObj(uri);
+
+ if (!dataObj) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ dataObj->AddRef();
+
+ // Now set it up with all the right data flavors & enums
+ nsresult res = SetupNativeDataObject(aTransferable, dataObj);
+ if (NS_OK == res) {
+ *aDataObj = dataObj;
+ } else {
+ dataObj->Release();
+ }
+ return res;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::SetupNativeDataObject(nsITransferable* aTransferable,
+ IDataObject* aDataObj) {
+ if (nullptr == aTransferable || nullptr == aDataObj) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsDataObj* dObj = static_cast<nsDataObj*>(aDataObj);
+
+ // Now give the Transferable to the DataObject
+ // for getting the data out of it
+ dObj->SetTransferable(aTransferable);
+
+ // Get the transferable list of data flavors
+ nsTArray<nsCString> flavors;
+ aTransferable->FlavorsTransferableCanExport(flavors);
+
+ // Walk through flavors that contain data and register them
+ // into the DataObj as supported flavors
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+
+ // When putting data onto the clipboard, we want to maintain kHTMLMime
+ // ("text/html") and not map it to CF_HTML here since this will be done
+ // below.
+ UINT format = GetFormat(flavorStr.get(), false);
+
+ // Now tell the native IDataObject about both our mime type and
+ // the native data format
+ FORMATETC fe;
+ SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ dObj->AddDataFlavor(flavorStr.get(), &fe);
+
+ // Do various things internal to the implementation, like map one
+ // flavor to another or add additional flavors based on what's required
+ // for the win32 impl.
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ // if we find text/unicode, also advertise text/plain (which we will
+ // convert on our own in nsDataObj::GetText().
+ FORMATETC textFE;
+ SET_FORMATETC(textFE, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ dObj->AddDataFlavor(kTextMime, &textFE);
+ } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
+ // if we find text/html, also advertise win32's html flavor (which we will
+ // convert on our own in nsDataObj::GetText().
+ FORMATETC htmlFE;
+ SET_FORMATETC(htmlFE, CF_HTML, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ dObj->AddDataFlavor(kHTMLMime, &htmlFE);
+ } else if (flavorStr.EqualsLiteral(kURLMime)) {
+ // if we're a url, in addition to also being text, we need to register
+ // the "file" flavors so that the win32 shell knows to create an internet
+ // shortcut when it sees one of these beasts.
+ FORMATETC shortcutFE;
+ SET_FORMATETC(shortcutFE,
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA), 0,
+ DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(kURLMime, &shortcutFE);
+ SET_FORMATETC(shortcutFE,
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW), 0,
+ DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(kURLMime, &shortcutFE);
+ SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILECONTENTS),
+ 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(kURLMime, &shortcutFE);
+ SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLA), 0,
+ DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(kURLMime, &shortcutFE);
+ SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLW), 0,
+ DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(kURLMime, &shortcutFE);
+ } else if (flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kGIFImageMime) ||
+ flavorStr.EqualsLiteral(kNativeImageMime)) {
+ // if we're an image, register the native bitmap flavor
+ FORMATETC imageFE;
+ // Add DIBv5
+ SET_FORMATETC(imageFE, CF_DIBV5, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(flavorStr.get(), &imageFE);
+ // Add DIBv3
+ SET_FORMATETC(imageFE, CF_DIB, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(flavorStr.get(), &imageFE);
+ } else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
+ // if we're a file promise flavor, also register the
+ // CFSTR_PREFERREDDROPEFFECT format. The data object
+ // returns a value of DROPEFFECTS_MOVE to the drop target
+ // when it asks for the value of this format. This causes
+ // the file to be moved from the temporary location instead
+ // of being copied. The right thing to do here is to call
+ // SetData() on the data object and set the value of this format
+ // to DROPEFFECTS_MOVE on this particular data object. But,
+ // since all the other clipboard formats follow the model of setting
+ // data on the data object only when the drop object calls GetData(),
+ // I am leaving this format's value hard coded in the data object.
+ // We can change this if other consumers of this format get added to this
+ // codebase and they need different values.
+ FORMATETC shortcutFE;
+ SET_FORMATETC(shortcutFE,
+ ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), 0,
+ DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(kFilePromiseMime, &shortcutFE);
+ }
+ }
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard) {
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIgnoreEmptyNotification = true;
+
+ // make sure we have a good transferable
+ if (nullptr == mTransferable) {
+ return NS_ERROR_FAILURE;
+ }
+
+ IDataObject* dataObj;
+ if (NS_SUCCEEDED(CreateNativeDataObject(mTransferable, &dataObj,
+ nullptr))) { // this add refs dataObj
+ ::OleSetClipboard(dataObj);
+ dataObj->Release();
+ } else {
+ // Clear the native clipboard
+ ::OleSetClipboard(nullptr);
+ }
+
+ mIgnoreEmptyNotification = false;
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::GetGlobalData(HGLOBAL aHGBL, void** aData,
+ uint32_t* aLen) {
+ // Allocate a new memory buffer and copy the data from global memory.
+ // Recall that win98 allocates to nearest DWORD boundary. As a safety
+ // precaution, allocate an extra 3 bytes (but don't report them in |aLen|!)
+ // and null them out to ensure that all of our NS_strlen calls will succeed.
+ // NS_strlen operates on char16_t, so we need 3 NUL bytes to ensure it finds
+ // a full NUL char16_t when |*aLen| is odd.
+ nsresult result = NS_ERROR_FAILURE;
+ if (aHGBL != nullptr) {
+ LPSTR lpStr = (LPSTR)GlobalLock(aHGBL);
+ CheckedInt<uint32_t> allocSize =
+ CheckedInt<uint32_t>(GlobalSize(aHGBL)) + 3;
+ if (!allocSize.isValid()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ char* data = static_cast<char*>(malloc(allocSize.value()));
+ if (data) {
+ uint32_t size = allocSize.value() - 3;
+ memcpy(data, lpStr, size);
+ // null terminate for safety
+ data[size] = data[size + 1] = data[size + 2] = '\0';
+
+ GlobalUnlock(aHGBL);
+ *aData = data;
+ *aLen = size;
+
+ result = NS_OK;
+ }
+ } else {
+ // We really shouldn't ever get here
+ // but just in case
+ *aData = nullptr;
+ *aLen = 0;
+ LPVOID lpMsgBuf;
+
+ FormatMessageW(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr,
+ GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
+ (LPWSTR)&lpMsgBuf, 0, nullptr);
+
+ // Display the string.
+ MessageBoxW(nullptr, (LPCWSTR)lpMsgBuf, L"GetLastError",
+ MB_OK | MB_ICONINFORMATION);
+
+ // Free the buffer.
+ LocalFree(lpMsgBuf);
+ }
+
+ return result;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::GetNativeDataOffClipboard(nsIWidget* aWidget,
+ UINT /*aIndex*/, UINT aFormat,
+ void** aData, uint32_t* aLen) {
+ HGLOBAL hglb;
+ nsresult result = NS_ERROR_FAILURE;
+
+ HWND nativeWin = nullptr;
+ if (::OpenClipboard(nativeWin)) {
+ hglb = ::GetClipboardData(aFormat);
+ result = GetGlobalData(hglb, aData, aLen);
+ ::CloseClipboard();
+ }
+ return result;
+}
+
+static void DisplayErrCode(HRESULT hres) {
+#if defined(DEBUG_rods) || defined(DEBUG_pinkerton)
+ if (hres == E_INVALIDARG) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_INVALIDARG\n"));
+ } else if (hres == E_UNEXPECTED) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_UNEXPECTED\n"));
+ } else if (hres == E_OUTOFMEMORY) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_OUTOFMEMORY\n"));
+ } else if (hres == DV_E_LINDEX) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_LINDEX\n"));
+ } else if (hres == DV_E_FORMATETC) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_FORMATETC\n"));
+ } else if (hres == DV_E_TYMED) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_TYMED\n"));
+ } else if (hres == DV_E_DVASPECT) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_DVASPECT\n"));
+ } else if (hres == OLE_E_NOTRUNNING) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("OLE_E_NOTRUNNING\n"));
+ } else if (hres == STG_E_MEDIUMFULL) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("STG_E_MEDIUMFULL\n"));
+ } else if (hres == DV_E_CLIPFORMAT) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_CLIPFORMAT\n"));
+ } else if (hres == S_OK) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("S_OK\n"));
+ } else {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info,
+ ("****** DisplayErrCode 0x%X\n", hres));
+ }
+#endif
+}
+
+//-------------------------------------------------------------------------
+static HRESULT FillSTGMedium(IDataObject* aDataObject, UINT aFormat,
+ LPFORMATETC pFE, LPSTGMEDIUM pSTM, DWORD aTymed) {
+ SET_FORMATETC(*pFE, aFormat, 0, DVASPECT_CONTENT, -1, aTymed);
+
+ // Starting by querying for the data to see if we can get it as from global
+ // memory
+ HRESULT hres = S_FALSE;
+ hres = aDataObject->QueryGetData(pFE);
+ DisplayErrCode(hres);
+ if (S_OK == hres) {
+ hres = aDataObject->GetData(pFE, pSTM);
+ DisplayErrCode(hres);
+ }
+ return hres;
+}
+
+//-------------------------------------------------------------------------
+// If aFormat is CF_DIBV5, aMIMEImageFormat must be a type for which we have
+// an image encoder (e.g. image/png).
+// For other values of aFormat, it is OK to pass null for aMIMEImageFormat.
+nsresult nsClipboard::GetNativeDataOffClipboard(IDataObject* aDataObject,
+ UINT aIndex, UINT aFormat,
+ const char* aMIMEImageFormat,
+ void** aData, uint32_t* aLen) {
+ nsresult result = NS_ERROR_FAILURE;
+ *aData = nullptr;
+ *aLen = 0;
+
+ if (!aDataObject) {
+ return result;
+ }
+
+ UINT format = aFormat;
+ HRESULT hres = S_FALSE;
+
+ // XXX at the moment we only support global memory transfers
+ // It is here where we will add support for native images
+ // and IStream
+ FORMATETC fe;
+ STGMEDIUM stm;
+ hres = FillSTGMedium(aDataObject, format, &fe, &stm, TYMED_HGLOBAL);
+
+ // Currently this is only handling TYMED_HGLOBAL data
+ // For Text, Dibs, Files, and generic data (like HTML)
+ if (S_OK == hres) {
+ static CLIPFORMAT fileDescriptorFlavorA =
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
+ static CLIPFORMAT fileDescriptorFlavorW =
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
+ static CLIPFORMAT fileFlavor =
+ ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
+ static CLIPFORMAT preferredDropEffect =
+ ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
+
+ switch (stm.tymed) {
+ case TYMED_HGLOBAL: {
+ switch (fe.cfFormat) {
+ case CF_TEXT: {
+ // Get the data out of the global data handle. The size we
+ // return should not include the null because the other
+ // platforms don't use nulls, so just return the length we get
+ // back from strlen(), since we know CF_TEXT is null
+ // terminated. Recall that GetGlobalData() returns the size of
+ // the allocated buffer, not the size of the data (on 98, these
+ // are not the same) so we can't use that.
+ uint32_t allocLen = 0;
+ if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
+ *aLen = strlen(reinterpret_cast<char*>(*aData));
+ result = NS_OK;
+ }
+ } break;
+
+ case CF_UNICODETEXT: {
+ // Get the data out of the global data handle. The size we
+ // return should not include the null because the other
+ // platforms don't use nulls, so just return the length we get
+ // back from strlen(), since we know CF_UNICODETEXT is null
+ // terminated. Recall that GetGlobalData() returns the size of
+ // the allocated buffer, not the size of the data (on 98, these
+ // are not the same) so we can't use that.
+ uint32_t allocLen = 0;
+ if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
+ *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) * 2;
+ result = NS_OK;
+ }
+ } break;
+
+ case CF_DIBV5:
+ if (aMIMEImageFormat) {
+ uint32_t allocLen = 0;
+ const char* clipboardData;
+ if (NS_SUCCEEDED(GetGlobalData(
+ stm.hGlobal, (void**)&clipboardData, &allocLen))) {
+ nsCOMPtr<imgIContainer> container;
+ nsCOMPtr<imgITools> imgTools =
+ do_CreateInstance("@mozilla.org/image/tools;1");
+ result = imgTools->DecodeImageFromBuffer(
+ clipboardData, allocLen,
+ nsLiteralCString(IMAGE_BMP_MS_CLIPBOARD),
+ getter_AddRefs(container));
+ if (NS_FAILED(result)) {
+ break;
+ }
+
+ nsAutoCString mimeType;
+ if (strcmp(aMIMEImageFormat, kJPGImageMime) == 0) {
+ mimeType.Assign(IMAGE_JPEG);
+ } else {
+ mimeType.Assign(aMIMEImageFormat);
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ result = imgTools->EncodeImage(container, mimeType, u""_ns,
+ getter_AddRefs(inputStream));
+ if (NS_FAILED(result)) {
+ break;
+ }
+
+ if (!inputStream) {
+ result = NS_ERROR_FAILURE;
+ break;
+ }
+
+ *aData = inputStream.forget().take();
+ *aLen = sizeof(nsIInputStream*);
+ }
+ }
+ break;
+
+ case CF_HDROP: {
+ // in the case of a file drop, multiple files are stashed within a
+ // single data object. In order to match mozilla's D&D apis, we
+ // just pull out the file at the requested index, pretending as
+ // if there really are multiple drag items.
+ HDROP dropFiles = (HDROP)GlobalLock(stm.hGlobal);
+
+ UINT numFiles = ::DragQueryFileW(dropFiles, 0xFFFFFFFF, nullptr, 0);
+ NS_ASSERTION(numFiles > 0,
+ "File drop flavor, but no files...hmmmm");
+ NS_ASSERTION(aIndex < numFiles,
+ "Asked for a file index out of range of list");
+ if (numFiles > 0) {
+ UINT fileNameLen =
+ ::DragQueryFileW(dropFiles, aIndex, nullptr, 0);
+ wchar_t* buffer = reinterpret_cast<wchar_t*>(
+ moz_xmalloc((fileNameLen + 1) * sizeof(wchar_t)));
+ ::DragQueryFileW(dropFiles, aIndex, buffer, fileNameLen + 1);
+ *aData = buffer;
+ *aLen = fileNameLen * sizeof(char16_t);
+ result = NS_OK;
+ }
+ GlobalUnlock(stm.hGlobal);
+
+ } break;
+
+ default: {
+ if (fe.cfFormat == fileDescriptorFlavorA ||
+ fe.cfFormat == fileDescriptorFlavorW ||
+ fe.cfFormat == fileFlavor) {
+ NS_WARNING(
+ "Mozilla doesn't yet understand how to read this type of "
+ "file flavor");
+ } else {
+ // Get the data out of the global data handle. The size we
+ // return should not include the null because the other
+ // platforms don't use nulls, so just return the length we get
+ // back from strlen(), since we know CF_UNICODETEXT is null
+ // terminated. Recall that GetGlobalData() returns the size of
+ // the allocated buffer, not the size of the data (on 98, these
+ // are not the same) so we can't use that.
+ //
+ // NOTE: we are assuming that anything that falls into this
+ // default case is unicode. As we start to get more
+ // kinds of binary data, this may become an incorrect
+ // assumption. Stay tuned.
+ uint32_t allocLen = 0;
+ if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
+ if (fe.cfFormat == CF_HTML) {
+ // CF_HTML is actually UTF8, not unicode, so disregard the
+ // assumption above. We have to check the header for the
+ // actual length, and we'll do that in FindPlatformHTML().
+ // For now, return the allocLen. This case is mostly to
+ // ensure we don't try to call strlen on the buffer.
+ *aLen = allocLen;
+ } else if (fe.cfFormat == CF_CUSTOMTYPES) {
+ // Binary data
+ *aLen = allocLen;
+ } else if (fe.cfFormat == preferredDropEffect) {
+ // As per the MSDN doc entitled: "Shell Clipboard Formats"
+ // CFSTR_PREFERREDDROPEFFECT should return a DWORD
+ // Reference:
+ // http://msdn.microsoft.com/en-us/library/bb776902(v=vs.85).aspx
+ NS_ASSERTION(
+ allocLen == sizeof(DWORD),
+ "CFSTR_PREFERREDDROPEFFECT should return a DWORD");
+ *aLen = allocLen;
+ } else {
+ *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) *
+ sizeof(char16_t);
+ }
+ result = NS_OK;
+ }
+ }
+ } break;
+ } // switch
+ } break;
+
+ case TYMED_GDI: {
+#ifdef DEBUG
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info,
+ ("*********************** TYMED_GDI\n"));
+#endif
+ } break;
+
+ default:
+ break;
+ } // switch
+
+ ReleaseStgMedium(&stm);
+ }
+
+ return result;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::GetDataFromDataObject(IDataObject* aDataObject,
+ UINT anIndex, nsIWidget* aWindow,
+ nsITransferable* aTransferable) {
+ // make sure we have a good transferable
+ if (!aTransferable) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult res = NS_ERROR_FAILURE;
+
+ // get flavor list that includes all flavors that can be written (including
+ // ones obtained through conversion)
+ nsTArray<nsCString> flavors;
+ res = aTransferable->FlavorsTransferableCanImport(flavors);
+ if (NS_FAILED(res)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Walk through flavors and see which flavor is on the clipboard them on the
+ // native clipboard,
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+ UINT format = GetFormat(flavorStr.get());
+
+ // Try to get the data using the desired flavor. This might fail, but all is
+ // not lost.
+ void* data = nullptr;
+ uint32_t dataLen = 0;
+ bool dataFound = false;
+ if (nullptr != aDataObject) {
+ if (NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject, anIndex, format,
+ flavorStr.get(), &data,
+ &dataLen))) {
+ dataFound = true;
+ }
+ } else if (nullptr != aWindow) {
+ if (NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow, anIndex, format,
+ &data, &dataLen))) {
+ dataFound = true;
+ }
+ }
+
+ // This is our second chance to try to find some data, having not found it
+ // when directly asking for the flavor. Let's try digging around in other
+ // flavors to help satisfy our craving for data.
+ if (!dataFound) {
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ dataFound =
+ FindUnicodeFromPlainText(aDataObject, anIndex, &data, &dataLen);
+ } else if (flavorStr.EqualsLiteral(kURLMime)) {
+ // drags from other windows apps expose the native
+ // CFSTR_INETURL{A,W} flavor
+ dataFound = FindURLFromNativeURL(aDataObject, anIndex, &data, &dataLen);
+ if (!dataFound) {
+ dataFound =
+ FindURLFromLocalFile(aDataObject, anIndex, &data, &dataLen);
+ }
+ }
+ } // if we try one last ditch effort to find our data
+
+ // Hopefully by this point we've found it and can go about our business
+ if (dataFound) {
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ if (flavorStr.EqualsLiteral(kFileMime)) {
+ // we have a file path in |data|. Create an nsLocalFile object.
+ nsDependentString filepath(reinterpret_cast<char16_t*>(data));
+ nsCOMPtr<nsIFile> file;
+ if (NS_SUCCEEDED(
+ NS_NewLocalFile(filepath, false, getter_AddRefs(file)))) {
+ genericDataWrapper = do_QueryInterface(file);
+ }
+ free(data);
+ } else if (flavorStr.EqualsLiteral(kNativeHTMLMime)) {
+ uint32_t dummy;
+ // the editor folks want CF_HTML exactly as it's on the clipboard, no
+ // conversions, no fancy stuff. Pull it off the clipboard, stuff it into
+ // a wrapper and hand it back to them.
+ if (FindPlatformHTML(aDataObject, anIndex, &data, &dummy, &dataLen)) {
+ nsPrimitiveHelpers::CreatePrimitiveForData(
+ flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
+ } else {
+ free(data);
+ continue; // something wrong with this flavor, keep looking for other
+ // data
+ }
+ free(data);
+ } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
+ uint32_t startOfData = 0;
+ // The JS folks want CF_HTML exactly as it is on the clipboard, but
+ // minus the CF_HTML header index information.
+ // It also needs to be converted to UTF16 and have linebreaks changed.
+ if (FindPlatformHTML(aDataObject, anIndex, &data, &startOfData,
+ &dataLen)) {
+ dataLen -= startOfData;
+ nsPrimitiveHelpers::CreatePrimitiveForCFHTML(
+ static_cast<char*>(data) + startOfData, &dataLen,
+ getter_AddRefs(genericDataWrapper));
+ } else {
+ free(data);
+ continue; // something wrong with this flavor, keep looking for other
+ // data
+ }
+ free(data);
+ } else if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime)) {
+ nsIInputStream* imageStream = reinterpret_cast<nsIInputStream*>(data);
+ genericDataWrapper = do_QueryInterface(imageStream);
+ NS_IF_RELEASE(imageStream);
+ } else {
+ // Treat custom types as a string of bytes.
+ if (!flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ // we probably have some form of text. The DOM only wants LF, so
+ // convert from Win32 line endings to DOM line endings.
+ int32_t signedLen = static_cast<int32_t>(dataLen);
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &data,
+ &signedLen);
+ dataLen = signedLen;
+
+ if (flavorStr.EqualsLiteral(kRTFMime)) {
+ // RTF on Windows is known to sometimes deliver an extra null byte.
+ if (dataLen > 0 && static_cast<char*>(data)[dataLen - 1] == '\0') {
+ dataLen--;
+ }
+ }
+ }
+
+ nsPrimitiveHelpers::CreatePrimitiveForData(
+ flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
+ free(data);
+ }
+
+ NS_ASSERTION(genericDataWrapper,
+ "About to put null data into the transferable");
+ aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
+ res = NS_OK;
+
+ // we found one, get out of the loop
+ break;
+ }
+ } // foreach flavor
+
+ return res;
+}
+
+//
+// FindPlatformHTML
+//
+// Someone asked for the OS CF_HTML flavor. We give it back to them exactly
+// as-is.
+//
+bool nsClipboard ::FindPlatformHTML(IDataObject* inDataObject, UINT inIndex,
+ void** outData, uint32_t* outStartOfData,
+ uint32_t* outDataLen) {
+ // Reference: MSDN doc entitled "HTML Clipboard Format"
+ // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
+ // CF_HTML is UTF8, not unicode. We also can't rely on it being
+ // null-terminated so we have to check the CF_HTML header for the correct
+ // length. The length we return is the bytecount from the beginning of the
+ // selected data to the end of the selected data, without the null
+ // termination. Because it's UTF8, we're guaranteed the header is ASCII.
+
+ if (!outData || !*outData) {
+ return false;
+ }
+
+ char version[8] = {0};
+ int32_t startOfData = 0;
+ int32_t endOfData = 0;
+ int numFound =
+ sscanf((char*)*outData, "Version:%7s\nStartHTML:%d\nEndHTML:%d", version,
+ &startOfData, &endOfData);
+
+ if (numFound != 3 || startOfData < -1 || endOfData < -1) {
+ return false;
+ }
+
+ // Fixup the start and end markers if they have no context (set to -1)
+ if (startOfData == -1) {
+ startOfData = 0;
+ }
+ if (endOfData == -1) {
+ endOfData = *outDataLen;
+ }
+
+ // Make sure we were passed sane values within our buffer size.
+ // (Note that we've handled all cases of negative endOfData above, so we can
+ // safely cast it to be unsigned here.)
+ if (!endOfData || startOfData >= endOfData ||
+ static_cast<uint32_t>(endOfData) > *outDataLen) {
+ return false;
+ }
+
+ // We want to return the buffer not offset by startOfData because it will be
+ // parsed out later (probably by HTMLEditor::ParseCFHTML) when it is still
+ // in CF_HTML format.
+
+ // We return the byte offset from the start of the data buffer to where the
+ // HTML data starts. The caller might want to extract the HTML only.
+ *outStartOfData = startOfData;
+ *outDataLen = endOfData;
+ return true;
+}
+
+//
+// FindUnicodeFromPlainText
+//
+// we are looking for text/unicode and we failed to find it on the clipboard
+// first, try again with text/plain. If that is present, convert it to unicode.
+//
+bool nsClipboard ::FindUnicodeFromPlainText(IDataObject* inDataObject,
+ UINT inIndex, void** outData,
+ uint32_t* outDataLen) {
+ // we are looking for text/unicode and we failed to find it on the clipboard
+ // first, try again with text/plain. If that is present, convert it to
+ // unicode.
+ nsresult rv =
+ GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kTextMime),
+ nullptr, outData, outDataLen);
+ if (NS_FAILED(rv) || !*outData) {
+ return false;
+ }
+
+ const char* castedText = static_cast<char*>(*outData);
+ nsAutoString tmp;
+ rv = NS_CopyNativeToUnicode(nsDependentCSubstring(castedText, *outDataLen),
+ tmp);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ // out with the old, in with the new
+ free(*outData);
+ *outData = ToNewUnicode(tmp);
+ *outDataLen = tmp.Length() * sizeof(char16_t);
+
+ return true;
+
+} // FindUnicodeFromPlainText
+
+//
+// FindURLFromLocalFile
+//
+// we are looking for a URL and couldn't find it, try again with looking for
+// a local file. If we have one, it may either be a normal file or an internet
+// shortcut. In both cases, however, we can get a URL (it will be a file:// url
+// in the local file case).
+//
+bool nsClipboard ::FindURLFromLocalFile(IDataObject* inDataObject, UINT inIndex,
+ void** outData, uint32_t* outDataLen) {
+ bool dataFound = false;
+
+ nsresult loadResult =
+ GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kFileMime),
+ nullptr, outData, outDataLen);
+ if (NS_SUCCEEDED(loadResult) && *outData) {
+ // we have a file path in |data|. Is it an internet shortcut or a normal
+ // file?
+ const nsDependentString filepath(static_cast<char16_t*>(*outData));
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_NewLocalFile(filepath, true, getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ free(*outData);
+ return dataFound;
+ }
+
+ if (IsInternetShortcut(filepath)) {
+ free(*outData);
+ nsAutoCString url;
+ ResolveShortcut(file, url);
+ if (!url.IsEmpty()) {
+ // convert it to unicode and pass it out
+ NS_ConvertUTF8toUTF16 urlString(url);
+ // the internal mozilla URL format, text/x-moz-url, contains
+ // URL\ntitle. We can guess the title from the file's name.
+ nsAutoString title;
+ file->GetLeafName(title);
+ // We rely on IsInternetShortcut check that file has a .url extension.
+ title.SetLength(title.Length() - 4);
+ if (title.IsEmpty()) {
+ title = urlString;
+ }
+ *outData = ToNewUnicode(urlString + u"\n"_ns + title);
+ *outDataLen =
+ NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
+
+ dataFound = true;
+ }
+ } else {
+ // we have a normal file, use some Necko objects to get our file path
+ nsAutoCString urlSpec;
+ NS_GetURLSpecFromFile(file, urlSpec);
+
+ // convert it to unicode and pass it out
+ free(*outData);
+ *outData = UTF8ToNewUnicode(urlSpec);
+ *outDataLen =
+ NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
+ dataFound = true;
+ } // else regular file
+ }
+
+ return dataFound;
+} // FindURLFromLocalFile
+
+//
+// FindURLFromNativeURL
+//
+// we are looking for a URL and couldn't find it using our internal
+// URL flavor, so look for it using the native URL flavor,
+// CF_INETURLSTRW (We don't handle CF_INETURLSTRA currently)
+//
+bool nsClipboard ::FindURLFromNativeURL(IDataObject* inDataObject, UINT inIndex,
+ void** outData, uint32_t* outDataLen) {
+ bool dataFound = false;
+
+ void* tempOutData = nullptr;
+ uint32_t tempDataLen = 0;
+
+ nsresult loadResult = GetNativeDataOffClipboard(
+ inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLW), nullptr,
+ &tempOutData, &tempDataLen);
+ if (NS_SUCCEEDED(loadResult) && tempOutData) {
+ nsDependentString urlString(static_cast<char16_t*>(tempOutData));
+ // the internal mozilla URL format, text/x-moz-url, contains
+ // URL\ntitle. Since we don't actually have a title here,
+ // just repeat the URL to fake it.
+ *outData = ToNewUnicode(urlString + u"\n"_ns + urlString);
+ *outDataLen =
+ NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
+ free(tempOutData);
+ dataFound = true;
+ } else {
+ loadResult = GetNativeDataOffClipboard(
+ inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLA),
+ nullptr, &tempOutData, &tempDataLen);
+ if (NS_SUCCEEDED(loadResult) && tempOutData) {
+ // CFSTR_INETURLA is (currently) equal to CFSTR_SHELLURL which is equal to
+ // CF_TEXT which is by definition ANSI encoded.
+ nsCString urlUnescapedA;
+ bool unescaped =
+ NS_UnescapeURL(static_cast<char*>(tempOutData), tempDataLen,
+ esc_OnlyNonASCII | esc_SkipControl, urlUnescapedA);
+
+ nsString urlString;
+ if (unescaped) {
+ NS_CopyNativeToUnicode(urlUnescapedA, urlString);
+ } else {
+ NS_CopyNativeToUnicode(
+ nsDependentCString(static_cast<char*>(tempOutData), tempDataLen),
+ urlString);
+ }
+
+ // the internal mozilla URL format, text/x-moz-url, contains
+ // URL\ntitle. Since we don't actually have a title here,
+ // just repeat the URL to fake it.
+ *outData = ToNewUnicode(urlString + u"\n"_ns + urlString);
+ *outDataLen =
+ NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
+ free(tempOutData);
+ dataFound = true;
+ }
+ }
+
+ return dataFound;
+} // FindURLFromNativeURL
+
+//
+// ResolveShortcut
+//
+void nsClipboard ::ResolveShortcut(nsIFile* aFile, nsACString& outURL) {
+ nsCOMPtr<nsIFileProtocolHandler> fph;
+ nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = fph->ReadURLFile(aFile, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ uri->GetSpec(outURL);
+} // ResolveShortcut
+
+//
+// IsInternetShortcut
+//
+// A file is an Internet Shortcut if it ends with .URL
+//
+bool nsClipboard ::IsInternetShortcut(const nsAString& inFileName) {
+ return StringEndsWith(inFileName, u".url"_ns,
+ nsCaseInsensitiveStringComparator);
+} // IsInternetShortcut
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) {
+ // make sure we have a good transferable
+ if (!aTransferable || aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult res;
+
+ // This makes sure we can use the OLE functionality for the clipboard
+ IDataObject* dataObj;
+ if (S_OK == ::OleGetClipboard(&dataObj)) {
+ // Use OLE IDataObject for clipboard operations
+ res = GetDataFromDataObject(dataObj, 0, nullptr, aTransferable);
+ dataObj->Release();
+ } else {
+ // do it the old manual way
+ res = GetDataFromDataObject(nullptr, 0, mWindow, aTransferable);
+ }
+ return res;
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
+ // Some programs such as ZoneAlarm monitor clipboard usage and then open the
+ // clipboard to scan it. If we i) empty and then ii) set data, then the
+ // 'set data' can sometimes fail with access denied becacuse another program
+ // has the clipboard open. So to avoid this race condition for OpenClipboard
+ // we do not empty the clipboard when we're setting it.
+ if (aWhichClipboard == kGlobalClipboard && !mEmptyingForSetData) {
+ OleSetClipboard(nullptr);
+ }
+ return nsBaseClipboard::EmptyClipboard(aWhichClipboard);
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP nsClipboard::HasDataMatchingFlavors(
+ const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
+ bool* _retval) {
+ *_retval = false;
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_OK;
+ }
+
+ for (auto& flavor : aFlavorList) {
+#ifdef DEBUG
+ if (flavor.EqualsLiteral(kTextMime)) {
+ NS_WARNING(
+ "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode "
+ "INSTEAD");
+ }
+#endif
+
+ UINT format = GetFormat(flavor.get());
+ if (IsClipboardFormatAvailable(format)) {
+ *_retval = true;
+ break;
+ } else {
+ // We haven't found the exact flavor the client asked for, but maybe we
+ // can still find it from something else that's on the clipboard...
+ if (flavor.EqualsLiteral(kUnicodeMime)) {
+ // client asked for unicode and it wasn't present, check if we have
+ // CF_TEXT. We'll handle the actual data substitution in the data
+ // object.
+ if (IsClipboardFormatAvailable(GetFormat(kTextMime))) {
+ *_retval = true;
+ break;
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/widget/windows/nsClipboard.h b/widget/windows/nsClipboard.h
new file mode 100644
index 0000000000..ace5c85e4c
--- /dev/null
+++ b/widget/windows/nsClipboard.h
@@ -0,0 +1,96 @@
+/* -*- 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 "nsBaseClipboard.h"
+#include "nsIObserver.h"
+#include "nsIURI.h"
+#include <windows.h>
+
+class nsITransferable;
+class nsIWidget;
+class nsIFile;
+struct IDataObject;
+
+/**
+ * Native Win32 Clipboard wrapper
+ */
+
+class nsClipboard : public nsBaseClipboard, public nsIObserver {
+ virtual ~nsClipboard();
+
+ public:
+ nsClipboard();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIObserver
+ NS_DECL_NSIOBSERVER
+
+ // nsIClipboard
+ NS_IMETHOD HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
+ int32_t aWhichClipboard,
+ bool* _retval) override;
+ NS_IMETHOD EmptyClipboard(int32_t aWhichClipboard) override;
+
+ // Internal Native Routines
+ static nsresult CreateNativeDataObject(nsITransferable* aTransferable,
+ IDataObject** aDataObj, nsIURI* uri);
+ static nsresult SetupNativeDataObject(nsITransferable* aTransferable,
+ IDataObject* aDataObj);
+ static nsresult GetDataFromDataObject(IDataObject* aDataObject, UINT anIndex,
+ nsIWidget* aWindow,
+ nsITransferable* aTransferable);
+ static nsresult GetNativeDataOffClipboard(nsIWidget* aWindow, UINT aIndex,
+ UINT aFormat, void** aData,
+ uint32_t* aLen);
+ static nsresult GetNativeDataOffClipboard(IDataObject* aDataObject,
+ UINT aIndex, UINT aFormat,
+ const char* aMIMEImageFormat,
+ void** aData, uint32_t* aLen);
+ static nsresult GetGlobalData(HGLOBAL aHGBL, void** aData, uint32_t* aLen);
+
+ // This function returns the internal Windows clipboard format identifier
+ // for a given Mime string. The default is to map kHTMLMime ("text/html")
+ // to the clipboard format CF_HTML ("HTLM Format"), but it can also be
+ // registered as clipboard format "text/html" to support previous versions
+ // of Gecko.
+ static UINT GetFormat(const char* aMimeStr, bool aMapHTMLMime = true);
+
+ static UINT CF_HTML;
+ static UINT CF_CUSTOMTYPES;
+
+ protected:
+ NS_IMETHOD SetNativeClipboardData(int32_t aWhichClipboard) override;
+ NS_IMETHOD GetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) override;
+
+ static bool IsInternetShortcut(const nsAString& inFileName);
+ static bool FindURLFromLocalFile(IDataObject* inDataObject, UINT inIndex,
+ void** outData, uint32_t* outDataLen);
+ static bool FindURLFromNativeURL(IDataObject* inDataObject, UINT inIndex,
+ void** outData, uint32_t* outDataLen);
+ static bool FindUnicodeFromPlainText(IDataObject* inDataObject, UINT inIndex,
+ void** outData, uint32_t* outDataLen);
+ static bool FindPlatformHTML(IDataObject* inDataObject, UINT inIndex,
+ void** outData, uint32_t* outStartOfData,
+ uint32_t* outDataLen);
+ static void ResolveShortcut(nsIFile* inFileName, nsACString& outURL);
+
+ nsIWidget* mWindow;
+};
+
+#define SET_FORMATETC(fe, cf, td, asp, li, med) \
+ { \
+ (fe).cfFormat = cf; \
+ (fe).ptd = td; \
+ (fe).dwAspect = asp; \
+ (fe).lindex = li; \
+ (fe).tymed = med; \
+ }
+
+#endif // nsClipboard_h__
diff --git a/widget/windows/nsColorPicker.cpp b/widget/windows/nsColorPicker.cpp
new file mode 100644
index 0000000000..7bc3e25d2f
--- /dev/null
+++ b/widget/windows/nsColorPicker.cpp
@@ -0,0 +1,208 @@
+/* -*- 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 "nsColorPicker.h"
+
+#include <shlwapi.h>
+
+#include "mozilla/AutoRestore.h"
+#include "nsIWidget.h"
+#include "nsString.h"
+#include "WidgetUtils.h"
+#include "nsPIDOMWindow.h"
+
+using namespace mozilla::widget;
+
+namespace {
+// Manages NS_NATIVE_TMP_WINDOW child windows. NS_NATIVE_TMP_WINDOWs are
+// temporary child windows of mParentWidget created to address RTL issues
+// in picker dialogs. We are responsible for destroying these.
+class AutoDestroyTmpWindow {
+ public:
+ explicit AutoDestroyTmpWindow(HWND aTmpWnd) : mWnd(aTmpWnd) {}
+
+ ~AutoDestroyTmpWindow() {
+ if (mWnd) DestroyWindow(mWnd);
+ }
+
+ inline HWND get() const { return mWnd; }
+
+ private:
+ HWND mWnd;
+};
+
+static DWORD ColorStringToRGB(const nsAString& aColor) {
+ DWORD result = 0;
+
+ for (uint32_t i = 1; i < aColor.Length(); ++i) {
+ result *= 16;
+
+ char16_t c = aColor[i];
+ if (c >= '0' && c <= '9') {
+ result += c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ result += 10 + (c - 'a');
+ } else {
+ result += 10 + (c - 'A');
+ }
+ }
+
+ DWORD r = result & 0x00FF0000;
+ DWORD g = result & 0x0000FF00;
+ DWORD b = result & 0x000000FF;
+
+ r = r >> 16;
+ b = b << 16;
+
+ result = r | g | b;
+
+ return result;
+}
+
+static nsString ToHexString(BYTE n) {
+ nsString result;
+ if (n <= 0x0F) {
+ result.Append('0');
+ }
+ result.AppendInt(n, 16);
+ return result;
+}
+
+static void BGRIntToRGBString(DWORD color, nsAString& aResult) {
+ BYTE r = GetRValue(color);
+ BYTE g = GetGValue(color);
+ BYTE b = GetBValue(color);
+
+ aResult.Assign('#');
+ aResult.Append(ToHexString(r));
+ aResult.Append(ToHexString(g));
+ aResult.Append(ToHexString(b));
+}
+} // namespace
+
+static AsyncColorChooser* gColorChooser;
+
+AsyncColorChooser::AsyncColorChooser(COLORREF aInitialColor,
+ nsIWidget* aParentWidget,
+ nsIColorPickerShownCallback* aCallback)
+ : mozilla::Runnable("AsyncColorChooser"),
+ mInitialColor(aInitialColor),
+ mColor(aInitialColor),
+ mParentWidget(aParentWidget),
+ mCallback(aCallback) {}
+
+NS_IMETHODIMP
+AsyncColorChooser::Run() {
+ static COLORREF sCustomColors[16] = {0};
+
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Color pickers can only be opened from main thread currently");
+
+ // Allow only one color picker to be opened at a time, to workaround bug
+ // 944737
+ if (!gColorChooser) {
+ mozilla::AutoRestore<AsyncColorChooser*> restoreColorChooser(gColorChooser);
+ gColorChooser = this;
+
+ AutoDestroyTmpWindow adtw((HWND)(
+ mParentWidget.get() ? mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW)
+ : nullptr));
+
+ CHOOSECOLOR options;
+ options.lStructSize = sizeof(options);
+ options.hwndOwner = adtw.get();
+ options.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ENABLEHOOK;
+ options.rgbResult = mInitialColor;
+ options.lpCustColors = sCustomColors;
+ options.lpfnHook = HookProc;
+
+ mColor = ChooseColor(&options) ? options.rgbResult : mInitialColor;
+ } else {
+ NS_WARNING(
+ "Currently, it's not possible to open more than one color "
+ "picker at a time");
+ mColor = mInitialColor;
+ }
+
+ if (mCallback) {
+ nsAutoString colorStr;
+ BGRIntToRGBString(mColor, colorStr);
+ mCallback->Done(colorStr);
+ }
+
+ return NS_OK;
+}
+
+void AsyncColorChooser::Update(COLORREF aColor) {
+ if (mColor != aColor) {
+ mColor = aColor;
+
+ nsAutoString colorStr;
+ BGRIntToRGBString(mColor, colorStr);
+ mCallback->Update(colorStr);
+ }
+}
+
+/* static */ UINT_PTR CALLBACK AsyncColorChooser::HookProc(HWND aDialog,
+ UINT aMsg,
+ WPARAM aWParam,
+ LPARAM aLParam) {
+ if (!gColorChooser) {
+ return 0;
+ }
+
+ if (aMsg == WM_INITDIALOG) {
+ // "The default dialog box procedure processes the WM_INITDIALOG message
+ // before passing it to the hook procedure.
+ // For all other messages, the hook procedure receives the message first."
+ // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/nc-commdlg-lpcchookproc
+ // "The dialog box procedure should return TRUE to direct the system to
+ // set the keyboard focus to the control specified by wParam."
+ // https://docs.microsoft.com/en-us/windows/win32/dlgbox/wm-initdialog
+ return 1;
+ }
+
+ if (aMsg == WM_CTLCOLORSTATIC) {
+ // The color picker does not expose a proper way to retrieve the current
+ // color, so we need to obtain it from the static control displaying the
+ // current color instead.
+ const int kCurrentColorBoxID = 709;
+ if ((HWND)aLParam == GetDlgItem(aDialog, kCurrentColorBoxID)) {
+ gColorChooser->Update(GetPixel((HDC)aWParam, 0, 0));
+ }
+ }
+
+ // Let the default dialog box procedure processes the message.
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIColorPicker
+
+nsColorPicker::nsColorPicker() {}
+
+nsColorPicker::~nsColorPicker() {}
+
+NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
+
+NS_IMETHODIMP
+nsColorPicker::Init(mozIDOMWindowProxy* parent, const nsAString& title,
+ const nsAString& aInitialColor) {
+ MOZ_ASSERT(parent,
+ "Null parent passed to colorpicker, no color picker for you!");
+ mParentWidget =
+ WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(parent));
+ mInitialColor = ColorStringToRGB(aInitialColor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsColorPicker::Open(nsIColorPickerShownCallback* aCallback) {
+ NS_ENSURE_ARG(aCallback);
+ nsCOMPtr<nsIRunnable> event =
+ new AsyncColorChooser(mInitialColor, mParentWidget, aCallback);
+ return NS_DispatchToMainThread(event);
+}
diff --git a/widget/windows/nsColorPicker.h b/widget/windows/nsColorPicker.h
new file mode 100644
index 0000000000..fe568df952
--- /dev/null
+++ b/widget/windows/nsColorPicker.h
@@ -0,0 +1,54 @@
+/* -*- 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 <windows.h>
+#include <commdlg.h>
+
+#include "nsCOMPtr.h"
+#include "nsIColorPicker.h"
+#include "nsThreadUtils.h"
+
+class nsIWidget;
+
+class AsyncColorChooser : public mozilla::Runnable {
+ public:
+ AsyncColorChooser(COLORREF aInitialColor, nsIWidget* aParentWidget,
+ nsIColorPickerShownCallback* aCallback);
+ NS_IMETHOD Run() override;
+
+ private:
+ void Update(COLORREF aColor);
+
+ static UINT_PTR CALLBACK HookProc(HWND aDialog, UINT aMsg, WPARAM aWParam,
+ LPARAM aLParam);
+
+ COLORREF mInitialColor;
+ COLORREF mColor;
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsCOMPtr<nsIColorPickerShownCallback> mCallback;
+};
+
+class nsColorPicker : public nsIColorPicker {
+ virtual ~nsColorPicker();
+
+ public:
+ nsColorPicker();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init(mozIDOMWindowProxy* parent, const nsAString& title,
+ const nsAString& aInitialColor);
+ NS_IMETHOD Open(nsIColorPickerShownCallback* aCallback) override;
+
+ private:
+ COLORREF mInitialColor;
+ nsCOMPtr<nsIWidget> mParentWidget;
+};
+
+#endif // nsColorPicker_h__
diff --git a/widget/windows/nsDataObj.cpp b/widget/windows/nsDataObj.cpp
new file mode 100644
index 0000000000..1f4d9ecbf2
--- /dev/null
+++ b/widget/windows/nsDataObj.cpp
@@ -0,0 +1,2231 @@
+/* -*- 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 "mozilla/TextUtils.h"
+
+#include <ole2.h>
+#include <shlobj.h>
+
+#include "nsDataObj.h"
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#include "nsReadableUtils.h"
+#include "nsICookieJarSettings.h"
+#include "nsITransferable.h"
+#include "nsISupportsPrimitives.h"
+#include "IEnumFE.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "nsPrintfCString.h"
+#include "nsIStringBundle.h"
+#include "nsEscape.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/Unused.h"
+#include "nsIOutputStream.h"
+#include "nscore.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsITimer.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsContentUtils.h"
+#include "nsIPrincipal.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsMimeTypes.h"
+#include "imgIEncoder.h"
+#include "imgITools.h"
+
+#include "mozilla/LazyIdleThread.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::glue;
+using namespace mozilla::widget;
+
+#define BFH_LENGTH 14
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
+//-----------------------------------------------------------------------------
+// CStreamBase implementation
+nsDataObj::CStreamBase::CStreamBase() : mStreamRead(0) {}
+
+//-----------------------------------------------------------------------------
+nsDataObj::CStreamBase::~CStreamBase() {}
+
+NS_IMPL_ISUPPORTS(nsDataObj::CStream, nsIStreamListener)
+
+//-----------------------------------------------------------------------------
+// CStream implementation
+nsDataObj::CStream::CStream() : mChannelRead(false) {}
+
+//-----------------------------------------------------------------------------
+nsDataObj::CStream::~CStream() {}
+
+//-----------------------------------------------------------------------------
+// helper - initializes the stream
+nsresult nsDataObj::CStream::Init(nsIURI* pSourceURI,
+ nsContentPolicyType aContentPolicyType,
+ nsIPrincipal* aRequestingPrincipal,
+ nsICookieJarSettings* aCookieJarSettings) {
+ // we can not create a channel without a requestingPrincipal
+ if (!aRequestingPrincipal) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv;
+ rv = NS_NewChannel(getter_AddRefs(mChannel), pSourceURI, aRequestingPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
+ aContentPolicyType, aCookieJarSettings,
+ nullptr, // PerformanceStorage
+ nullptr, // loadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_FROM_CACHE);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mChannel->AsyncOpen(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// IUnknown's QueryInterface, nsISupport's AddRef and Release are shared by
+// IUnknown and nsIStreamListener.
+STDMETHODIMP nsDataObj::CStream::QueryInterface(REFIID refiid,
+ void** ppvResult) {
+ *ppvResult = nullptr;
+ if (IID_IUnknown == refiid || refiid == IID_IStream)
+
+ {
+ *ppvResult = this;
+ }
+
+ if (nullptr != *ppvResult) {
+ ((LPUNKNOWN)*ppvResult)->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+// nsIStreamListener implementation
+NS_IMETHODIMP
+nsDataObj::CStream::OnDataAvailable(
+ nsIRequest* aRequest, nsIInputStream* aInputStream,
+ uint64_t aOffset, // offset within the stream
+ uint32_t aCount) // bytes available on this call
+{
+ // Extend the write buffer for the incoming data.
+ uint8_t* buffer = mChannelData.AppendElements(aCount, fallible);
+ if (!buffer) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ NS_ASSERTION((mChannelData.Length() == (aOffset + aCount)),
+ "stream length mismatch w/write buffer");
+
+ // Read() may not return aCount on a single call, so loop until we've
+ // accumulated all the data OnDataAvailable has promised.
+ nsresult rv;
+ uint32_t odaBytesReadTotal = 0;
+ do {
+ uint32_t bytesReadByCall = 0;
+ rv = aInputStream->Read((char*)(buffer + odaBytesReadTotal), aCount,
+ &bytesReadByCall);
+ odaBytesReadTotal += bytesReadByCall;
+ } while (aCount < odaBytesReadTotal && NS_SUCCEEDED(rv));
+ return rv;
+}
+
+NS_IMETHODIMP nsDataObj::CStream::OnStartRequest(nsIRequest* aRequest) {
+ mChannelResult = NS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDataObj::CStream::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ mChannelRead = true;
+ mChannelResult = aStatusCode;
+ return NS_OK;
+}
+
+// Pumps thread messages while waiting for the async listener operation to
+// complete. Failing this call will fail the stream incall from Windows
+// and cancel the operation.
+nsresult nsDataObj::CStream::WaitForCompletion() {
+ // We are guaranteed OnStopRequest will get called, so this should be ok.
+ SpinEventLoopUntil([&]() { return mChannelRead; });
+
+ if (!mChannelData.Length()) mChannelResult = NS_ERROR_FAILURE;
+
+ return mChannelResult;
+}
+
+//-----------------------------------------------------------------------------
+// IStream
+STDMETHODIMP nsDataObj::CStreamBase::Clone(IStream** ppStream) {
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::Commit(DWORD dwFrags) { return E_NOTIMPL; }
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::CopyTo(IStream* pDestStream,
+ ULARGE_INTEGER nBytesToCopy,
+ ULARGE_INTEGER* nBytesRead,
+ ULARGE_INTEGER* nBytesWritten) {
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::LockRegion(ULARGE_INTEGER nStart,
+ ULARGE_INTEGER nBytes,
+ DWORD dwFlags) {
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::Read(void* pvBuffer, ULONG nBytesToRead,
+ ULONG* nBytesRead) {
+ // Wait for the write into our buffer to complete via the stream listener.
+ // We can't respond to this by saying "call us back later".
+ if (NS_FAILED(WaitForCompletion())) return E_FAIL;
+
+ // Bytes left for Windows to read out of our buffer
+ ULONG bytesLeft = mChannelData.Length() - mStreamRead;
+ // Let Windows know what we will hand back, usually this is the entire buffer
+ *nBytesRead = std::min(bytesLeft, nBytesToRead);
+ // Copy the buffer data over
+ memcpy(pvBuffer, ((char*)mChannelData.Elements() + mStreamRead), *nBytesRead);
+ // Update our bytes read tracking
+ mStreamRead += *nBytesRead;
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::Revert(void) { return E_NOTIMPL; }
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::Seek(LARGE_INTEGER nMove, DWORD dwOrigin,
+ ULARGE_INTEGER* nNewPos) {
+ if (nNewPos == nullptr) return STG_E_INVALIDPOINTER;
+
+ if (nMove.LowPart == 0 && nMove.HighPart == 0 &&
+ (dwOrigin == STREAM_SEEK_SET || dwOrigin == STREAM_SEEK_CUR)) {
+ nNewPos->LowPart = 0;
+ nNewPos->HighPart = 0;
+ return S_OK;
+ }
+
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::SetSize(ULARGE_INTEGER nNewSize) {
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::Stat(STATSTG* statstg, DWORD dwFlags) {
+ if (statstg == nullptr) return STG_E_INVALIDPOINTER;
+
+ if (!mChannel || NS_FAILED(WaitForCompletion())) return E_FAIL;
+
+ memset((void*)statstg, 0, sizeof(STATSTG));
+
+ if (dwFlags != STATFLAG_NONAME) {
+ nsCOMPtr<nsIURI> sourceURI;
+ if (NS_FAILED(mChannel->GetURI(getter_AddRefs(sourceURI)))) {
+ return E_FAIL;
+ }
+
+ nsAutoCString strFileName;
+ nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
+ sourceURL->GetFileName(strFileName);
+
+ if (strFileName.IsEmpty()) return E_FAIL;
+
+ NS_UnescapeURL(strFileName);
+ NS_ConvertUTF8toUTF16 wideFileName(strFileName);
+
+ uint32_t nMaxNameLength = (wideFileName.Length() * 2) + 2;
+ void* retBuf = CoTaskMemAlloc(nMaxNameLength); // freed by caller
+ if (!retBuf) return STG_E_INSUFFICIENTMEMORY;
+
+ ZeroMemory(retBuf, nMaxNameLength);
+ memcpy(retBuf, wideFileName.get(), wideFileName.Length() * 2);
+ statstg->pwcsName = (LPOLESTR)retBuf;
+ }
+
+ SYSTEMTIME st;
+
+ statstg->type = STGTY_STREAM;
+
+ GetSystemTime(&st);
+ SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime);
+ statstg->ctime = statstg->atime = statstg->mtime;
+
+ statstg->cbSize.QuadPart = mChannelData.Length();
+ statstg->grfMode = STGM_READ;
+ statstg->grfLocksSupported = LOCK_ONLYONCE;
+ statstg->clsid = CLSID_NULL;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::UnlockRegion(ULARGE_INTEGER nStart,
+ ULARGE_INTEGER nBytes,
+ DWORD dwFlags) {
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::Write(const void* pvBuffer,
+ ULONG nBytesToRead,
+ ULONG* nBytesRead) {
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+HRESULT nsDataObj::CreateStream(IStream** outStream) {
+ NS_ENSURE_TRUE(outStream, E_INVALIDARG);
+
+ nsresult rv = NS_ERROR_FAILURE;
+ nsAutoString wideFileName;
+ nsCOMPtr<nsIURI> sourceURI;
+ HRESULT res;
+
+ res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
+ if (FAILED(res)) return res;
+
+ nsDataObj::CStream* pStream = new nsDataObj::CStream();
+ NS_ENSURE_TRUE(pStream, E_OUTOFMEMORY);
+
+ pStream->AddRef();
+
+ // query the requestingPrincipal from the transferable and add it to the new
+ // channel
+ nsCOMPtr<nsIPrincipal> requestingPrincipal =
+ mTransferable->GetRequestingPrincipal();
+ MOZ_ASSERT(requestingPrincipal, "can not create channel without a principal");
+
+ // Note that the cookieJarSettings could be null if the data object is for the
+ // image copy. We will fix this in Bug 1690532.
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ mTransferable->GetCookieJarSettings();
+
+ nsContentPolicyType contentPolicyType = mTransferable->GetContentPolicyType();
+ rv = pStream->Init(sourceURI, contentPolicyType, requestingPrincipal,
+ cookieJarSettings);
+ if (NS_FAILED(rv)) {
+ pStream->Release();
+ return E_FAIL;
+ }
+ *outStream = pStream;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// AutoCloseEvent implementation
+nsDataObj::AutoCloseEvent::AutoCloseEvent()
+ : mEvent(::CreateEventW(nullptr, TRUE, FALSE, nullptr)) {}
+
+bool nsDataObj::AutoCloseEvent::IsInited() const { return !!mEvent; }
+
+void nsDataObj::AutoCloseEvent::Signal() const { ::SetEvent(mEvent); }
+
+DWORD nsDataObj::AutoCloseEvent::Wait(DWORD aMillisec) const {
+ return ::WaitForSingleObject(mEvent, aMillisec);
+}
+
+//-----------------------------------------------------------------------------
+// AutoSetEvent implementation
+nsDataObj::AutoSetEvent::AutoSetEvent(NotNull<AutoCloseEvent*> aEvent)
+ : mEvent(aEvent) {}
+
+nsDataObj::AutoSetEvent::~AutoSetEvent() { Signal(); }
+
+void nsDataObj::AutoSetEvent::Signal() const { mEvent->Signal(); }
+
+bool nsDataObj::AutoSetEvent::IsWaiting() const {
+ return mEvent->Wait(0) == WAIT_TIMEOUT;
+}
+
+//-----------------------------------------------------------------------------
+// CMemStream implementation
+Win32SRWLock nsDataObj::CMemStream::mLock;
+
+//-----------------------------------------------------------------------------
+nsDataObj::CMemStream::CMemStream(nsHGLOBAL aGlobalMem, uint32_t aTotalLength,
+ already_AddRefed<AutoCloseEvent> aEvent)
+ : mGlobalMem(aGlobalMem), mEvent(aEvent), mTotalLength(aTotalLength) {
+ ::CoCreateFreeThreadedMarshaler(this, getter_AddRefs(mMarshaler));
+}
+
+//-----------------------------------------------------------------------------
+nsDataObj::CMemStream::~CMemStream() {}
+
+//-----------------------------------------------------------------------------
+// IUnknown
+STDMETHODIMP nsDataObj::CMemStream::QueryInterface(REFIID refiid,
+ void** ppvResult) {
+ *ppvResult = nullptr;
+ if (refiid == IID_IUnknown || refiid == IID_IStream ||
+ refiid == IID_IAgileObject) {
+ *ppvResult = this;
+ } else if (refiid == IID_IMarshal && mMarshaler) {
+ return mMarshaler->QueryInterface(refiid, ppvResult);
+ }
+
+ if (nullptr != *ppvResult) {
+ ((LPUNKNOWN)*ppvResult)->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+void nsDataObj::CMemStream::WaitForCompletion() {
+ if (!mEvent) {
+ // We are not waiting for obtaining the icon cache.
+ return;
+ }
+ if (!NS_IsMainThread()) {
+ mEvent->Wait(INFINITE);
+ } else {
+ // We should not block the main thread.
+ mEvent->Signal();
+ }
+ // mEvent will always be in the signaled state here.
+}
+
+//-----------------------------------------------------------------------------
+// IStream
+STDMETHODIMP nsDataObj::CMemStream::Read(void* pvBuffer, ULONG nBytesToRead,
+ ULONG* nBytesRead) {
+ // Wait until the event is signaled.
+ WaitForCompletion();
+
+ AutoExclusiveLock lock(mLock);
+ char* contents = reinterpret_cast<char*>(GlobalLock(mGlobalMem.get()));
+ if (!contents) {
+ return E_OUTOFMEMORY;
+ }
+
+ // Bytes left for Windows to read out of our buffer
+ ULONG bytesLeft = mTotalLength - mStreamRead;
+ // Let Windows know what we will hand back, usually this is the entire buffer
+ *nBytesRead = std::min(bytesLeft, nBytesToRead);
+ // Copy the buffer data over
+ memcpy(pvBuffer, contents + mStreamRead, *nBytesRead);
+ // Update our bytes read tracking
+ mStreamRead += *nBytesRead;
+
+ GlobalUnlock(mGlobalMem.get());
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CMemStream::Stat(STATSTG* statstg, DWORD dwFlags) {
+ if (statstg == nullptr) return STG_E_INVALIDPOINTER;
+
+ memset((void*)statstg, 0, sizeof(STATSTG));
+
+ if (dwFlags != STATFLAG_NONAME) {
+ constexpr size_t kMaxNameLength = sizeof(wchar_t);
+ void* retBuf = CoTaskMemAlloc(kMaxNameLength); // freed by caller
+ if (!retBuf) return STG_E_INSUFFICIENTMEMORY;
+
+ ZeroMemory(retBuf, kMaxNameLength);
+ statstg->pwcsName = (LPOLESTR)retBuf;
+ }
+
+ SYSTEMTIME st;
+
+ statstg->type = STGTY_STREAM;
+
+ GetSystemTime(&st);
+ SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime);
+ statstg->ctime = statstg->atime = statstg->mtime;
+
+ statstg->cbSize.QuadPart = mTotalLength;
+ statstg->grfMode = STGM_READ;
+ statstg->grfLocksSupported = LOCK_ONLYONCE;
+ statstg->clsid = CLSID_NULL;
+
+ return S_OK;
+}
+
+/*
+ * deliberately not using MAX_PATH. This is because on platforms < XP
+ * a file created with a long filename may be mishandled by the shell
+ * resulting in it not being able to be deleted or moved.
+ * See bug 250392 for more details.
+ */
+#define NS_MAX_FILEDESCRIPTOR 128 + 1
+
+/*
+ * Class nsDataObj
+ */
+
+//-----------------------------------------------------
+// construction
+//-----------------------------------------------------
+nsDataObj::nsDataObj(nsIURI* uri)
+ : m_cRef(0),
+ mTransferable(nullptr),
+ mIsAsyncMode(FALSE),
+ mIsInOperation(FALSE) {
+ mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "nsDataObj"_ns,
+ LazyIdleThread::ManualShutdown);
+ m_enumFE = new CEnumFormatEtc();
+ m_enumFE->AddRef();
+
+ if (uri) {
+ // A URI was obtained, so pass this through to the DataObject
+ // so it can create a SourceURL for CF_HTML flavour
+ uri->GetSpec(mSourceURL);
+ }
+}
+//-----------------------------------------------------
+// destruction
+//-----------------------------------------------------
+nsDataObj::~nsDataObj() {
+ NS_IF_RELEASE(mTransferable);
+
+ mDataFlavors.Clear();
+
+ m_enumFE->Release();
+
+ // Free arbitrary system formats
+ for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) {
+ CoTaskMemFree(mDataEntryList[idx]->fe.ptd);
+ ReleaseStgMedium(&mDataEntryList[idx]->stgm);
+ CoTaskMemFree(mDataEntryList[idx]);
+ }
+}
+
+//-----------------------------------------------------
+// IUnknown interface methods - see inknown.h for documentation
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+
+ if ((IID_IUnknown == riid) || (IID_IDataObject == riid)) {
+ *ppv = this;
+ AddRef();
+ return S_OK;
+ } else if (IID_IDataObjectAsyncCapability == riid) {
+ *ppv = static_cast<IDataObjectAsyncCapability*>(this);
+ AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP_(ULONG) nsDataObj::AddRef() {
+ ++m_cRef;
+ NS_LOG_ADDREF(this, m_cRef, "nsDataObj", sizeof(*this));
+ return m_cRef;
+}
+
+namespace {
+class RemoveTempFileHelper final : public nsIObserver {
+ public:
+ explicit RemoveTempFileHelper(nsIFile* aTempFile) : mTempFile(aTempFile) {
+ MOZ_ASSERT(mTempFile);
+ }
+
+ // The attach method is seperate from the constructor as we may be addref-ing
+ // ourself, and we want to be sure someone has a strong reference to us.
+ void Attach() {
+ // We need to listen to both the xpcom shutdown message and our timer, and
+ // fire when the first of either of these two messages is received.
+ nsresult rv;
+ rv = NS_NewTimerWithObserver(getter_AddRefs(mTimer), this, 500,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (NS_WARN_IF(!observerService)) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ return;
+ }
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ private:
+ ~RemoveTempFileHelper() {
+ if (mTempFile) {
+ mTempFile->Remove(false);
+ }
+ }
+
+ nsCOMPtr<nsIFile> mTempFile;
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+NS_IMPL_ISUPPORTS(RemoveTempFileHelper, nsIObserver);
+
+NS_IMETHODIMP
+RemoveTempFileHelper::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ // Let's be careful and make sure that we don't die immediately
+ RefPtr<RemoveTempFileHelper> grip = this;
+
+ // Make sure that we aren't called again by destroying references to ourself.
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ // Remove the tempfile
+ if (mTempFile) {
+ mTempFile->Remove(false);
+ mTempFile = nullptr;
+ }
+ return NS_OK;
+}
+} // namespace
+
+//-----------------------------------------------------
+STDMETHODIMP_(ULONG) nsDataObj::Release() {
+ --m_cRef;
+
+ NS_LOG_RELEASE(this, m_cRef, "nsDataObj");
+ if (0 != m_cRef) return m_cRef;
+
+ // We have released our last ref on this object and need to delete the
+ // temp file. External app acting as drop target may still need to open the
+ // temp file. Addref a timer so it can delay deleting file and destroying
+ // this object.
+ if (mCachedTempFile) {
+ RefPtr<RemoveTempFileHelper> helper =
+ new RemoveTempFileHelper(mCachedTempFile);
+ mCachedTempFile = nullptr;
+ helper->Attach();
+ }
+
+ delete this;
+
+ return 0;
+}
+
+//-----------------------------------------------------
+BOOL nsDataObj::FormatsMatch(const FORMATETC& source,
+ const FORMATETC& target) const {
+ if ((source.cfFormat == target.cfFormat) &&
+ (source.dwAspect & target.dwAspect) && (source.tymed & target.tymed)) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+//-----------------------------------------------------
+// IDataObject methods
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::GetData(LPFORMATETC aFormat, LPSTGMEDIUM pSTM) {
+ if (!mTransferable) return DV_E_FORMATETC;
+
+ uint32_t dfInx = 0;
+
+ static CLIPFORMAT fileDescriptorFlavorA =
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
+ static CLIPFORMAT fileDescriptorFlavorW =
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
+ static CLIPFORMAT uniformResourceLocatorA =
+ ::RegisterClipboardFormat(CFSTR_INETURLA);
+ static CLIPFORMAT uniformResourceLocatorW =
+ ::RegisterClipboardFormat(CFSTR_INETURLW);
+ static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
+ static CLIPFORMAT PreferredDropEffect =
+ ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
+
+ // Arbitrary system formats are used for image feedback during drag
+ // and drop. We are responsible for storing these internally during
+ // drag operations.
+ LPDATAENTRY pde;
+ if (LookupArbitraryFormat(aFormat, &pde, FALSE)) {
+ return CopyMediumData(pSTM, &pde->stgm, aFormat, FALSE) ? S_OK
+ : E_UNEXPECTED;
+ }
+
+ // Firefox internal formats
+ ULONG count;
+ FORMATETC fe;
+ m_enumFE->Reset();
+ while (NOERROR == m_enumFE->Next(1, &fe, &count) &&
+ dfInx < mDataFlavors.Length()) {
+ nsCString& df = mDataFlavors.ElementAt(dfInx);
+ if (FormatsMatch(fe, *aFormat)) {
+ pSTM->pUnkForRelease =
+ nullptr; // caller is responsible for deleting this data
+ CLIPFORMAT format = aFormat->cfFormat;
+ switch (format) {
+ // Someone is asking for plain or unicode text
+ case CF_TEXT:
+ case CF_UNICODETEXT:
+ return GetText(df, *aFormat, *pSTM);
+
+ // Some 3rd party apps that receive drag and drop files from the browser
+ // window require support for this.
+ case CF_HDROP:
+ return GetFile(*aFormat, *pSTM);
+
+ // Someone is asking for an image
+ case CF_DIBV5:
+ case CF_DIB:
+ return GetDib(df, *aFormat, *pSTM);
+
+ default:
+ if (format == fileDescriptorFlavorA)
+ return GetFileDescriptor(*aFormat, *pSTM, false);
+ if (format == fileDescriptorFlavorW)
+ return GetFileDescriptor(*aFormat, *pSTM, true);
+ if (format == uniformResourceLocatorA)
+ return GetUniformResourceLocator(*aFormat, *pSTM, false);
+ if (format == uniformResourceLocatorW)
+ return GetUniformResourceLocator(*aFormat, *pSTM, true);
+ if (format == fileFlavor) return GetFileContents(*aFormat, *pSTM);
+ if (format == PreferredDropEffect)
+ return GetPreferredDropEffect(*aFormat, *pSTM);
+ // MOZ_LOG(gWindowsLog, LogLevel::Info,
+ // ("***** nsDataObj::GetData - Unknown format %u\n", format));
+ return GetText(df, *aFormat, *pSTM);
+ } // switch
+ } // if
+ dfInx++;
+ } // while
+
+ return DATA_E_FORMATETC;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
+ return E_FAIL;
+}
+
+//-----------------------------------------------------
+// Other objects querying to see if we support a
+// particular format
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::QueryGetData(LPFORMATETC pFE) {
+ // Arbitrary system formats are used for image feedback during drag
+ // and drop. We are responsible for storing these internally during
+ // drag operations.
+ LPDATAENTRY pde;
+ if (LookupArbitraryFormat(pFE, &pde, FALSE)) return S_OK;
+
+ // Firefox internal formats
+ ULONG count;
+ FORMATETC fe;
+ m_enumFE->Reset();
+ while (NOERROR == m_enumFE->Next(1, &fe, &count)) {
+ if (fe.cfFormat == pFE->cfFormat) {
+ return S_OK;
+ }
+ }
+ return E_FAIL;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::GetCanonicalFormatEtc(LPFORMATETC pFEIn,
+ LPFORMATETC pFEOut) {
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::SetData(LPFORMATETC aFormat, LPSTGMEDIUM aMedium,
+ BOOL shouldRel) {
+ // Arbitrary system formats are used for image feedback during drag
+ // and drop. We are responsible for storing these internally during
+ // drag operations.
+ LPDATAENTRY pde;
+ if (LookupArbitraryFormat(aFormat, &pde, TRUE)) {
+ // Release the old data the lookup handed us for this format. This
+ // may have been set in CopyMediumData when we originally stored the
+ // data.
+ if (pde->stgm.tymed) {
+ ReleaseStgMedium(&pde->stgm);
+ memset(&pde->stgm, 0, sizeof(STGMEDIUM));
+ }
+
+ bool result = true;
+ if (shouldRel) {
+ // If shouldRel is TRUE, the data object called owns the storage medium
+ // after the call returns. Store the incoming data in our data array for
+ // release when we are destroyed. This is the common case with arbitrary
+ // data from explorer.
+ pde->stgm = *aMedium;
+ } else {
+ // Copy the incoming data into our data array. (AFAICT, this never gets
+ // called with arbitrary formats for drag images.)
+ result = CopyMediumData(&pde->stgm, aMedium, aFormat, TRUE);
+ }
+ pde->fe.tymed = pde->stgm.tymed;
+
+ return result ? S_OK : DV_E_TYMED;
+ }
+
+ if (shouldRel) ReleaseStgMedium(aMedium);
+
+ return S_OK;
+}
+
+bool nsDataObj::LookupArbitraryFormat(FORMATETC* aFormat,
+ LPDATAENTRY* aDataEntry,
+ BOOL aAddorUpdate) {
+ *aDataEntry = nullptr;
+
+ if (aFormat->ptd != nullptr) return false;
+
+ // See if it's already in our list. If so return the data entry.
+ for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) {
+ if (mDataEntryList[idx]->fe.cfFormat == aFormat->cfFormat &&
+ mDataEntryList[idx]->fe.dwAspect == aFormat->dwAspect &&
+ mDataEntryList[idx]->fe.lindex == aFormat->lindex) {
+ if (aAddorUpdate || (mDataEntryList[idx]->fe.tymed & aFormat->tymed)) {
+ // If the caller requests we update, or if the
+ // medium type matches, return the entry.
+ *aDataEntry = mDataEntryList[idx];
+ return true;
+ } else {
+ // Medium does not match, not found.
+ return false;
+ }
+ }
+ }
+
+ if (!aAddorUpdate) return false;
+
+ // Add another entry to mDataEntryList
+ LPDATAENTRY dataEntry = (LPDATAENTRY)CoTaskMemAlloc(sizeof(DATAENTRY));
+ if (!dataEntry) return false;
+
+ dataEntry->fe = *aFormat;
+ *aDataEntry = dataEntry;
+ memset(&dataEntry->stgm, 0, sizeof(STGMEDIUM));
+
+ // Add this to our IEnumFORMATETC impl. so we can return it when
+ // it's requested.
+ m_enumFE->AddFormatEtc(aFormat);
+
+ // Store a copy internally in the arbitrary formats array.
+ mDataEntryList.AppendElement(dataEntry);
+
+ return true;
+}
+
+bool nsDataObj::CopyMediumData(STGMEDIUM* aMediumDst, STGMEDIUM* aMediumSrc,
+ LPFORMATETC aFormat, BOOL aSetData) {
+ STGMEDIUM stgmOut = *aMediumSrc;
+
+ switch (stgmOut.tymed) {
+ case TYMED_ISTREAM:
+ stgmOut.pstm->AddRef();
+ break;
+ case TYMED_ISTORAGE:
+ stgmOut.pstg->AddRef();
+ break;
+ case TYMED_HGLOBAL:
+ if (!aMediumSrc->pUnkForRelease) {
+ if (aSetData) {
+ if (aMediumSrc->tymed != TYMED_HGLOBAL) return false;
+ stgmOut.hGlobal =
+ OleDuplicateData(aMediumSrc->hGlobal, aFormat->cfFormat, 0);
+ if (!stgmOut.hGlobal) return false;
+ } else {
+ // We are returning this data from LookupArbitraryFormat, indicate to
+ // the shell we hold it and will free it.
+ stgmOut.pUnkForRelease = static_cast<IDataObject*>(this);
+ }
+ }
+ break;
+ default:
+ return false;
+ }
+
+ if (stgmOut.pUnkForRelease) stgmOut.pUnkForRelease->AddRef();
+
+ *aMediumDst = stgmOut;
+
+ return true;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::EnumFormatEtc(DWORD dwDir, LPENUMFORMATETC* ppEnum) {
+ switch (dwDir) {
+ case DATADIR_GET:
+ m_enumFE->Clone(ppEnum);
+ break;
+ case DATADIR_SET:
+ // fall through
+ default:
+ *ppEnum = nullptr;
+ } // switch
+
+ if (nullptr == *ppEnum) return E_FAIL;
+
+ (*ppEnum)->Reset();
+ // Clone already AddRefed the result so don't addref it again.
+ return NOERROR;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::DAdvise(LPFORMATETC pFE, DWORD dwFlags,
+ LPADVISESINK pIAdviseSink, DWORD* pdwConn) {
+ return OLE_E_ADVISENOTSUPPORTED;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::DUnadvise(DWORD dwConn) {
+ return OLE_E_ADVISENOTSUPPORTED;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::EnumDAdvise(LPENUMSTATDATA* ppEnum) {
+ return OLE_E_ADVISENOTSUPPORTED;
+}
+
+// IDataObjectAsyncCapability methods
+STDMETHODIMP nsDataObj::EndOperation(HRESULT hResult, IBindCtx* pbcReserved,
+ DWORD dwEffects) {
+ mIsInOperation = FALSE;
+ return S_OK;
+}
+
+STDMETHODIMP nsDataObj::GetAsyncMode(BOOL* pfIsOpAsync) {
+ *pfIsOpAsync = mIsAsyncMode;
+
+ return S_OK;
+}
+
+STDMETHODIMP nsDataObj::InOperation(BOOL* pfInAsyncOp) {
+ *pfInAsyncOp = mIsInOperation;
+
+ return S_OK;
+}
+
+STDMETHODIMP nsDataObj::SetAsyncMode(BOOL fDoOpAsync) {
+ mIsAsyncMode = fDoOpAsync;
+ return S_OK;
+}
+
+STDMETHODIMP nsDataObj::StartOperation(IBindCtx* pbcReserved) {
+ mIsInOperation = TRUE;
+ return S_OK;
+}
+
+//
+// GetDIB
+//
+// Someone is asking for a bitmap. The data in the transferable will be a
+// straight imgIContainer, so just QI it.
+//
+HRESULT
+nsDataObj::GetDib(const nsACString& inFlavor, FORMATETC& aFormat,
+ STGMEDIUM& aSTG) {
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ if (NS_FAILED(
+ mTransferable->GetTransferData(PromiseFlatCString(inFlavor).get(),
+ getter_AddRefs(genericDataWrapper)))) {
+ return E_FAIL;
+ }
+
+ nsCOMPtr<imgIContainer> image = do_QueryInterface(genericDataWrapper);
+ if (!image) {
+ return E_FAIL;
+ }
+
+ nsCOMPtr<imgITools> imgTools =
+ do_CreateInstance("@mozilla.org/image/tools;1");
+
+ nsAutoString options(u"bpp=32;"_ns);
+ if (aFormat.cfFormat == CF_DIBV5) {
+ options.AppendLiteral("version=5");
+ } else {
+ options.AppendLiteral("version=3");
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = imgTools->EncodeImage(image, nsLiteralCString(IMAGE_BMP),
+ options, getter_AddRefs(inputStream));
+ if (NS_FAILED(rv) || !inputStream) {
+ return E_FAIL;
+ }
+
+ nsCOMPtr<imgIEncoder> encoder = do_QueryInterface(inputStream);
+ if (!encoder) {
+ return E_FAIL;
+ }
+
+ uint32_t size = 0;
+ rv = encoder->GetImageBufferUsed(&size);
+ if (NS_FAILED(rv) || size <= BFH_LENGTH) {
+ return E_FAIL;
+ }
+
+ char* src = nullptr;
+ rv = encoder->GetImageBuffer(&src);
+ if (NS_FAILED(rv) || !src) {
+ return E_FAIL;
+ }
+
+ // We don't want the file header.
+ src += BFH_LENGTH;
+ size -= BFH_LENGTH;
+
+ HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
+ if (!glob) {
+ return E_FAIL;
+ }
+
+ char* dst = (char*)::GlobalLock(glob);
+ ::CopyMemory(dst, src, size);
+ ::GlobalUnlock(glob);
+
+ aSTG.hGlobal = glob;
+ aSTG.tymed = TYMED_HGLOBAL;
+ return S_OK;
+}
+
+//
+// GetFileDescriptor
+//
+
+HRESULT
+nsDataObj ::GetFileDescriptor(FORMATETC& aFE, STGMEDIUM& aSTG,
+ bool aIsUnicode) {
+ HRESULT res = S_OK;
+
+ // How we handle this depends on if we're dealing with an internet
+ // shortcut, since those are done under the covers.
+ if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime)) {
+ if (aIsUnicode)
+ return GetFileDescriptor_IStreamW(aFE, aSTG);
+ else
+ return GetFileDescriptor_IStreamA(aFE, aSTG);
+ } else if (IsFlavourPresent(kURLMime)) {
+ if (aIsUnicode)
+ res = GetFileDescriptorInternetShortcutW(aFE, aSTG);
+ else
+ res = GetFileDescriptorInternetShortcutA(aFE, aSTG);
+ } else
+ NS_WARNING("Not yet implemented\n");
+
+ return res;
+} // GetFileDescriptor
+
+//
+HRESULT
+nsDataObj ::GetFileContents(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ HRESULT res = S_OK;
+
+ // How we handle this depends on if we're dealing with an internet
+ // shortcut, since those are done under the covers.
+ if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime))
+ return GetFileContents_IStream(aFE, aSTG);
+ else if (IsFlavourPresent(kURLMime))
+ return GetFileContentsInternetShortcut(aFE, aSTG);
+ else
+ NS_WARNING("Not yet implemented\n");
+
+ return res;
+
+} // GetFileContents
+
+//
+// Given a unicode string, we ensure that it contains only characters which are
+// valid within the file system. Remove all forbidden characters from the name,
+// and completely disallow any title that starts with a forbidden name and
+// extension (e.g. "nul" is invalid, but "nul." and "nul.txt" are also invalid
+// and will cause problems).
+//
+// It would seem that this is more functionality suited to being in nsIFile.
+//
+static void MangleTextToValidFilename(nsString& aText) {
+ static const char* forbiddenNames[] = {
+ "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8",
+ "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7",
+ "LPT8", "LPT9", "CON", "PRN", "AUX", "NUL", "CLOCK$"};
+
+ aText.StripChars(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS);
+ aText.CompressWhitespace(true, true);
+ uint32_t nameLen;
+ for (size_t n = 0; n < ArrayLength(forbiddenNames); ++n) {
+ nameLen = (uint32_t)strlen(forbiddenNames[n]);
+ if (aText.EqualsIgnoreCase(forbiddenNames[n], nameLen)) {
+ // invalid name is either the entire string, or a prefix with a period
+ if (aText.Length() == nameLen || aText.CharAt(nameLen) == char16_t('.')) {
+ aText.Truncate();
+ break;
+ }
+ }
+ }
+}
+
+//
+// Given a unicode string, convert it down to a valid local charset filename
+// with the supplied extension. This ensures that we do not cut MBCS characters
+// in the middle.
+//
+// It would seem that this is more functionality suited to being in nsIFile.
+//
+static bool CreateFilenameFromTextA(nsString& aText, const char* aExtension,
+ char* aFilename, uint32_t aFilenameLen) {
+ // ensure that the supplied name doesn't have invalid characters. If
+ // a valid mangled filename couldn't be created then it will leave the
+ // text empty.
+ MangleTextToValidFilename(aText);
+ if (aText.IsEmpty()) return false;
+
+ // repeatably call WideCharToMultiByte as long as the title doesn't fit in the
+ // buffer available to us. Continually reduce the length of the source title
+ // until the MBCS version will fit in the buffer with room for the supplied
+ // extension. Doing it this way ensures that even in MBCS environments there
+ // will be a valid MBCS filename of the correct length.
+ int maxUsableFilenameLen =
+ aFilenameLen - strlen(aExtension) - 1; // space for ext + null byte
+ int currLen, textLen = (int)std::min(aText.Length(), aFilenameLen);
+ char defaultChar = '_';
+ do {
+ currLen = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,
+ aText.get(), textLen--, aFilename,
+ maxUsableFilenameLen, &defaultChar, nullptr);
+ } while (currLen == 0 && textLen > 0 &&
+ GetLastError() == ERROR_INSUFFICIENT_BUFFER);
+ if (currLen > 0 && textLen > 0) {
+ strcpy(&aFilename[currLen], aExtension);
+ return true;
+ } else {
+ // empty names aren't permitted
+ return false;
+ }
+}
+
+static bool CreateFilenameFromTextW(nsString& aText, const wchar_t* aExtension,
+ wchar_t* aFilename, uint32_t aFilenameLen) {
+ // ensure that the supplied name doesn't have invalid characters. If
+ // a valid mangled filename couldn't be created then it will leave the
+ // text empty.
+ MangleTextToValidFilename(aText);
+ if (aText.IsEmpty()) return false;
+
+ const int extensionLen = wcslen(aExtension);
+ if (aText.Length() + extensionLen + 1 > aFilenameLen)
+ aText.Truncate(aFilenameLen - extensionLen - 1);
+ wcscpy(&aFilename[0], aText.get());
+ wcscpy(&aFilename[aText.Length()], aExtension);
+ return true;
+}
+
+#define PAGEINFO_PROPERTIES "chrome://navigator/locale/pageInfo.properties"
+
+static bool GetLocalizedString(const char* aName, nsAString& aString) {
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::services::GetStringBundleService();
+ if (!stringService) return false;
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ nsresult rv = stringService->CreateBundle(PAGEINFO_PROPERTIES,
+ getter_AddRefs(stringBundle));
+ if (NS_FAILED(rv)) return false;
+
+ rv = stringBundle->GetStringFromName(aName, aString);
+ return NS_SUCCEEDED(rv);
+}
+
+//
+// GetFileDescriptorInternetShortcut
+//
+// Create the special format for an internet shortcut and build up the data
+// structures the shell is expecting.
+//
+HRESULT
+nsDataObj ::GetFileDescriptorInternetShortcutA(FORMATETC& aFE,
+ STGMEDIUM& aSTG) {
+ // get the title of the shortcut
+ nsAutoString title;
+ if (NS_FAILED(ExtractShortcutTitle(title))) return E_OUTOFMEMORY;
+
+ HGLOBAL fileGroupDescHandle =
+ ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORA));
+ if (!fileGroupDescHandle) return E_OUTOFMEMORY;
+
+ LPFILEGROUPDESCRIPTORA fileGroupDescA =
+ reinterpret_cast<LPFILEGROUPDESCRIPTORA>(
+ ::GlobalLock(fileGroupDescHandle));
+ if (!fileGroupDescA) {
+ ::GlobalFree(fileGroupDescHandle);
+ return E_OUTOFMEMORY;
+ }
+
+ // get a valid filename in the following order: 1) from the page title,
+ // 2) localized string for an untitled page, 3) just use "Untitled.URL"
+ if (!CreateFilenameFromTextA(title, ".URL", fileGroupDescA->fgd[0].cFileName,
+ NS_MAX_FILEDESCRIPTOR)) {
+ nsAutoString untitled;
+ if (!GetLocalizedString("noPageTitle", untitled) ||
+ !CreateFilenameFromTextA(untitled, ".URL",
+ fileGroupDescA->fgd[0].cFileName,
+ NS_MAX_FILEDESCRIPTOR)) {
+ strcpy(fileGroupDescA->fgd[0].cFileName, "Untitled.URL");
+ }
+ }
+
+ // one file in the file block
+ fileGroupDescA->cItems = 1;
+ fileGroupDescA->fgd[0].dwFlags = FD_LINKUI;
+
+ ::GlobalUnlock(fileGroupDescHandle);
+ aSTG.hGlobal = fileGroupDescHandle;
+ aSTG.tymed = TYMED_HGLOBAL;
+
+ return S_OK;
+} // GetFileDescriptorInternetShortcutA
+
+HRESULT
+nsDataObj ::GetFileDescriptorInternetShortcutW(FORMATETC& aFE,
+ STGMEDIUM& aSTG) {
+ // get the title of the shortcut
+ nsAutoString title;
+ if (NS_FAILED(ExtractShortcutTitle(title))) return E_OUTOFMEMORY;
+
+ HGLOBAL fileGroupDescHandle =
+ ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
+ if (!fileGroupDescHandle) return E_OUTOFMEMORY;
+
+ LPFILEGROUPDESCRIPTORW fileGroupDescW =
+ reinterpret_cast<LPFILEGROUPDESCRIPTORW>(
+ ::GlobalLock(fileGroupDescHandle));
+ if (!fileGroupDescW) {
+ ::GlobalFree(fileGroupDescHandle);
+ return E_OUTOFMEMORY;
+ }
+
+ // get a valid filename in the following order: 1) from the page title,
+ // 2) localized string for an untitled page, 3) just use "Untitled.URL"
+ if (!CreateFilenameFromTextW(title, L".URL", fileGroupDescW->fgd[0].cFileName,
+ NS_MAX_FILEDESCRIPTOR)) {
+ nsAutoString untitled;
+ if (!GetLocalizedString("noPageTitle", untitled) ||
+ !CreateFilenameFromTextW(untitled, L".URL",
+ fileGroupDescW->fgd[0].cFileName,
+ NS_MAX_FILEDESCRIPTOR)) {
+ wcscpy(fileGroupDescW->fgd[0].cFileName, L"Untitled.URL");
+ }
+ }
+
+ // one file in the file block
+ fileGroupDescW->cItems = 1;
+ fileGroupDescW->fgd[0].dwFlags = FD_LINKUI;
+
+ ::GlobalUnlock(fileGroupDescHandle);
+ aSTG.hGlobal = fileGroupDescHandle;
+ aSTG.tymed = TYMED_HGLOBAL;
+
+ return S_OK;
+} // GetFileDescriptorInternetShortcutW
+
+//
+// GetFileContentsInternetShortcut
+//
+// Create the special format for an internet shortcut and build up the data
+// structures the shell is expecting.
+//
+HRESULT
+nsDataObj ::GetFileContentsInternetShortcut(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ static const char* kShellIconPref = "browser.shell.shortcutFavicons";
+ nsAutoString url;
+ if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
+
+ nsCOMPtr<nsIURI> aUri;
+ nsresult rv = NS_NewURI(getter_AddRefs(aUri), url);
+ if (NS_FAILED(rv)) {
+ return E_FAIL;
+ }
+
+ nsAutoCString asciiUrl;
+ rv = aUri->GetAsciiSpec(asciiUrl);
+ if (NS_FAILED(rv)) {
+ return E_FAIL;
+ }
+
+ RefPtr<AutoCloseEvent> event;
+
+ const char* shortcutFormatStr;
+ int totalLen;
+ nsCString asciiPath;
+ if (!Preferences::GetBool(kShellIconPref, true)) {
+ shortcutFormatStr = "[InternetShortcut]\r\nURL=%s\r\n";
+ const int formatLen = strlen(shortcutFormatStr) - 2; // don't include %s
+ totalLen = formatLen + asciiUrl.Length(); // don't include null character
+ } else {
+ nsCOMPtr<nsIFile> icoFile;
+
+ nsAutoString aUriHash;
+
+ event = new AutoCloseEvent();
+ if (!event->IsInited()) {
+ return E_FAIL;
+ }
+
+ RefPtr<AutoSetEvent> e = new AutoSetEvent(WrapNotNull(event));
+ mozilla::widget::FaviconHelper::ObtainCachedIconFile(
+ aUri, aUriHash, mIOThread, true,
+ NS_NewRunnableFunction(
+ "FaviconHelper::RefreshDesktop", [e = std::move(e)] {
+ if (e->IsWaiting()) {
+ // Unblock IStream:::Read.
+ e->Signal();
+ } else {
+ // We could not wait until the favicon was available. We have
+ // to refresh to refect the favicon.
+ SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE,
+ SPI_SETNONCLIENTMETRICS, 0);
+ }
+ }));
+
+ rv = mozilla::widget::FaviconHelper::GetOutputIconPath(aUri, icoFile, true);
+ NS_ENSURE_SUCCESS(rv, E_FAIL);
+ nsString path;
+ rv = icoFile->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, E_FAIL);
+
+ if (IsAsciiNullTerminated(static_cast<const char16_t*>(path.get()))) {
+ LossyCopyUTF16toASCII(path, asciiPath);
+ shortcutFormatStr =
+ "[InternetShortcut]\r\nURL=%s\r\n"
+ "IDList=\r\nHotKey=0\r\nIconFile=%s\r\n"
+ "IconIndex=0\r\n";
+ } else {
+ int len =
+ WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()),
+ path.Length(), nullptr, 0, nullptr, nullptr);
+ NS_ENSURE_TRUE(len > 0, E_FAIL);
+ asciiPath.SetLength(len);
+ WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()),
+ path.Length(), asciiPath.BeginWriting(), len, nullptr,
+ nullptr);
+ shortcutFormatStr =
+ "[InternetShortcut]\r\nURL=%s\r\n"
+ "IDList=\r\nHotKey=0\r\nIconIndex=0\r\n"
+ "[InternetShortcut.W]\r\nIconFile=%s\r\n";
+ }
+ const int formatLen = strlen(shortcutFormatStr) - 2 * 2; // no %s twice
+ totalLen = formatLen + asciiUrl.Length() +
+ asciiPath.Length(); // we don't want a null character on the end
+ }
+
+ // create a global memory area and build up the file contents w/in it
+ nsAutoGlobalMem globalMem(nsHGLOBAL(::GlobalAlloc(GMEM_SHARE, totalLen)));
+ if (!globalMem) return E_OUTOFMEMORY;
+
+ char* contents = reinterpret_cast<char*>(::GlobalLock(globalMem.get()));
+ if (!contents) {
+ return E_OUTOFMEMORY;
+ }
+
+ // NOTE: we intentionally use the Microsoft version of snprintf here because
+ // it does NOT null
+ // terminate strings which reach the maximum size of the buffer. Since we know
+ // that the formatted length here is totalLen, this call to _snprintf will
+ // format the string into the buffer without appending the null character.
+
+ if (!Preferences::GetBool(kShellIconPref, true)) {
+ _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get());
+ } else {
+ _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get(),
+ asciiPath.get());
+ }
+
+ ::GlobalUnlock(globalMem.get());
+
+ if (aFE.tymed & TYMED_ISTREAM) {
+ if (!mIsInOperation) {
+ // The drop target didn't initiate an async operation.
+ // We can't block CMemStream::Read.
+ event = nullptr;
+ }
+ RefPtr<IStream> stream =
+ new CMemStream(globalMem.disown(), totalLen, event.forget());
+ stream.forget(&aSTG.pstm);
+ aSTG.tymed = TYMED_ISTREAM;
+ } else {
+ if (event && event->IsInited()) {
+ event->Signal(); // We can't block reading the global memory
+ }
+ aSTG.hGlobal = globalMem.disown();
+ aSTG.tymed = TYMED_HGLOBAL;
+ }
+
+ return S_OK;
+} // GetFileContentsInternetShortcut
+
+// check if specified flavour is present in the transferable
+bool nsDataObj ::IsFlavourPresent(const char* inFlavour) {
+ bool retval = false;
+ NS_ENSURE_TRUE(mTransferable, false);
+
+ // get the list of flavors available in the transferable
+ nsTArray<nsCString> flavors;
+ nsresult rv = mTransferable->FlavorsTransferableCanExport(flavors);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // try to find requested flavour
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ if (flavors[i].Equals(inFlavour)) {
+ retval = true; // found it!
+ break;
+ }
+ } // for each flavor
+
+ return retval;
+}
+
+HRESULT nsDataObj::GetPreferredDropEffect(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ HRESULT res = S_OK;
+ aSTG.tymed = TYMED_HGLOBAL;
+ aSTG.pUnkForRelease = nullptr;
+ HGLOBAL hGlobalMemory = nullptr;
+ hGlobalMemory = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD));
+ if (hGlobalMemory) {
+ DWORD* pdw = (DWORD*)GlobalLock(hGlobalMemory);
+ // The PreferredDropEffect clipboard format is only registered if a
+ // drag/drop of an image happens from Mozilla to the desktop. We want its
+ // value to be DROPEFFECT_MOVE in that case so that the file is moved from
+ // the temporary location, not copied. This value should, ideally, be set on
+ // the data object via SetData() but our IDataObject implementation doesn't
+ // implement SetData. It adds data to the data object lazily only when the
+ // drop target asks for it.
+ *pdw = (DWORD)DROPEFFECT_MOVE;
+ GlobalUnlock(hGlobalMemory);
+ } else {
+ res = E_OUTOFMEMORY;
+ }
+ aSTG.hGlobal = hGlobalMemory;
+ return res;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::GetText(const nsACString& aDataFlavor, FORMATETC& aFE,
+ STGMEDIUM& aSTG) {
+ void* data = nullptr;
+
+ // if someone asks for text/plain, look up text/unicode instead in the
+ // transferable.
+ const char* flavorStr;
+ const nsPromiseFlatCString& flat = PromiseFlatCString(aDataFlavor);
+ if (aDataFlavor.EqualsLiteral("text/plain"))
+ flavorStr = kUnicodeMime;
+ else
+ flavorStr = flat.get();
+
+ // NOTE: CreateDataFromPrimitive creates new memory, that needs to be deleted
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsresult rv = mTransferable->GetTransferData(
+ flavorStr, getter_AddRefs(genericDataWrapper));
+ if (NS_FAILED(rv) || !genericDataWrapper) {
+ return E_FAIL;
+ }
+
+ uint32_t len;
+ nsPrimitiveHelpers::CreateDataFromPrimitive(nsDependentCString(flavorStr),
+ genericDataWrapper, &data, &len);
+ if (!data) return E_FAIL;
+
+ HGLOBAL hGlobalMemory = nullptr;
+
+ aSTG.tymed = TYMED_HGLOBAL;
+ aSTG.pUnkForRelease = nullptr;
+
+ // We play games under the hood and advertise flavors that we know we
+ // can support, only they require a bit of conversion or munging of the data.
+ // Do that here.
+ //
+ // The transferable gives us data that is null-terminated, but this isn't
+ // reflected in the |len| parameter. Windoze apps expect this null to be there
+ // so bump our data buffer by the appropriate size to account for the null
+ // (one char for CF_TEXT, one char16_t for CF_UNICODETEXT).
+ DWORD allocLen = (DWORD)len;
+ if (aFE.cfFormat == CF_TEXT) {
+ // Someone is asking for text/plain; convert the unicode (assuming it's
+ // present) to text with the correct platform encoding.
+ size_t bufferSize = sizeof(char) * (len + 2);
+ char* plainTextData = static_cast<char*>(moz_xmalloc(bufferSize));
+ char16_t* castedUnicode = reinterpret_cast<char16_t*>(data);
+ int32_t plainTextLen =
+ WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)castedUnicode, len / 2 + 1,
+ plainTextData, bufferSize, NULL, NULL);
+ // replace the unicode data with our plaintext data. Recall that
+ // |plainTextLen| doesn't include the null in the length.
+ free(data);
+ if (plainTextLen) {
+ data = plainTextData;
+ allocLen = plainTextLen;
+ } else {
+ free(plainTextData);
+ NS_WARNING("Oh no, couldn't convert unicode to plain text");
+ return S_OK;
+ }
+ } else if (aFE.cfFormat == nsClipboard::CF_HTML) {
+ // Someone is asking for win32's HTML flavor. Convert our html fragment
+ // from unicode to UTF-8 then put it into a format specified by msft.
+ NS_ConvertUTF16toUTF8 converter(reinterpret_cast<char16_t*>(data));
+ char* utf8HTML = nullptr;
+ nsresult rv =
+ BuildPlatformHTML(converter.get(), &utf8HTML); // null terminates
+
+ free(data);
+ if (NS_SUCCEEDED(rv) && utf8HTML) {
+ // replace the unicode data with our HTML data. Don't forget the null.
+ data = utf8HTML;
+ allocLen = strlen(utf8HTML) + sizeof(char);
+ } else {
+ NS_WARNING("Oh no, couldn't convert to HTML");
+ return S_OK;
+ }
+ } else if (aFE.cfFormat != nsClipboard::CF_CUSTOMTYPES) {
+ // we assume that any data that isn't caught above is unicode. This may
+ // be an erroneous assumption, but is true so far.
+ allocLen += sizeof(char16_t);
+ }
+
+ hGlobalMemory = (HGLOBAL)GlobalAlloc(GMEM_MOVEABLE, allocLen);
+
+ // Copy text to Global Memory Area
+ if (hGlobalMemory) {
+ char* dest = reinterpret_cast<char*>(GlobalLock(hGlobalMemory));
+ char* source = reinterpret_cast<char*>(data);
+ memcpy(dest, source, allocLen); // copies the null as well
+ GlobalUnlock(hGlobalMemory);
+ }
+ aSTG.hGlobal = hGlobalMemory;
+
+ // Now, delete the memory that was created by CreateDataFromPrimitive (or our
+ // text/plain data)
+ free(data);
+
+ return S_OK;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::GetFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ uint32_t dfInx = 0;
+ ULONG count;
+ FORMATETC fe;
+ m_enumFE->Reset();
+ while (NOERROR == m_enumFE->Next(1, &fe, &count) &&
+ dfInx < mDataFlavors.Length()) {
+ if (mDataFlavors[dfInx].EqualsLiteral(kNativeImageMime))
+ return DropImage(aFE, aSTG);
+ if (mDataFlavors[dfInx].EqualsLiteral(kFileMime))
+ return DropFile(aFE, aSTG);
+ if (mDataFlavors[dfInx].EqualsLiteral(kFilePromiseMime))
+ return DropTempFile(aFE, aSTG);
+ dfInx++;
+ }
+ return E_FAIL;
+}
+
+HRESULT nsDataObj::DropFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ nsresult rv;
+ nsCOMPtr<nsISupports> genericDataWrapper;
+
+ if (NS_FAILED(mTransferable->GetTransferData(
+ kFileMime, getter_AddRefs(genericDataWrapper)))) {
+ return E_FAIL;
+ }
+ nsCOMPtr<nsIFile> file(do_QueryInterface(genericDataWrapper));
+ if (!file) return E_FAIL;
+
+ aSTG.tymed = TYMED_HGLOBAL;
+ aSTG.pUnkForRelease = nullptr;
+
+ nsAutoString path;
+ rv = file->GetPath(path);
+ if (NS_FAILED(rv)) return E_FAIL;
+
+ uint32_t allocLen = path.Length() + 2;
+ HGLOBAL hGlobalMemory = nullptr;
+ char16_t* dest;
+
+ hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
+ sizeof(DROPFILES) + allocLen * sizeof(char16_t));
+ if (!hGlobalMemory) return E_FAIL;
+
+ DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
+
+ // First, populate the drop file structure
+ pDropFile->pFiles = sizeof(DROPFILES); // Offset to start of file name string
+ pDropFile->fNC = 0;
+ pDropFile->pt.x = 0;
+ pDropFile->pt.y = 0;
+ pDropFile->fWide = TRUE;
+
+ // Copy the filename right after the DROPFILES structure
+ dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
+ memcpy(dest, path.get(), (allocLen - 1) * sizeof(char16_t));
+
+ // Two null characters are needed at the end of the file name.
+ // Lookup the CF_HDROP shell clipboard format for more info.
+ // Add the second null character right after the first one.
+ dest[allocLen - 1] = L'\0';
+
+ GlobalUnlock(hGlobalMemory);
+
+ aSTG.hGlobal = hGlobalMemory;
+
+ return S_OK;
+}
+
+HRESULT nsDataObj::DropImage(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ nsresult rv;
+ if (!mCachedTempFile) {
+ nsCOMPtr<nsISupports> genericDataWrapper;
+
+ if (NS_FAILED(mTransferable->GetTransferData(
+ kNativeImageMime, getter_AddRefs(genericDataWrapper)))) {
+ return E_FAIL;
+ }
+ nsCOMPtr<imgIContainer> image(do_QueryInterface(genericDataWrapper));
+ if (!image) return E_FAIL;
+
+ nsCOMPtr<imgITools> imgTools =
+ do_CreateInstance("@mozilla.org/image/tools;1");
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = imgTools->EncodeImage(image, nsLiteralCString(IMAGE_BMP),
+ u"bpp=32;version=3"_ns,
+ getter_AddRefs(inputStream));
+ if (NS_FAILED(rv) || !inputStream) {
+ return E_FAIL;
+ }
+
+ nsCOMPtr<imgIEncoder> encoder = do_QueryInterface(inputStream);
+ if (!encoder) {
+ return E_FAIL;
+ }
+
+ uint32_t size = 0;
+ rv = encoder->GetImageBufferUsed(&size);
+ if (NS_FAILED(rv)) {
+ return E_FAIL;
+ }
+
+ char* src = nullptr;
+ rv = encoder->GetImageBuffer(&src);
+ if (NS_FAILED(rv) || !src) {
+ return E_FAIL;
+ }
+
+ // Save the bitmap to a temporary location.
+ nsCOMPtr<nsIFile> dropFile;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile));
+ if (!dropFile) {
+ return E_FAIL;
+ }
+
+ // Filename must be random so as not to confuse apps like
+ // Photoshop which handle multiple drags into a single window.
+ char buf[13];
+ nsCString filename;
+ NS_MakeRandomString(buf, 8);
+ memcpy(buf + 8, ".bmp", 5);
+ filename.Append(nsDependentCString(buf, 12));
+ dropFile->AppendNative(filename);
+ rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
+ if (NS_FAILED(rv)) {
+ return E_FAIL;
+ }
+
+ // Cache the temp file so we can delete it later and so
+ // it doesn't get recreated over and over on multiple calls
+ // which does occur from windows shell.
+ dropFile->Clone(getter_AddRefs(mCachedTempFile));
+
+ // Write the data to disk.
+ nsCOMPtr<nsIOutputStream> outStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile);
+ if (NS_FAILED(rv)) {
+ return E_FAIL;
+ }
+
+ uint32_t written = 0;
+ rv = outStream->Write(src, size, &written);
+ if (NS_FAILED(rv) || written != size) {
+ return E_FAIL;
+ }
+
+ outStream->Close();
+ }
+
+ // Pass the file name back to the drop target so that it can access the file.
+ nsAutoString path;
+ rv = mCachedTempFile->GetPath(path);
+ if (NS_FAILED(rv)) return E_FAIL;
+
+ // Two null characters are needed to terminate the file name list.
+ HGLOBAL hGlobalMemory = nullptr;
+
+ uint32_t allocLen = path.Length() + 2;
+
+ aSTG.tymed = TYMED_HGLOBAL;
+ aSTG.pUnkForRelease = nullptr;
+
+ hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
+ sizeof(DROPFILES) + allocLen * sizeof(char16_t));
+ if (!hGlobalMemory) return E_FAIL;
+
+ DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
+
+ // First, populate the drop file structure.
+ pDropFile->pFiles =
+ sizeof(DROPFILES); // Offset to start of file name char array.
+ pDropFile->fNC = 0;
+ pDropFile->pt.x = 0;
+ pDropFile->pt.y = 0;
+ pDropFile->fWide = TRUE;
+
+ // Copy the filename right after the DROPFILES structure.
+ char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
+ memcpy(dest, path.get(),
+ (allocLen - 1) *
+ sizeof(char16_t)); // Copies the null character in path as well.
+
+ // Two null characters are needed at the end of the file name.
+ // Lookup the CF_HDROP shell clipboard format for more info.
+ // Add the second null character right after the first one.
+ dest[allocLen - 1] = L'\0';
+
+ GlobalUnlock(hGlobalMemory);
+
+ aSTG.hGlobal = hGlobalMemory;
+
+ return S_OK;
+}
+
+HRESULT nsDataObj::DropTempFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ nsresult rv;
+ if (!mCachedTempFile) {
+ // Tempfile will need a temporary location.
+ nsCOMPtr<nsIFile> dropFile;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile));
+ if (!dropFile) return E_FAIL;
+
+ // Filename must be random
+ nsCString filename;
+ nsAutoString wideFileName;
+ nsCOMPtr<nsIURI> sourceURI;
+ HRESULT res;
+ res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
+ if (FAILED(res)) return res;
+ NS_CopyUnicodeToNative(wideFileName, filename);
+
+ dropFile->AppendNative(filename);
+ rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
+ if (NS_FAILED(rv)) return E_FAIL;
+
+ // Cache the temp file so we can delete it later and so
+ // it doesn't get recreated over and over on multiple calls
+ // which does occur from windows shell.
+ dropFile->Clone(getter_AddRefs(mCachedTempFile));
+
+ // Write the data to disk.
+ nsCOMPtr<nsIOutputStream> outStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile);
+ if (NS_FAILED(rv)) return E_FAIL;
+
+ IStream* pStream = nullptr;
+ nsDataObj::CreateStream(&pStream);
+ NS_ENSURE_TRUE(pStream, E_FAIL);
+
+ char buffer[512];
+ ULONG readCount = 0;
+ uint32_t writeCount = 0;
+ while (1) {
+ HRESULT hres = pStream->Read(buffer, sizeof(buffer), &readCount);
+ if (FAILED(hres)) return E_FAIL;
+ if (readCount == 0) break;
+ rv = outStream->Write(buffer, readCount, &writeCount);
+ if (NS_FAILED(rv)) return E_FAIL;
+ }
+ outStream->Close();
+ pStream->Release();
+ }
+
+ // Pass the file name back to the drop target so that it can access the file.
+ nsAutoString path;
+ rv = mCachedTempFile->GetPath(path);
+ if (NS_FAILED(rv)) return E_FAIL;
+
+ uint32_t allocLen = path.Length() + 2;
+
+ // Two null characters are needed to terminate the file name list.
+ HGLOBAL hGlobalMemory = nullptr;
+
+ aSTG.tymed = TYMED_HGLOBAL;
+ aSTG.pUnkForRelease = nullptr;
+
+ hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
+ sizeof(DROPFILES) + allocLen * sizeof(char16_t));
+ if (!hGlobalMemory) return E_FAIL;
+
+ DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
+
+ // First, populate the drop file structure.
+ pDropFile->pFiles =
+ sizeof(DROPFILES); // Offset to start of file name char array.
+ pDropFile->fNC = 0;
+ pDropFile->pt.x = 0;
+ pDropFile->pt.y = 0;
+ pDropFile->fWide = TRUE;
+
+ // Copy the filename right after the DROPFILES structure.
+ char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
+ memcpy(dest, path.get(),
+ (allocLen - 1) *
+ sizeof(char16_t)); // Copies the null character in path as well.
+
+ // Two null characters are needed at the end of the file name.
+ // Lookup the CF_HDROP shell clipboard format for more info.
+ // Add the second null character right after the first one.
+ dest[allocLen - 1] = L'\0';
+
+ GlobalUnlock(hGlobalMemory);
+
+ aSTG.hGlobal = hGlobalMemory;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------
+// Registers the DataFlavor/FE pair.
+//-----------------------------------------------------
+void nsDataObj::AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE) {
+ // These two lists are the mapping to and from data flavors and FEs.
+ // Later, OLE will tell us it needs a certain type of FORMATETC (text,
+ // unicode, etc) unicode, etc), so we will look up the data flavor that
+ // corresponds to the FE and then ask the transferable for that type of data.
+ mDataFlavors.AppendElement(aDataFlavor);
+ m_enumFE->AddFormatEtc(aFE);
+}
+
+//-----------------------------------------------------
+// Sets the transferable object
+//-----------------------------------------------------
+void nsDataObj::SetTransferable(nsITransferable* aTransferable) {
+ NS_IF_RELEASE(mTransferable);
+
+ mTransferable = aTransferable;
+ if (nullptr == mTransferable) {
+ return;
+ }
+
+ NS_ADDREF(mTransferable);
+
+ return;
+}
+
+//
+// ExtractURL
+//
+// Roots around in the transferable for the appropriate flavor that indicates
+// a url and pulls out the url portion of the data. Used mostly for creating
+// internet shortcuts on the desktop. The url flavor is of the format:
+//
+// <url> <linefeed> <page title>
+//
+nsresult nsDataObj ::ExtractShortcutURL(nsString& outURL) {
+ NS_ASSERTION(mTransferable, "We don't have a good transferable");
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISupports> genericURL;
+ if (NS_SUCCEEDED(mTransferable->GetTransferData(
+ kURLMime, getter_AddRefs(genericURL)))) {
+ nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
+ if (urlObject) {
+ nsAutoString url;
+ urlObject->GetData(url);
+ outURL = url;
+
+ // find the first linefeed in the data, that's where the url ends. trunc
+ // the result string at that point.
+ int32_t lineIndex = outURL.FindChar('\n');
+ NS_ASSERTION(lineIndex > 0,
+ "Format for url flavor is <url> <linefeed> <page title>");
+ if (lineIndex > 0) {
+ outURL.Truncate(lineIndex);
+ rv = NS_OK;
+ }
+ }
+ } else if (NS_SUCCEEDED(mTransferable->GetTransferData(
+ kURLDataMime, getter_AddRefs(genericURL))) ||
+ NS_SUCCEEDED(mTransferable->GetTransferData(
+ kURLPrivateMime, getter_AddRefs(genericURL)))) {
+ nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
+ if (urlObject) {
+ nsAutoString url;
+ urlObject->GetData(url);
+ outURL = url;
+
+ rv = NS_OK;
+ }
+
+ } // if found flavor
+
+ return rv;
+
+} // ExtractShortcutURL
+
+//
+// ExtractShortcutTitle
+//
+// Roots around in the transferable for the appropriate flavor that indicates
+// a url and pulls out the title portion of the data. Used mostly for creating
+// internet shortcuts on the desktop. The url flavor is of the format:
+//
+// <url> <linefeed> <page title>
+//
+nsresult nsDataObj ::ExtractShortcutTitle(nsString& outTitle) {
+ NS_ASSERTION(mTransferable, "We'd don't have a good transferable");
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISupports> genericURL;
+ if (NS_SUCCEEDED(mTransferable->GetTransferData(
+ kURLMime, getter_AddRefs(genericURL)))) {
+ nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
+ if (urlObject) {
+ nsAutoString url;
+ urlObject->GetData(url);
+
+ // find the first linefeed in the data, that's where the url ends. we want
+ // everything after that linefeed. FindChar() returns -1 if we can't find
+ int32_t lineIndex = url.FindChar('\n');
+ NS_ASSERTION(lineIndex != -1,
+ "Format for url flavor is <url> <linefeed> <page title>");
+ if (lineIndex != -1) {
+ url.Mid(outTitle, lineIndex + 1, url.Length() - (lineIndex + 1));
+ rv = NS_OK;
+ }
+ }
+ } // if found flavor
+
+ return rv;
+
+} // ExtractShortcutTitle
+
+//
+// BuildPlatformHTML
+//
+// Munge our HTML data to win32's CF_HTML spec. Basically, put the requisite
+// header information on it. This will null terminate |outPlatformHTML|. See
+// http://msdn.microsoft.com/workshop/networking/clipboard/htmlclipboard.asp
+// for details.
+//
+// We assume that |inOurHTML| is already a fragment (ie, doesn't have <HTML>
+// or <BODY> tags). We'll wrap the fragment with them to make other apps
+// happy.
+//
+nsresult nsDataObj ::BuildPlatformHTML(const char* inOurHTML,
+ char** outPlatformHTML) {
+ *outPlatformHTML = nullptr;
+
+ nsDependentCString inHTMLString(inOurHTML);
+ const char* const numPlaceholder = "00000000";
+ const char* const startHTMLPrefix = "Version:0.9\r\nStartHTML:";
+ const char* const endHTMLPrefix = "\r\nEndHTML:";
+ const char* const startFragPrefix = "\r\nStartFragment:";
+ const char* const endFragPrefix = "\r\nEndFragment:";
+ const char* const startSourceURLPrefix = "\r\nSourceURL:";
+ const char* const endFragTrailer = "\r\n";
+
+ // Do we already have mSourceURL from a drag?
+ if (mSourceURL.IsEmpty()) {
+ nsAutoString url;
+ ExtractShortcutURL(url);
+
+ AppendUTF16toUTF8(url, mSourceURL);
+ }
+
+ const int32_t kSourceURLLength = mSourceURL.Length();
+ const int32_t kNumberLength = strlen(numPlaceholder);
+
+ const int32_t kTotalHeaderLen =
+ strlen(startHTMLPrefix) + strlen(endHTMLPrefix) +
+ strlen(startFragPrefix) + strlen(endFragPrefix) + strlen(endFragTrailer) +
+ (kSourceURLLength > 0 ? strlen(startSourceURLPrefix) : 0) +
+ kSourceURLLength + (4 * kNumberLength);
+
+ constexpr auto htmlHeaderString = "<html><body>\r\n"_ns;
+
+ constexpr auto fragmentHeaderString = "<!--StartFragment-->"_ns;
+
+ nsDependentCString trailingString(
+ "<!--EndFragment-->\r\n"
+ "</body>\r\n"
+ "</html>");
+
+ // calculate the offsets
+ int32_t startHTMLOffset = kTotalHeaderLen;
+ int32_t startFragOffset = startHTMLOffset + htmlHeaderString.Length() +
+ fragmentHeaderString.Length();
+
+ int32_t endFragOffset = startFragOffset + inHTMLString.Length();
+
+ int32_t endHTMLOffset = endFragOffset + trailingString.Length();
+
+ // now build the final version
+ nsCString clipboardString;
+ clipboardString.SetCapacity(endHTMLOffset);
+
+ clipboardString.Append(startHTMLPrefix);
+ clipboardString.Append(nsPrintfCString("%08u", startHTMLOffset));
+
+ clipboardString.Append(endHTMLPrefix);
+ clipboardString.Append(nsPrintfCString("%08u", endHTMLOffset));
+
+ clipboardString.Append(startFragPrefix);
+ clipboardString.Append(nsPrintfCString("%08u", startFragOffset));
+
+ clipboardString.Append(endFragPrefix);
+ clipboardString.Append(nsPrintfCString("%08u", endFragOffset));
+
+ if (kSourceURLLength > 0) {
+ clipboardString.Append(startSourceURLPrefix);
+ clipboardString.Append(mSourceURL);
+ }
+
+ clipboardString.Append(endFragTrailer);
+
+ clipboardString.Append(htmlHeaderString);
+ clipboardString.Append(fragmentHeaderString);
+ clipboardString.Append(inHTMLString);
+ clipboardString.Append(trailingString);
+
+ *outPlatformHTML = ToNewCString(clipboardString, mozilla::fallible);
+ if (!*outPlatformHTML) return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+HRESULT
+nsDataObj ::GetUniformResourceLocator(FORMATETC& aFE, STGMEDIUM& aSTG,
+ bool aIsUnicode) {
+ HRESULT res = S_OK;
+ if (IsFlavourPresent(kURLMime)) {
+ if (aIsUnicode)
+ res = ExtractUniformResourceLocatorW(aFE, aSTG);
+ else
+ res = ExtractUniformResourceLocatorA(aFE, aSTG);
+ } else
+ NS_WARNING("Not yet implemented\n");
+ return res;
+}
+
+HRESULT
+nsDataObj::ExtractUniformResourceLocatorA(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ HRESULT result = S_OK;
+
+ nsAutoString url;
+ if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
+
+ NS_LossyConvertUTF16toASCII asciiUrl(url);
+ const int totalLen = asciiUrl.Length() + 1;
+ HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen);
+ if (!hGlobalMemory) return E_OUTOFMEMORY;
+
+ char* contents = reinterpret_cast<char*>(GlobalLock(hGlobalMemory));
+ if (!contents) {
+ GlobalFree(hGlobalMemory);
+ return E_OUTOFMEMORY;
+ }
+
+ strcpy(contents, asciiUrl.get());
+ GlobalUnlock(hGlobalMemory);
+ aSTG.hGlobal = hGlobalMemory;
+ aSTG.tymed = TYMED_HGLOBAL;
+
+ return result;
+}
+
+HRESULT
+nsDataObj::ExtractUniformResourceLocatorW(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ HRESULT result = S_OK;
+
+ nsAutoString url;
+ if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
+
+ const int totalLen = (url.Length() + 1) * sizeof(char16_t);
+ HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen);
+ if (!hGlobalMemory) return E_OUTOFMEMORY;
+
+ wchar_t* contents = reinterpret_cast<wchar_t*>(GlobalLock(hGlobalMemory));
+ if (!contents) {
+ GlobalFree(hGlobalMemory);
+ return E_OUTOFMEMORY;
+ }
+
+ wcscpy(contents, url.get());
+ GlobalUnlock(hGlobalMemory);
+ aSTG.hGlobal = hGlobalMemory;
+ aSTG.tymed = TYMED_HGLOBAL;
+
+ return result;
+}
+
+// Gets the filename from the kFilePromiseURLMime flavour
+HRESULT nsDataObj::GetDownloadDetails(nsIURI** aSourceURI,
+ nsAString& aFilename) {
+ *aSourceURI = nullptr;
+
+ NS_ENSURE_TRUE(mTransferable, E_FAIL);
+
+ // get the URI from the kFilePromiseURLMime flavor
+ nsCOMPtr<nsISupports> urlPrimitive;
+ nsresult rv = mTransferable->GetTransferData(kFilePromiseURLMime,
+ getter_AddRefs(urlPrimitive));
+ NS_ENSURE_SUCCESS(rv, E_FAIL);
+ nsCOMPtr<nsISupportsString> srcUrlPrimitive = do_QueryInterface(urlPrimitive);
+ NS_ENSURE_TRUE(srcUrlPrimitive, E_FAIL);
+
+ nsAutoString srcUri;
+ srcUrlPrimitive->GetData(srcUri);
+ if (srcUri.IsEmpty()) return E_FAIL;
+ nsCOMPtr<nsIURI> sourceURI;
+ NS_NewURI(getter_AddRefs(sourceURI), srcUri);
+
+ nsAutoString srcFileName;
+ nsCOMPtr<nsISupports> fileNamePrimitive;
+ Unused << mTransferable->GetTransferData(kFilePromiseDestFilename,
+ getter_AddRefs(fileNamePrimitive));
+ nsCOMPtr<nsISupportsString> srcFileNamePrimitive =
+ do_QueryInterface(fileNamePrimitive);
+ if (srcFileNamePrimitive) {
+ srcFileNamePrimitive->GetData(srcFileName);
+ } else {
+ nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
+ if (!sourceURL) return E_FAIL;
+
+ nsAutoCString urlFileName;
+ sourceURL->GetFileName(urlFileName);
+ NS_UnescapeURL(urlFileName);
+ CopyUTF8toUTF16(urlFileName, srcFileName);
+ }
+ if (srcFileName.IsEmpty()) return E_FAIL;
+
+ // make the name safe for the filesystem
+ MangleTextToValidFilename(srcFileName);
+
+ sourceURI.swap(*aSourceURI);
+ aFilename = srcFileName;
+ return S_OK;
+}
+
+HRESULT nsDataObj::GetFileDescriptor_IStreamA(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ HGLOBAL fileGroupDescHandle =
+ ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
+ NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY);
+
+ LPFILEGROUPDESCRIPTORA fileGroupDescA =
+ reinterpret_cast<LPFILEGROUPDESCRIPTORA>(GlobalLock(fileGroupDescHandle));
+ if (!fileGroupDescA) {
+ ::GlobalFree(fileGroupDescHandle);
+ return E_OUTOFMEMORY;
+ }
+
+ nsAutoString wideFileName;
+ HRESULT res;
+ nsCOMPtr<nsIURI> sourceURI;
+ res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
+ if (FAILED(res)) {
+ ::GlobalFree(fileGroupDescHandle);
+ return res;
+ }
+
+ nsAutoCString nativeFileName;
+ NS_CopyUnicodeToNative(wideFileName, nativeFileName);
+
+ strncpy(fileGroupDescA->fgd[0].cFileName, nativeFileName.get(),
+ NS_MAX_FILEDESCRIPTOR - 1);
+ fileGroupDescA->fgd[0].cFileName[NS_MAX_FILEDESCRIPTOR - 1] = '\0';
+
+ // one file in the file block
+ fileGroupDescA->cItems = 1;
+ fileGroupDescA->fgd[0].dwFlags = FD_PROGRESSUI;
+
+ GlobalUnlock(fileGroupDescHandle);
+ aSTG.hGlobal = fileGroupDescHandle;
+ aSTG.tymed = TYMED_HGLOBAL;
+
+ return S_OK;
+}
+
+HRESULT nsDataObj::GetFileDescriptor_IStreamW(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ HGLOBAL fileGroupDescHandle =
+ ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
+ NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY);
+
+ LPFILEGROUPDESCRIPTORW fileGroupDescW =
+ reinterpret_cast<LPFILEGROUPDESCRIPTORW>(GlobalLock(fileGroupDescHandle));
+ if (!fileGroupDescW) {
+ ::GlobalFree(fileGroupDescHandle);
+ return E_OUTOFMEMORY;
+ }
+
+ nsAutoString wideFileName;
+ HRESULT res;
+ nsCOMPtr<nsIURI> sourceURI;
+ res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
+ if (FAILED(res)) {
+ ::GlobalFree(fileGroupDescHandle);
+ return res;
+ }
+
+ wcsncpy(fileGroupDescW->fgd[0].cFileName, wideFileName.get(),
+ NS_MAX_FILEDESCRIPTOR - 1);
+ fileGroupDescW->fgd[0].cFileName[NS_MAX_FILEDESCRIPTOR - 1] = '\0';
+ // one file in the file block
+ fileGroupDescW->cItems = 1;
+ fileGroupDescW->fgd[0].dwFlags = FD_PROGRESSUI;
+
+ GlobalUnlock(fileGroupDescHandle);
+ aSTG.hGlobal = fileGroupDescHandle;
+ aSTG.tymed = TYMED_HGLOBAL;
+
+ return S_OK;
+}
+
+HRESULT nsDataObj::GetFileContents_IStream(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ IStream* pStream = nullptr;
+
+ nsDataObj::CreateStream(&pStream);
+ NS_ENSURE_TRUE(pStream, E_FAIL);
+
+ aSTG.tymed = TYMED_ISTREAM;
+ aSTG.pstm = pStream;
+ aSTG.pUnkForRelease = nullptr;
+
+ return S_OK;
+}
diff --git a/widget/windows/nsDataObj.h b/widget/windows/nsDataObj.h
new file mode 100644
index 0000000000..d19a21860d
--- /dev/null
+++ b/widget/windows/nsDataObj.h
@@ -0,0 +1,308 @@
+/* -*- 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 _NSDATAOBJ_H_
+#define _NSDATAOBJ_H_
+
+#include <oleidl.h>
+#include <shldisp.h>
+
+#include "mozilla/glue/WinUtils.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsIStreamListener.h"
+#include "nsIChannel.h"
+#include "nsCOMArray.h"
+#include "nsITimer.h"
+
+class nsICookieJarSettings;
+class nsIThread;
+class nsIPrincipal;
+class CEnumFormatEtc;
+class nsITransferable;
+
+/*
+ * This ole registered class is used to facilitate drag-drop of objects which
+ * can be adapted by an object derived from CfDragDrop. The CfDragDrop is
+ * associated with instances via SetDragDrop().
+ */
+class nsDataObj : public IDataObject, public IDataObjectAsyncCapability {
+ nsCOMPtr<nsIThread> mIOThread;
+
+ public: // construction, destruction
+ explicit nsDataObj(nsIURI* uri = nullptr);
+
+ protected:
+ virtual ~nsDataObj();
+
+ public: // IUnknown methods - see iunknown.h for documentation
+ STDMETHODIMP_(ULONG) AddRef() override;
+ STDMETHODIMP QueryInterface(REFIID, void**) override;
+ STDMETHODIMP_(ULONG) Release() override;
+
+ // support for clipboard
+ virtual void AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE);
+ void SetTransferable(nsITransferable* aTransferable);
+
+ public: // IDataObject methods - these are general comments. see CfDragDrop
+ // for overriding behavior
+ // Store data in pSTM according to the format specified by pFE, if the
+ // format is supported (supported formats are specified in CfDragDrop::
+ // GetFormats) and return NOERROR; otherwise return DATA_E_FORMATETC. It
+ // is the callers responsibility to free pSTM if NOERROR is returned.
+ STDMETHODIMP GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) override;
+
+ // Similar to GetData except that the caller allocates the structure
+ // referenced by pSTM.
+ STDMETHODIMP GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) override;
+
+ // Returns S_TRUE if this object supports the format specified by pSTM,
+ // S_FALSE otherwise.
+ STDMETHODIMP QueryGetData(LPFORMATETC pFE) override;
+
+ // Set pCanonFE to the canonical format of pFE if one exists and return
+ // NOERROR, otherwise return DATA_S_SAMEFORMATETC. A canonical format
+ // implies an identical rendering.
+ STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC pFE,
+ LPFORMATETC pCanonFE) final;
+
+ // Set this objects data according to the format specified by pFE and
+ // the storage medium specified by pSTM and return NOERROR, if the format
+ // is supported. If release is TRUE this object must release the storage
+ // associated with pSTM.
+ STDMETHODIMP SetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM,
+ BOOL release) override;
+
+ // Set ppEnum to an IEnumFORMATETC object which will iterate all of the
+ // data formats that this object supports. direction is either DATADIR_GET
+ // or DATADIR_SET.
+ STDMETHODIMP EnumFormatEtc(DWORD direction, LPENUMFORMATETC* ppEnum) final;
+
+ // Set up an advisory connection to this object based on the format specified
+ // by pFE, flags, and the pAdvise. Set pConn to the established advise
+ // connection.
+ STDMETHODIMP DAdvise(LPFORMATETC pFE, DWORD flags, LPADVISESINK pAdvise,
+ DWORD* pConn) final;
+
+ // Turn off advising of a previous call to DAdvise which set pConn.
+ STDMETHODIMP DUnadvise(DWORD pConn) final;
+
+ // Set ppEnum to an IEnumSTATDATA object which will iterate over the
+ // existing objects which have established advisory connections to this
+ // object.
+ STDMETHODIMP EnumDAdvise(LPENUMSTATDATA* ppEnum) final;
+
+ // IDataObjectAsyncCapability methods
+ STDMETHODIMP EndOperation(HRESULT hResult, IBindCtx* pbcReserved,
+ DWORD dwEffects) final;
+ STDMETHODIMP GetAsyncMode(BOOL* pfIsOpAsync) final;
+ STDMETHODIMP InOperation(BOOL* pfInAsyncOp) final;
+ STDMETHODIMP SetAsyncMode(BOOL fDoOpAsync) final;
+ STDMETHODIMP StartOperation(IBindCtx* pbcReserved) final;
+
+ private: // other methods
+ // Gets the filename from the kFilePromiseURLMime flavour
+ HRESULT GetDownloadDetails(nsIURI** aSourceURI, nsAString& aFilename);
+
+ // help determine the kind of drag
+ bool IsFlavourPresent(const char* inFlavour);
+
+ protected:
+ HRESULT GetFile(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT GetText(const nsACString& aDF, FORMATETC& aFE, STGMEDIUM& aSTG);
+
+ private:
+ HRESULT GetDib(const nsACString& inFlavor, FORMATETC&, STGMEDIUM& aSTG);
+
+ HRESULT DropImage(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT DropFile(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT DropTempFile(FORMATETC& aFE, STGMEDIUM& aSTG);
+
+ HRESULT GetUniformResourceLocator(FORMATETC& aFE, STGMEDIUM& aSTG,
+ bool aIsUnicode);
+ HRESULT ExtractUniformResourceLocatorA(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT ExtractUniformResourceLocatorW(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT GetFileDescriptor(FORMATETC& aFE, STGMEDIUM& aSTG, bool aIsUnicode);
+
+ protected:
+ HRESULT GetFileContents(FORMATETC& aFE, STGMEDIUM& aSTG);
+
+ private:
+ HRESULT GetPreferredDropEffect(FORMATETC& aFE, STGMEDIUM& aSTG);
+
+ // Provide the structures needed for an internet shortcut by the shell
+ HRESULT GetFileDescriptorInternetShortcutA(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT GetFileDescriptorInternetShortcutW(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT GetFileContentsInternetShortcut(FORMATETC& aFE, STGMEDIUM& aSTG);
+
+ // IStream implementation
+ HRESULT GetFileDescriptor_IStreamA(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT GetFileDescriptor_IStreamW(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT GetFileContents_IStream(FORMATETC& aFE, STGMEDIUM& aSTG);
+
+ nsresult ExtractShortcutURL(nsString& outURL);
+ nsresult ExtractShortcutTitle(nsString& outTitle);
+
+ // munge our HTML data to win32's CF_HTML spec. Will null terminate
+ nsresult BuildPlatformHTML(const char* inOurHTML, char** outPlatformHTML);
+
+ // Used for the SourceURL part of CF_HTML
+ nsCString mSourceURL;
+
+ protected:
+ BOOL FormatsMatch(const FORMATETC& source, const FORMATETC& target) const;
+
+ ULONG m_cRef; // the reference count
+
+ private:
+ nsTArray<nsCString> mDataFlavors;
+
+ nsITransferable* mTransferable; // nsDataObj owns and ref counts
+ // nsITransferable, the nsITransferable does
+ // know anything about the nsDataObj
+
+ protected:
+ CEnumFormatEtc* m_enumFE; // Ownership Rules:
+ // nsDataObj owns and ref counts CEnumFormatEtc,
+
+ private:
+ nsCOMPtr<nsIFile> mCachedTempFile;
+
+ BOOL mIsAsyncMode;
+ BOOL mIsInOperation;
+ ///////////////////////////////////////////////////////////////////////////////
+ // CStream class implementation
+ // this class is used in Drag and drop with download sample
+ // called from IDataObject::GetData
+ class CStreamBase : public IStream {
+ // IStream
+ STDMETHODIMP Clone(IStream** ppStream) final;
+ STDMETHODIMP Commit(DWORD dwFrags) final;
+ STDMETHODIMP CopyTo(IStream* pDestStream, ULARGE_INTEGER nBytesToCopy,
+ ULARGE_INTEGER* nBytesRead,
+ ULARGE_INTEGER* nBytesWritten) final;
+ STDMETHODIMP LockRegion(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes,
+ DWORD dwFlags) final;
+ STDMETHODIMP Revert(void) final;
+ STDMETHODIMP Seek(LARGE_INTEGER nMove, DWORD dwOrigin,
+ ULARGE_INTEGER* nNewPos) final;
+ STDMETHODIMP SetSize(ULARGE_INTEGER nNewSize) final;
+ STDMETHODIMP UnlockRegion(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes,
+ DWORD dwFlags) final;
+ STDMETHODIMP Write(const void* pvBuffer, ULONG nBytesToRead,
+ ULONG* nBytesRead) final;
+
+ protected:
+ uint32_t mStreamRead;
+
+ CStreamBase();
+ virtual ~CStreamBase();
+ };
+
+ class CStream final : public CStreamBase, public nsIStreamListener {
+ nsCOMPtr<nsIChannel> mChannel;
+ FallibleTArray<uint8_t> mChannelData;
+ nsresult mChannelResult;
+ bool mChannelRead;
+
+ virtual ~CStream();
+ nsresult WaitForCompletion();
+
+ // IUnknown
+ STDMETHOD(QueryInterface)(REFIID refiid, void** ppvResult) final;
+
+ // IStream
+ STDMETHODIMP Read(void* pvBuffer, ULONG nBytesToRead,
+ ULONG* nBytesRead) final;
+ STDMETHODIMP Stat(STATSTG* statstg, DWORD dwFlags) final;
+
+ public:
+ CStream();
+ nsresult Init(nsIURI* pSourceURI, nsContentPolicyType aContentPolicyType,
+ nsIPrincipal* aRequestingPrincipal,
+ nsICookieJarSettings* aCookieJarSettings);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ };
+
+ HRESULT CreateStream(IStream** outStream);
+
+ // This class must be thread-safe.
+ class AutoCloseEvent final {
+ const nsAutoHandle mEvent;
+
+ AutoCloseEvent(const AutoCloseEvent&) = delete;
+ void operator=(const AutoCloseEvent&) = delete;
+ ~AutoCloseEvent() = default;
+
+ public:
+ AutoCloseEvent();
+ bool IsInited() const;
+ void Signal() const;
+ DWORD Wait(DWORD aMillisec) const;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AutoCloseEvent)
+ };
+
+ // This class must be thread-safe.
+ class AutoSetEvent final {
+ const RefPtr<AutoCloseEvent> mEvent;
+
+ AutoSetEvent(const AutoSetEvent&) = delete;
+ void operator=(const AutoSetEvent&) = delete;
+ ~AutoSetEvent();
+
+ public:
+ explicit AutoSetEvent(mozilla::NotNull<AutoCloseEvent*> aEvent);
+ void Signal() const;
+ bool IsWaiting() const;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AutoSetEvent)
+ };
+
+ // This class must be thread-safe.
+ class CMemStream final : public CStreamBase {
+ static mozilla::glue::Win32SRWLock mLock;
+ const nsAutoGlobalMem mGlobalMem;
+ const RefPtr<AutoCloseEvent> mEvent;
+ const uint32_t mTotalLength;
+ RefPtr<IUnknown> mMarshaler;
+
+ virtual ~CMemStream();
+ void WaitForCompletion();
+
+ // IStream
+ STDMETHODIMP Read(void* pvBuffer, ULONG nBytesToRead,
+ ULONG* nBytesRead) final;
+ STDMETHODIMP Stat(STATSTG* statstg, DWORD dwFlags) final;
+
+ public:
+ CMemStream(nsHGLOBAL aGlobalMem, uint32_t mTotalLength,
+ already_AddRefed<AutoCloseEvent> aEvent);
+
+ // IUnknown
+ STDMETHOD(QueryInterface)(REFIID refiid, void** ppvResult) final;
+ NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(CMemStream, final)
+ };
+
+ private:
+ // Drag and drop helper data for implementing drag and drop image support
+ typedef struct {
+ FORMATETC fe;
+ STGMEDIUM stgm;
+ } DATAENTRY, *LPDATAENTRY;
+
+ nsTArray<LPDATAENTRY> mDataEntryList;
+ nsCOMPtr<nsITimer> mTimer;
+
+ bool LookupArbitraryFormat(FORMATETC* aFormat, LPDATAENTRY* aDataEntry,
+ BOOL aAddorUpdate);
+ bool CopyMediumData(STGMEDIUM* aMediumDst, STGMEDIUM* aMediumSrc,
+ LPFORMATETC aFormat, BOOL aSetData);
+};
+
+#endif // _NSDATAOBJ_H_
diff --git a/widget/windows/nsDataObjCollection.cpp b/widget/windows/nsDataObjCollection.cpp
new file mode 100644
index 0000000000..8750563602
--- /dev/null
+++ b/widget/windows/nsDataObjCollection.cpp
@@ -0,0 +1,370 @@
+/* -*- 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 <shlobj.h>
+
+#include "nsDataObjCollection.h"
+#include "nsClipboard.h"
+#include "IEnumFE.h"
+
+#include <ole2.h>
+
+// {25589C3E-1FAC-47b9-BF43-CAEA89B79533}
+const IID IID_IDataObjCollection = {
+ 0x25589c3e,
+ 0x1fac,
+ 0x47b9,
+ {0xbf, 0x43, 0xca, 0xea, 0x89, 0xb7, 0x95, 0x33}};
+
+/*
+ * Class nsDataObjCollection
+ */
+
+nsDataObjCollection::nsDataObjCollection() {}
+
+nsDataObjCollection::~nsDataObjCollection() { mDataObjects.Clear(); }
+
+// IUnknown interface methods - see iunknown.h for documentation
+STDMETHODIMP nsDataObjCollection::QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+
+ if ((IID_IUnknown == riid) || (IID_IDataObject == riid)) {
+ *ppv = static_cast<IDataObject*>(this);
+ AddRef();
+ return NOERROR;
+ }
+
+ if (IID_IDataObjCollection == riid) {
+ *ppv = static_cast<nsIDataObjCollection*>(this);
+ AddRef();
+ return NOERROR;
+ }
+ // offer to operate asynchronously (required by nsDragService)
+ if (IID_IDataObjectAsyncCapability == riid) {
+ *ppv = static_cast<IDataObjectAsyncCapability*>(this);
+ AddRef();
+ return NOERROR;
+ }
+
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG) nsDataObjCollection::AddRef() { return ++m_cRef; }
+
+STDMETHODIMP_(ULONG) nsDataObjCollection::Release() {
+ if (0 != --m_cRef) return m_cRef;
+
+ delete this;
+
+ return 0;
+}
+
+// IDataObject methods
+STDMETHODIMP nsDataObjCollection::GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
+ static CLIPFORMAT fileDescriptorFlavorA =
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
+ static CLIPFORMAT fileDescriptorFlavorW =
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
+ static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
+
+ switch (pFE->cfFormat) {
+ case CF_TEXT:
+ case CF_UNICODETEXT:
+ return GetText(pFE, pSTM);
+ case CF_HDROP:
+ return GetFile(pFE, pSTM);
+ default:
+ if (pFE->cfFormat == fileDescriptorFlavorA ||
+ pFE->cfFormat == fileDescriptorFlavorW) {
+ return GetFileDescriptors(pFE, pSTM);
+ }
+ if (pFE->cfFormat == fileFlavor) {
+ return GetFileContents(pFE, pSTM);
+ }
+ }
+ return GetFirstSupporting(pFE, pSTM);
+}
+
+STDMETHODIMP nsDataObjCollection::GetDataHere(LPFORMATETC pFE,
+ LPSTGMEDIUM pSTM) {
+ return E_FAIL;
+}
+
+// Other objects querying to see if we support a particular format
+STDMETHODIMP nsDataObjCollection::QueryGetData(LPFORMATETC pFE) {
+ UINT format = nsClipboard::GetFormat(MULTI_MIME);
+
+ if (format == pFE->cfFormat) {
+ return S_OK;
+ }
+
+ for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+ IDataObject* dataObj = mDataObjects.ElementAt(i);
+ if (S_OK == dataObj->QueryGetData(pFE)) {
+ return S_OK;
+ }
+ }
+
+ return DV_E_FORMATETC;
+}
+
+STDMETHODIMP nsDataObjCollection::SetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM,
+ BOOL fRelease) {
+ // Set arbitrary data formats on the first object in the collection and let
+ // it handle the heavy lifting
+ if (mDataObjects.Length() == 0) return E_FAIL;
+ return mDataObjects.ElementAt(0)->SetData(pFE, pSTM, fRelease);
+}
+
+// Registers a DataFlavor/FE pair
+void nsDataObjCollection::AddDataFlavor(const char* aDataFlavor,
+ LPFORMATETC aFE) {
+ // Add the FormatEtc to our list if it's not already there. We don't care
+ // about the internal aDataFlavor because nsDataObj handles that.
+ IEnumFORMATETC* ifEtc;
+ FORMATETC fEtc;
+ ULONG num;
+ if (S_OK != this->EnumFormatEtc(DATADIR_GET, &ifEtc)) return;
+ while (S_OK == ifEtc->Next(1, &fEtc, &num)) {
+ NS_ASSERTION(
+ 1 == num,
+ "Bit off more than we can chew in nsDataObjCollection::AddDataFlavor");
+ if (FormatsMatch(fEtc, *aFE)) {
+ ifEtc->Release();
+ return;
+ }
+ } // If we didn't find a matching format, add this one
+ ifEtc->Release();
+ m_enumFE->AddFormatEtc(aFE);
+}
+
+// We accept ownership of the nsDataObj which we free on destruction
+void nsDataObjCollection::AddDataObject(IDataObject* aDataObj) {
+ nsDataObj* dataObj = reinterpret_cast<nsDataObj*>(aDataObj);
+ mDataObjects.AppendElement(dataObj);
+}
+
+// Methods for getting data
+HRESULT nsDataObjCollection::GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
+ STGMEDIUM workingmedium;
+ FORMATETC fe = *pFE;
+ HGLOBAL hGlobalMemory;
+ HRESULT hr;
+ // Make enough space for the header and the trailing null
+ uint32_t buffersize = sizeof(DROPFILES) + sizeof(char16_t);
+ uint32_t alloclen = 0;
+ char16_t* realbuffer;
+ nsAutoString filename;
+
+ hGlobalMemory = GlobalAlloc(GHND, buffersize);
+
+ for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+ nsDataObj* dataObj = mDataObjects.ElementAt(i);
+ hr = dataObj->GetData(&fe, &workingmedium);
+ if (hr != S_OK) {
+ switch (hr) {
+ case DV_E_FORMATETC:
+ continue;
+ default:
+ return hr;
+ }
+ }
+ // Now we need to pull out the filename
+ char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal);
+ if (buffer == nullptr) return E_FAIL;
+ buffer += sizeof(DROPFILES) / sizeof(char16_t);
+ filename = buffer;
+ GlobalUnlock(workingmedium.hGlobal);
+ ReleaseStgMedium(&workingmedium);
+ // Now put the filename into our buffer
+ alloclen = (filename.Length() + 1) * sizeof(char16_t);
+ hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND);
+ if (hGlobalMemory == nullptr) return E_FAIL;
+ realbuffer = (char16_t*)((char*)GlobalLock(hGlobalMemory) + buffersize);
+ if (!realbuffer) return E_FAIL;
+ realbuffer--; // Overwrite the preceding null
+ memcpy(realbuffer, filename.get(), alloclen);
+ GlobalUnlock(hGlobalMemory);
+ buffersize += alloclen;
+ }
+ // We get the last null (on the double null terminator) for free since we used
+ // the zero memory flag when we allocated. All we need to do is fill the
+ // DROPFILES structure
+ DROPFILES* df = (DROPFILES*)GlobalLock(hGlobalMemory);
+ if (!df) return E_FAIL;
+ df->pFiles = sizeof(DROPFILES); // Offset to start of file name string
+ df->fNC = 0;
+ df->pt.x = 0;
+ df->pt.y = 0;
+ df->fWide = TRUE; // utf-16 chars
+ GlobalUnlock(hGlobalMemory);
+ // Finally fill out the STGMEDIUM struct
+ pSTM->tymed = TYMED_HGLOBAL;
+ pSTM->pUnkForRelease = nullptr; // Caller gets to free the data
+ pSTM->hGlobal = hGlobalMemory;
+ return S_OK;
+}
+
+HRESULT nsDataObjCollection::GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
+ STGMEDIUM workingmedium;
+ FORMATETC fe = *pFE;
+ HGLOBAL hGlobalMemory;
+ HRESULT hr;
+ uint32_t buffersize = 1;
+ uint32_t alloclen = 0;
+
+ hGlobalMemory = GlobalAlloc(GHND, buffersize);
+
+ if (pFE->cfFormat == CF_TEXT) {
+ nsAutoCString text;
+ for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+ nsDataObj* dataObj = mDataObjects.ElementAt(i);
+ hr = dataObj->GetData(&fe, &workingmedium);
+ if (hr != S_OK) {
+ switch (hr) {
+ case DV_E_FORMATETC:
+ continue;
+ default:
+ return hr;
+ }
+ }
+ // Now we need to pull out the text
+ char* buffer = (char*)GlobalLock(workingmedium.hGlobal);
+ if (buffer == nullptr) return E_FAIL;
+ text = buffer;
+ GlobalUnlock(workingmedium.hGlobal);
+ ReleaseStgMedium(&workingmedium);
+ // Now put the text into our buffer
+ alloclen = text.Length();
+ hGlobalMemory =
+ ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND);
+ if (hGlobalMemory == nullptr) return E_FAIL;
+ buffer = ((char*)GlobalLock(hGlobalMemory) + buffersize);
+ if (!buffer) return E_FAIL;
+ buffer--; // Overwrite the preceding null
+ memcpy(buffer, text.get(), alloclen);
+ GlobalUnlock(hGlobalMemory);
+ buffersize += alloclen;
+ }
+ pSTM->tymed = TYMED_HGLOBAL;
+ pSTM->pUnkForRelease = nullptr; // Caller gets to free the data
+ pSTM->hGlobal = hGlobalMemory;
+ return S_OK;
+ }
+ if (pFE->cfFormat == CF_UNICODETEXT) {
+ buffersize = sizeof(char16_t);
+ nsAutoString text;
+ for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+ nsDataObj* dataObj = mDataObjects.ElementAt(i);
+ hr = dataObj->GetData(&fe, &workingmedium);
+ if (hr != S_OK) {
+ switch (hr) {
+ case DV_E_FORMATETC:
+ continue;
+ default:
+ return hr;
+ }
+ }
+ // Now we need to pull out the text
+ char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal);
+ if (buffer == nullptr) return E_FAIL;
+ text = buffer;
+ GlobalUnlock(workingmedium.hGlobal);
+ ReleaseStgMedium(&workingmedium);
+ // Now put the text into our buffer
+ alloclen = text.Length() * sizeof(char16_t);
+ hGlobalMemory =
+ ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND);
+ if (hGlobalMemory == nullptr) return E_FAIL;
+ buffer = (char16_t*)((char*)GlobalLock(hGlobalMemory) + buffersize);
+ if (!buffer) return E_FAIL;
+ buffer--; // Overwrite the preceding null
+ memcpy(buffer, text.get(), alloclen);
+ GlobalUnlock(hGlobalMemory);
+ buffersize += alloclen;
+ }
+ pSTM->tymed = TYMED_HGLOBAL;
+ pSTM->pUnkForRelease = nullptr; // Caller gets to free the data
+ pSTM->hGlobal = hGlobalMemory;
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+HRESULT nsDataObjCollection::GetFileDescriptors(LPFORMATETC pFE,
+ LPSTGMEDIUM pSTM) {
+ STGMEDIUM workingmedium;
+ FORMATETC fe = *pFE;
+ HGLOBAL hGlobalMemory;
+ HRESULT hr;
+ uint32_t buffersize = sizeof(UINT);
+ uint32_t alloclen = sizeof(FILEDESCRIPTOR);
+
+ hGlobalMemory = GlobalAlloc(GHND, buffersize);
+
+ for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+ nsDataObj* dataObj = mDataObjects.ElementAt(i);
+ hr = dataObj->GetData(&fe, &workingmedium);
+ if (hr != S_OK) {
+ switch (hr) {
+ case DV_E_FORMATETC:
+ continue;
+ default:
+ return hr;
+ }
+ }
+ // Now we need to pull out the filedescriptor
+ FILEDESCRIPTOR* buffer =
+ (FILEDESCRIPTOR*)((char*)GlobalLock(workingmedium.hGlobal) +
+ sizeof(UINT));
+ if (buffer == nullptr) return E_FAIL;
+ hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND);
+ if (hGlobalMemory == nullptr) return E_FAIL;
+ FILEGROUPDESCRIPTOR* realbuffer =
+ (FILEGROUPDESCRIPTOR*)GlobalLock(hGlobalMemory);
+ if (!realbuffer) return E_FAIL;
+ FILEDESCRIPTOR* copyloc = (FILEDESCRIPTOR*)((char*)realbuffer + buffersize);
+ memcpy(copyloc, buffer, alloclen);
+ realbuffer->cItems++;
+ GlobalUnlock(hGlobalMemory);
+ GlobalUnlock(workingmedium.hGlobal);
+ ReleaseStgMedium(&workingmedium);
+ buffersize += alloclen;
+ }
+ pSTM->tymed = TYMED_HGLOBAL;
+ pSTM->pUnkForRelease = nullptr; // Caller gets to free the data
+ pSTM->hGlobal = hGlobalMemory;
+ return S_OK;
+}
+
+HRESULT nsDataObjCollection::GetFileContents(LPFORMATETC pFE,
+ LPSTGMEDIUM pSTM) {
+ ULONG num = 0;
+ ULONG numwanted = (pFE->lindex == -1) ? 0 : pFE->lindex;
+ FORMATETC fEtc = *pFE;
+ fEtc.lindex = -1; // We're lying to the data object so it thinks it's alone
+
+ // The key for this data type is to figure out which data object the index
+ // corresponds to and then just pass it along
+ for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+ nsDataObj* dataObj = mDataObjects.ElementAt(i);
+ if (dataObj->QueryGetData(&fEtc) != S_OK) continue;
+ if (num == numwanted) return dataObj->GetData(pFE, pSTM);
+ num++;
+ }
+ return DV_E_LINDEX;
+}
+
+HRESULT nsDataObjCollection::GetFirstSupporting(LPFORMATETC pFE,
+ LPSTGMEDIUM pSTM) {
+ // There is no way to pass more than one of this, so just find the first data
+ // object that supports it and pass it along
+ for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+ if (mDataObjects.ElementAt(i)->QueryGetData(pFE) == S_OK)
+ return mDataObjects.ElementAt(i)->GetData(pFE, pSTM);
+ }
+ return DV_E_FORMATETC;
+}
diff --git a/widget/windows/nsDataObjCollection.h b/widget/windows/nsDataObjCollection.h
new file mode 100644
index 0000000000..02ec7e8916
--- /dev/null
+++ b/widget/windows/nsDataObjCollection.h
@@ -0,0 +1,92 @@
+/* -*- 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 _NSDATAOBJCOLLECTION_H_
+#define _NSDATAOBJCOLLECTION_H_
+
+#include <oleidl.h>
+
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsDataObj.h"
+#include "mozilla/Attributes.h"
+
+#define MULTI_MIME "Mozilla/IDataObjectCollectionFormat"
+
+EXTERN_C const IID IID_IDataObjCollection;
+
+// An interface to make sure we have the right kind of object for D&D
+// this way we can filter out collection objects that aren't ours
+class nsIDataObjCollection : public IUnknown {
+ public:
+};
+
+/*
+ * This ole registered class is used to facilitate drag-drop of objects which
+ * can be adapted by an object derived from CfDragDrop. The CfDragDrop is
+ * associated with instances via SetDragDrop().
+ */
+
+class nsDataObjCollection final : public nsIDataObjCollection,
+ public nsDataObj {
+ public:
+ nsDataObjCollection();
+
+ private:
+ ~nsDataObjCollection() final;
+
+ public: // IUnknown methods - see iunknown.h for documentation
+ STDMETHODIMP_(ULONG) AddRef() final;
+ STDMETHODIMP QueryInterface(REFIID, void**) final;
+ STDMETHODIMP_(ULONG) Release() final;
+
+ private: // DataGet and DataSet helper methods
+ HRESULT GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ HRESULT GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ HRESULT GetFileDescriptors(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ HRESULT GetFileContents(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ HRESULT GetFirstSupporting(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+
+ using nsDataObj::GetFile;
+ using nsDataObj::GetFileContents;
+ using nsDataObj::GetText;
+
+ // support for clipboard
+ void AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE) final;
+
+ public: // from nsPIDataObjCollection
+ void AddDataObject(IDataObject* aDataObj);
+ int32_t GetNumDataObjects() { return mDataObjects.Length(); }
+ nsDataObj* GetDataObjectAt(uint32_t aItem) {
+ return mDataObjects.SafeElementAt(aItem, RefPtr<nsDataObj>());
+ }
+
+ public:
+ // Store data in pSTM according to the format specified by pFE, if the
+ // format is supported (supported formats are specified in CfDragDrop::
+ // GetFormats) and return NOERROR; otherwise return DATA_E_FORMATETC. It
+ // is the callers responsibility to free pSTM if NOERROR is returned.
+ STDMETHODIMP GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) final;
+
+ // Similar to GetData except that the caller allocates the structure
+ // referenced by pSTM.
+ STDMETHODIMP GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) final;
+
+ // Returns S_TRUE if this object supports the format specified by pSTM,
+ // S_FALSE otherwise.
+ STDMETHODIMP QueryGetData(LPFORMATETC pFE) final;
+
+ // Set this objects data according to the format specified by pFE and
+ // the storage medium specified by pSTM and return NOERROR, if the format
+ // is supported. If release is TRUE this object must release the storage
+ // associated with pSTM.
+ STDMETHODIMP SetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM, BOOL release) final;
+
+ private:
+ nsTArray<RefPtr<nsDataObj> > mDataObjects;
+};
+
+#endif //
diff --git a/widget/windows/nsDeviceContextSpecWin.cpp b/widget/windows/nsDeviceContextSpecWin.cpp
new file mode 100644
index 0000000000..7fd71b4db8
--- /dev/null
+++ b/widget/windows/nsDeviceContextSpecWin.cpp
@@ -0,0 +1,656 @@
+/* -*- 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 "nsDeviceContextSpecWin.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/gfx/PrintTargetPDF.h"
+#include "mozilla/gfx/PrintTargetWindows.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Telemetry.h"
+#include "nsAnonymousTemporaryFile.h"
+
+#include <wchar.h>
+#include <windef.h>
+#include <winspool.h>
+
+#include "nsIWidget.h"
+
+#include "nsTArray.h"
+#include "nsIPrintSettingsWin.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsPrinterWin.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+
+#include "gfxWindowsSurface.h"
+
+#include "nsIFileStreams.h"
+#include "nsWindowsHelpers.h"
+
+#include "mozilla/gfx/Logging.h"
+
+#ifdef MOZ_ENABLE_SKIA_PDF
+# include "mozilla/gfx/PrintTargetSkPDF.h"
+# include "mozilla/gfx/PrintTargetEMF.h"
+# include "nsIUUIDGenerator.h"
+# include "nsDirectoryServiceDefs.h"
+# include "nsPrintfCString.h"
+# include "nsThreadUtils.h"
+#endif
+
+extern mozilla::LazyLogModule gPrintingLog;
+#define PR_PL(_p1) MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1)
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+#ifdef MOZ_ENABLE_SKIA_PDF
+using namespace mozilla::widget;
+#endif
+
+static const wchar_t kDriverName[] = L"WINSPOOL";
+
+//----------------------------------------------------------------------------------
+//---------------
+// static members
+//----------------------------------------------------------------------------------
+nsDeviceContextSpecWin::nsDeviceContextSpecWin() = default;
+
+//----------------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsDeviceContextSpecWin, nsIDeviceContextSpec)
+
+nsDeviceContextSpecWin::~nsDeviceContextSpecWin() {
+ SetDevMode(nullptr);
+
+ if (mTempFile) {
+ mTempFile->Remove(/* recursive = */ false);
+ }
+
+ if (nsCOMPtr<nsIPrintSettingsWin> ps = do_QueryInterface(mPrintSettings)) {
+ ps->SetDeviceName(u""_ns);
+ ps->SetDriverName(u""_ns);
+ ps->SetDevMode(nullptr);
+ }
+}
+
+static bool GetDefaultPrinterName(nsAString& aDefaultPrinterName) {
+ DWORD length = 0;
+ GetDefaultPrinterW(nullptr, &length);
+
+ if (length) {
+ aDefaultPrinterName.SetLength(length);
+ if (GetDefaultPrinterW((LPWSTR)aDefaultPrinterName.BeginWriting(),
+ &length)) {
+ // `length` includes the terminating null, so we subtract that from our
+ // string length.
+ aDefaultPrinterName.SetLength(length - 1);
+ PR_PL(("DEFAULT PRINTER [%s]\n",
+ NS_ConvertUTF16toUTF8(aDefaultPrinterName).get()));
+ return true;
+ }
+ }
+
+ aDefaultPrinterName.Truncate();
+ PR_PL(("NO DEFAULT PRINTER\n"));
+ return false;
+}
+
+//----------------------------------------------------------------------------------
+NS_IMETHODIMP nsDeviceContextSpecWin::Init(nsIWidget* aWidget,
+ nsIPrintSettings* aPrintSettings,
+ bool aIsPrintPreview) {
+ mPrintSettings = aPrintSettings;
+
+ // Get the Printer Name to be used and output format.
+ nsAutoString printerName;
+ if (mPrintSettings) {
+ mPrintSettings->GetOutputFormat(&mOutputFormat);
+ mPrintSettings->GetPrinterName(printerName);
+ }
+
+ // If there is no name then use the default printer
+ if (printerName.IsEmpty()) {
+ GetDefaultPrinterName(printerName);
+ }
+
+ // Gather telemetry on the print target type.
+ //
+ // Unfortunately, if we're not using our own internal save-to-pdf codepaths,
+ // there isn't a good way to determine whether a print is going to be to a
+ // physical printer or to a file or some other non-physical output. We do our
+ // best by checking for what seems to be the most common save-to-PDF virtual
+ // printers.
+ //
+ // We use StringBeginsWith below, since printer names are often followed by a
+ // version number or other product differentiating string. (True for doPDF,
+ // novaPDF, PDF-XChange and Soda PDF, for example.)
+ if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
+ u"pdf_file"_ns, 1);
+ } else if (StringBeginsWith(printerName, u"Microsoft Print to PDF"_ns) ||
+ StringBeginsWith(printerName, u"Adobe PDF"_ns) ||
+ StringBeginsWith(printerName, u"Bullzip PDF Printer"_ns) ||
+ StringBeginsWith(printerName, u"CutePDF Writer"_ns) ||
+ StringBeginsWith(printerName, u"doPDF"_ns) ||
+ StringBeginsWith(printerName, u"Foxit Reader PDF Printer"_ns) ||
+ StringBeginsWith(printerName, u"Nitro PDF Creator"_ns) ||
+ StringBeginsWith(printerName, u"novaPDF"_ns) ||
+ StringBeginsWith(printerName, u"PDF-XChange"_ns) ||
+ StringBeginsWith(printerName, u"PDF24 PDF"_ns) ||
+ StringBeginsWith(printerName, u"PDFCreator"_ns) ||
+ StringBeginsWith(printerName, u"PrimoPDF"_ns) ||
+ StringBeginsWith(printerName, u"Soda PDF"_ns) ||
+ StringBeginsWith(printerName, u"Solid PDF Creator"_ns) ||
+ StringBeginsWith(printerName,
+ u"Universal Document Converter"_ns)) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
+ u"pdf_file"_ns, 1);
+ } else if (printerName.EqualsLiteral("Microsoft XPS Document Writer")) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
+ u"xps_file"_ns, 1);
+ } else {
+ nsAString::const_iterator start, end;
+ printerName.BeginReading(start);
+ printerName.EndReading(end);
+ if (CaseInsensitiveFindInReadable(u"pdf"_ns, start, end)) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
+ u"pdf_unknown"_ns, 1);
+ } else {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
+ u"unknown"_ns, 1);
+ }
+ }
+
+ nsresult rv = NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE;
+ if (aPrintSettings) {
+#ifdef MOZ_ENABLE_SKIA_PDF
+ nsAutoString printViaPdf;
+ Preferences::GetString("print.print_via_pdf_encoder", printViaPdf);
+ if (printViaPdf.EqualsLiteral("skia-pdf")) {
+ mPrintViaSkPDF = true;
+ }
+#endif
+
+ // If we're in the child and printing via the parent or we're printing to
+ // PDF we only need information from the print settings.
+ if ((XRE_IsContentProcess() &&
+ Preferences::GetBool("print.print_via_parent")) ||
+ mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings));
+ if (psWin) {
+ nsAutoString deviceName;
+ nsAutoString driverName;
+ psWin->GetDeviceName(deviceName);
+ psWin->GetDriverName(driverName);
+
+ LPDEVMODEW devMode;
+ psWin->GetDevMode(&devMode); // creates new memory (makes a copy)
+
+ if (!deviceName.IsEmpty() && !driverName.IsEmpty() && devMode) {
+ // Scaling is special, it is one of the few
+ // devMode items that we control in layout
+ if (devMode->dmFields & DM_SCALE) {
+ double scale = double(devMode->dmScale) / 100.0f;
+ if (scale != 1.0) {
+ aPrintSettings->SetScaling(scale);
+ devMode->dmScale = 100;
+ }
+ }
+
+ SetDeviceName(deviceName);
+ SetDriverName(driverName);
+ SetDevMode(devMode);
+
+ return NS_OK;
+ } else {
+ PR_PL(
+ ("***** nsDeviceContextSpecWin::Init - "
+ "deviceName/driverName/devMode was NULL!\n"));
+ if (devMode) ::HeapFree(::GetProcessHeap(), 0, devMode);
+ }
+ }
+ } else {
+ PR_PL(("***** nsDeviceContextSpecWin::Init - aPrintSettingswas NULL!\n"));
+ }
+
+ if (printerName.IsEmpty()) {
+ return rv;
+ }
+
+ return GetDataFromPrinter(printerName, mPrintSettings);
+}
+
+//----------------------------------------------------------
+
+already_AddRefed<PrintTarget> nsDeviceContextSpecWin::MakePrintTarget() {
+ NS_ASSERTION(mDevMode || mOutputFormat == nsIPrintSettings::kOutputFormatPDF,
+ "DevMode can't be NULL here unless we're printing to PDF.");
+
+#ifdef MOZ_ENABLE_SKIA_PDF
+ if (mPrintViaSkPDF) {
+ double width, height;
+ mPrintSettings->GetEffectivePageSize(&width, &height);
+ if (width <= 0 || height <= 0) {
+ return nullptr;
+ }
+
+ // convert twips to points
+ width /= TWIPS_PER_POINT_FLOAT;
+ height /= TWIPS_PER_POINT_FLOAT;
+ IntSize size = IntSize::Ceil(width, height);
+
+ if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ nsString filename;
+ mPrintSettings->GetToFileName(filename);
+
+ nsAutoCString printFile(NS_ConvertUTF16toUTF8(filename).get());
+ auto skStream = MakeUnique<SkFILEWStream>(printFile.get());
+ return PrintTargetSkPDF::CreateOrNull(std::move(skStream), size);
+ }
+
+ if (mDevMode) {
+ NS_WARNING_ASSERTION(!mDriverName.IsEmpty(), "No driver!");
+ HDC dc =
+ ::CreateDCW(mDriverName.get(), mDeviceName.get(), nullptr, mDevMode);
+ if (!dc) {
+ gfxCriticalError(gfxCriticalError::DefaultOptions(false))
+ << "Failed to create device context in GetSurfaceForPrinter";
+ return nullptr;
+ }
+ return PrintTargetEMF::CreateOrNull(dc, size);
+ }
+ }
+#endif
+
+ if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ nsString filename;
+ mPrintSettings->GetToFileName(filename);
+
+ double width, height;
+ mPrintSettings->GetEffectiveSheetSize(&width, &height);
+ if (width <= 0 || height <= 0) {
+ return nullptr;
+ }
+
+ // convert twips to points
+ width /= TWIPS_PER_POINT_FLOAT;
+ height /= TWIPS_PER_POINT_FLOAT;
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv;
+ if (!filename.IsEmpty()) {
+ file = do_CreateInstance("@mozilla.org/file/local;1");
+ rv = file->InitWithPath(filename);
+ } else {
+ rv = NS_OpenAnonymousTemporaryNsIFile(getter_AddRefs(mTempFile));
+ file = mTempFile;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIFileOutputStream> stream =
+ do_CreateInstance("@mozilla.org/network/file-output-stream;1");
+ rv = stream->Init(file, -1, -1, 0);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return PrintTargetPDF::CreateOrNull(stream, IntSize::Ceil(width, height));
+ }
+
+ if (mDevMode) {
+ NS_WARNING_ASSERTION(!mDriverName.IsEmpty(), "No driver!");
+ HDC dc =
+ ::CreateDCW(mDriverName.get(), mDeviceName.get(), nullptr, mDevMode);
+ if (!dc) {
+ gfxCriticalError(gfxCriticalError::DefaultOptions(false))
+ << "Failed to create device context in GetSurfaceForPrinter";
+ return nullptr;
+ }
+
+ // The PrintTargetWindows takes over ownership of this DC
+ return PrintTargetWindows::CreateOrNull(dc);
+ }
+
+ return nullptr;
+}
+
+float nsDeviceContextSpecWin::GetDPI() {
+ if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF || mPrintViaSkPDF) {
+ return nsIDeviceContextSpec::GetDPI();
+ }
+ // To match the previous printing code we need to return 144 when printing to
+ // a Windows surface.
+ return 144.0f;
+}
+
+float nsDeviceContextSpecWin::GetPrintingScale() {
+ MOZ_ASSERT(mPrintSettings);
+ if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF || mPrintViaSkPDF) {
+ return nsIDeviceContextSpec::GetPrintingScale();
+ }
+
+ // The print settings will have the resolution stored from the real device.
+ //
+ // FIXME: Shouldn't we use this in GetDPI then instead of hard-coding 144.0?
+ int32_t resolution;
+ mPrintSettings->GetResolution(&resolution);
+ return float(resolution) / GetDPI();
+}
+
+gfxPoint nsDeviceContextSpecWin::GetPrintingTranslate() {
+ // The underlying surface on windows is the size of the printable region. When
+ // the region is smaller than the actual paper size the (0, 0) coordinate
+ // refers top-left of that unwritable region. To instead have (0, 0) become
+ // the top-left of the actual paper, translate it's coordinate system by the
+ // unprintable region's width.
+ double marginTop, marginLeft;
+ mPrintSettings->GetUnwriteableMarginTop(&marginTop);
+ mPrintSettings->GetUnwriteableMarginLeft(&marginLeft);
+ int32_t resolution;
+ mPrintSettings->GetResolution(&resolution);
+ return gfxPoint(-marginLeft * resolution, -marginTop * resolution);
+}
+
+//----------------------------------------------------------------------------------
+void nsDeviceContextSpecWin::SetDeviceName(const nsAString& aDeviceName) {
+ mDeviceName = aDeviceName;
+}
+
+//----------------------------------------------------------------------------------
+void nsDeviceContextSpecWin::SetDriverName(const nsAString& aDriverName) {
+ mDriverName = aDriverName;
+}
+
+//----------------------------------------------------------------------------------
+void nsDeviceContextSpecWin::SetDevMode(LPDEVMODEW aDevMode) {
+ if (mDevMode) {
+ ::HeapFree(::GetProcessHeap(), 0, mDevMode);
+ }
+
+ mDevMode = aDevMode;
+}
+
+//------------------------------------------------------------------
+void nsDeviceContextSpecWin::GetDevMode(LPDEVMODEW& aDevMode) {
+ aDevMode = mDevMode;
+}
+
+#define DISPLAY_LAST_ERROR
+
+//----------------------------------------------------------------------------------
+// Setup the object's data member with the selected printer's data
+nsresult nsDeviceContextSpecWin::GetDataFromPrinter(const nsAString& aName,
+ nsIPrintSettings* aPS) {
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsHPRINTER hPrinter = nullptr;
+ const nsString& flat = PromiseFlatString(aName);
+ wchar_t* name =
+ (wchar_t*)flat.get(); // Windows APIs use non-const name argument
+
+ BOOL status = ::OpenPrinterW(name, &hPrinter, nullptr);
+ if (status) {
+ nsAutoPrinter autoPrinter(hPrinter);
+
+ LPDEVMODEW pDevMode;
+
+ // Allocate a buffer of the correct size.
+ LONG needed =
+ ::DocumentPropertiesW(nullptr, hPrinter, name, nullptr, nullptr, 0);
+ if (needed < 0) {
+ PR_PL(
+ ("**** nsDeviceContextSpecWin::GetDataFromPrinter - Couldn't get "
+ "size of DEVMODE using DocumentPropertiesW(pDeviceName = \"%s\"). "
+ "GetLastEror() = %08x\n",
+ NS_ConvertUTF16toUTF8(aName).get(), GetLastError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Some drivers do not return the correct size for their DEVMODE, so we
+ // over-allocate to try and compensate.
+ // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5)
+ needed *= 2;
+ pDevMode =
+ (LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, needed);
+ if (!pDevMode) return NS_ERROR_FAILURE;
+
+ // Get the default DevMode for the printer and modify it for our needs.
+ LONG ret = ::DocumentPropertiesW(nullptr, hPrinter, name, pDevMode, nullptr,
+ DM_OUT_BUFFER);
+
+ if (ret == IDOK && aPS) {
+ nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS);
+ MOZ_ASSERT(psWin);
+ psWin->CopyToNative(pDevMode);
+ // Sets back the changes we made to the DevMode into the Printer Driver
+ ret = ::DocumentPropertiesW(nullptr, hPrinter, name, pDevMode, pDevMode,
+ DM_IN_BUFFER | DM_OUT_BUFFER);
+
+ // We need to copy the final DEVMODE settings back to our print settings,
+ // because they may have been set from invalid prefs.
+ if (ret == IDOK) {
+ // We need to get information from the device as well.
+ nsAutoHDC printerDC(::CreateICW(kDriverName, name, nullptr, pDevMode));
+ if (NS_WARN_IF(!printerDC)) {
+ ::HeapFree(::GetProcessHeap(), 0, pDevMode);
+ return NS_ERROR_FAILURE;
+ }
+
+ psWin->CopyFromNative(printerDC, pDevMode);
+ }
+ }
+
+ if (ret != IDOK) {
+ ::HeapFree(::GetProcessHeap(), 0, pDevMode);
+ PR_PL(
+ ("***** nsDeviceContextSpecWin::GetDataFromPrinter - "
+ "DocumentProperties call failed code: %d/0x%x\n",
+ ret, ret));
+ DISPLAY_LAST_ERROR
+ return NS_ERROR_FAILURE;
+ }
+
+ SetDevMode(
+ pDevMode); // cache the pointer and takes responsibility for the memory
+
+ SetDeviceName(aName);
+
+ SetDriverName(nsDependentString(kDriverName));
+
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_GFX_PRINTER_NAME_NOT_FOUND;
+ PR_PL(
+ ("***** nsDeviceContextSpecWin::GetDataFromPrinter - Couldn't open "
+ "printer: [%s]\n",
+ NS_ConvertUTF16toUTF8(aName).get()));
+ DISPLAY_LAST_ERROR
+ }
+ return rv;
+}
+
+//***********************************************************
+// Printer List
+//***********************************************************
+
+nsPrinterListWin::~nsPrinterListWin() = default;
+
+// Helper to get the array of PRINTER_INFO_4 records from the OS into a
+// caller-supplied byte array; returns the number of records present.
+static unsigned GetPrinterInfo4(nsTArray<BYTE>& aBuffer) {
+ const DWORD kLevel = 4;
+ DWORD needed = 0;
+ DWORD count = 0;
+ const DWORD kFlags = PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS;
+ BOOL ok = ::EnumPrintersW(kFlags,
+ nullptr, // Name
+ kLevel, // Level
+ nullptr, // pPrinterEnum
+ 0, // cbBuf (buffer size)
+ &needed, // Bytes needed in buffer
+ &count);
+ if (needed > 0) {
+ aBuffer.SetLength(needed);
+ ok = ::EnumPrintersW(kFlags, nullptr, kLevel, aBuffer.Elements(),
+ aBuffer.Length(), &needed, &count);
+ }
+ if (!ok || !count) {
+ return 0;
+ }
+ return count;
+}
+
+nsTArray<nsPrinterListBase::PrinterInfo> nsPrinterListWin::Printers() const {
+ PR_PL(("nsPrinterListWin::Printers\n"));
+
+ AutoTArray<BYTE, 1024> buffer;
+ unsigned count = GetPrinterInfo4(buffer);
+
+ if (!count) {
+ PR_PL(("[No printers found]\n"));
+ return {};
+ }
+
+ const auto* printers =
+ reinterpret_cast<const _PRINTER_INFO_4W*>(buffer.Elements());
+ nsTArray<PrinterInfo> list;
+ for (unsigned i = 0; i < count; i++) {
+ // For LOCAL printers, we check whether OpenPrinter succeeds, and omit
+ // them from the list if not. This avoids presenting printers that the
+ // user cannot actually use (e.g. due to Windows permissions).
+ // For NETWORK printers, this check may block for a long time (waiting for
+ // network timeout), so we skip it; if the user tries to access a printer
+ // that isn't available, we'll have to show an error later.
+ // (We always need to be able to handle an error, anyhow, as the printer
+ // could get disconnected after we've created the list, for example.)
+ bool isAvailable = false;
+ if (printers[i].Attributes & PRINTER_ATTRIBUTE_NETWORK) {
+ isAvailable = true;
+ } else if (printers[i].Attributes & PRINTER_ATTRIBUTE_LOCAL) {
+ HANDLE handle;
+ if (::OpenPrinterW(printers[i].pPrinterName, &handle, nullptr)) {
+ ::ClosePrinter(handle);
+ isAvailable = true;
+ }
+ }
+ if (isAvailable) {
+ list.AppendElement(PrinterInfo{nsString(printers[i].pPrinterName)});
+ PR_PL(("Printer Name: %s\n",
+ NS_ConvertUTF16toUTF8(printers[i].pPrinterName).get()));
+ }
+ }
+
+ if (!count) {
+ PR_PL(("[No usable printers found]\n"));
+ return {};
+ }
+
+ return list;
+}
+
+Maybe<nsPrinterListBase::PrinterInfo> nsPrinterListWin::PrinterByName(
+ nsString aName) const {
+ Maybe<PrinterInfo> rv;
+
+ AutoTArray<BYTE, 1024> buffer;
+ unsigned count = GetPrinterInfo4(buffer);
+
+ const auto* printers =
+ reinterpret_cast<const _PRINTER_INFO_4W*>(buffer.Elements());
+ for (unsigned i = 0; i < count; ++i) {
+ if (aName.Equals(nsString(printers[i].pPrinterName))) {
+ rv.emplace(PrinterInfo{aName});
+ break;
+ }
+ }
+
+ return rv;
+}
+
+Maybe<nsPrinterListBase::PrinterInfo> nsPrinterListWin::PrinterBySystemName(
+ nsString aName) const {
+ return PrinterByName(std::move(aName));
+}
+
+RefPtr<nsIPrinter> nsPrinterListWin::CreatePrinter(PrinterInfo aInfo) const {
+ return nsPrinterWin::Create(mCommonPaperInfo, std::move(aInfo.mName));
+}
+
+nsresult nsPrinterListWin::SystemDefaultPrinterName(nsAString& aName) const {
+ if (GetDefaultPrinterName(aName)) {
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsPrinterListWin::InitPrintSettingsFromPrinter(
+ const nsAString& aPrinterName, nsIPrintSettings* aPrintSettings) {
+ NS_ENSURE_ARG_POINTER(aPrintSettings);
+
+ if (aPrinterName.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // When printing to PDF on Windows there is no associated printer driver.
+ int16_t outputFormat;
+ aPrintSettings->GetOutputFormat(&outputFormat);
+ if (outputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ return NS_OK;
+ }
+
+ RefPtr<nsDeviceContextSpecWin> devSpecWin = new nsDeviceContextSpecWin();
+ if (!devSpecWin) return NS_ERROR_OUT_OF_MEMORY;
+
+ // If the settings have already been initialized from prefs then pass these to
+ // GetDataFromPrinter, so that they are saved to the printer.
+ bool initializedFromPrefs;
+ nsresult rv =
+ aPrintSettings->GetIsInitializedFromPrefs(&initializedFromPrefs);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (initializedFromPrefs) {
+ // If we pass in print settings to GetDataFromPrinter it already copies
+ // things back to the settings, so we can return here.
+ return devSpecWin->GetDataFromPrinter(aPrinterName, aPrintSettings);
+ }
+
+ devSpecWin->GetDataFromPrinter(aPrinterName);
+
+ LPDEVMODEW devmode;
+ devSpecWin->GetDevMode(devmode);
+ if (NS_WARN_IF(!devmode)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aPrintSettings->SetPrinterName(aPrinterName);
+
+ // We need to get information from the device as well.
+ const nsString& flat = PromiseFlatString(aPrinterName);
+ char16ptr_t printerName = flat.get();
+ HDC dc = ::CreateICW(kDriverName, printerName, nullptr, devmode);
+ if (NS_WARN_IF(!dc)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPrintSettings);
+ MOZ_ASSERT(psWin);
+ psWin->CopyFromNative(dc, devmode);
+ ::DeleteDC(dc);
+
+ return NS_OK;
+}
diff --git a/widget/windows/nsDeviceContextSpecWin.h b/widget/windows/nsDeviceContextSpecWin.h
new file mode 100644
index 0000000000..df3a6a9492
--- /dev/null
+++ b/widget/windows/nsDeviceContextSpecWin.h
@@ -0,0 +1,109 @@
+/* -*- 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 nsDeviceContextSpecWin_h___
+#define nsDeviceContextSpecWin_h___
+
+#include "nsCOMPtr.h"
+#include "nsIDeviceContextSpec.h"
+#include "nsPrinterListBase.h"
+#include "nsIPrintSettings.h"
+#include <windows.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+
+class nsIFile;
+class nsIWidget;
+
+class nsDeviceContextSpecWin : public nsIDeviceContextSpec {
+ public:
+ nsDeviceContextSpecWin();
+
+ NS_DECL_ISUPPORTS
+
+ already_AddRefed<PrintTarget> MakePrintTarget() final;
+ NS_IMETHOD BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) override {
+ return NS_OK;
+ }
+ NS_IMETHOD EndDocument() override { return NS_OK; }
+ NS_IMETHOD BeginPage() override { return NS_OK; }
+ NS_IMETHOD EndPage() override { return NS_OK; }
+
+ NS_IMETHOD Init(nsIWidget* aWidget, nsIPrintSettings* aPS,
+ bool aIsPrintPreview) override;
+
+ float GetDPI() final;
+
+ float GetPrintingScale() final;
+ gfxPoint GetPrintingTranslate() final;
+
+ void GetDriverName(nsAString& aDriverName) const {
+ aDriverName = mDriverName;
+ }
+ void GetDeviceName(nsAString& aDeviceName) const {
+ aDeviceName = mDeviceName;
+ }
+
+ // The GetDevMode will return a pointer to a DevMode
+ // whether it is from the Global memory handle or just the DevMode
+ // To get the DevMode from the Global memory Handle it must lock it
+ // So this call must be paired with a call to UnlockGlobalHandle
+ void GetDevMode(LPDEVMODEW& aDevMode);
+
+ // helper functions
+ nsresult GetDataFromPrinter(const nsAString& aName,
+ nsIPrintSettings* aPS = nullptr);
+
+ protected:
+ void SetDeviceName(const nsAString& aDeviceName);
+ void SetDriverName(const nsAString& aDriverName);
+ void SetDevMode(LPDEVMODEW aDevMode);
+
+ virtual ~nsDeviceContextSpecWin();
+
+ nsString mDriverName;
+ nsString mDeviceName;
+ LPDEVMODEW mDevMode = nullptr;
+
+ nsCOMPtr<nsIPrintSettings> mPrintSettings;
+ int16_t mOutputFormat = nsIPrintSettings::kOutputFormatNative;
+
+ // A temporary file to create an "anonymous" print target. See bug 1664253,
+ // this should ideally not be needed.
+ nsCOMPtr<nsIFile> mTempFile;
+
+ // This variable is independant of nsIPrintSettings::kOutputFormatPDF.
+ // It controls both whether normal printing is done via PDF using Skia and
+ // whether print-to-PDF uses Skia.
+ bool mPrintViaSkPDF = false;
+};
+
+//-------------------------------------------------------------------------
+// Printer List
+//-------------------------------------------------------------------------
+class nsPrinterListWin final : public nsPrinterListBase {
+ public:
+ NS_IMETHOD InitPrintSettingsFromPrinter(const nsAString&,
+ nsIPrintSettings*) final;
+
+ nsTArray<PrinterInfo> Printers() const final;
+ RefPtr<nsIPrinter> CreatePrinter(PrinterInfo) const final;
+
+ nsPrinterListWin() = default;
+
+ protected:
+ nsresult SystemDefaultPrinterName(nsAString&) const final;
+
+ mozilla::Maybe<PrinterInfo> PrinterByName(nsString) const final;
+ mozilla::Maybe<PrinterInfo> PrinterBySystemName(
+ nsString aPrinterName) const final;
+
+ private:
+ ~nsPrinterListWin();
+};
+
+#endif
diff --git a/widget/windows/nsDragService.cpp b/widget/windows/nsDragService.cpp
new file mode 100644
index 0000000000..39247a033b
--- /dev/null
+++ b/widget/windows/nsDragService.cpp
@@ -0,0 +1,613 @@
+/* -*- 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 <ole2.h>
+#include <oleidl.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+
+// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
+#include <shellapi.h>
+
+#include "mozilla/RefPtr.h"
+#include "nsDragService.h"
+#include "nsITransferable.h"
+#include "nsDataObj.h"
+
+#include "nsWidgetsCID.h"
+#include "nsNativeDragTarget.h"
+#include "nsNativeDragSource.h"
+#include "nsClipboard.h"
+#include "mozilla/dom/Document.h"
+#include "nsDataObjCollection.h"
+
+#include "nsArrayUtils.h"
+#include "nsString.h"
+#include "nsEscape.h"
+#include "nsIScreenManager.h"
+#include "nsToolkit.h"
+#include "nsCRT.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsUnicharUtils.h"
+#include "nsRect.h"
+#include "nsMathUtils.h"
+#include "WinUtils.h"
+#include "KeyboardLayout.h"
+#include "gfxContext.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Tools.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+
+//-------------------------------------------------------------------------
+//
+// DragService constructor
+//
+//-------------------------------------------------------------------------
+nsDragService::nsDragService()
+ : mDataObject(nullptr), mSentLocalDropEvent(false) {}
+
+//-------------------------------------------------------------------------
+//
+// DragService destructor
+//
+//-------------------------------------------------------------------------
+nsDragService::~nsDragService() { NS_IF_RELEASE(mDataObject); }
+
+bool nsDragService::CreateDragImage(nsINode* aDOMNode,
+ const Maybe<CSSIntRegion>& aRegion,
+ SHDRAGIMAGE* psdi) {
+ if (!psdi) return false;
+
+ memset(psdi, 0, sizeof(SHDRAGIMAGE));
+ if (!aDOMNode) return false;
+
+ // Prepare the drag image
+ LayoutDeviceIntRect dragRect;
+ RefPtr<SourceSurface> surface;
+ nsPresContext* pc;
+ DrawDrag(aDOMNode, aRegion, mScreenPosition, &dragRect, &surface, &pc);
+ if (!surface) return false;
+
+ uint32_t bmWidth = dragRect.Width(), bmHeight = dragRect.Height();
+
+ if (bmWidth == 0 || bmHeight == 0) return false;
+
+ psdi->crColorKey = CLR_NONE;
+
+ RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface(
+ IntSize(bmWidth, bmHeight), SurfaceFormat::B8G8R8A8);
+ NS_ENSURE_TRUE(dataSurface, false);
+
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
+ return false;
+ }
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
+ BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride,
+ dataSurface->GetFormat());
+ if (!dt) {
+ dataSurface->Unmap();
+ return false;
+ }
+
+ dt->DrawSurface(
+ surface,
+ Rect(0, 0, dataSurface->GetSize().width, dataSurface->GetSize().height),
+ Rect(0, 0, surface->GetSize().width, surface->GetSize().height),
+ DrawSurfaceOptions(), DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+ dt->Flush();
+
+ BITMAPV5HEADER bmih;
+ memset((void*)&bmih, 0, sizeof(BITMAPV5HEADER));
+ bmih.bV5Size = sizeof(BITMAPV5HEADER);
+ bmih.bV5Width = bmWidth;
+ bmih.bV5Height = -(int32_t)bmHeight; // flip vertical
+ bmih.bV5Planes = 1;
+ bmih.bV5BitCount = 32;
+ bmih.bV5Compression = BI_BITFIELDS;
+ bmih.bV5RedMask = 0x00FF0000;
+ bmih.bV5GreenMask = 0x0000FF00;
+ bmih.bV5BlueMask = 0x000000FF;
+ bmih.bV5AlphaMask = 0xFF000000;
+
+ HDC hdcSrc = CreateCompatibleDC(nullptr);
+ void* lpBits = nullptr;
+ if (hdcSrc) {
+ psdi->hbmpDragImage =
+ ::CreateDIBSection(hdcSrc, (BITMAPINFO*)&bmih, DIB_RGB_COLORS,
+ (void**)&lpBits, nullptr, 0);
+ if (psdi->hbmpDragImage && lpBits) {
+ CopySurfaceDataToPackedArray(map.mData, static_cast<uint8_t*>(lpBits),
+ dataSurface->GetSize(), map.mStride,
+ BytesPerPixel(dataSurface->GetFormat()));
+ }
+
+ psdi->sizeDragImage.cx = bmWidth;
+ psdi->sizeDragImage.cy = bmHeight;
+
+ LayoutDeviceIntPoint screenPoint =
+ ConvertToUnscaledDevPixels(pc, mScreenPosition);
+ psdi->ptOffset.x = screenPoint.x - dragRect.X();
+ psdi->ptOffset.y = screenPoint.y - dragRect.Y();
+
+ DeleteDC(hdcSrc);
+ }
+
+ dataSurface->Unmap();
+
+ return psdi->hbmpDragImage != nullptr;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsDragService::InvokeDragSessionImpl(
+ nsIArray* anArrayTransferables, const Maybe<CSSIntRegion>& aRegion,
+ uint32_t aActionType) {
+ // Try and get source URI of the items that are being dragged
+ nsIURI* uri = nullptr;
+
+ RefPtr<dom::Document> doc(mSourceDocument);
+ if (doc) {
+ uri = doc->GetDocumentURI();
+ }
+
+ uint32_t numItemsToDrag = 0;
+ nsresult rv = anArrayTransferables->GetLength(&numItemsToDrag);
+ if (!numItemsToDrag) return NS_ERROR_FAILURE;
+
+ // The clipboard class contains some static utility methods that we
+ // can use to create an IDataObject from the transferable
+
+ // if we're dragging more than one item, we need to create a
+ // "collection" object to fake out the OS. This collection contains
+ // one |IDataObject| for each transferable. If there is just the one
+ // (most cases), only pass around the native |IDataObject|.
+ RefPtr<IDataObject> itemToDrag;
+ if (numItemsToDrag > 1) {
+ nsDataObjCollection* dataObjCollection = new nsDataObjCollection();
+ if (!dataObjCollection) return NS_ERROR_OUT_OF_MEMORY;
+ itemToDrag = dataObjCollection;
+ for (uint32_t i = 0; i < numItemsToDrag; ++i) {
+ nsCOMPtr<nsITransferable> trans =
+ do_QueryElementAt(anArrayTransferables, i);
+ if (trans) {
+ RefPtr<IDataObject> dataObj;
+ rv = nsClipboard::CreateNativeDataObject(trans, getter_AddRefs(dataObj),
+ uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Add the flavors to the collection object too
+ rv = nsClipboard::SetupNativeDataObject(trans, dataObjCollection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dataObjCollection->AddDataObject(dataObj);
+ }
+ }
+ } // if dragging multiple items
+ else {
+ nsCOMPtr<nsITransferable> trans =
+ do_QueryElementAt(anArrayTransferables, 0);
+ if (trans) {
+ rv = nsClipboard::CreateNativeDataObject(trans,
+ getter_AddRefs(itemToDrag), uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } // else dragging a single object
+
+ // Create a drag image if support is available
+ IDragSourceHelper* pdsh;
+ if (SUCCEEDED(CoCreateInstance(CLSID_DragDropHelper, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IDragSourceHelper,
+ (void**)&pdsh))) {
+ SHDRAGIMAGE sdi;
+ if (CreateDragImage(mSourceNode, aRegion, &sdi)) {
+ if (FAILED(pdsh->InitializeFromBitmap(&sdi, itemToDrag)))
+ DeleteObject(sdi.hbmpDragImage);
+ }
+ pdsh->Release();
+ }
+
+ // Kick off the native drag session
+ return StartInvokingDragSession(itemToDrag, aActionType);
+}
+
+static bool LayoutDevicePointToCSSPoint(const LayoutDevicePoint& aDevPos,
+ CSSPoint& aCSSPos) {
+ nsCOMPtr<nsIScreenManager> screenMgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (!screenMgr) {
+ return false;
+ }
+
+ nsCOMPtr<nsIScreen> screen;
+ screenMgr->ScreenForRect(NSToIntRound(aDevPos.x), NSToIntRound(aDevPos.y), 1,
+ 1, getter_AddRefs(screen));
+ if (!screen) {
+ return false;
+ }
+
+ int32_t w, h; // unused
+ LayoutDeviceIntPoint screenOriginDev;
+ screen->GetRect(&screenOriginDev.x, &screenOriginDev.y, &w, &h);
+
+ double scale;
+ screen->GetDefaultCSSScaleFactor(&scale);
+ LayoutDeviceToCSSScale devToCSSScale =
+ CSSToLayoutDeviceScale(scale).Inverse();
+
+ // Desktop pixels and CSS pixels share the same screen origin.
+ CSSIntPoint screenOriginCSS;
+ screen->GetRectDisplayPix(&screenOriginCSS.x, &screenOriginCSS.y, &w, &h);
+
+ aCSSPos = (aDevPos - screenOriginDev) * devToCSSScale + screenOriginCSS;
+ return true;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsDragService::StartInvokingDragSession(IDataObject* aDataObj,
+ uint32_t aActionType) {
+ // To do the drag we need to create an object that
+ // implements the IDataObject interface (for OLE)
+ RefPtr<nsNativeDragSource> nativeDragSrc =
+ new nsNativeDragSource(mDataTransfer);
+
+ // Now figure out what the native drag effect should be
+ DWORD winDropRes;
+ DWORD effects = DROPEFFECT_SCROLL;
+ if (aActionType & DRAGDROP_ACTION_COPY) {
+ effects |= DROPEFFECT_COPY;
+ }
+ if (aActionType & DRAGDROP_ACTION_MOVE) {
+ effects |= DROPEFFECT_MOVE;
+ }
+ if (aActionType & DRAGDROP_ACTION_LINK) {
+ effects |= DROPEFFECT_LINK;
+ }
+
+ // XXX not sure why we bother to cache this, it can change during
+ // the drag
+ mDragAction = aActionType;
+ mSentLocalDropEvent = false;
+
+ // Start dragging
+ StartDragSession();
+ OpenDragPopup();
+
+ RefPtr<IDataObjectAsyncCapability> pAsyncOp;
+ // Offer to do an async drag
+ if (SUCCEEDED(aDataObj->QueryInterface(IID_IDataObjectAsyncCapability,
+ getter_AddRefs(pAsyncOp)))) {
+ pAsyncOp->SetAsyncMode(VARIANT_TRUE);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("When did our data object stop being async");
+ }
+
+ // Call the native D&D method
+ HRESULT res = ::DoDragDrop(aDataObj, nativeDragSrc, effects, &winDropRes);
+
+ // In cases where the drop operation completed outside the application,
+ // update the source node's DataTransfer dropEffect value so it is up to date.
+ if (!mSentLocalDropEvent) {
+ uint32_t dropResult;
+ // Order is important, since multiple flags can be returned.
+ if (winDropRes & DROPEFFECT_COPY)
+ dropResult = DRAGDROP_ACTION_COPY;
+ else if (winDropRes & DROPEFFECT_LINK)
+ dropResult = DRAGDROP_ACTION_LINK;
+ else if (winDropRes & DROPEFFECT_MOVE)
+ dropResult = DRAGDROP_ACTION_MOVE;
+ else
+ dropResult = DRAGDROP_ACTION_NONE;
+
+ if (mDataTransfer) {
+ if (res == DRAGDROP_S_DROP) // Success
+ mDataTransfer->SetDropEffectInt(dropResult);
+ else
+ mDataTransfer->SetDropEffectInt(DRAGDROP_ACTION_NONE);
+ }
+ }
+
+ mUserCancelled = nativeDragSrc->UserCancelled();
+
+ // We're done dragging, get the cursor position and end the drag
+ // Use GetMessagePos to get the position of the mouse at the last message
+ // seen by the event loop. (Bug 489729)
+ // Note that we must convert this from device pixels back to Windows logical
+ // pixels (bug 818927).
+ DWORD pos = ::GetMessagePos();
+ CSSPoint cssPos;
+ if (!LayoutDevicePointToCSSPoint(
+ LayoutDevicePoint(GET_X_LPARAM(pos), GET_Y_LPARAM(pos)), cssPos)) {
+ // fallback to the simple scaling
+ POINT pt = {GET_X_LPARAM(pos), GET_Y_LPARAM(pos)};
+ HMONITOR monitor = ::MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY);
+ double dpiScale = widget::WinUtils::LogToPhysFactor(monitor);
+ cssPos.x = GET_X_LPARAM(pos) / dpiScale;
+ cssPos.y = GET_Y_LPARAM(pos) / dpiScale;
+ }
+ // We have to abuse SetDragEndPoint to pass CSS pixels because
+ // Event::GetScreenCoords will not convert pixels for dragend events
+ // until bug 1224754 is fixed.
+ SetDragEndPoint(
+ LayoutDeviceIntPoint(NSToIntRound(cssPos.x), NSToIntRound(cssPos.y)));
+ ModifierKeyState modifierKeyState;
+ EndDragSession(true, modifierKeyState.GetModifiers());
+
+ mDoingDrag = false;
+
+ return DRAGDROP_S_DROP == res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+//-------------------------------------------------------------------------
+// Make Sure we have the right kind of object
+nsDataObjCollection* nsDragService::GetDataObjCollection(
+ IDataObject* aDataObj) {
+ nsDataObjCollection* dataObjCol = nullptr;
+ if (aDataObj) {
+ nsIDataObjCollection* dataObj;
+ if (aDataObj->QueryInterface(IID_IDataObjCollection, (void**)&dataObj) ==
+ S_OK) {
+ dataObjCol = static_cast<nsDataObjCollection*>(aDataObj);
+ dataObj->Release();
+ }
+ }
+
+ return dataObjCol;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsDragService::GetNumDropItems(uint32_t* aNumItems) {
+ if (!mDataObject) {
+ *aNumItems = 0;
+ return NS_OK;
+ }
+
+ if (IsCollectionObject(mDataObject)) {
+ nsDataObjCollection* dataObjCol = GetDataObjCollection(mDataObject);
+ if (dataObjCol) {
+ *aNumItems = dataObjCol->GetNumDataObjects();
+ } else {
+ // If the count cannot be determined just return 0.
+ // This can happen if we have collection data of type
+ // MULTI_MIME ("Mozilla/IDataObjectCollectionFormat") on the clipboard
+ // from another process but we can't obtain an IID_IDataObjCollection
+ // from this process.
+ *aNumItems = 0;
+ }
+ } else {
+ // Next check if we have a file drop. Return the number of files in
+ // the file drop as the number of items we have, pretending like we
+ // actually have > 1 drag item.
+ FORMATETC fe2;
+ SET_FORMATETC(fe2, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (mDataObject->QueryGetData(&fe2) == S_OK) {
+ STGMEDIUM stm;
+ if (mDataObject->GetData(&fe2, &stm) == S_OK) {
+ HDROP hdrop = (HDROP)GlobalLock(stm.hGlobal);
+ *aNumItems = ::DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0);
+ ::GlobalUnlock(stm.hGlobal);
+ ::ReleaseStgMedium(&stm);
+ // Data may be provided later, so assume we have 1 item
+ if (*aNumItems == 0) *aNumItems = 1;
+ } else
+ *aNumItems = 1;
+ } else
+ *aNumItems = 1;
+ }
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsDragService::GetData(nsITransferable* aTransferable, uint32_t anItem) {
+ // This typcially happens on a drop, the target would be asking
+ // for it's transferable to be filled in
+ // Use a static clipboard utility method for this
+ if (!mDataObject) return NS_ERROR_FAILURE;
+
+ nsresult dataFound = NS_ERROR_FAILURE;
+
+ if (IsCollectionObject(mDataObject)) {
+ // multiple items, use |anItem| as an index into our collection
+ nsDataObjCollection* dataObjCol = GetDataObjCollection(mDataObject);
+ uint32_t cnt = dataObjCol->GetNumDataObjects();
+ if (anItem < cnt) {
+ IDataObject* dataObj = dataObjCol->GetDataObjectAt(anItem);
+ dataFound = nsClipboard::GetDataFromDataObject(dataObj, 0, nullptr,
+ aTransferable);
+ } else
+ NS_WARNING("Index out of range!");
+ } else {
+ // If they are asking for item "0", we can just get it...
+ if (anItem == 0) {
+ dataFound = nsClipboard::GetDataFromDataObject(mDataObject, anItem,
+ nullptr, aTransferable);
+ } else {
+ // It better be a file drop, or else non-zero indexes are invalid!
+ FORMATETC fe2;
+ SET_FORMATETC(fe2, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (mDataObject->QueryGetData(&fe2) == S_OK)
+ dataFound = nsClipboard::GetDataFromDataObject(mDataObject, anItem,
+ nullptr, aTransferable);
+ else
+ NS_WARNING(
+ "Reqesting non-zero index, but clipboard data is not a "
+ "collection!");
+ }
+ }
+ return dataFound;
+}
+
+//---------------------------------------------------------
+NS_IMETHODIMP
+nsDragService::SetIDataObject(IDataObject* aDataObj) {
+ // When the native drag starts the DragService gets
+ // the IDataObject that is being dragged
+ NS_IF_RELEASE(mDataObject);
+ mDataObject = aDataObj;
+ NS_IF_ADDREF(mDataObject);
+
+ return NS_OK;
+}
+
+//---------------------------------------------------------
+void nsDragService::SetDroppedLocal() {
+ // Sent from the native drag handler, letting us know
+ // a drop occurred within the application vs. outside of it.
+ mSentLocalDropEvent = true;
+ return;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
+ if (!aDataFlavor || !mDataObject || !_retval) return NS_ERROR_FAILURE;
+
+#ifdef DEBUG
+ if (strcmp(aDataFlavor, kTextMime) == 0)
+ NS_WARNING(
+ "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode "
+ "INSTEAD");
+#endif
+
+ *_retval = false;
+
+ FORMATETC fe;
+ UINT format = 0;
+
+ if (IsCollectionObject(mDataObject)) {
+ // We know we have one of our special collection objects.
+ format = nsClipboard::GetFormat(aDataFlavor);
+ SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1,
+ TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI);
+
+ // See if any one of the IDataObjects in the collection supports
+ // this data type
+ nsDataObjCollection* dataObjCol = GetDataObjCollection(mDataObject);
+ if (dataObjCol) {
+ uint32_t cnt = dataObjCol->GetNumDataObjects();
+ for (uint32_t i = 0; i < cnt; ++i) {
+ IDataObject* dataObj = dataObjCol->GetDataObjectAt(i);
+ if (S_OK == dataObj->QueryGetData(&fe)) *_retval = true; // found it!
+ }
+ }
+ } // if special collection object
+ else {
+ // Ok, so we have a single object. Check to see if has the correct
+ // data type. Since this can come from an outside app, we also
+ // need to see if we need to perform text->unicode conversion if
+ // the client asked for unicode and it wasn't available.
+ format = nsClipboard::GetFormat(aDataFlavor);
+ SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1,
+ TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI);
+ if (mDataObject->QueryGetData(&fe) == S_OK)
+ *_retval = true; // found it!
+ else {
+ // We haven't found the exact flavor the client asked for, but
+ // maybe we can still find it from something else that's on the
+ // clipboard
+ if (strcmp(aDataFlavor, kUnicodeMime) == 0) {
+ // client asked for unicode and it wasn't present, check if we
+ // have CF_TEXT. We'll handle the actual data substitution in
+ // the data object.
+ format = nsClipboard::GetFormat(kTextMime);
+ SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1,
+ TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI);
+ if (mDataObject->QueryGetData(&fe) == S_OK)
+ *_retval = true; // found it!
+ } else if (strcmp(aDataFlavor, kURLMime) == 0) {
+ // client asked for a url and it wasn't present, but if we
+ // have a file, then we have a URL to give them (the path, or
+ // the internal URL if an InternetShortcut).
+ format = nsClipboard::GetFormat(kFileMime);
+ SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1,
+ TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI);
+ if (mDataObject->QueryGetData(&fe) == S_OK)
+ *_retval = true; // found it!
+ }
+ } // else try again
+ }
+
+ return NS_OK;
+}
+
+//
+// IsCollectionObject
+//
+// Determine if this is a single |IDataObject| or one of our private
+// collection objects. We know the difference because our collection
+// object will respond to supporting the private |MULTI_MIME| format.
+//
+bool nsDragService::IsCollectionObject(IDataObject* inDataObj) {
+ bool isCollection = false;
+
+ // setup the format object to ask for the MULTI_MIME format. We only
+ // need to do this once
+ static UINT sFormat = 0;
+ static FORMATETC sFE;
+ if (!sFormat) {
+ sFormat = nsClipboard::GetFormat(MULTI_MIME);
+ SET_FORMATETC(sFE, sFormat, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ }
+
+ // ask the object if it supports it. If yes, we have a collection
+ // object
+ if (inDataObj->QueryGetData(&sFE) == S_OK) isCollection = true;
+
+ return isCollection;
+
+} // IsCollectionObject
+
+//
+// EndDragSession
+//
+// Override the default to make sure that we release the data object
+// when the drag ends. It seems that OLE doesn't like to let apps quit
+// w/out crashing when we're still holding onto their data
+//
+NS_IMETHODIMP
+nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
+ // Bug 100180: If we've got mouse events captured, make sure we release it -
+ // that way, if we happen to call EndDragSession before diving into a nested
+ // event loop, we can still respond to mouse events.
+ if (::GetCapture()) {
+ ::ReleaseCapture();
+ }
+
+ nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
+ NS_IF_RELEASE(mDataObject);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDragService::UpdateDragImage(nsINode* aImage, int32_t aImageX,
+ int32_t aImageY) {
+ if (!mDataObject) {
+ return NS_OK;
+ }
+
+ nsBaseDragService::UpdateDragImage(aImage, aImageX, aImageY);
+
+ IDragSourceHelper* pdsh;
+ if (SUCCEEDED(CoCreateInstance(CLSID_DragDropHelper, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IDragSourceHelper,
+ (void**)&pdsh))) {
+ SHDRAGIMAGE sdi;
+ if (CreateDragImage(mSourceNode, Nothing(), &sdi)) {
+ nsNativeDragTarget::DragImageChanged();
+ if (FAILED(pdsh->InitializeFromBitmap(&sdi, mDataObject)))
+ DeleteObject(sdi.hbmpDragImage);
+ }
+ pdsh->Release();
+ }
+
+ return NS_OK;
+}
diff --git a/widget/windows/nsDragService.h b/widget/windows/nsDragService.h
new file mode 100644
index 0000000000..5a86a74f76
--- /dev/null
+++ b/widget/windows/nsDragService.h
@@ -0,0 +1,67 @@
+/* -*- 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 <windows.h>
+#include <shlobj.h>
+
+struct IDataObject;
+class nsDataObjCollection;
+
+/**
+ * Native Win32 DragService wrapper
+ */
+
+class nsDragService : public nsBaseDragService {
+ public:
+ nsDragService();
+ virtual ~nsDragService();
+
+ // nsBaseDragService
+ MOZ_CAN_RUN_SCRIPT virtual nsresult InvokeDragSessionImpl(
+ nsIArray* anArrayTransferables,
+ const mozilla::Maybe<mozilla::CSSIntRegion>& aRegion,
+ uint32_t aActionType);
+
+ // nsIDragSession
+ NS_IMETHOD GetData(nsITransferable* aTransferable, uint32_t anItem) override;
+ NS_IMETHOD GetNumDropItems(uint32_t* aNumItems) override;
+ NS_IMETHOD IsDataFlavorSupported(const char* aDataFlavor,
+ bool* _retval) override;
+ MOZ_CAN_RUN_SCRIPT NS_IMETHOD EndDragSession(bool aDoneDrag,
+ uint32_t aKeyModifiers) override;
+ NS_IMETHOD UpdateDragImage(nsINode* aImage, int32_t aImageX,
+ int32_t aImageY) override;
+
+ // native impl.
+ NS_IMETHOD SetIDataObject(IDataObject* aDataObj);
+ MOZ_CAN_RUN_SCRIPT nsresult StartInvokingDragSession(IDataObject* aDataObj,
+ uint32_t aActionType);
+
+ // A drop occurred within the application vs. outside of it.
+ void SetDroppedLocal();
+
+ IDataObject* GetDataObject() { return mDataObject; }
+
+ protected:
+ nsDataObjCollection* GetDataObjCollection(IDataObject* aDataObj);
+
+ // determine if we have a single data object or one of our private
+ // collections
+ bool IsCollectionObject(IDataObject* inDataObj);
+
+ // Create a bitmap for drag operations
+ bool CreateDragImage(nsINode* aDOMNode,
+ const mozilla::Maybe<mozilla::CSSIntRegion>& aRegion,
+ SHDRAGIMAGE* psdi);
+
+ IDataObject* mDataObject;
+ bool mSentLocalDropEvent;
+};
+
+#endif // nsDragService_h__
diff --git a/widget/windows/nsFilePicker.cpp b/widget/windows/nsFilePicker.cpp
new file mode 100644
index 0000000000..9d6e4d79c4
--- /dev/null
+++ b/widget/windows/nsFilePicker.cpp
@@ -0,0 +1,622 @@
+/* -*- 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 "nsFilePicker.h"
+
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <cderr.h>
+
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/mscom/EnsureMTA.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsReadableUtils.h"
+#include "nsNetUtil.h"
+#include "nsWindow.h"
+#include "nsEnumeratorUtils.h"
+#include "nsCRT.h"
+#include "nsString.h"
+#include "nsToolkit.h"
+#include "WinUtils.h"
+#include "nsPIDOMWindow.h"
+#include "GeckoProfiler.h"
+
+using mozilla::IsWin8OrLater;
+using mozilla::MakeUnique;
+using mozilla::UniquePtr;
+using mozilla::mscom::EnsureMTA;
+
+using namespace mozilla::widget;
+
+UniquePtr<char16_t[], nsFilePicker::FreeDeleter>
+ nsFilePicker::sLastUsedUnicodeDirectory;
+
+#define MAX_EXTENSION_LENGTH 10
+#define FILE_BUFFER_SIZE 4096
+
+typedef DWORD FILEOPENDIALOGOPTIONS;
+
+///////////////////////////////////////////////////////////////////////////////
+// Helper classes
+
+// Manages NS_NATIVE_TMP_WINDOW child windows. NS_NATIVE_TMP_WINDOWs are
+// temporary child windows of mParentWidget created to address RTL issues
+// in picker dialogs. We are responsible for destroying these.
+class AutoDestroyTmpWindow {
+ public:
+ explicit AutoDestroyTmpWindow(HWND aTmpWnd) : mWnd(aTmpWnd) {}
+
+ ~AutoDestroyTmpWindow() {
+ if (mWnd) DestroyWindow(mWnd);
+ }
+
+ inline HWND get() const { return mWnd; }
+
+ private:
+ HWND mWnd;
+};
+
+// Manages matching PickerOpen/PickerClosed calls on the parent widget.
+class AutoWidgetPickerState {
+ public:
+ explicit AutoWidgetPickerState(nsIWidget* aWidget)
+ : mWindow(static_cast<nsWindow*>(aWidget)) {
+ PickerState(true);
+ }
+
+ ~AutoWidgetPickerState() { PickerState(false); }
+
+ private:
+ void PickerState(bool aFlag) {
+ if (mWindow) {
+ if (aFlag)
+ mWindow->PickerOpen();
+ else
+ mWindow->PickerClosed();
+ }
+ }
+ RefPtr<nsWindow> mWindow;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIFilePicker
+
+nsFilePicker::nsFilePicker() : mSelectedType(1) {}
+
+NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
+
+NS_IMETHODIMP nsFilePicker::Init(mozIDOMWindowProxy* aParent,
+ const nsAString& aTitle, int16_t aMode) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aParent);
+ nsIDocShell* docShell = window ? window->GetDocShell() : nullptr;
+ mLoadContext = do_QueryInterface(docShell);
+
+ return nsBaseFilePicker::Init(aParent, aTitle, aMode);
+}
+
+/*
+ * Folder picker invocation
+ */
+
+/*
+ * Show a folder picker.
+ *
+ * @param aInitialDir The initial directory, the last used directory will be
+ * used if left blank.
+ * @return true if a file was selected successfully.
+ */
+bool nsFilePicker::ShowFolderPicker(const nsString& aInitialDir) {
+ if (!IsWin8OrLater()) {
+ // Some Windows 7 users are experiencing a race condition when some dlls
+ // that are loaded by the file picker cause a crash while attempting to shut
+ // down the COM multithreaded apartment. By instantiating EnsureMTA, we hold
+ // an additional reference to the MTA that should prevent this race, since
+ // the MTA will remain alive until shutdown.
+ EnsureMTA ensureMTA;
+ }
+
+ RefPtr<IFileOpenDialog> dialog;
+ if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IFileOpenDialog,
+ getter_AddRefs(dialog)))) {
+ return false;
+ }
+
+ // options
+ FILEOPENDIALOGOPTIONS fos = FOS_PICKFOLDERS;
+ HRESULT hr = dialog->SetOptions(fos);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ // initial strings
+ hr = dialog->SetTitle(mTitle.get());
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ if (!mOkButtonLabel.IsEmpty()) {
+ hr = dialog->SetOkButtonLabel(mOkButtonLabel.get());
+ if (FAILED(hr)) {
+ return false;
+ }
+ }
+
+ if (!aInitialDir.IsEmpty()) {
+ RefPtr<IShellItem> folder;
+ if (SUCCEEDED(SHCreateItemFromParsingName(aInitialDir.get(), nullptr,
+ IID_IShellItem,
+ getter_AddRefs(folder)))) {
+ hr = dialog->SetFolder(folder);
+ if (FAILED(hr)) {
+ return false;
+ }
+ }
+ }
+
+ AutoDestroyTmpWindow adtw((HWND)(
+ mParentWidget.get() ? mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW)
+ : nullptr));
+
+ // display
+ mozilla::BackgroundHangMonitor().NotifyWait();
+ RefPtr<IShellItem> item;
+ if (FAILED(dialog->Show(adtw.get())) ||
+ FAILED(dialog->GetResult(getter_AddRefs(item))) || !item) {
+ return false;
+ }
+
+ // results
+
+ // If the user chose a Win7 Library, resolve to the library's
+ // default save folder.
+ RefPtr<IShellItem> folderPath;
+ RefPtr<IShellLibrary> shellLib;
+ if (FAILED(CoCreateInstance(CLSID_ShellLibrary, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IShellLibrary, getter_AddRefs(shellLib)))) {
+ return false;
+ }
+
+ if (shellLib && SUCCEEDED(shellLib->LoadLibraryFromItem(item, STGM_READ)) &&
+ SUCCEEDED(shellLib->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem,
+ getter_AddRefs(folderPath)))) {
+ item.swap(folderPath);
+ }
+
+ // get the folder's file system path
+ return WinUtils::GetShellItemPath(item, mUnicodeFile);
+}
+
+/*
+ * File open and save picker invocation
+ */
+
+/*
+ * Show a file picker.
+ *
+ * @param aInitialDir The initial directory, the last used directory will be
+ * used if left blank.
+ * @return true if a file was selected successfully.
+ */
+bool nsFilePicker::ShowFilePicker(const nsString& aInitialDir) {
+ AUTO_PROFILER_LABEL("nsFilePicker::ShowFilePicker", OTHER);
+
+ if (!IsWin8OrLater()) {
+ // Some Windows 7 users are experiencing a race condition when some dlls
+ // that are loaded by the file picker cause a crash while attempting to shut
+ // down the COM multithreaded apartment. By instantiating EnsureMTA, we hold
+ // an additional reference to the MTA that should prevent this race, since
+ // the MTA will remain alive until shutdown.
+ EnsureMTA ensureMTA;
+ }
+
+ RefPtr<IFileDialog> dialog;
+ if (mMode != modeSave) {
+ if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IFileOpenDialog,
+ getter_AddRefs(dialog)))) {
+ return false;
+ }
+ } else {
+ if (FAILED(CoCreateInstance(CLSID_FileSaveDialog, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IFileSaveDialog,
+ getter_AddRefs(dialog)))) {
+ return false;
+ }
+ }
+
+ // options
+
+ FILEOPENDIALOGOPTIONS fos = 0;
+ fos |= FOS_SHAREAWARE | FOS_OVERWRITEPROMPT | FOS_FORCEFILESYSTEM;
+
+ // Handle add to recent docs settings
+ if (IsPrivacyModeEnabled() || !mAddToRecentDocs) {
+ fos |= FOS_DONTADDTORECENT;
+ }
+
+ // mode specific
+ switch (mMode) {
+ case modeOpen:
+ fos |= FOS_FILEMUSTEXIST;
+ break;
+
+ case modeOpenMultiple:
+ fos |= FOS_FILEMUSTEXIST | FOS_ALLOWMULTISELECT;
+ break;
+
+ case modeSave:
+ fos |= FOS_NOREADONLYRETURN;
+ // Don't follow shortcuts when saving a shortcut, this can be used
+ // to trick users (bug 271732)
+ if (IsDefaultPathLink()) fos |= FOS_NODEREFERENCELINKS;
+ break;
+ }
+
+ HRESULT hr = dialog->SetOptions(fos);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ // initial strings
+
+ // title
+ hr = dialog->SetTitle(mTitle.get());
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ // default filename
+ if (!mDefaultFilename.IsEmpty()) {
+ hr = dialog->SetFileName(mDefaultFilename.get());
+ if (FAILED(hr)) {
+ return false;
+ }
+ }
+
+ // default extension to append to new files
+ if (!mDefaultExtension.IsEmpty()) {
+ hr = dialog->SetDefaultExtension(mDefaultExtension.get());
+ if (FAILED(hr)) {
+ return false;
+ }
+ } else if (IsDefaultPathHtml()) {
+ hr = dialog->SetDefaultExtension(L"html");
+ if (FAILED(hr)) {
+ return false;
+ }
+ }
+
+ // initial location
+ if (!aInitialDir.IsEmpty()) {
+ RefPtr<IShellItem> folder;
+ if (SUCCEEDED(SHCreateItemFromParsingName(aInitialDir.get(), nullptr,
+ IID_IShellItem,
+ getter_AddRefs(folder)))) {
+ hr = dialog->SetFolder(folder);
+ if (FAILED(hr)) {
+ return false;
+ }
+ }
+ }
+
+ // filter types and the default index
+ if (!mComFilterList.IsEmpty()) {
+ hr = dialog->SetFileTypes(mComFilterList.Length(), mComFilterList.get());
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ hr = dialog->SetFileTypeIndex(mSelectedType);
+ if (FAILED(hr)) {
+ return false;
+ }
+ }
+
+ // display
+
+ {
+ AutoDestroyTmpWindow adtw((HWND)(
+ mParentWidget.get() ? mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW)
+ : nullptr));
+ AutoWidgetPickerState awps(mParentWidget);
+
+ mozilla::BackgroundHangMonitor().NotifyWait();
+ if (FAILED(dialog->Show(adtw.get()))) {
+ return false;
+ }
+ }
+
+ // results
+
+ // Remember what filter type the user selected
+ UINT filterIdxResult;
+ if (SUCCEEDED(dialog->GetFileTypeIndex(&filterIdxResult))) {
+ mSelectedType = (int16_t)filterIdxResult;
+ }
+
+ // single selection
+ if (mMode != modeOpenMultiple) {
+ RefPtr<IShellItem> item;
+ if (FAILED(dialog->GetResult(getter_AddRefs(item))) || !item) return false;
+ return WinUtils::GetShellItemPath(item, mUnicodeFile);
+ }
+
+ // multiple selection
+ RefPtr<IFileOpenDialog> openDlg;
+ dialog->QueryInterface(IID_IFileOpenDialog, getter_AddRefs(openDlg));
+ if (!openDlg) {
+ // should not happen
+ return false;
+ }
+
+ RefPtr<IShellItemArray> items;
+ if (FAILED(openDlg->GetResults(getter_AddRefs(items))) || !items) {
+ return false;
+ }
+
+ DWORD count = 0;
+ items->GetCount(&count);
+ for (unsigned int idx = 0; idx < count; idx++) {
+ RefPtr<IShellItem> item;
+ nsAutoString str;
+ if (SUCCEEDED(items->GetItemAt(idx, getter_AddRefs(item)))) {
+ if (!WinUtils::GetShellItemPath(item, str)) continue;
+ nsCOMPtr<nsIFile> file;
+ if (NS_SUCCEEDED(NS_NewLocalFile(str, false, getter_AddRefs(file)))) {
+ mFiles.AppendObject(file);
+ }
+ }
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIFilePicker impl.
+
+nsresult nsFilePicker::ShowW(int16_t* aReturnVal) {
+ NS_ENSURE_ARG_POINTER(aReturnVal);
+
+ *aReturnVal = returnCancel;
+
+ nsAutoString initialDir;
+ if (mDisplayDirectory) mDisplayDirectory->GetPath(initialDir);
+
+ // If no display directory, re-use the last one.
+ if (initialDir.IsEmpty()) {
+ // Allocate copy of last used dir.
+ initialDir = sLastUsedUnicodeDirectory.get();
+ }
+
+ // Clear previous file selections
+ mUnicodeFile.Truncate();
+ mFiles.Clear();
+
+ // On Win10, the picker doesn't support per-monitor DPI, so we open it
+ // with our context set temporarily to system-dpi-aware
+ WinUtils::AutoSystemDpiAware dpiAwareness;
+
+ bool result = false;
+ if (mMode == modeGetFolder) {
+ result = ShowFolderPicker(initialDir);
+ } else {
+ result = ShowFilePicker(initialDir);
+ }
+
+ // exit, and return returnCancel in aReturnVal
+ if (!result) return NS_OK;
+
+ RememberLastUsedDirectory();
+
+ int16_t retValue = returnOK;
+ if (mMode == modeSave) {
+ // Windows does not return resultReplace, we must check if file
+ // already exists.
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_NewLocalFile(mUnicodeFile, false, getter_AddRefs(file));
+
+ bool flag = false;
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(file->Exists(&flag)) && flag) {
+ retValue = returnReplace;
+ }
+ }
+
+ *aReturnVal = retValue;
+ return NS_OK;
+}
+
+nsresult nsFilePicker::Show(int16_t* aReturnVal) { return ShowW(aReturnVal); }
+
+NS_IMETHODIMP
+nsFilePicker::GetFile(nsIFile** aFile) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ *aFile = nullptr;
+
+ if (mUnicodeFile.IsEmpty()) return NS_OK;
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_NewLocalFile(mUnicodeFile, false, getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ file.forget(aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFileURL(nsIURI** aFileURL) {
+ *aFileURL = nullptr;
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetFile(getter_AddRefs(file));
+ if (!file) return rv;
+
+ return NS_NewFileURI(aFileURL, file);
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFiles(nsISimpleEnumerator** aFiles) {
+ NS_ENSURE_ARG_POINTER(aFiles);
+ return NS_NewArrayEnumerator(aFiles, mFiles, NS_GET_IID(nsIFile));
+}
+
+// Get the file + path
+NS_IMETHODIMP
+nsBaseWinFilePicker::SetDefaultString(const nsAString& aString) {
+ mDefaultFilePath = aString;
+
+ // First, make sure the file name is not too long.
+ int32_t nameLength;
+ int32_t nameIndex = mDefaultFilePath.RFind("\\");
+ if (nameIndex == kNotFound)
+ nameIndex = 0;
+ else
+ nameIndex++;
+ nameLength = mDefaultFilePath.Length() - nameIndex;
+ mDefaultFilename.Assign(Substring(mDefaultFilePath, nameIndex));
+
+ if (nameLength > MAX_PATH) {
+ int32_t extIndex = mDefaultFilePath.RFind(".");
+ if (extIndex == kNotFound) extIndex = mDefaultFilePath.Length();
+
+ // Let's try to shave the needed characters from the name part.
+ int32_t charsToRemove = nameLength - MAX_PATH;
+ if (extIndex - nameIndex >= charsToRemove) {
+ mDefaultFilePath.Cut(extIndex - charsToRemove, charsToRemove);
+ }
+ }
+
+ // Then, we need to replace illegal characters. At this stage, we cannot
+ // replace the backslash as the string might represent a file path.
+ mDefaultFilePath.ReplaceChar(FILE_ILLEGAL_CHARACTERS, '-');
+ mDefaultFilename.ReplaceChar(FILE_ILLEGAL_CHARACTERS, '-');
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseWinFilePicker::GetDefaultString(nsAString& aString) {
+ return NS_ERROR_FAILURE;
+}
+
+// The default extension to use for files
+NS_IMETHODIMP
+nsBaseWinFilePicker::GetDefaultExtension(nsAString& aExtension) {
+ aExtension = mDefaultExtension;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseWinFilePicker::SetDefaultExtension(const nsAString& aExtension) {
+ mDefaultExtension = aExtension;
+ return NS_OK;
+}
+
+// Set the filter index
+NS_IMETHODIMP
+nsFilePicker::GetFilterIndex(int32_t* aFilterIndex) {
+ // Windows' filter index is 1-based, we use a 0-based system.
+ *aFilterIndex = mSelectedType - 1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::SetFilterIndex(int32_t aFilterIndex) {
+ // Windows' filter index is 1-based, we use a 0-based system.
+ mSelectedType = aFilterIndex + 1;
+ return NS_OK;
+}
+
+void nsFilePicker::InitNative(nsIWidget* aParent, const nsAString& aTitle) {
+ mParentWidget = aParent;
+ mTitle.Assign(aTitle);
+}
+
+NS_IMETHODIMP
+nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) {
+ mComFilterList.Append(aTitle, aFilter);
+ return NS_OK;
+}
+
+void nsFilePicker::RememberLastUsedDirectory() {
+ if (IsPrivacyModeEnabled()) {
+ // Don't remember the directory if private browsing was in effect
+ return;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ if (NS_FAILED(NS_NewLocalFile(mUnicodeFile, false, getter_AddRefs(file)))) {
+ NS_WARNING("RememberLastUsedDirectory failed to init file path.");
+ return;
+ }
+
+ nsCOMPtr<nsIFile> dir;
+ nsAutoString newDir;
+ if (NS_FAILED(file->GetParent(getter_AddRefs(dir))) ||
+ !(mDisplayDirectory = dir) ||
+ NS_FAILED(mDisplayDirectory->GetPath(newDir)) || newDir.IsEmpty()) {
+ NS_WARNING("RememberLastUsedDirectory failed to get parent directory.");
+ return;
+ }
+
+ sLastUsedUnicodeDirectory.reset(ToNewUnicode(newDir));
+}
+
+bool nsFilePicker::IsPrivacyModeEnabled() {
+ return mLoadContext && mLoadContext->UsePrivateBrowsing();
+}
+
+bool nsFilePicker::IsDefaultPathLink() {
+ NS_ConvertUTF16toUTF8 ext(mDefaultFilePath);
+ ext.Trim(" .", false, true); // watch out for trailing space and dots
+ ToLowerCase(ext);
+ if (StringEndsWith(ext, ".lnk"_ns) || StringEndsWith(ext, ".pif"_ns) ||
+ StringEndsWith(ext, ".url"_ns))
+ return true;
+ return false;
+}
+
+bool nsFilePicker::IsDefaultPathHtml() {
+ int32_t extIndex = mDefaultFilePath.RFind(".");
+ if (extIndex >= 0) {
+ nsAutoString ext;
+ mDefaultFilePath.Right(ext, mDefaultFilePath.Length() - extIndex);
+ if (ext.LowerCaseEqualsLiteral(".htm") ||
+ ext.LowerCaseEqualsLiteral(".html") ||
+ ext.LowerCaseEqualsLiteral(".shtml"))
+ return true;
+ }
+ return false;
+}
+
+void nsFilePicker::ComDlgFilterSpec::Append(const nsAString& aTitle,
+ const nsAString& aFilter) {
+ COMDLG_FILTERSPEC* pSpecForward = mSpecList.AppendElement();
+ if (!pSpecForward) {
+ NS_WARNING("mSpecList realloc failed.");
+ return;
+ }
+ memset(pSpecForward, 0, sizeof(*pSpecForward));
+ nsString* pStr = mStrings.AppendElement(aTitle);
+ if (!pStr) {
+ NS_WARNING("mStrings.AppendElement failed.");
+ return;
+ }
+ pSpecForward->pszName = pStr->get();
+ pStr = mStrings.AppendElement(aFilter);
+ if (!pStr) {
+ NS_WARNING("mStrings.AppendElement failed.");
+ return;
+ }
+ if (aFilter.EqualsLiteral("..apps"))
+ pStr->AssignLiteral("*.exe;*.com");
+ else {
+ pStr->StripWhitespace();
+ if (pStr->EqualsLiteral("*")) pStr->AppendLiteral(".*");
+ }
+ pSpecForward->pszSpec = pStr->get();
+}
diff --git a/widget/windows/nsFilePicker.h b/widget/windows/nsFilePicker.h
new file mode 100644
index 0000000000..56fcc3895b
--- /dev/null
+++ b/widget/windows/nsFilePicker.h
@@ -0,0 +1,109 @@
+/* -*- 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 <windows.h>
+
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "nsCOMArray.h"
+#include "nsBaseFilePicker.h"
+#include "nsString.h"
+#include "nsdefs.h"
+#include <commdlg.h>
+#include <shobjidl.h>
+#undef LogSeverity // SetupAPI.h #defines this as DWORD
+
+class nsILoadContext;
+
+class nsBaseWinFilePicker : public nsBaseFilePicker {
+ public:
+ NS_IMETHOD GetDefaultString(nsAString& aDefaultString) override;
+ NS_IMETHOD SetDefaultString(const nsAString& aDefaultString) override;
+ NS_IMETHOD GetDefaultExtension(nsAString& aDefaultExtension) override;
+ NS_IMETHOD SetDefaultExtension(const nsAString& aDefaultExtension) override;
+
+ protected:
+ nsString mDefaultFilePath;
+ nsString mDefaultFilename;
+ nsString mDefaultExtension;
+};
+
+/**
+ * Native Windows FileSelector wrapper
+ */
+
+class nsFilePicker : public nsBaseWinFilePicker {
+ virtual ~nsFilePicker() = default;
+
+ public:
+ nsFilePicker();
+
+ NS_IMETHOD Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ int16_t aMode) override;
+
+ NS_DECL_ISUPPORTS
+
+ // nsIFilePicker (less what's in nsBaseFilePicker and nsBaseWinFilePicker)
+ NS_IMETHOD GetFilterIndex(int32_t* aFilterIndex) override;
+ NS_IMETHOD SetFilterIndex(int32_t aFilterIndex) override;
+ NS_IMETHOD GetFile(nsIFile** aFile) override;
+ NS_IMETHOD GetFileURL(nsIURI** aFileURL) override;
+ NS_IMETHOD GetFiles(nsISimpleEnumerator** aFiles) override;
+ NS_IMETHOD AppendFilter(const nsAString& aTitle,
+ const nsAString& aFilter) override;
+
+ protected:
+ /* method from nsBaseFilePicker */
+ virtual void InitNative(nsIWidget* aParent, const nsAString& aTitle) override;
+ nsresult Show(int16_t* aReturnVal) override;
+ nsresult ShowW(int16_t* aReturnVal);
+ void GetFilterListArray(nsString& aFilterList);
+ bool ShowFolderPicker(const nsString& aInitialDir);
+ bool ShowFilePicker(const nsString& aInitialDir);
+ void RememberLastUsedDirectory();
+ bool IsPrivacyModeEnabled();
+ bool IsDefaultPathLink();
+ bool IsDefaultPathHtml();
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsString mTitle;
+ nsCString mFile;
+ nsString mFilterList;
+ int16_t mSelectedType;
+ nsCOMArray<nsIFile> mFiles;
+ nsString mUnicodeFile;
+
+ struct FreeDeleter {
+ void operator()(void* aPtr) { ::free(aPtr); }
+ };
+ static mozilla::UniquePtr<char16_t[], FreeDeleter> sLastUsedUnicodeDirectory;
+
+ class ComDlgFilterSpec {
+ public:
+ ComDlgFilterSpec() {}
+ ~ComDlgFilterSpec() {}
+
+ const uint32_t Length() { return mSpecList.Length(); }
+
+ const bool IsEmpty() { return (mSpecList.Length() == 0); }
+
+ const COMDLG_FILTERSPEC* get() { return mSpecList.Elements(); }
+
+ void Append(const nsAString& aTitle, const nsAString& aFilter);
+
+ private:
+ AutoTArray<COMDLG_FILTERSPEC, 1> mSpecList;
+ AutoTArray<nsString, 2> mStrings;
+ };
+
+ ComDlgFilterSpec mComFilterList;
+};
+
+#endif // nsFilePicker_h__
diff --git a/widget/windows/nsLookAndFeel.cpp b/widget/windows/nsLookAndFeel.cpp
new file mode 100644
index 0000000000..4b7b732727
--- /dev/null
+++ b/widget/windows/nsLookAndFeel.cpp
@@ -0,0 +1,976 @@
+/* -*- 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 "nsLookAndFeel.h"
+#include <windows.h>
+#include <shellapi.h>
+#include "nsStyleConsts.h"
+#include "nsUXThemeData.h"
+#include "nsUXThemeConstants.h"
+#include "nsWindowsHelpers.h"
+#include "WinUtils.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/WindowsVersion.h"
+#include "gfxFontConstants.h"
+#include "gfxWindowsPlatform.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+// static
+LookAndFeel::OperatingSystemVersion nsLookAndFeel::GetOperatingSystemVersion() {
+ static OperatingSystemVersion version = OperatingSystemVersion::Unknown;
+
+ if (version != OperatingSystemVersion::Unknown) {
+ return version;
+ }
+
+ if (IsWin10OrLater()) {
+ version = OperatingSystemVersion::Windows10;
+ } else if (IsWin8OrLater()) {
+ version = OperatingSystemVersion::Windows8;
+ } else {
+ version = OperatingSystemVersion::Windows7;
+ }
+
+ return version;
+}
+
+static nsresult GetColorFromTheme(nsUXThemeClass cls, int32_t aPart,
+ int32_t aState, int32_t aPropId,
+ nscolor& aColor) {
+ COLORREF color;
+ HRESULT hr = GetThemeColor(nsUXThemeData::GetTheme(cls), aPart, aState,
+ aPropId, &color);
+ if (hr == S_OK) {
+ aColor = COLOREF_2_NSRGB(color);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+static int32_t GetSystemParam(long flag, int32_t def) {
+ DWORD value;
+ return ::SystemParametersInfo(flag, 0, &value, 0) ? value : def;
+}
+
+static nsresult SystemWantsDarkTheme(int32_t& darkThemeEnabled) {
+ if (!IsWin10OrLater()) {
+ darkThemeEnabled = 0;
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIWindowsRegKey> personalizeKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = personalizeKey->Open(
+ nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ nsLiteralString(
+ u"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"),
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint32_t lightThemeEnabled;
+ rv =
+ personalizeKey->ReadIntValue(u"AppsUseLightTheme"_ns, &lightThemeEnabled);
+ if (NS_SUCCEEDED(rv)) {
+ darkThemeEnabled = !lightThemeEnabled;
+ }
+
+ return rv;
+}
+
+nsLookAndFeel::nsLookAndFeel(const LookAndFeelCache* aCache)
+ : nsXPLookAndFeel(),
+ mUseAccessibilityTheme(0),
+ mUseDefaultTheme(0),
+ mNativeThemeId(eWindowsTheme_Generic),
+ mCaretBlinkTime(-1),
+ mHasColorMenuHoverText(false),
+ mHasColorAccent(false),
+ mHasColorAccentText(false),
+ mHasColorMediaText(false),
+ mHasColorCommunicationsText(false),
+ mInitialized(false) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::TOUCH_ENABLED_DEVICE,
+ WinUtils::IsTouchDeviceSupportPresent());
+ if (aCache) {
+ DoSetCache(*aCache);
+ }
+}
+
+nsLookAndFeel::~nsLookAndFeel() {}
+
+void nsLookAndFeel::NativeInit() { EnsureInit(); }
+
+/* virtual */
+void nsLookAndFeel::RefreshImpl() {
+ nsXPLookAndFeel::RefreshImpl();
+
+ for (auto e = mSystemFontCache.begin(), end = mSystemFontCache.end();
+ e != end; ++e) {
+ e->mCacheValid = false;
+ }
+ mCaretBlinkTime = -1;
+
+ mInitialized = false;
+}
+
+nsresult nsLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor) {
+ EnsureInit();
+
+ nsresult res = NS_OK;
+
+ int idx;
+ switch (aID) {
+ case ColorID::WindowBackground:
+ idx = COLOR_WINDOW;
+ break;
+ case ColorID::WindowForeground:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case ColorID::WidgetBackground:
+ idx = COLOR_BTNFACE;
+ break;
+ case ColorID::WidgetForeground:
+ idx = COLOR_BTNTEXT;
+ break;
+ case ColorID::WidgetSelectBackground:
+ idx = COLOR_HIGHLIGHT;
+ break;
+ case ColorID::WidgetSelectForeground:
+ idx = COLOR_HIGHLIGHTTEXT;
+ break;
+ case ColorID::Widget3DHighlight:
+ idx = COLOR_BTNHIGHLIGHT;
+ break;
+ case ColorID::Widget3DShadow:
+ idx = COLOR_BTNSHADOW;
+ break;
+ case ColorID::TextBackground:
+ idx = COLOR_WINDOW;
+ break;
+ case ColorID::TextForeground:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case ColorID::TextSelectBackground:
+ case ColorID::IMESelectedRawTextBackground:
+ case ColorID::IMESelectedConvertedTextBackground:
+ idx = COLOR_HIGHLIGHT;
+ break;
+ case ColorID::TextSelectForeground:
+ case ColorID::IMESelectedRawTextForeground:
+ case ColorID::IMESelectedConvertedTextForeground:
+ idx = COLOR_HIGHLIGHTTEXT;
+ break;
+ case ColorID::IMERawInputBackground:
+ case ColorID::IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ return NS_OK;
+ case ColorID::IMERawInputForeground:
+ case ColorID::IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ return NS_OK;
+ case ColorID::IMERawInputUnderline:
+ case ColorID::IMEConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ return NS_OK;
+ case ColorID::IMESelectedRawTextUnderline:
+ case ColorID::IMESelectedConvertedTextUnderline:
+ aColor = NS_TRANSPARENT;
+ return NS_OK;
+ case ColorID::SpellCheckerUnderline:
+ aColor = NS_RGB(0xff, 0, 0);
+ return NS_OK;
+
+ // New CSS 2 Color definitions
+ case ColorID::Activeborder:
+ idx = COLOR_ACTIVEBORDER;
+ break;
+ case ColorID::Activecaption:
+ idx = COLOR_ACTIVECAPTION;
+ break;
+ case ColorID::Appworkspace:
+ idx = COLOR_APPWORKSPACE;
+ break;
+ case ColorID::Background:
+ idx = COLOR_BACKGROUND;
+ break;
+ case ColorID::Buttonface:
+ case ColorID::MozButtonhoverface:
+ idx = COLOR_BTNFACE;
+ break;
+ case ColorID::Buttonhighlight:
+ idx = COLOR_BTNHIGHLIGHT;
+ break;
+ case ColorID::Buttonshadow:
+ idx = COLOR_BTNSHADOW;
+ break;
+ case ColorID::Buttontext:
+ case ColorID::MozButtonhovertext:
+ idx = COLOR_BTNTEXT;
+ break;
+ case ColorID::Captiontext:
+ idx = COLOR_CAPTIONTEXT;
+ break;
+ case ColorID::Graytext:
+ idx = COLOR_GRAYTEXT;
+ break;
+ case ColorID::Highlight:
+ case ColorID::MozHtmlCellhighlight:
+ case ColorID::MozMenuhover:
+ idx = COLOR_HIGHLIGHT;
+ break;
+ case ColorID::MozMenubarhovertext:
+ if (!nsUXThemeData::IsAppThemed()) {
+ idx = nsUXThemeData::AreFlatMenusEnabled() ? COLOR_HIGHLIGHTTEXT
+ : COLOR_MENUTEXT;
+ break;
+ }
+ // Fall through
+ case ColorID::MozMenuhovertext:
+ if (mHasColorMenuHoverText) {
+ aColor = mColorMenuHoverText;
+ return NS_OK;
+ }
+ // Fall through
+ case ColorID::Highlighttext:
+ case ColorID::MozHtmlCellhighlighttext:
+ idx = COLOR_HIGHLIGHTTEXT;
+ break;
+ case ColorID::Inactiveborder:
+ idx = COLOR_INACTIVEBORDER;
+ break;
+ case ColorID::Inactivecaption:
+ idx = COLOR_INACTIVECAPTION;
+ break;
+ case ColorID::Inactivecaptiontext:
+ idx = COLOR_INACTIVECAPTIONTEXT;
+ break;
+ case ColorID::Infobackground:
+ idx = COLOR_INFOBK;
+ break;
+ case ColorID::Infotext:
+ idx = COLOR_INFOTEXT;
+ break;
+ case ColorID::Menu:
+ idx = COLOR_MENU;
+ break;
+ case ColorID::Menutext:
+ case ColorID::MozMenubartext:
+ idx = COLOR_MENUTEXT;
+ break;
+ case ColorID::Scrollbar:
+ idx = COLOR_SCROLLBAR;
+ break;
+ case ColorID::Threeddarkshadow:
+ idx = COLOR_3DDKSHADOW;
+ break;
+ case ColorID::Threedface:
+ idx = COLOR_3DFACE;
+ break;
+ case ColorID::Threedhighlight:
+ idx = COLOR_3DHIGHLIGHT;
+ break;
+ case ColorID::Threedlightshadow:
+ idx = COLOR_3DLIGHT;
+ break;
+ case ColorID::Threedshadow:
+ idx = COLOR_3DSHADOW;
+ break;
+ case ColorID::Window:
+ idx = COLOR_WINDOW;
+ break;
+ case ColorID::Windowframe:
+ idx = COLOR_WINDOWFRAME;
+ break;
+ case ColorID::Windowtext:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case ColorID::MozEventreerow:
+ case ColorID::MozOddtreerow:
+ case ColorID::Field:
+ case ColorID::MozCombobox:
+ idx = COLOR_WINDOW;
+ break;
+ case ColorID::Fieldtext:
+ case ColorID::MozComboboxtext:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case ColorID::MozDialog:
+ case ColorID::MozCellhighlight:
+ idx = COLOR_3DFACE;
+ break;
+ case ColorID::MozWinAccentcolor:
+ if (mHasColorAccent) {
+ aColor = mColorAccent;
+ } else {
+ // Seems to be the default color (hardcoded because of bug 1065998)
+ aColor = NS_RGB(158, 158, 158);
+ }
+ return NS_OK;
+ case ColorID::MozWinAccentcolortext:
+ if (mHasColorAccentText) {
+ aColor = mColorAccentText;
+ } else {
+ aColor = NS_RGB(0, 0, 0);
+ }
+ return NS_OK;
+ case ColorID::MozWinMediatext:
+ if (mHasColorMediaText) {
+ aColor = mColorMediaText;
+ return NS_OK;
+ }
+ // if we've gotten here just return -moz-dialogtext instead
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case ColorID::MozWinCommunicationstext:
+ if (mHasColorCommunicationsText) {
+ aColor = mColorCommunicationsText;
+ return NS_OK;
+ }
+ // if we've gotten here just return -moz-dialogtext instead
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case ColorID::MozDialogtext:
+ case ColorID::MozCellhighlighttext:
+ case ColorID::MozColheadertext:
+ case ColorID::MozColheaderhovertext:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case ColorID::MozDragtargetzone:
+ idx = COLOR_HIGHLIGHTTEXT;
+ break;
+ case ColorID::MozButtondefault:
+ idx = COLOR_3DDKSHADOW;
+ break;
+ case ColorID::MozNativehyperlinktext:
+ idx = COLOR_HOTLIGHT;
+ break;
+ default:
+ NS_WARNING("Unknown color for nsLookAndFeel");
+ idx = COLOR_WINDOW;
+ res = NS_ERROR_FAILURE;
+ break;
+ }
+
+ aColor = GetColorForSysColorIndex(idx);
+
+ return res;
+}
+
+nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ case IntID::ScrollButtonLeftMouseButtonAction:
+ aResult = 0;
+ break;
+ case IntID::ScrollButtonMiddleMouseButtonAction:
+ case IntID::ScrollButtonRightMouseButtonAction:
+ aResult = 3;
+ break;
+ case IntID::CaretBlinkTime:
+ // IntID::CaretBlinkTime is often called by updating editable text
+ // that has focus. So it should be cached to improve performance.
+ if (mCaretBlinkTime < 0) {
+ mCaretBlinkTime = static_cast<int32_t>(::GetCaretBlinkTime());
+ }
+ aResult = mCaretBlinkTime;
+ break;
+ case IntID::CaretWidth:
+ aResult = 1;
+ break;
+ case IntID::ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case IntID::SelectTextfieldsOnKeyFocus:
+ // Select textfield content when focused by kbd
+ // used by EventStateManager::sTextfieldSelectModel
+ aResult = 1;
+ break;
+ case IntID::SubmenuDelay:
+ // This will default to the Windows' default
+ // (400ms) on error.
+ aResult = GetSystemParam(SPI_GETMENUSHOWDELAY, 400);
+ break;
+ case IntID::TooltipDelay:
+ aResult = 500;
+ break;
+ case IntID::MenusCanOverlapOSBar:
+ // we want XUL popups to be able to overlap the task bar.
+ aResult = 1;
+ break;
+ case IntID::DragThresholdX:
+ // The system metric is the number of pixels at which a drag should
+ // start. Our look and feel metric is the number of pixels you can
+ // move before starting a drag, so subtract 1.
+
+ aResult = ::GetSystemMetrics(SM_CXDRAG) - 1;
+ break;
+ case IntID::DragThresholdY:
+ aResult = ::GetSystemMetrics(SM_CYDRAG) - 1;
+ break;
+ case IntID::UseAccessibilityTheme:
+ // High contrast is a misnomer under Win32 -- any theme can be used with
+ // it, e.g. normal contrast with large fonts, low contrast, etc. The high
+ // contrast flag really means -- use this theme and don't override it.
+ if (XRE_IsContentProcess()) {
+ // If we're running in the content process, then the parent should
+ // have sent us the accessibility state when nsLookAndFeel
+ // initialized, and stashed it in the mUseAccessibilityTheme cache.
+ aResult = mUseAccessibilityTheme;
+ } else {
+ // Otherwise, we can ask the OS to see if we're using High Contrast
+ // mode.
+ aResult = nsUXThemeData::IsHighContrastOn();
+ }
+ break;
+ case IntID::ScrollArrowStyle:
+ aResult = eScrollArrowStyle_Single;
+ break;
+ case IntID::ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+ case IntID::TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case IntID::TreeCloseDelay:
+ aResult = 0;
+ break;
+ case IntID::TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case IntID::TreeScrollDelay:
+ aResult = 100;
+ break;
+ case IntID::TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case IntID::WindowsClassic:
+ aResult = !nsUXThemeData::IsAppThemed();
+ break;
+ case IntID::TouchEnabled:
+ aResult = WinUtils::IsTouchDeviceSupportPresent();
+ break;
+ case IntID::WindowsDefaultTheme:
+ if (XRE_IsContentProcess()) {
+ aResult = mUseDefaultTheme;
+ } else {
+ aResult = nsUXThemeData::IsDefaultWindowTheme();
+ }
+ break;
+ case IntID::WindowsThemeIdentifier:
+ if (XRE_IsContentProcess()) {
+ aResult = mNativeThemeId;
+ } else {
+ aResult = nsUXThemeData::GetNativeThemeId();
+ }
+ break;
+
+ case IntID::OperatingSystemVersionIdentifier: {
+ aResult = int32_t(GetOperatingSystemVersion());
+ break;
+ }
+
+ case IntID::MacGraphiteTheme:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case IntID::DWMCompositor:
+ aResult = gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled();
+ break;
+ case IntID::WindowsAccentColorInTitlebar: {
+ nscolor unused;
+ if (NS_WARN_IF(NS_FAILED(GetAccentColor(unused)))) {
+ aResult = 0;
+ break;
+ }
+
+ uint32_t colorPrevalence;
+ nsresult rv = mDwmKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ u"SOFTWARE\\Microsoft\\Windows\\DWM"_ns,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // The ColorPrevalence value is set to 1 when the "Show color on title
+ // bar" setting in the Color section of Window's Personalization settings
+ // is turned on.
+ aResult = (NS_SUCCEEDED(mDwmKey->ReadIntValue(u"ColorPrevalence"_ns,
+ &colorPrevalence)) &&
+ colorPrevalence == 1)
+ ? 1
+ : 0;
+
+ mDwmKey->Close();
+ } break;
+ case IntID::WindowsGlass:
+ // Aero Glass is only available prior to Windows 8 when DWM is used.
+ aResult = (gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled() &&
+ !IsWin8OrLater());
+ break;
+ case IntID::AlertNotificationOrigin:
+ aResult = 0;
+ {
+ // Get task bar window handle
+ HWND shellWindow = FindWindowW(L"Shell_TrayWnd", nullptr);
+
+ if (shellWindow != nullptr) {
+ // Determine position
+ APPBARDATA appBarData;
+ appBarData.hWnd = shellWindow;
+ appBarData.cbSize = sizeof(appBarData);
+ if (SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData)) {
+ // Set alert origin as a bit field - see LookAndFeel.h
+ // 0 represents bottom right, sliding vertically.
+ switch (appBarData.uEdge) {
+ case ABE_LEFT:
+ aResult = NS_ALERT_HORIZONTAL | NS_ALERT_LEFT;
+ break;
+ case ABE_RIGHT:
+ aResult = NS_ALERT_HORIZONTAL;
+ break;
+ case ABE_TOP:
+ aResult = NS_ALERT_TOP;
+ // fall through for the right-to-left handling.
+ case ABE_BOTTOM:
+ // If the task bar is right-to-left,
+ // move the origin to the left
+ if (::GetWindowLong(shellWindow, GWL_EXSTYLE) & WS_EX_LAYOUTRTL)
+ aResult |= NS_ALERT_LEFT;
+ break;
+ }
+ }
+ }
+ }
+ break;
+ case IntID::IMERawInputUnderlineStyle:
+ case IntID::IMEConvertedTextUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_DASHED;
+ break;
+ case IntID::IMESelectedRawTextUnderlineStyle:
+ case IntID::IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
+ break;
+ case IntID::SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
+ break;
+ case IntID::ScrollbarButtonAutoRepeatBehavior:
+ aResult = 0;
+ break;
+ case IntID::SwipeAnimationEnabled:
+ aResult = 0;
+ break;
+ case IntID::UseOverlayScrollbars:
+ aResult = false;
+ break;
+ case IntID::AllowOverlayScrollbarsOverlap:
+ aResult = 0;
+ break;
+ case IntID::ScrollbarDisplayOnMouseMove:
+ aResult = 1;
+ break;
+ case IntID::ScrollbarFadeBeginDelay:
+ aResult = 2500;
+ break;
+ case IntID::ScrollbarFadeDuration:
+ aResult = 350;
+ break;
+ case IntID::ContextMenuOffsetVertical:
+ case IntID::ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+ case IntID::SystemUsesDarkTheme:
+ res = SystemWantsDarkTheme(aResult);
+ break;
+ case IntID::PrefersReducedMotion: {
+ BOOL enableAnimation = TRUE;
+ ::SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &enableAnimation,
+ 0);
+ aResult = enableAnimation ? 0 : 1;
+ break;
+ }
+ case IntID::PrimaryPointerCapabilities: {
+ PointerCapabilities caps =
+ widget::WinUtils::GetPrimaryPointerCapabilities();
+ aResult = static_cast<int32_t>(caps);
+ break;
+ }
+ case IntID::AllPointerCapabilities: {
+ PointerCapabilities caps = widget::WinUtils::GetAllPointerCapabilities();
+ aResult = static_cast<int32_t>(caps);
+ break;
+ }
+ default:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ case FloatID::IMEUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case FloatID::SpellCheckerUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ default:
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+LookAndFeelFont nsLookAndFeel::GetLookAndFeelFontInternal(
+ const LOGFONTW& aLogFont, bool aUseShellDlg) {
+ LookAndFeelFont result{};
+
+ result.haveFont() = false;
+
+ // Get scaling factor from physical to logical pixels
+ double pixelScale = 1.0 / WinUtils::SystemScaleFactor();
+
+ // The lfHeight is in pixels, and it needs to be adjusted for the
+ // device it will be displayed on.
+ // Screens and Printers will differ in DPI
+ //
+ // So this accounts for the difference in the DeviceContexts
+ // The pixelScale will typically be 1.0 for the screen
+ // (though larger for hi-dpi screens where the Windows resolution
+ // scale factor is 125% or 150% or even more), and could be
+ // any value when going to a printer, for example pixelScale is
+ // 6.25 when going to a 600dpi printer.
+ float pixelHeight = -aLogFont.lfHeight;
+ if (pixelHeight < 0) {
+ nsAutoFont hFont(::CreateFontIndirectW(&aLogFont));
+ if (!hFont) {
+ return result;
+ }
+
+ nsAutoHDC dc(::GetDC(nullptr));
+ HGDIOBJ hObject = ::SelectObject(dc, hFont);
+ TEXTMETRIC tm;
+ ::GetTextMetrics(dc, &tm);
+ ::SelectObject(dc, hObject);
+
+ pixelHeight = tm.tmAscent;
+ }
+
+ pixelHeight *= pixelScale;
+
+ // we have problem on Simplified Chinese system because the system
+ // report the default font size is 8 points. but if we use 8, the text
+ // display very ugly. force it to be at 9 points (12 pixels) on that
+ // system (cp936), but leave other sizes alone.
+ if (pixelHeight < 12 && ::GetACP() == 936) {
+ pixelHeight = 12;
+ }
+
+ result.haveFont() = true;
+
+ if (aUseShellDlg) {
+ result.name() = u"MS Shell Dlg 2"_ns;
+ } else {
+ result.name() = aLogFont.lfFaceName;
+ }
+
+ result.size() = pixelHeight;
+ result.italic() = !!aLogFont.lfItalic;
+ // FIXME: Other weights?
+ result.weight() = ((aLogFont.lfWeight == FW_BOLD) ? FontWeight::Bold()
+ : FontWeight::Normal())
+ .ToFloat();
+
+ return result;
+}
+
+LookAndFeelFont nsLookAndFeel::GetLookAndFeelFont(LookAndFeel::FontID anID) {
+ if (XRE_IsContentProcess()) {
+ return mFontCache[size_t(anID)];
+ }
+
+ LookAndFeelFont result{};
+
+ result.haveFont() = false;
+
+ // FontID::Icon is handled differently than the others
+ if (anID == LookAndFeel::FontID::Icon) {
+ LOGFONTW logFont;
+ if (::SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(logFont),
+ (PVOID)&logFont, 0)) {
+ result = GetLookAndFeelFontInternal(logFont, false);
+ }
+ return result;
+ }
+
+ NONCLIENTMETRICSW ncm;
+ ncm.cbSize = sizeof(NONCLIENTMETRICSW);
+ if (!::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm),
+ (PVOID)&ncm, 0)) {
+ return result;
+ }
+
+ switch (anID) {
+ case LookAndFeel::FontID::Menu:
+ case LookAndFeel::FontID::PullDownMenu:
+ result = GetLookAndFeelFontInternal(ncm.lfMenuFont, false);
+ break;
+ case LookAndFeel::FontID::Caption:
+ result = GetLookAndFeelFontInternal(ncm.lfCaptionFont, false);
+ break;
+ case LookAndFeel::FontID::SmallCaption:
+ result = GetLookAndFeelFontInternal(ncm.lfSmCaptionFont, false);
+ break;
+ case LookAndFeel::FontID::StatusBar:
+ case LookAndFeel::FontID::Tooltips:
+ result = GetLookAndFeelFontInternal(ncm.lfStatusFont, false);
+ break;
+ case LookAndFeel::FontID::Widget:
+ case LookAndFeel::FontID::Dialog:
+ case LookAndFeel::FontID::Button:
+ case LookAndFeel::FontID::Field:
+ case LookAndFeel::FontID::List:
+ // XXX It's not clear to me whether this is exactly the right
+ // set of LookAndFeel values to map to the dialog font; we may
+ // want to add or remove cases here after reviewing the visual
+ // results under various Windows versions.
+ result = GetLookAndFeelFontInternal(ncm.lfMessageFont, true);
+ break;
+ default:
+ result = GetLookAndFeelFontInternal(ncm.lfMessageFont, false);
+ break;
+ }
+
+ return result;
+}
+
+bool nsLookAndFeel::GetSysFont(LookAndFeel::FontID anID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) {
+ LookAndFeelFont font = GetLookAndFeelFont(anID);
+
+ if (!font.haveFont()) {
+ return false;
+ }
+
+ aFontName = std::move(font.name());
+
+ aFontStyle.size = font.size();
+
+ // FIXME: What about oblique?
+ aFontStyle.style =
+ font.italic() ? FontSlantStyle::Italic() : FontSlantStyle::Normal();
+
+ aFontStyle.weight = FontWeight(font.weight());
+
+ // FIXME: Set aFontStyle->stretch correctly!
+ aFontStyle.stretch = FontStretch::Normal();
+
+ aFontStyle.systemFont = true;
+
+ return true;
+}
+
+bool nsLookAndFeel::NativeGetFont(FontID anID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) {
+ CachedSystemFont& cacheSlot = mSystemFontCache[size_t(anID)];
+
+ bool status;
+ if (cacheSlot.mCacheValid) {
+ status = cacheSlot.mHaveFont;
+ if (status) {
+ aFontName = cacheSlot.mFontName;
+ aFontStyle = cacheSlot.mFontStyle;
+ }
+ } else {
+ status = GetSysFont(anID, aFontName, aFontStyle);
+
+ cacheSlot.mCacheValid = true;
+ cacheSlot.mHaveFont = status;
+ if (status) {
+ cacheSlot.mFontName = aFontName;
+ cacheSlot.mFontStyle = aFontStyle;
+ }
+ }
+ return status;
+}
+
+/* virtual */
+char16_t nsLookAndFeel::GetPasswordCharacterImpl() {
+#define UNICODE_BLACK_CIRCLE_CHAR 0x25cf
+ return UNICODE_BLACK_CIRCLE_CHAR;
+}
+
+LookAndFeelCache nsLookAndFeel::GetCacheImpl() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ LookAndFeelCache cache = nsXPLookAndFeel::GetCacheImpl();
+
+ LookAndFeelInt lafInt;
+ lafInt.id() = IntID::UseAccessibilityTheme;
+ lafInt.value() = GetInt(IntID::UseAccessibilityTheme);
+ cache.mInts().AppendElement(lafInt);
+
+ lafInt.id() = IntID::WindowsDefaultTheme;
+ lafInt.value() = GetInt(IntID::WindowsDefaultTheme);
+ cache.mInts().AppendElement(lafInt);
+
+ lafInt.id() = IntID::WindowsThemeIdentifier;
+ lafInt.value() = GetInt(IntID::WindowsThemeIdentifier);
+ cache.mInts().AppendElement(lafInt);
+
+ for (size_t i = size_t(LookAndFeel::FontID::MINIMUM);
+ i <= size_t(LookAndFeel::FontID::MAXIMUM); ++i) {
+ cache.mFonts().AppendElement(GetLookAndFeelFont(LookAndFeel::FontID(i)));
+ }
+
+ return cache;
+}
+
+void nsLookAndFeel::SetCacheImpl(const LookAndFeelCache& aCache) {
+ DoSetCache(aCache);
+}
+
+void nsLookAndFeel::DoSetCache(const LookAndFeelCache& aCache) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ MOZ_RELEASE_ASSERT(aCache.mFonts().Length() == mFontCache.length());
+
+ for (auto entry : aCache.mInts()) {
+ switch (entry.id()) {
+ case IntID::UseAccessibilityTheme:
+ mUseAccessibilityTheme = entry.value();
+ break;
+ case IntID::WindowsDefaultTheme:
+ mUseDefaultTheme = entry.value();
+ break;
+ case IntID::WindowsThemeIdentifier:
+ mNativeThemeId = entry.value();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Bogus Int ID in cache");
+ break;
+ }
+ }
+
+ size_t i = mFontCache.minIndex();
+ for (const auto& font : aCache.mFonts()) {
+ mFontCache[i] = font;
+ ++i;
+ }
+}
+
+/* static */
+nsresult nsLookAndFeel::GetAccentColor(nscolor& aColor) {
+ nsresult rv;
+
+ if (!mDwmKey) {
+ mDwmKey = do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = mDwmKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ u"SOFTWARE\\Microsoft\\Windows\\DWM"_ns,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ uint32_t accentColor;
+ if (NS_SUCCEEDED(mDwmKey->ReadIntValue(u"AccentColor"_ns, &accentColor))) {
+ // The order of the color components in the DWORD stored in the registry
+ // happens to be the same order as we store the components in nscolor
+ // so we can just assign directly here.
+ aColor = accentColor;
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mDwmKey->Close();
+
+ return rv;
+}
+
+/* static */
+nsresult nsLookAndFeel::GetAccentColorText(nscolor& aColor) {
+ nscolor accentColor;
+ nsresult rv = GetAccentColor(accentColor);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // We want the color that we return for text that will be drawn over
+ // a background that has the accent color to have good contrast with
+ // the accent color. Windows itself uses either white or black text
+ // depending on how light or dark the accent color is. We do the same
+ // here based on the luminance of the accent color with a threshhold
+ // value. This algorithm should match what Windows does. It comes from:
+ //
+ // https://docs.microsoft.com/en-us/windows/uwp/style/color
+
+ float luminance = (NS_GET_R(accentColor) * 2 + NS_GET_G(accentColor) * 5 +
+ NS_GET_B(accentColor)) /
+ 8;
+
+ aColor = (luminance <= 128) ? NS_RGB(255, 255, 255) : NS_RGB(0, 0, 0);
+
+ return NS_OK;
+}
+
+nscolor nsLookAndFeel::GetColorForSysColorIndex(int index) {
+ MOZ_ASSERT(index >= SYS_COLOR_MIN && index <= SYS_COLOR_MAX);
+ return mSysColorTable[index - SYS_COLOR_MIN];
+}
+
+void nsLookAndFeel::EnsureInit() {
+ if (mInitialized) {
+ return;
+ }
+ mInitialized = true;
+
+ nsresult res;
+
+ res = GetAccentColor(mColorAccent);
+ mHasColorAccent = NS_SUCCEEDED(res);
+
+ res = GetAccentColorText(mColorAccentText);
+ mHasColorAccentText = NS_SUCCEEDED(res);
+
+ if (nsUXThemeData::IsAppThemed()) {
+ res = ::GetColorFromTheme(eUXMenu, MENU_POPUPITEM, MPI_HOT, TMT_TEXTCOLOR,
+ mColorMenuHoverText);
+ mHasColorMenuHoverText = NS_SUCCEEDED(res);
+
+ res = ::GetColorFromTheme(eUXMediaToolbar, TP_BUTTON, TS_NORMAL,
+ TMT_TEXTCOLOR, mColorMediaText);
+ mHasColorMediaText = NS_SUCCEEDED(res);
+
+ res = ::GetColorFromTheme(eUXCommunicationsToolbar, TP_BUTTON, TS_NORMAL,
+ TMT_TEXTCOLOR, mColorCommunicationsText);
+ mHasColorCommunicationsText = NS_SUCCEEDED(res);
+ }
+
+ // Fill out the sys color table.
+ for (int i = SYS_COLOR_MIN; i <= SYS_COLOR_MAX; ++i) {
+ DWORD color = ::GetSysColor(i);
+ mSysColorTable[i - SYS_COLOR_MIN] = COLOREF_2_NSRGB(color);
+ }
+
+ RecordTelemetry();
+}
diff --git a/widget/windows/nsLookAndFeel.h b/widget/windows/nsLookAndFeel.h
new file mode 100644
index 0000000000..ded464fa05
--- /dev/null
+++ b/widget/windows/nsLookAndFeel.h
@@ -0,0 +1,138 @@
+/* -*- 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 __nsLookAndFeel
+#define __nsLookAndFeel
+
+#include <windows.h>
+
+#include "nsXPLookAndFeel.h"
+#include "gfxFont.h"
+#include "mozilla/RangedArray.h"
+#include "nsIWindowsRegKey.h"
+
+/*
+ * Gesture System Metrics
+ */
+#ifndef SM_DIGITIZER
+# define SM_DIGITIZER 94
+# define TABLET_CONFIG_NONE 0x00000000
+# define NID_INTEGRATED_TOUCH 0x00000001
+# define NID_EXTERNAL_TOUCH 0x00000002
+# define NID_INTEGRATED_PEN 0x00000004
+# define NID_EXTERNAL_PEN 0x00000008
+# define NID_MULTI_INPUT 0x00000040
+# define NID_READY 0x00000080
+#endif
+
+/*
+ * Tablet mode detection
+ */
+#ifndef SM_SYSTEMDOCKED
+# define SM_CONVERTIBLESLATEMODE 0x00002003
+# define SM_SYSTEMDOCKED 0x00002004
+#endif
+
+/*
+ * Color constant inclusive bounds for GetSysColor
+ */
+#define SYS_COLOR_MIN 0
+#define SYS_COLOR_MAX 30
+#define SYS_COLOR_COUNT (SYS_COLOR_MAX - SYS_COLOR_MIN + 1)
+
+class nsLookAndFeel final : public nsXPLookAndFeel {
+ static OperatingSystemVersion GetOperatingSystemVersion();
+
+ public:
+ explicit nsLookAndFeel(const LookAndFeelCache* aCache);
+ virtual ~nsLookAndFeel();
+
+ void NativeInit() final;
+ void RefreshImpl() override;
+ nsresult NativeGetInt(IntID aID, int32_t& aResult) override;
+ nsresult NativeGetFloat(FloatID aID, float& aResult) override;
+ nsresult NativeGetColor(ColorID aID, nscolor& aResult) override;
+ bool NativeGetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) override;
+ char16_t GetPasswordCharacterImpl() override;
+
+ LookAndFeelCache GetCacheImpl() override;
+ void SetCacheImpl(const LookAndFeelCache& aCache) override;
+
+ private:
+ void DoSetCache(const LookAndFeelCache& aCache);
+
+ /**
+ * Fetches the Windows accent color from the Windows settings if
+ * the accent color is set to apply to the title bar, otherwise
+ * returns an error code.
+ */
+ nsresult GetAccentColor(nscolor& aColor);
+
+ /**
+ * If the Windows accent color from the Windows settings is set
+ * to apply to the title bar, this computes the color that should
+ * be used for text that is to be written over a background that has
+ * the accent color. Otherwise, (if the accent color should not
+ * apply to the title bar) this returns an error code.
+ */
+ nsresult GetAccentColorText(nscolor& aColor);
+
+ nscolor GetColorForSysColorIndex(int index);
+
+ LookAndFeelFont GetLookAndFeelFontInternal(const LOGFONTW& aLogFont,
+ bool aUseShellDlg);
+
+ LookAndFeelFont GetLookAndFeelFont(LookAndFeel::FontID anID);
+
+ bool GetSysFont(LookAndFeel::FontID anID, nsString& aFontName,
+ gfxFontStyle& aFontStyle);
+
+ // Content process cached values that get shipped over from the browser
+ // process.
+ int32_t mUseAccessibilityTheme;
+ int32_t mUseDefaultTheme; // is the current theme a known default?
+ int32_t mNativeThemeId; // see LookAndFeel enum 'WindowsTheme'
+ int32_t mCaretBlinkTime;
+
+ // Cached colors and flags indicating success in their retrieval.
+ nscolor mColorMenuHoverText;
+ bool mHasColorMenuHoverText;
+ nscolor mColorAccent;
+ bool mHasColorAccent;
+ nscolor mColorAccentText;
+ bool mHasColorAccentText;
+ nscolor mColorMediaText;
+ bool mHasColorMediaText;
+ nscolor mColorCommunicationsText;
+ bool mHasColorCommunicationsText;
+
+ nscolor mSysColorTable[SYS_COLOR_COUNT];
+
+ bool mInitialized;
+
+ void EnsureInit();
+
+ struct CachedSystemFont {
+ CachedSystemFont() : mCacheValid(false) {}
+
+ bool mCacheValid;
+ bool mHaveFont;
+ nsString mFontName;
+ gfxFontStyle mFontStyle;
+ };
+
+ mozilla::RangedArray<CachedSystemFont, size_t(FontID::MINIMUM),
+ size_t(FontID::MAXIMUM) + 1 - size_t(FontID::MINIMUM)>
+ mSystemFontCache;
+
+ mozilla::RangedArray<LookAndFeelFont, size_t(FontID::MINIMUM),
+ size_t(FontID::MAXIMUM) + 1 - size_t(FontID::MINIMUM)>
+ mFontCache;
+
+ nsCOMPtr<nsIWindowsRegKey> mDwmKey;
+};
+
+#endif
diff --git a/widget/windows/nsNativeBasicThemeWin.cpp b/widget/windows/nsNativeBasicThemeWin.cpp
new file mode 100644
index 0000000000..bb4054d8d6
--- /dev/null
+++ b/widget/windows/nsNativeBasicThemeWin.cpp
@@ -0,0 +1,299 @@
+/* -*- Mode: C++; tab-width: 40; 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 "nsNativeBasicThemeWin.h"
+
+#include "LookAndFeel.h"
+#include "ScrollbarUtil.h"
+
+nsITheme::Transparency nsNativeBasicThemeWin::GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ if (auto transparency =
+ ScrollbarUtil::GetScrollbarPartTransparency(aFrame, aAppearance)) {
+ return *transparency;
+ }
+ return nsNativeBasicTheme::GetWidgetTransparency(aFrame, aAppearance);
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeCheckboxColors(
+ const EventStates& aState, StyleAppearance aAppearance) {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeCheckboxColors(aState, aAppearance);
+ }
+
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+ bool isChecked = aState.HasState(NS_EVENT_STATE_CHECKED);
+ bool isIndeterminate = aAppearance == StyleAppearance::Checkbox &&
+ aState.HasState(NS_EVENT_STATE_INDETERMINATE);
+
+ sRGBColor backgroundColor = sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::TextBackground));
+ sRGBColor borderColor = sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::Buttontext));
+ if (isDisabled && (isChecked || isIndeterminate)) {
+ backgroundColor = borderColor = sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::Graytext));
+ } else if (isDisabled) {
+ borderColor = sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::Graytext));
+ } else if (isChecked || isIndeterminate) {
+ backgroundColor = borderColor = sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::Highlight));
+ }
+
+ return std::make_pair(backgroundColor, borderColor);
+}
+
+sRGBColor nsNativeBasicThemeWin::ComputeCheckmarkColor(
+ const EventStates& aState) {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeCheckmarkColor(aState);
+ }
+
+ return sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::TextBackground));
+}
+
+std::pair<sRGBColor, sRGBColor>
+nsNativeBasicThemeWin::ComputeRadioCheckmarkColors(const EventStates& aState) {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeRadioCheckmarkColors(aState);
+ }
+
+ auto [unusedColor, checkColor] =
+ ComputeCheckboxColors(aState, StyleAppearance::Radio);
+ (void)unusedColor;
+ sRGBColor backgroundColor = ComputeCheckmarkColor(aState);
+
+ return std::make_pair(backgroundColor, checkColor);
+}
+
+sRGBColor nsNativeBasicThemeWin::ComputeBorderColor(const EventStates& aState) {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeBorderColor(aState);
+ }
+
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+
+ if (isDisabled) {
+ return sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::Graytext));
+ }
+ return sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::Buttontext));
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeButtonColors(
+ const EventStates& aState, nsIFrame* aFrame) {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeButtonColors(aState, aFrame);
+ }
+
+ return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::Buttonface)),
+ ComputeBorderColor(aState));
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeTextfieldColors(
+ const EventStates& aState) {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeTextfieldColors(aState);
+ }
+
+ return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::TextBackground)),
+ ComputeBorderColor(aState));
+}
+
+std::pair<sRGBColor, sRGBColor>
+nsNativeBasicThemeWin::ComputeRangeProgressColors(const EventStates& aState) {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeRangeProgressColors(aState);
+ }
+
+ return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::Highlight)),
+ sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::Buttontext)));
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeRangeTrackColors(
+ const EventStates& aState) {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeRangeTrackColors(aState);
+ }
+
+ return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::TextBackground)),
+ sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::Buttontext)));
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeRangeThumbColors(
+ const EventStates& aState) {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeRangeThumbColors(aState);
+ }
+
+ return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::Highlight)),
+ sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::Highlight)));
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeProgressColors() {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeProgressColors();
+ }
+
+ return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::Highlight)),
+ sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::Buttontext)));
+}
+
+std::pair<sRGBColor, sRGBColor>
+nsNativeBasicThemeWin::ComputeProgressTrackColors() {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeProgressTrackColors();
+ }
+
+ return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::TextBackground)),
+ sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::Buttontext)));
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeMeterchunkColors(
+ const EventStates& aMeterState) {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeMeterchunkColors(aMeterState);
+ }
+
+ return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::Highlight)),
+ sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::TextForeground)));
+}
+
+std::pair<sRGBColor, sRGBColor>
+nsNativeBasicThemeWin::ComputeMeterTrackColors() {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeMeterTrackColors();
+ }
+
+ return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::TextBackground)),
+ sRGBColor::FromABGR(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::TextForeground)));
+}
+
+sRGBColor nsNativeBasicThemeWin::ComputeMenulistArrowButtonColor(
+ const EventStates& aState) {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeMenulistArrowButtonColor(aState);
+ }
+
+ bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
+
+ if (isDisabled) {
+ return sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::Graytext));
+ }
+ return sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::TextForeground));
+}
+
+std::array<sRGBColor, 3> nsNativeBasicThemeWin::ComputeFocusRectColors() {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return nsNativeBasicTheme::ComputeFocusRectColors();
+ }
+
+ return {sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::Highlight)),
+ sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::Buttontext)),
+ sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::TextBackground))};
+}
+
+std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeScrollbarColors(
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aDocumentState, bool aIsRoot) {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ nscolor trackColor = ScrollbarUtil::GetScrollbarTrackColor(aFrame);
+ sRGBColor color = sRGBColor::FromABGR(trackColor);
+ return std::make_pair(color, color);
+ }
+
+ sRGBColor color = sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::TextBackground));
+ return std::make_pair(color, color);
+}
+
+sRGBColor nsNativeBasicThemeWin::ComputeScrollbarThumbColor(
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aElementState, const EventStates& aDocumentState) {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ return gfx::sRGBColor::FromABGR(
+ ScrollbarUtil::GetScrollbarThumbColor(aFrame, aElementState));
+ }
+
+ bool isActive = aElementState.HasState(NS_EVENT_STATE_ACTIVE);
+ bool isHovered = aElementState.HasState(NS_EVENT_STATE_HOVER);
+ const nsStyleUI* ui = aStyle.StyleUI();
+ nscolor color;
+
+ if (ui->mScrollbarColor.IsColors()) {
+ color = ui->mScrollbarColor.AsColors().thumb.CalcColor(aStyle);
+ } else if (isActive || isHovered) {
+ color = LookAndFeel::GetColor(LookAndFeel::ColorID::Highlight);
+ } else {
+ color = LookAndFeel::GetColor(LookAndFeel::ColorID::TextForeground);
+ }
+
+ return gfx::sRGBColor::FromABGR(color);
+}
+
+std::array<sRGBColor, 3> nsNativeBasicThemeWin::ComputeScrollbarButtonColors(
+ nsIFrame* aFrame, StyleAppearance aAppearance, const ComputedStyle& aStyle,
+ const EventStates& aElementState, const EventStates& aDocumentState) {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) {
+ nscolor trackColor = ScrollbarUtil::GetScrollbarTrackColor(aFrame);
+ nscolor buttonColor =
+ ScrollbarUtil::GetScrollbarButtonColor(trackColor, aElementState);
+ nscolor arrowColor = ScrollbarUtil::GetScrollbarArrowColor(buttonColor);
+ return {sRGBColor::FromABGR(buttonColor), sRGBColor::FromABGR(arrowColor),
+ sRGBColor::FromABGR(buttonColor)};
+ }
+
+ bool isActive = aElementState.HasState(NS_EVENT_STATE_ACTIVE);
+ bool isHovered = aElementState.HasState(NS_EVENT_STATE_HOVER);
+
+ sRGBColor buttonColor;
+ sRGBColor arrowColor;
+ if (isActive || isHovered) {
+ buttonColor = sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::Highlight));
+ arrowColor = sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::Buttonface));
+ } else {
+ buttonColor = sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::TextBackground));
+ arrowColor = sRGBColor::FromABGR(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::TextForeground));
+ }
+
+ return {buttonColor, arrowColor, buttonColor};
+}
+
+already_AddRefed<nsITheme> do_GetBasicNativeThemeDoNotUseDirectly() {
+ static StaticRefPtr<nsITheme> gInstance;
+ if (MOZ_UNLIKELY(!gInstance)) {
+ gInstance = new nsNativeBasicThemeWin();
+ ClearOnShutdown(&gInstance);
+ }
+ return do_AddRef(gInstance);
+}
diff --git a/widget/windows/nsNativeBasicThemeWin.h b/widget/windows/nsNativeBasicThemeWin.h
new file mode 100644
index 0000000000..25eaeefb3b
--- /dev/null
+++ b/widget/windows/nsNativeBasicThemeWin.h
@@ -0,0 +1,58 @@
+/* -*- 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 nsNativeBasicThemeWin_h
+#define nsNativeBasicThemeWin_h
+
+#include "nsNativeBasicTheme.h"
+
+class nsNativeBasicThemeWin : public nsNativeBasicTheme {
+ public:
+ nsNativeBasicThemeWin() = default;
+
+ Transparency GetWidgetTransparency(nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ protected:
+ virtual ~nsNativeBasicThemeWin() = default;
+
+ std::pair<sRGBColor, sRGBColor> ComputeCheckboxColors(
+ const EventStates& aState, StyleAppearance aAppearance) override;
+ sRGBColor ComputeCheckmarkColor(const EventStates& aState) override;
+ std::pair<sRGBColor, sRGBColor> ComputeRadioCheckmarkColors(
+ const EventStates& aState) override;
+ sRGBColor ComputeBorderColor(const EventStates& aState) override;
+ std::pair<sRGBColor, sRGBColor> ComputeButtonColors(
+ const EventStates& aState, nsIFrame* aFrame = nullptr) override;
+ std::pair<sRGBColor, sRGBColor> ComputeTextfieldColors(
+ const EventStates& aState) override;
+ std::pair<sRGBColor, sRGBColor> ComputeRangeProgressColors(
+ const EventStates& aState) override;
+ std::pair<sRGBColor, sRGBColor> ComputeRangeTrackColors(
+ const EventStates& aState) override;
+ std::pair<sRGBColor, sRGBColor> ComputeRangeThumbColors(
+ const EventStates& aState) override;
+ std::pair<sRGBColor, sRGBColor> ComputeProgressColors() override;
+ std::pair<sRGBColor, sRGBColor> ComputeProgressTrackColors() override;
+ std::pair<sRGBColor, sRGBColor> ComputeMeterchunkColors(
+ const EventStates& aMeterState) override;
+ std::pair<sRGBColor, sRGBColor> ComputeMeterTrackColors() override;
+ sRGBColor ComputeMenulistArrowButtonColor(const EventStates& aState) override;
+ std::array<sRGBColor, 3> ComputeFocusRectColors() override;
+ std::pair<sRGBColor, sRGBColor> ComputeScrollbarColors(
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aDocumentState, bool aIsRoot) override;
+ sRGBColor ComputeScrollbarThumbColor(
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aElementState,
+ const EventStates& aDocumentState) override;
+ std::array<sRGBColor, 3> ComputeScrollbarButtonColors(
+ nsIFrame* aFrame, StyleAppearance aAppearance,
+ const ComputedStyle& aStyle, const EventStates& aElementState,
+ const EventStates& aDocumentState) override;
+};
+
+#endif
diff --git a/widget/windows/nsNativeDragSource.cpp b/widget/windows/nsNativeDragSource.cpp
new file mode 100644
index 0000000000..aed8e31d5a
--- /dev/null
+++ b/widget/windows/nsNativeDragSource.cpp
@@ -0,0 +1,98 @@
+/* -*- 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 "nsNativeDragSource.h"
+#include <stdio.h>
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsToolkit.h"
+#include "nsWidgetsCID.h"
+#include "nsIDragService.h"
+#include "mozilla/dom/DataTransfer.h"
+
+/*
+ * class nsNativeDragSource
+ */
+nsNativeDragSource::nsNativeDragSource(
+ mozilla::dom::DataTransfer* aDataTransfer)
+ : m_cRef(0), m_hCursor(nullptr), mUserCancelled(false) {
+ mDataTransfer = aDataTransfer;
+}
+
+nsNativeDragSource::~nsNativeDragSource() {}
+
+STDMETHODIMP
+nsNativeDragSource::QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+
+ if (IID_IUnknown == riid || IID_IDropSource == riid) *ppv = this;
+
+ if (nullptr != *ppv) {
+ ((LPUNKNOWN)*ppv)->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG)
+nsNativeDragSource::AddRef(void) {
+ ++m_cRef;
+ NS_LOG_ADDREF(this, m_cRef, "nsNativeDragSource", sizeof(*this));
+ return m_cRef;
+}
+
+STDMETHODIMP_(ULONG)
+nsNativeDragSource::Release(void) {
+ --m_cRef;
+ NS_LOG_RELEASE(this, m_cRef, "nsNativeDragSource");
+ if (0 != m_cRef) return m_cRef;
+
+ delete this;
+ return 0;
+}
+
+STDMETHODIMP
+nsNativeDragSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState) {
+ static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID);
+
+ nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
+ if (dragService) {
+ DWORD pos = ::GetMessagePos();
+ dragService->DragMoved(GET_X_LPARAM(pos), GET_Y_LPARAM(pos));
+ }
+
+ if (fEsc) {
+ mUserCancelled = true;
+ return DRAGDROP_S_CANCEL;
+ }
+
+ if (!(grfKeyState & MK_LBUTTON) || (grfKeyState & MK_RBUTTON))
+ return DRAGDROP_S_DROP;
+
+ return S_OK;
+}
+
+STDMETHODIMP
+nsNativeDragSource::GiveFeedback(DWORD dwEffect) {
+ // For drags involving tabs, we do some custom work with cursors.
+ if (mDataTransfer) {
+ nsAutoString cursor;
+ mDataTransfer->GetMozCursor(cursor);
+ if (cursor.EqualsLiteral("default")) {
+ m_hCursor = ::LoadCursor(0, IDC_ARROW);
+ } else {
+ m_hCursor = nullptr;
+ }
+ }
+
+ if (m_hCursor) {
+ ::SetCursor(m_hCursor);
+ return S_OK;
+ }
+
+ // Let the system choose which cursor to apply.
+ return DRAGDROP_S_USEDEFAULTCURSORS;
+}
diff --git a/widget/windows/nsNativeDragSource.h b/widget/windows/nsNativeDragSource.h
new file mode 100644
index 0000000000..a8e37b90a8
--- /dev/null
+++ b/widget/windows/nsNativeDragSource.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 _nsNativeDragSource_h_
+#define _nsNativeDragSource_h_
+
+#include "nscore.h"
+#include <ole2.h>
+#include <oleidl.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+namespace dom {
+class DataTransfer;
+} // namespace dom
+} // namespace mozilla
+
+// class nsIDragSource;
+
+/*
+ * nsNativeDragSource implements the IDropSource interface and gets
+ * most of its behavior from the associated adapter (m_dragDrop).
+ */
+class nsNativeDragSource final : public IDropSource {
+ public:
+ // construct an nsNativeDragSource referencing adapter
+ // nsNativeDragSource(nsIDragSource * adapter);
+ explicit nsNativeDragSource(mozilla::dom::DataTransfer* aDataTransfer);
+ ~nsNativeDragSource();
+
+ // IUnknown methods - see iunknown.h for documentation
+
+ STDMETHODIMP QueryInterface(REFIID, void**);
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP_(ULONG) Release();
+
+ // IDropSource methods - see idropsrc.h for documentation
+
+ // Return DRAGDROP_S_USEDEFAULTCURSORS if this object lets OLE provide
+ // default cursors, otherwise return NOERROR. This method gets called in
+ // response to changes that the target makes to dEffect (DragEnter,
+ // DragOver).
+ STDMETHODIMP GiveFeedback(DWORD dEffect);
+
+ // This method gets called if there is any change in the mouse or key
+ // state. Return DRAGDROP_S_CANCEL to stop the drag, DRAGDROP_S_DROP
+ // to execute the drop, otherwise NOERROR.
+ STDMETHODIMP QueryContinueDrag(BOOL fESC, DWORD grfKeyState);
+
+ bool UserCancelled() { return mUserCancelled; }
+
+ protected:
+ // Reference count
+ ULONG m_cRef;
+
+ // Data object, hold information about cursor state
+ RefPtr<mozilla::dom::DataTransfer> mDataTransfer;
+
+ // Custom drag cursor
+ HCURSOR m_hCursor;
+
+ // true if the user cancelled the drag by pressing escape
+ bool mUserCancelled;
+};
+
+#endif // _nsNativeDragSource_h_
diff --git a/widget/windows/nsNativeDragTarget.cpp b/widget/windows/nsNativeDragTarget.cpp
new file mode 100644
index 0000000000..cdb53dfde3
--- /dev/null
+++ b/widget/windows/nsNativeDragTarget.cpp
@@ -0,0 +1,479 @@
+/* -*- 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 <stdio.h>
+#include "nsIDragService.h"
+#include "nsWidgetsCID.h"
+#include "nsNativeDragTarget.h"
+#include "nsDragService.h"
+#include "nsINode.h"
+#include "nsCOMPtr.h"
+
+#include "nsIWidget.h"
+#include "nsWindow.h"
+#include "nsClipboard.h"
+#include "KeyboardLayout.h"
+
+#include "mozilla/MouseEvents.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+/* Define Interface IDs */
+static NS_DEFINE_IID(kIDragServiceIID, NS_IDRAGSERVICE_IID);
+
+// This is cached for Leave notification
+static POINTL gDragLastPoint;
+
+bool nsNativeDragTarget::gDragImageChanged = false;
+
+/*
+ * class nsNativeDragTarget
+ */
+nsNativeDragTarget::nsNativeDragTarget(nsIWidget* aWidget)
+ : m_cRef(0),
+ mEffectsAllowed(DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK),
+ mEffectsPreferred(DROPEFFECT_NONE),
+ mTookOwnRef(false),
+ mWidget(aWidget),
+ mDropTargetHelper(nullptr) {
+ static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID);
+
+ mHWnd = (HWND)mWidget->GetNativeData(NS_NATIVE_WINDOW);
+
+ /*
+ * Create/Get the DragService that we have implemented
+ */
+ CallGetService(kCDragServiceCID, &mDragService);
+}
+
+nsNativeDragTarget::~nsNativeDragTarget() {
+ NS_RELEASE(mDragService);
+
+ if (mDropTargetHelper) {
+ mDropTargetHelper->Release();
+ mDropTargetHelper = nullptr;
+ }
+}
+
+// IUnknown methods - see iunknown.h for documentation
+STDMETHODIMP
+nsNativeDragTarget::QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+
+ if (IID_IUnknown == riid || IID_IDropTarget == riid) *ppv = this;
+
+ if (nullptr != *ppv) {
+ ((LPUNKNOWN)*ppv)->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG)
+nsNativeDragTarget::AddRef(void) {
+ ++m_cRef;
+ NS_LOG_ADDREF(this, m_cRef, "nsNativeDragTarget", sizeof(*this));
+ return m_cRef;
+}
+
+STDMETHODIMP_(ULONG) nsNativeDragTarget::Release(void) {
+ --m_cRef;
+ NS_LOG_RELEASE(this, m_cRef, "nsNativeDragTarget");
+ if (0 != m_cRef) return m_cRef;
+
+ delete this;
+ return 0;
+}
+
+void nsNativeDragTarget::GetGeckoDragAction(DWORD grfKeyState,
+ LPDWORD pdwEffect,
+ uint32_t* aGeckoAction) {
+ // If a window is disabled or a modal window is on top of it
+ // (which implies it is disabled), then we should not allow dropping.
+ if (!mWidget->IsEnabled()) {
+ *pdwEffect = DROPEFFECT_NONE;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_NONE;
+ return;
+ }
+
+ // If the user explicitly uses a modifier key, they want the associated action
+ // Shift + Control -> LINK, Shift -> MOVE, Ctrl -> COPY
+ DWORD desiredEffect = DROPEFFECT_NONE;
+ if ((grfKeyState & MK_CONTROL) && (grfKeyState & MK_SHIFT)) {
+ desiredEffect = DROPEFFECT_LINK;
+ } else if (grfKeyState & MK_SHIFT) {
+ desiredEffect = DROPEFFECT_MOVE;
+ } else if (grfKeyState & MK_CONTROL) {
+ desiredEffect = DROPEFFECT_COPY;
+ }
+
+ // Determine the desired effect from what is allowed and preferred.
+ if (!(desiredEffect &= mEffectsAllowed)) {
+ // No modifier key effect is set which is also allowed, check
+ // the preference of the data.
+ desiredEffect = mEffectsPreferred & mEffectsAllowed;
+ if (!desiredEffect) {
+ // No preference is set, so just fall back to the allowed effect itself
+ desiredEffect = mEffectsAllowed;
+ }
+ }
+
+ // Otherwise we should specify the first available effect
+ // from MOVE, COPY, or LINK.
+ if (desiredEffect & DROPEFFECT_MOVE) {
+ *pdwEffect = DROPEFFECT_MOVE;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_MOVE;
+ } else if (desiredEffect & DROPEFFECT_COPY) {
+ *pdwEffect = DROPEFFECT_COPY;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_COPY;
+ } else if (desiredEffect & DROPEFFECT_LINK) {
+ *pdwEffect = DROPEFFECT_LINK;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_LINK;
+ } else {
+ *pdwEffect = DROPEFFECT_NONE;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_NONE;
+ }
+}
+
+inline bool IsKeyDown(char key) { return GetKeyState(key) < 0; }
+
+void nsNativeDragTarget::DispatchDragDropEvent(EventMessage aEventMessage,
+ const POINTL& aPT) {
+ WidgetDragEvent event(true, aEventMessage, mWidget);
+
+ nsWindow* win = static_cast<nsWindow*>(mWidget);
+ win->InitEvent(event);
+ POINT cpos;
+
+ cpos.x = aPT.x;
+ cpos.y = aPT.y;
+
+ if (mHWnd != nullptr) {
+ ::ScreenToClient(mHWnd, &cpos);
+ event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y);
+ } else {
+ event.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ }
+
+ ModifierKeyState modifierKeyState;
+ modifierKeyState.InitInputEvent(event);
+
+ event.mInputSource =
+ static_cast<nsBaseDragService*>(mDragService)->GetInputSource();
+
+ mWidget->DispatchInputEvent(&event);
+}
+
+void nsNativeDragTarget::ProcessDrag(EventMessage aEventMessage,
+ DWORD grfKeyState, POINTL ptl,
+ DWORD* pdwEffect) {
+ // Before dispatching the event make sure we have the correct drop action set
+ uint32_t geckoAction;
+ GetGeckoDragAction(grfKeyState, pdwEffect, &geckoAction);
+
+ // Set the current action into the Gecko specific type
+ nsCOMPtr<nsIDragSession> currSession;
+ mDragService->GetCurrentSession(getter_AddRefs(currSession));
+ if (!currSession) {
+ return;
+ }
+
+ currSession->SetDragAction(geckoAction);
+
+ // Dispatch the event into Gecko
+ DispatchDragDropEvent(aEventMessage, ptl);
+
+ // If TakeChildProcessDragAction returns something other than
+ // DRAGDROP_ACTION_UNINITIALIZED, it means that the last event was sent
+ // to the child process and this event is also being sent to the child
+ // process. In this case, use the last event's action instead.
+ nsDragService* dragService = static_cast<nsDragService*>(mDragService);
+ currSession->GetDragAction(&geckoAction);
+
+ int32_t childDragAction = dragService->TakeChildProcessDragAction();
+ if (childDragAction != nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) {
+ geckoAction = childDragAction;
+ }
+
+ if (nsIDragService::DRAGDROP_ACTION_LINK & geckoAction) {
+ *pdwEffect = DROPEFFECT_LINK;
+ } else if (nsIDragService::DRAGDROP_ACTION_COPY & geckoAction) {
+ *pdwEffect = DROPEFFECT_COPY;
+ } else if (nsIDragService::DRAGDROP_ACTION_MOVE & geckoAction) {
+ *pdwEffect = DROPEFFECT_MOVE;
+ } else {
+ *pdwEffect = DROPEFFECT_NONE;
+ }
+
+ if (aEventMessage != eDrop) {
+ // Get the cached drag effect from the drag service, the data member should
+ // have been set by whoever handled the WidgetGUIEvent or nsIDOMEvent on
+ // drags.
+ bool canDrop;
+ currSession->GetCanDrop(&canDrop);
+ if (!canDrop) {
+ *pdwEffect = DROPEFFECT_NONE;
+ }
+ }
+
+ // Clear the cached value
+ currSession->SetCanDrop(false);
+}
+
+// IDropTarget methods
+STDMETHODIMP
+nsNativeDragTarget::DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState,
+ POINTL ptl, DWORD* pdwEffect) {
+ if (!mDragService) {
+ return E_FAIL;
+ }
+
+ mEffectsAllowed = *pdwEffect;
+ AddLinkSupportIfCanBeGenerated(pIDataSource);
+
+ // Drag and drop image helper
+ if (GetDropTargetHelper()) {
+ // We get a lot of crashes (often uncaught by our handler) later on during
+ // DragOver calls, see bug 1465513. It looks like this might be because
+ // we're not cleaning up previous drags fully and now released resources get
+ // used. Calling IDropTargetHelper::DragLeave before DragEnter seems to fix
+ // this for at least one reproduction of this crash.
+ GetDropTargetHelper()->DragLeave();
+ POINT pt = {ptl.x, ptl.y};
+ GetDropTargetHelper()->DragEnter(mHWnd, pIDataSource, &pt, *pdwEffect);
+ }
+
+ // save a ref to this, in case the window is destroyed underneath us
+ NS_ASSERTION(!mTookOwnRef, "own ref already taken!");
+ this->AddRef();
+ mTookOwnRef = true;
+
+ // tell the drag service about this drag (it may have come from an
+ // outside app).
+ mDragService->StartDragSession();
+
+ void* tempOutData = nullptr;
+ uint32_t tempDataLen = 0;
+ nsresult loadResult = nsClipboard::GetNativeDataOffClipboard(
+ pIDataSource, 0, ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT),
+ nullptr, &tempOutData, &tempDataLen);
+ if (NS_SUCCEEDED(loadResult) && tempOutData) {
+ mEffectsPreferred = *((DWORD*)tempOutData);
+ free(tempOutData);
+ } else {
+ // We have no preference if we can't obtain it
+ mEffectsPreferred = DROPEFFECT_NONE;
+ }
+
+ // Set the native data object into drag service
+ //
+ // This cast is ok because in the constructor we created a
+ // the actual implementation we wanted, so we know this is
+ // a nsDragService. It should be a private interface, though.
+ nsDragService* winDragService = static_cast<nsDragService*>(mDragService);
+ winDragService->SetIDataObject(pIDataSource);
+
+ // Now process the native drag state and then dispatch the event
+ ProcessDrag(eDragEnter, grfKeyState, ptl, pdwEffect);
+
+ return S_OK;
+}
+
+void nsNativeDragTarget::AddLinkSupportIfCanBeGenerated(
+ LPDATAOBJECT aIDataSource) {
+ // If we don't have a link effect, but we can generate one, fix the
+ // drop effect to include it.
+ if (!(mEffectsAllowed & DROPEFFECT_LINK) && aIDataSource) {
+ if (S_OK == ::OleQueryLinkFromData(aIDataSource)) {
+ mEffectsAllowed |= DROPEFFECT_LINK;
+ }
+ }
+}
+
+STDMETHODIMP
+nsNativeDragTarget::DragOver(DWORD grfKeyState, POINTL ptl, LPDWORD pdwEffect) {
+ if (!mDragService) {
+ return E_FAIL;
+ }
+
+ bool dragImageChanged = gDragImageChanged;
+ gDragImageChanged = false;
+
+ // If a LINK effect could be generated previously from a DragEnter(),
+ // then we should include it as an allowed effect.
+ mEffectsAllowed = (*pdwEffect) | (mEffectsAllowed & DROPEFFECT_LINK);
+
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ mDragService->GetCurrentSession(getter_AddRefs(currentDragSession));
+ if (!currentDragSession) {
+ return S_OK; // Drag was canceled.
+ }
+
+ // without the AddRef() |this| can get destroyed in an event handler
+ this->AddRef();
+
+ // Drag and drop image helper
+ if (GetDropTargetHelper()) {
+ if (dragImageChanged) {
+ // See comment in nsNativeDragTarget::DragEnter.
+ GetDropTargetHelper()->DragLeave();
+ // The drop helper only updates the image during DragEnter, so emulate
+ // a DragEnter if the image was changed.
+ POINT pt = {ptl.x, ptl.y};
+ nsDragService* dragService = static_cast<nsDragService*>(mDragService);
+ GetDropTargetHelper()->DragEnter(mHWnd, dragService->GetDataObject(), &pt,
+ *pdwEffect);
+ }
+ POINT pt = {ptl.x, ptl.y};
+ GetDropTargetHelper()->DragOver(&pt, *pdwEffect);
+ }
+
+ ModifierKeyState modifierKeyState;
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ dragService->FireDragEventAtSource(eDrag, modifierKeyState.GetModifiers());
+ // Now process the native drag state and then dispatch the event
+ ProcessDrag(eDragOver, grfKeyState, ptl, pdwEffect);
+
+ this->Release();
+
+ return S_OK;
+}
+
+STDMETHODIMP
+nsNativeDragTarget::DragLeave() {
+ if (!mDragService) {
+ return E_FAIL;
+ }
+
+ // Drag and drop image helper
+ if (GetDropTargetHelper()) {
+ GetDropTargetHelper()->DragLeave();
+ }
+
+ // dispatch the event into Gecko
+ DispatchDragDropEvent(eDragExit, gDragLastPoint);
+
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ mDragService->GetCurrentSession(getter_AddRefs(currentDragSession));
+
+ if (currentDragSession) {
+ nsCOMPtr<nsINode> sourceNode;
+ currentDragSession->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).
+ ModifierKeyState modifierKeyState;
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ dragService->EndDragSession(false, modifierKeyState.GetModifiers());
+ }
+ }
+
+ // release the ref that was taken in DragEnter
+ NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!");
+ if (mTookOwnRef) {
+ this->Release();
+ mTookOwnRef = false;
+ }
+
+ return S_OK;
+}
+
+void nsNativeDragTarget::DragCancel() {
+ // Cancel the drag session if we did DragEnter.
+ if (mTookOwnRef) {
+ if (GetDropTargetHelper()) {
+ GetDropTargetHelper()->DragLeave();
+ }
+ if (mDragService) {
+ ModifierKeyState modifierKeyState;
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ dragService->EndDragSession(false, modifierKeyState.GetModifiers());
+ }
+ this->Release(); // matching the AddRef in DragEnter
+ mTookOwnRef = false;
+ }
+}
+
+STDMETHODIMP
+nsNativeDragTarget::Drop(LPDATAOBJECT pData, DWORD grfKeyState, POINTL aPT,
+ LPDWORD pdwEffect) {
+ if (!mDragService) {
+ return E_FAIL;
+ }
+
+ mEffectsAllowed = *pdwEffect;
+ AddLinkSupportIfCanBeGenerated(pData);
+
+ // Drag and drop image helper
+ if (GetDropTargetHelper()) {
+ POINT pt = {aPT.x, aPT.y};
+ GetDropTargetHelper()->Drop(pData, &pt, *pdwEffect);
+ }
+
+ // Set the native data object into the drag service
+ //
+ // This cast is ok because in the constructor we created a
+ // the actual implementation we wanted, so we know this is
+ // a nsDragService (but it should still be a private interface)
+ nsDragService* winDragService = static_cast<nsDragService*>(mDragService);
+ winDragService->SetIDataObject(pData);
+
+ // NOTE: ProcessDrag spins the event loop which may destroy arbitrary objects.
+ // We use strong refs to prevent it from destroying these:
+ RefPtr<nsNativeDragTarget> kungFuDeathGrip = this;
+ nsCOMPtr<nsIDragService> serv = mDragService;
+
+ // Now process the native drag state and then dispatch the event
+ ProcessDrag(eDrop, grfKeyState, aPT, pdwEffect);
+
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ serv->GetCurrentSession(getter_AddRefs(currentDragSession));
+ if (!currentDragSession) {
+ return S_OK; // DragCancel() was called.
+ }
+
+ // Let the win drag service know whether this session experienced
+ // a drop event within the application. Drop will not oocur if the
+ // drop landed outside the app. (used in tab tear off, bug 455884)
+ winDragService->SetDroppedLocal();
+
+ // tell the drag service we're done with the session
+ // Use GetMessagePos to get the position of the mouse at the last message
+ // seen by the event loop. (Bug 489729)
+ DWORD pos = ::GetMessagePos();
+ POINT cpos;
+ cpos.x = GET_X_LPARAM(pos);
+ cpos.y = GET_Y_LPARAM(pos);
+ winDragService->SetDragEndPoint(nsIntPoint(cpos.x, cpos.y));
+ ModifierKeyState modifierKeyState;
+ serv->EndDragSession(true, modifierKeyState.GetModifiers());
+
+ // release the ref that was taken in DragEnter
+ NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!");
+ if (mTookOwnRef) {
+ this->Release();
+ mTookOwnRef = false;
+ }
+
+ return S_OK;
+}
+
+/**
+ * By lazy loading mDropTargetHelper we save 50-70ms of startup time
+ * which is ~5% of startup time.
+ */
+IDropTargetHelper* nsNativeDragTarget::GetDropTargetHelper() {
+ if (!mDropTargetHelper) {
+ CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IDropTargetHelper, (LPVOID*)&mDropTargetHelper);
+ }
+
+ return mDropTargetHelper;
+}
diff --git a/widget/windows/nsNativeDragTarget.h b/widget/windows/nsNativeDragTarget.h
new file mode 100644
index 0000000000..5c3896dd0a
--- /dev/null
+++ b/widget/windows/nsNativeDragTarget.h
@@ -0,0 +1,103 @@
+/* -*- 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 _nsNativeDragTarget_h_
+#define _nsNativeDragTarget_h_
+
+#include "nsCOMPtr.h"
+#include <ole2.h>
+#include <shlobj.h>
+
+#ifndef IDropTargetHelper
+# include <shobjidl.h> // Vista drag image interfaces
+# undef LogSeverity // SetupAPI.h #defines this as DWORD
+#endif
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+
+class nsIDragService;
+class nsIWidget;
+
+/*
+ * nsNativeDragTarget implements the IDropTarget interface and gets most of its
+ * behavior from the associated adapter (m_dragDrop).
+ */
+
+class nsNativeDragTarget final : public IDropTarget {
+ public:
+ explicit nsNativeDragTarget(nsIWidget* aWidget);
+ ~nsNativeDragTarget();
+
+ // IUnknown members - see iunknown.h for documentation
+ STDMETHODIMP QueryInterface(REFIID, void**);
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP_(ULONG) Release();
+
+ // IDataTarget members
+
+ // Set pEffect based on whether this object can support a drop based on
+ // the data available from pSource, the key and mouse states specified
+ // in grfKeyState, and the coordinates specified by point. This is
+ // called by OLE when a drag enters this object's window (as registered
+ // by Initialize).
+ STDMETHODIMP DragEnter(LPDATAOBJECT pSource, DWORD grfKeyState, POINTL point,
+ DWORD* pEffect);
+
+ // Similar to DragEnter except it is called frequently while the drag
+ // is over this object's window.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP DragOver(DWORD grfKeyState,
+ POINTL point,
+ DWORD* pEffect);
+
+ // Release the drag-drop source and put internal state back to the point
+ // before the call to DragEnter. This is called when the drag leaves
+ // without a drop occurring.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP DragLeave();
+
+ // If point is within our region of interest and pSource's data supports
+ // one of our formats, get the data and set pEffect according to
+ // grfKeyState (DROPEFFECT_MOVE if the control key was not pressed,
+ // DROPEFFECT_COPY if the control key was pressed). Otherwise return
+ // E_FAIL.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP Drop(LPDATAOBJECT pSource,
+ DWORD grfKeyState, POINTL point,
+ DWORD* pEffect);
+ /**
+ * Cancel the current drag session, if any.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void DragCancel();
+
+ static void DragImageChanged() { gDragImageChanged = true; }
+
+ protected:
+ void GetGeckoDragAction(DWORD grfKeyState, LPDWORD pdwEffect,
+ uint32_t* aGeckoAction);
+ void ProcessDrag(mozilla::EventMessage aEventMessage, DWORD grfKeyState,
+ POINTL pt, DWORD* pdwEffect);
+ void DispatchDragDropEvent(mozilla::EventMessage aEventMessage,
+ const POINTL& aPT);
+ void AddLinkSupportIfCanBeGenerated(LPDATAOBJECT aIDataSource);
+
+ // Native Stuff
+ ULONG m_cRef; // reference count
+ HWND mHWnd;
+ DWORD mEffectsAllowed;
+ DWORD mEffectsPreferred;
+ bool mTookOwnRef;
+
+ // Gecko Stuff
+ nsIWidget* mWidget;
+ nsIDragService* mDragService;
+ // Drag target helper
+ IDropTargetHelper* GetDropTargetHelper();
+
+ private:
+ // Drag target helper
+ IDropTargetHelper* mDropTargetHelper;
+
+ static bool gDragImageChanged;
+};
+
+#endif // _nsNativeDragTarget_h_
diff --git a/widget/windows/nsNativeThemeWin.cpp b/widget/windows/nsNativeThemeWin.cpp
new file mode 100644
index 0000000000..b6240d9ac7
--- /dev/null
+++ b/widget/windows/nsNativeThemeWin.cpp
@@ -0,0 +1,4013 @@
+/* -*- Mode: C++; tab-width: 40; 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 "nsNativeThemeWin.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/Logging.h"
+#include "mozilla/RelativeLuminanceUtils.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/gfx/Types.h" // for Color::FromABGR
+#include "nsNativeBasicTheme.h"
+#include "nsColor.h"
+#include "nsDeviceContext.h"
+#include "nsRect.h"
+#include "nsSize.h"
+#include "nsTransform2D.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsNameSpaceManager.h"
+#include "nsLookAndFeel.h"
+#include "nsMenuFrame.h"
+#include "nsGkAtoms.h"
+#include <malloc.h>
+#include "nsWindow.h"
+#include "nsComboboxControlFrame.h"
+#include "prinrval.h"
+#include "ScrollbarUtil.h"
+#include "WinUtils.h"
+
+#include "gfxPlatform.h"
+#include "gfxContext.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxWindowsSurface.h"
+#include "gfxWindowsNativeDrawing.h"
+
+#include "nsUXThemeData.h"
+#include "nsUXThemeConstants.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+
+extern mozilla::LazyLogModule gWindowsLog;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeWin, nsNativeTheme, nsITheme)
+
+nsNativeThemeWin::nsNativeThemeWin()
+ : mProgressDeterminateTimeStamp(TimeStamp::Now()),
+ mProgressIndeterminateTimeStamp(TimeStamp::Now()),
+ mBorderCacheValid(),
+ mMinimumWidgetSizeCacheValid(),
+ mGutterSizeCacheValid(false) {
+ // If there is a relevant change in forms.css for windows platform,
+ // static widget style variables (e.g. sButtonBorderSize) should be
+ // reinitialized here.
+}
+
+nsNativeThemeWin::~nsNativeThemeWin() { nsUXThemeData::Invalidate(); }
+
+static int32_t GetTopLevelWindowActiveState(nsIFrame* aFrame) {
+ // Used by window frame and button box rendering. We can end up in here in
+ // the content process when rendering one of these moz styles freely in a
+ // page. Bail in this case, there is no applicable window focus state.
+ if (!XRE_IsParentProcess()) {
+ return mozilla::widget::themeconst::FS_INACTIVE;
+ }
+ // All headless windows are considered active so they are painted.
+ if (gfxPlatform::IsHeadless()) {
+ return mozilla::widget::themeconst::FS_ACTIVE;
+ }
+ // Get the widget. nsIFrame's GetNearestWidget walks up the view chain
+ // until it finds a real window.
+ nsIWidget* widget = aFrame->GetNearestWidget();
+ nsWindowBase* window = static_cast<nsWindowBase*>(widget);
+ if (!window) return mozilla::widget::themeconst::FS_INACTIVE;
+ if (widget && !window->IsTopLevelWidget() &&
+ !(window = window->GetParentWindowBase(false)))
+ return mozilla::widget::themeconst::FS_INACTIVE;
+
+ if (window->GetWindowHandle() == ::GetActiveWindow())
+ return mozilla::widget::themeconst::FS_ACTIVE;
+ return mozilla::widget::themeconst::FS_INACTIVE;
+}
+
+static int32_t GetWindowFrameButtonState(nsIFrame* aFrame,
+ EventStates eventState) {
+ if (GetTopLevelWindowActiveState(aFrame) ==
+ mozilla::widget::themeconst::FS_INACTIVE) {
+ if (eventState.HasState(NS_EVENT_STATE_HOVER))
+ return mozilla::widget::themeconst::BS_HOT;
+ return mozilla::widget::themeconst::BS_INACTIVE;
+ }
+
+ if (eventState.HasState(NS_EVENT_STATE_HOVER)) {
+ if (eventState.HasState(NS_EVENT_STATE_ACTIVE))
+ return mozilla::widget::themeconst::BS_PUSHED;
+ return mozilla::widget::themeconst::BS_HOT;
+ }
+ return mozilla::widget::themeconst::BS_NORMAL;
+}
+
+static int32_t GetClassicWindowFrameButtonState(EventStates eventState) {
+ if (eventState.HasState(NS_EVENT_STATE_ACTIVE) &&
+ eventState.HasState(NS_EVENT_STATE_HOVER))
+ return DFCS_BUTTONPUSH | DFCS_PUSHED;
+ return DFCS_BUTTONPUSH;
+}
+
+static bool IsTopLevelMenu(nsIFrame* aFrame) {
+ bool isTopLevel(false);
+ nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
+ if (menuFrame) {
+ isTopLevel = menuFrame->IsOnMenuBar();
+ }
+ return isTopLevel;
+}
+
+static MARGINS GetCheckboxMargins(HANDLE theme, HDC hdc) {
+ MARGINS checkboxContent = {0};
+ GetThemeMargins(theme, hdc, MENU_POPUPCHECK, MCB_NORMAL, TMT_CONTENTMARGINS,
+ nullptr, &checkboxContent);
+ return checkboxContent;
+}
+
+static SIZE GetCheckboxBGSize(HANDLE theme, HDC hdc) {
+ SIZE checkboxSize;
+ GetThemePartSize(theme, hdc, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, nullptr,
+ TS_TRUE, &checkboxSize);
+
+ MARGINS checkboxMargins = GetCheckboxMargins(theme, hdc);
+
+ int leftMargin = checkboxMargins.cxLeftWidth;
+ int rightMargin = checkboxMargins.cxRightWidth;
+ int topMargin = checkboxMargins.cyTopHeight;
+ int bottomMargin = checkboxMargins.cyBottomHeight;
+
+ int width = leftMargin + checkboxSize.cx + rightMargin;
+ int height = topMargin + checkboxSize.cy + bottomMargin;
+ SIZE ret;
+ ret.cx = width;
+ ret.cy = height;
+ return ret;
+}
+
+static SIZE GetCheckboxBGBounds(HANDLE theme, HDC hdc) {
+ MARGINS checkboxBGSizing = {0};
+ MARGINS checkboxBGContent = {0};
+ GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
+ TMT_SIZINGMARGINS, nullptr, &checkboxBGSizing);
+ GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
+ TMT_CONTENTMARGINS, nullptr, &checkboxBGContent);
+
+#define posdx(d) ((d) > 0 ? d : 0)
+
+ int dx =
+ posdx(checkboxBGContent.cxRightWidth - checkboxBGSizing.cxRightWidth) +
+ posdx(checkboxBGContent.cxLeftWidth - checkboxBGSizing.cxLeftWidth);
+ int dy =
+ posdx(checkboxBGContent.cyTopHeight - checkboxBGSizing.cyTopHeight) +
+ posdx(checkboxBGContent.cyBottomHeight - checkboxBGSizing.cyBottomHeight);
+
+#undef posdx
+
+ SIZE ret(GetCheckboxBGSize(theme, hdc));
+ ret.cx += dx;
+ ret.cy += dy;
+ return ret;
+}
+
+static SIZE GetGutterSize(HANDLE theme, HDC hdc) {
+ SIZE gutterSize;
+ GetThemePartSize(theme, hdc, MENU_POPUPGUTTER, 0, nullptr, TS_TRUE,
+ &gutterSize);
+
+ SIZE checkboxBGSize(GetCheckboxBGBounds(theme, hdc));
+
+ SIZE itemSize;
+ GetThemePartSize(theme, hdc, MENU_POPUPITEM, MPI_NORMAL, nullptr, TS_TRUE,
+ &itemSize);
+
+ // Figure out how big the menuitem's icon will be (if present) at current DPI
+ // Needs the system scale for consistency with Windows Theme API.
+ double scaleFactor = WinUtils::SystemScaleFactor();
+ int iconDevicePixels = NSToIntRound(16 * scaleFactor);
+ SIZE iconSize = {iconDevicePixels, iconDevicePixels};
+ // Not really sure what margins should be used here, but this seems to work in
+ // practice...
+ MARGINS margins = {0};
+ GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
+ TMT_CONTENTMARGINS, nullptr, &margins);
+ iconSize.cx += margins.cxLeftWidth + margins.cxRightWidth;
+ iconSize.cy += margins.cyTopHeight + margins.cyBottomHeight;
+
+ int width = std::max(
+ itemSize.cx, std::max(iconSize.cx, checkboxBGSize.cx) + gutterSize.cx);
+ int height = std::max(itemSize.cy, std::max(iconSize.cy, checkboxBGSize.cy));
+
+ SIZE ret;
+ ret.cx = width;
+ ret.cy = height;
+ return ret;
+}
+
+SIZE nsNativeThemeWin::GetCachedGutterSize(HANDLE theme) {
+ if (mGutterSizeCacheValid) {
+ return mGutterSizeCache;
+ }
+
+ mGutterSizeCache = GetGutterSize(theme, nullptr);
+ mGutterSizeCacheValid = true;
+
+ return mGutterSizeCache;
+}
+
+/* DrawThemeBGRTLAware - render a theme part based on rtl state.
+ * Some widgets are not direction-neutral and need to be drawn reversed for
+ * RTL. Windows provides a way to do this with SetLayout, but this reverses
+ * the entire drawing area of a given device context, which means that its
+ * use will also affect the positioning of the widget. There are two ways
+ * to work around this:
+ *
+ * Option 1: Alter the position of the rect that we send so that we cancel
+ * out the positioning effects of SetLayout
+ * Option 2: Create a memory DC with the widgetRect's dimensions, draw onto
+ * that, and then transfer the results back to our DC
+ *
+ * This function tries to implement option 1, under the assumption that the
+ * correct way to reverse the effects of SetLayout is to translate the rect
+ * such that the offset from the DC bitmap's left edge to the old rect's
+ * left edge is equal to the offset from the DC bitmap's right edge to the
+ * new rect's right edge. In other words,
+ * (oldRect.left + vpOrg.x) == ((dcBMP.width - vpOrg.x) - newRect.right)
+ */
+static HRESULT DrawThemeBGRTLAware(HANDLE aTheme, HDC aHdc, int aPart,
+ int aState, const RECT* aWidgetRect,
+ const RECT* aClipRect, bool aIsRtl) {
+ NS_ASSERTION(aTheme, "Bad theme handle.");
+ NS_ASSERTION(aHdc, "Bad hdc.");
+ NS_ASSERTION(aWidgetRect, "Bad rect.");
+ NS_ASSERTION(aClipRect, "Bad clip rect.");
+
+ if (!aIsRtl) {
+ return DrawThemeBackground(aTheme, aHdc, aPart, aState, aWidgetRect,
+ aClipRect);
+ }
+
+ HGDIOBJ hObj = GetCurrentObject(aHdc, OBJ_BITMAP);
+ BITMAP bitmap;
+ POINT vpOrg;
+
+ if (hObj && GetObject(hObj, sizeof(bitmap), &bitmap) &&
+ GetViewportOrgEx(aHdc, &vpOrg)) {
+ RECT newWRect(*aWidgetRect);
+ newWRect.left = bitmap.bmWidth - (aWidgetRect->right + 2 * vpOrg.x);
+ newWRect.right = bitmap.bmWidth - (aWidgetRect->left + 2 * vpOrg.x);
+
+ RECT newCRect;
+ RECT* newCRectPtr = nullptr;
+
+ if (aClipRect) {
+ newCRect.top = aClipRect->top;
+ newCRect.bottom = aClipRect->bottom;
+ newCRect.left = bitmap.bmWidth - (aClipRect->right + 2 * vpOrg.x);
+ newCRect.right = bitmap.bmWidth - (aClipRect->left + 2 * vpOrg.x);
+ newCRectPtr = &newCRect;
+ }
+
+ SetLayout(aHdc, LAYOUT_RTL);
+ HRESULT hr = DrawThemeBackground(aTheme, aHdc, aPart, aState, &newWRect,
+ newCRectPtr);
+ SetLayout(aHdc, 0);
+ if (SUCCEEDED(hr)) {
+ return hr;
+ }
+ }
+ return DrawThemeBackground(aTheme, aHdc, aPart, aState, aWidgetRect,
+ aClipRect);
+}
+
+/*
+ * Caption button padding data - 'hot' button padding.
+ * These areas are considered hot, in that they activate
+ * a button when hovered or clicked. The button graphic
+ * is drawn inside the padding border. Unrecognized themes
+ * are treated as their recognized counterparts for now.
+ * left top right bottom
+ * classic min 1 2 0 1
+ * classic max 0 2 1 1
+ * classic close 1 2 2 1
+ *
+ * aero basic min 1 2 0 2
+ * aero basic max 0 2 1 2
+ * aero basic close 1 2 1 2
+ *
+ * 'cold' button padding - generic button padding, should
+ * be handled in css.
+ * left top right bottom
+ * classic min 0 0 0 0
+ * classic max 0 0 0 0
+ * classic close 0 0 0 0
+ *
+ * aero basic min 0 0 1 0
+ * aero basic max 1 0 0 0
+ * aero basic close 0 0 0 0
+ */
+
+enum CaptionDesktopTheme {
+ CAPTION_CLASSIC = 0,
+ CAPTION_BASIC,
+};
+
+enum CaptionButton {
+ CAPTIONBUTTON_MINIMIZE = 0,
+ CAPTIONBUTTON_RESTORE,
+ CAPTIONBUTTON_CLOSE,
+};
+
+struct CaptionButtonPadding {
+ RECT hotPadding[3];
+};
+
+// RECT: left, top, right, bottom
+static CaptionButtonPadding buttonData[3] = {
+ {{{1, 2, 0, 1}, {0, 2, 1, 1}, {1, 2, 2, 1}}},
+ {{{1, 2, 0, 2}, {0, 2, 1, 2}, {1, 2, 2, 2}}},
+ {{{0, 2, 0, 2}, {0, 2, 1, 2}, {1, 2, 2, 2}}}};
+
+// Adds "hot" caption button padding to minimum widget size.
+static void AddPaddingRect(LayoutDeviceIntSize* aSize, CaptionButton button) {
+ if (!aSize) return;
+ RECT offset;
+ if (!nsUXThemeData::IsAppThemed())
+ offset = buttonData[CAPTION_CLASSIC].hotPadding[button];
+ else
+ offset = buttonData[CAPTION_BASIC].hotPadding[button];
+ aSize->width += offset.left + offset.right;
+ aSize->height += offset.top + offset.bottom;
+}
+
+// If we've added padding to the minimum widget size, offset
+// the area we draw into to compensate.
+static void OffsetBackgroundRect(RECT& rect, CaptionButton button) {
+ RECT offset;
+ if (!nsUXThemeData::IsAppThemed())
+ offset = buttonData[CAPTION_CLASSIC].hotPadding[button];
+ else
+ offset = buttonData[CAPTION_BASIC].hotPadding[button];
+ rect.left += offset.left;
+ rect.top += offset.top;
+ rect.right -= offset.right;
+ rect.bottom -= offset.bottom;
+}
+
+/*
+ * Notes on progress track and meter part constants:
+ * xp and up:
+ * PP_BAR(_VERT) - base progress track
+ * PP_TRANSPARENTBAR(_VERT) - transparent progress track. this only works if
+ * the underlying surface supports alpha. otherwise
+ * theme lib's DrawThemeBackground falls back on
+ * opaque PP_BAR. we currently don't use this.
+ * PP_CHUNK(_VERT) - xp progress meter. this does not draw an xp style
+ * progress w/chunks, it draws fill using the chunk
+ * graphic.
+ * vista and up:
+ * PP_FILL(_VERT) - progress meter. these have four states/colors.
+ * PP_PULSEOVERLAY(_VERT) - white reflection - an overlay, not sure what this
+ * is used for.
+ * PP_MOVEOVERLAY(_VERT) - green pulse - the pulse effect overlay on
+ * determined progress bars. we also use this for
+ * indeterminate chunk.
+ *
+ * Notes on state constants:
+ * PBBS_NORMAL - green progress
+ * PBBVS_PARTIAL/PBFVS_ERROR - red error progress
+ * PBFS_PAUSED - yellow paused progress
+ *
+ * There is no common controls style indeterminate part on vista and up.
+ */
+
+/*
+ * Progress bar related constants. These values are found by experimenting and
+ * comparing against native widgets used by the system. They are very unlikely
+ * exact but try to not be too wrong.
+ */
+// The amount of time we animate progress meters parts across the frame.
+static const double kProgressDeterminateTimeSpan = 3.0;
+static const double kProgressIndeterminateTimeSpan = 5.0;
+// The width of the overlay used to animate the horizontal progress bar (Vista
+// and later).
+static const int32_t kProgressHorizontalOverlaySize = 120;
+// The height of the overlay used to animate the vertical progress bar (Vista
+// and later).
+static const int32_t kProgressVerticalOverlaySize = 45;
+// The height of the overlay used for the vertical indeterminate progress bar
+// (Vista and later).
+static const int32_t kProgressVerticalIndeterminateOverlaySize = 60;
+// The width of the overlay used to animate the indeterminate progress bar
+// (Windows Classic).
+static const int32_t kProgressClassicOverlaySize = 40;
+
+/*
+ * GetProgressOverlayStyle - returns the proper overlay part for themed
+ * progress bars based on os and orientation.
+ */
+static int32_t GetProgressOverlayStyle(bool aIsVertical) {
+ return aIsVertical ? PP_MOVEOVERLAYVERT : PP_MOVEOVERLAY;
+}
+
+/*
+ * GetProgressOverlaySize - returns the minimum width or height for themed
+ * progress bar overlays. This includes the width of indeterminate chunks
+ * and vista pulse overlays.
+ */
+static int32_t GetProgressOverlaySize(bool aIsVertical, bool aIsIndeterminate) {
+ if (aIsVertical) {
+ return aIsIndeterminate ? kProgressVerticalIndeterminateOverlaySize
+ : kProgressVerticalOverlaySize;
+ }
+ return kProgressHorizontalOverlaySize;
+}
+
+/*
+ * IsProgressMeterFilled - Determines if a progress meter is at 100% fill based
+ * on a comparison of the current value and maximum.
+ */
+static bool IsProgressMeterFilled(nsIFrame* aFrame) {
+ NS_ENSURE_TRUE(aFrame, false);
+ nsIFrame* parentFrame = aFrame->GetParent();
+ NS_ENSURE_TRUE(parentFrame, false);
+ return nsNativeTheme::GetProgressValue(parentFrame) ==
+ nsNativeTheme::GetProgressMaxValue(parentFrame);
+}
+
+/*
+ * CalculateProgressOverlayRect - returns the padded overlay animation rect
+ * used in rendering progress bars. Resulting rects are used in rendering
+ * vista+ pulse overlays and indeterminate progress meters. Graphics should
+ * be rendered at the origin.
+ */
+RECT nsNativeThemeWin::CalculateProgressOverlayRect(nsIFrame* aFrame,
+ RECT* aWidgetRect,
+ bool aIsVertical,
+ bool aIsIndeterminate,
+ bool aIsClassic) {
+ NS_ASSERTION(aFrame, "bad frame pointer");
+ NS_ASSERTION(aWidgetRect, "bad rect pointer");
+
+ int32_t frameSize = aIsVertical ? aWidgetRect->bottom - aWidgetRect->top
+ : aWidgetRect->right - aWidgetRect->left;
+
+ // Recycle a set of progress pulse timers - these timers control the position
+ // of all progress overlays and indeterminate chunks that get rendered.
+ double span = aIsIndeterminate ? kProgressIndeterminateTimeSpan
+ : kProgressDeterminateTimeSpan;
+ TimeDuration period;
+ if (!aIsIndeterminate) {
+ if (TimeStamp::Now() >
+ (mProgressDeterminateTimeStamp + TimeDuration::FromSeconds(span))) {
+ mProgressDeterminateTimeStamp = TimeStamp::Now();
+ }
+ period = TimeStamp::Now() - mProgressDeterminateTimeStamp;
+ } else {
+ if (TimeStamp::Now() >
+ (mProgressIndeterminateTimeStamp + TimeDuration::FromSeconds(span))) {
+ mProgressIndeterminateTimeStamp = TimeStamp::Now();
+ }
+ period = TimeStamp::Now() - mProgressIndeterminateTimeStamp;
+ }
+
+ double percent = period / TimeDuration::FromSeconds(span);
+
+ if (!aIsVertical && IsFrameRTL(aFrame)) percent = 1 - percent;
+
+ RECT overlayRect = *aWidgetRect;
+ int32_t overlaySize;
+ if (!aIsClassic) {
+ overlaySize = GetProgressOverlaySize(aIsVertical, aIsIndeterminate);
+ } else {
+ overlaySize = kProgressClassicOverlaySize;
+ }
+
+ // Calculate a bounds that is larger than the meters frame such that the
+ // overlay starts and ends completely off the edge of the frame:
+ // [overlay][frame][overlay]
+ // This also yields a nice delay on rotation. Use overlaySize as the minimum
+ // size for [overlay] based on the graphics dims. If [frame] is larger, use
+ // the frame size instead.
+ int trackWidth = frameSize > overlaySize ? frameSize : overlaySize;
+ if (!aIsVertical) {
+ int xPos = aWidgetRect->left - trackWidth;
+ xPos += (int)ceil(((double)(trackWidth * 2) * percent));
+ overlayRect.left = xPos;
+ overlayRect.right = xPos + overlaySize;
+ } else {
+ int yPos = aWidgetRect->bottom + trackWidth;
+ yPos -= (int)ceil(((double)(trackWidth * 2) * percent));
+ overlayRect.bottom = yPos;
+ overlayRect.top = yPos - overlaySize;
+ }
+ return overlayRect;
+}
+
+/*
+ * DrawProgressMeter - render an appropriate progress meter based on progress
+ * meter style, orientation, and os. Note, this does not render the underlying
+ * progress track.
+ *
+ * @param aFrame the widget frame
+ * @param aAppearance type of widget
+ * @param aTheme progress theme handle
+ * @param aHdc hdc returned by gfxWindowsNativeDrawing
+ * @param aPart the PP_X progress part
+ * @param aState the theme state
+ * @param aWidgetRect bounding rect for the widget
+ * @param aClipRect dirty rect that needs drawing.
+ * @param aAppUnits app units per device pixel
+ */
+void nsNativeThemeWin::DrawThemedProgressMeter(
+ nsIFrame* aFrame, StyleAppearance aAppearance, HANDLE aTheme, HDC aHdc,
+ int aPart, int aState, RECT* aWidgetRect, RECT* aClipRect) {
+ if (!aFrame || !aTheme || !aHdc) return;
+
+ NS_ASSERTION(aWidgetRect, "bad rect pointer");
+ NS_ASSERTION(aClipRect, "bad clip rect pointer");
+
+ RECT adjWidgetRect, adjClipRect;
+ adjWidgetRect = *aWidgetRect;
+ adjClipRect = *aClipRect;
+
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (!parentFrame) {
+ // We have no parent to work with, just bail.
+ NS_WARNING("No parent frame for progress rendering. Can't paint.");
+ return;
+ }
+
+ EventStates eventStates = GetContentState(parentFrame, aAppearance);
+ bool vertical = IsVerticalProgress(parentFrame);
+ bool indeterminate = IsIndeterminateProgress(parentFrame, eventStates);
+ bool animate = indeterminate;
+
+ // Vista and up progress meter is fill style, rendered here. We render
+ // the pulse overlay in the follow up section below.
+ DrawThemeBackground(aTheme, aHdc, aPart, aState, &adjWidgetRect,
+ &adjClipRect);
+ if (!IsProgressMeterFilled(aFrame)) {
+ animate = true;
+ }
+
+ if (animate) {
+ // Indeterminate rendering
+ int32_t overlayPart = GetProgressOverlayStyle(vertical);
+ RECT overlayRect = CalculateProgressOverlayRect(
+ aFrame, &adjWidgetRect, vertical, indeterminate, false);
+ DrawThemeBackground(aTheme, aHdc, overlayPart, aState, &overlayRect,
+ &adjClipRect);
+
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
+ NS_WARNING("unable to animate progress widget!");
+ }
+ }
+}
+
+LayoutDeviceIntMargin nsNativeThemeWin::GetCachedWidgetBorder(
+ HTHEME aTheme, nsUXThemeClass aThemeClass, StyleAppearance aAppearance,
+ int32_t aPart, int32_t aState) {
+ int32_t cacheIndex = aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + aPart;
+ int32_t cacheBitIndex = cacheIndex / 8;
+ uint8_t cacheBit = 1u << (cacheIndex % 8);
+
+ if (mBorderCacheValid[cacheBitIndex] & cacheBit) {
+ return mBorderCache[cacheIndex];
+ }
+
+ // Get our info.
+ RECT outerRect; // Create a fake outer rect.
+ outerRect.top = outerRect.left = 100;
+ outerRect.right = outerRect.bottom = 200;
+ RECT contentRect(outerRect);
+ HRESULT res = GetThemeBackgroundContentRect(aTheme, nullptr, aPart, aState,
+ &outerRect, &contentRect);
+
+ if (FAILED(res)) {
+ return LayoutDeviceIntMargin();
+ }
+
+ // Now compute the delta in each direction and place it in our
+ // nsIntMargin struct.
+ LayoutDeviceIntMargin result;
+ result.top = contentRect.top - outerRect.top;
+ result.bottom = outerRect.bottom - contentRect.bottom;
+ result.left = contentRect.left - outerRect.left;
+ result.right = outerRect.right - contentRect.right;
+
+ mBorderCacheValid[cacheBitIndex] |= cacheBit;
+ mBorderCache[cacheIndex] = result;
+
+ return result;
+}
+
+nsresult nsNativeThemeWin::GetCachedMinimumWidgetSize(
+ nsIFrame* aFrame, HANDLE aTheme, nsUXThemeClass aThemeClass,
+ StyleAppearance aAppearance, int32_t aPart, int32_t aState,
+ THEMESIZE aSizeReq, mozilla::LayoutDeviceIntSize* aResult) {
+ int32_t cachePart = aPart;
+
+ if (aAppearance == StyleAppearance::Button && aSizeReq == TS_MIN) {
+ // In practice, StyleAppearance::Button is the only widget type which has an
+ // aSizeReq that varies for us, and it can only be TS_MIN or TS_TRUE. Just
+ // stuff that extra bit into the aPart part of the cache, since BP_Count is
+ // well below THEME_PART_DISTINCT_VALUE_COUNT anyway.
+ cachePart = BP_Count;
+ }
+
+ MOZ_ASSERT(aPart < THEME_PART_DISTINCT_VALUE_COUNT);
+ int32_t cacheIndex =
+ aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + cachePart;
+ int32_t cacheBitIndex = cacheIndex / 8;
+ uint8_t cacheBit = 1u << (cacheIndex % 8);
+
+ if (mMinimumWidgetSizeCacheValid[cacheBitIndex] & cacheBit) {
+ *aResult = mMinimumWidgetSizeCache[cacheIndex];
+ return NS_OK;
+ }
+
+ HDC hdc = ::GetDC(NULL);
+ if (!hdc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SIZE sz;
+ GetThemePartSize(aTheme, hdc, aPart, aState, nullptr, aSizeReq, &sz);
+ aResult->width = sz.cx;
+ aResult->height = sz.cy;
+
+ switch (aAppearance) {
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ aResult->width++;
+ aResult->height = aResult->height / 2 + 1;
+ break;
+
+ case StyleAppearance::Menuseparator: {
+ SIZE gutterSize(GetGutterSize(aTheme, hdc));
+ aResult->width += gutterSize.cx;
+ break;
+ }
+
+ case StyleAppearance::Menuarrow:
+ // Use the width of the arrow glyph as padding. See the drawing
+ // code for details.
+ aResult->width *= 2;
+ break;
+
+ default:
+ break;
+ }
+
+ ::ReleaseDC(nullptr, hdc);
+
+ mMinimumWidgetSizeCacheValid[cacheBitIndex] |= cacheBit;
+ mMinimumWidgetSizeCache[cacheIndex] = *aResult;
+
+ return NS_OK;
+}
+
+mozilla::Maybe<nsUXThemeClass> nsNativeThemeWin::GetThemeClass(
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Groupbox:
+ return Some(eUXButton);
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::FocusOutline:
+ return Some(eUXEdit);
+ case StyleAppearance::Tooltip:
+ return Some(eUXTooltip);
+ case StyleAppearance::Toolbox:
+ return Some(eUXRebar);
+ case StyleAppearance::MozWinMediaToolbox:
+ return Some(eUXMediaRebar);
+ case StyleAppearance::MozWinCommunicationsToolbox:
+ return Some(eUXCommunicationsRebar);
+ case StyleAppearance::MozWinBrowsertabbarToolbox:
+ return Some(eUXBrowserTabBarRebar);
+ case StyleAppearance::Toolbar:
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::Separator:
+ return Some(eUXToolbar);
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Progresschunk:
+ return Some(eUXProgress);
+ case StyleAppearance::Tab:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Tabpanels:
+ return Some(eUXTab);
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::Scrollcorner:
+ return Some(eUXScrollbar);
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ return Some(eUXTrackbar);
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ return Some(eUXSpin);
+ case StyleAppearance::Statusbar:
+ case StyleAppearance::Statusbarpanel:
+ case StyleAppearance::Resizerpanel:
+ case StyleAppearance::Resizer:
+ return Some(eUXStatus);
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::MozMenulistArrowButton:
+ return Some(eUXCombobox);
+ case StyleAppearance::Treeheadercell:
+ case StyleAppearance::Treeheadersortarrow:
+ return Some(eUXHeader);
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Treetwistyopen:
+ case StyleAppearance::Treeitem:
+ return Some(eUXListview);
+ case StyleAppearance::Menubar:
+ case StyleAppearance::Menupopup:
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem:
+ case StyleAppearance::Menucheckbox:
+ case StyleAppearance::Menuradio:
+ case StyleAppearance::Menuseparator:
+ case StyleAppearance::Menuarrow:
+ case StyleAppearance::Menuimage:
+ case StyleAppearance::Menuitemtext:
+ return Some(eUXMenu);
+ case StyleAppearance::MozWindowTitlebar:
+ case StyleAppearance::MozWindowTitlebarMaximized:
+ case StyleAppearance::MozWindowFrameLeft:
+ case StyleAppearance::MozWindowFrameRight:
+ case StyleAppearance::MozWindowFrameBottom:
+ case StyleAppearance::MozWindowButtonClose:
+ case StyleAppearance::MozWindowButtonMinimize:
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore:
+ case StyleAppearance::MozWindowButtonBox:
+ case StyleAppearance::MozWindowButtonBoxMaximized:
+ case StyleAppearance::MozWinGlass:
+ case StyleAppearance::MozWinBorderlessGlass:
+ return Some(eUXWindowFrame);
+ default:
+ return Nothing();
+ }
+}
+
+HANDLE
+nsNativeThemeWin::GetTheme(StyleAppearance aAppearance) {
+ mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
+ if (themeClass.isNothing()) {
+ return nullptr;
+ }
+ return nsUXThemeData::GetTheme(themeClass.value());
+}
+
+int32_t nsNativeThemeWin::StandardGetState(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ bool wantFocused) {
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+ if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) {
+ return TS_ACTIVE;
+ }
+ if (eventState.HasState(NS_EVENT_STATE_HOVER)) {
+ return TS_HOVER;
+ }
+ if (wantFocused) {
+ if (eventState.HasState(NS_EVENT_STATE_FOCUSRING)) {
+ return TS_FOCUSED;
+ }
+ // On Windows, focused buttons are always drawn as such by the native
+ // theme, that's why we check NS_EVENT_STATE_FOCUS instead of
+ // NS_EVENT_STATE_FOCUSRING.
+ if (aAppearance == StyleAppearance::Button &&
+ eventState.HasState(NS_EVENT_STATE_FOCUS)) {
+ return TS_FOCUSED;
+ }
+ }
+
+ return TS_NORMAL;
+}
+
+bool nsNativeThemeWin::IsMenuActive(nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ nsIContent* content = aFrame->GetContent();
+ if (content->IsXULElement() &&
+ content->NodeInfo()->Equals(nsGkAtoms::richlistitem))
+ return CheckBooleanAttr(aFrame, nsGkAtoms::selected);
+
+ return CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+}
+
+/**
+ * aPart is filled in with the UXTheme part code. On return, values > 0
+ * are the actual UXTheme part code; -1 means the widget will be drawn by
+ * us; 0 means that we should use part code 0, which isn't a real part code
+ * but elicits some kind of default behaviour from UXTheme when drawing
+ * (but isThemeBackgroundPartiallyTransparent may not work).
+ */
+nsresult nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ int32_t& aPart,
+ int32_t& aState) {
+ switch (aAppearance) {
+ case StyleAppearance::Button: {
+ aPart = BP_BUTTON;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+ if (IsDisabled(aFrame, eventState)) {
+ aState = TS_DISABLED;
+ return NS_OK;
+ } else if (IsOpenButton(aFrame) || IsCheckedButton(aFrame)) {
+ aState = TS_ACTIVE;
+ return NS_OK;
+ }
+
+ aState = StandardGetState(aFrame, aAppearance, true);
+
+ // Check for default dialog buttons. These buttons should always look
+ // focused.
+ if (aState == TS_NORMAL && IsDefaultButton(aFrame)) aState = TS_FOCUSED;
+ return NS_OK;
+ }
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio: {
+ bool isCheckbox = (aAppearance == StyleAppearance::Checkbox);
+ aPart = isCheckbox ? BP_CHECKBOX : BP_RADIO;
+
+ enum InputState { UNCHECKED = 0, CHECKED, INDETERMINATE };
+ InputState inputState = UNCHECKED;
+
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ } else {
+ if (GetCheckedOrSelected(aFrame, !isCheckbox)) {
+ inputState = CHECKED;
+ }
+ if (isCheckbox && GetIndeterminate(aFrame)) {
+ inputState = INDETERMINATE;
+ }
+
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+ if (IsDisabled(aFrame, eventState)) {
+ aState = TS_DISABLED;
+ } else {
+ aState = StandardGetState(aFrame, aAppearance, false);
+ }
+ }
+
+ // 4 unchecked states, 4 checked states, 4 indeterminate states.
+ aState += inputState * 4;
+ return NS_OK;
+ }
+ case StyleAppearance::Groupbox: {
+ aPart = BP_GROUPBOX;
+ aState = TS_NORMAL;
+ // Since we don't support groupbox disabled and GBS_DISABLED looks the
+ // same as GBS_NORMAL don't bother supporting GBS_DISABLED.
+ return NS_OK;
+ }
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea: {
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+
+ /* Note: the NOSCROLL type has a rounded corner in each corner. The more
+ * specific HSCROLL, VSCROLL, HVSCROLL types have side and/or top/bottom
+ * edges rendered as straight horizontal lines with sharp corners to
+ * accommodate a scrollbar. However, the scrollbar gets rendered on top
+ * of this for us, so we don't care, and can just use NOSCROLL here.
+ */
+ aPart = TFP_EDITBORDER_NOSCROLL;
+
+ if (!aFrame) {
+ aState = TFS_EDITBORDER_NORMAL;
+ } else if (IsDisabled(aFrame, eventState)) {
+ aState = TFS_EDITBORDER_DISABLED;
+ } else if (IsReadOnly(aFrame)) {
+ /* no special read-only state */
+ aState = TFS_EDITBORDER_NORMAL;
+ } else {
+ nsIContent* content = aFrame->GetContent();
+
+ /* XUL textboxes don't get focused themselves, because they have child
+ * html:input.. but we can check the XUL focused attributes on them
+ */
+ if (content && content->IsXULElement() && IsFocused(aFrame))
+ aState = TFS_EDITBORDER_FOCUSED;
+ else if (eventState.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE |
+ NS_EVENT_STATE_FOCUSRING))
+ aState = TFS_EDITBORDER_FOCUSED;
+ else if (eventState.HasState(NS_EVENT_STATE_HOVER))
+ aState = TFS_EDITBORDER_HOVER;
+ else
+ aState = TFS_EDITBORDER_NORMAL;
+ }
+
+ return NS_OK;
+ }
+ case StyleAppearance::FocusOutline: {
+ // XXX the EDITBORDER values don't respect DTBG_OMITCONTENT
+ aPart = TFP_TEXTFIELD; // TFP_EDITBORDER_NOSCROLL;
+ aState = TS_FOCUSED; // TFS_EDITBORDER_FOCUSED;
+ return NS_OK;
+ }
+ case StyleAppearance::Tooltip: {
+ aPart = TTP_STANDARD;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::ProgressBar: {
+ bool vertical = IsVerticalProgress(aFrame);
+ aPart = vertical ? PP_BARVERT : PP_BAR;
+ aState = PBBS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Progresschunk: {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (IsVerticalProgress(parentFrame)) {
+ aPart = PP_FILLVERT;
+ } else {
+ aPart = PP_FILL;
+ }
+
+ aState = PBBVS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Toolbarbutton: {
+ aPart = BP_BUTTON;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+ if (IsDisabled(aFrame, eventState)) {
+ aState = TS_DISABLED;
+ return NS_OK;
+ }
+ if (IsOpenButton(aFrame)) {
+ aState = TS_ACTIVE;
+ return NS_OK;
+ }
+
+ if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE))
+ aState = TS_ACTIVE;
+ else if (eventState.HasState(NS_EVENT_STATE_HOVER)) {
+ if (IsCheckedButton(aFrame))
+ aState = TB_HOVER_CHECKED;
+ else
+ aState = TS_HOVER;
+ } else {
+ if (IsCheckedButton(aFrame))
+ aState = TB_CHECKED;
+ else
+ aState = TS_NORMAL;
+ }
+
+ return NS_OK;
+ }
+ case StyleAppearance::Separator: {
+ aPart = TP_SEPARATOR;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight: {
+ aPart = SP_BUTTON;
+ aState = (int(aAppearance) - int(StyleAppearance::ScrollbarbuttonUp)) * 4;
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+ if (!aFrame)
+ aState += TS_NORMAL;
+ else if (IsDisabled(aFrame, eventState))
+ aState += TS_DISABLED;
+ else {
+ nsIFrame* parent = aFrame->GetParent();
+ EventStates parentState = GetContentState(
+ parent, parent->StyleDisplay()->EffectiveAppearance());
+ if (eventState.HasAllStates(NS_EVENT_STATE_HOVER |
+ NS_EVENT_STATE_ACTIVE))
+ aState += TS_ACTIVE;
+ else if (eventState.HasState(NS_EVENT_STATE_HOVER))
+ aState += TS_HOVER;
+ else if (parentState.HasState(NS_EVENT_STATE_HOVER))
+ aState =
+ (int(aAppearance) - int(StyleAppearance::ScrollbarbuttonUp)) +
+ SP_BUTTON_IMPLICIT_HOVER_BASE;
+ else
+ aState += TS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical: {
+ aPart = (aAppearance == StyleAppearance::ScrollbarHorizontal)
+ ? SP_TRACKSTARTHOR
+ : SP_TRACKSTARTVERT;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarthumbVertical: {
+ aPart = (aAppearance == StyleAppearance::ScrollbarthumbHorizontal)
+ ? SP_THUMBHOR
+ : SP_THUMBVERT;
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+ if (!aFrame)
+ aState = TS_NORMAL;
+ else if (IsDisabled(aFrame, eventState))
+ aState = TS_DISABLED;
+ else {
+ if (eventState.HasState(
+ NS_EVENT_STATE_ACTIVE)) // Hover is not also a requirement for
+ // the thumb, since the drag is not
+ // canceled when you move outside the
+ // thumb.
+ aState = TS_ACTIVE;
+ else if (eventState.HasState(NS_EVENT_STATE_HOVER))
+ aState = TS_HOVER;
+ else
+ aState = TS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case StyleAppearance::Range: {
+ if (IsRangeHorizontal(aFrame)) {
+ aPart = TKP_TRACK;
+ aState = TRS_NORMAL;
+ } else {
+ aPart = TKP_TRACKVERT;
+ aState = TRVS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case StyleAppearance::RangeThumb: {
+ if (IsRangeHorizontal(aFrame)) {
+ aPart = TKP_THUMBBOTTOM;
+ } else {
+ aPart = IsFrameRTL(aFrame) ? TKP_THUMBLEFT : TKP_THUMBRIGHT;
+ }
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+ if (!aFrame)
+ aState = TS_NORMAL;
+ else if (IsDisabled(aFrame, eventState)) {
+ aState = TKP_DISABLED;
+ } else {
+ if (eventState.HasState(
+ NS_EVENT_STATE_ACTIVE)) // Hover is not also a requirement for
+ // the thumb, since the drag is not
+ // canceled when you move outside the
+ // thumb.
+ aState = TS_ACTIVE;
+ else if (eventState.HasState(NS_EVENT_STATE_FOCUSRING))
+ aState = TKP_FOCUSED;
+ else if (eventState.HasState(NS_EVENT_STATE_HOVER))
+ aState = TS_HOVER;
+ else
+ aState = TS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton: {
+ aPart = (aAppearance == StyleAppearance::SpinnerUpbutton) ? SPNP_UP
+ : SPNP_DOWN;
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+ if (!aFrame)
+ aState = TS_NORMAL;
+ else if (IsDisabled(aFrame, eventState))
+ aState = TS_DISABLED;
+ else
+ aState = StandardGetState(aFrame, aAppearance, false);
+ return NS_OK;
+ }
+ case StyleAppearance::Toolbox:
+ case StyleAppearance::MozWinMediaToolbox:
+ case StyleAppearance::MozWinCommunicationsToolbox:
+ case StyleAppearance::MozWinBrowsertabbarToolbox:
+ case StyleAppearance::Statusbar:
+ case StyleAppearance::Scrollcorner: {
+ aState = 0;
+ aPart = RP_BACKGROUND;
+ return NS_OK;
+ }
+ case StyleAppearance::Toolbar: {
+ // Use -1 to indicate we don't wish to have the theme background drawn
+ // for this item. We will pass any nessessary information via aState,
+ // and will render the item using separate code.
+ aPart = -1;
+ aState = 0;
+ if (aFrame) {
+ nsIContent* content = aFrame->GetContent();
+ nsIContent* parent = content->GetParent();
+ // XXXzeniko hiding the first toolbar will result in an unwanted margin
+ if (parent && parent->GetFirstChild() == content) {
+ aState = 1;
+ }
+ }
+ return NS_OK;
+ }
+ case StyleAppearance::Statusbarpanel:
+ case StyleAppearance::Resizerpanel:
+ case StyleAppearance::Resizer: {
+ switch (aAppearance) {
+ case StyleAppearance::Statusbarpanel:
+ aPart = 1;
+ break;
+ case StyleAppearance::Resizerpanel:
+ aPart = 2;
+ break;
+ case StyleAppearance::Resizer:
+ aPart = 3;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Oops, we're missing a case");
+ aPart = 1; // just something valid
+ }
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Listbox: {
+ aPart = TREEVIEW_BODY;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Tabpanels: {
+ aPart = TABP_PANELS;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Tabpanel: {
+ aPart = TABP_PANEL;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Tab: {
+ aPart = TABP_TAB;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+ if (IsDisabled(aFrame, eventState)) {
+ aState = TS_DISABLED;
+ return NS_OK;
+ }
+
+ if (IsSelectedTab(aFrame)) {
+ aPart = TABP_TAB_SELECTED;
+ aState = TS_ACTIVE; // The selected tab is always "pressed".
+ } else
+ aState = StandardGetState(aFrame, aAppearance, true);
+
+ return NS_OK;
+ }
+ case StyleAppearance::Treeheadersortarrow: {
+ // XXX Probably will never work due to a bug in the Luna theme.
+ aPart = 4;
+ aState = 1;
+ return NS_OK;
+ }
+ case StyleAppearance::Treeheadercell: {
+ aPart = 1;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ aState = StandardGetState(aFrame, aAppearance, true);
+
+ return NS_OK;
+ }
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Menulist: {
+ nsIContent* content = aFrame->GetContent();
+ bool useDropBorder = content && content->IsHTMLElement();
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+
+ /* On Vista/Win7, we use CBP_DROPBORDER instead of DROPFRAME for HTML
+ * content or for editable menulists; this gives us the thin outline,
+ * instead of the gradient-filled background */
+ if (useDropBorder)
+ aPart = CBP_DROPBORDER;
+ else
+ aPart = CBP_DROPFRAME;
+
+ if (IsDisabled(aFrame, eventState)) {
+ aState = TS_DISABLED;
+ } else if (IsReadOnly(aFrame)) {
+ aState = TS_NORMAL;
+ } else if (IsOpenButton(aFrame)) {
+ aState = TS_ACTIVE;
+ } else {
+ if (useDropBorder && (eventState.HasState(NS_EVENT_STATE_FOCUSRING) ||
+ IsFocused(aFrame)))
+ aState = TS_ACTIVE;
+ else if (eventState.HasAllStates(NS_EVENT_STATE_HOVER |
+ NS_EVENT_STATE_ACTIVE))
+ aState = TS_ACTIVE;
+ else if (eventState.HasState(NS_EVENT_STATE_HOVER))
+ aState = TS_HOVER;
+ else
+ aState = TS_NORMAL;
+ }
+
+ return NS_OK;
+ }
+ case StyleAppearance::MozMenulistArrowButton: {
+ bool isHTML = IsHTMLContent(aFrame);
+ nsIFrame* parentFrame = aFrame->GetParent();
+ bool isMenulist = !isHTML && parentFrame->IsMenuFrame();
+ bool isOpen = false;
+
+ // HTML select and XUL menulist dropdown buttons get state from the
+ // parent.
+ if (isHTML || isMenulist) aFrame = parentFrame;
+
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+ aPart = CBP_DROPMARKER_VISTA;
+
+ // For HTML controls with author styling, we should fall
+ // back to the old dropmarker style to avoid clashes with
+ // author-specified backgrounds and borders (bug #441034)
+ if (isHTML && IsWidgetStyled(aFrame->PresContext(), aFrame,
+ StyleAppearance::Menulist))
+ aPart = CBP_DROPMARKER;
+
+ if (IsDisabled(aFrame, eventState)) {
+ aState = TS_DISABLED;
+ return NS_OK;
+ }
+
+ if (isHTML) {
+ nsComboboxControlFrame* ccf = do_QueryFrame(aFrame);
+ isOpen = (ccf && ccf->IsDroppedDownOrHasParentPopup());
+ } else
+ isOpen = IsOpenButton(aFrame);
+
+ if (isHTML) {
+ if (isOpen) {
+ /* Hover is propagated, but we need to know whether we're hovering
+ * just the combobox frame, not the dropdown frame. But, we can't get
+ * that information, since hover is on the content node, and they
+ * share the same content node. So, instead, we cheat -- if the
+ * dropdown is open, we always show the hover state. This looks fine
+ * in practice.
+ */
+ aState = TS_HOVER;
+ return NS_OK;
+ }
+ } else {
+ /* The dropdown indicator on a menulist button in chrome is not given a
+ * hover effect. When the frame isn't isn't HTML content, we cheat and
+ * force the dropdown state to be normal. (Bug 430434)
+ */
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ aState = TS_NORMAL;
+
+ // Dropdown button active state doesn't need :hover.
+ if (eventState.HasState(NS_EVENT_STATE_ACTIVE)) {
+ if (isOpen && (isHTML || isMenulist)) {
+ // XXX Button should look active until the mouse is released, but
+ // without making it look active when the popup is clicked.
+ return NS_OK;
+ }
+ aState = TS_ACTIVE;
+ } else if (eventState.HasState(NS_EVENT_STATE_HOVER)) {
+ // No hover effect for XUL menulists and autocomplete dropdown buttons
+ // while the dropdown menu is open.
+ if (isOpen) {
+ // XXX HTML select dropdown buttons should have the hover effect when
+ // hovering the combobox frame, but not the popup frame.
+ return NS_OK;
+ }
+ aState = TS_HOVER;
+ }
+ return NS_OK;
+ }
+ case StyleAppearance::Menupopup: {
+ aPart = MENU_POPUPBACKGROUND;
+ aState = MB_ACTIVE;
+ return NS_OK;
+ }
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem: {
+ bool isTopLevel = false;
+ bool isOpen = false;
+ bool isHover = false;
+ nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+
+ isTopLevel = IsTopLevelMenu(aFrame);
+
+ if (menuFrame) isOpen = menuFrame->IsOpen();
+
+ isHover = IsMenuActive(aFrame, aAppearance);
+
+ if (isTopLevel) {
+ aPart = MENU_BARITEM;
+
+ if (isOpen)
+ aState = MBI_PUSHED;
+ else if (isHover)
+ aState = MBI_HOT;
+ else
+ aState = MBI_NORMAL;
+
+ // the disabled states are offset by 3
+ if (IsDisabled(aFrame, eventState)) aState += 3;
+ } else {
+ aPart = MENU_POPUPITEM;
+
+ if (isHover)
+ aState = MPI_HOT;
+ else
+ aState = MPI_NORMAL;
+
+ // the disabled states are offset by 2
+ if (IsDisabled(aFrame, eventState)) aState += 2;
+ }
+
+ return NS_OK;
+ }
+ case StyleAppearance::Menuseparator:
+ aPart = MENU_POPUPSEPARATOR;
+ aState = 0;
+ return NS_OK;
+ case StyleAppearance::Menuarrow: {
+ aPart = MENU_POPUPSUBMENU;
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+ aState = IsDisabled(aFrame, eventState) ? MSM_DISABLED : MSM_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Menucheckbox:
+ case StyleAppearance::Menuradio: {
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+
+ aPart = MENU_POPUPCHECK;
+ aState = MC_CHECKMARKNORMAL;
+
+ // Radio states are offset by 2
+ if (aAppearance == StyleAppearance::Menuradio) aState += 2;
+
+ // the disabled states are offset by 1
+ if (IsDisabled(aFrame, eventState)) aState += 1;
+
+ return NS_OK;
+ }
+ case StyleAppearance::Menuitemtext:
+ case StyleAppearance::Menuimage:
+ aPart = -1;
+ aState = 0;
+ return NS_OK;
+
+ case StyleAppearance::MozWindowTitlebar:
+ aPart = mozilla::widget::themeconst::WP_CAPTION;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case StyleAppearance::MozWindowTitlebarMaximized:
+ aPart = mozilla::widget::themeconst::WP_MAXCAPTION;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case StyleAppearance::MozWindowFrameLeft:
+ aPart = mozilla::widget::themeconst::WP_FRAMELEFT;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case StyleAppearance::MozWindowFrameRight:
+ aPart = mozilla::widget::themeconst::WP_FRAMERIGHT;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case StyleAppearance::MozWindowFrameBottom:
+ aPart = mozilla::widget::themeconst::WP_FRAMEBOTTOM;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case StyleAppearance::MozWindowButtonClose:
+ aPart = mozilla::widget::themeconst::WP_CLOSEBUTTON;
+ aState = GetWindowFrameButtonState(aFrame,
+ GetContentState(aFrame, aAppearance));
+ return NS_OK;
+ case StyleAppearance::MozWindowButtonMinimize:
+ aPart = mozilla::widget::themeconst::WP_MINBUTTON;
+ aState = GetWindowFrameButtonState(aFrame,
+ GetContentState(aFrame, aAppearance));
+ return NS_OK;
+ case StyleAppearance::MozWindowButtonMaximize:
+ aPart = mozilla::widget::themeconst::WP_MAXBUTTON;
+ aState = GetWindowFrameButtonState(aFrame,
+ GetContentState(aFrame, aAppearance));
+ return NS_OK;
+ case StyleAppearance::MozWindowButtonRestore:
+ aPart = mozilla::widget::themeconst::WP_RESTOREBUTTON;
+ aState = GetWindowFrameButtonState(aFrame,
+ GetContentState(aFrame, aAppearance));
+ return NS_OK;
+ case StyleAppearance::MozWindowButtonBox:
+ case StyleAppearance::MozWindowButtonBoxMaximized:
+ case StyleAppearance::MozWinGlass:
+ case StyleAppearance::MozWinBorderlessGlass:
+ aPart = -1;
+ aState = 0;
+ return NS_OK;
+ default:
+ aPart = 0;
+ aState = 0;
+ return NS_ERROR_FAILURE;
+ }
+}
+
+static bool AssumeThemePartAndStateAreTransparent(int32_t aPart,
+ int32_t aState) {
+ if (!(IsWin8Point1OrLater() && nsUXThemeData::IsHighContrastOn()) &&
+ aPart == MENU_POPUPITEM && aState == MBI_NORMAL) {
+ return true;
+ }
+ return false;
+}
+
+// When running with per-monitor DPI (on Win8.1+), and rendering on a display
+// with a different DPI setting from the system's default scaling, we need to
+// apply scaling to native-themed elements as the Windows theme APIs assume
+// the system default resolution.
+static inline double GetThemeDpiScaleFactor(nsIFrame* aFrame) {
+ if (WinUtils::IsPerMonitorDPIAware() ||
+ StaticPrefs::layout_css_devPixelsPerPx() > 0.0) {
+ nsIWidget* rootWidget = aFrame->PresContext()->GetRootWidget();
+ if (rootWidget) {
+ double systemScale = WinUtils::SystemScaleFactor();
+ return rootWidget->GetDefaultScale().scale / systemScale;
+ }
+ }
+ return 1.0;
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) {
+ if (IsWidgetScrollbarPart(aAppearance)) {
+ if (MayDrawCustomScrollbarPart(aContext, aFrame, aAppearance, aRect,
+ aDirtyRect)) {
+ return NS_OK;
+ }
+ }
+
+ HANDLE theme = GetTheme(aAppearance);
+ if (!theme)
+ return ClassicDrawWidgetBackground(aContext, aFrame, aAppearance, aRect,
+ aDirtyRect);
+
+ // ^^ without the right sdk, assume xp theming and fall through.
+ if (gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ switch (aAppearance) {
+ case StyleAppearance::MozWindowTitlebar:
+ case StyleAppearance::MozWindowTitlebarMaximized:
+ case StyleAppearance::MozWindowFrameLeft:
+ case StyleAppearance::MozWindowFrameRight:
+ case StyleAppearance::MozWindowFrameBottom:
+ // Nothing to draw, these areas are glass. Minimum dimensions
+ // should be set, so xul content should be layed out correctly.
+ return NS_OK;
+ case StyleAppearance::MozWindowButtonClose:
+ case StyleAppearance::MozWindowButtonMinimize:
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore:
+ // Not conventional bitmaps, can't be retrieved. If we fall
+ // through here and call the theme library we'll get aero
+ // basic bitmaps.
+ return NS_OK;
+ case StyleAppearance::MozWinGlass:
+ case StyleAppearance::MozWinBorderlessGlass:
+ // Nothing to draw, this is the glass background.
+ return NS_OK;
+ case StyleAppearance::MozWindowButtonBox:
+ case StyleAppearance::MozWindowButtonBoxMaximized:
+ // We handle these through nsIWidget::UpdateThemeGeometries
+ return NS_OK;
+ default:
+ break;
+ }
+ }
+
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
+ if (NS_FAILED(rv)) return rv;
+
+ if (AssumeThemePartAndStateAreTransparent(part, state)) {
+ return NS_OK;
+ }
+
+ RefPtr<gfxContext> ctx = aContext;
+ gfxContextMatrixAutoSaveRestore save(ctx);
+
+ double themeScale = GetThemeDpiScaleFactor(aFrame);
+ if (themeScale != 1.0) {
+ ctx->SetMatrix(ctx->CurrentMatrix().PreScale(themeScale, themeScale));
+ }
+
+ gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel());
+ RECT widgetRect;
+ RECT clipRect;
+ gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()),
+ dr(aDirtyRect.X(), aDirtyRect.Y(), aDirtyRect.Width(),
+ aDirtyRect.Height());
+
+ tr.Scale(1.0 / (p2a * themeScale));
+ dr.Scale(1.0 / (p2a * themeScale));
+
+ gfxWindowsNativeDrawing nativeDrawing(
+ ctx, dr, GetWidgetNativeDrawingFlags(aAppearance));
+
+RENDER_AGAIN:
+
+ HDC hdc = nativeDrawing.BeginNativeDrawing();
+ if (!hdc) return NS_ERROR_FAILURE;
+
+ nativeDrawing.TransformToNativeRect(tr, widgetRect);
+ nativeDrawing.TransformToNativeRect(dr, clipRect);
+
+#if 0
+ {
+ MOZ_LOG(gWindowsLog, LogLevel::Error,
+ (stderr, "xform: %f %f %f %f [%f %f]\n", m._11, m._21, m._12, m._22,
+ m._31, m._32));
+ MOZ_LOG(gWindowsLog, LogLevel::Error,
+ (stderr, "tr: [%d %d %d %d]\ndr: [%d %d %d %d]\noff: [%f %f]\n",
+ tr.x, tr.y, tr.width, tr.height, dr.x, dr.y, dr.width, dr.height,
+ offset.x, offset.y));
+ }
+#endif
+
+ if (aAppearance == StyleAppearance::MozWindowTitlebar) {
+ // Clip out the left and right corners of the frame, all we want in
+ // is the middle section.
+ widgetRect.left -= GetSystemMetrics(SM_CXFRAME);
+ widgetRect.right += GetSystemMetrics(SM_CXFRAME);
+ } else if (aAppearance == StyleAppearance::MozWindowTitlebarMaximized) {
+ // The origin of the window is off screen when maximized and windows
+ // doesn't compensate for this in rendering the background. Push the
+ // top of the bitmap down by SM_CYFRAME so we get the full graphic.
+ widgetRect.top += GetSystemMetrics(SM_CYFRAME);
+ } else if (aAppearance == StyleAppearance::Tab) {
+ // For left edge and right edge tabs, we need to adjust the widget
+ // rects and clip rects so that the edges don't get drawn.
+ bool isLeft = IsLeftToSelectedTab(aFrame);
+ bool isRight = !isLeft && IsRightToSelectedTab(aFrame);
+
+ if (isLeft || isRight) {
+ // HACK ALERT: There appears to be no way to really obtain this value, so
+ // we're forced to just use the default value for Luna (which also happens
+ // to be correct for all the other skins I've tried).
+ int32_t edgeSize = 2;
+
+ // Armed with the size of the edge, we now need to either shift to the
+ // left or to the right. The clip rect won't include this extra area, so
+ // we know that we're effectively shifting the edge out of view (such that
+ // it won't be painted).
+ if (isLeft)
+ // The right edge should not be drawn. Extend our rect by the edge
+ // size.
+ widgetRect.right += edgeSize;
+ else
+ // The left edge should not be drawn. Move the widget rect's left coord
+ // back.
+ widgetRect.left -= edgeSize;
+ }
+ } else if (aAppearance == StyleAppearance::MozWindowButtonMinimize) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE);
+ } else if (aAppearance == StyleAppearance::MozWindowButtonMaximize ||
+ aAppearance == StyleAppearance::MozWindowButtonRestore) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE);
+ } else if (aAppearance == StyleAppearance::MozWindowButtonClose) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_CLOSE);
+ }
+
+ // widgetRect is the bounding box for a widget, yet the scale track is only
+ // a small portion of this size, so the edges of the scale need to be
+ // adjusted to the real size of the track.
+ if (aAppearance == StyleAppearance::Range) {
+ RECT contentRect;
+ GetThemeBackgroundContentRect(theme, hdc, part, state, &widgetRect,
+ &contentRect);
+
+ SIZE siz;
+ GetThemePartSize(theme, hdc, part, state, &widgetRect, TS_TRUE, &siz);
+
+ // When rounding is necessary, we round the position of the track
+ // away from the chevron of the thumb to make it look better.
+ if (IsRangeHorizontal(aFrame)) {
+ contentRect.top += (contentRect.bottom - contentRect.top - siz.cy) / 2;
+ contentRect.bottom = contentRect.top + siz.cy;
+ } else {
+ if (!IsFrameRTL(aFrame)) {
+ contentRect.left += (contentRect.right - contentRect.left - siz.cx) / 2;
+ contentRect.right = contentRect.left + siz.cx;
+ } else {
+ contentRect.right -=
+ (contentRect.right - contentRect.left - siz.cx) / 2;
+ contentRect.left = contentRect.right - siz.cx;
+ }
+ }
+
+ DrawThemeBackground(theme, hdc, part, state, &contentRect, &clipRect);
+ } else if (aAppearance == StyleAppearance::Menucheckbox ||
+ aAppearance == StyleAppearance::Menuradio) {
+ bool isChecked = false;
+ isChecked = CheckBooleanAttr(aFrame, nsGkAtoms::checked);
+
+ if (isChecked) {
+ int bgState = MCB_NORMAL;
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+
+ // the disabled states are offset by 1
+ if (IsDisabled(aFrame, eventState)) bgState += 1;
+
+ SIZE checkboxBGSize(GetCheckboxBGSize(theme, hdc));
+
+ RECT checkBGRect = widgetRect;
+ if (IsFrameRTL(aFrame)) {
+ checkBGRect.left = checkBGRect.right - checkboxBGSize.cx;
+ } else {
+ checkBGRect.right = checkBGRect.left + checkboxBGSize.cx;
+ }
+
+ // Center the checkbox background vertically in the menuitem
+ checkBGRect.top +=
+ (checkBGRect.bottom - checkBGRect.top) / 2 - checkboxBGSize.cy / 2;
+ checkBGRect.bottom = checkBGRect.top + checkboxBGSize.cy;
+
+ DrawThemeBackground(theme, hdc, MENU_POPUPCHECKBACKGROUND, bgState,
+ &checkBGRect, &clipRect);
+
+ MARGINS checkMargins = GetCheckboxMargins(theme, hdc);
+ RECT checkRect = checkBGRect;
+ checkRect.left += checkMargins.cxLeftWidth;
+ checkRect.right -= checkMargins.cxRightWidth;
+ checkRect.top += checkMargins.cyTopHeight;
+ checkRect.bottom -= checkMargins.cyBottomHeight;
+ DrawThemeBackground(theme, hdc, MENU_POPUPCHECK, state, &checkRect,
+ &clipRect);
+ }
+ } else if (aAppearance == StyleAppearance::Menupopup) {
+ DrawThemeBackground(theme, hdc, MENU_POPUPBORDERS, /* state */ 0,
+ &widgetRect, &clipRect);
+ SIZE borderSize;
+ GetThemePartSize(theme, hdc, MENU_POPUPBORDERS, 0, nullptr, TS_TRUE,
+ &borderSize);
+
+ RECT bgRect = widgetRect;
+ bgRect.top += borderSize.cy;
+ bgRect.bottom -= borderSize.cy;
+ bgRect.left += borderSize.cx;
+ bgRect.right -= borderSize.cx;
+
+ DrawThemeBackground(theme, hdc, MENU_POPUPBACKGROUND, /* state */ 0,
+ &bgRect, &clipRect);
+
+ SIZE gutterSize(GetGutterSize(theme, hdc));
+
+ RECT gutterRect;
+ gutterRect.top = bgRect.top;
+ gutterRect.bottom = bgRect.bottom;
+ if (IsFrameRTL(aFrame)) {
+ gutterRect.right = bgRect.right;
+ gutterRect.left = gutterRect.right - gutterSize.cx;
+ } else {
+ gutterRect.left = bgRect.left;
+ gutterRect.right = gutterRect.left + gutterSize.cx;
+ }
+
+ DrawThemeBGRTLAware(theme, hdc, MENU_POPUPGUTTER, /* state */ 0,
+ &gutterRect, &clipRect, IsFrameRTL(aFrame));
+ } else if (aAppearance == StyleAppearance::Menuseparator) {
+ SIZE gutterSize(GetGutterSize(theme, hdc));
+
+ RECT sepRect = widgetRect;
+ if (IsFrameRTL(aFrame))
+ sepRect.right -= gutterSize.cx;
+ else
+ sepRect.left += gutterSize.cx;
+
+ DrawThemeBackground(theme, hdc, MENU_POPUPSEPARATOR, /* state */ 0,
+ &sepRect, &clipRect);
+ } else if (aAppearance == StyleAppearance::Menuarrow) {
+ // We're dpi aware and as such on systems that have dpi > 96 set, the
+ // theme library expects us to do proper positioning and scaling of glyphs.
+ // For StyleAppearance::Menuarrow, layout may hand us a widget rect larger
+ // than the glyph rect we request in GetMinimumWidgetSize. To prevent
+ // distortion we have to position and scale what we draw.
+
+ SIZE glyphSize;
+ GetThemePartSize(theme, hdc, part, state, nullptr, TS_TRUE, &glyphSize);
+
+ int32_t widgetHeight = widgetRect.bottom - widgetRect.top;
+
+ RECT renderRect = widgetRect;
+
+ // We request (glyph width * 2, glyph height) in GetMinimumWidgetSize. In
+ // Firefox some menu items provide the full height of the item to us, in
+ // others our widget rect is the exact dims of our arrow glyph. Adjust the
+ // vertical position by the added space, if any exists.
+ renderRect.top += ((widgetHeight - glyphSize.cy) / 2);
+ renderRect.bottom = renderRect.top + glyphSize.cy;
+ // I'm using the width of the arrow glyph for the arrow-side padding.
+ // AFAICT there doesn't appear to be a theme constant we can query
+ // for this value. Generally this looks correct, and has the added
+ // benefit of being a dpi adjusted value.
+ if (!IsFrameRTL(aFrame)) {
+ renderRect.right = widgetRect.right - glyphSize.cx;
+ renderRect.left = renderRect.right - glyphSize.cx;
+ } else {
+ renderRect.left = glyphSize.cx;
+ renderRect.right = renderRect.left + glyphSize.cx;
+ }
+ DrawThemeBGRTLAware(theme, hdc, part, state, &renderRect, &clipRect,
+ IsFrameRTL(aFrame));
+ }
+ // The following widgets need to be RTL-aware
+ else if (aAppearance == StyleAppearance::Resizer ||
+ aAppearance == StyleAppearance::MozMenulistArrowButton) {
+ DrawThemeBGRTLAware(theme, hdc, part, state, &widgetRect, &clipRect,
+ IsFrameRTL(aFrame));
+ } else if (aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::Textfield ||
+ aAppearance == StyleAppearance::Textarea) {
+ DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
+
+ if (state == TFS_EDITBORDER_DISABLED) {
+ InflateRect(&widgetRect, -1, -1);
+ ::FillRect(hdc, &widgetRect, reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1));
+ }
+ } else if (aAppearance == StyleAppearance::ProgressBar) {
+ // DrawThemeBackground renders each corner with a solid white pixel.
+ // Restore these pixels to the underlying color. Tracks are rendered
+ // using alpha recovery, so this makes the corners transparent.
+ COLORREF color;
+ color = GetPixel(hdc, widgetRect.left, widgetRect.top);
+ DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
+ SetPixel(hdc, widgetRect.left, widgetRect.top, color);
+ SetPixel(hdc, widgetRect.right - 1, widgetRect.top, color);
+ SetPixel(hdc, widgetRect.right - 1, widgetRect.bottom - 1, color);
+ SetPixel(hdc, widgetRect.left, widgetRect.bottom - 1, color);
+ } else if (aAppearance == StyleAppearance::Progresschunk) {
+ DrawThemedProgressMeter(aFrame, aAppearance, theme, hdc, part, state,
+ &widgetRect, &clipRect);
+ } else if (aAppearance == StyleAppearance::FocusOutline) {
+ // Inflate 'widgetRect' with the focus outline size.
+ LayoutDeviceIntMargin border = GetWidgetBorder(
+ aFrame->PresContext()->DeviceContext(), aFrame, aAppearance);
+ widgetRect.left -= border.left;
+ widgetRect.right += border.right;
+ widgetRect.top -= border.top;
+ widgetRect.bottom += border.bottom;
+
+ DTBGOPTS opts = {sizeof(DTBGOPTS), DTBG_OMITCONTENT | DTBG_CLIPRECT,
+ clipRect};
+ DrawThemeBackgroundEx(theme, hdc, part, state, &widgetRect, &opts);
+ }
+ // If part is negative, the element wishes us to not render a themed
+ // background, instead opting to be drawn specially below.
+ else if (part >= 0) {
+ DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
+ }
+
+ // Draw focus rectangles for range elements
+ // XXX it'd be nice to draw these outside of the frame
+ if (aAppearance == StyleAppearance::Range) {
+ EventStates contentState = GetContentState(aFrame, aAppearance);
+
+ if (contentState.HasState(NS_EVENT_STATE_FOCUSRING)) {
+ POINT vpOrg;
+ HPEN hPen = nullptr;
+
+ uint8_t id = SaveDC(hdc);
+
+ ::SelectClipRgn(hdc, nullptr);
+ ::GetViewportOrgEx(hdc, &vpOrg);
+ ::SetBrushOrgEx(hdc, vpOrg.x + widgetRect.left, vpOrg.y + widgetRect.top,
+ nullptr);
+ ::SetTextColor(hdc, 0);
+ ::DrawFocusRect(hdc, &widgetRect);
+ ::RestoreDC(hdc, id);
+ if (hPen) {
+ ::DeleteObject(hPen);
+ }
+ }
+ } else if (aAppearance == StyleAppearance::Toolbar && state == 0) {
+ // Draw toolbar separator lines above all toolbars except the first one.
+ // The lines are part of the Rebar theme, which is loaded for
+ // StyleAppearance::Toolbox.
+ theme = GetTheme(StyleAppearance::Toolbox);
+ if (!theme) return NS_ERROR_FAILURE;
+
+ widgetRect.bottom = widgetRect.top + TB_SEPARATOR_HEIGHT;
+ DrawThemeEdge(theme, hdc, RP_BAND, 0, &widgetRect, EDGE_ETCHED, BF_TOP,
+ nullptr);
+ } else if (aAppearance == StyleAppearance::ScrollbarthumbHorizontal ||
+ aAppearance == StyleAppearance::ScrollbarthumbVertical) {
+ // Draw the decorative gripper for the scrollbar thumb button, if it fits
+
+ SIZE gripSize;
+ MARGINS thumbMgns;
+ int gripPart = (aAppearance == StyleAppearance::ScrollbarthumbHorizontal)
+ ? SP_GRIPPERHOR
+ : SP_GRIPPERVERT;
+
+ if (GetThemePartSize(theme, hdc, gripPart, state, nullptr, TS_TRUE,
+ &gripSize) == S_OK &&
+ GetThemeMargins(theme, hdc, part, state, TMT_CONTENTMARGINS, nullptr,
+ &thumbMgns) == S_OK &&
+ gripSize.cx + thumbMgns.cxLeftWidth + thumbMgns.cxRightWidth <=
+ widgetRect.right - widgetRect.left &&
+ gripSize.cy + thumbMgns.cyTopHeight + thumbMgns.cyBottomHeight <=
+ widgetRect.bottom - widgetRect.top) {
+ DrawThemeBackground(theme, hdc, gripPart, state, &widgetRect, &clipRect);
+ }
+ }
+
+ nativeDrawing.EndNativeDrawing();
+
+ if (nativeDrawing.ShouldRenderAgain()) goto RENDER_AGAIN;
+
+ nativeDrawing.PaintToContext();
+
+ return NS_OK;
+}
+
+static void ScaleForFrameDPI(LayoutDeviceIntMargin* aMargin, nsIFrame* aFrame) {
+ double themeScale = GetThemeDpiScaleFactor(aFrame);
+ if (themeScale != 1.0) {
+ aMargin->top = NSToIntRound(aMargin->top * themeScale);
+ aMargin->left = NSToIntRound(aMargin->left * themeScale);
+ aMargin->bottom = NSToIntRound(aMargin->bottom * themeScale);
+ aMargin->right = NSToIntRound(aMargin->right * themeScale);
+ }
+}
+
+static void ScaleForFrameDPI(LayoutDeviceIntSize* aSize, nsIFrame* aFrame) {
+ double themeScale = GetThemeDpiScaleFactor(aFrame);
+ if (themeScale != 1.0) {
+ aSize->width = NSToIntRound(aSize->width * themeScale);
+ aSize->height = NSToIntRound(aSize->height * themeScale);
+ }
+}
+
+LayoutDeviceIntMargin nsNativeThemeWin::GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
+ LayoutDeviceIntMargin result;
+ mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
+ HTHEME theme = NULL;
+ if (!themeClass.isNothing()) {
+ theme = nsUXThemeData::GetTheme(themeClass.value());
+ }
+ if (!theme) {
+ result = ClassicGetWidgetBorder(aContext, aFrame, aAppearance);
+ ScaleForFrameDPI(&result, aFrame);
+ return result;
+ }
+
+ if (!WidgetIsContainer(aAppearance) ||
+ aAppearance == StyleAppearance::Toolbox ||
+ aAppearance == StyleAppearance::MozWinMediaToolbox ||
+ aAppearance == StyleAppearance::MozWinCommunicationsToolbox ||
+ aAppearance == StyleAppearance::MozWinBrowsertabbarToolbox ||
+ aAppearance == StyleAppearance::Statusbar ||
+ aAppearance == StyleAppearance::Resizer ||
+ aAppearance == StyleAppearance::Tabpanel ||
+ aAppearance == StyleAppearance::ScrollbarHorizontal ||
+ aAppearance == StyleAppearance::ScrollbarVertical ||
+ aAppearance == StyleAppearance::Scrollcorner ||
+ aAppearance == StyleAppearance::Menuitem ||
+ aAppearance == StyleAppearance::Checkmenuitem ||
+ aAppearance == StyleAppearance::Radiomenuitem ||
+ aAppearance == StyleAppearance::Menupopup ||
+ aAppearance == StyleAppearance::Menuimage ||
+ aAppearance == StyleAppearance::Menuitemtext ||
+ aAppearance == StyleAppearance::Separator ||
+ aAppearance == StyleAppearance::MozWindowTitlebar ||
+ aAppearance == StyleAppearance::MozWindowTitlebarMaximized ||
+ aAppearance == StyleAppearance::MozWinGlass ||
+ aAppearance == StyleAppearance::MozWinBorderlessGlass)
+ return result; // Don't worry about it.
+
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
+ if (NS_FAILED(rv)) return result;
+
+ if (aAppearance == StyleAppearance::Toolbar) {
+ // make space for the separator line above all toolbars but the first
+ if (state == 0) result.top = TB_SEPARATOR_HEIGHT;
+ return result;
+ }
+
+ result = GetCachedWidgetBorder(theme, themeClass.value(), aAppearance, part,
+ state);
+
+ // Remove the edges for tabs that are before or after the selected tab,
+ if (aAppearance == StyleAppearance::Tab) {
+ if (IsLeftToSelectedTab(aFrame))
+ // Remove the right edge, since we won't be drawing it.
+ result.right = 0;
+ else if (IsRightToSelectedTab(aFrame))
+ // Remove the left edge, since we won't be drawing it.
+ result.left = 0;
+ }
+
+ if (aFrame && (aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::Textfield ||
+ aAppearance == StyleAppearance::Textarea)) {
+ nsIContent* content = aFrame->GetContent();
+ if (content && content->IsHTMLElement()) {
+ // We need to pad textfields by 1 pixel, since the caret will draw
+ // flush against the edge by default if we don't.
+ result.top++;
+ result.left++;
+ result.bottom++;
+ result.right++;
+ }
+ }
+
+ ScaleForFrameDPI(&result, aFrame);
+ return result;
+}
+
+bool nsNativeThemeWin::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) {
+ switch (aAppearance) {
+ // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
+ // and have a meaningful baseline, so they can't have
+ // author-specified padding.
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio:
+ aResult->SizeTo(0, 0, 0, 0);
+ return true;
+ default:
+ break;
+ }
+
+ bool ok = true;
+
+ if (aAppearance == StyleAppearance::MozWindowButtonBox ||
+ aAppearance == StyleAppearance::MozWindowButtonBoxMaximized) {
+ aResult->SizeTo(0, 0, 0, 0);
+
+ // aero glass doesn't display custom buttons
+ if (gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) return true;
+
+ // button padding for standard windows
+ if (aAppearance == StyleAppearance::MozWindowButtonBox) {
+ aResult->top = GetSystemMetrics(SM_CXFRAME);
+ }
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ }
+
+ // Content padding
+ if (aAppearance == StyleAppearance::MozWindowTitlebar ||
+ aAppearance == StyleAppearance::MozWindowTitlebarMaximized) {
+ aResult->SizeTo(0, 0, 0, 0);
+ // XXX Maximized windows have an offscreen offset equal to
+ // the border padding. This should be addressed in nsWindow,
+ // but currently can't be, see UpdateNonClientMargins.
+ if (aAppearance == StyleAppearance::MozWindowTitlebarMaximized) {
+ nsIWidget* rootWidget = nullptr;
+ if (WinUtils::HasSystemMetricsForDpi()) {
+ rootWidget = aFrame->PresContext()->GetRootWidget();
+ }
+ if (rootWidget) {
+ double dpi = rootWidget->GetDPI();
+ aResult->top = WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi) +
+ WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
+ } else {
+ aResult->top =
+ GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
+ }
+ }
+ return ok;
+ }
+
+ HANDLE theme = GetTheme(aAppearance);
+ if (!theme) {
+ ok = ClassicGetWidgetPadding(aContext, aFrame, aAppearance, aResult);
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ }
+
+ if (aAppearance == StyleAppearance::Menupopup) {
+ SIZE popupSize;
+ GetThemePartSize(theme, nullptr, MENU_POPUPBORDERS, /* state */ 0, nullptr,
+ TS_TRUE, &popupSize);
+ aResult->top = aResult->bottom = popupSize.cy;
+ aResult->left = aResult->right = popupSize.cx;
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ }
+
+ if (aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::Textfield ||
+ aAppearance == StyleAppearance::Textarea ||
+ aAppearance == StyleAppearance::MenulistButton ||
+ aAppearance == StyleAppearance::Menulist) {
+ // If we have author-specified padding for these elements, don't do the
+ // fixups below.
+ if (aFrame->PresContext()->HasAuthorSpecifiedRules(
+ aFrame, NS_AUTHOR_SPECIFIED_PADDING))
+ return false;
+ }
+
+ /* textfields need extra pixels on all sides, otherwise they wrap their
+ * content too tightly. The actual border is drawn 1px inside the specified
+ * rectangle, so Gecko will end up making the contents look too small.
+ * Instead, we add 2px padding for the contents and fix this. (Used to be 1px
+ * added, see bug 430212)
+ */
+ if (aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::Textfield ||
+ aAppearance == StyleAppearance::Textarea) {
+ aResult->top = aResult->bottom = 2;
+ aResult->left = aResult->right = 2;
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ } else if (IsHTMLContent(aFrame) &&
+ (aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton)) {
+ /* For content menulist controls, we need an extra pixel so that we have
+ * room to draw our focus rectangle stuff. Otherwise, the focus rect might
+ * overlap the control's border.
+ */
+ aResult->top = aResult->bottom = 1;
+ aResult->left = aResult->right = 1;
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ }
+
+ int32_t right, left, top, bottom;
+ right = left = top = bottom = 0;
+ switch (aAppearance) {
+ case StyleAppearance::Menuimage:
+ right = 8;
+ left = 3;
+ break;
+ case StyleAppearance::Menucheckbox:
+ case StyleAppearance::Menuradio:
+ right = 8;
+ left = 0;
+ break;
+ case StyleAppearance::Menuitemtext:
+ // There seem to be exactly 4 pixels from the edge
+ // of the gutter to the text: 2px margin (CSS) + 2px padding (here)
+ {
+ SIZE size(GetGutterSize(theme, nullptr));
+ left = size.cx + 2;
+ }
+ break;
+ case StyleAppearance::Menuseparator: {
+ SIZE size(GetGutterSize(theme, nullptr));
+ left = size.cx + 5;
+ top = 10;
+ bottom = 7;
+ } break;
+ default:
+ return false;
+ }
+
+ if (IsFrameRTL(aFrame)) {
+ aResult->right = left;
+ aResult->left = right;
+ } else {
+ aResult->right = right;
+ aResult->left = left;
+ }
+
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+}
+
+bool nsNativeThemeWin::GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) {
+ /* This is disabled for now, because it causes invalidation problems --
+ * see bug 420381. The effect of not updating the overflow area is that
+ * for dropdown buttons in content areas, there is a 1px border on 3 sides
+ * where, if invalidated, the dropdown control probably won't be repainted.
+ * This is fairly minor, as by default there is nothing in that area, and
+ * a border only shows up if the widget is being hovered.
+ *
+ * TODO(jwatt): Figure out what do to about
+ * StyleAppearance::MozMenulistArrowButton too.
+ */
+#if 0
+ /* We explicitly draw dropdown buttons in HTML content 1px bigger up, right,
+ * and bottom so that they overlap the dropdown's border like they're
+ * supposed to.
+ */
+ if (aAppearance == StyleAppearance::MenulistButton &&
+ IsHTMLContent(aFrame) &&
+ !IsWidgetStyled(aFrame->GetParent()->PresContext(),
+ aFrame->GetParent(),
+ StyleAppearance::Menulist))
+ {
+ int32_t p2a = aContext->AppUnitsPerDevPixel();
+ /* Note: no overflow on the left */
+ nsMargin m(p2a, p2a, p2a, 0);
+ aOverflowRect->Inflate (m);
+ return true;
+ }
+#endif
+
+ if (aAppearance == StyleAppearance::FocusOutline) {
+ LayoutDeviceIntMargin border =
+ GetWidgetBorder(aContext, aFrame, aAppearance);
+ int32_t p2a = aContext->AppUnitsPerDevPixel();
+ nsMargin m(NSIntPixelsToAppUnits(border.top, p2a),
+ NSIntPixelsToAppUnits(border.right, p2a),
+ NSIntPixelsToAppUnits(border.bottom, p2a),
+ NSIntPixelsToAppUnits(border.left, p2a));
+ aOverflowRect->Inflate(m);
+ return true;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) {
+ aResult->width = aResult->height = 0;
+ *aIsOverridable = true;
+ nsresult rv = NS_OK;
+
+ mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
+ HTHEME theme = NULL;
+ if (!themeClass.isNothing()) {
+ theme = nsUXThemeData::GetTheme(themeClass.value());
+ }
+ if (!theme) {
+ rv = ClassicGetMinimumWidgetSize(aFrame, aAppearance, aResult,
+ aIsOverridable);
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+
+ switch (aAppearance) {
+ case StyleAppearance::Groupbox:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Toolbox:
+ case StyleAppearance::MozWinMediaToolbox:
+ case StyleAppearance::MozWinCommunicationsToolbox:
+ case StyleAppearance::MozWinBrowsertabbarToolbox:
+ case StyleAppearance::Toolbar:
+ case StyleAppearance::Statusbar:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Tabpanels:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Menuitemtext:
+ case StyleAppearance::MozWinGlass:
+ case StyleAppearance::MozWinBorderlessGlass:
+ return NS_OK; // Don't worry about it.
+ default:
+ break;
+ }
+
+ if (aAppearance == StyleAppearance::Menuitem && IsTopLevelMenu(aFrame)) {
+ return NS_OK; // Don't worry about it for top level menus
+ }
+
+ // Call GetSystemMetrics to determine size for WinXP scrollbars
+ // (GetThemeSysSize API returns the optimal size for the theme, but
+ // Windows appears to always use metrics when drawing standard scrollbars)
+ THEMESIZE sizeReq = TS_TRUE; // Best-fit size
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::MozMenulistArrowButton: {
+ rv = ClassicGetMinimumWidgetSize(aFrame, aAppearance, aResult,
+ aIsOverridable);
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem:
+ if (!IsTopLevelMenu(aFrame)) {
+ SIZE gutterSize(GetCachedGutterSize(theme));
+ aResult->width = gutterSize.cx;
+ aResult->height = gutterSize.cy;
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+ break;
+
+ case StyleAppearance::Menuimage:
+ case StyleAppearance::Menucheckbox:
+ case StyleAppearance::Menuradio: {
+ SIZE boxSize(GetCachedGutterSize(theme));
+ aResult->width = boxSize.cx + 2;
+ aResult->height = boxSize.cy;
+ *aIsOverridable = false;
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+
+ case StyleAppearance::Menuitemtext:
+ return NS_OK;
+
+ case StyleAppearance::ProgressBar:
+ // Best-fit size for progress meters is too large for most
+ // themes. We want these widgets to be able to really shrink
+ // down, so use the min-size request value (of 0).
+ sizeReq = TS_MIN;
+ break;
+
+ case StyleAppearance::Resizer:
+ *aIsOverridable = false;
+ break;
+
+ case StyleAppearance::RangeThumb: {
+ *aIsOverridable = false;
+ if (IsRangeHorizontal(aFrame)) {
+ aResult->width = 12;
+ aResult->height = 20;
+ } else {
+ aResult->width = 20;
+ aResult->height = 12;
+ }
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+
+ case StyleAppearance::Scrollcorner: {
+ if (nsLookAndFeel::GetInt(nsLookAndFeel::IntID::UseOverlayScrollbars) !=
+ 0) {
+ aResult->SizeTo(::GetSystemMetrics(SM_CXHSCROLL),
+ ::GetSystemMetrics(SM_CYVSCROLL));
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+ break;
+ }
+
+ case StyleAppearance::Separator:
+ // that's 2px left margin, 2px right margin and 2px separator
+ // (the margin is drawn as part of the separator, though)
+ aResult->width = 6;
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+
+ case StyleAppearance::Button:
+ // We should let HTML buttons shrink to their min size.
+ // FIXME bug 403934: We should probably really separate
+ // GetPreferredWidgetSize from GetMinimumWidgetSize, so callers can
+ // use the one they want.
+ if (aFrame->GetContent()->IsHTMLElement()) {
+ sizeReq = TS_MIN;
+ }
+ break;
+
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore:
+ // The only way to get accurate titlebar button info is to query a
+ // window w/buttons when it's visible. nsWindow takes care of this and
+ // stores that info in nsUXThemeData.
+ aResult->width =
+ nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_RESTORE).cx;
+ aResult->height =
+ nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_RESTORE).cy;
+ AddPaddingRect(aResult, CAPTIONBUTTON_RESTORE);
+ *aIsOverridable = false;
+ return rv;
+
+ case StyleAppearance::MozWindowButtonMinimize:
+ aResult->width =
+ nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_MINIMIZE).cx;
+ aResult->height =
+ nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_MINIMIZE).cy;
+ AddPaddingRect(aResult, CAPTIONBUTTON_MINIMIZE);
+ *aIsOverridable = false;
+ return rv;
+
+ case StyleAppearance::MozWindowButtonClose:
+ aResult->width =
+ nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_CLOSE).cx;
+ aResult->height =
+ nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_CLOSE).cy;
+ AddPaddingRect(aResult, CAPTIONBUTTON_CLOSE);
+ *aIsOverridable = false;
+ return rv;
+
+ case StyleAppearance::MozWindowTitlebar:
+ case StyleAppearance::MozWindowTitlebarMaximized:
+ aResult->height = GetSystemMetrics(SM_CYCAPTION);
+ aResult->height += GetSystemMetrics(SM_CYFRAME);
+ aResult->height += GetSystemMetrics(SM_CXPADDEDBORDER);
+ // On Win8.1, we don't want this scaling, because Windows doesn't scale
+ // the non-client area of the window, and we can end up with ugly overlap
+ // of the window frame controls into the tab bar or content area. But on
+ // Win10, we render the window controls ourselves, and the result looks
+ // better if we do apply this scaling (particularly with themes such as
+ // DevEdition; see bug 1267636).
+ if (IsWin10OrLater()) {
+ ScaleForFrameDPI(aResult, aFrame);
+ }
+ *aIsOverridable = false;
+ return rv;
+
+ case StyleAppearance::MozWindowButtonBox:
+ case StyleAppearance::MozWindowButtonBoxMaximized:
+ if (gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ aResult->width = nsUXThemeData::GetCommandButtonBoxMetrics().cx;
+ aResult->height = nsUXThemeData::GetCommandButtonBoxMetrics().cy -
+ GetSystemMetrics(SM_CYFRAME) -
+ GetSystemMetrics(SM_CXPADDEDBORDER);
+ if (aAppearance == StyleAppearance::MozWindowButtonBoxMaximized) {
+ aResult->width += 1;
+ aResult->height -= 2;
+ }
+ *aIsOverridable = false;
+ return rv;
+ }
+ break;
+
+ case StyleAppearance::MozWindowFrameLeft:
+ case StyleAppearance::MozWindowFrameRight:
+ case StyleAppearance::MozWindowFrameBottom:
+ aResult->width = GetSystemMetrics(SM_CXFRAME);
+ aResult->height = GetSystemMetrics(SM_CYFRAME);
+ *aIsOverridable = false;
+ return rv;
+
+ default:
+ break;
+ }
+
+ int32_t part, state;
+ rv = GetThemePartAndState(aFrame, aAppearance, part, state);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = GetCachedMinimumWidgetSize(aFrame, theme, themeClass.value(),
+ aAppearance, part, state, sizeReq, aResult);
+
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::WidgetStateChanged(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) {
+ // Some widget types just never change state.
+ if (aAppearance == StyleAppearance::Toolbox ||
+ aAppearance == StyleAppearance::MozWinMediaToolbox ||
+ aAppearance == StyleAppearance::MozWinCommunicationsToolbox ||
+ aAppearance == StyleAppearance::MozWinBrowsertabbarToolbox ||
+ aAppearance == StyleAppearance::Toolbar ||
+ aAppearance == StyleAppearance::Statusbar ||
+ aAppearance == StyleAppearance::Statusbarpanel ||
+ aAppearance == StyleAppearance::Resizerpanel ||
+ aAppearance == StyleAppearance::Progresschunk ||
+ aAppearance == StyleAppearance::ProgressBar ||
+ aAppearance == StyleAppearance::Tooltip ||
+ aAppearance == StyleAppearance::Tabpanels ||
+ aAppearance == StyleAppearance::Tabpanel ||
+ aAppearance == StyleAppearance::Separator ||
+ aAppearance == StyleAppearance::MozWinGlass ||
+ aAppearance == StyleAppearance::MozWinBorderlessGlass) {
+ *aShouldRepaint = false;
+ return NS_OK;
+ }
+
+ if (aAppearance == StyleAppearance::MozWindowTitlebar ||
+ aAppearance == StyleAppearance::MozWindowTitlebarMaximized ||
+ aAppearance == StyleAppearance::MozWindowFrameLeft ||
+ aAppearance == StyleAppearance::MozWindowFrameRight ||
+ aAppearance == StyleAppearance::MozWindowFrameBottom ||
+ aAppearance == StyleAppearance::MozWindowButtonClose ||
+ aAppearance == StyleAppearance::MozWindowButtonMinimize ||
+ aAppearance == StyleAppearance::MozWindowButtonMaximize ||
+ aAppearance == StyleAppearance::MozWindowButtonRestore) {
+ *aShouldRepaint = true;
+ return NS_OK;
+ }
+
+ // We need to repaint the dropdown arrow in vista HTML combobox controls when
+ // the control is closed to get rid of the hover effect.
+ if ((aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton ||
+ aAppearance == StyleAppearance::MozMenulistArrowButton) &&
+ nsNativeTheme::IsHTMLContent(aFrame)) {
+ *aShouldRepaint = true;
+ 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::readonly || aAttribute == nsGkAtoms::open ||
+ aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::focused)
+ *aShouldRepaint = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::ThemeChanged() {
+ nsUXThemeData::Invalidate();
+ memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid));
+ memset(mMinimumWidgetSizeCacheValid, 0, sizeof(mMinimumWidgetSizeCacheValid));
+ mGutterSizeCacheValid = false;
+ return NS_OK;
+}
+
+bool nsNativeThemeWin::ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ // XXXdwh We can go even further and call the API to ask if support exists for
+ // specific widgets.
+
+ if (aAppearance == StyleAppearance::FocusOutline) {
+ return true;
+ }
+
+ HANDLE theme = nullptr;
+ if (aAppearance == StyleAppearance::CheckboxContainer)
+ theme = GetTheme(StyleAppearance::Checkbox);
+ else if (aAppearance == StyleAppearance::RadioContainer)
+ theme = GetTheme(StyleAppearance::Radio);
+ else
+ theme = GetTheme(aAppearance);
+
+ if (theme && aAppearance == StyleAppearance::Resizer) return true;
+
+ if ((theme) || (!theme && ClassicThemeSupportsWidget(aFrame, aAppearance)))
+ // turn off theming for some HTML widgets styled by the page
+ return (!IsWidgetStyled(aPresContext, aFrame, aAppearance));
+
+ return false;
+}
+
+bool nsNativeThemeWin::WidgetIsContainer(StyleAppearance aAppearance) {
+ // XXXdwh At some point flesh all of this out.
+ if (aAppearance == StyleAppearance::MozMenulistArrowButton ||
+ aAppearance == StyleAppearance::Radio ||
+ aAppearance == StyleAppearance::Checkbox)
+ return false;
+ return true;
+}
+
+bool nsNativeThemeWin::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::NumberInput:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool nsNativeThemeWin::ThemeNeedsComboboxDropmarker() { return true; }
+
+bool nsNativeThemeWin::WidgetAppearanceDependsOnWindowFocus(
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::MozWindowTitlebar:
+ case StyleAppearance::MozWindowTitlebarMaximized:
+ case StyleAppearance::MozWindowFrameLeft:
+ case StyleAppearance::MozWindowFrameRight:
+ case StyleAppearance::MozWindowFrameBottom:
+ case StyleAppearance::MozWindowButtonClose:
+ case StyleAppearance::MozWindowButtonMinimize:
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore:
+ return true;
+ default:
+ return false;
+ }
+}
+
+nsITheme::ThemeGeometryType nsNativeThemeWin::ThemeGeometryTypeForWidget(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::MozWindowButtonBox:
+ case StyleAppearance::MozWindowButtonBoxMaximized:
+ return eThemeGeometryTypeWindowButtons;
+ default:
+ return eThemeGeometryTypeUnknown;
+ }
+}
+
+nsITheme::Transparency nsNativeThemeWin::GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ if (auto transparency =
+ ScrollbarUtil::GetScrollbarPartTransparency(aFrame, aAppearance)) {
+ return *transparency;
+ }
+
+ switch (aAppearance) {
+ case StyleAppearance::MozWinGlass:
+ case StyleAppearance::MozWinBorderlessGlass:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Range:
+ return eTransparent;
+ default:
+ break;
+ }
+
+ HANDLE theme = GetTheme(aAppearance);
+ // For the classic theme we don't really have a way of knowing
+ if (!theme) {
+ // menu backgrounds and tooltips which can't be themed are opaque
+ if (aAppearance == StyleAppearance::Menupopup ||
+ aAppearance == StyleAppearance::Tooltip) {
+ return eOpaque;
+ }
+ return eUnknownTransparency;
+ }
+
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
+ // Fail conservatively
+ NS_ENSURE_SUCCESS(rv, eUnknownTransparency);
+
+ if (part <= 0) {
+ // Not a real part code, so IsThemeBackgroundPartiallyTransparent may
+ // not work, so don't call it.
+ return eUnknownTransparency;
+ }
+
+ if (IsThemeBackgroundPartiallyTransparent(theme, part, state))
+ return eTransparent;
+ return eOpaque;
+}
+
+/* Windows 9x/NT/2000/Classic XP Theme Support */
+
+bool nsNativeThemeWin::ClassicThemeSupportsWidget(nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Resizer: {
+ // The classic native resizer has an opaque grey background which doesn't
+ // match the usually white background of the scrollable container, so
+ // only support the native resizer if not in a scrollframe.
+ nsIFrame* parentFrame = aFrame->GetParent();
+ return !parentFrame || !parentFrame->IsScrollFrame();
+ }
+ case StyleAppearance::Menubar:
+ case StyleAppearance::Menupopup:
+ // Classic non-flat menus are handled almost entirely through CSS.
+ if (!nsUXThemeData::AreFlatMenusEnabled()) return false;
+ case StyleAppearance::Button:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio:
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ case StyleAppearance::Groupbox:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarNonDisappearing:
+ case StyleAppearance::Scrollcorner:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Tooltip:
+ case StyleAppearance::Statusbar:
+ case StyleAppearance::Statusbarpanel:
+ case StyleAppearance::Resizerpanel:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Tab:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Tabpanels:
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem:
+ case StyleAppearance::Menucheckbox:
+ case StyleAppearance::Menuradio:
+ case StyleAppearance::Menuarrow:
+ case StyleAppearance::Menuseparator:
+ case StyleAppearance::Menuitemtext:
+ case StyleAppearance::MozWindowTitlebar:
+ case StyleAppearance::MozWindowTitlebarMaximized:
+ case StyleAppearance::MozWindowFrameLeft:
+ case StyleAppearance::MozWindowFrameRight:
+ case StyleAppearance::MozWindowFrameBottom:
+ case StyleAppearance::MozWindowButtonClose:
+ case StyleAppearance::MozWindowButtonMinimize:
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore:
+ case StyleAppearance::MozWindowButtonBox:
+ case StyleAppearance::MozWindowButtonBoxMaximized:
+ return true;
+ default:
+ return false;
+ }
+}
+
+LayoutDeviceIntMargin nsNativeThemeWin::ClassicGetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
+ LayoutDeviceIntMargin result;
+ switch (aAppearance) {
+ case StyleAppearance::Groupbox:
+ case StyleAppearance::Button:
+ result.top = result.left = result.bottom = result.right = 2;
+ break;
+ case StyleAppearance::Statusbar:
+ result.bottom = result.left = result.right = 0;
+ result.top = 2;
+ break;
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Tab:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::FocusOutline:
+ result.top = result.left = result.bottom = result.right = 2;
+ break;
+ case StyleAppearance::Statusbarpanel:
+ case StyleAppearance::Resizerpanel: {
+ result.top = 1;
+ result.left = 1;
+ result.bottom = 1;
+ result.right = aFrame->GetNextSibling() ? 3 : 1;
+ break;
+ }
+ case StyleAppearance::Tooltip:
+ result.top = result.left = result.bottom = result.right = 1;
+ break;
+ case StyleAppearance::ProgressBar:
+ result.top = result.left = result.bottom = result.right = 1;
+ break;
+ case StyleAppearance::Menubar:
+ result.top = result.left = result.bottom = result.right = 0;
+ break;
+ case StyleAppearance::Menupopup:
+ result.top = result.left = result.bottom = result.right = 3;
+ break;
+ default:
+ result.top = result.bottom = result.left = result.right = 0;
+ break;
+ }
+ return result;
+}
+
+bool nsNativeThemeWin::ClassicGetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) {
+ switch (aAppearance) {
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem: {
+ int32_t part, state;
+ bool focused;
+
+ if (NS_FAILED(ClassicGetThemePartAndState(aFrame, aAppearance, part,
+ state, focused)))
+ return false;
+
+ if (part == 1) { // top-level menu
+ if (nsUXThemeData::AreFlatMenusEnabled() || !(state & DFCS_PUSHED)) {
+ (*aResult).top = (*aResult).bottom = (*aResult).left =
+ (*aResult).right = 2;
+ } else {
+ // make top-level menus look sunken when pushed in the Classic look
+ (*aResult).top = (*aResult).left = 3;
+ (*aResult).bottom = (*aResult).right = 1;
+ }
+ } else {
+ (*aResult).top = 0;
+ (*aResult).bottom = (*aResult).left = (*aResult).right = 2;
+ }
+ return true;
+ }
+ case StyleAppearance::ProgressBar:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right =
+ 1;
+ return true;
+ default:
+ return false;
+ }
+}
+
+nsresult nsNativeThemeWin::ClassicGetMinimumWidgetSize(
+ nsIFrame* aFrame, StyleAppearance aAppearance, LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) {
+ (*aResult).width = (*aResult).height = 0;
+ *aIsOverridable = true;
+ switch (aAppearance) {
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ (*aResult).width = (*aResult).height = 13;
+ break;
+ case StyleAppearance::Menucheckbox:
+ case StyleAppearance::Menuradio:
+ case StyleAppearance::Menuarrow:
+ (*aResult).width = ::GetSystemMetrics(SM_CXMENUCHECK);
+ (*aResult).height = ::GetSystemMetrics(SM_CYMENUCHECK);
+ break;
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
+ (*aResult).height = 8; // No good metrics available for this
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ // For scrollbar-width:thin, we don't display the buttons.
+ if (!ScrollbarUtil::IsScrollbarWidthThin(aFrame)) {
+ (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
+ (*aResult).height = ::GetSystemMetrics(SM_CYVSCROLL);
+ }
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ // For scrollbar-width:thin, we don't display the buttons.
+ if (!ScrollbarUtil::IsScrollbarWidthThin(aFrame)) {
+ (*aResult).width = ::GetSystemMetrics(SM_CXHSCROLL);
+ (*aResult).height = ::GetSystemMetrics(SM_CYHSCROLL);
+ }
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::ScrollbarVertical:
+ // XXX HACK We should be able to have a minimum height for the scrollbar
+ // track. However, this causes problems when uncollapsing a scrollbar
+ // inside a tree. See bug 201379 for details.
+
+ // (*aResult).height = ::GetSystemMetrics(SM_CYVTHUMB) << 1;
+ break;
+ case StyleAppearance::ScrollbarNonDisappearing: {
+ aResult->SizeTo(::GetSystemMetrics(SM_CXVSCROLL),
+ ::GetSystemMetrics(SM_CYHSCROLL));
+ break;
+ }
+ case StyleAppearance::RangeThumb: {
+ if (IsRangeHorizontal(aFrame)) {
+ (*aResult).width = 12;
+ (*aResult).height = 20;
+ } else {
+ (*aResult).width = 20;
+ (*aResult).height = 12;
+ }
+ *aIsOverridable = false;
+ break;
+ }
+ case StyleAppearance::MozMenulistArrowButton:
+ (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
+ break;
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Button:
+ case StyleAppearance::Groupbox:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Statusbar:
+ case StyleAppearance::Statusbarpanel:
+ case StyleAppearance::Resizerpanel:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Tooltip:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Tab:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Tabpanels:
+ // no minimum widget size
+ break;
+ case StyleAppearance::Resizer: {
+ NONCLIENTMETRICS nc;
+ nc.cbSize = sizeof(nc);
+ if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(nc), &nc, 0))
+ (*aResult).width = (*aResult).height =
+ abs(nc.lfStatusFont.lfHeight) + 4;
+ else
+ (*aResult).width = (*aResult).height = 15;
+ *aIsOverridable = false;
+ break;
+ }
+ case StyleAppearance::ScrollbarthumbVertical:
+ (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
+ (*aResult).height = ::GetSystemMetrics(SM_CYVTHUMB);
+ // Without theming, divide the thumb size by two in order to look more
+ // native
+ if (!GetTheme(aAppearance)) {
+ (*aResult).height >>= 1;
+ }
+ // If scrollbar-width is thin, divide the thickness by two to make
+ // it look more compact.
+ if (ScrollbarUtil::IsScrollbarWidthThin(aFrame)) {
+ aResult->width >>= 1;
+ }
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ (*aResult).width = ::GetSystemMetrics(SM_CXHTHUMB);
+ (*aResult).height = ::GetSystemMetrics(SM_CYHSCROLL);
+ // Without theming, divide the thumb size by two in order to look more
+ // native
+ if (!GetTheme(aAppearance)) {
+ (*aResult).width >>= 1;
+ }
+ // If scrollbar-width is thin, divide the thickness by two to make
+ // it look more compact.
+ if (ScrollbarUtil::IsScrollbarWidthThin(aFrame)) {
+ aResult->height >>= 1;
+ }
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::ScrollbarHorizontal:
+ (*aResult).width = ::GetSystemMetrics(SM_CXHTHUMB) << 1;
+ break;
+ case StyleAppearance::Menuseparator: {
+ aResult->width = 0;
+ aResult->height = 10;
+ break;
+ }
+
+ case StyleAppearance::MozWindowTitlebarMaximized:
+ case StyleAppearance::MozWindowTitlebar:
+ aResult->height = GetSystemMetrics(SM_CYCAPTION);
+ aResult->height += GetSystemMetrics(SM_CYFRAME);
+ aResult->width = 0;
+ break;
+ case StyleAppearance::MozWindowFrameLeft:
+ case StyleAppearance::MozWindowFrameRight:
+ aResult->width = GetSystemMetrics(SM_CXFRAME);
+ aResult->height = 0;
+ break;
+
+ case StyleAppearance::MozWindowFrameBottom:
+ aResult->height = GetSystemMetrics(SM_CYFRAME);
+ aResult->width = 0;
+ break;
+
+ case StyleAppearance::MozWindowButtonClose:
+ case StyleAppearance::MozWindowButtonMinimize:
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore:
+ aResult->width = GetSystemMetrics(SM_CXSIZE);
+ aResult->height = GetSystemMetrics(SM_CYSIZE);
+ // XXX I have no idea why these caption metrics are always off,
+ // but they are.
+ aResult->width -= 2;
+ aResult->height -= 4;
+ if (aAppearance == StyleAppearance::MozWindowButtonMinimize) {
+ AddPaddingRect(aResult, CAPTIONBUTTON_MINIMIZE);
+ } else if (aAppearance == StyleAppearance::MozWindowButtonMaximize ||
+ aAppearance == StyleAppearance::MozWindowButtonRestore) {
+ AddPaddingRect(aResult, CAPTIONBUTTON_RESTORE);
+ } else if (aAppearance == StyleAppearance::MozWindowButtonClose) {
+ AddPaddingRect(aResult, CAPTIONBUTTON_CLOSE);
+ }
+ break;
+
+ default:
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult nsNativeThemeWin::ClassicGetThemePartAndState(
+ nsIFrame* aFrame, StyleAppearance aAppearance, int32_t& aPart,
+ int32_t& aState, bool& aFocused) {
+ aFocused = false;
+ switch (aAppearance) {
+ case StyleAppearance::Button: {
+ EventStates contentState;
+
+ aPart = DFC_BUTTON;
+ aState = DFCS_BUTTONPUSH;
+ aFocused = false;
+
+ contentState = GetContentState(aFrame, aAppearance);
+ if (IsDisabled(aFrame, contentState))
+ aState |= DFCS_INACTIVE;
+ else if (IsOpenButton(aFrame))
+ aState |= DFCS_PUSHED;
+ else if (IsCheckedButton(aFrame))
+ aState |= DFCS_CHECKED;
+ else {
+ if (contentState.HasAllStates(NS_EVENT_STATE_ACTIVE |
+ NS_EVENT_STATE_HOVER)) {
+ aState |= DFCS_PUSHED;
+ const nsStyleUI* uiData = aFrame->StyleUI();
+ // The down state is flat if the button is focusable
+ if (uiData->mUserFocus == StyleUserFocus::Normal) {
+ if (!aFrame->GetContent()->IsHTMLElement()) aState |= DFCS_FLAT;
+
+ aFocused = true;
+ }
+ }
+ // On Windows, focused buttons are always drawn as such by the native
+ // theme, that's why we check NS_EVENT_STATE_FOCUS instead of
+ // NS_EVENT_STATE_FOCUSRING.
+ if (contentState.HasState(NS_EVENT_STATE_FOCUS) ||
+ (aState == DFCS_BUTTONPUSH && IsDefaultButton(aFrame))) {
+ aFocused = true;
+ }
+ }
+
+ return NS_OK;
+ }
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio: {
+ EventStates contentState;
+ aFocused = false;
+
+ aPart = DFC_BUTTON;
+ aState = 0;
+ nsIContent* content = aFrame->GetContent();
+ bool isCheckbox = (aAppearance == StyleAppearance::Checkbox);
+ bool isChecked = GetCheckedOrSelected(aFrame, !isCheckbox);
+ bool isIndeterminate = isCheckbox && GetIndeterminate(aFrame);
+
+ if (isCheckbox) {
+ // indeterminate state takes precedence over checkedness.
+ if (isIndeterminate) {
+ aState = DFCS_BUTTON3STATE | DFCS_CHECKED;
+ } else {
+ aState = DFCS_BUTTONCHECK;
+ }
+ } else {
+ aState = DFCS_BUTTONRADIO;
+ }
+ if (isChecked) {
+ aState |= DFCS_CHECKED;
+ }
+
+ contentState = GetContentState(aFrame, aAppearance);
+ if (!content->IsXULElement() &&
+ contentState.HasState(NS_EVENT_STATE_FOCUSRING)) {
+ aFocused = true;
+ }
+
+ if (IsDisabled(aFrame, contentState)) {
+ aState |= DFCS_INACTIVE;
+ } else if (contentState.HasAllStates(NS_EVENT_STATE_ACTIVE |
+ NS_EVENT_STATE_HOVER)) {
+ aState |= DFCS_PUSHED;
+ }
+
+ return NS_OK;
+ }
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem: {
+ bool isTopLevel = false;
+ bool isOpen = false;
+ nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+
+ // We indicate top-level-ness using aPart. 0 is a normal menu item,
+ // 1 is a top-level menu item. The state of the item is composed of
+ // DFCS_* flags only.
+ aPart = 0;
+ aState = 0;
+
+ if (menuFrame) {
+ // If this is a real menu item, we should check if it is part of the
+ // main menu bar or not, and if it is a container, as these affect
+ // rendering.
+ isTopLevel = menuFrame->IsOnMenuBar();
+ isOpen = menuFrame->IsOpen();
+ }
+
+ if (IsDisabled(aFrame, eventState)) aState |= DFCS_INACTIVE;
+
+ if (isTopLevel) {
+ aPart = 1;
+ if (isOpen) aState |= DFCS_PUSHED;
+ }
+
+ if (IsMenuActive(aFrame, aAppearance)) aState |= DFCS_HOT;
+
+ return NS_OK;
+ }
+ case StyleAppearance::Menucheckbox:
+ case StyleAppearance::Menuradio:
+ case StyleAppearance::Menuarrow: {
+ aState = 0;
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+
+ if (IsDisabled(aFrame, eventState)) aState |= DFCS_INACTIVE;
+ if (IsMenuActive(aFrame, aAppearance)) aState |= DFCS_HOT;
+
+ if (aAppearance == StyleAppearance::Menucheckbox ||
+ aAppearance == StyleAppearance::Menuradio) {
+ if (IsCheckedButton(aFrame)) aState |= DFCS_CHECKED;
+ } else if (IsFrameRTL(aFrame)) {
+ aState |= DFCS_RTL;
+ }
+ return NS_OK;
+ }
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::FocusOutline:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::Scrollcorner:
+ case StyleAppearance::Statusbar:
+ case StyleAppearance::Statusbarpanel:
+ case StyleAppearance::Resizerpanel:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Tooltip:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Tab:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Tabpanels:
+ case StyleAppearance::Menubar:
+ case StyleAppearance::Menupopup:
+ case StyleAppearance::Groupbox:
+ // these don't use DrawFrameControl
+ return NS_OK;
+ case StyleAppearance::MozMenulistArrowButton: {
+ aPart = DFC_SCROLL;
+ aState = DFCS_SCROLLCOMBOBOX;
+
+ nsIFrame* parentFrame = aFrame->GetParent();
+ bool isHTML = IsHTMLContent(aFrame);
+ bool isMenulist = !isHTML && parentFrame->IsMenuFrame();
+ bool isOpen = false;
+
+ // HTML select and XUL menulist dropdown buttons get state from the
+ // parent.
+ if (isHTML || isMenulist) aFrame = parentFrame;
+
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+
+ if (IsDisabled(aFrame, eventState)) {
+ aState |= DFCS_INACTIVE;
+ return NS_OK;
+ }
+
+ if (isHTML) {
+ nsComboboxControlFrame* ccf = do_QueryFrame(aFrame);
+ isOpen = (ccf && ccf->IsDroppedDownOrHasParentPopup());
+ } else
+ isOpen = IsOpenButton(aFrame);
+
+ // XXX Button should look active until the mouse is released, but
+ // without making it look active when the popup is clicked.
+ if (isOpen && (isHTML || isMenulist)) return NS_OK;
+
+ // Dropdown button active state doesn't need :hover.
+ if (eventState.HasState(NS_EVENT_STATE_ACTIVE))
+ aState |= DFCS_PUSHED | DFCS_FLAT;
+
+ return NS_OK;
+ }
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight: {
+ EventStates contentState = GetContentState(aFrame, aAppearance);
+
+ aPart = DFC_SCROLL;
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarbuttonUp:
+ aState = DFCS_SCROLLUP;
+ break;
+ case StyleAppearance::ScrollbarbuttonDown:
+ aState = DFCS_SCROLLDOWN;
+ break;
+ case StyleAppearance::ScrollbarbuttonLeft:
+ aState = DFCS_SCROLLLEFT;
+ break;
+ case StyleAppearance::ScrollbarbuttonRight:
+ aState = DFCS_SCROLLRIGHT;
+ break;
+ default:
+ break;
+ }
+
+ if (IsDisabled(aFrame, contentState))
+ aState |= DFCS_INACTIVE;
+ else {
+ if (contentState.HasAllStates(NS_EVENT_STATE_HOVER |
+ NS_EVENT_STATE_ACTIVE))
+ aState |= DFCS_PUSHED | DFCS_FLAT;
+ }
+
+ return NS_OK;
+ }
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton: {
+ EventStates contentState = GetContentState(aFrame, aAppearance);
+
+ aPart = DFC_SCROLL;
+ switch (aAppearance) {
+ case StyleAppearance::SpinnerUpbutton:
+ aState = DFCS_SCROLLUP;
+ break;
+ case StyleAppearance::SpinnerDownbutton:
+ aState = DFCS_SCROLLDOWN;
+ break;
+ default:
+ break;
+ }
+
+ if (IsDisabled(aFrame, contentState))
+ aState |= DFCS_INACTIVE;
+ else {
+ if (contentState.HasAllStates(NS_EVENT_STATE_HOVER |
+ NS_EVENT_STATE_ACTIVE))
+ aState |= DFCS_PUSHED;
+ }
+
+ return NS_OK;
+ }
+ case StyleAppearance::Resizer:
+ aPart = DFC_SCROLL;
+ aState =
+ (IsFrameRTL(aFrame)) ? DFCS_SCROLLSIZEGRIPRIGHT : DFCS_SCROLLSIZEGRIP;
+ return NS_OK;
+ case StyleAppearance::Menuseparator:
+ aPart = 0;
+ aState = 0;
+ return NS_OK;
+ case StyleAppearance::MozWindowTitlebar:
+ aPart = mozilla::widget::themeconst::WP_CAPTION;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case StyleAppearance::MozWindowTitlebarMaximized:
+ aPart = mozilla::widget::themeconst::WP_MAXCAPTION;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case StyleAppearance::MozWindowFrameLeft:
+ aPart = mozilla::widget::themeconst::WP_FRAMELEFT;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case StyleAppearance::MozWindowFrameRight:
+ aPart = mozilla::widget::themeconst::WP_FRAMERIGHT;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case StyleAppearance::MozWindowFrameBottom:
+ aPart = mozilla::widget::themeconst::WP_FRAMEBOTTOM;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case StyleAppearance::MozWindowButtonClose:
+ aPart = DFC_CAPTION;
+ aState = DFCS_CAPTIONCLOSE | GetClassicWindowFrameButtonState(
+ GetContentState(aFrame, aAppearance));
+ return NS_OK;
+ case StyleAppearance::MozWindowButtonMinimize:
+ aPart = DFC_CAPTION;
+ aState = DFCS_CAPTIONMIN | GetClassicWindowFrameButtonState(
+ GetContentState(aFrame, aAppearance));
+ return NS_OK;
+ case StyleAppearance::MozWindowButtonMaximize:
+ aPart = DFC_CAPTION;
+ aState = DFCS_CAPTIONMAX | GetClassicWindowFrameButtonState(
+ GetContentState(aFrame, aAppearance));
+ return NS_OK;
+ case StyleAppearance::MozWindowButtonRestore:
+ aPart = DFC_CAPTION;
+ aState = DFCS_CAPTIONRESTORE | GetClassicWindowFrameButtonState(
+ GetContentState(aFrame, aAppearance));
+ return NS_OK;
+ default:
+ return NS_ERROR_FAILURE;
+ }
+}
+
+// Draw classic Windows tab
+// (no system API for this, but DrawEdge can draw all the parts of a tab)
+static void DrawTab(HDC hdc, const RECT& R, int32_t aPosition, bool aSelected,
+ bool aDrawLeft, bool aDrawRight) {
+ int32_t leftFlag, topFlag, rightFlag, lightFlag, shadeFlag;
+ RECT topRect, sideRect, bottomRect, lightRect, shadeRect;
+ int32_t selectedOffset, lOffset, rOffset;
+
+ selectedOffset = aSelected ? 1 : 0;
+ lOffset = aDrawLeft ? 2 : 0;
+ rOffset = aDrawRight ? 2 : 0;
+
+ // Get info for tab orientation/position (Left, Top, Right, Bottom)
+ switch (aPosition) {
+ case BF_LEFT:
+ leftFlag = BF_TOP;
+ topFlag = BF_LEFT;
+ rightFlag = BF_BOTTOM;
+ lightFlag = BF_DIAGONAL_ENDTOPRIGHT;
+ shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT;
+
+ ::SetRect(&topRect, R.left, R.top + lOffset, R.right, R.bottom - rOffset);
+ ::SetRect(&sideRect, R.left + 2, R.top, R.right - 2 + selectedOffset,
+ R.bottom);
+ ::SetRect(&bottomRect, R.right - 2, R.top, R.right, R.bottom);
+ ::SetRect(&lightRect, R.left, R.top, R.left + 3, R.top + 3);
+ ::SetRect(&shadeRect, R.left + 1, R.bottom - 2, R.left + 2, R.bottom - 1);
+ break;
+ case BF_TOP:
+ leftFlag = BF_LEFT;
+ topFlag = BF_TOP;
+ rightFlag = BF_RIGHT;
+ lightFlag = BF_DIAGONAL_ENDTOPRIGHT;
+ shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT;
+
+ ::SetRect(&topRect, R.left + lOffset, R.top, R.right - rOffset, R.bottom);
+ ::SetRect(&sideRect, R.left, R.top + 2, R.right,
+ R.bottom - 1 + selectedOffset);
+ ::SetRect(&bottomRect, R.left, R.bottom - 1, R.right, R.bottom);
+ ::SetRect(&lightRect, R.left, R.top, R.left + 3, R.top + 3);
+ ::SetRect(&shadeRect, R.right - 2, R.top + 1, R.right - 1, R.top + 2);
+ break;
+ case BF_RIGHT:
+ leftFlag = BF_TOP;
+ topFlag = BF_RIGHT;
+ rightFlag = BF_BOTTOM;
+ lightFlag = BF_DIAGONAL_ENDTOPLEFT;
+ shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT;
+
+ ::SetRect(&topRect, R.left, R.top + lOffset, R.right, R.bottom - rOffset);
+ ::SetRect(&sideRect, R.left + 2 - selectedOffset, R.top, R.right - 2,
+ R.bottom);
+ ::SetRect(&bottomRect, R.left, R.top, R.left + 2, R.bottom);
+ ::SetRect(&lightRect, R.right - 3, R.top, R.right - 1, R.top + 2);
+ ::SetRect(&shadeRect, R.right - 2, R.bottom - 3, R.right, R.bottom - 1);
+ break;
+ case BF_BOTTOM:
+ leftFlag = BF_LEFT;
+ topFlag = BF_BOTTOM;
+ rightFlag = BF_RIGHT;
+ lightFlag = BF_DIAGONAL_ENDTOPLEFT;
+ shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT;
+
+ ::SetRect(&topRect, R.left + lOffset, R.top, R.right - rOffset, R.bottom);
+ ::SetRect(&sideRect, R.left, R.top + 2 - selectedOffset, R.right,
+ R.bottom - 2);
+ ::SetRect(&bottomRect, R.left, R.top, R.right, R.top + 2);
+ ::SetRect(&lightRect, R.left, R.bottom - 3, R.left + 2, R.bottom - 1);
+ ::SetRect(&shadeRect, R.right - 2, R.bottom - 3, R.right, R.bottom - 1);
+ break;
+ default:
+ MOZ_CRASH();
+ }
+
+ // Background
+ ::FillRect(hdc, &R, (HBRUSH)(COLOR_3DFACE + 1));
+
+ // Tab "Top"
+ ::DrawEdge(hdc, &topRect, EDGE_RAISED, BF_SOFT | topFlag);
+
+ // Tab "Bottom"
+ if (!aSelected) ::DrawEdge(hdc, &bottomRect, EDGE_RAISED, BF_SOFT | topFlag);
+
+ // Tab "Sides"
+ if (!aDrawLeft) leftFlag = 0;
+ if (!aDrawRight) rightFlag = 0;
+ ::DrawEdge(hdc, &sideRect, EDGE_RAISED, BF_SOFT | leftFlag | rightFlag);
+
+ // Tab Diagonal Corners
+ if (aDrawLeft) ::DrawEdge(hdc, &lightRect, EDGE_RAISED, BF_SOFT | lightFlag);
+
+ if (aDrawRight) ::DrawEdge(hdc, &shadeRect, EDGE_RAISED, BF_SOFT | shadeFlag);
+}
+
+static void DrawMenuImage(HDC hdc, const RECT& rc, int32_t aComponent,
+ uint32_t aColor) {
+ // This procedure creates a memory bitmap to contain the check mark, draws
+ // it into the bitmap (it is a mask image), then composes it onto the menu
+ // item in appropriate colors.
+ HDC hMemoryDC = ::CreateCompatibleDC(hdc);
+ if (hMemoryDC) {
+ // XXXjgr We should ideally be caching these, but we wont be notified when
+ // they change currently, so we can't do so easily. Same for the bitmap.
+ int checkW = ::GetSystemMetrics(SM_CXMENUCHECK);
+ int checkH = ::GetSystemMetrics(SM_CYMENUCHECK);
+
+ HBITMAP hMonoBitmap = ::CreateBitmap(checkW, checkH, 1, 1, nullptr);
+ if (hMonoBitmap) {
+ HBITMAP hPrevBitmap = (HBITMAP)::SelectObject(hMemoryDC, hMonoBitmap);
+ if (hPrevBitmap) {
+ // XXXjgr This will go pear-shaped if the image is bigger than the
+ // provided rect. What should we do?
+ RECT imgRect = {0, 0, checkW, checkH};
+ POINT imgPos = {rc.left + (rc.right - rc.left - checkW) / 2,
+ rc.top + (rc.bottom - rc.top - checkH) / 2};
+
+ // XXXzeniko Windows renders these 1px lower than you'd expect
+ if (aComponent == DFCS_MENUCHECK || aComponent == DFCS_MENUBULLET)
+ imgPos.y++;
+
+ ::DrawFrameControl(hMemoryDC, &imgRect, DFC_MENU, aComponent);
+ COLORREF oldTextCol = ::SetTextColor(hdc, 0x00000000);
+ COLORREF oldBackCol = ::SetBkColor(hdc, 0x00FFFFFF);
+ ::BitBlt(hdc, imgPos.x, imgPos.y, checkW, checkH, hMemoryDC, 0, 0,
+ SRCAND);
+ ::SetTextColor(hdc, ::GetSysColor(aColor));
+ ::SetBkColor(hdc, 0x00000000);
+ ::BitBlt(hdc, imgPos.x, imgPos.y, checkW, checkH, hMemoryDC, 0, 0,
+ SRCPAINT);
+ ::SetTextColor(hdc, oldTextCol);
+ ::SetBkColor(hdc, oldBackCol);
+ ::SelectObject(hMemoryDC, hPrevBitmap);
+ }
+ ::DeleteObject(hMonoBitmap);
+ }
+ ::DeleteDC(hMemoryDC);
+ }
+}
+
+void nsNativeThemeWin::DrawCheckedRect(HDC hdc, const RECT& rc, int32_t fore,
+ int32_t back, HBRUSH defaultBack) {
+ static WORD patBits[8] = {0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55};
+
+ HBITMAP patBmp = ::CreateBitmap(8, 8, 1, 1, patBits);
+ if (patBmp) {
+ HBRUSH brush = (HBRUSH)::CreatePatternBrush(patBmp);
+ if (brush) {
+ COLORREF oldForeColor = ::SetTextColor(hdc, ::GetSysColor(fore));
+ COLORREF oldBackColor = ::SetBkColor(hdc, ::GetSysColor(back));
+ POINT vpOrg;
+
+ ::UnrealizeObject(brush);
+ ::GetViewportOrgEx(hdc, &vpOrg);
+ ::SetBrushOrgEx(hdc, vpOrg.x + rc.left, vpOrg.y + rc.top, nullptr);
+ HBRUSH oldBrush = (HBRUSH)::SelectObject(hdc, brush);
+ ::FillRect(hdc, &rc, brush);
+ ::SetTextColor(hdc, oldForeColor);
+ ::SetBkColor(hdc, oldBackColor);
+ ::SelectObject(hdc, oldBrush);
+ ::DeleteObject(brush);
+ } else
+ ::FillRect(hdc, &rc, defaultBack);
+
+ ::DeleteObject(patBmp);
+ }
+}
+
+nsresult nsNativeThemeWin::ClassicDrawWidgetBackground(
+ gfxContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance,
+ const nsRect& aRect, const nsRect& aDirtyRect) {
+ int32_t part, state;
+ bool focused;
+ nsresult rv;
+ rv = ClassicGetThemePartAndState(aFrame, aAppearance, part, state, focused);
+ if (NS_FAILED(rv)) return rv;
+
+ if (AssumeThemePartAndStateAreTransparent(part, state)) {
+ return NS_OK;
+ }
+
+ gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel());
+ RECT widgetRect;
+ gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()),
+ dr(aDirtyRect.X(), aDirtyRect.Y(), aDirtyRect.Width(),
+ aDirtyRect.Height());
+
+ tr.Scale(1.0 / p2a);
+ dr.Scale(1.0 / p2a);
+
+ RefPtr<gfxContext> ctx = aContext;
+
+ gfxWindowsNativeDrawing nativeDrawing(
+ ctx, dr, GetWidgetNativeDrawingFlags(aAppearance));
+
+RENDER_AGAIN:
+
+ HDC hdc = nativeDrawing.BeginNativeDrawing();
+ if (!hdc) return NS_ERROR_FAILURE;
+
+ nativeDrawing.TransformToNativeRect(tr, widgetRect);
+
+ rv = NS_OK;
+ switch (aAppearance) {
+ // Draw button
+ case StyleAppearance::Button: {
+ if (focused) {
+ // draw dark button focus border first
+ HBRUSH brush;
+ brush = ::GetSysColorBrush(COLOR_3DDKSHADOW);
+ if (brush) ::FrameRect(hdc, &widgetRect, brush);
+ InflateRect(&widgetRect, -1, -1);
+ }
+ // fall-through...
+ }
+ // Draw controls supported by DrawFrameControl
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ case StyleAppearance::MozMenulistArrowButton:
+ case StyleAppearance::Resizer: {
+ int32_t oldTA;
+ // setup DC to make DrawFrameControl draw correctly
+ oldTA = ::SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
+ ::DrawFrameControl(hdc, &widgetRect, part, state);
+ ::SetTextAlign(hdc, oldTA);
+ break;
+ }
+ // Draw controls with 2px 3D inset border
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton: {
+ // Draw inset edge
+ ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+
+ // Fill in background
+ if (IsDisabled(aFrame, eventState) ||
+ (aFrame->GetContent()->IsXULElement() && IsReadOnly(aFrame)))
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
+ else
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW + 1));
+
+ break;
+ }
+ case StyleAppearance::Treeview: {
+ // Draw inset edge
+ ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+
+ // Fill in window color background
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW + 1));
+
+ break;
+ }
+ // Draw ToolTip background
+ case StyleAppearance::Tooltip:
+ ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_WINDOWFRAME));
+ InflateRect(&widgetRect, -1, -1);
+ ::FillRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_INFOBK));
+
+ break;
+ case StyleAppearance::Groupbox:
+ ::DrawEdge(hdc, &widgetRect, EDGE_ETCHED, BF_RECT | BF_ADJUST);
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
+ break;
+ // Draw 3D face background controls
+ case StyleAppearance::ProgressBar:
+ // Draw 3D border
+ ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
+ InflateRect(&widgetRect, -1, -1);
+ // fall through
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Statusbar:
+ case StyleAppearance::Resizerpanel: {
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
+
+ break;
+ }
+ // Draw 3D inset statusbar panel
+ case StyleAppearance::Statusbarpanel: {
+ if (aFrame->GetNextSibling())
+ widgetRect.right -= 2; // space between sibling status panels
+
+ ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
+
+ break;
+ }
+ // Draw scrollbar thumb
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RECT | BF_MIDDLE);
+
+ break;
+ case StyleAppearance::RangeThumb: {
+ EventStates eventState = GetContentState(aFrame, aAppearance);
+
+ ::DrawEdge(hdc, &widgetRect, EDGE_RAISED,
+ BF_RECT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
+ if (IsDisabled(aFrame, eventState)) {
+ DrawCheckedRect(hdc, widgetRect, COLOR_3DFACE, COLOR_3DHILIGHT,
+ (HBRUSH)COLOR_3DHILIGHT);
+ }
+
+ break;
+ }
+ // Draw scrollbar track background
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal: {
+ // Windows fills in the scrollbar track differently
+ // depending on whether these are equal
+ DWORD color3D, colorScrollbar, colorWindow;
+
+ color3D = ::GetSysColor(COLOR_3DFACE);
+ colorWindow = ::GetSysColor(COLOR_WINDOW);
+ colorScrollbar = ::GetSysColor(COLOR_SCROLLBAR);
+
+ if ((color3D != colorScrollbar) && (colorWindow != colorScrollbar))
+ // Use solid brush
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_SCROLLBAR + 1));
+ else {
+ DrawCheckedRect(hdc, widgetRect, COLOR_3DHILIGHT, COLOR_3DFACE,
+ (HBRUSH)COLOR_SCROLLBAR + 1);
+ }
+ // XXX should invert the part of the track being clicked here
+ // but the track is never :active
+
+ break;
+ }
+ case StyleAppearance::Scrollcorner: {
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_SCROLLBAR + 1));
+ }
+ // Draw scale track background
+ case StyleAppearance::Range: {
+ const int32_t trackWidth = 4;
+ // When rounding is necessary, we round the position of the track
+ // away from the chevron of the thumb to make it look better.
+ if (IsRangeHorizontal(aFrame)) {
+ widgetRect.top += (widgetRect.bottom - widgetRect.top - trackWidth) / 2;
+ widgetRect.bottom = widgetRect.top + trackWidth;
+ } else {
+ if (!IsFrameRTL(aFrame)) {
+ widgetRect.left +=
+ (widgetRect.right - widgetRect.left - trackWidth) / 2;
+ widgetRect.right = widgetRect.left + trackWidth;
+ } else {
+ widgetRect.right -=
+ (widgetRect.right - widgetRect.left - trackWidth) / 2;
+ widgetRect.left = widgetRect.right - trackWidth;
+ }
+ }
+
+ ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+ ::FillRect(hdc, &widgetRect, (HBRUSH)GetStockObject(GRAY_BRUSH));
+
+ break;
+ }
+ case StyleAppearance::Progresschunk: {
+ nsIFrame* stateFrame = aFrame->GetParent();
+ EventStates eventStates = GetContentState(stateFrame, aAppearance);
+
+ bool indeterminate = IsIndeterminateProgress(stateFrame, eventStates);
+ bool vertical = IsVerticalProgress(stateFrame);
+
+ nsIContent* content = aFrame->GetContent();
+ if (!indeterminate || !content) {
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
+ break;
+ }
+
+ RECT overlayRect = CalculateProgressOverlayRect(
+ aFrame, &widgetRect, vertical, indeterminate, true);
+
+ ::FillRect(hdc, &overlayRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
+
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
+ NS_WARNING("unable to animate progress widget!");
+ }
+ break;
+ }
+
+ // Draw Tab
+ case StyleAppearance::Tab: {
+ DrawTab(hdc, widgetRect, IsBottomTab(aFrame) ? BF_BOTTOM : BF_TOP,
+ IsSelectedTab(aFrame), !IsRightToSelectedTab(aFrame),
+ !IsLeftToSelectedTab(aFrame));
+
+ break;
+ }
+ case StyleAppearance::Tabpanels:
+ ::DrawEdge(hdc, &widgetRect, EDGE_RAISED,
+ BF_SOFT | BF_MIDDLE | BF_LEFT | BF_RIGHT | BF_BOTTOM);
+
+ break;
+ case StyleAppearance::Menubar:
+ break;
+ case StyleAppearance::Menupopup:
+ NS_ASSERTION(nsUXThemeData::AreFlatMenusEnabled(),
+ "Classic menus are styled entirely through CSS");
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_MENU + 1));
+ ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_BTNSHADOW));
+ break;
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem:
+ // part == 0 for normal items
+ // part == 1 for top-level menu items
+ if (nsUXThemeData::AreFlatMenusEnabled()) {
+ // Not disabled and hot/pushed.
+ if ((state & (DFCS_HOT | DFCS_PUSHED)) != 0) {
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_MENUHILIGHT + 1));
+ ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_HIGHLIGHT));
+ }
+ } else {
+ if (part == 1) {
+ if ((state & DFCS_INACTIVE) == 0) {
+ if ((state & DFCS_PUSHED) != 0) {
+ ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT);
+ } else if ((state & DFCS_HOT) != 0) {
+ ::DrawEdge(hdc, &widgetRect, BDR_RAISEDINNER, BF_RECT);
+ }
+ }
+ } else {
+ if ((state & (DFCS_HOT | DFCS_PUSHED)) != 0) {
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
+ }
+ }
+ }
+ break;
+ case StyleAppearance::Menucheckbox:
+ case StyleAppearance::Menuradio:
+ if (!(state & DFCS_CHECKED)) break; // nothin' to do
+ case StyleAppearance::Menuarrow: {
+ uint32_t color = COLOR_MENUTEXT;
+ if ((state & DFCS_INACTIVE))
+ color = COLOR_GRAYTEXT;
+ else if ((state & DFCS_HOT))
+ color = COLOR_HIGHLIGHTTEXT;
+
+ if (aAppearance == StyleAppearance::Menucheckbox)
+ DrawMenuImage(hdc, widgetRect, DFCS_MENUCHECK, color);
+ else if (aAppearance == StyleAppearance::Menuradio)
+ DrawMenuImage(hdc, widgetRect, DFCS_MENUBULLET, color);
+ else if (aAppearance == StyleAppearance::Menuarrow)
+ DrawMenuImage(hdc, widgetRect,
+ (state & DFCS_RTL) ? DFCS_MENUARROWRIGHT : DFCS_MENUARROW,
+ color);
+ break;
+ }
+ case StyleAppearance::Menuseparator: {
+ // separators are offset by a bit (see menu.css)
+ widgetRect.left++;
+ widgetRect.right--;
+
+ // This magic number is brought to you by the value in menu.css
+ widgetRect.top += 4;
+ // Our rectangles are 1 pixel high (see border size in menu.css)
+ widgetRect.bottom = widgetRect.top + 1;
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_3DSHADOW + 1));
+ widgetRect.top++;
+ widgetRect.bottom++;
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_3DHILIGHT + 1));
+ break;
+ }
+
+ case StyleAppearance::MozWindowTitlebar:
+ case StyleAppearance::MozWindowTitlebarMaximized: {
+ RECT rect = widgetRect;
+ int32_t offset = GetSystemMetrics(SM_CXFRAME);
+
+ // first fill the area to the color of the window background
+ FillRect(hdc, &rect, (HBRUSH)(COLOR_3DFACE + 1));
+
+ // inset the caption area so it doesn't overflow.
+ rect.top += offset;
+ // if enabled, draw a gradient titlebar background, otherwise
+ // fill with a solid color.
+ BOOL bFlag = TRUE;
+ SystemParametersInfo(SPI_GETGRADIENTCAPTIONS, 0, &bFlag, 0);
+ if (!bFlag) {
+ if (state == mozilla::widget::themeconst::FS_ACTIVE)
+ FillRect(hdc, &rect, (HBRUSH)(COLOR_ACTIVECAPTION + 1));
+ else
+ FillRect(hdc, &rect, (HBRUSH)(COLOR_INACTIVECAPTION + 1));
+ } else {
+ DWORD startColor, endColor;
+ if (state == mozilla::widget::themeconst::FS_ACTIVE) {
+ startColor = GetSysColor(COLOR_ACTIVECAPTION);
+ endColor = GetSysColor(COLOR_GRADIENTACTIVECAPTION);
+ } else {
+ startColor = GetSysColor(COLOR_INACTIVECAPTION);
+ endColor = GetSysColor(COLOR_GRADIENTINACTIVECAPTION);
+ }
+
+ TRIVERTEX vertex[2];
+ vertex[0].x = rect.left;
+ vertex[0].y = rect.top;
+ vertex[0].Red = GetRValue(startColor) << 8;
+ vertex[0].Green = GetGValue(startColor) << 8;
+ vertex[0].Blue = GetBValue(startColor) << 8;
+ vertex[0].Alpha = 0;
+
+ vertex[1].x = rect.right;
+ vertex[1].y = rect.bottom;
+ vertex[1].Red = GetRValue(endColor) << 8;
+ vertex[1].Green = GetGValue(endColor) << 8;
+ vertex[1].Blue = GetBValue(endColor) << 8;
+ vertex[1].Alpha = 0;
+
+ GRADIENT_RECT gRect;
+ gRect.UpperLeft = 0;
+ gRect.LowerRight = 1;
+ // available on win2k & up
+ GradientFill(hdc, vertex, 2, &gRect, 1, GRADIENT_FILL_RECT_H);
+ }
+
+ if (aAppearance == StyleAppearance::MozWindowTitlebar) {
+ // frame things up with a top raised border.
+ DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_TOP);
+ }
+ break;
+ }
+
+ case StyleAppearance::MozWindowFrameLeft:
+ DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_LEFT);
+ break;
+
+ case StyleAppearance::MozWindowFrameRight:
+ DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RIGHT);
+ break;
+
+ case StyleAppearance::MozWindowFrameBottom:
+ DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_BOTTOM);
+ break;
+
+ case StyleAppearance::MozWindowButtonClose:
+ case StyleAppearance::MozWindowButtonMinimize:
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore: {
+ if (aAppearance == StyleAppearance::MozWindowButtonMinimize) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE);
+ } else if (aAppearance == StyleAppearance::MozWindowButtonMaximize ||
+ aAppearance == StyleAppearance::MozWindowButtonRestore) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE);
+ } else if (aAppearance == StyleAppearance::MozWindowButtonClose) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_CLOSE);
+ }
+ int32_t oldTA = SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
+ DrawFrameControl(hdc, &widgetRect, part, state);
+ SetTextAlign(hdc, oldTA);
+ break;
+ }
+
+ default:
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ nativeDrawing.EndNativeDrawing();
+
+ if (NS_FAILED(rv)) return rv;
+
+ if (nativeDrawing.ShouldRenderAgain()) goto RENDER_AGAIN;
+
+ nativeDrawing.PaintToContext();
+
+ return rv;
+}
+
+uint32_t nsNativeThemeWin::GetWidgetNativeDrawingFlags(
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::FocusOutline:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
+ gfxWindowsNativeDrawing::CAN_AXIS_ALIGNED_SCALE |
+ gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
+
+ // the dropdown button /almost/ renders correctly with scaling,
+ // except that the graphic in the dropdown button (the downward arrow)
+ // doesn't get scaled up.
+ case StyleAppearance::MozMenulistArrowButton:
+ // these are definitely no; they're all graphics that don't get scaled up
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio:
+ case StyleAppearance::Groupbox:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem:
+ case StyleAppearance::Menucheckbox:
+ case StyleAppearance::Menuradio:
+ case StyleAppearance::Menuarrow:
+ return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
+ gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
+ gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
+
+ // need to check these others
+ default:
+ return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
+ gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
+ gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
+ }
+}
+
+// This tries to draw a Windows 10 style scrollbar with given colors.
+bool nsNativeThemeWin::MayDrawCustomScrollbarPart(gfxContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aClipRect) {
+ ComputedStyle* style = ScrollbarUtil::GetCustomScrollbarStyle(aFrame);
+ if (!style) {
+ return false;
+ }
+
+ EventStates eventStates = GetContentState(aFrame, aAppearance);
+
+ gfxContextAutoSaveRestore autoSave(aContext);
+ RefPtr<gfxContext> ctx = aContext;
+ DrawTarget* dt = ctx->GetDrawTarget();
+ gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel());
+ gfxRect rect = ThebesRect(NSRectToSnappedRect(aRect, p2a, *dt));
+ gfxRect clipRect = ThebesRect(NSRectToSnappedRect(aClipRect, p2a, *dt));
+ ctx->Clip(clipRect);
+
+ nscolor trackColor = ScrollbarUtil::GetScrollbarTrackColor(aFrame);
+
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::Scrollcorner: {
+ ctx->SetColor(sRGBColor::FromABGR(trackColor));
+ ctx->Rectangle(rect);
+ ctx->Fill();
+ return true;
+ }
+ default:
+ break;
+ }
+
+ // Scrollbar thumb and button are two CSS pixels thinner than the track.
+ gfxRect bgRect = rect;
+ gfxFloat dev2css = round(AppUnitsPerCSSPixel() / p2a);
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ bgRect.Deflate(dev2css, 0);
+ break;
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ bgRect.Deflate(0, dev2css);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown widget type");
+ }
+
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal: {
+ nscolor faceColor =
+ ScrollbarUtil::GetScrollbarThumbColor(aFrame, eventStates);
+ ctx->SetColor(sRGBColor::FromABGR(faceColor));
+ ctx->Rectangle(bgRect);
+ ctx->Fill();
+ break;
+ }
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight: {
+ nscolor buttonColor =
+ ScrollbarUtil::GetScrollbarButtonColor(trackColor, eventStates);
+ ctx->SetColor(sRGBColor::FromABGR(buttonColor));
+ ctx->Rectangle(bgRect);
+ ctx->Fill();
+
+ // We use the path of scrollbar up arrow on Windows 10 which is
+ // in a 17x17 area.
+ const gfxFloat kSize = 17.0;
+ // Setup the transform matrix.
+ gfxFloat width = rect.Width();
+ gfxFloat height = rect.Height();
+ gfxFloat size = std::min(width, height);
+ gfxFloat left = (width - size) / 2.0 + rect.x;
+ gfxFloat top = (height - size) / 2.0 + rect.y;
+ gfxFloat scale = size / kSize;
+ gfxFloat rad = 0.0;
+ if (aAppearance == StyleAppearance::ScrollbarbuttonRight) {
+ rad = M_PI / 2;
+ } else if (aAppearance == StyleAppearance::ScrollbarbuttonDown) {
+ rad = M_PI;
+ } else if (aAppearance == StyleAppearance::ScrollbarbuttonLeft) {
+ rad = -M_PI / 2;
+ }
+ gfx::Matrix mat = ctx->CurrentMatrix();
+ mat.PreTranslate(left, top);
+ mat.PreScale(scale, scale);
+ if (rad != 0.0) {
+ const gfxFloat kOffset = kSize / 2.0;
+ mat.PreTranslate(kOffset, kOffset);
+ mat.PreRotate(rad);
+ mat.PreTranslate(-kOffset, -kOffset);
+ }
+ ctx->SetMatrix(mat);
+ // The arrow should not have antialias applied.
+ ctx->SetAntialiasMode(gfx::AntialiasMode::NONE);
+ // Set the arrow path.
+ ctx->NewPath();
+ ctx->MoveTo(gfxPoint(5.0, 9.0));
+ ctx->LineTo(gfxPoint(8.5, 6.0));
+ ctx->LineTo(gfxPoint(12.0, 9.0));
+ ctx->LineTo(gfxPoint(12.0, 12.0));
+ ctx->LineTo(gfxPoint(8.5, 9.0));
+ ctx->LineTo(gfxPoint(5.0, 12.0));
+ ctx->ClosePath();
+ // And paint the arrow.
+ nscolor arrowColor = ScrollbarUtil::GetScrollbarArrowColor(buttonColor);
+ ctx->SetColor(sRGBColor::FromABGR(arrowColor));
+ ctx->Fill();
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown widget type");
+ }
+ return true;
+}
+
+///////////////////////////////////////////
+// Creation Routine
+///////////////////////////////////////////
+
+already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() {
+ static nsCOMPtr<nsITheme> inst;
+
+ if (!inst) {
+ inst = new nsNativeThemeWin();
+ ClearOnShutdown(&inst);
+ }
+
+ return do_AddRef(inst);
+}
diff --git a/widget/windows/nsNativeThemeWin.h b/widget/windows/nsNativeThemeWin.h
new file mode 100644
index 0000000000..0b41d6a2fa
--- /dev/null
+++ b/widget/windows/nsNativeThemeWin.h
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNativeThemeWin_h
+#define nsNativeThemeWin_h
+
+#include "nsITheme.h"
+#include "nsCOMPtr.h"
+#include "nsAtom.h"
+#include "nsNativeTheme.h"
+#include "nsStyleConsts.h"
+#include "nsUXThemeConstants.h"
+#include "nsUXThemeData.h"
+#include "gfxTypes.h"
+#include <windows.h>
+#include "mozilla/Maybe.h"
+#include "mozilla/TimeStamp.h"
+#include "nsSize.h"
+
+class nsNativeThemeWin : private nsNativeTheme, public nsITheme {
+ virtual ~nsNativeThemeWin();
+
+ public:
+ typedef mozilla::TimeStamp TimeStamp;
+ typedef mozilla::TimeDuration TimeDuration;
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // The nsITheme interface.
+ NS_IMETHOD DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) override;
+
+ [[nodiscard]] LayoutDeviceIntMargin GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ bool GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) override;
+
+ virtual bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) override;
+
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) override;
+
+ virtual Transparency GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) override;
+
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) override;
+
+ NS_IMETHOD ThemeChanged() override;
+
+ bool ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ bool WidgetIsContainer(StyleAppearance aAppearance) override;
+
+ bool ThemeDrawsFocusForWidget(StyleAppearance aAppearance) override;
+
+ bool ThemeWantsButtonInnerFocusRing(StyleAppearance) override { return true; }
+
+ bool ThemeNeedsComboboxDropmarker() override;
+
+ virtual bool WidgetAppearanceDependsOnWindowFocus(
+ StyleAppearance aAppearance) override;
+
+ enum { eThemeGeometryTypeWindowButtons = eThemeGeometryTypeUnknown + 1 };
+ virtual ThemeGeometryType ThemeGeometryTypeForWidget(
+ nsIFrame* aFrame, StyleAppearance aAppearance) override;
+
+ nsNativeThemeWin();
+
+ protected:
+ mozilla::Maybe<nsUXThemeClass> GetThemeClass(StyleAppearance aAppearance);
+ HANDLE GetTheme(StyleAppearance aAppearance);
+ nsresult GetThemePartAndState(nsIFrame* aFrame, StyleAppearance aAppearance,
+ int32_t& aPart, int32_t& aState);
+ nsresult ClassicGetThemePartAndState(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ int32_t& aPart, int32_t& aState,
+ bool& aFocused);
+ nsresult ClassicDrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aClipRect);
+ [[nodiscard]] LayoutDeviceIntMargin ClassicGetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance);
+ bool ClassicGetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult);
+ nsresult ClassicGetMinimumWidgetSize(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable);
+ bool ClassicThemeSupportsWidget(nsIFrame* aFrame,
+ StyleAppearance aAppearance);
+ void DrawCheckedRect(HDC hdc, const RECT& rc, int32_t fore, int32_t back,
+ HBRUSH defaultBack);
+ bool MayDrawCustomScrollbarPart(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect, const nsRect& aClipRect);
+ uint32_t GetWidgetNativeDrawingFlags(StyleAppearance aAppearance);
+ int32_t StandardGetState(nsIFrame* aFrame, StyleAppearance aAppearance,
+ bool wantFocused);
+ bool IsMenuActive(nsIFrame* aFrame, StyleAppearance aAppearance);
+ RECT CalculateProgressOverlayRect(nsIFrame* aFrame, RECT* aWidgetRect,
+ bool aIsVertical, bool aIsIndeterminate,
+ bool aIsClassic);
+ void DrawThemedProgressMeter(nsIFrame* aFrame, StyleAppearance aAppearance,
+ HANDLE aTheme, HDC aHdc, int aPart, int aState,
+ RECT* aWidgetRect, RECT* aClipRect);
+
+ [[nodiscard]] LayoutDeviceIntMargin GetCachedWidgetBorder(
+ HANDLE aTheme, nsUXThemeClass aThemeClass, StyleAppearance aAppearance,
+ int32_t aPart, int32_t aState);
+
+ nsresult GetCachedMinimumWidgetSize(nsIFrame* aFrame, HANDLE aTheme,
+ nsUXThemeClass aThemeClass,
+ StyleAppearance aAppearance,
+ int32_t aPart, int32_t aState,
+ THEMESIZE aSizeReq,
+ mozilla::LayoutDeviceIntSize* aResult);
+
+ SIZE GetCachedGutterSize(HANDLE theme);
+
+ private:
+ TimeStamp mProgressDeterminateTimeStamp;
+ TimeStamp mProgressIndeterminateTimeStamp;
+
+ // eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT is about 800 at the time of
+ // writing this, and nsIntMargin is 16 bytes wide, which makes this cache (1/8
+ // + 16) * 800 bytes, or about ~12KB. We could probably reduce this cache to
+ // 3KB by caching on the aAppearance value instead, but there would be some
+ // uncacheable values, since we derive some theme parts from other arguments.
+ uint8_t
+ mBorderCacheValid[(eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT + 7) /
+ 8];
+ LayoutDeviceIntMargin
+ mBorderCache[eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT];
+
+ // See the above not for mBorderCache and friends. However
+ // mozilla::LayoutDeviceIntSize is half the size of nsIntMargin, making the
+ // cache roughly half as large. In total the caches should come to about 18KB.
+ uint8_t mMinimumWidgetSizeCacheValid
+ [(eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT + 7) / 8];
+ mozilla::LayoutDeviceIntSize
+ mMinimumWidgetSizeCache[eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT];
+
+ bool mGutterSizeCacheValid;
+ SIZE mGutterSizeCache;
+};
+
+#endif
diff --git a/widget/windows/nsPrintDialogUtil.cpp b/widget/windows/nsPrintDialogUtil.cpp
new file mode 100644
index 0000000000..f624018b48
--- /dev/null
+++ b/widget/windows/nsPrintDialogUtil.cpp
@@ -0,0 +1,371 @@
+/* -*- 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/. */
+
+/* -------------------------------------------------------------------
+To Build This:
+
+ You need to add this to the the makefile.win in mozilla/dom/base:
+
+ .\$(OBJDIR)\nsFlyOwnPrintDialog.obj \
+
+
+ And this to the makefile.win in mozilla/content/build:
+
+WIN_LIBS= \
+ winspool.lib \
+ comctl32.lib \
+ comdlg32.lib
+
+---------------------------------------------------------------------- */
+
+#include "plstr.h"
+#include <windows.h>
+#include <tchar.h>
+
+#include <unknwn.h>
+#include <commdlg.h>
+
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/ScopeExit.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsWin.h"
+#include "nsIPrinterList.h"
+
+#include "nsRect.h"
+
+#include "nsCRT.h"
+#include "prenv.h" /* for PR_GetEnv */
+
+#include <windows.h>
+#include <winspool.h>
+
+// For Localization
+
+// For NS_CopyUnicodeToNative
+#include "nsNativeCharsetUtils.h"
+
+// This is for extending the dialog
+#include <dlgs.h>
+
+#include "nsWindowsHelpers.h"
+#include "WinUtils.h"
+
+//-----------------------------------------------
+// Global Data
+//-----------------------------------------------
+
+static HWND gParentWnd = nullptr;
+
+//----------------------------------------------------------------------------------
+// Returns a Global Moveable Memory Handle to a DevMode
+// from the Printer by the name of aPrintName
+//
+// NOTE:
+// This function assumes that aPrintName has already been converted from
+// unicode
+//
+static nsReturnRef<nsHGLOBAL> CreateGlobalDevModeAndInit(
+ const nsString& aPrintName, nsIPrintSettings* aPS) {
+ nsHPRINTER hPrinter = nullptr;
+ // const cast kludge for silly Win32 api's
+ LPWSTR printName =
+ const_cast<wchar_t*>(static_cast<const wchar_t*>(aPrintName.get()));
+ BOOL status = ::OpenPrinterW(printName, &hPrinter, nullptr);
+ if (!status) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ // Make sure hPrinter is closed on all paths
+ nsAutoPrinter autoPrinter(hPrinter);
+
+ // Get the buffer size
+ LONG needed = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr,
+ nullptr, 0);
+ if (needed < 0) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ // Some drivers do not return the correct size for their DEVMODE, so we
+ // over-allocate to try and compensate.
+ // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5)
+ needed *= 2;
+ nsAutoDevMode newDevMode(
+ (LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, needed));
+ if (!newDevMode) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, needed);
+ nsAutoGlobalMem globalDevMode(hDevMode);
+ if (!hDevMode) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ LONG ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode,
+ nullptr, DM_OUT_BUFFER);
+ if (ret != IDOK) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ // Lock memory and copy contents from DEVMODE (current printer)
+ // to Global Memory DEVMODE
+ LPDEVMODEW devMode = (DEVMODEW*)::GlobalLock(hDevMode);
+ if (!devMode) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ memcpy(devMode, newDevMode.get(), needed);
+ // Initialize values from the PrintSettings
+ nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS);
+ MOZ_ASSERT(psWin);
+ psWin->CopyToNative(devMode);
+
+ // Sets back the changes we made to the DevMode into the Printer Driver
+ ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode,
+ DM_IN_BUFFER | DM_OUT_BUFFER);
+ if (ret != IDOK) {
+ ::GlobalUnlock(hDevMode);
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ ::GlobalUnlock(hDevMode);
+
+ return globalDevMode.out();
+}
+
+//------------------------------------------------------------------
+// helper
+static void GetDefaultPrinterNameFromGlobalPrinters(nsAString& aPrinterName) {
+ aPrinterName.Truncate();
+ nsCOMPtr<nsIPrinterList> printerList =
+ do_GetService("@mozilla.org/gfx/printerlist;1");
+ if (printerList) {
+ printerList->GetSystemDefaultPrinterName(aPrinterName);
+ }
+}
+
+//------------------------------------------------------------------
+// Displays the native Print Dialog
+static nsresult ShowNativePrintDialog(HWND aHWnd,
+ nsIPrintSettings* aPrintSettings) {
+ // NS_ENSURE_ARG_POINTER(aHWnd);
+ NS_ENSURE_ARG_POINTER(aPrintSettings);
+
+ // Get the Print Name to be used
+ nsString printerName;
+ aPrintSettings->GetPrinterName(printerName);
+
+ // If there is no name then use the default printer
+ if (printerName.IsEmpty()) {
+ GetDefaultPrinterNameFromGlobalPrinters(printerName);
+ } else {
+ HANDLE hPrinter = nullptr;
+ if (!::OpenPrinterW(const_cast<wchar_t*>(
+ static_cast<const wchar_t*>(printerName.get())),
+ &hPrinter, nullptr)) {
+ // If the last used printer is not found, we should use default printer.
+ GetDefaultPrinterNameFromGlobalPrinters(printerName);
+ } else {
+ ::ClosePrinter(hPrinter);
+ }
+ }
+
+ // Now create a DEVNAMES struct so the the dialog is initialized correctly.
+
+ uint32_t len = printerName.Length();
+ nsHGLOBAL hDevNames =
+ ::GlobalAlloc(GHND, sizeof(wchar_t) * (len + 1) + sizeof(DEVNAMES));
+ nsAutoGlobalMem autoDevNames(hDevNames);
+ if (!hDevNames) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ DEVNAMES* pDevNames = (DEVNAMES*)::GlobalLock(hDevNames);
+ if (!pDevNames) {
+ return NS_ERROR_FAILURE;
+ }
+ pDevNames->wDriverOffset = sizeof(DEVNAMES) / sizeof(wchar_t);
+ pDevNames->wDeviceOffset = sizeof(DEVNAMES) / sizeof(wchar_t);
+ pDevNames->wOutputOffset = sizeof(DEVNAMES) / sizeof(wchar_t) + len;
+ pDevNames->wDefault = 0;
+
+ memcpy(pDevNames + 1, printerName.get(), (len + 1) * sizeof(wchar_t));
+ ::GlobalUnlock(hDevNames);
+
+ // Create a Moveable Memory Object that holds a new DevMode
+ // from the Printer Name
+ // The PRINTDLG.hDevMode requires that it be a moveable memory object
+ // NOTE: autoDevMode is automatically freed when any error occurred
+ nsAutoGlobalMem autoDevMode(
+ CreateGlobalDevModeAndInit(printerName, aPrintSettings));
+
+ // Prepare to Display the Print Dialog
+ // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms646942(v=vs.85)
+ // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-printdlgexw
+ PRINTDLGEXW prntdlg;
+ memset(&prntdlg, 0, sizeof(prntdlg));
+
+ prntdlg.lStructSize = sizeof(prntdlg);
+ prntdlg.hwndOwner = aHWnd;
+ prntdlg.hDevMode = autoDevMode.get();
+ prntdlg.hDevNames = hDevNames;
+ prntdlg.hDC = nullptr;
+ prntdlg.Flags = PD_ALLPAGES | PD_RETURNIC | PD_USEDEVMODECOPIESANDCOLLATE |
+ PD_COLLATE | PD_NOCURRENTPAGE;
+
+ // If there is a current selection then enable the "Selection" radio button
+ if (!aPrintSettings->GetIsPrintSelectionRBEnabled()) {
+ prntdlg.Flags |= PD_NOSELECTION;
+ }
+
+ // 10 seems like a reasonable max number of ranges to support by default if
+ // the user doesn't choose a greater thing in the UI.
+ constexpr size_t kMinSupportedRanges = 10;
+
+ AutoTArray<PRINTPAGERANGE, kMinSupportedRanges> winPageRanges;
+ // Set up the page ranges.
+ {
+ AutoTArray<int32_t, kMinSupportedRanges * 2> pageRanges;
+ aPrintSettings->GetPageRanges(pageRanges);
+ // If there is a specified page range then enable the "Custom" radio button
+ if (!pageRanges.IsEmpty()) {
+ prntdlg.Flags |= PD_PAGENUMS;
+ }
+
+ const size_t specifiedRanges = pageRanges.Length() / 2;
+ const size_t maxRanges = std::max(kMinSupportedRanges, specifiedRanges);
+
+ prntdlg.nMaxPageRanges = maxRanges;
+ prntdlg.nPageRanges = specifiedRanges;
+
+ winPageRanges.SetCapacity(maxRanges);
+ for (size_t i = 0; i < pageRanges.Length(); i += 2) {
+ PRINTPAGERANGE* range = winPageRanges.AppendElement();
+ range->nFromPage = pageRanges[i];
+ range->nToPage = pageRanges[i + 1];
+ }
+ prntdlg.lpPageRanges = winPageRanges.Elements();
+
+ prntdlg.nMinPage = 1;
+ // TODO(emilio): Could probably get the right page number here from the
+ // new print UI.
+ prntdlg.nMaxPage = 0xFFFF;
+ }
+
+ // NOTE(emilio): This can always be 1 because we use the DEVMODE copies
+ // feature (see PD_USEDEVMODECOPIESANDCOLLATE).
+ prntdlg.nCopies = 1;
+
+ prntdlg.hInstance = nullptr;
+ prntdlg.lpPrintTemplateName = nullptr;
+
+ prntdlg.lpCallback = nullptr;
+ prntdlg.nPropertyPages = 0;
+ prntdlg.lphPropertyPages = nullptr;
+
+ prntdlg.nStartPage = START_PAGE_GENERAL;
+ prntdlg.dwResultAction = 0;
+
+ HRESULT result;
+ {
+ mozilla::widget::WinUtils::AutoSystemDpiAware dpiAwareness;
+ mozilla::BackgroundHangMonitor().NotifyWait();
+ result = ::PrintDlgExW(&prntdlg);
+ }
+
+ auto cancelOnExit = mozilla::MakeScopeExit([&] {
+ ::SetFocus(aHWnd);
+ aPrintSettings->SetIsCancelled(true);
+ });
+
+ if (NS_WARN_IF(!SUCCEEDED(result))) {
+#ifdef DEBUG
+ printf_stderr("PrintDlgExW failed with %x\n", result);
+#endif
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_WARN_IF(prntdlg.dwResultAction != PD_RESULT_PRINT)) {
+ return NS_ERROR_ABORT;
+ }
+ // check to make sure we don't have any nullptr pointers
+ NS_ENSURE_TRUE(prntdlg.hDevMode, NS_ERROR_ABORT);
+ NS_ENSURE_TRUE(prntdlg.hDevNames, NS_ERROR_ABORT);
+ // Lock the deviceNames and check for nullptr
+ DEVNAMES* devnames = (DEVNAMES*)::GlobalLock(prntdlg.hDevNames);
+ NS_ENSURE_TRUE(devnames, NS_ERROR_ABORT);
+
+ char16_t* device = &(((char16_t*)devnames)[devnames->wDeviceOffset]);
+ char16_t* driver = &(((char16_t*)devnames)[devnames->wDriverOffset]);
+
+ // Check to see if the "Print To File" control is checked
+ // then take the name from devNames and set it in the PrintSettings
+ //
+ // NOTE:
+ // As per Microsoft SDK documentation the returned value offset from
+ // devnames->wOutputOffset is either "FILE:" or nullptr
+ // if the "Print To File" checkbox is checked it MUST be "FILE:"
+ // We assert as an extra safety check.
+ if (prntdlg.Flags & PD_PRINTTOFILE) {
+ char16ptr_t fileName = &(((wchar_t*)devnames)[devnames->wOutputOffset]);
+ NS_ASSERTION(wcscmp(fileName, L"FILE:") == 0, "FileName must be `FILE:`");
+ aPrintSettings->SetToFileName(nsDependentString(fileName));
+ aPrintSettings->SetPrintToFile(true);
+ } else {
+ // clear "print to file" info
+ aPrintSettings->SetPrintToFile(false);
+ aPrintSettings->SetToFileName(u""_ns);
+ }
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings));
+ MOZ_RELEASE_ASSERT(psWin);
+
+ // Setup local Data members
+ psWin->SetDeviceName(nsDependentString(device));
+ psWin->SetDriverName(nsDependentString(driver));
+
+ // Fill the print options with the info from the dialog
+ aPrintSettings->SetPrinterName(nsDependentString(device));
+ aPrintSettings->SetPrintSelectionOnly(prntdlg.Flags & PD_SELECTION);
+
+ AutoTArray<int32_t, kMinSupportedRanges * 2> pageRanges;
+ if (prntdlg.Flags & PD_PAGENUMS) {
+ pageRanges.SetCapacity(prntdlg.nPageRanges * 2);
+ for (const auto& range : Span(prntdlg.lpPageRanges, prntdlg.nPageRanges)) {
+ pageRanges.AppendElement(range.nFromPage);
+ pageRanges.AppendElement(range.nToPage);
+ }
+ }
+ aPrintSettings->SetPageRanges(pageRanges);
+
+ // Unlock DeviceNames
+ ::GlobalUnlock(prntdlg.hDevNames);
+
+ // Transfer the settings from the native data to the PrintSettings
+ LPDEVMODEW devMode = (LPDEVMODEW)::GlobalLock(prntdlg.hDevMode);
+ if (!devMode || !prntdlg.hDC) {
+ return NS_ERROR_FAILURE;
+ }
+ psWin->SetDevMode(devMode); // copies DevMode
+ psWin->CopyFromNative(prntdlg.hDC, devMode);
+ ::GlobalUnlock(prntdlg.hDevMode);
+ ::DeleteDC(prntdlg.hDC);
+
+ cancelOnExit.release();
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------------
+//-- Show Print Dialog
+//----------------------------------------------------------------------------------
+nsresult NativeShowPrintDialog(HWND aHWnd, nsIPrintSettings* aPrintSettings) {
+ nsresult rv = ShowNativePrintDialog(aHWnd, aPrintSettings);
+ if (aHWnd) {
+ ::DestroyWindow(aHWnd);
+ }
+
+ return rv;
+}
diff --git a/widget/windows/nsPrintDialogUtil.h b/widget/windows/nsPrintDialogUtil.h
new file mode 100644
index 0000000000..3599118880
--- /dev/null
+++ b/widget/windows/nsPrintDialogUtil.h
@@ -0,0 +1,10 @@
+/* -*- 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 nsFlyOwnDialog_h___
+#define nsFlyOwnDialog_h___
+
+nsresult NativeShowPrintDialog(HWND aHWnd, nsIPrintSettings* aPrintSettings);
+
+#endif /* nsFlyOwnDialog_h___ */
diff --git a/widget/windows/nsPrintDialogWin.cpp b/widget/windows/nsPrintDialogWin.cpp
new file mode 100644
index 0000000000..40e4b0356f
--- /dev/null
+++ b/widget/windows/nsPrintDialogWin.cpp
@@ -0,0 +1,180 @@
+/* -*- 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 "nsPrintDialogWin.h"
+
+#include "nsArray.h"
+#include "nsCOMPtr.h"
+#include "nsIBaseWindow.h"
+#include "nsIBrowserChild.h"
+#include "nsIDialogParamBlock.h"
+#include "nsIDocShell.h"
+#include "nsIEmbeddingSiteWindow.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPrintSettings.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIWidget.h"
+#include "nsPrintDialogUtil.h"
+#include "nsIPrintSettings.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsPIDOMWindow.h"
+#include "nsQueryObject.h"
+
+static const char* kPageSetupDialogURL =
+ "chrome://global/content/printPageSetup.xhtml";
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+/**
+ * ParamBlock
+ */
+
+class ParamBlock {
+ public:
+ ParamBlock() { mBlock = 0; }
+ ~ParamBlock() { NS_IF_RELEASE(mBlock); }
+ nsresult Init() {
+ return CallCreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID, &mBlock);
+ }
+ nsIDialogParamBlock* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN {
+ return mBlock;
+ }
+ operator nsIDialogParamBlock* const() { return mBlock; }
+
+ private:
+ nsIDialogParamBlock* mBlock;
+};
+
+NS_IMPL_ISUPPORTS(nsPrintDialogServiceWin, nsIPrintDialogService)
+
+nsPrintDialogServiceWin::nsPrintDialogServiceWin() {}
+
+nsPrintDialogServiceWin::~nsPrintDialogServiceWin() {}
+
+NS_IMETHODIMP
+nsPrintDialogServiceWin::Init() {
+ nsresult rv;
+ mWatcher = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceWin::Show(nsPIDOMWindowOuter* aParent,
+ nsIPrintSettings* aSettings) {
+ NS_ENSURE_ARG(aParent);
+ HWND hWnd = GetHWNDForDOMWindow(aParent);
+ NS_ASSERTION(hWnd, "Couldn't get native window for PRint Dialog!");
+
+ return NativeShowPrintDialog(hWnd, aSettings);
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceWin::ShowPageSetup(nsPIDOMWindowOuter* aParent,
+ nsIPrintSettings* aNSSettings) {
+ NS_ENSURE_ARG(aParent);
+ NS_ENSURE_ARG(aNSSettings);
+
+ ParamBlock block;
+ nsresult rv = block.Init();
+ if (NS_FAILED(rv)) return rv;
+
+ block->SetInt(0, 0);
+ rv = DoDialog(aParent, block, aNSSettings, kPageSetupDialogURL);
+
+ // if aWebBrowserPrint is not null then we are printing
+ // so we want to pass back NS_ERROR_ABORT on cancel
+ if (NS_SUCCEEDED(rv)) {
+ int32_t status;
+ block->GetInt(0, &status);
+ return status == 0 ? NS_ERROR_ABORT : NS_OK;
+ }
+
+ // We don't call nsPrintSettingsService::SavePrintSettingsToPrefs here since
+ // it's called for us in printPageSetup.js. Maybe we should move that call
+ // here for consistency with the other platforms though?
+
+ return rv;
+}
+
+nsresult nsPrintDialogServiceWin::DoDialog(mozIDOMWindowProxy* aParent,
+ nsIDialogParamBlock* aParamBlock,
+ nsIPrintSettings* aPS,
+ const char* aChromeURL) {
+ NS_ENSURE_ARG(aParamBlock);
+ NS_ENSURE_ARG(aPS);
+ NS_ENSURE_ARG(aChromeURL);
+
+ if (!mWatcher) return NS_ERROR_FAILURE;
+
+ // get a parent, if at all possible
+ // (though we'd rather this didn't fail, it's OK if it does. so there's
+ // no failure or null check.)
+ // retain ownership for method lifetime
+ nsCOMPtr<mozIDOMWindowProxy> activeParent;
+ if (!aParent) {
+ mWatcher->GetActiveWindow(getter_AddRefs(activeParent));
+ aParent = activeParent;
+ }
+
+ // create a nsIMutableArray of the parameters
+ // being passed to the window
+ nsCOMPtr<nsIMutableArray> array = nsArray::Create();
+
+ nsCOMPtr<nsISupports> psSupports(do_QueryInterface(aPS));
+ NS_ASSERTION(psSupports, "PrintSettings must be a supports");
+ array->AppendElement(psSupports);
+
+ nsCOMPtr<nsISupports> blkSupps(do_QueryInterface(aParamBlock));
+ NS_ASSERTION(blkSupps, "IOBlk must be a supports");
+ array->AppendElement(blkSupps);
+
+ nsCOMPtr<mozIDOMWindowProxy> dialog;
+ nsresult rv = mWatcher->OpenWindow(
+ aParent, nsDependentCString(aChromeURL), "_blank"_ns,
+ "centerscreen,chrome,modal,titlebar"_ns, array, getter_AddRefs(dialog));
+
+ return rv;
+}
+
+HWND nsPrintDialogServiceWin::GetHWNDForDOMWindow(mozIDOMWindowProxy* aWindow) {
+ nsCOMPtr<nsIWebBrowserChrome> chrome;
+
+ // We might be embedded so check this path first
+ if (mWatcher) {
+ nsCOMPtr<mozIDOMWindowProxy> fosterParent;
+ // it will be a dependent window. try to find a foster parent.
+ if (!aWindow) {
+ mWatcher->GetActiveWindow(getter_AddRefs(fosterParent));
+ aWindow = fosterParent;
+ }
+ mWatcher->GetChromeForWindow(aWindow, getter_AddRefs(chrome));
+ }
+
+ if (chrome) {
+ nsCOMPtr<nsIEmbeddingSiteWindow> site(do_QueryInterface(chrome));
+ if (site) {
+ HWND w;
+ site->GetSiteWindow(reinterpret_cast<void**>(&w));
+ return w;
+ }
+ }
+
+ // Now we might be the Browser so check this path
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome =
+ window->GetWebBrowserChrome();
+ if (!webBrowserChrome) return nullptr;
+
+ nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(webBrowserChrome));
+ if (!baseWin) return nullptr;
+
+ nsCOMPtr<nsIWidget> widget;
+ baseWin->GetMainWidget(getter_AddRefs(widget));
+ if (!widget) return nullptr;
+
+ return (HWND)widget->GetNativeData(NS_NATIVE_TMP_WINDOW);
+}
diff --git a/widget/windows/nsPrintDialogWin.h b/widget/windows/nsPrintDialogWin.h
new file mode 100644
index 0000000000..d4b77817ea
--- /dev/null
+++ b/widget/windows/nsPrintDialogWin.h
@@ -0,0 +1,41 @@
+/* -*- 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 "nsIWindowWatcher.h"
+
+class nsIPrintSettings;
+class nsIDialogParamBlock;
+
+class nsPrintDialogServiceWin : public nsIPrintDialogService {
+ virtual ~nsPrintDialogServiceWin();
+
+ public:
+ nsPrintDialogServiceWin();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init() override;
+ NS_IMETHOD Show(nsPIDOMWindowOuter* aParent,
+ nsIPrintSettings* aSettings) override;
+ NS_IMETHOD ShowPageSetup(nsPIDOMWindowOuter* aParent,
+ nsIPrintSettings* aSettings) override;
+
+ private:
+ nsresult DoDialog(mozIDOMWindowProxy* aParent,
+ nsIDialogParamBlock* aParamBlock, nsIPrintSettings* aPS,
+ const char* aChromeURL);
+
+ HWND GetHWNDForDOMWindow(mozIDOMWindowProxy* aWindow);
+
+ nsCOMPtr<nsIWindowWatcher> mWatcher;
+};
+
+#endif
diff --git a/widget/windows/nsPrintSettingsServiceWin.cpp b/widget/windows/nsPrintSettingsServiceWin.cpp
new file mode 100644
index 0000000000..ab0f95abba
--- /dev/null
+++ b/widget/windows/nsPrintSettingsServiceWin.cpp
@@ -0,0 +1,127 @@
+/* -*- 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 "nsPrintSettingsServiceWin.h"
+
+#include "nsCOMPtr.h"
+#include "nsPrintSettingsWin.h"
+#include "nsPrintDialogUtil.h"
+
+#include "nsGfxCIID.h"
+#include "nsIServiceManager.h"
+#include "nsWindowsHelpers.h"
+#include "ipc/IPCMessageUtils.h"
+
+const char kPrinterListContractID[] = "@mozilla.org/gfx/printerlist;1";
+
+using namespace mozilla::embedding;
+
+NS_IMETHODIMP
+nsPrintSettingsServiceWin::SerializeToPrintData(nsIPrintSettings* aSettings,
+ PrintData* data) {
+ nsresult rv = nsPrintSettingsService::SerializeToPrintData(aSettings, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aSettings);
+ if (!psWin) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString deviceName;
+ nsAutoString driverName;
+
+ psWin->GetDeviceName(deviceName);
+ psWin->GetDriverName(driverName);
+
+ data->deviceName().Assign(deviceName);
+ data->driverName().Assign(driverName);
+
+ // When creating the print dialog on Windows, we only need to send certain
+ // print settings information from the parent to the child not vice versa.
+ if (XRE_IsParentProcess()) {
+ // A DEVMODE can actually be of arbitrary size. If it turns out that it'll
+ // make our IPC message larger than the limit, then we'll error out.
+ LPDEVMODEW devModeRaw;
+ psWin->GetDevMode(&devModeRaw); // This actually allocates a copy of the
+ // the nsIPrintSettingsWin DEVMODE, so
+ // we're now responsible for deallocating
+ // it. We'll use an nsAutoDevMode helper
+ // to do this.
+ if (devModeRaw) {
+ nsAutoDevMode devMode(devModeRaw);
+ devModeRaw = nullptr;
+
+ size_t devModeTotalSize = devMode->dmSize + devMode->dmDriverExtra;
+ size_t msgTotalSize = sizeof(PrintData) + devModeTotalSize;
+
+ if (msgTotalSize > IPC::Channel::kMaximumMessageSize / 2) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Instead of reaching in and manually reading each member, we'll just
+ // copy the bits over.
+ const char* devModeData = reinterpret_cast<const char*>(devMode.get());
+ nsTArray<uint8_t> arrayBuf;
+ arrayBuf.AppendElements(devModeData, devModeTotalSize);
+ data->devModeData() = std::move(arrayBuf);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsServiceWin::DeserializeToPrintSettings(
+ const PrintData& data, nsIPrintSettings* settings) {
+ nsresult rv =
+ nsPrintSettingsService::DeserializeToPrintSettings(data, settings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(settings);
+ if (!settings) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (XRE_IsContentProcess()) {
+ psWin->SetDeviceName(data.deviceName());
+ psWin->SetDriverName(data.driverName());
+
+ if (data.devModeData().IsEmpty()) {
+ psWin->SetDevMode(nullptr);
+ } else {
+ // Check minimum length of DEVMODE data.
+ auto devModeDataLength = data.devModeData().Length();
+ if (devModeDataLength < sizeof(DEVMODEW)) {
+ NS_WARNING("DEVMODE data is too short.");
+ return NS_ERROR_FAILURE;
+ }
+
+ DEVMODEW* devMode = reinterpret_cast<DEVMODEW*>(
+ const_cast<uint8_t*>(data.devModeData().Elements()));
+
+ // Check actual length of DEVMODE data.
+ if ((devMode->dmSize + devMode->dmDriverExtra) != devModeDataLength) {
+ NS_WARNING("DEVMODE length is incorrect.");
+ return NS_ERROR_FAILURE;
+ }
+
+ psWin->SetDevMode(devMode); // Copies
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsPrintSettingsServiceWin::_CreatePrintSettings(
+ nsIPrintSettings** _retval) {
+ *_retval = nullptr;
+ nsPrintSettingsWin* printSettings =
+ new nsPrintSettingsWin(); // does not initially ref count
+ NS_ENSURE_TRUE(printSettings, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ADDREF(*_retval = printSettings); // ref count
+
+ return NS_OK;
+}
diff --git a/widget/windows/nsPrintSettingsServiceWin.h b/widget/windows/nsPrintSettingsServiceWin.h
new file mode 100644
index 0000000000..88a5b22b51
--- /dev/null
+++ b/widget/windows/nsPrintSettingsServiceWin.h
@@ -0,0 +1,30 @@
+/* -*- Mode: IDL; 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 nsPrintSettingsServiceWin_h
+#define nsPrintSettingsServiceWin_h
+
+#include "mozilla/embedding/PPrinting.h"
+#include "nsPrintSettingsService.h"
+
+class nsIPrintSettings;
+
+class nsPrintSettingsServiceWin final : public nsPrintSettingsService {
+ public:
+ nsPrintSettingsServiceWin() {}
+
+ NS_IMETHODIMP SerializeToPrintData(
+ nsIPrintSettings* aSettings,
+ mozilla::embedding::PrintData* data) override;
+
+ NS_IMETHODIMP DeserializeToPrintSettings(
+ const mozilla::embedding::PrintData& data,
+ nsIPrintSettings* settings) override;
+
+ nsresult _CreatePrintSettings(nsIPrintSettings** _retval) override;
+};
+
+#endif // nsPrintSettingsServiceWin_h
diff --git a/widget/windows/nsPrintSettingsWin.cpp b/widget/windows/nsPrintSettingsWin.cpp
new file mode 100644
index 0000000000..3978d87256
--- /dev/null
+++ b/widget/windows/nsPrintSettingsWin.cpp
@@ -0,0 +1,536 @@
+/* -*- 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 "nsPrintSettingsWin.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "nsCRT.h"
+#include "nsDeviceContextSpecWin.h"
+#include "nsPrintSettingsImpl.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+
+// Using paper sizes from wingdi.h and the units given there, plus a little
+// extra research for the ones it doesn't give. Looks like the list hasn't
+// changed since Windows 2000, so should be fairly stable now.
+const short kPaperSizeUnits[] = {
+ nsIPrintSettings::kPaperSizeMillimeters, // Not Used default to mm as
+ // DEVMODE uses tenths of mm, just
+ // in case
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTERSMALL
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_TABLOID
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LEDGER
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LEGAL
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_STATEMENT
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_EXECUTIVE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4SMALL
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B4
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_FOLIO
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_QUARTO
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_10X14
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_11X17
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_NOTE
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_9
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_10
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_11
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_12
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_14
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_CSHEET
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_DSHEET
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ESHEET
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_DL
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C5
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C3
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C4
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C6
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C65
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_B4
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_B5
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_B6
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_ITALY
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_MONARCH
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_PERSONAL
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_FANFOLD_US
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_FANFOLD_STD_GERMAN
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_FANFOLD_LGL_GERMAN
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ISO_B4
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JAPANESE_POSTCARD
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_9X11
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_10X11
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_15X11
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_INVITE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_RESERVED_48
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_RESERVED_49
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_EXTRA
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LEGAL_EXTRA
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_TABLOID_EXTRA
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_A4_EXTRA
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_TRANSVERSE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4_TRANSVERSE
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_EXTRA_TRANSVERSE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A_PLUS
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B_PLUS
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_PLUS
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4_PLUS
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5_TRANSVERSE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5_TRANSVERSE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_EXTRA
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5_EXTRA
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5_EXTRA
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A2
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_TRANSVERSE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_EXTRA_TRANSVERSE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_DBL_JAPANESE_POSTCARD
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A6
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU2
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU3
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU3
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU4
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B4_JIS_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5_JIS_ROTATED
+ nsIPrintSettings::
+ kPaperSizeMillimeters, // DMPAPER_JAPANESE_POSTCARD_ROTATED
+ nsIPrintSettings::
+ kPaperSizeMillimeters, // DMPAPER_DBL_JAPANESE_POSTCARD_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A6_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU2_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU3_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU3_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU4_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B6_JIS
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B6_JIS_ROTATED
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_12X11
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_YOU4
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_YOU4_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P16K
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32K
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32KBIG
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_1
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_2
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_3
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_4
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_5
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_6
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_7
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_8
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_9
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_10
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P16K_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32K_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32KBIG_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_1_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_2_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_3_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_4_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_5_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_6_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_7_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_8_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_9_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_10_ROTATED
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsWin, nsPrintSettings,
+ nsIPrintSettingsWin)
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsWin.h
+ * @update
+ */
+nsPrintSettingsWin::nsPrintSettingsWin()
+ : nsPrintSettings(),
+ mDeviceName(nullptr),
+ mDriverName(nullptr),
+ mDevMode(nullptr) {}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsWin.h
+ * @update
+ */
+nsPrintSettingsWin::nsPrintSettingsWin(const nsPrintSettingsWin& aPS)
+ : mDevMode(nullptr) {
+ *this = aPS;
+}
+
+void nsPrintSettingsWin::InitWithInitializer(
+ const PrintSettingsInitializer& aSettings) {
+ nsPrintSettings::InitWithInitializer(aSettings);
+
+ if (aSettings.mDevmodeWStorage.Length() < sizeof(DEVMODEW)) {
+ return;
+ }
+
+ // SetDevMode copies the DEVMODE.
+ SetDevMode(const_cast<DEVMODEW*>(reinterpret_cast<const DEVMODEW*>(
+ aSettings.mDevmodeWStorage.Elements())));
+
+ if (mDevMode->dmFields & DM_ORIENTATION) {
+ const bool areSheetsOfPaperPortraitMode =
+ (mDevMode->dmOrientation == DMORIENT_PORTRAIT);
+
+ // If our Windows print settings say that we're producing portrait-mode
+ // sheets of paper, then our page format must also be portrait-mode; unless
+ // we've got a pages-per-sheet value with orthogonal pages/sheets, in which
+ // case it's reversed.
+ const bool arePagesPortraitMode =
+ (areSheetsOfPaperPortraitMode != HasOrthogonalSheetsAndPages());
+
+ SetOrientation(arePagesPortraitMode ? kPortraitOrientation
+ : kLandscapeOrientation);
+ }
+
+ if (mDevMode->dmFields & DM_COPIES) {
+ SetNumCopies(mDevMode->dmCopies);
+ }
+
+ if (mDevMode->dmFields & DM_SCALE) {
+ // Since we do the scaling, grab the DEVMODE value and reset it back to 100.
+ double scale = double(mDevMode->dmScale) / 100.0f;
+ if (mScaling == 1.0 || scale != 1.0) {
+ SetScaling(scale);
+ }
+ mDevMode->dmScale = 100;
+ }
+
+ if (mDevMode->dmFields & DM_PAPERSIZE) {
+ nsString paperIdString;
+ paperIdString.AppendInt(mDevMode->dmPaperSize);
+ SetPaperId(paperIdString);
+ if (mDevMode->dmPaperSize > 0 &&
+ mDevMode->dmPaperSize < int32_t(ArrayLength(kPaperSizeUnits))) {
+ SetPaperSizeUnit(kPaperSizeUnits[mDevMode->dmPaperSize]);
+ }
+ }
+
+ if (mDevMode->dmFields & DM_COLOR) {
+ SetPrintInColor(mDevMode->dmColor == DMCOLOR_COLOR);
+ }
+
+ if (mDevMode->dmFields & DM_DUPLEX) {
+ switch (mDevMode->dmDuplex) {
+ default:
+ MOZ_ASSERT_UNREACHABLE("bad value for dmDuplex field");
+ [[fallthrough]];
+ case DMDUP_SIMPLEX:
+ SetDuplex(kSimplex);
+ break;
+ case DMDUP_VERTICAL:
+ SetDuplex(kDuplexHorizontal);
+ break;
+ case DMDUP_HORIZONTAL:
+ SetDuplex(kDuplexVertical);
+ break;
+ }
+ }
+
+ // Set the paper sizes to match the unit.
+ double pointsToSizeUnit =
+ mPaperSizeUnit == kPaperSizeInches ? 1.0 / 72.0 : 25.4 / 72.0;
+ SetPaperWidth(aSettings.mPaperInfo.mSize.width * pointsToSizeUnit);
+ SetPaperHeight(aSettings.mPaperInfo.mSize.height * pointsToSizeUnit);
+}
+
+already_AddRefed<nsIPrintSettings> CreatePlatformPrintSettings(
+ const PrintSettingsInitializer& aSettings) {
+ auto settings = MakeRefPtr<nsPrintSettingsWin>();
+ settings->InitWithInitializer(aSettings);
+ return settings.forget();
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsWin.h
+ * @update
+ */
+nsPrintSettingsWin::~nsPrintSettingsWin() {
+ if (mDevMode) ::HeapFree(::GetProcessHeap(), 0, mDevMode);
+}
+
+NS_IMETHODIMP nsPrintSettingsWin::SetDeviceName(const nsAString& aDeviceName) {
+ mDeviceName = aDeviceName;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettingsWin::GetDeviceName(nsAString& aDeviceName) {
+ aDeviceName = mDeviceName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettingsWin::SetDriverName(const nsAString& aDriverName) {
+ mDriverName = aDriverName;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettingsWin::GetDriverName(nsAString& aDriverName) {
+ aDriverName = mDriverName;
+ return NS_OK;
+}
+
+void nsPrintSettingsWin::CopyDevMode(DEVMODEW* aInDevMode,
+ DEVMODEW*& aOutDevMode) {
+ aOutDevMode = nullptr;
+ size_t size = aInDevMode->dmSize + aInDevMode->dmDriverExtra;
+ aOutDevMode =
+ (LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, size);
+ if (aOutDevMode) {
+ memcpy(aOutDevMode, aInDevMode, size);
+ }
+}
+
+NS_IMETHODIMP nsPrintSettingsWin::GetDevMode(DEVMODEW** aDevMode) {
+ NS_ENSURE_ARG_POINTER(aDevMode);
+
+ if (mDevMode) {
+ CopyDevMode(mDevMode, *aDevMode);
+ } else {
+ *aDevMode = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettingsWin::SetDevMode(DEVMODEW* aDevMode) {
+ if (mDevMode) {
+ ::HeapFree(::GetProcessHeap(), 0, mDevMode);
+ mDevMode = nullptr;
+ }
+
+ if (aDevMode) {
+ CopyDevMode(aDevMode, mDevMode);
+ }
+ return NS_OK;
+}
+
+void nsPrintSettingsWin::InitUnwriteableMargin(HDC aHdc) {
+ mozilla::gfx::MarginDouble margin =
+ mozilla::widget::WinUtils::GetUnwriteableMarginsForDeviceInInches(aHdc);
+
+ mUnwriteableMargin.SizeTo(NS_INCHES_TO_INT_TWIPS(margin.top),
+ NS_INCHES_TO_INT_TWIPS(margin.right),
+ NS_INCHES_TO_INT_TWIPS(margin.bottom),
+ NS_INCHES_TO_INT_TWIPS(margin.left));
+}
+
+void nsPrintSettingsWin::CopyFromNative(HDC aHdc, DEVMODEW* aDevMode) {
+ MOZ_ASSERT(aHdc);
+ MOZ_ASSERT(aDevMode);
+
+ mIsInitedFromPrinter = true;
+ if (aDevMode->dmFields & DM_ORIENTATION) {
+ const bool areSheetsOfPaperPortraitMode =
+ (aDevMode->dmOrientation == DMORIENT_PORTRAIT);
+
+ // If our Windows print settings say that we're producing portrait-mode
+ // sheets of paper, then our page format must also be portrait-mode; unless
+ // we've got a pages-per-sheet value with orthogonal pages/sheets, in which
+ // case it's reversed.
+ const bool arePagesPortraitMode =
+ (areSheetsOfPaperPortraitMode != HasOrthogonalSheetsAndPages());
+
+ // Record the orientation of the pages (determined above) in mOrientation:
+ mOrientation = int32_t(arePagesPortraitMode ? kPortraitOrientation
+ : kLandscapeOrientation);
+ }
+
+ if (aDevMode->dmFields & DM_COPIES) {
+ mNumCopies = aDevMode->dmCopies;
+ }
+
+ if (aDevMode->dmFields & DM_DUPLEX) {
+ switch (aDevMode->dmDuplex) {
+ default:
+ MOZ_ASSERT_UNREACHABLE("bad value for dmDuplex field");
+ [[fallthrough]];
+ case DMDUP_SIMPLEX:
+ mDuplex = kSimplex;
+ break;
+ case DMDUP_VERTICAL:
+ mDuplex = kDuplexHorizontal;
+ break;
+ case DMDUP_HORIZONTAL:
+ mDuplex = kDuplexVertical;
+ break;
+ }
+ }
+
+ // Since we do the scaling, grab their value and reset back to 100.
+ if (aDevMode->dmFields & DM_SCALE) {
+ double scale = double(aDevMode->dmScale) / 100.0f;
+ if (mScaling == 1.0 || scale != 1.0) {
+ mScaling = scale;
+ }
+ aDevMode->dmScale = 100;
+ }
+
+ if (aDevMode->dmFields & DM_PAPERSIZE) {
+ mPaperId.Truncate(0);
+ mPaperId.AppendInt(aDevMode->dmPaperSize);
+ // If not a paper size we know about, the unit will be the last one saved.
+ if (aDevMode->dmPaperSize > 0 &&
+ aDevMode->dmPaperSize < int32_t(ArrayLength(kPaperSizeUnits))) {
+ mPaperSizeUnit = kPaperSizeUnits[aDevMode->dmPaperSize];
+ }
+ }
+
+ if (aDevMode->dmFields & DM_COLOR) {
+ mPrintInColor = aDevMode->dmColor == DMCOLOR_COLOR;
+ }
+
+ InitUnwriteableMargin(aHdc);
+
+ int pixelsPerInchY = ::GetDeviceCaps(aHdc, LOGPIXELSY);
+ int physicalHeight = ::GetDeviceCaps(aHdc, PHYSICALHEIGHT);
+ double physicalHeightInch = double(physicalHeight) / pixelsPerInchY;
+ int pixelsPerInchX = ::GetDeviceCaps(aHdc, LOGPIXELSX);
+ int physicalWidth = ::GetDeviceCaps(aHdc, PHYSICALWIDTH);
+ double physicalWidthInch = double(physicalWidth) / pixelsPerInchX;
+
+ // The length and width in DEVMODE are always in tenths of a millimeter.
+ double sizeUnitToTenthsOfAmm =
+ 10L * (mPaperSizeUnit == kPaperSizeInches ? MM_PER_INCH_FLOAT : 1L);
+ if (aDevMode->dmFields & DM_PAPERLENGTH) {
+ mPaperHeight = aDevMode->dmPaperLength / sizeUnitToTenthsOfAmm;
+ } else {
+ // We need the paper height to be set, because it is used in the child for
+ // layout. If it is not set in the DEVMODE, get it from the device context.
+ // We want the normalized (in portrait orientation) paper height, so account
+ // for orientation.
+ double paperHeightInch = mOrientation == kPortraitOrientation
+ ? physicalHeightInch
+ : physicalWidthInch;
+ mPaperHeight = mPaperSizeUnit == kPaperSizeInches
+ ? paperHeightInch
+ : paperHeightInch * MM_PER_INCH_FLOAT;
+ }
+
+ if (aDevMode->dmFields & DM_PAPERWIDTH) {
+ mPaperWidth = aDevMode->dmPaperWidth / sizeUnitToTenthsOfAmm;
+ } else {
+ // We need the paper width to be set, because it is used in the child for
+ // layout. If it is not set in the DEVMODE, get it from the device context.
+ // We want the normalized (in portrait orientation) paper width, so account
+ // for orientation.
+ double paperWidthInch = mOrientation == kPortraitOrientation
+ ? physicalWidthInch
+ : physicalHeightInch;
+ mPaperWidth = mPaperSizeUnit == kPaperSizeInches
+ ? paperWidthInch
+ : paperWidthInch * MM_PER_INCH_FLOAT;
+ }
+
+ // Using LOGPIXELSY to match existing code for print scaling calculations.
+ mResolution = pixelsPerInchY;
+}
+
+void nsPrintSettingsWin::CopyToNative(DEVMODEW* aDevMode) {
+ MOZ_ASSERT(aDevMode);
+
+ if (!mPaperId.IsEmpty()) {
+ aDevMode->dmPaperSize = _wtoi((const wchar_t*)mPaperId.BeginReading());
+ aDevMode->dmFields |= DM_PAPERSIZE;
+ } else {
+ aDevMode->dmPaperSize = 0;
+ aDevMode->dmFields &= ~DM_PAPERSIZE;
+ }
+
+ aDevMode->dmFields |= DM_COLOR;
+ aDevMode->dmColor = mPrintInColor ? DMCOLOR_COLOR : DMCOLOR_MONOCHROME;
+
+ // The length and width in DEVMODE are always in tenths of a millimeter.
+ double sizeUnitToTenthsOfAmm =
+ 10L * (mPaperSizeUnit == kPaperSizeInches ? MM_PER_INCH_FLOAT : 1L);
+
+ // Note: small page sizes can be required here for sticker, label and slide
+ // printers etc. see bug 1271900.
+ if (mPaperHeight > 0) {
+ aDevMode->dmPaperLength = mPaperHeight * sizeUnitToTenthsOfAmm;
+ aDevMode->dmFields |= DM_PAPERLENGTH;
+ } else {
+ aDevMode->dmPaperLength = 0;
+ aDevMode->dmFields &= ~DM_PAPERLENGTH;
+ }
+
+ if (mPaperWidth > 0) {
+ aDevMode->dmPaperWidth = mPaperWidth * sizeUnitToTenthsOfAmm;
+ aDevMode->dmFields |= DM_PAPERWIDTH;
+ } else {
+ aDevMode->dmPaperWidth = 0;
+ aDevMode->dmFields &= ~DM_PAPERWIDTH;
+ }
+
+ // Setup Orientation
+ aDevMode->dmOrientation = GetSheetOrientation() == kPortraitOrientation
+ ? DMORIENT_PORTRAIT
+ : DMORIENT_LANDSCAPE;
+ aDevMode->dmFields |= DM_ORIENTATION;
+
+ // Setup Number of Copies
+ aDevMode->dmCopies = mNumCopies;
+ aDevMode->dmFields |= DM_COPIES;
+
+ // Setup Simplex/Duplex mode
+ switch (mDuplex) {
+ case kSimplex:
+ aDevMode->dmDuplex = DMDUP_SIMPLEX;
+ aDevMode->dmFields |= DM_DUPLEX;
+ break;
+ case kDuplexHorizontal:
+ aDevMode->dmDuplex = DMDUP_VERTICAL;
+ aDevMode->dmFields |= DM_DUPLEX;
+ break;
+ case kDuplexVertical:
+ aDevMode->dmDuplex = DMDUP_HORIZONTAL;
+ aDevMode->dmFields |= DM_DUPLEX;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bad value for duplex option");
+ break;
+ }
+}
+
+//-------------------------------------------
+nsresult nsPrintSettingsWin::_Clone(nsIPrintSettings** _retval) {
+ RefPtr<nsPrintSettingsWin> printSettings = new nsPrintSettingsWin(*this);
+ printSettings.forget(_retval);
+ return NS_OK;
+}
+
+//-------------------------------------------
+nsPrintSettingsWin& nsPrintSettingsWin::operator=(
+ const nsPrintSettingsWin& rhs) {
+ if (this == &rhs) {
+ return *this;
+ }
+
+ ((nsPrintSettings&)*this) = rhs;
+
+ // Use free because we used the native malloc to create the memory
+ if (mDevMode) {
+ ::HeapFree(::GetProcessHeap(), 0, mDevMode);
+ }
+
+ mDeviceName = rhs.mDeviceName;
+ mDriverName = rhs.mDriverName;
+
+ if (rhs.mDevMode) {
+ CopyDevMode(rhs.mDevMode, mDevMode);
+ } else {
+ mDevMode = nullptr;
+ }
+
+ return *this;
+}
+
+//-------------------------------------------
+nsresult nsPrintSettingsWin::_Assign(nsIPrintSettings* aPS) {
+ nsPrintSettingsWin* psWin = static_cast<nsPrintSettingsWin*>(aPS);
+ *this = *psWin;
+ return NS_OK;
+}
diff --git a/widget/windows/nsPrintSettingsWin.h b/widget/windows/nsPrintSettingsWin.h
new file mode 100644
index 0000000000..a193eac1e0
--- /dev/null
+++ b/widget/windows/nsPrintSettingsWin.h
@@ -0,0 +1,53 @@
+/* -*- Mode: IDL; 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 nsPrintSettingsWin_h__
+#define nsPrintSettingsWin_h__
+
+#include "nsPrintSettingsImpl.h"
+#include "nsIPrintSettingsWin.h"
+#include <windows.h>
+
+//*****************************************************************************
+//*** nsPrintSettingsWin
+//*****************************************************************************
+class nsPrintSettingsWin : public nsPrintSettings, public nsIPrintSettingsWin {
+ virtual ~nsPrintSettingsWin();
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIPRINTSETTINGSWIN
+
+ nsPrintSettingsWin();
+ nsPrintSettingsWin(const nsPrintSettingsWin& aPS);
+
+ void InitWithInitializer(const PrintSettingsInitializer& aSettings) final;
+
+ /**
+ * Makes a new copy
+ */
+ virtual nsresult _Clone(nsIPrintSettings** _retval);
+
+ /**
+ * Assigns values
+ */
+ virtual nsresult _Assign(nsIPrintSettings* aPS);
+
+ /**
+ * Assignment
+ */
+ nsPrintSettingsWin& operator=(const nsPrintSettingsWin& rhs);
+
+ protected:
+ void CopyDevMode(DEVMODEW* aInDevMode, DEVMODEW*& aOutDevMode);
+ void InitUnwriteableMargin(HDC aHdc);
+
+ nsString mDeviceName;
+ nsString mDriverName;
+ LPDEVMODEW mDevMode;
+};
+
+#endif /* nsPrintSettingsWin_h__ */
diff --git a/widget/windows/nsPrinterWin.cpp b/widget/windows/nsPrinterWin.cpp
new file mode 100644
index 0000000000..2af06de4e4
--- /dev/null
+++ b/widget/windows/nsPrinterWin.cpp
@@ -0,0 +1,371 @@
+/* -*- 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 "nsPrinterWin.h"
+
+#include <algorithm>
+#include <windows.h>
+#include <winspool.h>
+
+#include "mozilla/Array.h"
+#include "nsPaper.h"
+#include "nsPrintSettingsImpl.h"
+#include "nsWindowsHelpers.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+
+static const double kPointsPerTenthMM = 72.0 / 254.0;
+static const double kPointsPerInch = 72.0;
+
+nsPrinterWin::nsPrinterWin(const CommonPaperInfoArray* aArray,
+ const nsAString& aName)
+ : nsPrinterBase(aArray),
+ mName(aName),
+ mDefaultDevmodeWStorage("nsPrinterWin::mDefaultDevmodeWStorage") {}
+
+// static
+already_AddRefed<nsPrinterWin> nsPrinterWin::Create(
+ const CommonPaperInfoArray* aArray, const nsAString& aName) {
+ return do_AddRef(new nsPrinterWin(aArray, aName));
+}
+
+template <class T>
+static nsTArray<T> GetDeviceCapabilityArray(const LPWSTR aPrinterName,
+ WORD aCapabilityID,
+ const DEVMODEW* aDevmodeW,
+ int& aCount) {
+ MOZ_ASSERT(aCount >= 0, "Possibly passed aCount from previous error case.");
+
+ nsTArray<T> caps;
+
+ // We only want to access printer drivers in the parent process.
+ if (!XRE_IsParentProcess()) {
+ return caps;
+ }
+
+ // Both the call to get the size and the call to actually populate the array
+ // are relatively expensive, so as sometimes the lengths of the arrays that we
+ // retrieve depend on each other we allow a count to be passed in to save the
+ // first call. As we allocate double the count anyway this should allay any
+ // safety worries.
+ if (!aCount) {
+ // Passing nullptr as the port here seems to work. Given that we would have
+ // to OpenPrinter with just the name anyway to get the port that makes
+ // sense. Also, the printer set-up seems to stop you from having two
+ // printers with the same name. Note: this (and the call below) are blocking
+ // calls, which could be slow.
+ aCount = ::DeviceCapabilitiesW(aPrinterName, nullptr, aCapabilityID,
+ nullptr, aDevmodeW);
+ if (aCount <= 0) {
+ return caps;
+ }
+ }
+
+ // As DeviceCapabilitiesW doesn't take a size, there is a greater risk of the
+ // buffer being overflowed, so we over-allocate for safety.
+ caps.SetLength(aCount * 2);
+ int count = ::DeviceCapabilitiesW(aPrinterName, nullptr, aCapabilityID,
+ reinterpret_cast<LPWSTR>(caps.Elements()),
+ aDevmodeW);
+ if (count <= 0) {
+ caps.Clear();
+ return caps;
+ }
+
+ // We know from bug 1673708 that sometimes the final array returned is smaller
+ // than the required array count. Assert here to see if this is reproduced on
+ // test servers.
+ MOZ_ASSERT(count == aCount, "Different array count returned than expected.");
+
+ // Note that TruncateLength will crash if count > caps.Length().
+ caps.TruncateLength(count);
+ return caps;
+}
+
+NS_IMETHODIMP
+nsPrinterWin::GetName(nsAString& aName) {
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrinterWin::GetSystemName(nsAString& aName) {
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+bool nsPrinterWin::SupportsDuplex() const {
+ // Some printer drivers have threading issues around using their default
+ // DEVMODE, so we use a copy of our cached one.
+ nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
+ if (devmodeWStorage.IsEmpty()) {
+ return false;
+ }
+ auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements());
+
+ return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_DUPLEX, nullptr,
+ devmode) == 1;
+}
+
+bool nsPrinterWin::SupportsColor() const {
+ // Some printer drivers have threading issues around using their default
+ // DEVMODE, so we use a copy of our cached one.
+ nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
+ if (devmodeWStorage.IsEmpty()) {
+ return false;
+ }
+ auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements());
+
+ return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_COLORDEVICE, nullptr,
+ devmode) == 1;
+}
+
+bool nsPrinterWin::SupportsMonochrome() const {
+ if (!SupportsColor()) {
+ return true;
+ }
+
+ nsHPRINTER hPrinter = nullptr;
+ if (NS_WARN_IF(!::OpenPrinterW(mName.get(), &hPrinter, nullptr))) {
+ return false;
+ }
+ nsAutoPrinter autoPrinter(hPrinter);
+
+ nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
+ if (devmodeWStorage.IsEmpty()) {
+ return false;
+ }
+
+ auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements());
+
+ devmode->dmFields |= DM_COLOR;
+ devmode->dmColor = DMCOLOR_MONOCHROME;
+ // Try to modify the devmode settings and see if the setting sticks.
+ //
+ // This has been the only reliable way to detect it that we've found.
+ LONG ret =
+ ::DocumentPropertiesW(nullptr, autoPrinter.get(), mName.get(), devmode,
+ devmode, DM_IN_BUFFER | DM_OUT_BUFFER);
+ if (ret != IDOK) {
+ return false;
+ }
+ return !(devmode->dmFields & DM_COLOR) ||
+ devmode->dmColor == DMCOLOR_MONOCHROME;
+}
+
+bool nsPrinterWin::SupportsCollation() const {
+ // Some printer drivers have threading issues around using their default
+ // DEVMODE, so we use a copy of our cached one.
+ nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
+ if (devmodeWStorage.IsEmpty()) {
+ return false;
+ }
+ auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements());
+
+ return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_COLLATE, nullptr,
+ devmode) == 1;
+}
+
+nsPrinterBase::PrinterInfo nsPrinterWin::CreatePrinterInfo() const {
+ return PrinterInfo{PaperList(), DefaultSettings()};
+}
+
+mozilla::gfx::MarginDouble nsPrinterWin::GetMarginsForPaper(
+ nsString aPaperId) const {
+ gfx::MarginDouble margin;
+
+ nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
+ if (devmodeWStorage.IsEmpty()) {
+ return margin;
+ }
+
+ auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements());
+
+ devmode->dmFields = DM_PAPERSIZE;
+ devmode->dmPaperSize = _wtoi((const wchar_t*)aPaperId.BeginReading());
+ nsAutoHDC printerDc(::CreateICW(nullptr, mName.get(), nullptr, devmode));
+ MOZ_ASSERT(printerDc, "CreateICW failed");
+ if (!printerDc) {
+ return margin;
+ }
+ margin = WinUtils::GetUnwriteableMarginsForDeviceInInches(printerDc);
+ margin.top *= kPointsPerInch;
+ margin.right *= kPointsPerInch;
+ margin.bottom *= kPointsPerInch;
+ margin.left *= kPointsPerInch;
+
+ return margin;
+}
+
+nsTArray<uint8_t> nsPrinterWin::CopyDefaultDevmodeW() const {
+ nsTArray<uint8_t> devmodeStorageW;
+
+ auto devmodeStorageWLock = mDefaultDevmodeWStorage.Lock();
+ if (devmodeStorageWLock->IsEmpty()) {
+ nsHPRINTER hPrinter = nullptr;
+ // OpenPrinter could fail if, for example, the printer has been removed
+ // or otherwise become inaccessible since it was selected.
+ if (NS_WARN_IF(!::OpenPrinterW(mName.get(), &hPrinter, nullptr))) {
+ return devmodeStorageW;
+ }
+ nsAutoPrinter autoPrinter(hPrinter);
+ // Allocate devmode storage of the correct size.
+ LONG bytesNeeded = ::DocumentPropertiesW(nullptr, autoPrinter.get(),
+ mName.get(), nullptr, nullptr, 0);
+ // Note that we must cast the sizeof() to a signed type so that comparison
+ // with the signed, potentially-negative bytesNeeded will work!
+ MOZ_ASSERT(bytesNeeded >= LONG(sizeof(DEVMODEW)),
+ "DocumentPropertiesW failed to get valid size");
+ if (bytesNeeded < LONG(sizeof(DEVMODEW))) {
+ return devmodeStorageW;
+ }
+
+ // Allocate extra space in case of bad drivers that return a too-small
+ // result from DocumentProperties.
+ // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5)
+ devmodeStorageWLock->SetLength(bytesNeeded * 2);
+ auto* devmode =
+ reinterpret_cast<DEVMODEW*>(devmodeStorageWLock->Elements());
+ LONG ret = ::DocumentPropertiesW(nullptr, autoPrinter.get(), mName.get(),
+ devmode, nullptr, DM_OUT_BUFFER);
+ MOZ_ASSERT(ret == IDOK, "DocumentPropertiesW failed");
+ if (ret != IDOK) {
+ return devmodeStorageW;
+ }
+ }
+
+ devmodeStorageW.Assign(devmodeStorageWLock.ref());
+ return devmodeStorageW;
+}
+
+nsTArray<mozilla::PaperInfo> nsPrinterWin::PaperList() const {
+ // Some printer drivers have threading issues around using their default
+ // DEVMODE, so we use a copy of our cached one.
+ nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
+ if (devmodeWStorage.IsEmpty()) {
+ return {};
+ }
+ auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements());
+
+ // Paper IDs are returned as WORDs.
+ int requiredArrayCount = 0;
+ auto paperIds = GetDeviceCapabilityArray<WORD>(mName.get(), DC_PAPERS,
+ devmode, requiredArrayCount);
+ if (!paperIds.Length()) {
+ return {};
+ }
+
+ // Paper names are returned in 64 long character buffers.
+ auto paperNames = GetDeviceCapabilityArray<Array<wchar_t, 64>>(
+ mName.get(), DC_PAPERNAMES, devmode, requiredArrayCount);
+ // Check that we have the same number of names as IDs.
+ if (paperNames.Length() != paperIds.Length()) {
+ return {};
+ }
+
+ // Paper sizes are returned as POINT structs with a tenth of a millimeter as
+ // the unit.
+ auto paperSizes = GetDeviceCapabilityArray<POINT>(
+ mName.get(), DC_PAPERSIZE, devmode, requiredArrayCount);
+ // Check that we have the same number of sizes as IDs.
+ if (paperSizes.Length() != paperIds.Length()) {
+ return {};
+ }
+
+ nsTArray<mozilla::PaperInfo> paperList;
+ paperList.SetCapacity(paperNames.Length());
+ for (size_t i = 0; i < paperNames.Length(); ++i) {
+ // Paper names are null terminated unless they are 64 characters long.
+ auto firstNull =
+ std::find(paperNames[i].cbegin(), paperNames[i].cend(), L'\0');
+ auto nameLength = firstNull - paperNames[i].cbegin();
+ double width = paperSizes[i].x * kPointsPerTenthMM;
+ double height = paperSizes[i].y * kPointsPerTenthMM;
+
+ // Skip if no name or invalid size.
+ if (!nameLength || width <= 0 || height <= 0) {
+ continue;
+ }
+
+ // Windows paper IDs are 16-bit integers; we stringify them to store in the
+ // PaperInfo.mId field.
+ nsString paperIdString;
+ paperIdString.AppendInt(paperIds[i]);
+
+ // We don't resolve the margins eagerly because they're really expensive (on
+ // the order of seconds for some drivers).
+ nsDependentSubstring name(paperNames[i].cbegin(), nameLength);
+ paperList.AppendElement(mozilla::PaperInfo(paperIdString, nsString(name),
+ {width, height}, Nothing()));
+ }
+
+ return paperList;
+}
+
+PrintSettingsInitializer nsPrinterWin::DefaultSettings() const {
+ // Initialize to something reasonable, in case we fail to get usable data
+ // from the devmode below.
+ nsString paperIdString(u"1"); // DMPAPER_LETTER
+ bool color = true;
+
+ nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
+ if (devmodeWStorage.IsEmpty()) {
+ return {};
+ }
+
+ const auto* devmode =
+ reinterpret_cast<const DEVMODEW*>(devmodeWStorage.Elements());
+
+ // XXX If DM_PAPERSIZE is not set, or is not a known value, should we
+ // be returning a "Custom" name of some kind?
+ if (devmode->dmFields & DM_PAPERSIZE) {
+ paperIdString.Truncate(0);
+ paperIdString.AppendInt(devmode->dmPaperSize);
+ }
+
+ if (devmode->dmFields & DM_COLOR) {
+ // See comment for PrintSettingsInitializer.mPrintInColor
+ color = devmode->dmColor != DMCOLOR_MONOCHROME;
+ }
+
+ nsAutoHDC printerDc(::CreateICW(nullptr, mName.get(), nullptr, devmode));
+ MOZ_ASSERT(printerDc, "CreateICW failed");
+ if (!printerDc) {
+ return {};
+ }
+
+ int pixelsPerInchY = ::GetDeviceCaps(printerDc, LOGPIXELSY);
+ int physicalHeight = ::GetDeviceCaps(printerDc, PHYSICALHEIGHT);
+ double heightInInches = double(physicalHeight) / pixelsPerInchY;
+ int pixelsPerInchX = ::GetDeviceCaps(printerDc, LOGPIXELSX);
+ int physicalWidth = ::GetDeviceCaps(printerDc, PHYSICALWIDTH);
+ double widthInches = double(physicalWidth) / pixelsPerInchX;
+ if (devmode->dmFields & DM_ORIENTATION &&
+ devmode->dmOrientation == DMORIENT_LANDSCAPE) {
+ std::swap(widthInches, heightInInches);
+ }
+ SizeDouble paperSize(widthInches * kPointsPerInch,
+ heightInInches * kPointsPerInch);
+
+ gfx::MarginDouble margin =
+ WinUtils::GetUnwriteableMarginsForDeviceInInches(printerDc);
+ margin.top *= kPointsPerInch;
+ margin.right *= kPointsPerInch;
+ margin.bottom *= kPointsPerInch;
+ margin.left *= kPointsPerInch;
+
+ // Using Y to match existing code for print scaling calculations.
+ int resolution = pixelsPerInchY;
+
+ // We don't actually need the paper name in the settings because the
+ // paperIdString is used to look up the paper details in the front end.
+ nsString paperName;
+ return PrintSettingsInitializer{
+ mName, PaperInfo(paperIdString, paperName, paperSize, Some(margin)),
+ color, resolution, std::move(devmodeWStorage)};
+}
diff --git a/widget/windows/nsPrinterWin.h b/widget/windows/nsPrinterWin.h
new file mode 100644
index 0000000000..7e84ab66f9
--- /dev/null
+++ b/widget/windows/nsPrinterWin.h
@@ -0,0 +1,42 @@
+/* -*- 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 nsPrinterWin_h_
+#define nsPrinterWin_h_
+
+#include "nsPrinterBase.h"
+#include "mozilla/DataMutex.h"
+#include "nsTArrayForwardDeclare.h"
+
+class nsPrinterWin final : public nsPrinterBase {
+ public:
+ NS_IMETHOD GetName(nsAString& aName) override;
+ NS_IMETHOD GetSystemName(nsAString& aName) override;
+ bool SupportsDuplex() const final;
+ bool SupportsColor() const final;
+ bool SupportsMonochrome() const final;
+ bool SupportsCollation() const final;
+ PrinterInfo CreatePrinterInfo() const final;
+ MarginDouble GetMarginsForPaper(nsString aPaperId) const final;
+
+ nsPrinterWin() = delete;
+ static already_AddRefed<nsPrinterWin> Create(
+ const mozilla::CommonPaperInfoArray* aPaperInfoArray,
+ const nsAString& aName);
+
+ private:
+ nsPrinterWin(const mozilla::CommonPaperInfoArray* aPaperInfoArray,
+ const nsAString& aName);
+ ~nsPrinterWin() = default;
+
+ nsTArray<uint8_t> CopyDefaultDevmodeW() const;
+ nsTArray<mozilla::PaperInfo> PaperList() const;
+ PrintSettingsInitializer DefaultSettings() const;
+
+ const nsString mName;
+ mutable mozilla::DataMutex<nsTArray<uint8_t>> mDefaultDevmodeWStorage;
+};
+
+#endif // nsPrinterWin_h_
diff --git a/widget/windows/nsSharePicker.cpp b/widget/windows/nsSharePicker.cpp
new file mode 100644
index 0000000000..9cdc792718
--- /dev/null
+++ b/widget/windows/nsSharePicker.cpp
@@ -0,0 +1,81 @@
+/* -*- 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 "nsSharePicker.h"
+
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "WindowsUIUtils.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Unused.h"
+
+using mozilla::dom::Promise;
+
+///////////////////////////////////////////////////////////////////////////////
+// nsISharePicker
+
+NS_IMPL_ISUPPORTS(nsSharePicker, nsISharePicker)
+
+namespace {
+inline NS_ConvertUTF8toUTF16 NS_ConvertUTF8toUTF16_MaybeVoid(
+ const nsACString& aStr) {
+ auto str = NS_ConvertUTF8toUTF16(aStr);
+ str.SetIsVoid(aStr.IsVoid());
+ return str;
+}
+inline nsIGlobalObject* GetGlobalObject() {
+ return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+} // namespace
+
+NS_IMETHODIMP
+nsSharePicker::Init(mozIDOMWindowProxy* aOpenerWindow) {
+ if (mInited) {
+ return NS_ERROR_FAILURE;
+ }
+ mOpenerWindow = aOpenerWindow;
+ mInited = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSharePicker::GetOpenerWindow(mozIDOMWindowProxy** aOpenerWindow) {
+ *aOpenerWindow = mOpenerWindow;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSharePicker::Share(const nsACString& aTitle, const nsACString& aText,
+ nsIURI* aUrl, Promise** aPromise) {
+ mozilla::ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(GetGlobalObject(), result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+ nsAutoCString urlString;
+ if (aUrl) {
+ nsresult rv = aUrl->GetSpec(urlString);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mozilla::Unused << rv;
+ } else {
+ urlString.SetIsVoid(true);
+ }
+
+ auto mozPromise =
+ WindowsUIUtils::Share(NS_ConvertUTF8toUTF16_MaybeVoid(aTitle),
+ NS_ConvertUTF8toUTF16_MaybeVoid(aText),
+ NS_ConvertUTF8toUTF16_MaybeVoid(urlString));
+ mozPromise->Then(
+ mozilla::GetCurrentSerialEventTarget(), __func__,
+ [promise]() { promise->MaybeResolveWithUndefined(); },
+ [promise]() { promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); });
+
+ promise.forget(aPromise);
+
+ return NS_OK;
+}
diff --git a/widget/windows/nsSharePicker.h b/widget/windows/nsSharePicker.h
new file mode 100644
index 0000000000..d9ba9d2a6d
--- /dev/null
+++ b/widget/windows/nsSharePicker.h
@@ -0,0 +1,29 @@
+/* -*- 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 nsSharePicker_h__
+#define nsSharePicker_h__
+
+#include "nsCOMPtr.h"
+#include "nsISharePicker.h"
+#include "nsPIDOMWindow.h"
+#include "nsThreadUtils.h"
+
+class nsSharePicker : public nsISharePicker {
+ virtual ~nsSharePicker() = default;
+
+ public:
+ nsSharePicker() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHAREPICKER
+
+ private:
+ bool mInited = false;
+ mozIDOMWindowProxy* mOpenerWindow;
+};
+
+#endif // nsSharePicker_h__
diff --git a/widget/windows/nsSound.cpp b/widget/windows/nsSound.cpp
new file mode 100644
index 0000000000..c187174705
--- /dev/null
+++ b/widget/windows/nsSound.cpp
@@ -0,0 +1,330 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h"
+#include "plstr.h"
+#include <stdio.h>
+#include "nsString.h"
+#include <windows.h>
+
+// mmsystem.h is needed to build with WIN32_LEAN_AND_MEAN
+#include <mmsystem.h>
+
+#include "HeadlessSound.h"
+#include "nsSound.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsContentUtils.h"
+#include "nsCRT.h"
+#include "nsIObserverService.h"
+
+#include "mozilla/Logging.h"
+#include "prtime.h"
+
+#include "nsNativeCharsetUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "gfxPlatform.h"
+
+using mozilla::LogLevel;
+
+static mozilla::LazyLogModule gWin32SoundLog("nsSound");
+
+// Hackaround for bug 1644240
+// When we call PlaySound for the first time in the process, winmm.dll creates
+// a new thread and starts a message loop in winmm!mciwindow. After that,
+// every call of PlaySound communicates with that thread via Window messages.
+// It seems that Warsaw application hooks USER32!GetMessageA, and there is
+// a timing window where they free their trampoline region without reverting
+// the hook on USER32!GetMessageA, resulting in crash when winmm!mciwindow
+// receives a message because it tries to jump to a freed buffer.
+// Based on the crash reports, it happened on all versions of Windows x64, and
+// the possible condition was wslbdhm64.dll was loaded but wslbscrwh64.dll was
+// unloaded. Therefore we suppress playing a sound under such a condition.
+static bool ShouldSuppressPlaySound() {
+#if defined(_M_AMD64)
+ if (::GetModuleHandle(L"wslbdhm64.dll") &&
+ !::GetModuleHandle(L"wslbscrwh64.dll")) {
+ return true;
+ }
+#endif // defined(_M_AMD64)
+ return false;
+}
+
+class nsSoundPlayer : public mozilla::Runnable {
+ public:
+ explicit nsSoundPlayer(const nsAString& aSoundName)
+ : mozilla::Runnable("nsSoundPlayer"),
+ mSoundName(aSoundName),
+ mSoundData(nullptr) {}
+
+ nsSoundPlayer(const uint8_t* aData, size_t aSize)
+ : mozilla::Runnable("nsSoundPlayer"), mSoundName(u""_ns) {
+ MOZ_ASSERT(aSize > 0, "Size should not be zero");
+ MOZ_ASSERT(aData, "Data shoud not be null");
+
+ // We will disptach nsSoundPlayer to playerthread, so keep a data copy
+ mSoundData = new uint8_t[aSize];
+ memcpy(mSoundData, aData, aSize);
+ }
+
+ NS_DECL_NSIRUNNABLE
+
+ protected:
+ ~nsSoundPlayer();
+
+ nsString mSoundName;
+ uint8_t* mSoundData;
+};
+
+NS_IMETHODIMP
+nsSoundPlayer::Run() {
+ if (ShouldSuppressPlaySound()) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mSoundName.IsEmpty() || mSoundData,
+ "Sound name or sound data should be specified");
+ DWORD flags = SND_NODEFAULT | SND_ASYNC;
+
+ if (mSoundData) {
+ flags |= SND_MEMORY;
+ ::PlaySoundW(reinterpret_cast<LPCWSTR>(mSoundData), nullptr, flags);
+ } else {
+ flags |= SND_ALIAS;
+ ::PlaySoundW(mSoundName.get(), nullptr, flags);
+ }
+ return NS_OK;
+}
+
+nsSoundPlayer::~nsSoundPlayer() { delete[] mSoundData; }
+
+mozilla::StaticRefPtr<nsISound> nsSound::sInstance;
+
+/* static */
+already_AddRefed<nsISound> nsSound::GetInstance() {
+ if (!sInstance) {
+ if (gfxPlatform::IsHeadless()) {
+ sInstance = new mozilla::widget::HeadlessSound();
+ } else {
+ RefPtr<nsSound> sound = new nsSound();
+ nsresult rv = sound->CreatePlayerThread();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ sInstance = sound.forget();
+ }
+ ClearOnShutdown(&sInstance);
+ }
+
+ RefPtr<nsISound> service = sInstance;
+ return service.forget();
+}
+
+#ifndef SND_PURGE
+// Not available on Windows CE, and according to MSDN
+// doesn't do anything on recent windows either.
+# define SND_PURGE 0
+#endif
+
+NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver, nsIObserver)
+
+nsSound::nsSound() : mInited(false) {}
+
+nsSound::~nsSound() {}
+
+void nsSound::PurgeLastSound() {
+ // Halt any currently playing sound.
+ if (mSoundPlayer) {
+ if (mPlayerThread) {
+ mPlayerThread->Dispatch(
+ NS_NewRunnableFunction("nsSound::PurgeLastSound",
+ [player = std::move(mSoundPlayer)]() {
+ // Capture move mSoundPlayer to lambda then
+ // PlaySoundW(nullptr, nullptr, SND_PURGE)
+ // will be called before freeing the
+ // nsSoundPlayer.
+ if (ShouldSuppressPlaySound()) {
+ return;
+ }
+ ::PlaySoundW(nullptr, nullptr, SND_PURGE);
+ }),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+}
+
+NS_IMETHODIMP nsSound::Beep() {
+ ::MessageBeep(0);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* context, nsresult aStatus,
+ uint32_t dataLen, const uint8_t* data) {
+ MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
+ // print a load error on bad status
+ if (NS_FAILED(aStatus)) {
+#ifdef DEBUG
+ if (aLoader) {
+ nsCOMPtr<nsIRequest> request;
+ nsCOMPtr<nsIChannel> channel;
+ aLoader->GetRequest(getter_AddRefs(request));
+ if (request) channel = do_QueryInterface(request);
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ nsAutoCString uriSpec;
+ uri->GetSpec(uriSpec);
+ MOZ_LOG(gWin32SoundLog, LogLevel::Info,
+ ("Failed to load %s\n", uriSpec.get()));
+ }
+ }
+ }
+#endif
+ return aStatus;
+ }
+
+ PurgeLastSound();
+
+ if (data && dataLen > 0) {
+ MOZ_ASSERT(!mSoundPlayer, "mSoundPlayer should be null");
+ mSoundPlayer = new nsSoundPlayer(data, dataLen);
+ MOZ_ASSERT(mSoundPlayer, "Could not create player");
+
+ nsresult rv = mPlayerThread->Dispatch(mSoundPlayer, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSound::Play(nsIURL* aURL) {
+ nsresult rv;
+
+#ifdef DEBUG_SOUND
+ char* url;
+ aURL->GetSpec(&url);
+ MOZ_LOG(gWin32SoundLog, LogLevel::Info, ("%s\n", url));
+#endif
+
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(
+ getter_AddRefs(loader), aURL,
+ this, // aObserver
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ return rv;
+}
+
+nsresult nsSound::CreatePlayerThread() {
+ if (mPlayerThread) {
+ return NS_OK;
+ }
+ if (NS_WARN_IF(NS_FAILED(NS_NewNamedThread("PlayEventSound",
+ getter_AddRefs(mPlayerThread))))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Add an observer for shutdown event to release the thread at that time
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ observerService->AddObserver(this, "xpcom-shutdown-threads", false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSound::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "xpcom-shutdown-threads")) {
+ PurgeLastSound();
+
+ if (mPlayerThread) {
+ mPlayerThread->Shutdown();
+ mPlayerThread = nullptr;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSound::Init() {
+ if (mInited) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
+ // This call halts a sound if it was still playing.
+ // We have to use the sound library for something to make sure
+ // it is initialized.
+ // If we wait until the first sound is played, there will
+ // be a time lag as the library gets loaded.
+ // This should be done in player thread otherwise it will block main thread
+ // at the first time loading sound library.
+ mPlayerThread->Dispatch(
+ NS_NewRunnableFunction("nsSound::Init",
+ []() {
+ if (ShouldSuppressPlaySound()) {
+ return;
+ }
+ ::PlaySoundW(nullptr, nullptr, SND_PURGE);
+ }),
+ NS_DISPATCH_NORMAL);
+
+ mInited = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId) {
+ MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
+ PurgeLastSound();
+
+ const wchar_t* sound = nullptr;
+ switch (aEventId) {
+ case EVENT_NEW_MAIL_RECEIVED:
+ sound = L"MailBeep";
+ break;
+ case EVENT_ALERT_DIALOG_OPEN:
+ sound = L"SystemExclamation";
+ break;
+ case EVENT_CONFIRM_DIALOG_OPEN:
+ sound = L"SystemQuestion";
+ break;
+ case EVENT_MENU_EXECUTE:
+ sound = L"MenuCommand";
+ break;
+ case EVENT_MENU_POPUP:
+ sound = L"MenuPopup";
+ break;
+ case EVENT_EDITOR_MAX_LEN:
+ sound = L".Default";
+ break;
+ default:
+ // Win32 plays no sounds at NS_SYSSOUND_PROMPT_DIALOG and
+ // NS_SYSSOUND_SELECT_DIALOG.
+ return NS_OK;
+ }
+ NS_ASSERTION(sound, "sound is null");
+ MOZ_ASSERT(!mSoundPlayer, "mSoundPlayer should be null");
+ mSoundPlayer = new nsSoundPlayer(nsDependentString(sound));
+ MOZ_ASSERT(mSoundPlayer, "Could not create player");
+ nsresult rv = mPlayerThread->Dispatch(mSoundPlayer, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
diff --git a/widget/windows/nsSound.h b/widget/windows/nsSound.h
new file mode 100644
index 0000000000..d600b0873a
--- /dev/null
+++ b/widget/windows/nsSound.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsSound_h__
+#define __nsSound_h__
+
+#include "nsISound.h"
+#include "nsIObserver.h"
+#include "nsIStreamLoader.h"
+#include "nsCOMPtr.h"
+#include "mozilla/StaticPtr.h"
+
+class nsIThread;
+class nsIRunnable;
+
+class nsSound : public nsISound,
+ public nsIStreamLoaderObserver,
+ public nsIObserver
+
+{
+ public:
+ nsSound();
+ static already_AddRefed<nsISound> GetInstance();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISOUND
+ NS_DECL_NSISTREAMLOADEROBSERVER
+ NS_DECL_NSIOBSERVER
+
+ private:
+ virtual ~nsSound();
+ void PurgeLastSound();
+
+ private:
+ nsresult CreatePlayerThread();
+
+ nsCOMPtr<nsIThread> mPlayerThread;
+ nsCOMPtr<nsIRunnable> mSoundPlayer;
+ bool mInited;
+
+ static mozilla::StaticRefPtr<nsISound> sInstance;
+};
+
+#endif /* __nsSound_h__ */
diff --git a/widget/windows/nsToolkit.cpp b/widget/windows/nsToolkit.cpp
new file mode 100644
index 0000000000..6eea9c958f
--- /dev/null
+++ b/widget/windows/nsToolkit.cpp
@@ -0,0 +1,69 @@
+/* -*- 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 "nsAppShell.h"
+#include "nsWindow.h"
+#include "nsWidgetsCID.h"
+#include "prmon.h"
+#include "prtime.h"
+#include "nsComponentManagerUtils.h"
+#include <objbase.h>
+#include "WinUtils.h"
+
+#include "nsUXThemeData.h"
+
+// unknwn.h is needed to build with WIN32_LEAN_AND_MEAN
+#include <unknwn.h>
+
+using namespace mozilla::widget;
+
+nsToolkit* nsToolkit::gToolkit = nullptr;
+HINSTANCE nsToolkit::mDllInstance = 0;
+
+//-------------------------------------------------------------------------
+//
+// constructor
+//
+//-------------------------------------------------------------------------
+nsToolkit::nsToolkit() {
+ MOZ_COUNT_CTOR(nsToolkit);
+
+#if defined(MOZ_STATIC_COMPONENT_LIBS)
+ nsToolkit::Startup(GetModuleHandle(nullptr));
+#endif
+}
+
+//-------------------------------------------------------------------------
+//
+// destructor
+//
+//-------------------------------------------------------------------------
+nsToolkit::~nsToolkit() { MOZ_COUNT_DTOR(nsToolkit); }
+
+void nsToolkit::Startup(HMODULE hModule) {
+ nsToolkit::mDllInstance = hModule;
+ WinUtils::Initialize();
+}
+
+void nsToolkit::Shutdown() {
+ delete gToolkit;
+ gToolkit = nullptr;
+}
+
+//-------------------------------------------------------------------------
+//
+// Return the nsToolkit for the current thread. If a toolkit does not
+// yet exist, then one will be created...
+//
+//-------------------------------------------------------------------------
+// static
+nsToolkit* nsToolkit::GetToolkit() {
+ if (!gToolkit) {
+ gToolkit = new nsToolkit();
+ }
+
+ return gToolkit;
+}
diff --git a/widget/windows/nsToolkit.h b/widget/windows/nsToolkit.h
new file mode 100644
index 0000000000..4be7bdb80a
--- /dev/null
+++ b/widget/windows/nsToolkit.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsToolkit_h__
+#define nsToolkit_h__
+
+#include "nsdefs.h"
+
+#include "nsCOMPtr.h"
+#include <windows.h>
+
+// Avoid including windowsx.h to prevent macro pollution
+#ifndef GET_X_LPARAM
+# define GET_X_LPARAM(pt) (short(LOWORD(pt)))
+#endif
+#ifndef GET_Y_LPARAM
+# define GET_Y_LPARAM(pt) (short(HIWORD(pt)))
+#endif
+
+/**
+ * Wrapper around the thread running the message pump.
+ * The toolkit abstraction is necessary because the message pump must
+ * execute within the same thread that created the widget under Win32.
+ */
+
+class nsToolkit {
+ public:
+ nsToolkit();
+
+ private:
+ ~nsToolkit();
+
+ public:
+ static nsToolkit* GetToolkit();
+
+ static HINSTANCE mDllInstance;
+
+ static void Startup(HMODULE hModule);
+ static void Shutdown();
+
+ protected:
+ static nsToolkit* gToolkit;
+};
+
+#endif // TOOLKIT_H
diff --git a/widget/windows/nsUXThemeConstants.h b/widget/windows/nsUXThemeConstants.h
new file mode 100644
index 0000000000..e47ca8f108
--- /dev/null
+++ b/widget/windows/nsUXThemeConstants.h
@@ -0,0 +1,255 @@
+/* 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 nsUXThemeConstants_h
+#define nsUXThemeConstants_h
+
+/*
+ * The following constants are used to determine how a widget is drawn using
+ * Windows' Theme API. For more information on theme parts and states see
+ * http://msdn.microsoft.com/en-us/library/bb773210(VS.85).aspx
+ */
+
+#include <vssym32.h>
+#include <vsstyle.h>
+
+#define THEME_COLOR 204
+#define THEME_FONT 210
+
+// Generic state constants
+#define TS_NORMAL 1
+#define TS_HOVER 2
+#define TS_ACTIVE 3
+#define TS_DISABLED 4
+#define TS_FOCUSED 5
+
+// These constants are reversed for the trackbar (scale) thumb
+#define TKP_FOCUSED 4
+#define TKP_DISABLED 5
+
+// Toolbarbutton constants
+#define TB_CHECKED 5
+#define TB_HOVER_CHECKED 6
+
+// Button constants
+#define BP_BUTTON 1
+#define BP_RADIO 2
+#define BP_CHECKBOX 3
+#define BP_GROUPBOX 4
+#define BP_Count 5
+
+// Textfield constants
+/* This is the EP_EDITTEXT part */
+#define TFP_TEXTFIELD 1
+#define TFP_EDITBORDER_NOSCROLL 6
+#define TFS_READONLY 6
+
+/* These are the state constants for the EDITBORDER parts */
+#define TFS_EDITBORDER_NORMAL 1
+#define TFS_EDITBORDER_HOVER 2
+#define TFS_EDITBORDER_FOCUSED 3
+#define TFS_EDITBORDER_DISABLED 4
+
+// Treeview/listbox constants
+#define TREEVIEW_BODY 1
+
+// Scrollbar constants
+#define SP_BUTTON 1
+#define SP_THUMBHOR 2
+#define SP_THUMBVERT 3
+#define SP_TRACKSTARTHOR 4
+#define SP_TRACKENDHOR 5
+#define SP_TRACKSTARTVERT 6
+#define SP_TRACKENDVERT 7
+#define SP_GRIPPERHOR 8
+#define SP_GRIPPERVERT 9
+
+// Implicit hover state.
+// BASE + 0 = UP, + 1 = DOWN, etc.
+#define SP_BUTTON_IMPLICIT_HOVER_BASE 17
+
+// Scale constants
+#define TKP_TRACK 1
+#define TKP_TRACKVERT 2
+#define TKP_THUMB 3
+#define TKP_THUMBBOTTOM 4
+#define TKP_THUMBTOP 5
+#define TKP_THUMBVERT 6
+#define TKP_THUMBLEFT 7
+#define TKP_THUMBRIGHT 8
+
+// Track state contstants
+#define TRS_NORMAL 1
+
+// Track vertical state constants
+#define TRVS_NORMAL 1
+
+// Spin constants
+#define SPNP_UP 1
+#define SPNP_DOWN 2
+
+// Tab constants
+#define TABP_TAB 4
+#define TABP_TAB_SELECTED 5
+#define TABP_PANELS 9
+#define TABP_PANEL 10
+
+// Tooltip constants
+#define TTP_STANDARD 1
+
+// Dropdown constants
+#define CBP_DROPMARKER 1
+#define CBP_DROPBORDER 4
+/* This is actually the 'READONLY' style */
+#define CBP_DROPFRAME 5
+#define CBP_DROPMARKER_VISTA 6
+
+// Menu Constants
+#define MENU_BARBACKGROUND 7
+#define MENU_BARITEM 8
+#define MENU_POPUPBACKGROUND 9
+#define MENU_POPUPBORDERS 10
+#define MENU_POPUPCHECK 11
+#define MENU_POPUPCHECKBACKGROUND 12
+#define MENU_POPUPGUTTER 13
+#define MENU_POPUPITEM 14
+#define MENU_POPUPSEPARATOR 15
+#define MENU_POPUPSUBMENU 16
+#define MENU_SYSTEMCLOSE 17
+#define MENU_SYSTEMMAXIMIZE 18
+#define MENU_SYSTEMMINIMIZE 19
+#define MENU_SYSTEMRESTORE 20
+
+#define MB_ACTIVE 1
+#define MB_INACTIVE 2
+
+#define MS_NORMAL 1
+#define MS_SELECTED 2
+#define MS_DEMOTED 3
+
+#define MBI_NORMAL 1
+#define MBI_HOT 2
+#define MBI_PUSHED 3
+#define MBI_DISABLED 4
+#define MBI_DISABLEDHOT 5
+#define MBI_DISABLEDPUSHED 6
+
+#define MC_CHECKMARKNORMAL 1
+#define MC_CHECKMARKDISABLED 2
+#define MC_BULLETNORMAL 3
+#define MC_BULLETDISABLED 4
+
+#define MCB_DISABLED 1
+#define MCB_NORMAL 2
+#define MCB_BITMAP 3
+
+#define MPI_NORMAL 1
+#define MPI_HOT 2
+#define MPI_DISABLED 3
+#define MPI_DISABLEDHOT 4
+
+#define MSM_NORMAL 1
+#define MSM_DISABLED 2
+
+// Rebar constants
+#define RP_BAND 3
+#define RP_BACKGROUND 6
+
+// Constants only found in new (98+, 2K+, XP+, etc.) Windows.
+#ifdef DFCS_HOT
+# undef DFCS_HOT
+#endif
+#define DFCS_HOT 0x00001000
+
+#ifdef COLOR_MENUHILIGHT
+# undef COLOR_MENUHILIGHT
+#endif
+#define COLOR_MENUHILIGHT 29
+
+#ifdef SPI_GETFLATMENU
+# undef SPI_GETFLATMENU
+#endif
+#define SPI_GETFLATMENU 0x1022
+#ifndef SPI_GETMENUSHOWDELAY
+# define SPI_GETMENUSHOWDELAY 106
+#endif // SPI_GETMENUSHOWDELAY
+#ifndef WS_EX_LAYOUTRTL
+# define WS_EX_LAYOUTRTL 0x00400000L // Right to left mirroring
+#endif
+
+// Our extra constants for passing a little bit more info to the renderer.
+#define DFCS_RTL 0x00010000
+
+// Toolbar separator dimension which can't be gotten from Windows
+#define TB_SEPARATOR_HEIGHT 2
+
+namespace mozilla {
+namespace widget {
+namespace themeconst {
+
+// Pulled from sdk/include/vsstyle.h
+enum {
+ WP_CAPTION = 1,
+ WP_SMALLCAPTION = 2,
+ WP_MINCAPTION = 3,
+ WP_SMALLMINCAPTION = 4,
+ WP_MAXCAPTION = 5,
+ WP_SMALLMAXCAPTION = 6,
+ WP_FRAMELEFT = 7,
+ WP_FRAMERIGHT = 8,
+ WP_FRAMEBOTTOM = 9,
+ WP_SMALLFRAMELEFT = 10,
+ WP_SMALLFRAMERIGHT = 11,
+ WP_SMALLFRAMEBOTTOM = 12,
+ WP_SYSBUTTON = 13,
+ WP_MDISYSBUTTON = 14,
+ WP_MINBUTTON = 15,
+ WP_MDIMINBUTTON = 16,
+ WP_MAXBUTTON = 17,
+ WP_CLOSEBUTTON = 18,
+ WP_SMALLCLOSEBUTTON = 19,
+ WP_MDICLOSEBUTTON = 20,
+ WP_RESTOREBUTTON = 21,
+ WP_MDIRESTOREBUTTON = 22,
+ WP_HELPBUTTON = 23,
+ WP_MDIHELPBUTTON = 24,
+ WP_HORZSCROLL = 25,
+ WP_HORZTHUMB = 26,
+ WP_VERTSCROLL = 27,
+ WP_VERTTHUMB = 28,
+ WP_DIALOG = 29,
+ WP_CAPTIONSIZINGTEMPLATE = 30,
+ WP_SMALLCAPTIONSIZINGTEMPLATE = 31,
+ WP_FRAMELEFTSIZINGTEMPLATE = 32,
+ WP_SMALLFRAMELEFTSIZINGTEMPLATE = 33,
+ WP_FRAMERIGHTSIZINGTEMPLATE = 34,
+ WP_SMALLFRAMERIGHTSIZINGTEMPLATE = 35,
+ WP_FRAMEBOTTOMSIZINGTEMPLATE = 36,
+ WP_SMALLFRAMEBOTTOMSIZINGTEMPLATE = 37,
+ WP_FRAME = 38,
+ WP_Count
+};
+
+enum FRAMESTATES { FS_ACTIVE = 1, FS_INACTIVE = 2 };
+
+enum {
+ BS_NORMAL = 1,
+ BS_HOT = 2,
+ BS_PUSHED = 3,
+ BS_DISABLED = 4,
+ BS_INACTIVE = 5 /* undocumented, inactive caption button */
+};
+
+} // namespace themeconst
+} // namespace widget
+} // namespace mozilla
+
+// If any theme part ends up having a value higher than WP_Count, this will
+// need to change.
+#define THEME_PART_DISTINCT_VALUE_COUNT mozilla::widget::themeconst::WP_Count
+
+#endif
diff --git a/widget/windows/nsUXThemeData.cpp b/widget/windows/nsUXThemeData.cpp
new file mode 100644
index 0000000000..b0e34deb6c
--- /dev/null
+++ b/widget/windows/nsUXThemeData.cpp
@@ -0,0 +1,410 @@
+/* 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/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/WindowsVersion.h"
+
+#include "nsUXThemeData.h"
+#include "nsDebug.h"
+#include "nsToolkit.h"
+#include "nsUXThemeConstants.h"
+#include "WinContentSystemParameters.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+nsUXThemeData::ThemeHandle nsUXThemeData::sThemes[eUXNumClasses];
+
+const int NUM_COMMAND_BUTTONS = 3;
+SIZE nsUXThemeData::sCommandButtonMetrics[NUM_COMMAND_BUTTONS];
+bool nsUXThemeData::sCommandButtonMetricsInitialized = false;
+SIZE nsUXThemeData::sCommandButtonBoxMetrics;
+bool nsUXThemeData::sCommandButtonBoxMetricsInitialized = false;
+
+bool nsUXThemeData::sTitlebarInfoPopulatedAero = false;
+bool nsUXThemeData::sTitlebarInfoPopulatedThemed = false;
+
+nsUXThemeData::ThemeHandle::~ThemeHandle() { Close(); }
+
+void nsUXThemeData::ThemeHandle::OpenOnce(HWND aWindow, LPCWSTR aClassList) {
+ if (mHandle.isSome()) {
+ return;
+ }
+
+ mHandle = Some(OpenThemeData(aWindow, aClassList));
+}
+
+void nsUXThemeData::ThemeHandle::Close() {
+ if (mHandle.isNothing() || !mHandle.value()) {
+ return;
+ }
+
+ CloseThemeData(mHandle.value());
+ mHandle = Nothing();
+}
+
+nsUXThemeData::ThemeHandle::operator HANDLE() {
+ return mHandle.isSome() ? mHandle.value() : nullptr;
+}
+
+void nsUXThemeData::Invalidate() {
+ for (auto& theme : sThemes) {
+ theme.Close();
+ }
+}
+
+HANDLE
+nsUXThemeData::GetTheme(nsUXThemeClass cls) {
+ NS_ASSERTION(cls < eUXNumClasses, "Invalid theme class!");
+ sThemes[cls].OpenOnce(nullptr, GetClassName(cls));
+ return sThemes[cls];
+}
+
+const wchar_t* nsUXThemeData::GetClassName(nsUXThemeClass cls) {
+ switch (cls) {
+ case eUXButton:
+ return L"Button";
+ case eUXEdit:
+ return L"Edit";
+ case eUXTooltip:
+ return L"Tooltip";
+ case eUXRebar:
+ return L"Rebar";
+ case eUXMediaRebar:
+ return L"Media::Rebar";
+ case eUXCommunicationsRebar:
+ return L"Communications::Rebar";
+ case eUXBrowserTabBarRebar:
+ return L"BrowserTabBar::Rebar";
+ case eUXToolbar:
+ return L"Toolbar";
+ case eUXMediaToolbar:
+ return L"Media::Toolbar";
+ case eUXCommunicationsToolbar:
+ return L"Communications::Toolbar";
+ case eUXProgress:
+ return L"Progress";
+ case eUXTab:
+ return L"Tab";
+ case eUXScrollbar:
+ return L"Scrollbar";
+ case eUXTrackbar:
+ return L"Trackbar";
+ case eUXSpin:
+ return L"Spin";
+ case eUXStatus:
+ return L"Status";
+ case eUXCombobox:
+ return L"Combobox";
+ case eUXHeader:
+ return L"Header";
+ case eUXListview:
+ return L"Listview";
+ case eUXMenu:
+ return L"Menu";
+ case eUXWindowFrame:
+ return L"Window";
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown uxtheme class");
+ return L"";
+ }
+}
+
+// static
+void nsUXThemeData::EnsureCommandButtonMetrics() {
+ if (sCommandButtonMetricsInitialized) {
+ return;
+ }
+ sCommandButtonMetricsInitialized = true;
+
+ // This code should never need to be evaluated for our UI since if we need
+ // these metrics for our UI we should make sure that we obtain the correct
+ // metrics when nsWindow::Create() is called. The generic metrics that we
+ // fetch here will likley not match the current theme, but we provide these
+ // values in case arbitrary content is styled with the '-moz-appearance'
+ // value '-moz-window-button-close' etc.
+ //
+ // ISSUE: We'd prefer to use MOZ_ASSERT_UNREACHABLE here, but since content
+ // (and at least one of our crashtests) can use '-moz-window-button-close'
+ // we need to use NS_WARNING instead.
+ NS_WARNING("Making expensive and likely unnecessary GetSystemMetrics calls");
+
+ sCommandButtonMetrics[0].cx = GetSystemMetrics(SM_CXSIZE);
+ sCommandButtonMetrics[0].cy = GetSystemMetrics(SM_CYSIZE);
+ sCommandButtonMetrics[1].cx = sCommandButtonMetrics[2].cx =
+ sCommandButtonMetrics[0].cx;
+ sCommandButtonMetrics[1].cy = sCommandButtonMetrics[2].cy =
+ sCommandButtonMetrics[0].cy;
+
+ // Trigger a refresh on the next layout.
+ sTitlebarInfoPopulatedAero = sTitlebarInfoPopulatedThemed = false;
+}
+
+// static
+void nsUXThemeData::EnsureCommandButtonBoxMetrics() {
+ if (sCommandButtonBoxMetricsInitialized) {
+ return;
+ }
+ sCommandButtonBoxMetricsInitialized = true;
+
+ EnsureCommandButtonMetrics();
+
+ sCommandButtonBoxMetrics.cx = sCommandButtonMetrics[0].cx +
+ sCommandButtonMetrics[1].cx +
+ sCommandButtonMetrics[2].cx;
+ sCommandButtonBoxMetrics.cy = sCommandButtonMetrics[0].cy +
+ sCommandButtonMetrics[1].cy +
+ sCommandButtonMetrics[2].cy;
+
+ // Trigger a refresh on the next layout.
+ sTitlebarInfoPopulatedAero = sTitlebarInfoPopulatedThemed = false;
+}
+
+// static
+void nsUXThemeData::UpdateTitlebarInfo(HWND aWnd) {
+ if (!aWnd) return;
+
+ if (!sTitlebarInfoPopulatedAero &&
+ gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ RECT captionButtons;
+ if (SUCCEEDED(DwmGetWindowAttribute(aWnd, DWMWA_CAPTION_BUTTON_BOUNDS,
+ &captionButtons,
+ sizeof(captionButtons)))) {
+ sCommandButtonBoxMetrics.cx =
+ captionButtons.right - captionButtons.left - 3;
+ sCommandButtonBoxMetrics.cy =
+ (captionButtons.bottom - captionButtons.top) - 1;
+ sCommandButtonBoxMetricsInitialized = true;
+ MOZ_ASSERT(
+ sCommandButtonBoxMetrics.cx > 0 && sCommandButtonBoxMetrics.cy > 0,
+ "We must not cache bad command button box dimensions");
+ sTitlebarInfoPopulatedAero = true;
+ }
+ }
+
+ // NB: sTitlebarInfoPopulatedThemed is always true pre-vista.
+ if (sTitlebarInfoPopulatedThemed || IsWin8OrLater()) return;
+
+ // Query a temporary, visible window with command buttons to get
+ // the right metrics.
+ WNDCLASSW wc;
+ wc.style = 0;
+ wc.lpfnWndProc = ::DefWindowProcW;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = nsToolkit::mDllInstance;
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+ wc.lpszClassName = kClassNameTemp;
+ ::RegisterClassW(&wc);
+
+ // Create a transparent descendant of the window passed in. This
+ // keeps the window from showing up on the desktop or the taskbar.
+ // Note the parent (browser) window is usually still hidden, we
+ // don't want to display it, so we can't query it directly.
+ HWND hWnd = CreateWindowExW(WS_EX_LAYERED, kClassNameTemp, L"",
+ WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, aWnd, nullptr,
+ nsToolkit::mDllInstance, nullptr);
+ NS_ASSERTION(hWnd, "UpdateTitlebarInfo window creation failed.");
+
+ int showType = SW_SHOWNA;
+ // We try to avoid activating this window, but on Aero basic (aero without
+ // compositor) and aero lite (special theme for win server 2012/2013) we may
+ // get the wrong information if the window isn't activated, so we have to:
+ if (sThemeId == LookAndFeel::eWindowsTheme_AeroLite ||
+ (sThemeId == LookAndFeel::eWindowsTheme_Aero &&
+ !gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled())) {
+ showType = SW_SHOW;
+ }
+ ShowWindow(hWnd, showType);
+ TITLEBARINFOEX info = {0};
+ info.cbSize = sizeof(TITLEBARINFOEX);
+ SendMessage(hWnd, WM_GETTITLEBARINFOEX, 0, (LPARAM)&info);
+ DestroyWindow(hWnd);
+
+ // Only set if we have valid data for all three buttons we use.
+ if ((info.rgrect[2].right - info.rgrect[2].left) == 0 ||
+ (info.rgrect[3].right - info.rgrect[3].left) == 0 ||
+ (info.rgrect[5].right - info.rgrect[5].left) == 0) {
+ NS_WARNING("WM_GETTITLEBARINFOEX query failed to find usable metrics.");
+ return;
+ }
+ // minimize
+ sCommandButtonMetrics[0].cx = info.rgrect[2].right - info.rgrect[2].left;
+ sCommandButtonMetrics[0].cy = info.rgrect[2].bottom - info.rgrect[2].top;
+ // maximize/restore
+ sCommandButtonMetrics[1].cx = info.rgrect[3].right - info.rgrect[3].left;
+ sCommandButtonMetrics[1].cy = info.rgrect[3].bottom - info.rgrect[3].top;
+ // close
+ sCommandButtonMetrics[2].cx = info.rgrect[5].right - info.rgrect[5].left;
+ sCommandButtonMetrics[2].cy = info.rgrect[5].bottom - info.rgrect[5].top;
+ sCommandButtonMetricsInitialized = true;
+
+#ifdef DEBUG
+ // Verify that all values for the command buttons are positive values
+ // otherwise we have cached bad values for the caption buttons
+ for (int i = 0; i < NUM_COMMAND_BUTTONS; i++) {
+ MOZ_ASSERT(sCommandButtonMetrics[i].cx > 0);
+ MOZ_ASSERT(sCommandButtonMetrics[i].cy > 0);
+ }
+#endif
+
+ sTitlebarInfoPopulatedThemed = true;
+}
+
+// visual style (aero glass, aero basic)
+// theme (aero, luna, zune)
+// theme color (silver, olive, blue)
+// system colors
+
+struct THEMELIST {
+ LPCWSTR name;
+ int type;
+};
+
+const THEMELIST knownThemes[] = {{L"aero.msstyles", WINTHEME_AERO},
+ {L"aerolite.msstyles", WINTHEME_AERO_LITE},
+ {L"luna.msstyles", WINTHEME_LUNA},
+ {L"zune.msstyles", WINTHEME_ZUNE},
+ {L"royale.msstyles", WINTHEME_ROYALE}};
+
+const THEMELIST knownColors[] = {{L"normalcolor", WINTHEMECOLOR_NORMAL},
+ {L"homestead", WINTHEMECOLOR_HOMESTEAD},
+ {L"metallic", WINTHEMECOLOR_METALLIC}};
+
+LookAndFeel::WindowsTheme nsUXThemeData::sThemeId =
+ LookAndFeel::eWindowsTheme_Generic;
+
+bool nsUXThemeData::sIsDefaultWindowsTheme = false;
+bool nsUXThemeData::sIsHighContrastOn = false;
+
+// static
+LookAndFeel::WindowsTheme nsUXThemeData::GetNativeThemeId() { return sThemeId; }
+
+// static
+bool nsUXThemeData::IsDefaultWindowTheme() { return sIsDefaultWindowsTheme; }
+
+bool nsUXThemeData::IsHighContrastOn() { return sIsHighContrastOn; }
+
+// static
+void nsUXThemeData::UpdateNativeThemeInfo() {
+ // Trigger a refresh of themed button metrics if needed
+ sTitlebarInfoPopulatedThemed = false;
+
+ sIsDefaultWindowsTheme = false;
+ sThemeId = LookAndFeel::eWindowsTheme_Generic;
+
+ HIGHCONTRAST highContrastInfo;
+ highContrastInfo.cbSize = sizeof(HIGHCONTRAST);
+ if (SystemParametersInfo(SPI_GETHIGHCONTRAST, 0, &highContrastInfo, 0)) {
+ sIsHighContrastOn = ((highContrastInfo.dwFlags & HCF_HIGHCONTRASTON) != 0);
+ } else {
+ sIsHighContrastOn = false;
+ }
+
+ if (!nsUXThemeData::IsAppThemed()) {
+ sThemeId = LookAndFeel::eWindowsTheme_Classic;
+ return;
+ }
+
+ WCHAR themeFileName[MAX_PATH + 1];
+ WCHAR themeColor[MAX_PATH + 1];
+ if (FAILED(GetCurrentThemeName(themeFileName, MAX_PATH, themeColor, MAX_PATH,
+ nullptr, 0))) {
+ sThemeId = LookAndFeel::eWindowsTheme_Classic;
+ return;
+ }
+
+ LPCWSTR themeName = wcsrchr(themeFileName, L'\\');
+ themeName = themeName ? themeName + 1 : themeFileName;
+
+ WindowsTheme theme = WINTHEME_UNRECOGNIZED;
+ for (size_t i = 0; i < ArrayLength(knownThemes); ++i) {
+ if (!lstrcmpiW(themeName, knownThemes[i].name)) {
+ theme = (WindowsTheme)knownThemes[i].type;
+ break;
+ }
+ }
+
+ if (theme == WINTHEME_UNRECOGNIZED) return;
+
+ // We're using the default theme if we're using any of Aero, Aero Lite, or
+ // luna. However, on Win8, GetCurrentThemeName (see above) returns
+ // AeroLite.msstyles for the 4 builtin highcontrast themes as well. Those
+ // themes "don't count" as default themes, so we specifically check for high
+ // contrast mode in that situation.
+ if (!(IsWin8OrLater() && sIsHighContrastOn) &&
+ (theme == WINTHEME_AERO || theme == WINTHEME_AERO_LITE ||
+ theme == WINTHEME_LUNA)) {
+ sIsDefaultWindowsTheme = true;
+ }
+
+ if (theme != WINTHEME_LUNA) {
+ switch (theme) {
+ case WINTHEME_AERO:
+ sThemeId = LookAndFeel::eWindowsTheme_Aero;
+ return;
+ case WINTHEME_AERO_LITE:
+ sThemeId = LookAndFeel::eWindowsTheme_AeroLite;
+ return;
+ case WINTHEME_ZUNE:
+ sThemeId = LookAndFeel::eWindowsTheme_Zune;
+ return;
+ case WINTHEME_ROYALE:
+ sThemeId = LookAndFeel::eWindowsTheme_Royale;
+ return;
+ default:
+ NS_WARNING("unhandled theme type.");
+ return;
+ }
+ }
+
+ // calculate the luna color scheme
+ WindowsThemeColor color = WINTHEMECOLOR_UNRECOGNIZED;
+ for (size_t i = 0; i < ArrayLength(knownColors); ++i) {
+ if (!lstrcmpiW(themeColor, knownColors[i].name)) {
+ color = (WindowsThemeColor)knownColors[i].type;
+ break;
+ }
+ }
+
+ switch (color) {
+ case WINTHEMECOLOR_NORMAL:
+ sThemeId = LookAndFeel::eWindowsTheme_LunaBlue;
+ return;
+ case WINTHEMECOLOR_HOMESTEAD:
+ sThemeId = LookAndFeel::eWindowsTheme_LunaOlive;
+ return;
+ case WINTHEMECOLOR_METALLIC:
+ sThemeId = LookAndFeel::eWindowsTheme_LunaSilver;
+ return;
+ default:
+ NS_WARNING("unhandled theme color.");
+ return;
+ }
+}
+
+// static
+bool nsUXThemeData::AreFlatMenusEnabled() {
+ if (XRE_IsContentProcess()) {
+ return WinContentSystemParameters::GetSingleton()->AreFlatMenusEnabled();
+ }
+
+ BOOL useFlat = FALSE;
+ return !!::SystemParametersInfo(SPI_GETFLATMENU, 0, &useFlat, 0) ? useFlat
+ : false;
+}
+
+// static
+bool nsUXThemeData::IsAppThemed() {
+ if (XRE_IsContentProcess()) {
+ return WinContentSystemParameters::GetSingleton()->IsAppThemed();
+ }
+ return !!::IsAppThemed();
+}
diff --git a/widget/windows/nsUXThemeData.h b/widget/windows/nsUXThemeData.h
new file mode 100644
index 0000000000..604dae6089
--- /dev/null
+++ b/widget/windows/nsUXThemeData.h
@@ -0,0 +1,133 @@
+/* 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 __UXThemeData_h__
+#define __UXThemeData_h__
+#include <windows.h>
+#include <uxtheme.h>
+
+#include "nscore.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Maybe.h"
+#include "WinUtils.h"
+
+#include "nsWindowDefs.h"
+
+enum nsUXThemeClass {
+ eUXButton = 0,
+ eUXEdit,
+ eUXTooltip,
+ eUXRebar,
+ eUXMediaRebar,
+ eUXCommunicationsRebar,
+ eUXBrowserTabBarRebar,
+ eUXToolbar,
+ eUXMediaToolbar,
+ eUXCommunicationsToolbar,
+ eUXProgress,
+ eUXTab,
+ eUXScrollbar,
+ eUXTrackbar,
+ eUXSpin,
+ eUXStatus,
+ eUXCombobox,
+ eUXHeader,
+ eUXListview,
+ eUXMenu,
+ eUXWindowFrame,
+ eUXNumClasses
+};
+
+// Native windows style constants
+enum WindowsTheme {
+ WINTHEME_UNRECOGNIZED = 0,
+ WINTHEME_CLASSIC = 1, // no theme
+ WINTHEME_AERO = 2,
+ WINTHEME_LUNA = 3,
+ WINTHEME_ROYALE = 4,
+ WINTHEME_ZUNE = 5,
+ WINTHEME_AERO_LITE = 6
+};
+enum WindowsThemeColor {
+ WINTHEMECOLOR_UNRECOGNIZED = 0,
+ WINTHEMECOLOR_NORMAL = 1,
+ WINTHEMECOLOR_HOMESTEAD = 2,
+ WINTHEMECOLOR_METALLIC = 3
+};
+enum CmdButtonIdx {
+ CMDBUTTONIDX_MINIMIZE = 0,
+ CMDBUTTONIDX_RESTORE,
+ CMDBUTTONIDX_CLOSE,
+ CMDBUTTONIDX_BUTTONBOX
+};
+
+class nsUXThemeData {
+ // This class makes sure we don't attempt to open a theme if the previous
+ // loading attempt has failed because OpenThemeData is a heavy task and
+ // it's less likely that the API returns a different result.
+ class ThemeHandle final {
+ mozilla::Maybe<HANDLE> mHandle;
+
+ public:
+ ThemeHandle() = default;
+ ~ThemeHandle();
+
+ // Disallow copy and move
+ ThemeHandle(const ThemeHandle&) = delete;
+ ThemeHandle(ThemeHandle&&) = delete;
+ ThemeHandle& operator=(const ThemeHandle&) = delete;
+ ThemeHandle& operator=(ThemeHandle&&) = delete;
+
+ operator HANDLE();
+ void OpenOnce(HWND aWindow, LPCWSTR aClassList);
+ void Close();
+ };
+
+ static ThemeHandle sThemes[eUXNumClasses];
+
+ // We initialize sCommandButtonBoxMetrics separately as a performance
+ // optimization to avoid fetching dummy values for sCommandButtonMetrics
+ // when we don't need those.
+ static SIZE sCommandButtonMetrics[3];
+ static bool sCommandButtonMetricsInitialized;
+ static SIZE sCommandButtonBoxMetrics;
+ static bool sCommandButtonBoxMetricsInitialized;
+
+ static const wchar_t* GetClassName(nsUXThemeClass);
+ static void EnsureCommandButtonMetrics();
+ static void EnsureCommandButtonBoxMetrics();
+
+ public:
+ static bool sTitlebarInfoPopulatedAero;
+ static bool sTitlebarInfoPopulatedThemed;
+ static mozilla::LookAndFeel::WindowsTheme sThemeId;
+ static bool sIsDefaultWindowsTheme;
+ static bool sIsHighContrastOn;
+
+ static void Invalidate();
+ static HANDLE GetTheme(nsUXThemeClass cls);
+ static HMODULE GetThemeDLL();
+
+ // nsWindow calls this to update desktop settings info
+ static void UpdateTitlebarInfo(HWND aWnd);
+
+ static SIZE GetCommandButtonMetrics(CmdButtonIdx aMetric) {
+ EnsureCommandButtonMetrics();
+ return sCommandButtonMetrics[aMetric];
+ }
+ static SIZE GetCommandButtonBoxMetrics() {
+ EnsureCommandButtonBoxMetrics();
+ return sCommandButtonBoxMetrics;
+ }
+ static void UpdateNativeThemeInfo();
+ static mozilla::LookAndFeel::WindowsTheme GetNativeThemeId();
+ static bool IsDefaultWindowTheme();
+ static bool IsHighContrastOn();
+
+ static bool AreFlatMenusEnabled();
+ static bool IsAppThemed();
+};
+#endif // __UXThemeData_h__
diff --git a/widget/windows/nsUserIdleServiceWin.cpp b/widget/windows/nsUserIdleServiceWin.cpp
new file mode 100644
index 0000000000..1176b8a43a
--- /dev/null
+++ b/widget/windows/nsUserIdleServiceWin.cpp
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsUserIdleServiceWin.h"
+#include <windows.h>
+
+bool nsUserIdleServiceWin::PollIdleTime(uint32_t* aIdleTime) {
+ LASTINPUTINFO inputInfo;
+ inputInfo.cbSize = sizeof(inputInfo);
+ if (!::GetLastInputInfo(&inputInfo)) return false;
+
+ *aIdleTime =
+ SAFE_COMPARE_EVEN_WITH_WRAPPING(GetTickCount(), inputInfo.dwTime);
+
+ return true;
+}
+
+bool nsUserIdleServiceWin::UsePollMode() { return true; }
diff --git a/widget/windows/nsUserIdleServiceWin.h b/widget/windows/nsUserIdleServiceWin.h
new file mode 100644
index 0000000000..352f1e0a3e
--- /dev/null
+++ b/widget/windows/nsUserIdleServiceWin.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsUserIdleServiceWin_h__
+#define nsUserIdleServiceWin_h__
+
+#include "nsUserIdleService.h"
+
+/* NOTE: Compare of GetTickCount() could overflow. This corrects for
+ * overflow situations.
+ ***/
+#ifndef SAFE_COMPARE_EVEN_WITH_WRAPPING
+# define SAFE_COMPARE_EVEN_WITH_WRAPPING(A, B) \
+ (((int)((long)A - (long)B) & 0xFFFFFFFF))
+#endif
+
+class nsUserIdleServiceWin : public nsUserIdleService {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsUserIdleServiceWin, nsUserIdleService)
+
+ bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsUserIdleServiceWin> GetInstance() {
+ RefPtr<nsUserIdleServiceWin> idleService =
+ nsUserIdleService::GetInstance().downcast<nsUserIdleServiceWin>();
+ if (!idleService) {
+ idleService = new nsUserIdleServiceWin();
+ }
+
+ return idleService.forget();
+ }
+
+ protected:
+ nsUserIdleServiceWin() {}
+ virtual ~nsUserIdleServiceWin() {}
+ bool UsePollMode() override;
+};
+
+#endif // nsUserIdleServiceWin_h__
diff --git a/widget/windows/nsWidgetFactory.cpp b/widget/windows/nsWidgetFactory.cpp
new file mode 100644
index 0000000000..47c7d021ed
--- /dev/null
+++ b/widget/windows/nsWidgetFactory.cpp
@@ -0,0 +1,60 @@
+/* -*- 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 "nsWidgetFactory.h"
+
+#include "mozilla/Components.h"
+#include "nsISupports.h"
+#include "nsdefs.h"
+#include "nsWidgetsCID.h"
+#include "nsAppShell.h"
+#include "nsAppShellSingleton.h"
+#include "mozilla/WidgetUtils.h"
+#include "mozilla/widget/ScreenManager.h"
+#include "nsLookAndFeel.h"
+#include "WinMouseScrollHandler.h"
+#include "KeyboardLayout.h"
+#include "nsToolkit.h"
+
+// Modules that switch out based on the environment
+#include "nsXULAppAPI.h"
+// Desktop
+#include "nsFilePicker.h" // needs to be included before other shobjidl.h includes
+#include "nsColorPicker.h"
+// Content processes
+#include "nsFilePickerProxy.h"
+
+// Clipboard
+#include "nsClipboardHelper.h"
+#include "nsClipboard.h"
+#include "HeadlessClipboard.h"
+
+#include "WindowsUIUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+NS_IMPL_COMPONENT_FACTORY(nsIClipboard) {
+ nsCOMPtr<nsIClipboard> inst;
+ if (gfxPlatform::IsHeadless()) {
+ inst = new HeadlessClipboard();
+ } else {
+ inst = new nsClipboard();
+ }
+ return inst.forget().downcast<nsISupports>();
+}
+
+nsresult nsWidgetWindowsModuleCtor() { return nsAppShellInit(); }
+
+void nsWidgetWindowsModuleDtor() {
+ // Shutdown all XP level widget classes.
+ WidgetUtils::Shutdown();
+
+ KeyboardLayout::Shutdown();
+ MouseScrollHandler::Shutdown();
+ nsLookAndFeel::Shutdown();
+ nsToolkit::Shutdown();
+ nsAppShellShutdown();
+}
diff --git a/widget/windows/nsWidgetFactory.h b/widget/windows/nsWidgetFactory.h
new file mode 100644
index 0000000000..797d0a60bb
--- /dev/null
+++ b/widget/windows/nsWidgetFactory.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 widget_windows_nsWidgetFactory_h
+#define widget_windows_nsWidgetFactory_h
+
+#include "nscore.h"
+#include "nsID.h"
+
+class nsISupports;
+
+nsresult nsAppShellConstructor(nsISupports* outer, const nsIID& iid,
+ void** result);
+
+nsresult nsWidgetWindowsModuleCtor();
+void nsWidgetWindowsModuleDtor();
+
+#endif // defined widget_windows_nsWidgetFactory_h
diff --git a/widget/windows/nsWinGesture.cpp b/widget/windows/nsWinGesture.cpp
new file mode 100644
index 0000000000..8fd00b3ff0
--- /dev/null
+++ b/widget/windows/nsWinGesture.cpp
@@ -0,0 +1,388 @@
+/* -*- 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/. */
+
+/*
+ * nsWinGesture - Touch input handling for tablet displays.
+ */
+
+#include "nscore.h"
+#include "nsWinGesture.h"
+#include "nsUXThemeData.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/dom/SimpleGestureEventBinding.h"
+#include "mozilla/dom/WheelEventBinding.h"
+
+#include <cmath>
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+extern mozilla::LazyLogModule gWindowsLog;
+
+static bool gEnableSingleFingerPanEvents = false;
+
+nsWinGesture::nsWinGesture()
+ : mPanActive(false),
+ mFeedbackActive(false),
+ mXAxisFeedback(false),
+ mYAxisFeedback(false),
+ mPanInertiaActive(false) {
+ (void)InitLibrary();
+ mPixelScrollOverflow = 0;
+}
+
+/* Load and shutdown */
+
+bool nsWinGesture::InitLibrary() {
+ // Check to see if we want single finger gesture input. Only do this once
+ // for the app so we don't have to look it up on every window create.
+ gEnableSingleFingerPanEvents =
+ Preferences::GetBool("gestures.enable_single_finger_input", false);
+
+ return true;
+}
+
+#define GCOUNT 5
+
+bool nsWinGesture::SetWinGestureSupport(
+ HWND hWnd, WidgetGestureNotifyEvent::PanDirection aDirection) {
+ GESTURECONFIG config[GCOUNT];
+
+ memset(&config, 0, sizeof(config));
+
+ config[0].dwID = GID_ZOOM;
+ config[0].dwWant = GC_ZOOM;
+ config[0].dwBlock = 0;
+
+ config[1].dwID = GID_ROTATE;
+ config[1].dwWant = GC_ROTATE;
+ config[1].dwBlock = 0;
+
+ config[2].dwID = GID_PAN;
+ config[2].dwWant = GC_PAN | GC_PAN_WITH_INERTIA | GC_PAN_WITH_GUTTER;
+ config[2].dwBlock = GC_PAN_WITH_SINGLE_FINGER_VERTICALLY |
+ GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
+
+ if (gEnableSingleFingerPanEvents) {
+ if (aDirection == WidgetGestureNotifyEvent::ePanVertical ||
+ aDirection == WidgetGestureNotifyEvent::ePanBoth) {
+ config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY;
+ config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY;
+ }
+
+ if (aDirection == WidgetGestureNotifyEvent::ePanHorizontal ||
+ aDirection == WidgetGestureNotifyEvent::ePanBoth) {
+ config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
+ config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
+ }
+ }
+
+ config[3].dwWant = GC_TWOFINGERTAP;
+ config[3].dwID = GID_TWOFINGERTAP;
+ config[3].dwBlock = 0;
+
+ config[4].dwWant = GC_PRESSANDTAP;
+ config[4].dwID = GID_PRESSANDTAP;
+ config[4].dwBlock = 0;
+
+ return SetGestureConfig(hWnd, 0, GCOUNT, (PGESTURECONFIG)&config,
+ sizeof(GESTURECONFIG));
+}
+
+/* Helpers */
+
+bool nsWinGesture::IsPanEvent(LPARAM lParam) {
+ GESTUREINFO gi;
+
+ ZeroMemory(&gi, sizeof(GESTUREINFO));
+ gi.cbSize = sizeof(GESTUREINFO);
+
+ BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
+ if (!result) return false;
+
+ if (gi.dwID == GID_PAN) return true;
+
+ return false;
+}
+
+/* Gesture event processing */
+
+bool nsWinGesture::ProcessGestureMessage(HWND hWnd, WPARAM wParam,
+ LPARAM lParam,
+ WidgetSimpleGestureEvent& evt) {
+ GESTUREINFO gi;
+
+ ZeroMemory(&gi, sizeof(GESTUREINFO));
+ gi.cbSize = sizeof(GESTUREINFO);
+
+ BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
+ if (!result) return false;
+
+ // The coordinates of this event
+ nsPointWin coord;
+ coord = gi.ptsLocation;
+ coord.ScreenToClient(hWnd);
+
+ evt.mRefPoint = LayoutDeviceIntPoint(coord.x, coord.y);
+
+ // Multiple gesture can occur at the same time so gesture state
+ // info can't be shared.
+ switch (gi.dwID) {
+ case GID_BEGIN:
+ case GID_END:
+ // These should always fall through to DefWndProc
+ return false;
+ break;
+
+ case GID_ZOOM: {
+ if (gi.dwFlags & GF_BEGIN) {
+ // Send a zoom start event
+
+ // The low 32 bits are the distance in pixels.
+ mZoomIntermediate = (float)gi.ullArguments;
+
+ evt.mMessage = eMagnifyGestureStart;
+ evt.mDelta = 0.0;
+ } else if (gi.dwFlags & GF_END) {
+ // Send a zoom end event, the delta is the change
+ // in touch points.
+ evt.mMessage = eMagnifyGesture;
+ // (positive for a "zoom in")
+ evt.mDelta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments);
+ mZoomIntermediate = (float)gi.ullArguments;
+ } else {
+ // Send a zoom intermediate event, the delta is the change
+ // in touch points.
+ evt.mMessage = eMagnifyGestureUpdate;
+ // (positive for a "zoom in")
+ evt.mDelta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments);
+ mZoomIntermediate = (float)gi.ullArguments;
+ }
+ } break;
+
+ case GID_ROTATE: {
+ // Send a rotate start event
+ double radians = 0.0;
+
+ // On GF_BEGIN, ullArguments contains the absolute rotation at the
+ // start of the gesture. In later events it contains the offset from
+ // the start angle.
+ if (gi.ullArguments != 0)
+ radians = GID_ROTATE_ANGLE_FROM_ARGUMENT(gi.ullArguments);
+
+ double degrees = -1 * radians * (180 / M_PI);
+
+ if (gi.dwFlags & GF_BEGIN) {
+ // At some point we should pass the initial angle in
+ // along with delta. It's useful.
+ degrees = mRotateIntermediate = 0.0;
+ }
+
+ evt.mDirection = 0;
+ evt.mDelta = degrees - mRotateIntermediate;
+ mRotateIntermediate = degrees;
+
+ if (evt.mDelta > 0) {
+ evt.mDirection =
+ dom::SimpleGestureEvent_Binding::ROTATION_COUNTERCLOCKWISE;
+ } else if (evt.mDelta < 0) {
+ evt.mDirection = dom::SimpleGestureEvent_Binding::ROTATION_CLOCKWISE;
+ }
+
+ if (gi.dwFlags & GF_BEGIN) {
+ evt.mMessage = eRotateGestureStart;
+ } else if (gi.dwFlags & GF_END) {
+ evt.mMessage = eRotateGesture;
+ } else {
+ evt.mMessage = eRotateGestureUpdate;
+ }
+ } break;
+
+ case GID_TWOFINGERTAP:
+ // Normally maps to "restore" from whatever you may have recently changed.
+ // A simple double click.
+ evt.mMessage = eTapGesture;
+ evt.mClickCount = 1;
+ break;
+
+ case GID_PRESSANDTAP:
+ // Two finger right click. Defaults to right click if it falls through.
+ evt.mMessage = ePressTapGesture;
+ evt.mClickCount = 1;
+ break;
+ }
+
+ return true;
+}
+
+bool nsWinGesture::ProcessPanMessage(HWND hWnd, WPARAM wParam, LPARAM lParam) {
+ GESTUREINFO gi;
+
+ ZeroMemory(&gi, sizeof(GESTUREINFO));
+ gi.cbSize = sizeof(GESTUREINFO);
+
+ BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
+ if (!result) return false;
+
+ // The coordinates of this event
+ nsPointWin coord;
+ coord = mPanRefPoint = gi.ptsLocation;
+ // We want screen coordinates in our local offsets as client coordinates will
+ // change when feedback is taking place. Gui events though require client
+ // coordinates.
+ mPanRefPoint.ScreenToClient(hWnd);
+
+ switch (gi.dwID) {
+ case GID_BEGIN:
+ case GID_END:
+ // These should always fall through to DefWndProc
+ return false;
+ break;
+
+ // Setup pixel scroll events for both axis
+ case GID_PAN: {
+ if (gi.dwFlags & GF_BEGIN) {
+ mPanIntermediate = coord;
+ mPixelScrollDelta = 0;
+ mPanActive = true;
+ mPanInertiaActive = false;
+ } else {
+#ifdef DBG_jimm
+ int32_t deltaX = mPanIntermediate.x - coord.x;
+ int32_t deltaY = mPanIntermediate.y - coord.y;
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("coordX=%d coordY=%d deltaX=%d deltaY=%d x:%d y:%d\n", coord.x,
+ coord.y, deltaX, deltaY, mXAxisFeedback, mYAxisFeedback));
+#endif
+
+ mPixelScrollDelta.x = mPanIntermediate.x - coord.x;
+ mPixelScrollDelta.y = mPanIntermediate.y - coord.y;
+ mPanIntermediate = coord;
+
+ if (gi.dwFlags & GF_INERTIA) mPanInertiaActive = true;
+
+ if (gi.dwFlags & GF_END) {
+ mPanActive = false;
+ mPanInertiaActive = false;
+ PanFeedbackFinalize(hWnd, true);
+ }
+ }
+ } break;
+ }
+ return true;
+}
+
+inline bool TestTransition(int32_t a, int32_t b) {
+ // If a is zero, overflow is zero, implying the cursor has moved back to the
+ // start position. If b is zero, cached overscroll is zero, implying feedback
+ // just begun.
+ if (a == 0 || b == 0) return true;
+ // Test for different signs.
+ return (a < 0) == (b < 0);
+}
+
+void nsWinGesture::UpdatePanFeedbackX(HWND hWnd, int32_t scrollOverflow,
+ bool& endFeedback) {
+ // If scroll overflow was returned indicating we panned past the bounds of
+ // the scrollable view port, start feeback.
+ if (scrollOverflow != 0) {
+ if (!mFeedbackActive) {
+ BeginPanningFeedback(hWnd);
+ mFeedbackActive = true;
+ }
+ endFeedback = false;
+ mXAxisFeedback = true;
+ return;
+ }
+
+ if (mXAxisFeedback) {
+ int32_t newOverflow = mPixelScrollOverflow.x - mPixelScrollDelta.x;
+
+ // Detect a reverse transition past the starting drag point. This tells us
+ // the user has panned all the way back so we can stop providing feedback
+ // for this axis.
+ if (!TestTransition(newOverflow, mPixelScrollOverflow.x) ||
+ newOverflow == 0)
+ return;
+
+ // Cache the total over scroll in pixels.
+ mPixelScrollOverflow.x = newOverflow;
+ endFeedback = false;
+ }
+}
+
+void nsWinGesture::UpdatePanFeedbackY(HWND hWnd, int32_t scrollOverflow,
+ bool& endFeedback) {
+ // If scroll overflow was returned indicating we panned past the bounds of
+ // the scrollable view port, start feeback.
+ if (scrollOverflow != 0) {
+ if (!mFeedbackActive) {
+ BeginPanningFeedback(hWnd);
+ mFeedbackActive = true;
+ }
+ endFeedback = false;
+ mYAxisFeedback = true;
+ return;
+ }
+
+ if (mYAxisFeedback) {
+ int32_t newOverflow = mPixelScrollOverflow.y - mPixelScrollDelta.y;
+
+ // Detect a reverse transition past the starting drag point. This tells us
+ // the user has panned all the way back so we can stop providing feedback
+ // for this axis.
+ if (!TestTransition(newOverflow, mPixelScrollOverflow.y) ||
+ newOverflow == 0)
+ return;
+
+ // Cache the total over scroll in pixels.
+ mPixelScrollOverflow.y = newOverflow;
+ endFeedback = false;
+ }
+}
+
+void nsWinGesture::PanFeedbackFinalize(HWND hWnd, bool endFeedback) {
+ if (!mFeedbackActive) return;
+
+ if (endFeedback) {
+ mFeedbackActive = false;
+ mXAxisFeedback = false;
+ mYAxisFeedback = false;
+ mPixelScrollOverflow = 0;
+ EndPanningFeedback(hWnd, TRUE);
+ return;
+ }
+
+ UpdatePanningFeedback(hWnd, mPixelScrollOverflow.x, mPixelScrollOverflow.y,
+ mPanInertiaActive);
+}
+
+bool nsWinGesture::PanDeltaToPixelScroll(WidgetWheelEvent& aWheelEvent) {
+ aWheelEvent.mDeltaX = aWheelEvent.mDeltaY = aWheelEvent.mDeltaZ = 0.0;
+ aWheelEvent.mLineOrPageDeltaX = aWheelEvent.mLineOrPageDeltaY = 0;
+
+ aWheelEvent.mRefPoint = LayoutDeviceIntPoint(mPanRefPoint.x, mPanRefPoint.y);
+ aWheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_PIXEL;
+ aWheelEvent.mScrollType = WidgetWheelEvent::SCROLL_SYNCHRONOUSLY;
+ aWheelEvent.mIsNoLineOrPageDelta = true;
+
+ aWheelEvent.mOverflowDeltaX = 0.0;
+ aWheelEvent.mOverflowDeltaY = 0.0;
+
+ // Don't scroll the view if we are currently at a bounds, or, if we are
+ // panning back from a max feedback position. This keeps the original drag
+ // point constant.
+ if (!mXAxisFeedback) {
+ aWheelEvent.mDeltaX = mPixelScrollDelta.x;
+ }
+ if (!mYAxisFeedback) {
+ aWheelEvent.mDeltaY = mPixelScrollDelta.y;
+ }
+
+ return (aWheelEvent.mDeltaX != 0 || aWheelEvent.mDeltaY != 0);
+}
diff --git a/widget/windows/nsWinGesture.h b/widget/windows/nsWinGesture.h
new file mode 100644
index 0000000000..040b460da9
--- /dev/null
+++ b/widget/windows/nsWinGesture.h
@@ -0,0 +1,91 @@
+/* -*- 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 WinGesture_h__
+#define WinGesture_h__
+
+/*
+ * nsWinGesture - Touch input handling for tablet displays.
+ */
+
+#include "nsdefs.h"
+#include <winuser.h>
+#include <tpcshrd.h>
+#include "nsPoint.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TouchEvents.h"
+
+// WM_TABLET_QUERYSYSTEMGESTURESTATUS return values
+#define TABLET_ROTATE_GESTURE_ENABLE 0x02000000
+
+class nsPointWin : public nsIntPoint {
+ public:
+ nsPointWin& operator=(const POINTS& aPoint) {
+ x = aPoint.x;
+ y = aPoint.y;
+ return *this;
+ }
+ nsPointWin& operator=(const POINT& aPoint) {
+ x = aPoint.x;
+ y = aPoint.y;
+ return *this;
+ }
+ nsPointWin& operator=(int val) {
+ x = y = val;
+ return *this;
+ }
+ void ScreenToClient(HWND hWnd) {
+ POINT tmp;
+ tmp.x = x;
+ tmp.y = y;
+ ::ScreenToClient(hWnd, &tmp);
+ *this = tmp;
+ }
+};
+
+class nsWinGesture {
+ public:
+ nsWinGesture();
+
+ public:
+ bool SetWinGestureSupport(
+ HWND hWnd, mozilla::WidgetGestureNotifyEvent::PanDirection aDirection);
+ bool ShutdownWinGestureSupport();
+
+ // Simple gesture process
+ bool ProcessGestureMessage(HWND hWnd, WPARAM wParam, LPARAM lParam,
+ mozilla::WidgetSimpleGestureEvent& evt);
+
+ // Pan processing
+ bool IsPanEvent(LPARAM lParam);
+ bool ProcessPanMessage(HWND hWnd, WPARAM wParam, LPARAM lParam);
+ bool PanDeltaToPixelScroll(mozilla::WidgetWheelEvent& aWheelEvent);
+ void UpdatePanFeedbackX(HWND hWnd, int32_t scrollOverflow, bool& endFeedback);
+ void UpdatePanFeedbackY(HWND hWnd, int32_t scrollOverflow, bool& endFeedback);
+ void PanFeedbackFinalize(HWND hWnd, bool endFeedback);
+
+ private:
+ // Delay load info
+ bool InitLibrary();
+
+ // Pan and feedback state
+ nsPointWin mPanIntermediate;
+ nsPointWin mPanRefPoint;
+ nsPointWin mPixelScrollDelta;
+ bool mPanActive;
+ bool mFeedbackActive;
+ bool mXAxisFeedback;
+ bool mYAxisFeedback;
+ bool mPanInertiaActive;
+ nsPointWin mPixelScrollOverflow;
+
+ // Zoom state
+ double mZoomIntermediate;
+
+ // Rotate state
+ double mRotateIntermediate;
+};
+
+#endif /* WinGesture_h__ */
diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp
new file mode 100644
index 0000000000..814ead00d2
--- /dev/null
+++ b/widget/windows/nsWindow.cpp
@@ -0,0 +1,8663 @@
+/* -*- 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/. */
+
+/*
+ * nsWindow - Native window management and event handling.
+ *
+ * nsWindow is organized into a set of major blocks and
+ * block subsections. The layout is as follows:
+ *
+ * Includes
+ * Variables
+ * nsIWidget impl.
+ * nsIWidget methods and utilities
+ * nsSwitchToUIThread impl.
+ * nsSwitchToUIThread methods and utilities
+ * Moz events
+ * Event initialization
+ * Event dispatching
+ * Native events
+ * Wndproc(s)
+ * Event processing
+ * OnEvent event handlers
+ * IME management and accessibility
+ * Transparency
+ * Popup hook handling
+ * Misc. utilities
+ * Child window impl.
+ *
+ * Search for "BLOCK:" to find major blocks.
+ * Search for "SECTION:" to find specific sections.
+ *
+ * Blocks should be split out into separate files if they
+ * become unmanageable.
+ *
+ * Related source:
+ *
+ * nsWindowDefs.h - Definitions, macros, structs, enums
+ * and general setup.
+ * nsWindowDbg.h/.cpp - Debug related code and directives.
+ * nsWindowGfx.h/.cpp - Graphics and painting.
+ *
+ */
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Includes
+ **
+ ** Include headers.
+ **
+ **************************************************************
+ **************************************************************/
+
+#include "gfx2DGlue.h"
+#include "gfxEnv.h"
+#include "gfxPlatform.h"
+
+#include "mozilla/AppShutdown.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/PreXULSkeletonUI.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/TimeStamp.h"
+
+#include "mozilla/ipc/MessageChannel.h"
+#include <algorithm>
+#include <limits>
+
+#include "nsWindow.h"
+#include "nsAppRunner.h"
+
+#include <shellapi.h>
+#include <windows.h>
+#include <wtsapi32.h>
+#include <process.h>
+#include <commctrl.h>
+#include <dbt.h>
+#include <unknwn.h>
+#include <psapi.h>
+#include <rpc.h>
+
+#include "mozilla/Logging.h"
+#include "prtime.h"
+#include "prenv.h"
+
+#include "mozilla/WidgetTraceEvent.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIKeyEventInPluginCallback.h"
+#include "nsITheme.h"
+#include "nsIObserverService.h"
+#include "nsIScreenManager.h"
+#include "imgIContainer.h"
+#include "nsIFile.h"
+#include "nsIRollupListener.h"
+#include "nsIClipboard.h"
+#include "WinMouseScrollHandler.h"
+#include "nsFontMetrics.h"
+#include "nsIFontEnumerator.h"
+#include "nsFont.h"
+#include "nsRect.h"
+#include "nsThreadUtils.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsGkAtoms.h"
+#include "nsCRT.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsWidgetsCID.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsString.h"
+#include "mozilla/Services.h"
+#include "nsNativeThemeWin.h"
+#include "nsWindowsDllInterceptor.h"
+#include "nsLayoutUtils.h"
+#include "nsView.h"
+#include "nsWindowGfx.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxDWriteFonts.h"
+#include "Layers.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Preferences.h"
+#include "SystemTimeConverter.h"
+#include "WinTaskbar.h"
+#include "WidgetUtils.h"
+#include "WinContentSystemParameters.h"
+#include "nsIWidgetListener.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/TextEvents.h" // For WidgetKeyboardEvent
+#include "mozilla/TextEventDispatcherListener.h"
+#include "mozilla/widget/nsAutoRollup.h"
+#include "mozilla/widget/WinNativeEventData.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsStyleConsts.h"
+#include "nsBidiKeyboard.h"
+#include "nsStyleConsts.h"
+#include "gfxConfig.h"
+#include "InProcessWinCompositorWidget.h"
+#include "InputDeviceUtils.h"
+#include "ScreenHelperWin.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_layout.h"
+
+#include "nsIGfxInfo.h"
+#include "nsUXThemeConstants.h"
+#include "KeyboardLayout.h"
+#include "nsNativeDragTarget.h"
+#include <mmsystem.h> // needed for WIN32_LEAN_AND_MEAN
+#include <zmouse.h>
+#include <richedit.h>
+
+#if defined(ACCESSIBILITY)
+
+# ifdef DEBUG
+# include "mozilla/a11y/Logging.h"
+# endif
+
+# include "oleidl.h"
+# include <winuser.h>
+# include "nsAccessibilityService.h"
+# include "mozilla/PresShell.h"
+# include "mozilla/a11y/DocAccessible.h"
+# include "mozilla/a11y/LazyInstantiator.h"
+# include "mozilla/a11y/Platform.h"
+# if !defined(WINABLEAPI)
+# include <winable.h>
+# endif // !defined(WINABLEAPI)
+#endif // defined(ACCESSIBILITY)
+
+#include "nsIWinTaskbar.h"
+#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1"
+
+#include "nsIWindowsUIUtils.h"
+
+#include "nsWindowDefs.h"
+
+#include "nsCrashOnException.h"
+
+#include "nsIContent.h"
+
+#include "mozilla/BackgroundHangMonitor.h"
+#include "WinIMEHandler.h"
+
+#include "npapi.h"
+
+#include <d3d11.h>
+
+#include "InkCollector.h"
+
+// ERROR from wingdi.h (below) gets undefined by some code.
+// #define ERROR 0
+// #define RGN_ERROR ERROR
+#define ERROR 0
+
+#if !defined(SM_CONVERTIBLESLATEMODE)
+# define SM_CONVERTIBLESLATEMODE 0x2003
+#endif
+
+#if !defined(WM_DPICHANGED)
+# define WM_DPICHANGED 0x02E0
+#endif
+
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/layers/APZInputBridge.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/layers/KnowsCompositor.h"
+#include "mozilla/layers/ScrollInputMethods.h"
+#include "InputData.h"
+
+#include "mozilla/Telemetry.h"
+#include "mozilla/plugins/PluginProcessParent.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+
+#include "DirectManipulationOwner.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+using namespace mozilla::plugins;
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Variables
+ **
+ ** nsWindow Class static initializations and global variables.
+ **
+ **************************************************************
+ **************************************************************/
+
+/**************************************************************
+ *
+ * SECTION: nsWindow statics
+ *
+ **************************************************************/
+
+bool nsWindow::sDropShadowEnabled = true;
+uint32_t nsWindow::sInstanceCount = 0;
+bool nsWindow::sSwitchKeyboardLayout = false;
+BOOL nsWindow::sIsOleInitialized = FALSE;
+HCURSOR nsWindow::sHCursor = nullptr;
+imgIContainer* nsWindow::sCursorImgContainer = nullptr;
+nsWindow* nsWindow::sCurrentWindow = nullptr;
+bool nsWindow::sJustGotDeactivate = false;
+bool nsWindow::sJustGotActivate = false;
+bool nsWindow::sIsInMouseCapture = false;
+
+// imported in nsWidgetFactory.cpp
+TriStateBool nsWindow::sCanQuit = TRI_UNKNOWN;
+
+// Hook Data Memebers for Dropdowns. sProcessHook Tells the
+// hook methods whether they should be processing the hook
+// messages.
+HHOOK nsWindow::sMsgFilterHook = nullptr;
+HHOOK nsWindow::sCallProcHook = nullptr;
+HHOOK nsWindow::sCallMouseHook = nullptr;
+bool nsWindow::sProcessHook = false;
+UINT nsWindow::sRollupMsgId = 0;
+HWND nsWindow::sRollupMsgWnd = nullptr;
+UINT nsWindow::sHookTimerId = 0;
+
+// Mouse Clicks - static variable definitions for figuring
+// out 1 - 3 Clicks.
+POINT nsWindow::sLastMousePoint = {0};
+POINT nsWindow::sLastMouseMovePoint = {0};
+LONG nsWindow::sLastMouseDownTime = 0L;
+LONG nsWindow::sLastClickCount = 0L;
+BYTE nsWindow::sLastMouseButton = 0;
+
+bool nsWindow::sHaveInitializedPrefs = false;
+bool nsWindow::sIsRestoringSession = false;
+
+TriStateBool nsWindow::sHasBogusPopupsDropShadowOnMultiMonitor = TRI_UNKNOWN;
+
+static SystemTimeConverter<DWORD>& TimeConverter() {
+ static SystemTimeConverter<DWORD> timeConverterSingleton;
+ return timeConverterSingleton;
+}
+
+namespace mozilla {
+
+class CurrentWindowsTimeGetter {
+ public:
+ explicit CurrentWindowsTimeGetter(HWND aWnd) : mWnd(aWnd) {}
+
+ DWORD GetCurrentTime() const { return ::GetTickCount(); }
+
+ void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
+ DWORD currentTime = GetCurrentTime();
+ if (sBackwardsSkewStamp && currentTime == sLastPostTime) {
+ // There's already one inflight with this timestamp. Don't
+ // send a duplicate.
+ return;
+ }
+ sBackwardsSkewStamp = Some(aNow);
+ sLastPostTime = currentTime;
+ static_assert(sizeof(WPARAM) >= sizeof(DWORD),
+ "Can't fit a DWORD in a WPARAM");
+ ::PostMessage(mWnd, MOZ_WM_SKEWFIX, sLastPostTime, 0);
+ }
+
+ static bool GetAndClearBackwardsSkewStamp(DWORD aPostTime,
+ TimeStamp* aOutSkewStamp) {
+ if (aPostTime != sLastPostTime) {
+ // The SKEWFIX message is stale; we've sent a new one since then.
+ // Ignore this one.
+ return false;
+ }
+ MOZ_ASSERT(sBackwardsSkewStamp);
+ *aOutSkewStamp = sBackwardsSkewStamp.value();
+ sBackwardsSkewStamp = Nothing();
+ return true;
+ }
+
+ private:
+ static Maybe<TimeStamp> sBackwardsSkewStamp;
+ static DWORD sLastPostTime;
+ HWND mWnd;
+};
+
+Maybe<TimeStamp> CurrentWindowsTimeGetter::sBackwardsSkewStamp;
+DWORD CurrentWindowsTimeGetter::sLastPostTime = 0;
+
+} // namespace mozilla
+
+/**************************************************************
+ *
+ * SECTION: globals variables
+ *
+ **************************************************************/
+
+static const char* sScreenManagerContractID =
+ "@mozilla.org/gfx/screenmanager;1";
+
+extern mozilla::LazyLogModule gWindowsLog;
+
+// True if we have sent a notification that we are suspending/sleeping.
+static bool gIsSleepMode = false;
+
+static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
+
+// General purpose user32.dll hook object
+static WindowsDllInterceptor sUser32Intercept;
+
+// 2 pixel offset for eTransparencyBorderlessGlass which equals the size of
+// the default window border Windows paints. Glass will be extended inward
+// this distance to remove the border.
+static const int32_t kGlassMarginAdjustment = 2;
+
+// When the client area is extended out into the default window frame area,
+// this is the minimum amount of space along the edge of resizable windows
+// we will always display a resize cursor in, regardless of the underlying
+// content.
+static const int32_t kResizableBorderMinSize = 3;
+
+// We should never really try to accelerate windows bigger than this. In some
+// cases this might lead to no D3D9 acceleration where we could have had it
+// but D3D9 does not reliably report when it supports bigger windows. 8192
+// is as safe as we can get, we know at least D3D10 hardware always supports
+// this, other hardware we expect to report correctly in D3D9.
+#define MAX_ACCELERATED_DIMENSION 8192
+
+// On window open (as well as after), Windows has an unfortunate habit of
+// sending rather a lot of WM_NCHITTEST messages. Because we have to do point
+// to DOM target conversions for these, we cache responses for a given
+// coordinate this many milliseconds:
+#define HITTEST_CACHE_LIFETIME_MS 50
+
+#if defined(ACCESSIBILITY)
+
+namespace mozilla {
+
+/**
+ * Windows touchscreen code works by setting a global WH_GETMESSAGE hook and
+ * injecting tiptsf.dll. The touchscreen process then posts registered messages
+ * to our main thread. The tiptsf hook picks up those registered messages and
+ * uses them as commands, some of which call into UIA, which then calls into
+ * MSAA, which then sends WM_GETOBJECT to us.
+ *
+ * We can get ahead of this by installing our own thread-local WH_GETMESSAGE
+ * hook. Since thread-local hooks are called ahead of global hooks, we will
+ * see these registered messages before tiptsf does. At this point we can then
+ * raise a flag that blocks a11y before invoking CallNextHookEx which will then
+ * invoke the global tiptsf hook. Then when we see WM_GETOBJECT, we check the
+ * flag by calling TIPMessageHandler::IsA11yBlocked().
+ *
+ * For Windows 8, we also hook tiptsf!ProcessCaretEvents, which is an a11y hook
+ * function that also calls into UIA.
+ */
+class TIPMessageHandler {
+ public:
+ ~TIPMessageHandler() {
+ if (mHook) {
+ ::UnhookWindowsHookEx(mHook);
+ }
+ }
+
+ static void Initialize() {
+ if (!IsWin8OrLater()) {
+ return;
+ }
+
+ if (sInstance) {
+ return;
+ }
+
+ sInstance = new TIPMessageHandler();
+ ClearOnShutdown(&sInstance);
+ }
+
+ static bool IsA11yBlocked() {
+ if (!sInstance) {
+ return false;
+ }
+
+ return sInstance->mA11yBlockCount > 0;
+ }
+
+ private:
+ TIPMessageHandler() : mHook(nullptr), mA11yBlockCount(0) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Registered messages used by tiptsf
+ mMessages[0] = ::RegisterWindowMessage(L"ImmersiveFocusNotification");
+ mMessages[1] = ::RegisterWindowMessage(L"TipCloseMenus");
+ mMessages[2] = ::RegisterWindowMessage(L"TabletInputPanelOpening");
+ mMessages[3] = ::RegisterWindowMessage(L"IHM Pen or Touch Event noticed");
+ mMessages[4] = ::RegisterWindowMessage(L"ProgrammabilityCaretVisibility");
+ mMessages[5] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPHidden");
+ mMessages[6] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPInfo");
+
+ mHook = ::SetWindowsHookEx(WH_GETMESSAGE, &TIPHook, nullptr,
+ ::GetCurrentThreadId());
+ MOZ_ASSERT(mHook);
+
+ // On touchscreen devices, tiptsf.dll will have been loaded when STA COM was
+ // first initialized.
+ if (!IsWin10OrLater() && GetModuleHandle(L"tiptsf.dll") &&
+ !sProcessCaretEventsStub) {
+ sTipTsfInterceptor.Init("tiptsf.dll");
+ DebugOnly<bool> ok = sProcessCaretEventsStub.Set(
+ sTipTsfInterceptor, "ProcessCaretEvents", &ProcessCaretEventsHook);
+ MOZ_ASSERT(ok);
+ }
+
+ if (!sSendMessageTimeoutWStub) {
+ sUser32Intercept.Init("user32.dll");
+ DebugOnly<bool> hooked = sSendMessageTimeoutWStub.Set(
+ sUser32Intercept, "SendMessageTimeoutW", &SendMessageTimeoutWHook);
+ MOZ_ASSERT(hooked);
+ }
+ }
+
+ class MOZ_RAII A11yInstantiationBlocker {
+ public:
+ A11yInstantiationBlocker() {
+ if (!TIPMessageHandler::sInstance) {
+ return;
+ }
+ ++TIPMessageHandler::sInstance->mA11yBlockCount;
+ }
+
+ ~A11yInstantiationBlocker() {
+ if (!TIPMessageHandler::sInstance) {
+ return;
+ }
+ MOZ_ASSERT(TIPMessageHandler::sInstance->mA11yBlockCount > 0);
+ --TIPMessageHandler::sInstance->mA11yBlockCount;
+ }
+ };
+
+ friend class A11yInstantiationBlocker;
+
+ static LRESULT CALLBACK TIPHook(int aCode, WPARAM aWParam, LPARAM aLParam) {
+ if (aCode < 0 || !sInstance) {
+ return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
+ }
+
+ MSG* msg = reinterpret_cast<MSG*>(aLParam);
+ UINT& msgCode = msg->message;
+
+ for (uint32_t i = 0; i < ArrayLength(sInstance->mMessages); ++i) {
+ if (msgCode == sInstance->mMessages[i]) {
+ A11yInstantiationBlocker block;
+ return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
+ }
+ }
+
+ return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
+ }
+
+ static void CALLBACK ProcessCaretEventsHook(HWINEVENTHOOK aWinEventHook,
+ DWORD aEvent, HWND aHwnd,
+ LONG aObjectId, LONG aChildId,
+ DWORD aGeneratingTid,
+ DWORD aEventTime) {
+ A11yInstantiationBlocker block;
+ sProcessCaretEventsStub(aWinEventHook, aEvent, aHwnd, aObjectId, aChildId,
+ aGeneratingTid, aEventTime);
+ }
+
+ static LRESULT WINAPI SendMessageTimeoutWHook(HWND aHwnd, UINT aMsgCode,
+ WPARAM aWParam, LPARAM aLParam,
+ UINT aFlags, UINT aTimeout,
+ PDWORD_PTR aMsgResult) {
+ // We don't want to handle this unless the message is a WM_GETOBJECT that we
+ // want to block, and the aHwnd is a nsWindow that belongs to the current
+ // thread.
+ if (!aMsgResult || aMsgCode != WM_GETOBJECT ||
+ static_cast<DWORD>(aLParam) != OBJID_CLIENT ||
+ !WinUtils::GetNSWindowPtr(aHwnd) ||
+ ::GetWindowThreadProcessId(aHwnd, nullptr) != ::GetCurrentThreadId() ||
+ !IsA11yBlocked()) {
+ return sSendMessageTimeoutWStub(aHwnd, aMsgCode, aWParam, aLParam, aFlags,
+ aTimeout, aMsgResult);
+ }
+
+ // In this case we want to fake the result that would happen if we had
+ // decided not to handle WM_GETOBJECT in our WndProc. We hand the message
+ // off to DefWindowProc to accomplish this.
+ *aMsgResult = static_cast<DWORD_PTR>(
+ ::DefWindowProcW(aHwnd, aMsgCode, aWParam, aLParam));
+
+ return static_cast<LRESULT>(TRUE);
+ }
+
+ static WindowsDllInterceptor sTipTsfInterceptor;
+ static WindowsDllInterceptor::FuncHookType<WINEVENTPROC>
+ sProcessCaretEventsStub;
+ static WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
+ sSendMessageTimeoutWStub;
+ static StaticAutoPtr<TIPMessageHandler> sInstance;
+
+ HHOOK mHook;
+ UINT mMessages[7];
+ uint32_t mA11yBlockCount;
+};
+
+WindowsDllInterceptor TIPMessageHandler::sTipTsfInterceptor;
+WindowsDllInterceptor::FuncHookType<WINEVENTPROC>
+ TIPMessageHandler::sProcessCaretEventsStub;
+WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
+ TIPMessageHandler::sSendMessageTimeoutWStub;
+StaticAutoPtr<TIPMessageHandler> TIPMessageHandler::sInstance;
+
+} // namespace mozilla
+
+#endif // defined(ACCESSIBILITY)
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: nsIWidget impl.
+ **
+ ** nsIWidget interface implementation, broken down into
+ ** sections.
+ **
+ **************************************************************
+ **************************************************************/
+
+/**************************************************************
+ *
+ * SECTION: nsWindow construction and destruction
+ *
+ **************************************************************/
+
+nsWindow::nsWindow(bool aIsChildWindow)
+ : nsWindowBase(),
+ mResizeState(NOT_RESIZING),
+ mIsChildWindow(aIsChildWindow) {
+ mIconSmall = nullptr;
+ mIconBig = nullptr;
+ mWnd = nullptr;
+ mLastKillFocusWindow = nullptr;
+ mTransitionWnd = nullptr;
+ mPaintDC = nullptr;
+ mPrevWndProc = nullptr;
+ mNativeDragTarget = nullptr;
+ mDeviceNotifyHandle = nullptr;
+ mInDtor = false;
+ mIsVisible = false;
+ mIsTopWidgetWindow = false;
+ mDisplayPanFeedback = false;
+ mTouchWindow = false;
+ mFutureMarginsToUse = false;
+ mCustomNonClient = false;
+ mHideChrome = false;
+ mFullscreenMode = false;
+ mMousePresent = false;
+ mMouseInDraggableArea = false;
+ mDestroyCalled = false;
+ mIsEarlyBlankWindow = false;
+ mIsShowingPreXULSkeletonUI = false;
+ mResizable = false;
+ mHasTaskbarIconBeenCreated = false;
+ mMouseTransparent = false;
+ mPickerDisplayCount = 0;
+ mWindowType = eWindowType_child;
+ mBorderStyle = eBorderStyle_default;
+ mOldSizeMode = nsSizeMode_Normal;
+ mLastSizeMode = nsSizeMode_Normal;
+ mLastSize.width = 0;
+ mLastSize.height = 0;
+ mOldStyle = 0;
+ mOldExStyle = 0;
+ mPainting = 0;
+ mLastKeyboardLayout = 0;
+ mLastPaintEndTime = TimeStamp::Now();
+ mCachedHitTestPoint.x = 0;
+ mCachedHitTestPoint.y = 0;
+ mCachedHitTestTime = TimeStamp::Now();
+ mCachedHitTestResult = 0;
+#ifdef MOZ_XUL
+ mTransparencyMode = eTransparencyOpaque;
+ memset(&mGlassMargins, 0, sizeof mGlassMargins);
+#endif
+ DWORD background = ::GetSysColor(COLOR_BTNFACE);
+ mBrush = ::CreateSolidBrush(NSRGB_2_COLOREF(background));
+ mSendingSetText = false;
+ mDefaultScale = -1.0; // not yet set, will be calculated on first use
+ mAspectRatio = 0.0; // not yet set, will be calculated on first use
+
+ mTaskbarPreview = nullptr;
+
+ mCompositorWidgetDelegate = nullptr;
+
+ // Global initialization
+ if (!sInstanceCount) {
+ // Global app registration id for Win7 and up. See
+ // WinTaskbar.cpp for details.
+ mozilla::widget::WinTaskbar::RegisterAppUserModelID();
+ KeyboardLayout::GetInstance()->OnLayoutChange(::GetKeyboardLayout(0));
+#if defined(ACCESSIBILITY)
+ mozilla::TIPMessageHandler::Initialize();
+#endif // defined(ACCESSIBILITY)
+ if (SUCCEEDED(::OleInitialize(nullptr))) {
+ sIsOleInitialized = TRUE;
+ }
+ NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
+ MouseScrollHandler::Initialize();
+ // Init theme data
+ nsUXThemeData::UpdateNativeThemeInfo();
+ RedirectedKeyDownMessageManager::Forget();
+ if (mPointerEvents.ShouldEnableInkCollector()) {
+ InkCollector::sInkCollector = new InkCollector();
+ }
+ } // !sInstanceCount
+
+ mIdleService = nullptr;
+
+ mSizeConstraintsScale = GetDefaultScale().scale;
+ mMaxTextureSize = -1; // Will be calculated when layer manager is created.
+
+ mRequestFxrOutputPending = false;
+
+ sInstanceCount++;
+}
+
+nsWindow::~nsWindow() {
+ mInDtor = true;
+
+ // If the widget was released without calling Destroy() then the native window
+ // still exists, and we need to destroy it. Destroy() will early-return if it
+ // was already called. In any case it is important to call it before
+ // destroying mPresentLock (cf. 1156182).
+ Destroy();
+
+ // Free app icon resources. This must happen after `OnDestroy` (see bug
+ // 708033).
+ if (mIconSmall) ::DestroyIcon(mIconSmall);
+
+ if (mIconBig) ::DestroyIcon(mIconBig);
+
+ sInstanceCount--;
+
+ // Global shutdown
+ if (sInstanceCount == 0) {
+ if (InkCollector::sInkCollector) {
+ InkCollector::sInkCollector->Shutdown();
+ InkCollector::sInkCollector = nullptr;
+ }
+ IMEHandler::Terminate();
+ NS_IF_RELEASE(sCursorImgContainer);
+ if (sIsOleInitialized) {
+ ::OleFlushClipboard();
+ ::OleUninitialize();
+ sIsOleInitialized = FALSE;
+ }
+ }
+
+ NS_IF_RELEASE(mNativeDragTarget);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Create, nsIWidget::Destroy
+ *
+ * Creating and destroying windows for this widget.
+ *
+ **************************************************************/
+
+// Allow Derived classes to modify the height that is passed
+// when the window is created or resized.
+int32_t nsWindow::GetHeight(int32_t aProposedHeight) { return aProposedHeight; }
+
+static bool ShouldCacheTitleBarInfo(nsWindowType aWindowType,
+ nsBorderStyle aBorderStyle) {
+ return (aWindowType == eWindowType_toplevel) &&
+ (aBorderStyle == eBorderStyle_default ||
+ aBorderStyle == eBorderStyle_all) &&
+ (!nsUXThemeData::sTitlebarInfoPopulatedThemed ||
+ !nsUXThemeData::sTitlebarInfoPopulatedAero);
+}
+
+void nsWindow::SendAnAPZEvent(InputData& aEvent) {
+ LRESULT popupHandlingResult;
+ if (DealWithPopups(mWnd, MOZ_WM_DMANIP, 0, 0, &popupHandlingResult)) {
+ // We need to consume the event after using it to roll up the popup(s).
+ return;
+ }
+
+ APZEventResult result;
+ if (mAPZC) {
+ result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent);
+ }
+ if (result.mStatus == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+
+ MOZ_ASSERT(aEvent.mInputType == PANGESTURE_INPUT ||
+ aEvent.mInputType == PINCHGESTURE_INPUT);
+
+ if (aEvent.mInputType == PANGESTURE_INPUT) {
+ PanGestureInput& panInput = aEvent.AsPanGestureInput();
+ WidgetWheelEvent event = panInput.ToWidgetEvent(this);
+ ProcessUntransformedAPZEvent(&event, result);
+
+ return;
+ }
+
+ PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
+ WidgetWheelEvent event = pinchInput.ToWidgetEvent(this);
+ ProcessUntransformedAPZEvent(&event, result);
+}
+
+void nsWindow::RecreateDirectManipulationIfNeeded() {
+ DestroyDirectManipulation();
+
+ if (mWindowType != eWindowType_toplevel && mWindowType != eWindowType_popup) {
+ return;
+ }
+
+ if (!(StaticPrefs::apz_allow_zooming() ||
+ StaticPrefs::apz_windows_use_direct_manipulation()) ||
+ StaticPrefs::apz_windows_force_disable_direct_manipulation()) {
+ return;
+ }
+
+ if (!IsWin10OrLater()) {
+ // Chrome source said the Windows Direct Manipulation implementation had
+ // important bugs until Windows 10 (although IE on Windows 8.1 seems to use
+ // Direct Manipulation).
+ return;
+ }
+
+ mDmOwner = MakeUnique<DirectManipulationOwner>(this);
+
+ LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(),
+ GetHeight(mBounds.Height()));
+ mDmOwner->Init(bounds);
+}
+
+void nsWindow::ResizeDirectManipulationViewport() {
+ if (mDmOwner) {
+ LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(),
+ GetHeight(mBounds.Height()));
+ mDmOwner->ResizeViewport(bounds);
+ }
+}
+
+void nsWindow::DestroyDirectManipulation() {
+ if (mDmOwner) {
+ mDmOwner->Destroy();
+ mDmOwner.reset();
+ }
+}
+
+// Create the proper widget
+nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) {
+ nsWidgetInitData defaultInitData;
+ if (!aInitData) aInitData = &defaultInitData;
+
+ nsIWidget* baseParent =
+ aInitData->mWindowType == eWindowType_dialog ||
+ aInitData->mWindowType == eWindowType_toplevel ||
+ aInitData->mWindowType == eWindowType_invisible
+ ? nullptr
+ : aParent;
+
+ mIsTopWidgetWindow = (nullptr == baseParent);
+ mBounds = aRect;
+
+ // Ensure that the toolkit is created.
+ nsToolkit::GetToolkit();
+
+ BaseCreate(baseParent, aInitData);
+
+ HWND parent;
+ if (aParent) { // has a nsIWidget parent
+ parent = aParent ? (HWND)aParent->GetNativeData(NS_NATIVE_WINDOW) : nullptr;
+ mParent = aParent;
+ } else { // has a nsNative parent
+ parent = (HWND)aNativeParent;
+ mParent =
+ aNativeParent ? WinUtils::GetNSWindowPtr((HWND)aNativeParent) : nullptr;
+ }
+
+ mIsRTL = aInitData->mRTL;
+ mOpeningAnimationSuppressed = aInitData->mIsAnimationSuppressed;
+ mAlwaysOnTop = aInitData->mAlwaysOnTop;
+ mResizable = aInitData->mResizable;
+
+ DWORD style = WindowStyle();
+ DWORD extendedStyle = WindowExStyle();
+
+ // When window is PiP window on Windows7, WS_EX_COMPOSITED is set to suppress
+ // flickering during resizing with hardware acceleration.
+ bool isPIPWindow = aInitData && aInitData->mPIPWindow;
+ if (isPIPWindow && !IsWin8OrLater() &&
+ gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING) &&
+ WidgetTypeSupportsAcceleration()) {
+ extendedStyle |= WS_EX_COMPOSITED;
+ }
+
+ if (mWindowType == eWindowType_popup) {
+ if (!aParent) {
+ parent = nullptr;
+ }
+
+ if (!IsWin8OrLater() && HasBogusPopupsDropShadowOnMultiMonitor() &&
+ ShouldUseOffMainThreadCompositing()) {
+ extendedStyle |= WS_EX_COMPOSITED;
+ }
+
+ if (aInitData->mMouseTransparent) {
+ // This flag makes the window transparent to mouse events
+ mMouseTransparent = true;
+ extendedStyle |= WS_EX_TRANSPARENT;
+ }
+ } else if (mWindowType == eWindowType_invisible) {
+ // Make sure CreateWindowEx succeeds at creating a toplevel window
+ style &= ~0x40000000; // WS_CHILDWINDOW
+ } else {
+ // See if the caller wants to explictly set clip children and clip siblings
+ if (aInitData->clipChildren) {
+ style |= WS_CLIPCHILDREN;
+ } else {
+ style &= ~WS_CLIPCHILDREN;
+ }
+ if (aInitData->clipSiblings) {
+ style |= WS_CLIPSIBLINGS;
+ }
+ }
+
+ const wchar_t* className;
+ if (aInitData->mDropShadow) {
+ className = GetWindowPopupClass();
+ } else {
+ className = GetWindowClass();
+ }
+ // Plugins are created in the disabled state so that they can't
+ // steal focus away from our main window. This is especially
+ // important if the plugin has loaded in a background tab.
+ if (aInitData->mWindowType == eWindowType_plugin ||
+ aInitData->mWindowType == eWindowType_plugin_ipc_chrome ||
+ aInitData->mWindowType == eWindowType_plugin_ipc_content) {
+ style |= WS_DISABLED;
+ }
+
+ if (aInitData->mWindowType == eWindowType_toplevel && !aParent) {
+ mWnd = ConsumePreXULSkeletonUIHandle();
+ if (mWnd) {
+ MOZ_ASSERT(style == kPreXULSkeletonUIWindowStyle,
+ "The skeleton UI window style should match the expected "
+ "style for the first window created");
+ MOZ_ASSERT(extendedStyle == kPreXULSkeletonUIWindowStyleEx,
+ "The skeleton UI window extended style should match the "
+ "expected extended style for the first window created");
+ mIsShowingPreXULSkeletonUI = true;
+
+ // If we successfully consumed the pre-XUL skeleton UI, just update
+ // our internal state to match what is currently being displayed.
+ mIsVisible = true;
+ mSizeMode = WasPreXULSkeletonUIMaximized() ? nsSizeMode_Maximized
+ : nsSizeMode_Normal;
+
+ // These match the margins set in browser-tabsintitlebar.js with
+ // default prefs on Windows. Bug 1673092 tracks lining this up with
+ // that more correctly instead of hard-coding it.
+ LayoutDeviceIntMargin margins(0, 2, 2, 2);
+ SetNonClientMargins(margins);
+
+ // Reset the WNDPROC for this window and its whole class, as we had
+ // to use our own WNDPROC when creating the the skeleton UI window.
+ ::SetWindowLongPtrW(mWnd, GWLP_WNDPROC,
+ reinterpret_cast<LONG_PTR>(
+ WinUtils::NonClientDpiScalingDefWindowProcW));
+ ::SetClassLongPtrW(mWnd, GCLP_WNDPROC,
+ reinterpret_cast<LONG_PTR>(
+ WinUtils::NonClientDpiScalingDefWindowProcW));
+ }
+ }
+
+ if (!mWnd) {
+ mWnd =
+ ::CreateWindowExW(extendedStyle, className, L"", style, aRect.X(),
+ aRect.Y(), aRect.Width(), GetHeight(aRect.Height()),
+ parent, nullptr, nsToolkit::mDllInstance, nullptr);
+ }
+
+ if (!mWnd) {
+ NS_WARNING("nsWindow CreateWindowEx failed.");
+ return NS_ERROR_FAILURE;
+ }
+
+ mDeviceNotifyHandle = InputDeviceUtils::RegisterNotification(mWnd);
+
+ // If mDefaultScale is set before mWnd has been set, it will have the scale of
+ // the primary monitor, rather than the monitor that the window is actually
+ // on. For non-popup windows this gets corrected by the WM_DPICHANGED message
+ // which resets mDefaultScale, but for popup windows we don't reset
+ // mDefaultScale on that message. In order to ensure that popup windows
+ // spawned on a non-primary monitor end up with the correct scale, we reset
+ // mDefaultScale here so that it gets recomputed using the correct monitor now
+ // that we have a mWnd.
+ mDefaultScale = -1.0;
+
+ if (mIsRTL) {
+ DWORD dwAttribute = TRUE;
+ DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
+ sizeof dwAttribute);
+ }
+
+ if (mOpeningAnimationSuppressed) {
+ SuppressAnimation(true);
+ }
+
+ if (mAlwaysOnTop) {
+ ::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
+ }
+
+ if (!IsPlugin() && mWindowType != eWindowType_invisible &&
+ MouseScrollHandler::Device::IsFakeScrollableWindowNeeded()) {
+ // Ugly Thinkpad Driver Hack (Bugs 507222 and 594977)
+ //
+ // We create two zero-sized windows as descendants of the top-level window,
+ // like so:
+ //
+ // Top-level window (MozillaWindowClass)
+ // FAKETRACKPOINTSCROLLCONTAINER (MozillaWindowClass)
+ // FAKETRACKPOINTSCROLLABLE (MozillaWindowClass)
+ //
+ // We need to have the middle window, otherwise the Trackpoint driver
+ // will fail to deliver scroll messages. WM_MOUSEWHEEL messages are
+ // sent to the FAKETRACKPOINTSCROLLABLE, which then propagate up the
+ // window hierarchy until they are handled by nsWindow::WindowProc.
+ // WM_HSCROLL messages are also sent to the FAKETRACKPOINTSCROLLABLE,
+ // but these do not propagate automatically, so we have the window
+ // procedure pretend that they were dispatched to the top-level window
+ // instead.
+ //
+ // The FAKETRACKPOINTSCROLLABLE needs to have the specific window styles it
+ // is given below so that it catches the Trackpoint driver's heuristics.
+ HWND scrollContainerWnd = ::CreateWindowW(
+ className, L"FAKETRACKPOINTSCROLLCONTAINER", WS_CHILD | WS_VISIBLE, 0,
+ 0, 0, 0, mWnd, nullptr, nsToolkit::mDllInstance, nullptr);
+ HWND scrollableWnd = ::CreateWindowW(
+ className, L"FAKETRACKPOINTSCROLLABLE",
+ WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | 0x30, 0, 0, 0, 0,
+ scrollContainerWnd, nullptr, nsToolkit::mDllInstance, nullptr);
+
+ // Give the FAKETRACKPOINTSCROLLABLE window a specific ID so that
+ // WindowProcInternal can distinguish it from the top-level window
+ // easily.
+ ::SetWindowLongPtrW(scrollableWnd, GWLP_ID, eFakeTrackPointScrollableID);
+
+ // Make FAKETRACKPOINTSCROLLABLE use nsWindow::WindowProc, and store the
+ // old window procedure in its "user data".
+ WNDPROC oldWndProc = (WNDPROC)::SetWindowLongPtrW(
+ scrollableWnd, GWLP_WNDPROC, (LONG_PTR)nsWindow::WindowProc);
+ ::SetWindowLongPtrW(scrollableWnd, GWLP_USERDATA, (LONG_PTR)oldWndProc);
+ }
+
+ SubclassWindow(TRUE);
+
+ // Starting with Windows XP, a process always runs within a terminal services
+ // session. In order to play nicely with RDP, fast user switching, and the
+ // lock screen, we should be handling WM_WTSSESSION_CHANGE. We must register
+ // our HWND in order to receive this message.
+ DebugOnly<BOOL> wtsRegistered =
+ ::WTSRegisterSessionNotification(mWnd, NOTIFY_FOR_THIS_SESSION);
+ NS_ASSERTION(wtsRegistered, "WTSRegisterSessionNotification failed!\n");
+
+ mDefaultIMC.Init(this);
+ IMEHandler::InitInputContext(this, mInputContext);
+
+ // Do some initialization work, but only if (a) it hasn't already been done,
+ // and (b) this is the hidden window (which is conveniently created before
+ // any visible windows but after the profile has been initialized).
+ if (!sHaveInitializedPrefs && mWindowType == eWindowType_invisible) {
+ sSwitchKeyboardLayout =
+ Preferences::GetBool("intl.keyboard.per_window_layout", false);
+ sHaveInitializedPrefs = true;
+ }
+
+ // Query for command button metric data for rendering the titlebar. We
+ // only do this once on the first window that has an actual titlebar
+ if (ShouldCacheTitleBarInfo(mWindowType, mBorderStyle)) {
+ nsUXThemeData::UpdateTitlebarInfo(mWnd);
+ }
+
+ static bool a11yPrimed = false;
+ if (!a11yPrimed && mWindowType == eWindowType_toplevel) {
+ a11yPrimed = true;
+ if (Preferences::GetInt("accessibility.force_disabled", 0) == -1) {
+ ::PostMessage(mWnd, MOZ_WM_STARTA11Y, 0, 0);
+ }
+ }
+
+ RecreateDirectManipulationIfNeeded();
+
+ return NS_OK;
+}
+
+// Close this nsWindow
+void nsWindow::Destroy() {
+ // WM_DESTROY has already fired, avoid calling it twice
+ if (mOnDestroyCalled) return;
+
+ // Don't destroy windows that have file pickers open, we'll tear these down
+ // later once the picker is closed.
+ mDestroyCalled = true;
+ if (mPickerDisplayCount) return;
+
+ // During the destruction of all of our children, make sure we don't get
+ // deleted.
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ DestroyDirectManipulation();
+
+ /**
+ * On windows the LayerManagerOGL destructor wants the widget to be around for
+ * cleanup. It also would like to have the HWND intact, so we nullptr it here.
+ */
+ DestroyLayerManager();
+
+ /* We should clear our cached resources now and not wait for the GC to
+ * delete the nsWindow. */
+ ClearCachedResources();
+
+ InputDeviceUtils::UnregisterNotification(mDeviceNotifyHandle);
+ mDeviceNotifyHandle = nullptr;
+
+ // The DestroyWindow function destroys the specified window. The function
+ // sends WM_DESTROY and WM_NCDESTROY messages to the window to deactivate it
+ // and remove the keyboard focus from it. The function also destroys the
+ // window's menu, flushes the thread message queue, destroys timers, removes
+ // clipboard ownership, and breaks the clipboard viewer chain (if the window
+ // is at the top of the viewer chain).
+ //
+ // If the specified window is a parent or owner window, DestroyWindow
+ // automatically destroys the associated child or owned windows when it
+ // destroys the parent or owner window. The function first destroys child or
+ // owned windows, and then it destroys the parent or owner window.
+ VERIFY(::DestroyWindow(mWnd));
+
+ // Our windows can be subclassed which may prevent us receiving WM_DESTROY. If
+ // OnDestroy() didn't get called, call it now.
+ if (false == mOnDestroyCalled) {
+ MSGResult msgResult;
+ mWindowHook.Notify(mWnd, WM_DESTROY, 0, 0, msgResult);
+ OnDestroy();
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: Window class utilities
+ *
+ * Utilities for calculating the proper window class name for
+ * Create window.
+ *
+ **************************************************************/
+
+const wchar_t* nsWindow::RegisterWindowClass(const wchar_t* aClassName,
+ UINT aExtraStyle,
+ LPWSTR aIconID) const {
+ WNDCLASSW wc;
+ if (::GetClassInfoW(nsToolkit::mDllInstance, aClassName, &wc)) {
+ // already registered
+ return aClassName;
+ }
+
+ wc.style = CS_DBLCLKS | aExtraStyle;
+ wc.lpfnWndProc = WinUtils::NonClientDpiScalingDefWindowProcW;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = nsToolkit::mDllInstance;
+ wc.hIcon =
+ aIconID ? ::LoadIconW(::GetModuleHandleW(nullptr), aIconID) : nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = mBrush;
+ wc.lpszMenuName = nullptr;
+ wc.lpszClassName = aClassName;
+
+ if (!::RegisterClassW(&wc)) {
+ // For older versions of Win32 (i.e., not XP), the registration may
+ // fail with aExtraStyle, so we have to re-register without it.
+ wc.style = CS_DBLCLKS;
+ ::RegisterClassW(&wc);
+ }
+ return aClassName;
+}
+
+static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
+
+// Return the proper window class for everything except popups.
+const wchar_t* nsWindow::GetWindowClass() const {
+ switch (mWindowType) {
+ case eWindowType_invisible:
+ return RegisterWindowClass(kClassNameHidden, 0, gStockApplicationIcon);
+ case eWindowType_dialog:
+ return RegisterWindowClass(kClassNameDialog, 0, 0);
+ default:
+ return RegisterWindowClass(GetMainWindowClass(), 0,
+ gStockApplicationIcon);
+ }
+}
+
+// Return the proper popup window class
+const wchar_t* nsWindow::GetWindowPopupClass() const {
+ return RegisterWindowClass(kClassNameDropShadow, CS_XP_DROPSHADOW,
+ gStockApplicationIcon);
+}
+
+/**************************************************************
+ *
+ * SECTION: Window styles utilities
+ *
+ * Return the proper windows styles and extended styles.
+ *
+ **************************************************************/
+
+// Return nsWindow styles
+DWORD nsWindow::WindowStyle() {
+ DWORD style;
+
+ switch (mWindowType) {
+ case eWindowType_plugin:
+ case eWindowType_plugin_ipc_chrome:
+ case eWindowType_plugin_ipc_content:
+ case eWindowType_child:
+ style = WS_OVERLAPPED;
+ break;
+
+ case eWindowType_dialog:
+ style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU | DS_3DLOOK |
+ DS_MODALFRAME | WS_CLIPCHILDREN;
+ if (mBorderStyle != eBorderStyle_default)
+ style |= WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
+ break;
+
+ case eWindowType_popup:
+ style = WS_POPUP;
+ if (!HasGlass()) {
+ style |= WS_OVERLAPPED;
+ }
+ break;
+
+ default:
+ NS_ERROR("unknown border style");
+ // fall through
+
+ case eWindowType_toplevel:
+ case eWindowType_invisible:
+ style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU |
+ WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CLIPCHILDREN;
+ break;
+ }
+
+ if (mBorderStyle != eBorderStyle_default &&
+ mBorderStyle != eBorderStyle_all) {
+ if (mBorderStyle == eBorderStyle_none ||
+ !(mBorderStyle & eBorderStyle_border))
+ style &= ~WS_BORDER;
+
+ if (mBorderStyle == eBorderStyle_none ||
+ !(mBorderStyle & eBorderStyle_title)) {
+ style &= ~WS_DLGFRAME;
+ style |= WS_POPUP;
+ style &= ~WS_CHILD;
+ }
+
+ if (mBorderStyle == eBorderStyle_none ||
+ !(mBorderStyle & eBorderStyle_close))
+ style &= ~0;
+ // XXX The close box can only be removed by changing the window class,
+ // as far as I know --- roc+moz@cs.cmu.edu
+
+ if (mBorderStyle == eBorderStyle_none ||
+ !(mBorderStyle & (eBorderStyle_menu | eBorderStyle_close)))
+ style &= ~WS_SYSMENU;
+ // Looks like getting rid of the system menu also does away with the
+ // close box. So, we only get rid of the system menu if you want neither it
+ // nor the close box. How does the Windows "Dialog" window class get just
+ // closebox and no sysmenu? Who knows.
+
+ if (mBorderStyle == eBorderStyle_none ||
+ !(mBorderStyle & eBorderStyle_resizeh))
+ style &= ~WS_THICKFRAME;
+
+ if (mBorderStyle == eBorderStyle_none ||
+ !(mBorderStyle & eBorderStyle_minimize))
+ style &= ~WS_MINIMIZEBOX;
+
+ if (mBorderStyle == eBorderStyle_none ||
+ !(mBorderStyle & eBorderStyle_maximize))
+ style &= ~WS_MAXIMIZEBOX;
+
+ if (IsPopupWithTitleBar()) {
+ style |= WS_CAPTION;
+ if (mBorderStyle & eBorderStyle_close) {
+ style |= WS_SYSMENU;
+ }
+ }
+ }
+
+ if (mIsChildWindow) {
+ style |= WS_CLIPCHILDREN;
+ if (!(style & WS_POPUP)) {
+ style |= WS_CHILD; // WS_POPUP and WS_CHILD are mutually exclusive.
+ }
+ }
+
+ VERIFY_WINDOW_STYLE(style);
+ return style;
+}
+
+// Return nsWindow extended styles
+DWORD nsWindow::WindowExStyle() {
+ switch (mWindowType) {
+ case eWindowType_plugin:
+ case eWindowType_plugin_ipc_chrome:
+ case eWindowType_plugin_ipc_content:
+ case eWindowType_child:
+ return 0;
+
+ case eWindowType_dialog:
+ return WS_EX_WINDOWEDGE | WS_EX_DLGMODALFRAME;
+
+ case eWindowType_popup: {
+ DWORD extendedStyle = WS_EX_TOOLWINDOW;
+ if (mPopupLevel == ePopupLevelTop) extendedStyle |= WS_EX_TOPMOST;
+ return extendedStyle;
+ }
+ default:
+ NS_ERROR("unknown border style");
+ // fall through
+
+ case eWindowType_toplevel:
+ case eWindowType_invisible:
+ return WS_EX_WINDOWEDGE;
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: Window subclassing utilities
+ *
+ * Set or clear window subclasses on native windows. Used in
+ * Create and Destroy.
+ *
+ **************************************************************/
+
+// Subclass (or remove the subclass from) this component's nsWindow
+void nsWindow::SubclassWindow(BOOL bState) {
+ if (bState) {
+ if (!mWnd || !IsWindow(mWnd)) {
+ NS_ERROR("Invalid window handle");
+ }
+
+ mPrevWndProc = reinterpret_cast<WNDPROC>(SetWindowLongPtrW(
+ mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(nsWindow::WindowProc)));
+ NS_ASSERTION(mPrevWndProc, "Null standard window procedure");
+ // connect the this pointer to the nsWindow handle
+ WinUtils::SetNSWindowBasePtr(mWnd, this);
+ } else {
+ if (IsWindow(mWnd)) {
+ SetWindowLongPtrW(mWnd, GWLP_WNDPROC,
+ reinterpret_cast<LONG_PTR>(mPrevWndProc));
+ }
+ WinUtils::SetNSWindowBasePtr(mWnd, nullptr);
+ mPrevWndProc = nullptr;
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetParent, nsIWidget::GetParent
+ *
+ * Set or clear the parent widgets using window properties, and
+ * handles calculating native parent handles.
+ *
+ **************************************************************/
+
+// Get and set parent widgets
+void nsWindow::SetParent(nsIWidget* aNewParent) {
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+ nsIWidget* parent = GetParent();
+ if (parent) {
+ parent->RemoveChild(this);
+ }
+
+ mParent = aNewParent;
+
+ if (aNewParent) {
+ ReparentNativeWidget(aNewParent);
+ aNewParent->AddChild(this);
+ return;
+ }
+ if (mWnd) {
+ // If we have no parent, SetParent should return the desktop.
+ VERIFY(::SetParent(mWnd, nullptr));
+ RecreateDirectManipulationIfNeeded();
+ }
+}
+
+void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
+ MOZ_ASSERT(aNewParent, "null widget");
+
+ mParent = aNewParent;
+ if (mWindowType == eWindowType_popup) {
+ return;
+ }
+ HWND newParent = (HWND)aNewParent->GetNativeData(NS_NATIVE_WINDOW);
+ NS_ASSERTION(newParent, "Parent widget has a null native window handle");
+ if (newParent && mWnd) {
+ ::SetParent(mWnd, newParent);
+ RecreateDirectManipulationIfNeeded();
+ }
+}
+
+nsIWidget* nsWindow::GetParent(void) {
+ if (mIsTopWidgetWindow) {
+ return nullptr;
+ }
+ if (mInDtor || mOnDestroyCalled) {
+ return nullptr;
+ }
+ return mParent;
+}
+
+static int32_t RoundDown(double aDouble) {
+ return aDouble > 0 ? static_cast<int32_t>(floor(aDouble))
+ : static_cast<int32_t>(ceil(aDouble));
+}
+
+float nsWindow::GetDPI() {
+ float dpi = 96.0f;
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ if (screen) {
+ screen->GetDpi(&dpi);
+ }
+ return dpi;
+}
+
+double nsWindow::GetDefaultScaleInternal() {
+ if (mDefaultScale <= 0.0) {
+ mDefaultScale = WinUtils::LogToPhysFactor(mWnd);
+ }
+ return mDefaultScale;
+}
+
+int32_t nsWindow::LogToPhys(double aValue) {
+ return WinUtils::LogToPhys(
+ ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTOPRIMARY), aValue);
+}
+
+nsWindow* nsWindow::GetParentWindow(bool aIncludeOwner) {
+ return static_cast<nsWindow*>(GetParentWindowBase(aIncludeOwner));
+}
+
+nsWindowBase* nsWindow::GetParentWindowBase(bool aIncludeOwner) {
+ if (mIsTopWidgetWindow) {
+ // Must use a flag instead of mWindowType to tell if the window is the
+ // owned by the topmost widget, because a child window can be embedded
+ // inside a HWND which is not associated with a nsIWidget.
+ return nullptr;
+ }
+
+ // If this widget has already been destroyed, pretend we have no parent.
+ // This corresponds to code in Destroy which removes the destroyed
+ // widget from its parent's child list.
+ if (mInDtor || mOnDestroyCalled) return nullptr;
+
+ // aIncludeOwner set to true implies walking the parent chain to retrieve the
+ // root owner. aIncludeOwner set to false implies the search will stop at the
+ // true parent (default).
+ nsWindow* widget = nullptr;
+ if (mWnd) {
+ HWND parent = nullptr;
+ if (aIncludeOwner)
+ parent = ::GetParent(mWnd);
+ else
+ parent = ::GetAncestor(mWnd, GA_PARENT);
+
+ if (parent) {
+ widget = WinUtils::GetNSWindowPtr(parent);
+ if (widget) {
+ // If the widget is in the process of being destroyed then
+ // do NOT return it
+ if (widget->mInDtor) {
+ widget = nullptr;
+ }
+ }
+ }
+ }
+
+ return static_cast<nsWindowBase*>(widget);
+}
+
+BOOL CALLBACK nsWindow::EnumAllChildWindProc(HWND aWnd, LPARAM aParam) {
+ nsWindow* wnd = WinUtils::GetNSWindowPtr(aWnd);
+ if (wnd) {
+ reinterpret_cast<nsTArray<nsWindow*>*>(aParam)->AppendElement(wnd);
+ }
+ return TRUE;
+}
+
+BOOL CALLBACK nsWindow::EnumAllThreadWindowProc(HWND aWnd, LPARAM aParam) {
+ nsWindow* wnd = WinUtils::GetNSWindowPtr(aWnd);
+ if (wnd) {
+ reinterpret_cast<nsTArray<nsWindow*>*>(aParam)->AppendElement(wnd);
+ }
+ EnumChildWindows(aWnd, EnumAllChildWindProc, aParam);
+ return TRUE;
+}
+
+/* static*/
+nsTArray<nsWindow*> nsWindow::EnumAllWindows() {
+ nsTArray<nsWindow*> windows;
+ EnumThreadWindows(GetCurrentThreadId(), EnumAllThreadWindowProc,
+ reinterpret_cast<LPARAM>(&windows));
+ return windows;
+}
+
+static already_AddRefed<SourceSurface> CreateSourceSurfaceForGfxSurface(
+ gfxASurface* aSurface) {
+ MOZ_ASSERT(aSurface);
+ return Factory::CreateSourceSurfaceForCairoSurface(
+ aSurface->CairoSurface(), aSurface->GetSize(),
+ aSurface->GetSurfaceFormat());
+}
+
+nsWindow::ScrollSnapshot* nsWindow::EnsureSnapshotSurface(
+ ScrollSnapshot& aSnapshotData, const mozilla::gfx::IntSize& aSize) {
+ // If the surface doesn't exist or is the wrong size then create new one.
+ if (!aSnapshotData.surface || aSnapshotData.surface->GetSize() != aSize) {
+ aSnapshotData.surface = new gfxWindowsSurface(aSize, kScrollCaptureFormat);
+ aSnapshotData.surfaceHasSnapshot = false;
+ }
+
+ return &aSnapshotData;
+}
+
+already_AddRefed<SourceSurface> nsWindow::CreateScrollSnapshot() {
+ RECT clip = {0};
+ int rgnType = ::GetWindowRgnBox(mWnd, &clip);
+ if (rgnType == RGN_ERROR) {
+ // We failed to get the clip assume that we need a full fallback.
+ clip.left = 0;
+ clip.top = 0;
+ clip.right = mBounds.Width();
+ clip.bottom = mBounds.Height();
+ return GetFallbackScrollSnapshot(clip);
+ }
+
+ // Check that the window is in a position to snapshot. We don't check for
+ // clipped width as that doesn't currently matter for APZ scrolling.
+ if (clip.top || clip.bottom != mBounds.Height()) {
+ return GetFallbackScrollSnapshot(clip);
+ }
+
+ HDC windowDC = ::GetDC(mWnd);
+ if (!windowDC) {
+ return GetFallbackScrollSnapshot(clip);
+ }
+ auto releaseDC = MakeScopeExit([&] { ::ReleaseDC(mWnd, windowDC); });
+
+ gfx::IntSize snapshotSize(mBounds.Width(), mBounds.Height());
+ ScrollSnapshot* snapshot;
+ if (clip.left || clip.right != mBounds.Width()) {
+ // Can't do a full snapshot, so use the partial snapshot.
+ snapshot = EnsureSnapshotSurface(mPartialSnapshot, snapshotSize);
+ } else {
+ snapshot = EnsureSnapshotSurface(mFullSnapshot, snapshotSize);
+ }
+
+ // Note that we know that the clip is full height.
+ if (!::BitBlt(snapshot->surface->GetDC(), clip.left, 0,
+ clip.right - clip.left, clip.bottom, windowDC, clip.left, 0,
+ SRCCOPY)) {
+ return GetFallbackScrollSnapshot(clip);
+ }
+ ::GdiFlush();
+ snapshot->surface->Flush();
+ snapshot->surfaceHasSnapshot = true;
+ snapshot->clip = clip;
+ mCurrentSnapshot = snapshot;
+
+ return CreateSourceSurfaceForGfxSurface(mCurrentSnapshot->surface);
+}
+
+already_AddRefed<SourceSurface> nsWindow::GetFallbackScrollSnapshot(
+ const RECT& aRequiredClip) {
+ gfx::IntSize snapshotSize(mBounds.Width(), mBounds.Height());
+
+ // If the current snapshot is the correct size and covers the required clip,
+ // just keep that by returning null.
+ // Note: we know the clip is always full height.
+ if (mCurrentSnapshot &&
+ mCurrentSnapshot->surface->GetSize() == snapshotSize &&
+ mCurrentSnapshot->clip.left <= aRequiredClip.left &&
+ mCurrentSnapshot->clip.right >= aRequiredClip.right) {
+ return nullptr;
+ }
+
+ // Otherwise we'll use the full snapshot, making sure it is big enough first.
+ mCurrentSnapshot = EnsureSnapshotSurface(mFullSnapshot, snapshotSize);
+
+ // If there is no snapshot, create a default.
+ if (!mCurrentSnapshot->surfaceHasSnapshot) {
+ gfx::SurfaceFormat format = mCurrentSnapshot->surface->GetSurfaceFormat();
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForCairoSurface(
+ mCurrentSnapshot->surface->CairoSurface(),
+ mCurrentSnapshot->surface->GetSize(), &format);
+
+ DefaultFillScrollCapture(dt);
+ }
+
+ return CreateSourceSurfaceForGfxSurface(mCurrentSnapshot->surface);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Show
+ *
+ * Hide or show this component.
+ *
+ **************************************************************/
+
+void nsWindow::Show(bool bState) {
+ if (bState && mIsShowingPreXULSkeletonUI) {
+ // The first time we decide to actually show the window is when we decide
+ // that we've taken over the window from the skeleton UI, and we should
+ // no longer treat resizes / moves specially.
+ mIsShowingPreXULSkeletonUI = false;
+ // Initialize the UI state - this would normally happen below, but since
+ // we're actually already showing, we won't hit it in the normal way.
+ ::SendMessageW(mWnd, WM_CHANGEUISTATE,
+ MAKEWPARAM(UIS_INITIALIZE, UISF_HIDEFOCUS | UISF_HIDEACCEL),
+ 0);
+ }
+
+ if (mWindowType == eWindowType_popup) {
+ // See bug 603793. When we try to draw D3D9/10 windows with a drop shadow
+ // without the DWM on a secondary monitor, windows fails to composite
+ // our windows correctly. We therefor switch off the drop shadow for
+ // pop-up windows when the DWM is disabled and two monitors are
+ // connected.
+ if (HasBogusPopupsDropShadowOnMultiMonitor() &&
+ WinUtils::GetMonitorCount() > 1 &&
+ !gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ if (sDropShadowEnabled) {
+ ::SetClassLongA(mWnd, GCL_STYLE, 0);
+ sDropShadowEnabled = false;
+ }
+ } else {
+ if (!sDropShadowEnabled) {
+ ::SetClassLongA(mWnd, GCL_STYLE, CS_DROPSHADOW);
+ sDropShadowEnabled = true;
+ }
+ }
+
+ // WS_EX_COMPOSITED conflicts with the WS_EX_LAYERED style and causes
+ // some popup menus to become invisible.
+ LONG_PTR exStyle = ::GetWindowLongPtrW(mWnd, GWL_EXSTYLE);
+ if (exStyle & WS_EX_LAYERED) {
+ ::SetWindowLongPtrW(mWnd, GWL_EXSTYLE, exStyle & ~WS_EX_COMPOSITED);
+ }
+ }
+
+ bool syncInvalidate = false;
+
+ bool wasVisible = mIsVisible;
+ // Set the status now so that anyone asking during ShowWindow or
+ // SetWindowPos would get the correct answer.
+ mIsVisible = bState;
+
+ // We may have cached an out of date visible state. This can happen
+ // when session restore sets the full screen mode.
+ if (mIsVisible)
+ mOldStyle |= WS_VISIBLE;
+ else
+ mOldStyle &= ~WS_VISIBLE;
+
+ if (!mIsVisible && wasVisible) {
+ ClearCachedResources();
+ }
+
+ if (mWnd) {
+ if (bState) {
+ if (!wasVisible && mWindowType == eWindowType_toplevel) {
+ // speed up the initial paint after show for
+ // top level windows:
+ syncInvalidate = true;
+
+ // Set the cursor before showing the window to avoid the default wait
+ // cursor.
+ SetCursor(eCursor_standard, nullptr, 0, 0);
+
+ switch (mSizeMode) {
+ case nsSizeMode_Fullscreen:
+ ::ShowWindow(mWnd, SW_SHOW);
+ break;
+ case nsSizeMode_Maximized:
+ ::ShowWindow(mWnd, SW_SHOWMAXIMIZED);
+ break;
+ case nsSizeMode_Minimized:
+ ::ShowWindow(mWnd, SW_SHOWMINIMIZED);
+ break;
+ default:
+ if (CanTakeFocus() && !mAlwaysOnTop) {
+ ::ShowWindow(mWnd, SW_SHOWNORMAL);
+ } else {
+ ::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
+ // Don't flicker the window if we're restoring session
+ if (!sIsRestoringSession) {
+ Unused << GetAttention(2);
+ }
+ }
+ break;
+ }
+ } else {
+ DWORD flags = SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW;
+ if (wasVisible) flags |= SWP_NOZORDER;
+ if (mAlwaysOnTop) flags |= SWP_NOACTIVATE;
+
+ if (mWindowType == eWindowType_popup) {
+ // ensure popups are the topmost of the TOPMOST
+ // layer. Remember not to set the SWP_NOZORDER
+ // flag as that might allow the taskbar to overlap
+ // the popup.
+ flags |= SWP_NOACTIVATE;
+ HWND owner = ::GetWindow(mWnd, GW_OWNER);
+ ::SetWindowPos(mWnd, owner ? 0 : HWND_TOPMOST, 0, 0, 0, 0, flags);
+ } else {
+ if (mWindowType == eWindowType_dialog && !CanTakeFocus())
+ flags |= SWP_NOACTIVATE;
+
+ ::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags);
+ }
+ }
+
+ if (!wasVisible && (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog)) {
+ // when a toplevel window or dialog is shown, initialize the UI state
+ ::SendMessageW(
+ mWnd, WM_CHANGEUISTATE,
+ MAKEWPARAM(UIS_INITIALIZE, UISF_HIDEFOCUS | UISF_HIDEACCEL), 0);
+ }
+ } else {
+ // Clear contents to avoid ghosting of old content if we display
+ // this window again.
+ if (wasVisible && mTransparencyMode == eTransparencyTransparent) {
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->ClearTransparentWindow();
+ }
+ }
+ if (mWindowType != eWindowType_dialog) {
+ ::ShowWindow(mWnd, SW_HIDE);
+ } else {
+ ::SetWindowPos(mWnd, 0, 0, 0, 0, 0,
+ SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER |
+ SWP_NOACTIVATE);
+ }
+ }
+ }
+
+#ifdef MOZ_XUL
+ if (!wasVisible && bState) {
+ Invalidate();
+ if (syncInvalidate && !mInDtor && !mOnDestroyCalled) {
+ ::UpdateWindow(mWnd);
+ }
+ }
+#endif
+
+ if (mOpeningAnimationSuppressed) {
+ SuppressAnimation(false);
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::IsVisible
+ *
+ * Returns the visibility state.
+ *
+ **************************************************************/
+
+// Return true if the whether the component is visible, false otherwise
+bool nsWindow::IsVisible() const { return mIsVisible; }
+
+/**************************************************************
+ *
+ * SECTION: Window clipping utilities
+ *
+ * Used in Size and Move operations for setting the proper
+ * window clipping regions for window transparency.
+ *
+ **************************************************************/
+
+// XP and Vista visual styles sometimes require window clipping regions to be
+// applied for proper transparency. These routines are called on size and move
+// operations.
+// XXX this is apparently still needed in Windows 7 and later
+void nsWindow::ClearThemeRegion() {
+ if (!HasGlass() &&
+ (mWindowType == eWindowType_popup && !IsPopupWithTitleBar() &&
+ (mPopupType == ePopupTypeTooltip || mPopupType == ePopupTypePanel))) {
+ SetWindowRgn(mWnd, nullptr, false);
+ }
+}
+
+void nsWindow::SetThemeRegion() {
+ // Popup types that have a visual styles region applied (bug 376408). This can
+ // be expanded for other window types as needed. The regions are applied
+ // generically to the base window so default constants are used for part and
+ // state. At some point we might need part and state values from
+ // nsNativeThemeWin's GetThemePartAndState, but currently windows that change
+ // shape based on state haven't come up.
+ if (!HasGlass() &&
+ (mWindowType == eWindowType_popup && !IsPopupWithTitleBar() &&
+ (mPopupType == ePopupTypeTooltip || mPopupType == ePopupTypePanel))) {
+ HRGN hRgn = nullptr;
+ RECT rect = {0, 0, mBounds.Width(), mBounds.Height()};
+
+ HDC dc = ::GetDC(mWnd);
+ GetThemeBackgroundRegion(nsUXThemeData::GetTheme(eUXTooltip), dc,
+ TTP_STANDARD, TS_NORMAL, &rect, &hRgn);
+ if (hRgn) {
+ if (!SetWindowRgn(mWnd, hRgn,
+ false)) // do not delete or alter hRgn if accepted.
+ DeleteObject(hRgn);
+ }
+ ::ReleaseDC(mWnd, dc);
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: Touch and APZ-related functions
+ *
+ **************************************************************/
+
+void nsWindow::RegisterTouchWindow() {
+ mTouchWindow = true;
+ ::RegisterTouchWindow(mWnd, TWF_WANTPALM);
+ ::EnumChildWindows(mWnd, nsWindow::RegisterTouchForDescendants, 0);
+}
+
+BOOL CALLBACK nsWindow::RegisterTouchForDescendants(HWND aWnd, LPARAM aMsg) {
+ nsWindow* win = WinUtils::GetNSWindowPtr(aWnd);
+ if (win) {
+ ::RegisterTouchWindow(aWnd, TWF_WANTPALM);
+ }
+ return TRUE;
+}
+
+void nsWindow::LockAspectRatio(bool aShouldLock) {
+ if (aShouldLock) {
+ mAspectRatio = (float)mBounds.Height() / (float)mBounds.Width();
+ } else {
+ mAspectRatio = 0.0;
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetWindowMouseTransparent
+ *
+ * Sets whether the window should ignore mouse events.
+ *
+ **************************************************************/
+void nsWindow::SetWindowMouseTransparent(bool aIsTransparent) {
+ if (!mWnd) {
+ return;
+ }
+
+ LONG_PTR oldStyle = ::GetWindowLongPtrW(mWnd, GWL_EXSTYLE);
+ LONG_PTR newStyle = aIsTransparent ? (oldStyle | WS_EX_TRANSPARENT)
+ : (oldStyle & ~WS_EX_TRANSPARENT);
+ ::SetWindowLongPtrW(mWnd, GWL_EXSTYLE, newStyle);
+ mMouseTransparent = aIsTransparent;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Move, nsIWidget::Resize,
+ * nsIWidget::Size, nsIWidget::BeginResizeDrag
+ *
+ * Repositioning and sizing a window.
+ *
+ **************************************************************/
+
+void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
+ SizeConstraints c = aConstraints;
+
+ if (mWindowType != eWindowType_popup && mResizable) {
+ c.mMinSize.width =
+ std::max(int32_t(::GetSystemMetrics(SM_CXMINTRACK)), c.mMinSize.width);
+ c.mMinSize.height =
+ std::max(int32_t(::GetSystemMetrics(SM_CYMINTRACK)), c.mMinSize.height);
+ }
+
+ if (mMaxTextureSize > 0) {
+ // We can't make ThebesLayers bigger than this anyway.. no point it letting
+ // a window grow bigger as we won't be able to draw content there in
+ // general.
+ c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize);
+ c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize);
+ }
+
+ mSizeConstraintsScale = GetDefaultScale().scale;
+
+ nsBaseWidget::SetSizeConstraints(c);
+}
+
+const SizeConstraints nsWindow::GetSizeConstraints() {
+ double scale = GetDefaultScale().scale;
+ if (mSizeConstraintsScale == scale || mSizeConstraintsScale == 0.0) {
+ return mSizeConstraints;
+ }
+ scale /= mSizeConstraintsScale;
+ SizeConstraints c = mSizeConstraints;
+ if (c.mMinSize.width != NS_MAXSIZE) {
+ c.mMinSize.width = NSToIntRound(c.mMinSize.width * scale);
+ }
+ if (c.mMinSize.height != NS_MAXSIZE) {
+ c.mMinSize.height = NSToIntRound(c.mMinSize.height * scale);
+ }
+ if (c.mMaxSize.width != NS_MAXSIZE) {
+ c.mMaxSize.width = NSToIntRound(c.mMaxSize.width * scale);
+ }
+ if (c.mMaxSize.height != NS_MAXSIZE) {
+ c.mMaxSize.height = NSToIntRound(c.mMaxSize.height * scale);
+ }
+ return c;
+}
+
+// Move this component
+void nsWindow::Move(double aX, double aY) {
+ if (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog) {
+ SetSizeMode(nsSizeMode_Normal);
+ }
+
+ // for top-level windows only, convert coordinates from desktop pixels
+ // (the "parent" coordinate space) to the window's device pixel space
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t x = NSToIntRound(aX * scale);
+ int32_t y = NSToIntRound(aY * scale);
+
+ // Check to see if window needs to be moved first
+ // to avoid a costly call to SetWindowPos. This check
+ // can not be moved to the calling code in nsView, because
+ // some platforms do not position child windows correctly
+
+ // Only perform this check for non-popup windows, since the positioning can
+ // in fact change even when the x/y do not. We always need to perform the
+ // check. See bug #97805 for details.
+ if (mWindowType != eWindowType_popup && mBounds.IsEqualXY(x, y)) {
+ // Nothing to do, since it is already positioned correctly.
+ return;
+ }
+
+ mBounds.MoveTo(x, y);
+
+ if (mWnd) {
+#ifdef DEBUG
+ // complain if a window is moved offscreen (legal, but potentially
+ // worrisome)
+ if (mIsTopWidgetWindow) { // only a problem for top-level windows
+ // Make sure this window is actually on the screen before we move it
+ // XXX: Needs multiple monitor support
+ HDC dc = ::GetDC(mWnd);
+ if (dc) {
+ if (::GetDeviceCaps(dc, TECHNOLOGY) == DT_RASDISPLAY) {
+ RECT workArea;
+ ::SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
+ // no annoying assertions. just mention the issue.
+ if (x < 0 || x >= workArea.right || y < 0 || y >= workArea.bottom) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("window moved to offscreen position\n"));
+ }
+ }
+ ::ReleaseDC(mWnd, dc);
+ }
+ }
+#endif
+
+ // Normally, when the skeleton UI is disabled, we resize+move the window
+ // before showing it in order to ensure that it restores to the correct
+ // position when the user un-maximizes it. However, when we are using the
+ // skeleton UI, this results in the skeleton UI window being moved around
+ // undesirably before being locked back into the maximized position. To
+ // avoid this, we simply set the placement to restore to via
+ // SetWindowPlacement. It's a little bit more of a dance, though, since we
+ // need to convert the workspace coords that SetWindowPlacement uses to the
+ // screen space coordinates we normally use with SetWindowPos.
+ if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
+ WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
+ VERIFY(::GetWindowPlacement(mWnd, &pl));
+
+ HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
+ if (NS_WARN_IF(!monitor)) {
+ return;
+ }
+ MONITORINFO mi = {sizeof(MONITORINFO)};
+ VERIFY(::GetMonitorInfo(monitor, &mi));
+
+ int32_t deltaX =
+ x + mi.rcWork.left - mi.rcMonitor.left - pl.rcNormalPosition.left;
+ int32_t deltaY =
+ y + mi.rcWork.top - mi.rcMonitor.top - pl.rcNormalPosition.top;
+ pl.rcNormalPosition.left += deltaX;
+ pl.rcNormalPosition.right += deltaX;
+ pl.rcNormalPosition.top += deltaY;
+ pl.rcNormalPosition.bottom += deltaY;
+ VERIFY(::SetWindowPlacement(mWnd, &pl));
+ } else {
+ ClearThemeRegion();
+
+ UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE;
+ // Workaround SetWindowPos bug with D3D9. If our window has a clip
+ // region, some drivers or OSes may incorrectly copy into the clipped-out
+ // area.
+ if (IsPlugin() && !mLayerManager && mClipRects &&
+ (mClipRectCount != 1 ||
+ !mClipRects[0].IsEqualInterior(
+ LayoutDeviceIntRect(0, 0, mBounds.Width(), mBounds.Height())))) {
+ flags |= SWP_NOCOPYBITS;
+ }
+ double oldScale = mDefaultScale;
+ mResizeState = IN_SIZEMOVE;
+ VERIFY(::SetWindowPos(mWnd, nullptr, x, y, 0, 0, flags));
+ mResizeState = NOT_RESIZING;
+ if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
+ ChangedDPI();
+ }
+ }
+
+ SetThemeRegion();
+
+ ResizeDirectManipulationViewport();
+ }
+ NotifyRollupGeometryChange();
+}
+
+// Resize this component
+void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
+ // for top-level windows only, convert coordinates from desktop pixels
+ // (the "parent" coordinate space) to the window's device pixel space
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t width = NSToIntRound(aWidth * scale);
+ int32_t height = NSToIntRound(aHeight * scale);
+
+ NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize");
+ NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize");
+
+ ConstrainSize(&width, &height);
+
+ // Avoid unnecessary resizing calls
+ if (mBounds.IsEqualSize(width, height)) {
+ if (aRepaint) {
+ Invalidate();
+ }
+ return;
+ }
+
+ // Set cached value for lightweight and printing
+ bool wasLocking = mAspectRatio != 0.0;
+ mBounds.SizeTo(width, height);
+ if (wasLocking) {
+ LockAspectRatio(true); // This causes us to refresh the mAspectRatio value
+ }
+
+ if (mWnd) {
+ // Refer to the comment above a similar check in nsWindow::Move
+ if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
+ WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
+ VERIFY(::GetWindowPlacement(mWnd, &pl));
+ pl.rcNormalPosition.right = pl.rcNormalPosition.left + width;
+ pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + GetHeight(height);
+ mResizeState = RESIZING;
+ VERIFY(::SetWindowPlacement(mWnd, &pl));
+ mResizeState = NOT_RESIZING;
+ } else {
+ UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE;
+
+ if (!aRepaint) {
+ flags |= SWP_NOREDRAW;
+ }
+
+ ClearThemeRegion();
+ double oldScale = mDefaultScale;
+ mResizeState = RESIZING;
+ VERIFY(
+ ::SetWindowPos(mWnd, nullptr, 0, 0, width, GetHeight(height), flags));
+
+ mResizeState = NOT_RESIZING;
+ if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
+ ChangedDPI();
+ }
+ SetThemeRegion();
+
+ ResizeDirectManipulationViewport();
+ }
+ }
+
+ if (aRepaint) Invalidate();
+
+ NotifyRollupGeometryChange();
+}
+
+// Resize this component
+void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) {
+ // for top-level windows only, convert coordinates from desktop pixels
+ // (the "parent" coordinate space) to the window's device pixel space
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t x = NSToIntRound(aX * scale);
+ int32_t y = NSToIntRound(aY * scale);
+ int32_t width = NSToIntRound(aWidth * scale);
+ int32_t height = NSToIntRound(aHeight * scale);
+
+ NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize");
+ NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize");
+
+ ConstrainSize(&width, &height);
+
+ // Avoid unnecessary resizing calls
+ if (mBounds.IsEqualRect(x, y, width, height)) {
+ if (aRepaint) {
+ Invalidate();
+ }
+ return;
+ }
+
+ // Set cached value for lightweight and printing
+ mBounds.SetRect(x, y, width, height);
+
+ if (mWnd) {
+ // Refer to the comment above a similar check in nsWindow::Move
+ if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
+ WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
+ VERIFY(::GetWindowPlacement(mWnd, &pl));
+
+ HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
+ if (NS_WARN_IF(!monitor)) {
+ return;
+ }
+ MONITORINFO mi = {sizeof(MONITORINFO)};
+ VERIFY(::GetMonitorInfo(monitor, &mi));
+
+ int32_t deltaX =
+ x + mi.rcWork.left - mi.rcMonitor.left - pl.rcNormalPosition.left;
+ int32_t deltaY =
+ y + mi.rcWork.top - mi.rcMonitor.top - pl.rcNormalPosition.top;
+ pl.rcNormalPosition.left += deltaX;
+ pl.rcNormalPosition.right = pl.rcNormalPosition.left + width;
+ pl.rcNormalPosition.top += deltaY;
+ pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + GetHeight(height);
+ VERIFY(::SetWindowPlacement(mWnd, &pl));
+ } else {
+ UINT flags = SWP_NOZORDER | SWP_NOACTIVATE;
+ if (!aRepaint) {
+ flags |= SWP_NOREDRAW;
+ }
+
+ ClearThemeRegion();
+
+ double oldScale = mDefaultScale;
+ mResizeState = RESIZING;
+ VERIFY(
+ ::SetWindowPos(mWnd, nullptr, x, y, width, GetHeight(height), flags));
+ mResizeState = NOT_RESIZING;
+ if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
+ ChangedDPI();
+ }
+
+ if (mTransitionWnd) {
+ // If we have a fullscreen transition window, we need to make
+ // it topmost again, otherwise the taskbar may be raised by
+ // the system unexpectedly when we leave fullscreen state.
+ ::SetWindowPos(mTransitionWnd, HWND_TOPMOST, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
+ }
+ SetThemeRegion();
+
+ ResizeDirectManipulationViewport();
+ }
+ }
+
+ if (aRepaint) Invalidate();
+
+ NotifyRollupGeometryChange();
+}
+
+mozilla::Maybe<bool> nsWindow::IsResizingNativeWidget() {
+ if (mResizeState == RESIZING) {
+ return Some(true);
+ }
+ return Some(false);
+}
+
+nsresult nsWindow::BeginResizeDrag(WidgetGUIEvent* aEvent, int32_t aHorizontal,
+ int32_t aVertical) {
+ NS_ENSURE_ARG_POINTER(aEvent);
+
+ if (aEvent->mClass != eMouseEventClass) {
+ // you can only begin a resize drag with a mouse event
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aEvent->AsMouseEvent()->mButton != MouseButton::ePrimary) {
+ // you can only begin a resize drag with the left mouse button
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // work out what sizemode we're talking about
+ WPARAM syscommand;
+ if (aVertical < 0) {
+ if (aHorizontal < 0) {
+ syscommand = SC_SIZE | WMSZ_TOPLEFT;
+ } else if (aHorizontal == 0) {
+ syscommand = SC_SIZE | WMSZ_TOP;
+ } else {
+ syscommand = SC_SIZE | WMSZ_TOPRIGHT;
+ }
+ } else if (aVertical == 0) {
+ if (aHorizontal < 0) {
+ syscommand = SC_SIZE | WMSZ_LEFT;
+ } else if (aHorizontal == 0) {
+ return NS_ERROR_INVALID_ARG;
+ } else {
+ syscommand = SC_SIZE | WMSZ_RIGHT;
+ }
+ } else {
+ if (aHorizontal < 0) {
+ syscommand = SC_SIZE | WMSZ_BOTTOMLEFT;
+ } else if (aHorizontal == 0) {
+ syscommand = SC_SIZE | WMSZ_BOTTOM;
+ } else {
+ syscommand = SC_SIZE | WMSZ_BOTTOMRIGHT;
+ }
+ }
+
+ // resizing doesn't work if the mouse is already captured
+ CaptureMouse(false);
+
+ // find the top-level window
+ HWND toplevelWnd = WinUtils::GetTopLevelHWND(mWnd, true);
+
+ // tell Windows to start the resize
+ ::PostMessage(toplevelWnd, WM_SYSCOMMAND, syscommand,
+ POINTTOPOINTS(aEvent->mRefPoint));
+
+ return NS_OK;
+}
+
+/**************************************************************
+ *
+ * SECTION: Window Z-order and state.
+ *
+ * nsIWidget::PlaceBehind, nsIWidget::SetSizeMode,
+ * nsIWidget::ConstrainPosition
+ *
+ * Z-order, positioning, restore, minimize, and maximize.
+ *
+ **************************************************************/
+
+// Position the window behind the given window
+void nsWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
+ nsIWidget* aWidget, bool aActivate) {
+ HWND behind = HWND_TOP;
+ if (aPlacement == eZPlacementBottom)
+ behind = HWND_BOTTOM;
+ else if (aPlacement == eZPlacementBelow && aWidget)
+ behind = (HWND)aWidget->GetNativeData(NS_NATIVE_WINDOW);
+ UINT flags = SWP_NOMOVE | SWP_NOREPOSITION | SWP_NOSIZE;
+ if (!aActivate) flags |= SWP_NOACTIVATE;
+
+ if (!CanTakeFocus() && behind == HWND_TOP) {
+ // Can't place the window to top so place it behind the foreground window
+ // (as long as it is not topmost)
+ HWND wndAfter = ::GetForegroundWindow();
+ if (!wndAfter)
+ behind = HWND_BOTTOM;
+ else if (!(GetWindowLongPtrW(wndAfter, GWL_EXSTYLE) & WS_EX_TOPMOST))
+ behind = wndAfter;
+ flags |= SWP_NOACTIVATE;
+ }
+
+ ::SetWindowPos(mWnd, behind, 0, 0, 0, 0, flags);
+}
+
+static UINT GetCurrentShowCmd(HWND aWnd) {
+ WINDOWPLACEMENT pl;
+ pl.length = sizeof(pl);
+ ::GetWindowPlacement(aWnd, &pl);
+ return pl.showCmd;
+}
+
+// Maximize, minimize or restore the window.
+void nsWindow::SetSizeMode(nsSizeMode aMode) {
+ // Let's not try and do anything if we're already in that state.
+ // (This is needed to prevent problems when calling window.minimize(), which
+ // calls us directly, and then the OS triggers another call to us.)
+ if (aMode == mSizeMode) return;
+
+ // If we are still displaying a maximized pre-XUL skeleton UI, ignore the
+ // noise of sizemode changes. Once we have "shown" the window for the first
+ // time (called nsWindow::Show(true), even though the window is already
+ // technically displayed), we will again accept sizemode changes.
+ if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
+ return;
+ }
+
+ // save the requested state
+ mLastSizeMode = mSizeMode;
+ nsBaseWidget::SetSizeMode(aMode);
+ if (mIsVisible) {
+ int mode;
+
+ switch (aMode) {
+ case nsSizeMode_Fullscreen:
+ mode = SW_SHOW;
+ break;
+
+ case nsSizeMode_Maximized:
+ mode = SW_MAXIMIZE;
+ break;
+
+ case nsSizeMode_Minimized:
+ mode = SW_MINIMIZE;
+ break;
+
+ default:
+ mode = SW_RESTORE;
+ }
+
+ // Don't call ::ShowWindow if we're trying to "restore" a window that is
+ // already in a normal state. Prevents a bug where snapping to one side
+ // of the screen and then minimizing would cause Windows to forget our
+ // window's correct restored position/size.
+ if (!(GetCurrentShowCmd(mWnd) == SW_SHOWNORMAL && mode == SW_RESTORE)) {
+ ::ShowWindow(mWnd, mode);
+ }
+ // we activate here to ensure that the right child window is focused
+ if (mode == SW_MAXIMIZE || mode == SW_SHOW)
+ DispatchFocusToTopLevelWindow(true);
+ }
+}
+
+RefPtr<IVirtualDesktopManager> GetVirtualDesktopManager() {
+#ifdef __MINGW32__
+ return nullptr;
+#else
+ if (!IsWin10OrLater()) {
+ return nullptr;
+ }
+
+ RefPtr<IServiceProvider> serviceProvider;
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_ImmersiveShell, NULL, CLSCTX_LOCAL_SERVER,
+ __uuidof(IServiceProvider), getter_AddRefs(serviceProvider));
+ if (FAILED(hr)) {
+ return nullptr;
+ }
+
+ RefPtr<IVirtualDesktopManager> desktopManager;
+ serviceProvider->QueryService(__uuidof(IVirtualDesktopManager),
+ desktopManager.StartAssignment());
+ return desktopManager;
+#endif
+}
+
+void nsWindow::GetWorkspaceID(nsAString& workspaceID) {
+ RefPtr<IVirtualDesktopManager> desktopManager = GetVirtualDesktopManager();
+ if (!desktopManager) {
+ return;
+ }
+
+ GUID desktop;
+ HRESULT hr = desktopManager->GetWindowDesktopId(mWnd, &desktop);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ RPC_WSTR workspaceIDStr = nullptr;
+ if (UuidToStringW(&desktop, &workspaceIDStr) == RPC_S_OK) {
+ workspaceID.Assign((wchar_t*)workspaceIDStr);
+ RpcStringFreeW(&workspaceIDStr);
+ }
+}
+
+void nsWindow::MoveToWorkspace(const nsAString& workspaceID) {
+ RefPtr<IVirtualDesktopManager> desktopManager = GetVirtualDesktopManager();
+ if (!desktopManager) {
+ return;
+ }
+
+ GUID desktop;
+ const nsString& flat = PromiseFlatString(workspaceID);
+ RPC_WSTR workspaceIDStr = reinterpret_cast<RPC_WSTR>((wchar_t*)flat.get());
+ if (UuidFromStringW(workspaceIDStr, &desktop) == RPC_S_OK) {
+ desktopManager->MoveWindowToDesktop(mWnd, desktop);
+ }
+}
+
+void nsWindow::SuppressAnimation(bool aSuppress) {
+ DWORD dwAttribute = aSuppress ? TRUE : FALSE;
+ DwmSetWindowAttribute(mWnd, DWMWA_TRANSITIONS_FORCEDISABLED, &dwAttribute,
+ sizeof dwAttribute);
+}
+
+// Constrain a potential move to fit onscreen
+// Position (aX, aY) is specified in Windows screen (logical) pixels,
+// except when using per-monitor DPI, in which case it's device pixels.
+void nsWindow::ConstrainPosition(bool aAllowSlop, int32_t* aX, int32_t* aY) {
+ if (!mIsTopWidgetWindow) // only a problem for top-level windows
+ return;
+
+ double dpiScale = GetDesktopToDeviceScale().scale;
+
+ // We need to use the window size in the kind of pixels used for window-
+ // manipulation APIs.
+ int32_t logWidth =
+ std::max<int32_t>(NSToIntRound(mBounds.Width() / dpiScale), 1);
+ int32_t logHeight =
+ std::max<int32_t>(NSToIntRound(mBounds.Height() / dpiScale), 1);
+
+ /* get our playing field. use the current screen, or failing that
+ for any reason, use device caps for the default screen. */
+ RECT screenRect;
+
+ nsCOMPtr<nsIScreenManager> screenmgr =
+ do_GetService(sScreenManagerContractID);
+ if (!screenmgr) {
+ return;
+ }
+ nsCOMPtr<nsIScreen> screen;
+ int32_t left, top, width, height;
+
+ screenmgr->ScreenForRect(*aX, *aY, logWidth, logHeight,
+ getter_AddRefs(screen));
+ if (mSizeMode != nsSizeMode_Fullscreen) {
+ // For normalized windows, use the desktop work area.
+ nsresult rv = screen->GetAvailRectDisplayPix(&left, &top, &width, &height);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ } else {
+ // For full screen windows, use the desktop.
+ nsresult rv = screen->GetRectDisplayPix(&left, &top, &width, &height);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+ screenRect.left = left;
+ screenRect.right = left + width;
+ screenRect.top = top;
+ screenRect.bottom = top + height;
+
+ if (aAllowSlop) {
+ if (*aX < screenRect.left - logWidth + kWindowPositionSlop)
+ *aX = screenRect.left - logWidth + kWindowPositionSlop;
+ else if (*aX >= screenRect.right - kWindowPositionSlop)
+ *aX = screenRect.right - kWindowPositionSlop;
+
+ if (*aY < screenRect.top - logHeight + kWindowPositionSlop)
+ *aY = screenRect.top - logHeight + kWindowPositionSlop;
+ else if (*aY >= screenRect.bottom - kWindowPositionSlop)
+ *aY = screenRect.bottom - kWindowPositionSlop;
+
+ } else {
+ if (*aX < screenRect.left)
+ *aX = screenRect.left;
+ else if (*aX >= screenRect.right - logWidth)
+ *aX = screenRect.right - logWidth;
+
+ if (*aY < screenRect.top)
+ *aY = screenRect.top;
+ else if (*aY >= screenRect.bottom - logHeight)
+ *aY = screenRect.bottom - logHeight;
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Enable, nsIWidget::IsEnabled
+ *
+ * Enabling and disabling the widget.
+ *
+ **************************************************************/
+
+// Enable/disable this component
+void nsWindow::Enable(bool bState) {
+ if (mWnd) {
+ ::EnableWindow(mWnd, bState);
+ }
+}
+
+// Return the current enable state
+bool nsWindow::IsEnabled() const {
+ return !mWnd || (::IsWindowEnabled(mWnd) &&
+ ::IsWindowEnabled(::GetAncestor(mWnd, GA_ROOT)));
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetFocus
+ *
+ * Give the focus to this widget.
+ *
+ **************************************************************/
+
+void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
+ if (mWnd) {
+#ifdef WINSTATE_DEBUG_OUTPUT
+ if (mWnd == WinUtils::GetTopLevelHWND(mWnd)) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** SetFocus: [ top] raise=%d\n", aRaise == Raise::Yes));
+ } else {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** SetFocus: [child] raise=%d\n", aRaise == Raise::Yes));
+ }
+#endif
+ // Uniconify, if necessary
+ HWND toplevelWnd = WinUtils::GetTopLevelHWND(mWnd);
+ if (aRaise == Raise::Yes && ::IsIconic(toplevelWnd)) {
+ ::ShowWindow(toplevelWnd, SW_RESTORE);
+ }
+ ::SetFocus(mWnd);
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: Bounds
+ *
+ * GetBounds, GetClientBounds, GetScreenBounds,
+ * GetRestoredBounds, GetClientOffset
+ * SetDrawsInTitlebar, SetNonClientMargins
+ *
+ * Bound calculations.
+ *
+ **************************************************************/
+
+// Return the window's full dimensions in screen coordinates.
+// If the window has a parent, converts the origin to an offset
+// of the parent's screen origin.
+LayoutDeviceIntRect nsWindow::GetBounds() {
+ if (!mWnd) {
+ return mBounds;
+ }
+
+ RECT r;
+ VERIFY(::GetWindowRect(mWnd, &r));
+
+ LayoutDeviceIntRect rect;
+
+ // assign size
+ rect.SizeTo(r.right - r.left, r.bottom - r.top);
+
+ // popup window bounds' are in screen coordinates, not relative to parent
+ // window
+ if (mWindowType == eWindowType_popup) {
+ rect.MoveTo(r.left, r.top);
+ return rect;
+ }
+
+ // chrome on parent:
+ // ___ 5,5 (chrome start)
+ // | ____ 10,10 (client start)
+ // | | ____ 20,20 (child start)
+ // | | |
+ // 20,20 - 5,5 = 15,15 (??)
+ // minus GetClientOffset:
+ // 15,15 - 5,5 = 10,10
+ //
+ // no chrome on parent:
+ // ______ 10,10 (win start)
+ // | ____ 20,20 (child start)
+ // | |
+ // 20,20 - 10,10 = 10,10
+ //
+ // walking the chain:
+ // ___ 5,5 (chrome start)
+ // | ___ 10,10 (client start)
+ // | | ___ 20,20 (child start)
+ // | | | __ 30,30 (child start)
+ // | | | |
+ // 30,30 - 20,20 = 10,10 (offset from second child to first)
+ // 20,20 - 5,5 = 15,15 + 10,10 = 25,25 (??)
+ // minus GetClientOffset:
+ // 25,25 - 5,5 = 20,20 (offset from second child to parent client)
+
+ // convert coordinates if parent exists
+ HWND parent = ::GetParent(mWnd);
+ if (parent) {
+ RECT pr;
+ VERIFY(::GetWindowRect(parent, &pr));
+ r.left -= pr.left;
+ r.top -= pr.top;
+ // adjust for chrome
+ nsWindow* pWidget = static_cast<nsWindow*>(GetParent());
+ if (pWidget && pWidget->IsTopLevelWidget()) {
+ LayoutDeviceIntPoint clientOffset = pWidget->GetClientOffset();
+ r.left -= clientOffset.x;
+ r.top -= clientOffset.y;
+ }
+ }
+ rect.MoveTo(r.left, r.top);
+ return rect;
+}
+
+// Get this component dimension
+LayoutDeviceIntRect nsWindow::GetClientBounds() {
+ if (!mWnd) {
+ return LayoutDeviceIntRect(0, 0, 0, 0);
+ }
+
+ RECT r;
+ VERIFY(::GetClientRect(mWnd, &r));
+
+ LayoutDeviceIntRect bounds = GetBounds();
+ LayoutDeviceIntRect rect;
+ rect.MoveTo(bounds.TopLeft() + GetClientOffset());
+ rect.SizeTo(r.right - r.left, r.bottom - r.top);
+ return rect;
+}
+
+// Like GetBounds, but don't offset by the parent
+LayoutDeviceIntRect nsWindow::GetScreenBounds() {
+ if (!mWnd) {
+ return mBounds;
+ }
+
+ RECT r;
+ VERIFY(::GetWindowRect(mWnd, &r));
+
+ return LayoutDeviceIntRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
+}
+
+nsresult nsWindow::GetRestoredBounds(LayoutDeviceIntRect& aRect) {
+ if (SizeMode() == nsSizeMode_Normal) {
+ aRect = GetScreenBounds();
+ return NS_OK;
+ }
+ if (!mWnd) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
+ VERIFY(::GetWindowPlacement(mWnd, &pl));
+ const RECT& r = pl.rcNormalPosition;
+
+ HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
+ if (!monitor) {
+ return NS_ERROR_FAILURE;
+ }
+ MONITORINFO mi = {sizeof(MONITORINFO)};
+ VERIFY(::GetMonitorInfo(monitor, &mi));
+
+ aRect.SetRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
+ aRect.MoveBy(mi.rcWork.left - mi.rcMonitor.left,
+ mi.rcWork.top - mi.rcMonitor.top);
+ return NS_OK;
+}
+
+// Return the x,y offset of the client area from the origin of the window. If
+// the window is borderless returns (0,0).
+LayoutDeviceIntPoint nsWindow::GetClientOffset() {
+ if (!mWnd) {
+ return LayoutDeviceIntPoint(0, 0);
+ }
+
+ RECT r1;
+ GetWindowRect(mWnd, &r1);
+ LayoutDeviceIntPoint pt = WidgetToScreenOffset();
+ return LayoutDeviceIntPoint(pt.x - r1.left, pt.y - r1.top);
+}
+
+void nsWindow::SetDrawsInTitlebar(bool aState) {
+ nsWindow* window = GetTopLevelWindow(true);
+ if (window && window != this) {
+ return window->SetDrawsInTitlebar(aState);
+ }
+
+ if (aState) {
+ // top, right, bottom, left for nsIntMargin
+ LayoutDeviceIntMargin margins(0, -1, -1, -1);
+ SetNonClientMargins(margins);
+ } else {
+ LayoutDeviceIntMargin margins(-1, -1, -1, -1);
+ SetNonClientMargins(margins);
+ }
+}
+
+void nsWindow::ResetLayout() {
+ // This will trigger a frame changed event, triggering
+ // nc calc size and a sizemode gecko event.
+ SetWindowPos(mWnd, 0, 0, 0, 0, 0,
+ SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE |
+ SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER);
+
+ // If hidden, just send the frame changed event for now.
+ if (!mIsVisible) return;
+
+ // Send a gecko size event to trigger reflow.
+ RECT clientRc = {0};
+ GetClientRect(mWnd, &clientRc);
+ OnResize(WinUtils::ToIntRect(clientRc).Size());
+
+ // Invalidate and update
+ Invalidate();
+}
+
+// Internally track the caption status via a window property. Required
+// due to our internal handling of WM_NCACTIVATE when custom client
+// margins are set.
+static const wchar_t kManageWindowInfoProperty[] = L"ManageWindowInfoProperty";
+typedef BOOL(WINAPI* GetWindowInfoPtr)(HWND hwnd, PWINDOWINFO pwi);
+static WindowsDllInterceptor::FuncHookType<GetWindowInfoPtr>
+ sGetWindowInfoPtrStub;
+
+BOOL WINAPI GetWindowInfoHook(HWND hWnd, PWINDOWINFO pwi) {
+ if (!sGetWindowInfoPtrStub) {
+ NS_ASSERTION(FALSE, "Something is horribly wrong in GetWindowInfoHook!");
+ return FALSE;
+ }
+ int windowStatus =
+ reinterpret_cast<LONG_PTR>(GetPropW(hWnd, kManageWindowInfoProperty));
+ // No property set, return the default data.
+ if (!windowStatus) return sGetWindowInfoPtrStub(hWnd, pwi);
+ // Call GetWindowInfo and update dwWindowStatus with our
+ // internally tracked value.
+ BOOL result = sGetWindowInfoPtrStub(hWnd, pwi);
+ if (result && pwi)
+ pwi->dwWindowStatus = (windowStatus == 1 ? 0 : WS_ACTIVECAPTION);
+ return result;
+}
+
+void nsWindow::UpdateGetWindowInfoCaptionStatus(bool aActiveCaption) {
+ if (!mWnd) return;
+
+ sUser32Intercept.Init("user32.dll");
+ sGetWindowInfoPtrStub.Set(sUser32Intercept, "GetWindowInfo",
+ &GetWindowInfoHook);
+ if (!sGetWindowInfoPtrStub) {
+ return;
+ }
+
+ // Update our internally tracked caption status
+ SetPropW(mWnd, kManageWindowInfoProperty,
+ reinterpret_cast<HANDLE>(static_cast<INT_PTR>(aActiveCaption) + 1));
+}
+
+/**
+ * Called when the window layout changes: full screen mode transitions,
+ * theme changes, and composition changes. Calculates the new non-client
+ * margins and fires off a frame changed event, which triggers an nc calc
+ * size windows event, kicking the changes in.
+ *
+ * The offsets calculated here are based on the value of `mNonClientMargins`
+ * which is specified in the "chromemargins" attribute of the window. For
+ * each margin, the value specified has the following meaning:
+ * -1 - leave the default frame in place
+ * 0 - remove the frame
+ * >0 - frame size equals min(0, (default frame size - margin value))
+ *
+ * This function calculates and populates `mNonClientOffset`.
+ * In our processing of `WM_NCCALCSIZE`, the frame size will be calculated
+ * as (default frame size - offset). For example, if the left frame should
+ * be 1 pixel narrower than the default frame size, `mNonClientOffset.left`
+ * will equal 1.
+ *
+ * For maximized, fullscreen, and minimized windows, the values stored in
+ * `mNonClientMargins` are ignored, and special processing takes place.
+ *
+ * For non-glass windows, we only allow frames to be their default size
+ * or removed entirely.
+ */
+bool nsWindow::UpdateNonClientMargins(int32_t aSizeMode, bool aReflowWindow) {
+ if (!mCustomNonClient) return false;
+
+ if (aSizeMode == -1) {
+ aSizeMode = mSizeMode;
+ }
+
+ bool hasCaption = (mBorderStyle & (eBorderStyle_all | eBorderStyle_title |
+ eBorderStyle_menu | eBorderStyle_default));
+
+ float dpi = GetDPI();
+
+ // mCaptionHeight is the default size of the NC area at
+ // the top of the window. If the window has a caption,
+ // the size is calculated as the sum of:
+ // SM_CYFRAME - The thickness of the sizing border
+ // around a resizable window
+ // SM_CXPADDEDBORDER - The amount of border padding
+ // for captioned windows
+ // SM_CYCAPTION - The height of the caption area
+ //
+ // If the window does not have a caption, mCaptionHeight will be equal to
+ // `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)`
+ mCaptionHeight =
+ WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
+ (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CYCAPTION, dpi) +
+ WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
+ : 0);
+
+ // mHorResizeMargin is the size of the default NC areas on the
+ // left and right sides of our window. It is calculated as
+ // the sum of:
+ // SM_CXFRAME - The thickness of the sizing border
+ // SM_CXPADDEDBORDER - The amount of border padding
+ // for captioned windows
+ //
+ // If the window does not have a caption, mHorResizeMargin will be equal to
+ // `WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi)`
+ mHorResizeMargin =
+ WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi) +
+ (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
+ : 0);
+
+ // mVertResizeMargin is the size of the default NC area at the
+ // bottom of the window. It is calculated as the sum of:
+ // SM_CYFRAME - The thickness of the sizing border
+ // SM_CXPADDEDBORDER - The amount of border padding
+ // for captioned windows.
+ //
+ // If the window does not have a caption, mVertResizeMargin will be equal to
+ // `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)`
+ mVertResizeMargin =
+ WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
+ (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
+ : 0);
+
+ if (aSizeMode == nsSizeMode_Minimized) {
+ // Use default frame size for minimized windows
+ mNonClientOffset.top = 0;
+ mNonClientOffset.left = 0;
+ mNonClientOffset.right = 0;
+ mNonClientOffset.bottom = 0;
+ } else if (aSizeMode == nsSizeMode_Fullscreen) {
+ // Remove the default frame from the top of our fullscreen window. This
+ // makes the whole caption part of our client area, allowing us to draw
+ // in the whole caption area. Additionally remove the default frame from
+ // the left, right, and bottom.
+ mNonClientOffset.top = mCaptionHeight;
+ mNonClientOffset.bottom = mVertResizeMargin;
+ mNonClientOffset.left = mHorResizeMargin;
+ mNonClientOffset.right = mHorResizeMargin;
+ } else if (aSizeMode == nsSizeMode_Maximized) {
+ // Remove the default frame from the top of our maximized window. This
+ // makes the whole caption part of our client area, allowing us to draw
+ // in the whole caption area. Use default frame size on left, right, and
+ // bottom. The reason this works is that, for maximized windows,
+ // Windows positions them so that their frames fall off the screen.
+ // This gives the illusion of windows having no frames when they are
+ // maximized. If we try to mess with the frame sizes by setting these
+ // offsets to positive values, our client area will fall off the screen.
+ mNonClientOffset.top = mCaptionHeight;
+ mNonClientOffset.bottom = 0;
+ mNonClientOffset.left = 0;
+ mNonClientOffset.right = 0;
+
+ APPBARDATA appBarData;
+ appBarData.cbSize = sizeof(appBarData);
+ UINT taskbarState = SHAppBarMessage(ABM_GETSTATE, &appBarData);
+ if (ABS_AUTOHIDE & taskbarState) {
+ UINT edge = -1;
+ appBarData.hWnd = FindWindow(L"Shell_TrayWnd", nullptr);
+ if (appBarData.hWnd) {
+ HMONITOR taskbarMonitor =
+ ::MonitorFromWindow(appBarData.hWnd, MONITOR_DEFAULTTOPRIMARY);
+ HMONITOR windowMonitor =
+ ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONEAREST);
+ if (taskbarMonitor == windowMonitor) {
+ SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData);
+ edge = appBarData.uEdge;
+ }
+ }
+
+ if (ABE_LEFT == edge) {
+ mNonClientOffset.left -= 1;
+ } else if (ABE_RIGHT == edge) {
+ mNonClientOffset.right -= 1;
+ } else if (ABE_BOTTOM == edge || ABE_TOP == edge) {
+ mNonClientOffset.bottom -= 1;
+ }
+ }
+ } else {
+ bool glass = gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled();
+
+ // We're dealing with a "normal" window (not maximized, minimized, or
+ // fullscreen), so process `mNonClientMargins` and set `mNonClientOffset`
+ // accordingly.
+ //
+ // Setting `mNonClientOffset` to 0 has the effect of leaving the default
+ // frame intact. Setting it to a value greater than 0 reduces the frame
+ // size by that amount.
+
+ if (mNonClientMargins.top > 0 && glass) {
+ mNonClientOffset.top = std::min(mCaptionHeight, mNonClientMargins.top);
+ } else if (mNonClientMargins.top == 0) {
+ mNonClientOffset.top = mCaptionHeight;
+ } else {
+ mNonClientOffset.top = 0;
+ }
+
+ if (mNonClientMargins.bottom > 0 && glass) {
+ mNonClientOffset.bottom =
+ std::min(mVertResizeMargin, mNonClientMargins.bottom);
+ } else if (mNonClientMargins.bottom == 0) {
+ mNonClientOffset.bottom = mVertResizeMargin;
+ } else {
+ mNonClientOffset.bottom = 0;
+ }
+
+ if (mNonClientMargins.left > 0 && glass) {
+ mNonClientOffset.left =
+ std::min(mHorResizeMargin, mNonClientMargins.left);
+ } else if (mNonClientMargins.left == 0) {
+ mNonClientOffset.left = mHorResizeMargin;
+ } else {
+ mNonClientOffset.left = 0;
+ }
+
+ if (mNonClientMargins.right > 0 && glass) {
+ mNonClientOffset.right =
+ std::min(mHorResizeMargin, mNonClientMargins.right);
+ } else if (mNonClientMargins.right == 0) {
+ mNonClientOffset.right = mHorResizeMargin;
+ } else {
+ mNonClientOffset.right = 0;
+ }
+ }
+
+ if (aReflowWindow) {
+ // Force a reflow of content based on the new client
+ // dimensions.
+ ResetLayout();
+ }
+
+ return true;
+}
+
+nsresult nsWindow::SetNonClientMargins(LayoutDeviceIntMargin& margins) {
+ if (!mIsTopWidgetWindow || mBorderStyle == eBorderStyle_none)
+ return NS_ERROR_INVALID_ARG;
+
+ if (mHideChrome) {
+ mFutureMarginsOnceChromeShows = margins;
+ mFutureMarginsToUse = true;
+ return NS_OK;
+ }
+ mFutureMarginsToUse = false;
+
+ // Request for a reset
+ if (margins.top == -1 && margins.left == -1 && margins.right == -1 &&
+ margins.bottom == -1) {
+ mCustomNonClient = false;
+ mNonClientMargins = margins;
+ // Force a reflow of content based on the new client
+ // dimensions.
+ ResetLayout();
+
+ int windowStatus =
+ reinterpret_cast<LONG_PTR>(GetPropW(mWnd, kManageWindowInfoProperty));
+ if (windowStatus) {
+ ::SendMessageW(mWnd, WM_NCACTIVATE, 1 != windowStatus, 0);
+ }
+
+ return NS_OK;
+ }
+
+ if (margins.top < -1 || margins.bottom < -1 || margins.left < -1 ||
+ margins.right < -1)
+ return NS_ERROR_INVALID_ARG;
+
+ mNonClientMargins = margins;
+ mCustomNonClient = true;
+ if (!UpdateNonClientMargins()) {
+ NS_WARNING("UpdateNonClientMargins failed!");
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+void nsWindow::InvalidateNonClientRegion() {
+ // +-+-----------------------+-+
+ // | | app non-client chrome | |
+ // | +-----------------------+ |
+ // | | app client chrome | | }
+ // | +-----------------------+ | }
+ // | | app content | | } area we don't want to invalidate
+ // | +-----------------------+ | }
+ // | | app client chrome | | }
+ // | +-----------------------+ |
+ // +---------------------------+ <
+ // ^ ^ windows non-client chrome
+ // client area = app *
+ RECT rect;
+ GetWindowRect(mWnd, &rect);
+ MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
+ HRGN winRgn = CreateRectRgnIndirect(&rect);
+
+ // Subtract app client chrome and app content leaving
+ // windows non-client chrome and app non-client chrome
+ // in winRgn.
+ GetWindowRect(mWnd, &rect);
+ rect.top += mCaptionHeight;
+ rect.right -= mHorResizeMargin;
+ rect.bottom -= mHorResizeMargin;
+ rect.left += mVertResizeMargin;
+ MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
+ HRGN clientRgn = CreateRectRgnIndirect(&rect);
+ CombineRgn(winRgn, winRgn, clientRgn, RGN_DIFF);
+ DeleteObject(clientRgn);
+
+ // triggers ncpaint and paint events for the two areas
+ RedrawWindow(mWnd, nullptr, winRgn, RDW_FRAME | RDW_INVALIDATE);
+ DeleteObject(winRgn);
+}
+
+HRGN nsWindow::ExcludeNonClientFromPaintRegion(HRGN aRegion) {
+ RECT rect;
+ HRGN rgn = nullptr;
+ if (aRegion == (HRGN)1) { // undocumented value indicating a full refresh
+ GetWindowRect(mWnd, &rect);
+ rgn = CreateRectRgnIndirect(&rect);
+ } else {
+ rgn = aRegion;
+ }
+ GetClientRect(mWnd, &rect);
+ MapWindowPoints(mWnd, nullptr, (LPPOINT)&rect, 2);
+ HRGN nonClientRgn = CreateRectRgnIndirect(&rect);
+ CombineRgn(rgn, rgn, nonClientRgn, RGN_DIFF);
+ DeleteObject(nonClientRgn);
+ return rgn;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetBackgroundColor
+ *
+ * Sets the window background paint color.
+ *
+ **************************************************************/
+
+void nsWindow::SetBackgroundColor(const nscolor& aColor) {
+ if (mBrush) ::DeleteObject(mBrush);
+
+ mBrush = ::CreateSolidBrush(NSRGB_2_COLOREF(aColor));
+ if (mWnd != nullptr) {
+ ::SetClassLongPtrW(mWnd, GCLP_HBRBACKGROUND, (LONG_PTR)mBrush);
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetCursor
+ *
+ * SetCursor and related utilities for manging cursor state.
+ *
+ **************************************************************/
+
+// Set this component cursor
+static HCURSOR CursorFor(nsCursor aCursor) {
+ switch (aCursor) {
+ case eCursor_select:
+ return ::LoadCursor(nullptr, IDC_IBEAM);
+ case eCursor_wait:
+ return ::LoadCursor(nullptr, IDC_WAIT);
+ case eCursor_hyperlink:
+ return ::LoadCursor(nullptr, IDC_HAND);
+ case eCursor_standard:
+ case eCursor_context_menu: // XXX See bug 258960.
+ return ::LoadCursor(nullptr, IDC_ARROW);
+
+ case eCursor_n_resize:
+ case eCursor_s_resize:
+ return ::LoadCursor(nullptr, IDC_SIZENS);
+
+ case eCursor_w_resize:
+ case eCursor_e_resize:
+ return ::LoadCursor(nullptr, IDC_SIZEWE);
+
+ case eCursor_nw_resize:
+ case eCursor_se_resize:
+ return ::LoadCursor(nullptr, IDC_SIZENWSE);
+
+ case eCursor_ne_resize:
+ case eCursor_sw_resize:
+ return ::LoadCursor(nullptr, IDC_SIZENESW);
+
+ case eCursor_crosshair:
+ return ::LoadCursor(nullptr, IDC_CROSS);
+
+ case eCursor_move:
+ return ::LoadCursor(nullptr, IDC_SIZEALL);
+
+ case eCursor_help:
+ return ::LoadCursor(nullptr, IDC_HELP);
+
+ case eCursor_copy: // CSS3
+ return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_COPY));
+
+ case eCursor_alias:
+ return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ALIAS));
+
+ case eCursor_cell:
+ return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_CELL));
+ case eCursor_grab:
+ return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_GRAB));
+
+ case eCursor_grabbing:
+ return ::LoadCursor(nsToolkit::mDllInstance,
+ MAKEINTRESOURCE(IDC_GRABBING));
+
+ case eCursor_spinning:
+ return ::LoadCursor(nullptr, IDC_APPSTARTING);
+
+ case eCursor_zoom_in:
+ return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ZOOMIN));
+
+ case eCursor_zoom_out:
+ return ::LoadCursor(nsToolkit::mDllInstance,
+ MAKEINTRESOURCE(IDC_ZOOMOUT));
+
+ case eCursor_not_allowed:
+ case eCursor_no_drop:
+ return ::LoadCursor(nullptr, IDC_NO);
+
+ case eCursor_col_resize:
+ return ::LoadCursor(nsToolkit::mDllInstance,
+ MAKEINTRESOURCE(IDC_COLRESIZE));
+
+ case eCursor_row_resize:
+ return ::LoadCursor(nsToolkit::mDllInstance,
+ MAKEINTRESOURCE(IDC_ROWRESIZE));
+
+ case eCursor_vertical_text:
+ return ::LoadCursor(nsToolkit::mDllInstance,
+ MAKEINTRESOURCE(IDC_VERTICALTEXT));
+
+ case eCursor_all_scroll:
+ // XXX not 100% appropriate perhaps
+ return ::LoadCursor(nullptr, IDC_SIZEALL);
+
+ case eCursor_nesw_resize:
+ return ::LoadCursor(nullptr, IDC_SIZENESW);
+
+ case eCursor_nwse_resize:
+ return ::LoadCursor(nullptr, IDC_SIZENWSE);
+
+ case eCursor_ns_resize:
+ return ::LoadCursor(nullptr, IDC_SIZENS);
+
+ case eCursor_ew_resize:
+ return ::LoadCursor(nullptr, IDC_SIZEWE);
+
+ case eCursor_none:
+ return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_NONE));
+
+ default:
+ NS_ERROR("Invalid cursor type");
+ return nullptr;
+ }
+}
+
+static HCURSOR CursorForImage(imgIContainer* aImageContainer,
+ CSSIntPoint aHotspot,
+ CSSToLayoutDeviceScale aScale) {
+ if (!aImageContainer) {
+ return nullptr;
+ }
+
+ int32_t width = 0;
+ int32_t height = 0;
+
+ if (NS_FAILED(aImageContainer->GetWidth(&width)) ||
+ NS_FAILED(aImageContainer->GetHeight(&height))) {
+ return nullptr;
+ }
+
+ // Reject cursors greater than 128 pixels in either direction, to prevent
+ // spoofing.
+ // XXX ideally we should rescale. Also, we could modify the API to
+ // allow trusted content to set larger cursors.
+ if (width > 128 || height > 128) {
+ return nullptr;
+ }
+
+ LayoutDeviceIntSize size = RoundedToInt(CSSIntSize(width, height) * aScale);
+ LayoutDeviceIntPoint hotspot = RoundedToInt(aHotspot * aScale);
+ HCURSOR cursor;
+ nsresult rv =
+ nsWindowGfx::CreateIcon(aImageContainer, true, hotspot, size, &cursor);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return cursor;
+}
+
+// Setting the actual cursor
+void nsWindow::SetCursor(nsCursor aDefaultCursor, imgIContainer* aImageCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY) {
+ if (aImageCursor && sCursorImgContainer == aImageCursor && sHCursor) {
+ ::SetCursor(sHCursor);
+ return;
+ }
+
+ HCURSOR cursor = CursorForImage(
+ aImageCursor, CSSIntPoint(aHotspotX, aHotspotY), GetDefaultScale());
+ if (cursor) {
+ mCursor = eCursorInvalid;
+ ::SetCursor(cursor);
+
+ NS_IF_RELEASE(sCursorImgContainer);
+ sCursorImgContainer = aImageCursor;
+ NS_ADDREF(sCursorImgContainer);
+
+ if (sHCursor) {
+ ::DestroyIcon(sHCursor);
+ }
+ sHCursor = cursor;
+ return;
+ }
+
+ cursor = CursorFor(aDefaultCursor);
+ if (!cursor) {
+ return;
+ }
+
+ mCursor = aDefaultCursor;
+ HCURSOR oldCursor = ::SetCursor(cursor);
+
+ if (sHCursor == oldCursor) {
+ NS_IF_RELEASE(sCursorImgContainer);
+ if (sHCursor) {
+ ::DestroyIcon(sHCursor);
+ }
+ sHCursor = nullptr;
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Get/SetTransparencyMode
+ *
+ * Manage the transparency mode of the window containing this
+ * widget. Only works for popup and dialog windows when the
+ * Desktop Window Manager compositor is not enabled.
+ *
+ **************************************************************/
+
+#ifdef MOZ_XUL
+nsTransparencyMode nsWindow::GetTransparencyMode() {
+ return GetTopLevelWindow(true)->GetWindowTranslucencyInner();
+}
+
+void nsWindow::SetTransparencyMode(nsTransparencyMode aMode) {
+ nsWindow* window = GetTopLevelWindow(true);
+ MOZ_ASSERT(window);
+
+ if (!window || window->DestroyCalled()) {
+ return;
+ }
+
+ if (nsWindowType::eWindowType_toplevel == window->mWindowType &&
+ mTransparencyMode != aMode &&
+ !gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ NS_WARNING("Cannot set transparency mode on top-level windows.");
+ return;
+ }
+
+ window->SetWindowTranslucencyInner(aMode);
+}
+
+void nsWindow::UpdateOpaqueRegion(const LayoutDeviceIntRegion& aOpaqueRegion) {
+ if (!HasGlass() || GetParent()) return;
+
+ // If there is no opaque region or hidechrome=true, set margins
+ // to support a full sheet of glass. Comments in MSDN indicate
+ // all values must be set to -1 to get a full sheet of glass.
+ MARGINS margins = {-1, -1, -1, -1};
+ if (!aOpaqueRegion.IsEmpty()) {
+ LayoutDeviceIntRect pluginBounds;
+ for (nsIWidget* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsPlugin()) {
+ // Collect the bounds of all plugins for GetLargestRectangle.
+ LayoutDeviceIntRect childBounds = child->GetBounds();
+ pluginBounds.UnionRect(pluginBounds, childBounds);
+ }
+ }
+
+ LayoutDeviceIntRect clientBounds = GetClientBounds();
+
+ // Find the largest rectangle and use that to calculate the inset. Our top
+ // priority is to include the bounds of all plugins.
+ LayoutDeviceIntRect largest =
+ aOpaqueRegion.GetLargestRectangle(pluginBounds);
+ margins.cxLeftWidth = largest.X();
+ margins.cxRightWidth = clientBounds.Width() - largest.XMost();
+ margins.cyBottomHeight = clientBounds.Height() - largest.YMost();
+ if (mCustomNonClient) {
+ // The minimum glass height must be the caption buttons height,
+ // otherwise the buttons are drawn incorrectly.
+ largest.MoveToY(std::max<uint32_t>(
+ largest.Y(), nsUXThemeData::GetCommandButtonBoxMetrics().cy));
+ }
+ margins.cyTopHeight = largest.Y();
+ }
+
+ // Only update glass area if there are changes
+ if (memcmp(&mGlassMargins, &margins, sizeof mGlassMargins)) {
+ mGlassMargins = margins;
+ UpdateGlass();
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::UpdateWindowDraggingRegion
+ *
+ * For setting the draggable titlebar region from CSS
+ * with -moz-window-dragging: drag.
+ *
+ **************************************************************/
+
+void nsWindow::UpdateWindowDraggingRegion(
+ const LayoutDeviceIntRegion& aRegion) {
+ if (mDraggableRegion != aRegion) {
+ mDraggableRegion = aRegion;
+ }
+}
+
+void nsWindow::UpdateGlass() {
+ MARGINS margins = mGlassMargins;
+
+ // DWMNCRP_USEWINDOWSTYLE - The non-client rendering area is
+ // rendered based on the window style.
+ // DWMNCRP_ENABLED - The non-client area rendering is
+ // enabled; the window style is ignored.
+ DWMNCRENDERINGPOLICY policy = DWMNCRP_USEWINDOWSTYLE;
+ switch (mTransparencyMode) {
+ case eTransparencyBorderlessGlass:
+ // Only adjust if there is some opaque rectangle
+ if (margins.cxLeftWidth >= 0) {
+ margins.cxLeftWidth += kGlassMarginAdjustment;
+ margins.cyTopHeight += kGlassMarginAdjustment;
+ margins.cxRightWidth += kGlassMarginAdjustment;
+ margins.cyBottomHeight += kGlassMarginAdjustment;
+ }
+ // Fall through
+ case eTransparencyGlass:
+ policy = DWMNCRP_ENABLED;
+ break;
+ default:
+ break;
+ }
+
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("glass margins: left:%d top:%d right:%d bottom:%d\n",
+ margins.cxLeftWidth, margins.cyTopHeight, margins.cxRightWidth,
+ margins.cyBottomHeight));
+
+ // Extends the window frame behind the client area
+ if (gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ DwmExtendFrameIntoClientArea(mWnd, &margins);
+ DwmSetWindowAttribute(mWnd, DWMWA_NCRENDERING_POLICY, &policy,
+ sizeof policy);
+ }
+}
+#endif
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::HideWindowChrome
+ *
+ * Show or hide window chrome.
+ *
+ **************************************************************/
+
+void nsWindow::HideWindowChrome(bool aShouldHide) {
+ HWND hwnd = WinUtils::GetTopLevelHWND(mWnd, true);
+ if (!WinUtils::GetNSWindowPtr(hwnd)) {
+ NS_WARNING("Trying to hide window decorations in an embedded context");
+ return;
+ }
+
+ if (mHideChrome == aShouldHide) return;
+
+ DWORD_PTR style, exStyle;
+ mHideChrome = aShouldHide;
+ if (aShouldHide) {
+ DWORD_PTR tempStyle = ::GetWindowLongPtrW(hwnd, GWL_STYLE);
+ DWORD_PTR tempExStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
+
+ style = tempStyle & ~(WS_CAPTION | WS_THICKFRAME);
+ exStyle = tempExStyle & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
+ WS_EX_CLIENTEDGE | WS_EX_STATICEDGE);
+
+ mOldStyle = tempStyle;
+ mOldExStyle = tempExStyle;
+ } else {
+ if (!mOldStyle || !mOldExStyle) {
+ mOldStyle = ::GetWindowLongPtrW(hwnd, GWL_STYLE);
+ mOldExStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
+ }
+
+ style = mOldStyle;
+ exStyle = mOldExStyle;
+ if (mFutureMarginsToUse) {
+ SetNonClientMargins(mFutureMarginsOnceChromeShows);
+ }
+ }
+
+ VERIFY_WINDOW_STYLE(style);
+ ::SetWindowLongPtrW(hwnd, GWL_STYLE, style);
+ ::SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exStyle);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsWindow::Invalidate
+ *
+ * Invalidate an area of the client for painting.
+ *
+ **************************************************************/
+
+// Invalidate this component visible area
+void nsWindow::Invalidate(bool aEraseBackground, bool aUpdateNCArea,
+ bool aIncludeChildren) {
+ if (!mWnd) {
+ return;
+ }
+
+#ifdef WIDGET_DEBUG_OUTPUT
+ debug_DumpInvalidate(stdout, this, nullptr, "noname", (int32_t)mWnd);
+#endif // WIDGET_DEBUG_OUTPUT
+
+ DWORD flags = RDW_INVALIDATE;
+ if (aEraseBackground) {
+ flags |= RDW_ERASE;
+ }
+ if (aUpdateNCArea) {
+ flags |= RDW_FRAME;
+ }
+ if (aIncludeChildren) {
+ flags |= RDW_ALLCHILDREN;
+ }
+
+ VERIFY(::RedrawWindow(mWnd, nullptr, nullptr, flags));
+}
+
+// Invalidate this component visible area
+void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
+ if (mWnd) {
+#ifdef WIDGET_DEBUG_OUTPUT
+ debug_DumpInvalidate(stdout, this, &aRect, "noname", (int32_t)mWnd);
+#endif // WIDGET_DEBUG_OUTPUT
+
+ RECT rect;
+
+ rect.left = aRect.X();
+ rect.top = aRect.Y();
+ rect.right = aRect.XMost();
+ rect.bottom = aRect.YMost();
+
+ VERIFY(::InvalidateRect(mWnd, &rect, FALSE));
+ }
+}
+
+static LRESULT CALLBACK FullscreenTransitionWindowProc(HWND hWnd, UINT uMsg,
+ WPARAM wParam,
+ LPARAM lParam) {
+ switch (uMsg) {
+ case WM_FULLSCREEN_TRANSITION_BEFORE:
+ case WM_FULLSCREEN_TRANSITION_AFTER: {
+ DWORD duration = (DWORD)lParam;
+ DWORD flags = AW_BLEND;
+ if (uMsg == WM_FULLSCREEN_TRANSITION_AFTER) {
+ flags |= AW_HIDE;
+ }
+ ::AnimateWindow(hWnd, duration, flags);
+ // The message sender should have added ref for us.
+ NS_DispatchToMainThread(
+ already_AddRefed<nsIRunnable>((nsIRunnable*)wParam));
+ break;
+ }
+ case WM_DESTROY:
+ ::PostQuitMessage(0);
+ break;
+ default:
+ return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
+ }
+ return 0;
+}
+
+struct FullscreenTransitionInitData {
+ nsIntRect mBounds;
+ HANDLE mSemaphore;
+ HANDLE mThread;
+ HWND mWnd;
+
+ FullscreenTransitionInitData()
+ : mSemaphore(nullptr), mThread(nullptr), mWnd(nullptr) {}
+
+ ~FullscreenTransitionInitData() {
+ if (mSemaphore) {
+ ::CloseHandle(mSemaphore);
+ }
+ if (mThread) {
+ ::CloseHandle(mThread);
+ }
+ }
+};
+
+static DWORD WINAPI FullscreenTransitionThreadProc(LPVOID lpParam) {
+ // Initialize window class
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ WNDCLASSW wc = {};
+ wc.lpfnWndProc = ::FullscreenTransitionWindowProc;
+ wc.hInstance = nsToolkit::mDllInstance;
+ wc.hbrBackground = ::CreateSolidBrush(RGB(0, 0, 0));
+ wc.lpszClassName = kClassNameTransition;
+ ::RegisterClassW(&wc);
+ sInitialized = true;
+ }
+
+ auto data = static_cast<FullscreenTransitionInitData*>(lpParam);
+ HWND wnd = ::CreateWindowW(kClassNameTransition, L"", 0, 0, 0, 0, 0, nullptr,
+ nullptr, nsToolkit::mDllInstance, nullptr);
+ if (!wnd) {
+ ::ReleaseSemaphore(data->mSemaphore, 1, nullptr);
+ return 0;
+ }
+
+ // Since AnimateWindow blocks the thread of the transition window,
+ // we need to hide the cursor for that window, otherwise the system
+ // would show the busy pointer to the user.
+ ::ShowCursor(false);
+ ::SetWindowLongW(wnd, GWL_STYLE, 0);
+ ::SetWindowLongW(
+ wnd, GWL_EXSTYLE,
+ WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE);
+ ::SetWindowPos(wnd, HWND_TOPMOST, data->mBounds.X(), data->mBounds.Y(),
+ data->mBounds.Width(), data->mBounds.Height(), 0);
+ data->mWnd = wnd;
+ ::ReleaseSemaphore(data->mSemaphore, 1, nullptr);
+ // The initialization data may no longer be valid
+ // after we release the semaphore.
+ data = nullptr;
+
+ MSG msg;
+ while (::GetMessageW(&msg, nullptr, 0, 0)) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ }
+ ::ShowCursor(true);
+ ::DestroyWindow(wnd);
+ return 0;
+}
+
+class FullscreenTransitionData final : public nsISupports {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit FullscreenTransitionData(HWND aWnd) : mWnd(aWnd) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "FullscreenTransitionData "
+ "should be constructed in the main thread");
+ }
+
+ const HWND mWnd;
+
+ private:
+ ~FullscreenTransitionData() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "FullscreenTransitionData "
+ "should be deconstructed in the main thread");
+ ::PostMessageW(mWnd, WM_DESTROY, 0, 0);
+ }
+};
+
+NS_IMPL_ISUPPORTS0(FullscreenTransitionData)
+
+/* virtual */
+bool nsWindow::PrepareForFullscreenTransition(nsISupports** aData) {
+ // We don't support fullscreen transition when composition is not
+ // enabled, which could make the transition broken and annoying.
+ // See bug 1184201.
+ if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ return false;
+ }
+
+ FullscreenTransitionInitData initData;
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ int32_t x, y, width, height;
+ screen->GetRectDisplayPix(&x, &y, &width, &height);
+ MOZ_ASSERT(BoundsUseDesktopPixels(),
+ "Should only be called on top-level window");
+ double scale = GetDesktopToDeviceScale().scale; // XXX or GetDefaultScale() ?
+ initData.mBounds.SetRect(NSToIntRound(x * scale), NSToIntRound(y * scale),
+ NSToIntRound(width * scale),
+ NSToIntRound(height * scale));
+
+ // Create a semaphore for synchronizing the window handle which will
+ // be created by the transition thread and used by the main thread for
+ // posting the transition messages.
+ initData.mSemaphore = ::CreateSemaphore(nullptr, 0, 1, nullptr);
+ if (initData.mSemaphore) {
+ initData.mThread = ::CreateThread(
+ nullptr, 0, FullscreenTransitionThreadProc, &initData, 0, nullptr);
+ if (initData.mThread) {
+ ::WaitForSingleObject(initData.mSemaphore, INFINITE);
+ }
+ }
+ if (!initData.mWnd) {
+ return false;
+ }
+
+ mTransitionWnd = initData.mWnd;
+
+ auto data = new FullscreenTransitionData(initData.mWnd);
+ *aData = data;
+ NS_ADDREF(data);
+ return true;
+}
+
+/* virtual */
+void nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) {
+ auto data = static_cast<FullscreenTransitionData*>(aData);
+ nsCOMPtr<nsIRunnable> callback = aCallback;
+ UINT msg = aStage == eBeforeFullscreenToggle ? WM_FULLSCREEN_TRANSITION_BEFORE
+ : WM_FULLSCREEN_TRANSITION_AFTER;
+ WPARAM wparam = (WPARAM)callback.forget().take();
+ ::PostMessage(data->mWnd, msg, wparam, (LPARAM)aDuration);
+}
+
+/* virtual */
+void nsWindow::CleanupFullscreenTransition() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "CleanupFullscreenTransition "
+ "should only run on the main thread");
+
+ mTransitionWnd = nullptr;
+}
+
+nsresult nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen) {
+ // taskbarInfo will be nullptr pre Windows 7 until Bug 680227 is resolved.
+ nsCOMPtr<nsIWinTaskbar> taskbarInfo = do_GetService(NS_TASKBAR_CONTRACTID);
+
+ if (mWidgetListener) {
+ mWidgetListener->FullscreenWillChange(aFullScreen);
+ }
+
+ mFullscreenMode = aFullScreen;
+ if (aFullScreen) {
+ if (mSizeMode == nsSizeMode_Fullscreen) return NS_OK;
+ mOldSizeMode = mSizeMode;
+ SetSizeMode(nsSizeMode_Fullscreen);
+
+ // Notify the taskbar that we will be entering full screen mode.
+ if (taskbarInfo) {
+ taskbarInfo->PrepareFullScreenHWND(mWnd, TRUE);
+ }
+ } else {
+ if (mSizeMode != nsSizeMode_Fullscreen) return NS_OK;
+ SetSizeMode(mOldSizeMode);
+ }
+
+ // If we are going fullscreen, the window size continues to change
+ // and the window will be reflow again then.
+ UpdateNonClientMargins(mSizeMode, /* Reflow */ !aFullScreen);
+
+ // Will call hide chrome, reposition window. Note this will
+ // also cache dimensions for restoration, so it should only
+ // be called once per fullscreen request.
+ nsBaseWidget::InfallibleMakeFullScreen(aFullScreen, aTargetScreen);
+
+ if (mIsVisible && !aFullScreen && mOldSizeMode == nsSizeMode_Normal) {
+ // Ensure the window exiting fullscreen get activated. Window
+ // activation might be bypassed in SetSizeMode.
+ DispatchFocusToTopLevelWindow(true);
+ }
+
+ // Notify the taskbar that we have exited full screen mode.
+ if (!aFullScreen && taskbarInfo) {
+ taskbarInfo->PrepareFullScreenHWND(mWnd, FALSE);
+ }
+
+ OnSizeModeChange(mSizeMode);
+
+ if (mWidgetListener) {
+ mWidgetListener->FullscreenChanged(aFullScreen);
+ }
+
+ return NS_OK;
+}
+
+/**************************************************************
+ *
+ * SECTION: Native data storage
+ *
+ * nsIWidget::GetNativeData
+ * nsIWidget::FreeNativeData
+ *
+ * Set or clear native data based on a constant.
+ *
+ **************************************************************/
+
+// Return some native data according to aDataType
+void* nsWindow::GetNativeData(uint32_t aDataType) {
+ switch (aDataType) {
+ case NS_NATIVE_TMP_WINDOW:
+ return (void*)::CreateWindowExW(
+ mIsRTL ? WS_EX_LAYOUTRTL : 0, GetWindowClass(), L"", WS_CHILD,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, mWnd,
+ nullptr, nsToolkit::mDllInstance, nullptr);
+ case NS_NATIVE_PLUGIN_ID:
+ case NS_NATIVE_PLUGIN_PORT:
+ case NS_NATIVE_WIDGET:
+ case NS_NATIVE_WINDOW:
+ case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID:
+ return (void*)mWnd;
+ case NS_NATIVE_SHAREABLE_WINDOW:
+ return (void*)WinUtils::GetTopLevelHWND(mWnd);
+ case NS_NATIVE_GRAPHIC:
+ MOZ_ASSERT_UNREACHABLE("Not supported on Windows:");
+ return nullptr;
+ case NS_RAW_NATIVE_IME_CONTEXT: {
+ void* pseudoIMEContext = GetPseudoIMEContext();
+ if (pseudoIMEContext) {
+ return pseudoIMEContext;
+ }
+ [[fallthrough]];
+ }
+ case NS_NATIVE_TSF_THREAD_MGR:
+ case NS_NATIVE_TSF_CATEGORY_MGR:
+ case NS_NATIVE_TSF_DISPLAY_ATTR_MGR:
+ return IMEHandler::GetNativeData(this, aDataType);
+
+ default:
+ break;
+ }
+
+ return nullptr;
+}
+
+static void SetChildStyleAndParent(HWND aChildWindow, HWND aParentWindow) {
+ // Make sure the window is styled to be a child window.
+ LONG_PTR style = GetWindowLongPtr(aChildWindow, GWL_STYLE);
+ style |= WS_CHILD;
+ style &= ~WS_POPUP;
+ SetWindowLongPtr(aChildWindow, GWL_STYLE, style);
+
+ // Do the reparenting. Note that this call will probably cause a sync native
+ // message to the process that owns the child window.
+ ::SetParent(aChildWindow, aParentWindow);
+}
+
+void nsWindow::SetNativeData(uint32_t aDataType, uintptr_t aVal) {
+ switch (aDataType) {
+ case NS_NATIVE_CHILD_WINDOW:
+ case NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW: {
+ HWND childHwnd = reinterpret_cast<HWND>(aVal);
+ DWORD childProc = 0;
+ GetWindowThreadProcessId(childHwnd, &childProc);
+ if (!PluginProcessParent::IsPluginProcessId(
+ static_cast<base::ProcessId>(childProc))) {
+ MOZ_ASSERT_UNREACHABLE(
+ "SetNativeData window origin was not a plugin process.");
+ break;
+ }
+ HWND parentHwnd = aDataType == NS_NATIVE_CHILD_WINDOW
+ ? mWnd
+ : WinUtils::GetTopLevelHWND(mWnd);
+ SetChildStyleAndParent(childHwnd, parentHwnd);
+ RecreateDirectManipulationIfNeeded();
+ break;
+ }
+ default:
+ NS_ERROR("SetNativeData called with unsupported data type.");
+ }
+}
+
+// Free some native data according to aDataType
+void nsWindow::FreeNativeData(void* data, uint32_t aDataType) {
+ switch (aDataType) {
+ case NS_NATIVE_GRAPHIC:
+ case NS_NATIVE_WIDGET:
+ case NS_NATIVE_WINDOW:
+ case NS_NATIVE_PLUGIN_PORT:
+ break;
+ default:
+ break;
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetTitle
+ *
+ * Set the main windows title text.
+ *
+ **************************************************************/
+
+nsresult nsWindow::SetTitle(const nsAString& aTitle) {
+ const nsString& strTitle = PromiseFlatString(aTitle);
+ AutoRestore<bool> sendingText(mSendingSetText);
+ mSendingSetText = true;
+ ::SendMessageW(mWnd, WM_SETTEXT, (WPARAM)0, (LPARAM)(LPCWSTR)strTitle.get());
+ return NS_OK;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetIcon
+ *
+ * Set the main windows icon.
+ *
+ **************************************************************/
+
+void nsWindow::SetBigIcon(HICON aIcon) {
+ HICON icon =
+ (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)aIcon);
+ if (icon) {
+ ::DestroyIcon(icon);
+ }
+
+ mIconBig = aIcon;
+}
+
+void nsWindow::SetSmallIcon(HICON aIcon) {
+ HICON icon = (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_SMALL,
+ (LPARAM)aIcon);
+ if (icon) {
+ ::DestroyIcon(icon);
+ }
+
+ mIconSmall = aIcon;
+}
+
+void nsWindow::SetIcon(const nsAString& aIconSpec) {
+ // Assume the given string is a local identifier for an icon file.
+
+ nsCOMPtr<nsIFile> iconFile;
+ ResolveIconName(aIconSpec, u".ico"_ns, getter_AddRefs(iconFile));
+ if (!iconFile) return;
+
+ nsAutoString iconPath;
+ iconFile->GetPath(iconPath);
+
+ // XXX this should use MZLU (see bug 239279)
+
+ ::SetLastError(0);
+
+ HICON bigIcon =
+ (HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON,
+ ::GetSystemMetrics(SM_CXICON),
+ ::GetSystemMetrics(SM_CYICON), LR_LOADFROMFILE);
+ HICON smallIcon =
+ (HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON,
+ ::GetSystemMetrics(SM_CXSMICON),
+ ::GetSystemMetrics(SM_CYSMICON), LR_LOADFROMFILE);
+
+ if (bigIcon) {
+ SetBigIcon(bigIcon);
+ }
+#ifdef DEBUG_SetIcon
+ else {
+ NS_LossyConvertUTF16toASCII cPath(iconPath);
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("\nIcon load error; icon=%s, rc=0x%08X\n\n", cPath.get(),
+ ::GetLastError()));
+ }
+#endif
+ if (smallIcon) {
+ SetSmallIcon(smallIcon);
+ }
+#ifdef DEBUG_SetIcon
+ else {
+ NS_LossyConvertUTF16toASCII cPath(iconPath);
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("\nSmall icon load error; icon=%s, rc=0x%08X\n\n", cPath.get(),
+ ::GetLastError()));
+ }
+#endif
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::WidgetToScreenOffset
+ *
+ * Return this widget's origin in screen coordinates.
+ *
+ **************************************************************/
+
+LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
+ POINT point;
+ point.x = 0;
+ point.y = 0;
+ ::ClientToScreen(mWnd, &point);
+ return LayoutDeviceIntPoint(point.x, point.y);
+}
+
+LayoutDeviceIntSize nsWindow::ClientToWindowSize(
+ const LayoutDeviceIntSize& aClientSize) {
+ if (mWindowType == eWindowType_popup && !IsPopupWithTitleBar())
+ return aClientSize;
+
+ // just use (200, 200) as the position
+ RECT r;
+ r.left = 200;
+ r.top = 200;
+ r.right = 200 + aClientSize.width;
+ r.bottom = 200 + aClientSize.height;
+ ::AdjustWindowRectEx(&r, WindowStyle(), false, WindowExStyle());
+
+ return LayoutDeviceIntSize(r.right - r.left, r.bottom - r.top);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::EnableDragDrop
+ *
+ * Enables/Disables drag and drop of files on this widget.
+ *
+ **************************************************************/
+
+void nsWindow::EnableDragDrop(bool aEnable) {
+ if (!mWnd) {
+ // Return early if the window already closed
+ return;
+ }
+
+ if (aEnable) {
+ if (!mNativeDragTarget) {
+ mNativeDragTarget = new nsNativeDragTarget(this);
+ mNativeDragTarget->AddRef();
+ if (SUCCEEDED(::CoLockObjectExternal((LPUNKNOWN)mNativeDragTarget, TRUE,
+ FALSE))) {
+ ::RegisterDragDrop(mWnd, (LPDROPTARGET)mNativeDragTarget);
+ }
+ }
+ } else {
+ if (mWnd && mNativeDragTarget) {
+ ::RevokeDragDrop(mWnd);
+ ::CoLockObjectExternal((LPUNKNOWN)mNativeDragTarget, FALSE, TRUE);
+ mNativeDragTarget->DragCancel();
+ NS_RELEASE(mNativeDragTarget);
+ }
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::CaptureMouse
+ *
+ * Enables/Disables system mouse capture.
+ *
+ **************************************************************/
+
+void nsWindow::CaptureMouse(bool aCapture) {
+ TRACKMOUSEEVENT mTrack;
+ mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
+ mTrack.dwHoverTime = 0;
+ mTrack.hwndTrack = mWnd;
+ if (aCapture) {
+ mTrack.dwFlags = TME_CANCEL | TME_LEAVE;
+ ::SetCapture(mWnd);
+ } else {
+ mTrack.dwFlags = TME_LEAVE;
+ ::ReleaseCapture();
+ }
+ sIsInMouseCapture = aCapture;
+ TrackMouseEvent(&mTrack);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::CaptureRollupEvents
+ *
+ * Dealing with event rollup on destroy for popups. Enables &
+ * Disables system capture of any and all events that would
+ * cause a dropdown to be rolled up.
+ *
+ **************************************************************/
+
+void nsWindow::CaptureRollupEvents(nsIRollupListener* aListener,
+ bool aDoCapture) {
+ if (aDoCapture) {
+ gRollupListener = aListener;
+ if (!sMsgFilterHook && !sCallProcHook && !sCallMouseHook) {
+ RegisterSpecialDropdownHooks();
+ }
+ sProcessHook = true;
+ } else {
+ gRollupListener = nullptr;
+ sProcessHook = false;
+ UnregisterSpecialDropdownHooks();
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::GetAttention
+ *
+ * Bring this window to the user's attention.
+ *
+ **************************************************************/
+
+// Draw user's attention to this window until it comes to foreground.
+nsresult nsWindow::GetAttention(int32_t aCycleCount) {
+ // Got window?
+ if (!mWnd) return NS_ERROR_NOT_INITIALIZED;
+
+ HWND flashWnd = WinUtils::GetTopLevelHWND(mWnd, false, false);
+ HWND fgWnd = ::GetForegroundWindow();
+ // Don't flash if the flash count is 0 or if the foreground window is our
+ // window handle or that of our owned-most window.
+ if (aCycleCount == 0 || flashWnd == fgWnd ||
+ flashWnd == WinUtils::GetTopLevelHWND(fgWnd, false, false)) {
+ return NS_OK;
+ }
+
+ DWORD defaultCycleCount = 0;
+ ::SystemParametersInfo(SPI_GETFOREGROUNDFLASHCOUNT, 0, &defaultCycleCount, 0);
+
+ FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_ALL,
+ aCycleCount > 0 ? aCycleCount : defaultCycleCount, 0};
+ ::FlashWindowEx(&flashInfo);
+
+ return NS_OK;
+}
+
+void nsWindow::StopFlashing() {
+ HWND flashWnd = mWnd;
+ while (HWND ownerWnd = ::GetWindow(flashWnd, GW_OWNER)) {
+ flashWnd = ownerWnd;
+ }
+
+ FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_STOP, 0, 0};
+ ::FlashWindowEx(&flashInfo);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::HasPendingInputEvent
+ *
+ * Ask whether there user input events pending. All input events are
+ * included, including those not targeted at this nsIwidget instance.
+ *
+ **************************************************************/
+
+bool nsWindow::HasPendingInputEvent() {
+ // If there is pending input or the user is currently
+ // moving the window then return true.
+ // Note: When the user is moving the window WIN32 spins
+ // a separate event loop and input events are not
+ // reported to the application.
+ if (HIWORD(GetQueueStatus(QS_INPUT))) return true;
+ GUITHREADINFO guiInfo;
+ guiInfo.cbSize = sizeof(GUITHREADINFO);
+ if (!GetGUIThreadInfo(GetCurrentThreadId(), &guiInfo)) return false;
+ return GUI_INMOVESIZE == (guiInfo.flags & GUI_INMOVESIZE);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::GetLayerManager
+ *
+ * Get the layer manager associated with this widget.
+ *
+ **************************************************************/
+
+LayerManager* nsWindow::GetLayerManager(PLayerTransactionChild* aShadowManager,
+ LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence) {
+ if (mLayerManager) {
+ return mLayerManager;
+ }
+
+ RECT windowRect;
+ ::GetClientRect(mWnd, &windowRect);
+
+ // Try OMTC first.
+ if (!mLayerManager && ShouldUseOffMainThreadCompositing()) {
+ gfxWindowsPlatform::GetPlatform()->UpdateRenderMode();
+
+ // e10s uses the parameter to pass in the shadow manager from the
+ // BrowserChild so we don't expect to see it there since this doesn't
+ // support e10s.
+ NS_ASSERTION(aShadowManager == nullptr,
+ "Async Compositor not supported with e10s");
+ CreateCompositor();
+ }
+
+ if (!mLayerManager) {
+ MOZ_ASSERT(!mCompositorSession && !mCompositorBridgeChild);
+ MOZ_ASSERT(!mCompositorWidgetDelegate);
+
+ // Ensure we have a widget proxy even if we're not using the compositor,
+ // since all our transparent window handling lives there.
+ WinCompositorWidgetInitData initData(
+ reinterpret_cast<uintptr_t>(mWnd),
+ reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
+ mTransparencyMode, mSizeMode);
+ // If we're not using the compositor, the options don't actually matter.
+ CompositorOptions options(false, false);
+ mBasicLayersSurface =
+ new InProcessWinCompositorWidget(initData, options, this);
+ mCompositorWidgetDelegate = mBasicLayersSurface;
+ mLayerManager = CreateBasicLayerManager();
+ }
+
+ NS_ASSERTION(mLayerManager, "Couldn't provide a valid layer manager.");
+
+ if (mLayerManager) {
+ // Update the size constraints now that the layer manager has been
+ // created.
+ KnowsCompositor* knowsCompositor = mLayerManager->AsKnowsCompositor();
+ if (knowsCompositor) {
+ SizeConstraints c = mSizeConstraints;
+ mMaxTextureSize = knowsCompositor->GetMaxTextureSize();
+ c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize);
+ c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize);
+ nsBaseWidget::SetSizeConstraints(c);
+ }
+ }
+
+ return mLayerManager;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsBaseWidget::SetCompositorWidgetDelegate
+ *
+ * Called to connect the nsWindow to the delegate providing
+ * platform compositing API access.
+ *
+ **************************************************************/
+
+void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
+ if (delegate) {
+ mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
+ MOZ_ASSERT(mCompositorWidgetDelegate,
+ "nsWindow::SetCompositorWidgetDelegate called with a "
+ "non-PlatformCompositorWidgetDelegate");
+ } else {
+ mCompositorWidgetDelegate = nullptr;
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::OnDefaultButtonLoaded
+ *
+ * Called after the dialog is loaded and it has a default button.
+ *
+ **************************************************************/
+
+nsresult nsWindow::OnDefaultButtonLoaded(
+ const LayoutDeviceIntRect& aButtonRect) {
+ if (aButtonRect.IsEmpty()) return NS_OK;
+
+ // Don't snap when we are not active.
+ HWND activeWnd = ::GetActiveWindow();
+ if (activeWnd != ::GetForegroundWindow() ||
+ WinUtils::GetTopLevelHWND(mWnd, true) !=
+ WinUtils::GetTopLevelHWND(activeWnd, true)) {
+ return NS_OK;
+ }
+
+ bool isAlwaysSnapCursor =
+ Preferences::GetBool("ui.cursor_snapping.always_enabled", false);
+
+ if (!isAlwaysSnapCursor) {
+ BOOL snapDefaultButton;
+ if (!::SystemParametersInfo(SPI_GETSNAPTODEFBUTTON, 0, &snapDefaultButton,
+ 0) ||
+ !snapDefaultButton)
+ return NS_OK;
+ }
+
+ LayoutDeviceIntRect widgetRect = GetScreenBounds();
+ LayoutDeviceIntRect buttonRect(aButtonRect + widgetRect.TopLeft());
+
+ LayoutDeviceIntPoint centerOfButton(buttonRect.X() + buttonRect.Width() / 2,
+ buttonRect.Y() + buttonRect.Height() / 2);
+ // The center of the button can be outside of the widget.
+ // E.g., it could be hidden by scrolling.
+ if (!widgetRect.Contains(centerOfButton)) {
+ return NS_OK;
+ }
+
+ if (!::SetCursorPos(centerOfButton.x, centerOfButton.y)) {
+ NS_ERROR("SetCursorPos failed");
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+void nsWindow::UpdateThemeGeometries(
+ const nsTArray<ThemeGeometry>& aThemeGeometries) {
+ RefPtr<LayerManager> layerManager = GetLayerManager();
+ if (!layerManager) {
+ return;
+ }
+
+ nsIntRegion clearRegion;
+ if (!HasGlass() ||
+ !gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ // Make sure and clear old regions we've set previously. Note HasGlass can
+ // be false for glass desktops if the window we are rendering to doesn't
+ // make use of glass (e.g. fullscreen browsing).
+ layerManager->SetRegionToClear(clearRegion);
+ return;
+ }
+
+ // On Win10, force show the top border:
+ if (IsWin10OrLater() && mCustomNonClient && mSizeMode == nsSizeMode_Normal) {
+ RECT rect;
+ ::GetWindowRect(mWnd, &rect);
+ // We want 1 pixel of border for every whole 100% of scaling
+ double borderSize = std::min(1, RoundDown(GetDesktopToDeviceScale().scale));
+ clearRegion.Or(clearRegion, gfx::IntRect::Truncate(
+ 0, 0, rect.right - rect.left, borderSize));
+ }
+
+ mWindowButtonsRect = Nothing();
+
+ if (!IsWin10OrLater()) {
+ for (size_t i = 0; i < aThemeGeometries.Length(); i++) {
+ if (aThemeGeometries[i].mType ==
+ nsNativeThemeWin::eThemeGeometryTypeWindowButtons) {
+ LayoutDeviceIntRect bounds = aThemeGeometries[i].mRect;
+ // Extend the bounds by one pixel to the right, because that's how much
+ // the actual window button shape extends past the client area of the
+ // window (and overlaps the right window frame).
+ bounds.SetWidth(bounds.Width() + 1);
+ if (!mWindowButtonsRect) {
+ mWindowButtonsRect = Some(bounds);
+ }
+ clearRegion.Or(clearRegion, gfx::IntRect::Truncate(
+ bounds.X(), bounds.Y(), bounds.Width(),
+ bounds.Height() - 2.0));
+ clearRegion.Or(clearRegion, gfx::IntRect::Truncate(
+ bounds.X() + 1.0, bounds.YMost() - 2.0,
+ bounds.Width() - 2.0, 1.0));
+ clearRegion.Or(clearRegion, gfx::IntRect::Truncate(
+ bounds.X() + 2.0, bounds.YMost() - 1.0,
+ bounds.Width() - 4.0, 1.0));
+ }
+ }
+ }
+
+ layerManager->SetRegionToClear(clearRegion);
+}
+
+void nsWindow::AddWindowOverlayWebRenderCommands(
+ layers::WebRenderBridgeChild* aWrBridge, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources) {
+ if (mWindowButtonsRect) {
+ wr::LayoutRect rect = wr::ToLayoutRect(*mWindowButtonsRect);
+ auto complexRegion = wr::ToComplexClipRegion(
+ RoundedRect(IntRectToRect(mWindowButtonsRect->ToUnknownRect()),
+ RectCornerRadii(0, 0, 3, 3)));
+ aBuilder.PushClearRectWithComplexRegion(rect, complexRegion);
+ }
+}
+
+uint32_t nsWindow::GetMaxTouchPoints() const {
+ return WinUtils::GetMaxTouchPoints();
+}
+
+void nsWindow::SetWindowClass(const nsAString& xulWinType) {
+ mIsEarlyBlankWindow = xulWinType.EqualsLiteral("navigator:blank");
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Moz Events
+ **
+ ** Moz GUI event management.
+ **
+ **************************************************************
+ **************************************************************/
+
+/**************************************************************
+ *
+ * SECTION: Mozilla event initialization
+ *
+ * Helpers for initializing moz events.
+ *
+ **************************************************************/
+
+// Event initialization
+void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) {
+ if (nullptr == aPoint) { // use the point from the event
+ // get the message position in client coordinates
+ if (mWnd != nullptr) {
+ DWORD pos = ::GetMessagePos();
+ POINT cpos;
+
+ cpos.x = GET_X_LPARAM(pos);
+ cpos.y = GET_Y_LPARAM(pos);
+
+ ::ScreenToClient(mWnd, &cpos);
+ event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y);
+ } else {
+ event.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ }
+ } else {
+ // use the point override if provided
+ event.mRefPoint = *aPoint;
+ }
+
+ event.AssignEventTime(CurrentMessageWidgetEventTime());
+}
+
+WidgetEventTime nsWindow::CurrentMessageWidgetEventTime() const {
+ LONG messageTime = ::GetMessageTime();
+ return WidgetEventTime(messageTime, GetMessageTimeStamp(messageTime));
+}
+
+/**************************************************************
+ *
+ * SECTION: Moz event dispatch helpers
+ *
+ * Helpers for dispatching different types of moz events.
+ *
+ **************************************************************/
+
+// Main event dispatch. Invokes callback and ProcessEvent method on
+// Event Listener object. Part of nsIWidget.
+nsresult nsWindow::DispatchEvent(WidgetGUIEvent* event,
+ nsEventStatus& aStatus) {
+#ifdef WIDGET_DEBUG_OUTPUT
+ debug_DumpEvent(stdout, event->mWidget, event, "something", (int32_t)mWnd);
+#endif // WIDGET_DEBUG_OUTPUT
+
+ aStatus = nsEventStatus_eIgnore;
+
+ // Top level windows can have a view attached which requires events be sent
+ // to the underlying base window and the view. Added when we combined the
+ // base chrome window with the main content child for nc client area (title
+ // bar) rendering.
+ if (mAttachedWidgetListener) {
+ aStatus = mAttachedWidgetListener->HandleEvent(event, mUseAttachedEvents);
+ } else if (mWidgetListener) {
+ aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
+ }
+
+ // the window can be destroyed during processing of seemingly innocuous events
+ // like, say, mousedowns due to the magic of scripting. mousedowns will return
+ // nsEventStatus_eIgnore, which causes problems with the deleted window.
+ // therefore:
+ if (mOnDestroyCalled) aStatus = nsEventStatus_eConsumeNoDefault;
+ return NS_OK;
+}
+
+bool nsWindow::DispatchStandardEvent(EventMessage aMsg) {
+ WidgetGUIEvent event(true, aMsg, this);
+ InitEvent(event);
+
+ bool result = DispatchWindowEvent(&event);
+ return result;
+}
+
+bool nsWindow::DispatchKeyboardEvent(WidgetKeyboardEvent* event) {
+ nsEventStatus status = DispatchInputEvent(event);
+ return ConvertStatus(status);
+}
+
+bool nsWindow::DispatchContentCommandEvent(WidgetContentCommandEvent* aEvent) {
+ nsEventStatus status;
+ DispatchEvent(aEvent, status);
+ return ConvertStatus(status);
+}
+
+bool nsWindow::DispatchWheelEvent(WidgetWheelEvent* aEvent) {
+ nsEventStatus status = DispatchInputEvent(aEvent->AsInputEvent());
+ return ConvertStatus(status);
+}
+
+bool nsWindow::DispatchWindowEvent(WidgetGUIEvent* event) {
+ nsEventStatus status;
+ DispatchEvent(event, status);
+ return ConvertStatus(status);
+}
+
+bool nsWindow::DispatchWindowEvent(WidgetGUIEvent* event,
+ nsEventStatus& aStatus) {
+ DispatchEvent(event, aStatus);
+ return ConvertStatus(aStatus);
+}
+
+// Recursively dispatch synchronous paints for nsIWidget
+// descendants with invalidated rectangles.
+BOOL CALLBACK nsWindow::DispatchStarvedPaints(HWND aWnd, LPARAM aMsg) {
+ LONG_PTR proc = ::GetWindowLongPtrW(aWnd, GWLP_WNDPROC);
+ if (proc == (LONG_PTR)&nsWindow::WindowProc) {
+ // its one of our windows so check to see if it has a
+ // invalidated rect. If it does. Dispatch a synchronous
+ // paint.
+ if (GetUpdateRect(aWnd, nullptr, FALSE)) VERIFY(::UpdateWindow(aWnd));
+ }
+ return TRUE;
+}
+
+// Check for pending paints and dispatch any pending paint
+// messages for any nsIWidget which is a descendant of the
+// top-level window that *this* window is embedded within.
+//
+// Note: We do not dispatch pending paint messages for non
+// nsIWidget managed windows.
+void nsWindow::DispatchPendingEvents() {
+ if (mPainting) {
+ NS_WARNING(
+ "We were asked to dispatch pending events during painting, "
+ "denying since that's unsafe.");
+ return;
+ }
+
+ // We need to ensure that reflow events do not get starved.
+ // At the same time, we don't want to recurse through here
+ // as that would prevent us from dispatching starved paints.
+ static int recursionBlocker = 0;
+ if (recursionBlocker++ == 0) {
+ NS_ProcessPendingEvents(nullptr, PR_MillisecondsToInterval(100));
+ --recursionBlocker;
+ }
+
+ // Quickly check to see if there are any paint events pending,
+ // but only dispatch them if it has been long enough since the
+ // last paint completed.
+ if (::GetQueueStatus(QS_PAINT) &&
+ ((TimeStamp::Now() - mLastPaintEndTime).ToMilliseconds() >= 50)) {
+ // Find the top level window.
+ HWND topWnd = WinUtils::GetTopLevelHWND(mWnd);
+
+ // Dispatch pending paints for topWnd and all its descendant windows.
+ // Note: EnumChildWindows enumerates all descendant windows not just
+ // the children (but not the window itself).
+ nsWindow::DispatchStarvedPaints(topWnd, 0);
+ ::EnumChildWindows(topWnd, nsWindow::DispatchStarvedPaints, 0);
+ }
+}
+
+bool nsWindow::DispatchPluginEvent(UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam,
+ bool aDispatchPendingEvents) {
+ bool ret = nsWindowBase::DispatchPluginEvent(
+ WinUtils::InitMSG(aMessage, aWParam, aLParam, mWnd));
+ if (aDispatchPendingEvents && !Destroyed()) {
+ DispatchPendingEvents();
+ }
+ return ret;
+}
+
+void nsWindow::DispatchCustomEvent(const nsString& eventName) {
+ if (Document* doc = GetDocument()) {
+ if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
+ win->DispatchCustomEvent(eventName, ChromeOnlyDispatch::eYes);
+ }
+ }
+}
+
+bool nsWindow::TouchEventShouldStartDrag(EventMessage aEventMessage,
+ LayoutDeviceIntPoint aEventPoint) {
+ // Allow users to start dragging by double-tapping.
+ if (aEventMessage == eMouseDoubleClick) {
+ return true;
+ }
+
+ // In chrome UI, allow touchdownstartsdrag attributes
+ // to cause any touchdown event to trigger a drag.
+ if (aEventMessage == eMouseDown) {
+ WidgetMouseEvent hittest(true, eMouseHitTest, this,
+ WidgetMouseEvent::eReal);
+ hittest.mRefPoint = aEventPoint;
+ hittest.mIgnoreRootScrollFrame = true;
+ hittest.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
+ DispatchInputEvent(&hittest);
+
+ if (EventTarget* target = hittest.GetDOMEventTarget()) {
+ if (nsCOMPtr<nsIContent> content = do_QueryInterface(target)) {
+ // Check if the element or any parent element has the
+ // attribute we're looking for.
+ for (Element* element = content->GetAsElementOrParentElement(); element;
+ element = element->GetParentElement()) {
+ nsAutoString startDrag;
+ element->GetAttribute(u"touchdownstartsdrag"_ns, startDrag);
+ if (!startDrag.IsEmpty()) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+// Deal with all sort of mouse event
+bool nsWindow::DispatchMouseEvent(EventMessage aEventMessage, WPARAM wParam,
+ LPARAM lParam, bool aIsContextMenuKey,
+ int16_t aButton, uint16_t aInputSource,
+ WinPointerInfo* aPointerInfo) {
+ bool result = false;
+
+ UserActivity();
+
+ if (!mWidgetListener) {
+ return result;
+ }
+
+ LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
+ LayoutDeviceIntPoint mpScreen = eventPoint + WidgetToScreenOffset();
+
+ // Suppress mouse moves caused by widget creation. Make sure to do this early
+ // so that we update sLastMouseMovePoint even for touch-induced mousemove
+ // events.
+ if (aEventMessage == eMouseMove) {
+ if ((sLastMouseMovePoint.x == mpScreen.x) &&
+ (sLastMouseMovePoint.y == mpScreen.y)) {
+ return result;
+ }
+ sLastMouseMovePoint.x = mpScreen.x;
+ sLastMouseMovePoint.y = mpScreen.y;
+ }
+
+ if (WinUtils::GetIsMouseFromTouch(aEventMessage)) {
+ if (aEventMessage == eMouseDown) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_INPUT_TOUCH_EVENT_COUNT,
+ 1);
+ }
+
+ if (mTouchWindow) {
+ // If mTouchWindow is true, then we must have APZ enabled and be
+ // feeding it raw touch events. In that case we only want to
+ // send touch-generated mouse events to content if they should
+ // start a touch-based drag-and-drop gesture, such as on
+ // double-tapping or when tapping elements marked with the
+ // touchdownstartsdrag attribute in chrome UI.
+ MOZ_ASSERT(mAPZC);
+ if (TouchEventShouldStartDrag(aEventMessage, eventPoint)) {
+ aEventMessage = eMouseTouchDrag;
+ } else {
+ return result;
+ }
+ }
+ }
+
+ uint32_t pointerId =
+ aPointerInfo ? aPointerInfo->pointerId : MOUSE_POINTERID();
+
+ // Since it is unclear whether a user will use the digitizer,
+ // Postpone initialization until first PEN message will be found.
+ if (MouseEvent_Binding::MOZ_SOURCE_PEN == aInputSource
+ // Messages should be only at topLevel window.
+ && nsWindowType::eWindowType_toplevel == mWindowType
+ // Currently this scheme is used only when pointer events is enabled.
+ && StaticPrefs::dom_w3c_pointer_events_enabled() &&
+ InkCollector::sInkCollector) {
+ InkCollector::sInkCollector->SetTarget(mWnd);
+ InkCollector::sInkCollector->SetPointerId(pointerId);
+ }
+
+ switch (aEventMessage) {
+ case eMouseDown:
+ CaptureMouse(true);
+ break;
+
+ // eMouseMove and eMouseExitFromWidget are here because we need to make
+ // sure capture flag isn't left on after a drag where we wouldn't see a
+ // button up message (see bug 324131).
+ case eMouseUp:
+ case eMouseMove:
+ case eMouseExitFromWidget:
+ if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) &&
+ sIsInMouseCapture)
+ CaptureMouse(false);
+ break;
+
+ default:
+ break;
+
+ } // switch
+
+ WidgetMouseEvent event(true, aEventMessage, this, WidgetMouseEvent::eReal,
+ aIsContextMenuKey ? WidgetMouseEvent::eContextMenuKey
+ : WidgetMouseEvent::eNormal);
+ if (aEventMessage == eContextMenu && aIsContextMenuKey) {
+ LayoutDeviceIntPoint zero(0, 0);
+ InitEvent(event, &zero);
+ } else {
+ InitEvent(event, &eventPoint);
+ }
+
+ ModifierKeyState modifierKeyState;
+ modifierKeyState.InitInputEvent(event);
+
+ // eContextMenu with Shift state is special. It won't fire "contextmenu"
+ // event in the web content for blocking web content to prevent its default.
+ // However, Shift+F10 is a standard shortcut key on Windows. Therefore,
+ // this should not block web page to prevent its default. I.e., it should
+ // behave same as ContextMenu key without Shift key.
+ // XXX Should we allow to block web page to prevent its default with
+ // Ctrl+Shift+F10 or Alt+Shift+F10 instead?
+ if (aEventMessage == eContextMenu && aIsContextMenuKey && event.IsShift() &&
+ NativeKey::LastKeyOrCharMSG().message == WM_SYSKEYDOWN &&
+ NativeKey::LastKeyOrCharMSG().wParam == VK_F10) {
+ event.mModifiers &= ~MODIFIER_SHIFT;
+ }
+
+ event.mButton = aButton;
+ event.mInputSource = aInputSource;
+ if (aPointerInfo) {
+ // Mouse events from Windows WM_POINTER*. Fill more information in
+ // WidgetMouseEvent.
+ event.AssignPointerHelperData(*aPointerInfo);
+ event.mPressure = aPointerInfo->mPressure;
+ event.mButtons = aPointerInfo->mButtons;
+ } else {
+ // If we get here the mouse events must be from non-touch sources, so
+ // convert it to pointer events as well
+ event.convertToPointer = true;
+ event.pointerId = pointerId;
+ }
+
+ bool insideMovementThreshold =
+ (DeprecatedAbs(sLastMousePoint.x - eventPoint.x) <
+ (short)::GetSystemMetrics(SM_CXDOUBLECLK)) &&
+ (DeprecatedAbs(sLastMousePoint.y - eventPoint.y) <
+ (short)::GetSystemMetrics(SM_CYDOUBLECLK));
+
+ BYTE eventButton;
+ switch (aButton) {
+ case MouseButton::ePrimary:
+ eventButton = VK_LBUTTON;
+ break;
+ case MouseButton::eMiddle:
+ eventButton = VK_MBUTTON;
+ break;
+ case MouseButton::eSecondary:
+ eventButton = VK_RBUTTON;
+ break;
+ default:
+ eventButton = 0;
+ break;
+ }
+
+ // Doubleclicks are used to set the click count, then changed to mousedowns
+ // We're going to time double-clicks from mouse *up* to next mouse *down*
+ LONG curMsgTime = ::GetMessageTime();
+
+ switch (aEventMessage) {
+ case eMouseDoubleClick:
+ event.mMessage = eMouseDown;
+ event.mButton = aButton;
+ sLastClickCount = 2;
+ sLastMouseDownTime = curMsgTime;
+ break;
+ case eMouseUp:
+ // remember when this happened for the next mouse down
+ sLastMousePoint.x = eventPoint.x;
+ sLastMousePoint.y = eventPoint.y;
+ sLastMouseButton = eventButton;
+ break;
+ case eMouseDown:
+ // now look to see if we want to convert this to a double- or triple-click
+ if (((curMsgTime - sLastMouseDownTime) < (LONG)::GetDoubleClickTime()) &&
+ insideMovementThreshold && eventButton == sLastMouseButton) {
+ sLastClickCount++;
+ } else {
+ // reset the click count, to count *this* click
+ sLastClickCount = 1;
+ }
+ // Set last Click time on MouseDown only
+ sLastMouseDownTime = curMsgTime;
+ break;
+ case eMouseMove:
+ if (!insideMovementThreshold) {
+ sLastClickCount = 0;
+ }
+ break;
+ case eMouseExitFromWidget:
+ event.mExitFrom =
+ Some(IsTopLevelMouseExit(mWnd) ? WidgetMouseEvent::ePlatformTopLevel
+ : WidgetMouseEvent::ePlatformChild);
+ break;
+ default:
+ break;
+ }
+ event.mClickCount = sLastClickCount;
+
+#ifdef NS_DEBUG_XX
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("Msg Time: %d Click Count: %d\n", curMsgTime, event.mClickCount));
+#endif
+
+ NPEvent pluginEvent;
+
+ switch (aEventMessage) {
+ case eMouseDown:
+ switch (aButton) {
+ case MouseButton::ePrimary:
+ pluginEvent.event = WM_LBUTTONDOWN;
+ break;
+ case MouseButton::eMiddle:
+ pluginEvent.event = WM_MBUTTONDOWN;
+ break;
+ case MouseButton::eSecondary:
+ pluginEvent.event = WM_RBUTTONDOWN;
+ break;
+ default:
+ break;
+ }
+ break;
+ case eMouseUp:
+ switch (aButton) {
+ case MouseButton::ePrimary:
+ pluginEvent.event = WM_LBUTTONUP;
+ break;
+ case MouseButton::eMiddle:
+ pluginEvent.event = WM_MBUTTONUP;
+ break;
+ case MouseButton::eSecondary:
+ pluginEvent.event = WM_RBUTTONUP;
+ break;
+ default:
+ break;
+ }
+ break;
+ case eMouseDoubleClick:
+ switch (aButton) {
+ case MouseButton::ePrimary:
+ pluginEvent.event = WM_LBUTTONDBLCLK;
+ break;
+ case MouseButton::eMiddle:
+ pluginEvent.event = WM_MBUTTONDBLCLK;
+ break;
+ case MouseButton::eSecondary:
+ pluginEvent.event = WM_RBUTTONDBLCLK;
+ break;
+ default:
+ break;
+ }
+ break;
+ case eMouseMove:
+ pluginEvent.event = WM_MOUSEMOVE;
+ break;
+ case eMouseExitFromWidget:
+ pluginEvent.event = WM_MOUSELEAVE;
+ break;
+ default:
+ pluginEvent.event = WM_NULL;
+ break;
+ }
+
+ pluginEvent.wParam = wParam; // plugins NEED raw OS event flags!
+ pluginEvent.lParam = lParam;
+
+ event.mPluginEvent.Copy(pluginEvent);
+
+ // call the event callback
+ if (mWidgetListener) {
+ if (aEventMessage == eMouseMove) {
+ LayoutDeviceIntRect rect = GetBounds();
+ rect.MoveTo(0, 0);
+
+ if (rect.Contains(event.mRefPoint)) {
+ if (sCurrentWindow == nullptr || sCurrentWindow != this) {
+ if ((nullptr != sCurrentWindow) && (!sCurrentWindow->mInDtor)) {
+ LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
+ sCurrentWindow->DispatchMouseEvent(
+ eMouseExitFromWidget, wParam, pos, false, MouseButton::ePrimary,
+ aInputSource, aPointerInfo);
+ }
+ sCurrentWindow = this;
+ if (!mInDtor) {
+ LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
+ sCurrentWindow->DispatchMouseEvent(
+ eMouseEnterIntoWidget, wParam, pos, false,
+ MouseButton::ePrimary, aInputSource, aPointerInfo);
+ }
+ }
+ }
+ } else if (aEventMessage == eMouseExitFromWidget) {
+ if (sCurrentWindow == this) {
+ sCurrentWindow = nullptr;
+ }
+ }
+
+ result = ConvertStatus(DispatchInputEvent(&event));
+
+ // Release the widget with NS_IF_RELEASE() just in case
+ // the context menu key code in EventListenerManager::HandleEvent()
+ // released it already.
+ return result;
+ }
+
+ return result;
+}
+
+HWND nsWindow::GetTopLevelForFocus(HWND aCurWnd) {
+ // retrieve the toplevel window or dialogue
+ HWND toplevelWnd = nullptr;
+ while (aCurWnd) {
+ toplevelWnd = aCurWnd;
+ nsWindow* win = WinUtils::GetNSWindowPtr(aCurWnd);
+ if (win) {
+ if (win->mWindowType == eWindowType_toplevel ||
+ win->mWindowType == eWindowType_dialog) {
+ break;
+ }
+ }
+
+ aCurWnd = ::GetParent(aCurWnd); // Parent or owner (if has no parent)
+ }
+ return toplevelWnd;
+}
+
+void nsWindow::DispatchFocusToTopLevelWindow(bool aIsActivate) {
+ if (aIsActivate) {
+ sJustGotActivate = false;
+ }
+ sJustGotDeactivate = false;
+ mLastKillFocusWindow = nullptr;
+
+ HWND toplevelWnd = GetTopLevelForFocus(mWnd);
+
+ if (toplevelWnd) {
+ nsWindow* win = WinUtils::GetNSWindowPtr(toplevelWnd);
+ if (win && win->mWidgetListener) {
+ if (aIsActivate) {
+ win->mWidgetListener->WindowActivated();
+ } else {
+ win->mWidgetListener->WindowDeactivated();
+ }
+ }
+ }
+}
+
+HWND nsWindow::WindowAtMouse() {
+ DWORD pos = ::GetMessagePos();
+ POINT mp;
+ mp.x = GET_X_LPARAM(pos);
+ mp.y = GET_Y_LPARAM(pos);
+ return ::WindowFromPoint(mp);
+}
+
+bool nsWindow::IsTopLevelMouseExit(HWND aWnd) {
+ HWND mouseWnd = WindowAtMouse();
+
+ // WinUtils::GetTopLevelHWND() will return a HWND for the window frame
+ // (which includes the non-client area). If the mouse has moved into
+ // the non-client area, we should treat it as a top-level exit.
+ HWND mouseTopLevel = WinUtils::GetTopLevelHWND(mouseWnd);
+ if (mouseWnd == mouseTopLevel) return true;
+
+ return WinUtils::GetTopLevelHWND(aWnd) != mouseTopLevel;
+}
+
+bool nsWindow::ConvertStatus(nsEventStatus aStatus) {
+ return aStatus == nsEventStatus_eConsumeNoDefault;
+}
+
+/**************************************************************
+ *
+ * SECTION: IPC
+ *
+ * IPC related helpers.
+ *
+ **************************************************************/
+
+// static
+bool nsWindow::IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult) {
+ switch (aMsg) {
+ case WM_SETFOCUS:
+ case WM_KILLFOCUS:
+ case WM_ENABLE:
+ case WM_WINDOWPOSCHANGING:
+ case WM_WINDOWPOSCHANGED:
+ case WM_PARENTNOTIFY:
+ case WM_ACTIVATEAPP:
+ case WM_NCACTIVATE:
+ case WM_ACTIVATE:
+ case WM_CHILDACTIVATE:
+ case WM_IME_SETCONTEXT:
+ case WM_IME_NOTIFY:
+ case WM_SHOWWINDOW:
+ case WM_CANCELMODE:
+ case WM_MOUSEACTIVATE:
+ case WM_CONTEXTMENU:
+ aResult = 0;
+ return true;
+
+ case WM_SETTINGCHANGE:
+ case WM_SETCURSOR:
+ return false;
+ }
+
+#ifdef DEBUG
+ char szBuf[200];
+ sprintf(szBuf,
+ "An unhandled ISMEX_SEND message was received during spin loop! (%X)",
+ aMsg);
+ NS_WARNING(szBuf);
+#endif
+
+ return false;
+}
+
+void nsWindow::IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam) {
+ MOZ_ASSERT_IF(
+ msg != WM_GETOBJECT,
+ !mozilla::ipc::MessageChannel::IsPumpingMessages() ||
+ mozilla::ipc::SuppressedNeuteringRegion::IsNeuteringSuppressed());
+
+ // Modal UI being displayed in windowless plugins.
+ if (mozilla::ipc::MessageChannel::IsSpinLoopActive() &&
+ (InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) {
+ LRESULT res;
+ if (IsAsyncResponseEvent(msg, res)) {
+ ReplyMessage(res);
+ }
+ return;
+ }
+
+ // Handle certain sync plugin events sent to the parent which
+ // trigger ipc calls that result in deadlocks.
+
+ DWORD dwResult = 0;
+ bool handled = false;
+
+ switch (msg) {
+ // Windowless flash sending WM_ACTIVATE events to the main window
+ // via calls to ShowWindow.
+ case WM_ACTIVATE:
+ if (lParam != 0 && LOWORD(wParam) == WA_ACTIVE &&
+ IsWindow((HWND)lParam)) {
+ // Check for Adobe Reader X sync activate message from their
+ // helper window and ignore. Fixes an annoying focus problem.
+ if ((InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) ==
+ ISMEX_SEND) {
+ wchar_t szClass[10];
+ HWND focusWnd = (HWND)lParam;
+ if (IsWindowVisible(focusWnd) &&
+ GetClassNameW(focusWnd, szClass,
+ sizeof(szClass) / sizeof(char16_t)) &&
+ !wcscmp(szClass, L"Edit") &&
+ !WinUtils::IsOurProcessWindow(focusWnd)) {
+ break;
+ }
+ }
+ handled = true;
+ }
+ break;
+ // Plugins taking or losing focus triggering focus app messages.
+ case WM_SETFOCUS:
+ case WM_KILLFOCUS:
+ // Windowed plugins that pass sys key events to defwndproc generate
+ // WM_SYSCOMMAND events to the main window.
+ case WM_SYSCOMMAND:
+ // Windowed plugins that fire context menu selection events to parent
+ // windows.
+ case WM_CONTEXTMENU:
+ // IME events fired as a result of synchronous focus changes
+ case WM_IME_SETCONTEXT:
+ handled = true;
+ break;
+ }
+
+ if (handled &&
+ (InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) {
+ ReplyMessage(dwResult);
+ }
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Native events
+ **
+ ** Main Windows message handlers and OnXXX handlers for
+ ** Windows event handling.
+ **
+ **************************************************************
+ **************************************************************/
+
+/**************************************************************
+ *
+ * SECTION: Wind proc.
+ *
+ * The main Windows event procedures and associated
+ * message processing methods.
+ *
+ **************************************************************/
+
+static bool DisplaySystemMenu(HWND hWnd, nsSizeMode sizeMode, bool isRtl,
+ int32_t x, int32_t y) {
+ HMENU hMenu = GetSystemMenu(hWnd, FALSE);
+ if (hMenu) {
+ MENUITEMINFO mii;
+ mii.cbSize = sizeof(MENUITEMINFO);
+ mii.fMask = MIIM_STATE;
+ mii.fType = 0;
+
+ // update the options
+ mii.fState = MF_ENABLED;
+ SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii);
+ SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii);
+ SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii);
+ SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii);
+ SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii);
+
+ mii.fState = MF_GRAYED;
+ switch (sizeMode) {
+ case nsSizeMode_Fullscreen:
+ // intentional fall through
+ case nsSizeMode_Maximized:
+ SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii);
+ SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii);
+ SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii);
+ break;
+ case nsSizeMode_Minimized:
+ SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii);
+ break;
+ case nsSizeMode_Normal:
+ SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii);
+ break;
+ case nsSizeMode_Invalid:
+ NS_ASSERTION(false, "Did the argument come from invalid IPC?");
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unhnalded nsSizeMode value detected");
+ break;
+ }
+ LPARAM cmd = TrackPopupMenu(
+ hMenu,
+ (TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_TOPALIGN |
+ (isRtl ? TPM_RIGHTALIGN : TPM_LEFTALIGN)),
+ x, y, 0, hWnd, nullptr);
+ if (cmd) {
+ PostMessage(hWnd, WM_SYSCOMMAND, cmd, 0);
+ return true;
+ }
+ }
+ return false;
+}
+
+// The WndProc procedure for all nsWindows in this toolkit. This merely catches
+// exceptions and passes the real work to WindowProcInternal. See bug 587406
+// and http://msdn.microsoft.com/en-us/library/ms633573%28VS.85%29.aspx
+LRESULT CALLBACK nsWindow::WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
+ LPARAM lParam) {
+ mozilla::ipc::CancelCPOWs();
+
+ BackgroundHangMonitor().NotifyActivity();
+
+ return mozilla::CallWindowProcCrashProtected(WindowProcInternal, hWnd, msg,
+ wParam, lParam);
+}
+
+LRESULT CALLBACK nsWindow::WindowProcInternal(HWND hWnd, UINT msg,
+ WPARAM wParam, LPARAM lParam) {
+ if (::GetWindowLongPtrW(hWnd, GWLP_ID) == eFakeTrackPointScrollableID) {
+ // This message was sent to the FAKETRACKPOINTSCROLLABLE.
+ if (msg == WM_HSCROLL) {
+ // Route WM_HSCROLL messages to the main window.
+ hWnd = ::GetParent(::GetParent(hWnd));
+ } else {
+ // Handle all other messages with its original window procedure.
+ WNDPROC prevWindowProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_USERDATA);
+ return ::CallWindowProcW(prevWindowProc, hWnd, msg, wParam, lParam);
+ }
+ }
+
+ if (msg == MOZ_WM_TRACE) {
+ // This is a tracer event for measuring event loop latency.
+ // See WidgetTraceEvent.cpp for more details.
+ mozilla::SignalTracerThread();
+ return 0;
+ }
+
+ // Get the window which caused the event and ask it to process the message
+ nsWindow* targetWindow = WinUtils::GetNSWindowPtr(hWnd);
+ NS_ASSERTION(targetWindow, "nsWindow* is null!");
+ if (!targetWindow) return ::DefWindowProcW(hWnd, msg, wParam, lParam);
+
+ // Hold the window for the life of this method, in case it gets
+ // destroyed during processing, unless we're in the dtor already.
+ nsCOMPtr<nsIWidget> kungFuDeathGrip;
+ if (!targetWindow->mInDtor) kungFuDeathGrip = targetWindow;
+
+ targetWindow->IPCWindowProcHandler(msg, wParam, lParam);
+
+ // Create this here so that we store the last rolled up popup until after
+ // the event has been processed.
+ nsAutoRollup autoRollup;
+
+ LRESULT popupHandlingResult;
+ if (DealWithPopups(hWnd, msg, wParam, lParam, &popupHandlingResult))
+ return popupHandlingResult;
+
+ // Call ProcessMessage
+ LRESULT retValue;
+ if (targetWindow->ProcessMessage(msg, wParam, lParam, &retValue)) {
+ return retValue;
+ }
+
+ LRESULT res = ::CallWindowProcW(targetWindow->GetPrevWindowProc(), hWnd, msg,
+ wParam, lParam);
+
+ return res;
+}
+
+const char16_t* GetQuitType() {
+ if (Preferences::GetBool(PREF_WIN_REGISTER_APPLICATION_RESTART, false)) {
+ DWORD cchCmdLine = 0;
+ HRESULT rc = ::GetApplicationRestartSettings(::GetCurrentProcess(), nullptr,
+ &cchCmdLine, nullptr);
+ if (rc == S_OK) {
+ return u"os-restart";
+ }
+ }
+ return nullptr;
+}
+
+static void ForceFontUpdate() {
+ // update device context font cache
+ // Dirty but easiest way:
+ // Changing nsIPrefBranch entry which triggers callbacks
+ // and flows into calling mDeviceContext->FlushFontCache()
+ // to update the font cache in all the instance of Browsers
+ static const char kPrefName[] = "font.internaluseonly.changed";
+ bool fontInternalChange = Preferences::GetBool(kPrefName, false);
+ Preferences::SetBool(kPrefName, !fontInternalChange);
+}
+
+bool nsWindow::ExternalHandlerProcessMessage(UINT aMessage, WPARAM& aWParam,
+ LPARAM& aLParam,
+ MSGResult& aResult) {
+ if (mWindowHook.Notify(mWnd, aMessage, aWParam, aLParam, aResult)) {
+ return true;
+ }
+
+ if (IMEHandler::ProcessMessage(this, aMessage, aWParam, aLParam, aResult)) {
+ return true;
+ }
+
+ if (MouseScrollHandler::ProcessMessage(this, aMessage, aWParam, aLParam,
+ aResult)) {
+ return true;
+ }
+
+ return false;
+}
+
+// The main windows message processing method.
+bool nsWindow::ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam,
+ LRESULT* aRetValue) {
+#if defined(EVENT_DEBUG_OUTPUT)
+ // First param shows all events, second param indicates whether
+ // to show mouse move events. See nsWindowDbg for details.
+ PrintEvent(msg, SHOW_REPEAT_EVENTS, SHOW_MOUSEMOVE_EVENTS);
+#endif
+
+ MSGResult msgResult(aRetValue);
+ if (ExternalHandlerProcessMessage(msg, wParam, lParam, msgResult)) {
+ return (msgResult.mConsumed || !mWnd);
+ }
+
+ bool result = false; // call the default nsWindow proc
+ *aRetValue = 0;
+
+ // Glass hit testing w/custom transparent margins
+ LRESULT dwmHitResult;
+ if (mCustomNonClient &&
+ gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled() &&
+ /* We don't do this for win10 glass with a custom titlebar,
+ * in order to avoid the caption buttons breaking. */
+ !(IsWin10OrLater() && HasGlass()) &&
+ DwmDefWindowProc(mWnd, msg, wParam, lParam, &dwmHitResult)) {
+ *aRetValue = dwmHitResult;
+ return true;
+ }
+
+ // (Large blocks of code should be broken out into OnEvent handlers.)
+ switch (msg) {
+ // WM_QUERYENDSESSION must be handled by all windows.
+ // Otherwise Windows thinks the window can just be killed at will.
+ case WM_QUERYENDSESSION:
+ if (sCanQuit == TRI_UNKNOWN) {
+ // Ask if it's ok to quit, and store the answer until we
+ // get WM_ENDSESSION signaling the round is complete.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ nsCOMPtr<nsISupportsPRBool> cancelQuit =
+ do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID);
+ cancelQuit->SetData(false);
+
+ const char16_t* quitType = GetQuitType();
+ obsServ->NotifyObservers(cancelQuit, "quit-application-requested",
+ quitType);
+
+ bool abortQuit;
+ cancelQuit->GetData(&abortQuit);
+ sCanQuit = abortQuit ? TRI_FALSE : TRI_TRUE;
+ }
+ *aRetValue = sCanQuit ? TRUE : FALSE;
+ result = true;
+ break;
+
+ case MOZ_WM_STARTA11Y:
+#if defined(ACCESSIBILITY)
+ Unused << GetAccessible();
+ result = true;
+#else
+ result = false;
+#endif
+ break;
+
+ case WM_ENDSESSION:
+ case MOZ_WM_APP_QUIT:
+ if (msg == MOZ_WM_APP_QUIT || (wParam == TRUE && sCanQuit == TRI_TRUE)) {
+ // Let's fake a shutdown sequence without actually closing windows etc.
+ // to avoid Windows killing us in the middle. A proper shutdown would
+ // require having a chance to pump some messages. Unfortunately
+ // Windows won't let us do that. Bug 212316.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ const char16_t* context = u"shutdown-persist";
+ const char16_t* syncShutdown = u"syncShutdown";
+ const char16_t* quitType = GetQuitType();
+
+ obsServ->NotifyObservers(nullptr, "quit-application-granted",
+ syncShutdown);
+ obsServ->NotifyObservers(nullptr, "quit-application-forced", nullptr);
+ obsServ->NotifyObservers(nullptr, "quit-application", quitType);
+ obsServ->NotifyObservers(nullptr, "profile-change-net-teardown",
+ context);
+ obsServ->NotifyObservers(nullptr, "profile-change-teardown", context);
+ obsServ->NotifyObservers(nullptr, "profile-before-change", context);
+ obsServ->NotifyObservers(nullptr, "profile-before-change-qm", context);
+ obsServ->NotifyObservers(nullptr, "profile-before-change-telemetry",
+ context);
+ mozilla::AppShutdown::DoImmediateExit();
+ }
+ sCanQuit = TRI_UNKNOWN;
+ result = true;
+ break;
+
+ case WM_SYSCOLORCHANGE:
+ // No need to invalidate layout for system color changes, but we need to
+ // invalidate style.
+ NotifyThemeChanged(widget::ThemeChangeKind::Style);
+ break;
+
+ case WM_THEMECHANGED: {
+ // Before anything else, push updates to child processes
+ WinContentSystemParameters::GetSingleton()->OnThemeChanged();
+
+ // Update non-client margin offsets
+ UpdateNonClientMargins();
+ nsUXThemeData::UpdateNativeThemeInfo();
+
+ // We assume pretty much everything could've changed here.
+ NotifyThemeChanged(widget::ThemeChangeKind::StyleAndLayout);
+
+ // Invalidate the window so that the repaint will
+ // pick up the new theme.
+ Invalidate(true, true, true);
+ } break;
+
+ case WM_WTSSESSION_CHANGE: {
+ switch (wParam) {
+ case WTS_CONSOLE_CONNECT:
+ case WTS_REMOTE_CONNECT:
+ case WTS_SESSION_UNLOCK:
+ // When a session becomes visible, we should invalidate.
+ Invalidate(true, true, true);
+ break;
+ default:
+ break;
+ }
+ } break;
+
+ case WM_FONTCHANGE: {
+ // We only handle this message for the hidden window,
+ // as we only need to update the (global) font list once
+ // for any given change, not once per window!
+ if (mWindowType != eWindowType_invisible) {
+ break;
+ }
+
+ nsresult rv;
+ bool didChange = false;
+
+ // update the global font list
+ nsCOMPtr<nsIFontEnumerator> fontEnum =
+ do_GetService("@mozilla.org/gfx/fontenumerator;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ fontEnum->UpdateFontList(&didChange);
+ ForceFontUpdate();
+ } // if (NS_SUCCEEDED(rv))
+ } break;
+
+ case WM_SETTINGCHANGE: {
+ if (wParam == SPI_SETCLIENTAREAANIMATION ||
+ // CaretBlinkTime is cached in nsLookAndFeel
+ wParam == SPI_SETKEYBOARDDELAY) {
+ // This only affects reduced motion settings and and carent blink time,
+ // so no need to invalidate style / layout.
+ NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
+ break;
+ }
+ if (wParam == SPI_SETFONTSMOOTHING ||
+ wParam == SPI_SETFONTSMOOTHINGTYPE) {
+ gfxDWriteFont::UpdateSystemTextQuality();
+ break;
+ }
+ if (lParam) {
+ auto lParamString = reinterpret_cast<const wchar_t*>(lParam);
+ if (!wcscmp(lParamString, L"ImmersiveColorSet")) {
+ // This affects system colors (-moz-win-accentcolor), so gotta pass
+ // the style flag.
+ NotifyThemeChanged(widget::ThemeChangeKind::Style);
+ break;
+ }
+ if (IsWin10OrLater() && mWindowType == eWindowType_invisible) {
+ if (!wcscmp(lParamString, L"UserInteractionMode")) {
+ nsCOMPtr<nsIWindowsUIUtils> uiUtils(
+ do_GetService("@mozilla.org/windows-ui-utils;1"));
+ if (uiUtils) {
+ uiUtils->UpdateTabletModeState();
+ }
+ }
+ }
+ }
+ } break;
+
+ case WM_DEVICECHANGE: {
+ if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) {
+ DEV_BROADCAST_HDR* hdr = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam);
+ // Check dbch_devicetype explicitly since we will get other device types
+ // (e.g. DBT_DEVTYP_VOLUME) for some reasons even if we specify
+ // DBT_DEVTYP_DEVICEINTERFACE in the filter for
+ // RegisterDeviceNotification.
+ if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
+ // This can only change media queries (any-hover/any-pointer).
+ NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
+ }
+ }
+ } break;
+
+ case WM_NCCALCSIZE: {
+ // NOTE: the following block is mirrored in PreXULSkeletonUI.cpp, and
+ // will need to be kept in sync.
+ if (mCustomNonClient) {
+ // If `wParam` is `FALSE`, `lParam` points to a `RECT` that contains
+ // the proposed window rectangle for our window. During our
+ // processing of the `WM_NCCALCSIZE` message, we are expected to
+ // modify the `RECT` that `lParam` points to, so that its value upon
+ // our return is the new client area. We must return 0 if `wParam`
+ // is `FALSE`.
+ //
+ // If `wParam` is `TRUE`, `lParam` points to a `NCCALCSIZE_PARAMS`
+ // struct. This struct contains an array of 3 `RECT`s, the first of
+ // which has the exact same meaning as the `RECT` that is pointed to
+ // by `lParam` when `wParam` is `FALSE`. The remaining `RECT`s, in
+ // conjunction with our return value, can
+ // be used to specify portions of the source and destination window
+ // rectangles that are valid and should be preserved. We opt not to
+ // implement an elaborate client-area preservation technique, and
+ // simply return 0, which means "preserve the entire old client area
+ // and align it with the upper-left corner of our new client area".
+ RECT* clientRect =
+ wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0]
+ : (reinterpret_cast<RECT*>(lParam));
+ clientRect->top += mCaptionHeight - mNonClientOffset.top;
+ clientRect->left += mHorResizeMargin - mNonClientOffset.left;
+ clientRect->right -= mHorResizeMargin - mNonClientOffset.right;
+ clientRect->bottom -= mVertResizeMargin - mNonClientOffset.bottom;
+ // Make client rect's width and height more than 0 to
+ // avoid problems of webrender and angle.
+ clientRect->right = std::max(clientRect->right, clientRect->left + 1);
+ clientRect->bottom = std::max(clientRect->bottom, clientRect->top + 1);
+
+ result = true;
+ *aRetValue = 0;
+ }
+ break;
+ }
+
+ case WM_NCHITTEST: {
+ if (mMouseTransparent) {
+ // Treat this window as transparent.
+ *aRetValue = HTTRANSPARENT;
+ result = true;
+ break;
+ }
+
+ /*
+ * If an nc client area margin has been moved, we are responsible
+ * for calculating where the resize margins are and returning the
+ * appropriate set of hit test constants. DwmDefWindowProc (above)
+ * will handle hit testing on it's command buttons if we are on a
+ * composited desktop.
+ */
+
+ if (!mCustomNonClient) break;
+
+ *aRetValue =
+ ClientMarginHitTestPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
+ result = true;
+ break;
+ }
+
+ case WM_SETTEXT:
+ /*
+ * WM_SETTEXT paints the titlebar area. Avoid this if we have a
+ * custom titlebar we paint ourselves, or if we're the ones
+ * sending the message with an updated title
+ */
+
+ if ((mSendingSetText &&
+ gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) ||
+ !mCustomNonClient || mNonClientMargins.top == -1)
+ break;
+
+ {
+ // From msdn, the way around this is to disable the visible state
+ // temporarily. We need the text to be set but we don't want the
+ // redraw to occur. However, we need to make sure that we don't
+ // do this at the same time that a Present is happening.
+ //
+ // To do this we take mPresentLock in nsWindow::PreRender and
+ // if that lock is taken we wait before doing WM_SETTEXT
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->EnterPresentLock();
+ }
+ DWORD style = GetWindowLong(mWnd, GWL_STYLE);
+ SetWindowLong(mWnd, GWL_STYLE, style & ~WS_VISIBLE);
+ *aRetValue =
+ CallWindowProcW(GetPrevWindowProc(), mWnd, msg, wParam, lParam);
+ SetWindowLong(mWnd, GWL_STYLE, style);
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->LeavePresentLock();
+ }
+
+ return true;
+ }
+
+ case WM_NCACTIVATE: {
+ /*
+ * WM_NCACTIVATE paints nc areas. Avoid this and re-route painting
+ * through WM_NCPAINT via InvalidateNonClientRegion.
+ */
+ UpdateGetWindowInfoCaptionStatus(FALSE != wParam);
+
+ if (!mCustomNonClient) break;
+
+ // There is a case that rendered result is not kept. Bug 1237617
+ if (wParam == TRUE && !gfxEnv::DisableForcePresent() &&
+ gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ NS_DispatchToMainThread(NewRunnableMethod(
+ "nsWindow::ForcePresent", this, &nsWindow::ForcePresent));
+ }
+
+ // let the dwm handle nc painting on glass
+ // Never allow native painting if we are on fullscreen
+ if (mSizeMode != nsSizeMode_Fullscreen &&
+ gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled())
+ break;
+
+ if (wParam == TRUE) {
+ // going active
+ *aRetValue = FALSE; // ignored
+ result = true;
+ // invalidate to trigger a paint
+ InvalidateNonClientRegion();
+ break;
+ } else {
+ // going inactive
+ *aRetValue = TRUE; // go ahead and deactive
+ result = true;
+ // invalidate to trigger a paint
+ InvalidateNonClientRegion();
+ break;
+ }
+ }
+
+ case WM_NCPAINT: {
+ /*
+ * ClearType changes often don't send a WM_SETTINGCHANGE message. But they
+ * do seem to always send a WM_NCPAINT message, so let's update on that.
+ */
+ gfxDWriteFont::UpdateSystemTextQuality();
+
+ /*
+ * Reset the non-client paint region so that it excludes the
+ * non-client areas we paint manually. Then call defwndproc
+ * to do the actual painting.
+ */
+
+ if (!mCustomNonClient) break;
+
+ // let the dwm handle nc painting on glass
+ if (gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) break;
+
+ HRGN paintRgn = ExcludeNonClientFromPaintRegion((HRGN)wParam);
+ LRESULT res = CallWindowProcW(GetPrevWindowProc(), mWnd, msg,
+ (WPARAM)paintRgn, lParam);
+ if (paintRgn != (HRGN)wParam) DeleteObject(paintRgn);
+ *aRetValue = res;
+ result = true;
+ } break;
+
+ case WM_POWERBROADCAST:
+ switch (wParam) {
+ case PBT_APMSUSPEND:
+ PostSleepWakeNotification(true);
+ break;
+ case PBT_APMRESUMEAUTOMATIC:
+ case PBT_APMRESUMECRITICAL:
+ case PBT_APMRESUMESUSPEND:
+ PostSleepWakeNotification(false);
+ break;
+ }
+ break;
+
+ case WM_CLOSE: // close request
+ if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
+ result = true; // abort window closure
+ break;
+
+ case WM_DESTROY:
+ // clean up.
+ DestroyLayerManager();
+ OnDestroy();
+ result = true;
+ break;
+
+ case WM_PAINT:
+ *aRetValue = (int)OnPaint(nullptr, 0);
+ result = true;
+ break;
+
+ case WM_PRINTCLIENT:
+ result = OnPaint((HDC)wParam, 0);
+ break;
+
+ case WM_HOTKEY:
+ result = OnHotKey(wParam, lParam);
+ break;
+
+ case WM_SYSCHAR:
+ case WM_CHAR: {
+ MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
+ result = ProcessCharMessage(nativeMsg, nullptr);
+ DispatchPendingEvents();
+ } break;
+
+ case WM_SYSKEYUP:
+ case WM_KEYUP: {
+ MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
+ nativeMsg.time = ::GetMessageTime();
+ result = ProcessKeyUpMessage(nativeMsg, nullptr);
+ DispatchPendingEvents();
+ } break;
+
+ case WM_SYSKEYDOWN:
+ case WM_KEYDOWN: {
+ MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
+ result = ProcessKeyDownMessage(nativeMsg, nullptr);
+ DispatchPendingEvents();
+ } break;
+
+ // say we've dealt with erase background if widget does
+ // not need auto-erasing
+ case WM_ERASEBKGND:
+ if (!AutoErase((HDC)wParam)) {
+ *aRetValue = 1;
+ result = true;
+ }
+ break;
+
+ case WM_MOUSEMOVE: {
+ LPARAM lParamScreen = lParamToScreen(lParam);
+ mMouseInDraggableArea = WithinDraggableRegion(GET_X_LPARAM(lParamScreen),
+ GET_Y_LPARAM(lParamScreen));
+
+ if (!mMousePresent && !sIsInMouseCapture) {
+ // First MOUSEMOVE over the client area. Ask for MOUSELEAVE
+ TRACKMOUSEEVENT mTrack;
+ mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
+ mTrack.dwFlags = TME_LEAVE;
+ mTrack.dwHoverTime = 0;
+ mTrack.hwndTrack = mWnd;
+ TrackMouseEvent(&mTrack);
+ }
+ mMousePresent = true;
+
+ // Suppress dispatch of pending events
+ // when mouse moves are generated by widget
+ // creation instead of user input.
+ POINT mp;
+ mp.x = GET_X_LPARAM(lParamScreen);
+ mp.y = GET_Y_LPARAM(lParamScreen);
+ bool userMovedMouse = false;
+ if ((sLastMouseMovePoint.x != mp.x) || (sLastMouseMovePoint.y != mp.y)) {
+ userMovedMouse = true;
+ }
+
+ result =
+ DispatchMouseEvent(eMouseMove, wParam, lParam, false,
+ MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
+ mPointerEvents.GetCachedPointerInfo(msg, wParam));
+ if (userMovedMouse) {
+ DispatchPendingEvents();
+ }
+ } break;
+
+ case WM_NCMOUSEMOVE: {
+ LPARAM lParamClient = lParamToClient(lParam);
+ if (WithinDraggableRegion(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) {
+ if (!sIsInMouseCapture) {
+ TRACKMOUSEEVENT mTrack;
+ mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
+ mTrack.dwFlags = TME_LEAVE | TME_NONCLIENT;
+ mTrack.dwHoverTime = 0;
+ mTrack.hwndTrack = mWnd;
+ TrackMouseEvent(&mTrack);
+ }
+ // If we noticed the mouse moving in our draggable region, forward the
+ // message as a normal WM_MOUSEMOVE.
+ SendMessage(mWnd, WM_MOUSEMOVE, 0, lParamClient);
+ } else {
+ // We've transitioned from a draggable area to somewhere else within
+ // the non-client area - perhaps one of the edges of the window for
+ // resizing.
+ mMouseInDraggableArea = false;
+ }
+
+ if (mMousePresent && !sIsInMouseCapture && !mMouseInDraggableArea) {
+ SendMessage(mWnd, WM_MOUSELEAVE, 0, 0);
+ }
+ } break;
+
+ case WM_LBUTTONDOWN: {
+ result =
+ DispatchMouseEvent(eMouseDown, wParam, lParam, false,
+ MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
+ mPointerEvents.GetCachedPointerInfo(msg, wParam));
+ DispatchPendingEvents();
+ } break;
+
+ case WM_LBUTTONUP: {
+ result =
+ DispatchMouseEvent(eMouseUp, wParam, lParam, false,
+ MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
+ mPointerEvents.GetCachedPointerInfo(msg, wParam));
+ DispatchPendingEvents();
+ } break;
+
+ case WM_NCMOUSELEAVE: {
+ mMouseInDraggableArea = false;
+
+ if (EventIsInsideWindow(this)) {
+ // If we're handling WM_NCMOUSELEAVE and the mouse is still over the
+ // window, then by process of elimination, the mouse has moved from the
+ // non-client to client area, so no need to fall-through to the
+ // WM_MOUSELEAVE handler. We also need to re-register for the
+ // WM_MOUSELEAVE message, since according to the documentation at [1],
+ // all tracking requested via TrackMouseEvent is cleared once
+ // WM_NCMOUSELEAVE or WM_MOUSELEAVE fires.
+ // [1]:
+ // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-trackmouseevent
+ TRACKMOUSEEVENT mTrack;
+ mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
+ mTrack.dwFlags = TME_LEAVE;
+ mTrack.dwHoverTime = 0;
+ mTrack.hwndTrack = mWnd;
+ TrackMouseEvent(&mTrack);
+ break;
+ }
+ // We've transitioned from non-client to outside of the window, so
+ // fall-through to the WM_MOUSELEAVE handler.
+ }
+ case WM_MOUSELEAVE: {
+ if (!mMousePresent) break;
+ if (mMouseInDraggableArea) break;
+ mMousePresent = false;
+
+ // Check if the mouse is over the fullscreen transition window, if so
+ // clear sLastMouseMovePoint. This way the WM_MOUSEMOVE we get after the
+ // transition window disappears will not be ignored, even if the mouse
+ // hasn't moved.
+ if (mTransitionWnd && WindowAtMouse() == mTransitionWnd) {
+ sLastMouseMovePoint = {0};
+ }
+
+ // We need to check mouse button states and put them in for
+ // wParam.
+ WPARAM mouseState = (GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) |
+ (GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) |
+ (GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0);
+ // Synthesize an event position because we don't get one from
+ // WM_MOUSELEAVE.
+ LPARAM pos = lParamToClient(::GetMessagePos());
+ DispatchMouseEvent(eMouseExitFromWidget, mouseState, pos, false,
+ MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
+ } break;
+
+ case MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER: {
+ LPARAM pos = lParamToClient(::GetMessagePos());
+ MOZ_ASSERT(InkCollector::sInkCollector);
+ uint16_t pointerId = InkCollector::sInkCollector->GetPointerId();
+ if (pointerId != 0) {
+ WinPointerInfo pointerInfo;
+ pointerInfo.pointerId = pointerId;
+ DispatchMouseEvent(eMouseExitFromWidget, wParam, pos, false,
+ MouseButton::ePrimary,
+ MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
+ InkCollector::sInkCollector->ClearTarget();
+ InkCollector::sInkCollector->ClearPointerId();
+ }
+ } break;
+
+ case WM_CONTEXTMENU: {
+ // If the context menu is brought up by a touch long-press, then
+ // the APZ code is responsible for dealing with this, so we don't
+ // need to do anything.
+ if (mTouchWindow &&
+ MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
+ MOZ_ASSERT(mAPZC); // since mTouchWindow is true, APZ must be enabled
+ result = true;
+ break;
+ }
+
+ // if the context menu is brought up from the keyboard, |lParam|
+ // will be -1.
+ LPARAM pos;
+ bool contextMenukey = false;
+ if (lParam == -1) {
+ contextMenukey = true;
+ pos = lParamToClient(GetMessagePos());
+ } else {
+ pos = lParamToClient(lParam);
+ }
+
+ result = DispatchMouseEvent(
+ eContextMenu, wParam, pos, contextMenukey,
+ contextMenukey ? MouseButton::ePrimary : MouseButton::eSecondary,
+ MOUSE_INPUT_SOURCE());
+ if (lParam != -1 && !result && mCustomNonClient &&
+ mDraggableRegion.Contains(GET_X_LPARAM(pos), GET_Y_LPARAM(pos))) {
+ // Blank area hit, throw up the system menu.
+ DisplaySystemMenu(mWnd, mSizeMode, mIsRTL, GET_X_LPARAM(lParam),
+ GET_Y_LPARAM(lParam));
+ result = true;
+ }
+ } break;
+
+ case WM_POINTERLEAVE:
+ case WM_POINTERDOWN:
+ case WM_POINTERUP:
+ case WM_POINTERUPDATE:
+ result = OnPointerEvents(msg, wParam, lParam);
+ if (result) {
+ DispatchPendingEvents();
+ }
+ break;
+
+ case DM_POINTERHITTEST:
+ if (mDmOwner) {
+ UINT contactId = GET_POINTERID_WPARAM(wParam);
+ POINTER_INPUT_TYPE pointerType;
+ if (mPointerEvents.GetPointerType(contactId, &pointerType) &&
+ pointerType == PT_TOUCHPAD) {
+ mDmOwner->SetContact(contactId);
+ }
+ }
+ break;
+
+ case WM_LBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
+ MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_MBUTTONDOWN:
+ result = DispatchMouseEvent(eMouseDown, wParam, lParam, false,
+ MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_MBUTTONUP:
+ result = DispatchMouseEvent(eMouseUp, wParam, lParam, false,
+ MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_MBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
+ MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCMBUTTONDOWN:
+ result = DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
+ MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCMBUTTONUP:
+ result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
+ MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCMBUTTONDBLCLK:
+ result =
+ DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
+ false, MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_RBUTTONDOWN:
+ result =
+ DispatchMouseEvent(eMouseDown, wParam, lParam, false,
+ MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
+ mPointerEvents.GetCachedPointerInfo(msg, wParam));
+ DispatchPendingEvents();
+ break;
+
+ case WM_RBUTTONUP:
+ result =
+ DispatchMouseEvent(eMouseUp, wParam, lParam, false,
+ MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
+ mPointerEvents.GetCachedPointerInfo(msg, wParam));
+ DispatchPendingEvents();
+ break;
+
+ case WM_RBUTTONDBLCLK:
+ result =
+ DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
+ MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCRBUTTONDOWN:
+ result =
+ DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
+ MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCRBUTTONUP:
+ result =
+ DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
+ MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCRBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
+ false, MouseButton::eSecondary,
+ MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ // Windows doesn't provide to customize the behavior of 4th nor 5th button
+ // of mouse. If 5-button mouse works with standard mouse deriver of
+ // Windows, users cannot disable 4th button (browser back) nor 5th button
+ // (browser forward). We should allow to do it with our prefs since we can
+ // prevent Windows to generate WM_APPCOMMAND message if WM_XBUTTONUP
+ // messages are not sent to DefWindowProc.
+ case WM_XBUTTONDOWN:
+ case WM_XBUTTONUP:
+ case WM_NCXBUTTONDOWN:
+ case WM_NCXBUTTONUP:
+ *aRetValue = TRUE;
+ switch (GET_XBUTTON_WPARAM(wParam)) {
+ case XBUTTON1:
+ result = !Preferences::GetBool("mousebutton.4th.enabled", true);
+ break;
+ case XBUTTON2:
+ result = !Preferences::GetBool("mousebutton.5th.enabled", true);
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case WM_SIZING: {
+ if (mAspectRatio > 0) {
+ LPRECT rect = (LPRECT)lParam;
+ int32_t newWidth, newHeight;
+
+ // The following conditions and switch statement borrow heavily from the
+ // Chromium source code from
+ // https://chromium.googlesource.com/chromium/src/+/456d6e533cfb4531995e0ef52c279d4b5aa8a352/ui/views/window/window_resize_utils.cc#45
+ if (wParam == WMSZ_LEFT || wParam == WMSZ_RIGHT ||
+ wParam == WMSZ_TOPLEFT || wParam == WMSZ_BOTTOMLEFT) {
+ newWidth = rect->right - rect->left;
+ newHeight = newWidth * mAspectRatio;
+ } else {
+ newHeight = rect->bottom - rect->top;
+ newWidth = newHeight / mAspectRatio;
+ }
+
+ switch (wParam) {
+ case WMSZ_RIGHT:
+ case WMSZ_BOTTOM:
+ rect->right = newWidth + rect->left;
+ rect->bottom = rect->top + newHeight;
+ break;
+ case WMSZ_TOP:
+ rect->right = newWidth + rect->left;
+ rect->top = rect->bottom - newHeight;
+ break;
+ case WMSZ_LEFT:
+ case WMSZ_TOPLEFT:
+ rect->left = rect->right - newWidth;
+ rect->top = rect->bottom - newHeight;
+ break;
+ case WMSZ_TOPRIGHT:
+ rect->right = rect->left + newWidth;
+ rect->top = rect->bottom - newHeight;
+ break;
+ case WMSZ_BOTTOMLEFT:
+ rect->left = rect->right - newWidth;
+ rect->bottom = rect->top + newHeight;
+ break;
+ case WMSZ_BOTTOMRIGHT:
+ rect->right = rect->left + newWidth;
+ rect->bottom = rect->top + newHeight;
+ break;
+ }
+ }
+
+ // When we get WM_ENTERSIZEMOVE we don't know yet if we're in a live
+ // resize or move event. Instead we wait for first VM_SIZING message
+ // within a ENTERSIZEMOVE to consider this a live resize event.
+ if (mResizeState == IN_SIZEMOVE) {
+ mResizeState = RESIZING;
+ NotifyLiveResizeStarted();
+ }
+ break;
+ }
+
+ case WM_MOVING:
+ FinishLiveResizing(MOVING);
+ if (WinUtils::IsPerMonitorDPIAware()) {
+ // Sometimes, we appear to miss a WM_DPICHANGED message while moving
+ // a window around. Therefore, call ChangedDPI and ResetLayout here
+ // if it appears that the window's scaling is not what we expect.
+ // This causes the prescontext and appshell window management code to
+ // check the appUnitsPerDevPixel value and current widget size, and
+ // refresh them if necessary. If nothing has changed, these calls will
+ // return without actually triggering any extra reflow or painting.
+ if (WinUtils::LogToPhysFactor(mWnd) != mDefaultScale) {
+ ChangedDPI();
+ ResetLayout();
+ if (mWidgetListener) {
+ mWidgetListener->UIResolutionChanged();
+ }
+ }
+ }
+ break;
+
+ case WM_ENTERSIZEMOVE: {
+ if (mResizeState == NOT_RESIZING) {
+ mResizeState = IN_SIZEMOVE;
+ }
+ break;
+ }
+
+ case WM_EXITSIZEMOVE: {
+ FinishLiveResizing(NOT_RESIZING);
+
+ if (!sIsInMouseCapture) {
+ NotifySizeMoveDone();
+ }
+
+ break;
+ }
+
+ case WM_DISPLAYCHANGE: {
+ ScreenHelperWin::RefreshScreens();
+ if (mWidgetListener) {
+ mWidgetListener->UIResolutionChanged();
+ }
+ break;
+ }
+
+ case WM_NCLBUTTONDBLCLK:
+ DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam), false,
+ MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
+ result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
+ MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCLBUTTONDOWN: {
+ // Dispatch a custom event when this happens in the draggable region, so
+ // that non-popup-based panels can react to it. This doesn't send an
+ // actual mousedown event because that would break dragging or interfere
+ // with other mousedown handling in the caption area.
+ if (WithinDraggableRegion(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) {
+ DispatchCustomEvent(u"draggableregionleftmousedown"_ns);
+ }
+ break;
+ }
+
+ case WM_APPCOMMAND: {
+ MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
+ result = HandleAppCommandMsg(nativeMsg, aRetValue);
+ break;
+ }
+
+ // The WM_ACTIVATE event is fired when a window is raised or lowered,
+ // and the loword of wParam specifies which. But we don't want to tell
+ // the focus system about this until the WM_SETFOCUS or WM_KILLFOCUS
+ // events are fired. Instead, set either the sJustGotActivate or
+ // gJustGotDeactivate flags and activate/deactivate once the focus
+ // events arrive.
+ case WM_ACTIVATE:
+ if (mWidgetListener) {
+ int32_t fActive = LOWORD(wParam);
+
+ if (WA_INACTIVE == fActive) {
+ // when minimizing a window, the deactivation and focus events will
+ // be fired in the reverse order. Instead, just deactivate right away.
+ // This can also happen when a modal system dialog is opened, so check
+ // if the last window to receive the WM_KILLFOCUS message was this one
+ // or a child of this one.
+ if (HIWORD(wParam) ||
+ (mLastKillFocusWindow &&
+ (GetTopLevelForFocus(mLastKillFocusWindow) == mWnd))) {
+ DispatchFocusToTopLevelWindow(false);
+ } else {
+ sJustGotDeactivate = true;
+ }
+ if (mIsTopWidgetWindow) {
+ mLastKeyboardLayout = KeyboardLayout::GetInstance()->GetLayout();
+ }
+ } else {
+ StopFlashing();
+
+ sJustGotActivate = true;
+ WidgetMouseEvent event(true, eMouseActivate, this,
+ WidgetMouseEvent::eReal);
+ InitEvent(event);
+ ModifierKeyState modifierKeyState;
+ modifierKeyState.InitInputEvent(event);
+ DispatchInputEvent(&event);
+ if (sSwitchKeyboardLayout && mLastKeyboardLayout)
+ ActivateKeyboardLayout(mLastKeyboardLayout, 0);
+ }
+ }
+ break;
+
+ case WM_MOUSEACTIVATE:
+ // A popup with a parent owner should not be activated when clicked but
+ // should still allow the mouse event to be fired, so the return value
+ // is set to MA_NOACTIVATE. But if the owner isn't the frontmost window,
+ // just use default processing so that the window is activated.
+ if (IsPopup() && IsOwnerForegroundWindow()) {
+ *aRetValue = MA_NOACTIVATE;
+ result = true;
+ }
+ break;
+
+ case WM_WINDOWPOSCHANGING: {
+ LPWINDOWPOS info = (LPWINDOWPOS)lParam;
+ OnWindowPosChanging(info);
+ result = true;
+ } break;
+
+ case WM_GETMINMAXINFO: {
+ MINMAXINFO* mmi = (MINMAXINFO*)lParam;
+ // Set the constraints. The minimum size should also be constrained to the
+ // default window maximum size so that it fits on screen.
+ mmi->ptMinTrackSize.x =
+ std::min((int32_t)mmi->ptMaxTrackSize.x,
+ std::max((int32_t)mmi->ptMinTrackSize.x,
+ mSizeConstraints.mMinSize.width));
+ mmi->ptMinTrackSize.y =
+ std::min((int32_t)mmi->ptMaxTrackSize.y,
+ std::max((int32_t)mmi->ptMinTrackSize.y,
+ mSizeConstraints.mMinSize.height));
+ mmi->ptMaxTrackSize.x = std::min((int32_t)mmi->ptMaxTrackSize.x,
+ mSizeConstraints.mMaxSize.width);
+ mmi->ptMaxTrackSize.y = std::min((int32_t)mmi->ptMaxTrackSize.y,
+ mSizeConstraints.mMaxSize.height);
+ } break;
+
+ case WM_SETFOCUS:
+ // If previous focused window isn't ours, it must have received the
+ // redirected message. So, we should forget it.
+ if (!WinUtils::IsOurProcessWindow(HWND(wParam))) {
+ RedirectedKeyDownMessageManager::Forget();
+ }
+ if (sJustGotActivate) {
+ DispatchFocusToTopLevelWindow(true);
+ }
+ break;
+
+ case WM_KILLFOCUS:
+ if (sJustGotDeactivate) {
+ DispatchFocusToTopLevelWindow(false);
+ } else {
+ mLastKillFocusWindow = mWnd;
+ }
+ break;
+
+ case WM_WINDOWPOSCHANGED: {
+ WINDOWPOS* wp = (LPWINDOWPOS)lParam;
+ OnWindowPosChanged(wp);
+ result = true;
+ } break;
+
+ case WM_INPUTLANGCHANGEREQUEST:
+ *aRetValue = TRUE;
+ result = false;
+ break;
+
+ case WM_INPUTLANGCHANGE:
+ KeyboardLayout::GetInstance()->OnLayoutChange(
+ reinterpret_cast<HKL>(lParam));
+ nsBidiKeyboard::OnLayoutChange();
+ result = false; // always pass to child window
+ break;
+
+ case WM_DESTROYCLIPBOARD: {
+ nsIClipboard* clipboard;
+ nsresult rv = CallGetService(kCClipboardCID, &clipboard);
+ if (NS_SUCCEEDED(rv)) {
+ clipboard->EmptyClipboard(nsIClipboard::kGlobalClipboard);
+ NS_RELEASE(clipboard);
+ }
+ } break;
+
+#ifdef ACCESSIBILITY
+ case WM_GETOBJECT: {
+ *aRetValue = 0;
+ // Do explicit casting to make it working on 64bit systems (see bug 649236
+ // for details).
+ int32_t objId = static_cast<DWORD>(lParam);
+ if (objId == OBJID_CLIENT) { // oleacc.dll will be loaded dynamically
+ RefPtr<IAccessible> root(
+ a11y::LazyInstantiator::GetRootAccessible(mWnd));
+ if (root) {
+ *aRetValue = LresultFromObject(IID_IAccessible, wParam, root);
+ a11y::LazyInstantiator::EnableBlindAggregation(mWnd);
+ result = true;
+ }
+ }
+ } break;
+#endif
+
+ case WM_SYSCOMMAND: {
+ WPARAM filteredWParam = (wParam & 0xFFF0);
+ if (mSizeMode == nsSizeMode_Fullscreen && filteredWParam == SC_RESTORE &&
+ GetCurrentShowCmd(mWnd) != SW_SHOWMINIMIZED) {
+ MakeFullScreen(false);
+ result = true;
+ }
+
+ // Handle the system menu manually when we're in full screen mode
+ // so we can set the appropriate options.
+ if (filteredWParam == SC_KEYMENU && lParam == VK_SPACE &&
+ mSizeMode == nsSizeMode_Fullscreen) {
+ DisplaySystemMenu(mWnd, mSizeMode, mIsRTL, MOZ_SYSCONTEXT_X_POS,
+ MOZ_SYSCONTEXT_Y_POS);
+ result = true;
+ }
+ } break;
+
+ case WM_DWMCOMPOSITIONCHANGED:
+ // Every window will get this message, but gfxVars only broadcasts
+ // updates when the value actually changes
+ if (XRE_IsParentProcess()) {
+ BOOL dwmEnabled = FALSE;
+ if (FAILED(::DwmIsCompositionEnabled(&dwmEnabled)) || !dwmEnabled) {
+ gfxVars::SetDwmCompositionEnabled(false);
+ } else {
+ gfxVars::SetDwmCompositionEnabled(true);
+ }
+ }
+
+ UpdateNonClientMargins();
+ BroadcastMsg(mWnd, WM_DWMCOMPOSITIONCHANGED);
+ // TODO: Why is NotifyThemeChanged needed, what does it affect? And can we
+ // make it more granular by tweaking the ChangeKind we pass?
+ NotifyThemeChanged(widget::ThemeChangeKind::StyleAndLayout);
+ UpdateGlass();
+ Invalidate(true, true, true);
+ break;
+
+ case WM_DPICHANGED: {
+ LPRECT rect = (LPRECT)lParam;
+ OnDPIChanged(rect->left, rect->top, rect->right - rect->left,
+ rect->bottom - rect->top);
+ break;
+ }
+
+ case WM_UPDATEUISTATE: {
+ // If the UI state has changed, fire an event so the UI updates the
+ // keyboard cues based on the system setting and how the window was
+ // opened. For example, a dialog opened via a keyboard press on a button
+ // should enable cues, whereas the same dialog opened via a mouse click of
+ // the button should not.
+ if (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog) {
+ int32_t action = LOWORD(wParam);
+ if (action == UIS_SET || action == UIS_CLEAR) {
+ int32_t flags = HIWORD(wParam);
+ UIStateChangeType showFocusRings = UIStateChangeType_NoChange;
+ if (flags & UISF_HIDEFOCUS)
+ showFocusRings = (action == UIS_SET) ? UIStateChangeType_Clear
+ : UIStateChangeType_Set;
+ NotifyUIStateChanged(showFocusRings);
+ }
+ }
+
+ break;
+ }
+
+ /* Gesture support events */
+ case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
+ // According to MS samples, this must be handled to enable
+ // rotational support in multi-touch drivers.
+ result = true;
+ *aRetValue = TABLET_ROTATE_GESTURE_ENABLE;
+ break;
+
+ case WM_TOUCH:
+ result = OnTouch(wParam, lParam);
+ if (result) {
+ *aRetValue = 0;
+ }
+ break;
+
+ case WM_GESTURE:
+ result = OnGesture(wParam, lParam);
+ break;
+
+ case WM_GESTURENOTIFY: {
+ if (mWindowType != eWindowType_invisible && !IsPlugin()) {
+ // A GestureNotify event is dispatched to decide which single-finger
+ // panning direction should be active (including none) and if pan
+ // feedback should be displayed. Java and plugin windows can make their
+ // own calls.
+
+ GESTURENOTIFYSTRUCT* gestureinfo = (GESTURENOTIFYSTRUCT*)lParam;
+ nsPointWin touchPoint;
+ touchPoint = gestureinfo->ptsLocation;
+ touchPoint.ScreenToClient(mWnd);
+ WidgetGestureNotifyEvent gestureNotifyEvent(true, eGestureNotify, this);
+ gestureNotifyEvent.mRefPoint =
+ LayoutDeviceIntPoint::FromUnknownPoint(touchPoint);
+ nsEventStatus status;
+ DispatchEvent(&gestureNotifyEvent, status);
+ mDisplayPanFeedback = gestureNotifyEvent.mDisplayPanFeedback;
+ if (!mTouchWindow)
+ mGesture.SetWinGestureSupport(mWnd, gestureNotifyEvent.mPanDirection);
+ }
+ result = false; // should always bubble to DefWindowProc
+ } break;
+
+ case WM_CLEAR: {
+ WidgetContentCommandEvent command(true, eContentCommandDelete, this);
+ DispatchWindowEvent(&command);
+ result = true;
+ } break;
+
+ case WM_CUT: {
+ WidgetContentCommandEvent command(true, eContentCommandCut, this);
+ DispatchWindowEvent(&command);
+ result = true;
+ } break;
+
+ case WM_COPY: {
+ WidgetContentCommandEvent command(true, eContentCommandCopy, this);
+ DispatchWindowEvent(&command);
+ result = true;
+ } break;
+
+ case WM_PASTE: {
+ WidgetContentCommandEvent command(true, eContentCommandPaste, this);
+ DispatchWindowEvent(&command);
+ result = true;
+ } break;
+
+ case EM_UNDO: {
+ WidgetContentCommandEvent command(true, eContentCommandUndo, this);
+ DispatchWindowEvent(&command);
+ *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
+ result = true;
+ } break;
+
+ case EM_REDO: {
+ WidgetContentCommandEvent command(true, eContentCommandRedo, this);
+ DispatchWindowEvent(&command);
+ *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
+ result = true;
+ } break;
+
+ case EM_CANPASTE: {
+ // Support EM_CANPASTE message only when wParam isn't specified or
+ // is plain text format.
+ if (wParam == 0 || wParam == CF_TEXT || wParam == CF_UNICODETEXT) {
+ WidgetContentCommandEvent command(true, eContentCommandPaste, this,
+ true);
+ DispatchWindowEvent(&command);
+ *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
+ result = true;
+ }
+ } break;
+
+ case EM_CANUNDO: {
+ WidgetContentCommandEvent command(true, eContentCommandUndo, this, true);
+ DispatchWindowEvent(&command);
+ *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
+ result = true;
+ } break;
+
+ case EM_CANREDO: {
+ WidgetContentCommandEvent command(true, eContentCommandRedo, this, true);
+ DispatchWindowEvent(&command);
+ *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
+ result = true;
+ } break;
+
+ case MOZ_WM_SKEWFIX: {
+ TimeStamp skewStamp;
+ if (CurrentWindowsTimeGetter::GetAndClearBackwardsSkewStamp(wParam,
+ &skewStamp)) {
+ TimeConverter().CompensateForBackwardsSkew(::GetMessageTime(),
+ skewStamp);
+ }
+ } break;
+
+ default: {
+ if (msg == nsAppShell::GetTaskbarButtonCreatedMessage()) {
+ SetHasTaskbarIconBeenCreated();
+ }
+ } break;
+ }
+
+ //*aRetValue = result;
+ if (mWnd) {
+ return result;
+ } else {
+ // Events which caused mWnd destruction and aren't consumed
+ // will crash during the Windows default processing.
+ return true;
+ }
+}
+
+void nsWindow::FinishLiveResizing(ResizeState aNewState) {
+ if (mResizeState == RESIZING) {
+ NotifyLiveResizeStopped();
+ }
+ mResizeState = aNewState;
+ ForcePresent();
+}
+
+/**************************************************************
+ *
+ * SECTION: Broadcast messaging
+ *
+ * Broadcast messages to all windows.
+ *
+ **************************************************************/
+
+// Enumerate all child windows sending aMsg to each of them
+BOOL CALLBACK nsWindow::BroadcastMsgToChildren(HWND aWnd, LPARAM aMsg) {
+ WNDPROC winProc = (WNDPROC)::GetWindowLongPtrW(aWnd, GWLP_WNDPROC);
+ if (winProc == &nsWindow::WindowProc) {
+ // it's one of our windows so go ahead and send a message to it
+ ::CallWindowProcW(winProc, aWnd, aMsg, 0, 0);
+ }
+ return TRUE;
+}
+
+// Enumerate all top level windows specifying that the children of each
+// top level window should be enumerated. Do *not* send the message to
+// each top level window since it is assumed that the toolkit will send
+// aMsg to them directly.
+BOOL CALLBACK nsWindow::BroadcastMsg(HWND aTopWindow, LPARAM aMsg) {
+ // Iterate each of aTopWindows child windows sending the aMsg
+ // to each of them.
+ ::EnumChildWindows(aTopWindow, nsWindow::BroadcastMsgToChildren, aMsg);
+ return TRUE;
+}
+
+/**************************************************************
+ *
+ * SECTION: Event processing helpers
+ *
+ * Special processing for certain event types and
+ * synthesized events.
+ *
+ **************************************************************/
+
+int32_t nsWindow::ClientMarginHitTestPoint(int32_t mx, int32_t my) {
+ if (mSizeMode == nsSizeMode_Minimized || mSizeMode == nsSizeMode_Fullscreen) {
+ return HTCLIENT;
+ }
+
+ // Calculations are done in screen coords
+ RECT winRect;
+ GetWindowRect(mWnd, &winRect);
+
+ // hit return constants:
+ // HTBORDER - non-resizable border
+ // HTBOTTOM, HTLEFT, HTRIGHT, HTTOP - resizable border
+ // HTBOTTOMLEFT, HTBOTTOMRIGHT - resizable corner
+ // HTTOPLEFT, HTTOPRIGHT - resizable corner
+ // HTCAPTION - general title bar area
+ // HTCLIENT - area considered the client
+ // HTCLOSE - hovering over the close button
+ // HTMAXBUTTON - maximize button
+ // HTMINBUTTON - minimize button
+
+ int32_t testResult = HTCLIENT;
+
+ bool isResizable = (mBorderStyle & (eBorderStyle_all | eBorderStyle_resizeh |
+ eBorderStyle_default)) > 0
+ ? true
+ : false;
+ if (mSizeMode == nsSizeMode_Maximized) isResizable = false;
+
+ // Ensure being accessible to borders of window. Even if contents are in
+ // this area, the area must behave as border.
+ nsIntMargin nonClientSize(
+ std::max(mCaptionHeight - mNonClientOffset.top, kResizableBorderMinSize),
+ std::max(mHorResizeMargin - mNonClientOffset.right,
+ kResizableBorderMinSize),
+ std::max(mVertResizeMargin - mNonClientOffset.bottom,
+ kResizableBorderMinSize),
+ std::max(mHorResizeMargin - mNonClientOffset.left,
+ kResizableBorderMinSize));
+
+ bool allowContentOverride = mSizeMode == nsSizeMode_Maximized ||
+ (mx >= winRect.left + nonClientSize.left &&
+ mx <= winRect.right - nonClientSize.right &&
+ my >= winRect.top + nonClientSize.top &&
+ my <= winRect.bottom - nonClientSize.bottom);
+
+ // The border size. If there is no content under mouse cursor, the border
+ // size should be larger than the values in system settings. Otherwise,
+ // contents under the mouse cursor should be able to override the behavior.
+ // E.g., user must expect that Firefox button always opens the popup menu
+ // even when the user clicks on the above edge of it.
+ nsIntMargin borderSize(std::max(nonClientSize.top, mVertResizeMargin),
+ std::max(nonClientSize.right, mHorResizeMargin),
+ std::max(nonClientSize.bottom, mVertResizeMargin),
+ std::max(nonClientSize.left, mHorResizeMargin));
+
+ bool top = false;
+ bool bottom = false;
+ bool left = false;
+ bool right = false;
+
+ if (my >= winRect.top && my < winRect.top + borderSize.top) {
+ top = true;
+ } else if (my <= winRect.bottom && my > winRect.bottom - borderSize.bottom) {
+ bottom = true;
+ }
+
+ // (the 2x case here doubles the resize area for corners)
+ int multiplier = (top || bottom) ? 2 : 1;
+ if (mx >= winRect.left &&
+ mx < winRect.left + (multiplier * borderSize.left)) {
+ left = true;
+ } else if (mx <= winRect.right &&
+ mx > winRect.right - (multiplier * borderSize.right)) {
+ right = true;
+ }
+
+ if (isResizable) {
+ if (top) {
+ testResult = HTTOP;
+ if (left)
+ testResult = HTTOPLEFT;
+ else if (right)
+ testResult = HTTOPRIGHT;
+ } else if (bottom) {
+ testResult = HTBOTTOM;
+ if (left)
+ testResult = HTBOTTOMLEFT;
+ else if (right)
+ testResult = HTBOTTOMRIGHT;
+ } else {
+ if (left) testResult = HTLEFT;
+ if (right) testResult = HTRIGHT;
+ }
+ } else {
+ if (top)
+ testResult = HTCAPTION;
+ else if (bottom || left || right)
+ testResult = HTBORDER;
+ }
+
+ if (!sIsInMouseCapture && allowContentOverride) {
+ POINT pt = {mx, my};
+ ::ScreenToClient(mWnd, &pt);
+
+ if (pt.x == mCachedHitTestPoint.x && pt.y == mCachedHitTestPoint.y &&
+ TimeStamp::Now() - mCachedHitTestTime <
+ TimeDuration::FromMilliseconds(HITTEST_CACHE_LIFETIME_MS)) {
+ return mCachedHitTestResult;
+ }
+
+ mCachedHitTestPoint = {pt.x, pt.y};
+ mCachedHitTestTime = TimeStamp::Now();
+
+ if (mDraggableRegion.Contains(pt.x, pt.y)) {
+ testResult = HTCAPTION;
+ } else {
+ testResult = HTCLIENT;
+ }
+ mCachedHitTestResult = testResult;
+ }
+
+ return testResult;
+}
+
+bool nsWindow::WithinDraggableRegion(int32_t screenX, int32_t screenY) {
+ return ClientMarginHitTestPoint(screenX, screenY) == HTCAPTION;
+}
+
+TimeStamp nsWindow::GetMessageTimeStamp(LONG aEventTime) const {
+ CurrentWindowsTimeGetter getCurrentTime(mWnd);
+ return TimeConverter().GetTimeStampFromSystemTime(aEventTime, getCurrentTime);
+}
+
+void nsWindow::PostSleepWakeNotification(const bool aIsSleepMode) {
+ if (aIsSleepMode == gIsSleepMode) return;
+
+ gIsSleepMode = aIsSleepMode;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->NotifyObservers(nullptr,
+ aIsSleepMode
+ ? NS_WIDGET_SLEEP_OBSERVER_TOPIC
+ : NS_WIDGET_WAKE_OBSERVER_TOPIC,
+ nullptr);
+}
+
+LRESULT nsWindow::ProcessCharMessage(const MSG& aMsg, bool* aEventDispatched) {
+ if (IMEHandler::IsComposingOn(this)) {
+ IMEHandler::NotifyIME(this, REQUEST_TO_COMMIT_COMPOSITION);
+ }
+ // These must be checked here too as a lone WM_CHAR could be received
+ // if a child window didn't handle it (for example Alt+Space in a content
+ // window)
+ ModifierKeyState modKeyState;
+ NativeKey nativeKey(this, aMsg, modKeyState);
+ return static_cast<LRESULT>(nativeKey.HandleCharMessage(aEventDispatched));
+}
+
+LRESULT nsWindow::ProcessKeyUpMessage(const MSG& aMsg, bool* aEventDispatched) {
+ ModifierKeyState modKeyState;
+ NativeKey nativeKey(this, aMsg, modKeyState);
+ bool result = nativeKey.HandleKeyUpMessage(aEventDispatched);
+ if (aMsg.wParam == VK_F10) {
+ // Bug 1382199: Windows default behavior will trigger the System menu bar
+ // when F10 is released. Among other things, this causes the System menu bar
+ // to appear when a web page overrides the contextmenu event. We *never*
+ // want this default behavior, so eat this key (never pass it to Windows).
+ return true;
+ }
+ return result;
+}
+
+LRESULT nsWindow::ProcessKeyDownMessage(const MSG& aMsg,
+ bool* aEventDispatched) {
+ // If this method doesn't call NativeKey::HandleKeyDownMessage(), this method
+ // must clean up the redirected message information itself. For more
+ // information, see above comment of
+ // RedirectedKeyDownMessageManager::AutoFlusher class definition in
+ // KeyboardLayout.h.
+ RedirectedKeyDownMessageManager::AutoFlusher redirectedMsgFlusher(this, aMsg);
+
+ ModifierKeyState modKeyState;
+
+ NativeKey nativeKey(this, aMsg, modKeyState);
+ LRESULT result =
+ static_cast<LRESULT>(nativeKey.HandleKeyDownMessage(aEventDispatched));
+ // HandleKeyDownMessage cleaned up the redirected message information
+ // itself, so, we should do nothing.
+ redirectedMsgFlusher.Cancel();
+
+ if (aMsg.wParam == VK_MENU ||
+ (aMsg.wParam == VK_F10 && !modKeyState.IsShift())) {
+ // We need to let Windows handle this keypress,
+ // by returning false, if there's a native menu
+ // bar somewhere in our containing window hierarchy.
+ // Otherwise we handle the keypress and don't pass
+ // it on to Windows, by returning true.
+ bool hasNativeMenu = false;
+ HWND hWnd = mWnd;
+ while (hWnd) {
+ if (::GetMenu(hWnd)) {
+ hasNativeMenu = true;
+ break;
+ }
+ hWnd = ::GetParent(hWnd);
+ }
+ result = !hasNativeMenu;
+ }
+
+ return result;
+}
+
+nsresult nsWindow::SynthesizeNativeKeyEvent(
+ int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode,
+ uint32_t aModifierFlags, const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "keyevent");
+
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+ return keyboardLayout->SynthesizeNativeKeyEvent(
+ this, aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags, aCharacters,
+ aUnmodifiedCharacters);
+}
+
+nsresult nsWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ if (aNativeMessage == MOUSEEVENTF_MOVE) {
+ // Reset sLastMouseMovePoint so that even if we're moving the mouse
+ // to the position it's already at, we still dispatch a mousemove
+ // event, because the callers of this function expect that.
+ sLastMouseMovePoint = {0};
+ }
+ ::SetCursorPos(aPoint.x, aPoint.y);
+
+ INPUT input;
+ memset(&input, 0, sizeof(input));
+
+ input.type = INPUT_MOUSE;
+ input.mi.dwFlags = aNativeMessage;
+ ::SendInput(1, &input, sizeof(INPUT));
+
+ return NS_OK;
+}
+
+nsresult nsWindow::SynthesizeNativeMouseScrollEvent(
+ LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
+ double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mousescrollevent");
+ return MouseScrollHandler::SynthesizeNativeMouseScrollEvent(
+ this, aPoint, aNativeMessage,
+ (aNativeMessage == WM_MOUSEWHEEL || aNativeMessage == WM_VSCROLL)
+ ? static_cast<int32_t>(aDeltaY)
+ : static_cast<int32_t>(aDeltaX),
+ aModifierFlags, aAdditionalFlags);
+}
+
+/**************************************************************
+ *
+ * SECTION: OnXXX message handlers
+ *
+ * For message handlers that need to be broken out or
+ * implemented in specific platform code.
+ *
+ **************************************************************/
+
+void nsWindow::OnWindowPosChanged(WINDOWPOS* wp) {
+ if (wp == nullptr) return;
+
+#ifdef WINSTATE_DEBUG_OUTPUT
+ if (mWnd == WinUtils::GetTopLevelHWND(mWnd)) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [ top] "));
+ } else {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [child] "));
+ }
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("WINDOWPOS flags:"));
+ if (wp->flags & SWP_FRAMECHANGED) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_FRAMECHANGED "));
+ }
+ if (wp->flags & SWP_SHOWWINDOW) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_SHOWWINDOW "));
+ }
+ if (wp->flags & SWP_NOSIZE) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOSIZE "));
+ }
+ if (wp->flags & SWP_HIDEWINDOW) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_HIDEWINDOW "));
+ }
+ if (wp->flags & SWP_NOZORDER) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOZORDER "));
+ }
+ if (wp->flags & SWP_NOACTIVATE) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOACTIVATE "));
+ }
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("\n"));
+#endif
+
+ // Handle window size mode changes
+ if (wp->flags & SWP_FRAMECHANGED && mSizeMode != nsSizeMode_Fullscreen) {
+ // Bug 566135 - Windows theme code calls show window on SW_SHOWMINIMIZED
+ // windows when fullscreen games disable desktop composition. If we're
+ // minimized and not being activated, ignore the event and let windows
+ // handle it.
+ if (mSizeMode == nsSizeMode_Minimized && (wp->flags & SWP_NOACTIVATE))
+ return;
+
+ WINDOWPLACEMENT pl;
+ pl.length = sizeof(pl);
+ ::GetWindowPlacement(mWnd, &pl);
+
+ nsSizeMode previousSizeMode = mSizeMode;
+
+ // Windows has just changed the size mode of this window. The call to
+ // SizeModeChanged will trigger a call into SetSizeMode where we will
+ // set the min/max window state again or for nsSizeMode_Normal, call
+ // SetWindow with a parameter of SW_RESTORE. There's no need however as
+ // this window's mode has already changed. Updating mSizeMode here
+ // insures the SetSizeMode call is a no-op. Addresses a bug on Win7 related
+ // to window docking. (bug 489258)
+ if (pl.showCmd == SW_SHOWMAXIMIZED)
+ mSizeMode =
+ (mFullscreenMode ? nsSizeMode_Fullscreen : nsSizeMode_Maximized);
+ else if (pl.showCmd == SW_SHOWMINIMIZED)
+ mSizeMode = nsSizeMode_Minimized;
+ else if (mFullscreenMode)
+ mSizeMode = nsSizeMode_Fullscreen;
+ else
+ mSizeMode = nsSizeMode_Normal;
+
+#ifdef WINSTATE_DEBUG_OUTPUT
+ switch (mSizeMode) {
+ case nsSizeMode_Normal:
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** mSizeMode: nsSizeMode_Normal\n"));
+ break;
+ case nsSizeMode_Minimized:
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** mSizeMode: nsSizeMode_Minimized\n"));
+ break;
+ case nsSizeMode_Maximized:
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** mSizeMode: nsSizeMode_Maximized\n"));
+ break;
+ default:
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** mSizeMode: ??????\n"));
+ break;
+ }
+#endif
+
+ if (mSizeMode != previousSizeMode) OnSizeModeChange(mSizeMode);
+
+ // If window was restored, window activation was bypassed during the
+ // SetSizeMode call originating from OnWindowPosChanging to avoid saving
+ // pre-restore attributes. Force activation now to get correct attributes.
+ if (mLastSizeMode != nsSizeMode_Normal && mSizeMode == nsSizeMode_Normal)
+ DispatchFocusToTopLevelWindow(true);
+
+ mLastSizeMode = mSizeMode;
+
+ // Skip window size change events below on minimization.
+ if (mSizeMode == nsSizeMode_Minimized) return;
+ }
+
+ // Handle window position changes
+ if (!(wp->flags & SWP_NOMOVE)) {
+ mBounds.MoveTo(wp->x, wp->y);
+ NotifyWindowMoved(wp->x, wp->y);
+ }
+
+ // Handle window size changes
+ if (!(wp->flags & SWP_NOSIZE)) {
+ RECT r;
+ int32_t newWidth, newHeight;
+
+ ::GetWindowRect(mWnd, &r);
+
+ newWidth = r.right - r.left;
+ newHeight = r.bottom - r.top;
+
+ if (newWidth > mLastSize.width) {
+ RECT drect;
+
+ // getting wider
+ drect.left = wp->x + mLastSize.width;
+ drect.top = wp->y;
+ drect.right = drect.left + (newWidth - mLastSize.width);
+ drect.bottom = drect.top + newHeight;
+
+ ::RedrawWindow(mWnd, &drect, nullptr,
+ RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT |
+ RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+ if (newHeight > mLastSize.height) {
+ RECT drect;
+
+ // getting taller
+ drect.left = wp->x;
+ drect.top = wp->y + mLastSize.height;
+ drect.right = drect.left + newWidth;
+ drect.bottom = drect.top + (newHeight - mLastSize.height);
+
+ ::RedrawWindow(mWnd, &drect, nullptr,
+ RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT |
+ RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+
+ mBounds.SizeTo(newWidth, newHeight);
+ mLastSize.width = newWidth;
+ mLastSize.height = newHeight;
+
+#ifdef WINSTATE_DEBUG_OUTPUT
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** Resize window: %d x %d x %d x %d\n", wp->x, wp->y, newWidth,
+ newHeight));
+#endif
+
+ if (mAspectRatio > 0) {
+ // It's possible (via Windows Aero Snap) that the size of the window
+ // has changed such that it violates the aspect ratio constraint. If so,
+ // queue up an event to enforce the aspect ratio constraint and repaint.
+ // When resized with Windows Aero Snap, we are in the NOT_RESIZING state.
+ float newAspectRatio = (float)newHeight / newWidth;
+ if (mResizeState == NOT_RESIZING && mAspectRatio != newAspectRatio) {
+ // Hold a reference to self alive and pass it into the lambda to make
+ // sure this nsIWidget stays alive long enough to run this function.
+ nsCOMPtr<nsIWidget> self(this);
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "EnforceAspectRatio", [self, this, newWidth]() -> void {
+ if (mWnd) {
+ Resize(newWidth, newWidth * mAspectRatio, true);
+ }
+ }));
+ }
+ }
+
+ // If a maximized window is resized, recalculate the non-client margins.
+ if (mSizeMode == nsSizeMode_Maximized) {
+ if (UpdateNonClientMargins(nsSizeMode_Maximized, true)) {
+ // gecko resize event already sent by UpdateNonClientMargins.
+ return;
+ }
+ }
+ }
+
+ // Notify the widget listener for size change of client area for gecko
+ // events. This needs to be done when either window size is changed,
+ // or window frame is changed. They may not happen together.
+ // However, we don't invoke that for popup when window frame changes,
+ // because popups may trigger frame change before size change via
+ // {Set,Clear}ThemeRegion they invoke in Resize. That would make the
+ // code below call OnResize with a wrong client size first, which can
+ // lead to flickerling for some popups.
+ if (!(wp->flags & SWP_NOSIZE) ||
+ ((wp->flags & SWP_FRAMECHANGED) && !IsPopup())) {
+ RECT r;
+ LayoutDeviceIntSize clientSize;
+ if (::GetClientRect(mWnd, &r)) {
+ clientSize = WinUtils::ToIntRect(r).Size();
+ } else {
+ clientSize = mBounds.Size();
+ }
+ // Send a gecko resize event
+ OnResize(clientSize);
+ }
+}
+
+void nsWindow::OnWindowPosChanging(LPWINDOWPOS& info) {
+ // Update non-client margins if the frame size is changing, and let the
+ // browser know we are changing size modes, so alternative css can kick in.
+ // If we're going into fullscreen mode, ignore this, since it'll reset
+ // margins to normal mode.
+ if ((info->flags & SWP_FRAMECHANGED && !(info->flags & SWP_NOSIZE)) &&
+ mSizeMode != nsSizeMode_Fullscreen) {
+ WINDOWPLACEMENT pl;
+ pl.length = sizeof(pl);
+ ::GetWindowPlacement(mWnd, &pl);
+ nsSizeMode sizeMode;
+ if (pl.showCmd == SW_SHOWMAXIMIZED)
+ sizeMode =
+ (mFullscreenMode ? nsSizeMode_Fullscreen : nsSizeMode_Maximized);
+ else if (pl.showCmd == SW_SHOWMINIMIZED)
+ sizeMode = nsSizeMode_Minimized;
+ else if (mFullscreenMode)
+ sizeMode = nsSizeMode_Fullscreen;
+ else
+ sizeMode = nsSizeMode_Normal;
+
+ OnSizeModeChange(sizeMode);
+
+ UpdateNonClientMargins(sizeMode, false);
+ }
+
+ // Force fullscreen. This works around a bug in Windows 10 1809 where
+ // using fullscreen when a window is "snapped" causes a spurious resize
+ // smaller than the full screen, see bug 1482920.
+ if (mSizeMode == nsSizeMode_Fullscreen && !(info->flags & SWP_NOMOVE) &&
+ !(info->flags & SWP_NOSIZE)) {
+ nsCOMPtr<nsIScreenManager> screenmgr =
+ do_GetService(sScreenManagerContractID);
+ if (screenmgr) {
+ LayoutDeviceIntRect bounds(info->x, info->y, info->cx, info->cy);
+ DesktopIntRect deskBounds =
+ RoundedToInt(bounds / GetDesktopToDeviceScale());
+ nsCOMPtr<nsIScreen> screen;
+ screenmgr->ScreenForRect(deskBounds.X(), deskBounds.Y(),
+ deskBounds.Width(), deskBounds.Height(),
+ getter_AddRefs(screen));
+
+ if (screen) {
+ int32_t x, y, width, height;
+ screen->GetRect(&x, &y, &width, &height);
+
+ info->x = x;
+ info->y = y;
+ info->cx = width;
+ info->cy = height;
+ }
+ }
+ }
+
+ // enforce local z-order rules
+ if (!(info->flags & SWP_NOZORDER)) {
+ HWND hwndAfter = info->hwndInsertAfter;
+
+ nsWindow* aboveWindow = 0;
+ nsWindowZ placement;
+
+ if (hwndAfter == HWND_BOTTOM)
+ placement = nsWindowZBottom;
+ else if (hwndAfter == HWND_TOP || hwndAfter == HWND_TOPMOST ||
+ hwndAfter == HWND_NOTOPMOST)
+ placement = nsWindowZTop;
+ else {
+ placement = nsWindowZRelative;
+ aboveWindow = WinUtils::GetNSWindowPtr(hwndAfter);
+ }
+
+ if (mWidgetListener) {
+ nsCOMPtr<nsIWidget> actualBelow = nullptr;
+ if (mWidgetListener->ZLevelChanged(false, &placement, aboveWindow,
+ getter_AddRefs(actualBelow))) {
+ if (placement == nsWindowZBottom)
+ info->hwndInsertAfter = HWND_BOTTOM;
+ else if (placement == nsWindowZTop)
+ info->hwndInsertAfter = HWND_TOP;
+ else {
+ info->hwndInsertAfter =
+ (HWND)actualBelow->GetNativeData(NS_NATIVE_WINDOW);
+ }
+ }
+ }
+ }
+ // prevent rude external programs from making hidden window visible
+ if (mWindowType == eWindowType_invisible) info->flags &= ~SWP_SHOWWINDOW;
+
+ // When waking from sleep or switching out of tablet mode, Windows 10
+ // Version 1809 will reopen popup windows that should be hidden. Detect
+ // this case and refuse to show the window.
+ static bool sDWMUnhidesPopups = IsWin10Sep2018UpdateOrLater();
+ if (sDWMUnhidesPopups && (info->flags & SWP_SHOWWINDOW) &&
+ mWindowType == eWindowType_popup && mWidgetListener &&
+ mWidgetListener->ShouldNotBeVisible()) {
+ info->flags &= ~SWP_SHOWWINDOW;
+ }
+}
+
+void nsWindow::UserActivity() {
+ // Check if we have the idle service, if not we try to get it.
+ if (!mIdleService) {
+ mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1");
+ }
+
+ // Check that we now have the idle service.
+ if (mIdleService) {
+ mIdleService->ResetIdleTimeOut(0);
+ }
+}
+
+nsIntPoint nsWindow::GetTouchCoordinates(WPARAM wParam, LPARAM lParam) {
+ nsIntPoint ret;
+ uint32_t cInputs = LOWORD(wParam);
+ if (cInputs != 1) {
+ // Just return 0,0 if there isn't exactly one touch point active
+ return ret;
+ }
+ PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
+ if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs,
+ sizeof(TOUCHINPUT))) {
+ ret.x = TOUCH_COORD_TO_PIXEL(pInputs[0].x);
+ ret.y = TOUCH_COORD_TO_PIXEL(pInputs[0].y);
+ }
+ delete[] pInputs;
+ // Note that we don't call CloseTouchInputHandle here because we need
+ // to read the touch input info again in OnTouch later.
+ return ret;
+}
+
+// Determine if the touch device that originated |aOSEvent| needs to have
+// touch events representing a two-finger gesture converted to pan
+// gesture events.
+// We only do this for touch devices with a specific name and identifiers.
+bool TouchDeviceNeedsPanGestureConversion(PTOUCHINPUT aOSEvent,
+ uint32_t aTouchCount) {
+ if (aTouchCount == 0) {
+ return false;
+ }
+ HANDLE source = aOSEvent[0].hSource;
+ std::string deviceName;
+ UINT dataSize = 0;
+ // The first call just queries how long the name string will be.
+ GetRawInputDeviceInfoA(source, RIDI_DEVICENAME, nullptr, &dataSize);
+ if (!dataSize || dataSize > 0x10000) {
+ return false;
+ }
+ deviceName.resize(dataSize);
+ // The second call actually populates the string.
+ UINT result = GetRawInputDeviceInfoA(source, RIDI_DEVICENAME, &deviceName[0],
+ &dataSize);
+ if (result == UINT_MAX) {
+ return false;
+ }
+ // The affected device name is "\\?\VIRTUAL_DIGITIZER", but each backslash
+ // needs to be escaped with another one.
+ std::string expectedDeviceName = "\\\\?\\VIRTUAL_DIGITIZER";
+ // For some reason, the dataSize returned by the first call is double the
+ // actual length of the device name (as if it were returning the size of a
+ // wide-character string in bytes) even though we are using the narrow
+ // version of the API. For the comparison against the expected device name
+ // to pass, we truncate the buffer to be no longer tha the expected device
+ // name.
+ if (deviceName.substr(0, expectedDeviceName.length()) != expectedDeviceName) {
+ return false;
+ }
+
+ RID_DEVICE_INFO deviceInfo;
+ deviceInfo.cbSize = sizeof(deviceInfo);
+ dataSize = sizeof(deviceInfo);
+ result =
+ GetRawInputDeviceInfoA(source, RIDI_DEVICEINFO, &deviceInfo, &dataSize);
+ if (result == UINT_MAX) {
+ return false;
+ }
+ // The device identifiers that we check for here come from bug 1355162
+ // comment 1 (see also bug 1511901 comment 35).
+ return deviceInfo.dwType == RIM_TYPEHID && deviceInfo.hid.dwVendorId == 0 &&
+ deviceInfo.hid.dwProductId == 0 &&
+ deviceInfo.hid.dwVersionNumber == 1 &&
+ deviceInfo.hid.usUsagePage == 13 && deviceInfo.hid.usUsage == 4;
+}
+
+Maybe<PanGestureInput> nsWindow::ConvertTouchToPanGesture(
+ const MultiTouchInput& aTouchInput, PTOUCHINPUT aOSEvent) {
+ // The first time this function is called, perform some checks on the
+ // touch device that originated the touch event, to see if it's a device
+ // for which we want to convert the touch events to pang gesture events.
+ static bool shouldConvert = TouchDeviceNeedsPanGestureConversion(
+ aOSEvent, aTouchInput.mTouches.Length());
+ if (!shouldConvert) {
+ return Nothing();
+ }
+
+ // Only two-finger gestures need conversion.
+ if (aTouchInput.mTouches.Length() != 2) {
+ return Nothing();
+ }
+
+ PanGestureInput::PanGestureType eventType = PanGestureInput::PANGESTURE_PAN;
+ if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_START) {
+ eventType = PanGestureInput::PANGESTURE_START;
+ } else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_END) {
+ eventType = PanGestureInput::PANGESTURE_END;
+ } else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
+ eventType = PanGestureInput::PANGESTURE_CANCELLED;
+ }
+
+ // Use the midpoint of the two touches as the start point of the pan gesture.
+ ScreenPoint focusPoint = (aTouchInput.mTouches[0].mScreenPoint +
+ aTouchInput.mTouches[1].mScreenPoint) /
+ 2;
+ // To compute the displacement of the pan gesture, we keep track of the
+ // location of the previous event.
+ ScreenPoint displacement = (eventType == PanGestureInput::PANGESTURE_START)
+ ? ScreenPoint(0, 0)
+ : (focusPoint - mLastPanGestureFocus);
+ mLastPanGestureFocus = focusPoint;
+
+ // We need to negate the displacement because for a touch event, moving the
+ // fingers down results in scrolling up, but for a touchpad gesture, we want
+ // moving the fingers down to result in scrolling down.
+ PanGestureInput result(eventType, aTouchInput.mTime, aTouchInput.mTimeStamp,
+ focusPoint, -displacement, aTouchInput.modifiers);
+ result.mSimulateMomentum = true;
+
+ return Some(result);
+}
+
+// Dispatch an event that originated as an OS touch event.
+// Usually, we want to dispatch it as a touch event, but some touchpads
+// produce touch events for two-finger scrolling, which need to be converted
+// to pan gesture events for correct behaviour.
+void nsWindow::DispatchTouchOrPanGestureInput(MultiTouchInput& aTouchInput,
+ PTOUCHINPUT aOSEvent) {
+ if (Maybe<PanGestureInput> panInput =
+ ConvertTouchToPanGesture(aTouchInput, aOSEvent)) {
+ DispatchPanGestureInput(*panInput);
+ return;
+ }
+
+ DispatchTouchInput(aTouchInput);
+}
+
+bool nsWindow::OnTouch(WPARAM wParam, LPARAM lParam) {
+ uint32_t cInputs = LOWORD(wParam);
+ PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
+
+ if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs,
+ sizeof(TOUCHINPUT))) {
+ MultiTouchInput touchInput, touchEndInput;
+
+ // Walk across the touch point array processing each contact point.
+ for (uint32_t i = 0; i < cInputs; i++) {
+ bool addToEvent = false, addToEndEvent = false;
+
+ // N.B.: According with MS documentation
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317334(v=vs.85).aspx
+ // TOUCHEVENTF_DOWN cannot be combined with TOUCHEVENTF_MOVE or
+ // TOUCHEVENTF_UP. Possibly, it means that TOUCHEVENTF_MOVE and
+ // TOUCHEVENTF_UP can be combined together.
+
+ if (pInputs[i].dwFlags & (TOUCHEVENTF_DOWN | TOUCHEVENTF_MOVE)) {
+ if (touchInput.mTimeStamp.IsNull()) {
+ // Initialize a touch event to send.
+ touchInput.mType = MultiTouchInput::MULTITOUCH_MOVE;
+ touchInput.mTime = ::GetMessageTime();
+ touchInput.mTimeStamp = GetMessageTimeStamp(touchInput.mTime);
+ ModifierKeyState modifierKeyState;
+ touchInput.modifiers = modifierKeyState.GetModifiers();
+ }
+ // Pres shell expects this event to be a eTouchStart
+ // if any new contact points have been added since the last event sent.
+ if (pInputs[i].dwFlags & TOUCHEVENTF_DOWN) {
+ touchInput.mType = MultiTouchInput::MULTITOUCH_START;
+ }
+ addToEvent = true;
+ }
+ if (pInputs[i].dwFlags & TOUCHEVENTF_UP) {
+ // Pres shell expects removed contacts points to be delivered in a
+ // separate eTouchEnd event containing only the contact points that were
+ // removed.
+ if (touchEndInput.mTimeStamp.IsNull()) {
+ // Initialize a touch event to send.
+ touchEndInput.mType = MultiTouchInput::MULTITOUCH_END;
+ touchEndInput.mTime = ::GetMessageTime();
+ touchEndInput.mTimeStamp = GetMessageTimeStamp(touchEndInput.mTime);
+ ModifierKeyState modifierKeyState;
+ touchEndInput.modifiers = modifierKeyState.GetModifiers();
+ }
+ addToEndEvent = true;
+ }
+ if (!addToEvent && !addToEndEvent) {
+ // Filter out spurious Windows events we don't understand, like palm
+ // contact.
+ continue;
+ }
+
+ // Setup the touch point we'll append to the touch event array.
+ nsPointWin touchPoint;
+ touchPoint.x = TOUCH_COORD_TO_PIXEL(pInputs[i].x);
+ touchPoint.y = TOUCH_COORD_TO_PIXEL(pInputs[i].y);
+ touchPoint.ScreenToClient(mWnd);
+
+ // Initialize the touch data.
+ SingleTouchData touchData(
+ pInputs[i].dwID, // aIdentifier
+ ScreenIntPoint::FromUnknownPoint(touchPoint), // aScreenPoint
+ /* radius, if known */
+ pInputs[i].dwMask & TOUCHINPUTMASKF_CONTACTAREA
+ ? ScreenSize(TOUCH_COORD_TO_PIXEL(pInputs[i].cxContact) / 2,
+ TOUCH_COORD_TO_PIXEL(pInputs[i].cyContact) / 2)
+ : ScreenSize(1, 1), // aRadius
+ 0.0f, // aRotationAngle
+ 0.0f); // aForce
+
+ // Append touch data to the appropriate event.
+ if (addToEvent) {
+ touchInput.mTouches.AppendElement(touchData);
+ }
+ if (addToEndEvent) {
+ touchEndInput.mTouches.AppendElement(touchData);
+ }
+ }
+
+ // Dispatch touch start and touch move event if we have one.
+ if (!touchInput.mTimeStamp.IsNull()) {
+ DispatchTouchOrPanGestureInput(touchInput, pInputs);
+ }
+ // Dispatch touch end event if we have one.
+ if (!touchEndInput.mTimeStamp.IsNull()) {
+ DispatchTouchOrPanGestureInput(touchEndInput, pInputs);
+ }
+ }
+
+ delete[] pInputs;
+ CloseTouchInputHandle((HTOUCHINPUT)lParam);
+ return true;
+}
+
+// Gesture event processing. Handles WM_GESTURE events.
+bool nsWindow::OnGesture(WPARAM wParam, LPARAM lParam) {
+ // Treatment for pan events which translate into scroll events:
+ if (mGesture.IsPanEvent(lParam)) {
+ if (!mGesture.ProcessPanMessage(mWnd, wParam, lParam))
+ return false; // ignore
+
+ nsEventStatus status;
+
+ WidgetWheelEvent wheelEvent(true, eWheel, this);
+
+ ModifierKeyState modifierKeyState;
+ modifierKeyState.InitInputEvent(wheelEvent);
+
+ wheelEvent.mButton = 0;
+ wheelEvent.mTime = ::GetMessageTime();
+ wheelEvent.mTimeStamp = GetMessageTimeStamp(wheelEvent.mTime);
+ wheelEvent.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
+
+ bool endFeedback = true;
+
+ if (mGesture.PanDeltaToPixelScroll(wheelEvent)) {
+ mozilla::Telemetry::Accumulate(
+ mozilla::Telemetry::SCROLL_INPUT_METHODS,
+ (uint32_t)ScrollInputMethod::MainThreadTouch);
+ DispatchEvent(&wheelEvent, status);
+ }
+
+ if (mDisplayPanFeedback) {
+ mGesture.UpdatePanFeedbackX(
+ mWnd, DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaX)),
+ endFeedback);
+ mGesture.UpdatePanFeedbackY(
+ mWnd, DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaY)),
+ endFeedback);
+ mGesture.PanFeedbackFinalize(mWnd, endFeedback);
+ }
+
+ CloseGestureInfoHandle((HGESTUREINFO)lParam);
+
+ return true;
+ }
+
+ // Other gestures translate into simple gesture events:
+ WidgetSimpleGestureEvent event(true, eVoidEvent, this);
+ if (!mGesture.ProcessGestureMessage(mWnd, wParam, lParam, event)) {
+ return false; // fall through to DefWndProc
+ }
+
+ // Polish up and send off the new event
+ ModifierKeyState modifierKeyState;
+ modifierKeyState.InitInputEvent(event);
+ event.mButton = 0;
+ event.mTime = ::GetMessageTime();
+ event.mTimeStamp = GetMessageTimeStamp(event.mTime);
+ event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
+
+ nsEventStatus status;
+ DispatchEvent(&event, status);
+ if (status == nsEventStatus_eIgnore) {
+ return false; // Ignored, fall through
+ }
+
+ // Only close this if we process and return true.
+ CloseGestureInfoHandle((HGESTUREINFO)lParam);
+
+ return true; // Handled
+}
+
+nsresult nsWindow::ConfigureChildren(
+ const nsTArray<Configuration>& aConfigurations) {
+ // If this is a remotely updated widget we receive clipping, position, and
+ // size information from a source other than our owner. Don't let our parent
+ // update this information.
+ if (mWindowType == eWindowType_plugin_ipc_chrome) {
+ return NS_OK;
+ }
+
+ // XXXroc we could use BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos
+ // here, if that helps in some situations. So far I haven't seen a
+ // need.
+ for (uint32_t i = 0; i < aConfigurations.Length(); ++i) {
+ const Configuration& configuration = aConfigurations[i];
+ nsWindow* w = static_cast<nsWindow*>(configuration.mChild.get());
+ NS_ASSERTION(w->GetParent() == this, "Configured widget is not a child");
+ nsresult rv = w->SetWindowClipRegion(configuration.mClipRegion, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LayoutDeviceIntRect bounds = w->GetBounds();
+ if (bounds.Size() != configuration.mBounds.Size()) {
+ w->Resize(configuration.mBounds.X(), configuration.mBounds.Y(),
+ configuration.mBounds.Width(), configuration.mBounds.Height(),
+ true);
+ } else if (bounds.TopLeft() != configuration.mBounds.TopLeft()) {
+ w->Move(configuration.mBounds.X(), configuration.mBounds.Y());
+
+ if (gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend() ||
+ GetLayerManager()->GetBackendType() != LayersBackend::LAYERS_BASIC) {
+ // XXX - Workaround for Bug 587508. This will invalidate the part of the
+ // plugin window that might be touched by moving content somehow. The
+ // underlying problem should be found and fixed!
+ LayoutDeviceIntRegion r;
+ r.Sub(bounds, configuration.mBounds);
+ r.MoveBy(-bounds.X(), -bounds.Y());
+ LayoutDeviceIntRect toInvalidate = r.GetBounds();
+
+ WinUtils::InvalidatePluginAsWorkaround(w, toInvalidate);
+ }
+ }
+ rv = w->SetWindowClipRegion(configuration.mClipRegion, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+static HRGN CreateHRGNFromArray(const nsTArray<LayoutDeviceIntRect>& aRects) {
+ int32_t size = sizeof(RGNDATAHEADER) + sizeof(RECT) * aRects.Length();
+ AutoTArray<uint8_t, 100> buf;
+ buf.SetLength(size);
+ RGNDATA* data = reinterpret_cast<RGNDATA*>(buf.Elements());
+ RECT* rects = reinterpret_cast<RECT*>(data->Buffer);
+ data->rdh.dwSize = sizeof(data->rdh);
+ data->rdh.iType = RDH_RECTANGLES;
+ data->rdh.nCount = aRects.Length();
+ LayoutDeviceIntRect bounds;
+ for (uint32_t i = 0; i < aRects.Length(); ++i) {
+ const LayoutDeviceIntRect& r = aRects[i];
+ bounds.UnionRect(bounds, r);
+ ::SetRect(&rects[i], r.X(), r.Y(), r.XMost(), r.YMost());
+ }
+ ::SetRect(&data->rdh.rcBound, bounds.X(), bounds.Y(), bounds.XMost(),
+ bounds.YMost());
+ return ::ExtCreateRegion(nullptr, buf.Length(), data);
+}
+
+nsresult nsWindow::SetWindowClipRegion(
+ const nsTArray<LayoutDeviceIntRect>& aRects, bool aIntersectWithExisting) {
+ if (IsWindowClipRegionEqual(aRects)) {
+ return NS_OK;
+ }
+
+ nsBaseWidget::SetWindowClipRegion(aRects, aIntersectWithExisting);
+
+ HRGN dest = CreateHRGNFromArray(aRects);
+ if (!dest) return NS_ERROR_OUT_OF_MEMORY;
+
+ if (aIntersectWithExisting) {
+ HRGN current = ::CreateRectRgn(0, 0, 0, 0);
+ if (current) {
+ if (::GetWindowRgn(mWnd, current) != 0 /*ERROR*/) {
+ ::CombineRgn(dest, dest, current, RGN_AND);
+ }
+ ::DeleteObject(current);
+ }
+ }
+
+ // If a plugin is not visible, especially if it is in a background tab,
+ // it should not be able to steal keyboard focus. This code checks whether
+ // the region that the plugin is being clipped to is NULLREGION. If it is,
+ // the plugin window gets disabled.
+ if (IsPlugin()) {
+ if (NULLREGION == ::CombineRgn(dest, dest, dest, RGN_OR)) {
+ ::ShowWindow(mWnd, SW_HIDE);
+ ::EnableWindow(mWnd, FALSE);
+ } else {
+ ::EnableWindow(mWnd, TRUE);
+ ::ShowWindow(mWnd, SW_SHOW);
+ }
+ }
+ if (!::SetWindowRgn(mWnd, dest, TRUE)) {
+ ::DeleteObject(dest);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+// WM_DESTROY event handler
+void nsWindow::OnDestroy() {
+ mOnDestroyCalled = true;
+
+ // Make sure we don't get destroyed in the process of tearing down.
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ // Dispatch the destroy notification.
+ if (!mInDtor) NotifyWindowDestroyed();
+
+ // Prevent the widget from sending additional events.
+ mWidgetListener = nullptr;
+ mAttachedWidgetListener = nullptr;
+
+ DestroyDirectManipulation();
+
+ if (mWnd == mLastKillFocusWindow) {
+ mLastKillFocusWindow = nullptr;
+ }
+ // Unregister notifications from terminal services
+ ::WTSUnRegisterSessionNotification(mWnd);
+
+ // Free our subclass and clear |this| stored in the window props. We will no
+ // longer receive events from Windows after this point.
+ SubclassWindow(FALSE);
+
+ // Once mWidgetListener is cleared and the subclass is reset, sCurrentWindow
+ // can be cleared. (It's used in tracking windows for mouse events.)
+ if (sCurrentWindow == this) sCurrentWindow = nullptr;
+
+ // Disconnects us from our parent, will call our GetParent().
+ nsBaseWidget::Destroy();
+
+ // Release references to children, device context, toolkit, and app shell.
+ nsBaseWidget::OnDestroy();
+
+ // Clear our native parent handle.
+ // XXX Windows will take care of this in the proper order, and
+ // SetParent(nullptr)'s remove child on the parent already took place in
+ // nsBaseWidget's Destroy call above.
+ // SetParent(nullptr);
+ mParent = nullptr;
+
+ // We have to destroy the native drag target before we null out our window
+ // pointer.
+ EnableDragDrop(false);
+
+ // If we're going away and for some reason we're still the rollup widget,
+ // rollup and turn off capture.
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ nsCOMPtr<nsIWidget> rollupWidget;
+ if (rollupListener) {
+ rollupWidget = rollupListener->GetRollupWidget();
+ }
+ if (this == rollupWidget) {
+ if (rollupListener) rollupListener->Rollup(0, false, nullptr, nullptr);
+ CaptureRollupEvents(nullptr, false);
+ }
+
+ IMEHandler::OnDestroyWindow(this);
+
+ // Free GDI window class objects
+ if (mBrush) {
+ VERIFY(::DeleteObject(mBrush));
+ mBrush = nullptr;
+ }
+
+ // Destroy any custom cursor resources.
+ if (mCursor == eCursorInvalid) {
+ SetCursor(eCursor_standard, nullptr, 0, 0);
+ }
+
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->OnDestroyWindow();
+ }
+ mBasicLayersSurface = nullptr;
+
+ // Finalize panning feedback to possibly restore window displacement
+ mGesture.PanFeedbackFinalize(mWnd, true);
+
+ // Clear the main HWND.
+ mWnd = nullptr;
+}
+
+// Send a resize message to the listener
+bool nsWindow::OnResize(const LayoutDeviceIntSize& aSize) {
+ if (mCompositorWidgetDelegate &&
+ !mCompositorWidgetDelegate->OnWindowResize(aSize)) {
+ return false;
+ }
+
+ bool result = false;
+ if (mWidgetListener) {
+ result = mWidgetListener->WindowResized(this, aSize.width, aSize.height);
+ }
+
+ // If there is an attached view, inform it as well as the normal widget
+ // listener.
+ if (mAttachedWidgetListener) {
+ return mAttachedWidgetListener->WindowResized(this, aSize.width,
+ aSize.height);
+ }
+
+ return result;
+}
+
+void nsWindow::OnSizeModeChange(nsSizeMode aSizeMode) {
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->OnWindowModeChange(aSizeMode);
+ }
+
+ if (mWidgetListener) {
+ mWidgetListener->SizeModeChanged(aSizeMode);
+ }
+}
+
+bool nsWindow::OnHotKey(WPARAM wParam, LPARAM lParam) { return true; }
+
+// Can be overriden. Controls auto-erase of background.
+bool nsWindow::AutoErase(HDC dc) { return false; }
+
+bool nsWindow::IsPopup() { return mWindowType == eWindowType_popup; }
+
+bool nsWindow::ShouldUseOffMainThreadCompositing() {
+ if (IsSmallPopup()) {
+ return false;
+ }
+
+ return nsBaseWidget::ShouldUseOffMainThreadCompositing();
+}
+
+void nsWindow::WindowUsesOMTC() {
+ ULONG_PTR style = ::GetClassLongPtr(mWnd, GCL_STYLE);
+ if (!style) {
+ NS_WARNING("Could not get window class style");
+ return;
+ }
+ style |= CS_HREDRAW | CS_VREDRAW;
+ DebugOnly<ULONG_PTR> result = ::SetClassLongPtr(mWnd, GCL_STYLE, style);
+ NS_WARNING_ASSERTION(result, "Could not reset window class style");
+}
+
+bool nsWindow::HasBogusPopupsDropShadowOnMultiMonitor() {
+ if (sHasBogusPopupsDropShadowOnMultiMonitor == TRI_UNKNOWN) {
+ // Since any change in the preferences requires a restart, this can be
+ // done just once.
+ // Check for Direct2D first.
+ sHasBogusPopupsDropShadowOnMultiMonitor =
+ gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend() ? TRI_TRUE
+ : TRI_FALSE;
+ if (!sHasBogusPopupsDropShadowOnMultiMonitor) {
+ // Otherwise check if Direct3D 9 may be used.
+ if (gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING) &&
+ !gfxConfig::IsEnabled(gfx::Feature::OPENGL_COMPOSITING)) {
+ nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
+ if (gfxInfo) {
+ int32_t status;
+ nsCString discardFailureId;
+ if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(
+ nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, discardFailureId,
+ &status))) {
+ if (status == nsIGfxInfo::FEATURE_STATUS_OK ||
+ gfxConfig::IsForcedOnByUser(gfx::Feature::HW_COMPOSITING)) {
+ sHasBogusPopupsDropShadowOnMultiMonitor = TRI_TRUE;
+ }
+ }
+ }
+ }
+ }
+ }
+ return !!sHasBogusPopupsDropShadowOnMultiMonitor;
+}
+
+void nsWindow::OnDPIChanged(int32_t x, int32_t y, int32_t width,
+ int32_t height) {
+ // Don't try to handle WM_DPICHANGED for popup windows (see bug 1239353);
+ // they remain tied to their original parent's resolution.
+ if (mWindowType == eWindowType_popup) {
+ return;
+ }
+ if (StaticPrefs::layout_css_devPixelsPerPx() > 0.0) {
+ return;
+ }
+ mDefaultScale = -1.0; // force recomputation of scale factor
+
+ if (mResizeState != RESIZING && mSizeMode == nsSizeMode_Normal) {
+ // Limit the position (if not in the middle of a drag-move) & size,
+ // if it would overflow the destination screen
+ nsCOMPtr<nsIScreenManager> sm = do_GetService(sScreenManagerContractID);
+ if (sm) {
+ nsCOMPtr<nsIScreen> screen;
+ sm->ScreenForRect(x, y, width, height, getter_AddRefs(screen));
+ if (screen) {
+ int32_t availLeft, availTop, availWidth, availHeight;
+ screen->GetAvailRect(&availLeft, &availTop, &availWidth, &availHeight);
+ if (mResizeState != MOVING) {
+ x = std::max(x, availLeft);
+ y = std::max(y, availTop);
+ }
+ width = std::min(width, availWidth);
+ height = std::min(height, availHeight);
+ }
+ }
+
+ Resize(x, y, width, height, true);
+ }
+ UpdateNonClientMargins();
+ ChangedDPI();
+ ResetLayout();
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: IME management and accessibility
+ **
+ ** Handles managing IME input and accessibility.
+ **
+ **************************************************************
+ **************************************************************/
+
+void nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) {
+ InputContext newInputContext = aContext;
+ IMEHandler::SetInputContext(this, newInputContext, aAction);
+ mInputContext = newInputContext;
+}
+
+InputContext nsWindow::GetInputContext() {
+ mInputContext.mIMEState.mOpen = IMEState::CLOSED;
+ if (WinUtils::IsIMEEnabled(mInputContext) && IMEHandler::GetOpenState(this)) {
+ mInputContext.mIMEState.mOpen = IMEState::OPEN;
+ } else {
+ mInputContext.mIMEState.mOpen = IMEState::CLOSED;
+ }
+ return mInputContext;
+}
+
+TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
+ return IMEHandler::GetNativeTextEventDispatcherListener();
+}
+
+#ifdef ACCESSIBILITY
+# ifdef DEBUG
+# define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc) \
+ if (a11y::logging::IsEnabled(a11y::logging::ePlatforms)) { \
+ printf( \
+ "Get the window:\n {\n HWND: %p, parent HWND: %p, wndobj: " \
+ "%p,\n", \
+ aHwnd, ::GetParent(aHwnd), aWnd); \
+ printf(" acc: %p", aAcc); \
+ if (aAcc) { \
+ nsAutoString name; \
+ aAcc->Name(name); \
+ printf(", accname: %s", NS_ConvertUTF16toUTF8(name).get()); \
+ } \
+ printf("\n }\n"); \
+ }
+
+# else
+# define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc)
+# endif
+
+a11y::Accessible* nsWindow::GetAccessible() {
+ // If the pref was ePlatformIsDisabled, return null here, disabling a11y.
+ if (a11y::PlatformDisabledState() == a11y::ePlatformIsDisabled)
+ return nullptr;
+
+ if (mInDtor || mOnDestroyCalled || mWindowType == eWindowType_invisible) {
+ return nullptr;
+ }
+
+ // In case of popup window return a popup accessible.
+ nsView* view = nsView::GetViewFor(this);
+ if (view) {
+ nsIFrame* frame = view->GetFrame();
+ if (frame && nsLayoutUtils::IsPopup(frame)) {
+ nsAccessibilityService* accService = GetOrCreateAccService();
+ if (accService) {
+ a11y::DocAccessible* docAcc =
+ GetAccService()->GetDocAccessible(frame->PresShell());
+ if (docAcc) {
+ NS_LOG_WMGETOBJECT(
+ this, mWnd,
+ docAcc->GetAccessibleOrDescendant(frame->GetContent()));
+ return docAcc->GetAccessibleOrDescendant(frame->GetContent());
+ }
+ }
+ }
+ }
+
+ // otherwise root document accessible.
+ NS_LOG_WMGETOBJECT(this, mWnd, GetRootAccessible());
+ return GetRootAccessible();
+}
+#endif
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Transparency
+ **
+ ** Window transparency helpers.
+ **
+ **************************************************************
+ **************************************************************/
+
+#ifdef MOZ_XUL
+
+void nsWindow::SetWindowTranslucencyInner(nsTransparencyMode aMode) {
+ if (aMode == mTransparencyMode) return;
+
+ // stop on dialogs and popups!
+ HWND hWnd = WinUtils::GetTopLevelHWND(mWnd, true);
+ nsWindow* parent = WinUtils::GetNSWindowPtr(hWnd);
+
+ if (!parent) {
+ NS_WARNING("Trying to use transparent chrome in an embedded context");
+ return;
+ }
+
+ if (parent != this) {
+ NS_WARNING(
+ "Setting SetWindowTranslucencyInner on a parent this is not us!");
+ }
+
+ if (aMode == eTransparencyTransparent) {
+ // If we're switching to the use of a transparent window, hide the chrome
+ // on our parent.
+ HideWindowChrome(true);
+ } else if (mHideChrome && mTransparencyMode == eTransparencyTransparent) {
+ // if we're switching out of transparent, re-enable our parent's chrome.
+ HideWindowChrome(false);
+ }
+
+ LONG_PTR style = ::GetWindowLongPtrW(hWnd, GWL_STYLE),
+ exStyle = ::GetWindowLongPtr(hWnd, GWL_EXSTYLE);
+
+ if (parent->mIsVisible) {
+ style |= WS_VISIBLE;
+ if (parent->mSizeMode == nsSizeMode_Maximized) {
+ style |= WS_MAXIMIZE;
+ } else if (parent->mSizeMode == nsSizeMode_Minimized) {
+ style |= WS_MINIMIZE;
+ }
+ }
+
+ if (aMode == eTransparencyTransparent)
+ exStyle |= WS_EX_LAYERED;
+ else
+ exStyle &= ~WS_EX_LAYERED;
+
+ VERIFY_WINDOW_STYLE(style);
+ ::SetWindowLongPtrW(hWnd, GWL_STYLE, style);
+ ::SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exStyle);
+
+ if (HasGlass()) memset(&mGlassMargins, 0, sizeof mGlassMargins);
+ mTransparencyMode = aMode;
+
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->UpdateTransparency(aMode);
+ }
+ UpdateGlass();
+
+ // Clear window by transparent black when compositor window is used in GPU
+ // process and non-client area rendering by DWM is enabled.
+ // It is for showing non-client area rendering. See nsWindow::UpdateGlass().
+ if (HasGlass() && GetLayerManager()->AsKnowsCompositor() &&
+ GetLayerManager()->AsKnowsCompositor()->GetUseCompositorWnd()) {
+ HDC hdc;
+ RECT rect;
+ hdc = ::GetWindowDC(mWnd);
+ ::GetWindowRect(mWnd, &rect);
+ ::MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
+ ::FillRect(hdc, &rect,
+ reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)));
+ ReleaseDC(mWnd, hdc);
+ }
+
+ // Disable double buffering with D3D compositor for disabling compositor
+ // window usage.
+ if (HasGlass() && !gfxVars::UseWebRender() &&
+ gfxVars::UseDoubleBufferingWithCompositor()) {
+ gfxVars::SetUseDoubleBufferingWithCompositor(false);
+ GPUProcessManager::Get()->ResetCompositors();
+ }
+}
+
+#endif // MOZ_XUL
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Popup rollup hooks
+ **
+ ** Deals with CaptureRollup on popup windows.
+ **
+ **************************************************************
+ **************************************************************/
+
+// Schedules a timer for a window, so we can rollup after processing the hook
+// event
+void nsWindow::ScheduleHookTimer(HWND aWnd, UINT aMsgId) {
+ // In some cases multiple hooks may be scheduled
+ // so ignore any other requests once one timer is scheduled
+ if (sHookTimerId == 0) {
+ // Remember the window handle and the message ID to be used later
+ sRollupMsgId = aMsgId;
+ sRollupMsgWnd = aWnd;
+ // Schedule native timer for doing the rollup after
+ // this event is done being processed
+ sHookTimerId = ::SetTimer(nullptr, 0, 0, (TIMERPROC)HookTimerForPopups);
+ NS_ASSERTION(sHookTimerId, "Timer couldn't be created.");
+ }
+}
+
+#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
+int gLastMsgCode = 0;
+extern MSGFEventMsgInfo gMSGFEvents[];
+#endif
+
+// Process Menu messages, rollup when popup is clicked.
+LRESULT CALLBACK nsWindow::MozSpecialMsgFilter(int code, WPARAM wParam,
+ LPARAM lParam) {
+#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
+ if (sProcessHook) {
+ MSG* pMsg = (MSG*)lParam;
+
+ int inx = 0;
+ while (gMSGFEvents[inx].mId != code && gMSGFEvents[inx].mStr != nullptr) {
+ inx++;
+ }
+ if (code != gLastMsgCode) {
+ if (gMSGFEvents[inx].mId == code) {
+# ifdef DEBUG
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("MozSpecialMessageProc - code: 0x%X - %s hw: %p\n", code,
+ gMSGFEvents[inx].mStr, pMsg->hwnd));
+# endif
+ } else {
+# ifdef DEBUG
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("MozSpecialMessageProc - code: 0x%X - %d hw: %p\n", code,
+ gMSGFEvents[inx].mId, pMsg->hwnd));
+# endif
+ }
+ gLastMsgCode = code;
+ }
+ PrintEvent(pMsg->message, FALSE, FALSE);
+ }
+#endif // #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
+
+ if (sProcessHook && code == MSGF_MENU) {
+ MSG* pMsg = (MSG*)lParam;
+ ScheduleHookTimer(pMsg->hwnd, pMsg->message);
+ }
+
+ return ::CallNextHookEx(sMsgFilterHook, code, wParam, lParam);
+}
+
+// Process all mouse messages. Roll up when a click is in a native window
+// that doesn't have an nsIWidget.
+LRESULT CALLBACK nsWindow::MozSpecialMouseProc(int code, WPARAM wParam,
+ LPARAM lParam) {
+ if (sProcessHook) {
+ switch (WinUtils::GetNativeMessage(wParam)) {
+ case WM_LBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL: {
+ MOUSEHOOKSTRUCT* ms = (MOUSEHOOKSTRUCT*)lParam;
+ nsIWidget* mozWin = WinUtils::GetNSWindowPtr(ms->hwnd);
+ if (mozWin) {
+ // If this window is windowed plugin window, the mouse events are not
+ // sent to us.
+ if (static_cast<nsWindow*>(mozWin)->IsPlugin())
+ ScheduleHookTimer(ms->hwnd, (UINT)wParam);
+ } else {
+ ScheduleHookTimer(ms->hwnd, (UINT)wParam);
+ }
+ break;
+ }
+ }
+ }
+ return ::CallNextHookEx(sCallMouseHook, code, wParam, lParam);
+}
+
+// Process all messages. Roll up when the window is moving, or
+// is resizing or when maximized or mininized.
+LRESULT CALLBACK nsWindow::MozSpecialWndProc(int code, WPARAM wParam,
+ LPARAM lParam) {
+#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
+ if (sProcessHook) {
+ CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam;
+ PrintEvent(cwpt->message, FALSE, FALSE);
+ }
+#endif
+
+ if (sProcessHook) {
+ CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam;
+ if (cwpt->message == WM_MOVING || cwpt->message == WM_SIZING ||
+ cwpt->message == WM_GETMINMAXINFO) {
+ ScheduleHookTimer(cwpt->hwnd, (UINT)cwpt->message);
+ }
+ }
+
+ return ::CallNextHookEx(sCallProcHook, code, wParam, lParam);
+}
+
+// Register the special "hooks" for dropdown processing.
+void nsWindow::RegisterSpecialDropdownHooks() {
+ NS_ASSERTION(!sMsgFilterHook, "sMsgFilterHook must be NULL!");
+ NS_ASSERTION(!sCallProcHook, "sCallProcHook must be NULL!");
+
+ DISPLAY_NMM_PRT("***************** Installing Msg Hooks ***************\n");
+
+ // Install msg hook for moving the window and resizing
+ if (!sMsgFilterHook) {
+ DISPLAY_NMM_PRT("***** Hooking sMsgFilterHook!\n");
+ sMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, MozSpecialMsgFilter,
+ nullptr, GetCurrentThreadId());
+#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
+ if (!sMsgFilterHook) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("***** SetWindowsHookEx is NOT installed for WH_MSGFILTER!\n"));
+ }
+#endif
+ }
+
+ // Install msg hook for menus
+ if (!sCallProcHook) {
+ DISPLAY_NMM_PRT("***** Hooking sCallProcHook!\n");
+ sCallProcHook = SetWindowsHookEx(WH_CALLWNDPROC, MozSpecialWndProc, nullptr,
+ GetCurrentThreadId());
+#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
+ if (!sCallProcHook) {
+ MOZ_LOG(
+ gWindowsLog, LogLevel::Info,
+ ("***** SetWindowsHookEx is NOT installed for WH_CALLWNDPROC!\n"));
+ }
+#endif
+ }
+
+ // Install msg hook for the mouse
+ if (!sCallMouseHook) {
+ DISPLAY_NMM_PRT("***** Hooking sCallMouseHook!\n");
+ sCallMouseHook = SetWindowsHookEx(WH_MOUSE, MozSpecialMouseProc, nullptr,
+ GetCurrentThreadId());
+#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
+ if (!sCallMouseHook) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("***** SetWindowsHookEx is NOT installed for WH_MOUSE!\n"));
+ }
+#endif
+ }
+}
+
+// Unhook special message hooks for dropdowns.
+void nsWindow::UnregisterSpecialDropdownHooks() {
+ DISPLAY_NMM_PRT(
+ "***************** De-installing Msg Hooks ***************\n");
+
+ if (sCallProcHook) {
+ DISPLAY_NMM_PRT("***** Unhooking sCallProcHook!\n");
+ if (!::UnhookWindowsHookEx(sCallProcHook)) {
+ DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallProcHook!\n");
+ }
+ sCallProcHook = nullptr;
+ }
+
+ if (sMsgFilterHook) {
+ DISPLAY_NMM_PRT("***** Unhooking sMsgFilterHook!\n");
+ if (!::UnhookWindowsHookEx(sMsgFilterHook)) {
+ DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sMsgFilterHook!\n");
+ }
+ sMsgFilterHook = nullptr;
+ }
+
+ if (sCallMouseHook) {
+ DISPLAY_NMM_PRT("***** Unhooking sCallMouseHook!\n");
+ if (!::UnhookWindowsHookEx(sCallMouseHook)) {
+ DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallMouseHook!\n");
+ }
+ sCallMouseHook = nullptr;
+ }
+}
+
+// This timer is designed to only fire one time at most each time a "hook"
+// function is used to rollup the dropdown. In some cases, the timer may be
+// scheduled from the hook, but that hook event or a subsequent event may roll
+// up the dropdown before this timer function is executed.
+//
+// For example, if an MFC control takes focus, the combobox will lose focus and
+// rollup before this function fires.
+VOID CALLBACK nsWindow::HookTimerForPopups(HWND hwnd, UINT uMsg, UINT idEvent,
+ DWORD dwTime) {
+ if (sHookTimerId != 0) {
+ // if the window is nullptr then we need to use the ID to kill the timer
+ DebugOnly<BOOL> status = ::KillTimer(nullptr, sHookTimerId);
+ NS_ASSERTION(status, "Hook Timer was not killed.");
+ sHookTimerId = 0;
+ }
+
+ if (sRollupMsgId != 0) {
+ // Note: DealWithPopups does the check to make sure that the rollup widget
+ // is set.
+ LRESULT popupHandlingResult;
+ nsAutoRollup autoRollup;
+ DealWithPopups(sRollupMsgWnd, sRollupMsgId, 0, 0, &popupHandlingResult);
+ sRollupMsgId = 0;
+ sRollupMsgWnd = nullptr;
+ }
+}
+
+BOOL CALLBACK nsWindow::ClearResourcesCallback(HWND aWnd, LPARAM aMsg) {
+ nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
+ if (window) {
+ window->ClearCachedResources();
+ }
+ return TRUE;
+}
+
+void nsWindow::ClearCachedResources() {
+ if (mLayerManager &&
+ mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+ mLayerManager->ClearCachedResources();
+ }
+ ::EnumChildWindows(mWnd, nsWindow::ClearResourcesCallback, 0);
+}
+
+static bool IsDifferentThreadWindow(HWND aWnd) {
+ return ::GetCurrentThreadId() != ::GetWindowThreadProcessId(aWnd, nullptr);
+}
+
+// static
+bool nsWindow::EventIsInsideWindow(nsWindow* aWindow,
+ Maybe<POINT> aEventPoint) {
+ RECT r;
+ ::GetWindowRect(aWindow->mWnd, &r);
+ POINT mp;
+ if (aEventPoint) {
+ mp = *aEventPoint;
+ } else {
+ DWORD pos = ::GetMessagePos();
+ mp.x = GET_X_LPARAM(pos);
+ mp.y = GET_Y_LPARAM(pos);
+ }
+
+ // was the event inside this window?
+ return static_cast<bool>(::PtInRect(&r, mp));
+}
+
+// static
+bool nsWindow::GetPopupsToRollup(nsIRollupListener* aRollupListener,
+ uint32_t* aPopupsToRollup,
+ Maybe<POINT> aEventPoint) {
+ // If we're dealing with menus, we probably have submenus and we don't want
+ // to rollup some of them if the click is in a parent menu of the current
+ // submenu.
+ *aPopupsToRollup = UINT32_MAX;
+ AutoTArray<nsIWidget*, 5> widgetChain;
+ uint32_t sameTypeCount = aRollupListener->GetSubmenuWidgetChain(&widgetChain);
+ for (uint32_t i = 0; i < widgetChain.Length(); ++i) {
+ nsIWidget* widget = widgetChain[i];
+ if (EventIsInsideWindow(static_cast<nsWindow*>(widget), aEventPoint)) {
+ // 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) {
+ return false;
+ }
+
+ *aPopupsToRollup = sameTypeCount;
+ break;
+ }
+ }
+ return true;
+}
+
+// static
+bool nsWindow::NeedsToHandleNCActivateDelayed(HWND aWnd) {
+ // While popup is open, popup window might be activated by other application.
+ // At this time, we need to take back focus to the previous window but it
+ // causes flickering its nonclient area because WM_NCACTIVATE comes before
+ // WM_ACTIVATE and we cannot know which window will take focus at receiving
+ // WM_NCACTIVATE. Therefore, we need a hack for preventing the flickerling.
+ //
+ // If non-popup window receives WM_NCACTIVATE at deactivating, default
+ // wndproc shouldn't handle it as deactivating. Instead, at receiving
+ // WM_ACTIVIATE after that, WM_NCACTIVATE should be sent again manually.
+ // This returns true if the window needs to handle WM_NCACTIVATE later.
+
+ nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
+ return window && !window->IsPopup();
+}
+
+static bool IsTouchSupportEnabled(HWND aWnd) {
+ nsWindow* topWindow =
+ WinUtils::GetNSWindowPtr(WinUtils::GetTopLevelHWND(aWnd, true));
+ return topWindow ? topWindow->IsTouchWindow() : false;
+}
+
+// static
+bool nsWindow::DealWithPopups(HWND aWnd, UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam, LRESULT* aResult) {
+ NS_ASSERTION(aResult, "Bad outResult");
+
+ // XXX Why do we use the return value of WM_MOUSEACTIVATE for all messages?
+ *aResult = MA_NOACTIVATE;
+
+ if (!::IsWindowVisible(aWnd)) {
+ return false;
+ }
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ NS_ENSURE_TRUE(rollupListener, false);
+
+ nsCOMPtr<nsIWidget> popup = rollupListener->GetRollupWidget();
+ if (!popup) {
+ return false;
+ }
+
+ static bool sSendingNCACTIVATE = false;
+ static bool sPendingNCACTIVATE = false;
+ uint32_t popupsToRollup = UINT32_MAX;
+
+ bool consumeRollupEvent = false;
+
+ nsWindow* popupWindow = static_cast<nsWindow*>(popup.get());
+ UINT nativeMessage = WinUtils::GetNativeMessage(aMessage);
+ switch (nativeMessage) {
+ case WM_TOUCH:
+ if (!IsTouchSupportEnabled(aWnd)) {
+ // If APZ is disabled, don't allow touch inputs to dismiss popups. The
+ // compatibility mouse events will do it instead.
+ return false;
+ }
+ [[fallthrough]];
+ case WM_LBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_NCLBUTTONDOWN:
+ case WM_NCRBUTTONDOWN:
+ case WM_NCMBUTTONDOWN:
+ if (nativeMessage != WM_TOUCH && IsTouchSupportEnabled(aWnd) &&
+ MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
+ // If any of these mouse events are really compatibility events that
+ // Windows is sending for touch inputs, then don't allow them to dismiss
+ // popups when APZ is enabled (instead we do the dismissing as part of
+ // WM_TOUCH handling which is more correct).
+ // If we don't do this, then when the user lifts their finger after a
+ // long-press, the WM_RBUTTONDOWN compatibility event that Windows sends
+ // us will dismiss the contextmenu popup that we displayed as part of
+ // handling the long-tap-up.
+ return false;
+ }
+ if (!EventIsInsideWindow(popupWindow) &&
+ GetPopupsToRollup(rollupListener, &popupsToRollup)) {
+ break;
+ }
+ return false;
+ case WM_POINTERDOWN: {
+ WinPointerEvents pointerEvents;
+ if (!pointerEvents.ShouldRollupOnPointerEvent(nativeMessage, aWParam)) {
+ return false;
+ }
+ POINT pt;
+ pt.x = GET_X_LPARAM(aLParam);
+ pt.y = GET_Y_LPARAM(aLParam);
+ if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) {
+ return false;
+ }
+ if (EventIsInsideWindow(popupWindow, Some(pt))) {
+ // Don't roll up if the event is inside the popup window.
+ return false;
+ }
+ } break;
+ case MOZ_WM_DMANIP: {
+ POINT pt;
+ ::GetCursorPos(&pt);
+ if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) {
+ return false;
+ }
+ if (EventIsInsideWindow(popupWindow, Some(pt))) {
+ // Don't roll up if the event is inside the popup window
+ return false;
+ }
+ } break;
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL:
+ // We need to check if the popup thinks that it should cause closing
+ // itself when mouse wheel events are fired outside the rollup widget.
+ if (!EventIsInsideWindow(popupWindow)) {
+ // Check if we should consume this event even if we don't roll-up:
+ consumeRollupEvent = rollupListener->ShouldConsumeOnMouseWheelEvent();
+ *aResult = MA_ACTIVATE;
+ if (rollupListener->ShouldRollupOnMouseWheelEvent() &&
+ GetPopupsToRollup(rollupListener, &popupsToRollup)) {
+ break;
+ }
+ }
+ return consumeRollupEvent;
+
+ case WM_ACTIVATEAPP:
+ break;
+
+ case WM_ACTIVATE:
+ // NOTE: Don't handle WA_INACTIVE for preventing popup taking focus
+ // because we cannot distinguish it's caused by mouse or not.
+ if (LOWORD(aWParam) == WA_ACTIVE && aLParam) {
+ nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
+ if (window && window->IsPopup()) {
+ // Cancel notifying widget listeners of deactivating the previous
+ // active window (see WM_KILLFOCUS case in ProcessMessage()).
+ sJustGotDeactivate = false;
+ // Reactivate the window later.
+ ::PostMessageW(aWnd, MOZ_WM_REACTIVATE, aWParam, aLParam);
+ return true;
+ }
+ // Don't rollup the popup when focus moves back to the parent window
+ // from a popup because such case is caused by strange mouse drivers.
+ nsWindow* prevWindow =
+ WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam));
+ if (prevWindow && prevWindow->IsPopup()) {
+ // Consume this message here since previous window must not have
+ // been inactivated since we've already stopped accepting the
+ // inactivation below.
+ return true;
+ }
+ } else if (LOWORD(aWParam) == WA_INACTIVE) {
+ nsWindow* activeWindow =
+ WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam));
+ if (sPendingNCACTIVATE && NeedsToHandleNCActivateDelayed(aWnd)) {
+ // If focus moves to non-popup widget or focusable popup, the window
+ // needs to update its nonclient area.
+ if (!activeWindow || !activeWindow->IsPopup()) {
+ sSendingNCACTIVATE = true;
+ ::SendMessageW(aWnd, WM_NCACTIVATE, false, 0);
+ sSendingNCACTIVATE = false;
+ }
+ sPendingNCACTIVATE = false;
+ }
+ // If focus moves from/to popup, we don't need to rollup the popup
+ // because such case is caused by strange mouse drivers. And in
+ // such case, we should consume the message here since we need to
+ // hide this odd focus move from our content. (If we didn't consume
+ // the message here, ProcessMessage() will notify widget listener of
+ // inactivation and that causes unnecessary reflow for supporting
+ // -moz-window-inactive pseudo class.
+ if (activeWindow) {
+ if (activeWindow->IsPopup()) {
+ return true;
+ }
+ nsWindow* deactiveWindow = WinUtils::GetNSWindowPtr(aWnd);
+ if (deactiveWindow && deactiveWindow->IsPopup()) {
+ return true;
+ }
+ }
+ } else if (LOWORD(aWParam) == WA_CLICKACTIVE) {
+ // If the WM_ACTIVATE message is caused by a click in a popup,
+ // we should not rollup any popups.
+ nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
+ if ((window && window->IsPopup()) ||
+ !GetPopupsToRollup(rollupListener, &popupsToRollup)) {
+ return false;
+ }
+ }
+ break;
+
+ case MOZ_WM_REACTIVATE:
+ // The previous active window should take back focus.
+ if (::IsWindow(reinterpret_cast<HWND>(aLParam))) {
+ // FYI: Even without this API call, you see expected result (e.g., the
+ // owner window of the popup keeps active without flickering
+ // the non-client area). And also this causes initializing
+ // TSF and it causes using CPU time a lot. However, even if we
+ // consume WM_ACTIVE messages, native focus change has already
+ // been occurred. I.e., a popup window is active now. Therefore,
+ // you'll see some odd behavior if we don't reactivate the owner
+ // window here. For example, if you do:
+ // 1. Turn wheel on a bookmark panel.
+ // 2. Turn wheel on another window.
+ // then, you'll see that the another window becomes active but the
+ // owner window of the bookmark panel looks still active and the
+ // bookmark panel keeps open. The reason is that the first wheel
+ // operation gives focus to the bookmark panel. Therefore, when
+ // the next operation gives focus to the another window, previous
+ // focus window is the bookmark panel (i.e., a popup window).
+ // So, in this case, our hack around here prevents to inactivate
+ // the owner window and roll up the bookmark panel.
+ ::SetForegroundWindow(reinterpret_cast<HWND>(aLParam));
+ }
+ return true;
+
+ case WM_NCACTIVATE:
+ if (!aWParam && !sSendingNCACTIVATE &&
+ NeedsToHandleNCActivateDelayed(aWnd)) {
+ // Don't just consume WM_NCACTIVATE. It doesn't handle only the
+ // nonclient area state change.
+ ::DefWindowProcW(aWnd, aMessage, TRUE, aLParam);
+ // Accept the deactivating because it's necessary to receive following
+ // WM_ACTIVATE.
+ *aResult = TRUE;
+ sPendingNCACTIVATE = true;
+ return true;
+ }
+ return false;
+
+ case WM_MOUSEACTIVATE:
+ if (!EventIsInsideWindow(popupWindow) &&
+ GetPopupsToRollup(rollupListener, &popupsToRollup)) {
+ // WM_MOUSEACTIVATE may be caused by moving the mouse (e.g., X-mouse
+ // of TweakUI is enabled. Then, check if the popup should be rolled up
+ // with rollup listener. If not, just consume the message.
+ if (HIWORD(aLParam) == WM_MOUSEMOVE &&
+ !rollupListener->ShouldRollupOnMouseActivate()) {
+ return true;
+ }
+ // Otherwise, it should be handled by wndproc.
+ return false;
+ }
+
+ // Prevent the click inside the popup from causing a change in window
+ // activation. Since the popup is shown non-activated, we need to eat any
+ // requests to activate the window while it is displayed. Windows will
+ // automatically activate the popup on the mousedown otherwise.
+ return true;
+
+ case WM_SHOWWINDOW:
+ // If the window is being minimized, close popups.
+ if (aLParam == SW_PARENTCLOSING) {
+ break;
+ }
+ return false;
+
+ case WM_KILLFOCUS:
+ // If focus moves to other window created in different process/thread,
+ // e.g., a plugin window, popups should be rolled up.
+ if (IsDifferentThreadWindow(reinterpret_cast<HWND>(aWParam))) {
+ break;
+ }
+ return false;
+
+ case WM_MOVING:
+ case WM_MENUSELECT:
+ break;
+
+ default:
+ return false;
+ }
+
+ // Only need to deal with the last rollup for left mouse down events.
+ NS_ASSERTION(!nsAutoRollup::GetLastRollup(), "last rollup is null");
+
+ if (nativeMessage == WM_TOUCH || nativeMessage == WM_LBUTTONDOWN ||
+ nativeMessage == WM_POINTERDOWN) {
+ nsIntPoint pos;
+ if (nativeMessage == WM_TOUCH) {
+ if (nsWindow* win = WinUtils::GetNSWindowPtr(aWnd)) {
+ pos = win->GetTouchCoordinates(aWParam, aLParam);
+ }
+ } else {
+ POINT pt;
+ pt.x = GET_X_LPARAM(aLParam);
+ pt.y = GET_Y_LPARAM(aLParam);
+ ::ClientToScreen(aWnd, &pt);
+ pos = nsIntPoint(pt.x, pt.y);
+ }
+
+ nsIContent* lastRollup;
+ consumeRollupEvent =
+ rollupListener->Rollup(popupsToRollup, true, &pos, &lastRollup);
+ nsAutoRollup::SetLastRollup(lastRollup);
+ } else {
+ consumeRollupEvent =
+ rollupListener->Rollup(popupsToRollup, true, nullptr, nullptr);
+ }
+
+ // Tell hook to stop processing messages
+ sProcessHook = false;
+ sRollupMsgId = 0;
+ sRollupMsgWnd = nullptr;
+
+ // If we are NOT supposed to be consuming events, let it go through
+ if (consumeRollupEvent && nativeMessage != WM_RBUTTONDOWN) {
+ *aResult = MA_ACTIVATE;
+ return true;
+ }
+
+ return false;
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Misc. utility methods and functions.
+ **
+ ** General use.
+ **
+ **************************************************************
+ **************************************************************/
+
+// Note that the result of GetTopLevelWindow method can be different from the
+// result of WinUtils::GetTopLevelHWND(). The result can be non-floating
+// window. Because our top level window may be contained in another window
+// which is not managed by us.
+nsWindow* nsWindow::GetTopLevelWindow(bool aStopOnDialogOrPopup) {
+ nsWindow* curWindow = this;
+
+ while (true) {
+ if (aStopOnDialogOrPopup) {
+ switch (curWindow->mWindowType) {
+ case eWindowType_dialog:
+ case eWindowType_popup:
+ return curWindow;
+ default:
+ break;
+ }
+ }
+
+ // Retrieve the top level parent or owner window
+ nsWindow* parentWindow = curWindow->GetParentWindow(true);
+
+ if (!parentWindow) return curWindow;
+
+ curWindow = parentWindow;
+ }
+}
+
+// Set a flag if hwnd is a (non-popup) visible window from this process,
+// and bail out of the enumeration. Otherwise leave the flag unmodified
+// and continue the enumeration.
+// lParam must be a bool* pointing at the flag to be set.
+static BOOL CALLBACK EnumVisibleWindowsProc(HWND hwnd, LPARAM lParam) {
+ DWORD pid;
+ ::GetWindowThreadProcessId(hwnd, &pid);
+ if (pid == ::GetCurrentProcessId() && ::IsWindowVisible(hwnd)) {
+ // Don't count popups as visible windows, since they don't take focus,
+ // in case we only have a popup visible (see bug 1554490 where the gfx
+ // test window is an offscreen popup).
+ nsWindow* window = WinUtils::GetNSWindowPtr(hwnd);
+ if (!window || !window->IsPopup()) {
+ bool* windowsVisible = reinterpret_cast<bool*>(lParam);
+ *windowsVisible = true;
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+// Determine if it would be ok to activate a window, taking focus.
+// We want to avoid stealing focus from another app (bug 225305).
+bool nsWindow::CanTakeFocus() {
+ HWND fgWnd = ::GetForegroundWindow();
+ if (!fgWnd) {
+ // There is no foreground window, so don't worry about stealing focus.
+ return true;
+ }
+ // We can take focus if the current foreground window is already from
+ // this process.
+ DWORD pid;
+ ::GetWindowThreadProcessId(fgWnd, &pid);
+ if (pid == ::GetCurrentProcessId()) {
+ return true;
+ }
+
+ bool windowsVisible = false;
+ ::EnumWindows(EnumVisibleWindowsProc,
+ reinterpret_cast<LPARAM>(&windowsVisible));
+
+ if (!windowsVisible) {
+ // We're probably creating our first visible window, allow that to
+ // take focus.
+ return true;
+ }
+ return false;
+}
+
+/* static */ const wchar_t* nsWindow::GetMainWindowClass() {
+ static const wchar_t* sMainWindowClass = nullptr;
+ if (!sMainWindowClass) {
+ nsAutoString className;
+ Preferences::GetString("ui.window_class_override", className);
+ if (!className.IsEmpty()) {
+ sMainWindowClass = wcsdup(className.get());
+ } else {
+ sMainWindowClass = kClassNameGeneral;
+ }
+ }
+ return sMainWindowClass;
+}
+
+LPARAM nsWindow::lParamToScreen(LPARAM lParam) {
+ POINT pt;
+ pt.x = GET_X_LPARAM(lParam);
+ pt.y = GET_Y_LPARAM(lParam);
+ ::ClientToScreen(mWnd, &pt);
+ return MAKELPARAM(pt.x, pt.y);
+}
+
+LPARAM nsWindow::lParamToClient(LPARAM lParam) {
+ POINT pt;
+ pt.x = GET_X_LPARAM(lParam);
+ pt.y = GET_Y_LPARAM(lParam);
+ ::ScreenToClient(mWnd, &pt);
+ return MAKELPARAM(pt.x, pt.y);
+}
+
+void nsWindow::PickerOpen() { mPickerDisplayCount++; }
+
+void nsWindow::PickerClosed() {
+ NS_ASSERTION(mPickerDisplayCount > 0, "mPickerDisplayCount out of sync!");
+ if (!mPickerDisplayCount) return;
+ mPickerDisplayCount--;
+ if (!mPickerDisplayCount && mDestroyCalled) {
+ Destroy();
+ }
+}
+
+bool nsWindow::WidgetTypeSupportsAcceleration() {
+ // We don't currently support using an accelerated layer manager with
+ // transparent windows so don't even try. I'm also not sure if we even
+ // want to support this case. See bug 593471.
+ //
+ // Also see bug 1150376, D3D11 composition can cause issues on some devices
+ // on Windows 7 where presentation fails randomly for windows with drop
+ // shadows.
+ return mTransparencyMode != eTransparencyTransparent &&
+ !(IsPopup() && DeviceManagerDx::Get()->IsWARP());
+}
+
+nsresult nsWindow::OnWindowedPluginKeyEvent(
+ const NativeEventData& aKeyEventData,
+ nsIKeyEventInPluginCallback* aCallback) {
+ if (NS_WARN_IF(!mWnd)) {
+ return NS_OK;
+ }
+ const WinNativeKeyEventData* eventData =
+ static_cast<const WinNativeKeyEventData*>(aKeyEventData);
+ switch (eventData->mMessage) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN: {
+ MSG mozMsg = WinUtils::InitMSG(MOZ_WM_KEYDOWN, eventData->mWParam,
+ eventData->mLParam, mWnd);
+ ModifierKeyState modifierKeyState(eventData->mModifiers);
+ NativeKey nativeKey(this, mozMsg, modifierKeyState,
+ eventData->GetKeyboardLayout());
+ return nativeKey.HandleKeyDownMessage() ? NS_SUCCESS_EVENT_CONSUMED
+ : NS_OK;
+ }
+ case WM_KEYUP:
+ case WM_SYSKEYUP: {
+ MSG mozMsg = WinUtils::InitMSG(MOZ_WM_KEYUP, eventData->mWParam,
+ eventData->mLParam, mWnd);
+ ModifierKeyState modifierKeyState(eventData->mModifiers);
+ NativeKey nativeKey(this, mozMsg, modifierKeyState,
+ eventData->GetKeyboardLayout());
+ return nativeKey.HandleKeyUpMessage() ? NS_SUCCESS_EVENT_CONSUMED : NS_OK;
+ }
+ default:
+ // We shouldn't consume WM_*CHAR messages here even if the preceding
+ // keydown or keyup event on the plugin is consumed. It should be
+ // managed in each plugin window rather than top level window.
+ return NS_OK;
+ }
+}
+
+bool nsWindow::OnPointerEvents(UINT msg, WPARAM aWParam, LPARAM aLParam) {
+ if (!mPointerEvents.ShouldHandleWinPointerMessages(msg, aWParam)) {
+ return false;
+ }
+ if (!mPointerEvents.ShouldFirePointerEventByWinPointerMessages()) {
+ // We have to handle WM_POINTER* to fetch and cache pen related information
+ // and fire WidgetMouseEvent with the cached information the WM_*BUTTONDOWN
+ // handler. This is because Windows doesn't support ::DoDragDrop in the
+ // touch or pen message handlers.
+ mPointerEvents.ConvertAndCachePointerInfo(msg, aWParam);
+ // Don't consume the Windows WM_POINTER* messages
+ return false;
+ }
+ // When dispatching mouse events with pen, there may be some
+ // WM_POINTERUPDATE messages between WM_POINTERDOWN and WM_POINTERUP with
+ // small movements. Those events will reset sLastMousePoint and reset
+ // sLastClickCount. To prevent that, we keep the last pen down position
+ // and compare it with the subsequent WM_POINTERUPDATE. If the movement is
+ // smaller than GetSystemMetrics(SM_CXDRAG), then we suppress firing
+ // eMouseMove for WM_POINTERUPDATE.
+ static POINT sLastPointerDownPoint = {0};
+
+ // We don't support chorded buttons for pen. Keep the button at
+ // WM_POINTERDOWN.
+ static mozilla::MouseButton sLastPenDownButton = MouseButton::ePrimary;
+ static bool sPointerDown = false;
+
+ EventMessage message;
+ mozilla::MouseButton button = MouseButton::ePrimary;
+ switch (msg) {
+ case WM_POINTERDOWN: {
+ LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam),
+ GET_Y_LPARAM(aLParam));
+ sLastPointerDownPoint.x = eventPoint.x;
+ sLastPointerDownPoint.y = eventPoint.y;
+ message = eMouseDown;
+ button = IS_POINTER_SECONDBUTTON_WPARAM(aWParam) ? MouseButton::eSecondary
+ : MouseButton::ePrimary;
+ sLastPenDownButton = button;
+ sPointerDown = true;
+ } break;
+ case WM_POINTERUP:
+ message = eMouseUp;
+ MOZ_ASSERT(sPointerDown, "receive WM_POINTERUP w/o WM_POINTERDOWN");
+ button = sPointerDown ? sLastPenDownButton : MouseButton::ePrimary;
+ sPointerDown = false;
+ break;
+ case WM_POINTERUPDATE:
+ message = eMouseMove;
+ if (sPointerDown) {
+ LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam),
+ GET_Y_LPARAM(aLParam));
+ int32_t movementX = sLastPointerDownPoint.x > eventPoint.x
+ ? sLastPointerDownPoint.x - eventPoint.x
+ : eventPoint.x - sLastPointerDownPoint.x;
+ int32_t movementY = sLastPointerDownPoint.y > eventPoint.y
+ ? sLastPointerDownPoint.y - eventPoint.y
+ : eventPoint.y - sLastPointerDownPoint.y;
+ bool insideMovementThreshold =
+ movementX < (int32_t)::GetSystemMetrics(SM_CXDRAG) &&
+ movementY < (int32_t)::GetSystemMetrics(SM_CYDRAG);
+
+ if (insideMovementThreshold) {
+ // Suppress firing eMouseMove for WM_POINTERUPDATE if the movement
+ // from last WM_POINTERDOWN is smaller than SM_CXDRAG / SM_CYDRAG
+ return false;
+ }
+ button = sLastPenDownButton;
+ }
+ break;
+ case WM_POINTERLEAVE:
+ message = eMouseExitFromWidget;
+ break;
+ default:
+ return false;
+ }
+ uint32_t pointerId = mPointerEvents.GetPointerId(aWParam);
+ POINTER_PEN_INFO penInfo;
+ mPointerEvents.GetPointerPenInfo(pointerId, &penInfo);
+
+ // Windows defines the pen pressure is normalized to a range between 0 and
+ // 1024. Convert it to float.
+ float pressure = penInfo.pressure ? (float)penInfo.pressure / 1024 : 0;
+ int16_t buttons = sPointerDown ? button == MouseButton::ePrimary
+ ? MouseButtonsFlag::ePrimaryFlag
+ : MouseButtonsFlag::eSecondaryFlag
+ : MouseButtonsFlag::eNoButtons;
+ WinPointerInfo pointerInfo(pointerId, penInfo.tiltX, penInfo.tiltY, pressure,
+ buttons);
+
+ // The aLParam of WM_POINTER* is the screen location. Convert it to client
+ // location
+ LPARAM newLParam = lParamToClient(aLParam);
+ DispatchMouseEvent(message, aWParam, newLParam, false, button,
+ MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
+ // Consume WM_POINTER* to stop Windows fires WM_*BUTTONDOWN / WM_*BUTTONUP
+ // WM_MOUSEMOVE.
+ return true;
+}
+
+void nsWindow::GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) {
+ *aInitData = WinCompositorWidgetInitData(
+ reinterpret_cast<uintptr_t>(mWnd),
+ reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
+ mTransparencyMode, mSizeMode);
+}
+
+bool nsWindow::SynchronouslyRepaintOnResize() {
+ return !gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled();
+}
+
+void nsWindow::MaybeDispatchInitialFocusEvent() {
+ if (mIsShowingPreXULSkeletonUI && ::GetActiveWindow() == mWnd) {
+ DispatchFocusToTopLevelWindow(true);
+ }
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow();
+ return window.forget();
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow(true);
+ return window.forget();
+}
diff --git a/widget/windows/nsWindow.h b/widget/windows/nsWindow.h
new file mode 100644
index 0000000000..20e8447c0a
--- /dev/null
+++ b/widget/windows/nsWindow.h
@@ -0,0 +1,747 @@
+/* -*- 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 Window_h__
+#define Window_h__
+
+/*
+ * nsWindow - Native window management and event handling.
+ */
+
+#include "mozilla/RefPtr.h"
+#include "nsBaseWidget.h"
+#include "CompositorWidget.h"
+#include "nsWindowBase.h"
+#include "nsdefs.h"
+#include "nsUserIdleService.h"
+#include "nsToolkit.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxWindowsSurface.h"
+#include "nsWindowDbg.h"
+#include "cairo.h"
+#include "nsRegion.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/UniquePtr.h"
+#include "nsMargin.h"
+#include "nsRegionFwd.h"
+
+#include "nsWinGesture.h"
+#include "WinPointerEvents.h"
+#include "WinUtils.h"
+#include "WindowHook.h"
+#include "TaskbarWindowPreview.h"
+
+#ifdef ACCESSIBILITY
+# include "oleacc.h"
+# include "mozilla/a11y/Accessible.h"
+#endif
+
+#include "nsUXThemeData.h"
+#include "nsIUserIdleServiceInternal.h"
+
+#include "IMMHandler.h"
+
+/**
+ * Forward class definitions
+ */
+
+class nsNativeDragTarget;
+class nsIRollupListener;
+class imgIContainer;
+
+namespace mozilla {
+namespace widget {
+class NativeKey;
+class InProcessWinCompositorWidget;
+struct MSGResult;
+class DirectManipulationOwner;
+} // namespace widget
+} // namespace mozilla
+
+/**
+ * Forward Windows-internal definitions of otherwise incomplete ones provided by
+ * the SDK.
+ */
+const CLSID CLSID_ImmersiveShell = {
+ 0xC2F03A33,
+ 0x21F5,
+ 0x47FA,
+ {0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39}};
+
+// Virtual Desktop.
+
+EXTERN_C const IID IID_IVirtualDesktopManager;
+MIDL_INTERFACE("a5cd92ff-29be-454c-8d04-d82879fb3f1b")
+IVirtualDesktopManager : public IUnknown {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE GetWindowDesktopId(
+ __RPC__in HWND topLevelWindow, __RPC__out GUID * desktopId) = 0;
+ virtual HRESULT STDMETHODCALLTYPE MoveWindowToDesktop(
+ __RPC__in HWND topLevelWindow, __RPC__in REFGUID desktopId) = 0;
+};
+
+/**
+ * Native WIN32 window wrapper.
+ */
+
+class nsWindow final : public nsWindowBase {
+ typedef mozilla::TimeStamp TimeStamp;
+ typedef mozilla::TimeDuration TimeDuration;
+ typedef mozilla::widget::WindowHook WindowHook;
+ typedef mozilla::widget::TaskbarWindowPreview TaskbarWindowPreview;
+ typedef mozilla::widget::NativeKey NativeKey;
+ typedef mozilla::widget::MSGResult MSGResult;
+ typedef mozilla::widget::IMEContext IMEContext;
+ typedef mozilla::widget::PlatformCompositorWidgetDelegate
+ PlatformCompositorWidgetDelegate;
+
+ public:
+ explicit nsWindow(bool aIsChildWindow = false);
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsWindow, nsWindowBase)
+
+ friend class nsWindowGfx;
+
+ void SendAnAPZEvent(mozilla::InputData& aEvent);
+
+ // nsWindowBase
+ virtual void InitEvent(mozilla::WidgetGUIEvent& aEvent,
+ LayoutDeviceIntPoint* aPoint = nullptr) override;
+ virtual WidgetEventTime CurrentMessageWidgetEventTime() const override;
+ virtual bool DispatchWindowEvent(mozilla::WidgetGUIEvent* aEvent) override;
+ virtual bool DispatchKeyboardEvent(
+ mozilla::WidgetKeyboardEvent* aEvent) override;
+ virtual bool DispatchWheelEvent(mozilla::WidgetWheelEvent* aEvent) override;
+ virtual bool DispatchContentCommandEvent(
+ mozilla::WidgetContentCommandEvent* aEvent) override;
+ virtual nsWindowBase* GetParentWindowBase(bool aIncludeOwner) override;
+ virtual bool IsTopLevelWidget() override { return mIsTopWidgetWindow; }
+
+ using nsWindowBase::DispatchPluginEvent;
+
+ // nsIWidget interface
+ using nsWindowBase::Create; // for Create signature not overridden here
+ [[nodiscard]] virtual nsresult Create(
+ nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr) override;
+ virtual void Destroy() override;
+ virtual void SetParent(nsIWidget* aNewParent) override;
+ virtual nsIWidget* GetParent(void) override;
+ virtual float GetDPI() override;
+ double GetDefaultScaleInternal() final;
+ int32_t LogToPhys(double aValue) final;
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() final {
+ if (mozilla::widget::WinUtils::IsPerMonitorDPIAware()) {
+ return mozilla::DesktopToLayoutDeviceScale(1.0);
+ } else {
+ return mozilla::DesktopToLayoutDeviceScale(GetDefaultScaleInternal());
+ }
+ }
+
+ virtual void Show(bool aState) override;
+ virtual bool IsVisible() const override;
+ virtual void ConstrainPosition(bool aAllowSlop, int32_t* aX,
+ int32_t* aY) override;
+ virtual void SetSizeConstraints(const SizeConstraints& aConstraints) override;
+ virtual void LockAspectRatio(bool aShouldLock) override;
+ virtual const SizeConstraints GetSizeConstraints() override;
+ virtual void SetWindowMouseTransparent(bool aIsTransparent) override;
+ virtual void Move(double aX, double aY) override;
+ virtual void Resize(double aWidth, double aHeight, bool aRepaint) override;
+ virtual void Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) override;
+ virtual mozilla::Maybe<bool> IsResizingNativeWidget() override;
+ [[nodiscard]] virtual nsresult BeginResizeDrag(
+ mozilla::WidgetGUIEvent* aEvent, int32_t aHorizontal,
+ int32_t aVertical) override;
+ virtual void PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
+ nsIWidget* aWidget, bool aActivate) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ virtual void GetWorkspaceID(nsAString& workspaceID) override;
+ virtual void MoveToWorkspace(const nsAString& workspaceID) override;
+ virtual void SuppressAnimation(bool aSuppress) override;
+ virtual void Enable(bool aState) override;
+ virtual bool IsEnabled() const override;
+ virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override;
+ virtual LayoutDeviceIntRect GetBounds() override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ [[nodiscard]] virtual nsresult GetRestoredBounds(
+ LayoutDeviceIntRect& aRect) override;
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntPoint GetClientOffset() override;
+ void SetBackgroundColor(const nscolor& aColor) override;
+ virtual void SetCursor(nsCursor aDefaultCursor, imgIContainer* aCursorImage,
+ uint32_t aHotspotX, uint32_t aHotspotY) override;
+ virtual nsresult ConfigureChildren(
+ const nsTArray<Configuration>& aConfigurations) override;
+ virtual bool PrepareForFullscreenTransition(nsISupports** aData) override;
+ virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) override;
+ virtual void CleanupFullscreenTransition() override;
+ virtual nsresult MakeFullScreen(bool aFullScreen,
+ nsIScreen* aScreen = nullptr) override;
+ virtual void HideWindowChrome(bool aShouldHide) override;
+ virtual void Invalidate(bool aEraseBackground = false,
+ bool aUpdateNCArea = false,
+ bool aIncludeChildren = false);
+ virtual void Invalidate(const LayoutDeviceIntRect& aRect);
+ virtual void* GetNativeData(uint32_t aDataType) override;
+ void SetNativeData(uint32_t aDataType, uintptr_t aVal) override;
+ virtual void FreeNativeData(void* data, uint32_t aDataType) override;
+ virtual nsresult SetTitle(const nsAString& aTitle) override;
+ virtual void SetIcon(const nsAString& aIconSpec) override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ virtual LayoutDeviceIntSize ClientToWindowSize(
+ const LayoutDeviceIntSize& aClientSize) override;
+ virtual nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+ virtual void EnableDragDrop(bool aEnable) override;
+ virtual void CaptureMouse(bool aCapture) override;
+ virtual void CaptureRollupEvents(nsIRollupListener* aListener,
+ bool aDoCapture) override;
+ [[nodiscard]] virtual nsresult GetAttention(int32_t aCycleCount) override;
+ virtual bool HasPendingInputEvent() override;
+ virtual LayerManager* GetLayerManager(
+ PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
+ void SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) override;
+ [[nodiscard]] virtual nsresult OnDefaultButtonLoaded(
+ const LayoutDeviceIntRect& aButtonRect) 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, MOUSEEVENTF_MOVE, 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 void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ virtual InputContext GetInputContext() override;
+ virtual TextEventDispatcherListener* GetNativeTextEventDispatcherListener()
+ override;
+#ifdef MOZ_XUL
+ virtual void SetTransparencyMode(nsTransparencyMode aMode) override;
+ virtual nsTransparencyMode GetTransparencyMode() override;
+ virtual void UpdateOpaqueRegion(
+ const LayoutDeviceIntRegion& aOpaqueRegion) override;
+#endif // MOZ_XUL
+ virtual nsresult SetNonClientMargins(
+ LayoutDeviceIntMargin& aMargins) override;
+ void SetDrawsInTitlebar(bool aState) override;
+ virtual void UpdateWindowDraggingRegion(
+ const LayoutDeviceIntRegion& aRegion) override;
+
+ virtual void UpdateThemeGeometries(
+ const nsTArray<ThemeGeometry>& aThemeGeometries) override;
+ virtual uint32_t GetMaxTouchPoints() const override;
+ virtual void SetWindowClass(const nsAString& xulWinType) override;
+
+ /**
+ * Event helpers
+ */
+ virtual bool DispatchMouseEvent(
+ mozilla::EventMessage aEventMessage, WPARAM wParam, LPARAM lParam,
+ bool aIsContextMenuKey = false,
+ int16_t aButton = mozilla::MouseButton::ePrimary,
+ uint16_t aInputSource =
+ mozilla::dom::MouseEvent_Binding::MOZ_SOURCE_MOUSE,
+ WinPointerInfo* aPointerInfo = nullptr);
+ virtual bool DispatchWindowEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus);
+ void DispatchPendingEvents();
+ bool DispatchPluginEvent(UINT aMessage, WPARAM aWParam, LPARAM aLParam,
+ bool aDispatchPendingEvents);
+ void DispatchCustomEvent(const nsString& eventName);
+
+#ifdef ACCESSIBILITY
+ /**
+ * Return an accessible associated with the window.
+ */
+ mozilla::a11y::Accessible* GetAccessible();
+#endif // ACCESSIBILITY
+
+ /**
+ * Window utilities
+ */
+ nsWindow* GetTopLevelWindow(bool aStopOnDialogOrPopup);
+ WNDPROC GetPrevWindowProc() { return mPrevWndProc; }
+ WindowHook& GetWindowHook() { return mWindowHook; }
+ nsWindow* GetParentWindow(bool aIncludeOwner);
+ // Get an array of all nsWindow*s on the main thread.
+ static nsTArray<nsWindow*> EnumAllWindows();
+
+ /**
+ * Misc.
+ */
+ virtual bool AutoErase(HDC dc);
+ bool WidgetTypeSupportsAcceleration() override;
+
+ void ForcePresent();
+ bool TouchEventShouldStartDrag(mozilla::EventMessage aEventMessage,
+ LayoutDeviceIntPoint aEventPoint);
+
+ void SetSmallIcon(HICON aIcon);
+ void SetBigIcon(HICON aIcon);
+
+ static void SetIsRestoringSession(const bool aIsRestoringSession) {
+ sIsRestoringSession = aIsRestoringSession;
+ }
+
+ /**
+ * AssociateDefaultIMC() associates or disassociates the default IMC for
+ * the window.
+ *
+ * @param aAssociate TRUE, associates the default IMC with the window.
+ * Otherwise, disassociates the default IMC from the
+ * window.
+ * @return TRUE if this method associated the default IMC with
+ * disassociated window or disassociated the default IMC
+ * from associated window.
+ * Otherwise, i.e., if this method did nothing actually,
+ * FALSE.
+ */
+ bool AssociateDefaultIMC(bool aAssociate);
+
+ bool HasTaskbarIconBeenCreated() { return mHasTaskbarIconBeenCreated; }
+ // Called when either the nsWindow or an nsITaskbarTabPreview receives the
+ // noticiation that this window has its icon placed on the taskbar.
+ void SetHasTaskbarIconBeenCreated(bool created = true) {
+ mHasTaskbarIconBeenCreated = created;
+ }
+
+ // Getter/setter for the nsITaskbarWindowPreview for this nsWindow
+ already_AddRefed<nsITaskbarWindowPreview> GetTaskbarPreview() {
+ nsCOMPtr<nsITaskbarWindowPreview> preview(
+ do_QueryReferent(mTaskbarPreview));
+ return preview.forget();
+ }
+ void SetTaskbarPreview(nsITaskbarWindowPreview* preview) {
+ mTaskbarPreview = do_GetWeakReference(preview);
+ }
+
+ virtual void ReparentNativeWidget(nsIWidget* aNewParent) override;
+
+ // Open file picker tracking
+ void PickerOpen();
+ void PickerClosed();
+
+ bool const DestroyCalled() { return mDestroyCalled; }
+
+ bool IsPopup();
+ virtual bool ShouldUseOffMainThreadCompositing() override;
+
+ const IMEContext& DefaultIMC() const { return mDefaultIMC; }
+
+ virtual nsresult OnWindowedPluginKeyEvent(
+ const mozilla::NativeEventData& aKeyEventData,
+ nsIKeyEventInPluginCallback* aCallback) override;
+
+ void GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) override;
+ bool IsTouchWindow() const { return mTouchWindow; }
+ bool SynchronouslyRepaintOnResize() override;
+ virtual void MaybeDispatchInitialFocusEvent() override;
+
+ protected:
+ virtual ~nsWindow();
+
+ virtual void WindowUsesOMTC() override;
+ virtual void RegisterTouchWindow() override;
+
+ // A magic number to identify the FAKETRACKPOINTSCROLLABLE window created
+ // when the trackpoint hack is enabled.
+ enum { eFakeTrackPointScrollableID = 0x46545053 };
+
+ // Used for displayport suppression during window resize
+ enum ResizeState { NOT_RESIZING, IN_SIZEMOVE, RESIZING, MOVING };
+
+ /**
+ * Callbacks
+ */
+ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
+ LPARAM lParam);
+ static LRESULT CALLBACK WindowProcInternal(HWND hWnd, UINT msg, WPARAM wParam,
+ LPARAM lParam);
+
+ static BOOL CALLBACK BroadcastMsgToChildren(HWND aWnd, LPARAM aMsg);
+ static BOOL CALLBACK BroadcastMsg(HWND aTopWindow, LPARAM aMsg);
+ static BOOL CALLBACK DispatchStarvedPaints(HWND aTopWindow, LPARAM aMsg);
+ static BOOL CALLBACK RegisterTouchForDescendants(HWND aTopWindow,
+ LPARAM aMsg);
+ static BOOL CALLBACK UnregisterTouchForDescendants(HWND aTopWindow,
+ LPARAM aMsg);
+ static LRESULT CALLBACK MozSpecialMsgFilter(int code, WPARAM wParam,
+ LPARAM lParam);
+ static LRESULT CALLBACK MozSpecialWndProc(int code, WPARAM wParam,
+ LPARAM lParam);
+ static LRESULT CALLBACK MozSpecialMouseProc(int code, WPARAM wParam,
+ LPARAM lParam);
+ static VOID CALLBACK HookTimerForPopups(HWND hwnd, UINT uMsg, UINT idEvent,
+ DWORD dwTime);
+ static BOOL CALLBACK ClearResourcesCallback(HWND aChild, LPARAM aParam);
+ static BOOL CALLBACK EnumAllChildWindProc(HWND aWnd, LPARAM aParam);
+ static BOOL CALLBACK EnumAllThreadWindowProc(HWND aWnd, LPARAM aParam);
+
+ /**
+ * Window utilities
+ */
+ LPARAM lParamToScreen(LPARAM lParam);
+ LPARAM lParamToClient(LPARAM lParam);
+ virtual void SubclassWindow(BOOL bState);
+ bool CanTakeFocus();
+ bool UpdateNonClientMargins(int32_t aSizeMode = -1,
+ bool aReflowWindow = true);
+ void UpdateGetWindowInfoCaptionStatus(bool aActiveCaption);
+ void ResetLayout();
+ void InvalidateNonClientRegion();
+ HRGN ExcludeNonClientFromPaintRegion(HRGN aRegion);
+ static const wchar_t* GetMainWindowClass();
+ bool HasGlass() const {
+ return mTransparencyMode == eTransparencyGlass ||
+ mTransparencyMode == eTransparencyBorderlessGlass;
+ }
+ HWND GetOwnerWnd() const { return ::GetWindow(mWnd, GW_OWNER); }
+ bool IsOwnerForegroundWindow() const {
+ HWND owner = GetOwnerWnd();
+ return owner && owner == ::GetForegroundWindow();
+ }
+ bool IsPopup() const { return mWindowType == eWindowType_popup; }
+
+ /**
+ * Event processing helpers
+ */
+ HWND GetTopLevelForFocus(HWND aCurWnd);
+ void DispatchFocusToTopLevelWindow(bool aIsActivate);
+ bool DispatchStandardEvent(mozilla::EventMessage aMsg);
+ void RelayMouseEvent(UINT aMsg, WPARAM wParam, LPARAM lParam);
+ virtual bool ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam,
+ LRESULT* aRetValue);
+ bool ExternalHandlerProcessMessage(UINT aMessage, WPARAM& aWParam,
+ LPARAM& aLParam, MSGResult& aResult);
+ LRESULT ProcessCharMessage(const MSG& aMsg, bool* aEventDispatched);
+ LRESULT ProcessKeyUpMessage(const MSG& aMsg, bool* aEventDispatched);
+ LRESULT ProcessKeyDownMessage(const MSG& aMsg, bool* aEventDispatched);
+ static bool EventIsInsideWindow(
+ nsWindow* aWindow,
+ mozilla::Maybe<POINT> aEventPoint = mozilla::Nothing());
+ // Convert nsEventStatus value to a windows boolean
+ static bool ConvertStatus(nsEventStatus aStatus);
+ static void PostSleepWakeNotification(const bool aIsSleepMode);
+ int32_t ClientMarginHitTestPoint(int32_t mx, int32_t my);
+ TimeStamp GetMessageTimeStamp(LONG aEventTime) const;
+ static void UpdateFirstEventTime(DWORD aEventTime);
+ void FinishLiveResizing(ResizeState aNewState);
+ nsIntPoint GetTouchCoordinates(WPARAM wParam, LPARAM lParam);
+ mozilla::Maybe<mozilla::PanGestureInput> ConvertTouchToPanGesture(
+ const mozilla::MultiTouchInput& aTouchInput, PTOUCHINPUT aOriginalEvent);
+ void DispatchTouchOrPanGestureInput(mozilla::MultiTouchInput& aTouchInput,
+ PTOUCHINPUT aOSEvent);
+
+ /**
+ * Event handlers
+ */
+ virtual void OnDestroy() override;
+ bool OnResize(const LayoutDeviceIntSize& aSize);
+ void OnSizeModeChange(nsSizeMode aSizeMode);
+ bool OnGesture(WPARAM wParam, LPARAM lParam);
+ bool OnTouch(WPARAM wParam, LPARAM lParam);
+ bool OnHotKey(WPARAM wParam, LPARAM lParam);
+ bool OnPaint(HDC aDC, uint32_t aNestingLevel);
+ void OnWindowPosChanged(WINDOWPOS* wp);
+ void OnWindowPosChanging(LPWINDOWPOS& info);
+ void OnSysColorChanged();
+ void OnDPIChanged(int32_t x, int32_t y, int32_t width, int32_t height);
+ bool OnPointerEvents(UINT msg, WPARAM wParam, LPARAM lParam);
+
+ /**
+ * Function that registers when the user has been active (used for detecting
+ * when the user is idle).
+ */
+ void UserActivity();
+
+ int32_t GetHeight(int32_t aProposedHeight);
+ const wchar_t* GetWindowClass() const;
+ const wchar_t* GetWindowPopupClass() const;
+ virtual DWORD WindowStyle();
+ DWORD WindowExStyle();
+
+ // This method registers the given window class, and returns the class name.
+ const wchar_t* RegisterWindowClass(const wchar_t* aClassName,
+ UINT aExtraStyle, LPWSTR aIconID) const;
+
+ /**
+ * XP and Vista theming support for windows with rounded edges
+ */
+ void ClearThemeRegion();
+ void SetThemeRegion();
+
+ /**
+ * Popup hooks
+ */
+ static void ScheduleHookTimer(HWND aWnd, UINT aMsgId);
+ static void RegisterSpecialDropdownHooks();
+ static void UnregisterSpecialDropdownHooks();
+ static bool GetPopupsToRollup(
+ nsIRollupListener* aRollupListener, uint32_t* aPopupsToRollup,
+ mozilla::Maybe<POINT> aEventPoint = mozilla::Nothing());
+ static bool NeedsToHandleNCActivateDelayed(HWND aWnd);
+ static bool DealWithPopups(HWND inWnd, UINT inMsg, WPARAM inWParam,
+ LPARAM inLParam, LRESULT* outResult);
+
+ /**
+ * Window transparency helpers
+ */
+#ifdef MOZ_XUL
+ private:
+ void SetWindowTranslucencyInner(nsTransparencyMode aMode);
+ nsTransparencyMode GetWindowTranslucencyInner() const {
+ return mTransparencyMode;
+ }
+ void UpdateGlass();
+ bool WithinDraggableRegion(int32_t clientX, int32_t clientY);
+
+ protected:
+#endif // MOZ_XUL
+
+ static bool IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult);
+ void IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam);
+
+ /**
+ * Misc.
+ */
+ void StopFlashing();
+ static HWND WindowAtMouse();
+ static bool IsTopLevelMouseExit(HWND aWnd);
+ virtual nsresult SetWindowClipRegion(
+ const nsTArray<LayoutDeviceIntRect>& aRects,
+ bool aIntersectWithExisting) override;
+ LayoutDeviceIntRegion GetRegionToPaint(bool aForceFullRepaint, PAINTSTRUCT ps,
+ HDC aDC);
+ void ClearCachedResources();
+ nsIWidgetListener* GetPaintListener();
+
+ virtual void AddWindowOverlayWebRenderCommands(
+ mozilla::layers::WebRenderBridgeChild* aWrBridge,
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResourceUpdates) override;
+
+ already_AddRefed<SourceSurface> CreateScrollSnapshot() override;
+
+ struct ScrollSnapshot {
+ RefPtr<gfxWindowsSurface> surface;
+ bool surfaceHasSnapshot = false;
+ RECT clip;
+ };
+
+ ScrollSnapshot* EnsureSnapshotSurface(ScrollSnapshot& aSnapshotData,
+ const mozilla::gfx::IntSize& aSize);
+
+ ScrollSnapshot mFullSnapshot;
+ ScrollSnapshot mPartialSnapshot;
+ ScrollSnapshot* mCurrentSnapshot = nullptr;
+
+ already_AddRefed<SourceSurface> GetFallbackScrollSnapshot(
+ const RECT& aRequiredClip);
+
+ void CreateCompositor() override;
+ void RequestFxrOutput();
+
+ void RecreateDirectManipulationIfNeeded();
+ void ResizeDirectManipulationViewport();
+ void DestroyDirectManipulation();
+
+ protected:
+ nsCOMPtr<nsIWidget> mParent;
+ nsIntSize mLastSize;
+ nsIntPoint mLastPoint;
+ HWND mWnd;
+ HWND mTransitionWnd;
+ WNDPROC mPrevWndProc;
+ HBRUSH mBrush;
+ IMEContext mDefaultIMC;
+ HDEVNOTIFY mDeviceNotifyHandle;
+ bool mIsTopWidgetWindow;
+ bool mInDtor;
+ bool mIsVisible;
+ bool mPainting;
+ bool mTouchWindow;
+ bool mDisplayPanFeedback;
+ bool mHideChrome;
+ bool mIsRTL;
+ bool mFullscreenMode;
+ bool mMousePresent;
+ bool mMouseInDraggableArea;
+ bool mDestroyCalled;
+ bool mOpeningAnimationSuppressed;
+ bool mAlwaysOnTop;
+ bool mIsEarlyBlankWindow;
+ bool mIsShowingPreXULSkeletonUI;
+ bool mResizable;
+ DWORD_PTR mOldStyle;
+ DWORD_PTR mOldExStyle;
+ nsNativeDragTarget* mNativeDragTarget;
+ HKL mLastKeyboardLayout;
+ nsSizeMode mOldSizeMode;
+ nsSizeMode mLastSizeMode;
+ WindowHook mWindowHook;
+ uint32_t mPickerDisplayCount;
+ HICON mIconSmall;
+ HICON mIconBig;
+ HWND mLastKillFocusWindow;
+ static bool sDropShadowEnabled;
+ static uint32_t sInstanceCount;
+ static TriStateBool sCanQuit;
+ static nsWindow* sCurrentWindow;
+ static BOOL sIsOleInitialized;
+ static HCURSOR sHCursor;
+ static imgIContainer* sCursorImgContainer;
+ static bool sSwitchKeyboardLayout;
+ static bool sJustGotDeactivate;
+ static bool sJustGotActivate;
+ static bool sIsInMouseCapture;
+ static bool sHaveInitializedPrefs;
+ static bool sIsRestoringSession;
+
+ PlatformCompositorWidgetDelegate* mCompositorWidgetDelegate;
+
+ // Always use the helper method to read this property. See bug 603793.
+ static TriStateBool sHasBogusPopupsDropShadowOnMultiMonitor;
+ static bool HasBogusPopupsDropShadowOnMultiMonitor();
+
+ // Non-client margin settings
+ // Pre-calculated outward offset applied to default frames
+ LayoutDeviceIntMargin mNonClientOffset;
+ // Margins set by the owner
+ LayoutDeviceIntMargin mNonClientMargins;
+ // Margins we'd like to set once chrome is reshown:
+ LayoutDeviceIntMargin mFutureMarginsOnceChromeShows;
+ // Indicates we need to apply margins once toggling chrome into showing:
+ bool mFutureMarginsToUse;
+
+ // Indicates custom frames are enabled
+ bool mCustomNonClient;
+ // Cached copy of L&F's resize border
+ int32_t mHorResizeMargin;
+ int32_t mVertResizeMargin;
+ // Height of the caption plus border
+ int32_t mCaptionHeight;
+
+ double mDefaultScale;
+
+ float mAspectRatio;
+
+ nsCOMPtr<nsIUserIdleServiceInternal> mIdleService;
+
+ // Draggable titlebar region maintained by UpdateWindowDraggingRegion
+ LayoutDeviceIntRegion mDraggableRegion;
+
+ // Hook Data Memebers for Dropdowns. sProcessHook Tells the
+ // hook methods whether they should be processing the hook
+ // messages.
+ static HHOOK sMsgFilterHook;
+ static HHOOK sCallProcHook;
+ static HHOOK sCallMouseHook;
+ static bool sProcessHook;
+ static UINT sRollupMsgId;
+ static HWND sRollupMsgWnd;
+ static UINT sHookTimerId;
+
+ // Mouse Clicks - static variable definitions for figuring
+ // out 1 - 3 Clicks.
+ static POINT sLastMousePoint;
+ static POINT sLastMouseMovePoint;
+ static LONG sLastMouseDownTime;
+ static LONG sLastClickCount;
+ static BYTE sLastMouseButton;
+
+ // Graphics
+ HDC mPaintDC; // only set during painting
+
+ LayoutDeviceIntRect mLastPaintBounds;
+
+ ResizeState mResizeState;
+
+ // Transparency
+#ifdef MOZ_XUL
+ nsTransparencyMode mTransparencyMode;
+ nsIntRegion mPossiblyTransparentRegion;
+ MARGINS mGlassMargins;
+#endif // MOZ_XUL
+
+ // Win7 Gesture processing and management
+ nsWinGesture mGesture;
+
+ // Weak ref to the nsITaskbarWindowPreview associated with this window
+ nsWeakPtr mTaskbarPreview;
+ // True if the taskbar (possibly through the tab preview) tells us that the
+ // icon has been created on the taskbar.
+ bool mHasTaskbarIconBeenCreated;
+
+ // Indicates that mouse events should be ignored and pass through to the
+ // window below. This is currently only used for popups.
+ bool mMouseTransparent;
+
+ // Whether we're in the process of sending a WM_SETTEXT ourselves
+ bool mSendingSetText;
+
+ // Whether we we're created as a child window (aka ChildWindow) or not.
+ bool mIsChildWindow : 1;
+
+ int32_t mCachedHitTestResult;
+
+ // The point in time at which the last paint completed. We use this to avoid
+ // painting too rapidly in response to frequent input events.
+ TimeStamp mLastPaintEndTime;
+
+ // The location of the window buttons in the window.
+ mozilla::Maybe<LayoutDeviceIntRect> mWindowButtonsRect;
+
+ // Caching for hit test results
+ POINT mCachedHitTestPoint;
+ TimeStamp mCachedHitTestTime;
+
+ RefPtr<mozilla::widget::InProcessWinCompositorWidget> mBasicLayersSurface;
+
+ static bool sNeedsToInitMouseWheelSettings;
+ static void InitMouseWheelScrollData();
+
+ double mSizeConstraintsScale; // scale in effect when setting constraints
+ int32_t mMaxTextureSize;
+
+ // Pointer events processing and management
+ WinPointerEvents mPointerEvents;
+
+ ScreenPoint mLastPanGestureFocus;
+
+ // When true, used to indicate an async call to RequestFxrOutput to the GPU
+ // process after the Compositor is created
+ bool mRequestFxrOutputPending;
+
+ mozilla::UniquePtr<mozilla::widget::DirectManipulationOwner> mDmOwner;
+};
+
+#endif // Window_h__
diff --git a/widget/windows/nsWindowBase.cpp b/widget/windows/nsWindowBase.cpp
new file mode 100644
index 0000000000..a9c9d2b53e
--- /dev/null
+++ b/widget/windows/nsWindowBase.cpp
@@ -0,0 +1,224 @@
+/* -*- 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 "nsWindowBase.h"
+
+#include "mozilla/MiscEvents.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "KeyboardLayout.h"
+#include "WinUtils.h"
+#include "npapi.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+static const wchar_t kUser32LibName[] = L"user32.dll";
+bool nsWindowBase::sTouchInjectInitialized = false;
+InjectTouchInputPtr nsWindowBase::sInjectTouchFuncPtr;
+
+bool nsWindowBase::DispatchPluginEvent(const MSG& aMsg) { return false; }
+
+// static
+bool nsWindowBase::InitTouchInjection() {
+ if (!sTouchInjectInitialized) {
+ // Initialize touch injection on the first call
+ HMODULE hMod = LoadLibraryW(kUser32LibName);
+ if (!hMod) {
+ return false;
+ }
+
+ InitializeTouchInjectionPtr func =
+ (InitializeTouchInjectionPtr)GetProcAddress(hMod,
+ "InitializeTouchInjection");
+ if (!func) {
+ WinUtils::Log("InitializeTouchInjection not available.");
+ return false;
+ }
+
+ if (!func(TOUCH_INJECT_MAX_POINTS, TOUCH_FEEDBACK_DEFAULT)) {
+ WinUtils::Log("InitializeTouchInjection failure. GetLastError=%d",
+ GetLastError());
+ return false;
+ }
+
+ sInjectTouchFuncPtr =
+ (InjectTouchInputPtr)GetProcAddress(hMod, "InjectTouchInput");
+ if (!sInjectTouchFuncPtr) {
+ WinUtils::Log("InjectTouchInput not available.");
+ return false;
+ }
+ sTouchInjectInitialized = true;
+ }
+ return true;
+}
+
+bool nsWindowBase::InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint,
+ POINTER_FLAGS aFlags, uint32_t aPressure,
+ uint32_t aOrientation) {
+ if (aId > TOUCH_INJECT_MAX_POINTS) {
+ WinUtils::Log("Pointer ID exceeds maximum. See TOUCH_INJECT_MAX_POINTS.");
+ return false;
+ }
+
+ POINTER_TOUCH_INFO info;
+ memset(&info, 0, sizeof(POINTER_TOUCH_INFO));
+
+ info.touchFlags = TOUCH_FLAG_NONE;
+ info.touchMask =
+ TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE;
+ info.pressure = aPressure;
+ info.orientation = aOrientation;
+
+ info.pointerInfo.pointerFlags = aFlags;
+ info.pointerInfo.pointerType = PT_TOUCH;
+ info.pointerInfo.pointerId = aId;
+ info.pointerInfo.ptPixelLocation.x = aPoint.x;
+ info.pointerInfo.ptPixelLocation.y = aPoint.y;
+
+ info.rcContact.top = info.pointerInfo.ptPixelLocation.y - 2;
+ info.rcContact.bottom = info.pointerInfo.ptPixelLocation.y + 2;
+ info.rcContact.left = info.pointerInfo.ptPixelLocation.x - 2;
+ info.rcContact.right = info.pointerInfo.ptPixelLocation.x + 2;
+
+ for (int i = 0; i < 3; i++) {
+ if (sInjectTouchFuncPtr(1, &info)) {
+ break;
+ }
+ DWORD error = GetLastError();
+ if (error == ERROR_NOT_READY && i < 2) {
+ // We sent it too quickly after the previous injection (see bug 1535140
+ // comment 10). On the first loop iteration we just yield (via Sleep(0))
+ // and try again. If it happens again on the second loop iteration we
+ // explicitly Sleep(1) and try again. If that doesn't work either we just
+ // error out.
+ ::Sleep(i);
+ continue;
+ }
+ WinUtils::Log("InjectTouchInput failure. GetLastError=%d", error);
+ return false;
+ }
+ return true;
+}
+
+void nsWindowBase::ChangedDPI() {
+ if (mWidgetListener) {
+ if (PresShell* presShell = mWidgetListener->GetPresShell()) {
+ presShell->BackingScaleFactorChanged();
+ }
+ }
+}
+
+nsresult nsWindowBase::SynthesizeNativeTouchPoint(
+ uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint, double aPointerPressure,
+ uint32_t aPointerOrientation, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ if (StaticPrefs::apz_test_fails_with_native_injection() ||
+ !InitTouchInjection()) {
+ // If we don't have touch injection from the OS, or if we are running a test
+ // that cannot properly inject events to satisfy the OS requirements (see
+ // bug 1313170) we can just fake it and synthesize the events from here.
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aPointerState == TOUCH_HOVER) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mSynthesizedTouchInput) {
+ mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
+ }
+
+ WidgetEventTime time = CurrentMessageWidgetEventTime();
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
+ mSynthesizedTouchInput.get(), time.mTime, time.mTimeStamp, aPointerId,
+ aPointerState, pointInWindow, aPointerPressure, aPointerOrientation);
+ DispatchTouchInput(inputToDispatch);
+ return NS_OK;
+ }
+
+ bool hover = aPointerState & TOUCH_HOVER;
+ bool contact = aPointerState & TOUCH_CONTACT;
+ bool remove = aPointerState & TOUCH_REMOVE;
+ bool cancel = aPointerState & TOUCH_CANCEL;
+
+ // win api expects a value from 0 to 1024. aPointerPressure is a value
+ // from 0.0 to 1.0.
+ uint32_t pressure = (uint32_t)ceil(aPointerPressure * 1024);
+
+ // If we already know about this pointer id get it's record
+ PointerInfo* info = mActivePointers.Get(aPointerId);
+
+ // We know about this pointer, send an update
+ if (info) {
+ POINTER_FLAGS flags = POINTER_FLAG_UPDATE;
+ if (hover) {
+ flags |= POINTER_FLAG_INRANGE;
+ } else if (contact) {
+ flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_INRANGE;
+ } else if (remove) {
+ flags = POINTER_FLAG_UP;
+ // Remove the pointer from our tracking list. This is nsAutPtr wrapped,
+ // so shouldn't leak.
+ mActivePointers.Remove(aPointerId);
+ }
+
+ if (cancel) {
+ flags |= POINTER_FLAG_CANCELED;
+ }
+
+ return !InjectTouchPoint(aPointerId, aPoint, flags, pressure,
+ aPointerOrientation)
+ ? NS_ERROR_UNEXPECTED
+ : NS_OK;
+ }
+
+ // Missing init state, error out
+ if (remove || cancel) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Create a new pointer
+ info = new PointerInfo(aPointerId, aPoint);
+
+ POINTER_FLAGS flags = POINTER_FLAG_INRANGE;
+ if (contact) {
+ flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN;
+ }
+
+ mActivePointers.Put(aPointerId, info);
+ return !InjectTouchPoint(aPointerId, aPoint, flags, pressure,
+ aPointerOrientation)
+ ? NS_ERROR_UNEXPECTED
+ : NS_OK;
+}
+
+nsresult nsWindowBase::ClearNativeTouchSequence(nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "cleartouch");
+ if (!sTouchInjectInitialized) {
+ return NS_OK;
+ }
+
+ // cancel all input points
+ for (auto iter = mActivePointers.Iter(); !iter.Done(); iter.Next()) {
+ auto info = iter.UserData();
+ InjectTouchPoint(info->mPointerId, info->mPosition, POINTER_FLAG_CANCELED);
+ iter.Remove();
+ }
+
+ nsBaseWidget::ClearNativeTouchSequence(nullptr);
+
+ return NS_OK;
+}
+
+bool nsWindowBase::HandleAppCommandMsg(const MSG& aAppCommandMsg,
+ LRESULT* aRetValue) {
+ ModifierKeyState modKeyState;
+ NativeKey nativeKey(this, aAppCommandMsg, modKeyState);
+ bool consumed = nativeKey.HandleAppCommandMessage();
+ *aRetValue = consumed ? 1 : 0;
+ return consumed;
+}
diff --git a/widget/windows/nsWindowBase.h b/widget/windows/nsWindowBase.h
new file mode 100644
index 0000000000..02a91b0b7c
--- /dev/null
+++ b/widget/windows/nsWindowBase.h
@@ -0,0 +1,139 @@
+/* -*- 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 nsWindowBase_h_
+#define nsWindowBase_h_
+
+#include "mozilla/EventForwards.h"
+#include "nsBaseWidget.h"
+#include "nsClassHashtable.h"
+
+#include <windows.h>
+#include "touchinjection_sdk80.h"
+
+/*
+ * nsWindowBase - Base class of common methods other classes need to access
+ * in both win32 and winrt window classes.
+ */
+class nsWindowBase : public nsBaseWidget {
+ public:
+ typedef mozilla::WidgetEventTime WidgetEventTime;
+
+ /*
+ * Return the HWND or null for this widget.
+ */
+ HWND GetWindowHandle() {
+ return static_cast<HWND>(GetNativeData(NS_NATIVE_WINDOW));
+ }
+
+ /*
+ * Return the parent window, if it exists.
+ */
+ virtual nsWindowBase* GetParentWindowBase(bool aIncludeOwner) = 0;
+
+ /*
+ * Return true if this is a top level widget.
+ */
+ virtual bool IsTopLevelWidget() = 0;
+
+ /*
+ * Init a standard gecko event for this widget.
+ * @param aEvent the event to initialize.
+ * @param aPoint message position in physical coordinates.
+ */
+ virtual void InitEvent(mozilla::WidgetGUIEvent& aEvent,
+ LayoutDeviceIntPoint* aPoint = nullptr) = 0;
+
+ /*
+ * Returns WidgetEventTime instance which is initialized with current message
+ * time.
+ */
+ virtual WidgetEventTime CurrentMessageWidgetEventTime() const = 0;
+
+ /*
+ * Dispatch a gecko event for this widget.
+ * Returns true if it's consumed. Otherwise, false.
+ */
+ virtual bool DispatchWindowEvent(mozilla::WidgetGUIEvent* aEvent) = 0;
+
+ /*
+ * Dispatch a gecko keyboard event for this widget. This
+ * is called by KeyboardLayout to dispatch gecko events.
+ * Returns true if it's consumed. Otherwise, false.
+ */
+ virtual bool DispatchKeyboardEvent(mozilla::WidgetKeyboardEvent* aEvent) = 0;
+
+ /*
+ * Dispatch a gecko wheel event for this widget. This
+ * is called by ScrollHandler to dispatch gecko events.
+ * Returns true if it's consumed. Otherwise, false.
+ */
+ virtual bool DispatchWheelEvent(mozilla::WidgetWheelEvent* aEvent) = 0;
+
+ /*
+ * Dispatch a gecko content command event for this widget. This
+ * is called by ScrollHandler to dispatch gecko events.
+ * Returns true if it's consumed. Otherwise, false.
+ */
+ virtual bool DispatchContentCommandEvent(
+ mozilla::WidgetContentCommandEvent* aEvent) = 0;
+
+ /*
+ * Default dispatch of a plugin event.
+ */
+ virtual bool DispatchPluginEvent(const MSG& aMsg);
+
+ /*
+ * Touch input injection apis
+ */
+ virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) override;
+ virtual nsresult ClearNativeTouchSequence(nsIObserver* aObserver) override;
+
+ /*
+ * WM_APPCOMMAND common handler.
+ * Sends events via NativeKey::HandleAppCommandMessage().
+ */
+ virtual bool HandleAppCommandMsg(const MSG& aAppCommandMsg,
+ LRESULT* aRetValue);
+
+ const InputContext& InputContextRef() const { return mInputContext; }
+
+ protected:
+ virtual int32_t LogToPhys(double aValue) = 0;
+ void ChangedDPI();
+
+ static bool InitTouchInjection();
+ bool InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint,
+ POINTER_FLAGS aFlags, uint32_t aPressure = 1024,
+ uint32_t aOrientation = 90);
+
+ class PointerInfo {
+ public:
+ PointerInfo(int32_t aPointerId, LayoutDeviceIntPoint& aPoint)
+ : mPointerId(aPointerId), mPosition(aPoint) {}
+
+ int32_t mPointerId;
+ LayoutDeviceIntPoint mPosition;
+ };
+
+ nsClassHashtable<nsUint32HashKey, PointerInfo> mActivePointers;
+ static bool sTouchInjectInitialized;
+ static InjectTouchInputPtr sInjectTouchFuncPtr;
+
+ // This is used by SynthesizeNativeTouchPoint to maintain state between
+ // multiple synthesized points, in the case where we can't call InjectTouch
+ // directly.
+ mozilla::UniquePtr<mozilla::MultiTouchInput> mSynthesizedTouchInput;
+
+ protected:
+ InputContext mInputContext;
+};
+
+#endif // nsWindowBase_h_
diff --git a/widget/windows/nsWindowDbg.cpp b/widget/windows/nsWindowDbg.cpp
new file mode 100644
index 0000000000..c0880817d0
--- /dev/null
+++ b/widget/windows/nsWindowDbg.cpp
@@ -0,0 +1,60 @@
+/* -*- 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/. */
+
+/*
+ * nsWindowDbg - Debug related utilities for nsWindow.
+ */
+
+#include "mozilla/Logging.h"
+#include "nsWindowDbg.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+extern mozilla::LazyLogModule gWindowsLog;
+
+#if defined(POPUP_ROLLUP_DEBUG_OUTPUT)
+MSGFEventMsgInfo gMSGFEvents[] = {
+ "MSGF_DIALOGBOX", 0, "MSGF_MESSAGEBOX", 1, "MSGF_MENU", 2,
+ "MSGF_SCROLLBAR", 5, "MSGF_NEXTWINDOW", 6, "MSGF_MAX", 8,
+ "MSGF_USER", 4096, nullptr, 0};
+#endif
+
+static long gEventCounter = 0;
+static long gLastEventMsg = 0;
+
+void PrintEvent(UINT msg, bool aShowAllEvents, bool aShowMouseMoves) {
+ int inx = 0;
+ while (gAllEvents[inx].mId != msg && gAllEvents[inx].mStr != nullptr) {
+ inx++;
+ }
+ if (aShowAllEvents || (!aShowAllEvents && gLastEventMsg != (long)msg)) {
+ if (aShowMouseMoves ||
+ (!aShowMouseMoves && msg != 0x0020 && msg != 0x0200 && msg != 0x0084)) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("%6d - 0x%04X %s\n", gEventCounter++, msg,
+ gAllEvents[inx].mStr ? gAllEvents[inx].mStr : "Unknown"));
+ gLastEventMsg = msg;
+ }
+ }
+}
+
+#ifdef DEBUG
+void DDError(const char* msg, HRESULT hr) {
+ /*XXX make nicer */
+ MOZ_LOG(gWindowsLog, LogLevel::Error,
+ ("direct draw error %s: 0x%08lx\n", msg, hr));
+}
+#endif
+
+#ifdef DEBUG_VK
+bool is_vk_down(int vk) {
+ SHORT st = GetKeyState(vk);
+# ifdef DEBUG
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("is_vk_down vk=%x st=%x\n", vk, st));
+# endif
+ return (st < 0);
+}
+#endif
diff --git a/widget/windows/nsWindowDbg.h b/widget/windows/nsWindowDbg.h
new file mode 100644
index 0000000000..c303b06855
--- /dev/null
+++ b/widget/windows/nsWindowDbg.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 WindowDbg_h__
+#define WindowDbg_h__
+
+/*
+ * nsWindowDbg - Debug related utilities for nsWindow.
+ */
+
+#include "nsWindowDefs.h"
+
+// Enabled main event loop debug event output
+//#define EVENT_DEBUG_OUTPUT
+
+// Enables debug output for popup rollup hooks
+//#define POPUP_ROLLUP_DEBUG_OUTPUT
+
+// Enable window size and state debug output
+//#define WINSTATE_DEBUG_OUTPUT
+
+// nsIWidget defines a set of debug output statements
+// that are called in various places within the code.
+//#define WIDGET_DEBUG_OUTPUT
+
+// Enable IS_VK_DOWN debug output
+//#define DEBUG_VK
+
+// Main event loop debug output flags
+#if defined(EVENT_DEBUG_OUTPUT)
+# define SHOW_REPEAT_EVENTS true
+# define SHOW_MOUSEMOVE_EVENTS false
+#endif // defined(EVENT_DEBUG_OUTPUT)
+
+void PrintEvent(UINT msg, bool aShowAllEvents, bool aShowMouseMoves);
+
+#if defined(POPUP_ROLLUP_DEBUG_OUTPUT)
+typedef struct {
+ char* mStr;
+ int mId;
+} MSGFEventMsgInfo;
+
+# define DISPLAY_NMM_PRT(_arg) \
+ MOZ_LOG(gWindowsLog, mozilla::LogLevel::Info, ((_arg)));
+#else
+# define DISPLAY_NMM_PRT(_arg)
+#endif // defined(POPUP_ROLLUP_DEBUG_OUTPUT)
+
+#if defined(DEBUG)
+void DDError(const char* msg, HRESULT hr);
+#endif // defined(DEBUG)
+
+#if defined(DEBUG_VK)
+bool is_vk_down(int vk);
+# define IS_VK_DOWN is_vk_down
+#else
+# define IS_VK_DOWN(a) (GetKeyState(a) < 0)
+#endif // defined(DEBUG_VK)
+
+#endif /* WindowDbg_h__ */
diff --git a/widget/windows/nsWindowDefs.h b/widget/windows/nsWindowDefs.h
new file mode 100644
index 0000000000..3e4c9d6dcb
--- /dev/null
+++ b/widget/windows/nsWindowDefs.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WindowDefs_h__
+#define WindowDefs_h__
+
+/*
+ * nsWindowDefs - nsWindow related definitions, consts, and macros.
+ */
+
+#include "mozilla/widget/WinMessages.h"
+#include "nsBaseWidget.h"
+#include "nsdefs.h"
+#include "resource.h"
+
+/**************************************************************
+ *
+ * SECTION: defines
+ *
+ **************************************************************/
+
+// ConstrainPosition window positioning slop value
+#define kWindowPositionSlop 20
+
+// Origin of the system context menu when displayed in full screen mode
+#define MOZ_SYSCONTEXT_X_POS 20
+#define MOZ_SYSCONTEXT_Y_POS 20
+
+// 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
+
+// Tablet PC Mouse Input Source
+#define TABLET_INK_SIGNATURE 0xFFFFFF00
+#define TABLET_INK_CHECK 0xFF515700
+#define TABLET_INK_TOUCH 0x00000080
+#define TABLET_INK_ID_MASK 0x0000007F
+#define MOUSE_INPUT_SOURCE() WinUtils::GetMouseInputSource()
+#define MOUSE_POINTERID() WinUtils::GetMousePointerID()
+
+/**************************************************************
+ *
+ * SECTION: enums
+ *
+ **************************************************************/
+
+// nsWindow::sCanQuit
+typedef enum { TRI_UNKNOWN = -1, TRI_FALSE = 0, TRI_TRUE = 1 } TriStateBool;
+
+/**************************************************************
+ *
+ * SECTION: constants
+ *
+ **************************************************************/
+
+/*
+ * Native windows class names
+ *
+ * ::: IMPORTANT :::
+ *
+ * External apps and drivers depend on window class names.
+ * For example, changing the window classes could break
+ * touchpad scrolling or screen readers.
+ */
+const uint32_t kMaxClassNameLength = 40;
+const wchar_t kClassNameHidden[] = L"MozillaHiddenWindowClass";
+const wchar_t kClassNameGeneral[] = L"MozillaWindowClass";
+const wchar_t kClassNameDialog[] = L"MozillaDialogClass";
+const wchar_t kClassNameDropShadow[] = L"MozillaDropShadowWindowClass";
+const wchar_t kClassNameTemp[] = L"MozillaTempWindowClass";
+const wchar_t kClassNameTransition[] = L"MozillaTransitionWindowClass";
+
+/**************************************************************
+ *
+ * SECTION: structs
+ *
+ **************************************************************/
+
+// Used for synthesizing events
+struct KeyPair {
+ uint8_t mGeneral;
+ uint8_t mSpecific;
+ uint16_t mScanCode;
+ KeyPair(uint32_t aGeneral, uint32_t aSpecific)
+ : mGeneral(aGeneral & 0xFF),
+ mSpecific(aSpecific & 0xFF),
+ mScanCode((aGeneral & 0xFFFF0000) >> 16) {}
+ KeyPair(uint8_t aGeneral, uint8_t aSpecific, uint16_t aScanCode)
+ : mGeneral(aGeneral), mSpecific(aSpecific), mScanCode(aScanCode) {}
+};
+
+#if (WINVER < 0x0600)
+struct TITLEBARINFOEX {
+ DWORD cbSize;
+ RECT rcTitleBar;
+ DWORD rgstate[CCHILDREN_TITLEBAR + 1];
+ RECT rgrect[CCHILDREN_TITLEBAR + 1];
+};
+#endif
+
+namespace mozilla {
+namespace widget {
+
+struct MSGResult {
+ // Result for the message.
+ LRESULT& mResult;
+ // If mConsumed is true, the caller shouldn't call next wndproc.
+ bool mConsumed;
+
+ explicit MSGResult(LRESULT* aResult = nullptr)
+ : mResult(aResult ? *aResult : mDefaultResult), mConsumed(false) {}
+
+ private:
+ LRESULT mDefaultResult;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+/**************************************************************
+ *
+ * SECTION: macros
+ *
+ **************************************************************/
+
+#define NSRGB_2_COLOREF(color) \
+ RGB(NS_GET_R(color), NS_GET_G(color), NS_GET_B(color))
+#define COLOREF_2_NSRGB(color) \
+ NS_RGB(GetRValue(color), GetGValue(color), GetBValue(color))
+
+#define VERIFY_WINDOW_STYLE(s) \
+ NS_ASSERTION(((s) & (WS_CHILD | WS_POPUP)) != (WS_CHILD | WS_POPUP), \
+ "WS_POPUP and WS_CHILD are mutually exclusive")
+
+#endif /* WindowDefs_h__ */
diff --git a/widget/windows/nsWindowGfx.cpp b/widget/windows/nsWindowGfx.cpp
new file mode 100644
index 0000000000..672345edb5
--- /dev/null
+++ b/widget/windows/nsWindowGfx.cpp
@@ -0,0 +1,693 @@
+/* -*- 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/. */
+
+/*
+ * nsWindowGfx - Painting and aceleration.
+ */
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Includes
+ **
+ ** Include headers.
+ **
+ **************************************************************
+ **************************************************************/
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/plugins/PluginInstanceParent.h"
+using mozilla::plugins::PluginInstanceParent;
+
+#include "nsWindowGfx.h"
+#include "nsAppRunner.h"
+#include <windows.h>
+#include "gfxEnv.h"
+#include "gfxImageSurface.h"
+#include "gfxUtils.h"
+#include "gfxWindowsSurface.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxDWriteFonts.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsGfxCIID.h"
+#include "gfxContext.h"
+#include "WinUtils.h"
+#include "nsIWidgetListener.h"
+#include "mozilla/Unused.h"
+#include "nsDebug.h"
+
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "ClientLayerManager.h"
+#include "InProcessWinCompositorWidget.h"
+
+#include "nsUXThemeData.h"
+#include "nsUXThemeConstants.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+using namespace mozilla::plugins;
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Variables
+ **
+ ** nsWindow Class static initializations and global variables.
+ **
+ **************************************************************
+ **************************************************************/
+
+/**************************************************************
+ *
+ * SECTION: nsWindow statics
+ *
+ **************************************************************/
+
+static UniquePtr<uint8_t[]> sSharedSurfaceData;
+static IntSize sSharedSurfaceSize;
+
+struct IconMetrics {
+ int32_t xMetric;
+ int32_t yMetric;
+ int32_t defaultSize;
+};
+
+// Corresponds 1:1 to the IconSizeType enum
+static IconMetrics sIconMetrics[] = {
+ {SM_CXSMICON, SM_CYSMICON, 16}, // small icon
+ {SM_CXICON, SM_CYICON, 32} // regular icon
+};
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: nsWindow impl.
+ **
+ ** Paint related nsWindow methods.
+ **
+ **************************************************************
+ **************************************************************/
+
+// GetRegionToPaint returns the invalidated region that needs to be painted
+LayoutDeviceIntRegion nsWindow::GetRegionToPaint(bool aForceFullRepaint,
+ PAINTSTRUCT ps, HDC aDC) {
+ if (aForceFullRepaint) {
+ RECT paintRect;
+ ::GetClientRect(mWnd, &paintRect);
+ return LayoutDeviceIntRegion(WinUtils::ToIntRect(paintRect));
+ }
+
+ HRGN paintRgn = ::CreateRectRgn(0, 0, 0, 0);
+ if (paintRgn != nullptr) {
+ int result = GetRandomRgn(aDC, paintRgn, SYSRGN);
+ if (result == 1) {
+ POINT pt = {0, 0};
+ ::MapWindowPoints(nullptr, mWnd, &pt, 1);
+ ::OffsetRgn(paintRgn, pt.x, pt.y);
+ }
+ LayoutDeviceIntRegion rgn(WinUtils::ConvertHRGNToRegion(paintRgn));
+ ::DeleteObject(paintRgn);
+ return rgn;
+ }
+ return LayoutDeviceIntRegion(WinUtils::ToIntRect(ps.rcPaint));
+}
+
+nsIWidgetListener* nsWindow::GetPaintListener() {
+ if (mDestroyCalled) return nullptr;
+ return mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener;
+}
+
+void nsWindow::ForcePresent() {
+ if (mResizeState != RESIZING) {
+ if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) {
+ remoteRenderer->SendForcePresent();
+ }
+ }
+}
+
+bool nsWindow::OnPaint(HDC aDC, uint32_t aNestingLevel) {
+ // We never have reentrant paint events, except when we're running our RPC
+ // windows event spin loop. If we don't trap for this, we'll try to paint,
+ // but view manager will refuse to paint the surface, resulting is black
+ // flashes on the plugin rendering surface.
+ if (mozilla::ipc::MessageChannel::IsSpinLoopActive() && mPainting)
+ return false;
+
+ DeviceResetReason resetReason = DeviceResetReason::OK;
+ if (gfxWindowsPlatform::GetPlatform()->DidRenderingDeviceReset(
+ &resetReason)) {
+ gfxCriticalNote << "(nsWindow) Detected device reset: " << (int)resetReason;
+
+ gfxWindowsPlatform::GetPlatform()->UpdateRenderMode();
+
+ bool guilty;
+ switch (resetReason) {
+ case DeviceResetReason::HUNG:
+ case DeviceResetReason::RESET:
+ case DeviceResetReason::INVALID_CALL:
+ guilty = true;
+ break;
+ default:
+ guilty = false;
+ break;
+ }
+
+ GPUProcessManager::Get()->OnInProcessDeviceReset(guilty);
+
+ gfxCriticalNote << "(nsWindow) Finished device reset.";
+ return false;
+ }
+
+ // After we CallUpdateWindow to the child, occasionally a WM_PAINT message
+ // is posted to the parent event loop with an empty update rect. Do a
+ // dummy paint so that Windows stops dispatching WM_PAINT in an inifinite
+ // loop. See bug 543788.
+ if (IsPlugin()) {
+ RECT updateRect;
+ if (!GetUpdateRect(mWnd, &updateRect, FALSE) ||
+ (updateRect.left == updateRect.right &&
+ updateRect.top == updateRect.bottom)) {
+ PAINTSTRUCT ps;
+ BeginPaint(mWnd, &ps);
+ EndPaint(mWnd, &ps);
+ return true;
+ }
+
+ if (mWindowType == eWindowType_plugin_ipc_chrome) {
+ // Fire off an async request to the plugin to paint its window
+ mozilla::dom::ContentParent::SendAsyncUpdate(this);
+ ValidateRect(mWnd, nullptr);
+ return true;
+ }
+
+ PluginInstanceParent* instance = reinterpret_cast<PluginInstanceParent*>(
+ ::GetPropW(mWnd, L"PluginInstanceParentProperty"));
+ if (instance) {
+ Unused << instance->CallUpdateWindow();
+ } else {
+ // We should never get here since in-process plugins should have
+ // subclassed our HWND and handled WM_PAINT, but in some cases that
+ // could fail. Return without asserting since it's not our fault.
+ NS_WARNING("Plugin failed to subclass our window");
+ }
+
+ ValidateRect(mWnd, nullptr);
+ return true;
+ }
+
+ PAINTSTRUCT ps;
+
+ // Avoid starting the GPU process for the initial navigator:blank window.
+ if (mIsEarlyBlankWindow) {
+ // Call BeginPaint/EndPaint or Windows will keep sending us messages.
+ ::BeginPaint(mWnd, &ps);
+ ::EndPaint(mWnd, &ps);
+ return true;
+ }
+
+ // Clear window by transparent black when compositor window is used in GPU
+ // process and non-client area rendering by DWM is enabled.
+ // It is for showing non-client area rendering. See nsWindow::UpdateGlass().
+ if (HasGlass() && GetLayerManager()->AsKnowsCompositor() &&
+ GetLayerManager()->AsKnowsCompositor()->GetUseCompositorWnd()) {
+ HDC hdc;
+ RECT rect;
+ hdc = ::GetWindowDC(mWnd);
+ ::GetWindowRect(mWnd, &rect);
+ ::MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
+ ::FillRect(hdc, &rect,
+ reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)));
+ ReleaseDC(mWnd, hdc);
+ }
+
+ if (GetLayerManager()->AsKnowsCompositor() &&
+ !mBounds.IsEqualEdges(mLastPaintBounds)) {
+ // Do an early async composite so that we at least have something on the
+ // screen in the right place, even if the content is out of date.
+ GetLayerManager()->ScheduleComposite();
+ }
+ mLastPaintBounds = mBounds;
+
+#ifdef MOZ_XUL
+ if (!aDC &&
+ (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) &&
+ (eTransparencyTransparent == mTransparencyMode)) {
+ // For layered translucent windows all drawing should go to memory DC and no
+ // WM_PAINT messages are normally generated. To support asynchronous
+ // painting we force generation of WM_PAINT messages by invalidating window
+ // areas with RedrawWindow, InvalidateRect or InvalidateRgn function calls.
+ // BeginPaint/EndPaint must be called to make Windows think that invalid
+ // area is painted. Otherwise it will continue sending the same message
+ // endlessly.
+ ::BeginPaint(mWnd, &ps);
+ ::EndPaint(mWnd, &ps);
+
+ // We're guaranteed to have a widget proxy since we called
+ // GetLayerManager().
+ aDC = mBasicLayersSurface->GetTransparentDC();
+ }
+#endif
+
+ mPainting = true;
+
+#ifdef WIDGET_DEBUG_OUTPUT
+ HRGN debugPaintFlashRegion = nullptr;
+ HDC debugPaintFlashDC = nullptr;
+
+ if (debug_WantPaintFlashing()) {
+ debugPaintFlashRegion = ::CreateRectRgn(0, 0, 0, 0);
+ ::GetUpdateRgn(mWnd, debugPaintFlashRegion, TRUE);
+ debugPaintFlashDC = ::GetDC(mWnd);
+ }
+#endif // WIDGET_DEBUG_OUTPUT
+
+ HDC hDC = aDC ? aDC : (::BeginPaint(mWnd, &ps));
+ mPaintDC = hDC;
+
+#ifdef MOZ_XUL
+ bool forceRepaint = aDC || (eTransparencyTransparent == mTransparencyMode);
+#else
+ bool forceRepaint = nullptr != aDC;
+#endif
+ LayoutDeviceIntRegion region = GetRegionToPaint(forceRepaint, ps, hDC);
+
+ if (GetLayerManager()->AsKnowsCompositor()) {
+ // We need to paint to the screen even if nothing changed, since if we
+ // don't have a compositing window manager, our pixels could be stale.
+ GetLayerManager()->SetNeedsComposite(true);
+ GetLayerManager()->SendInvalidRegion(region.ToUnknownRegion());
+ }
+
+ RefPtr<nsWindow> strongThis(this);
+
+ nsIWidgetListener* listener = GetPaintListener();
+ if (listener) {
+ listener->WillPaintWindow(this);
+ }
+ // Re-get the listener since the will paint notification may have killed it.
+ listener = GetPaintListener();
+ if (!listener) {
+ return false;
+ }
+
+ if (GetLayerManager()->AsKnowsCompositor() &&
+ GetLayerManager()->NeedsComposite()) {
+ GetLayerManager()->ScheduleComposite();
+ GetLayerManager()->SetNeedsComposite(false);
+ }
+
+ bool result = true;
+ if (!region.IsEmpty() && listener) {
+ // Should probably pass in a real region here, using GetRandomRgn
+ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/clipping_4q0e.asp
+
+#ifdef WIDGET_DEBUG_OUTPUT
+ debug_DumpPaintEvent(stdout, this, region.ToUnknownRegion(), "noname",
+ (int32_t)mWnd);
+#endif // WIDGET_DEBUG_OUTPUT
+
+ switch (GetLayerManager()->GetBackendType()) {
+ case LayersBackend::LAYERS_BASIC: {
+ RefPtr<gfxASurface> targetSurface;
+
+#if defined(MOZ_XUL)
+ // don't support transparency for non-GDI rendering, for now
+ if (eTransparencyTransparent == mTransparencyMode) {
+ // This mutex needs to be held when EnsureTransparentSurface is
+ // called.
+ MutexAutoLock lock(mBasicLayersSurface->GetTransparentSurfaceLock());
+ targetSurface = mBasicLayersSurface->EnsureTransparentSurface();
+ }
+#endif
+
+ RefPtr<gfxWindowsSurface> targetSurfaceWin;
+ if (!targetSurface) {
+ uint32_t flags = (mTransparencyMode == eTransparencyOpaque)
+ ? 0
+ : gfxWindowsSurface::FLAG_IS_TRANSPARENT;
+ targetSurfaceWin = new gfxWindowsSurface(hDC, flags);
+ targetSurface = targetSurfaceWin;
+ }
+
+ if (!targetSurface) {
+ NS_ERROR("Invalid RenderMode!");
+ return false;
+ }
+
+ RECT paintRect;
+ ::GetClientRect(mWnd, &paintRect);
+ RefPtr<DrawTarget> dt = gfxPlatform::CreateDrawTargetForSurface(
+ targetSurface, IntSize(paintRect.right - paintRect.left,
+ paintRect.bottom - paintRect.top));
+ if (!dt || !dt->IsValid()) {
+ gfxWarning()
+ << "nsWindow::OnPaint failed in CreateDrawTargetForSurface";
+ return false;
+ }
+
+ // don't need to double buffer with anything but GDI
+ BufferMode doubleBuffering = mozilla::layers::BufferMode::BUFFER_NONE;
+#ifdef MOZ_XUL
+ switch (mTransparencyMode) {
+ case eTransparencyGlass:
+ case eTransparencyBorderlessGlass:
+ default:
+ // If we're not doing translucency, then double buffer
+ doubleBuffering = mozilla::layers::BufferMode::BUFFERED;
+ break;
+ case eTransparencyTransparent:
+ // If we're rendering with translucency, we're going to be
+ // rendering the whole window; make sure we clear it first
+ dt->ClearRect(
+ Rect(0.f, 0.f, dt->GetSize().width, dt->GetSize().height));
+ break;
+ }
+#else
+ doubleBuffering = mozilla::layers::BufferMode::BUFFERED;
+#endif
+
+ RefPtr<gfxContext> thebesContext = gfxContext::CreateOrNull(dt);
+ MOZ_ASSERT(thebesContext); // already checked draw target above
+
+ {
+ AutoLayerManagerSetup setupLayerManager(this, thebesContext,
+ doubleBuffering);
+ result = listener->PaintWindow(this, region);
+ }
+
+#ifdef MOZ_XUL
+ if (eTransparencyTransparent == mTransparencyMode) {
+ // Data from offscreen drawing surface was copied to memory bitmap of
+ // transparent bitmap. Now it can be read from memory bitmap to apply
+ // alpha channel and after that displayed on the screen.
+ mBasicLayersSurface->RedrawTransparentWindow();
+ }
+#endif
+ } break;
+ case LayersBackend::LAYERS_CLIENT:
+ case LayersBackend::LAYERS_WR: {
+ result = listener->PaintWindow(this, region);
+ if (!gfxEnv::DisableForcePresent() &&
+ gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
+ "nsWindow::ForcePresent", this, &nsWindow::ForcePresent);
+ NS_DispatchToMainThread(event);
+ }
+ } break;
+ default:
+ NS_ERROR("Unknown layers backend used!");
+ break;
+ }
+ }
+
+ if (!aDC) {
+ ::EndPaint(mWnd, &ps);
+ }
+
+ mPaintDC = nullptr;
+ mLastPaintEndTime = TimeStamp::Now();
+
+#if defined(WIDGET_DEBUG_OUTPUT)
+ if (debug_WantPaintFlashing()) {
+ // Only flash paint events which have not ignored the paint message.
+ // Those that ignore the paint message aren't painting anything so there
+ // is only the overhead of the dispatching the paint event.
+ if (result) {
+ ::InvertRgn(debugPaintFlashDC, debugPaintFlashRegion);
+ PR_Sleep(PR_MillisecondsToInterval(30));
+ ::InvertRgn(debugPaintFlashDC, debugPaintFlashRegion);
+ PR_Sleep(PR_MillisecondsToInterval(30));
+ }
+ ::ReleaseDC(mWnd, debugPaintFlashDC);
+ ::DeleteObject(debugPaintFlashRegion);
+ }
+#endif // WIDGET_DEBUG_OUTPUT
+
+ mPainting = false;
+
+ // Re-get the listener since painting may have killed it.
+ listener = GetPaintListener();
+ if (listener) listener->DidPaintWindow();
+
+ if (aNestingLevel == 0 && ::GetUpdateRect(mWnd, nullptr, false)) {
+ OnPaint(aDC, 1);
+ }
+
+ return result;
+}
+
+// This override of CreateCompositor is to add support for sending the IPC
+// call for RequesetFxrOutput as soon as the compositor for this widget is
+// available.
+void nsWindow::CreateCompositor() {
+ nsWindowBase::CreateCompositor();
+
+ if (mRequestFxrOutputPending) {
+ GetRemoteRenderer()->SendRequestFxrOutput();
+ }
+}
+
+void nsWindow::RequestFxrOutput() {
+ if (GetRemoteRenderer() != nullptr) {
+ MOZ_CRASH("RequestFxrOutput should happen before Compositor is created.");
+ } else {
+ // The compositor isn't ready, so indicate to make the IPC call when
+ // it is available.
+ mRequestFxrOutputPending = true;
+ }
+}
+
+LayoutDeviceIntSize nsWindowGfx::GetIconMetrics(IconSizeType aSizeType) {
+ int32_t width = ::GetSystemMetrics(sIconMetrics[aSizeType].xMetric);
+ int32_t height = ::GetSystemMetrics(sIconMetrics[aSizeType].yMetric);
+
+ if (width == 0 || height == 0) {
+ width = height = sIconMetrics[aSizeType].defaultSize;
+ }
+
+ return LayoutDeviceIntSize(width, height);
+}
+
+nsresult nsWindowGfx::CreateIcon(imgIContainer* aContainer, bool aIsCursor,
+ LayoutDeviceIntPoint aHotspot,
+ LayoutDeviceIntSize aScaledSize,
+ HICON* aIcon) {
+ MOZ_ASSERT(aHotspot.x >= 0 && aHotspot.y >= 0);
+ MOZ_ASSERT((aScaledSize.width > 0 && aScaledSize.height > 0) ||
+ (aScaledSize.width == 0 && aScaledSize.height == 0));
+
+ // Get the image data
+ RefPtr<SourceSurface> surface = aContainer->GetFrame(
+ imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ NS_ENSURE_TRUE(surface, NS_ERROR_NOT_AVAILABLE);
+
+ IntSize frameSize = surface->GetSize();
+ if (frameSize.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ IntSize iconSize(aScaledSize.width, aScaledSize.height);
+ if (iconSize == IntSize(0, 0)) { // use frame's intrinsic size
+ iconSize = frameSize;
+ }
+
+ RefPtr<DataSourceSurface> dataSurface;
+ bool mappedOK;
+ DataSourceSurface::MappedSurface map;
+
+ if (iconSize != frameSize) {
+ // Scale the surface
+ dataSurface =
+ Factory::CreateDataSourceSurface(iconSize, SurfaceFormat::B8G8R8A8);
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+ mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map);
+ NS_ENSURE_TRUE(mappedOK, NS_ERROR_FAILURE);
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
+ BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride,
+ SurfaceFormat::B8G8R8A8);
+ if (!dt) {
+ gfxWarning()
+ << "nsWindowGfx::CreatesIcon failed in CreateDrawTargetForData";
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ dt->DrawSurface(surface, Rect(0, 0, iconSize.width, iconSize.height),
+ Rect(0, 0, frameSize.width, frameSize.height),
+ DrawSurfaceOptions(),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+ } else if (surface->GetFormat() != SurfaceFormat::B8G8R8A8) {
+ // Convert format to SurfaceFormat::B8G8R8A8
+ dataSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
+ surface, SurfaceFormat::B8G8R8A8);
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+ mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map);
+ } else {
+ dataSurface = surface->GetDataSurface();
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+ mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map);
+ }
+ NS_ENSURE_TRUE(dataSurface && mappedOK, NS_ERROR_FAILURE);
+ MOZ_ASSERT(dataSurface->GetFormat() == SurfaceFormat::B8G8R8A8);
+
+ uint8_t* data = nullptr;
+ UniquePtr<uint8_t[]> autoDeleteArray;
+ if (map.mStride == BytesPerPixel(dataSurface->GetFormat()) * iconSize.width) {
+ // Mapped data is already packed
+ data = map.mData;
+ } else {
+ // We can't use map.mData since the pixels are not packed (as required by
+ // CreateDIBitmap, which is called under the DataToBitmap call below).
+ //
+ // We must unmap before calling SurfaceToPackedBGRA because it needs access
+ // to the pixel data.
+ dataSurface->Unmap();
+ map.mData = nullptr;
+
+ autoDeleteArray = SurfaceToPackedBGRA(dataSurface);
+ data = autoDeleteArray.get();
+ NS_ENSURE_TRUE(data, NS_ERROR_FAILURE);
+ }
+
+ HBITMAP bmp = DataToBitmap(data, iconSize.width, -iconSize.height, 32);
+ uint8_t* a1data = Data32BitTo1Bit(data, iconSize.width, iconSize.height);
+ if (map.mData) {
+ dataSurface->Unmap();
+ }
+ if (!a1data) {
+ return NS_ERROR_FAILURE;
+ }
+
+ HBITMAP mbmp = DataToBitmap(a1data, iconSize.width, -iconSize.height, 1);
+ free(a1data);
+
+ ICONINFO info = {0};
+ info.fIcon = !aIsCursor;
+ info.xHotspot = aHotspot.x;
+ info.yHotspot = aHotspot.y;
+ info.hbmMask = mbmp;
+ info.hbmColor = bmp;
+
+ HCURSOR icon = ::CreateIconIndirect(&info);
+ ::DeleteObject(mbmp);
+ ::DeleteObject(bmp);
+ if (!icon) return NS_ERROR_FAILURE;
+ *aIcon = icon;
+ return NS_OK;
+}
+
+// Adjust cursor image data
+uint8_t* nsWindowGfx::Data32BitTo1Bit(uint8_t* aImageData, uint32_t aWidth,
+ uint32_t aHeight) {
+ // We need (aWidth + 7) / 8 bytes plus zero-padding up to a multiple of
+ // 4 bytes for each row (HBITMAP requirement). Bug 353553.
+ uint32_t outBpr = ((aWidth + 31) / 8) & ~3;
+
+ // Allocate and clear mask buffer
+ uint8_t* outData = (uint8_t*)calloc(outBpr, aHeight);
+ if (!outData) return nullptr;
+
+ int32_t* imageRow = (int32_t*)aImageData;
+ for (uint32_t curRow = 0; curRow < aHeight; curRow++) {
+ uint8_t* outRow = outData + curRow * outBpr;
+ uint8_t mask = 0x80;
+ for (uint32_t curCol = 0; curCol < aWidth; curCol++) {
+ // Use sign bit to test for transparency, as alpha byte is highest byte
+ if (*imageRow++ < 0) *outRow |= mask;
+
+ mask >>= 1;
+ if (!mask) {
+ outRow++;
+ mask = 0x80;
+ }
+ }
+ }
+
+ return outData;
+}
+
+/**
+ * Convert the given image data to a HBITMAP. If the requested depth is
+ * 32 bit, a bitmap with an alpha channel will be returned.
+ *
+ * @param aImageData The image data to convert. Must use the format accepted
+ * by CreateDIBitmap.
+ * @param aWidth With of the bitmap, in pixels.
+ * @param aHeight Height of the image, in pixels.
+ * @param aDepth Image depth, in bits. Should be one of 1, 24 and 32.
+ *
+ * @return The HBITMAP representing the image. Caller should call
+ * DeleteObject when done with the bitmap.
+ * On failure, nullptr will be returned.
+ */
+HBITMAP nsWindowGfx::DataToBitmap(uint8_t* aImageData, uint32_t aWidth,
+ uint32_t aHeight, uint32_t aDepth) {
+ HDC dc = ::GetDC(nullptr);
+
+ if (aDepth == 32) {
+ // Alpha channel. We need the new header.
+ BITMAPV4HEADER head = {0};
+ head.bV4Size = sizeof(head);
+ head.bV4Width = aWidth;
+ head.bV4Height = aHeight;
+ head.bV4Planes = 1;
+ head.bV4BitCount = aDepth;
+ head.bV4V4Compression = BI_BITFIELDS;
+ head.bV4SizeImage = 0; // Uncompressed
+ head.bV4XPelsPerMeter = 0;
+ head.bV4YPelsPerMeter = 0;
+ head.bV4ClrUsed = 0;
+ head.bV4ClrImportant = 0;
+
+ head.bV4RedMask = 0x00FF0000;
+ head.bV4GreenMask = 0x0000FF00;
+ head.bV4BlueMask = 0x000000FF;
+ head.bV4AlphaMask = 0xFF000000;
+
+ HBITMAP bmp = ::CreateDIBitmap(
+ dc, reinterpret_cast<CONST BITMAPINFOHEADER*>(&head), CBM_INIT,
+ aImageData, reinterpret_cast<CONST BITMAPINFO*>(&head), DIB_RGB_COLORS);
+ ::ReleaseDC(nullptr, dc);
+ return bmp;
+ }
+
+ char reserved_space[sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 2];
+ BITMAPINFOHEADER& head = *(BITMAPINFOHEADER*)reserved_space;
+
+ head.biSize = sizeof(BITMAPINFOHEADER);
+ head.biWidth = aWidth;
+ head.biHeight = aHeight;
+ head.biPlanes = 1;
+ head.biBitCount = (WORD)aDepth;
+ head.biCompression = BI_RGB;
+ head.biSizeImage = 0; // Uncompressed
+ head.biXPelsPerMeter = 0;
+ head.biYPelsPerMeter = 0;
+ head.biClrUsed = 0;
+ head.biClrImportant = 0;
+
+ BITMAPINFO& bi = *(BITMAPINFO*)reserved_space;
+
+ if (aDepth == 1) {
+ RGBQUAD black = {0, 0, 0, 0};
+ RGBQUAD white = {255, 255, 255, 0};
+
+ bi.bmiColors[0] = white;
+ bi.bmiColors[1] = black;
+ }
+
+ HBITMAP bmp =
+ ::CreateDIBitmap(dc, &head, CBM_INIT, aImageData, &bi, DIB_RGB_COLORS);
+ ::ReleaseDC(nullptr, dc);
+ return bmp;
+}
diff --git a/widget/windows/nsWindowGfx.h b/widget/windows/nsWindowGfx.h
new file mode 100644
index 0000000000..0d5fd9e01a
--- /dev/null
+++ b/widget/windows/nsWindowGfx.h
@@ -0,0 +1,35 @@
+/* -*- 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 WindowGfx_h__
+#define WindowGfx_h__
+
+/*
+ * nsWindowGfx - Painting and aceleration.
+ */
+
+#include "nsWindow.h"
+#include <imgIContainer.h>
+
+class nsWindowGfx {
+ public:
+ enum IconSizeType { kSmallIcon, kRegularIcon };
+ static mozilla::LayoutDeviceIntSize GetIconMetrics(IconSizeType aSizeType);
+ static nsresult CreateIcon(imgIContainer* aContainer, bool aIsCursor,
+ mozilla::LayoutDeviceIntPoint aHotspot,
+ mozilla::LayoutDeviceIntSize aScaledSize,
+ HICON* aIcon);
+
+ private:
+ /**
+ * Cursor helpers
+ */
+ static uint8_t* Data32BitTo1Bit(uint8_t* aImageData, uint32_t aWidth,
+ uint32_t aHeight);
+ static HBITMAP DataToBitmap(uint8_t* aImageData, uint32_t aWidth,
+ uint32_t aHeight, uint32_t aDepth);
+};
+
+#endif // WindowGfx_h__
diff --git a/widget/windows/nsdefs.h b/widget/windows/nsdefs.h
new file mode 100644
index 0000000000..5c4134c7fd
--- /dev/null
+++ b/widget/windows/nsdefs.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSDEFS_H
+#define NSDEFS_H
+
+#include <windows.h>
+
+#ifdef _DEBUG
+# define BREAK_TO_DEBUGGER DebugBreak()
+#else
+# define BREAK_TO_DEBUGGER
+#endif
+
+#ifdef _DEBUG
+# define VERIFY(exp) \
+ if (!(exp)) { \
+ GetLastError(); \
+ BREAK_TO_DEBUGGER; \
+ }
+#else // !_DEBUG
+# define VERIFY(exp) (exp)
+#endif // !_DEBUG
+
+// Win32 logging modules:
+// nsWindow, nsSound, and nsClipboard
+//
+// Logging can be changed at runtime without recompiling in the General
+// property page of Visual Studio under the "Environment" property.
+//
+// Two variables are of importance to be set:
+// MOZ_LOG and MOZ_LOG_FILE
+//
+// MOZ_LOG:
+// MOZ_LOG=all:5 (To log everything completely)
+// MOZ_LOG=nsWindow:5,nsSound:5,nsClipboard:5
+// (To log windows widget stuff)
+// MOZ_LOG= (To turn off logging)
+//
+// MOZ_LOG_FILE:
+// MOZ_LOG_FILE=C:\log.txt (To a file on disk)
+// MOZ_LOG_FILE=WinDebug (To the debug window)
+// MOZ_LOG_FILE= (To stdout/stderr)
+
+#endif // NSDEFS_H
diff --git a/widget/windows/res/aliasb.cur b/widget/windows/res/aliasb.cur
new file mode 100644
index 0000000000..8d9ac9478c
--- /dev/null
+++ b/widget/windows/res/aliasb.cur
Binary files differ
diff --git a/widget/windows/res/cell.cur b/widget/windows/res/cell.cur
new file mode 100644
index 0000000000..decfbdcac5
--- /dev/null
+++ b/widget/windows/res/cell.cur
Binary files differ
diff --git a/widget/windows/res/col_resize.cur b/widget/windows/res/col_resize.cur
new file mode 100644
index 0000000000..8f7f675122
--- /dev/null
+++ b/widget/windows/res/col_resize.cur
Binary files differ
diff --git a/widget/windows/res/copy.cur b/widget/windows/res/copy.cur
new file mode 100644
index 0000000000..87f1519cd1
--- /dev/null
+++ b/widget/windows/res/copy.cur
Binary files differ
diff --git a/widget/windows/res/grab.cur b/widget/windows/res/grab.cur
new file mode 100644
index 0000000000..db7ad5aed3
--- /dev/null
+++ b/widget/windows/res/grab.cur
Binary files differ
diff --git a/widget/windows/res/grabbing.cur b/widget/windows/res/grabbing.cur
new file mode 100644
index 0000000000..e0dfd04e4d
--- /dev/null
+++ b/widget/windows/res/grabbing.cur
Binary files differ
diff --git a/widget/windows/res/none.cur b/widget/windows/res/none.cur
new file mode 100644
index 0000000000..2114dfaee3
--- /dev/null
+++ b/widget/windows/res/none.cur
Binary files differ
diff --git a/widget/windows/res/row_resize.cur b/widget/windows/res/row_resize.cur
new file mode 100644
index 0000000000..a7369d32d1
--- /dev/null
+++ b/widget/windows/res/row_resize.cur
Binary files differ
diff --git a/widget/windows/res/select.cur b/widget/windows/res/select.cur
new file mode 100644
index 0000000000..5a88b3707e
--- /dev/null
+++ b/widget/windows/res/select.cur
Binary files differ
diff --git a/widget/windows/res/vertical_text.cur b/widget/windows/res/vertical_text.cur
new file mode 100644
index 0000000000..3de04ebec3
--- /dev/null
+++ b/widget/windows/res/vertical_text.cur
Binary files differ
diff --git a/widget/windows/res/zoom_in.cur b/widget/windows/res/zoom_in.cur
new file mode 100644
index 0000000000..b594d79271
--- /dev/null
+++ b/widget/windows/res/zoom_in.cur
Binary files differ
diff --git a/widget/windows/res/zoom_out.cur b/widget/windows/res/zoom_out.cur
new file mode 100644
index 0000000000..7e495fbaa4
--- /dev/null
+++ b/widget/windows/res/zoom_out.cur
Binary files differ
diff --git a/widget/windows/resource.h b/widget/windows/resource.h
new file mode 100644
index 0000000000..a367e9f3e9
--- /dev/null
+++ b/widget/windows/resource.h
@@ -0,0 +1,16 @@
+/* -*- 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/. */
+#define IDC_GRAB 4101
+#define IDC_GRABBING 4102
+#define IDC_CELL 4103
+#define IDC_COPY 4104
+#define IDC_ALIAS 4105
+#define IDC_ZOOMIN 4106
+#define IDC_ZOOMOUT 4107
+#define IDC_COLRESIZE 4108
+#define IDC_ROWRESIZE 4109
+#define IDC_VERTICALTEXT 4110
+#define IDC_DUMMY_CE_MENUBAR 4111
+#define IDC_NONE 4112
diff --git a/widget/windows/tests/TestUriValidation.cpp b/widget/windows/tests/TestUriValidation.cpp
new file mode 100644
index 0000000000..d8a0ca09ce
--- /dev/null
+++ b/widget/windows/tests/TestUriValidation.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#define MOZ_USE_LAUNCHER_ERROR
+
+#define UNICODE
+#include "mozilla/UrlmonHeaderOnlyUtils.h"
+#include "TestUrisToValidate.h"
+
+#include <urlmon.h>
+
+using namespace mozilla;
+
+static LauncherResult<_bstr_t> ShellValidateUri(const wchar_t* aUri) {
+ LauncherResult<UniqueAbsolutePidl> pidlResult = ShellParseDisplayName(aUri);
+ if (pidlResult.isErr()) {
+ return pidlResult.propagateErr();
+ }
+ UniqueAbsolutePidl pidl = pidlResult.unwrap();
+
+ // |pidl| is an absolute path. IShellFolder::GetDisplayNameOf requires a
+ // valid child ID, so the first thing we need to resolve is the IShellFolder
+ // for |pidl|'s parent, as well as the childId that represents |pidl|.
+ // Fortunately SHBindToParent does exactly that!
+ PCUITEMID_CHILD childId = nullptr;
+ RefPtr<IShellFolder> parentFolder;
+ HRESULT hr = SHBindToParent(pidl.get(), IID_IShellFolder,
+ getter_AddRefs(parentFolder), &childId);
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ // Now we retrieve the display name of |childId|, telling the shell that we
+ // plan to have the string parsed.
+ STRRET strret;
+ hr = parentFolder->GetDisplayNameOf(childId, SHGDN_FORPARSING, &strret);
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ // StrRetToBSTR automatically takes care of freeing any dynamically
+ // allocated memory in |strret|.
+ _bstr_t bstrUri;
+ hr = StrRetToBSTR(&strret, nullptr, bstrUri.GetAddress());
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ return bstrUri;
+}
+
+static LauncherResult<_bstr_t> GetFragment(const wchar_t* aUri) {
+ constexpr DWORD flags =
+ Uri_CREATE_NO_DECODE_EXTRA_INFO | Uri_CREATE_CANONICALIZE |
+ Uri_CREATE_CRACK_UNKNOWN_SCHEMES | Uri_CREATE_PRE_PROCESS_HTML_URI |
+ Uri_CREATE_IE_SETTINGS;
+ RefPtr<IUri> uri;
+ HRESULT hr = CreateUri(aUri, flags, 0, getter_AddRefs(uri));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ _bstr_t bstrFragment;
+ hr = uri->GetFragment(bstrFragment.GetAddress());
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+ return bstrFragment;
+}
+
+static bool RunSingleTest(const wchar_t* aUri) {
+ LauncherResult<_bstr_t> uriOld = ShellValidateUri(aUri),
+ uriNew = UrlmonValidateUri(aUri);
+ if (uriOld.isErr() != uriNew.isErr()) {
+ printf("TEST-FAILED | UriValidation | Validation result mismatch on %S\n",
+ aUri);
+ return false;
+ }
+
+ if (uriOld.isErr()) {
+ if (uriOld.unwrapErr().mError != uriNew.unwrapErr().mError) {
+ printf("TEST-FAILED | UriValidation | Error code mismatch on %S\n", aUri);
+ return false;
+ }
+ return true;
+ }
+
+ LauncherResult<_bstr_t> bstrFragment = GetFragment(aUri);
+ if (bstrFragment.isErr()) {
+ printf("TEST-FAILED | UriValidation | Failed to get a fragment from %S\n",
+ aUri);
+ return false;
+ }
+
+ // We validate a uri with two logics: the current one UrlmonValidateUri and
+ // the older one ShellValidateUri, to make sure the same validation result.
+ // We introduced UrlmonValidateUri because ShellValidateUri drops a fragment
+ // in a uri due to the design of Windows. To bypass the fragment issue, we
+ // extract a fragment and appends it into the validated string, and compare.
+ _bstr_t bstrUriOldCorrected = uriOld.unwrap() + bstrFragment.unwrap();
+ const _bstr_t& bstrUriNew = uriNew.unwrap();
+ if (bstrUriOldCorrected != bstrUriNew) {
+ printf("TEST-FAILED | UriValidation | %S %S %S\n", aUri,
+ static_cast<const wchar_t*>(bstrUriOldCorrected),
+ static_cast<const wchar_t*>(bstrUriNew));
+ return false;
+ }
+
+ return true;
+}
+
+int wmain(int argc, wchar_t* argv[]) {
+ HRESULT hr = CoInitialize(nullptr);
+ if (FAILED(hr)) {
+ return 1;
+ }
+
+ bool isOk = true;
+
+ if (argc == 2) {
+ isOk = RunSingleTest(argv[1]);
+ } else {
+ for (const wchar_t*& testUri : kTestUris) {
+ if (!RunSingleTest(testUri)) {
+ isOk = false;
+ }
+ }
+ }
+
+ CoUninitialize();
+ return isOk ? 0 : 1;
+}
diff --git a/widget/windows/tests/TestUrisToValidate.h b/widget/windows/tests/TestUrisToValidate.h
new file mode 100644
index 0000000000..cb00366d1e
--- /dev/null
+++ b/widget/windows/tests/TestUrisToValidate.h
@@ -0,0 +1,471 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_TestUrisToValidate_h
+#define mozilla_TestUrisToValidate_h
+
+const wchar_t* kTestUris[] = {
+ L"callto:%.txt",
+ L"callto:%00.txt",
+ L"callto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"fdaction:%.txt",
+ L"fdaction:%00.txt",
+ L"fdaction:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"feed:%.txt",
+ L"feed:%00.txt",
+ L"feed:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"feeds:%.txt",
+ L"feeds:%00.txt",
+ L"feeds:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"file:///%.txt",
+ L"file:///%00.txt",
+ L"file:///%41%2D%31%5Ftest%22ing?%41%31%00.txt",
+ L"firefox.url:%.txt",
+ L"firefox.url:%00.txt",
+ L"firefox.url:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"firefoxurl:%.txt",
+ L"firefoxurl:%00.txt",
+ L"firefoxurl:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ftp:%.txt",
+ L"ftp:%00.txt",
+ L"ftp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"gopher:%.txt",
+ L"gopher:%00.txt",
+ L"gopher:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"gtalk:%.txt",
+ L"gtalk:%00.txt",
+ L"gtalk:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"HTTP:%.txt",
+ L"HTTP:%00.txt",
+ L"HTTP:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"http:%.txt",
+ L"http:%00.txt",
+ L"http:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"https://bug389580.bmoattachments.org/%.txt",
+ L"https://bug389580.bmoattachments.org/%00.txt",
+ L"https://bug389580.bmoattachments.org/"
+ L"%41%2D%31%5Ftest%22ing?%41%31%00.txt",
+ L"ie.ftp:%.txt",
+ L"ie.ftp:%00.txt",
+ L"ie.ftp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ie.http:%.txt",
+ L"ie.http:%00.txt",
+ L"ie.http:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ie.https:%.txt",
+ L"ie.https:%00.txt",
+ L"ie.https:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"irc:%.txt",
+ L"irc:%00.txt",
+ L"irc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ircs:%.txt",
+ L"ircs:%00.txt",
+ L"ircs:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itms:%.txt",
+ L"itms:%00.txt",
+ L"itms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itmss:%.txt",
+ L"itmss:%00.txt",
+ L"itmss:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itpc:%.txt",
+ L"itpc:%00.txt",
+ L"itpc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itunes.assocprotocol.itms:%.txt",
+ L"itunes.assocprotocol.itms:%00.txt",
+ L"itunes.assocprotocol.itms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itunes.assocprotocol.itmss:%.txt",
+ L"itunes.assocprotocol.itmss:%00.txt",
+ L"itunes.assocprotocol.itmss:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itunes.assocprotocol.itpc:%.txt",
+ L"itunes.assocprotocol.itpc:%00.txt",
+ L"itunes.assocprotocol.itpc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ldap:%.txt",
+ L"ldap:%00.txt",
+ L"ldap:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mailto:%.txt",
+ L"mailto:%00.txt",
+ L"mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mms:%.txt",
+ L"mms:%00.txt",
+ L"mms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mmst:%.txt",
+ L"mmst:%00.txt",
+ L"mmst:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mmst:%.txt",
+ L"mmst:%00.txt",
+ L"mmst:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mmsu:%.txt",
+ L"mmsu:%00.txt",
+ L"mmsu:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mmsu:%.txt",
+ L"mmsu:%00.txt",
+ L"mmsu:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"https://bug389580.bmoattachments.org/"
+ L"Mozilla%20Thunderbird.Url.Mailto:%.txt",
+ L"https://bug389580.bmoattachments.org/"
+ L"Mozilla%20Thunderbird.Url.Mailto:%00.txt",
+ L"https://bug389580.bmoattachments.org/"
+ L"Mozilla%20Thunderbird.Url.Mailto:%41%2D%31%5Ftest%22ing?%41%31%00.txt",
+ L"navigatorurl:%.txt",
+ L"navigatorurl:%00.txt",
+ L"navigatorurl:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"news:%.txt",
+ L"news:%00.txt",
+ L"news:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"nntp:%.txt",
+ L"nntp:%00.txt",
+ L"nntp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"oms:%.txt",
+ L"oms:%00.txt",
+ L"oms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"outlook:%.txt",
+ L"outlook:%00.txt",
+ L"outlook:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"outlook.url.feed:%.txt",
+ L"outlook.url.feed:%00.txt",
+ L"outlook.url.feed:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"outlook.url.mailto:%.txt",
+ L"outlook.url.mailto:%00.txt",
+ L"outlook.url.mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"outlook.url.webcal:%.txt",
+ L"outlook.url.webcal:%00.txt",
+ L"outlook.url.webcal:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"outlookfeed:%.txt",
+ L"outlookfeed:%00.txt",
+ L"outlookfeed:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"outlookfeeds:%.txt",
+ L"outlookfeeds:%00.txt",
+ L"outlookfeeds:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"pnm:%.txt",
+ L"pnm:%00.txt",
+ L"pnm:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"prls.intappfile.ftp:%.txt",
+ L"prls.intappfile.ftp:%00.txt",
+ L"prls.intappfile.ftp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"prls.intappfile.http:%.txt",
+ L"prls.intappfile.http:%00.txt",
+ L"prls.intappfile.http:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"prls.intappfile.https:%.txt",
+ L"prls.intappfile.https:%00.txt",
+ L"prls.intappfile.https:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"prls.intappfile.mailto:%.txt",
+ L"prls.intappfile.mailto:%00.txt",
+ L"prls.intappfile.mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"rlogin:%.txt",
+ L"rlogin:%00.txt",
+ L"rlogin:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"rtsp:%.txt",
+ L"rtsp:%00.txt",
+ L"rtsp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"scp:%.txt",
+ L"scp:%00.txt",
+ L"scp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"sftp:%.txt",
+ L"sftp:%00.txt",
+ L"sftp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"sip:%.txt",
+ L"sip:%00.txt",
+ L"sip:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"skype:%.txt",
+ L"skype:%00.txt",
+ L"skype:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"snews:%.txt",
+ L"snews:%00.txt",
+ L"snews:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"telnet:%.txt",
+ L"telnet:%00.txt",
+ L"telnet:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"thunderbird.url.mailto:%.txt",
+ L"thunderbird.url.mailto:%00.txt",
+ L"thunderbird.url.mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"thunderbird.url.news:%.txt",
+ L"thunderbird.url.news:%00.txt",
+ L"thunderbird.url.news:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"tn3270:%.txt",
+ L"tn3270:%00.txt",
+ L"tn3270:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"tscrec4:%.txt",
+ L"tscrec4:%00.txt",
+ L"tscrec4:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"webcal:%.txt",
+ L"webcal:%00.txt",
+ L"webcal:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"webcal:%.txt",
+ L"webcal:%00.txt",
+ L"webcal:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"webcals:%.txt",
+ L"webcals:%00.txt",
+ L"webcals:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"windowscalendar.urlwebcal.1:%.txt",
+ L"windowscalendar.urlwebcal.1:%00.txt",
+ L"windowscalendar.urlwebcal.1:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"windowsmail.url.mailto:%.txt",
+ L"windowsmail.url.mailto:%00.txt",
+ L"windowsmail.url.mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"windowsmail.url.news:%.txt",
+ L"windowsmail.url.news:%00.txt",
+ L"windowsmail.url.news:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"windowsmail.url.nntp:%.txt",
+ L"windowsmail.url.nntp:%00.txt",
+ L"windowsmail.url.nntp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"windowsmail.url.snews:%.txt",
+ L"windowsmail.url.snews:%00.txt",
+ L"windowsmail.url.snews:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"wmp11.assocprotocol.mms:%.txt",
+ L"wmp11.assocprotocol.mms:%00.txt",
+ L"wmp11.assocprotocol.mms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"wpc:%.txt",
+ L"wpc:%00.txt",
+ L"wpc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ymsgr:%.txt",
+ L"ymsgr:%00.txt",
+ L"ymsgr:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"acrobat:%.txt",
+ L"acrobat:%00.txt",
+ L"acrobat:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"acsui:%.txt",
+ L"acsui:%00.txt",
+ L"acsui:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"aim:%.txt",
+ L"aim:%00.txt",
+ L"aim:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"aim:%.txt",
+ L"aim:%00.txt",
+ L"aim:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"allc8.commands.2:%.txt",
+ L"allc8.commands.2:%00.txt",
+ L"allc8.commands.2:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"allholdem.commands.2:%.txt",
+ L"allholdem.commands.2:%00.txt",
+ L"allholdem.commands.2:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"allpoker.commands.2:%.txt",
+ L"allpoker.commands.2:%00.txt",
+ L"allpoker.commands.2:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"aolautofix:%.txt",
+ L"aolautofix:%00.txt",
+ L"aolautofix:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"aolds:%.txt",
+ L"aolds:%00.txt",
+ L"aolds:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"bc:%.txt",
+ L"bc:%00.txt",
+ L"bc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"bctp:%.txt",
+ L"bctp:%00.txt",
+ L"bctp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"bittorrent:%.txt",
+ L"bittorrent:%00.txt",
+ L"bittorrent:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"camfrog:%.txt",
+ L"camfrog:%00.txt",
+ L"camfrog:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"csi:%.txt",
+ L"csi:%00.txt",
+ L"csi:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"cvs:%.txt",
+ L"cvs:%00.txt",
+ L"cvs:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"daap:%.txt",
+ L"daap:%00.txt",
+ L"daap:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ed2k:%.txt",
+ L"ed2k:%00.txt",
+ L"ed2k:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"explorer.assocprotocol.search-ms:%.txt",
+ L"explorer.assocprotocol.search-ms:%00.txt",
+ L"explorer.assocprotocol.search-ms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"gizmoproject:%.txt",
+ L"gizmoproject:%00.txt",
+ L"gizmoproject:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"gnet:%.txt",
+ L"gnet:%00.txt",
+ L"gnet:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"gnutella:%.txt",
+ L"gnutella:%00.txt",
+ L"gnutella:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"gsarcade:%.txt",
+ L"gsarcade:%00.txt",
+ L"gsarcade:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"hcp:%.txt",
+ L"hcp:%00.txt",
+ L"hcp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"icquser:%.txt",
+ L"icquser:%00.txt",
+ L"icquser:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"icy:%.txt",
+ L"icy:%00.txt",
+ L"icy:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"imesync:%.txt",
+ L"imesync:%00.txt",
+ L"imesync:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itunes.assocprotocol.daap:%.txt",
+ L"itunes.assocprotocol.daap:%00.txt",
+ L"itunes.assocprotocol.daap:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itunes.assocprotocol.pcast:%.txt",
+ L"itunes.assocprotocol.pcast:%00.txt",
+ L"itunes.assocprotocol.pcast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"joost:%.txt",
+ L"joost:%00.txt",
+ L"joost:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"m4macdrive:%.txt",
+ L"m4macdrive:%00.txt",
+ L"m4macdrive:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"magnet:%.txt",
+ L"magnet:%00.txt",
+ L"magnet:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mapi:%.txt",
+ L"mapi:%00.txt",
+ L"mapi:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mc12:%.txt",
+ L"mc12:%00.txt",
+ L"mc12:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mediajukebox:%.txt",
+ L"mediajukebox:%00.txt",
+ L"mediajukebox:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"morpheus:%.txt",
+ L"morpheus:%00.txt",
+ L"morpheus:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mp2p:%.txt",
+ L"mp2p:%00.txt",
+ L"mp2p:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mpodcast:%.txt",
+ L"mpodcast:%00.txt",
+ L"mpodcast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"msbd:%.txt",
+ L"msbd:%00.txt",
+ L"msbd:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"msbd:%.txt",
+ L"msbd:%00.txt",
+ L"msbd:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"msdigitallocker:%.txt",
+ L"msdigitallocker:%00.txt",
+ L"msdigitallocker:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"outlook.url.stssync:%.txt",
+ L"outlook.url.stssync:%00.txt",
+ L"outlook.url.stssync:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"p2p:%.txt",
+ L"p2p:%00.txt",
+ L"p2p:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"pando:%.txt",
+ L"pando:%00.txt",
+ L"pando:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"pcast:%.txt",
+ L"pcast:%00.txt",
+ L"pcast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"picasa:%.txt",
+ L"picasa:%00.txt",
+ L"picasa:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"plaxo:%.txt",
+ L"plaxo:%00.txt",
+ L"plaxo:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"play:%.txt",
+ L"play:%00.txt",
+ L"play:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"podcast:%.txt",
+ L"podcast:%00.txt",
+ L"podcast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ppmate:%.txt",
+ L"ppmate:%00.txt",
+ L"ppmate:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ppmates:%.txt",
+ L"ppmates:%00.txt",
+ L"ppmates:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ppstream:%.txt",
+ L"ppstream:%00.txt",
+ L"ppstream:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"quicktime:%.txt",
+ L"quicktime:%00.txt",
+ L"quicktime:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"realplayer.autoplay.6:%.txt",
+ L"realplayer.autoplay.6:%00.txt",
+ L"realplayer.autoplay.6:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"realplayer.cdburn.6:%.txt",
+ L"realplayer.cdburn.6:%00.txt",
+ L"realplayer.cdburn.6:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"rhap:%.txt",
+ L"rhap:%00.txt",
+ L"rhap:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"sc:%.txt",
+ L"sc:%00.txt",
+ L"sc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"search-ms:%.txt",
+ L"search-ms:%00.txt",
+ L"search-ms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"shareaza:%.txt",
+ L"shareaza:%00.txt",
+ L"shareaza:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"shell:%.txt",
+ L"shell:%00.txt",
+ L"shell:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"shout:%.txt",
+ L"shout:%00.txt",
+ L"shout:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"sig2dat:%.txt",
+ L"sig2dat:%00.txt",
+ L"sig2dat:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"sop:%.txt",
+ L"sop:%00.txt",
+ L"sop:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"steam:%.txt",
+ L"steam:%00.txt",
+ L"steam:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"stssync:%.txt",
+ L"stssync:%00.txt",
+ L"stssync:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"svn:%.txt",
+ L"svn:%00.txt",
+ L"svn:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"svn+ssh:%.txt",
+ L"svn+ssh:%00.txt",
+ L"svn+ssh:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"synacast:%.txt",
+ L"synacast:%00.txt",
+ L"synacast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"torrent:%.txt",
+ L"torrent:%00.txt",
+ L"torrent:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"tsvn:%.txt",
+ L"tsvn:%00.txt",
+ L"tsvn:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"tvants:%.txt",
+ L"tvants:%00.txt",
+ L"tvants:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"tvu:%.txt",
+ L"tvu:%00.txt",
+ L"tvu:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"unsv:%.txt",
+ L"unsv:%00.txt",
+ L"unsv:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"uvox:%.txt",
+ L"uvox:%00.txt",
+ L"uvox:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ventrilo:%.txt",
+ L"ventrilo:%00.txt",
+ L"ventrilo:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"vs:%.txt",
+ L"vs:%00.txt",
+ L"vs:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"zune:%.txt",
+ L"zune:%00.txt",
+ L"zune:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"https://example.com/?a=123&b=456",
+ L"https://example.com/#123?a=123&b=456",
+ L"https://example.com/?#123a=123&b=456",
+ L"https://example.com/?a=123&b=456#123",
+ L"mailto:%41%42%23%31",
+ L"mailto:%41%42%23%31#fragment",
+ L"news:%41%42%23%31",
+ L"news:%41%42%23%31#fragment",
+ L"microsoft-edge:%41%42%23%31",
+ L"microsoft-edge:%41%42%23%31#fragment",
+ L"microsoft-edge:%41%42%23%31#fragment#",
+ L"microsoft-edge:%41%42%23%31####",
+ L"something-unknown:",
+ L"something-unknown:x=123",
+ L"something-unknown:?=123",
+ L"something-unknown:#code=0123456789%200123456789&x=01234567890123456789",
+};
+
+#endif // mozilla_TestUrisToValidate_h
diff --git a/widget/windows/tests/TestWinDND.cpp b/widget/windows/tests/TestWinDND.cpp
new file mode 100644
index 0000000000..51e43cf19e
--- /dev/null
+++ b/widget/windows/tests/TestWinDND.cpp
@@ -0,0 +1,696 @@
+/* -*- 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/. */
+#define MOZILLA_INTERNAL_API
+
+#include <ole2.h>
+#include <shlobj.h>
+
+#include "TestHarness.h"
+#include "nsArray.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITransferable.h"
+#include "nsClipboard.h"
+#include "nsDataObjCollection.h"
+
+nsIFile* xferFile;
+
+nsresult CheckValidHDROP(STGMEDIUM* pSTG) {
+ if (pSTG->tymed != TYMED_HGLOBAL) {
+ fail("Received data is not in an HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ HGLOBAL hGlobal = pSTG->hGlobal;
+ DROPFILES* pDropFiles;
+ pDropFiles = (DROPFILES*)GlobalLock(hGlobal);
+ if (!pDropFiles) {
+ fail("There is no data at the given HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (pDropFiles->pFiles != sizeof(DROPFILES))
+ fail("DROPFILES struct has wrong size");
+
+ if (pDropFiles->fWide != true) {
+ fail("Received data is not Unicode");
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsString s;
+ unsigned long offset = 0;
+ while (1) {
+ s = (char16_t*)((char*)pDropFiles + pDropFiles->pFiles + offset);
+ if (s.IsEmpty()) break;
+ nsresult rv;
+ nsCOMPtr<nsIFile> localFile(
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ rv = localFile->InitWithPath(s);
+ if (NS_FAILED(rv)) {
+ fail("File could not be opened");
+ return NS_ERROR_UNEXPECTED;
+ }
+ offset += sizeof(char16_t) * (s.Length() + 1);
+ }
+ return NS_OK;
+}
+
+nsresult CheckValidTEXT(STGMEDIUM* pSTG) {
+ if (pSTG->tymed != TYMED_HGLOBAL) {
+ fail("Received data is not in an HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ HGLOBAL hGlobal = pSTG->hGlobal;
+ char* pText;
+ pText = (char*)GlobalLock(hGlobal);
+ if (!pText) {
+ fail("There is no data at the given HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCString string;
+ string = pText;
+
+ if (!string.EqualsLiteral("Mozilla can drag and drop")) {
+ fail("Text passed through drop object wrong");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult CheckValidTEXTTwo(STGMEDIUM* pSTG) {
+ if (pSTG->tymed != TYMED_HGLOBAL) {
+ fail("Received data is not in an HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ HGLOBAL hGlobal = pSTG->hGlobal;
+ char* pText;
+ pText = (char*)GlobalLock(hGlobal);
+ if (!pText) {
+ fail("There is no data at the given HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCString string;
+ string = pText;
+
+ if (!string.EqualsLiteral("Mozilla can drag and drop twice over")) {
+ fail("Text passed through drop object wrong");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult CheckValidUNICODE(STGMEDIUM* pSTG) {
+ if (pSTG->tymed != TYMED_HGLOBAL) {
+ fail("Received data is not in an HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ HGLOBAL hGlobal = pSTG->hGlobal;
+ char16_t* pText;
+ pText = (char16_t*)GlobalLock(hGlobal);
+ if (!pText) {
+ fail("There is no data at the given HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsString string;
+ string = pText;
+
+ if (!string.EqualsLiteral("Mozilla can drag and drop")) {
+ fail("Text passed through drop object wrong");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult CheckValidUNICODETwo(STGMEDIUM* pSTG) {
+ if (pSTG->tymed != TYMED_HGLOBAL) {
+ fail("Received data is not in an HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ HGLOBAL hGlobal = pSTG->hGlobal;
+ char16_t* pText;
+ pText = (char16_t*)GlobalLock(hGlobal);
+ if (!pText) {
+ fail("There is no data at the given HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsString string;
+ string = pText;
+
+ if (!string.EqualsLiteral("Mozilla can drag and drop twice over")) {
+ fail("Text passed through drop object wrong");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult GetTransferableFile(nsCOMPtr<nsITransferable>& pTransferable) {
+ nsresult rv;
+
+ nsCOMPtr<nsISupports> genericWrapper = do_QueryInterface(xferFile);
+
+ pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1");
+ pTransferable->Init(nullptr);
+ rv = pTransferable->SetTransferData("application/x-moz-file", genericWrapper,
+ 0);
+ return rv;
+}
+
+nsresult GetTransferableText(nsCOMPtr<nsITransferable>& pTransferable) {
+ nsresult rv;
+ constexpr auto mozString = u"Mozilla can drag and drop"_ns;
+ nsCOMPtr<nsISupportsString> xferString =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
+ rv = xferString->SetData(mozString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> genericWrapper = do_QueryInterface(xferString);
+
+ pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1");
+ pTransferable->Init(nullptr);
+ rv = pTransferable->SetTransferData("text/unicode", genericWrapper,
+ mozString.Length() * sizeof(char16_t));
+ return rv;
+}
+
+nsresult GetTransferableTextTwo(nsCOMPtr<nsITransferable>& pTransferable) {
+ nsresult rv;
+ constexpr auto mozString = u" twice over"_ns;
+ nsCOMPtr<nsISupportsString> xferString =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
+ rv = xferString->SetData(mozString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> genericWrapper = do_QueryInterface(xferString);
+
+ pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1");
+ pTransferable->Init(nullptr);
+ rv = pTransferable->SetTransferData("text/unicode", genericWrapper,
+ mozString.Length() * sizeof(char16_t));
+ return rv;
+}
+
+nsresult GetTransferableURI(nsCOMPtr<nsITransferable>& pTransferable) {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> xferURI;
+
+ rv = NS_NewURI(getter_AddRefs(xferURI), "http://www.mozilla.org");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> genericWrapper = do_QueryInterface(xferURI);
+
+ pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1");
+ pTransferable->Init(nullptr);
+ rv = pTransferable->SetTransferData("text/x-moz-url", genericWrapper, 0);
+ return rv;
+}
+
+nsresult MakeDataObject(nsIArray* transferableArray,
+ RefPtr<IDataObject>& itemToDrag) {
+ nsresult rv;
+ uint32_t itemCount = 0;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), "http://www.mozilla.org");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transferableArray->GetLength(&itemCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Copied more or less exactly from nsDragService::InvokeDragSession
+ // This is what lets us play fake Drag Service for the test
+ if (itemCount > 1) {
+ nsDataObjCollection* dataObjCollection = new nsDataObjCollection();
+ if (!dataObjCollection) return NS_ERROR_OUT_OF_MEMORY;
+ itemToDrag = dataObjCollection;
+ for (uint32_t i = 0; i < itemCount; ++i) {
+ nsCOMPtr<nsITransferable> trans = do_QueryElementAt(transferableArray, i);
+ if (trans) {
+ RefPtr<IDataObject> dataObj;
+ rv = nsClipboard::CreateNativeDataObject(trans, getter_AddRefs(dataObj),
+ uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Add the flavors to the collection object too
+ rv = nsClipboard::SetupNativeDataObject(trans, dataObjCollection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dataObjCollection->AddDataObject(dataObj);
+ }
+ }
+ } // if dragging multiple items
+ else {
+ nsCOMPtr<nsITransferable> trans = do_QueryElementAt(transferableArray, 0);
+ if (trans) {
+ rv = nsClipboard::CreateNativeDataObject(trans,
+ getter_AddRefs(itemToDrag), uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } // else dragging a single object
+ return rv;
+}
+
+nsresult Do_CheckOneFile() {
+ nsresult rv;
+ nsCOMPtr<nsITransferable> transferable;
+ nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create();
+ nsCOMPtr<nsISupports> genericWrapper;
+ RefPtr<IDataObject> dataObj;
+
+ rv = GetTransferableFile(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+
+ rv = MakeDataObject(transferableArray, dataObj);
+ if (NS_FAILED(rv)) {
+ fail("Could not create data object");
+ return rv;
+ }
+
+ FORMATETC fe;
+ SET_FORMATETC(fe, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (dataObj->QueryGetData(&fe) != S_OK) {
+ fail("File data object does not support the file data type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ STGMEDIUM* stg;
+ stg = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM));
+ if (dataObj->GetData(&fe, stg) != S_OK) {
+ fail("File data object did not provide data on request");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = CheckValidHDROP(stg);
+ if (NS_FAILED(rv)) {
+ fail("HDROP was invalid");
+ return rv;
+ }
+
+ ReleaseStgMedium(stg);
+
+ return NS_OK;
+}
+
+nsresult Do_CheckTwoFiles() {
+ nsresult rv;
+ nsCOMPtr<nsITransferable> transferable;
+ nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create();
+ nsCOMPtr<nsISupports> genericWrapper;
+ RefPtr<IDataObject> dataObj;
+
+ rv = GetTransferableFile(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+
+ rv = GetTransferableFile(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+
+ rv = MakeDataObject(transferableArray, dataObj);
+ if (NS_FAILED(rv)) {
+ fail("Could not create data object");
+ return rv;
+ }
+
+ FORMATETC fe;
+ SET_FORMATETC(fe, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (dataObj->QueryGetData(&fe) != S_OK) {
+ fail("File data object does not support the file data type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ STGMEDIUM* stg;
+ stg = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM));
+ if (dataObj->GetData(&fe, stg) != S_OK) {
+ fail("File data object did not provide data on request");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = CheckValidHDROP(stg);
+ if (NS_FAILED(rv)) {
+ fail("HDROP was invalid");
+ return rv;
+ }
+
+ ReleaseStgMedium(stg);
+
+ return NS_OK;
+}
+
+nsresult Do_CheckOneString() {
+ nsresult rv;
+ nsCOMPtr<nsITransferable> transferable;
+ nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create();
+ nsCOMPtr<nsISupports> genericWrapper;
+ RefPtr<IDataObject> dataObj;
+
+ rv = GetTransferableText(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+
+ rv = MakeDataObject(transferableArray, dataObj);
+ if (NS_FAILED(rv)) {
+ fail("Could not create data object");
+ return rv;
+ }
+
+ FORMATETC fe;
+ SET_FORMATETC(fe, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (dataObj->QueryGetData(&fe) != S_OK) {
+ fail("String data object does not support the ASCII text data type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ STGMEDIUM* stg;
+ stg = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM));
+ if (dataObj->GetData(&fe, stg) != S_OK) {
+ fail("String data object did not provide ASCII data on request");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = CheckValidTEXT(stg);
+ if (NS_FAILED(rv)) {
+ fail("TEXT was invalid");
+ return rv;
+ }
+
+ ReleaseStgMedium(stg);
+
+ SET_FORMATETC(fe, CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (dataObj->QueryGetData(&fe) != S_OK) {
+ fail("String data object does not support the wide text data type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (dataObj->GetData(&fe, stg) != S_OK) {
+ fail("String data object did not provide wide data on request");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = CheckValidUNICODE(stg);
+ if (NS_FAILED(rv)) {
+ fail("UNICODE was invalid");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult Do_CheckTwoStrings() {
+ nsresult rv;
+ nsCOMPtr<nsITransferable> transferable;
+ nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create();
+ nsCOMPtr<nsISupports> genericWrapper;
+ RefPtr<IDataObject> dataObj;
+
+ rv = GetTransferableText(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+
+ rv = GetTransferableTextTwo(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+
+ rv = MakeDataObject(transferableArray, dataObj);
+ if (NS_FAILED(rv)) {
+ fail("Could not create data object");
+ return rv;
+ }
+
+ FORMATETC fe;
+ SET_FORMATETC(fe, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (dataObj->QueryGetData(&fe) != S_OK) {
+ fail("String data object does not support the ASCII text data type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ STGMEDIUM* stg;
+ stg = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM));
+ if (dataObj->GetData(&fe, stg) != S_OK) {
+ fail("String data object did not provide ASCII data on request");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = CheckValidTEXTTwo(stg);
+ if (NS_FAILED(rv)) {
+ fail("TEXT was invalid");
+ return rv;
+ }
+
+ ReleaseStgMedium(stg);
+
+ SET_FORMATETC(fe, CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (dataObj->QueryGetData(&fe) != S_OK) {
+ fail("String data object does not support the wide text data type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (dataObj->GetData(&fe, stg) != S_OK) {
+ fail("String data object did not provide wide data on request");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = CheckValidUNICODETwo(stg);
+ if (NS_FAILED(rv)) {
+ fail("UNICODE was invalid");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult Do_CheckSetArbitraryData(bool aMultiple) {
+ nsresult rv;
+ nsCOMPtr<nsITransferable> transferable;
+ nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create();
+ nsCOMPtr<nsISupports> genericWrapper;
+ RefPtr<IDataObject> dataObj;
+
+ rv = GetTransferableText(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+
+ if (aMultiple) {
+ rv = GetTransferableText(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+ }
+
+ rv = MakeDataObject(transferableArray, dataObj);
+ if (NS_FAILED(rv)) {
+ fail("Could not create data object");
+ return rv;
+ }
+
+ static CLIPFORMAT mozArbitraryFormat =
+ ::RegisterClipboardFormatW(L"MozillaTestFormat");
+ FORMATETC fe;
+ STGMEDIUM stg;
+ SET_FORMATETC(fe, mozArbitraryFormat, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+
+ HGLOBAL hg = GlobalAlloc(GPTR, 1024);
+ stg.tymed = TYMED_HGLOBAL;
+ stg.hGlobal = hg;
+ stg.pUnkForRelease = nullptr;
+
+ if (dataObj->SetData(&fe, &stg, true) != S_OK) {
+ if (aMultiple) {
+ fail("Unable to set arbitrary data type on data object collection!");
+ } else {
+ fail("Unable to set arbitrary data type on data object!");
+ }
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (dataObj->QueryGetData(&fe) != S_OK) {
+ fail("Arbitrary data set on data object is not advertised!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ STGMEDIUM* stg2;
+ stg2 = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM));
+ if (dataObj->GetData(&fe, stg2) != S_OK) {
+ fail("Data object did not provide arbitrary data upon request!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (stg2->hGlobal != hg) {
+ fail("Arbitrary data was not returned properly!");
+ return rv;
+ }
+ ReleaseStgMedium(stg2);
+
+ return NS_OK;
+}
+
+// This function performs basic drop tests, testing a data object consisting
+// of one transferable
+nsresult Do_Test1() {
+ nsresult rv = NS_OK;
+ nsresult workingrv;
+
+ workingrv = Do_CheckOneFile();
+ if (NS_FAILED(workingrv)) {
+ fail("Drag object tests failed on a single file");
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ passed("Successfully created a working file drag object!");
+ }
+
+ workingrv = Do_CheckOneString();
+ if (NS_FAILED(workingrv)) {
+ fail("Drag object tests failed on a single string");
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ passed("Successfully created a working string drag object!");
+ }
+
+ workingrv = Do_CheckSetArbitraryData(false);
+ if (NS_FAILED(workingrv)) {
+ fail("Drag object tests failed on setting arbitrary data");
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ passed("Successfully set arbitrary data on a drag object");
+ }
+
+ return rv;
+}
+
+// This function performs basic drop tests, testing a data object consisting of
+// two transferables.
+nsresult Do_Test2() {
+ nsresult rv = NS_OK;
+ nsresult workingrv;
+
+ workingrv = Do_CheckTwoFiles();
+ if (NS_FAILED(workingrv)) {
+ fail("Drag object tests failed on multiple files");
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ passed("Successfully created a working multiple file drag object!");
+ }
+
+ workingrv = Do_CheckTwoStrings();
+ if (NS_FAILED(workingrv)) {
+ fail("Drag object tests failed on multiple strings");
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ passed("Successfully created a working multiple string drag object!");
+ }
+
+ workingrv = Do_CheckSetArbitraryData(true);
+ if (NS_FAILED(workingrv)) {
+ fail("Drag object tests failed on setting arbitrary data");
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ passed("Successfully set arbitrary data on a drag object");
+ }
+
+ return rv;
+}
+
+// This function performs advanced drag and drop tests, testing a data object
+// consisting of multiple transferables that have different data types
+nsresult Do_Test3() {
+ nsresult rv = NS_OK;
+ nsresult workingrv;
+
+ // XXX TODO Write more advanced tests in Bug 535860
+ return rv;
+}
+
+int main(int argc, char** argv) {
+ ScopedXPCOM xpcom("Test Windows Drag and Drop");
+
+ nsCOMPtr<nsIFile> file;
+ file = xpcom.GetProfileDirectory();
+ xferFile = file;
+
+ if (NS_SUCCEEDED(Do_Test1()))
+ passed(
+ "Basic Drag and Drop data type tests (single transferable) succeeded!");
+
+ if (NS_SUCCEEDED(Do_Test2()))
+ passed(
+ "Basic Drag and Drop data type tests (multiple transferables) "
+ "succeeded!");
+
+ // if (NS_SUCCEEDED(Do_Test3()))
+ // passed("Advanced Drag and Drop data type tests succeeded!");
+
+ return gFailCount;
+}
diff --git a/widget/windows/tests/moz.build b/widget/windows/tests/moz.build
new file mode 100644
index 0000000000..8d6c88c5e2
--- /dev/null
+++ b/widget/windows/tests/moz.build
@@ -0,0 +1,28 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+GeckoCppUnitTests(
+ [
+ "TestUriValidation",
+ ],
+ linkage=None,
+)
+
+LOCAL_INCLUDES += []
+
+OS_LIBS += [
+ "oleaut32",
+ "ole32",
+ "shell32",
+ "shlwapi",
+ "urlmon",
+]
+
+if CONFIG["OS_TARGET"] == "WINNT" and CONFIG["CC_TYPE"] in ("gcc", "clang"):
+ # This allows us to use wmain as the entry point on mingw
+ LDFLAGS += [
+ "-municode",
+ ]
diff --git a/widget/windows/touchinjection_sdk80.h b/widget/windows/touchinjection_sdk80.h
new file mode 100644
index 0000000000..e2eeceb15b
--- /dev/null
+++ b/widget/windows/touchinjection_sdk80.h
@@ -0,0 +1,117 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 touchinjection_sdk80_h
+#define touchinjection_sdk80_h
+
+// Note, this isn't inclusive of all touch injection header info.
+// You may need to add more to expand on current apis.
+
+#ifndef TOUCH_FEEDBACK_DEFAULT
+
+# define TOUCH_FEEDBACK_DEFAULT 0x1
+# define TOUCH_FEEDBACK_INDIRECT 0x2
+# define TOUCH_FEEDBACK_NONE 0x3
+
+enum {
+ PT_POINTER = 0x00000001, // Generic pointer
+ PT_TOUCH = 0x00000002, // Touch
+ PT_PEN = 0x00000003, // Pen
+ PT_MOUSE = 0x00000004, // Mouse
+ PT_TOUCHPAD = 0x00000005, // Touch pad
+};
+
+typedef DWORD POINTER_INPUT_TYPE;
+typedef UINT32 POINTER_FLAGS;
+
+typedef enum {
+ POINTER_CHANGE_NONE,
+ POINTER_CHANGE_FIRSTBUTTON_DOWN,
+ POINTER_CHANGE_FIRSTBUTTON_UP,
+ POINTER_CHANGE_SECONDBUTTON_DOWN,
+ POINTER_CHANGE_SECONDBUTTON_UP,
+ POINTER_CHANGE_THIRDBUTTON_DOWN,
+ POINTER_CHANGE_THIRDBUTTON_UP,
+ POINTER_CHANGE_FOURTHBUTTON_DOWN,
+ POINTER_CHANGE_FOURTHBUTTON_UP,
+ POINTER_CHANGE_FIFTHBUTTON_DOWN,
+ POINTER_CHANGE_FIFTHBUTTON_UP,
+} POINTER_BUTTON_CHANGE_TYPE;
+
+typedef struct {
+ POINTER_INPUT_TYPE pointerType;
+ UINT32 pointerId;
+ UINT32 frameId;
+ POINTER_FLAGS pointerFlags;
+ HANDLE sourceDevice;
+ HWND hwndTarget;
+ POINT ptPixelLocation;
+ POINT ptHimetricLocation;
+ POINT ptPixelLocationRaw;
+ POINT ptHimetricLocationRaw;
+ DWORD dwTime;
+ UINT32 historyCount;
+ INT32 InputData;
+ DWORD dwKeyStates;
+ UINT64 PerformanceCount;
+ POINTER_BUTTON_CHANGE_TYPE ButtonChangeType;
+} POINTER_INFO;
+
+typedef UINT32 TOUCH_FLAGS;
+typedef UINT32 TOUCH_MASK;
+
+typedef struct {
+ POINTER_INFO pointerInfo;
+ TOUCH_FLAGS touchFlags;
+ TOUCH_MASK touchMask;
+ RECT rcContact;
+ RECT rcContactRaw;
+ UINT32 orientation;
+ UINT32 pressure;
+} POINTER_TOUCH_INFO;
+
+# define TOUCH_FLAG_NONE 0x00000000 // Default
+
+# define TOUCH_MASK_NONE \
+ 0x00000000 // Default - none of the optional fields are valid
+# define TOUCH_MASK_CONTACTAREA 0x00000001 // The rcContact field is valid
+# define TOUCH_MASK_ORIENTATION 0x00000002 // The orientation field is valid
+# define TOUCH_MASK_PRESSURE 0x00000004 // The pressure field is valid
+
+# define POINTER_FLAG_NONE 0x00000000 // Default
+# define POINTER_FLAG_NEW 0x00000001 // New pointer
+# define POINTER_FLAG_INRANGE 0x00000002 // Pointer has not departed
+# define POINTER_FLAG_INCONTACT 0x00000004 // Pointer is in contact
+# define POINTER_FLAG_FIRSTBUTTON 0x00000010 // Primary action
+# define POINTER_FLAG_SECONDBUTTON 0x00000020 // Secondary action
+# define POINTER_FLAG_THIRDBUTTON 0x00000040 // Third button
+# define POINTER_FLAG_FOURTHBUTTON 0x00000080 // Fourth button
+# define POINTER_FLAG_FIFTHBUTTON 0x00000100 // Fifth button
+# define POINTER_FLAG_PRIMARY 0x00002000 // Pointer is primary
+# define POINTER_FLAG_CONFIDENCE \
+ 0x00004000 // Pointer is considered unlikely to be accidental
+# define POINTER_FLAG_CANCELED \
+ 0x00008000 // Pointer is departing in an abnormal manner
+# define POINTER_FLAG_DOWN \
+ 0x00010000 // Pointer transitioned to down state (made contact)
+# define POINTER_FLAG_UPDATE 0x00020000 // Pointer update
+# define POINTER_FLAG_UP \
+ 0x00040000 // Pointer transitioned from down state (broke contact)
+# define POINTER_FLAG_WHEEL 0x00080000 // Vertical wheel
+# define POINTER_FLAG_HWHEEL 0x00100000 // Horizontal wheel
+# define POINTER_FLAG_CAPTURECHANGED 0x00200000 // Lost capture
+
+#endif // TOUCH_FEEDBACK_DEFAULT
+
+#define TOUCH_FLAGS_CONTACTUPDATE \
+ (POINTER_FLAG_UPDATE | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT)
+#define TOUCH_FLAGS_CONTACTDOWN \
+ (POINTER_FLAG_DOWN | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT)
+
+typedef BOOL(WINAPI* InitializeTouchInjectionPtr)(UINT32 maxCount,
+ DWORD dwMode);
+typedef BOOL(WINAPI* InjectTouchInputPtr)(UINT32 count,
+ CONST POINTER_TOUCH_INFO* info);
+
+#endif // touchinjection_sdk80_h
diff --git a/widget/windows/widget.rc b/widget/windows/widget.rc
new file mode 100644
index 0000000000..9361f9e48c
--- /dev/null
+++ b/widget/windows/widget.rc
@@ -0,0 +1,30 @@
+/* -*- 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 "resource.h"
+#include <winresrc.h>
+#include <dlgs.h>
+
+IDC_GRAB CURSOR DISCARDABLE "res/grab.cur"
+IDC_GRABBING CURSOR DISCARDABLE "res/grabbing.cur"
+IDC_CELL CURSOR DISCARDABLE "res/cell.cur"
+IDC_COPY CURSOR DISCARDABLE "res/copy.cur"
+IDC_ALIAS CURSOR DISCARDABLE "res/aliasb.cur"
+IDC_ZOOMIN CURSOR DISCARDABLE "res/zoom_in.cur"
+IDC_ZOOMOUT CURSOR DISCARDABLE "res/zoom_out.cur"
+IDC_COLRESIZE CURSOR DISCARDABLE "res/col_resize.cur"
+IDC_ROWRESIZE CURSOR DISCARDABLE "res/row_resize.cur"
+IDC_VERTICALTEXT CURSOR DISCARDABLE "res/vertical_text.cur"
+IDC_NONE CURSOR DISCARDABLE "res/none.cur"
+
+OPTPROPSHEET DIALOG DISCARDABLE 32, 32, 288, 226
+STYLE DS_MODALFRAME | DS_3DLOOK | DS_CONTEXTHELP | WS_POPUP | WS_VISIBLE |
+ WS_CAPTION | WS_SYSMENU
+CAPTION "Options"
+FONT 8, "MS Sans Serif"
+BEGIN
+
+END
diff --git a/widget/x11/keysym2ucs.c b/widget/x11/keysym2ucs.c
new file mode 100644
index 0000000000..b0c2229e6d
--- /dev/null
+++ b/widget/x11/keysym2ucs.c
@@ -0,0 +1,866 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+/*
+ * This module converts keysym values into the corresponding ISO 10646-1
+ * (UCS, Unicode) values.
+ *
+ * The array keysymtab[] contains pairs of X11 keysym values for graphical
+ * characters and the corresponding Unicode value. The function
+ * keysym2ucs() maps a keysym onto a Unicode value using a binary search,
+ * therefore keysymtab[] must remain SORTED by keysym value.
+ *
+ * The keysym -> UTF-8 conversion will hopefully one day be provided
+ * by Xlib via XmbLookupString() and should ideally not have to be
+ * done in X applications. But we are not there yet.
+ *
+ * We allow to represent any UCS character in the range U+00000000 to
+ * U+00FFFFFF by a keysym value in the range 0x01000000 to 0x01ffffff.
+ * This admittedly does not cover the entire 31-bit space of UCS, but
+ * it does cover all of the characters up to U+10FFFF, which can be
+ * represented by UTF-16, and more, and it is very unlikely that higher
+ * UCS codes will ever be assigned by ISO. So to get Unicode character
+ * U+ABCD you can directly use keysym 0x1000abcd.
+ *
+ * NOTE: The comments in the table below contain the actual character
+ * encoded in UTF-8, so for viewing and editing best use an editor in
+ * UTF-8 mode.
+ *
+ * Author: Markus G. Kuhn <mkuhn@acm.org>, University of Cambridge, June 1999
+ *
+ * Special thanks to Richard Verhoeven <river@win.tue.nl> for preparing
+ * an initial draft of the mapping table.
+ *
+ * This software is in the public domain. Share and enjoy!
+ */
+
+#include "keysym2ucs.h"
+
+static const struct codepair {
+ unsigned short keysym;
+ unsigned short ucs;
+} keysymtab[] = {
+ // clang-format off
+ { 0x01a1, 0x0104 }, /* Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */
+ { 0x01a2, 0x02d8 }, /* breve ˘ BREVE */
+ { 0x01a3, 0x0141 }, /* Lstroke Ł LATIN CAPITAL LETTER L WITH STROKE */
+ { 0x01a5, 0x013d }, /* Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */
+ { 0x01a6, 0x015a }, /* Sacute Ś LATIN CAPITAL LETTER S WITH ACUTE */
+ { 0x01a9, 0x0160 }, /* Scaron Š LATIN CAPITAL LETTER S WITH CARON */
+ { 0x01aa, 0x015e }, /* Scedilla Ş LATIN CAPITAL LETTER S WITH CEDILLA */
+ { 0x01ab, 0x0164 }, /* Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */
+ { 0x01ac, 0x0179 }, /* Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */
+ { 0x01ae, 0x017d }, /* Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */
+ { 0x01af, 0x017b }, /* Zabovedot Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE */
+ { 0x01b1, 0x0105 }, /* aogonek ą LATIN SMALL LETTER A WITH OGONEK */
+ { 0x01b2, 0x02db }, /* ogonek ˛ OGONEK */
+ { 0x01b3, 0x0142 }, /* lstroke ł LATIN SMALL LETTER L WITH STROKE */
+ { 0x01b5, 0x013e }, /* lcaron ľ LATIN SMALL LETTER L WITH CARON */
+ { 0x01b6, 0x015b }, /* sacute ś LATIN SMALL LETTER S WITH ACUTE */
+ { 0x01b7, 0x02c7 }, /* caron ˇ CARON */
+ { 0x01b9, 0x0161 }, /* scaron š LATIN SMALL LETTER S WITH CARON */
+ { 0x01ba, 0x015f }, /* scedilla ş LATIN SMALL LETTER S WITH CEDILLA */
+ { 0x01bb, 0x0165 }, /* tcaron ť LATIN SMALL LETTER T WITH CARON */
+ { 0x01bc, 0x017a }, /* zacute ź LATIN SMALL LETTER Z WITH ACUTE */
+ { 0x01bd, 0x02dd }, /* doubleacute ˝ DOUBLE ACUTE ACCENT */
+ { 0x01be, 0x017e }, /* zcaron ž LATIN SMALL LETTER Z WITH CARON */
+ { 0x01bf, 0x017c }, /* zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */
+ { 0x01c0, 0x0154 }, /* Racute Ŕ LATIN CAPITAL LETTER R WITH ACUTE */
+ { 0x01c3, 0x0102 }, /* Abreve Ă LATIN CAPITAL LETTER A WITH BREVE */
+ { 0x01c5, 0x0139 }, /* Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */
+ { 0x01c6, 0x0106 }, /* Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */
+ { 0x01c8, 0x010c }, /* Ccaron Č LATIN CAPITAL LETTER C WITH CARON */
+ { 0x01ca, 0x0118 }, /* Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */
+ { 0x01cc, 0x011a }, /* Ecaron Ě LATIN CAPITAL LETTER E WITH CARON */
+ { 0x01cf, 0x010e }, /* Dcaron Ď LATIN CAPITAL LETTER D WITH CARON */
+ { 0x01d0, 0x0110 }, /* Dstroke Đ LATIN CAPITAL LETTER D WITH STROKE */
+ { 0x01d1, 0x0143 }, /* Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */
+ { 0x01d2, 0x0147 }, /* Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */
+ { 0x01d5, 0x0150 }, /* Odoubleacute Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */
+ { 0x01d8, 0x0158 }, /* Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */
+ { 0x01d9, 0x016e }, /* Uring Ů LATIN CAPITAL LETTER U WITH RING ABOVE */
+ { 0x01db, 0x0170 }, /* Udoubleacute Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */
+ { 0x01de, 0x0162 }, /* Tcedilla Ţ LATIN CAPITAL LETTER T WITH CEDILLA */
+ { 0x01e0, 0x0155 }, /* racute ŕ LATIN SMALL LETTER R WITH ACUTE */
+ { 0x01e3, 0x0103 }, /* abreve ă LATIN SMALL LETTER A WITH BREVE */
+ { 0x01e5, 0x013a }, /* lacute ĺ LATIN SMALL LETTER L WITH ACUTE */
+ { 0x01e6, 0x0107 }, /* cacute ć LATIN SMALL LETTER C WITH ACUTE */
+ { 0x01e8, 0x010d }, /* ccaron č LATIN SMALL LETTER C WITH CARON */
+ { 0x01ea, 0x0119 }, /* eogonek ę LATIN SMALL LETTER E WITH OGONEK */
+ { 0x01ec, 0x011b }, /* ecaron ě LATIN SMALL LETTER E WITH CARON */
+ { 0x01ef, 0x010f }, /* dcaron ď LATIN SMALL LETTER D WITH CARON */
+ { 0x01f0, 0x0111 }, /* dstroke đ LATIN SMALL LETTER D WITH STROKE */
+ { 0x01f1, 0x0144 }, /* nacute ń LATIN SMALL LETTER N WITH ACUTE */
+ { 0x01f2, 0x0148 }, /* ncaron ň LATIN SMALL LETTER N WITH CARON */
+ { 0x01f5, 0x0151 }, /* odoubleacute ő LATIN SMALL LETTER O WITH DOUBLE ACUTE */
+ { 0x01f8, 0x0159 }, /* rcaron ř LATIN SMALL LETTER R WITH CARON */
+ { 0x01f9, 0x016f }, /* uring ů LATIN SMALL LETTER U WITH RING ABOVE */
+ { 0x01fb, 0x0171 }, /* udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */
+ { 0x01fe, 0x0163 }, /* tcedilla ţ LATIN SMALL LETTER T WITH CEDILLA */
+ { 0x01ff, 0x02d9 }, /* abovedot ˙ DOT ABOVE */
+ { 0x02a1, 0x0126 }, /* Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */
+ { 0x02a6, 0x0124 }, /* Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */
+ { 0x02a9, 0x0130 }, /* Iabovedot İ LATIN CAPITAL LETTER I WITH DOT ABOVE */
+ { 0x02ab, 0x011e }, /* Gbreve Ğ LATIN CAPITAL LETTER G WITH BREVE */
+ { 0x02ac, 0x0134 }, /* Jcircumflex Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */
+ { 0x02b1, 0x0127 }, /* hstroke ħ LATIN SMALL LETTER H WITH STROKE */
+ { 0x02b6, 0x0125 }, /* hcircumflex ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX */
+ { 0x02b9, 0x0131 }, /* idotless ı LATIN SMALL LETTER DOTLESS I */
+ { 0x02bb, 0x011f }, /* gbreve ğ LATIN SMALL LETTER G WITH BREVE */
+ { 0x02bc, 0x0135 }, /* jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */
+ { 0x02c5, 0x010a }, /* Cabovedot Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE */
+ { 0x02c6, 0x0108 }, /* Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */
+ { 0x02d5, 0x0120 }, /* Gabovedot Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE */
+ { 0x02d8, 0x011c }, /* Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */
+ { 0x02dd, 0x016c }, /* Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */
+ { 0x02de, 0x015c }, /* Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */
+ { 0x02e5, 0x010b }, /* cabovedot ċ LATIN SMALL LETTER C WITH DOT ABOVE */
+ { 0x02e6, 0x0109 }, /* ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */
+ { 0x02f5, 0x0121 }, /* gabovedot ġ LATIN SMALL LETTER G WITH DOT ABOVE */
+ { 0x02f8, 0x011d }, /* gcircumflex ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX */
+ { 0x02fd, 0x016d }, /* ubreve ŭ LATIN SMALL LETTER U WITH BREVE */
+ { 0x02fe, 0x015d }, /* scircumflex ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX */
+ { 0x03a2, 0x0138 }, /* kra ĸ LATIN SMALL LETTER KRA */
+ { 0x03a3, 0x0156 }, /* Rcedilla Ŗ LATIN CAPITAL LETTER R WITH CEDILLA */
+ { 0x03a5, 0x0128 }, /* Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */
+ { 0x03a6, 0x013b }, /* Lcedilla Ļ LATIN CAPITAL LETTER L WITH CEDILLA */
+ { 0x03aa, 0x0112 }, /* Emacron Ē LATIN CAPITAL LETTER E WITH MACRON */
+ { 0x03ab, 0x0122 }, /* Gcedilla Ģ LATIN CAPITAL LETTER G WITH CEDILLA */
+ { 0x03ac, 0x0166 }, /* Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */
+ { 0x03b3, 0x0157 }, /* rcedilla ŗ LATIN SMALL LETTER R WITH CEDILLA */
+ { 0x03b5, 0x0129 }, /* itilde ĩ LATIN SMALL LETTER I WITH TILDE */
+ { 0x03b6, 0x013c }, /* lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */
+ { 0x03ba, 0x0113 }, /* emacron ē LATIN SMALL LETTER E WITH MACRON */
+ { 0x03bb, 0x0123 }, /* gcedilla ģ LATIN SMALL LETTER G WITH CEDILLA */
+ { 0x03bc, 0x0167 }, /* tslash ŧ LATIN SMALL LETTER T WITH STROKE */
+ { 0x03bd, 0x014a }, /* ENG Ŋ LATIN CAPITAL LETTER ENG */
+ { 0x03bf, 0x014b }, /* eng ŋ LATIN SMALL LETTER ENG */
+ { 0x03c0, 0x0100 }, /* Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */
+ { 0x03c7, 0x012e }, /* Iogonek Į LATIN CAPITAL LETTER I WITH OGONEK */
+ { 0x03cc, 0x0116 }, /* Eabovedot Ė LATIN CAPITAL LETTER E WITH DOT ABOVE */
+ { 0x03cf, 0x012a }, /* Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */
+ { 0x03d1, 0x0145 }, /* Ncedilla Ņ LATIN CAPITAL LETTER N WITH CEDILLA */
+ { 0x03d2, 0x014c }, /* Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */
+ { 0x03d3, 0x0136 }, /* Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */
+ { 0x03d9, 0x0172 }, /* Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */
+ { 0x03dd, 0x0168 }, /* Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */
+ { 0x03de, 0x016a }, /* Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */
+ { 0x03e0, 0x0101 }, /* amacron ā LATIN SMALL LETTER A WITH MACRON */
+ { 0x03e7, 0x012f }, /* iogonek į LATIN SMALL LETTER I WITH OGONEK */
+ { 0x03ec, 0x0117 }, /* eabovedot ė LATIN SMALL LETTER E WITH DOT ABOVE */
+ { 0x03ef, 0x012b }, /* imacron ī LATIN SMALL LETTER I WITH MACRON */
+ { 0x03f1, 0x0146 }, /* ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */
+ { 0x03f2, 0x014d }, /* omacron ō LATIN SMALL LETTER O WITH MACRON */
+ { 0x03f3, 0x0137 }, /* kcedilla ķ LATIN SMALL LETTER K WITH CEDILLA */
+ { 0x03f9, 0x0173 }, /* uogonek ų LATIN SMALL LETTER U WITH OGONEK */
+ { 0x03fd, 0x0169 }, /* utilde ũ LATIN SMALL LETTER U WITH TILDE */
+ { 0x03fe, 0x016b }, /* umacron ū LATIN SMALL LETTER U WITH MACRON */
+ { 0x047e, 0x203e }, /* overline ‾ OVERLINE */
+ { 0x04a1, 0x3002 }, /* kana_fullstop 。 IDEOGRAPHIC FULL STOP */
+ { 0x04a2, 0x300c }, /* kana_openingbracket 「 LEFT CORNER BRACKET */
+ { 0x04a3, 0x300d }, /* kana_closingbracket 」 RIGHT CORNER BRACKET */
+ { 0x04a4, 0x3001 }, /* kana_comma 、 IDEOGRAPHIC COMMA */
+ { 0x04a5, 0x30fb }, /* kana_conjunctive ・ KATAKANA MIDDLE DOT */
+ { 0x04a6, 0x30f2 }, /* kana_WO ヲ KATAKANA LETTER WO */
+ { 0x04a7, 0x30a1 }, /* kana_a ァ KATAKANA LETTER SMALL A */
+ { 0x04a8, 0x30a3 }, /* kana_i ィ KATAKANA LETTER SMALL I */
+ { 0x04a9, 0x30a5 }, /* kana_u ゥ KATAKANA LETTER SMALL U */
+ { 0x04aa, 0x30a7 }, /* kana_e ェ KATAKANA LETTER SMALL E */
+ { 0x04ab, 0x30a9 }, /* kana_o ォ KATAKANA LETTER SMALL O */
+ { 0x04ac, 0x30e3 }, /* kana_ya ャ KATAKANA LETTER SMALL YA */
+ { 0x04ad, 0x30e5 }, /* kana_yu ュ KATAKANA LETTER SMALL YU */
+ { 0x04ae, 0x30e7 }, /* kana_yo ョ KATAKANA LETTER SMALL YO */
+ { 0x04af, 0x30c3 }, /* kana_tsu ッ KATAKANA LETTER SMALL TU */
+ { 0x04b0, 0x30fc }, /* prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */
+ { 0x04b1, 0x30a2 }, /* kana_A ア KATAKANA LETTER A */
+ { 0x04b2, 0x30a4 }, /* kana_I イ KATAKANA LETTER I */
+ { 0x04b3, 0x30a6 }, /* kana_U ウ KATAKANA LETTER U */
+ { 0x04b4, 0x30a8 }, /* kana_E エ KATAKANA LETTER E */
+ { 0x04b5, 0x30aa }, /* kana_O オ KATAKANA LETTER O */
+ { 0x04b6, 0x30ab }, /* kana_KA カ KATAKANA LETTER KA */
+ { 0x04b7, 0x30ad }, /* kana_KI キ KATAKANA LETTER KI */
+ { 0x04b8, 0x30af }, /* kana_KU ク KATAKANA LETTER KU */
+ { 0x04b9, 0x30b1 }, /* kana_KE ケ KATAKANA LETTER KE */
+ { 0x04ba, 0x30b3 }, /* kana_KO コ KATAKANA LETTER KO */
+ { 0x04bb, 0x30b5 }, /* kana_SA サ KATAKANA LETTER SA */
+ { 0x04bc, 0x30b7 }, /* kana_SHI シ KATAKANA LETTER SI */
+ { 0x04bd, 0x30b9 }, /* kana_SU ス KATAKANA LETTER SU */
+ { 0x04be, 0x30bb }, /* kana_SE セ KATAKANA LETTER SE */
+ { 0x04bf, 0x30bd }, /* kana_SO ソ KATAKANA LETTER SO */
+ { 0x04c0, 0x30bf }, /* kana_TA タ KATAKANA LETTER TA */
+ { 0x04c1, 0x30c1 }, /* kana_CHI チ KATAKANA LETTER TI */
+ { 0x04c2, 0x30c4 }, /* kana_TSU ツ KATAKANA LETTER TU */
+ { 0x04c3, 0x30c6 }, /* kana_TE テ KATAKANA LETTER TE */
+ { 0x04c4, 0x30c8 }, /* kana_TO ト KATAKANA LETTER TO */
+ { 0x04c5, 0x30ca }, /* kana_NA ナ KATAKANA LETTER NA */
+ { 0x04c6, 0x30cb }, /* kana_NI ニ KATAKANA LETTER NI */
+ { 0x04c7, 0x30cc }, /* kana_NU ヌ KATAKANA LETTER NU */
+ { 0x04c8, 0x30cd }, /* kana_NE ネ KATAKANA LETTER NE */
+ { 0x04c9, 0x30ce }, /* kana_NO ノ KATAKANA LETTER NO */
+ { 0x04ca, 0x30cf }, /* kana_HA ハ KATAKANA LETTER HA */
+ { 0x04cb, 0x30d2 }, /* kana_HI ヒ KATAKANA LETTER HI */
+ { 0x04cc, 0x30d5 }, /* kana_FU フ KATAKANA LETTER HU */
+ { 0x04cd, 0x30d8 }, /* kana_HE ヘ KATAKANA LETTER HE */
+ { 0x04ce, 0x30db }, /* kana_HO ホ KATAKANA LETTER HO */
+ { 0x04cf, 0x30de }, /* kana_MA マ KATAKANA LETTER MA */
+ { 0x04d0, 0x30df }, /* kana_MI ミ KATAKANA LETTER MI */
+ { 0x04d1, 0x30e0 }, /* kana_MU ム KATAKANA LETTER MU */
+ { 0x04d2, 0x30e1 }, /* kana_ME メ KATAKANA LETTER ME */
+ { 0x04d3, 0x30e2 }, /* kana_MO モ KATAKANA LETTER MO */
+ { 0x04d4, 0x30e4 }, /* kana_YA ヤ KATAKANA LETTER YA */
+ { 0x04d5, 0x30e6 }, /* kana_YU ユ KATAKANA LETTER YU */
+ { 0x04d6, 0x30e8 }, /* kana_YO ヨ KATAKANA LETTER YO */
+ { 0x04d7, 0x30e9 }, /* kana_RA ラ KATAKANA LETTER RA */
+ { 0x04d8, 0x30ea }, /* kana_RI リ KATAKANA LETTER RI */
+ { 0x04d9, 0x30eb }, /* kana_RU ル KATAKANA LETTER RU */
+ { 0x04da, 0x30ec }, /* kana_RE レ KATAKANA LETTER RE */
+ { 0x04db, 0x30ed }, /* kana_RO ロ KATAKANA LETTER RO */
+ { 0x04dc, 0x30ef }, /* kana_WA ワ KATAKANA LETTER WA */
+ { 0x04dd, 0x30f3 }, /* kana_N ン KATAKANA LETTER N */
+ { 0x04de, 0x309b }, /* voicedsound ゛ KATAKANA-HIRAGANA VOICED SOUND MARK */
+ { 0x04df, 0x309c }, /* semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
+ { 0x05ac, 0x060c }, /* Arabic_comma ، ARABIC COMMA */
+ { 0x05bb, 0x061b }, /* Arabic_semicolon ؛ ARABIC SEMICOLON */
+ { 0x05bf, 0x061f }, /* Arabic_question_mark ؟ ARABIC QUESTION MARK */
+ { 0x05c1, 0x0621 }, /* Arabic_hamza ء ARABIC LETTER HAMZA */
+ { 0x05c2, 0x0622 }, /* Arabic_maddaonalef آ ARABIC LETTER ALEF WITH MADDA ABOVE */
+ { 0x05c3, 0x0623 }, /* Arabic_hamzaonalef أ ARABIC LETTER ALEF WITH HAMZA ABOVE */
+ { 0x05c4, 0x0624 }, /* Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */
+ { 0x05c5, 0x0625 }, /* Arabic_hamzaunderalef إ ARABIC LETTER ALEF WITH HAMZA BELOW */
+ { 0x05c6, 0x0626 }, /* Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */
+ { 0x05c7, 0x0627 }, /* Arabic_alef ا ARABIC LETTER ALEF */
+ { 0x05c8, 0x0628 }, /* Arabic_beh ب ARABIC LETTER BEH */
+ { 0x05c9, 0x0629 }, /* Arabic_tehmarbuta ة ARABIC LETTER TEH MARBUTA */
+ { 0x05ca, 0x062a }, /* Arabic_teh ت ARABIC LETTER TEH */
+ { 0x05cb, 0x062b }, /* Arabic_theh ث ARABIC LETTER THEH */
+ { 0x05cc, 0x062c }, /* Arabic_jeem ج ARABIC LETTER JEEM */
+ { 0x05cd, 0x062d }, /* Arabic_hah ح ARABIC LETTER HAH */
+ { 0x05ce, 0x062e }, /* Arabic_khah خ ARABIC LETTER KHAH */
+ { 0x05cf, 0x062f }, /* Arabic_dal د ARABIC LETTER DAL */
+ { 0x05d0, 0x0630 }, /* Arabic_thal ذ ARABIC LETTER THAL */
+ { 0x05d1, 0x0631 }, /* Arabic_ra ر ARABIC LETTER REH */
+ { 0x05d2, 0x0632 }, /* Arabic_zain ز ARABIC LETTER ZAIN */
+ { 0x05d3, 0x0633 }, /* Arabic_seen س ARABIC LETTER SEEN */
+ { 0x05d4, 0x0634 }, /* Arabic_sheen ش ARABIC LETTER SHEEN */
+ { 0x05d5, 0x0635 }, /* Arabic_sad ص ARABIC LETTER SAD */
+ { 0x05d6, 0x0636 }, /* Arabic_dad ض ARABIC LETTER DAD */
+ { 0x05d7, 0x0637 }, /* Arabic_tah ط ARABIC LETTER TAH */
+ { 0x05d8, 0x0638 }, /* Arabic_zah ظ ARABIC LETTER ZAH */
+ { 0x05d9, 0x0639 }, /* Arabic_ain ع ARABIC LETTER AIN */
+ { 0x05da, 0x063a }, /* Arabic_ghain غ ARABIC LETTER GHAIN */
+ { 0x05e0, 0x0640 }, /* Arabic_tatweel ـ ARABIC TATWEEL */
+ { 0x05e1, 0x0641 }, /* Arabic_feh ف ARABIC LETTER FEH */
+ { 0x05e2, 0x0642 }, /* Arabic_qaf ق ARABIC LETTER QAF */
+ { 0x05e3, 0x0643 }, /* Arabic_kaf ك ARABIC LETTER KAF */
+ { 0x05e4, 0x0644 }, /* Arabic_lam ل ARABIC LETTER LAM */
+ { 0x05e5, 0x0645 }, /* Arabic_meem م ARABIC LETTER MEEM */
+ { 0x05e6, 0x0646 }, /* Arabic_noon ن ARABIC LETTER NOON */
+ { 0x05e7, 0x0647 }, /* Arabic_ha ه ARABIC LETTER HEH */
+ { 0x05e8, 0x0648 }, /* Arabic_waw و ARABIC LETTER WAW */
+ { 0x05e9, 0x0649 }, /* Arabic_alefmaksura ى ARABIC LETTER ALEF MAKSURA */
+ { 0x05ea, 0x064a }, /* Arabic_yeh ي ARABIC LETTER YEH */
+ { 0x05eb, 0x064b }, /* Arabic_fathatan ً ARABIC FATHATAN */
+ { 0x05ec, 0x064c }, /* Arabic_dammatan ٌ ARABIC DAMMATAN */
+ { 0x05ed, 0x064d }, /* Arabic_kasratan ٍ ARABIC KASRATAN */
+ { 0x05ee, 0x064e }, /* Arabic_fatha َ ARABIC FATHA */
+ { 0x05ef, 0x064f }, /* Arabic_damma ُ ARABIC DAMMA */
+ { 0x05f0, 0x0650 }, /* Arabic_kasra ِ ARABIC KASRA */
+ { 0x05f1, 0x0651 }, /* Arabic_shadda ّ ARABIC SHADDA */
+ { 0x05f2, 0x0652 }, /* Arabic_sukun ْ ARABIC SUKUN */
+ { 0x06a1, 0x0452 }, /* Serbian_dje ђ CYRILLIC SMALL LETTER DJE */
+ { 0x06a2, 0x0453 }, /* Macedonia_gje ѓ CYRILLIC SMALL LETTER GJE */
+ { 0x06a3, 0x0451 }, /* Cyrillic_io ё CYRILLIC SMALL LETTER IO */
+ { 0x06a4, 0x0454 }, /* Ukrainian_ie є CYRILLIC SMALL LETTER UKRAINIAN IE */
+ { 0x06a5, 0x0455 }, /* Macedonia_dse ѕ CYRILLIC SMALL LETTER DZE */
+ { 0x06a6, 0x0456 }, /* Ukrainian_i і CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */
+ { 0x06a7, 0x0457 }, /* Ukrainian_yi ї CYRILLIC SMALL LETTER YI */
+ { 0x06a8, 0x0458 }, /* Cyrillic_je ј CYRILLIC SMALL LETTER JE */
+ { 0x06a9, 0x0459 }, /* Cyrillic_lje љ CYRILLIC SMALL LETTER LJE */
+ { 0x06aa, 0x045a }, /* Cyrillic_nje њ CYRILLIC SMALL LETTER NJE */
+ { 0x06ab, 0x045b }, /* Serbian_tshe ћ CYRILLIC SMALL LETTER TSHE */
+ { 0x06ac, 0x045c }, /* Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */
+ { 0x06ad, 0x0491 }, /* Ukrainian_ghe_with_upturn ґ CYRILLIC SMALL LETTER GHE WITH UPTURN */
+ { 0x06ae, 0x045e }, /* Byelorussian_shortu ў CYRILLIC SMALL LETTER SHORT U */
+ { 0x06af, 0x045f }, /* Cyrillic_dzhe џ CYRILLIC SMALL LETTER DZHE */
+ { 0x06b0, 0x2116 }, /* numerosign № NUMERO SIGN */
+ { 0x06b1, 0x0402 }, /* Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */
+ { 0x06b2, 0x0403 }, /* Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */
+ { 0x06b3, 0x0401 }, /* Cyrillic_IO Ё CYRILLIC CAPITAL LETTER IO */
+ { 0x06b4, 0x0404 }, /* Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */
+ { 0x06b5, 0x0405 }, /* Macedonia_DSE Ѕ CYRILLIC CAPITAL LETTER DZE */
+ { 0x06b6, 0x0406 }, /* Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */
+ { 0x06b7, 0x0407 }, /* Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */
+ { 0x06b8, 0x0408 }, /* Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */
+ { 0x06b9, 0x0409 }, /* Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */
+ { 0x06ba, 0x040a }, /* Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */
+ { 0x06bb, 0x040b }, /* Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */
+ { 0x06bc, 0x040c }, /* Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */
+ { 0x06bd, 0x0490 }, /* Ukrainian_GHE_WITH_UPTURN Ґ CYRILLIC CAPITAL LETTER GHE WITH UPTURN */
+ { 0x06be, 0x040e }, /* Byelorussian_SHORTU Ў CYRILLIC CAPITAL LETTER SHORT U */
+ { 0x06bf, 0x040f }, /* Cyrillic_DZHE Џ CYRILLIC CAPITAL LETTER DZHE */
+ { 0x06c0, 0x044e }, /* Cyrillic_yu ю CYRILLIC SMALL LETTER YU */
+ { 0x06c1, 0x0430 }, /* Cyrillic_a а CYRILLIC SMALL LETTER A */
+ { 0x06c2, 0x0431 }, /* Cyrillic_be б CYRILLIC SMALL LETTER BE */
+ { 0x06c3, 0x0446 }, /* Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */
+ { 0x06c4, 0x0434 }, /* Cyrillic_de д CYRILLIC SMALL LETTER DE */
+ { 0x06c5, 0x0435 }, /* Cyrillic_ie е CYRILLIC SMALL LETTER IE */
+ { 0x06c6, 0x0444 }, /* Cyrillic_ef ф CYRILLIC SMALL LETTER EF */
+ { 0x06c7, 0x0433 }, /* Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */
+ { 0x06c8, 0x0445 }, /* Cyrillic_ha х CYRILLIC SMALL LETTER HA */
+ { 0x06c9, 0x0438 }, /* Cyrillic_i и CYRILLIC SMALL LETTER I */
+ { 0x06ca, 0x0439 }, /* Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */
+ { 0x06cb, 0x043a }, /* Cyrillic_ka к CYRILLIC SMALL LETTER KA */
+ { 0x06cc, 0x043b }, /* Cyrillic_el л CYRILLIC SMALL LETTER EL */
+ { 0x06cd, 0x043c }, /* Cyrillic_em м CYRILLIC SMALL LETTER EM */
+ { 0x06ce, 0x043d }, /* Cyrillic_en н CYRILLIC SMALL LETTER EN */
+ { 0x06cf, 0x043e }, /* Cyrillic_o о CYRILLIC SMALL LETTER O */
+ { 0x06d0, 0x043f }, /* Cyrillic_pe п CYRILLIC SMALL LETTER PE */
+ { 0x06d1, 0x044f }, /* Cyrillic_ya я CYRILLIC SMALL LETTER YA */
+ { 0x06d2, 0x0440 }, /* Cyrillic_er р CYRILLIC SMALL LETTER ER */
+ { 0x06d3, 0x0441 }, /* Cyrillic_es с CYRILLIC SMALL LETTER ES */
+ { 0x06d4, 0x0442 }, /* Cyrillic_te т CYRILLIC SMALL LETTER TE */
+ { 0x06d5, 0x0443 }, /* Cyrillic_u у CYRILLIC SMALL LETTER U */
+ { 0x06d6, 0x0436 }, /* Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */
+ { 0x06d7, 0x0432 }, /* Cyrillic_ve в CYRILLIC SMALL LETTER VE */
+ { 0x06d8, 0x044c }, /* Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */
+ { 0x06d9, 0x044b }, /* Cyrillic_yeru ы CYRILLIC SMALL LETTER YERU */
+ { 0x06da, 0x0437 }, /* Cyrillic_ze з CYRILLIC SMALL LETTER ZE */
+ { 0x06db, 0x0448 }, /* Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */
+ { 0x06dc, 0x044d }, /* Cyrillic_e э CYRILLIC SMALL LETTER E */
+ { 0x06dd, 0x0449 }, /* Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */
+ { 0x06de, 0x0447 }, /* Cyrillic_che ч CYRILLIC SMALL LETTER CHE */
+ { 0x06df, 0x044a }, /* Cyrillic_hardsign ъ CYRILLIC SMALL LETTER HARD SIGN */
+ { 0x06e0, 0x042e }, /* Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */
+ { 0x06e1, 0x0410 }, /* Cyrillic_A А CYRILLIC CAPITAL LETTER A */
+ { 0x06e2, 0x0411 }, /* Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */
+ { 0x06e3, 0x0426 }, /* Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */
+ { 0x06e4, 0x0414 }, /* Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */
+ { 0x06e5, 0x0415 }, /* Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */
+ { 0x06e6, 0x0424 }, /* Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */
+ { 0x06e7, 0x0413 }, /* Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */
+ { 0x06e8, 0x0425 }, /* Cyrillic_HA Х CYRILLIC CAPITAL LETTER HA */
+ { 0x06e9, 0x0418 }, /* Cyrillic_I И CYRILLIC CAPITAL LETTER I */
+ { 0x06ea, 0x0419 }, /* Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */
+ { 0x06eb, 0x041a }, /* Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */
+ { 0x06ec, 0x041b }, /* Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */
+ { 0x06ed, 0x041c }, /* Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */
+ { 0x06ee, 0x041d }, /* Cyrillic_EN Н CYRILLIC CAPITAL LETTER EN */
+ { 0x06ef, 0x041e }, /* Cyrillic_O О CYRILLIC CAPITAL LETTER O */
+ { 0x06f0, 0x041f }, /* Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */
+ { 0x06f1, 0x042f }, /* Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */
+ { 0x06f2, 0x0420 }, /* Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */
+ { 0x06f3, 0x0421 }, /* Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */
+ { 0x06f4, 0x0422 }, /* Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */
+ { 0x06f5, 0x0423 }, /* Cyrillic_U У CYRILLIC CAPITAL LETTER U */
+ { 0x06f6, 0x0416 }, /* Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */
+ { 0x06f7, 0x0412 }, /* Cyrillic_VE В CYRILLIC CAPITAL LETTER VE */
+ { 0x06f8, 0x042c }, /* Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */
+ { 0x06f9, 0x042b }, /* Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */
+ { 0x06fa, 0x0417 }, /* Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */
+ { 0x06fb, 0x0428 }, /* Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */
+ { 0x06fc, 0x042d }, /* Cyrillic_E Э CYRILLIC CAPITAL LETTER E */
+ { 0x06fd, 0x0429 }, /* Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */
+ { 0x06fe, 0x0427 }, /* Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */
+ { 0x06ff, 0x042a }, /* Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */
+ { 0x07a1, 0x0386 }, /* Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */
+ { 0x07a2, 0x0388 }, /* Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */
+ { 0x07a3, 0x0389 }, /* Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */
+ { 0x07a4, 0x038a }, /* Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */
+ { 0x07a5, 0x03aa }, /* Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */
+ { 0x07a7, 0x038c }, /* Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */
+ { 0x07a8, 0x038e }, /* Greek_UPSILONaccent Ύ GREEK CAPITAL LETTER UPSILON WITH TONOS */
+ { 0x07a9, 0x03ab }, /* Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */
+ { 0x07ab, 0x038f }, /* Greek_OMEGAaccent Ώ GREEK CAPITAL LETTER OMEGA WITH TONOS */
+ { 0x07ae, 0x0385 }, /* Greek_accentdieresis ΅ GREEK DIALYTIKA TONOS */
+ { 0x07af, 0x2015 }, /* Greek_horizbar ― HORIZONTAL BAR */
+ { 0x07b1, 0x03ac }, /* Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */
+ { 0x07b2, 0x03ad }, /* Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */
+ { 0x07b3, 0x03ae }, /* Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */
+ { 0x07b4, 0x03af }, /* Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */
+ { 0x07b5, 0x03ca }, /* Greek_iotadieresis ϊ GREEK SMALL LETTER IOTA WITH DIALYTIKA */
+ { 0x07b6, 0x0390 }, /* Greek_iotaaccentdieresis ΐ GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */
+ { 0x07b7, 0x03cc }, /* Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */
+ { 0x07b8, 0x03cd }, /* Greek_upsilonaccent ύ GREEK SMALL LETTER UPSILON WITH TONOS */
+ { 0x07b9, 0x03cb }, /* Greek_upsilondieresis ϋ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */
+ { 0x07ba, 0x03b0 }, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */
+ { 0x07bb, 0x03ce }, /* Greek_omegaaccent ώ GREEK SMALL LETTER OMEGA WITH TONOS */
+ { 0x07c1, 0x0391 }, /* Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */
+ { 0x07c2, 0x0392 }, /* Greek_BETA Β GREEK CAPITAL LETTER BETA */
+ { 0x07c3, 0x0393 }, /* Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */
+ { 0x07c4, 0x0394 }, /* Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */
+ { 0x07c5, 0x0395 }, /* Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */
+ { 0x07c6, 0x0396 }, /* Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */
+ { 0x07c7, 0x0397 }, /* Greek_ETA Η GREEK CAPITAL LETTER ETA */
+ { 0x07c8, 0x0398 }, /* Greek_THETA Θ GREEK CAPITAL LETTER THETA */
+ { 0x07c9, 0x0399 }, /* Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */
+ { 0x07ca, 0x039a }, /* Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */
+ { 0x07cb, 0x039b }, /* Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */
+ { 0x07cc, 0x039c }, /* Greek_MU Μ GREEK CAPITAL LETTER MU */
+ { 0x07cd, 0x039d }, /* Greek_NU Ν GREEK CAPITAL LETTER NU */
+ { 0x07ce, 0x039e }, /* Greek_XI Ξ GREEK CAPITAL LETTER XI */
+ { 0x07cf, 0x039f }, /* Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */
+ { 0x07d0, 0x03a0 }, /* Greek_PI Π GREEK CAPITAL LETTER PI */
+ { 0x07d1, 0x03a1 }, /* Greek_RHO Ρ GREEK CAPITAL LETTER RHO */
+ { 0x07d2, 0x03a3 }, /* Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */
+ { 0x07d4, 0x03a4 }, /* Greek_TAU Τ GREEK CAPITAL LETTER TAU */
+ { 0x07d5, 0x03a5 }, /* Greek_UPSILON Υ GREEK CAPITAL LETTER UPSILON */
+ { 0x07d6, 0x03a6 }, /* Greek_PHI Φ GREEK CAPITAL LETTER PHI */
+ { 0x07d7, 0x03a7 }, /* Greek_CHI Χ GREEK CAPITAL LETTER CHI */
+ { 0x07d8, 0x03a8 }, /* Greek_PSI Ψ GREEK CAPITAL LETTER PSI */
+ { 0x07d9, 0x03a9 }, /* Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */
+ { 0x07e1, 0x03b1 }, /* Greek_alpha α GREEK SMALL LETTER ALPHA */
+ { 0x07e2, 0x03b2 }, /* Greek_beta β GREEK SMALL LETTER BETA */
+ { 0x07e3, 0x03b3 }, /* Greek_gamma γ GREEK SMALL LETTER GAMMA */
+ { 0x07e4, 0x03b4 }, /* Greek_delta δ GREEK SMALL LETTER DELTA */
+ { 0x07e5, 0x03b5 }, /* Greek_epsilon ε GREEK SMALL LETTER EPSILON */
+ { 0x07e6, 0x03b6 }, /* Greek_zeta ζ GREEK SMALL LETTER ZETA */
+ { 0x07e7, 0x03b7 }, /* Greek_eta η GREEK SMALL LETTER ETA */
+ { 0x07e8, 0x03b8 }, /* Greek_theta θ GREEK SMALL LETTER THETA */
+ { 0x07e9, 0x03b9 }, /* Greek_iota ι GREEK SMALL LETTER IOTA */
+ { 0x07ea, 0x03ba }, /* Greek_kappa κ GREEK SMALL LETTER KAPPA */
+ { 0x07eb, 0x03bb }, /* Greek_lambda λ GREEK SMALL LETTER LAMDA */
+ { 0x07ec, 0x03bc }, /* Greek_mu μ GREEK SMALL LETTER MU */
+ { 0x07ed, 0x03bd }, /* Greek_nu ν GREEK SMALL LETTER NU */
+ { 0x07ee, 0x03be }, /* Greek_xi ξ GREEK SMALL LETTER XI */
+ { 0x07ef, 0x03bf }, /* Greek_omicron ο GREEK SMALL LETTER OMICRON */
+ { 0x07f0, 0x03c0 }, /* Greek_pi π GREEK SMALL LETTER PI */
+ { 0x07f1, 0x03c1 }, /* Greek_rho ρ GREEK SMALL LETTER RHO */
+ { 0x07f2, 0x03c3 }, /* Greek_sigma σ GREEK SMALL LETTER SIGMA */
+ { 0x07f3, 0x03c2 }, /* Greek_finalsmallsigma ς GREEK SMALL LETTER FINAL SIGMA */
+ { 0x07f4, 0x03c4 }, /* Greek_tau τ GREEK SMALL LETTER TAU */
+ { 0x07f5, 0x03c5 }, /* Greek_upsilon υ GREEK SMALL LETTER UPSILON */
+ { 0x07f6, 0x03c6 }, /* Greek_phi φ GREEK SMALL LETTER PHI */
+ { 0x07f7, 0x03c7 }, /* Greek_chi χ GREEK SMALL LETTER CHI */
+ { 0x07f8, 0x03c8 }, /* Greek_psi ψ GREEK SMALL LETTER PSI */
+ { 0x07f9, 0x03c9 }, /* Greek_omega ω GREEK SMALL LETTER OMEGA */
+/* 0x08a1 leftradical ? ??? */
+/* 0x08a2 topleftradical ? ??? */
+/* 0x08a3 horizconnector ? ??? */
+ { 0x08a4, 0x2320 }, /* topintegral ⌠ TOP HALF INTEGRAL */
+ { 0x08a5, 0x2321 }, /* botintegral ⌡ BOTTOM HALF INTEGRAL */
+ { 0x08a6, 0x2502 }, /* vertconnector │ BOX DRAWINGS LIGHT VERTICAL */
+/* 0x08a7 topleftsqbracket ? ??? */
+/* 0x08a8 botleftsqbracket ? ??? */
+/* 0x08a9 toprightsqbracket ? ??? */
+/* 0x08aa botrightsqbracket ? ??? */
+/* 0x08ab topleftparens ? ??? */
+/* 0x08ac botleftparens ? ??? */
+/* 0x08ad toprightparens ? ??? */
+/* 0x08ae botrightparens ? ??? */
+/* 0x08af leftmiddlecurlybrace ? ??? */
+/* 0x08b0 rightmiddlecurlybrace ? ??? */
+/* 0x08b1 topleftsummation ? ??? */
+/* 0x08b2 botleftsummation ? ??? */
+/* 0x08b3 topvertsummationconnector ? ??? */
+/* 0x08b4 botvertsummationconnector ? ??? */
+/* 0x08b5 toprightsummation ? ??? */
+/* 0x08b6 botrightsummation ? ??? */
+/* 0x08b7 rightmiddlesummation ? ??? */
+ { 0x08bc, 0x2264 }, /* lessthanequal ≤ LESS-THAN OR EQUAL TO */
+ { 0x08bd, 0x2260 }, /* notequal ≠ NOT EQUAL TO */
+ { 0x08be, 0x2265 }, /* greaterthanequal ≥ GREATER-THAN OR EQUAL TO */
+ { 0x08bf, 0x222b }, /* integral ∫ INTEGRAL */
+ { 0x08c0, 0x2234 }, /* therefore ∴ THEREFORE */
+ { 0x08c1, 0x221d }, /* variation ∝ PROPORTIONAL TO */
+ { 0x08c2, 0x221e }, /* infinity ∞ INFINITY */
+ { 0x08c5, 0x2207 }, /* nabla ∇ NABLA */
+ { 0x08c8, 0x2245 }, /* approximate ≅ APPROXIMATELY EQUAL TO */
+/* 0x08c9 similarequal ? ??? */
+ { 0x08cd, 0x21d4 }, /* ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */
+ { 0x08ce, 0x21d2 }, /* implies ⇒ RIGHTWARDS DOUBLE ARROW */
+ { 0x08cf, 0x2261 }, /* identical ≡ IDENTICAL TO */
+ { 0x08d6, 0x221a }, /* radical √ SQUARE ROOT */
+ { 0x08da, 0x2282 }, /* includedin ⊂ SUBSET OF */
+ { 0x08db, 0x2283 }, /* includes ⊃ SUPERSET OF */
+ { 0x08dc, 0x2229 }, /* intersection ∩ INTERSECTION */
+ { 0x08dd, 0x222a }, /* union ∪ UNION */
+ { 0x08de, 0x2227 }, /* logicaland ∧ LOGICAL AND */
+ { 0x08df, 0x2228 }, /* logicalor ∨ LOGICAL OR */
+ { 0x08ef, 0x2202 }, /* partialderivative ∂ PARTIAL DIFFERENTIAL */
+ { 0x08f6, 0x0192 }, /* function ƒ LATIN SMALL LETTER F WITH HOOK */
+ { 0x08fb, 0x2190 }, /* leftarrow ← LEFTWARDS ARROW */
+ { 0x08fc, 0x2191 }, /* uparrow ↑ UPWARDS ARROW */
+ { 0x08fd, 0x2192 }, /* rightarrow → RIGHTWARDS ARROW */
+ { 0x08fe, 0x2193 }, /* downarrow ↓ DOWNWARDS ARROW */
+ { 0x09df, 0x2422 }, /* blank ␢ BLANK SYMBOL */
+ { 0x09e0, 0x25c6 }, /* soliddiamond ◆ BLACK DIAMOND */
+ { 0x09e1, 0x2592 }, /* checkerboard ▒ MEDIUM SHADE */
+ { 0x09e2, 0x2409 }, /* ht ␉ SYMBOL FOR HORIZONTAL TABULATION */
+ { 0x09e3, 0x240c }, /* ff ␌ SYMBOL FOR FORM FEED */
+ { 0x09e4, 0x240d }, /* cr ␍ SYMBOL FOR CARRIAGE RETURN */
+ { 0x09e5, 0x240a }, /* lf ␊ SYMBOL FOR LINE FEED */
+ { 0x09e8, 0x2424 }, /* nl ␤ SYMBOL FOR NEWLINE */
+ { 0x09e9, 0x240b }, /* vt ␋ SYMBOL FOR VERTICAL TABULATION */
+ { 0x09ea, 0x2518 }, /* lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */
+ { 0x09eb, 0x2510 }, /* uprightcorner ┐ BOX DRAWINGS LIGHT DOWN AND LEFT */
+ { 0x09ec, 0x250c }, /* upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */
+ { 0x09ed, 0x2514 }, /* lowleftcorner └ BOX DRAWINGS LIGHT UP AND RIGHT */
+ { 0x09ee, 0x253c }, /* crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
+/* 0x09ef horizlinescan1 ? ??? */
+/* 0x09f0 horizlinescan3 ? ??? */
+ { 0x09f1, 0x2500 }, /* horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */
+/* 0x09f2 horizlinescan7 ? ??? */
+/* 0x09f3 horizlinescan9 ? ??? */
+ { 0x09f4, 0x251c }, /* leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
+ { 0x09f5, 0x2524 }, /* rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */
+ { 0x09f6, 0x2534 }, /* bott ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL */
+ { 0x09f7, 0x252c }, /* topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
+ { 0x09f8, 0x2502 }, /* vertbar │ BOX DRAWINGS LIGHT VERTICAL */
+ { 0x0aa1, 0x2003 }, /* emspace   EM SPACE */
+ { 0x0aa2, 0x2002 }, /* enspace   EN SPACE */
+ { 0x0aa3, 0x2004 }, /* em3space   THREE-PER-EM SPACE */
+ { 0x0aa4, 0x2005 }, /* em4space   FOUR-PER-EM SPACE */
+ { 0x0aa5, 0x2007 }, /* digitspace   FIGURE SPACE */
+ { 0x0aa6, 0x2008 }, /* punctspace   PUNCTUATION SPACE */
+ { 0x0aa7, 0x2009 }, /* thinspace   THIN SPACE */
+ { 0x0aa8, 0x200a }, /* hairspace   HAIR SPACE */
+ { 0x0aa9, 0x2014 }, /* emdash — EM DASH */
+ { 0x0aaa, 0x2013 }, /* endash – EN DASH */
+/* 0x0aac signifblank ? ??? */
+ { 0x0aae, 0x2026 }, /* ellipsis … HORIZONTAL ELLIPSIS */
+/* 0x0aaf doubbaselinedot ? ??? */
+ { 0x0ab0, 0x2153 }, /* onethird ⅓ VULGAR FRACTION ONE THIRD */
+ { 0x0ab1, 0x2154 }, /* twothirds ⅔ VULGAR FRACTION TWO THIRDS */
+ { 0x0ab2, 0x2155 }, /* onefifth ⅕ VULGAR FRACTION ONE FIFTH */
+ { 0x0ab3, 0x2156 }, /* twofifths ⅖ VULGAR FRACTION TWO FIFTHS */
+ { 0x0ab4, 0x2157 }, /* threefifths ⅗ VULGAR FRACTION THREE FIFTHS */
+ { 0x0ab5, 0x2158 }, /* fourfifths ⅘ VULGAR FRACTION FOUR FIFTHS */
+ { 0x0ab6, 0x2159 }, /* onesixth ⅙ VULGAR FRACTION ONE SIXTH */
+ { 0x0ab7, 0x215a }, /* fivesixths ⅚ VULGAR FRACTION FIVE SIXTHS */
+ { 0x0ab8, 0x2105 }, /* careof ℅ CARE OF */
+ { 0x0abb, 0x2012 }, /* figdash ‒ FIGURE DASH */
+ { 0x0abc, 0x2329 }, /* leftanglebracket 〈 LEFT-POINTING ANGLE BRACKET */
+ { 0x0abd, 0x002e }, /* decimalpoint . FULL STOP */
+ { 0x0abe, 0x232a }, /* rightanglebracket 〉 RIGHT-POINTING ANGLE BRACKET */
+/* 0x0abf marker ? ??? */
+ { 0x0ac3, 0x215b }, /* oneeighth ⅛ VULGAR FRACTION ONE EIGHTH */
+ { 0x0ac4, 0x215c }, /* threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */
+ { 0x0ac5, 0x215d }, /* fiveeighths ⅝ VULGAR FRACTION FIVE EIGHTHS */
+ { 0x0ac6, 0x215e }, /* seveneighths ⅞ VULGAR FRACTION SEVEN EIGHTHS */
+ { 0x0ac9, 0x2122 }, /* trademark ™ TRADE MARK SIGN */
+ { 0x0aca, 0x2613 }, /* signaturemark ☓ SALTIRE */
+/* 0x0acb trademarkincircle ? ??? */
+ { 0x0acc, 0x25c1 }, /* leftopentriangle ◁ WHITE LEFT-POINTING TRIANGLE */
+ { 0x0acd, 0x25b7 }, /* rightopentriangle ▷ WHITE RIGHT-POINTING TRIANGLE */
+ { 0x0ace, 0x25cb }, /* emopencircle ○ WHITE CIRCLE */
+ { 0x0acf, 0x25a1 }, /* emopenrectangle □ WHITE SQUARE */
+ { 0x0ad0, 0x2018 }, /* leftsinglequotemark ‘ LEFT SINGLE QUOTATION MARK */
+ { 0x0ad1, 0x2019 }, /* rightsinglequotemark ’ RIGHT SINGLE QUOTATION MARK */
+ { 0x0ad2, 0x201c }, /* leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */
+ { 0x0ad3, 0x201d }, /* rightdoublequotemark ” RIGHT DOUBLE QUOTATION MARK */
+ { 0x0ad4, 0x211e }, /* prescription ℞ PRESCRIPTION TAKE */
+ { 0x0ad6, 0x2032 }, /* minutes ′ PRIME */
+ { 0x0ad7, 0x2033 }, /* seconds ″ DOUBLE PRIME */
+ { 0x0ad9, 0x271d }, /* latincross ✝ LATIN CROSS */
+/* 0x0ada hexagram ? ??? */
+ { 0x0adb, 0x25ac }, /* filledrectbullet ▬ BLACK RECTANGLE */
+ { 0x0adc, 0x25c0 }, /* filledlefttribullet ◀ BLACK LEFT-POINTING TRIANGLE */
+ { 0x0add, 0x25b6 }, /* filledrighttribullet ▶ BLACK RIGHT-POINTING TRIANGLE */
+ { 0x0ade, 0x25cf }, /* emfilledcircle ● BLACK CIRCLE */
+ { 0x0adf, 0x25a0 }, /* emfilledrect ■ BLACK SQUARE */
+ { 0x0ae0, 0x25e6 }, /* enopencircbullet ◦ WHITE BULLET */
+ { 0x0ae1, 0x25ab }, /* enopensquarebullet ▫ WHITE SMALL SQUARE */
+ { 0x0ae2, 0x25ad }, /* openrectbullet ▭ WHITE RECTANGLE */
+ { 0x0ae3, 0x25b3 }, /* opentribulletup △ WHITE UP-POINTING TRIANGLE */
+ { 0x0ae4, 0x25bd }, /* opentribulletdown ▽ WHITE DOWN-POINTING TRIANGLE */
+ { 0x0ae5, 0x2606 }, /* openstar ☆ WHITE STAR */
+ { 0x0ae6, 0x2022 }, /* enfilledcircbullet • BULLET */
+ { 0x0ae7, 0x25aa }, /* enfilledsqbullet ▪ BLACK SMALL SQUARE */
+ { 0x0ae8, 0x25b2 }, /* filledtribulletup ▲ BLACK UP-POINTING TRIANGLE */
+ { 0x0ae9, 0x25bc }, /* filledtribulletdown ▼ BLACK DOWN-POINTING TRIANGLE */
+ { 0x0aea, 0x261c }, /* leftpointer ☜ WHITE LEFT POINTING INDEX */
+ { 0x0aeb, 0x261e }, /* rightpointer ☞ WHITE RIGHT POINTING INDEX */
+ { 0x0aec, 0x2663 }, /* club ♣ BLACK CLUB SUIT */
+ { 0x0aed, 0x2666 }, /* diamond ♦ BLACK DIAMOND SUIT */
+ { 0x0aee, 0x2665 }, /* heart ♥ BLACK HEART SUIT */
+ { 0x0af0, 0x2720 }, /* maltesecross ✠ MALTESE CROSS */
+ { 0x0af1, 0x2020 }, /* dagger † DAGGER */
+ { 0x0af2, 0x2021 }, /* doubledagger ‡ DOUBLE DAGGER */
+ { 0x0af3, 0x2713 }, /* checkmark ✓ CHECK MARK */
+ { 0x0af4, 0x2717 }, /* ballotcross ✗ BALLOT X */
+ { 0x0af5, 0x266f }, /* musicalsharp ♯ MUSIC SHARP SIGN */
+ { 0x0af6, 0x266d }, /* musicalflat ♭ MUSIC FLAT SIGN */
+ { 0x0af7, 0x2642 }, /* malesymbol ♂ MALE SIGN */
+ { 0x0af8, 0x2640 }, /* femalesymbol ♀ FEMALE SIGN */
+ { 0x0af9, 0x260e }, /* telephone ☎ BLACK TELEPHONE */
+ { 0x0afa, 0x2315 }, /* telephonerecorder ⌕ TELEPHONE RECORDER */
+ { 0x0afb, 0x2117 }, /* phonographcopyright ℗ SOUND RECORDING COPYRIGHT */
+ { 0x0afc, 0x2038 }, /* caret ‸ CARET */
+ { 0x0afd, 0x201a }, /* singlelowquotemark ‚ SINGLE LOW-9 QUOTATION MARK */
+ { 0x0afe, 0x201e }, /* doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */
+/* 0x0aff cursor ? ??? */
+ { 0x0ba3, 0x003c }, /* leftcaret < LESS-THAN SIGN */
+ { 0x0ba6, 0x003e }, /* rightcaret > GREATER-THAN SIGN */
+ { 0x0ba8, 0x2228 }, /* downcaret ∨ LOGICAL OR */
+ { 0x0ba9, 0x2227 }, /* upcaret ∧ LOGICAL AND */
+ { 0x0bc0, 0x00af }, /* overbar ¯ MACRON */
+ { 0x0bc2, 0x22a4 }, /* downtack ⊤ DOWN TACK */
+ { 0x0bc3, 0x2229 }, /* upshoe ∩ INTERSECTION */
+ { 0x0bc4, 0x230a }, /* downstile ⌊ LEFT FLOOR */
+ { 0x0bc6, 0x005f }, /* underbar _ LOW LINE */
+ { 0x0bca, 0x2218 }, /* jot ∘ RING OPERATOR */
+ { 0x0bcc, 0x2395 }, /* quad ⎕ APL FUNCTIONAL SYMBOL QUAD (Unicode 3.0) */
+ { 0x0bce, 0x22a5 }, /* uptack ⊥ UP TACK */
+ { 0x0bcf, 0x25cb }, /* circle ○ WHITE CIRCLE */
+ { 0x0bd3, 0x2308 }, /* upstile ⌈ LEFT CEILING */
+ { 0x0bd6, 0x222a }, /* downshoe ∪ UNION */
+ { 0x0bd8, 0x2283 }, /* rightshoe ⊃ SUPERSET OF */
+ { 0x0bda, 0x2282 }, /* leftshoe ⊂ SUBSET OF */
+ { 0x0bdc, 0x22a3 }, /* lefttack ⊣ LEFT TACK */
+ { 0x0bfc, 0x22a2 }, /* righttack ⊢ RIGHT TACK */
+ { 0x0cdf, 0x2017 }, /* hebrew_doublelowline ‗ DOUBLE LOW LINE */
+ { 0x0ce0, 0x05d0 }, /* hebrew_aleph א HEBREW LETTER ALEF */
+ { 0x0ce1, 0x05d1 }, /* hebrew_bet ב HEBREW LETTER BET */
+ { 0x0ce2, 0x05d2 }, /* hebrew_gimel ג HEBREW LETTER GIMEL */
+ { 0x0ce3, 0x05d3 }, /* hebrew_dalet ד HEBREW LETTER DALET */
+ { 0x0ce4, 0x05d4 }, /* hebrew_he ה HEBREW LETTER HE */
+ { 0x0ce5, 0x05d5 }, /* hebrew_waw ו HEBREW LETTER VAV */
+ { 0x0ce6, 0x05d6 }, /* hebrew_zain ז HEBREW LETTER ZAYIN */
+ { 0x0ce7, 0x05d7 }, /* hebrew_chet ח HEBREW LETTER HET */
+ { 0x0ce8, 0x05d8 }, /* hebrew_tet ט HEBREW LETTER TET */
+ { 0x0ce9, 0x05d9 }, /* hebrew_yod י HEBREW LETTER YOD */
+ { 0x0cea, 0x05da }, /* hebrew_finalkaph ך HEBREW LETTER FINAL KAF */
+ { 0x0ceb, 0x05db }, /* hebrew_kaph כ HEBREW LETTER KAF */
+ { 0x0cec, 0x05dc }, /* hebrew_lamed ל HEBREW LETTER LAMED */
+ { 0x0ced, 0x05dd }, /* hebrew_finalmem ם HEBREW LETTER FINAL MEM */
+ { 0x0cee, 0x05de }, /* hebrew_mem מ HEBREW LETTER MEM */
+ { 0x0cef, 0x05df }, /* hebrew_finalnun ן HEBREW LETTER FINAL NUN */
+ { 0x0cf0, 0x05e0 }, /* hebrew_nun נ HEBREW LETTER NUN */
+ { 0x0cf1, 0x05e1 }, /* hebrew_samech ס HEBREW LETTER SAMEKH */
+ { 0x0cf2, 0x05e2 }, /* hebrew_ayin ע HEBREW LETTER AYIN */
+ { 0x0cf3, 0x05e3 }, /* hebrew_finalpe ף HEBREW LETTER FINAL PE */
+ { 0x0cf4, 0x05e4 }, /* hebrew_pe פ HEBREW LETTER PE */
+ { 0x0cf5, 0x05e5 }, /* hebrew_finalzade ץ HEBREW LETTER FINAL TSADI */
+ { 0x0cf6, 0x05e6 }, /* hebrew_zade צ HEBREW LETTER TSADI */
+ { 0x0cf7, 0x05e7 }, /* hebrew_qoph ק HEBREW LETTER QOF */
+ { 0x0cf8, 0x05e8 }, /* hebrew_resh ר HEBREW LETTER RESH */
+ { 0x0cf9, 0x05e9 }, /* hebrew_shin ש HEBREW LETTER SHIN */
+ { 0x0cfa, 0x05ea }, /* hebrew_taw ת HEBREW LETTER TAV */
+ { 0x0da1, 0x0e01 }, /* Thai_kokai ก THAI CHARACTER KO KAI */
+ { 0x0da2, 0x0e02 }, /* Thai_khokhai ข THAI CHARACTER KHO KHAI */
+ { 0x0da3, 0x0e03 }, /* Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */
+ { 0x0da4, 0x0e04 }, /* Thai_khokhwai ค THAI CHARACTER KHO KHWAI */
+ { 0x0da5, 0x0e05 }, /* Thai_khokhon ฅ THAI CHARACTER KHO KHON */
+ { 0x0da6, 0x0e06 }, /* Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */
+ { 0x0da7, 0x0e07 }, /* Thai_ngongu ง THAI CHARACTER NGO NGU */
+ { 0x0da8, 0x0e08 }, /* Thai_chochan จ THAI CHARACTER CHO CHAN */
+ { 0x0da9, 0x0e09 }, /* Thai_choching ฉ THAI CHARACTER CHO CHING */
+ { 0x0daa, 0x0e0a }, /* Thai_chochang ช THAI CHARACTER CHO CHANG */
+ { 0x0dab, 0x0e0b }, /* Thai_soso ซ THAI CHARACTER SO SO */
+ { 0x0dac, 0x0e0c }, /* Thai_chochoe ฌ THAI CHARACTER CHO CHOE */
+ { 0x0dad, 0x0e0d }, /* Thai_yoying ญ THAI CHARACTER YO YING */
+ { 0x0dae, 0x0e0e }, /* Thai_dochada ฎ THAI CHARACTER DO CHADA */
+ { 0x0daf, 0x0e0f }, /* Thai_topatak ฏ THAI CHARACTER TO PATAK */
+ { 0x0db0, 0x0e10 }, /* Thai_thothan ฐ THAI CHARACTER THO THAN */
+ { 0x0db1, 0x0e11 }, /* Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */
+ { 0x0db2, 0x0e12 }, /* Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */
+ { 0x0db3, 0x0e13 }, /* Thai_nonen ณ THAI CHARACTER NO NEN */
+ { 0x0db4, 0x0e14 }, /* Thai_dodek ด THAI CHARACTER DO DEK */
+ { 0x0db5, 0x0e15 }, /* Thai_totao ต THAI CHARACTER TO TAO */
+ { 0x0db6, 0x0e16 }, /* Thai_thothung ถ THAI CHARACTER THO THUNG */
+ { 0x0db7, 0x0e17 }, /* Thai_thothahan ท THAI CHARACTER THO THAHAN */
+ { 0x0db8, 0x0e18 }, /* Thai_thothong ธ THAI CHARACTER THO THONG */
+ { 0x0db9, 0x0e19 }, /* Thai_nonu น THAI CHARACTER NO NU */
+ { 0x0dba, 0x0e1a }, /* Thai_bobaimai บ THAI CHARACTER BO BAIMAI */
+ { 0x0dbb, 0x0e1b }, /* Thai_popla ป THAI CHARACTER PO PLA */
+ { 0x0dbc, 0x0e1c }, /* Thai_phophung ผ THAI CHARACTER PHO PHUNG */
+ { 0x0dbd, 0x0e1d }, /* Thai_fofa ฝ THAI CHARACTER FO FA */
+ { 0x0dbe, 0x0e1e }, /* Thai_phophan พ THAI CHARACTER PHO PHAN */
+ { 0x0dbf, 0x0e1f }, /* Thai_fofan ฟ THAI CHARACTER FO FAN */
+ { 0x0dc0, 0x0e20 }, /* Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */
+ { 0x0dc1, 0x0e21 }, /* Thai_moma ม THAI CHARACTER MO MA */
+ { 0x0dc2, 0x0e22 }, /* Thai_yoyak ย THAI CHARACTER YO YAK */
+ { 0x0dc3, 0x0e23 }, /* Thai_rorua ร THAI CHARACTER RO RUA */
+ { 0x0dc4, 0x0e24 }, /* Thai_ru ฤ THAI CHARACTER RU */
+ { 0x0dc5, 0x0e25 }, /* Thai_loling ล THAI CHARACTER LO LING */
+ { 0x0dc6, 0x0e26 }, /* Thai_lu ฦ THAI CHARACTER LU */
+ { 0x0dc7, 0x0e27 }, /* Thai_wowaen ว THAI CHARACTER WO WAEN */
+ { 0x0dc8, 0x0e28 }, /* Thai_sosala ศ THAI CHARACTER SO SALA */
+ { 0x0dc9, 0x0e29 }, /* Thai_sorusi ษ THAI CHARACTER SO RUSI */
+ { 0x0dca, 0x0e2a }, /* Thai_sosua ส THAI CHARACTER SO SUA */
+ { 0x0dcb, 0x0e2b }, /* Thai_hohip ห THAI CHARACTER HO HIP */
+ { 0x0dcc, 0x0e2c }, /* Thai_lochula ฬ THAI CHARACTER LO CHULA */
+ { 0x0dcd, 0x0e2d }, /* Thai_oang อ THAI CHARACTER O ANG */
+ { 0x0dce, 0x0e2e }, /* Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */
+ { 0x0dcf, 0x0e2f }, /* Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */
+ { 0x0dd0, 0x0e30 }, /* Thai_saraa ะ THAI CHARACTER SARA A */
+ { 0x0dd1, 0x0e31 }, /* Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */
+ { 0x0dd2, 0x0e32 }, /* Thai_saraaa า THAI CHARACTER SARA AA */
+ { 0x0dd3, 0x0e33 }, /* Thai_saraam ำ THAI CHARACTER SARA AM */
+ { 0x0dd4, 0x0e34 }, /* Thai_sarai ิ THAI CHARACTER SARA I */
+ { 0x0dd5, 0x0e35 }, /* Thai_saraii ี THAI CHARACTER SARA II */
+ { 0x0dd6, 0x0e36 }, /* Thai_saraue ึ THAI CHARACTER SARA UE */
+ { 0x0dd7, 0x0e37 }, /* Thai_sarauee ื THAI CHARACTER SARA UEE */
+ { 0x0dd8, 0x0e38 }, /* Thai_sarau ุ THAI CHARACTER SARA U */
+ { 0x0dd9, 0x0e39 }, /* Thai_sarauu ู THAI CHARACTER SARA UU */
+ { 0x0dda, 0x0e3a }, /* Thai_phinthu ฺ THAI CHARACTER PHINTHU */
+ { 0x0dde, 0x0e3e }, /* Thai_maihanakat_maitho ฾ ??? */
+ { 0x0ddf, 0x0e3f }, /* Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */
+ { 0x0de0, 0x0e40 }, /* Thai_sarae เ THAI CHARACTER SARA E */
+ { 0x0de1, 0x0e41 }, /* Thai_saraae แ THAI CHARACTER SARA AE */
+ { 0x0de2, 0x0e42 }, /* Thai_sarao โ THAI CHARACTER SARA O */
+ { 0x0de3, 0x0e43 }, /* Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */
+ { 0x0de4, 0x0e44 }, /* Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */
+ { 0x0de5, 0x0e45 }, /* Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */
+ { 0x0de6, 0x0e46 }, /* Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */
+ { 0x0de7, 0x0e47 }, /* Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */
+ { 0x0de8, 0x0e48 }, /* Thai_maiek ่ THAI CHARACTER MAI EK */
+ { 0x0de9, 0x0e49 }, /* Thai_maitho ้ THAI CHARACTER MAI THO */
+ { 0x0dea, 0x0e4a }, /* Thai_maitri ๊ THAI CHARACTER MAI TRI */
+ { 0x0deb, 0x0e4b }, /* Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */
+ { 0x0dec, 0x0e4c }, /* Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */
+ { 0x0ded, 0x0e4d }, /* Thai_nikhahit ํ THAI CHARACTER NIKHAHIT */
+ { 0x0df0, 0x0e50 }, /* Thai_leksun ๐ THAI DIGIT ZERO */
+ { 0x0df1, 0x0e51 }, /* Thai_leknung ๑ THAI DIGIT ONE */
+ { 0x0df2, 0x0e52 }, /* Thai_leksong ๒ THAI DIGIT TWO */
+ { 0x0df3, 0x0e53 }, /* Thai_leksam ๓ THAI DIGIT THREE */
+ { 0x0df4, 0x0e54 }, /* Thai_leksi ๔ THAI DIGIT FOUR */
+ { 0x0df5, 0x0e55 }, /* Thai_lekha ๕ THAI DIGIT FIVE */
+ { 0x0df6, 0x0e56 }, /* Thai_lekhok ๖ THAI DIGIT SIX */
+ { 0x0df7, 0x0e57 }, /* Thai_lekchet ๗ THAI DIGIT SEVEN */
+ { 0x0df8, 0x0e58 }, /* Thai_lekpaet ๘ THAI DIGIT EIGHT */
+ { 0x0df9, 0x0e59 }, /* Thai_lekkao ๙ THAI DIGIT NINE */
+ { 0x0ea1, 0x3131 }, /* Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */
+ { 0x0ea2, 0x3132 }, /* Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */
+ { 0x0ea3, 0x3133 }, /* Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */
+ { 0x0ea4, 0x3134 }, /* Hangul_Nieun ㄴ HANGUL LETTER NIEUN */
+ { 0x0ea5, 0x3135 }, /* Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */
+ { 0x0ea6, 0x3136 }, /* Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */
+ { 0x0ea7, 0x3137 }, /* Hangul_Dikeud ㄷ HANGUL LETTER TIKEUT */
+ { 0x0ea8, 0x3138 }, /* Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */
+ { 0x0ea9, 0x3139 }, /* Hangul_Rieul ㄹ HANGUL LETTER RIEUL */
+ { 0x0eaa, 0x313a }, /* Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */
+ { 0x0eab, 0x313b }, /* Hangul_RieulMieum ㄻ HANGUL LETTER RIEUL-MIEUM */
+ { 0x0eac, 0x313c }, /* Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */
+ { 0x0ead, 0x313d }, /* Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */
+ { 0x0eae, 0x313e }, /* Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */
+ { 0x0eaf, 0x313f }, /* Hangul_RieulPhieuf ㄿ HANGUL LETTER RIEUL-PHIEUPH */
+ { 0x0eb0, 0x3140 }, /* Hangul_RieulHieuh ㅀ HANGUL LETTER RIEUL-HIEUH */
+ { 0x0eb1, 0x3141 }, /* Hangul_Mieum ㅁ HANGUL LETTER MIEUM */
+ { 0x0eb2, 0x3142 }, /* Hangul_Pieub ㅂ HANGUL LETTER PIEUP */
+ { 0x0eb3, 0x3143 }, /* Hangul_SsangPieub ㅃ HANGUL LETTER SSANGPIEUP */
+ { 0x0eb4, 0x3144 }, /* Hangul_PieubSios ㅄ HANGUL LETTER PIEUP-SIOS */
+ { 0x0eb5, 0x3145 }, /* Hangul_Sios ㅅ HANGUL LETTER SIOS */
+ { 0x0eb6, 0x3146 }, /* Hangul_SsangSios ㅆ HANGUL LETTER SSANGSIOS */
+ { 0x0eb7, 0x3147 }, /* Hangul_Ieung ㅇ HANGUL LETTER IEUNG */
+ { 0x0eb8, 0x3148 }, /* Hangul_Jieuj ㅈ HANGUL LETTER CIEUC */
+ { 0x0eb9, 0x3149 }, /* Hangul_SsangJieuj ㅉ HANGUL LETTER SSANGCIEUC */
+ { 0x0eba, 0x314a }, /* Hangul_Cieuc ㅊ HANGUL LETTER CHIEUCH */
+ { 0x0ebb, 0x314b }, /* Hangul_Khieuq ㅋ HANGUL LETTER KHIEUKH */
+ { 0x0ebc, 0x314c }, /* Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */
+ { 0x0ebd, 0x314d }, /* Hangul_Phieuf ㅍ HANGUL LETTER PHIEUPH */
+ { 0x0ebe, 0x314e }, /* Hangul_Hieuh ㅎ HANGUL LETTER HIEUH */
+ { 0x0ebf, 0x314f }, /* Hangul_A ㅏ HANGUL LETTER A */
+ { 0x0ec0, 0x3150 }, /* Hangul_AE ㅐ HANGUL LETTER AE */
+ { 0x0ec1, 0x3151 }, /* Hangul_YA ㅑ HANGUL LETTER YA */
+ { 0x0ec2, 0x3152 }, /* Hangul_YAE ㅒ HANGUL LETTER YAE */
+ { 0x0ec3, 0x3153 }, /* Hangul_EO ㅓ HANGUL LETTER EO */
+ { 0x0ec4, 0x3154 }, /* Hangul_E ㅔ HANGUL LETTER E */
+ { 0x0ec5, 0x3155 }, /* Hangul_YEO ㅕ HANGUL LETTER YEO */
+ { 0x0ec6, 0x3156 }, /* Hangul_YE ㅖ HANGUL LETTER YE */
+ { 0x0ec7, 0x3157 }, /* Hangul_O ㅗ HANGUL LETTER O */
+ { 0x0ec8, 0x3158 }, /* Hangul_WA ㅘ HANGUL LETTER WA */
+ { 0x0ec9, 0x3159 }, /* Hangul_WAE ㅙ HANGUL LETTER WAE */
+ { 0x0eca, 0x315a }, /* Hangul_OE ㅚ HANGUL LETTER OE */
+ { 0x0ecb, 0x315b }, /* Hangul_YO ㅛ HANGUL LETTER YO */
+ { 0x0ecc, 0x315c }, /* Hangul_U ㅜ HANGUL LETTER U */
+ { 0x0ecd, 0x315d }, /* Hangul_WEO ㅝ HANGUL LETTER WEO */
+ { 0x0ece, 0x315e }, /* Hangul_WE ㅞ HANGUL LETTER WE */
+ { 0x0ecf, 0x315f }, /* Hangul_WI ㅟ HANGUL LETTER WI */
+ { 0x0ed0, 0x3160 }, /* Hangul_YU ㅠ HANGUL LETTER YU */
+ { 0x0ed1, 0x3161 }, /* Hangul_EU ㅡ HANGUL LETTER EU */
+ { 0x0ed2, 0x3162 }, /* Hangul_YI ㅢ HANGUL LETTER YI */
+ { 0x0ed3, 0x3163 }, /* Hangul_I ㅣ HANGUL LETTER I */
+ { 0x0ed4, 0x11a8 }, /* Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */
+ { 0x0ed5, 0x11a9 }, /* Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */
+ { 0x0ed6, 0x11aa }, /* Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */
+ { 0x0ed7, 0x11ab }, /* Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */
+ { 0x0ed8, 0x11ac }, /* Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */
+ { 0x0ed9, 0x11ad }, /* Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */
+ { 0x0eda, 0x11ae }, /* Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */
+ { 0x0edb, 0x11af }, /* Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */
+ { 0x0edc, 0x11b0 }, /* Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */
+ { 0x0edd, 0x11b1 }, /* Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */
+ { 0x0ede, 0x11b2 }, /* Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */
+ { 0x0edf, 0x11b3 }, /* Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */
+ { 0x0ee0, 0x11b4 }, /* Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */
+ { 0x0ee1, 0x11b5 }, /* Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */
+ { 0x0ee2, 0x11b6 }, /* Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */
+ { 0x0ee3, 0x11b7 }, /* Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */
+ { 0x0ee4, 0x11b8 }, /* Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */
+ { 0x0ee5, 0x11b9 }, /* Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */
+ { 0x0ee6, 0x11ba }, /* Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */
+ { 0x0ee7, 0x11bb }, /* Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */
+ { 0x0ee8, 0x11bc }, /* Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */
+ { 0x0ee9, 0x11bd }, /* Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */
+ { 0x0eea, 0x11be }, /* Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */
+ { 0x0eeb, 0x11bf }, /* Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */
+ { 0x0eec, 0x11c0 }, /* Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */
+ { 0x0eed, 0x11c1 }, /* Hangul_J_Phieuf ᇁ HANGUL JONGSEONG PHIEUPH */
+ { 0x0eee, 0x11c2 }, /* Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */
+ { 0x0eef, 0x316d }, /* Hangul_RieulYeorinHieuh ㅭ HANGUL LETTER RIEUL-YEORINHIEUH */
+ { 0x0ef0, 0x3171 }, /* Hangul_SunkyeongeumMieum ㅱ HANGUL LETTER KAPYEOUNMIEUM */
+ { 0x0ef1, 0x3178 }, /* Hangul_SunkyeongeumPieub ㅸ HANGUL LETTER KAPYEOUNPIEUP */
+ { 0x0ef2, 0x317f }, /* Hangul_PanSios ㅿ HANGUL LETTER PANSIOS */
+/* 0x0ef3 Hangul_KkogjiDalrinIeung ? ??? */
+ { 0x0ef4, 0x3184 }, /* Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */
+ { 0x0ef5, 0x3186 }, /* Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */
+ { 0x0ef6, 0x318d }, /* Hangul_AraeA ㆍ HANGUL LETTER ARAEA */
+ { 0x0ef7, 0x318e }, /* Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */
+ { 0x0ef8, 0x11eb }, /* Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */
+/* 0x0ef9 Hangul_J_KkogjiDalrinIeung ? ??? */
+ { 0x0efa, 0x11f9 }, /* Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */
+ { 0x0eff, 0x20a9 }, /* Korean_Won ₩ WON SIGN */
+ { 0x13bc, 0x0152 }, /* OE ΠLATIN CAPITAL LIGATURE OE */
+ { 0x13bd, 0x0153 }, /* oe œ LATIN SMALL LIGATURE OE */
+ { 0x13be, 0x0178 }, /* Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */
+ { 0x20a0, 0x20a0 }, /* EcuSign ₠ EURO-CURRENCY SIGN */
+ { 0x20a1, 0x20a1 }, /* ColonSign ₡ COLON SIGN */
+ { 0x20a2, 0x20a2 }, /* CruzeiroSign ₢ CRUZEIRO SIGN */
+ { 0x20a3, 0x20a3 }, /* FFrancSign ₣ FRENCH FRANC SIGN */
+ { 0x20a4, 0x20a4 }, /* LiraSign ₤ LIRA SIGN */
+ { 0x20a5, 0x20a5 }, /* MillSign ₥ MILL SIGN */
+ { 0x20a6, 0x20a6 }, /* NairaSign ₦ NAIRA SIGN */
+ { 0x20a7, 0x20a7 }, /* PesetaSign ₧ PESETA SIGN */
+ { 0x20a8, 0x20a8 }, /* RupeeSign ₨ RUPEE SIGN */
+ { 0x20a9, 0x20a9 }, /* WonSign ₩ WON SIGN */
+ { 0x20aa, 0x20aa }, /* NewSheqelSign ₪ NEW SHEQEL SIGN */
+ { 0x20ab, 0x20ab }, /* DongSign ₫ DONG SIGN */
+ { 0x20ac, 0x20ac }, /* EuroSign € EURO SIGN */
+ // clang-format on
+};
+
+long keysym2ucs(KeySym keysym) {
+ int min = 0;
+ int max = sizeof(keysymtab) / sizeof(struct codepair) - 1;
+ int mid;
+
+ /* first check for Latin-1 characters (1:1 mapping) */
+ if ((keysym >= 0x0020 && keysym <= 0x007e) ||
+ (keysym >= 0x00a0 && keysym <= 0x00ff))
+ return keysym;
+
+ /* also check for directly encoded 24-bit UCS characters */
+ if ((keysym & 0xff000000) == 0x01000000) return keysym & 0x00ffffff;
+
+ /* binary search in table */
+ while (max >= min) {
+ mid = (min + max) / 2;
+ if (keysymtab[mid].keysym < keysym)
+ min = mid + 1;
+ else if (keysymtab[mid].keysym > keysym)
+ max = mid - 1;
+ else {
+ /* found it */
+ return keysymtab[mid].ucs;
+ }
+ }
+
+ /* no matching Unicode value found */
+ return -1;
+}
diff --git a/widget/x11/keysym2ucs.h b/widget/x11/keysym2ucs.h
new file mode 100644
index 0000000000..b081e27605
--- /dev/null
+++ b/widget/x11/keysym2ucs.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+/*
+ * This module converts keysym values into the corresponding ISO 10646-1
+ * (UCS, Unicode) values.
+ */
+
+#ifdef MOZ_X11
+# include <X11/X.h>
+#else
+# define KeySym unsigned int
+#endif /* MOZ_X11 */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+long keysym2ucs(KeySym keysym);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
diff --git a/widget/x11/moz.build b/widget/x11/moz.build
new file mode 100644
index 0000000000..cef080906a
--- /dev/null
+++ b/widget/x11/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget: Gtk")
+
+SOURCES += [
+ "keysym2ucs.c",
+]
+
+FINAL_LIBRARY = "xul"
+
+CXXFLAGS += CONFIG["TK_CFLAGS"]